added frontend event system

This commit is contained in:
MasterGordon 2022-11-16 01:19:20 +01:00
parent 95744e9e33
commit f2c1886cee
14 changed files with 425 additions and 132 deletions

View File

@ -9,6 +9,8 @@ public class Publisher
{
private readonly Dictionary<string, HashSet<Delegate>> subscribers =
new();
private readonly HashSet<string> clientOnlySubscriptions = new();
private readonly HashSet<string> serverSubscriptions = new();
private readonly InteractorKind kind;
public Publisher(InteractorKind kind)
@ -24,20 +26,28 @@ public class Publisher
.GetTypesSafe();
foreach (var type in types)
{
var attrs = type.GetCustomAttributes(typeof(InteractorAttribute), false);
if (attrs.Length == 0)
var classAttrs = type.GetCustomAttributes(typeof(InteractorAttribute), false);
if (classAttrs.Length == 0)
{
continue;
}
var methods = type.GetMethods();
foreach (var method in methods)
{
var attrs2 = method.GetCustomAttributes(typeof(InteractionAttribute), false);
if (attrs2.Length == 0)
var methodAttrs = method.GetCustomAttributes(typeof(InteractionAttribute), false);
if (methodAttrs.Length == 0)
{
continue;
}
var attr = (InteractionAttribute)attrs2[0];
var attr = (InteractionAttribute)methodAttrs[0];
if (attr.Kind is InteractorKind.Server or InteractorKind.Hybrid)
{
this.serverSubscriptions.Add(attr.Type);
}
if (attr.Kind is InteractorKind.Client)
{
this.clientOnlySubscriptions.Add(attr.Type);
}
if (attr.Kind != this.kind && this.kind != InteractorKind.Hybrid)
{
continue;
@ -51,6 +61,7 @@ public class Publisher
this.Subscribe(attr.Type, del);
}
}
this.clientOnlySubscriptions.ExceptWith(this.serverSubscriptions);
}
private void Subscribe(string type, Delegate callback)
@ -100,4 +111,14 @@ public class Publisher
}
}
}
public bool IsClientOnlyPacket(string type)
{
return this.clientOnlySubscriptions.Contains(type);
}
public bool IsServerPacket(string type)
{
return this.serverSubscriptions.Contains(type);
}
}

View File

@ -1,5 +1,6 @@
using System.Text;
using mine2d.backend.data;
using mine2d.engine;
using mine2d.engine.system.annotations;
using mine2d.state;
using Newtonsoft.Json;
@ -7,7 +8,7 @@ using WatsonTcp;
namespace mine2d.backend;
class RemoteBackend : IBackend
public class RemoteBackend : IBackend
{
private WatsonTcpClient client;
private Publisher publisher;
@ -27,6 +28,10 @@ class RemoteBackend : IBackend
public void ProcessPacket(ValueType packet)
{
this.publisher.Publish(packet);
if (this.publisher.IsClientOnlyPacket(PacketUtils.GetType(packet)))
{
return;
}
var json = JsonConvert.SerializeObject(packet);
var bytes = Encoding.UTF8.GetBytes(json);
this.client.Send(bytes);
@ -53,4 +58,4 @@ class RemoteBackend : IBackend
};
this.client.Connect();
}
}
}

View File

@ -16,8 +16,8 @@ public static class TypeExtensions
{
var missingTypes = e.Types
.Where(t => typeArguments.Contains(t) && t != null);
throw new Exception($"Failed to make generic type {type} with arguments {string.Join(", ", missingTypes)}", e);
throw new ArgumentException($"Failed to make generic type {type} with arguments {string.Join(", ", missingTypes)}", e);
}
}
}
}

View File

@ -2,10 +2,10 @@ namespace mine2d.engine.system;
public enum EventPriority
{
Lowest = 0,
Low = 1,
Normal = 2,
High = 3,
Highest = 4,
Important = 5,
Lowest = -2,
Low = -1,
Normal = 0,
High = 1,
Highest = 2,
Important = 3,
}

View File

@ -0,0 +1,12 @@
namespace Mine2d.engine.system;
public enum EventType
{
Quit,
MouseMotion,
MouseButtonDown,
MouseButtonUp,
KeyDown,
KeyUp,
WindowResize,
}

View File

@ -0,0 +1,15 @@
using mine2d.engine.system;
namespace Mine2d.engine.system.annotations;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class EventListenerAttribute : Attribute
{
public EventType Type { get; }
public EventPriority Priority { get; set; }
public EventListenerAttribute(EventType type)
{
this.Type = type;
}
}

View File

@ -0,0 +1,108 @@
using mine2d.core.extensions;
using mine2d.engine.system;
using Mine2d.engine.system;
using Mine2d.engine.system.annotations;
namespace Mine2d.frontend;
internal struct EventAction
{
public EventPriority priority;
public Delegate del;
}
public class EventPublisher
{
private readonly Dictionary<EventType, List<EventAction>> eventListeners = new();
public EventPublisher()
{
this.Scan();
}
public void Scan()
{
var types = this.GetType().Assembly
.GetTypesSafe()
.Where(t => t.Namespace != null && t.Namespace.StartsWith("Mine2d.frontend.events", StringComparison.Ordinal));
foreach (var type in types)
{
var methods = type.GetMethods()
.Where(m => m.GetCustomAttributes(typeof(EventListenerAttribute), false).Length > 0);
foreach (var method in methods)
{
var attributes = method.GetCustomAttributes(typeof(EventListenerAttribute), false);
foreach (var eventType in from EventListenerAttribute attribute in attributes
let eventType = attribute.Type
select eventType)
{
if (!this.eventListeners.ContainsKey(eventType))
{
this.eventListeners.Add(eventType, new List<EventAction>());
}
var del = method.GetParameters().Length == 0 ?
Delegate.CreateDelegate(typeof(Action), method) :
Delegate.CreateDelegate(
typeof(Action<>).MakeGenericTypeSafely(method.GetParameters()[0].ParameterType),
method
);
this.eventListeners[eventType].Add(new EventAction
{
priority = ((EventListenerAttribute)attributes[0]).Priority,
del = del
});
}
}
}
foreach (var (_, value) in this.eventListeners)
{
value.Sort((a, b) => a.priority.CompareTo(b.priority));
}
}
public void Dump()
{
foreach (var eventType in this.eventListeners.Keys)
{
Console.WriteLine(eventType);
foreach (var action in this.eventListeners[eventType])
{
Console.WriteLine(action.del.Method.Name);
}
}
}
public void Publish(EventType eventType, SDL_Event e)
{
if (this.eventListeners.ContainsKey(eventType))
{
try
{
foreach (var action in this.eventListeners[eventType])
{
if (action.del.Method.GetParameters().Length == 0)
{
action.del.DynamicInvoke();
}
else
{
action.del.DynamicInvoke(e);
}
}
}
catch (CancelEventException)
{
}
}
}
}
public class CancelEventException : Exception
{
public CancelEventException(string message) : base(message)
{
}
}

View File

@ -0,0 +1,45 @@
using mine2d;
using mine2d.backend.data;
using Mine2d.engine.system;
namespace Mine2d.frontend;
public class EventService
{
private static readonly EventPublisher eventPublisher = new();
public static void PollEvents()
{
while (SDL_PollEvent(out var e) != 0)
{
if (e.type == SDL_EventType.SDL_QUIT)
{
eventPublisher.Publish(EventType.Quit, e);
}
if (e.type == SDL_EventType.SDL_WINDOWEVENT && e.window.windowEvent == SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED)
{
eventPublisher.Publish(EventType.WindowResize, e);
}
if (e.type == SDL_EventType.SDL_MOUSEMOTION)
{
eventPublisher.Publish(EventType.MouseMotion, e);
}
if (e.type == SDL_EventType.SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT)
{
eventPublisher.Publish(EventType.MouseButtonDown, e);
}
if (e.type == SDL_EventType.SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_LEFT)
{
eventPublisher.Publish(EventType.MouseButtonUp, e);
}
if (e.type == SDL_EventType.SDL_KEYDOWN)
{
eventPublisher.Publish(EventType.KeyDown, e);
}
if (e.type == SDL_EventType.SDL_KEYUP)
{
eventPublisher.Publish(EventType.KeyUp, e);
}
}
}
}

View File

@ -1,6 +1,7 @@
using mine2d.backend.data;
using mine2d.core;
using mine2d.frontend.renderer;
using Mine2d.frontend;
namespace mine2d.frontend;
@ -23,122 +24,7 @@ public class Frontend : IFrontend
public void Process()
{
var ctx = Context.Get();
while (SDL_PollEvent(out var e) != 0)
{
if (e.type == SDL_EventType.SDL_QUIT)
{
Environment.Exit(0);
}
if (e.type == SDL_EventType.SDL_WINDOWEVENT)
{
if (e.window.windowEvent == SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED)
{
ctx.FrontendGameState.WindowWidth = e.window.data1;
ctx.FrontendGameState.WindowHeight = e.window.data2;
Console.WriteLine($"Window resized to {e.window.data1}x{e.window.data2}");
var player = ctx.GameState.Players.Find(
p => p.Id == ctx.FrontendGameState.PlayerGuid
);
ctx.FrontendGameState.Camera.CenterOn(player.Position);
}
}
if (e.type == SDL_EventType.SDL_MOUSEMOTION)
{
var mousePos = new Vector2(e.motion.x, e.motion.y);
ctx.FrontendGameState.MousePosition = mousePos;
if (ctx.GameState.Players.Find(player => player.Id == ctx.FrontendGameState.PlayerGuid)?.Mining != Vector2.Zero)
{
var amp = ctx.FrontendGameState.MousePosition / ctx.FrontendGameState.Settings.GameScale + ctx.FrontendGameState.Camera.Position;
ctx.Backend.ProcessPacket(new BreakPacket(ctx.FrontendGameState.PlayerGuid, amp));
}
}
if (e.type == SDL_EventType.SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT)
{
var amp = ctx.FrontendGameState.MousePosition / ctx.FrontendGameState.Settings.GameScale + ctx.FrontendGameState.Camera.Position;
ctx.Backend.ProcessPacket(new BreakPacket(ctx.FrontendGameState.PlayerGuid, amp));
}
if (e.type == SDL_EventType.SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_LEFT)
{
ctx.Backend.ProcessPacket(new BreakPacket(ctx.FrontendGameState.PlayerGuid, Vector2.Zero));
}
if (e.type == SDL_EventType.SDL_KEYDOWN && e.key.repeat == 0)
{
var movementInput = ctx.FrontendGameState.MovementInput;
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_F11)
{
_ = SDL_SetWindowFullscreen(
ctx.Window.GetRaw(),
ctx.FrontendGameState.Settings.Fullscreen ? 0 : (uint)SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP
);
ctx.FrontendGameState.Settings.Fullscreen = !ctx.FrontendGameState.Settings.Fullscreen;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_A)
{
movementInput.X -= 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_D)
{
movementInput.X += 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_W)
{
movementInput.Y -= 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_S)
{
movementInput.Y += 1;
}
ctx.FrontendGameState.MovementInput = movementInput;
}
if (e.type == SDL_EventType.SDL_KEYUP && e.key.repeat == 0)
{
var movementInput = ctx.FrontendGameState.MovementInput;
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_A)
{
movementInput.X += 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_D)
{
movementInput.X -= 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_W)
{
movementInput.Y += 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_S)
{
movementInput.Y -= 1;
}
ctx.FrontendGameState.MovementInput = movementInput;
}
if (
e.key.keysym.scancode
is SDL_Scancode.SDL_SCANCODE_A
or SDL_Scancode.SDL_SCANCODE_D
or SDL_Scancode.SDL_SCANCODE_W
or SDL_Scancode.SDL_SCANCODE_S
&& e.key.repeat == 0
&& e.type is SDL_EventType.SDL_KEYDOWN or SDL_EventType.SDL_KEYUP
)
{
if (e.key.repeat == 1)
{
continue;
}
var movement = ctx.FrontendGameState.MovementInput;
if (movement.Length() > 0)
{
movement = Vector2.Normalize(movement);
}
ctx.Backend.ProcessPacket(new MovePacket(ctx.FrontendGameState.PlayerName, movement));
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_ESCAPE)
{
Environment.Exit(0);
}
}
EventService.PollEvents();
ctx.Renderer.Clear();
var scale = ctx.FrontendGameState.Settings.GameScale;

View File

@ -0,0 +1,22 @@
using Mine2d.engine.system;
using Mine2d.engine.system.annotations;
namespace Mine2d.frontend.events;
public class Exit
{
[EventListener(EventType.Quit)]
public static void onExit()
{
Environment.Exit(0);
}
[EventListener(EventType.KeyDown)]
public static void onKeyDown(SDL_Event e)
{
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_ESCAPE)
{
Environment.Exit(0);
}
}
}

View File

@ -0,0 +1,23 @@
using mine2d;
using Mine2d.engine.system;
using Mine2d.engine.system.annotations;
namespace Mine2d.frontend.events;
public class Fullscreen
{
[EventListener(EventType.KeyDown)]
public static void onToggleFullscreen(SDL_Event e)
{
if (e.key.keysym.scancode != SDL_Scancode.SDL_SCANCODE_F11)
{
return;
}
var ctx = Context.Get();
_ = SDL_SetWindowFullscreen(
ctx.Window.GetRaw(),
ctx.FrontendGameState.Settings.Fullscreen ? 0 : (uint)SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP);
ctx.FrontendGameState.Settings.Fullscreen = !ctx.FrontendGameState.Settings.Fullscreen;
}
}

View File

@ -0,0 +1,50 @@
using mine2d;
using mine2d.backend.data;
using Mine2d.engine.system;
using Mine2d.engine.system.annotations;
namespace Mine2d.frontend.events;
public class PlayerBreakInput
{
[EventListener(EventType.MouseMotion)]
public static void onBreak(SDL_Event e)
{
var ctx = Context.Get();
var mousePos = new Vector2(e.motion.x, e.motion.y);
ctx.FrontendGameState.MousePosition = mousePos;
if (ctx.GameState.Players.Find(player => player.Id == ctx.FrontendGameState.PlayerGuid)?.Mining
!= Vector2.Zero)
{
var amp = ctx.FrontendGameState.MousePosition
/ ctx.FrontendGameState.Settings.GameScale
+ ctx.FrontendGameState.Camera.Position;
ctx.Backend.ProcessPacket(new BreakPacket(ctx.FrontendGameState.PlayerGuid, amp));
}
}
[EventListener(EventType.MouseButtonDown)]
public static void onMouseDown(SDL_Event e)
{
if (e.button.button != SDL_BUTTON_LEFT)
{
return;
}
var ctx = Context.Get();
var amp = ctx.FrontendGameState.MousePosition / ctx.FrontendGameState.Settings.GameScale + ctx.FrontendGameState.Camera.Position;
ctx.Backend.ProcessPacket(new BreakPacket(ctx.FrontendGameState.PlayerGuid, amp));
}
[EventListener(EventType.MouseButtonUp)]
public static void onMouseUp(SDL_Event e)
{
if (e.button.button != SDL_BUTTON_LEFT)
{
return;
}
var ctx = Context.Get();
ctx.Backend.ProcessPacket(new BreakPacket(ctx.FrontendGameState.PlayerGuid, Vector2.Zero));
}
}

View File

@ -0,0 +1,86 @@
using mine2d;
using mine2d.backend.data;
using Mine2d.engine.system;
using Mine2d.engine.system.annotations;
namespace Mine2d.frontend.events;
public class PlayerInput
{
[EventListener(EventType.KeyDown)]
public static void move(SDL_Event e)
{
var ctx = Context.Get();
if (!isMovementKey(e.key.keysym.scancode) || e.key.repeat == 1)
{
return;
}
var movementInput = ctx.FrontendGameState.MovementInput;
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_A)
{
movementInput.X -= 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_D)
{
movementInput.X += 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_W)
{
movementInput.Y -= 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_S)
{
movementInput.Y += 1;
}
ctx.FrontendGameState.MovementInput = movementInput;
sendMovement();
}
[EventListener(EventType.KeyUp)]
public static void stopMove(SDL_Event e)
{
var ctx = Context.Get();
if (!isMovementKey(e.key.keysym.scancode) || e.key.repeat == 1)
{
return;
}
var movementInput = ctx.FrontendGameState.MovementInput;
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_A)
{
movementInput.X += 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_D)
{
movementInput.X -= 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_W)
{
movementInput.Y += 1;
}
if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_S)
{
movementInput.Y -= 1;
}
ctx.FrontendGameState.MovementInput = movementInput;
sendMovement();
}
private static bool isMovementKey(SDL_Scancode scancode)
{
return scancode is SDL_Scancode.SDL_SCANCODE_A
or SDL_Scancode.SDL_SCANCODE_D
or SDL_Scancode.SDL_SCANCODE_W
or SDL_Scancode.SDL_SCANCODE_S;
}
private static void sendMovement()
{
var ctx = Context.Get();
var movement = ctx.FrontendGameState.MovementInput;
if (movement.Length() > 0)
{
movement = Vector2.Normalize(movement);
}
ctx.Backend.ProcessPacket(new MovePacket(ctx.FrontendGameState.PlayerName, movement));
}
}

View File

@ -0,0 +1,20 @@
using mine2d;
using Mine2d.engine.system;
using Mine2d.engine.system.annotations;
namespace Mine2d.frontend.events;
public class Resize
{
[EventListener(EventType.WindowResize)]
public static void onResize(SDL_Event e)
{
var ctx = Context.Get();
ctx.FrontendGameState.WindowWidth = e.window.data1;
ctx.FrontendGameState.WindowHeight = e.window.data2;
var player = ctx.GameState.Players.Find(
p => p.Id == ctx.FrontendGameState.PlayerGuid
);
ctx.FrontendGameState.Camera.CenterOn(player.Position);
}
}