diff --git a/Engine b/Engine index 7a3202a..8d49fb4 160000 --- a/Engine +++ b/Engine @@ -1 +1 @@ -Subproject commit 7a3202a053b6fd7c9adf9bc210bf8026f3412328 +Subproject commit 8d49fb467cda79187a1a5677f7d594af4dbaef6e diff --git a/Platforms/Desktop/KeyboardInputsBehaviour.cs b/Platforms/Desktop/KeyboardInputsBehaviour.cs deleted file mode 100644 index 7cd1c91..0000000 --- a/Platforms/Desktop/KeyboardInputsBehaviour.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; - -using Microsoft.Xna.Framework.Input; - -using Syntriax.Engine.Core; -using Syntriax.Engine.Systems.Input; - -namespace Pong.Platforms.Desktop; - -public class KeyboardInputsBehaviour : Behaviour, IButtonInputs -{ - public Event, IButtonInputs.ButtonCallbackArguments> OnAnyButtonPressed { get; } = new(); - public Event, IButtonInputs.ButtonCallbackArguments> OnAnyButtonReleased { get; } = new(); - - private readonly Dictionary, IButtonInputs.ButtonCallbackArguments>> OnPressed = new(256); - private readonly Dictionary, IButtonInputs.ButtonCallbackArguments>> OnReleased = new(256); - - private int cachePressedCurrentlyCount = 0; - private readonly Keys[] cachePressedCurrently = new Keys[256]; - - private int cachePressedPreviouslyCount = 0; - private readonly Keys[] cachePressedPreviously = new Keys[256]; - - public void RegisterOnPress(Keys key, Event, IButtonInputs.ButtonCallbackArguments>.EventHandler callback) - { - if (!OnPressed.TryGetValue(key, out Event, IButtonInputs.ButtonCallbackArguments>? delegateCallback)) - { - delegateCallback = new(); - OnPressed.Add(key, delegateCallback); - } - - delegateCallback.AddListener(callback); - } - - public void UnregisterOnPress(Keys key, Event, IButtonInputs.ButtonCallbackArguments>.EventHandler callback) - { - if (OnPressed.TryGetValue(key, out Event, IButtonInputs.ButtonCallbackArguments>? delegateCallback)) - delegateCallback.RemoveListener(callback); - } - - public void RegisterOnRelease(Keys key, Event, IButtonInputs.ButtonCallbackArguments>.EventHandler callback) - { - if (!OnReleased.TryGetValue(key, out Event, IButtonInputs.ButtonCallbackArguments>? delegateCallback)) - { - delegateCallback = new(); - OnReleased.Add(key, delegateCallback); - } - - delegateCallback.AddListener(callback); - } - - public void UnregisterOnRelease(Keys key, Event, IButtonInputs.ButtonCallbackArguments>.EventHandler callback) - { - if (OnReleased.TryGetValue(key, out Event, IButtonInputs.ButtonCallbackArguments>? delegateCallback)) - delegateCallback.RemoveListener(callback); - } - - protected override void OnUpdate() - { - KeyboardState keyboardState = Keyboard.GetState(); - keyboardState.GetPressedKeys(cachePressedCurrently); - cachePressedCurrentlyCount = keyboardState.GetPressedKeyCount(); - - for (int i = 0; i < cachePressedCurrentlyCount; i++) - { - Keys currentlyPressedKey = cachePressedCurrently[i]; - - if (WasPressed(currentlyPressedKey)) - continue; - - if (OnPressed.TryGetValue(currentlyPressedKey, out Event, IButtonInputs.ButtonCallbackArguments>? callback)) - callback?.Invoke(this, new(currentlyPressedKey)); - - OnAnyButtonPressed?.Invoke(this, new(currentlyPressedKey)); - } - - for (int i = 0; i < cachePressedPreviouslyCount; i++) - { - Keys previouslyPressedKey = cachePressedPreviously[i]; - - if (IsPressed(previouslyPressedKey)) - continue; - - if (OnReleased.TryGetValue(previouslyPressedKey, out Event, IButtonInputs.ButtonCallbackArguments>? callback)) - callback?.Invoke(this, new(previouslyPressedKey)); - - OnAnyButtonReleased?.Invoke(this, new(previouslyPressedKey)); - } - - Array.Copy(cachePressedCurrently, cachePressedPreviously, cachePressedCurrentlyCount); - cachePressedPreviouslyCount = cachePressedCurrentlyCount; - } - - public bool IsPressed(Keys key) - { - for (int i = 0; i < cachePressedCurrentlyCount; i++) - if (cachePressedCurrently[i] == key) - return true; - return false; - } - - private bool WasPressed(Keys key) - { - for (int i = 0; i < cachePressedPreviouslyCount; i++) - if (cachePressedPreviously[i] == key) - return true; - return false; - } -} diff --git a/Platforms/Desktop/Program.cs b/Platforms/Desktop/Program.cs index 0afedc2..e5b5425 100644 --- a/Platforms/Desktop/Program.cs +++ b/Platforms/Desktop/Program.cs @@ -1,7 +1,7 @@ -using Pong.Platforms.Desktop; -using Syntriax.Engine.Core; +using Syntriax.Engine.Core; +using Syntriax.Engine.Integration.MonoGame; -Syntriax.Engine.Core.IUniverseObject universeObject = Syntriax.Engine.Core.Factory.UniverseObjectFactory.Instantiate().SetUniverseObject("Desktop HO"); +IUniverseObject universeObject = Syntriax.Engine.Core.Factory.UniverseObjectFactory.Instantiate().SetUniverseObject("Desktop HO"); universeObject.BehaviourController.AddBehaviour(); using var game = new Pong.GamePong(universeObject); game.Run(); diff --git a/Pong.sln b/Pong.sln index 156c86a..70c2a07 100644 --- a/Pong.sln +++ b/Pong.sln @@ -19,36 +19,104 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop", "Platforms\Deskto EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Systems", "Engine\Engine.Systems\Engine.Systems.csproj", "{8863A1BA-2E83-419F-BACB-D4A4156EC71C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine.Integration", "Engine.Integration", "{9059393F-4073-9273-0EEC-2B1BA61B620B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Integration.MonoGame", "Engine\Engine.Integration\Engine.Integration.MonoGame\Engine.Integration.MonoGame.csproj", "{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x64.ActiveCfg = Debug|Any CPU + {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x64.Build.0 = Debug|Any CPU + {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x86.Build.0 = Debug|Any CPU {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|Any CPU.ActiveCfg = Release|Any CPU {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|Any CPU.Build.0 = Release|Any CPU + {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x64.ActiveCfg = Release|Any CPU + {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x64.Build.0 = Release|Any CPU + {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x86.ActiveCfg = Release|Any CPU + {990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x86.Build.0 = Release|Any CPU {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x64.ActiveCfg = Debug|Any CPU + {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x64.Build.0 = Debug|Any CPU + {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x86.ActiveCfg = Debug|Any CPU + {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x86.Build.0 = Debug|Any CPU {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|Any CPU.ActiveCfg = Release|Any CPU {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|Any CPU.Build.0 = Release|Any CPU + {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x64.ActiveCfg = Release|Any CPU + {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x64.Build.0 = Release|Any CPU + {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x86.ActiveCfg = Release|Any CPU + {0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x86.Build.0 = Release|Any CPU {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x64.ActiveCfg = Debug|Any CPU + {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x64.Build.0 = Debug|Any CPU + {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x86.ActiveCfg = Debug|Any CPU + {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x86.Build.0 = Debug|Any CPU {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|Any CPU.ActiveCfg = Release|Any CPU {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|Any CPU.Build.0 = Release|Any CPU + {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x64.ActiveCfg = Release|Any CPU + {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x64.Build.0 = Release|Any CPU + {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x86.ActiveCfg = Release|Any CPU + {2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x86.Build.0 = Release|Any CPU {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x64.ActiveCfg = Debug|Any CPU + {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x64.Build.0 = Debug|Any CPU + {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x86.ActiveCfg = Debug|Any CPU + {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x86.Build.0 = Debug|Any CPU {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|Any CPU.ActiveCfg = Release|Any CPU {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|Any CPU.Build.0 = Release|Any CPU + {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x64.ActiveCfg = Release|Any CPU + {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x64.Build.0 = Release|Any CPU + {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x86.ActiveCfg = Release|Any CPU + {590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x86.Build.0 = Release|Any CPU {2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x64.ActiveCfg = Debug|Any CPU + {2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x64.Build.0 = Debug|Any CPU + {2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x86.ActiveCfg = Debug|Any CPU + {2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x86.Build.0 = Debug|Any CPU {2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|Any CPU.Build.0 = Release|Any CPU + {2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x64.ActiveCfg = Release|Any CPU + {2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x64.Build.0 = Release|Any CPU + {2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x86.ActiveCfg = Release|Any CPU + {2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x86.Build.0 = Release|Any CPU {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x64.ActiveCfg = Debug|Any CPU + {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x64.Build.0 = Debug|Any CPU + {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x86.ActiveCfg = Debug|Any CPU + {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x86.Build.0 = Debug|Any CPU {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|Any CPU.Build.0 = Release|Any CPU + {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x64.ActiveCfg = Release|Any CPU + {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x64.Build.0 = Release|Any CPU + {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x86.ActiveCfg = Release|Any CPU + {8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x86.Build.0 = Release|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x64.ActiveCfg = Debug|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x64.Build.0 = Debug|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x86.ActiveCfg = Debug|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x86.Build.0 = Debug|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|Any CPU.Build.0 = Release|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x64.ActiveCfg = Release|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x64.Build.0 = Release|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x86.ActiveCfg = Release|Any CPU + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -59,5 +127,7 @@ Global {2F6B1E26-1217-4EFD-874C-05ADEE4C7969} = {F7F62670-237A-4C93-A30E-CE661C6FC401} {2B627F66-5A61-4F69-B479-62EEAB603D01} = {FECFFD54-338F-4060-9161-1E5770D1DC33} {8863A1BA-2E83-419F-BACB-D4A4156EC71C} = {F7F62670-237A-4C93-A30E-CE661C6FC401} + {9059393F-4073-9273-0EEC-2B1BA61B620B} = {F7F62670-237A-4C93-A30E-CE661C6FC401} + {7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283} = {9059393F-4073-9273-0EEC-2B1BA61B620B} EndGlobalSection EndGlobal diff --git a/Shared/Abstract/IDisplayableSprite.cs b/Shared/Abstract/IDisplayableSprite.cs deleted file mode 100644 index 93ef048..0000000 --- a/Shared/Abstract/IDisplayableSprite.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.Xna.Framework.Graphics; - -namespace Syntriax.Engine.Core; - -public interface IDisplayableSprite -{ - public void Draw(SpriteBatch spriteBatch); -} diff --git a/Shared/Abstract/IDisplayableShape.cs b/Shared/Abstract/IDrawableShape.cs similarity index 67% rename from Shared/Abstract/IDisplayableShape.cs rename to Shared/Abstract/IDrawableShape.cs index 4a49ad4..2be25ff 100644 --- a/Shared/Abstract/IDisplayableShape.cs +++ b/Shared/Abstract/IDrawableShape.cs @@ -2,7 +2,7 @@ using Apos.Shapes; namespace Syntriax.Engine.Core; -public interface IDisplayableShape +public interface IDrawableShape : IBehaviour { void Draw(ShapeBatch shapeBatch); } diff --git a/Shared/Behaviours/BallBehaviour.cs b/Shared/Behaviours/BallBehaviour.cs index 46a77fa..7c28fb0 100644 --- a/Shared/Behaviours/BallBehaviour.cs +++ b/Shared/Behaviours/BallBehaviour.cs @@ -5,7 +5,7 @@ using Syntriax.Engine.Systems.Tween; namespace Pong.Behaviours; -public class BallBehaviour : Behaviour2D, IPhysicsUpdate, INetworkEntity, +public class BallBehaviour : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEntity, IPacketListenerClient { public float Speed { get; set; } = 500f; @@ -17,7 +17,7 @@ public class BallBehaviour : Behaviour2D, IPhysicsUpdate, INetworkEntity, private ITweenManager tweenManager = null!; private INetworkCommunicatorServer? networkServer = null; - protected override void OnFirstActiveFrame() + public void FirstActiveFrame() { BehaviourController.GetRequiredBehaviour().OnCollisionDetected.AddListener(OnCollisionDetected); physicsEngine2D = Universe.GetRequiredUniverseObject(); diff --git a/Shared/Behaviours/CameraController.cs b/Shared/Behaviours/CameraController.cs index 37fbd9b..f19e985 100644 --- a/Shared/Behaviours/CameraController.cs +++ b/Shared/Behaviours/CameraController.cs @@ -1,27 +1,28 @@ using Microsoft.Xna.Framework.Input; using Syntriax.Engine.Core; +using Syntriax.Engine.Integration.MonoGame; using Syntriax.Engine.Systems.Input; namespace Pong.Behaviours; -public class CameraController : Behaviour +public class CameraController : Behaviour, IFirstFrameUpdate, IUpdate { private MonoGameCamera2DBehaviour cameraBehaviour = null!; private IButtonInputs buttonInputs = null!; private float defaultZoomLevel = 1f; - protected override void OnFirstActiveFrame() + public void FirstActiveFrame() { - cameraBehaviour ??= BehaviourController.GetRequiredBehaviour(); + cameraBehaviour = BehaviourController.GetRequiredBehaviour(); buttonInputs = Universe.FindRequiredBehaviour>(); buttonInputs.RegisterOnPress(Keys.F, SwitchToFullScreen); buttonInputs.RegisterOnPress(Keys.R, ResetCamera); } - protected override void OnUpdate() + public void Update() { if (buttonInputs.IsPressed(Keys.U)) cameraBehaviour.Zoom += Universe.Time.DeltaTime * 5f; diff --git a/Shared/Behaviours/CircleBehaviour.cs b/Shared/Behaviours/CircleBehaviour.cs index 9c6d76e..c3d74db 100644 --- a/Shared/Behaviours/CircleBehaviour.cs +++ b/Shared/Behaviours/CircleBehaviour.cs @@ -6,7 +6,7 @@ using Syntriax.Engine.Core; namespace Pong.Behaviours; -public class CircleBehaviour : Syntriax.Engine.Physics2D.Collider2DCircleBehaviour, IDisplayableShape +public class CircleBehaviour : Syntriax.Engine.Physics2D.Collider2DCircleBehaviour, IDrawableShape { public Color Color { get; set; } = Color.White; public float Thickness { get; set; } = .5f; diff --git a/Shared/Behaviours/MonoGameCamera2DBehaviour.cs b/Shared/Behaviours/MonoGameCamera2DBehaviour.cs deleted file mode 100644 index a0dca2d..0000000 --- a/Shared/Behaviours/MonoGameCamera2DBehaviour.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; - -using Syntriax.Engine.Core; - -namespace Pong.Behaviours; - -public class MonoGameCamera2DBehaviour(GraphicsDeviceManager Graphics) : Behaviour2D, ICamera2D -{ - public event OnMatrixTransformChangedDelegate? OnMatrixTransformChanged = null; - public event OnViewportChangedDelegate? OnViewportChanged = null; - public event OnZoomChangedDelegate? OnZoomChanged = null; - - private Matrix _matrixTransform = Matrix.Identity; - - private Viewport _viewport = default; - private float _zoom = 1f; - - public GraphicsDeviceManager Graphics { get; } = Graphics; - - public Matrix MatrixTransform - { - get => _matrixTransform; - set - { - if (_matrixTransform == value) - return; - - _matrixTransform = value; - OnMatrixTransformChanged?.Invoke(this); - } - } - - public Vector2D Position - { - get => Transform.Position; - set => Transform.Position = value; - } - - public Viewport Viewport - { - get => _viewport; - set - { - if (_viewport.Equals(value)) - return; - - _viewport = value; - OnViewportChanged?.Invoke(this); - } - } - - public float Zoom - { - get => _zoom; - set - { - float newValue = Math.Max(0.1f, value); - - if (_zoom == newValue) - return; - - _zoom = newValue; - OnZoomChanged?.Invoke(this); - } - } - - public float Rotation - { - get => Transform.Rotation; - set => Transform.Rotation = value; - } - - // TODO This causes delay since OnPreDraw calls assuming this is called in in Update - public Vector2D ScreenToWorldPosition(Vector2D screenPosition) - { - Vector2D worldPosition = Vector2.Transform(screenPosition.ToVector2(), Matrix.Invert(MatrixTransform)).ToVector2D(); - return worldPosition.Scale(EngineConverter.screenScale); - } - public Vector2D WorldToScreenPosition(Vector2D worldPosition) - { - Vector2D screenPosition = Vector2.Transform(worldPosition.ToVector2(), MatrixTransform).ToVector2D(); - return screenPosition.Scale(EngineConverter.screenScale); - } - - protected override void OnFirstActiveFrame() - => Viewport = Graphics.GraphicsDevice.Viewport; - - protected override void OnPreDraw() - { - MatrixTransform = - Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) * - Matrix.CreateRotationZ(Rotation * Math.DegreeToRadian) * - Matrix.CreateScale(Zoom) * - Matrix.CreateTranslation(new Vector3(_viewport.Width * .5f, _viewport.Height * .5f, 0f)); - } - - public delegate void OnMatrixTransformChangedDelegate(MonoGameCamera2DBehaviour sender); - public delegate void OnViewportChangedDelegate(MonoGameCamera2DBehaviour sender); - public delegate void OnZoomChangedDelegate(MonoGameCamera2DBehaviour sender); -} diff --git a/Shared/Behaviours/MovementBallBehaviour.cs b/Shared/Behaviours/MovementBallBehaviour.cs index c986207..8c3daba 100644 --- a/Shared/Behaviours/MovementBallBehaviour.cs +++ b/Shared/Behaviours/MovementBallBehaviour.cs @@ -3,7 +3,7 @@ using Syntriax.Engine.Physics2D; namespace Pong.Behaviours; -public class MovementBallBehaviour : Behaviour2D +public class MovementBallBehaviour : Behaviour2D, IFirstFrameUpdate, IUpdate { public Vector2D StartDirection { get; private set; } = Vector2D.Zero; public float Speed { get; set; } = 500f; @@ -11,7 +11,7 @@ public class MovementBallBehaviour : Behaviour2D private IRigidBody2D rigidBody = null!; - protected override void OnFirstActiveFrame() + public void FirstActiveFrame() { rigidBody = BehaviourController.GetRequiredBehaviour(); @@ -19,7 +19,7 @@ public class MovementBallBehaviour : Behaviour2D BehaviourController.GetRequiredBehaviour().OnCollisionDetected.AddListener(OnCollisionDetected); } - protected override void OnUpdate() + public void Update() { if (rigidBody.Velocity.MagnitudeSquared <= 0.01f) return; diff --git a/Shared/Behaviours/PaddleBehaviour.cs b/Shared/Behaviours/PaddleBehaviour.cs index 0b78366..c705e72 100644 --- a/Shared/Behaviours/PaddleBehaviour.cs +++ b/Shared/Behaviours/PaddleBehaviour.cs @@ -9,7 +9,7 @@ using Syntriax.Engine.Systems.Input; namespace Pong.Behaviours; -public class PaddleBehaviour(Keys Up, Keys Down, float High, float Low, float Speed) : Behaviour2D, IPostPhysicsUpdate, +public class PaddleBehaviour(Keys Up, Keys Down, float High, float Low, float Speed) : Behaviour2D, IFirstFrameUpdate, IPostPhysicsUpdate, IPacketListenerServer, IPacketListenerClient { private Keys Up { get; } = Up; @@ -32,7 +32,7 @@ public class PaddleBehaviour(Keys Up, Keys Down, float High, float Low, float Sp Transform.Position = new Vector2D(Transform.Position.X, MathF.Max(MathF.Min(Transform.Position.Y, High), Low)); } - protected override void OnFirstActiveFrame() + public void FirstActiveFrame() { physicsEngine2D = Universe.GetRequiredUniverseObject(); inputs = Universe.FindRequired>(); @@ -99,14 +99,18 @@ public class PaddleBehaviour(Keys Up, Keys Down, float High, float Low, float Sp public bool IsDownPressed { get; set; } = default!; public long Timestamp { get; set; } = 0; - public PaddleKeyStatePacket() { } - public PaddleKeyStatePacket(PaddleBehaviour paddleBehaviour) + public PaddleKeyStatePacket Set(PaddleBehaviour paddleBehaviour) { EntityId = paddleBehaviour.Id; Position = paddleBehaviour.Transform.Position; IsUpPressed = paddleBehaviour.isUpPressed; IsDownPressed = paddleBehaviour.isDownPressed; Timestamp = DateTime.UtcNow.Ticks; + + return this; } + + public PaddleKeyStatePacket() { } + public PaddleKeyStatePacket(PaddleBehaviour paddleBehaviour) => Set(paddleBehaviour); } } diff --git a/Shared/Behaviours/PongManagerBehaviour.cs b/Shared/Behaviours/PongManagerBehaviour.cs index 7e4392f..45d5b94 100644 --- a/Shared/Behaviours/PongManagerBehaviour.cs +++ b/Shared/Behaviours/PongManagerBehaviour.cs @@ -8,7 +8,7 @@ using Syntriax.Engine.Systems.Input; namespace Pong.Behaviours; -public class PongManagerBehaviour : Behaviour, INetworkEntity, +public class PongManagerBehaviour : Behaviour, INetworkEntity, IFirstFrameUpdate, IPacketListenerServer { public Action? OnReset { get; set; } = null; @@ -30,7 +30,7 @@ public class PongManagerBehaviour : Behaviour, INetworkEntity, public PongManagerBehaviour() => WinScore = 5; public PongManagerBehaviour(int winScore) => WinScore = winScore; - protected override void OnFirstActiveFrame() + public void FirstActiveFrame() { IButtonInputs buttonInputs = Universe.FindRequired>(); buttonInputs.RegisterOnRelease(Keys.Space, (_, _1) => networkClient?.SendToServer(new RequestStartPacket())); diff --git a/Shared/Behaviours/ShapeAABBBehaviour.cs b/Shared/Behaviours/ShapeAABBBehaviour.cs index 47d196b..50fe7c4 100644 --- a/Shared/Behaviours/ShapeAABBBehaviour.cs +++ b/Shared/Behaviours/ShapeAABBBehaviour.cs @@ -8,7 +8,7 @@ using Syntriax.Engine.Systems.Input; namespace Pong.Behaviours; -public class ShapeAABBBehaviour : Behaviour2D, IDisplayableShape +public class ShapeAABBBehaviour : Behaviour2D, IDrawableShape, IFirstFrameUpdate { private IShapeCollider2D? shapeCollider = null; @@ -16,7 +16,7 @@ public class ShapeAABBBehaviour : Behaviour2D, IDisplayableShape public float Thickness { get; set; } = .5f; public bool display = true; - protected override void OnFirstActiveFrame() + public void FirstActiveFrame() { BehaviourController.TryGetBehaviour(out shapeCollider); diff --git a/Shared/Behaviours/ShapeBatcher.cs b/Shared/Behaviours/ShapeBatcher.cs new file mode 100644 index 0000000..983c3b3 --- /dev/null +++ b/Shared/Behaviours/ShapeBatcher.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +using Apos.Shapes; + +using Syntriax.Engine.Core; +using Syntriax.Engine.Integration.MonoGame; + +namespace Pong.Behaviours; + +public class ShapeBatcher : BehaviourBase, IFirstFrameUpdate, IDraw +{ + private static Comparer SortByPriority() => Comparer.Create((x, y) => y.Priority.CompareTo(x.Priority)); + private ShapeBatch shapeBatch = null!; + private MonoGameCamera2DBehaviour camera2D = null!; + private readonly ActiveBehaviourCollectorSorted drawableShapes = new() { SortBy = SortByPriority() }; + + public void FirstActiveFrame() + { + MonoGameWindowContainer windowContainer = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour(); + camera2D = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour(); + + shapeBatch = new(windowContainer.Window.GraphicsDevice, windowContainer.Window.Content); + + drawableShapes.Unassign(); + drawableShapes.Assign(Universe); + } + + public void Draw() + { + shapeBatch.Begin(camera2D.MatrixTransform); + for (int i = drawableShapes.Count - 1; i >= 0; i--) + drawableShapes[i].Draw(shapeBatch); + shapeBatch.End(); + } +} diff --git a/Shared/Behaviours/ShapeBehaviour.cs b/Shared/Behaviours/ShapeBehaviour.cs index 35bb2a4..7a6d986 100644 --- a/Shared/Behaviours/ShapeBehaviour.cs +++ b/Shared/Behaviours/ShapeBehaviour.cs @@ -6,7 +6,7 @@ using Syntriax.Engine.Core; namespace Pong.Behaviours; -public class ShapeBehaviour : Syntriax.Engine.Physics2D.Collider2DShapeBehaviour, IDisplayableShape +public class ShapeBehaviour : Syntriax.Engine.Physics2D.Collider2DShapeBehaviour, IDrawableShape { public Color Color { get; set; } = Color.White; public float Thickness { get; set; } = .5f; diff --git a/Shared/Behaviours/TextBehaviour.cs b/Shared/Behaviours/TextBehaviour.cs index d7d243f..d2cca62 100644 --- a/Shared/Behaviours/TextBehaviour.cs +++ b/Shared/Behaviours/TextBehaviour.cs @@ -2,10 +2,11 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Syntriax.Engine.Core; +using Syntriax.Engine.Integration.MonoGame; namespace Pong.Behaviours; -public class TextBehaviour : Behaviour2D, IDisplayableSprite +public class TextBehaviour : Behaviour2D, IDrawableSprite { public SpriteFont? Font { get; set; } = null; public int Size { get; set; } = 16; @@ -13,7 +14,7 @@ public class TextBehaviour : Behaviour2D, IDisplayableSprite public void Draw(SpriteBatch spriteBatch) { - if (!IsActive || Font is null) + if (Font is null) return; spriteBatch.DrawString(Font, Text, Transform.Position.ToDisplayVector2(), Color.White, Transform.Rotation, Vector2.One * .5f, Transform.Scale.Magnitude, SpriteEffects.None, 0f); diff --git a/Shared/Behaviours/TextScoreBehaviour.cs b/Shared/Behaviours/TextScoreBehaviour.cs index c986a0d..6693780 100644 --- a/Shared/Behaviours/TextScoreBehaviour.cs +++ b/Shared/Behaviours/TextScoreBehaviour.cs @@ -3,13 +3,13 @@ using Syntriax.Engine.Core; namespace Pong.Behaviours; -public class TextScoreBehaviour : TextBehaviour +public class TextScoreBehaviour : TextBehaviour, IFirstFrameUpdate { public bool IsLeft { get; } private PongManagerBehaviour? pongManager = null; - protected override void OnFirstActiveFrame() + public void FirstActiveFrame() { if (!UniverseObject.Universe.TryFindBehaviour(out pongManager)) return; diff --git a/Shared/Behaviours/WallScoreBehaviour.cs b/Shared/Behaviours/WallScoreBehaviour.cs index bb165b2..ed0bd94 100644 --- a/Shared/Behaviours/WallScoreBehaviour.cs +++ b/Shared/Behaviours/WallScoreBehaviour.cs @@ -5,11 +5,11 @@ using Syntriax.Engine.Physics2D; namespace Pong.Behaviours; -public class WallScoreBehaviour(Action OnCollision) : Behaviour2D +public class WallScoreBehaviour(Action OnCollision) : Behaviour2D, IFirstFrameUpdate { private Action OnCollision { get; } = OnCollision; - protected override void OnFirstActiveFrame() + public void FirstActiveFrame() { if (!BehaviourController.TryGetBehaviour(out ICollider2D? collider2D)) return; diff --git a/Shared/GamePong.cs b/Shared/GamePong.cs index f17d2bc..0c999dc 100644 --- a/Shared/GamePong.cs +++ b/Shared/GamePong.cs @@ -5,8 +5,6 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; -using Apos.Shapes; - using Pong.Behaviours; using Syntriax.Engine.Core; @@ -14,53 +12,24 @@ using Syntriax.Engine.Core.Factory; using Syntriax.Engine.Network; using Syntriax.Engine.Physics2D; using Syntriax.Engine.Systems.Tween; +using Syntriax.Engine.Integration.MonoGame; namespace Pong; -public class GamePong : Game +public class GamePong : MonoGameWindow { private readonly IUniverseObject platformSpecificUniverseObject = null!; - private readonly GraphicsDeviceManager graphics = null!; - private SpriteBatch spriteBatch = null!; - private ShapeBatch shapeBatch = null!; - - private Universe universe = null!; - private BehaviourCollector displayableCollector = null!; - private BehaviourCollector displayableShapeCollector = null!; - private MonoGameCamera2DBehaviour cameraBehaviour = null!; - private PongManagerBehaviour pongManager = null!; public GamePong(IUniverseObject platformSpecificUniverseObject) { this.platformSpecificUniverseObject = platformSpecificUniverseObject; - graphics = new GraphicsDeviceManager(this) - { - PreferredBackBufferWidth = 1024, - PreferredBackBufferHeight = 576, - GraphicsProfile = GraphicsProfile.HiDef - }; - - Content.RootDirectory = "Content"; - IsMouseVisible = true; + Graphics.PreferredBackBufferWidth = 1024; + Graphics.PreferredBackBufferHeight = 576; + Graphics.GraphicsProfile = GraphicsProfile.HiDef; } - protected override void Initialize() + protected override void PopulateUniverse(IUniverse universe) { - // TODO: Add your initialization logic here - universe = new(); - - universe.Initialize(); - - displayableCollector = new(universe); - displayableShapeCollector = new(universe); - - base.Initialize(); - } - - protected override void LoadContent() - { - spriteBatch = new SpriteBatch(GraphicsDevice); - shapeBatch = new ShapeBatch(GraphicsDevice, Content); SpriteFont spriteFont = Content.Load("UbuntuMono"); universe.Register(platformSpecificUniverseObject); @@ -79,7 +48,9 @@ public class GamePong : Game client.Connect("localhost", 8888); Window.Title = $"Client"; - universe.InstantiateUniverseObject().SetUniverseObject("Draw Manager"); + DrawManager drawManager = universe.InstantiateUniverseObject().SetUniverseObject("Draw Manager"); + universe.InstantiateUniverseObject().SetUniverseObject("Shape Batcher", drawManager).BehaviourController.AddBehaviour(); + universe.InstantiateUniverseObject().SetUniverseObject("Sprite Batcher", drawManager).BehaviourController.AddBehaviour(); } universe.InstantiateUniverseObject().SetUniverseObject("Update Manager"); @@ -89,14 +60,14 @@ public class GamePong : Game //////////////////////////////////////////////////////////////////////////////////// - cameraBehaviour = universe.InstantiateUniverseObject().SetUniverseObject("Camera") + universe.InstantiateUniverseObject().SetUniverseObject("Camera") .BehaviourController.AddBehaviour() .BehaviourController.AddBehaviour() - .BehaviourController.AddBehaviour(graphics); + .BehaviourController.AddBehaviour(); //////////////////////////////////////////////////////////////////////////////////// - pongManager = universe.InstantiateUniverseObject().SetUniverseObject("Pong Game Manager") + PongManagerBehaviour pongManager = universe.InstantiateUniverseObject().SetUniverseObject("Pong Game Manager") .BehaviourController.AddBehaviour(5); //////////////////////////////////////////////////////////////////////////////////// @@ -161,34 +132,4 @@ public class GamePong : Game .BehaviourController.AddBehaviour().SetTransform(position: new Vector2D(250f, 250f), scale: Vector2D.One * .25f) .BehaviourController.AddBehaviour(false, spriteFont); } - - protected override void Update(GameTime gameTime) - { - if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) - Exit(); - - universe.Update(gameTime.ToUniverseTime()); - - base.Update(gameTime); - } - - protected override void Draw(GameTime gameTime) - { - GraphicsDevice.Clear(new Color() { R = 32, G = 32, B = 32 }); - - // TODO: Add your drawing code here - universe.Draw(); - - spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cameraBehaviour.MatrixTransform); - for (int i = displayableCollector.Count - 1; i >= 0; i--) - displayableCollector[i].Draw(spriteBatch); - spriteBatch.End(); - - shapeBatch.Begin(cameraBehaviour.MatrixTransform); - for (int i = displayableShapeCollector.Count - 1; i >= 0; i--) - displayableShapeCollector[i].Draw(shapeBatch); - shapeBatch.End(); - - base.Draw(gameTime); - } } diff --git a/Shared/Network/NetworkManagerSlo.cs b/Shared/Network/NetworkManagerSlo.cs new file mode 100644 index 0000000..42bb26b --- /dev/null +++ b/Shared/Network/NetworkManagerSlo.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +using Syntriax.Engine.Core; + +namespace Syntriax.Engine.Network; + +public class NetworkManagerSlo : UniverseObject, INetworkManager +{ + private readonly List<(Type packetType, Delegate callback)> packetDelegates = []; + + private readonly Dictionary>> clientListenerDelegates = []; + private readonly Dictionary>> serverListenerDelegates = []; + + private readonly Dictionary>> clientPacketListeners = []; + private readonly Dictionary>> serverPacketListeners = []; + + private readonly Dictionary _networkEntities = []; + private readonly BehaviourCollector _networkEntityCollector = new(); + + public IReadOnlyDictionary NetworkEntities => _networkEntities; + public IBehaviourCollector NetworkEntityCollector => _networkEntityCollector; + + private INetworkCommunicator _networkCommunicator = null!; + public INetworkCommunicator NetworkCommunicator + { + get => _networkCommunicator; + set + { + if (_networkCommunicator == value) + return; + + INetworkCommunicator previousCommunicator = _networkCommunicator; + _networkCommunicator = value; + + if (previousCommunicator is not null) + UnsubscribeDelegates(previousCommunicator); + + if (_networkCommunicator is not null) + SubscribeDelegates(_networkCommunicator); + } + } + + #region Network Communicator Subscriptions + private static MethodInfo? GetCommunicatorMethod(string methodName) + => typeof(INetworkCommunicator).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + + private void SubscribeDelegates(INetworkCommunicator communicator) + { + MethodInfo subscribeMethod = GetCommunicatorMethod(nameof(INetworkCommunicator.SubscribeToPackets))!; + + foreach ((Type packetType, Delegate callback) in packetDelegates) + { + MethodInfo genericMethod = subscribeMethod.MakeGenericMethod(packetType); + genericMethod.Invoke(communicator, [callback]); + } + } + + private void UnsubscribeDelegates(INetworkCommunicator communicator) + { + MethodInfo unsubscribeMethod = GetCommunicatorMethod(nameof(INetworkCommunicator.UnsubscribeFromPackets))!; + + foreach ((Type packetType, Delegate callback) in packetDelegates) + { + MethodInfo genericMethod = unsubscribeMethod.MakeGenericMethod(packetType); + genericMethod.Invoke(communicator, [callback]); + } + } + #endregion + + #region Engine Callbacks + private void OnEntityCollected(IBehaviourCollector sender, IBehaviourCollector.BehaviourCollectedArguments args) + { + INetworkEntity entity = args.BehaviourCollected; + + if (!_networkEntities.TryAdd(entity.Id, entity)) + throw new Exception($"Unable to add {entity.Id} to {nameof(NetworkManager)}"); + + RegisterEntityListeners(entity); + } + + private void OnEntityRemoved(IBehaviourCollector sender, IBehaviourCollector.BehaviourRemovedArguments args) + { + INetworkEntity removedBehaviour = args.BehaviourRemoved; + if (!_networkEntities.Remove(args.BehaviourRemoved.Id)) + return; + + UnRegisterEntityListeners(removedBehaviour); + } + + protected override void OnExitingUniverse(IUniverse universe) + { + _networkEntityCollector.Unassign(); + } + + protected override void OnEnteringUniverse(IUniverse universe) + { + _networkEntityCollector.Assign(universe); + NetworkCommunicator = this.GetRequiredUniverseObjectInParent(); + } + #endregion + + #region Packet Retrieval + private void OnPacketReceived(string senderClientId, T packet) + { + if (packet is IEntityNetworkPacket entityPacket) + RouteEntityPacket(senderClientId, entityPacket.EntityId, packet); + else + RouteBroadcastPacket(senderClientId, packet!); + } + + private void RouteEntityPacket(string senderClientId, string entityId, T packet) + { + if (NetworkCommunicator is INetworkCommunicatorClient) + InvokeListenerIfExists(clientPacketListeners, entityId, senderClientId, packet!); + + if (NetworkCommunicator is INetworkCommunicatorServer) + InvokeListenerIfExists(serverPacketListeners, entityId, senderClientId, packet!); + } + + private void RouteBroadcastPacket(string senderClientId, T packet) + { + if (NetworkCommunicator is INetworkCommunicatorClient) + InvokeAllListeners(clientPacketListeners, senderClientId, packet!); + + if (NetworkCommunicator is INetworkCommunicatorServer) + InvokeAllListeners(serverPacketListeners, senderClientId, packet!); + } + + private static void InvokeListenerIfExists( + Dictionary>> listeners, + string entityId, + string senderClientId, + T packet) + { + if (!listeners.TryGetValue(packet!.GetType(), out Dictionary>? entityListeners)) + return; + + if (entityListeners.TryGetValue(entityId, out Event? listenerEvent)) + listenerEvent.Invoke(senderClientId, packet); + } + + private static void InvokeAllListeners( + Dictionary>> listeners, + string senderClientId, + T packet) + { + if (!listeners.TryGetValue(packet!.GetType(), out Dictionary>? entityListeners)) + return; + + foreach ((string _, Event listenerEvent) in entityListeners) + listenerEvent.Invoke(senderClientId, packet); + } + #endregion + + #region Listener Setups + private void RegisterEntityListeners(INetworkEntity entity) + { + RegisterEntityListenersForSide(entity, clientListenerDelegates, clientPacketListeners, isServer: false); + RegisterEntityListenersForSide(entity, serverListenerDelegates, serverPacketListeners, isServer: true); + } + + private static void RegisterEntityListenersForSide( + INetworkEntity entity, + Dictionary>> listenerDelegates, + Dictionary>> packetListeners, + bool isServer) + { + if (!listenerDelegates.TryGetValue(entity.GetType(), out Dictionary>? interfaceDelegates)) + return; + + foreach ((Type interfaceType, List delegateDataList) in interfaceDelegates) + foreach ((Type parameterType, MethodInfo receiveMethod) in delegateDataList) + { + Dictionary> listeners = GetOrCreateListenersForPacketType(packetListeners, parameterType); + Event listenerEvent = CreateListenerEvent(entity, receiveMethod, isServer); + listeners.Add(entity.Id, listenerEvent); + } + } + + private void UnRegisterEntityListeners(INetworkEntity entity) + { + UnregisterEntityListenersForSide(entity, clientListenerDelegates, clientPacketListeners); + UnregisterEntityListenersForSide(entity, serverListenerDelegates, serverPacketListeners); + } + + private static void UnregisterEntityListenersForSide( + INetworkEntity entity, + Dictionary>> listenerDelegates, + Dictionary>> packetListeners) + { + if (!listenerDelegates.TryGetValue(entity.GetType(), out Dictionary>? interfaceDelegates)) + return; + + foreach ((Type interfaceType, List delegateDataList) in interfaceDelegates) + foreach ((Type parameterType, MethodInfo receiveMethod) in delegateDataList) + { + Dictionary> listeners = GetOrCreateListenersForPacketType(packetListeners, parameterType); + + if (!listeners.TryGetValue(entity.Id, out Event? listenerEvent)) + continue; + + listeners.Remove(entity.Id); + listenerDelegates.Clear(); + } + } + + private static Dictionary> GetOrCreateListenersForPacketType( + Dictionary>> packetListeners, + Type packetType) + { + if (!packetListeners.TryGetValue(packetType, out Dictionary>? listeners)) + { + listeners = []; + packetListeners.Add(packetType, listeners); + } + return listeners; + } + + private static Event CreateListenerEvent(INetworkEntity entity, MethodInfo receiveMethod, bool isServer) + { + Event listenerEvent = new(); + + if (isServer) + listenerEvent.AddListener((sender, packet) => receiveMethod.Invoke(entity, [sender, packet])); + else + listenerEvent.AddListener((sender, packet) => receiveMethod.Invoke(entity, [packet])); + + return listenerEvent; + } + #endregion + + #region Initialization + private static IEnumerable DiscoverPacketTypes() + => AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetTypes()) + .Where( + t => typeof(INetworkPacket).IsAssignableFrom(t) + && !t.IsInterface + && !t.IsAbstract + && !t.IsGenericType + ); + + private static IEnumerable DiscoverClassesImplementing(Type interfaceType) + => AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetTypes()) + .Where( + t => t.GetInterfaces().Any( + i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType + ) + ); + + private static IEnumerable GetInterfacesOfType(Type type, Type interfaceType) + => type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType); + + private static void CacheListenerDelegatesForSide(Dictionary>> targetCache, Type listenerInterfaceType, string methodName) + { + IEnumerable listenerClasses = DiscoverClassesImplementing(listenerInterfaceType); + + foreach (Type listenerClass in listenerClasses) + { + Dictionary> interfaceListeners = []; + targetCache.Add(listenerClass, interfaceListeners); + + IEnumerable interfaces = GetInterfacesOfType(listenerClass, listenerInterfaceType); + + foreach (Type listenerInterface in interfaces) + { + Type packetType = listenerInterface.GetGenericArguments().First(); + + List methods = listenerInterface.GetMethods() + .Where(m => m.Name == methodName) + .Select(m => new DelegateData(packetType, m)) + .ToList(); + + interfaceListeners.Add(packetType, methods); + } + } + } + + private void CacheListenerDelegates() + { + CacheListenerDelegatesForSide(clientListenerDelegates, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient.OnClientPacketArrived)); + CacheListenerDelegatesForSide(serverListenerDelegates, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer.OnServerPacketArrived)); + } + + private void CachePacketDelegates() + { + IEnumerable packetTypes = DiscoverPacketTypes(); + MethodInfo onPacketReceivedMethod = GetType().GetMethod( + nameof(OnPacketReceived), + BindingFlags.NonPublic | BindingFlags.Instance + )!; + + foreach (Type packetType in packetTypes) + { + MethodInfo genericMethod = onPacketReceivedMethod.MakeGenericMethod(packetType); + Type delegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(string), packetType); + Delegate packetDelegate = Delegate.CreateDelegate(delegateType, this, genericMethod); + + packetDelegates.Add((packetType, packetDelegate)); + } + } + + public NetworkManagerSlo() + { + CachePacketDelegates(); + CacheListenerDelegates(); + + _networkEntityCollector.OnCollected.AddListener(OnEntityCollected); + _networkEntityCollector.OnRemoved.AddListener(OnEntityRemoved); + } + #endregion + + private readonly record struct DelegateData(Type ParameterType, MethodInfo ReceiveMethod) + { + public static implicit operator (Type ParameterType, MethodInfo ReceiveMethod)(DelegateData value) + => (value.ParameterType, value.ReceiveMethod); + + public static implicit operator DelegateData((Type ParameterType, MethodInfo ReceiveMethod) value) + => new(value.ParameterType, value.ReceiveMethod); + } +} diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj index b8bf6ce..2a19c0d 100644 --- a/Shared/Shared.csproj +++ b/Shared/Shared.csproj @@ -13,5 +13,6 @@ +