diff --git a/.editorconfig b/.editorconfig index b314c0c..4803563 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,7 +13,7 @@ spelling_exclusion_path = SpellingExclusions.dic [*.{cs,csx,vb,vbx}] indent_size = 4 insert_final_newline = true -charset = utf-8-bom +charset = utf-8 # XML project files [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] diff --git a/Engine.Core/Abstract/ICamera.cs b/Engine.Core/Abstract/ICamera.cs new file mode 100644 index 0000000..cc9912d --- /dev/null +++ b/Engine.Core/Abstract/ICamera.cs @@ -0,0 +1,22 @@ +namespace Engine.Core; + +/// +/// Represents a camera with view and projections matrices in the engine. +/// +public interface ICamera +{ + /// + /// The viewport of the . + /// + Vector2D Viewport { get; } + + /// + /// View of the . + /// + Matrix4x4 ViewMatrix { get; } + + /// + /// Projection of the . + /// + Matrix4x4 ProjectionMatrix { get; } +} diff --git a/Engine.Core/Abstract/ICamera2D.cs b/Engine.Core/Abstract/ICamera2D.cs index 4c12105..941fcd5 100644 --- a/Engine.Core/Abstract/ICamera2D.cs +++ b/Engine.Core/Abstract/ICamera2D.cs @@ -3,7 +3,7 @@ namespace Engine.Core; /// /// Represents a 2D camera in the engine. /// -public interface ICamera2D : IBehaviour2D +public interface ICamera2D : ICamera, IBehaviour2D { /// /// The zoom level of the camera. diff --git a/Engine.Core/Abstract/ICamera3D.cs b/Engine.Core/Abstract/ICamera3D.cs index a0024a0..b2346bb 100644 --- a/Engine.Core/Abstract/ICamera3D.cs +++ b/Engine.Core/Abstract/ICamera3D.cs @@ -3,7 +3,7 @@ namespace Engine.Core; /// /// Represents a 3D camera in the engine. /// -public interface ICamera3D : IBehaviour3D +public interface ICamera3D : ICamera, IBehaviour3D { /// /// Event triggered when the near plane of the changes. diff --git a/Engine.Core/Abstract/IHasId.cs b/Engine.Core/Abstract/IIdentifiable.cs similarity index 100% rename from Engine.Core/Abstract/IHasId.cs rename to Engine.Core/Abstract/IIdentifiable.cs diff --git a/Engine.Core/BehaviourInternal.cs b/Engine.Core/BehaviourIndependent.cs similarity index 100% rename from Engine.Core/BehaviourInternal.cs rename to Engine.Core/BehaviourIndependent.cs diff --git a/Engine.Core/Config/BasicConfiguration.cs b/Engine.Core/Config/BasicConfiguration.cs new file mode 100644 index 0000000..8f833c7 --- /dev/null +++ b/Engine.Core/Config/BasicConfiguration.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; + +namespace Engine.Core.Config; + +public class BasicConfiguration : IConfiguration +{ + public Event OnAdded { get; } = new(); + public Event OnSet { get; } = new(); + public Event OnRemoved { get; } = new(); + + private readonly Dictionary values = []; + + public IReadOnlyDictionary Values => values; + + public T Get(string key, T defaultValue) => Get(key) ?? defaultValue; + public T? Get(string key) + { + if (!values.TryGetValue(key, out object? value)) + return default; + + if (value is T castedObject) + return castedObject; + + try { return (T?)System.Convert.ChangeType(value, typeof(T)); } catch { } + + return default; + } + + public object? Get(string key) + { + values.TryGetValue(key, out object? value); + return value; + } + + public bool Has(string key) => values.ContainsKey(key); + + public void Remove(string key) + { + if (values.Remove(key)) + OnRemoved.Invoke(this, new(key)); + } + + public void Set(string key, T value) + { + if (!values.TryAdd(key, value)) + values[key] = value; + else + OnAdded.Invoke(this, new(key)); + OnSet.Invoke(this, new(key)); + } +} diff --git a/Engine.Core/Config/ConfigurationExtensions.cs b/Engine.Core/Config/ConfigurationExtensions.cs new file mode 100644 index 0000000..f83416f --- /dev/null +++ b/Engine.Core/Config/ConfigurationExtensions.cs @@ -0,0 +1,8 @@ +using Engine.Core.Exceptions; + +namespace Engine.Core.Config; + +public static class ConfigurationExtensions +{ + public static T GetRequired(this IConfiguration configuration, string key) => configuration.Get(key) ?? throw new NotFoundException($"Type of {typeof(T).FullName} with the key {key} was not present in the {configuration.GetType().FullName}"); +} diff --git a/Engine.Core/Config/IConfiguration.cs b/Engine.Core/Config/IConfiguration.cs new file mode 100644 index 0000000..15abbb9 --- /dev/null +++ b/Engine.Core/Config/IConfiguration.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Engine.Core.Config; + +public interface IConfiguration +{ + static IConfiguration System { get; set; } = new SystemConfiguration(); + static IConfiguration Shared { get; set; } = new BasicConfiguration(); + + Event OnAdded { get; } + Event OnSet { get; } + Event OnRemoved { get; } + + IReadOnlyDictionary Values { get; } + + bool Has(string key); + object? Get(string key); + T Get(string key, T defaultValue); + T? Get(string key); + void Set(string key, T value); + void Remove(string key); + + readonly record struct ConfigUpdateArguments(string Key); +} diff --git a/Engine.Core/Config/SystemConfiguration.cs b/Engine.Core/Config/SystemConfiguration.cs new file mode 100644 index 0000000..895b4bf --- /dev/null +++ b/Engine.Core/Config/SystemConfiguration.cs @@ -0,0 +1,11 @@ +namespace Engine.Core.Config; + +public class SystemConfiguration : BasicConfiguration, IConfiguration +{ + public SystemConfiguration() + { + foreach (System.Collections.DictionaryEntry entry in System.Environment.GetEnvironmentVariables()) + if (entry is { Key: string key, Value: not null }) + Set(key, entry.Value); + } +} diff --git a/Engine.Core/Debug/AssertHelpers.cs b/Engine.Core/Debug/Assert.cs similarity index 100% rename from Engine.Core/Debug/AssertHelpers.cs rename to Engine.Core/Debug/Assert.cs diff --git a/Engine.Core/Debug/RotatingFileLogger.cs b/Engine.Core/Debug/RotatingFileLogger.cs index ec3e007..fa4911d 100644 --- a/Engine.Core/Debug/RotatingFileLogger.cs +++ b/Engine.Core/Debug/RotatingFileLogger.cs @@ -55,7 +55,7 @@ public class RotatingFileLogger : ILogger private static void RotateLastLogs(string directory, string prefix, int rotateLength) { IOrderedEnumerable logs = System.IO.Directory.GetFiles(directory, $"{prefix}*.log") - .OrderBy(File.GetCreationTime); + .OrderByDescending(File.GetCreationTime); foreach (string file in logs.Skip(rotateLength)) try diff --git a/Engine.Core/Extensions/IdentifiableExtensions.cs b/Engine.Core/Extensions/IdentifiableExtensions.cs new file mode 100644 index 0000000..0bb8f74 --- /dev/null +++ b/Engine.Core/Extensions/IdentifiableExtensions.cs @@ -0,0 +1,12 @@ +namespace Engine.Core; + +public static class IdentifiableExtensions +{ + public static bool IsIdentical(this IIdentifiable? left, IIdentifiable? right) + { + if (left == null || right == null) + return false; + + return left?.Id?.CompareTo(right?.Id) == 0; + } +} diff --git a/Engine.Core/Helpers/Event.cs b/Engine.Core/Helpers/Event.cs index c008c52..042a29b 100644 --- a/Engine.Core/Helpers/Event.cs +++ b/Engine.Core/Helpers/Event.cs @@ -58,6 +58,9 @@ public class Event 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 listeners = null!; private readonly List onceListeners = null!; @@ -74,6 +77,9 @@ public class Event if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentCallIndex) + currentCallIndex++; + listeners.Insert(insertIndex, listenerData); } @@ -90,6 +96,9 @@ public class Event if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentOnceCallIndex) + currentOnceCallIndex++; + onceListeners.Insert(insertIndex, listenerData); } @@ -103,6 +112,8 @@ public class Event if (listeners[i].Callback == listener) { listeners.RemoveAt(i); + if (i < currentCallIndex) + currentCallIndex--; return; } } @@ -117,6 +128,8 @@ public class Event if (onceListeners[i].Callback == listener) { onceListeners.RemoveAt(i); + if (i < currentOnceCallIndex) + currentOnceCallIndex--; return; } } @@ -131,23 +144,23 @@ public class Event /// public void Invoke() { - for (int i = listeners.Count - 1; i >= 0; i--) - try { listeners[i].Callback.Invoke(); } + for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--) + try { listeners[currentCallIndex].Callback.Invoke(); } catch (Exception exception) { - string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}()"; - EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}()"; + 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) { - string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}()"; - EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}()"; + EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? this, Logger, exception, methodCallRepresentation); } - onceListeners.RemoveAt(i); + onceListeners.RemoveAt(currentOnceCallIndex); } } @@ -216,6 +229,9 @@ public class Event where TSender : class 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 listeners = null!; private readonly List onceListeners = null!; @@ -232,6 +248,9 @@ public class Event where TSender : class if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentCallIndex) + currentCallIndex++; + listeners.Insert(insertIndex, listenerData); } @@ -248,6 +267,9 @@ public class Event where TSender : class if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentOnceCallIndex) + currentOnceCallIndex++; + onceListeners.Insert(insertIndex, listenerData); } @@ -261,6 +283,8 @@ public class Event where TSender : class if (listeners[i].Callback == listener) { listeners.RemoveAt(i); + if (i < currentCallIndex) + currentCallIndex--; return; } } @@ -275,6 +299,8 @@ public class Event where TSender : class if (onceListeners[i].Callback == listener) { onceListeners.RemoveAt(i); + if (i < currentOnceCallIndex) + currentOnceCallIndex--; return; } } @@ -290,23 +316,23 @@ public class Event where TSender : class /// The caller that's triggering this event. public void Invoke(TSender sender) { - for (int i = listeners.Count - 1; i >= 0; i--) - try { listeners[i].Callback.Invoke(sender); } + for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--) + try { listeners[currentCallIndex].Callback.Invoke(sender); } catch (Exception exception) { - string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender})"; - EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}({sender})"; + 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) { - string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender})"; - EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}({sender})"; + EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); } - onceListeners.RemoveAt(i); + onceListeners.RemoveAt(currentOnceCallIndex); } } @@ -382,6 +408,9 @@ public class Event where TSender : class 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 listeners = null!; private readonly List onceListeners = null!; @@ -398,6 +427,9 @@ public class Event where TSender : class if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentCallIndex) + currentCallIndex++; + listeners.Insert(insertIndex, listenerData); } @@ -414,6 +446,9 @@ public class Event where TSender : class if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentOnceCallIndex) + currentOnceCallIndex++; + onceListeners.Insert(insertIndex, listenerData); } @@ -427,6 +462,8 @@ public class Event where TSender : class if (listeners[i].Callback == listener) { listeners.RemoveAt(i); + if (i < currentCallIndex) + currentCallIndex--; return; } } @@ -441,6 +478,8 @@ public class Event where TSender : class if (onceListeners[i].Callback == listener) { onceListeners.RemoveAt(i); + if (i < currentOnceCallIndex) + currentOnceCallIndex--; return; } } @@ -457,23 +496,23 @@ public class Event where TSender : class /// The arguments provided for this event. public void Invoke(TSender sender, TArguments args) { - for (int i = listeners.Count - 1; i >= 0; i--) - try { listeners[i].Callback.Invoke(sender, args); } + for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--) + try { listeners[currentCallIndex].Callback.Invoke(sender, args); } catch (Exception exception) { - string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender}, {args})"; - EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}({sender}, {args})"; + 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) { - string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender}, {args})"; - EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}({sender}, {args})"; + EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); } - onceListeners.RemoveAt(i); + onceListeners.RemoveAt(currentOnceCallIndex); } } diff --git a/Engine.Core/Helpers/FastListOrdered.cs b/Engine.Core/Helpers/FastListOrdered.cs index d99c826..cd72982 100644 --- a/Engine.Core/Helpers/FastListOrdered.cs +++ b/Engine.Core/Helpers/FastListOrdered.cs @@ -16,8 +16,7 @@ public class FastListOrdered : IList, IReadOnlyList private readonly Func getIndexFunc = null!; private readonly IComparer sortBy = null!; - private int count = 0; - public int Count => count; + public int Count { get; private set; } = 0; public bool IsReadOnly { get; set; } = false; @@ -35,10 +34,10 @@ public class FastListOrdered : IList, IReadOnlyList private (TIndex TIndex, int i) GetAt(Index index) { int actualIndex = index.IsFromEnd - ? count - index.Value + ? Count - index.Value : index.Value; - if (actualIndex < 0 || actualIndex >= count) + if (actualIndex < 0 || actualIndex >= Count) throw new IndexOutOfRangeException(); int leftIndex = actualIndex; @@ -75,7 +74,7 @@ public class FastListOrdered : IList, IReadOnlyList items[key] = list = []; list.Add(item); - count++; + Count++; } public void Insert(int index, TItem item) @@ -88,7 +87,7 @@ public class FastListOrdered : IList, IReadOnlyList items[tIndex] = list = []; list.Insert(index, item); - count++; + Count++; } public bool Remove(TItem item) @@ -103,7 +102,7 @@ public class FastListOrdered : IList, IReadOnlyList if (!list.Remove(item)) return false; - count--; + Count--; return true; } @@ -114,7 +113,7 @@ public class FastListOrdered : IList, IReadOnlyList (TIndex tIndex, int i) = GetAt(index); items[tIndex].RemoveAt(i); - count--; + Count--; } public void Clear() @@ -125,7 +124,7 @@ public class FastListOrdered : IList, IReadOnlyList foreach ((TIndex index, FastList list) in items) list.Clear(); - count = 0; + Count = 0; } public bool Contains(TItem item) diff --git a/Engine.Core/Primitives/ColorRGB.cs b/Engine.Core/Primitives/ColorRGB.cs index 1fada67..afcc5e7 100644 --- a/Engine.Core/Primitives/ColorRGB.cs +++ b/Engine.Core/Primitives/ColorRGB.cs @@ -95,7 +95,11 @@ public readonly struct ColorRGB(byte r, byte g, byte b) : IEquatable int greenDiff = to.G - from.G; int blueDiff = to.B - from.B; - return from + new ColorRGB((byte)(redDiff * t), (byte)(greenDiff * t), (byte)(blueDiff * t)); + return new( + (byte)(from.R + redDiff * t), + (byte)(from.G + greenDiff * t), + (byte)(from.B + blueDiff * t) + ); } /// diff --git a/Engine.Core/Primitives/ColorRGBA.cs b/Engine.Core/Primitives/ColorRGBA.cs index 8163840..ce14ac8 100644 --- a/Engine.Core/Primitives/ColorRGBA.cs +++ b/Engine.Core/Primitives/ColorRGBA.cs @@ -1,4 +1,5 @@ using System; +using Engine.Core.Debug; namespace Engine.Core; @@ -125,7 +126,12 @@ public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255) : IEquata int blueDiff = to.B - from.B; int alphaDiff = to.A - from.A; - return from + new ColorRGBA((byte)(redDiff * t), (byte)(greenDiff * t), (byte)(blueDiff * t), (byte)(alphaDiff * t)); + return new( + (byte)(from.R + redDiff * t), + (byte)(from.G + greenDiff * t), + (byte)(from.B + blueDiff * t), + (byte)(from.A + alphaDiff * t) + ); } /// diff --git a/Engine.Core/Primitives/Matrix4x4.cs b/Engine.Core/Primitives/Matrix4x4.cs index 0288b41..cc6632f 100644 --- a/Engine.Core/Primitives/Matrix4x4.cs +++ b/Engine.Core/Primitives/Matrix4x4.cs @@ -1,12 +1,11 @@ using System; -using System.Numerics; namespace Engine.Core; // TODO Comments /// -/// Represents a 4D left handed space matrix. +/// Represents a 4D left handed space matrix in a Column Major convention. /// /// /// Initializes a new instance of the struct with the specified values. @@ -62,6 +61,16 @@ public readonly struct Matrix4x4( 0f, 0f, 0f, 1f ); + /// + /// Represents the inverted version of this . + /// + public Matrix4x4 Inverse => Invert(this); + + /// + /// Represents the transposed version of this . + /// + public Matrix4x4 Transposed => Transpose(this); + 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.M12 + a.M12 * b.M22 + a.M13 * b.M32 + a.M14 * b.M42, @@ -84,6 +93,13 @@ public readonly struct Matrix4x4( 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) => 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 && @@ -129,11 +145,98 @@ public readonly struct Matrix4x4( m.M13 * m.M21 * m.M32 * m.M44 - m.M11 * m.M23 * m.M32 * m.M44 - m.M12 * m.M21 * m.M33 * m.M44 + m.M11 * m.M22 * m.M33 * m.M44; + /// + /// Inverts the given . + /// + /// The . + /// The inverted of the given . + public static Matrix4x4 Invert(Matrix4x4 m) + { + float m1 = m.M11, m2 = m.M12, m3 = m.M13, m4 = m.M14; + float m5 = m.M21, m6 = m.M22, m7 = m.M23, m8 = m.M24; + float m9 = m.M31, m10 = m.M32, m11 = m.M33, m12 = m.M34; + float m13 = m.M41, m14 = m.M42, m15 = m.M43, m16 = m.M44; + + float num = m11 * m16 - m12 * m15; + float num2 = m10 * m16 - m12 * m14; + float num3 = m10 * m15 - m11 * m14; + float num4 = m9 * m16 - m12 * m13; + float num5 = m9 * m15 - m11 * m13; + float num6 = m9 * m14 - m10 * m13; + + float num7 = m6 * num - m7 * num2 + m8 * num3; + float num8 = -(m5 * num - m7 * num4 + m8 * num5); + float num9 = m5 * num2 - m6 * num4 + m8 * num6; + float num10 = -(m5 * num3 - m6 * num5 + m7 * num6); + + float invDet = 1f / (m1 * num7 + m2 * num8 + m3 * num9 + m4 * num10); + + float r11 = num7 * invDet; + float r21 = num8 * invDet; + float r31 = num9 * invDet; + float r41 = num10 * invDet; + + float r12 = (-(m2 * num - m3 * num2 + m4 * num3)) * invDet; + float r22 = (m1 * num - m3 * num4 + m4 * num5) * invDet; + float r32 = (-(m1 * num2 - m2 * num4 + m4 * num6)) * invDet; + float r42 = (m1 * num3 - m2 * num5 + m3 * num6) * invDet; + + float num12 = m7 * m16 - m8 * m15; + float num13 = m6 * m16 - m8 * m14; + float num14 = m6 * m15 - m7 * m14; + float num15 = m5 * m16 - m8 * m13; + float num16 = m5 * m15 - m7 * m13; + float num17 = m5 * m14 - m6 * m13; + + float r13 = (m2 * num12 - m3 * num13 + m4 * num14) * invDet; + float r23 = (-(m1 * num12 - m3 * num15 + m4 * num16)) * invDet; + float r33 = (m1 * num13 - m2 * num15 + m4 * num17) * invDet; + float r43 = (-(m1 * num14 - m2 * num16 + m3 * num17)) * invDet; + + float num18 = m7 * m12 - m8 * m11; + float num19 = m6 * m12 - m8 * m10; + float num20 = m6 * m11 - m7 * m10; + float num21 = m5 * m12 - m8 * m9; + float num22 = m5 * m11 - m7 * m9; + float num23 = m5 * m10 - m6 * m9; + + float r14 = (-(m2 * num18 - m3 * num19 + m4 * num20)) * invDet; + float r24 = (m1 * num18 - m3 * num21 + m4 * num22) * invDet; + float r34 = (-(m1 * num19 - m2 * num21 + m4 * num23)) * invDet; + float r44 = (m1 * num20 - m2 * num22 + m3 * num23) * invDet; + + return new( + r11, r12, r13, r14, + r21, r22, r23, r24, + r31, r32, r33, r34, + r41, r42, r43, r44 + ); + } + + /// + /// Transposes the given . + /// + /// The . + /// The transposed of the given . + 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( - 1f, 0f, 0f, 0f, - 0f, 1f, 0f, 0f, - 0f, 0f, 1f, 0f, - position.X, position.Y, position.Z, 1 + 1f, 0f, 0f, position.X, + 0f, 1f, 0f, position.Y, + 0f, 0f, 1f, position.Z, + 0f, 0f, 0f, 1f + ); + + public static Matrix4x4 CreateScale(float scale) => new( + scale, 0f, 0f, 0f, + 0f, scale, 0f, 0f, + 0f, 0f, scale, 0f, + 0f, 0f, 0f, 1f ); public static Matrix4x4 CreateScale(Vector3D scale) => new( @@ -148,11 +251,11 @@ public readonly struct Matrix4x4( float c = Math.Cos(radians); float s = Math.Sin(radians); - return new Matrix4x4( - 1f, 0f, 0f, 0f, - 0f, c, s, 0f, - 0f, -s, c, 0f, - 0f, 0f, 0f, 1f + return new( + 1f, 0f, 0f, 0f, + 0f, c, -s, 0f, + 0f, s, c, 0f, + 0f, 0f, 0f, 1f ); } @@ -161,10 +264,10 @@ public readonly struct Matrix4x4( float c = Math.Cos(radians); float s = Math.Sin(radians); - return new Matrix4x4( - c, 0f, -s, 0f, + return new( + c, 0f, s, 0f, 0f, 1f, 0f, 0f, - s, 0f, c, 0f, + -s, 0f, c, 0f, 0f, 0f, 0f, 1f ); } @@ -174,9 +277,9 @@ public readonly struct Matrix4x4( float c = Math.Cos(radians); float s = Math.Sin(radians); - return new Matrix4x4( - c, s, 0f, 0f, - -s, c, 0f, 0f, + return new( + c, -s, 0f, 0f, + s, c, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 1f ); @@ -195,7 +298,7 @@ public readonly struct Matrix4x4( Vector3D x = up.Cross(z).Normalized; Vector3D y = z.Cross(x); - return new Matrix4x4( + return new( x.X, y.X, z.X, 0f, x.Y, y.Y, z.Y, 0f, x.Z, y.Z, z.Z, 0f, @@ -205,28 +308,53 @@ public readonly struct Matrix4x4( public static Matrix4x4 CreateLookMatrix(Vector3D position, Vector3D target, Vector3D up) { - Vector3D z = position.FromTo(target).Normalized; - Vector3D x = up.Cross(z).Normalized; - Vector3D y = z.Cross(x); + Vector3D f = (target - position).Normalized; + Vector3D s = f.Cross(up).Normalized; + Vector3D u = s.Cross(f); - return new Matrix4x4( - x.X, y.X, z.X, 0f, - x.Y, y.Y, z.Y, 0f, - x.Z, y.Z, z.Z, 0f, - -x.Dot(position), -y.Dot(position), -z.Dot(position), 1f + return new( + s.X, u.X, -f.X, 0f, + s.Y, u.Y, -f.Y, 0f, + s.Z, u.Z, -f.Z, 0f, + -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 xScale = yScale / aspectRatio; + float invDepth = 1f / (farPlane - nearPlane); - 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, 0f, yScale, 0f, 0f, - 0f, 0f, farPlane / (farPlane - nearPlane), 1f, - 0f, 0f, -nearPlane * farPlane / (farPlane - nearPlane), 0f + 0f, 0f, -(farPlane + nearPlane) / zRange, -1f, + 0f, 0f, -(2f * nearPlane * farPlane) / zRange, 0f ); } @@ -261,11 +389,20 @@ public static class Matrix4x4Extensions /// public static float Determinant(this Matrix4x4 matrix) => Matrix4x4.Determinant(matrix); + /// + public static Matrix4x4 Invert(this Matrix4x4 matrix) => Matrix4x4.Invert(matrix); + + /// + public static Matrix4x4 Transpose(this Matrix4x4 matrix) => Matrix4x4.Transpose(matrix); + /// public static Matrix4x4 ApplyTranslation(this Matrix4x4 matrix, Vector3D translation) => matrix * Matrix4x4.CreateTranslation(translation); /// - 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); + + /// + public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, float scale) => matrix * Matrix4x4.CreateScale(scale); /// public static Matrix4x4 ApplyRotationX(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationX(radians); @@ -280,15 +417,28 @@ public static class Matrix4x4Extensions public static Matrix4x4 ApplyRotation(this Matrix4x4 matrix, Quaternion quaternion) => matrix * Matrix4x4.CreateRotation(quaternion); /// - 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); /// - 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); /// public static Matrix4x4 ApplyPerspectiveFieldOfView(this Matrix4x4 matrix, float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane) => matrix * Matrix4x4.CreatePerspectiveFieldOfView(fieldOfViewInRadians, aspectRatio, nearPlane, farPlane); + /// + public static Matrix4x4 ApplyOrthographicView(this Matrix4x4 matrix, float width, float height, float nearPlane = -1f, float farPlane = 1f) + => matrix * Matrix4x4.CreateOrthographicView(width, height, nearPlane, farPlane); + + /// + public static Matrix4x4 ApplyOrthographicViewCentered(this Matrix4x4 matrix, float width, float height, float nearPlane = -1f, float farPlane = 1f) + => matrix * Matrix4x4.CreateOrthographicViewCentered(width, height, nearPlane, farPlane); + /// 's. + /// + public static Matrix4x4 ApplyMatrix(this Matrix4x4 left, Matrix4x4 right) => left * right; } diff --git a/Engine.Core/Primitives/Vector2D.cs b/Engine.Core/Primitives/Vector2D.cs index 6009875..fbe4291 100644 --- a/Engine.Core/Primitives/Vector2D.cs +++ b/Engine.Core/Primitives/Vector2D.cs @@ -82,11 +82,19 @@ public readonly struct Vector2D(float x, float y) : IEquatable 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(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.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); /// /// Calculates the length of the . diff --git a/Engine.Core/Primitives/Vector2DInt.cs b/Engine.Core/Primitives/Vector2DInt.cs index 31b9f37..ba9a58e 100644 --- a/Engine.Core/Primitives/Vector2DInt.cs +++ b/Engine.Core/Primitives/Vector2DInt.cs @@ -74,9 +74,12 @@ public readonly struct Vector2DInt(int x, int y) : IEquatable 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(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()); + /// /// Calculates the length of the . /// diff --git a/Engine.Core/Primitives/Vector3D.cs b/Engine.Core/Primitives/Vector3D.cs index 3551f35..8b0598b 100644 --- a/Engine.Core/Primitives/Vector3D.cs +++ b/Engine.Core/Primitives/Vector3D.cs @@ -92,11 +92,19 @@ public readonly struct Vector3D(float x, float y, float z) : IEquatable 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(System.Numerics.Vector3 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(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.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); /// /// Calculates the length of the . diff --git a/Engine.Core/Primitives/Vector3DInt.cs b/Engine.Core/Primitives/Vector3DInt.cs index 817351f..e35c195 100644 --- a/Engine.Core/Primitives/Vector3DInt.cs +++ b/Engine.Core/Primitives/Vector3DInt.cs @@ -84,9 +84,12 @@ public readonly struct Vector3DInt(int x, int y, int z) : IEquatable 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(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()); + /// /// Calculates the length of the . /// diff --git a/Engine.Core/Primitives/Vector4D.cs b/Engine.Core/Primitives/Vector4D.cs index 4e38feb..b613933 100644 --- a/Engine.Core/Primitives/Vector4D.cs +++ b/Engine.Core/Primitives/Vector4D.cs @@ -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 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 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(Vector2D vector) => new(vector.X, vector.Y, 0f, 0f); + public static implicit operator Vector4D(Vector3D vector) => new(vector.X, vector.Y, vector.Z, 0f); + /// /// Calculates the length of the . /// diff --git a/Engine.Core/Serialization/ISerializer.cs b/Engine.Core/Serialization/ISerializer.cs index 0ada93c..783f2f7 100644 --- a/Engine.Core/Serialization/ISerializer.cs +++ b/Engine.Core/Serialization/ISerializer.cs @@ -4,15 +4,15 @@ namespace Engine.Core.Serialization; public interface ISerializer { - object Deserialize(string configuration); - object Deserialize(string configuration, Type type); - T Deserialize(string configuration); + object Deserialize(string content); + object Deserialize(string content, Type type); + T Deserialize(string content); string Serialize(object instance); - ProgressiveTask DeserializeAsync(string configuration); - ProgressiveTask DeserializeAsync(string configuration, Type type); - ProgressiveTask DeserializeAsync(string configuration); + ProgressiveTask DeserializeAsync(string content); + ProgressiveTask DeserializeAsync(string content, Type type); + ProgressiveTask DeserializeAsync(string content); ProgressiveTask SerializeAsync(object instance); } diff --git a/Engine.Core/Systems/UniverseEntranceManager.cs b/Engine.Core/Systems/UniverseEntranceManager.cs index 85f579e..db40ce7 100644 --- a/Engine.Core/Systems/UniverseEntranceManager.cs +++ b/Engine.Core/Systems/UniverseEntranceManager.cs @@ -9,65 +9,56 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent private static System.Func GetPriority() => (b) => b.Priority; private readonly ActiveBehaviourCollectorOrdered enterUniverses = new(GetPriority(), SortByAscendingPriority()); + private readonly ActiveBehaviourCollectorOrdered exitUniverses = new(GetPriority(), SortByAscendingPriority()); - private readonly List toCallEnterUniverses = new(32); - private readonly List toCallExitUniverses = new(32); + private bool isInitialCollectionDone = false; + private readonly FastListOrdered toCallEnterUniverses = new(GetPriority(), SortByAscendingPriority()); protected override void OnEnteredUniverse(IUniverse universe) { - // FIXME: This causes an issue when the UniverseEntranceManager is already attached to a UniverseObject then registered into a Universe, - // the enter/exit universe collectors call OnUniverseObjectRegistered internally on Assign, but since the Universe calls the OnUniverseObjectRegistered - // event it tries to call OnUniverseObjectRegistered again on the same object, causing a duplicate entry error. - Debug.Assert.AssertTrue(BehaviourController.Count == 1, $"{nameof(UniverseEntranceManager)} must be in it's own {nameof(IUniverseObject)} with no other {nameof(IBehaviour)}s attached at the moment. Failing to do so might cause instantiation or serialization issues."); + exitUniverses.Assign(universe); + + // FIXME: the isInitialCollectionDone is for the sole reason of some behaviours + // adding more behaviours during entrance calls and the internal workings of + // behaviour collector not being able to tell which behaviour was already called + // (because it just runs a for loop with the behaviour count, and priority ordering doesn't help as well) + // so it sometimes double processes or misses behaviours. A more elegant way of + // handling this would be nice but for now it works good enough. + // + // SIDE NOTE: This same issue has the potential to occur on exitUniverses as well, but I've yet to run + // into an instance of it actually happening so... I'm not gonna touch it until the edge case happens. + isInitialCollectionDone = false; enterUniverses.Assign(universe); + isInitialCollectionDone = true; - foreach (IUniverseObject universeObject in universe) - OnUniverseObjectRegistered(universe, new(universeObject)); - - universe.OnUniverseObjectRegistered.AddListener(OnUniverseObjectRegistered); - universe.OnUniverseObjectUnRegistered.AddListener(OnUniverseObjectUnRegistered); + for (int i = toCallEnterUniverses.Count - 1; i >= 0; i--) + toCallEnterUniverses[i].EnterUniverse(universe); + toCallEnterUniverses.Clear(); } protected override void OnExitedUniverse(IUniverse universe) { enterUniverses.Unassign(); - - foreach (IUniverseObject universeObject in universe) - OnUniverseObjectUnRegistered(universe, new(universeObject)); - - universe.OnUniverseObjectRegistered.RemoveListener(OnUniverseObjectRegistered); - universe.OnUniverseObjectUnRegistered.RemoveListener(OnUniverseObjectUnRegistered); - } - - private void OnUniverseObjectUnRegistered(IUniverse sender, IUniverse.UniverseObjectUnRegisteredArguments args) - { - args.UniverseObjectUnregistered.BehaviourController.GetBehavioursInChildren(toCallExitUniverses); - - for (int i = toCallExitUniverses.Count - 1; i >= 0; i--) - { - IExitUniverse exitUniverse = toCallExitUniverses[i]; - toCallExitUniverses.RemoveAt(i); - exitUniverse.ExitUniverse(Universe); - } - } - - private void OnUniverseObjectRegistered(IUniverse sender, IUniverse.UniverseObjectRegisteredArguments args) - { - for (int i = toCallEnterUniverses.Count - 1; i >= 0; i--) - { - IEnterUniverse enterUniverse = toCallEnterUniverses[i]; - toCallEnterUniverses.RemoveAt(i); - enterUniverse.EnterUniverse(Universe); - } + exitUniverses.Unassign(); } private void OnEnterUniverseCollected(IBehaviourCollector sender, IBehaviourCollector.BehaviourCollectedArguments args) { - toCallEnterUniverses.Add(args.BehaviourCollected); + if (!isInitialCollectionDone) + { + toCallEnterUniverses.Add(args.BehaviourCollected); + return; + } + + args.BehaviourCollected.EnterUniverse(Universe); } + private void OnExitUniverseRemoved(IBehaviourCollector sender, IBehaviourCollector.BehaviourRemovedArguments args) + => args.BehaviourRemoved.ExitUniverse(Universe); + public UniverseEntranceManager() { enterUniverses.OnCollected.AddListener(OnEnterUniverseCollected); + exitUniverses.OnRemoved.AddListener(OnExitUniverseRemoved); } } diff --git a/Engine.Core/Systems/UpdateManager.cs b/Engine.Core/Systems/UpdateManager.cs index 6af1be4..f07ee7b 100644 --- a/Engine.Core/Systems/UpdateManager.cs +++ b/Engine.Core/Systems/UpdateManager.cs @@ -44,6 +44,17 @@ public class UpdateManager : Behaviour, IEnterUniverse, IExitUniverse universe.OnPostUpdate.RemoveListener(OnPostUpdate); } + /// + /// Call the early if it's in queue to be called by this the . + /// It will not be called in the next natural cycle. + /// + /// The instance that will be called now rather than later. + public void CallFirstActiveFrameImmediately(IFirstFrameUpdate instance) + { + if (toCallFirstFrameUpdates.Remove(instance)) + instance.FirstActiveFrame(); + } + private void OnFirstUpdate(IUniverse sender, IUniverse.UpdateArguments args) { for (int i = toCallFirstFrameUpdates.Count - 1; i >= 0; i--) diff --git a/Engine.Core/Systems/Yields/WaitForTaskYield.cs b/Engine.Core/Systems/Yields/WaitForTaskYield.cs new file mode 100644 index 0000000..bc39cba --- /dev/null +++ b/Engine.Core/Systems/Yields/WaitForTaskYield.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; + +using static Engine.Core.WaitForTaskYield; + +namespace Engine.Core; + +public class WaitForTaskYield(Task task, TaskCompletionStatus completionStatus = TaskCompletionStatus.Either) : ICoroutineYield +{ + public bool Yield() + { + switch (completionStatus) + { + case TaskCompletionStatus.Successful: + if (task.IsCanceled) + throw new("Task has been canceled."); + if (task.IsFaulted) + throw new("Task has faulted."); + return task.IsCompletedSuccessfully; + + case TaskCompletionStatus.Failed: + if (task.IsCompletedSuccessfully) + throw new("Task was completed successfully."); + return task.IsFaulted; + } + + return task.IsCompleted; + } + + public enum TaskCompletionStatus + { + Either, + Successful, + Failed + } +} diff --git a/Engine.Core/Systems/Yields/WaitForTimeYield.cs b/Engine.Core/Systems/Yields/WaitForTimeYield.cs new file mode 100644 index 0000000..6d213ef --- /dev/null +++ b/Engine.Core/Systems/Yields/WaitForTimeYield.cs @@ -0,0 +1,14 @@ +using System; + +namespace Engine.Core; + +public class WaitForTimeYield(float seconds = 0f, float milliseconds = 0f, float minutes = 0f, float hours = 0f) : ICoroutineYield +{ + private readonly DateTime triggerTime = DateTime.UtcNow + .AddHours(hours) + .AddMinutes(minutes) + .AddSeconds(seconds) + .AddMilliseconds(milliseconds); + + public bool Yield() => DateTime.UtcNow < triggerTime; +} diff --git a/Engine.Core/Systems/Yields/WaitUntilYield.cs b/Engine.Core/Systems/Yields/WaitUntilYield.cs new file mode 100644 index 0000000..18d29f9 --- /dev/null +++ b/Engine.Core/Systems/Yields/WaitUntilYield.cs @@ -0,0 +1,10 @@ +using System; + +namespace Engine.Core; + +public class WaitUntilYield(Func condition) : ICoroutineYield +{ + private readonly Func condition = condition; + + public bool Yield() => !condition.Invoke(); +} diff --git a/Engine.Core/Systems/CoroutineYield.cs b/Engine.Core/Systems/Yields/WaitWhileYield.cs similarity index 70% rename from Engine.Core/Systems/CoroutineYield.cs rename to Engine.Core/Systems/Yields/WaitWhileYield.cs index 44e3f5b..0c1bb4f 100644 --- a/Engine.Core/Systems/CoroutineYield.cs +++ b/Engine.Core/Systems/Yields/WaitWhileYield.cs @@ -2,7 +2,7 @@ using System; namespace Engine.Core; -public class CoroutineYield(Func condition) : ICoroutineYield +public class WaitWhileYield(Func condition) : ICoroutineYield { private readonly Func condition = condition; diff --git a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs index daa7a0d..78eb3b3 100644 --- a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs +++ b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs @@ -178,6 +178,7 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IE netPacketProcessor.RegisterNestedType(Vector3DNetPacker.Write, Vector3DNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector3DIntNetPacker.Write, Vector3DIntNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector4DNetPacker.Write, Vector4DNetPacker.Read); + netPacketProcessor.RegisterNestedType(Matrix4x4NetPacker.Write, Matrix4x4NetPacker.Read); } public INetworkCommunicator SubscribeToPackets(Event.EventHandler callback) diff --git a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibServer.cs b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibServer.cs index 9bcee1a..3b0f9cc 100644 --- a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibServer.cs +++ b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibServer.cs @@ -6,7 +6,7 @@ using Engine.Core.Debug; namespace Engine.Systems.Network; -public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicatorServer +public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicatorServer, IPostUpdate { public string Password { get; private set; } = string.Empty; 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 override void EnterUniverse(IUniverse universe) + public void PostUpdate() { - base.EnterUniverse(universe); - universe.OnPostUpdate.AddListener(PollEvents); - } - - public override void ExitUniverse(IUniverse universe) - { - base.ExitUniverse(universe); - universe.OnPostUpdate.RemoveListener(PollEvents); + Manager.PollEvents(); } } diff --git a/Engine.Integration/Engine.Integration.LiteNetLib/Packers/Matrix4x4NetPacker.cs b/Engine.Integration/Engine.Integration.LiteNetLib/Packers/Matrix4x4NetPacker.cs new file mode 100644 index 0000000..a72536b --- /dev/null +++ b/Engine.Integration/Engine.Integration.LiteNetLib/Packers/Matrix4x4NetPacker.cs @@ -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 + ); + } +} diff --git a/Engine.Integration/Engine.Integration.MonoGame/Abstract/ITriangleBatch.cs b/Engine.Integration/Engine.Integration.MonoGame/Abstract/ITriangleBatch.cs deleted file mode 100644 index 1de8f80..0000000 --- a/Engine.Integration/Engine.Integration.MonoGame/Abstract/ITriangleBatch.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Xna.Framework; - -using Engine.Core; - -namespace Engine.Integration.MonoGame; - -public interface ITriangleBatch -{ - void Begin(Matrix? view = null, Matrix? projection = null); - void Draw(Triangle triangle, ColorRGBA colorRGBA); - void End(); -} diff --git a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera2D.cs b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera2D.cs index 6811eb9..aa15833 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera2D.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera2D.cs @@ -1,5 +1,4 @@ using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using Engine.Core; @@ -7,33 +6,41 @@ namespace Engine.Integration.MonoGame; public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw { - public Event OnMatrixTransformChanged { get; } = new(); + public Event OnViewMatrixChanged { get; } = new(); + public Event OnProjectionMatrixChanged { get; } = new(); public Event OnViewportChanged { get; } = new(); public Event OnZoomChanged { get; } = new(); public GraphicsDeviceManager Graphics { get; private set; } = null!; public ITransform2D Transform { get; private set; } = null!; - public Matrix MatrixTransform + public Matrix4x4 ProjectionMatrix { get; - set + private set { if (field == value) return; field = value; - OnMatrixTransformChanged.Invoke(this); + OnProjectionMatrixChanged.Invoke(this); } - } = Matrix.Identity; + } = Matrix4x4.Identity; - public Vector2D Position + public Matrix4x4 ViewMatrix { - get => Transform.Position; - set => Transform.Position = value; - } + get; + private set + { + if (field == value) + return; - public Viewport Viewport + field = value; + OnViewMatrixChanged.Invoke(this); + } + } = Matrix4x4.Identity; + + public Vector2D Viewport { get; set @@ -61,39 +68,54 @@ public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFr } } = 1f; - public float Rotation - { - get => Transform.Rotation; - set => Transform.Rotation = value; - } - // TODO This causes delay since OnPreDraw calls assuming this is called in in Update public Vector2D ScreenToWorldPosition(Vector2D screenPosition) { - Vector2D worldPosition = Vector2.Transform(screenPosition.ToVector2(), Matrix.Invert(MatrixTransform)).ToVector2D(); - return worldPosition.Scale(EngineConverterExtensions.screenScale); + float x = 2f * screenPosition.X / Viewport.X - 1f; + float y = 1f - 2f * screenPosition.Y / Viewport.Y; + 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) { - Vector2D screenPosition = Vector2.Transform(worldPosition.ToVector2(), MatrixTransform).ToVector2D(); - return screenPosition.Scale(EngineConverterExtensions.screenScale); + Vector4D worldPosition4D = new(worldPosition.X, worldPosition.Y, 0f, 1f); + + Matrix4x4 viewProjection = ProjectionMatrix * ViewMatrix; + Vector4D clip = viewProjection * worldPosition4D; + + if (clip.W != 0f) + clip /= clip.W; + + float screenX = (clip.X + 1f) * .5f * Viewport.X; + float screenY = (1f - clip.Y) * .5f * Viewport.Y; + + return new(screenX, screenY); } public void LastActiveFrame() => Transform = null!; public void FirstActiveFrame() { Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour().Window.Graphics; - Viewport = Graphics.GraphicsDevice.Viewport; + Viewport = new(Graphics.GraphicsDevice.Viewport.Width, Graphics.GraphicsDevice.Viewport.Height); Transform = BehaviourController.GetRequiredBehaviour(); } public void PreDraw() { - MatrixTransform = - Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) * - Matrix.CreateRotationZ(Rotation * Math.DegreeToRadian) * - Matrix.CreateScale(Transform.Scale.X.Max(Transform.Scale.Y)) * - Matrix.CreateScale(Zoom) * - Matrix.CreateTranslation(new Vector3(Viewport.Width * .5f, Viewport.Height * .5f, 0f)); + ProjectionMatrix = Matrix4x4.CreateOrthographicViewCentered(Viewport.X, Viewport.Y); + ViewMatrix = Matrix4x4.Identity + .ApplyTranslation(new Vector3D(-Transform.Position.X, -Transform.Position.Y, 0f)) + .ApplyRotationZ(Transform.Rotation * Math.DegreeToRadian) + .ApplyScale(Transform.Scale.X.Max(Transform.Scale.Y)) + .ApplyScale(Zoom); } } diff --git a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs index 5516551..b5b98e1 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs @@ -21,7 +21,7 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr public GraphicsDeviceManager Graphics { get; private set; } = null!; public ITransform3D Transform { get; private set; } = null!; - public Matrix View + public Matrix4x4 ViewMatrix { get; set @@ -29,13 +29,13 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr if (field == value) return; - Matrix previousView = field; + Matrix4x4 previousView = field; field = value; OnViewChanged.Invoke(this, new(previousView)); } - } = Matrix.Identity; + } = Matrix4x4.Identity; - public Matrix Projection + public Matrix4x4 ProjectionMatrix { get; set @@ -43,13 +43,13 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr if (field == value) return; - Matrix previousProjection = field; + Matrix4x4 previousProjection = field; field = value; OnProjectionChanged.Invoke(this, new(previousProjection)); } - } = Matrix.Identity; + } = Matrix4x4.Identity; - public Viewport Viewport + public Vector2D Viewport { get; set @@ -57,7 +57,7 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr if (field.Equals(value)) return; - Viewport previousViewport = field; + Vector2D previousViewport = field; field = value; SetForRecalculation(); OnViewportChanged.Invoke(this, new(previousViewport)); @@ -111,21 +111,24 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr Vector3 nearPoint = new(screenPosition.X, screenPosition.Y, 0f); Vector3 farPoint = new(screenPosition.X, screenPosition.Y, 1f); - Vector3 worldNear = Viewport.Unproject(nearPoint, Projection, View, Matrix.Identity); - Vector3 worldFar = Viewport.Unproject(farPoint, Projection, View, Matrix.Identity); + Matrix projection = ProjectionMatrix.ToXnaMatrix(); + Matrix view = ViewMatrix.ToXnaMatrix(); + + Vector3 worldNear = Graphics.GraphicsDevice.Viewport.Unproject(nearPoint, projection, view, Matrix.Identity); + Vector3 worldFar = Graphics.GraphicsDevice.Viewport.Unproject(farPoint, projection, view, Matrix.Identity); Vector3 direction = Vector3.Normalize(worldFar - worldNear); return new(worldNear.ToVector3D(), direction.ToVector3D()); } - public Vector2D WorldToScreenPosition(Vector3D worldPosition) => Viewport.Project(worldPosition.ToVector3(), Projection, View, Matrix.Identity).ToVector3D(); + public Vector2D WorldToScreenPosition(Vector3D worldPosition) => Graphics.GraphicsDevice.Viewport.Project(worldPosition.ToVector3(), ProjectionMatrix.ToXnaMatrix(), ViewMatrix.ToXnaMatrix(), Matrix.Identity).ToVector3D(); public void LastActiveFrame() => Transform.OnTransformUpdated.RemoveListener(SetDirtyOnTransformUpdate); public void FirstActiveFrame() { Transform = BehaviourController.GetRequiredBehaviour(); Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour().Window.Graphics; - Viewport = Graphics.GraphicsDevice.Viewport; + Viewport = new(Graphics.GraphicsDevice.Viewport.Width, Graphics.GraphicsDevice.Viewport.Height); Transform.OnTransformUpdated.AddListener(SetDirtyOnTransformUpdate); } @@ -150,7 +153,7 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr Vector3 right = Vector3.Normalize(Transform.Right.ToVector3()); Vector3 position = Transform.Position.ToVector3(); - View = new Matrix( + ViewMatrix = new Matrix4x4( right.X, up.X, forward.X, 0, right.Y, up.Y, forward.Y, 0, right.Z, up.Z, forward.Z, 0, @@ -164,9 +167,9 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr private void CalculateProjection() { float yScale = 1f / (float)Math.Tan(fieldOfView / 2f); - float xScale = yScale / Viewport.AspectRatio; + float xScale = yScale / (Viewport.X / Viewport.Y); - Projection = new Matrix( + ProjectionMatrix = new Matrix4x4( xScale, 0, 0, 0, 0, yScale, 0, 0, 0, 0, FarPlane / (FarPlane - NearPlane), 1, @@ -174,7 +177,7 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr ); } - public readonly record struct ViewChangedArguments(Matrix PreviousView); - public readonly record struct ProjectionChangedArguments(Matrix PreviousProjection); - public readonly record struct ViewportChangedArguments(Viewport PreviousViewport); + public readonly record struct ViewChangedArguments(Matrix4x4 PreviousView); + public readonly record struct ProjectionChangedArguments(Matrix4x4 PreviousProjection); + public readonly record struct ViewportChangedArguments(Vector2D PreviousViewport); } diff --git a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/SpriteBatchWrapper.cs b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/SpriteBatchWrapper.cs index 8940127..57610d9 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/SpriteBatchWrapper.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/SpriteBatchWrapper.cs @@ -15,49 +15,49 @@ public class SpriteBatchWrapper(GraphicsDevice graphicsDevice) : ISpriteBatch => 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) - => 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) - => 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) - => 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) - => 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) => spriteBatch.Draw(texture, destinationAABB.ToRectangle(), sourceAABB?.ToRectangle(), 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) => spriteBatch.Draw(texture, destinationAABB.ToRectangle(), 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) - => 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) - => 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) - => 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) - => 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) - => 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) - => 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) - => 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() => spriteBatch.End(); diff --git a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/SpriteBatcher.cs b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/SpriteBatcher.cs index b9ca10e..c34d84f 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/SpriteBatcher.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/SpriteBatcher.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using Microsoft.Xna.Framework.Graphics; + using Engine.Core; namespace Engine.Integration.MonoGame; @@ -12,6 +14,7 @@ public class SpriteBatcher : Behaviour, IFirstFrameUpdate, IDraw private ISpriteBatch spriteBatch = null!; private MonoGameCamera2D camera2D = null!; + private readonly RasterizerState rasterizerState = new() { CullMode = CullMode.CullClockwiseFace }; private readonly ActiveBehaviourCollectorOrdered drawableSprites = new(GetPriority(), SortByPriority()); public void FirstActiveFrame() @@ -26,7 +29,13 @@ public class SpriteBatcher : Behaviour, IFirstFrameUpdate, IDraw public void Draw() { - spriteBatch.Begin(transformMatrix: camera2D.MatrixTransform); + Matrix4x4 transformMatrix = Matrix4x4.Identity + .ApplyTranslation(new Vector2D(camera2D.Viewport.X, camera2D.Viewport.Y) * .5f) + .ApplyScale(new Vector3D(1f, -1f, 1f)) + .ApplyMatrix(camera2D.ViewMatrix) + .Transposed; + + spriteBatch.Begin(transformMatrix: transformMatrix.ToXnaMatrix(), rasterizerState: rasterizerState); for (int i = 0; i < drawableSprites.Count; i++) drawableSprites[i].Draw(spriteBatch); spriteBatch.End(); diff --git a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/TriangleBatcher.cs b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/TriangleBatcher.cs deleted file mode 100644 index b8bf5d0..0000000 --- a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/TriangleBatcher.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; - -using Microsoft.Xna.Framework; - -using Engine.Core; - -namespace Engine.Integration.MonoGame; - -public class TriangleBatcher : Behaviour, ITriangleBatch, IFirstFrameUpdate, IDraw -{ - private static Comparer SortByAscendingPriority() => Comparer.Create((x, y) => x.CompareTo(y)); - private static System.Func GetPriority() => (b) => b.Priority; - - private TriangleBatch triangleBatch = null!; - private MonoGameCamera2D camera2D = null!; - - private readonly ActiveBehaviourCollectorOrdered drawableShapes = new(GetPriority(), SortByAscendingPriority()); - - public void FirstActiveFrame() - { - MonoGameWindowContainer windowContainer = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour(); - camera2D = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour(); - - triangleBatch = new(windowContainer.Window.GraphicsDevice); - drawableShapes.Unassign(); - drawableShapes.Assign(BehaviourController.UniverseObject.Universe); - } - - public void Draw() - { - triangleBatch.Begin(camera2D.MatrixTransform); - for (int i = 0; i < drawableShapes.Count; i++) - drawableShapes[i].Draw(triangleBatch); - triangleBatch.End(); - } - - public void Begin(Matrix? view = null, Matrix? projection = null) => triangleBatch.Begin(view, projection); - public void Draw(Triangle triangle, ColorRGBA colorRGBA) => triangleBatch.Draw(triangle, colorRGBA); - public void End() => triangleBatch.End(); -} diff --git a/Engine.Integration/Engine.Integration.MonoGame/EngineConverter.cs b/Engine.Integration/Engine.Integration.MonoGame/EngineConverterExtensions.cs similarity index 86% rename from Engine.Integration/Engine.Integration.MonoGame/EngineConverter.cs rename to Engine.Integration/Engine.Integration.MonoGame/EngineConverterExtensions.cs index 354b196..42d5d49 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/EngineConverter.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/EngineConverterExtensions.cs @@ -9,8 +9,6 @@ namespace Engine.Integration.MonoGame; public static class EngineConverterExtensions { - public readonly static Vector2D screenScale = Vector2D.Down + Vector2D.Right; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UniverseTime ToEngineTime(this GameTime gameTime) => new(gameTime.TotalGameTime, gameTime.ElapsedGameTime); @@ -49,15 +47,17 @@ public static class EngineConverterExtensions m.M41, m.M42, m.M43, m.M44 ); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Matrix4x4 FromXnaMatrix(this Matrix m) => new( + m.M11, m.M12, m.M13, m.M14, + m.M21, m.M22, m.M23, m.M24, + m.M31, m.M32, m.M33, m.M34, + m.M41, m.M42, m.M43, m.M44 + ); + [MethodImpl(MethodImplOptions.AggressiveInlining)] 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)] public static Rectangle ToDisplayRectangle(this Rectangle rectangle, DisplayMode displayMode) => new() { @@ -70,8 +70,8 @@ public static class EngineConverterExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rectangle ToRectangle(this AABB2D aabb) => new() { - X = (int)(aabb.LowerBoundary.X * screenScale.X), - Y = (int)(aabb.LowerBoundary.Y * screenScale.Y), + X = (int)aabb.LowerBoundary.X, + Y = (int)aabb.LowerBoundary.Y, Width = (int)(aabb.UpperBoundary.X - aabb.LowerBoundary.X), Height = (int)(aabb.UpperBoundary.Y - aabb.LowerBoundary.Y) }; diff --git a/Engine.Integration/Engine.Integration.MonoGame/TriangleBatch.cs b/Engine.Integration/Engine.Integration.MonoGame/MonoGameTriangleBatch.cs similarity index 59% rename from Engine.Integration/Engine.Integration.MonoGame/TriangleBatch.cs rename to Engine.Integration/Engine.Integration.MonoGame/MonoGameTriangleBatch.cs index 499a6ed..73cf212 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/TriangleBatch.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/MonoGameTriangleBatch.cs @@ -2,24 +2,29 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Engine.Core; +using Engine.Systems.Graphics; namespace Engine.Integration.MonoGame; -public class TriangleBatch : ITriangleBatch +public class MonoGameTriangleBatch : Behaviour, ITriangleBatch, IFirstFrameUpdate { - private readonly GraphicsDevice graphicsDevice; - private readonly VertexBuffer vertexBuffer = default!; + private GraphicsDevice graphicsDevice = null!; + private VertexBuffer vertexBuffer = default!; private readonly VertexPositionColor[] vertices = new VertexPositionColor[1024]; private int verticesIndex = 0; private Matrix view = Matrix.Identity; private Matrix projection = Matrix.Identity; - private readonly BasicEffect basicEffect = null!; + private BasicEffect basicEffect = null!; private readonly RasterizerState rasterizerState = new() { CullMode = CullMode.None }; - public TriangleBatch(GraphicsDevice graphicsDevice) + private ICamera camera = null!; + + public void FirstActiveFrame() { + camera = Universe.FindRequiredBehaviour(); + GraphicsDevice graphicsDevice = Universe.FindRequiredBehaviour().Window.GraphicsDevice; this.graphicsDevice = graphicsDevice; basicEffect = new(graphicsDevice); basicEffect.VertexColorEnabled = true; @@ -31,30 +36,20 @@ public class TriangleBatch : ITriangleBatch if (verticesIndex + 3 >= vertices.Length) Flush(); - Vector2 A = triangle.A.ToDisplayVector2(); - Vector2 B = triangle.B.ToDisplayVector2(); - Vector2 C = triangle.C.ToDisplayVector2(); - Color color = colorRGBA.ToColor(); + Vector2 A = triangle.A.ToVector2(); + Vector2 B = triangle.B.ToVector2(); + Vector2 C = triangle.C.ToVector2(); + Color color = colorRGBA.ToPreMultipliedColor(); vertices[verticesIndex++] = new(new(A.X, A.Y, 0f), color); vertices[verticesIndex++] = new(new(B.X, B.Y, 0f), color); vertices[verticesIndex++] = new(new(C.X, C.Y, 0f), color); } - public void Begin(Matrix? view = null, Matrix? projection = null) + public void Begin(Matrix4x4? view = null, Matrix4x4? projection = null) { - if (view != null) - this.view = view.Value; - else - this.view = Matrix.Identity; - - if (projection != null) - this.projection = projection.Value; - else - { - Viewport viewport = graphicsDevice.Viewport; - this.projection = Matrix.CreateOrthographicOffCenter(viewport.X, viewport.Width, viewport.Height, viewport.Y, 0, 1); - } + this.view = (view ?? camera.ViewMatrix).Transposed.ToXnaMatrix(); + this.projection = (projection ?? camera.ProjectionMatrix).Transposed.ToXnaMatrix(); } public void End() => Flush(); @@ -65,6 +60,8 @@ public class TriangleBatch : ITriangleBatch return; graphicsDevice.RasterizerState = rasterizerState; + graphicsDevice.BlendState = BlendState.AlphaBlend; + basicEffect.Projection = projection; basicEffect.View = view; vertexBuffer.SetData(vertices); @@ -72,8 +69,10 @@ public class TriangleBatch : ITriangleBatch graphicsDevice.SetVertexBuffer(vertexBuffer); foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes) + { pass.Apply(); - graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, verticesIndex / 3); + graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, verticesIndex / 3); + } verticesIndex = 0; } diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourControllerConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourControllerConverter.cs index 530ae2c..a0f6941 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourControllerConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourControllerConverter.cs @@ -11,7 +11,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class BehaviourControllerConverter : EngineTypeYamlSerializerBase +public class BehaviourControllerConverter : EngineTypeYamlConverterBase { private const string BEHAVIOURS_SCALAR_NAME = "Behaviours"; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourConverter.cs index a475f96..99bbff4 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourConverter.cs @@ -9,7 +9,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class BehaviourConverter : EngineTypeYamlSerializerBase +public class BehaviourConverter : EngineTypeYamlConverterBase { public override IBehaviour? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/EngineTypeYamlConverterBase.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/EngineTypeYamlConverterBase.cs index 99167ee..e074ccb 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/EngineTypeYamlConverterBase.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/EngineTypeYamlConverterBase.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public abstract class EngineTypeYamlSerializerBase : IEngineTypeYamlConverter +public abstract class EngineTypeYamlConverterBase : IEngineTypeYamlConverter { protected const string SERIALIZED_SCALAR_NAME = "Properties"; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB2DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB2DConverter.cs index 1c2a967..3f52477 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB2DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB2DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class AABB2DConverter : EngineTypeYamlSerializerBase +public class AABB2DConverter : EngineTypeYamlConverterBase { public override AABB2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB3DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB3DConverter.cs index 83558e8..a50f8b8 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB3DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB3DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class AABB3DConverter : EngineTypeYamlSerializerBase +public class AABB3DConverter : EngineTypeYamlConverterBase { public override AABB3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/CircleConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/CircleConverter.cs index d06bc53..494c960 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/CircleConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/CircleConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class CircleConverter : EngineTypeYamlSerializerBase +public class CircleConverter : EngineTypeYamlConverterBase { public override Circle Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorHSVAConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorHSVAConverter.cs index 42aff81..df3bcba 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorHSVAConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorHSVAConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class ColorHSVAConverter : EngineTypeYamlSerializerBase +public class ColorHSVAConverter : EngineTypeYamlConverterBase { private static readonly int SUBSTRING_START_LENGTH = nameof(ColorHSVA).Length + 1; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorHSVConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorHSVConverter.cs index a03f490..ec81ae5 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorHSVConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorHSVConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class ColorHSVConverter : EngineTypeYamlSerializerBase +public class ColorHSVConverter : EngineTypeYamlConverterBase { private static readonly int SUBSTRING_START_LENGTH = nameof(ColorHSV).Length + 1; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorRGBAConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorRGBAConverter.cs index 50ec7aa..d1e3a73 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorRGBAConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorRGBAConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class ColorRGBAConverter : EngineTypeYamlSerializerBase +public class ColorRGBAConverter : EngineTypeYamlConverterBase { private static readonly int SUBSTRING_START_LENGTH = nameof(ColorRGBA).Length + 1; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorRGBConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorRGBConverter.cs index 8133885..f769547 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorRGBConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/ColorRGBConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class ColorRGBConverter : EngineTypeYamlSerializerBase +public class ColorRGBConverter : EngineTypeYamlConverterBase { private static readonly int SUBSTRING_START_LENGTH = nameof(ColorRGB).Length + 1; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line2DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line2DConverter.cs index 1d45331..8901d1b 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line2DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line2DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Line2DConverter : EngineTypeYamlSerializerBase +public class Line2DConverter : EngineTypeYamlConverterBase { public override Line2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line2DEquationConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line2DEquationConverter.cs index ed13cc4..113f6dc 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line2DEquationConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line2DEquationConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Line2DEquationConverter : EngineTypeYamlSerializerBase +public class Line2DEquationConverter : EngineTypeYamlConverterBase { public override Line2DEquation Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line3DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line3DConverter.cs index a49fd16..d11f050 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line3DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Line3DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Line3DConverter : EngineTypeYamlSerializerBase +public class Line3DConverter : EngineTypeYamlConverterBase { public override Line3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Matrix4x4Converter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Matrix4x4Converter.cs new file mode 100644 index 0000000..0071d67 --- /dev/null +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Matrix4x4Converter.cs @@ -0,0 +1,33 @@ +using System; + +using Engine.Core; + +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Engine.Serializers.Yaml; + +public class Matrix4x4Converter : EngineTypeYamlConverterBase +{ + private static readonly int SUBSTRING_START_LENGTH = nameof(Matrix4x4).Length + 1; + + public override Matrix4x4 Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + string value = parser.Consume().Value; + string insideParenthesis = value[SUBSTRING_START_LENGTH..^1]; + string[] values = insideParenthesis.Split(", "); + return new Matrix4x4( + float.Parse(values[0]), float.Parse(values[1]), float.Parse(values[2]), float.Parse(values[3]), + float.Parse(values[4]), float.Parse(values[5]), float.Parse(values[6]), float.Parse(values[7]), + float.Parse(values[8]), float.Parse(values[9]), float.Parse(values[10]), float.Parse(values[11]), + float.Parse(values[12]), float.Parse(values[13]), float.Parse(values[14]), float.Parse(values[15]) + ); + } + + public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + Matrix4x4 m = (Matrix4x4)value!; + emitter.Emit(new Scalar($"{nameof(Matrix4x4)}({m.M11}, {m.M12}, {m.M13}, {m.M14},{m.M21}, {m.M22}, {m.M23}, {m.M24},{m.M31}, {m.M32}, {m.M33}, {m.M34},{m.M41}, {m.M42}, {m.M43}, {m.M44})")); + } +} diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Projection1DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Projection1DConverter.cs index b1ea9fe..0ba7ef1 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Projection1DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Projection1DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Projection1DConverter : EngineTypeYamlSerializerBase +public class Projection1DConverter : EngineTypeYamlConverterBase { public override Projection1D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/QuaternionConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/QuaternionConverter.cs index 76317b3..a979536 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/QuaternionConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/QuaternionConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class QuaternionConverter : EngineTypeYamlSerializerBase +public class QuaternionConverter : EngineTypeYamlConverterBase { private static readonly int SUBSTRING_START_LENGTH = nameof(Quaternion).Length + 1; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Ray2DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Ray2DConverter.cs index 300b00d..f3c8c9b 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Ray2DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Ray2DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Ray2DConverter : EngineTypeYamlSerializerBase +public class Ray2DConverter : EngineTypeYamlConverterBase { public override Ray2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Ray3DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Ray3DConverter.cs index 0120690..f9295a5 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Ray3DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Ray3DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Ray3DConverter : EngineTypeYamlSerializerBase +public class Ray3DConverter : EngineTypeYamlConverterBase { public override Ray3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Shape2DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Shape2DConverter.cs index cd88d96..e3f6b60 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Shape2DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Shape2DConverter.cs @@ -9,7 +9,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Shape2DConverter : EngineTypeYamlSerializerBase +public class Shape2DConverter : EngineTypeYamlConverterBase { public override Shape2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Sphere3DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Sphere3DConverter.cs index 1fc0e71..531bb02 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Sphere3DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Sphere3DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Sphere3DConverter : EngineTypeYamlSerializerBase +public class Sphere3DConverter : EngineTypeYamlConverterBase { public override Sphere3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/TriangleConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/TriangleConverter.cs index c544b37..a999ca7 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/TriangleConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/TriangleConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class TriangleConverter : EngineTypeYamlSerializerBase +public class TriangleConverter : EngineTypeYamlConverterBase { public override Triangle Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector2DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector2DConverter.cs index 654416c..1e1c62b 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector2DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector2DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Vector2DConverter : EngineTypeYamlSerializerBase +public class Vector2DConverter : EngineTypeYamlConverterBase { private static readonly int SUBSTRING_START_LENGTH = nameof(Vector2D).Length + 1; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector2DIntConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector2DIntConverter.cs index 8b0eea3..ba70c11 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector2DIntConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector2DIntConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Vector2DIntConverter : EngineTypeYamlSerializerBase +public class Vector2DIntConverter : EngineTypeYamlConverterBase { private static readonly int SUBSTRING_START_LENGTH = nameof(Vector2DInt).Length + 1; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector3DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector3DConverter.cs index 4bdca07..9f0a17c 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector3DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector3DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Vector3DConverter : EngineTypeYamlSerializerBase +public class Vector3DConverter : EngineTypeYamlConverterBase { private static readonly int SUBSTRING_START_LENGTH = nameof(Vector3D).Length + 1; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector3DIntConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector3DIntConverter.cs index d1adf01..3efae7f 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector3DIntConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector3DIntConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Vector3DIntConverter : EngineTypeYamlSerializerBase +public class Vector3DIntConverter : EngineTypeYamlConverterBase { private static readonly int SUBSTRING_START_LENGTH = nameof(Vector3DInt).Length + 1; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector4DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector4DConverter.cs index 5249c10..8a4bf1d 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector4DConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Vector4DConverter.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class Vector4DConverter : EngineTypeYamlSerializerBase +public class Vector4DConverter : EngineTypeYamlConverterBase { private static readonly int SUBSTRING_START_LENGTH = nameof(Vector4D).Length + 1; diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/SerializedClassConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/SerializedClassConverter.cs index 525de45..5a970a2 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/SerializedClassConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/SerializedClassConverter.cs @@ -12,7 +12,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class SerializedClassConverter : EngineTypeYamlSerializerBase +public class SerializedClassConverter : EngineTypeYamlConverterBase { public override SerializedClass? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/StateEnableConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/StateEnableConverter.cs index d5c0bc0..3847ca7 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/StateEnableConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/StateEnableConverter.cs @@ -9,7 +9,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class StateEnableConverter : EngineTypeYamlSerializerBase +public class StateEnableConverter : EngineTypeYamlConverterBase { public override IStateEnable? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/TypeContainerConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/TypeContainerConverter.cs index 94f045c..eaf591c 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/TypeContainerConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/TypeContainerConverter.cs @@ -9,7 +9,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class TypeContainerConverter : EngineTypeYamlSerializerBase +public class TypeContainerConverter : EngineTypeYamlConverterBase { public override TypeContainer Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseConverter.cs index 4a21d0e..1a1a291 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseConverter.cs @@ -11,7 +11,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class UniverseConverter : EngineTypeYamlSerializerBase +public class UniverseConverter : EngineTypeYamlConverterBase { public override IUniverse? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseObjectConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseObjectConverter.cs index 47d8513..79bae1e 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseObjectConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseObjectConverter.cs @@ -11,7 +11,7 @@ using YamlDotNet.Serialization; namespace Engine.Serializers.Yaml; -public class UniverseObjectSerializer : EngineTypeYamlSerializerBase +public class UniverseObjectConverter : EngineTypeYamlConverterBase { public override IUniverseObject? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) { diff --git a/Engine.Integration/Engine.Integration.Yaml/YamlConfiguration.cs b/Engine.Integration/Engine.Integration.Yaml/YamlConfiguration.cs new file mode 100644 index 0000000..57eef32 --- /dev/null +++ b/Engine.Integration/Engine.Integration.Yaml/YamlConfiguration.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using Engine.Core.Config; + +namespace Engine.Serializers.Yaml; + +public class YamlConfiguration : BasicConfiguration +{ + public readonly string FilePath; + + private readonly YamlSerializer yamlSerializer = new(); + + public YamlConfiguration(string filePath) + { + if (!filePath.EndsWith(".yaml")) + filePath += ".yaml"; + + FilePath = filePath; + + bool isRelativePath = Path.GetFullPath(filePath).CompareTo(filePath) != 0; + + if (isRelativePath) + FilePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePath)); + + if (Path.GetDirectoryName(FilePath) is string directoryPath) + Directory.CreateDirectory(directoryPath); + + if (!File.Exists(FilePath)) + return; + + string yamlFileText = File.ReadAllText(FilePath); + Dictionary valuePairs = yamlSerializer.Deserialize>(yamlFileText); + + foreach ((string key, string value) in valuePairs) + Set(key, value); + } + + public void Save() + { + File.WriteAllText(FilePath, yamlSerializer.Serialize(Values)); + } +} diff --git a/Engine.Integration/Engine.Integration.Yaml/YamlSerializer.cs b/Engine.Integration/Engine.Integration.Yaml/YamlSerializer.cs index 6df1e67..3357040 100644 --- a/Engine.Integration/Engine.Integration.Yaml/YamlSerializer.cs +++ b/Engine.Integration/Engine.Integration.Yaml/YamlSerializer.cs @@ -62,65 +62,65 @@ public class YamlSerializer : Core.Serialization.ISerializer } } - public object Deserialize(string configuration) + public object Deserialize(string content) { lock (Lock) { identifiableRegistry.Reset(); - object result = deserializer.Deserialize(configuration)!; + object result = deserializer.Deserialize(content)!; identifiableRegistry.AssignAll(); return result; } } - public object Deserialize(string configuration, Type type) + public object Deserialize(string content, Type type) { lock (Lock) { identifiableRegistry.Reset(); - object result = deserializer.Deserialize(configuration, type)!; + object result = deserializer.Deserialize(content, type)!; identifiableRegistry.AssignAll(); return result; } } - public T Deserialize(string configuration) + public T Deserialize(string content) { lock (Lock) { identifiableRegistry.Reset(); - T result = deserializer.Deserialize(configuration); + T result = deserializer.Deserialize(content); identifiableRegistry.AssignAll(); return result; } } - public ProgressiveTask DeserializeAsync(string configuration) + public ProgressiveTask DeserializeAsync(string content) { lock (Lock) { progressionTracker.Reset(); - Task task = Task.Run(() => Deserialize(configuration)); + Task task = Task.Run(() => Deserialize(content)); return new ProgressiveTask(progressionTracker, task); } } - public ProgressiveTask DeserializeAsync(string configuration, Type type) + public ProgressiveTask DeserializeAsync(string content, Type type) { lock (Lock) { progressionTracker.Reset(); - Task task = Task.Run(() => Deserialize(configuration, type)); + Task task = Task.Run(() => Deserialize(content, type)); return new ProgressiveTask(progressionTracker, task); } } - public ProgressiveTask DeserializeAsync(string configuration) + public ProgressiveTask DeserializeAsync(string content) { lock (Lock) { progressionTracker.Reset(); - Task task = Task.Run(() => Deserialize(configuration)); + Task task = Task.Run(() => Deserialize(content)); return new ProgressiveTask(progressionTracker, task); } } @@ -135,8 +135,8 @@ public class YamlSerializer : Core.Serialization.ISerializer } } - internal object InternalDeserialize(string configuration, Type type) + internal object InternalDeserialize(string content, Type type) { - return deserializer.Deserialize(configuration, type)!; + return deserializer.Deserialize(content, type)!; } } diff --git a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/DrawableShape.cs b/Engine.Systems/Graphics/DrawableShape2D.cs similarity index 66% rename from Engine.Integration/Engine.Integration.MonoGame/Behaviours/DrawableShape.cs rename to Engine.Systems/Graphics/DrawableShape2D.cs index 0078088..2a6d179 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/DrawableShape.cs +++ b/Engine.Systems/Graphics/DrawableShape2D.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using Engine.Core; -namespace Engine.Integration.MonoGame; +namespace Engine.Systems.Graphics; -public class DrawableShape : Behaviour2D, IDrawableTriangle, IPreDraw +public class DrawableShape2D : Behaviour2D, IDrawableTriangle, IPreDraw { private readonly Shape2D shape = new([]); private readonly List worldTriangles = []; @@ -23,7 +23,7 @@ public class DrawableShape : Behaviour2D, IDrawableTriangle, IPreDraw protected void UpdateWorldShape() => shape.Transform(Transform, worldShape); - public DrawableShape() => shape = Shape2D.Triangle; - public DrawableShape(Shape2D shape) => this.shape = shape; - public DrawableShape(Shape2D shape, ColorRGB color) { this.shape = shape; this.color = color; } + public DrawableShape2D() => shape = Shape2D.Triangle; + public DrawableShape2D(Shape2D shape) => this.shape = shape; + public DrawableShape2D(Shape2D shape, ColorRGB color) { this.shape = shape; this.color = color; } } diff --git a/Engine.Integration/Engine.Integration.MonoGame/Abstract/IDrawableTriangle.cs b/Engine.Systems/Graphics/IDrawableTriangle.cs similarity index 75% rename from Engine.Integration/Engine.Integration.MonoGame/Abstract/IDrawableTriangle.cs rename to Engine.Systems/Graphics/IDrawableTriangle.cs index 7d3372d..2606b18 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/Abstract/IDrawableTriangle.cs +++ b/Engine.Systems/Graphics/IDrawableTriangle.cs @@ -1,6 +1,6 @@ using Engine.Core; -namespace Engine.Integration.MonoGame; +namespace Engine.Systems.Graphics; public interface IDrawableTriangle : IBehaviour { diff --git a/Engine.Systems/Graphics/ITriangleBatch.cs b/Engine.Systems/Graphics/ITriangleBatch.cs new file mode 100644 index 0000000..c25d6aa --- /dev/null +++ b/Engine.Systems/Graphics/ITriangleBatch.cs @@ -0,0 +1,10 @@ +using Engine.Core; + +namespace Engine.Systems.Graphics; + +public interface ITriangleBatch +{ + void Begin(Matrix4x4? view = null, Matrix4x4? projection = null); + void Draw(Triangle triangle, ColorRGBA colorRGBA); + void End(); +} diff --git a/Engine.Systems/Graphics/TriangleBatcher.cs b/Engine.Systems/Graphics/TriangleBatcher.cs new file mode 100644 index 0000000..872f2be --- /dev/null +++ b/Engine.Systems/Graphics/TriangleBatcher.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +using Engine.Core; + +namespace Engine.Systems.Graphics; + +public class TriangleBatcher : Behaviour, IFirstFrameUpdate, ILastFrameUpdate, IDraw +{ + private static Comparer SortByAscendingPriority() => Comparer.Create((x, y) => x.CompareTo(y)); + private static System.Func GetPriority() => (b) => b.Priority; + + private readonly BehaviourCollector triangleBatches = new(); + private readonly ActiveBehaviourCollectorOrdered drawableShapes = new(GetPriority(), SortByAscendingPriority()); + + public void FirstActiveFrame() + { + drawableShapes.Assign(Universe); + triangleBatches.Assign(Universe); + } + + public void Draw() + { + for (int i = 0; i < triangleBatches.Count; i++) + { + ITriangleBatch triangleBatch = triangleBatches[i]; + triangleBatch.Begin(); + for (int j = 0; j < drawableShapes.Count; j++) + drawableShapes[j].Draw(triangleBatch); + triangleBatch.End(); + } + } + + public void LastActiveFrame() + { + triangleBatches.Unassign(); + drawableShapes.Unassign(); + } +} diff --git a/Engine.Systems/Network/Abstract/IPacketListenerClientEntity.cs b/Engine.Systems/Network/Abstract/IPacketListenerClientEntity.cs new file mode 100644 index 0000000..46f0ed7 --- /dev/null +++ b/Engine.Systems/Network/Abstract/IPacketListenerClientEntity.cs @@ -0,0 +1,6 @@ +namespace Engine.Systems.Network; + +public interface IPacketListenerClientEntity : INetworkEntity where T : IEntityNetworkPacket +{ + void OnEntityClientPacketArrived(IConnection sender, T packet); +} diff --git a/Engine.Systems/Network/Abstract/IPacketListenerServerEntity.cs b/Engine.Systems/Network/Abstract/IPacketListenerServerEntity.cs new file mode 100644 index 0000000..90e0051 --- /dev/null +++ b/Engine.Systems/Network/Abstract/IPacketListenerServerEntity.cs @@ -0,0 +1,6 @@ +namespace Engine.Systems.Network; + +public interface IPacketListenerServerEntity : INetworkEntity where T : IEntityNetworkPacket +{ + void OnEntityServerPacketArrived(IConnection sender, T packet); +} diff --git a/Engine.Systems/Network/CommonNetworkBehaviour.cs b/Engine.Systems/Network/CommonNetworkBehaviour.cs new file mode 100644 index 0000000..7b7043b --- /dev/null +++ b/Engine.Systems/Network/CommonNetworkBehaviour.cs @@ -0,0 +1,40 @@ +using Engine.Core; + +namespace Engine.Systems.Network; + +/// +/// Basic network behaviour that supports both client & server behaviour. Finds both +/// the and the +/// in the universe in it's first active frame. Recommended to use or +///
+/// Disclaimer: It implements and in virtual methods. +///
+public class CommonNetworkBehaviour : Behaviour, IFirstFrameUpdate, ILastFrameUpdate +{ + protected INetworkCommunicatorServer? server = null!; + protected INetworkCommunicatorClient? client = null!; + + protected bool IsServer { get; private set; } = false; + protected bool IsClient { get; private set; } = false; + + public INetworkCommunicatorServer Server => server ?? throw new Core.Exceptions.NotFoundException($"Universe does not contain a {nameof(INetworkCommunicatorServer)}"); + public INetworkCommunicatorClient Client => client ?? throw new Core.Exceptions.NotFoundException($"Universe does not contain a {nameof(INetworkCommunicatorClient)}"); + + public virtual void FirstActiveFrame() + { + client = Universe.FindBehaviour(); + server = Universe.FindBehaviour(); + + IsServer = server is not null; + IsClient = client is not null; + } + + public virtual void LastActiveFrame() + { + client = null!; + server = null!; + + IsServer = false; + IsClient = false; + } +} diff --git a/Engine.Systems/Network/Extensions/CommunicatorExtensions.cs b/Engine.Systems/Network/Extensions/CommunicatorExtensions.cs new file mode 100644 index 0000000..0d68c56 --- /dev/null +++ b/Engine.Systems/Network/Extensions/CommunicatorExtensions.cs @@ -0,0 +1,13 @@ + +using System.Collections.Generic; + +namespace Engine.Systems.Network; + +public static class CommunicatorExtensions +{ + public static void SendToClients(this INetworkCommunicatorServer server, IEnumerable connections, T packet, PacketDelivery packetDelivery = PacketDelivery.ReliableInOrder) where T : class, new() + { + foreach (IConnection connection in connections) + server.SendToClient(connection, packet, packetDelivery); + } +} diff --git a/Engine.Systems/Network/NetworkManager.cs b/Engine.Systems/Network/NetworkManager.cs index 0082b2a..2847efe 100644 --- a/Engine.Systems/Network/NetworkManager.cs +++ b/Engine.Systems/Network/NetworkManager.cs @@ -10,25 +10,127 @@ namespace Engine.Systems.Network; /// /// Intermediary manager that looks up in it's hierarchy for a to route/broadcast it's received packets to their destinations. /// +/// 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 { - private readonly Dictionary>> clientPacketArrivalMethods = []; - private readonly Dictionary>> serverPacketArrivalMethods = []; + #region Packet Router/Broadcaster to Listener Delegates - private readonly Dictionary> clientPacketRouters = []; - private readonly Dictionary> serverPacketRouters = []; + /// + /// Behaviour Type → Packet Type → List of listener methods (broadcast packets, client-side) + /// + private readonly Dictionary>> clientBroadcastPacketListenerMethods = []; - private readonly List<(Type packetType, Delegate @delegate)> packetRetrievalDelegates = []; + /// + /// Behaviour Type → Packet Type → List of listener methods (broadcast packets, server-side) + /// + private readonly Dictionary>> serverBroadcastPacketListenerMethods = []; + /// + /// Behaviour Type → Packet Type → List of listener methods (entity packets, client-side) + /// + private readonly Dictionary>> clientEntityPacketListenerMethods = []; + + /// + /// Behaviour Type → Packet Type → List of listener methods (entity packets, server-side) + /// + private readonly Dictionary>> serverEntityPacketListenerMethods = []; + + #endregion + + #region Packet Router/Broadcaster Events + + /// + /// Packet Type → Behaviour.Id → Broadcaster Event (broadcast, client-side) + /// + private readonly Dictionary> clientPacketBroadcastEvents = []; + + /// + /// Packet Type → Behaviour.Id → Broadcaster Event (broadcast, server-side) + /// + private readonly Dictionary> serverPacketBroadcastEvents = []; + + /// + /// Maps an type to a set of routing events, + /// keyed by , for CLIENT entity listeners. + /// The packet is routed to the correct instance + /// by matching . + /// + private readonly Dictionary> clientEntityPacketRouterEvents = []; + + /// + /// Maps an type to a set of routing events, + /// keyed by , for SERVER entity listeners. + /// The packet is routed to the correct instance + /// by matching . + /// + private readonly Dictionary> serverEntityPacketRouterEvents = []; + + #endregion + + #region Packet Retrieval Delegates + + /// + /// Stores delegates that connect incoming broadcast packets from + /// to . + /// These are used to subscribe/unsubscribe from events. + /// + private readonly List broadcastPacketRetrievalSubscriptionDelegates = []; + + /// + /// Stores delegates that connect incoming entity packets from + /// to . + /// These are used to subscribe/unsubscribe from events. + /// + private readonly List entityPacketRetrievalSubscriptionDelegates = []; + + /// + /// Stores delegates that connect incoming all packets from to + /// . This is a combination of all subscription + /// delegates filtered so there are no duplicates packet entries. + /// + private readonly List uniqueRetrievalSubscriptionDelegates = []; + + #endregion + + #region Method Caches + + /// + /// Packet type → method. + /// private readonly Dictionary clearRoutesMethods = []; - private readonly Dictionary registerPacketListenersMethods = []; + /// + /// Packet type → method. + /// + private readonly Dictionary registerBroadcastPacketListenersMethods = []; + + /// + /// Packet type → method. + /// + private readonly Dictionary registerEntityPacketListenersMethods = []; + + #endregion + + #region Network Entity Collector + + /// + /// All active network , keyed by . + /// private readonly Dictionary _networkEntities = []; public IReadOnlyDictionary NetworkEntities => _networkEntities; + /// + /// Collector responsible for detecting s entering/leaving the universe. + /// private readonly BehaviourCollector _networkEntityCollector = new(); + public IBehaviourCollector NetworkEntityCollector => _networkEntityCollector; + #endregion + + #region Network Communicator + public INetworkCommunicator NetworkCommunicator { get; @@ -40,68 +142,74 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork INetworkCommunicator? previousCommunicator = field; field = value; - if (previousCommunicator is not null) UnsubscribeCommunicatorMethods(previousCommunicator); - if (field is not null) SubscribeCommunicatorMethods(field); + // Unsubscribe packet delegates from old communicator + 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!; - #region Communicator Subscriptions - private void SubscribeCommunicatorMethods(INetworkCommunicator networkCommunicator) - { - MethodInfo subscribeToPacketsMethod = typeof(INetworkCommunicator) - .GetMethod(nameof(INetworkCommunicator.SubscribeToPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!; - - foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates) - { - MethodInfo genericSubscribeMethod = subscribeToPacketsMethod.MakeGenericMethod(packetType); - genericSubscribeMethod.Invoke(networkCommunicator, [@delegate]); - } - } - - private void UnsubscribeCommunicatorMethods(INetworkCommunicator networkCommunicator) + /// + /// Dynamically invokes + /// or + /// on the provided for all known packet types. + /// + private void InvokeCommunicatorMethods(string methodName, INetworkCommunicator networkCommunicator) { 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); - genericUnsubscribeMethod.Invoke(networkCommunicator, [@delegate]); + MethodInfo genericMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType); + genericMethod.Invoke(networkCommunicator, [@delegate]); } } + #endregion - #region Packet Routing + //////////////////////////////////////////////////////////////// + + #region Packet Routing/Broadcasting + + /// + /// Entry point for ALL incoming packets from the . + /// private void OnPacketReceived(IConnection sender, T entityDataPacket) { + BroadcastPacket(sender, entityDataPacket); + if (entityDataPacket is IEntityNetworkPacket entityPacket) RoutePacket(sender, entityDataPacket, entityPacket); - else - BroadcastPacket(sender, entityDataPacket); } private void RoutePacket(IConnection sender, T entityDataPacket, IEntityNetworkPacket entityPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) - RoutePacket(clientPacketRouters, entityPacket.EntityId, sender, entityDataPacket); + RoutePacket(clientEntityPacketRouterEvents, entityPacket.EntityId, sender, entityDataPacket); + if (NetworkCommunicator is INetworkCommunicatorServer) - RoutePacket(serverPacketRouters, entityPacket.EntityId, sender, entityDataPacket); + RoutePacket(serverEntityPacketRouterEvents, entityPacket.EntityId, sender, entityDataPacket); } private void BroadcastPacket(IConnection sender, T entityDataPacket) { if (NetworkCommunicator is INetworkCommunicatorClient) - BroadcastPacket(clientPacketRouters, sender, entityDataPacket); + BroadcastPacket(clientPacketBroadcastEvents, sender, entityDataPacket); + if (NetworkCommunicator is INetworkCommunicatorServer) - BroadcastPacket(serverPacketRouters, sender, entityDataPacket); + BroadcastPacket(serverPacketBroadcastEvents, sender, entityDataPacket); } - private static void BroadcastPacket( - Dictionary> packetRouters, + private void BroadcastPacket( + Dictionary> packetBroadcasters, IConnection sender, T entityDataPacket) { - if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary? routers)) + if (!packetBroadcasters.TryGetValue(entityDataPacket!.GetType(), out Dictionary? routers)) return; foreach ((string behaviourId, object routerEventReference) in routers) @@ -111,7 +219,7 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork } } - private static void RoutePacket( + private void RoutePacket( Dictionary> packetRouters, string entityId, IConnection sender, @@ -126,65 +234,114 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork Event routerEvent = (Event)routerEventReference; routerEvent.Invoke(sender, entityDataPacket!); } + #endregion #region Packet Routers + + /// + /// Registers routing events for the behaviour based on cached packet listener methods. + /// private void RegisterPacketRoutersFor( INetworkEntity behaviour, Dictionary> packetRouters, - Dictionary>> packetArrivalMethods, - NetworkType networkType) + Dictionary>> packetListenerMethods, + NetworkType networkType, + Dictionary registerPacketListenerListenersMethods) { - if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary>? arrivalMethods)) + if (!packetListenerMethods.TryGetValue(behaviour.GetType(), out Dictionary>? listenerMethods)) return; - foreach ((Type packetType, List methods) in arrivalMethods) - foreach (MethodInfo receiveMethod in methods) + foreach (Type packetType in listenerMethods.Keys) + { + if (!packetRouters.TryGetValue(packetType, out Dictionary? routers)) { - if (!packetRouters.TryGetValue(packetType, out Dictionary? routers)) - { - routers = []; - packetRouters.Add(packetType, routers); - } - - object packetListenerEvent = CreateEventAndRegister(packetType, behaviour, networkType); - routers.Add(behaviour.Id, packetListenerEvent); + routers = []; + packetRouters.Add(packetType, routers); } + + object packetListenerEvent = CreateEventAndRegister(packetType, behaviour, networkType, registerPacketListenerListenersMethods); + + routers.Add(behaviour.Id, packetListenerEvent); + } } - private object CreateEventAndRegister(Type packetType, INetworkEntity behaviour, NetworkType networkType) + /// + /// Creates an Event and attaches listener callbacks. + /// + private object CreateEventAndRegister( + Type packetType, + INetworkEntity behaviour, + NetworkType networkType, + Dictionary registerPacketListenersMethods) { Type genericEventType = typeof(Event<,>).MakeGenericType(typeof(IConnection), packetType); object packetListenerEvent = Activator.CreateInstance(genericEventType)!; 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]); return packetListenerEvent; } - private static void RegisterPacketListenerEvent( + /// + /// Registers broadcast packet listeners on the behaviour. + /// + private static void RegisterBroadcastPacketListenerEvent( INetworkEntity behaviour, Event packetListenerEvent, NetworkType networkType) { switch (networkType) { - case NetworkType.Client: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerClient)behaviour).OnClientPacketArrived(sender, packet)); break; - case NetworkType.Server: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerServer)behaviour).OnServerPacketArrived(sender, packet)); break; + case NetworkType.Client: + packetListenerEvent.AddListener( + (sender, packet) => ((IPacketListenerClient)behaviour).OnClientPacketArrived(sender, packet)); + break; + + case NetworkType.Server: + packetListenerEvent.AddListener( + (sender, packet) => ((IPacketListenerServer)behaviour).OnServerPacketArrived(sender, packet)); + break; } } + /// + /// Registers entity-specific packet listeners on a network behaviour. + /// + private static void RegisterEntityPacketListenerEvent( + INetworkEntity behaviour, + Event packetListenerEvent, + NetworkType networkType + ) where T : IEntityNetworkPacket + { + switch (networkType) + { + case NetworkType.Client: + packetListenerEvent.AddListener( + (sender, packet) => ((IPacketListenerClientEntity)behaviour).OnEntityClientPacketArrived(sender, packet)); + break; + + case NetworkType.Server: + packetListenerEvent.AddListener( + (sender, packet) => ((IPacketListenerServerEntity)behaviour).OnEntityServerPacketArrived(sender, packet)); + break; + } + } + + /// + /// Unregisters all routing events associated with the behaviour. + /// private void UnregisterPacketRoutersFor( INetworkEntity behaviour, Dictionary> packetRouters, - Dictionary>> packetArrivalMethods) + Dictionary>> packetListenerMethods) { - if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary>? arrivalMethods)) + if (!packetListenerMethods.TryGetValue(behaviour.GetType(), out Dictionary>? listenerMethods)) return; - foreach ((Type packetType, List methods) in arrivalMethods) + foreach ((Type packetType, List methods) in listenerMethods) { if (!packetRouters.TryGetValue(packetType, out Dictionary? routers)) continue; @@ -199,6 +356,9 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork } } + /// + /// Clears all listeners from a router event. + /// private static void ClearRouter(object routerEventReference) { Event routerEvent = (Event)routerEventReference; @@ -208,124 +368,192 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork #endregion #region Engine Callbacks - private void OnCollected(IBehaviourCollector sender, IBehaviourCollector.BehaviourCollectedArguments args) + + /// + /// Called when an enters the universe. + /// Registers all packet routing for that entity. + /// + private void OnCollected( + IBehaviourCollector sender, + IBehaviourCollector.BehaviourCollectedArguments args) { INetworkEntity collectedBehaviour = args.BehaviourCollected; if (!_networkEntities.TryAdd(collectedBehaviour.Id, collectedBehaviour)) throw new($"Unable to add {collectedBehaviour.Id} to {nameof(NetworkManager)}"); - RegisterPacketRoutersFor(collectedBehaviour, clientPacketRouters, clientPacketArrivalMethods, NetworkType.Client); - RegisterPacketRoutersFor(collectedBehaviour, serverPacketRouters, serverPacketArrivalMethods, NetworkType.Server); + RegisterPacketRoutersFor(collectedBehaviour, clientPacketBroadcastEvents, clientBroadcastPacketListenerMethods, NetworkType.Client, registerBroadcastPacketListenersMethods); + RegisterPacketRoutersFor(collectedBehaviour, clientEntityPacketRouterEvents, clientEntityPacketListenerMethods, NetworkType.Client, registerEntityPacketListenersMethods); + + RegisterPacketRoutersFor(collectedBehaviour, serverPacketBroadcastEvents, serverBroadcastPacketListenerMethods, NetworkType.Server, registerBroadcastPacketListenersMethods); + RegisterPacketRoutersFor(collectedBehaviour, serverEntityPacketRouterEvents, serverEntityPacketListenerMethods, NetworkType.Server, registerEntityPacketListenersMethods); } + /// + /// Called when an is removed from the universe. + /// Cleans up all routing. + /// private void OnRemoved(IBehaviourCollector sender, IBehaviourCollector.BehaviourRemovedArguments args) { INetworkEntity removedBehaviour = args.BehaviourRemoved; - if (!_networkEntities.Remove(args.BehaviourRemoved.Id)) + + if (!_networkEntities.Remove(removedBehaviour.Id)) return; - UnregisterPacketRoutersFor(removedBehaviour, clientPacketRouters, clientPacketArrivalMethods); - UnregisterPacketRoutersFor(removedBehaviour, serverPacketRouters, serverPacketArrivalMethods); + UnregisterPacketRoutersFor(removedBehaviour, clientPacketBroadcastEvents, clientBroadcastPacketListenerMethods); + UnregisterPacketRoutersFor(removedBehaviour, clientEntityPacketRouterEvents, clientEntityPacketListenerMethods); + + UnregisterPacketRoutersFor(removedBehaviour, serverPacketBroadcastEvents, serverBroadcastPacketListenerMethods); + UnregisterPacketRoutersFor(removedBehaviour, serverEntityPacketRouterEvents, serverEntityPacketListenerMethods); } public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign(); + public void EnterUniverse(IUniverse universe) { _networkEntityCollector.Assign(universe); NetworkCommunicator = BehaviourController.GetRequiredBehaviourInParent(); } + #endregion #region Initialization + public NetworkManager() { - CachePacketRetrievalDelegates(); + CacheRetrievalSubscriptionDelegates(); CacheRegistrationMethods(); - CachePacketArrivalMethods(); + CachePacketListenerMethods(); _networkEntityCollector.OnCollected.AddListener(OnCollected); _networkEntityCollector.OnRemoved.AddListener(OnRemoved); } - private void CachePacketRetrievalDelegates() + /// + /// Caches all retrieval subscription delegates for packets. + /// + private void CacheRetrievalSubscriptionDelegates() { - IEnumerable packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()) - .Where(t => typeof(INetworkPacket).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType); + CachePacketRetrievalDelegates(typeof(INetworkPacket), broadcastPacketRetrievalSubscriptionDelegates); + CachePacketRetrievalDelegates(typeof(IEntityNetworkPacket), entityPacketRetrievalSubscriptionDelegates); - MethodInfo onPacketArrivedMethod = GetType() - .GetMethod(nameof(OnPacketReceived), BindingFlags.NonPublic | BindingFlags.Instance)!; + uniqueRetrievalSubscriptionDelegates.AddRange(broadcastPacketRetrievalSubscriptionDelegates.Concat(entityPacketRetrievalSubscriptionDelegates).DistinctBy(pair => pair.PacketType)); + } - foreach (Type packetType in packetTypes) + /// + /// Creates delegates for all concrete packet types that forward packets to . + /// + private void CachePacketRetrievalDelegates(Type packetType, List retrievalDelegates) + { + IEnumerable 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); - packetRetrievalDelegates.Add((packetType, genericPacketReceivedDelegate)); + retrievalDelegates.Add((type, genericPacketReceivedDelegate)); } } + /// + /// Caches all registration and cleanup methods for packets. + /// private void CacheRegistrationMethods() { - CacheRegistrationMethods(registerPacketListenersMethods, nameof(RegisterPacketListenerEvent)); - CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter)); + CacheRegistrationMethods(registerBroadcastPacketListenersMethods, nameof(RegisterBroadcastPacketListenerEvent), broadcastPacketRetrievalSubscriptionDelegates); + CacheRegistrationMethods(registerEntityPacketListenersMethods, nameof(RegisterEntityPacketListenerEvent), entityPacketRetrievalSubscriptionDelegates); + CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter), uniqueRetrievalSubscriptionDelegates); } - private void CacheRegistrationMethods(Dictionary registrationMethods, string methodName) + /// + /// Creates generic method instances for each packet type listener to be registered into the . + /// + private void CacheRegistrationMethods( + Dictionary listenerRegistrationMethods, + string methodName, + List packetRetrievalDelegates) { MethodInfo registerPacketMethod = typeof(NetworkManager).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static)!; + foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates) { MethodInfo genericMethod = registerPacketMethod.MakeGenericMethod(packetType); - registrationMethods.Add(packetType, genericMethod); + listenerRegistrationMethods.TryAdd(packetType, genericMethod); } } - private void CachePacketArrivalMethods() + /// + /// Caches packet listener methods for all packet listener interfaces. + /// + private void CachePacketListenerMethods() { - CachePacketArrivalMethods(clientPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient.OnClientPacketArrived)); - CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer.OnServerPacketArrived)); + CachePacketListenerMethods(clientBroadcastPacketListenerMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<>.OnClientPacketArrived)); + 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>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName) + /// + /// Discovers all types implementing a given packet listener interface and caches their methods. + /// + private static void CachePacketListenerMethods( + Dictionary>> packetListenerMethods, + Type listenerType, + string packetListenerMethodName) { foreach (Type listenerClass in GetGenericsWith(listenerType)) { - Dictionary> packetRouters = []; - packetArrivalMethods.Add(listenerClass, packetRouters); + Dictionary> listenerMethodDictionary = []; + packetListenerMethods.Add(listenerClass, listenerMethodDictionary); foreach (Type packetListener in GetGenericInterfacesWith(listenerType, listenerClass)) { Type packetType = packetListener.GetGenericArguments().First(); - List arrivalMethods = packetListener - .GetMethods() - .Where(m => m.Name == packetArrivalMethodName) - .ToList(); + List listenerMethods = [.. packetListener.GetMethods().Where(m => m.Name == packetListenerMethodName)]; - packetRouters.Add(packetType, arrivalMethods); + listenerMethodDictionary.Add(packetType, listenerMethods); } } } + /// + /// Finds all types that implement a generic interface. + /// private static IEnumerable GetGenericsWith(Type type) => AppDomain.CurrentDomain .GetAssemblies() .SelectMany(a => a.GetTypes().Where( 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 GetGenericInterfacesWith(Type interfaceType, Type type) - => type.GetInterfaces().Where( - i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType - ); + => type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType); + #endregion + // Identifies whether packet routing is client-side or server-side 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); + } } diff --git a/Engine.Systems/Tween/Tween.cs b/Engine.Systems/Tween/Tween.cs index 0642edf..bea0c59 100644 --- a/Engine.Systems/Tween/Tween.cs +++ b/Engine.Systems/Tween/Tween.cs @@ -27,8 +27,16 @@ internal class Tween : ITween field = value; switch (value) { - case TweenState.Completed: OnCompleted?.Invoke(this); OnEnded?.Invoke(this); break; - case TweenState.Cancelled: OnCancelled?.Invoke(this); OnEnded?.Invoke(this); break; + case TweenState.Completed: + OnCompleted?.Invoke(this); + if (State == TweenState.Completed) + OnEnded?.Invoke(this); + break; + case TweenState.Cancelled: + OnCancelled?.Invoke(this); + if (State == TweenState.Cancelled) + OnEnded?.Invoke(this); + break; case TweenState.Paused: OnPaused?.Invoke(this); break; case TweenState.Playing: if (previousState == TweenState.Idle) diff --git a/Engine.Systems/Tween/TweenExtensions.cs b/Engine.Systems/Tween/TweenExtensions.cs index e0ffc09..b2f1b1a 100644 --- a/Engine.Systems/Tween/TweenExtensions.cs +++ b/Engine.Systems/Tween/TweenExtensions.cs @@ -4,35 +4,48 @@ namespace Engine.Systems.Tween; public static class TweenExtensions { + private static readonly System.Collections.Generic.Dictionary loopDictionary = []; + public static ITween Loop(this ITween tween, int count) { - Tween tweenConcrete = (Tween)tween; - int counter = count; + if (!loopDictionary.TryAdd(tween, count)) + throw new($"Tween already has a loop in progress."); - tweenConcrete.OnCompleted.AddListener(_ => - { - if (counter-- <= 0) - return; - - tweenConcrete.Reset(); - tweenConcrete.State = TweenState.Playing; - }); + tween.OnCompleted.AddListener(looperDelegate); + tween.OnEnded.AddListener(looperEndDelegate); return tween; } + private static readonly Core.Event.EventHandler looperEndDelegate = sender => loopDictionary.Remove(sender); + private static readonly Core.Event.EventHandler looperDelegate = sender => + { + int counter = loopDictionary[sender] = loopDictionary[sender] - 1; + + if (counter <= 0) + { + loopDictionary.Remove(sender); + return; + } + + Tween tweenConcrete = (Tween)sender; + tweenConcrete.Reset(); + tweenConcrete.State = TweenState.Playing; + }; + public static ITween LoopInfinitely(this ITween tween) { - Tween tweenConcrete = (Tween)tween; - tweenConcrete.OnCompleted.AddListener(_ => - { - tweenConcrete.Reset(); - tweenConcrete.State = TweenState.Playing; - }); - + tween.OnCompleted.AddListener(repeaterDelegate); return tween; } + private static readonly Core.Event.EventHandler repeaterDelegate = sender => + { + Tween tweenConcrete = (Tween)sender; + tweenConcrete.Reset(); + tweenConcrete.State = TweenState.Playing; + }; + public static ITween Ease(this ITween tween, IEasing easing) { Tween tweenConcrete = (Tween)tween; diff --git a/Engine.Systems/Tween/Yields/WaitForTweenCompleteCoroutineYield.cs b/Engine.Systems/Tween/Yields/WaitForTweenCompleteCoroutineYield.cs index b574530..db4702e 100644 --- a/Engine.Systems/Tween/Yields/WaitForTweenCompleteCoroutineYield.cs +++ b/Engine.Systems/Tween/Yields/WaitForTweenCompleteCoroutineYield.cs @@ -2,4 +2,4 @@ using Engine.Core; namespace Engine.Systems.Tween; -public class WaitForTweenCompleteCoroutineYield(ITween tween) : CoroutineYield(() => tween.State == TweenState.Completed); +public class WaitForTweenCompleteCoroutineYield(ITween tween) : WaitUntilYield(() => tween.State == TweenState.Completed); diff --git a/Engine.Systems/Tween/Yields/WaitForTweenDoneCoroutineYield.cs b/Engine.Systems/Tween/Yields/WaitForTweenDoneCoroutineYield.cs index 3ab3d78..ac66745 100644 --- a/Engine.Systems/Tween/Yields/WaitForTweenDoneCoroutineYield.cs +++ b/Engine.Systems/Tween/Yields/WaitForTweenDoneCoroutineYield.cs @@ -2,4 +2,4 @@ using Engine.Core; namespace Engine.Systems.Tween; -public class WaitWhileTweenActiveCoroutineYield(ITween tween) : CoroutineYield(() => tween.State.CheckFlag(TweenState.Completed | TweenState.Cancelled)); +public class WaitForTweenDoneCoroutineYield(ITween tween) : WaitUntilYield(() => tween.State.CheckFlag(TweenState.Completed | TweenState.Cancelled));