using System; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using gaemstone.ECS; using gaemstone.Flecs; using Silk.NET.Input; using static gaemstone.Client.Components.InputComponents; using static gaemstone.Client.Systems.Windowing; namespace gaemstone.Client.Systems; [Module] [DependsOn] [DependsOn] public class InputManager { [Component, Path("InputContext"), Proxy] public struct ContextProxy { } [Component, Path("MouseImpl" ), Proxy] public struct MouseProxy { } [Component, Path("KeyboardImpl"), Proxy] public struct KeyboardProxy { } [Component, Path("GamepadImpl" ), Proxy] public struct GamepadProxy { } [System] public static void Initialize(Universe universe, [Game] GameWindow window, [Source, Not] IInputContext _) { var input = universe.LookupByTypeOrThrow(); var context = window.Handle.CreateInput(); input.Set(context); // TODO: Add device names as documentation names to these entities. foreach (var impl in context.Mice.Take(1)) input.LookupChildOrThrow("Mouse").Set(impl); foreach (var impl in context.Keyboards.Take(1)) input.LookupChildOrThrow("Keyboard").Set(impl); foreach (var impl in context.Gamepads) input.NewChild("Gamepad" + impl.Index).Add().Set(impl).Build(); // TODO: Should we even support joysticks? } [Observer] [Expression("CursorCapturedBy(Input, *)")] public static void OnCursorCaptured(Universe universe) => universe.LookupByTypeOrThrow().GetOrThrow() .Cursor.CursorMode = CursorMode.Raw; [Observer] [Expression("CursorCapturedBy(Input, *)")] public static void OnCursorReleased(Universe universe) => universe.LookupByTypeOrThrow().GetOrThrow() .Cursor.CursorMode = CursorMode.Normal; [System] public static void ProcessMouse(TimeSpan delta, EntityRef mouse, IMouse impl) { var isCaptured = mouse.Parent!.Has(); ref var position = ref mouse.NewChild("Position").Build().GetMut(); ref var posDelta = ref mouse.NewChild("Delta" ).Build().GetMut(); posDelta = impl.Position - position; if (isCaptured) impl.Position = position; else position = impl.Position; Update1D(delta, mouse.NewChild("Wheel").Build(), impl.ScrollWheels[0].Y); var buttons = mouse.NewChild("Buttons").Build(); foreach (var button in impl.SupportedButtons) Update1D(delta, buttons.NewChild(button.ToString()).Build(), impl.IsButtonPressed(button) ? 1 : 0); } [System] public static void ProcessKeyboard(TimeSpan delta, EntityRef keyboard, IKeyboard impl) { foreach (var key in impl.SupportedKeys) { var entity = keyboard.NewChild(key.ToString()).Build(); Update1D(delta, entity, impl.IsKeyPressed(key) ? 1 : 0); } } [System] public static void ProcessGamepad(TimeSpan delta, EntityRef gamepad, IGamepad impl) { var buttons = gamepad.NewChild("Buttons").Build(); foreach (var button in impl.Buttons) Update1D(delta, buttons.NewChild(button.Name.ToString()).Build(), button.Pressed ? 1 : 0); foreach (var trigger in impl.Triggers) Update1D(delta, gamepad.NewChild("Trigger" + trigger.Index).Build(), trigger.Position); foreach (var thumbstick in impl.Thumbsticks) Update2D(delta, gamepad.NewChild("Thumbstick" + thumbstick.Index).Build(), new(thumbstick.X, thumbstick.Y)); } private const float ActivationThreshold = 0.90f; private const float DeactivationThreshold = 0.75f; private static void Update1D(TimeSpan delta, EntityRef entity, float current) { entity.GetMut() = current; if (current >= ActivationThreshold) { ref var active = ref entity.GetRefOrNull(); if (Unsafe.IsNullRef(ref active)) { entity.Set(new Active()); entity.Add(); } else active.Duration += delta; } else if (current <= DeactivationThreshold) entity.Remove(); } private static void Update2D(TimeSpan delta, EntityRef entity, Vector2 current) { entity.GetMut() = current; var magnitude = current.Length(); if (magnitude >= ActivationThreshold) { ref var active = ref entity.GetRefOrNull(); if (Unsafe.IsNullRef(ref active)) { entity.Set(new Active()); entity.Add(); } else active.Duration += delta; } else if (magnitude <= DeactivationThreshold) entity.Remove(); } // TODO: flecs calls OnAdd observers repeatedly as the entity has other things added to it. // So at least for now we need to add Activated manually, until this changes. // [Observer] // public static void OnActiveAdded(EntityRef entity, Active _) // => entity.Add(); [Observer] public static void OnActiveRemoved(EntityRef entity, Active _) => entity.Add(); [System] public static void ClearDeActivated(EntityRef entity, [Or] Activated _1, [Or] Deactivated _2) => entity.Remove().Remove(); }