20 Commits

Author SHA1 Message Date
499f875903 chore: removed unused piece of code 2026-02-02 14:42:34 +03:00
b2cfb2a590 docs: added NetworkManager comments 2026-02-01 13:34:02 +03:00
1d6b9d2421 feat: added WaitForSeconds and WaitWhile yields 2026-01-31 13:08:59 +03:00
882f9e8b29 feat: added new yields 2026-01-31 13:08:13 +03:00
913af2a4a4 fix: added dynamic index updates during event invocation so there are no missing/duplicate invocations
This also makes the events very not ideal for multithreaded applications at the moment.
2026-01-31 00:49:00 +03:00
4e9fda3d7c refactor: auto property on FastListOrdered 2026-01-30 18:33:07 +03:00
7675f9acac chore: added missing Matrix4x4NetPacker 2026-01-30 18:02:34 +03:00
72f86478f2 feat!: added broadcast & routing support for NetworkManager
It used to only route or broadcast, now the same packet can be both routed to it's own behaviour AND at the same time the broadcast listeners can get alerted of the same package such as universe-wide managers
2026-01-30 13:43:48 +03:00
64e7321f0f fix: rotating file logger deleting from the wrong order 2026-01-30 10:54:56 +03:00
c355c666e0 refactor: fixed LiteNetLibServer using events to subscribe to PostUpdate instead of the interface 2026-01-29 22:29:19 +03:00
b9f3227f73 refactor: removed parameters on triangle batch calls on TriangleBatcher for multi window support 2026-01-28 22:19:04 +03:00
c68de39c83 fix: MonoGame view matrix calculation issues 2026-01-28 12:58:25 +03:00
ad444decbb perf: removed old position flipping on MonoGame 2026-01-28 11:16:47 +03:00
297e0eb790 fix: typos in Matrix4x4 methods 2026-01-28 11:14:31 +03:00
efa4da4398 fix: ScreenToWorldPosition & vice versa methods on MonoGameCamera2D fixed 2026-01-28 11:06:15 +03:00
e30280f1f8 fix: missing implicit conversion operators on Vectors 2026-01-28 11:02:07 +03:00
d4437edfbf feat: added Matrix4x4 x Vector4D multiplication 2026-01-28 10:22:36 +03:00
08f32f96e4 BREAKING CHANGE: 4x4 matrices are now column major 2026-01-27 23:45:50 +03:00
9294df8a19 feat: added support for multiple batches 2026-01-27 21:28:31 +03:00
50a0269798 feat: added matrix 4x4 transpose & orthographic view matrix methods 2026-01-27 20:48:39 +03:00
25 changed files with 706 additions and 253 deletions

View File

@@ -55,7 +55,7 @@ public class RotatingFileLogger : ILogger
private static void RotateLastLogs(string directory, string prefix, int rotateLength) private static void RotateLastLogs(string directory, string prefix, int rotateLength)
{ {
IOrderedEnumerable<string> logs = System.IO.Directory.GetFiles(directory, $"{prefix}*.log") IOrderedEnumerable<string> logs = System.IO.Directory.GetFiles(directory, $"{prefix}*.log")
.OrderBy(File.GetCreationTime); .OrderByDescending(File.GetCreationTime);
foreach (string file in logs.Skip(rotateLength)) foreach (string file in logs.Skip(rotateLength))
try try

View File

@@ -58,6 +58,9 @@ public class Event
public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared; public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared;
private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private readonly List<ListenerData> listeners = null!; private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!; private readonly List<ListenerData> onceListeners = null!;
@@ -74,6 +77,9 @@ public class Event
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentCallIndex)
currentCallIndex++;
listeners.Insert(insertIndex, listenerData); listeners.Insert(insertIndex, listenerData);
} }
@@ -90,6 +96,9 @@ public class Event
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentOnceCallIndex)
currentOnceCallIndex++;
onceListeners.Insert(insertIndex, listenerData); onceListeners.Insert(insertIndex, listenerData);
} }
@@ -103,6 +112,8 @@ public class Event
if (listeners[i].Callback == listener) if (listeners[i].Callback == listener)
{ {
listeners.RemoveAt(i); listeners.RemoveAt(i);
if (i < currentCallIndex)
currentCallIndex--;
return; return;
} }
} }
@@ -117,6 +128,8 @@ public class Event
if (onceListeners[i].Callback == listener) if (onceListeners[i].Callback == listener)
{ {
onceListeners.RemoveAt(i); onceListeners.RemoveAt(i);
if (i < currentOnceCallIndex)
currentOnceCallIndex--;
return; return;
} }
} }
@@ -131,23 +144,23 @@ public class Event
/// </summary> /// </summary>
public void Invoke() public void Invoke()
{ {
for (int i = listeners.Count - 1; i >= 0; i--) for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--)
try { listeners[i].Callback.Invoke(); } try { listeners[currentCallIndex].Callback.Invoke(); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}()"; string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}()";
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(listeners[currentCallIndex].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
} }
for (int i = onceListeners.Count - 1; i >= 0; i--) for (currentOnceCallIndex = onceListeners.Count - 1; currentOnceCallIndex >= 0; currentOnceCallIndex--)
{ {
try { onceListeners[i].Callback.Invoke(); } try { onceListeners[currentOnceCallIndex].Callback.Invoke(); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}()"; string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}()";
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
} }
onceListeners.RemoveAt(i); onceListeners.RemoveAt(currentOnceCallIndex);
} }
} }
@@ -216,6 +229,9 @@ public class Event<TSender> where TSender : class
public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared; public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared;
private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private readonly List<ListenerData> listeners = null!; private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!; private readonly List<ListenerData> onceListeners = null!;
@@ -232,6 +248,9 @@ public class Event<TSender> where TSender : class
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentCallIndex)
currentCallIndex++;
listeners.Insert(insertIndex, listenerData); listeners.Insert(insertIndex, listenerData);
} }
@@ -248,6 +267,9 @@ public class Event<TSender> where TSender : class
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentOnceCallIndex)
currentOnceCallIndex++;
onceListeners.Insert(insertIndex, listenerData); onceListeners.Insert(insertIndex, listenerData);
} }
@@ -261,6 +283,8 @@ public class Event<TSender> where TSender : class
if (listeners[i].Callback == listener) if (listeners[i].Callback == listener)
{ {
listeners.RemoveAt(i); listeners.RemoveAt(i);
if (i < currentCallIndex)
currentCallIndex--;
return; return;
} }
} }
@@ -275,6 +299,8 @@ public class Event<TSender> where TSender : class
if (onceListeners[i].Callback == listener) if (onceListeners[i].Callback == listener)
{ {
onceListeners.RemoveAt(i); onceListeners.RemoveAt(i);
if (i < currentOnceCallIndex)
currentOnceCallIndex--;
return; return;
} }
} }
@@ -290,23 +316,23 @@ public class Event<TSender> where TSender : class
/// <param name="sender">The caller that's triggering this event.</param> /// <param name="sender">The caller that's triggering this event.</param>
public void Invoke(TSender sender) public void Invoke(TSender sender)
{ {
for (int i = listeners.Count - 1; i >= 0; i--) for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--)
try { listeners[i].Callback.Invoke(sender); } try { listeners[currentCallIndex].Callback.Invoke(sender); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender})"; string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}({sender})";
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(listeners[currentCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
} }
for (int i = onceListeners.Count - 1; i >= 0; i--) for (currentOnceCallIndex = onceListeners.Count - 1; currentOnceCallIndex >= 0; currentOnceCallIndex--)
{ {
try { onceListeners[i].Callback.Invoke(sender); } try { onceListeners[currentOnceCallIndex].Callback.Invoke(sender); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender})"; string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}({sender})";
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
} }
onceListeners.RemoveAt(i); onceListeners.RemoveAt(currentOnceCallIndex);
} }
} }
@@ -382,6 +408,9 @@ public class Event<TSender, TArguments> where TSender : class
public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared; public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared;
private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private readonly List<ListenerData> listeners = null!; private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!; private readonly List<ListenerData> onceListeners = null!;
@@ -398,6 +427,9 @@ public class Event<TSender, TArguments> where TSender : class
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentCallIndex)
currentCallIndex++;
listeners.Insert(insertIndex, listenerData); listeners.Insert(insertIndex, listenerData);
} }
@@ -414,6 +446,9 @@ public class Event<TSender, TArguments> where TSender : class
if (insertIndex < 0) if (insertIndex < 0)
insertIndex = ~insertIndex; insertIndex = ~insertIndex;
if (insertIndex < currentOnceCallIndex)
currentOnceCallIndex++;
onceListeners.Insert(insertIndex, listenerData); onceListeners.Insert(insertIndex, listenerData);
} }
@@ -427,6 +462,8 @@ public class Event<TSender, TArguments> where TSender : class
if (listeners[i].Callback == listener) if (listeners[i].Callback == listener)
{ {
listeners.RemoveAt(i); listeners.RemoveAt(i);
if (i < currentCallIndex)
currentCallIndex--;
return; return;
} }
} }
@@ -441,6 +478,8 @@ public class Event<TSender, TArguments> where TSender : class
if (onceListeners[i].Callback == listener) if (onceListeners[i].Callback == listener)
{ {
onceListeners.RemoveAt(i); onceListeners.RemoveAt(i);
if (i < currentOnceCallIndex)
currentOnceCallIndex--;
return; return;
} }
} }
@@ -457,23 +496,23 @@ public class Event<TSender, TArguments> where TSender : class
/// <param name="args">The arguments provided for this event.</param> /// <param name="args">The arguments provided for this event.</param>
public void Invoke(TSender sender, TArguments args) public void Invoke(TSender sender, TArguments args)
{ {
for (int i = listeners.Count - 1; i >= 0; i--) for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--)
try { listeners[i].Callback.Invoke(sender, args); } try { listeners[currentCallIndex].Callback.Invoke(sender, args); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender}, {args})"; string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}({sender}, {args})";
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(listeners[currentCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
} }
for (int i = onceListeners.Count - 1; i >= 0; i--) for (currentOnceCallIndex = onceListeners.Count - 1; currentOnceCallIndex >= 0; currentOnceCallIndex--)
{ {
try { onceListeners[i].Callback.Invoke(sender, args); } try { onceListeners[currentOnceCallIndex].Callback.Invoke(sender, args); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender}, {args})"; string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}({sender}, {args})";
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
} }
onceListeners.RemoveAt(i); onceListeners.RemoveAt(currentOnceCallIndex);
} }
} }

View File

@@ -16,8 +16,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
private readonly Func<TItem, TIndex> getIndexFunc = null!; private readonly Func<TItem, TIndex> getIndexFunc = null!;
private readonly IComparer<TIndex> sortBy = null!; private readonly IComparer<TIndex> sortBy = null!;
private int count = 0; public int Count { get; private set; } = 0;
public int Count => count;
public bool IsReadOnly { get; set; } = false; public bool IsReadOnly { get; set; } = false;
@@ -35,10 +34,10 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
private (TIndex TIndex, int i) GetAt(Index index) private (TIndex TIndex, int i) GetAt(Index index)
{ {
int actualIndex = index.IsFromEnd int actualIndex = index.IsFromEnd
? count - index.Value ? Count - index.Value
: index.Value; : index.Value;
if (actualIndex < 0 || actualIndex >= count) if (actualIndex < 0 || actualIndex >= Count)
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
int leftIndex = actualIndex; int leftIndex = actualIndex;
@@ -75,7 +74,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
items[key] = list = []; items[key] = list = [];
list.Add(item); list.Add(item);
count++; Count++;
} }
public void Insert(int index, TItem item) public void Insert(int index, TItem item)
@@ -88,7 +87,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
items[tIndex] = list = []; items[tIndex] = list = [];
list.Insert(index, item); list.Insert(index, item);
count++; Count++;
} }
public bool Remove(TItem item) public bool Remove(TItem item)
@@ -103,7 +102,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
if (!list.Remove(item)) if (!list.Remove(item))
return false; return false;
count--; Count--;
return true; return true;
} }
@@ -114,7 +113,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
(TIndex tIndex, int i) = GetAt(index); (TIndex tIndex, int i) = GetAt(index);
items[tIndex].RemoveAt(i); items[tIndex].RemoveAt(i);
count--; Count--;
} }
public void Clear() public void Clear()
@@ -125,7 +124,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
foreach ((TIndex index, FastList<TItem> list) in items) foreach ((TIndex index, FastList<TItem> list) in items)
list.Clear(); list.Clear();
count = 0; Count = 0;
} }
public bool Contains(TItem item) public bool Contains(TItem item)

View File

@@ -1,12 +1,11 @@
using System; using System;
using System.Numerics;
namespace Engine.Core; namespace Engine.Core;
// TODO Comments // TODO Comments
/// <summary> /// <summary>
/// Represents a 4D left handed space matrix. /// Represents a 4D left handed space matrix in a Column Major convention.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Initializes a new instance of the <see cref="Matrix4x4"/> struct with the specified values. /// Initializes a new instance of the <see cref="Matrix4x4"/> struct with the specified values.
@@ -67,6 +66,11 @@ public readonly struct Matrix4x4(
/// </summary> /// </summary>
public Matrix4x4 Inverse => Invert(this); public Matrix4x4 Inverse => Invert(this);
/// <summary>
/// Represents the transposed version of this <see cref="Matrix4x4"/>.
/// </summary>
public Matrix4x4 Transposed => Transpose(this);
public static Matrix4x4 operator *(Matrix4x4 a, Matrix4x4 b) => new( public static Matrix4x4 operator *(Matrix4x4 a, Matrix4x4 b) => new(
a.M11 * b.M11 + a.M12 * b.M21 + a.M13 * b.M31 + a.M14 * b.M41, a.M11 * b.M11 + a.M12 * b.M21 + a.M13 * b.M31 + a.M14 * b.M41,
a.M11 * b.M12 + a.M12 * b.M22 + a.M13 * b.M32 + a.M14 * b.M42, a.M11 * b.M12 + a.M12 * b.M22 + a.M13 * b.M32 + a.M14 * b.M42,
@@ -89,6 +93,13 @@ public readonly struct Matrix4x4(
a.M41 * b.M14 + a.M42 * b.M24 + a.M43 * b.M34 + a.M44 * b.M44 a.M41 * b.M14 + a.M42 * b.M24 + a.M43 * b.M34 + a.M44 * b.M44
); );
public static Vector4D operator *(Matrix4x4 m, Vector4D v) => new(
m.M11 * v.X + m.M12 * v.Y + m.M13 * v.Z + m.M14 * v.W,
m.M21 * v.X + m.M22 * v.Y + m.M23 * v.Z + m.M24 * v.W,
m.M31 * v.X + m.M32 * v.Y + m.M33 * v.Z + m.M34 * v.W,
m.M41 * v.X + m.M42 * v.Y + m.M43 * v.Z + m.M44 * v.W
);
public static bool operator ==(Matrix4x4 left, Matrix4x4 right) => public static bool operator ==(Matrix4x4 left, Matrix4x4 right) =>
left.M11 == right.M11 && left.M12 == right.M12 && left.M13 == right.M13 && left.M14 == right.M14 && left.M11 == right.M11 && left.M12 == right.M12 && left.M13 == right.M13 && left.M14 == right.M14 &&
left.M21 == right.M21 && left.M22 == right.M22 && left.M23 == right.M23 && left.M24 == right.M24 && left.M21 == right.M21 && left.M22 == right.M22 && left.M23 == right.M23 && left.M24 == right.M24 &&
@@ -202,11 +213,23 @@ public readonly struct Matrix4x4(
); );
} }
/// <summary>
/// Transposes the given <see cref="Matrix4x4"/>.
/// </summary>
/// <param name="m">The <see cref="Matrix4x4"/>.</param>
/// <returns>The transposed <see cref="Matrix4x4"/> of the given <see cref="Matrix4x4"/>.</returns>
public static Matrix4x4 Transpose(Matrix4x4 m) => new(
m.M11, m.M21, m.M31, m.M41,
m.M12, m.M22, m.M32, m.M42,
m.M13, m.M23, m.M33, m.M43,
m.M14, m.M24, m.M34, m.M44
);
public static Matrix4x4 CreateTranslation(Vector3D position) => new( public static Matrix4x4 CreateTranslation(Vector3D position) => new(
1f, 0f, 0f, 0f, 1f, 0f, 0f, position.X,
0f, 1f, 0f, 0f, 0f, 1f, 0f, position.Y,
0f, 0f, 1f, 0f, 0f, 0f, 1f, position.Z,
position.X, position.Y, position.Z, 1 0f, 0f, 0f, 1f
); );
public static Matrix4x4 CreateScale(float scale) => new( public static Matrix4x4 CreateScale(float scale) => new(
@@ -228,11 +251,11 @@ public readonly struct Matrix4x4(
float c = Math.Cos(radians); float c = Math.Cos(radians);
float s = Math.Sin(radians); float s = Math.Sin(radians);
return new Matrix4x4( return new(
1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f,
0f, c, s, 0f, 0f, c, -s, 0f,
0f, -s, c, 0f, 0f, s, c, 0f,
0f, 0f, 0f, 1f 0f, 0f, 0f, 1f
); );
} }
@@ -241,10 +264,10 @@ public readonly struct Matrix4x4(
float c = Math.Cos(radians); float c = Math.Cos(radians);
float s = Math.Sin(radians); float s = Math.Sin(radians);
return new Matrix4x4( return new(
c, 0f, -s, 0f, c, 0f, s, 0f,
0f, 1f, 0f, 0f, 0f, 1f, 0f, 0f,
s, 0f, c, 0f, -s, 0f, c, 0f,
0f, 0f, 0f, 1f 0f, 0f, 0f, 1f
); );
} }
@@ -254,9 +277,9 @@ public readonly struct Matrix4x4(
float c = Math.Cos(radians); float c = Math.Cos(radians);
float s = Math.Sin(radians); float s = Math.Sin(radians);
return new Matrix4x4( return new(
c, s, 0f, 0f, c, -s, 0f, 0f,
-s, c, 0f, 0f, s, c, 0f, 0f,
0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f 0f, 0f, 0f, 1f
); );
@@ -275,7 +298,7 @@ public readonly struct Matrix4x4(
Vector3D x = up.Cross(z).Normalized; Vector3D x = up.Cross(z).Normalized;
Vector3D y = z.Cross(x); Vector3D y = z.Cross(x);
return new Matrix4x4( return new(
x.X, y.X, z.X, 0f, x.X, y.X, z.X, 0f,
x.Y, y.Y, z.Y, 0f, x.Y, y.Y, z.Y, 0f,
x.Z, y.Z, z.Z, 0f, x.Z, y.Z, z.Z, 0f,
@@ -285,28 +308,53 @@ public readonly struct Matrix4x4(
public static Matrix4x4 CreateLookMatrix(Vector3D position, Vector3D target, Vector3D up) public static Matrix4x4 CreateLookMatrix(Vector3D position, Vector3D target, Vector3D up)
{ {
Vector3D z = position.FromTo(target).Normalized; Vector3D f = (target - position).Normalized;
Vector3D x = up.Cross(z).Normalized; Vector3D s = f.Cross(up).Normalized;
Vector3D y = z.Cross(x); Vector3D u = s.Cross(f);
return new Matrix4x4( return new(
x.X, y.X, z.X, 0f, s.X, u.X, -f.X, 0f,
x.Y, y.Y, z.Y, 0f, s.Y, u.Y, -f.Y, 0f,
x.Z, y.Z, z.Z, 0f, s.Z, u.Z, -f.Z, 0f,
-x.Dot(position), -y.Dot(position), -z.Dot(position), 1f -s.Dot(position), -u.Dot(position), f.Dot(position), 1f
); );
} }
public static Matrix4x4 CreatePerspectiveFieldOfView(float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane) public static Matrix4x4 CreateOrthographicView(float width, float height, float nearPlane = -1f, float farPlane = 1f)
{ {
float yScale = 1f / Math.Tan(fieldOfViewInRadians / 2f); float invDepth = 1f / (farPlane - nearPlane);
float xScale = yScale / aspectRatio;
return new Matrix4x4( return new(
2f / width, 0f, 0f, 0f,
0f, -2f / height, 0f, 0f,
0f, 0f, 1f * invDepth, 0f,
-1f, 1f, -nearPlane * invDepth, 1f
);
}
public static Matrix4x4 CreateOrthographicViewCentered(float width, float height, float nearPlane = -1f, float farPlane = 1f)
{
float invDepth = 1f / (farPlane - nearPlane);
return new(
2f / width, 0f, 0f, 0f,
0f, 2f / height, 0f, 0f,
0f, 0f, 1f * invDepth, 0f,
0f, 0f, -nearPlane * invDepth, 1f
);
}
public static Matrix4x4 CreatePerspectiveFieldOfView(float fovRadians, float aspectRatio, float nearPlane, float farPlane)
{
float yScale = 1f / Math.Tan(fovRadians / 2f);
float xScale = yScale / aspectRatio;
float zRange = farPlane - nearPlane;
return new(
xScale, 0f, 0f, 0f, xScale, 0f, 0f, 0f,
0f, yScale, 0f, 0f, 0f, yScale, 0f, 0f,
0f, 0f, farPlane / (farPlane - nearPlane), 1f, 0f, 0f, -(farPlane + nearPlane) / zRange, -1f,
0f, 0f, -nearPlane * farPlane / (farPlane - nearPlane), 0f 0f, 0f, -(2f * nearPlane * farPlane) / zRange, 0f
); );
} }
@@ -344,11 +392,14 @@ public static class Matrix4x4Extensions
/// <inheritdoc cref="Matrix4x4.Invert(Matrix4x4)" /> /// <inheritdoc cref="Matrix4x4.Invert(Matrix4x4)" />
public static Matrix4x4 Invert(this Matrix4x4 matrix) => Matrix4x4.Invert(matrix); public static Matrix4x4 Invert(this Matrix4x4 matrix) => Matrix4x4.Invert(matrix);
/// <inheritdoc cref="Matrix4x4.Transpose(Matrix4x4)" />
public static Matrix4x4 Transpose(this Matrix4x4 matrix) => Matrix4x4.Transpose(matrix);
/// <inheritdoc cref="Matrix4x4.CreateTranslation(Vector3D)" /> /// <inheritdoc cref="Matrix4x4.CreateTranslation(Vector3D)" />
public static Matrix4x4 ApplyTranslation(this Matrix4x4 matrix, Vector3D translation) => matrix * Matrix4x4.CreateTranslation(translation); public static Matrix4x4 ApplyTranslation(this Matrix4x4 matrix, Vector3D translation) => matrix * Matrix4x4.CreateTranslation(translation);
/// <inheritdoc cref="Matrix4x4.CreateScale(Vector3D)" /> /// <inheritdoc cref="Matrix4x4.CreateScale(Vector3D)" />
public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, Vector3 scale) => matrix * Matrix4x4.CreateScale(scale); public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, Vector3D scale) => matrix * Matrix4x4.CreateScale(scale);
/// <inheritdoc cref="Matrix4x4.CreateScale(float)" /> /// <inheritdoc cref="Matrix4x4.CreateScale(float)" />
public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, float scale) => matrix * Matrix4x4.CreateScale(scale); public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, float scale) => matrix * Matrix4x4.CreateScale(scale);
@@ -366,15 +417,23 @@ public static class Matrix4x4Extensions
public static Matrix4x4 ApplyRotation(this Matrix4x4 matrix, Quaternion quaternion) => matrix * Matrix4x4.CreateRotation(quaternion); public static Matrix4x4 ApplyRotation(this Matrix4x4 matrix, Quaternion quaternion) => matrix * Matrix4x4.CreateRotation(quaternion);
/// <inheritdoc cref="Matrix4x4.CreateLookMatrix( Vector3D, Vector3D)" /> /// <inheritdoc cref="Matrix4x4.CreateLookMatrix( Vector3D, Vector3D)" />
public static Matrix4x4 ApplyLookRotationTo(this Matrix4x4 matrix, Vector3D forward, Vector3 up) => matrix * Matrix4x4.CreateLookMatrix(forward, up); public static Matrix4x4 ApplyLookRotationTo(this Matrix4x4 matrix, Vector3D forward, Vector3D up) => matrix * Matrix4x4.CreateLookMatrix(forward, up);
/// <inheritdoc cref="Matrix4x4.CreateLookMatrix(Vector3D, Vector3D, Vector3D)" /> /// <inheritdoc cref="Matrix4x4.CreateLookMatrix(Vector3D, Vector3D, Vector3D)" />
public static Matrix4x4 CreateLookMatrixTo(this Vector3D from, Vector3D to, Vector3 up) => Matrix4x4.CreateLookMatrix(from, to, up); public static Matrix4x4 CreateLookMatrixTo(this Vector3D from, Vector3D to, Vector3D up) => Matrix4x4.CreateLookMatrix(from, to, up);
/// <inheritdoc cref="Matrix4x4.CreatePerspectiveFieldOfView(float, float, float, float)" /> /// <inheritdoc cref="Matrix4x4.CreatePerspectiveFieldOfView(float, float, float, float)" />
public static Matrix4x4 ApplyPerspectiveFieldOfView(this Matrix4x4 matrix, float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane) public static Matrix4x4 ApplyPerspectiveFieldOfView(this Matrix4x4 matrix, float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane)
=> matrix * Matrix4x4.CreatePerspectiveFieldOfView(fieldOfViewInRadians, aspectRatio, nearPlane, farPlane); => matrix * Matrix4x4.CreatePerspectiveFieldOfView(fieldOfViewInRadians, aspectRatio, nearPlane, farPlane);
/// <inheritdoc cref="Matrix4x4.CreateOrthographicView(float, float, float, float)" />
public static Matrix4x4 ApplyOrthographicView(this Matrix4x4 matrix, float width, float height, float nearPlane = -1f, float farPlane = 1f)
=> matrix * Matrix4x4.CreateOrthographicView(width, height, nearPlane, farPlane);
/// <inheritdoc cref="Matrix4x4.CreateOrthographicViewCentered(float, float, float, float)" />
public static Matrix4x4 ApplyOrthographicViewCentered(this Matrix4x4 matrix, float width, float height, float nearPlane = -1f, float farPlane = 1f)
=> matrix * Matrix4x4.CreateOrthographicViewCentered(width, height, nearPlane, farPlane);
/// <inheritdoc cref="Matrix4x4.ToRightHanded(Matrix4x4) /> /// <inheritdoc cref="Matrix4x4.ToRightHanded(Matrix4x4) />
public static Matrix4x4 ToRightHanded(this Matrix4x4 matrix) => Matrix4x4.ToRightHanded(matrix); public static Matrix4x4 ToRightHanded(this Matrix4x4 matrix) => Matrix4x4.ToRightHanded(matrix);
} }

View File

@@ -82,11 +82,19 @@ public readonly struct Vector2D(float x, float y) : IEquatable<Vector2D>
public static bool operator ==(Vector2D left, Vector2D right) => left.X == right.X && left.Y == right.Y; public static bool operator ==(Vector2D left, Vector2D right) => left.X == right.X && left.Y == right.Y;
public static bool operator !=(Vector2D left, Vector2D right) => left.X != right.X || left.Y != right.Y; public static bool operator !=(Vector2D left, Vector2D right) => left.X != right.X || left.Y != right.Y;
public static implicit operator System.Numerics.Vector2(Vector2D vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector2DInt vector) => new(vector.X, vector.Y); public static implicit operator Vector2D(Vector2DInt vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector3D vector) => new(vector.X, vector.Y); public static implicit operator Vector2D(Vector3DInt vector) => new(vector.X, vector.Y);
public static implicit operator System.Numerics.Vector2(Vector2D vector) => new(vector.X, vector.Y);
public static implicit operator System.Numerics.Vector3(Vector2D vector) => new(vector.X, vector.Y, 0f);
public static implicit operator System.Numerics.Vector4(Vector2D vector) => new(vector.X, vector.Y, 0f, 0f);
public static implicit operator Vector2D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y); public static implicit operator Vector2D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(System.Numerics.Vector4 vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector3D vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector4D vector) => new(vector.X, vector.Y);
/// <summary> /// <summary>
/// Calculates the length of the <see cref="Vector2D"/>. /// Calculates the length of the <see cref="Vector2D"/>.

View File

@@ -74,9 +74,12 @@ public readonly struct Vector2DInt(int x, int y) : IEquatable<Vector2DInt>
public static bool operator ==(Vector2DInt left, Vector2DInt right) => left.X == right.X && left.Y == right.Y; public static bool operator ==(Vector2DInt left, Vector2DInt right) => left.X == right.X && left.Y == right.Y;
public static bool operator !=(Vector2DInt left, Vector2DInt right) => left.X != right.X || left.Y != right.Y; public static bool operator !=(Vector2DInt left, Vector2DInt right) => left.X != right.X || left.Y != right.Y;
public static implicit operator Vector2DInt(Vector2D vector) => new(vector.X.RoundToInt(), vector.Y.RoundToInt());
public static implicit operator Vector2DInt(Vector3DInt vector) => new(vector.X, vector.Y); public static implicit operator Vector2DInt(Vector3DInt vector) => new(vector.X, vector.Y);
public static implicit operator Vector2DInt(Vector2D vector) => new(vector.X.RoundToInt(), vector.Y.RoundToInt());
public static implicit operator Vector2DInt(Vector3D vector) => new(vector.X.RoundToInt(), vector.Y.RoundToInt());
public static implicit operator Vector2DInt(Vector4D vector) => new(vector.X.RoundToInt(), vector.Y.RoundToInt());
/// <summary> /// <summary>
/// Calculates the length of the <see cref="Vector2DInt"/>. /// Calculates the length of the <see cref="Vector2DInt"/>.
/// </summary> /// </summary>

View File

@@ -92,11 +92,19 @@ public readonly struct Vector3D(float x, float y, float z) : IEquatable<Vector3D
public static bool operator ==(Vector3D left, Vector3D right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z; public static bool operator ==(Vector3D left, Vector3D right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z;
public static bool operator !=(Vector3D left, Vector3D right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z; public static bool operator !=(Vector3D left, Vector3D right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z;
public static implicit operator System.Numerics.Vector3(Vector3D vector) => new(vector.X, vector.Y, vector.Z); public static implicit operator Vector3D(Vector2DInt vector) => new(vector.X, vector.Y, 0f);
public static implicit operator Vector3D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator Vector3D(Vector3DInt vector) => new(vector.X, vector.Y, vector.Z); public static implicit operator Vector3D(Vector3DInt vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator Vector3D(Vector2D vector) => new(vector.X, vector.Y, 0f);
public static implicit operator System.Numerics.Vector2(Vector3D vector) => new(vector.X, vector.Y);
public static implicit operator System.Numerics.Vector3(Vector3D vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator System.Numerics.Vector4(Vector3D vector) => new(vector.X, vector.Y, vector.Z, 0f);
public static implicit operator Vector3D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y, 0f); public static implicit operator Vector3D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y, 0f);
public static implicit operator Vector3D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator Vector3D(System.Numerics.Vector4 vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator Vector3D(Vector2D vector) => new(vector.X, vector.Y, 0f);
public static implicit operator Vector3D(Vector4D vector) => new(vector.X, vector.Y, vector.Z);
/// <summary> /// <summary>
/// Calculates the length of the <see cref="Vector3D"/>. /// Calculates the length of the <see cref="Vector3D"/>.

View File

@@ -84,9 +84,12 @@ public readonly struct Vector3DInt(int x, int y, int z) : IEquatable<Vector3DInt
public static bool operator ==(Vector3DInt left, Vector3DInt right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z; public static bool operator ==(Vector3DInt left, Vector3DInt right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z;
public static bool operator !=(Vector3DInt left, Vector3DInt right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z; public static bool operator !=(Vector3DInt left, Vector3DInt right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z;
public static implicit operator Vector3DInt(Vector3D vector) => new(vector.X.RoundToInt(), vector.Y.RoundToInt(), vector.Z.RoundToInt());
public static implicit operator Vector3DInt(Vector2DInt vector) => new(vector.X, vector.Y, 0); public static implicit operator Vector3DInt(Vector2DInt vector) => new(vector.X, vector.Y, 0);
public static implicit operator Vector3DInt(Vector2D vector) => new(vector.X.RoundToInt(), vector.Y.RoundToInt(), 0);
public static implicit operator Vector3DInt(Vector3D vector) => new(vector.X.RoundToInt(), vector.Y.RoundToInt(), vector.Z.RoundToInt());
public static implicit operator Vector3DInt(Vector4D vector) => new(vector.X.RoundToInt(), vector.Y.RoundToInt(), vector.Z.RoundToInt());
/// <summary> /// <summary>
/// Calculates the length of the <see cref="Vector3DInt"/>. /// Calculates the length of the <see cref="Vector3DInt"/>.
/// </summary> /// </summary>

View File

@@ -90,9 +90,20 @@ public readonly struct Vector4D(float x, float y, float z, float w) : IEquatable
public static bool operator ==(Vector4D left, Vector4D right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z && left.W == right.W; public static bool operator ==(Vector4D left, Vector4D right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z && left.W == right.W;
public static bool operator !=(Vector4D left, Vector4D right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z || left.W != right.W; public static bool operator !=(Vector4D left, Vector4D right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z || left.W != right.W;
public static implicit operator Vector4D(Vector2DInt vector) => new(vector.X, vector.Y, 0f, 0f);
public static implicit operator Vector4D(Vector3DInt vector) => new(vector.X, vector.Y, vector.Z, 0f);
public static implicit operator System.Numerics.Vector2(Vector4D vector) => new(vector.X, vector.Y);
public static implicit operator System.Numerics.Vector3(Vector4D vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator System.Numerics.Vector4(Vector4D vector) => new(vector.X, vector.Y, vector.Z, vector.W); public static implicit operator System.Numerics.Vector4(Vector4D vector) => new(vector.X, vector.Y, vector.Z, vector.W);
public static implicit operator Vector4D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y, 0f, 0f);
public static implicit operator Vector4D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y, vector.Z, 0f);
public static implicit operator Vector4D(System.Numerics.Vector4 vector) => new(vector.X, vector.Y, vector.Z, vector.W); public static implicit operator Vector4D(System.Numerics.Vector4 vector) => new(vector.X, vector.Y, vector.Z, vector.W);
public static implicit operator Vector4D(Vector2D vector) => new(vector.X, vector.Y, 0f, 0f);
public static implicit operator Vector4D(Vector3D vector) => new(vector.X, vector.Y, vector.Z, 0f);
/// <summary> /// <summary>
/// Calculates the length of the <see cref="Vector4D"/>. /// Calculates the length of the <see cref="Vector4D"/>.
/// </summary> /// </summary>

View File

@@ -0,0 +1,10 @@
using System;
namespace Engine.Core;
public class WaitForSecondsYield(float seconds) : ICoroutineYield
{
private readonly DateTime triggerTime = DateTime.UtcNow.AddSeconds(seconds);
public bool Yield() => DateTime.UtcNow < triggerTime;
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Engine.Core;
public class WaitUntilYield(Func<bool> condition) : ICoroutineYield
{
private readonly Func<bool> condition = condition;
public bool Yield() => !condition.Invoke();
}

View File

@@ -2,7 +2,7 @@ using System;
namespace Engine.Core; namespace Engine.Core;
public class CoroutineYield(Func<bool> condition) : ICoroutineYield public class WaitWhileYield(Func<bool> condition) : ICoroutineYield
{ {
private readonly Func<bool> condition = condition; private readonly Func<bool> condition = condition;

View File

@@ -178,6 +178,7 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IE
netPacketProcessor.RegisterNestedType(Vector3DNetPacker.Write, Vector3DNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector3DNetPacker.Write, Vector3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector3DIntNetPacker.Write, Vector3DIntNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector3DIntNetPacker.Write, Vector3DIntNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector4DNetPacker.Write, Vector4DNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector4DNetPacker.Write, Vector4DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Matrix4x4NetPacker.Write, Matrix4x4NetPacker.Read);
} }
public INetworkCommunicator SubscribeToPackets<T>(Event<IConnection, T>.EventHandler callback) public INetworkCommunicator SubscribeToPackets<T>(Event<IConnection, T>.EventHandler callback)

View File

@@ -6,7 +6,7 @@ using Engine.Core.Debug;
namespace Engine.Systems.Network; namespace Engine.Systems.Network;
public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicatorServer public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicatorServer, IPostUpdate
{ {
public string Password { get; private set; } = string.Empty; public string Password { get; private set; } = string.Empty;
public int MaxConnectionCount { get; private set; } = 2; public int MaxConnectionCount { get; private set; } = 2;
@@ -104,17 +104,8 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
} }
} }
private void PollEvents(IUniverse sender, IUniverse.UpdateArguments args) => Manager.PollEvents(); public void PostUpdate()
public override void EnterUniverse(IUniverse universe)
{ {
base.EnterUniverse(universe); Manager.PollEvents();
universe.OnPostUpdate.AddListener(PollEvents);
}
public override void ExitUniverse(IUniverse universe)
{
base.ExitUniverse(universe);
universe.OnPostUpdate.RemoveListener(PollEvents);
} }
} }

View File

@@ -0,0 +1,61 @@
using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network.Packers;
internal static class Matrix4x4NetPacker
{
internal static void Write(NetDataWriter writer, Matrix4x4 data)
{
writer.Put(data.M11);
writer.Put(data.M12);
writer.Put(data.M13);
writer.Put(data.M14);
writer.Put(data.M21);
writer.Put(data.M22);
writer.Put(data.M23);
writer.Put(data.M24);
writer.Put(data.M31);
writer.Put(data.M32);
writer.Put(data.M33);
writer.Put(data.M34);
writer.Put(data.M41);
writer.Put(data.M42);
writer.Put(data.M43);
writer.Put(data.M44);
}
internal static Matrix4x4 Read(NetDataReader reader)
{
float m11 = reader.GetFloat();
float m12 = reader.GetFloat();
float m13 = reader.GetFloat();
float m14 = reader.GetFloat();
float m21 = reader.GetFloat();
float m22 = reader.GetFloat();
float m23 = reader.GetFloat();
float m24 = reader.GetFloat();
float m31 = reader.GetFloat();
float m32 = reader.GetFloat();
float m33 = reader.GetFloat();
float m34 = reader.GetFloat();
float m41 = reader.GetFloat();
float m42 = reader.GetFloat();
float m43 = reader.GetFloat();
float m44 = reader.GetFloat();
return new Matrix4x4(
m11, m12, m13, m14,
m21, m22, m23, m24,
m31, m32, m33, m34,
m41, m42, m43, m44
);
}
}

View File

@@ -72,13 +72,34 @@ public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFr
// TODO This causes delay since OnPreDraw calls assuming this is called in in Update // TODO This causes delay since OnPreDraw calls assuming this is called in in Update
public Vector2D ScreenToWorldPosition(Vector2D screenPosition) public Vector2D ScreenToWorldPosition(Vector2D screenPosition)
{ {
Vector2D worldPosition = Vector2.Transform(screenPosition.ToVector2(), ViewMatrix.Inverse.ToXnaMatrix()).ToVector2D(); float x = 2f * screenPosition.X / Viewport.Width - 1f;
return worldPosition.Scale(EngineConverterExtensions.screenScale); float y = 1f - 2f * screenPosition.Y / Viewport.Height;
Vector4D normalizedCoordinates = new(x, y, 0f, 1f);
Matrix4x4 invertedViewProjectionMatrix = (ProjectionMatrix * ViewMatrix).Inverse;
Vector4D worldPosition = invertedViewProjectionMatrix * normalizedCoordinates;
if (worldPosition.W != 0f)
worldPosition /= worldPosition.W;
return new(worldPosition.X, worldPosition.Y);
} }
public Vector2D WorldToScreenPosition(Vector2D worldPosition) public Vector2D WorldToScreenPosition(Vector2D worldPosition)
{ {
Vector2D screenPosition = Vector2.Transform(worldPosition.ToVector2(), ViewMatrix.ToXnaMatrix()).ToVector2D(); Vector4D worldPosition4D = new(worldPosition.X, worldPosition.Y, 0f, 1f);
return screenPosition.Scale(EngineConverterExtensions.screenScale);
Matrix4x4 viewProjection = ProjectionMatrix * ViewMatrix;
Vector4D clip = viewProjection * worldPosition4D;
if (clip.W != 0f)
clip /= clip.W;
float screenX = (clip.X + 1f) * .5f * Viewport.Width;
float screenY = (1f - clip.Y) * .5f * Viewport.Height;
return new(screenX, screenY);
} }
public void LastActiveFrame() => Transform = null!; public void LastActiveFrame() => Transform = null!;
@@ -91,12 +112,11 @@ public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFr
public void PreDraw() public void PreDraw()
{ {
ProjectionMatrix = Matrix.CreateOrthographicOffCenter(Viewport.X, Viewport.Width, Viewport.Height, Viewport.Y, 0, 1).FromXnaMatrix(); ProjectionMatrix = Matrix4x4.CreateOrthographicViewCentered(Viewport.Width, Viewport.Height);
ViewMatrix = ViewMatrix = Matrix4x4.Identity
Matrix4x4.CreateTranslation(new Vector3D(-Transform.Position.X, Transform.Position.Y, 0f)) * .ApplyScale(Transform.Scale.X.Max(Transform.Scale.Y))
Matrix4x4.CreateRotationZ(Transform.Rotation * Math.DegreeToRadian) * .ApplyScale(Zoom)
Matrix4x4.CreateScale(Transform.Scale.X.Max(Transform.Scale.Y)) * .ApplyRotationZ(-Transform.Rotation * Math.DegreeToRadian)
Matrix4x4.CreateScale(Zoom) * .ApplyTranslation(new Vector3D(-Transform.Position.X, -Transform.Position.Y, 0f));
Matrix4x4.CreateTranslation(new Vector3D(Viewport.Width * .5f, Viewport.Height * .5f, 0f));
} }
} }

View File

@@ -15,49 +15,49 @@ public class SpriteBatchWrapper(GraphicsDevice graphicsDevice) : ISpriteBatch
=> spriteBatch.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); => spriteBatch.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix);
public void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth) public void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth)
=> spriteBatch.Draw(texture, position.ToDisplayVector2(), sourceAABB?.ToRectangle(), color, rotation, origin.ToDisplayVector2(), scale.ToDisplayVector2(), effects, layerDepth); => spriteBatch.Draw(texture, position.ToVector2(), sourceAABB?.ToRectangle(), color, rotation, origin.ToVector2(), scale.ToVector2(), effects, layerDepth);
public void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth) public void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth)
=> spriteBatch.Draw(texture, position.ToDisplayVector2(), sourceAABB?.ToRectangle(), color, rotation, origin.ToDisplayVector2(), scale, effects, layerDepth); => spriteBatch.Draw(texture, position.ToVector2(), sourceAABB?.ToRectangle(), color, rotation, origin.ToVector2(), scale, effects, layerDepth);
public void Draw(Texture2D texture, AABB2D destinationAABB, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, SpriteEffects effects, float layerDepth) public void Draw(Texture2D texture, AABB2D destinationAABB, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, SpriteEffects effects, float layerDepth)
=> spriteBatch.Draw(texture, destinationAABB.ToRectangle(), sourceAABB?.ToRectangle(), color, rotation, origin.ToDisplayVector2(), effects, layerDepth); => spriteBatch.Draw(texture, destinationAABB.ToRectangle(), sourceAABB?.ToRectangle(), color, rotation, origin.ToVector2(), effects, layerDepth);
public void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color) public void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color)
=> spriteBatch.Draw(texture, position.ToDisplayVector2(), sourceAABB?.ToRectangle(), color); => spriteBatch.Draw(texture, position.ToVector2(), sourceAABB?.ToRectangle(), color);
public void Draw(Texture2D texture, AABB2D destinationAABB, AABB2D? sourceAABB, Color color) public void Draw(Texture2D texture, AABB2D destinationAABB, AABB2D? sourceAABB, Color color)
=> spriteBatch.Draw(texture, destinationAABB.ToRectangle(), sourceAABB?.ToRectangle(), color); => spriteBatch.Draw(texture, destinationAABB.ToRectangle(), sourceAABB?.ToRectangle(), color);
public void Draw(Texture2D texture, Vector2D position, Color color) public void Draw(Texture2D texture, Vector2D position, Color color)
=> spriteBatch.Draw(texture, position.ToDisplayVector2(), color); => spriteBatch.Draw(texture, position.ToVector2(), color);
public void Draw(Texture2D texture, AABB2D destinationAABB, Color color) public void Draw(Texture2D texture, AABB2D destinationAABB, Color color)
=> spriteBatch.Draw(texture, destinationAABB.ToRectangle(), color); => spriteBatch.Draw(texture, destinationAABB.ToRectangle(), color);
public void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color) public void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color)
=> spriteBatch.DrawString(spriteFont, text, position.ToDisplayVector2(), color); => spriteBatch.DrawString(spriteFont, text, position.ToVector2(), color);
public void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth) public void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth)
=> spriteBatch.DrawString(spriteFont, text, position.ToDisplayVector2(), color, rotation, origin.ToDisplayVector2(), scale, effects, layerDepth); => spriteBatch.DrawString(spriteFont, text, position.ToVector2(), color, rotation, origin.ToVector2(), scale, effects, layerDepth);
public void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth) public void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth)
=> spriteBatch.DrawString(spriteFont, text, position.ToDisplayVector2(), color, rotation, origin.ToDisplayVector2(), scale.ToDisplayVector2(), effects, layerDepth); => spriteBatch.DrawString(spriteFont, text, position.ToVector2(), color, rotation, origin.ToVector2(), scale.ToVector2(), effects, layerDepth);
public void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth, bool rtl) public void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth, bool rtl)
=> spriteBatch.DrawString(spriteFont, text, position.ToDisplayVector2(), color, rotation, origin.ToDisplayVector2(), scale.ToDisplayVector2(), effects, layerDepth, rtl); => spriteBatch.DrawString(spriteFont, text, position.ToVector2(), color, rotation, origin.ToVector2(), scale.ToVector2(), effects, layerDepth, rtl);
public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2D position, Color color) public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2D position, Color color)
=> spriteBatch.DrawString(spriteFont, text, position.ToDisplayVector2(), color); => spriteBatch.DrawString(spriteFont, text, position.ToVector2(), color);
public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2D position, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth) public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2D position, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth)
=> spriteBatch.DrawString(spriteFont, text, position.ToDisplayVector2(), color, rotation, origin.ToDisplayVector2(), scale, effects, layerDepth); => spriteBatch.DrawString(spriteFont, text, position.ToVector2(), color, rotation, origin.ToVector2(), scale, effects, layerDepth);
public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2D position, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth) public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2D position, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth)
=> spriteBatch.DrawString(spriteFont, text, position.ToDisplayVector2(), color, rotation, origin.ToDisplayVector2(), scale.ToDisplayVector2(), effects, layerDepth); => spriteBatch.DrawString(spriteFont, text, position.ToVector2(), color, rotation, origin.ToVector2(), scale.ToVector2(), effects, layerDepth);
public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2D position, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth, bool rtl) public void DrawString(SpriteFont spriteFont, StringBuilder text, Vector2D position, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth, bool rtl)
=> spriteBatch.DrawString(spriteFont, text, position.ToDisplayVector2(), color, rotation, origin.ToDisplayVector2(), scale.ToDisplayVector2(), effects, layerDepth, rtl); => spriteBatch.DrawString(spriteFont, text, position.ToVector2(), color, rotation, origin.ToVector2(), scale.ToVector2(), effects, layerDepth, rtl);
public void End() public void End()
=> spriteBatch.End(); => spriteBatch.End();

View File

@@ -9,8 +9,6 @@ namespace Engine.Integration.MonoGame;
public static class EngineConverterExtensions public static class EngineConverterExtensions
{ {
public readonly static Vector2D screenScale = Vector2D.Down + Vector2D.Right;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UniverseTime ToEngineTime(this GameTime gameTime) => new(gameTime.TotalGameTime, gameTime.ElapsedGameTime); public static UniverseTime ToEngineTime(this GameTime gameTime) => new(gameTime.TotalGameTime, gameTime.ElapsedGameTime);
@@ -60,12 +58,6 @@ public static class EngineConverterExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Microsoft.Xna.Framework.Quaternion ToXnaQuaternion(this Core.Quaternion quaternion) => new(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W); public static Microsoft.Xna.Framework.Quaternion ToXnaQuaternion(this Core.Quaternion quaternion) => new(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 ToDisplayVector2(this Vector2D vector) => vector.Scale(screenScale).ToVector2();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2D ApplyDisplayScale(this Vector2D vector) => vector.Scale(screenScale);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle ToDisplayRectangle(this Rectangle rectangle, DisplayMode displayMode) => new() public static Rectangle ToDisplayRectangle(this Rectangle rectangle, DisplayMode displayMode) => new()
{ {
@@ -78,8 +70,8 @@ public static class EngineConverterExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle ToRectangle(this AABB2D aabb) => new() public static Rectangle ToRectangle(this AABB2D aabb) => new()
{ {
X = (int)(aabb.LowerBoundary.X * screenScale.X), X = (int)aabb.LowerBoundary.X,
Y = (int)(aabb.LowerBoundary.Y * screenScale.Y), Y = (int)aabb.LowerBoundary.Y,
Width = (int)(aabb.UpperBoundary.X - aabb.LowerBoundary.X), Width = (int)(aabb.UpperBoundary.X - aabb.LowerBoundary.X),
Height = (int)(aabb.UpperBoundary.Y - aabb.LowerBoundary.Y) Height = (int)(aabb.UpperBoundary.Y - aabb.LowerBoundary.Y)
}; };

View File

@@ -19,8 +19,11 @@ public class MonoGameTriangleBatch : Behaviour, ITriangleBatch, IFirstFrameUpdat
private BasicEffect basicEffect = null!; private BasicEffect basicEffect = null!;
private readonly RasterizerState rasterizerState = new() { CullMode = CullMode.None }; private readonly RasterizerState rasterizerState = new() { CullMode = CullMode.None };
private ICamera camera = null!;
public void FirstActiveFrame() public void FirstActiveFrame()
{ {
camera = Universe.FindRequiredBehaviour<ICamera>();
GraphicsDevice graphicsDevice = Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.GraphicsDevice; GraphicsDevice graphicsDevice = Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.GraphicsDevice;
this.graphicsDevice = graphicsDevice; this.graphicsDevice = graphicsDevice;
basicEffect = new(graphicsDevice); basicEffect = new(graphicsDevice);
@@ -33,9 +36,9 @@ public class MonoGameTriangleBatch : Behaviour, ITriangleBatch, IFirstFrameUpdat
if (verticesIndex + 3 >= vertices.Length) if (verticesIndex + 3 >= vertices.Length)
Flush(); Flush();
Vector2 A = triangle.A.ToDisplayVector2(); Vector2 A = triangle.A.ToVector2();
Vector2 B = triangle.B.ToDisplayVector2(); Vector2 B = triangle.B.ToVector2();
Vector2 C = triangle.C.ToDisplayVector2(); Vector2 C = triangle.C.ToVector2();
Color color = colorRGBA.ToColor(); Color color = colorRGBA.ToColor();
vertices[verticesIndex++] = new(new(A.X, A.Y, 0f), color); vertices[verticesIndex++] = new(new(A.X, A.Y, 0f), color);
@@ -45,18 +48,8 @@ public class MonoGameTriangleBatch : Behaviour, ITriangleBatch, IFirstFrameUpdat
public void Begin(Matrix4x4? view = null, Matrix4x4? projection = null) public void Begin(Matrix4x4? view = null, Matrix4x4? projection = null)
{ {
if (view != null) this.view = (view ?? camera.ViewMatrix).Transposed.ToXnaMatrix();
this.view = view.Value.ToXnaMatrix(); this.projection = (projection ?? camera.ProjectionMatrix).Transposed.ToXnaMatrix();
else
this.view = Matrix.Identity;
if (projection != null)
this.projection = projection.Value.ToXnaMatrix();
else
{
Viewport viewport = graphicsDevice.Viewport;
this.projection = Matrix.CreateOrthographicOffCenter(viewport.X, viewport.Width, viewport.Height, viewport.Y, 0, 1);
}
} }
public void End() => Flush(); public void End() => Flush();

View File

@@ -4,30 +4,35 @@ using Engine.Core;
namespace Engine.Systems.Graphics; namespace Engine.Systems.Graphics;
public class TriangleBatcher : Behaviour, IFirstFrameUpdate, IDraw public class TriangleBatcher : Behaviour, IFirstFrameUpdate, ILastFrameUpdate, IDraw
{ {
private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y)); private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority; private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private ITriangleBatch triangleBatch = null!; private readonly BehaviourCollector<ITriangleBatch> triangleBatches = new();
private ICamera camera = null!;
private readonly ActiveBehaviourCollectorOrdered<int, IDrawableTriangle> drawableShapes = new(GetPriority(), SortByAscendingPriority()); private readonly ActiveBehaviourCollectorOrdered<int, IDrawableTriangle> drawableShapes = new(GetPriority(), SortByAscendingPriority());
public void FirstActiveFrame() public void FirstActiveFrame()
{ {
camera = Universe.FindRequiredBehaviour<ICamera>();
triangleBatch = Universe.FindRequiredBehaviour<ITriangleBatch>();
drawableShapes.Unassign();
drawableShapes.Assign(Universe); drawableShapes.Assign(Universe);
triangleBatches.Assign(Universe);
} }
public void Draw() public void Draw()
{ {
triangleBatch.Begin(camera.ViewMatrix, camera.ProjectionMatrix); for (int i = 0; i < triangleBatches.Count; i++)
for (int i = 0; i < drawableShapes.Count; i++) {
drawableShapes[i].Draw(triangleBatch); ITriangleBatch triangleBatch = triangleBatches[i];
triangleBatch.End(); triangleBatch.Begin();
for (int j = 0; j < drawableShapes.Count; j++)
drawableShapes[j].Draw(triangleBatch);
triangleBatch.End();
}
}
public void LastActiveFrame()
{
triangleBatches.Unassign();
drawableShapes.Unassign();
} }
} }

View File

@@ -0,0 +1,6 @@
namespace Engine.Systems.Network;
public interface IPacketListenerClientEntity<T> : INetworkEntity where T : IEntityNetworkPacket
{
void OnEntityClientPacketArrived(IConnection sender, T packet);
}

View File

@@ -0,0 +1,6 @@
namespace Engine.Systems.Network;
public interface IPacketListenerServerEntity<T> : INetworkEntity where T : IEntityNetworkPacket
{
void OnEntityServerPacketArrived(IConnection sender, T packet);
}

View File

@@ -10,25 +10,127 @@ namespace Engine.Systems.Network;
/// <summary> /// <summary>
/// Intermediary manager that looks up in it's hierarchy for a <see cref="INetworkCommunicator"/> to route/broadcast it's received packets to their destinations. /// Intermediary manager that looks up in it's hierarchy for a <see cref="INetworkCommunicator"/> to route/broadcast it's received packets to their destinations.
/// </summary> /// </summary>
/// TODO: I need to peer check this class, I don't exactly remember the state I was in when I was originally writing it and left it uncommented and the current comments are added later on.
/// It's a fairly complex manager that relies heavily on Reflection and lots of generic method delegation which is making it very hard to read back.
public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetworkManager public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetworkManager
{ {
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientPacketArrivalMethods = []; #region Packet Router/Broadcaster to Listener Delegates
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<string, object>> clientPacketRouters = []; /// <summary>
private readonly Dictionary<Type, Dictionary<string, object>> serverPacketRouters = []; /// Behaviour Type → Packet Type → List of <see cref="IPacketListenerClient{T}"/> listener methods (broadcast packets, client-side)
/// </summary>
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientBroadcastPacketListenerMethods = [];
private readonly List<(Type packetType, Delegate @delegate)> packetRetrievalDelegates = []; /// <summary>
/// Behaviour Type → Packet Type → List of <see cref="IPacketListenerServer{T}"/> listener methods (broadcast packets, server-side)
/// </summary>
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverBroadcastPacketListenerMethods = [];
/// <summary>
/// Behaviour Type → Packet Type → List of <see cref="IPacketListenerClientEntity{T}"/> listener methods (entity packets, client-side)
/// </summary>
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientEntityPacketListenerMethods = [];
/// <summary>
/// Behaviour Type → Packet Type → List of <see cref="IPacketListenerServerEntity{T}"/> listener methods (entity packets, server-side)
/// </summary>
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverEntityPacketListenerMethods = [];
#endregion
#region Packet Router/Broadcaster Events
/// <summary>
/// Packet Type → Behaviour.Id → Broadcaster Event (broadcast, client-side)
/// </summary>
private readonly Dictionary<Type, Dictionary<string, object>> clientPacketBroadcastEvents = [];
/// <summary>
/// Packet Type → Behaviour.Id → Broadcaster Event (broadcast, server-side)
/// </summary>
private readonly Dictionary<Type, Dictionary<string, object>> serverPacketBroadcastEvents = [];
/// <summary>
/// Maps an <see cref="IEntityNetworkPacket"/> type to a set of routing events,
/// keyed by <see cref="IIdentifiable.Id"/>, for CLIENT entity listeners.
/// The packet is routed to the correct <see cref="INetworkEntity"/> instance
/// by matching <see cref="IEntityNetworkPacket.EntityId"/>.
/// </summary>
private readonly Dictionary<Type, Dictionary<string, object>> clientEntityPacketRouterEvents = [];
/// <summary>
/// Maps an <see cref="IEntityNetworkPacket"/> type to a set of routing events,
/// keyed by <see cref="IIdentifiable.Id"/>, for SERVER entity listeners.
/// The packet is routed to the correct <see cref="INetworkEntity"/> instance
/// by matching <see cref="IEntityNetworkPacket.EntityId"/>.
/// </summary>
private readonly Dictionary<Type, Dictionary<string, object>> serverEntityPacketRouterEvents = [];
#endregion
#region Packet Retrieval Delegates
/// <summary>
/// Stores delegates that connect incoming broadcast packets from <see cref="INetworkCommunicator"/>
/// to <see cref="OnPacketReceived{T}(IConnection, T)"/>.
/// These are used to subscribe/unsubscribe from <see cref="INetworkCommunicator"/> events.
/// </summary>
private readonly List<PacketRetrievalDelegatePair> broadcastPacketRetrievalSubscriptionDelegates = [];
/// <summary>
/// Stores delegates that connect incoming entity packets from <see cref="INetworkCommunicator"/>
/// to <see cref="OnPacketReceived{T}(IConnection, T)"/>.
/// These are used to subscribe/unsubscribe from <see cref="INetworkCommunicator"/> events.
/// </summary>
private readonly List<PacketRetrievalDelegatePair> entityPacketRetrievalSubscriptionDelegates = [];
/// <summary>
/// Stores delegates that connect incoming all packets from <see cref="INetworkCommunicator"/> to
/// <see cref="OnPacketReceived{T}(IConnection, T)"/>. This is a combination of all subscription
/// delegates filtered so there are no duplicates packet entries.
/// </summary>
private readonly List<PacketRetrievalDelegatePair> uniqueRetrievalSubscriptionDelegates = [];
#endregion
#region Method Caches
/// <summary>
/// Packet type → <see cref="ClearRouter{T}(object)"/> method.
/// </summary>
private readonly Dictionary<Type, MethodInfo> clearRoutesMethods = []; private readonly Dictionary<Type, MethodInfo> clearRoutesMethods = [];
private readonly Dictionary<Type, MethodInfo> registerPacketListenersMethods = [];
/// <summary>
/// Packet type → <see cref="RegisterBroadcastPacketListenerEvent{T}(INetworkEntity, Event{IConnection, T}, NetworkType)"/> method.
/// </summary>
private readonly Dictionary<Type, MethodInfo> registerBroadcastPacketListenersMethods = [];
/// <summary>
/// Packet type → <see cref="RegisterEntityPacketListenerEvent{T}(INetworkEntity, Event{IConnection, T}, NetworkType)"/> method.
/// </summary>
private readonly Dictionary<Type, MethodInfo> registerEntityPacketListenersMethods = [];
#endregion
#region Network Entity Collector
/// <summary>
/// All active network <see cref="INetworkEntity"/>, keyed by <see cref="IIdentifiable.Id"/>.
/// </summary>
private readonly Dictionary<string, INetworkEntity> _networkEntities = []; private readonly Dictionary<string, INetworkEntity> _networkEntities = [];
public IReadOnlyDictionary<string, INetworkEntity> NetworkEntities => _networkEntities; public IReadOnlyDictionary<string, INetworkEntity> NetworkEntities => _networkEntities;
/// <summary>
/// Collector responsible for detecting <see cref="INetworkEntity"/>s entering/leaving the universe.
/// </summary>
private readonly BehaviourCollector<INetworkEntity> _networkEntityCollector = new(); private readonly BehaviourCollector<INetworkEntity> _networkEntityCollector = new();
public IBehaviourCollector<INetworkEntity> NetworkEntityCollector => _networkEntityCollector; public IBehaviourCollector<INetworkEntity> NetworkEntityCollector => _networkEntityCollector;
#endregion
#region Network Communicator
public INetworkCommunicator NetworkCommunicator public INetworkCommunicator NetworkCommunicator
{ {
get; get;
@@ -40,68 +142,74 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
INetworkCommunicator? previousCommunicator = field; INetworkCommunicator? previousCommunicator = field;
field = value; field = value;
if (previousCommunicator is not null) UnsubscribeCommunicatorMethods(previousCommunicator); // Unsubscribe packet delegates from old communicator
if (field is not null) SubscribeCommunicatorMethods(field); if (previousCommunicator is not null)
InvokeCommunicatorMethods(nameof(INetworkCommunicator.UnsubscribeFromPackets), previousCommunicator);
// Subscribe packet delegates to new communicator
if (field is not null)
InvokeCommunicatorMethods(nameof(INetworkCommunicator.SubscribeToPackets), field);
} }
} = null!; } = null!;
#region Communicator Subscriptions /// <summary>
private void SubscribeCommunicatorMethods(INetworkCommunicator networkCommunicator) /// Dynamically invokes <see cref="INetworkCommunicator.SubscribeToPackets{T}(Event{IConnection, T}.EventHandler)"/>
{ /// or <see cref="INetworkCommunicator.UnsubscribeFromPackets{T}(Event{IConnection, T}.EventHandler)"/>
MethodInfo subscribeToPacketsMethod = typeof(INetworkCommunicator) /// on the provided <see cref="INetworkCommunicator"/> for all known packet types.
.GetMethod(nameof(INetworkCommunicator.SubscribeToPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; /// </summary>
private void InvokeCommunicatorMethods(string methodName, INetworkCommunicator networkCommunicator)
foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates)
{
MethodInfo genericSubscribeMethod = subscribeToPacketsMethod.MakeGenericMethod(packetType);
genericSubscribeMethod.Invoke(networkCommunicator, [@delegate]);
}
}
private void UnsubscribeCommunicatorMethods(INetworkCommunicator networkCommunicator)
{ {
MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator) MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator)
.GetMethod(nameof(INetworkCommunicator.UnsubscribeFromPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!;
foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates) foreach ((Type packetType, Delegate @delegate) in uniqueRetrievalSubscriptionDelegates)
{ {
MethodInfo genericUnsubscribeMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType); MethodInfo genericMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType);
genericUnsubscribeMethod.Invoke(networkCommunicator, [@delegate]); genericMethod.Invoke(networkCommunicator, [@delegate]);
} }
} }
#endregion #endregion
#region Packet Routing ////////////////////////////////////////////////////////////////
#region Packet Routing/Broadcasting
/// <summary>
/// Entry point for ALL incoming packets from the <see cref="NetworkCommunicator"/>.
/// </summary>
private void OnPacketReceived<T>(IConnection sender, T entityDataPacket) private void OnPacketReceived<T>(IConnection sender, T entityDataPacket)
{ {
BroadcastPacket(sender, entityDataPacket);
if (entityDataPacket is IEntityNetworkPacket entityPacket) if (entityDataPacket is IEntityNetworkPacket entityPacket)
RoutePacket(sender, entityDataPacket, entityPacket); RoutePacket(sender, entityDataPacket, entityPacket);
else
BroadcastPacket(sender, entityDataPacket);
} }
private void RoutePacket<T>(IConnection sender, T entityDataPacket, IEntityNetworkPacket entityPacket) private void RoutePacket<T>(IConnection sender, T entityDataPacket, IEntityNetworkPacket entityPacket)
{ {
if (NetworkCommunicator is INetworkCommunicatorClient) if (NetworkCommunicator is INetworkCommunicatorClient)
RoutePacket(clientPacketRouters, entityPacket.EntityId, sender, entityDataPacket); RoutePacket(clientEntityPacketRouterEvents, entityPacket.EntityId, sender, entityDataPacket);
if (NetworkCommunicator is INetworkCommunicatorServer) if (NetworkCommunicator is INetworkCommunicatorServer)
RoutePacket(serverPacketRouters, entityPacket.EntityId, sender, entityDataPacket); RoutePacket(serverEntityPacketRouterEvents, entityPacket.EntityId, sender, entityDataPacket);
} }
private void BroadcastPacket<T>(IConnection sender, T entityDataPacket) private void BroadcastPacket<T>(IConnection sender, T entityDataPacket)
{ {
if (NetworkCommunicator is INetworkCommunicatorClient) if (NetworkCommunicator is INetworkCommunicatorClient)
BroadcastPacket(clientPacketRouters, sender, entityDataPacket); BroadcastPacket(clientPacketBroadcastEvents, sender, entityDataPacket);
if (NetworkCommunicator is INetworkCommunicatorServer) if (NetworkCommunicator is INetworkCommunicatorServer)
BroadcastPacket(serverPacketRouters, sender, entityDataPacket); BroadcastPacket(serverPacketBroadcastEvents, sender, entityDataPacket);
} }
private static void BroadcastPacket<T>( private void BroadcastPacket<T>(
Dictionary<Type, Dictionary<string, object>> packetRouters, Dictionary<Type, Dictionary<string, object>> packetBroadcasters,
IConnection sender, IConnection sender,
T entityDataPacket) T entityDataPacket)
{ {
if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary<string, object>? routers)) if (!packetBroadcasters.TryGetValue(entityDataPacket!.GetType(), out Dictionary<string, object>? routers))
return; return;
foreach ((string behaviourId, object routerEventReference) in routers) foreach ((string behaviourId, object routerEventReference) in routers)
@@ -111,7 +219,7 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
} }
} }
private static void RoutePacket<T>( private void RoutePacket<T>(
Dictionary<Type, Dictionary<string, object>> packetRouters, Dictionary<Type, Dictionary<string, object>> packetRouters,
string entityId, string entityId,
IConnection sender, IConnection sender,
@@ -126,65 +234,114 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
Event<IConnection, T> routerEvent = (Event<IConnection, T>)routerEventReference; Event<IConnection, T> routerEvent = (Event<IConnection, T>)routerEventReference;
routerEvent.Invoke(sender, entityDataPacket!); routerEvent.Invoke(sender, entityDataPacket!);
} }
#endregion #endregion
#region Packet Routers #region Packet Routers
/// <summary>
/// Registers routing events for the behaviour based on cached packet listener methods.
/// </summary>
private void RegisterPacketRoutersFor( private void RegisterPacketRoutersFor(
INetworkEntity behaviour, INetworkEntity behaviour,
Dictionary<Type, Dictionary<string, object>> packetRouters, Dictionary<Type, Dictionary<string, object>> packetRouters,
Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods, Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetListenerMethods,
NetworkType networkType) NetworkType networkType,
Dictionary<Type, MethodInfo> registerPacketListenerListenersMethods)
{ {
if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary<Type, List<MethodInfo>>? arrivalMethods)) if (!packetListenerMethods.TryGetValue(behaviour.GetType(), out Dictionary<Type, List<MethodInfo>>? listenerMethods))
return; return;
foreach ((Type packetType, List<MethodInfo> methods) in arrivalMethods) foreach (Type packetType in listenerMethods.Keys)
foreach (MethodInfo receiveMethod in methods) {
if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers))
{ {
if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers)) routers = [];
{ packetRouters.Add(packetType, routers);
routers = [];
packetRouters.Add(packetType, routers);
}
object packetListenerEvent = CreateEventAndRegister(packetType, behaviour, networkType);
routers.Add(behaviour.Id, packetListenerEvent);
} }
object packetListenerEvent = CreateEventAndRegister(packetType, behaviour, networkType, registerPacketListenerListenersMethods);
routers.Add(behaviour.Id, packetListenerEvent);
}
} }
private object CreateEventAndRegister(Type packetType, INetworkEntity behaviour, NetworkType networkType) /// <summary>
/// Creates an Event<IConnection, TPacket> and attaches listener callbacks.
/// </summary>
private object CreateEventAndRegister(
Type packetType,
INetworkEntity behaviour,
NetworkType networkType,
Dictionary<Type, MethodInfo> registerPacketListenersMethods)
{ {
Type genericEventType = typeof(Event<,>).MakeGenericType(typeof(IConnection), packetType); Type genericEventType = typeof(Event<,>).MakeGenericType(typeof(IConnection), packetType);
object packetListenerEvent = Activator.CreateInstance(genericEventType)!; object packetListenerEvent = Activator.CreateInstance(genericEventType)!;
if (!registerPacketListenersMethods.TryGetValue(packetType, out MethodInfo? registerPacketListenerMethod)) if (!registerPacketListenersMethods.TryGetValue(packetType, out MethodInfo? registerPacketListenerMethod))
throw new($"{nameof(RegisterPacketListenerEvent)} for {packetType.Name} has not been cached."); throw new($"Packet Listener Events for {packetType.Name} has not been cached.");
registerPacketListenerMethod.Invoke(this, [behaviour, packetListenerEvent, networkType]); registerPacketListenerMethod.Invoke(this, [behaviour, packetListenerEvent, networkType]);
return packetListenerEvent; return packetListenerEvent;
} }
private static void RegisterPacketListenerEvent<T>( /// <summary>
/// Registers broadcast packet listeners on the behaviour.
/// </summary>
private static void RegisterBroadcastPacketListenerEvent<T>(
INetworkEntity behaviour, INetworkEntity behaviour,
Event<IConnection, T> packetListenerEvent, Event<IConnection, T> packetListenerEvent,
NetworkType networkType) NetworkType networkType)
{ {
switch (networkType) switch (networkType)
{ {
case NetworkType.Client: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerClient<T>)behaviour).OnClientPacketArrived(sender, packet)); break; case NetworkType.Client:
case NetworkType.Server: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerServer<T>)behaviour).OnServerPacketArrived(sender, packet)); break; packetListenerEvent.AddListener(
(sender, packet) => ((IPacketListenerClient<T>)behaviour).OnClientPacketArrived(sender, packet));
break;
case NetworkType.Server:
packetListenerEvent.AddListener(
(sender, packet) => ((IPacketListenerServer<T>)behaviour).OnServerPacketArrived(sender, packet));
break;
} }
} }
/// <summary>
/// Registers entity-specific packet listeners on a network behaviour.
/// </summary>
private static void RegisterEntityPacketListenerEvent<T>(
INetworkEntity behaviour,
Event<IConnection, T> packetListenerEvent,
NetworkType networkType
) where T : IEntityNetworkPacket
{
switch (networkType)
{
case NetworkType.Client:
packetListenerEvent.AddListener(
(sender, packet) => ((IPacketListenerClientEntity<T>)behaviour).OnEntityClientPacketArrived(sender, packet));
break;
case NetworkType.Server:
packetListenerEvent.AddListener(
(sender, packet) => ((IPacketListenerServerEntity<T>)behaviour).OnEntityServerPacketArrived(sender, packet));
break;
}
}
/// <summary>
/// Unregisters all routing events associated with the behaviour.
/// </summary>
private void UnregisterPacketRoutersFor( private void UnregisterPacketRoutersFor(
INetworkEntity behaviour, INetworkEntity behaviour,
Dictionary<Type, Dictionary<string, object>> packetRouters, Dictionary<Type, Dictionary<string, object>> packetRouters,
Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods) Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetListenerMethods)
{ {
if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary<Type, List<MethodInfo>>? arrivalMethods)) if (!packetListenerMethods.TryGetValue(behaviour.GetType(), out Dictionary<Type, List<MethodInfo>>? listenerMethods))
return; return;
foreach ((Type packetType, List<MethodInfo> methods) in arrivalMethods) foreach ((Type packetType, List<MethodInfo> methods) in listenerMethods)
{ {
if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers)) if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers))
continue; continue;
@@ -199,6 +356,9 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
} }
} }
/// <summary>
/// Clears all listeners from a router event.
/// </summary>
private static void ClearRouter<T>(object routerEventReference) private static void ClearRouter<T>(object routerEventReference)
{ {
Event<IConnection, T> routerEvent = (Event<IConnection, T>)routerEventReference; Event<IConnection, T> routerEvent = (Event<IConnection, T>)routerEventReference;
@@ -208,124 +368,192 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
#endregion #endregion
#region Engine Callbacks #region Engine Callbacks
private void OnCollected(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourCollectedArguments args)
/// <summary>
/// Called when an <see cref="INetworkEntity"/> enters the universe.
/// Registers all packet routing for that entity.
/// </summary>
private void OnCollected(
IBehaviourCollector<INetworkEntity> sender,
IBehaviourCollector<INetworkEntity>.BehaviourCollectedArguments args)
{ {
INetworkEntity collectedBehaviour = args.BehaviourCollected; INetworkEntity collectedBehaviour = args.BehaviourCollected;
if (!_networkEntities.TryAdd(collectedBehaviour.Id, collectedBehaviour)) if (!_networkEntities.TryAdd(collectedBehaviour.Id, collectedBehaviour))
throw new($"Unable to add {collectedBehaviour.Id} to {nameof(NetworkManager)}"); throw new($"Unable to add {collectedBehaviour.Id} to {nameof(NetworkManager)}");
RegisterPacketRoutersFor(collectedBehaviour, clientPacketRouters, clientPacketArrivalMethods, NetworkType.Client); RegisterPacketRoutersFor(collectedBehaviour, clientPacketBroadcastEvents, clientBroadcastPacketListenerMethods, NetworkType.Client, registerBroadcastPacketListenersMethods);
RegisterPacketRoutersFor(collectedBehaviour, serverPacketRouters, serverPacketArrivalMethods, NetworkType.Server); RegisterPacketRoutersFor(collectedBehaviour, clientEntityPacketRouterEvents, clientEntityPacketListenerMethods, NetworkType.Client, registerEntityPacketListenersMethods);
RegisterPacketRoutersFor(collectedBehaviour, serverPacketBroadcastEvents, serverBroadcastPacketListenerMethods, NetworkType.Server, registerBroadcastPacketListenersMethods);
RegisterPacketRoutersFor(collectedBehaviour, serverEntityPacketRouterEvents, serverEntityPacketListenerMethods, NetworkType.Server, registerEntityPacketListenersMethods);
} }
/// <summary>
/// Called when an <see cref="INetworkEntity"/> is removed from the universe.
/// Cleans up all routing.
/// </summary>
private void OnRemoved(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourRemovedArguments args) private void OnRemoved(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourRemovedArguments args)
{ {
INetworkEntity removedBehaviour = args.BehaviourRemoved; INetworkEntity removedBehaviour = args.BehaviourRemoved;
if (!_networkEntities.Remove(args.BehaviourRemoved.Id))
if (!_networkEntities.Remove(removedBehaviour.Id))
return; return;
UnregisterPacketRoutersFor(removedBehaviour, clientPacketRouters, clientPacketArrivalMethods); UnregisterPacketRoutersFor(removedBehaviour, clientPacketBroadcastEvents, clientBroadcastPacketListenerMethods);
UnregisterPacketRoutersFor(removedBehaviour, serverPacketRouters, serverPacketArrivalMethods); UnregisterPacketRoutersFor(removedBehaviour, clientEntityPacketRouterEvents, clientEntityPacketListenerMethods);
UnregisterPacketRoutersFor(removedBehaviour, serverPacketBroadcastEvents, serverBroadcastPacketListenerMethods);
UnregisterPacketRoutersFor(removedBehaviour, serverEntityPacketRouterEvents, serverEntityPacketListenerMethods);
} }
public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign(); public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign();
public void EnterUniverse(IUniverse universe) public void EnterUniverse(IUniverse universe)
{ {
_networkEntityCollector.Assign(universe); _networkEntityCollector.Assign(universe);
NetworkCommunicator = BehaviourController.GetRequiredBehaviourInParent<INetworkCommunicator>(); NetworkCommunicator = BehaviourController.GetRequiredBehaviourInParent<INetworkCommunicator>();
} }
#endregion #endregion
#region Initialization #region Initialization
public NetworkManager() public NetworkManager()
{ {
CachePacketRetrievalDelegates(); CacheRetrievalSubscriptionDelegates();
CacheRegistrationMethods(); CacheRegistrationMethods();
CachePacketArrivalMethods(); CachePacketListenerMethods();
_networkEntityCollector.OnCollected.AddListener(OnCollected); _networkEntityCollector.OnCollected.AddListener(OnCollected);
_networkEntityCollector.OnRemoved.AddListener(OnRemoved); _networkEntityCollector.OnRemoved.AddListener(OnRemoved);
} }
private void CachePacketRetrievalDelegates() /// <summary>
/// Caches all retrieval subscription delegates for packets.
/// </summary>
private void CacheRetrievalSubscriptionDelegates()
{ {
IEnumerable<Type> packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()) CachePacketRetrievalDelegates(typeof(INetworkPacket), broadcastPacketRetrievalSubscriptionDelegates);
.Where(t => typeof(INetworkPacket).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType); CachePacketRetrievalDelegates(typeof(IEntityNetworkPacket), entityPacketRetrievalSubscriptionDelegates);
MethodInfo onPacketArrivedMethod = GetType() uniqueRetrievalSubscriptionDelegates.AddRange(broadcastPacketRetrievalSubscriptionDelegates.Concat(entityPacketRetrievalSubscriptionDelegates).DistinctBy(pair => pair.PacketType));
.GetMethod(nameof(OnPacketReceived), BindingFlags.NonPublic | BindingFlags.Instance)!; }
foreach (Type packetType in packetTypes) /// <summary>
/// Creates delegates for all concrete packet types that forward packets to <see cref="OnPacketReceived{T}(IConnection, T)"/>.
/// </summary>
private void CachePacketRetrievalDelegates(Type packetType, List<PacketRetrievalDelegatePair> retrievalDelegates)
{
IEnumerable<Type> packetTypes =
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t =>
packetType.IsAssignableFrom(t) &&
!t.IsInterface &&
!t.IsAbstract &&
!t.IsGenericType
);
MethodInfo onPacketArrivedMethod = GetType().GetMethod(nameof(OnPacketReceived), BindingFlags.NonPublic | BindingFlags.Instance)!;
foreach (Type type in packetTypes)
{ {
MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(packetType); MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(type);
Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(IConnection), type);
Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(IConnection), packetType);
Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod); Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod);
packetRetrievalDelegates.Add((packetType, genericPacketReceivedDelegate)); retrievalDelegates.Add((type, genericPacketReceivedDelegate));
} }
} }
/// <summary>
/// Caches all registration and cleanup methods for packets.
/// </summary>
private void CacheRegistrationMethods() private void CacheRegistrationMethods()
{ {
CacheRegistrationMethods(registerPacketListenersMethods, nameof(RegisterPacketListenerEvent)); CacheRegistrationMethods(registerBroadcastPacketListenersMethods, nameof(RegisterBroadcastPacketListenerEvent), broadcastPacketRetrievalSubscriptionDelegates);
CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter)); CacheRegistrationMethods(registerEntityPacketListenersMethods, nameof(RegisterEntityPacketListenerEvent), entityPacketRetrievalSubscriptionDelegates);
CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter), uniqueRetrievalSubscriptionDelegates);
} }
private void CacheRegistrationMethods(Dictionary<Type, MethodInfo> registrationMethods, string methodName) /// <summary>
/// Creates generic method instances for each packet type listener to be registered into the <see cref="NetworkEntity"/>.
/// </summary>
private void CacheRegistrationMethods(
Dictionary<Type, MethodInfo> listenerRegistrationMethods,
string methodName,
List<PacketRetrievalDelegatePair> packetRetrievalDelegates)
{ {
MethodInfo registerPacketMethod = typeof(NetworkManager).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static)!; MethodInfo registerPacketMethod = typeof(NetworkManager).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static)!;
foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates) foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates)
{ {
MethodInfo genericMethod = registerPacketMethod.MakeGenericMethod(packetType); MethodInfo genericMethod = registerPacketMethod.MakeGenericMethod(packetType);
registrationMethods.Add(packetType, genericMethod); listenerRegistrationMethods.TryAdd(packetType, genericMethod);
} }
} }
private void CachePacketArrivalMethods() /// <summary>
/// Caches packet listener methods for all packet listener interfaces.
/// </summary>
private void CachePacketListenerMethods()
{ {
CachePacketArrivalMethods(clientPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<INetworkEntity>.OnClientPacketArrived)); CachePacketListenerMethods(clientBroadcastPacketListenerMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<>.OnClientPacketArrived));
CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<INetworkEntity>.OnServerPacketArrived)); CachePacketListenerMethods(serverBroadcastPacketListenerMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<>.OnServerPacketArrived));
CachePacketListenerMethods(clientEntityPacketListenerMethods, typeof(IPacketListenerClientEntity<>), nameof(IPacketListenerClientEntity<>.OnEntityClientPacketArrived));
CachePacketListenerMethods(serverEntityPacketListenerMethods, typeof(IPacketListenerServerEntity<>), nameof(IPacketListenerServerEntity<>.OnEntityServerPacketArrived));
} }
private static void CachePacketArrivalMethods(Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName) /// <summary>
/// Discovers all types implementing a given packet listener interface and caches their methods.
/// </summary>
private static void CachePacketListenerMethods(
Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetListenerMethods,
Type listenerType,
string packetListenerMethodName)
{ {
foreach (Type listenerClass in GetGenericsWith(listenerType)) foreach (Type listenerClass in GetGenericsWith(listenerType))
{ {
Dictionary<Type, List<MethodInfo>> packetRouters = []; Dictionary<Type, List<MethodInfo>> listenerMethodDictionary = [];
packetArrivalMethods.Add(listenerClass, packetRouters); packetListenerMethods.Add(listenerClass, listenerMethodDictionary);
foreach (Type packetListener in GetGenericInterfacesWith(listenerType, listenerClass)) foreach (Type packetListener in GetGenericInterfacesWith(listenerType, listenerClass))
{ {
Type packetType = packetListener.GetGenericArguments().First(); Type packetType = packetListener.GetGenericArguments().First();
List<MethodInfo> arrivalMethods = packetListener List<MethodInfo> listenerMethods = [.. packetListener.GetMethods().Where(m => m.Name == packetListenerMethodName)];
.GetMethods()
.Where(m => m.Name == packetArrivalMethodName)
.ToList();
packetRouters.Add(packetType, arrivalMethods); listenerMethodDictionary.Add(packetType, listenerMethods);
} }
} }
} }
/// <summary>
/// Finds all types that implement a generic interface.
/// </summary>
private static IEnumerable<Type> GetGenericsWith(Type type) private static IEnumerable<Type> GetGenericsWith(Type type)
=> AppDomain.CurrentDomain => AppDomain.CurrentDomain
.GetAssemblies() .GetAssemblies()
.SelectMany(a => .SelectMany(a =>
a.GetTypes().Where( a.GetTypes().Where(
t => t.GetInterfaces().Any( t => t.GetInterfaces().Any(
i => i.IsGenericType && i.GetGenericTypeDefinition() == type i => i.IsGenericType && i.GetGenericTypeDefinition() == type)));
)
)
);
// Gets all generic interfaces of a specific definition on a type
private static IEnumerable<Type> GetGenericInterfacesWith(Type interfaceType, Type type) private static IEnumerable<Type> GetGenericInterfacesWith(Type interfaceType, Type type)
=> type.GetInterfaces().Where( => type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType);
i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType
);
#endregion #endregion
// Identifies whether packet routing is client-side or server-side
private enum NetworkType { Client, Server } private enum NetworkType { Client, Server }
private readonly record struct PacketRetrievalDelegatePair(Type PacketType, Delegate Delegate)
{
public static implicit operator (Type packetType, Delegate @delegate)(PacketRetrievalDelegatePair value) => (value.PacketType, value.Delegate);
public static implicit operator PacketRetrievalDelegatePair((Type packetType, Delegate @delegate) value) => new(value.packetType, value.@delegate);
}
} }

View File

@@ -2,4 +2,4 @@ using Engine.Core;
namespace Engine.Systems.Tween; namespace Engine.Systems.Tween;
public class WaitForTweenCompleteCoroutineYield(ITween tween) : CoroutineYield(() => tween.State == TweenState.Completed); public class WaitForTweenCompleteCoroutineYield(ITween tween) : WaitUntilYield(() => tween.State == TweenState.Completed);

View File

@@ -2,4 +2,4 @@ using Engine.Core;
namespace Engine.Systems.Tween; namespace Engine.Systems.Tween;
public class WaitWhileTweenActiveCoroutineYield(ITween tween) : CoroutineYield(() => tween.State.CheckFlag(TweenState.Completed | TweenState.Cancelled)); public class WaitWhileTweenActiveCoroutineYield(ITween tween) : WaitUntilYield(() => tween.State.CheckFlag(TweenState.Completed | TweenState.Cancelled));