27 Commits

Author SHA1 Message Date
7e07cd30db refactor: monogame camera transform caching 2025-11-03 15:05:32 +03:00
e9ba7867ac fix: ordered behaviour controllers not removing behaviours properly because of index getter function time dependence 2025-11-02 10:54:13 +03:00
f28307be80 fix: entrance manager exits not being managed properly 2025-11-01 23:27:55 +03:00
5f019892f1 feat: IBehaviourController.CollectionMethod added 2025-11-01 23:27:40 +03:00
0bd2b0618d feat: wip network packet encryption 2025-10-28 19:47:54 +03:00
0691d7092c fix: half of the universe objects not getting deserialized because of for loop 2025-10-28 10:32:01 +03:00
4ed049573a feat: client & server behaviours added 2025-10-28 10:04:25 +03:00
599774ea8b fix: litenetlib server throwing an exception on sending packets 2025-10-28 10:04:25 +03:00
b17f880ecf feat: non-generic FNV1a hasher added 2025-10-28 10:04:25 +03:00
98bc0693dd fix: possible hash code collisions on Matrix4x4 2025-10-28 10:04:25 +03:00
ac2e160abb refactor!: Identifiable interface extracted from IEntity 2025-10-28 10:04:25 +03:00
30a07dd034 chore: added to xna matrix extension method 2025-10-28 10:04:25 +03:00
f180713f4b feat: added basic 4d matrix 2025-10-28 10:04:25 +03:00
2d612ea0d4 fix: universe object registration logic order fixed 2025-10-28 10:04:25 +03:00
5a6883a87f BREAKING CHANGE: replaced universe objects with root universe object 2025-10-28 10:02:59 +03:00
a6eb67551d chore: put monogame behaviours under a parent universe object 2025-10-23 22:45:57 +03:00
981732ff75 fix: Quaternion.SLerp snapping issue 2025-10-23 12:51:36 +03:00
4bdd32b808 feat: missing extension methods for Math.Tan and Atan 2025-10-23 12:45:00 +03:00
cac69f5f35 chore: Camera3D now uses left handed matrices 2025-10-23 10:05:43 +03:00
1660915678 feat: added near & far planes to camera3D 2025-10-23 10:04:56 +03:00
1d02b0eba2 feat: added missing Math.Tan & Atan methods 2025-10-23 10:00:52 +03:00
2ef7fa6577 chore: added assert for universe entrance manager issue 2025-10-22 23:44:29 +03:00
193b2c5af0 fix: yaml serialization issues caused by class converter treating primitives as classes 2025-10-22 23:35:33 +03:00
a859c0cbff fix: quaternion string format order fixed 2025-10-22 22:24:48 +03:00
fba64c3854 fix: behaviours being serialized in reverse 2025-10-22 22:13:46 +03:00
a975cbb56b chore: added fixme comment 2025-10-22 21:30:09 +03:00
1664a9ccf7 fix: entrance manager not calling exits 2025-10-22 20:21:58 +03:00
45 changed files with 870 additions and 261 deletions

View File

@@ -62,7 +62,8 @@ public interface IBehaviourController : IEntity, IHasUniverseObject
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
/// <param name="results">The list to store the <see cref="IBehaviour"/>s.</param>
void GetBehaviours<T>(IList<T> results);
/// <param name="collectionMethod">Whether to clear the <paramref name="results"/> before collection or append the results to the list.</param>
void GetBehaviours<T>(IList<T> results, CollectionMethod collectionMethod = CollectionMethod.Clear);
/// <summary>
/// Removes <see cref="IBehaviour"/>s of the specified type from the <see cref="IBehaviourController"/>.
@@ -78,6 +79,8 @@ public interface IBehaviourController : IEntity, IHasUniverseObject
/// <param name="behaviour">The <see cref="IBehaviour"/> to remove.</param>
void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour;
enum CollectionMethod { Clear, Append };
readonly record struct BehaviourAddedArguments(IBehaviour BehaviourAdded);
readonly record struct BehaviourRemovedArguments(IBehaviour BehaviourRemoved);
}

View File

@@ -6,7 +6,32 @@ namespace Engine.Core;
public interface ICamera3D : IBehaviour3D
{
/// <summary>
/// Field of View (FOV) value of the camera
/// Event triggered when the near plane of the <see cref="ICamera3D"/> changes.
/// </summary>
Event<ICamera3D, NearPlaneChangedArguments> OnNearPlaneChanged { get; }
/// <summary>
/// Event triggered when the far plane of the <see cref="ICamera3D"/> changes.
/// </summary>
Event<ICamera3D, FarPlaneChangedArguments> OnFarPlaneChanged { get; }
/// <summary>
/// Event triggered when the field of view of the <see cref="ICamera3D"/> changes.
/// </summary>
Event<ICamera3D, FieldOfViewChangedArguments> OnFieldOfViewChanged { get; }
/// <summary>
/// Near plane distance of the camera.
/// </summary>
float NearPlane { get; set; }
/// <summary>
/// Far plane distance of the camera.
/// </summary>
float FarPlane { get; set; }
/// <summary>
/// Field of View (FOV) value of the camera in degrees.
/// </summary>
float FieldOfView { get; set; }
@@ -23,4 +48,8 @@ public interface ICamera3D : IBehaviour3D
/// <param name="worldPosition">The position in world coordinates.</param>
/// <returns>The position in screen coordinates.</returns>
Vector2D WorldToScreenPosition(Vector3D worldPosition);
readonly record struct NearPlaneChangedArguments(float PreviousNearPlane);
readonly record struct FarPlaneChangedArguments(float PreviousFarPlane);
readonly record struct FieldOfViewChangedArguments(float PreviousFieldOfView);
}

View File

@@ -3,18 +3,4 @@ namespace Engine.Core;
/// <summary>
/// Represents a basic entity in the engine.
/// </summary>
public interface IEntity : IInitializable, IHasStateEnable
{
/// <summary>
/// Event triggered when the <see cref="Id"/> of the <see cref="IEntity"/> changes.
/// The string action parameter is the previous <see cref="Id"/> of the <see cref="IEntity"/>.
/// </summary>
Event<IEntity, IdChangedArguments> OnIdChanged { get; }
/// <summary>
/// The ID of the <see cref="IEntity"/>.
/// </summary>
string Id { get; set; }
readonly record struct IdChangedArguments(string PreviousId);
}
public interface IEntity : IInitializable, IIdentifiable, IHasStateEnable;

View File

@@ -0,0 +1,20 @@
namespace Engine.Core;
/// <summary>
/// Represents any instance in the engine with an id.
/// </summary>
public interface IIdentifiable
{
/// <summary>
/// Event triggered when the <see cref="Id"/> of the <see cref="IIdentifiable"/> changes.
/// The string action parameter is the previous <see cref="Id"/> of the <see cref="IIdentifiable"/>.
/// </summary>
Event<IIdentifiable, IdChangedArguments> OnIdChanged { get; }
/// <summary>
/// The ID of the <see cref="IIdentifiable"/>.
/// </summary>
string Id { get; set; }
readonly record struct IdChangedArguments(string PreviousId);
}

View File

@@ -78,9 +78,9 @@ public interface IUniverse : IEntity, IEnumerable<IUniverseObject>
UniverseTime UnscaledTime { get; }
/// <summary>
/// Gets a read-only list of <see cref="IUniverseObject"/>s managed by the <see cref="IUniverse"/>.
/// Gets the root <see cref="IUniverseObject"/> of the <see cref="IUniverse"/>.
/// </summary>
IReadOnlyList<IUniverseObject> UniverseObjects { get; }
IUniverseObject Root { get; }
/// <summary>
/// Registers an <see cref="IUniverseObject"/> to the <see cref="IUniverse"/>.

View File

@@ -4,7 +4,7 @@ namespace Engine.Core;
public abstract class BaseEntity : IEntity
{
public Event<IEntity, IEntity.IdChangedArguments> OnIdChanged { get; } = new();
public Event<IIdentifiable, IIdentifiable.IdChangedArguments> OnIdChanged { get; } = new();
public Event<IInitializable> OnInitialized { get; } = new();
public Event<IInitializable> OnFinalized { get; } = new();
public Event<IHasStateEnable> OnStateEnableAssigned { get; } = new();

View File

@@ -58,9 +58,11 @@ public class BehaviourController : BaseEntity, IBehaviourController
return behaviours;
}
public void GetBehaviours<T>(IList<T> results)
public void GetBehaviours<T>(IList<T> results, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear)
{
if (collectionMethod == IBehaviourController.CollectionMethod.Clear)
results.Clear();
foreach (IBehaviour behaviourItem in behaviours)
{
if (behaviourItem is not T behaviour)

View File

@@ -29,7 +29,7 @@ public abstract class ActiveBehaviourCollectorBase<T> : IBehaviourCollector<T> w
if (Universe is not null)
return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects)
foreach (IUniverseObject universeObject in universe)
OnUniverseObjectRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered);
@@ -46,7 +46,7 @@ public abstract class ActiveBehaviourCollectorBase<T> : IBehaviourCollector<T> w
if (Universe is null)
return false;
foreach (IUniverseObject universeObject in Universe.UniverseObjects)
foreach (IUniverseObject universeObject in Universe)
OnUniverseObjectUnregistered(Universe, new(universeObject));
Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered);

View File

@@ -5,9 +5,8 @@ namespace Engine.Core;
public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCollector<TItem> where TItem : class, IBehaviour where TIndex : IComparable
{
private readonly Event<IBehaviour, IBehaviour.PriorityChangedArguments>.EventHandler delegateOnPriorityChanged = null!;
private readonly SortedDictionary<TIndex, FastList<TItem>> behaviours = null!;
private readonly Dictionary<TItem, TIndex> indexCache = [];
private readonly Func<TItem, TIndex> getIndexFunc = null!;
private readonly IComparer<TIndex> sortBy = null!;
@@ -39,11 +38,10 @@ public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCol
protected override bool RemoveBehaviour(TItem tBehaviour)
{
TIndex index = getIndexFunc(tBehaviour);
if (!behaviours.TryGetValue(index, out FastList<TItem>? list))
if (!indexCache.TryGetValue(tBehaviour, out TIndex? index) || !behaviours.TryGetValue(index, out FastList<TItem>? list))
throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector");
if (!list.Remove(tBehaviour))
if (!list.Remove(tBehaviour) || !indexCache.Remove(tBehaviour))
return false;
count--;
@@ -58,21 +56,11 @@ public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCol
count++;
list.Add(behaviour);
}
protected override void OnBehaviourAdd(IBehaviour behaviour) => behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged);
protected override void OnBehaviourRemove(IBehaviour behaviour) => behaviour.OnPriorityChanged.RemoveListener(delegateOnPriorityChanged);
private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args)
{
TItem behaviour = (TItem)sender;
RemoveBehaviour(behaviour);
AddBehaviour(behaviour);
indexCache.Add(behaviour, key);
}
public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
@@ -80,7 +68,6 @@ public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCol
public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
@@ -89,14 +76,12 @@ public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCol
public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
{
this.getIndexFunc = getIndexFunc;
delegateOnPriorityChanged = OnPriorityChanged;
this.sortBy = sortBy;
behaviours = new(sortBy);
}
public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = sortBy;
behaviours = new(sortBy);

View File

@@ -25,7 +25,7 @@ public abstract class BehaviourCollectorBase<T> : IBehaviourCollector<T> where T
if (Universe is not null)
return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects)
foreach (IUniverseObject universeObject in universe)
OnUniverseObjectRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered);
@@ -43,7 +43,7 @@ public abstract class BehaviourCollectorBase<T> : IBehaviourCollector<T> where T
if (Universe is null)
return false;
foreach (IUniverseObject universeObject in Universe.UniverseObjects)
foreach (IUniverseObject universeObject in Universe)
OnUniverseObjectUnregistered(Universe, new(universeObject));
Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered);

View File

@@ -5,9 +5,8 @@ namespace Engine.Core;
public class BehaviourCollectorOrdered<TIndex, TItem> : BehaviourCollectorBase<TItem> where TItem : class where TIndex : IComparable
{
private readonly Event<IBehaviour, IBehaviour.PriorityChangedArguments>.EventHandler delegateOnPriorityChanged = null!;
private readonly SortedDictionary<TIndex, FastList<TItem>> behaviours = null!;
private readonly Dictionary<TItem, TIndex> indexCache = null!;
private readonly Func<TItem, TIndex> getIndexFunc = null!;
private readonly IComparer<TIndex> sortBy = null!;
@@ -39,11 +38,10 @@ public class BehaviourCollectorOrdered<TIndex, TItem> : BehaviourCollectorBase<T
protected override bool RemoveBehaviour(TItem tBehaviour)
{
TIndex index = getIndexFunc(tBehaviour);
if (!behaviours.TryGetValue(index, out FastList<TItem>? list))
if (!indexCache.TryGetValue(tBehaviour, out TIndex? index) || !behaviours.TryGetValue(index, out FastList<TItem>? list))
throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector");
if (!list.Remove(tBehaviour))
if (!list.Remove(tBehaviour) || !indexCache.Remove(tBehaviour))
return false;
count--;
@@ -58,21 +56,11 @@ public class BehaviourCollectorOrdered<TIndex, TItem> : BehaviourCollectorBase<T
count++;
list.Add(behaviour);
}
protected override void OnBehaviourAdd(IBehaviour behaviour) => behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged);
protected override void OnBehaviourRemove(IBehaviour behaviour) => behaviour.OnPriorityChanged.RemoveListener(delegateOnPriorityChanged);
private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args)
{
TItem behaviour = (TItem)sender;
RemoveBehaviour(behaviour);
AddBehaviour(behaviour);
indexCache.Add(behaviour, key);
}
public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
@@ -80,7 +68,6 @@ public class BehaviourCollectorOrdered<TIndex, TItem> : BehaviourCollectorBase<T
public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
@@ -89,14 +76,12 @@ public class BehaviourCollectorOrdered<TIndex, TItem> : BehaviourCollectorBase<T
public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
{
this.getIndexFunc = getIndexFunc;
delegateOnPriorityChanged = OnPriorityChanged;
this.sortBy = sortBy;
behaviours = new(sortBy);
}
public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = sortBy;
behaviours = new(sortBy);

View File

@@ -100,18 +100,16 @@ public static class BehaviourControllerExtensions
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
/// <param name="behavioursInParent">The list to store the <see cref="IBehaviour"/>s.</param>
public static void GetBehavioursInParent<T>(this IBehaviourController behaviourController, IList<T> behavioursInParent) where T : class
/// <param name="collectionMethod">Whether to clear the <paramref name="behavioursInParent"/> before collection or append the results to the list.</param>
public static void GetBehavioursInParent<T>(this IBehaviourController behaviourController, IList<T> behavioursInParent, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear) where T : class
{
IBehaviourController? controller = behaviourController;
List<T> cache = [];
if (collectionMethod == IBehaviourController.CollectionMethod.Clear)
behavioursInParent.Clear();
IBehaviourController? controller = behaviourController;
while (controller is not null)
{
controller.GetBehaviours(cache);
foreach (T behaviour in cache)
behavioursInParent.Add(behaviour);
controller.GetBehaviours(behavioursInParent, IBehaviourController.CollectionMethod.Append);
controller = controller.UniverseObject.Parent?.BehaviourController;
}
}
@@ -161,22 +159,20 @@ public static class BehaviourControllerExtensions
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
/// <param name="behavioursInChildren">The list to store the <see cref="IBehaviour"/>s.</param>
public static void GetBehavioursInChildren<T>(this IBehaviourController behaviourController, IList<T> behavioursInChildren) where T : class
/// <param name="collectionMethod">Whether to clear the <paramref name="behavioursInChildren"/> before collection or append the results to the list.</param>
public static void GetBehavioursInChildren<T>(this IBehaviourController behaviourController, IList<T> behavioursInChildren, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear) where T : class
{
List<T> cache = [];
if (collectionMethod == IBehaviourController.CollectionMethod.Clear)
behavioursInChildren.Clear();
TraverseChildrenForBehaviour(behaviourController.UniverseObject, behavioursInChildren, cache);
TraverseChildrenForBehaviour(behaviourController.UniverseObject, behavioursInChildren);
}
private static void TraverseChildrenForBehaviour<T>(IUniverseObject universeObject, IList<T> behaviours, IList<T> cache) where T : class
private static void TraverseChildrenForBehaviour<T>(IUniverseObject universeObject, IList<T> behaviours) where T : class
{
universeObject.BehaviourController.GetBehaviours(cache);
foreach (T behaviour in cache)
behaviours.Add(behaviour);
universeObject.BehaviourController.GetBehaviours(behaviours, IBehaviourController.CollectionMethod.Append);
foreach (IUniverseObject child in universeObject.Children)
TraverseChildrenForBehaviour(child, behaviours, cache);
TraverseChildrenForBehaviour(child, behaviours);
}
}

View File

@@ -32,5 +32,5 @@ public static class UniverseExtensions
/// <typeparam name="T">Type to be searched through the <see cref="IUniverse"/>.</typeparam>
/// <returns>The specified type if found; otherwise, throws <see cref="NotFoundException"/>.</returns>
public static T FindRequired<T>(this IUniverse universe) where T : class
=> universe.Find<T>() ?? throw new NotFoundException($"{universe.GetType().FullName}({universe.Id}) does not contain any {nameof(IUniverseObject)} or {nameof(IBehaviour)} of type {typeof(T).FullName}");
=> universe.Root.BehaviourController.GetBehaviourInChildren<T>() ?? throw new NotFoundException($"{universe.GetType().FullName}({universe.Id}) does not contain any {nameof(IUniverseObject)} or {nameof(IBehaviour)} of type {typeof(T).FullName}");
}

View File

@@ -16,6 +16,21 @@ public static class UniverseObjectExtensions
return universeObject;
}
public static IEnumerator<IUniverseObject> TraverseChildren(this IUniverseObject universeObject)
{
static IEnumerable<IUniverseObject> Traverse(IUniverseObject obj)
{
foreach (IUniverseObject child in obj.Children)
{
yield return child;
foreach (IUniverseObject descendant in Traverse(child))
yield return descendant;
}
}
return Traverse(universeObject).GetEnumerator();
}
#region Universe Object Search
/// <summary>
/// Gets a <see cref="IUniverseObject"/> of the specified type.

View File

@@ -110,6 +110,13 @@ public static class Math
/// <returns>The sine of <paramref name="x"/>.</returns>
public static float Sin(float x) => MathF.Sin(x);
/// <summary>
/// Returns the tangent of a number.
/// </summary>
/// <param name="x">The angle, in radians.</param>
/// <returns>The tangent of <paramref name="x"/>.</returns>
public static float Tan(float x) => MathF.Tan(x);
/// <summary>
/// Returns the arccosine of a number.
/// </summary>
@@ -124,6 +131,13 @@ public static class Math
/// <returns>The arcsine of <paramref name="x"/>.</returns>
public static float Asin(float x) => MathF.Asin(x);
/// <summary>
/// Returns the angle whose tangent is the specified number.
/// </summary>
/// <param name="x">The tangent value.</param>
/// <returns>The angle, in radians.</returns>
public static float Atan(float x) => MathF.Atan(x);
/// <summary>
/// Returns the angle whose tangent is the quotient of two specified numbers.
/// </summary>

View File

@@ -32,12 +32,18 @@ public static class MathExtensions
/// <inheritdoc cref="Math.Sin(float)" />
public static float Sin(this float x) => Math.Sin(x);
/// <inheritdoc cref="Math.Tan(float)" />
public static float Tan(this float x) => Math.Tan(x);
/// <inheritdoc cref="Math.Acos(float)" />
public static float Acos(this float x) => Math.Acos(x);
/// <inheritdoc cref="Math.Asin(float)" />
public static float Asin(this float x) => Math.Asin(x);
/// <inheritdoc cref="Math.Atan(float)" />
public static float Atan(this float x) => Math.Atan(x);
/// <inheritdoc cref="Math.Atan2(float, float)" />
public static float Atan2(this float y, float x) => Math.Atan2(y, x);

View File

@@ -0,0 +1,294 @@
using System;
using System.Numerics;
namespace Engine.Core;
// TODO Comments
/// <summary>
/// Represents a 4D left handed space matrix.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="Matrix4x4"/> struct with the specified values.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
public readonly struct Matrix4x4(
float m11, float m12, float m13, float m14,
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44
) : IEquatable<Matrix4x4>
{
public readonly float M11 = m11, M12 = m12, M13 = m13, M14 = m14;
public readonly float M21 = m21, M22 = m22, M23 = m23, M24 = m24;
public readonly float M31 = m31, M32 = m32, M33 = m33, M34 = m34;
public readonly float M41 = m41, M42 = m42, M43 = m43, M44 = m44;
/// <summary>
/// Extracts the position (translation) from the <see cref="Matrix4x4"/>.
/// </summary>
public readonly Vector3D Position => new(M41, M42, M43);
/// <summary>
/// Extracts the scale from the <see cref="Matrix4x4"/>.
/// </summary>
public readonly Vector3D Scale
{
get
{
float scaleX = new Vector3D(M11, M12, M13).Length();
float scaleY = new Vector3D(M21, M22, M23).Length();
float scaleZ = new Vector3D(M31, M32, M33).Length();
if (Determinant(this) < 0)
scaleX *= -1;
return new(scaleX, scaleY, scaleZ);
}
}
/// <summary>
/// Extracts the rotation from the <see cref="Matrix4x4"/>.
/// </summary>
public readonly Quaternion Rotation => Quaternion.FromRotationMatrix4x4(this).Normalized;
/// <summary>
/// Represents the identity <see cref="Matrix4x4"/>.
/// </summary>
public static Matrix4x4 Identity => new(
1f, 0f, 0f, 0f,
0f, 1f, 0f, 0f,
0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f
);
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,
a.M11 * b.M13 + a.M12 * b.M23 + a.M13 * b.M33 + a.M14 * b.M43,
a.M11 * b.M14 + a.M12 * b.M24 + a.M13 * b.M34 + a.M14 * b.M44,
a.M21 * b.M11 + a.M22 * b.M21 + a.M23 * b.M31 + a.M24 * b.M41,
a.M21 * b.M12 + a.M22 * b.M22 + a.M23 * b.M32 + a.M24 * b.M42,
a.M21 * b.M13 + a.M22 * b.M23 + a.M23 * b.M33 + a.M24 * b.M43,
a.M21 * b.M14 + a.M22 * b.M24 + a.M23 * b.M34 + a.M24 * b.M44,
a.M31 * b.M11 + a.M32 * b.M21 + a.M33 * b.M31 + a.M34 * b.M41,
a.M31 * b.M12 + a.M32 * b.M22 + a.M33 * b.M32 + a.M34 * b.M42,
a.M31 * b.M13 + a.M32 * b.M23 + a.M33 * b.M33 + a.M34 * b.M43,
a.M31 * b.M14 + a.M32 * b.M24 + a.M33 * b.M34 + a.M34 * b.M44,
a.M41 * b.M11 + a.M42 * b.M21 + a.M43 * b.M31 + a.M44 * b.M41,
a.M41 * b.M12 + a.M42 * b.M22 + a.M43 * b.M32 + a.M44 * b.M42,
a.M41 * b.M13 + a.M42 * b.M23 + a.M43 * b.M33 + a.M44 * b.M43,
a.M41 * b.M14 + a.M42 * b.M24 + a.M43 * b.M34 + a.M44 * b.M44
);
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 &&
left.M31 == right.M31 && left.M32 == right.M32 && left.M33 == right.M33 && left.M34 == right.M34 &&
left.M41 == right.M41 && left.M42 == right.M42 && left.M43 == right.M43 && left.M44 == right.M44;
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 ||
left.M31 != right.M31 || left.M32 != right.M32 || left.M33 != right.M33 || left.M34 != right.M34 ||
left.M41 != right.M41 || left.M42 != right.M42 || left.M43 != right.M43 || left.M44 != right.M44;
public static implicit operator System.Numerics.Matrix4x4(Matrix4x4 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
);
public static implicit operator Matrix4x4(System.Numerics.Matrix4x4 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
);
/// <summary>
/// Calculates the determinant of the <see cref="Matrix4x4"/>.
/// </summary>
/// <param name="m">The <see cref="Matrix4x4"/>.</param>
/// <returns>The determinant of the <see cref="Matrix4x4"/>.</returns>
public static float Determinant(Matrix4x4 m) => // https://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/fourD/index.htm
m.M14 * m.M23 * m.M32 * m.M41 - m.M13 * m.M24 * m.M32 * m.M41 -
m.M14 * m.M22 * m.M33 * m.M41 + m.M12 * m.M24 * m.M33 * m.M41 +
m.M13 * m.M22 * m.M34 * m.M41 - m.M12 * m.M23 * m.M34 * m.M41 -
m.M14 * m.M23 * m.M31 * m.M42 + m.M13 * m.M24 * m.M31 * m.M42 +
m.M14 * m.M21 * m.M33 * m.M42 - m.M11 * m.M24 * m.M33 * m.M42 -
m.M13 * m.M21 * m.M34 * m.M42 + m.M11 * m.M23 * m.M34 * m.M42 +
m.M14 * m.M22 * m.M31 * m.M43 - m.M12 * m.M24 * m.M31 * m.M43 -
m.M14 * m.M21 * m.M32 * m.M43 + m.M11 * m.M24 * m.M32 * m.M43 +
m.M12 * m.M21 * m.M34 * m.M43 - m.M11 * m.M22 * m.M34 * m.M43 -
m.M13 * m.M22 * m.M31 * m.M44 + m.M12 * m.M23 * m.M31 * m.M44 +
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;
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
);
public static Matrix4x4 CreateScale(Vector3D scale) => new(
scale.X, 0f, 0f, 0f,
0f, scale.Y, 0f, 0f,
0f, 0f, scale.Z, 0f,
0f, 0f, 0f, 1f
);
public static Matrix4x4 CreateRotationX(float radians)
{
float c = Math.Cos(radians);
float s = Math.Sin(radians);
return new Matrix4x4(
1f, 0f, 0f, 0f,
0f, c, s, 0f,
0f, -s, c, 0f,
0f, 0f, 0f, 1f
);
}
public static Matrix4x4 CreateRotationY(float radians)
{
float c = Math.Cos(radians);
float s = Math.Sin(radians);
return new Matrix4x4(
c, 0f, -s, 0f,
0f, 1f, 0f, 0f,
s, 0f, c, 0f,
0f, 0f, 0f, 1f
);
}
public static Matrix4x4 CreateRotationZ(float radians)
{
float c = Math.Cos(radians);
float s = Math.Sin(radians);
return new Matrix4x4(
c, s, 0f, 0f,
-s, c, 0f, 0f,
0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f
);
}
// TODO Find a better calculation for this
public static Matrix4x4 CreateRotation(Quaternion quaternion)
{
Vector3D angles = quaternion.ToAngles();
return Identity * CreateRotationX(angles.X) * CreateRotationY(angles.Y) * CreateRotationZ(angles.Z);
}
public static Matrix4x4 CreateLookMatrix(Vector3D forward, Vector3D up)
{
Vector3D z = forward.Normalized;
Vector3D x = up.Cross(z).Normalized;
Vector3D y = z.Cross(x);
return new Matrix4x4(
x.X, y.X, z.X, 0f,
x.Y, y.Y, z.Y, 0f,
x.Z, y.Z, z.Z, 0f,
0f, 0f, 0f, 1f
);
}
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);
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
);
}
public static Matrix4x4 CreatePerspectiveFieldOfView(float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane)
{
float yScale = 1f / Math.Tan(fieldOfViewInRadians / 2f);
float xScale = yScale / aspectRatio;
return new Matrix4x4(
xScale, 0f, 0f, 0f,
0f, yScale, 0f, 0f,
0f, 0f, farPlane / (farPlane - nearPlane), 1f,
0f, 0f, -nearPlane * farPlane / (farPlane - nearPlane), 0f
);
}
public static Matrix4x4 ToRightHanded(Matrix4x4 m) => new(
m.M11, m.M12, m.M13, m.M14,
m.M31, m.M32, m.M33, m.M34,
m.M21, m.M22, m.M23, m.M24,
m.M41, m.M42, m.M43, m.M44
);
public override bool Equals(object? obj) => obj is Matrix4x4 matrix && this == matrix;
public bool Equals(Matrix4x4 other) => this == other;
public override int GetHashCode()
{
HashCode hashCode = new();
hashCode.Add(M11); hashCode.Add(M12); hashCode.Add(M13); hashCode.Add(M14);
hashCode.Add(M21); hashCode.Add(M22); hashCode.Add(M23); hashCode.Add(M24);
hashCode.Add(M31); hashCode.Add(M32); hashCode.Add(M33); hashCode.Add(M34);
hashCode.Add(M41); hashCode.Add(M42); hashCode.Add(M43); hashCode.Add(M44);
return hashCode.ToHashCode();
}
public override string ToString() => $"Matrix4x4({M11}, {M12}, {M13}, {M14},{M21}, {M22}, {M23}, {M24},{M31}, {M32}, {M33}, {M34},{M41}, {M42}, {M43}, {M44})";
}
/// <summary>
/// Provides extension methods for <see cref="Matrix4x4"/> type.
/// </summary>
public static class Matrix4x4Extensions
{
/// <inheritdoc cref="Matrix4x4.Determinant(Matrix4x4)" />
public static float Determinant(this Matrix4x4 matrix) => Matrix4x4.Determinant(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);
/// <inheritdoc cref="Matrix4x4.CreateRotationZ(float)" />
public static Matrix4x4 ApplyRotationX(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationX(radians);
/// <inheritdoc cref="Matrix4x4.CreateRotationY(float)" />
public static Matrix4x4 ApplyRotationY(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationY(radians);
/// <inheritdoc cref="Matrix4x4.CreateRotationZ(float)" />
public static Matrix4x4 ApplyRotationZ(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationZ(radians);
/// <inheritdoc cref="Matrix4x4.CreateRotation(Quater)" />
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);
/// <inheritdoc cref="Matrix4x4.CreateLookMatrix(Vector3D, Vector3D, Vector3D)" />
public static Matrix4x4 CreateLookMatrixTo(this Vector3D from, Vector3D to, Vector3 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.ToRightHanded(Matrix4x4) />
public static Matrix4x4 ToRightHanded(this Matrix4x4 matrix) => Matrix4x4.ToRightHanded(matrix);
}

View File

@@ -156,6 +156,27 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
/// <returns>The normalized <see cref="Quaternion"/>.</returns>
public static Quaternion Normalize(Quaternion quaternion) => quaternion / Length(quaternion);
public static Quaternion LookAt(Vector3D origin, Vector3D target, Vector3D up) => LookAt(target - origin, up);
public static Quaternion LookAt(Vector3D target, Vector3D up)
{
Vector3D forward = target.Normalized;
if (forward.LengthSquared() < 1e-6f)
return Identity;
Vector3D right = up.Cross(forward).Normalized;
Vector3D newUp = forward.Cross(right);
Matrix4x4 rot = new(
right.X, right.Y, right.Z, 0f,
newUp.X, newUp.Y, newUp.Z, 0f,
forward.X, forward.Y, forward.Z, 0f,
0f, 0f, 0f, 1f
);
return FromRotationMatrix4x4(rot);
}
/// <summary>
/// Rotates a <see cref="Quaternion"/> around a axis by the specified angle (in radians).
/// </summary>
@@ -212,8 +233,8 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
dot = -dot;
}
if (dot > 0.9995f)
return Lerp(from, to, t);
if (dot > 0.999999f)
return to;
float angle = Math.Acos(dot);
float sinAngle = Math.Sin(angle);
@@ -276,12 +297,11 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
}
/// <summary>
/// Calculates the <see cref="System.Numerics.Matrix4x4"/> from given <see cref="Quaternion"/>.
/// Calculates the <see cref="Matrix4x4"/> from given <see cref="Quaternion"/>.
/// </summary>
/// <param name="axis">The axis of the rotation in <see cref="Vector3D"/>.</param>
/// <param name="angle">The angle in radians.</param>
/// <returns>The rotation <see cref="System.Numerics.Matrix4x4"/> calculated by the given <see cref="Quaternion"/>.</returns>
public static System.Numerics.Matrix4x4 ToRotationMatrix4x4(Quaternion quaternion)
/// <param name="quaternion">The rotation <see cref="Quaternion"/>.</param>
/// <returns>The rotation <see cref="Matrix4x4"/> calculated by the given <see cref="Quaternion"/>.</returns>
public static Matrix4x4 ToRotationMatrix4x4(Quaternion quaternion)
{
float m00 = 1 - 2 * (quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z);
float m01 = 2 * (quaternion.X * quaternion.Y - quaternion.W * quaternion.Z);
@@ -311,6 +331,52 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
);
}
/// <summary>
/// Calculates the <see cref="Quaternion"/> from given <see cref="Matrix4x4"/>.
/// </summary>
/// <param name="martix">The rotation <see cref="Matrix4x4"/>.</param>
/// <returns>The rotation <see cref="Quaternion"/> calculated by the given <see cref="Matrix4x4"/>.</returns>
public static Quaternion FromRotationMatrix4x4(Matrix4x4 martix)
{
float trace = martix.M11 + martix.M22 + martix.M33;
float w, x, y, z;
if (trace > 0)
{
float s = Math.Sqrt(trace + 1.0f) * 2f;
w = .25f * s;
x = (martix.M23 - martix.M32) / s;
y = (martix.M31 - martix.M13) / s;
z = (martix.M12 - martix.M21) / s;
}
else if ((martix.M11 > martix.M22) && (martix.M11 > martix.M33))
{
float s = Math.Sqrt(1.0f + martix.M11 - martix.M22 - martix.M33) * 2f;
w = (martix.M23 - martix.M32) / s;
x = .25f * s;
y = (martix.M12 + martix.M21) / s;
z = (martix.M31 + martix.M13) / s;
}
else if (martix.M22 > martix.M33)
{
float s = Math.Sqrt(1.0f + martix.M22 - martix.M11 - martix.M33) * 2f;
w = (martix.M31 - martix.M13) / s;
x = (martix.M12 + martix.M21) / s;
y = .25f * s;
z = (martix.M23 + martix.M32) / s;
}
else
{
float s = Math.Sqrt(1.0f + martix.M33 - martix.M11 - martix.M22) * 2f;
w = (martix.M12 - martix.M21) / s;
x = (martix.M31 + martix.M13) / s;
y = (martix.M23 + martix.M32) / s;
z = .25f * s;
}
return new(x, y, z, w);
}
/// <summary>
/// Checks if two <see cref="Quaternion"/>s are approximately equal within a specified epsilon range.
/// </summary>
@@ -333,13 +399,13 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
/// Generates a hash code for the <see cref="Quaternion"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Quaternion"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(W, X, Y, Z);
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z, W);
/// <summary>
/// Converts the <see cref="Quaternion"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Quaternion"/>.</returns>
public override string ToString() => $"{nameof(Quaternion)}({W}, {X}, {Y}, {Z})";
public override string ToString() => $"{nameof(Quaternion)}({X}, {Y}, {Z}, {W})";
}
/// <summary>
@@ -393,7 +459,7 @@ public static class QuaternionExtensions
public static float Dot(this Quaternion left, Quaternion right) => Quaternion.Dot(left, right);
/// <inheritdoc cref="Quaternion.ToRotationMatrix4x4(Quaternion, Quaternion)" />
public static System.Numerics.Matrix4x4 ToRotationMatrix4x4(this Quaternion quaternion) => Quaternion.ToRotationMatrix4x4(quaternion);
public static Matrix4x4 ToRotationMatrix4x4(this Quaternion quaternion) => Quaternion.ToRotationMatrix4x4(quaternion);
/// <inheritdoc cref="Quaternion.FromAxisAngle(Vector3D, float)" />
public static Quaternion CreateRotation(this Vector3D axis, float angle) => Quaternion.FromAxisAngle(axis, angle);

View File

@@ -1,39 +0,0 @@
using System;
using System.Collections.Generic;
namespace Engine.Core.Serialization;
public class EntityRegistry
{
public Event<EntityRegistry, EntityRegisteredArguments> OnEntityRegistered = null!;
private readonly Dictionary<string, Action<IEntity>?> assignCallbacks = [];
private readonly Dictionary<string, IEntity> registeredEntities = [];
public IReadOnlyDictionary<string, IEntity> RegisteredEntities => registeredEntities;
public void Add(IEntity entity)
{
if (registeredEntities.TryAdd(entity.Id, entity))
OnEntityRegistered?.Invoke(this, new(entity));
}
public void QueueAssign(string id, Action<IEntity> setMethod)
{
assignCallbacks.TryAdd(id, null);
assignCallbacks[id] = assignCallbacks[id] + setMethod;
}
public void AssignAll()
{
foreach ((string id, Action<IEntity>? action) in assignCallbacks)
action?.Invoke(registeredEntities[id]);
}
public void Reset()
{
assignCallbacks.Clear();
registeredEntities.Clear();
}
public readonly record struct EntityRegisteredArguments(IEntity Entity);
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
namespace Engine.Core.Serialization;
public class IdentifiableRegistry
{
public Event<IdentifiableRegistry, EntityRegisteredArguments> OnEntityRegistered = null!;
private readonly Dictionary<string, Action<IIdentifiable>?> assignCallbacks = [];
private readonly Dictionary<string, IIdentifiable> registeredEntities = [];
public IReadOnlyDictionary<string, IIdentifiable> RegisteredEntities => registeredEntities;
public void Add(IIdentifiable identifiable)
{
if (registeredEntities.TryAdd(identifiable.Id, identifiable))
OnEntityRegistered?.Invoke(this, new(identifiable));
}
public void QueueAssign(string id, Action<IIdentifiable> setMethod)
{
assignCallbacks.TryAdd(id, null);
assignCallbacks[id] = assignCallbacks[id] + setMethod;
}
public void AssignAll()
{
foreach ((string id, Action<IIdentifiable>? action) in assignCallbacks)
action?.Invoke(registeredEntities[id]);
}
public void Reset()
{
assignCallbacks.Clear();
registeredEntities.Clear();
}
public readonly record struct EntityRegisteredArguments(IIdentifiable Entity);
}

View File

@@ -43,8 +43,8 @@ public class SerializedClass
continue;
object? value = privatePropertyInfo.GetValue(@class);
if (value is IEntity entity)
Private.Add(privatePropertyInfo.Name, entity.Id);
if (value is IIdentifiable identifiable)
Private.Add(privatePropertyInfo.Name, identifiable.Id);
else
Private.Add(privatePropertyInfo.Name, value);
}
@@ -61,8 +61,8 @@ public class SerializedClass
continue;
object? value = publicPropertyInfo.GetValue(@class);
if (value is IEntity entity)
Public.Add(publicPropertyInfo.Name, entity.Id);
if (value is IIdentifiable identifiable)
Public.Add(publicPropertyInfo.Name, identifiable.Id);
else
Public.Add(publicPropertyInfo.Name, value);
}
@@ -76,8 +76,8 @@ public class SerializedClass
continue;
object? value = privateFieldInfo.GetValue(@class);
if (value is IEntity entity)
Private.Add(privateFieldInfo.Name, entity.Id);
if (value is IIdentifiable identifiable)
Private.Add(privateFieldInfo.Name, identifiable.Id);
else
Private.Add(privateFieldInfo.Name, value);
}
@@ -91,8 +91,8 @@ public class SerializedClass
continue;
object? value = publicFieldInfo.GetValue(@class);
if (value is IEntity entity)
Public.Add(publicFieldInfo.Name, entity.Id);
if (value is IIdentifiable identifiable)
Public.Add(publicFieldInfo.Name, identifiable.Id);
else
Public.Add(publicFieldInfo.Name, value);
}
@@ -112,36 +112,36 @@ public class SerializedClass
return instance;
}
public object CreateInstance(EntityRegistry? entityRegistry)
public object CreateInstance(IdentifiableRegistry? identifiableRegistry)
{
if (entityRegistry is null)
if (identifiableRegistry is null)
return CreateInstance();
Type type = TypeFactory.GetType(Type);
object instance = TypeFactory.Get(type);
foreach ((string key, object? value) in Private)
AssignVariable(key, type, instance, value, PRIVATE_BINDING_FLAGS, entityRegistry);
AssignVariable(key, type, instance, value, PRIVATE_BINDING_FLAGS, identifiableRegistry);
foreach ((string key, object? value) in Public)
AssignVariable(key, type, instance, value, PUBLIC_BINDING_FLAGS, entityRegistry);
AssignVariable(key, type, instance, value, PUBLIC_BINDING_FLAGS, identifiableRegistry);
return instance;
}
private static void AssignVariable(string key, Type type, object instance, object? value, BindingFlags bindingFlags, EntityRegistry entityRegistry)
private static void AssignVariable(string key, Type type, object instance, object? value, BindingFlags bindingFlags, IdentifiableRegistry identifiableRegistry)
{
if (type.GetField(key, bindingFlags) is FieldInfo fieldInfo)
{
if (typeof(IEntity).IsAssignableFrom(fieldInfo.FieldType))
entityRegistry.QueueAssign(value?.ToString() ?? "", (entity) => fieldInfo.SetValue(instance, entity));
if (typeof(IIdentifiable).IsAssignableFrom(fieldInfo.FieldType))
identifiableRegistry.QueueAssign(value?.ToString() ?? "", (entity) => fieldInfo.SetValue(instance, entity));
else
fieldInfo.SetValue(instance, value);
}
else if (type.GetProperty(key, bindingFlags) is PropertyInfo propertyInfo)
{
if (typeof(IEntity).IsAssignableFrom(propertyInfo.PropertyType))
entityRegistry.QueueAssign(value?.ToString() ?? "", (entity) => propertyInfo.SetValue(instance, entity));
if (typeof(IIdentifiable).IsAssignableFrom(propertyInfo.PropertyType))
identifiableRegistry.QueueAssign(value?.ToString() ?? "", (entity) => propertyInfo.SetValue(instance, entity));
else
propertyInfo.SetValue(instance, value);
}

View File

@@ -9,16 +9,19 @@ 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);
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.");
enterUniverses.Assign(universe);
foreach (IUniverseObject universeObject in universe.UniverseObjects)
foreach (IUniverseObject universeObject in universe)
OnUniverseObjectRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.AddListener(OnUniverseObjectRegistered);
@@ -29,7 +32,7 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent
{
enterUniverses.Unassign();
foreach (IUniverseObject universeObject in universe.UniverseObjects)
foreach (IUniverseObject universeObject in universe)
OnUniverseObjectUnRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.RemoveListener(OnUniverseObjectRegistered);
@@ -38,6 +41,8 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent
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];
@@ -61,14 +66,8 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent
toCallEnterUniverses.Add(args.BehaviourCollected);
}
private void OnExitUniverseCollected(IBehaviourCollector<IExitUniverse> sender, IBehaviourCollector<IExitUniverse>.BehaviourCollectedArguments args)
{
toCallExitUniverses.Add(args.BehaviourCollected);
}
public UniverseEntranceManager()
{
enterUniverses.OnCollected.AddListener(OnEnterUniverseCollected);
exitUniverses.OnCollected.AddListener(OnExitUniverseCollected);
}
}

View File

@@ -31,7 +31,7 @@ public class Universe : BaseEntity, IUniverse
delegateOnUniverseObjectExitedUniverse = OnUniverseObjectExitedUniverse;
}
public IReadOnlyList<IUniverseObject> UniverseObjects => _universeObjects;
public IUniverseObject Root { get; private set; } = Factory.UniverseObjectFactory.Instantiate().SetUniverseObject("Root");
public UniverseTime Time { get; private set; } = new();
public UniverseTime UnscaledTime { get; private set; } = new();
@@ -63,14 +63,17 @@ public class Universe : BaseEntity, IUniverse
if (!universeObject.Initialize())
throw new Exception($"{universeObject.Name} can't be initialized");
for (int i = 0; i < universeObject.Children.Count; i++)
Register(universeObject.Children[i]);
if (universeObject.Parent == null)
universeObject.Parent = Root;
_universeObjects.Add(universeObject);
if (!universeObject.EnterUniverse(this))
throw new Exception($"{universeObject.Name} can't enter the universe");
for (int i = 0; i < universeObject.Children.Count; i++)
Register(universeObject.Children[i]);
OnUniverseObjectRegistered?.Invoke(this, new(universeObject));
}
@@ -113,15 +116,15 @@ public class Universe : BaseEntity, IUniverse
protected override void InitializeInternal()
{
foreach (IUniverseObject universeObject in UniverseObjects)
foreach (IUniverseObject universeObject in _universeObjects)
universeObject.Initialize();
}
protected override void FinalizeInternal()
{
base.FinalizeInternal();
for (int i = UniverseObjects.Count - 1; i >= 0; i--)
Remove(UniverseObjects[i]);
for (int i = _universeObjects.Count - 1; i >= 0; i--)
Remove(_universeObjects[i]);
}
public void Update(UniverseTime engineTime)
@@ -158,6 +161,6 @@ public class Universe : BaseEntity, IUniverse
Remove(universeObject);
}
public IEnumerator<IUniverseObject> GetEnumerator() => _universeObjects.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _universeObjects.GetEnumerator();
public IEnumerator<IUniverseObject> GetEnumerator() => Root.TraverseChildren();
IEnumerator IEnumerable.GetEnumerator() => Root.TraverseChildren();
}

View File

@@ -11,6 +11,7 @@ namespace Engine.Systems.Network;
public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicatorClient
{
private readonly NetDataWriter netDataWriter = new();
private readonly NetDataWriter netDataWriterEncrypted = new();
private CancellationTokenSource? cancellationTokenSource = null;
@@ -42,7 +43,20 @@ public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicator
public INetworkCommunicatorClient SendToServer<T>(T packet, PacketDelivery packetDelivery) where T : class, new()
{
netDataWriter.Reset();
netDataWriterEncrypted.Reset();
if (packet is INetworkPacketEncrypted)
{
netPacketProcessor.Write(netDataWriterEncrypted, packet);
byte[] encryptedData = cryptor.Encrypt(netDataWriterEncrypted.CopyData());
netDataWriter.Put(true);
netDataWriter.PutBytesWithLength(encryptedData);
}
else
{
netDataWriter.Put(false);
netPacketProcessor.Write(netDataWriter, packet);
}
switch (packetDelivery)
{

View File

@@ -12,6 +12,7 @@ namespace Engine.Systems.Network;
public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IExitUniverse, INetworkCommunicator
{
protected readonly NetPacketProcessor netPacketProcessor = new();
protected readonly PacketCryptor cryptor = new("At4ywW9PGoWH3g==", "NmpMFTvd3pvUbA=="); // TODO implement public key exchange
private readonly Dictionary<Type, Event<IConnection, object>> listeners = [];
private readonly Dictionary<string, IConnection> _connections = [];
@@ -54,7 +55,20 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IE
private void NetworkReceiveEvent(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod)
{
try { netPacketProcessor.ReadAllPackets(reader, peer); }
try
{
bool isEncrypted = reader.GetBool();
if (isEncrypted) // TODO performance improvements
{
byte[] encryptedData = reader.GetBytesWithLength();
byte[] decryptedData = cryptor.Decrypt(encryptedData);
NetDataReader innerReader = new(decryptedData);
netPacketProcessor.ReadAllPackets(innerReader, peer);
return;
}
netPacketProcessor.ReadAllPackets(reader, peer);
}
catch (Exception exception) { logger?.LogException(this, exception, force: true); }
}

View File

@@ -13,6 +13,7 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
public int Port { get; private set; } = 8888;
private readonly NetDataWriter netDataWriter = new();
private readonly NetDataWriter netDataWriterEncrypted = new();
public LiteNetLibServer() : this(8888, 2) { }
public LiteNetLibServer(int port, int maxConnectionCount) : base()
@@ -53,11 +54,25 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
public INetworkCommunicatorServer SendToClient<T>(IConnection connection, T packet, PacketDelivery packetDelivery) where T : class, new()
{
netDataWriter.Reset();
netPacketProcessor.Write(netDataWriter, packet);
if (Manager.ConnectedPeerList.FirstOrDefault(p => p.Id.CompareTo(connection.Id) == 0) is not NetPeer netPeer)
if (Manager.ConnectedPeerList.FirstOrDefault(p => p.Id.ToString().CompareTo(connection.Id) == 0) is not NetPeer netPeer)
throw new($"Peer {connection} couldn't be found.");
if (packet is INetworkPacketEncrypted) // TODO performance improvements
{
netDataWriterEncrypted.Reset();
netPacketProcessor.Write(netDataWriterEncrypted, packet);
byte[] encryptedData = cryptor.Encrypt(netDataWriterEncrypted.CopyData());
netDataWriter.Put(true);
netDataWriter.PutBytesWithLength(encryptedData);
}
else
{
netDataWriter.Put(false);
netPacketProcessor.Write(netDataWriter, packet);
}
switch (packetDelivery)
{
case PacketDelivery.ReliableInOrder: netPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break;
@@ -75,6 +90,23 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
netDataWriter.Reset();
netPacketProcessor.Write(netDataWriter, packet);
if (packet is INetworkPacketEncrypted)
{
netDataWriterEncrypted.Reset();
logger?.Log($"Encrypted Packet Sending");
netPacketProcessor.Write(netDataWriterEncrypted, packet);
byte[] encryptedData = cryptor.Encrypt(netDataWriterEncrypted.CopyData());
netDataWriter.PutBytesWithLength(encryptedData);
netDataWriter.Put(true);
}
else
{
logger?.Log($"Regular Packet Sending");
netPacketProcessor.Write(netDataWriter, packet);
netDataWriter.Put(false);
}
switch (packetDelivery)
{
case PacketDelivery.ReliableInOrder: Manager.SendToAll(netDataWriter, DeliveryMethod.ReliableOrdered); break;

View File

@@ -0,0 +1,26 @@
using System.Security.Cryptography;
using System.Text;
namespace Engine.Systems.Network;
public class PacketCryptor // TODO performance improvements
{
private readonly Aes aes = null!;
private readonly ICryptoTransform encrpytor = null!;
private readonly ICryptoTransform decryptor = null!;
public byte[] Encrypt(byte[] data) => encrpytor.TransformFinalBlock(data, 0, data.Length);
public byte[] Decrypt(byte[] data) => decryptor.TransformFinalBlock(data, 0, data.Length);
public PacketCryptor(string key, string initializationVector)
{
aes = Aes.Create();
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = Encoding.UTF8.GetBytes(initializationVector);
encrpytor = aes.CreateEncryptor();
decryptor = aes.CreateDecryptor();
}
}

View File

@@ -5,7 +5,7 @@ using Engine.Core;
namespace Engine.Integration.MonoGame;
public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, IPreDraw
public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw
{
public Event<MonoGameCamera2D> OnMatrixTransformChanged { get; } = new();
public Event<MonoGameCamera2D> OnViewportChanged { get; } = new();
@@ -56,7 +56,7 @@ public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, IPreDra
get => _zoom;
set
{
float newValue = Engine.Core.Math.Max(0.1f, value);
float newValue = Math.Max(0.1f, value);
if (_zoom == newValue)
return;
@@ -84,22 +84,21 @@ public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, IPreDra
return screenPosition.Scale(EngineConverterExtensions.screenScale);
}
public void LastActiveFrame() => Transform = null!;
public void FirstActiveFrame()
{
Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics;
Viewport = Graphics.GraphicsDevice.Viewport;
Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
}
public void PreDraw()
{
MatrixTransform =
Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) *
Matrix.CreateRotationZ(Rotation * Engine.Core.Math.DegreeToRadian) *
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));
}
protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
protected sealed override void FinalizeInternal() => Transform = null!;
}

View File

@@ -5,17 +5,23 @@ using Engine.Core;
namespace Engine.Integration.MonoGame;
public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDraw
public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw
{
public Event<MonoGameCamera3D, ViewChangedArguments> OnViewChanged { get; } = new();
public Event<MonoGameCamera3D, ProjectionChangedArguments> OnProjectionChanged { get; } = new();
public Event<MonoGameCamera3D, ViewportChangedArguments> OnViewportChanged { get; } = new();
public Event<MonoGameCamera3D, FieldOfViewChangedArguments> OnFieldOfViewChanged { get; } = new();
public Event<ICamera3D, ICamera3D.NearPlaneChangedArguments> OnNearPlaneChanged { get; } = new();
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 _fieldOfView = 1f;
private float _nearPlane = 0.01f;
private float _farPlane = 100f;
private float _fieldOfView = Math.DegreeToRadian * 70f;
private bool isRecalculationNeeded = true;
public GraphicsDeviceManager Graphics { get; private set; } = null!;
public ITransform3D Transform { get; private set; } = null!;
@@ -47,12 +53,6 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDra
}
}
public Vector3D Position
{
get => Transform.Position;
set => Transform.Position = value;
}
public Viewport Viewport
{
get => _viewport;
@@ -63,32 +63,52 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDra
Viewport previousViewport = _viewport;
_viewport = value;
SetForRecalculation();
OnViewportChanged.Invoke(this, new(previousViewport));
}
}
public float NearPlane
{
get => _nearPlane;
set
{
float previousNearPlane = _nearPlane;
_nearPlane = value.Max(0.0001f);
SetForRecalculation();
OnNearPlaneChanged.Invoke(this, new(previousNearPlane));
}
}
public float FarPlane
{
get => _farPlane;
set
{
float previousFarPlane = _farPlane;
_farPlane = value.Max(NearPlane);
SetForRecalculation();
OnFarPlaneChanged.Invoke(this, new(previousFarPlane));
}
}
public float FieldOfView
{
get => _fieldOfView;
get => _fieldOfView * Math.RadianToDegree;
set
{
float newValue = Math.Max(0.1f, value);
value = value.Max(0.1f) * Math.DegreeToRadian;
if (_fieldOfView == newValue)
if (_fieldOfView == value)
return;
float previousFieldOfView = _fieldOfView;
_fieldOfView = newValue;
_fieldOfView = value;
SetForRecalculation();
OnFieldOfViewChanged.Invoke(this, new(previousFieldOfView));
}
}
public Engine.Core.Quaternion Rotation
{
get => Transform.Rotation;
set => Transform.Rotation = value;
}
// TODO This causes delay since OnPreDraw calls assuming this is called in in Update
public Ray3D ScreenToWorldRay(Vector2D screenPosition)
{
@@ -104,28 +124,61 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDra
public Vector2D WorldToScreenPosition(Vector3D worldPosition) => Viewport.Project(worldPosition.ToVector3(), _projection, _view, Matrix.Identity).ToVector3D();
public void LastActiveFrame() => Transform.OnTransformUpdated.RemoveListener(SetDirtyOnTransformUpdate);
public void FirstActiveFrame()
{
Transform = BehaviourController.GetRequiredBehaviour<ITransform3D>();
Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics;
Viewport = Graphics.GraphicsDevice.Viewport;
Transform.OnTransformUpdated.AddListener(SetDirtyOnTransformUpdate);
}
private void SetDirtyOnTransformUpdate(ITransform3D sender) => SetForRecalculation();
private void SetForRecalculation() => isRecalculationNeeded = true;
public void PreDraw()
{
Vector3 cameraPosition = Position.ToVector3();
View = Matrix.CreateLookAt(
cameraPosition,
Transform.Forward.ToVector3() + cameraPosition,
Transform.Up.ToVector3()
);
Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Viewport.AspectRatio, 0.1f, 100f);
if (!isRecalculationNeeded)
return;
CalculateView();
CalculateProjection();
isRecalculationNeeded = false;
}
protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour<ITransform3D>();
protected sealed override void FinalizeInternal() => Transform = null!;
private void CalculateView()
{
Vector3 forward = Vector3.Normalize(Transform.Forward.ToVector3());
Vector3 up = Vector3.Normalize(Transform.Up.ToVector3());
Vector3 right = Vector3.Normalize(Transform.Right.ToVector3());
Vector3 position = Transform.Position.ToVector3();
View = new Matrix(
right.X, up.X, forward.X, 0,
right.Y, up.Y, forward.Y, 0,
right.Z, up.Z, forward.Z, 0,
-Vector3.Dot(right, position),
-Vector3.Dot(up, position),
-Vector3.Dot(forward, position),
1
);
}
private void CalculateProjection()
{
float yScale = 1f / (float)Math.Tan(_fieldOfView / 2f);
float xScale = yScale / Viewport.AspectRatio;
Projection = new Matrix(
xScale, 0, 0, 0,
0, yScale, 0, 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 ViewportChangedArguments(Viewport PreviousViewport);
public readonly record struct FieldOfViewChangedArguments(float PreviousFieldOfView);
}

View File

@@ -41,6 +41,14 @@ public static class EngineConverterExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 ToVector3(this Vector3D vector) => new(vector.X, vector.Y, vector.Z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix ToXnaMatrix(this Matrix4x4 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);

View File

@@ -19,10 +19,12 @@ public class MonoGameWindow : Game
IsMouseVisible = true;
Universe = universe ?? new Universe();
Universe.InstantiateUniverseObject().SetUniverseObject("Window Container")
IUniverseObject monogameParent = Universe.InstantiateUniverseObject().SetUniverseObject("MonoGame");
Universe.InstantiateUniverseObject().SetUniverseObject("Window Container", monogameParent)
.BehaviourController.AddBehaviour<MonoGameWindowContainer>(this);
Universe.InstantiateUniverseObject().SetUniverseObject("Content Loader")
Universe.InstantiateUniverseObject().SetUniverseObject("Content Loader", monogameParent)
.BehaviourController.AddBehaviour<LoadContentManager>();
}

View File

@@ -8,6 +8,6 @@ namespace Engine.Serializers.Yaml;
public interface IEngineTypeYamlConverter : IYamlTypeConverter
{
YamlSerializer Serializer { get; set; }
EntityRegistry EntityRegistry { get; set; }
IdentifiableRegistry IdentifiableRegistry { get; set; }
IProgressionTracker ProgressionTracker { get; set; }
}

View File

@@ -36,7 +36,7 @@ public class BehaviourControllerConverter : EngineTypeYamlSerializerBase<IBehavi
throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
ProgressionTracker.Set(isTrackingController ? .5f : ProgressionTracker.Progression, $"Creating {instanceSerializedClass.Type}");
behaviourController = (IBehaviourController)instanceSerializedClass.CreateInstance(EntityRegistry);
behaviourController = (IBehaviourController)instanceSerializedClass.CreateInstance(IdentifiableRegistry);
string value = parser.Consume<Scalar>().Value;
if (value.CompareTo(nameof(IBehaviourController.StateEnable)) != 0)
@@ -80,7 +80,7 @@ public class BehaviourControllerConverter : EngineTypeYamlSerializerBase<IBehavi
serializer(behaviourController.StateEnable);
emitter.Emit(new Scalar(BEHAVIOURS_SCALAR_NAME));
serializer(behaviourController.GetBehaviours<IBehaviour>().Where(b => !b.GetType().HasAttribute<IgnoreSerializationAttribute>()));
serializer(behaviourController.GetBehaviours<IBehaviour>().Where(b => !b.GetType().HasAttribute<IgnoreSerializationAttribute>()).Reverse());
ProgressionTracker.Set(isTrackingController ? 1f : ProgressionTracker.Progression, $"Serialized behaviour controller");
emitter.Emit(new MappingEnd());

View File

@@ -36,7 +36,7 @@ public class BehaviourConverter : EngineTypeYamlSerializerBase<IBehaviour>
throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
ProgressionTracker.Set(isTrackingController ? .5f : ProgressionTracker.Progression, $"Creating {instanceSerializedClass.Type}");
behaviour = (IBehaviour)instanceSerializedClass.CreateInstance(EntityRegistry);
behaviour = (IBehaviour)instanceSerializedClass.CreateInstance(IdentifiableRegistry);
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IBehaviour.StateEnable)) != 0)
throw new();

View File

@@ -12,7 +12,7 @@ public abstract class EngineTypeYamlSerializerBase<T> : IEngineTypeYamlConverter
{
protected const string SERIALIZED_SCALAR_NAME = "Properties";
public EntityRegistry EntityRegistry { get; set; } = null!;
public IdentifiableRegistry IdentifiableRegistry { get; set; } = null!;
public YamlSerializer Serializer { get; set; } = null!;
public IProgressionTracker ProgressionTracker { get; set; } = null!;
@@ -24,7 +24,7 @@ public abstract class EngineTypeYamlSerializerBase<T> : IEngineTypeYamlConverter
T? result = Read(parser, type, rootDeserializer);
if (result is IEntity entity)
EntityRegistry.Add(entity);
IdentifiableRegistry.Add(entity);
return result;
}

View File

@@ -20,8 +20,8 @@ public class SerializedClassConverter : EngineTypeYamlSerializerBase<SerializedC
ProgressionTracker.Set(isTrackingController ? .1f : ProgressionTracker.Progression, $"Reading class");
SerializedClass serializedClass = new();
Dictionary<string, TypeContainer> publicDictionary = [];
Dictionary<string, TypeContainer> privateDictionary = [];
Dictionary<string, object> publicDictionary = [];
Dictionary<string, object> privateDictionary = [];
parser.Consume<MappingStart>();
@@ -32,16 +32,24 @@ public class SerializedClassConverter : EngineTypeYamlSerializerBase<SerializedC
switch (key)
{
case nameof(SerializedClass.Type): serializedClass.Type = parser.Consume<Scalar>().Value; break;
case nameof(SerializedClass.Public): publicDictionary = (Dictionary<string, TypeContainer>)rootDeserializer(typeof(Dictionary<string, TypeContainer>))!; break;
case nameof(SerializedClass.Private): privateDictionary = (Dictionary<string, TypeContainer>)rootDeserializer(typeof(Dictionary<string, TypeContainer>))!; break;
case nameof(SerializedClass.Public): publicDictionary = (Dictionary<string, object>)rootDeserializer(typeof(Dictionary<string, object>))!; break;
case nameof(SerializedClass.Private): privateDictionary = (Dictionary<string, object>)rootDeserializer(typeof(Dictionary<string, object>))!; break;
}
}
foreach ((string key, TypeContainer typeContainer) in publicDictionary)
Type classType = TypeFactory.GetType(serializedClass.Type);
foreach ((string key, object @object) in publicDictionary)
if (@object is TypeContainer typeContainer)
serializedClass.Public.Add(key, Serializer.InternalDeserialize(typeContainer.Value!.ToString()!, TypeFactory.GetType(typeContainer.Type)));
else
serializedClass.Public.Add(key, Serializer.InternalDeserialize(@object.ToString()!, (classType.GetProperty(key, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)?.PropertyType ?? classType.GetField(key, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)?.FieldType)!));
foreach ((string key, TypeContainer typeContainer) in privateDictionary)
foreach ((string key, object @object) in privateDictionary)
if (@object is TypeContainer typeContainer)
serializedClass.Private.Add(key, Serializer.InternalDeserialize(typeContainer.Value!.ToString()!, TypeFactory.GetType(typeContainer.Type)));
else
serializedClass.Private.Add(key, Serializer.InternalDeserialize(@object.ToString()!, (classType.GetProperty(key, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Default)?.PropertyType ?? classType.GetField(key, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Default)?.FieldType)!));
ProgressionTracker.Set(isTrackingController ? 1f : ProgressionTracker.Progression, $"Read {serializedClass.Type}");
return serializedClass;
@@ -54,13 +62,19 @@ public class SerializedClassConverter : EngineTypeYamlSerializerBase<SerializedC
bool isTrackingController = ProgressionTracker.Progression.ApproximatelyEquals(0f);
ProgressionTracker.Set(isTrackingController ? .25f : ProgressionTracker.Progression, $"Serializing {serializedClass.Type}");
Dictionary<string, TypeContainer> publics = [];
Dictionary<string, TypeContainer> privates = [];
Dictionary<string, object> publics = [];
Dictionary<string, object> privates = [];
foreach ((string key, object? @object) in serializedClass.Public.Where(v => !v.GetType().HasAttribute<IgnoreSerializationAttribute>()))
if (@object?.GetType().IsClass == false)
publics.Add(key, @object!);
else
publics.Add(key, new TypeContainer(@object));
foreach ((string key, object? @object) in serializedClass.Private.Where(v => !v.GetType().HasAttribute<IgnoreSerializationAttribute>()))
if (@object?.GetType().IsClass == false)
privates.Add(key, @object!);
else
privates.Add(key, new TypeContainer(@object));
emitter.Emit(new MappingStart());

View File

@@ -29,7 +29,7 @@ public class StateEnableConverter : EngineTypeYamlSerializerBase<IStateEnable>
if (parser.Consume<Scalar>().Value.CompareTo(SERIALIZED_SCALAR_NAME) != 0)
throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
stateEnable = (IStateEnable)instanceSerializedClass.CreateInstance(EntityRegistry);
stateEnable = (IStateEnable)instanceSerializedClass.CreateInstance(IdentifiableRegistry);
parser.Consume<MappingEnd>();

View File

@@ -22,7 +22,7 @@ public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse>
IUniverse universe;
IStateEnable stateEnable;
List<IUniverseObject> universeObjects;
IUniverseObject rootUniverseObject;
parser.Consume<MappingStart>();
@@ -34,16 +34,16 @@ public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse>
throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
ProgressionTracker.Set(isTrackingController ? .2f : ProgressionTracker.Progression, $"Creating {instanceSerializedClass.Type}");
universe = (IUniverse)instanceSerializedClass.CreateInstance(EntityRegistry);
universe = (IUniverse)instanceSerializedClass.CreateInstance(IdentifiableRegistry);
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IUniverse.StateEnable)) != 0)
throw new();
stateEnable = (IStateEnable)rootDeserializer(typeof(IStateEnable))!;
ProgressionTracker.Set(isTrackingController ? .5f : ProgressionTracker.Progression, $"Reading universe objects");
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IUniverse.UniverseObjects)) != 0)
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IUniverse.Root)) != 0)
throw new();
universeObjects = (List<IUniverseObject>)rootDeserializer(typeof(List<IUniverseObject>))!;
rootUniverseObject = (IUniverseObject)rootDeserializer(typeof(IUniverseObject))!;
parser.Consume<MappingEnd>();
@@ -52,13 +52,12 @@ public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse>
stateEnable.Assign(universe);
universe.Assign(stateEnable);
ProgressionTracker.Set(isTrackingController ? .9f : ProgressionTracker.Progression, "Registering universe objects");
for (int i = 0; i < universeObjects.Count; i++)
ProgressionTracker.Set(isTrackingController ? .9f : ProgressionTracker.Progression, "Registering root universe object");
for (int i = rootUniverseObject.Children.Count - 1; i >= 0; i--)
{
IUniverseObject uo = universeObjects[i];
ProgressionTracker.Set(isTrackingController ? .9f + .1f * ((float)i / universeObjects.Count) : ProgressionTracker.Progression, $"Registering {uo.Name}");
universe.Register(uo);
IUniverseObject uo = rootUniverseObject.Children[i];
ProgressionTracker.Set(isTrackingController ? .9f + .1f * ((float)i / rootUniverseObject.Children.Count) : ProgressionTracker.Progression, $"Registering {uo.Name}");
universe.Root.AddChild(uo);
}
ProgressionTracker.Set(isTrackingController ? 1f : ProgressionTracker.Progression, $"Created {instanceSerializedClass.Type}");
@@ -72,7 +71,7 @@ public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse>
bool isTrackingController = ProgressionTracker.Progression.ApproximatelyEquals(0f);
ProgressionTracker.Set(isTrackingController ? .25f : ProgressionTracker.Progression, $"Serializing universe");
IEnumerable<IUniverseObject> rootUniverseObjects = universe.UniverseObjects.Where(uo => uo.Parent is null);
IUniverseObject rootUniverseObject = universe.Root;
emitter.Emit(new MappingStart());
@@ -85,8 +84,8 @@ public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse>
emitter.Emit(new Scalar(nameof(IUniverse.StateEnable)));
serializer(universe.StateEnable);
emitter.Emit(new Scalar(nameof(IUniverse.UniverseObjects)));
serializer(rootUniverseObjects);
emitter.Emit(new Scalar(nameof(IUniverse.Root)));
serializer(rootUniverseObject);
ProgressionTracker.Set(isTrackingController ? 1f : ProgressionTracker.Progression, $"Serialized universe");
emitter.Emit(new MappingEnd());

View File

@@ -40,7 +40,7 @@ public class UniverseObjectSerializer : EngineTypeYamlSerializerBase<IUniverseOb
throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
ProgressionTracker.Set(isTrackingController ? .3f : ProgressionTracker.Progression, $"Creating {instanceSerializedClass.Type}");
universeObject = (IUniverseObject)instanceSerializedClass.CreateInstance(EntityRegistry);
universeObject = (IUniverseObject)instanceSerializedClass.CreateInstance(IdentifiableRegistry);
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IUniverseObject.StateEnable)) != 0)
throw new();

View File

@@ -17,14 +17,14 @@ public class YamlSerializer : Core.Serialization.ISerializer
private readonly YamlDotNet.Serialization.ISerializer serializer = null!;
private readonly YamlDotNet.Serialization.IDeserializer deserializer = null!;
private readonly EntityRegistry entityRegistry = null!;
private readonly IdentifiableRegistry identifiableRegistry = null!;
private readonly IProgressionTracker progressionTracker = null!;
private readonly System.Threading.Lock Lock = new();
public YamlSerializer()
{
entityRegistry = new();
identifiableRegistry = new();
progressionTracker = new ProgressionTracker();
SerializerBuilder serializerBuilder = new SerializerBuilder()
@@ -37,7 +37,7 @@ public class YamlSerializer : Core.Serialization.ISerializer
foreach (IEngineTypeYamlConverter typeConverter in GetEngineYamlTypeConverters())
{
typeConverter.Serializer = this;
typeConverter.EntityRegistry = entityRegistry;
typeConverter.IdentifiableRegistry = identifiableRegistry;
typeConverter.ProgressionTracker = progressionTracker;
deserializerBuilder = deserializerBuilder.WithTypeConverter(typeConverter);
@@ -66,9 +66,9 @@ public class YamlSerializer : Core.Serialization.ISerializer
{
lock (Lock)
{
entityRegistry.Reset();
identifiableRegistry.Reset();
object result = deserializer.Deserialize(configuration)!;
entityRegistry.AssignAll();
identifiableRegistry.AssignAll();
return result;
}
}
@@ -77,9 +77,9 @@ public class YamlSerializer : Core.Serialization.ISerializer
{
lock (Lock)
{
entityRegistry.Reset();
identifiableRegistry.Reset();
object result = deserializer.Deserialize(configuration, type)!;
entityRegistry.AssignAll();
identifiableRegistry.AssignAll();
return result;
}
}
@@ -88,9 +88,9 @@ public class YamlSerializer : Core.Serialization.ISerializer
{
lock (Lock)
{
entityRegistry.Reset();
identifiableRegistry.Reset();
T result = deserializer.Deserialize<T>(configuration);
entityRegistry.AssignAll();
identifiableRegistry.AssignAll();
return result;
}
}

View File

@@ -0,0 +1,3 @@
namespace Engine.Systems.Network;
public interface INetworkPacketEncrypted : INetworkPacket;

View File

@@ -0,0 +1,16 @@
using Engine.Core;
namespace Engine.Systems.Network;
/// <summary>
/// Basic client behaviour that finds the <see cref="INetworkCommunicatorClient"/> in the universe in it's first active frame.
/// <br/>
/// Disclaimer: It implements <see cref="IFirstFrameUpdate"/> and <see cref="ILastFrameUpdate"/> in virtual methods.
/// </summary>
public class ClientBehaviour : Behaviour, IFirstFrameUpdate, ILastFrameUpdate
{
public INetworkCommunicatorClient Client { get; private set; } = null!;
public virtual void FirstActiveFrame() => Client = Universe.FindRequiredBehaviour<INetworkCommunicatorClient>();
public virtual void LastActiveFrame() => Client = null!;
}

View File

@@ -0,0 +1,21 @@
namespace Engine.Systems.Network;
public static class Hasher
{
public static long FNV1a(string name)
{
const long fnvPrime = 1099511628211;
long result = 1469598103934665603;
unchecked
{
foreach (char c in name)
{
result ^= c;
result *= fnvPrime;
}
}
return result;
}
}

View File

@@ -0,0 +1,16 @@
using Engine.Core;
namespace Engine.Systems.Network;
/// <summary>
/// Basic server behaviour that finds the <see cref="INetworkCommunicatorServer"/> in the universe in it's first active frame.
/// <br/>
/// Disclaimer: It implements <see cref="IFirstFrameUpdate"/> and <see cref="ILastFrameUpdate"/> in virtual methods.
/// </summary>
public class ServerBehaviour : Behaviour, IFirstFrameUpdate, ILastFrameUpdate
{
public INetworkCommunicatorServer Server { get; private set; } = null!;
public virtual void FirstActiveFrame() => Server = Universe.FindRequiredBehaviour<INetworkCommunicatorServer>();
public virtual void LastActiveFrame() => Server = null!;
}

View File

@@ -8,18 +8,7 @@ public static class TypeHasher<T>
get
{
if (_fnv1a == 0)
unchecked
{
const long fnvPrime = 1099511628211;
_fnv1a = 1469598103934665603;
string typeName = typeof(T).FullName ?? typeof(T).Name;
foreach (char c in typeName)
{
_fnv1a ^= c;
_fnv1a *= fnvPrime;
}
}
_fnv1a = Hasher.FNV1a(typeof(T).FullName ?? typeof(T).Name);
return _fnv1a;
}