You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

144 lines
5.3 KiB

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<gaemstone.Client.Components.InputComponents>]
[DependsOn<gaemstone.Client.Systems.Windowing>]
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<SystemPhase.OnLoad>]
public static void Initialize(Universe universe,
[Game] GameWindow window, [Source<Input>, Not] InputContext _)
{
var input = universe.LookupByTypeOrThrow<Input>();
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<Gamepad>().Set(new GamepadImpl(gamepad)).Build();
// TODO: Should we even support joysticks?
}
[Observer<ObserverEvent.OnAdd>]
[Expression("CursorCapturedBy(Input, *)")]
public static void OnCursorCaptured(Universe universe)
=> universe.LookupByTypeOrThrow<Mouse>().GetOrThrow<MouseImpl>()
.Value.Cursor.CursorMode = CursorMode.Raw;
[Observer<ObserverEvent.OnRemove>]
[Expression("CursorCapturedBy(Input, *)")]
public static void OnCursorReleased(Universe universe)
=> universe.LookupByTypeOrThrow<Mouse>().GetOrThrow<MouseImpl>()
.Value.Cursor.CursorMode = CursorMode.Normal;
[System<SystemPhase.OnLoad>]
public static void ProcessMouse(TimeSpan delta, EntityRef entity, MouseImpl impl)
{
var mouse = impl.Value;
var isCaptured = entity.Parent!.Has<CursorCapturedBy, Core.Any>();
ref var position = ref entity.NewChild("Position").Build().GetMut<RawValue2D>();
ref var posDelta = ref entity.NewChild("Delta" ).Build().GetMut<RawValue2D>();
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<SystemPhase.OnLoad>]
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<SystemPhase.OnLoad>]
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<RawValue1D>() = current;
if (current >= ActivationThreshold) {
ref var active = ref entity.GetRefOrNull<Active>();
if (Unsafe.IsNullRef(ref active)) {
entity.Set(new Active());
entity.Add<Activated>();
} else active.Duration += delta;
} else if (current <= DeactivationThreshold)
entity.Remove<Active>();
}
private static void Update2D(TimeSpan delta, EntityRef entity, Vector2 current)
{
entity.GetMut<RawValue2D>() = current;
var magnitude = current.Length();
if (magnitude >= ActivationThreshold) {
ref var active = ref entity.GetRefOrNull<Active>();
if (Unsafe.IsNullRef(ref active)) {
entity.Set(new Active());
entity.Add<Activated>();
} else active.Duration += delta;
} else if (magnitude <= DeactivationThreshold)
entity.Remove<Active>();
}
// 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<ObserverEvent.OnAdd>]
// public static void OnActiveAdded(EntityRef entity, Active _)
// => entity.Add<Activated>();
[Observer<ObserverEvent.OnRemove>]
public static void OnActiveRemoved(EntityRef entity, Active _)
=> entity.Add<Deactivated>();
[System<SystemPhase.PostFrame>]
public static void ClearDeActivated(EntityRef entity, [Or] Activated _1, [Or] Deactivated _2)
=> entity.Remove<Activated>().Remove<Deactivated>();
}