30 Commits

Author SHA1 Message Date
913af2a4a4 fix: added dynamic index updates during event invocation so there are no missing/duplicate invocations
This also makes the events very not ideal for multithreaded applications at the moment.
2026-01-31 00:49:00 +03:00
4e9fda3d7c refactor: auto property on FastListOrdered 2026-01-30 18:33:07 +03:00
7675f9acac chore: added missing Matrix4x4NetPacker 2026-01-30 18:02:34 +03:00
72f86478f2 feat!: added broadcast & routing support for NetworkManager
It used to only route or broadcast, now the same packet can be both routed to it's own behaviour AND at the same time the broadcast listeners can get alerted of the same package such as universe-wide managers
2026-01-30 13:43:48 +03:00
64e7321f0f fix: rotating file logger deleting from the wrong order 2026-01-30 10:54:56 +03:00
c355c666e0 refactor: fixed LiteNetLibServer using events to subscribe to PostUpdate instead of the interface 2026-01-29 22:29:19 +03:00
b9f3227f73 refactor: removed parameters on triangle batch calls on TriangleBatcher for multi window support 2026-01-28 22:19:04 +03:00
c68de39c83 fix: MonoGame view matrix calculation issues 2026-01-28 12:58:25 +03:00
ad444decbb perf: removed old position flipping on MonoGame 2026-01-28 11:16:47 +03:00
297e0eb790 fix: typos in Matrix4x4 methods 2026-01-28 11:14:31 +03:00
efa4da4398 fix: ScreenToWorldPosition & vice versa methods on MonoGameCamera2D fixed 2026-01-28 11:06:15 +03:00
e30280f1f8 fix: missing implicit conversion operators on Vectors 2026-01-28 11:02:07 +03:00
d4437edfbf feat: added Matrix4x4 x Vector4D multiplication 2026-01-28 10:22:36 +03:00
08f32f96e4 BREAKING CHANGE: 4x4 matrices are now column major 2026-01-27 23:45:50 +03:00
9294df8a19 feat: added support for multiple batches 2026-01-27 21:28:31 +03:00
50a0269798 feat: added matrix 4x4 transpose & orthographic view matrix methods 2026-01-27 20:48:39 +03:00
985f898327 refactor: moved drawable triangles into systems project for platform agnosticism 2026-01-27 13:04:04 +03:00
7c9973a5e7 refactor: ICamera interface added with view and projection matrices fields 2026-01-26 23:48:16 +03:00
af6dde84fd fix: universe entrance issues where entering behaviours adding more behaviours causing duplicate/missing calls 2026-01-26 23:23:49 +03:00
e2820670c6 fix: classname x filename inconsistency fixed 2026-01-26 22:01:36 +03:00
5ce5e4eb0b feat: added inverse & float scale methods to 4x4 matrices 2026-01-26 21:48:15 +03:00
ee58e60ef1 fix: universe entrance issues caused by wrong logic 2026-01-26 12:15:42 +03:00
1a8f396766 fix: cs files encoding being set as utf-8-bom 2026-01-23 12:34:41 +03:00
90e59802c6 chore: bumped dotnet version to 10 2026-01-23 12:29:52 +03:00
097f1897c2 revert: "fix: entity packets not being broadcasted" for unexpected behaviour
This reverts commit edda4b873c.
2025-11-25 22:55:19 +03:00
49a6c9665a chore: missing arguments for trace logging 2025-11-24 22:13:29 +03:00
7c8b7debca fix: client polling issues 2025-11-24 22:13:09 +03:00
55a6fca1fd feat: new logger trace method with message added 2025-11-18 11:35:53 +03:00
48daeb2f19 chore: code style issues fixed on logger extensions 2025-11-18 11:35:27 +03:00
edda4b873c fix: entity packets not being broadcasted 2025-11-14 23:59:11 +03:00
62 changed files with 877 additions and 585 deletions

View File

@@ -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}]

View File

@@ -0,0 +1,17 @@
namespace Engine.Core;
/// <summary>
/// Represents a camera with view and projections matrices in the engine.
/// </summary>
public interface ICamera
{
/// <summary>
/// View <see cref="Matrix4x4"/> of the <see cref="ICamera"/>.
/// </summary>
Matrix4x4 ViewMatrix { get; }
/// <summary>
/// Projection <see cref="Matrix4x4"/> of the <see cref="ICamera"/>.
/// </summary>
Matrix4x4 ProjectionMatrix { get; }
}

View File

@@ -3,7 +3,7 @@ namespace Engine.Core;
/// <summary>
/// Represents a 2D camera in the engine.
/// </summary>
public interface ICamera2D : IBehaviour2D
public interface ICamera2D : ICamera, IBehaviour2D
{
/// <summary>
/// The zoom level of the camera.

View File

@@ -3,7 +3,7 @@ namespace Engine.Core;
/// <summary>
/// Represents a 3D camera in the engine.
/// </summary>
public interface ICamera3D : IBehaviour3D
public interface ICamera3D : ICamera, IBehaviour3D
{
/// <summary>
/// Event triggered when the near plane of the <see cref="ICamera3D"/> changes.

View File

@@ -10,46 +10,41 @@ public abstract class BaseEntity : IEntity
public Event<IHasStateEnable> OnStateEnableAssigned { get; } = new();
public Event<IAssignable> OnUnassigned { get; } = new();
private IStateEnable _stateEnable = null!;
private bool _initialized = false;
private string _id = string.Empty;
public virtual IStateEnable StateEnable => _stateEnable;
public virtual IStateEnable StateEnable { get; private set; } = null!;
public string Id
{
get => _id;
get;
set
{
if (IsInitialized)
throw new($"Can't change {nameof(Id)} of {_id} because it's initialized");
throw new($"Can't change {nameof(Id)} of {field} because it's initialized");
if (value == _id)
if (value == field)
return;
string previousId = _id;
string previousId = field;
_id = value;
field = value;
OnIdChanged?.Invoke(this, new(previousId));
}
}
} = string.Empty;
public bool IsInitialized
{
get => _initialized;
get;
private set
{
if (value == _initialized)
if (value == field)
return;
_initialized = value;
field = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
}
} = false;
protected virtual void OnAssign(IStateEnable stateEnable) { }
public bool Assign(IStateEnable stateEnable)
@@ -57,8 +52,8 @@ public abstract class BaseEntity : IEntity
if (IsInitialized)
return false;
_stateEnable = stateEnable;
_stateEnable.Assign(this);
StateEnable = stateEnable;
StateEnable.Assign(this);
OnAssign(stateEnable);
OnStateEnableAssigned?.Invoke(this);
return true;
@@ -72,8 +67,8 @@ public abstract class BaseEntity : IEntity
UnassignInternal();
_stateEnable = null!;
_stateEnable.Unassign();
StateEnable = null!;
StateEnable.Unassign();
OnUnassigned?.Invoke(this);
return true;
}
@@ -84,7 +79,7 @@ public abstract class BaseEntity : IEntity
if (IsInitialized)
return false;
_stateEnable ??= Factory.StateEnableFactory.Instantiate(this);
StateEnable ??= Factory.StateEnableFactory.Instantiate(this);
InitializeInternal();
@@ -104,6 +99,6 @@ public abstract class BaseEntity : IEntity
return true;
}
protected BaseEntity() => _id = Guid.NewGuid().ToString("D");
protected BaseEntity(string id) => _id = id;
protected BaseEntity() => Id = Guid.NewGuid().ToString("D");
protected BaseEntity(string id) => Id = id;
}

View File

@@ -14,26 +14,23 @@ public abstract class Behaviour : BaseEntity, IBehaviour
public IUniverse Universe => BehaviourController.UniverseObject.Universe;
public IUniverseObject UniverseObject => BehaviourController.UniverseObject;
private IBehaviourController _behaviourController = null!;
public IBehaviourController BehaviourController => _behaviourController;
public IBehaviourController BehaviourController { get; private set; } = null!;
private int _priority = 0;
public int Priority
{
get => _priority;
get;
set
{
if (value == _priority)
if (value == field)
return;
int previousPriority = _priority;
_priority = value;
int previousPriority = field;
field = value;
OnPriorityChanged?.Invoke(this, new(previousPriority));
}
}
} = 0;
private bool _isActive = false;
public bool IsActive => _isActive;
public bool IsActive { get; private set; } = false;
protected virtual void OnAssign(IBehaviourController behaviourController) { }
public bool Assign(IBehaviourController behaviourController)
@@ -41,7 +38,7 @@ public abstract class Behaviour : BaseEntity, IBehaviour
if (IsInitialized)
return false;
_behaviourController = behaviourController;
BehaviourController = behaviourController;
OnAssign(behaviourController);
behaviourController.OnUniverseObjectAssigned.AddListener(delegateOnUniverseObjectAssigned);
behaviourController.StateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged);
@@ -71,7 +68,7 @@ public abstract class Behaviour : BaseEntity, IBehaviour
BehaviourController.OnUniverseObjectAssigned.RemoveListener(delegateOnUniverseObjectAssigned);
BehaviourController.StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged);
base.UnassignInternal();
_behaviourController = null!;
BehaviourController = null!;
}
protected override void InitializeInternal()
@@ -88,7 +85,7 @@ public abstract class Behaviour : BaseEntity, IBehaviour
private void UpdateActive()
{
bool previousActive = IsActive;
_isActive = StateEnable.Enabled && _behaviourController.StateEnable.Enabled && _behaviourController.UniverseObject.IsActive;
IsActive = StateEnable.Enabled && BehaviourController.StateEnable.Enabled && BehaviourController.UniverseObject.IsActive;
if (previousActive != IsActive)
OnActiveChanged?.Invoke(this, new(previousActive));

View File

@@ -12,9 +12,8 @@ public class BehaviourController : BaseEntity, IBehaviourController
private readonly FastList<IBehaviour> behaviours = new(Constants.BEHAVIOURS_SIZE_INITIAL);
private IUniverseObject _universeObject = null!;
public IUniverseObject UniverseObject { get; private set; } = null!;
public IUniverseObject UniverseObject => _universeObject;
public int Count => behaviours.Count;
public IBehaviour this[Index index] => behaviours[index];
@@ -103,7 +102,7 @@ public class BehaviourController : BaseEntity, IBehaviourController
if (UniverseObject is not null && UniverseObject.IsInitialized)
return false;
_universeObject = universeObject;
UniverseObject = universeObject;
OnAssign(universeObject);
OnUniverseObjectAssigned?.Invoke(this);
return true;

View File

@@ -11,7 +11,8 @@ public static class LoggerExtensions
logger.Log(body, level, force);
}
public static void LogWarning<T>(this ILogger logger, T caller, string message, bool force = false) => Log(logger, caller, message, ILogger.Level.Info, force);
public static void LogWarning<T>(this ILogger logger, T caller, string message, bool force = false)
=> Log(logger, caller, message, ILogger.Level.Info, force);
public static void LogError<T>(this ILogger logger, T caller, string message, bool force = false)
{
@@ -30,7 +31,11 @@ public static class LoggerExtensions
}
public static void LogTrace<T>(this ILogger logger, T caller, StackTrace? stackTrace = null, bool force = false)
=> Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{stackTrace ?? new()}", ILogger.Level.Trace, force);
public static void LogTrace<T>(this ILogger logger, T caller, string message, StackTrace? stackTrace = null, bool force = false)
{
Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{stackTrace ?? new()}", ILogger.Level.Trace, force);
Log(logger, caller, message, ILogger.Level.Trace, force);
LogTrace(logger, caller, stackTrace, force);
}
}

View File

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

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>false</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Engine.Core</RootNamespace>

View File

@@ -56,8 +56,10 @@ public class Event
// We use Ascending order because draw calls are running from last to first
private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
private ILogger _logger = ILogger.Shared;
public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared;
private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!;
@@ -75,6 +77,9 @@ public class Event
if (insertIndex < 0)
insertIndex = ~insertIndex;
if (insertIndex < currentCallIndex)
currentCallIndex++;
listeners.Insert(insertIndex, listenerData);
}
@@ -91,6 +96,9 @@ public class Event
if (insertIndex < 0)
insertIndex = ~insertIndex;
if (insertIndex < currentOnceCallIndex)
currentOnceCallIndex++;
onceListeners.Insert(insertIndex, listenerData);
}
@@ -104,6 +112,8 @@ public class Event
if (listeners[i].Callback == listener)
{
listeners.RemoveAt(i);
if (i < currentCallIndex)
currentCallIndex--;
return;
}
}
@@ -118,6 +128,8 @@ public class Event
if (onceListeners[i].Callback == listener)
{
onceListeners.RemoveAt(i);
if (i < currentOnceCallIndex)
currentOnceCallIndex--;
return;
}
}
@@ -132,23 +144,23 @@ public class Event
/// </summary>
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);
}
}
@@ -215,8 +227,10 @@ public class Event<TSender> where TSender : class
// We use Ascending order because draw calls are running from last to first
private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
private ILogger _logger = ILogger.Shared;
public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared;
private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!;
@@ -234,6 +248,9 @@ public class Event<TSender> where TSender : class
if (insertIndex < 0)
insertIndex = ~insertIndex;
if (insertIndex < currentCallIndex)
currentCallIndex++;
listeners.Insert(insertIndex, listenerData);
}
@@ -250,6 +267,9 @@ public class Event<TSender> where TSender : class
if (insertIndex < 0)
insertIndex = ~insertIndex;
if (insertIndex < currentOnceCallIndex)
currentOnceCallIndex++;
onceListeners.Insert(insertIndex, listenerData);
}
@@ -263,6 +283,8 @@ public class Event<TSender> where TSender : class
if (listeners[i].Callback == listener)
{
listeners.RemoveAt(i);
if (i < currentCallIndex)
currentCallIndex--;
return;
}
}
@@ -277,6 +299,8 @@ public class Event<TSender> where TSender : class
if (onceListeners[i].Callback == listener)
{
onceListeners.RemoveAt(i);
if (i < currentOnceCallIndex)
currentOnceCallIndex--;
return;
}
}
@@ -292,23 +316,23 @@ public class Event<TSender> where TSender : class
/// <param name="sender">The caller that's triggering this event.</param>
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,8 +406,10 @@ public class Event<TSender, TArguments> where TSender : class
// We use Ascending order because draw calls are running from last to first
private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
private ILogger _logger = ILogger.Shared;
public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared;
private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen.
private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!;
@@ -401,6 +427,9 @@ public class Event<TSender, TArguments> where TSender : class
if (insertIndex < 0)
insertIndex = ~insertIndex;
if (insertIndex < currentCallIndex)
currentCallIndex++;
listeners.Insert(insertIndex, listenerData);
}
@@ -417,6 +446,9 @@ public class Event<TSender, TArguments> where TSender : class
if (insertIndex < 0)
insertIndex = ~insertIndex;
if (insertIndex < currentOnceCallIndex)
currentOnceCallIndex++;
onceListeners.Insert(insertIndex, listenerData);
}
@@ -430,6 +462,8 @@ public class Event<TSender, TArguments> where TSender : class
if (listeners[i].Callback == listener)
{
listeners.RemoveAt(i);
if (i < currentCallIndex)
currentCallIndex--;
return;
}
}
@@ -444,6 +478,8 @@ public class Event<TSender, TArguments> where TSender : class
if (onceListeners[i].Callback == listener)
{
onceListeners.RemoveAt(i);
if (i < currentOnceCallIndex)
currentOnceCallIndex--;
return;
}
}
@@ -460,23 +496,23 @@ public class Event<TSender, TArguments> where TSender : class
/// <param name="args">The arguments provided for this event.</param>
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);
}
}

View File

@@ -16,8 +16,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
private readonly Func<TItem, TIndex> getIndexFunc = null!;
private readonly IComparer<TIndex> 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<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
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<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
items[key] = list = [];
list.Add(item);
count++;
Count++;
}
public void Insert(int index, TItem item)
@@ -88,7 +87,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
items[tIndex] = list = [];
list.Insert(index, item);
count++;
Count++;
}
public bool Remove(TItem item)
@@ -103,7 +102,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
if (!list.Remove(item))
return false;
count--;
Count--;
return true;
}
@@ -114,7 +113,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
(TIndex tIndex, int i) = GetAt(index);
items[tIndex].RemoveAt(i);
count--;
Count--;
}
public void Clear()
@@ -125,7 +124,7 @@ public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>
foreach ((TIndex index, FastList<TItem> list) in items)
list.Clear();
count = 0;
Count = 0;
}
public bool Contains(TItem item)

View File

@@ -1,12 +1,11 @@
using System;
using System.Numerics;
namespace Engine.Core;
// TODO Comments
/// <summary>
/// Represents a 4D left handed space matrix.
/// Represents a 4D left handed space matrix in a Column Major convention.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="Matrix4x4"/> struct with the specified values.
@@ -62,6 +61,16 @@ public readonly struct Matrix4x4(
0f, 0f, 0f, 1f
);
/// <summary>
/// Represents the inverted version of this <see cref="Matrix4x4"/>.
/// </summary>
public Matrix4x4 Inverse => Invert(this);
/// <summary>
/// Represents the transposed version of this <see cref="Matrix4x4"/>.
/// </summary>
public Matrix4x4 Transposed => Transpose(this);
public static Matrix4x4 operator *(Matrix4x4 a, Matrix4x4 b) => new(
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;
/// <summary>
/// Inverts the given <see cref="Matrix4x4"/>.
/// </summary>
/// <param name="m">The <see cref="Matrix4x4"/>.</param>
/// <returns>The inverted <see cref="Matrix4x4"/> of the given <see cref="Matrix4x4"/>.</returns>
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
);
}
/// <summary>
/// Transposes the given <see cref="Matrix4x4"/>.
/// </summary>
/// <param name="m">The <see cref="Matrix4x4"/>.</param>
/// <returns>The transposed <see cref="Matrix4x4"/> of the given <see cref="Matrix4x4"/>.</returns>
public static Matrix4x4 Transpose(Matrix4x4 m) => new(
m.M11, m.M21, m.M31, m.M41,
m.M12, m.M22, m.M32, m.M42,
m.M13, m.M23, m.M33, m.M43,
m.M14, m.M24, m.M34, m.M44
);
public static Matrix4x4 CreateTranslation(Vector3D position) => new(
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,10 +251,10 @@ public readonly struct Matrix4x4(
float c = Math.Cos(radians);
float s = Math.Sin(radians);
return new Matrix4x4(
return new(
1f, 0f, 0f, 0f,
0f, c, s, 0f,
0f, -s, c, 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
/// <inheritdoc cref="Matrix4x4.Determinant(Matrix4x4)" />
public static float Determinant(this Matrix4x4 matrix) => Matrix4x4.Determinant(matrix);
/// <inheritdoc cref="Matrix4x4.Invert(Matrix4x4)" />
public static Matrix4x4 Invert(this Matrix4x4 matrix) => Matrix4x4.Invert(matrix);
/// <inheritdoc cref="Matrix4x4.Transpose(Matrix4x4)" />
public static Matrix4x4 Transpose(this Matrix4x4 matrix) => Matrix4x4.Transpose(matrix);
/// <inheritdoc cref="Matrix4x4.CreateTranslation(Vector3D)" />
public static Matrix4x4 ApplyTranslation(this Matrix4x4 matrix, Vector3D translation) => matrix * Matrix4x4.CreateTranslation(translation);
/// <inheritdoc cref="Matrix4x4.CreateScale(Vector3D)" />
public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, Vector3 scale) => matrix * Matrix4x4.CreateScale(scale);
public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, Vector3D scale) => matrix * Matrix4x4.CreateScale(scale);
/// <inheritdoc cref="Matrix4x4.CreateScale(float)" />
public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, float scale) => matrix * Matrix4x4.CreateScale(scale);
/// <inheritdoc cref="Matrix4x4.CreateRotationZ(float)" />
public static Matrix4x4 ApplyRotationX(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationX(radians);
@@ -280,15 +417,23 @@ public static class Matrix4x4Extensions
public static Matrix4x4 ApplyRotation(this Matrix4x4 matrix, Quaternion quaternion) => matrix * Matrix4x4.CreateRotation(quaternion);
/// <inheritdoc cref="Matrix4x4.CreateLookMatrix( Vector3D, Vector3D)" />
public static Matrix4x4 ApplyLookRotationTo(this Matrix4x4 matrix, Vector3D forward, Vector3 up) => matrix * Matrix4x4.CreateLookMatrix(forward, up);
public static Matrix4x4 ApplyLookRotationTo(this Matrix4x4 matrix, Vector3D forward, Vector3D up) => matrix * Matrix4x4.CreateLookMatrix(forward, up);
/// <inheritdoc cref="Matrix4x4.CreateLookMatrix(Vector3D, Vector3D, Vector3D)" />
public static Matrix4x4 CreateLookMatrixTo(this Vector3D from, Vector3D to, Vector3 up) => Matrix4x4.CreateLookMatrix(from, to, up);
public static Matrix4x4 CreateLookMatrixTo(this Vector3D from, Vector3D to, Vector3D up) => Matrix4x4.CreateLookMatrix(from, to, up);
/// <inheritdoc cref="Matrix4x4.CreatePerspectiveFieldOfView(float, float, float, float)" />
public static Matrix4x4 ApplyPerspectiveFieldOfView(this Matrix4x4 matrix, float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane)
=> matrix * Matrix4x4.CreatePerspectiveFieldOfView(fieldOfViewInRadians, aspectRatio, nearPlane, farPlane);
/// <inheritdoc cref="Matrix4x4.CreateOrthographicView(float, float, float, float)" />
public static Matrix4x4 ApplyOrthographicView(this Matrix4x4 matrix, float width, float height, float nearPlane = -1f, float farPlane = 1f)
=> matrix * Matrix4x4.CreateOrthographicView(width, height, nearPlane, farPlane);
/// <inheritdoc cref="Matrix4x4.CreateOrthographicViewCentered(float, float, float, float)" />
public static Matrix4x4 ApplyOrthographicViewCentered(this Matrix4x4 matrix, float width, float height, float nearPlane = -1f, float farPlane = 1f)
=> matrix * Matrix4x4.CreateOrthographicViewCentered(width, height, nearPlane, farPlane);
/// <inheritdoc cref="Matrix4x4.ToRightHanded(Matrix4x4) />
public static Matrix4x4 ToRightHanded(this Matrix4x4 matrix) => Matrix4x4.ToRightHanded(matrix);
}

View File

@@ -20,7 +20,7 @@ public class Shape2D(List<Vector2D> vertices) : IEnumerable<Vector2D>
public Event<Shape2D> OnShapeUpdated { get; } = new();
private List<Vector2D> _vertices = vertices;
private readonly List<Vector2D> _vertices = vertices;
/// <summary>
/// Gets the vertices of the <see cref="Shape2D"/>.

View File

@@ -82,11 +82,19 @@ public readonly struct Vector2D(float x, float y) : IEquatable<Vector2D>
public static bool operator ==(Vector2D left, Vector2D right) => left.X == right.X && left.Y == right.Y;
public static bool operator !=(Vector2D left, Vector2D right) => left.X != right.X || left.Y != right.Y;
public static 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);
/// <summary>
/// Calculates the length of the <see cref="Vector2D"/>.

View File

@@ -74,9 +74,12 @@ public readonly struct Vector2DInt(int x, int y) : IEquatable<Vector2DInt>
public static bool operator ==(Vector2DInt left, Vector2DInt right) => left.X == right.X && left.Y == right.Y;
public static bool operator !=(Vector2DInt left, Vector2DInt right) => left.X != right.X || left.Y != right.Y;
public static 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());
/// <summary>
/// Calculates the length of the <see cref="Vector2DInt"/>.
/// </summary>

View File

@@ -92,11 +92,19 @@ public readonly struct Vector3D(float x, float y, float z) : IEquatable<Vector3D
public static bool operator ==(Vector3D left, Vector3D right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z;
public static bool operator !=(Vector3D left, Vector3D right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z;
public static 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);
/// <summary>
/// Calculates the length of the <see cref="Vector3D"/>.

View File

@@ -84,9 +84,12 @@ public readonly struct Vector3DInt(int x, int y, int z) : IEquatable<Vector3DInt
public static bool operator ==(Vector3DInt left, Vector3DInt right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z;
public static bool operator !=(Vector3DInt left, Vector3DInt right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z;
public static 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());
/// <summary>
/// Calculates the length of the <see cref="Vector3DInt"/>.
/// </summary>

View File

@@ -90,9 +90,20 @@ public readonly struct Vector4D(float x, float y, float z, float w) : IEquatable
public static bool operator ==(Vector4D left, Vector4D right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z && left.W == right.W;
public static bool operator !=(Vector4D left, Vector4D right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z || left.W != right.W;
public static 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);
/// <summary>
/// Calculates the length of the <see cref="Vector4D"/>.
/// </summary>

View File

@@ -6,32 +6,29 @@ public class StateEnable : IStateEnable
public Event<IHasEntity> OnEntityAssigned { get; } = new();
public Event<IAssignable>? OnUnassigned { get; } = new();
private bool _enabled = true;
private IEntity _entity = null!;
public IEntity Entity => _entity;
public IEntity Entity { get; private set; } = null!;
public bool Enabled
{
get => _enabled;
get;
set
{
if (value == _enabled)
if (value == field)
return;
bool previousState = _enabled;
_enabled = value;
bool previousState = field;
field = value;
OnEnabledChanged?.Invoke(this, new(previousState));
}
}
} = true;
protected virtual void OnAssign(IEntity entity) { }
public bool Assign(IEntity entity)
{
if (_entity is not null && _entity.IsInitialized)
if (Entity is not null && Entity.IsInitialized)
return false;
_entity = entity;
Entity = entity;
OnAssign(entity);
OnEntityAssigned?.Invoke(this);
return true;
@@ -39,10 +36,10 @@ public class StateEnable : IStateEnable
public bool Unassign()
{
if (_entity is null)
if (Entity is null)
return false;
_entity = null!;
Entity = null!;
OnUnassigned?.Invoke(this);
return true;
}

View File

@@ -9,65 +9,56 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private readonly ActiveBehaviourCollectorOrdered<int, IEnterUniverse> enterUniverses = new(GetPriority(), SortByAscendingPriority());
private readonly ActiveBehaviourCollectorOrdered<int, IExitUniverse> exitUniverses = new(GetPriority(), SortByAscendingPriority());
private readonly List<IEnterUniverse> toCallEnterUniverses = new(32);
private readonly List<IExitUniverse> toCallExitUniverses = new(32);
private bool isInitialCollectionDone = false;
private readonly FastListOrdered<int, IEnterUniverse> 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<IEnterUniverse> sender, IBehaviourCollector<IEnterUniverse>.BehaviourCollectedArguments args)
{
if (!isInitialCollectionDone)
{
toCallEnterUniverses.Add(args.BehaviourCollected);
return;
}
args.BehaviourCollected.EnterUniverse(Universe);
}
private void OnExitUniverseRemoved(IBehaviourCollector<IExitUniverse> sender, IBehaviourCollector<IExitUniverse>.BehaviourRemovedArguments args)
=> args.BehaviourRemoved.ExitUniverse(Universe);
public UniverseEntranceManager()
{
enterUniverses.OnCollected.AddListener(OnEnterUniverseCollected);
exitUniverses.OnRemoved.AddListener(OnExitUniverseRemoved);
}
}

View File

@@ -23,7 +23,6 @@ public class Universe : BaseEntity, IUniverse
private readonly Event<IUniverseObject, IUniverseObject.ExitedUniverseArguments>.EventHandler delegateOnUniverseObjectExitedUniverse = null!;
private readonly FastList<IUniverseObject> _universeObjects = new(Constants.UNIVERSE_OBJECTS_SIZE_INITIAL);
private float _timeScale = 1f;
public Universe()
{
@@ -37,18 +36,18 @@ public class Universe : BaseEntity, IUniverse
public UniverseTime UnscaledTime { get; private set; } = new();
public float TimeScale
{
get => _timeScale;
get;
set
{
value = value.Max(0f);
if (value == _timeScale)
if (value == field)
return;
float previousTimeScale = _timeScale;
_timeScale = value;
float previousTimeScale = field;
field = value;
OnTimeScaleChanged?.Invoke(this, new(previousTimeScale));
}
}
} = 1f;
public void Register(IUniverseObject universeObject)
{

View File

@@ -15,41 +15,37 @@ public class UniverseObject : BaseEntity, IUniverseObject
public Event<INameable, INameable.NameChangedArguments> OnNameChanged { get; } = new();
public Event<IHasBehaviourController> OnBehaviourControllerAssigned { get; } = new();
private string _name = nameof(UniverseObject);
private IUniverse _universe = null!;
private IBehaviourController _behaviourController = null!;
private bool _isActive = false;
private readonly FastList<IUniverseObject> _children = [];
private IUniverseObject? _parent = null;
public IBehaviourController BehaviourController { get; private set; } = null!;
public IUniverse Universe { get; private set; } = null!;
public bool IsActive { get; private set; } = false;
public IReadOnlyList<IUniverseObject> Children => _children;
public IBehaviourController BehaviourController => _behaviourController;
public IUniverse Universe => _universe;
public bool IsInUniverse => _universe is not null;
public bool IsActive => _isActive;
public bool IsInUniverse => Universe is not null;
public string Name
{
get => _name;
get;
set
{
if (value == _name) return;
if (value == field) return;
string previousName = _name;
_name = value;
string previousName = field;
field = value;
OnNameChanged?.Invoke(this, new(previousName));
}
}
} = nameof(UniverseObject);
public IUniverseObject? Parent
{
get => _parent;
get;
set
{
if (value == this)
throw new Exceptions.AssignFailedException($"{Name} can not parent itself");
if (_parent == value)
if (field == value)
return;
IUniverseObject? previousParent = Parent;
@@ -59,7 +55,7 @@ public class UniverseObject : BaseEntity, IUniverseObject
previousParent.OnActiveChanged.RemoveListener(OnParentActiveChanged);
}
_parent = value;
field = value;
if (value is not null)
{
@@ -73,7 +69,7 @@ public class UniverseObject : BaseEntity, IUniverseObject
UpdateActive();
OnParentChanged?.Invoke(this, new(previousParent, value));
}
}
} = null;
protected virtual void OnEnteringUniverse(IUniverse universe) { }
bool IUniverseObject.EnterUniverse(IUniverse universe)
@@ -81,7 +77,7 @@ public class UniverseObject : BaseEntity, IUniverseObject
if (IsInUniverse)
return false;
_universe = universe;
Universe = universe;
UpdateActive();
OnEnteringUniverse(universe);
OnEnteredUniverse?.Invoke(this, new(universe));
@@ -91,11 +87,11 @@ public class UniverseObject : BaseEntity, IUniverseObject
protected virtual void OnExitingUniverse(IUniverse universe) { }
bool IUniverseObject.ExitUniverse()
{
if (!IsInUniverse || _universe is not IUniverse universe)
if (!IsInUniverse || Universe is not IUniverse universe)
return false;
OnExitingUniverse(universe);
_universe = null!;
Universe = null!;
OnExitedUniverse?.Invoke(this, new(universe));
return true;
}
@@ -125,7 +121,7 @@ public class UniverseObject : BaseEntity, IUniverseObject
if (IsInitialized)
return false;
_behaviourController = behaviourController;
BehaviourController = behaviourController;
OnAssign(behaviourController);
OnBehaviourControllerAssigned?.Invoke(this);
return true;
@@ -145,7 +141,7 @@ public class UniverseObject : BaseEntity, IUniverseObject
private void UpdateActive()
{
bool previousActive = IsActive;
_isActive = StateEnable.Enabled && (Parent?.IsActive ?? true);
IsActive = StateEnable.Enabled && (Parent?.IsActive ?? true);
if (previousActive != IsActive)
OnActiveChanged?.Invoke(this, new(previousActive));
@@ -160,12 +156,12 @@ public class UniverseObject : BaseEntity, IUniverseObject
protected override void InitializeInternal()
{
base.InitializeInternal();
_behaviourController ??= Factory.BehaviourControllerFactory.Instantiate(this);
_behaviourController.Initialize();
BehaviourController ??= Factory.BehaviourControllerFactory.Instantiate(this);
BehaviourController.Initialize();
}
public UniverseObject()
{
_name = GetType().Name;
Name = GetType().Name;
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Engine.Systems.Network</RootNamespace>

View File

@@ -92,8 +92,9 @@ public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicator
{
while (true)
{
Manager.PollEvents();
Thread.Sleep(1);
try { Manager.PollEvents(); }
catch (Exception e) { logger?.LogException(this, e, force: true); }
}
}, cancellationToken);
}

View File

@@ -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<T>(Event<IConnection, T>.EventHandler callback)

View File

@@ -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();
}
}

View File

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

View File

@@ -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();
}

View File

@@ -7,81 +7,99 @@ namespace Engine.Integration.MonoGame;
public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw
{
public Event<MonoGameCamera2D> OnMatrixTransformChanged { get; } = new();
public Event<MonoGameCamera2D> OnViewMatrixChanged { get; } = new();
public Event<MonoGameCamera2D> OnProjectionMatrixChanged { get; } = new();
public Event<MonoGameCamera2D> OnViewportChanged { get; } = new();
public Event<MonoGameCamera2D> OnZoomChanged { get; } = new();
private Matrix _matrixTransform = Matrix.Identity;
private Viewport _viewport = default;
private float _zoom = 1f;
public GraphicsDeviceManager Graphics { get; private set; } = null!;
public ITransform2D Transform { get; private set; } = null!;
public Matrix MatrixTransform
public Matrix4x4 ProjectionMatrix
{
get => _matrixTransform;
set
get;
private set
{
if (_matrixTransform == value)
if (field == value)
return;
_matrixTransform = value;
OnMatrixTransformChanged.Invoke(this);
}
field = value;
OnProjectionMatrixChanged.Invoke(this);
}
} = Matrix4x4.Identity;
public Vector2D Position
public Matrix4x4 ViewMatrix
{
get => Transform.Position;
set => Transform.Position = value;
get;
private set
{
if (field == value)
return;
field = value;
OnViewMatrixChanged.Invoke(this);
}
} = Matrix4x4.Identity;
public Viewport Viewport
{
get => _viewport;
get;
set
{
if (_viewport.Equals(value))
if (field.Equals(value))
return;
_viewport = value;
field = value;
OnViewportChanged.Invoke(this);
}
}
} = default;
public float Zoom
{
get => _zoom;
get;
set
{
float newValue = Math.Max(0.1f, value);
if (_zoom == newValue)
if (field == newValue)
return;
_zoom = newValue;
field = newValue;
OnZoomChanged.Invoke(this);
}
}
public float Rotation
{
get => Transform.Rotation;
set => Transform.Rotation = value;
}
} = 1f;
// 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.Width - 1f;
float y = 1f - 2f * screenPosition.Y / Viewport.Height;
Vector4D normalizedCoordinates = new(x, y, 0f, 1f);
Matrix4x4 invertedViewProjectionMatrix = (ProjectionMatrix * ViewMatrix).Inverse;
Vector4D worldPosition = invertedViewProjectionMatrix * normalizedCoordinates;
if (worldPosition.W != 0f)
worldPosition /= worldPosition.W;
return new(worldPosition.X, worldPosition.Y);
}
public Vector2D WorldToScreenPosition(Vector2D worldPosition)
{
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.Width;
float screenY = (1f - clip.Y) * .5f * Viewport.Height;
return new(screenX, screenY);
}
public void LastActiveFrame() => Transform = null!;
@@ -94,11 +112,11 @@ public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFr
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.Width, Viewport.Height);
ViewMatrix = Matrix4x4.Identity
.ApplyScale(Transform.Scale.X.Max(Transform.Scale.Y))
.ApplyScale(Zoom)
.ApplyRotationZ(-Transform.Rotation * Math.DegreeToRadian)
.ApplyTranslation(new Vector3D(-Transform.Position.X, -Transform.Position.Y, 0f));
}
}

View File

@@ -15,54 +15,50 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr
public Event<ICamera3D, ICamera3D.FarPlaneChangedArguments> OnFarPlaneChanged { get; } = new();
public Event<ICamera3D, ICamera3D.FieldOfViewChangedArguments> OnFieldOfViewChanged { get; } = new();
private Matrix _view = Matrix.Identity;
private Matrix _projection = Matrix.Identity;
private Viewport _viewport = default;
private float _nearPlane = 0.01f;
private float _farPlane = 100f;
private float _fieldOfView = Math.DegreeToRadian * 70f;
private float fieldOfView = Math.DegreeToRadian * 70f;
private bool isRecalculationNeeded = true;
public GraphicsDeviceManager Graphics { get; private set; } = null!;
public ITransform3D Transform { get; private set; } = null!;
public Matrix View
public Matrix4x4 ViewMatrix
{
get => _view;
get;
set
{
if (_view == value)
if (field == value)
return;
Matrix previousView = _view;
_view = value;
Matrix4x4 previousView = field;
field = value;
OnViewChanged.Invoke(this, new(previousView));
}
}
public Matrix Projection
} = Matrix4x4.Identity;
public Matrix4x4 ProjectionMatrix
{
get => _projection;
get;
set
{
if (_projection == value)
if (field == value)
return;
Matrix previousProjection = _projection;
_projection = value;
Matrix4x4 previousProjection = field;
field = value;
OnProjectionChanged.Invoke(this, new(previousProjection));
}
}
} = Matrix4x4.Identity;
public Viewport Viewport
{
get => _viewport;
get;
set
{
if (_viewport.Equals(value))
if (field.Equals(value))
return;
Viewport previousViewport = _viewport;
_viewport = value;
Viewport previousViewport = field;
field = value;
SetForRecalculation();
OnViewportChanged.Invoke(this, new(previousViewport));
}
@@ -70,40 +66,40 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr
public float NearPlane
{
get => _nearPlane;
get;
set
{
float previousNearPlane = _nearPlane;
_nearPlane = value.Max(0.0001f);
float previousNearPlane = field;
field = value.Max(0.0001f);
SetForRecalculation();
OnNearPlaneChanged.Invoke(this, new(previousNearPlane));
}
}
} = 0.01f;
public float FarPlane
{
get => _farPlane;
get;
set
{
float previousFarPlane = _farPlane;
_farPlane = value.Max(NearPlane);
float previousFarPlane = field;
field = value.Max(NearPlane);
SetForRecalculation();
OnFarPlaneChanged.Invoke(this, new(previousFarPlane));
}
}
} = 100f;
public float FieldOfView
{
get => _fieldOfView * Math.RadianToDegree;
get => fieldOfView * Math.RadianToDegree;
set
{
value = value.Max(0.1f) * Math.DegreeToRadian;
if (_fieldOfView == value)
if (fieldOfView == value)
return;
float previousFieldOfView = _fieldOfView;
_fieldOfView = value;
float previousFieldOfView = fieldOfView;
fieldOfView = value;
SetForRecalculation();
OnFieldOfViewChanged.Invoke(this, new(previousFieldOfView));
}
@@ -115,14 +111,17 @@ 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 = Viewport.Unproject(nearPoint, projection, view, Matrix.Identity);
Vector3 worldFar = 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) => Viewport.Project(worldPosition.ToVector3(), ProjectionMatrix.ToXnaMatrix(), ViewMatrix.ToXnaMatrix(), Matrix.Identity).ToVector3D();
public void LastActiveFrame() => Transform.OnTransformUpdated.RemoveListener(SetDirtyOnTransformUpdate);
public void FirstActiveFrame()
@@ -154,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,
@@ -167,18 +166,18 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFr
private void CalculateProjection()
{
float yScale = 1f / (float)Math.Tan(_fieldOfView / 2f);
float yScale = 1f / (float)Math.Tan(fieldOfView / 2f);
float xScale = yScale / Viewport.AspectRatio;
Projection = new Matrix(
ProjectionMatrix = new Matrix4x4(
xScale, 0, 0, 0,
0, yScale, 0, 0,
0, 0, _farPlane / (_farPlane - _nearPlane), 1,
0, 0, -_nearPlane * _farPlane / (_farPlane - _nearPlane), 0
0, 0, FarPlane / (FarPlane - NearPlane), 1,
0, 0, -NearPlane * FarPlane / (FarPlane - NearPlane), 0
);
}
public readonly record struct ViewChangedArguments(Matrix PreviousView);
public readonly record struct ProjectionChangedArguments(Matrix PreviousProjection);
public readonly record struct ViewChangedArguments(Matrix4x4 PreviousView);
public readonly record struct ProjectionChangedArguments(Matrix4x4 PreviousProjection);
public readonly record struct ViewportChangedArguments(Viewport PreviousViewport);
}

View File

@@ -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();

View File

@@ -26,7 +26,7 @@ public class SpriteBatcher : Behaviour, IFirstFrameUpdate, IDraw
public void Draw()
{
spriteBatch.Begin(transformMatrix: camera2D.MatrixTransform);
spriteBatch.Begin(transformMatrix: camera2D.ViewMatrix.ToXnaMatrix());
for (int i = 0; i < drawableSprites.Count; i++)
drawableSprites[i].Draw(spriteBatch);
spriteBatch.End();

View File

@@ -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<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private TriangleBatch triangleBatch = null!;
private MonoGameCamera2D camera2D = null!;
private readonly ActiveBehaviourCollectorOrdered<int, IDrawableTriangle> drawableShapes = new(GetPriority(), SortByAscendingPriority());
public void FirstActiveFrame()
{
MonoGameWindowContainer windowContainer = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>();
camera2D = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameCamera2D>();
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();
}

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Engine.Integration.MonoGame</RootNamespace>

View File

@@ -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)
};

View File

@@ -2,22 +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 GraphicsDevice graphicsDevice = null!;
private VertexBuffer vertexBuffer = default!;
private readonly VertexPositionColor[] vertices = new VertexPositionColor[1024];
private int verticesIndex = 0;
private Matrix _view;
private Matrix _projection;
private readonly BasicEffect basicEffect;
private Matrix view = Matrix.Identity;
private Matrix projection = Matrix.Identity;
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<ICamera>();
GraphicsDevice graphicsDevice = Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.GraphicsDevice;
this.graphicsDevice = graphicsDevice;
basicEffect = new(graphicsDevice);
basicEffect.VertexColorEnabled = true;
@@ -29,9 +36,9 @@ 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();
Vector2 A = triangle.A.ToVector2();
Vector2 B = triangle.B.ToVector2();
Vector2 C = triangle.C.ToVector2();
Color color = colorRGBA.ToColor();
vertices[verticesIndex++] = new(new(A.X, A.Y, 0f), color);
@@ -39,20 +46,12 @@ public class TriangleBatch : ITriangleBatch
vertices[verticesIndex++] = new(new(C.X, C.Y, 0f), color);
}
public void Begin(Matrix? view = null, Matrix? projection = null)
{
if (view != null)
_view = view.Value;
else
_view = Matrix.Identity;
if (projection != null)
_projection = projection.Value;
else
public void Begin(Matrix4x4? view = null, Matrix4x4? projection = null)
{
Viewport viewport = graphicsDevice.Viewport;
_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();
@@ -63,8 +62,8 @@ public class TriangleBatch : ITriangleBatch
return;
graphicsDevice.RasterizerState = rasterizerState;
basicEffect.Projection = _projection;
basicEffect.View = _view;
basicEffect.Projection = projection;
basicEffect.View = view;
vertexBuffer.SetData(vertices);
graphicsDevice.SetVertexBuffer(vertexBuffer);

View File

@@ -1,4 +1,4 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Engine.Core;

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Engine.Integration.Yaml</RootNamespace>

View File

@@ -16,19 +16,8 @@ public abstract class Collider2DBase : Behaviour2D, ICollider2D
private readonly Event<IUniverseObject, IUniverseObject.ParentChangedArguments>.EventHandler delegateUpdateRigidBody2D = null!;
protected bool NeedsRecalculation { get; set; } = true;
protected IRigidBody2D? _rigidBody2D = null;
protected Collider2DBase()
{
delegateOnBehaviourAddedToController = OnBehaviourAddedToController;
delegateOnBehaviourRemovedFromController = OnBehaviourRemovedFromController;
delegateSetNeedsRecalculationFromPosition = SetNeedsRecalculationFromPosition;
delegateSetNeedsRecalculationFromRotation = SetNeedsRecalculationFromRotation;
delegateSetNeedsRecalculationFromScale = SetNeedsRecalculationFromScale;
delegateUpdateRigidBody2D = UpdateRigidBody2D;
}
public IRigidBody2D? RigidBody2D => _rigidBody2D;
public IRigidBody2D? RigidBody2D { get; protected set; } = null;
public bool IsTrigger { get; set; } = false;
public void Recalculate()
@@ -46,7 +35,8 @@ public abstract class Collider2DBase : Behaviour2D, ICollider2D
{
base.OnInitialize();
BehaviourController.TryGetBehaviourInParent(out _rigidBody2D);
BehaviourController.TryGetBehaviourInParent(out IRigidBody2D? foundRigidBody2D);
RigidBody2D = foundRigidBody2D;
BehaviourController.OnBehaviourAdded.AddListener(delegateOnBehaviourAddedToController);
BehaviourController.OnBehaviourRemoved.AddListener(delegateOnBehaviourRemovedFromController);
@@ -59,19 +49,20 @@ public abstract class Collider2DBase : Behaviour2D, ICollider2D
private void UpdateRigidBody2D(IUniverseObject sender, IUniverseObject.ParentChangedArguments args)
{
BehaviourController.TryGetBehaviourInParent(out _rigidBody2D);
BehaviourController.TryGetBehaviourInParent(out IRigidBody2D? foundRigidBody2D);
RigidBody2D = foundRigidBody2D;
}
private void OnBehaviourAddedToController(IBehaviourController sender, IBehaviourController.BehaviourAddedArguments args)
{
if (args.BehaviourAdded is IRigidBody2D rigidBody)
_rigidBody2D = rigidBody;
RigidBody2D = rigidBody;
}
private void OnBehaviourRemovedFromController(IBehaviourController sender, IBehaviourController.BehaviourRemovedArguments args)
{
if (args.BehaviourRemoved is IRigidBody2D)
_rigidBody2D = null;
RigidBody2D = null;
}
private void SetNeedsRecalculationFromPosition(ITransform2D sender, ITransform2D.PositionChangedArguments args) => NeedsRecalculation = true;
@@ -93,4 +84,14 @@ public abstract class Collider2DBase : Behaviour2D, ICollider2D
public void Detect(CollisionDetectionInformation collisionDetectionInformation) => OnCollisionDetected?.Invoke(this, collisionDetectionInformation);
public void Resolve(CollisionDetectionInformation collisionDetectionInformation) => OnCollisionResolved?.Invoke(this, collisionDetectionInformation);
public void Trigger(ICollider2D initiator) => OnTriggered?.Invoke(this, initiator);
protected Collider2DBase()
{
delegateOnBehaviourAddedToController = OnBehaviourAddedToController;
delegateOnBehaviourRemovedFromController = OnBehaviourRemovedFromController;
delegateSetNeedsRecalculationFromPosition = SetNeedsRecalculationFromPosition;
delegateSetNeedsRecalculationFromRotation = SetNeedsRecalculationFromRotation;
delegateSetNeedsRecalculationFromScale = SetNeedsRecalculationFromScale;
delegateUpdateRigidBody2D = UpdateRigidBody2D;
}
}

View File

@@ -4,20 +4,18 @@ namespace Engine.Physics2D;
public class Collider2DCircle : Collider2DBase, ICircleCollider2D
{
private Circle _circleLocal = Circle.UnitCircle;
public Circle CircleWorld { get; protected set; } = Circle.UnitCircle;
public Circle CircleLocal
{
get => _circleLocal;
get;
set
{
_circleLocal = value;
field = value;
NeedsRecalculation = true;
}
}
} = Circle.UnitCircle;
public override void CalculateCollider() => CircleWorld = Transform.Transform(_circleLocal);
public override void CalculateCollider() => CircleWorld = Transform.Transform(CircleLocal);
public Collider2DCircle() { }
public Collider2DCircle(Circle circle) => CircleLocal = circle;

View File

@@ -4,25 +4,19 @@ namespace Engine.Physics2D;
public class Collider2DShape : Collider2DBase, IShapeCollider2D
{
public Shape2D ShapeWorld { get => _shapeWorld; protected set => _shapeWorld = value; }
public Shape2D ShapeWorld { get; protected set; } = Shape2D.Square;
public Shape2D ShapeLocal
{
get => _shapeLocal;
get;
set
{
_shapeLocal = value;
field = value;
NeedsRecalculation = true;
}
}
} = Shape2D.Square;
private Shape2D _shapeWorld = Shape2D.Square;
private Shape2D _shapeLocal = Shape2D.Square;
public override void CalculateCollider() => ShapeLocal.Transform(Transform, _shapeWorld);
public override void CalculateCollider() => ShapeLocal.Transform(Transform, ShapeWorld);
public Collider2DShape() { }
public Collider2DShape(Shape2D shape)
{
ShapeLocal = shape;
}
public Collider2DShape(Shape2D shape) { ShapeLocal = shape; }
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Engine.Physics2D</RootNamespace>

View File

@@ -10,8 +10,6 @@ public class PhysicsEngine2D : Behaviour, IEnterUniverse, IExitUniverse, IPreUpd
public Event<IPhysicsEngine2D, float> OnPhysicsStep { get; } = new();
private float physicsTicker = 0f;
private int _iterationPerStep = 1;
private float _iterationPeriod = 1f / 60f;
protected readonly ICollisionDetector2D collisionDetector = null!;
protected readonly ICollisionResolver2D collisionResolver = null!;
@@ -32,8 +30,8 @@ public class PhysicsEngine2D : Behaviour, IEnterUniverse, IExitUniverse, IPreUpd
private readonly ListPool<IPhysicsIteration> physicsIterationPool = new();
private readonly ListPool<IPostPhysicsUpdate> postPhysicsUpdatePool = new();
public int IterationPerStep { get => _iterationPerStep; set => _iterationPerStep = value < 1 ? 1 : value; }
public float IterationPeriod { get => _iterationPeriod; set => _iterationPeriod = value.Max(0.0001f); }
public int IterationPerStep { get; set => field = value < 1 ? 1 : value; } = 1;
public float IterationPeriod { get; set => field = value.Max(0.0001f); } = 1f / 60f;
public RaycastResult? Raycast(Ray2D ray, float length = float.MaxValue)
{

View File

@@ -14,9 +14,6 @@ public class PhysicsEngine2DStandalone : IPhysicsEngine2D
private readonly List<IRigidBody2D> rigidBodies = new(32);
private readonly List<ICollider2D> colliders = new(64);
private int _iterationCount = 1;
private readonly ICollisionDetector2D collisionDetector = null!;
private readonly ICollisionResolver2D collisionResolver = null!;
private readonly IRaycastResolver2D raycastResolver = null!;
@@ -27,7 +24,7 @@ public class PhysicsEngine2DStandalone : IPhysicsEngine2D
private readonly ListPool<IPhysicsIteration> physicsIterationPool = new();
private readonly ListPool<IPostPhysicsUpdate> postPhysicsUpdatePool = new();
public int IterationPerStep { get => _iterationCount; set => _iterationCount = value < 1 ? 1 : value; }
public int IterationPerStep { get; set => field = value.Max(1); } = 1;
public void AddRigidBody(IRigidBody2D rigidBody)
{

View File

@@ -5,7 +5,6 @@ namespace Engine.Physics2D;
public class RigidBody2D : Behaviour2D, IRigidBody2D
{
private const float LOWEST_ALLOWED_MASS = 0.00001f;
private float _mass = 1f;
public IPhysicsMaterial2D Material { get; set; } = new PhysicsMaterial2DDefault();
@@ -13,5 +12,5 @@ public class RigidBody2D : Behaviour2D, IRigidBody2D
public float AngularVelocity { get; set; } = 0f;
public bool IsStatic { get; set; } = false;
public float Mass { get => _mass; set => _mass = Core.Math.Max(value, LOWEST_ALLOWED_MASS); }
public float Mass { get; set => field = Math.Max(value, LOWEST_ALLOWED_MASS); } = 1f;
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Engine.Systems</RootNamespace>

View File

@@ -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<Triangle> 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; }
}

View File

@@ -1,6 +1,6 @@
using Engine.Core;
namespace Engine.Integration.MonoGame;
namespace Engine.Systems.Graphics;
public interface IDrawableTriangle : IBehaviour
{

View File

@@ -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();
}

View File

@@ -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<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private readonly BehaviourCollector<ITriangleBatch> triangleBatches = new();
private readonly ActiveBehaviourCollectorOrdered<int, IDrawableTriangle> 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();
}
}

View File

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

View File

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

View File

@@ -10,18 +10,28 @@ namespace Engine.Systems.Network;
/// <summary>
/// Intermediary manager that looks up in it's hierarchy for a <see cref="INetworkCommunicator"/> to route/broadcast it's received packets to their destinations.
/// </summary>
/// TODO: I urgently need to add proper comments on this manager, I don't exactly remember the state I was in when I was writing it.
/// 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<Type, Dictionary<Type, List<MethodInfo>>> clientPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientBroadcastPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverBroadcastPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<string, object>> clientPacketRouters = [];
private readonly Dictionary<Type, Dictionary<string, object>> serverPacketRouters = [];
private readonly Dictionary<Type, Dictionary<string, object>> clientBroadcastPacketRouters = [];
private readonly Dictionary<Type, Dictionary<string, object>> serverBroadcastPacketRouters = [];
private readonly List<(Type packetType, Delegate @delegate)> packetRetrievalDelegates = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientEntityPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverEntityPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<string, object>> clientEntityPacketRouters = [];
private readonly Dictionary<Type, Dictionary<string, object>> serverEntityPacketRouters = [];
private readonly List<(Type packetType, Delegate @delegate)> broadcastPacketRetrievalDelegates = [];
private readonly List<(Type packetType, Delegate @delegate)> entityPacketRetrievalDelegates = [];
private readonly Dictionary<Type, MethodInfo> clearRoutesMethods = [];
private readonly Dictionary<Type, MethodInfo> registerPacketListenersMethods = [];
private readonly Dictionary<Type, MethodInfo> registerBroadcastPacketListenersMethods = [];
private readonly Dictionary<Type, MethodInfo> registerEntityPacketListenersMethods = [];
private readonly Dictionary<string, INetworkEntity> _networkEntities = [];
public IReadOnlyDictionary<string, INetworkEntity> NetworkEntities => _networkEntities;
@@ -29,75 +39,66 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
private readonly BehaviourCollector<INetworkEntity> _networkEntityCollector = new();
public IBehaviourCollector<INetworkEntity> NetworkEntityCollector => _networkEntityCollector;
private INetworkCommunicator _networkCommunicator = null!;
public INetworkCommunicator NetworkCommunicator
{
get => _networkCommunicator;
get;
set
{
if (_networkCommunicator == value)
if (field == value)
return;
INetworkCommunicator? previousCommunicator = _networkCommunicator;
_networkCommunicator = value;
INetworkCommunicator? previousCommunicator = field;
field = value;
if (previousCommunicator is not null) UnsubscribeCommunicatorMethods(previousCommunicator);
if (_networkCommunicator is not null) SubscribeCommunicatorMethods(_networkCommunicator);
}
if (previousCommunicator is not null) InvokeCommunicatorMethods(previousCommunicator, nameof(INetworkCommunicator.UnsubscribeFromPackets));
if (field is not null) InvokeCommunicatorMethods(field, nameof(INetworkCommunicator.SubscribeToPackets));
}
} = 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)
/// Used to delegate subscription and unsubscription methods on the <see cref="INetworkCommunicator"/> to <see cref="OnPacketReceived{T}(IConnection, T)"/>.
private void InvokeCommunicatorMethods(INetworkCommunicator networkCommunicator, string name)
{
MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator)
.GetMethod(nameof(INetworkCommunicator.UnsubscribeFromPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!;
.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!;
foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates)
/// Get unique entries by the packetType so we don't get duplicate calls to <see cref="OnPacketReceived{T}(IConnection, T)"/>
/// Because a class might have both <see cref="IPacketListenerClient{T}"/> and <see cref="IPacketListenerClientEntity{T}"/>
/// or <see cref="IPacketListenerServer{T}"/> and <see cref="IPacketListenerServerEntity{T}"/> together.
IEnumerable<(Type packetType, Delegate @delegate)> distinctRetrievalSubscriptionDelegates =
broadcastPacketRetrievalDelegates.Concat(entityPacketRetrievalDelegates).DistinctBy(pair => pair.packetType);
foreach ((Type packetType, Delegate @delegate) in distinctRetrievalSubscriptionDelegates)
{
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
private void OnPacketReceived<T>(IConnection sender, T entityDataPacket)
{
BroadcastPacket(sender, entityDataPacket);
if (entityDataPacket is IEntityNetworkPacket entityPacket)
RoutePacket(sender, entityDataPacket, entityPacket);
else
BroadcastPacket(sender, entityDataPacket);
}
private void RoutePacket<T>(IConnection sender, T entityDataPacket, IEntityNetworkPacket entityPacket)
{
if (NetworkCommunicator is INetworkCommunicatorClient)
RoutePacket(clientPacketRouters, entityPacket.EntityId, sender, entityDataPacket);
RoutePacket(clientEntityPacketRouters, entityPacket.EntityId, sender, entityDataPacket);
if (NetworkCommunicator is INetworkCommunicatorServer)
RoutePacket(serverPacketRouters, entityPacket.EntityId, sender, entityDataPacket);
RoutePacket(serverEntityPacketRouters, entityPacket.EntityId, sender, entityDataPacket);
}
private void BroadcastPacket<T>(IConnection sender, T entityDataPacket)
{
if (NetworkCommunicator is INetworkCommunicatorClient)
BroadcastPacket(clientPacketRouters, sender, entityDataPacket);
BroadcastPacket(clientBroadcastPacketRouters, sender, entityDataPacket);
if (NetworkCommunicator is INetworkCommunicatorServer)
BroadcastPacket(serverPacketRouters, sender, entityDataPacket);
BroadcastPacket(serverBroadcastPacketRouters, sender, entityDataPacket);
}
private static void BroadcastPacket<T>(
private void BroadcastPacket<T>(
Dictionary<Type, Dictionary<string, object>> packetRouters,
IConnection sender,
T entityDataPacket)
@@ -112,7 +113,7 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
}
}
private static void RoutePacket<T>(
private void RoutePacket<T>(
Dictionary<Type, Dictionary<string, object>> packetRouters,
string entityId,
IConnection sender,
@@ -134,13 +135,12 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
INetworkEntity behaviour,
Dictionary<Type, Dictionary<string, object>> packetRouters,
Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods,
NetworkType networkType)
NetworkType networkType, Dictionary<Type, MethodInfo> registerPacketListenersMethods)
{
if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary<Type, List<MethodInfo>>? arrivalMethods))
return;
foreach ((Type packetType, List<MethodInfo> methods) in arrivalMethods)
foreach (MethodInfo receiveMethod in methods)
foreach (Type packetType in arrivalMethods.Keys)
{
if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers))
{
@@ -148,18 +148,20 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
packetRouters.Add(packetType, routers);
}
object packetListenerEvent = CreateEventAndRegister(packetType, behaviour, networkType);
object packetListenerEvent =
CreateEventAndRegister(packetType, behaviour, networkType, registerPacketListenersMethods);
routers.Add(behaviour.Id, packetListenerEvent);
}
}
private object CreateEventAndRegister(Type packetType, INetworkEntity behaviour, NetworkType networkType)
private object CreateEventAndRegister(Type packetType, INetworkEntity behaviour, NetworkType networkType, Dictionary<Type, MethodInfo> 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;
@@ -177,6 +179,19 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
}
}
private static void RegisterEntityPacketListenerEvent<T>(
INetworkEntity behaviour,
Event<IConnection, T> packetListenerEvent,
NetworkType networkType
) where T : IEntityNetworkPacket
{
switch (networkType)
{
case NetworkType.Client: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerClientEntity<T>)behaviour).OnEntityClientPacketArrived(sender, packet)); break;
case NetworkType.Server: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerServerEntity<T>)behaviour).OnEntityServerPacketArrived(sender, packet)); break;
}
}
private void UnregisterPacketRoutersFor(
INetworkEntity behaviour,
Dictionary<Type, Dictionary<string, object>> packetRouters,
@@ -216,8 +231,11 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
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, clientBroadcastPacketRouters, clientBroadcastPacketArrivalMethods, NetworkType.Client, registerBroadcastPacketListenersMethods);
RegisterPacketRoutersFor(collectedBehaviour, clientEntityPacketRouters, clientEntityPacketArrivalMethods, NetworkType.Client, registerEntityPacketListenersMethods);
RegisterPacketRoutersFor(collectedBehaviour, serverBroadcastPacketRouters, serverBroadcastPacketArrivalMethods, NetworkType.Server, registerBroadcastPacketListenersMethods);
RegisterPacketRoutersFor(collectedBehaviour, serverEntityPacketRouters, serverEntityPacketArrivalMethods, NetworkType.Server, registerEntityPacketListenersMethods);
}
private void OnRemoved(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourRemovedArguments args)
@@ -226,8 +244,11 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
if (!_networkEntities.Remove(args.BehaviourRemoved.Id))
return;
UnregisterPacketRoutersFor(removedBehaviour, clientPacketRouters, clientPacketArrivalMethods);
UnregisterPacketRoutersFor(removedBehaviour, serverPacketRouters, serverPacketArrivalMethods);
UnregisterPacketRoutersFor(removedBehaviour, clientBroadcastPacketRouters, clientBroadcastPacketArrivalMethods);
UnregisterPacketRoutersFor(removedBehaviour, clientEntityPacketRouters, clientEntityPacketArrivalMethods);
UnregisterPacketRoutersFor(removedBehaviour, serverBroadcastPacketRouters, serverBroadcastPacketArrivalMethods);
UnregisterPacketRoutersFor(removedBehaviour, serverEntityPacketRouters, serverEntityPacketArrivalMethods);
}
public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign();
@@ -241,7 +262,8 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
#region Initialization
public NetworkManager()
{
CachePacketRetrievalDelegates();
CachePacketRetrievalDelegates(typeof(INetworkPacket), broadcastPacketRetrievalDelegates);
CachePacketRetrievalDelegates(typeof(IEntityNetworkPacket), entityPacketRetrievalDelegates);
CacheRegistrationMethods();
CachePacketArrivalMethods();
@@ -249,45 +271,47 @@ public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetwork
_networkEntityCollector.OnRemoved.AddListener(OnRemoved);
}
private void CachePacketRetrievalDelegates()
private void CachePacketRetrievalDelegates(Type packetType, List<(Type packetType, Delegate @delegate)> retrievalDelegates)
{
IEnumerable<Type> packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
.Where(t => typeof(INetworkPacket).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType);
.Where(t => packetType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType);
MethodInfo onPacketArrivedMethod = GetType()
.GetMethod(nameof(OnPacketReceived), BindingFlags.NonPublic | BindingFlags.Instance)!;
foreach (Type packetType in packetTypes)
foreach (Type pType in packetTypes)
{
MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(packetType);
MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(pType);
Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(IConnection), packetType);
Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(IConnection), pType);
Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod);
packetRetrievalDelegates.Add((packetType, genericPacketReceivedDelegate));
retrievalDelegates.Add((pType, genericPacketReceivedDelegate));
}
}
private void CacheRegistrationMethods()
{
CacheRegistrationMethods(registerPacketListenersMethods, nameof(RegisterPacketListenerEvent));
CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter));
CacheRegistrationMethods(registerBroadcastPacketListenersMethods, nameof(RegisterPacketListenerEvent), broadcastPacketRetrievalDelegates);
CacheRegistrationMethods(registerEntityPacketListenersMethods, nameof(RegisterEntityPacketListenerEvent), entityPacketRetrievalDelegates);
CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter), broadcastPacketRetrievalDelegates);
}
private void CacheRegistrationMethods(Dictionary<Type, MethodInfo> registrationMethods, string methodName)
private void CacheRegistrationMethods(Dictionary<Type, MethodInfo> registrationMethods, string methodName, List<(Type packetType, Delegate @delegate)> 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);
registrationMethods.TryAdd(packetType, genericMethod);
}
}
private void CachePacketArrivalMethods()
{
CachePacketArrivalMethods(clientPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<INetworkEntity>.OnClientPacketArrived));
CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<INetworkEntity>.OnServerPacketArrived));
CachePacketArrivalMethods(clientBroadcastPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<>.OnClientPacketArrived));
CachePacketArrivalMethods(serverBroadcastPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<>.OnServerPacketArrived));
CachePacketArrivalMethods(clientEntityPacketArrivalMethods, typeof(IPacketListenerClientEntity<>), nameof(IPacketListenerClientEntity<>.OnEntityClientPacketArrived));
CachePacketArrivalMethods(serverEntityPacketArrivalMethods, typeof(IPacketListenerServerEntity<>), nameof(IPacketListenerServerEntity<>.OnEntityServerPacketArrived));
}
private static void CachePacketArrivalMethods(Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName)

View File

@@ -2,15 +2,14 @@ namespace Engine.Systems.Network;
public static class TypeHasher<T>
{
private static long _fnv1a = 0;
public static long FNV1a
{
get
{
if (_fnv1a == 0)
_fnv1a = Hasher.FNV1a(typeof(T).FullName ?? typeof(T).Name);
if (field == 0)
field = Hasher.FNV1a(typeof(T).FullName ?? typeof(T).Name);
return _fnv1a;
}
return field;
}
} = 0;
}

View File

@@ -14,23 +14,22 @@ public class State : BaseEntity, IState
private readonly List<StateTransition> transitions = [];
private readonly Dictionary<string, StateTransition> possibleTransitions = [];
private string _name = "Default State Name";
public IReadOnlyList<StateTransition> Transitions => transitions;
public IReadOnlyDictionary<string, StateTransition> PossibleTransitions => possibleTransitions;
public string Name
{
get => _name;
get;
set
{
if (_name.CompareTo(value) == 0)
if (field.CompareTo(value) == 0)
return;
string previousName = _name;
_name = value;
string previousName = field;
field = value;
OnNameChanged?.Invoke(this, new(previousName));
}
}
} = "Default State Name";
public void RemoveTransition(string name)
{

View File

@@ -10,20 +10,19 @@ public abstract class StateBehaviourBase : Behaviour, IState
public Event<IState, IState.StateTransitionReadyArguments> OnStateTransitionReady { get; } = new();
public Event<INameable, INameable.NameChangedArguments> OnNameChanged { get; } = new();
private string _name = string.Empty;
public string Name
{
get => _name;
get;
set
{
if (_name.CompareTo(value) == 0)
if (field.CompareTo(value) == 0)
return;
string previousName = _name;
_name = value;
string previousName = field;
field = value;
OnNameChanged?.Invoke(this, new(previousName));
}
}
} = string.Empty;
protected virtual void OnUpdateState() { }
public void Update()

View File

@@ -9,33 +9,26 @@ public class StateMachine : Behaviour, IUpdate
private readonly Event<IState, IState.StateTransitionReadyArguments>.EventHandler delegateOnStateTransitionReady = null!;
private IState _state = new State();
public StateMachine()
{
delegateOnStateTransitionReady = OnStateTransitionReady;
}
[Serialize]
public IState State
{
get => _state;
get;
set
{
if (_state == value)
if (field == value)
return;
IState previousState = _state;
IState previousState = field;
previousState.OnStateTransitionReady.RemoveListener(delegateOnStateTransitionReady);
_state = value;
field = value;
previousState.TransitionFrom(value);
value.TransitionTo(_state);
value.TransitionTo(field);
OnStateChanged?.Invoke(this, new(value, previousState));
value.OnStateTransitionReady.AddListener(delegateOnStateTransitionReady);
}
}
} = new State();
private void OnStateTransitionReady(IState sender, IState.StateTransitionReadyArguments args)
{
@@ -55,5 +48,10 @@ public class StateMachine : Behaviour, IUpdate
State.Update();
}
public StateMachine()
{
delegateOnStateTransitionReady = OnStateTransitionReady;
}
public readonly record struct StateChangedArguments(IState CurrentState, IState PreviousState);
}

View File

@@ -14,18 +14,17 @@ public class Timer : Behaviour, IUpdate, IEnterUniverse, IExitUniverse, ITimer
public double StartTime { get; protected set; } = 0f;
public float Percentage => (float)(1f - (Remaining / StartTime));
private double _remaining = 0f;
public double Remaining
{
get => _remaining;
get;
protected set
{
if (value < .0f)
value = .0f;
_remaining = value;
}
field = value;
}
} = 0f;
private bool shouldBeTicking = false;
private bool hasStartedTickingBefore = false;

View File

@@ -13,17 +13,18 @@ internal class Tween : ITween
public Event<ITween> OnUpdated { get; } = new();
public Event<ITween, ITween.TweenDeltaArguments> OnDeltaUpdated { get; } = new();
private TweenState _state = TweenState.Idle;
private float _counter = 0f;
public TweenState State
{
get => _state;
get;
set
{
if (value == _state)
if (value == field)
return;
TweenState previousState = _state;
_state = value;
TweenState previousState = field;
field = value;
switch (value)
{
case TweenState.Completed: OnCompleted?.Invoke(this); OnEnded?.Invoke(this); break;
@@ -37,11 +38,10 @@ internal class Tween : ITween
break;
}
}
}
} = TweenState.Idle;
public float Duration { get; internal set; } = 1f;
public float Progress { get; internal set; } = 0f;
private float _counter = 0f;
public IEasing Easing { get; set; } = EaseLinear.Instance;
public float Value => Easing.Evaluate(Progress);

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Engine</RootNamespace>