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] public record class InputContext(IInputContext Value) { } [Component] public record class MouseImpl(IMouse Value) { } [Component] public record class KeyboardImpl(IKeyboard Value) { } [Component] public record class GamepadImpl(IGamepad Value) { } [System] public static void Initialize(Universe universe, [Game] GameWindow window, [Source, Not] InputContext _) { var input = universe.LookupByTypeOrThrow(); var context = window.Handle.CreateInput(); input.Set(new InputContext(context)); // TODO: Add device names as documentation names to these entities. foreach (var mouse in context.Mice.Take(1)) input.LookupChildOrThrow("Mouse").Set(new MouseImpl(mouse)); foreach (var keyboard in context.Keyboards.Take(1)) input.LookupChildOrThrow("Keyboard").Set(new KeyboardImpl(keyboard)); foreach (var gamepad in context.Gamepads) input.NewChild("Gamepad" + gamepad.Index).Add().Set(new GamepadImpl(gamepad)).Build(); // TODO: Should we even support joysticks? } [Observer] [Expression("CursorCapturedBy(Input, *)")] public static void OnCursorCaptured(Universe universe) => universe.LookupByTypeOrThrow().GetOrThrow() .Value.Cursor.CursorMode = CursorMode.Raw; [Observer] [Expression("CursorCapturedBy(Input, *)")] public static void OnCursorReleased(Universe universe) => universe.LookupByTypeOrThrow().GetOrThrow() .Value.Cursor.CursorMode = CursorMode.Normal; [System] public static void ProcessMouse(TimeSpan delta, EntityRef entity, MouseImpl impl) { var mouse = impl.Value; var isCaptured = entity.Parent!.Has(); ref var position = ref entity.NewChild("Position").Build().GetMut(); ref var posDelta = ref entity.NewChild("Delta" ).Build().GetMut(); posDelta = mouse.Position - position; if (isCaptured) mouse.Position = position; else position = mouse.Position; Update1D(delta, entity.NewChild("Wheel").Build(), mouse.ScrollWheels[0].Y); var buttons = entity.NewChild("Buttons").Build(); foreach (var button in mouse.SupportedButtons) Update1D(delta, buttons.NewChild(button.ToString()).Build(), mouse.IsButtonPressed(button) ? 1 : 0); } [System] public static void ProcessKeyboard(TimeSpan delta, EntityRef entity, KeyboardImpl impl) { var keyboard = impl.Value; foreach (var key in keyboard.SupportedKeys) { var keyEntity = entity.NewChild(key.ToString()).Build(); Update1D(delta, keyEntity, keyboard.IsKeyPressed(key) ? 1 : 0); } } [System] public static void ProcessGamepad(TimeSpan delta, EntityRef entity, GamepadImpl impl) { var gamepad = impl.Value; var buttons = entity.NewChild("Buttons").Build(); foreach (var button in gamepad.Buttons) Update1D(delta, buttons.NewChild(button.Name.ToString()).Build(), button.Pressed ? 1 : 0); foreach (var trigger in gamepad.Triggers) Update1D(delta, entity.NewChild("Trigger" + trigger.Index).Build(), trigger.Position); foreach (var thumbstick in gamepad.Thumbsticks) Update2D(delta, entity.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(); }