74 Commits

Author SHA1 Message Date
105b87da3a fix: file & type name mismatches fixed 2026-03-08 15:12:16 +03:00
51534606c8 feat: added WaitForTask yield 2026-03-08 12:55:22 +03:00
1418927c32 refactor: renamed WaitForSeconds to a more general WaitForTime class 2026-03-08 12:53:02 +03:00
35c7eb9578 refactor: renamed NetworkBehaviour to CommonNetworkBehaviour 2026-03-06 11:55:10 +03:00
6ca3f22b17 feat: added UpdateManager.CallFirstActiveFrameImmediately method for early calls of behaviours 2026-03-06 11:51:15 +03:00
7ae8b4feb0 feat: added NetworkBehaviour for oth client & server communication 2026-03-05 23:48:57 +03:00
e84c6edce1 refactor: removed the IComparable from IIdentifiable and implemented in extension method 2026-03-04 20:16:07 +03:00
4326d5615e feat: INetworkCommunicatorServer.SendToClients extension method added 2026-03-04 12:32:58 +03:00
fa514531bf feat: added IIdentifiable comparison interface & IsIdentical extension method 2026-03-03 23:30:56 +03:00
1d3dd8b046 chore: fixed IIdentifiable file name and class name being different 2026-03-03 23:14:37 +03:00
785fee9b6b perf: memory allocation improvements on ITween.Loop method 2026-02-20 11:22:46 +03:00
9f54f89f6d fix: ITween.OnEnded getting multiple calls and getting unnecessary calls on repeats fixed 2026-02-20 11:22:00 +03:00
aadc87d78a perf: memory allocation improvements on ITween.LoopIndefinitely method 2026-02-20 11:00:49 +03:00
d653774357 fix: forgotten Save method for YamlConfiguration 2026-02-09 13:23:22 +03:00
45bd505da7 chore: renamed parameter names for ISerializer methods 2026-02-09 13:22:37 +03:00
3b1c291588 feat: IConfiguration for system and other configurations 2026-02-09 13:17:10 +03:00
32a7e9be24 fix: forgotten yaml converter for Matrix4x4 2026-02-09 13:14:45 +03:00
499f875903 chore: removed unused piece of code 2026-02-02 14:42:34 +03:00
b2cfb2a590 docs: added NetworkManager comments 2026-02-01 13:34:02 +03:00
1d6b9d2421 feat: added WaitForSeconds and WaitWhile yields 2026-01-31 13:08:59 +03:00
882f9e8b29 feat: added new yields 2026-01-31 13:08:13 +03:00
913af2a4a4 fix: added dynamic index updates during event invocation so there are no missing/duplicate invocations
This also makes the events very not ideal for multithreaded applications at the moment.
2026-01-31 00:49:00 +03:00
4e9fda3d7c refactor: auto property on FastListOrdered 2026-01-30 18:33:07 +03:00
7675f9acac chore: added missing Matrix4x4NetPacker 2026-01-30 18:02:34 +03:00
72f86478f2 feat!: added broadcast & routing support for NetworkManager
It used to only route or broadcast, now the same packet can be both routed to it's own behaviour AND at the same time the broadcast listeners can get alerted of the same package such as universe-wide managers
2026-01-30 13:43:48 +03:00
64e7321f0f fix: rotating file logger deleting from the wrong order 2026-01-30 10:54:56 +03:00
c355c666e0 refactor: fixed LiteNetLibServer using events to subscribe to PostUpdate instead of the interface 2026-01-29 22:29:19 +03:00
b9f3227f73 refactor: removed parameters on triangle batch calls on TriangleBatcher for multi window support 2026-01-28 22:19:04 +03:00
c68de39c83 fix: MonoGame view matrix calculation issues 2026-01-28 12:58:25 +03:00
ad444decbb perf: removed old position flipping on MonoGame 2026-01-28 11:16:47 +03:00
297e0eb790 fix: typos in Matrix4x4 methods 2026-01-28 11:14:31 +03:00
efa4da4398 fix: ScreenToWorldPosition & vice versa methods on MonoGameCamera2D fixed 2026-01-28 11:06:15 +03:00
e30280f1f8 fix: missing implicit conversion operators on Vectors 2026-01-28 11:02:07 +03:00
d4437edfbf feat: added Matrix4x4 x Vector4D multiplication 2026-01-28 10:22:36 +03:00
08f32f96e4 BREAKING CHANGE: 4x4 matrices are now column major 2026-01-27 23:45:50 +03:00
9294df8a19 feat: added support for multiple batches 2026-01-27 21:28:31 +03:00
50a0269798 feat: added matrix 4x4 transpose & orthographic view matrix methods 2026-01-27 20:48:39 +03:00
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
a050909375 fix: server when sending to individual client messes packet order 2025-11-13 20:24:23 +03:00
24d9cdeafa chore: removed unnecessary logs 2025-11-12 23:23:08 +03:00
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
135 changed files with 2285 additions and 892 deletions

View File

@@ -13,7 +13,7 @@ spelling_exclusion_path = SpellingExclusions.dic
[*.{cs,csx,vb,vbx}] [*.{cs,csx,vb,vbx}]
indent_size = 4 indent_size = 4
insert_final_newline = true insert_final_newline = true
charset = utf-8-bom charset = utf-8
# XML project files # XML project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]

View File

@@ -62,7 +62,8 @@ public interface IBehaviourController : IEntity, IHasUniverseObject
/// </summary> /// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam> /// <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> /// <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> /// <summary>
/// Removes <see cref="IBehaviour"/>s of the specified type from the <see cref="IBehaviourController"/>. /// 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> /// <param name="behaviour">The <see cref="IBehaviour"/> to remove.</param>
void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour; void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour;
enum CollectionMethod { Clear, Append };
readonly record struct BehaviourAddedArguments(IBehaviour BehaviourAdded); readonly record struct BehaviourAddedArguments(IBehaviour BehaviourAdded);
readonly record struct BehaviourRemovedArguments(IBehaviour BehaviourRemoved); readonly record struct BehaviourRemovedArguments(IBehaviour BehaviourRemoved);
} }

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> /// <summary>
/// Represents a 2D camera in the engine. /// Represents a 2D camera in the engine.
/// </summary> /// </summary>
public interface ICamera2D : IBehaviour2D public interface ICamera2D : ICamera, IBehaviour2D
{ {
/// <summary> /// <summary>
/// The zoom level of the camera. /// The zoom level of the camera.

View File

@@ -3,10 +3,35 @@ namespace Engine.Core;
/// <summary> /// <summary>
/// Represents a 3D camera in the engine. /// Represents a 3D camera in the engine.
/// </summary> /// </summary>
public interface ICamera3D : IBehaviour3D public interface ICamera3D : ICamera, IBehaviour3D
{ {
/// <summary> /// <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> /// </summary>
float FieldOfView { get; set; } float FieldOfView { get; set; }
@@ -23,4 +48,8 @@ public interface ICamera3D : IBehaviour3D
/// <param name="worldPosition">The position in world coordinates.</param> /// <param name="worldPosition">The position in world coordinates.</param>
/// <returns>The position in screen coordinates.</returns> /// <returns>The position in screen coordinates.</returns>
Vector2D WorldToScreenPosition(Vector3D worldPosition); 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> /// <summary>
/// Represents a basic entity in the engine. /// Represents a basic entity in the engine.
/// </summary> /// </summary>
public interface IEntity : IInitializable, IHasStateEnable public interface IEntity : IInitializable, IIdentifiable, 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);
}

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; } UniverseTime UnscaledTime { get; }
/// <summary> /// <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> /// </summary>
IReadOnlyList<IUniverseObject> UniverseObjects { get; } IUniverseObject Root { get; }
/// <summary> /// <summary>
/// Registers an <see cref="IUniverseObject"/> to the <see cref="IUniverse"/>. /// Registers an <see cref="IUniverseObject"/> to the <see cref="IUniverse"/>.

View File

@@ -4,52 +4,47 @@ namespace Engine.Core;
public abstract class BaseEntity : IEntity 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> OnInitialized { get; } = new();
public Event<IInitializable> OnFinalized { get; } = new(); public Event<IInitializable> OnFinalized { get; } = new();
public Event<IHasStateEnable> OnStateEnableAssigned { get; } = new(); public Event<IHasStateEnable> OnStateEnableAssigned { get; } = new();
public Event<IAssignable> OnUnassigned { get; } = new(); public Event<IAssignable> OnUnassigned { get; } = new();
private IStateEnable _stateEnable = null!; public virtual IStateEnable StateEnable { get; private set; } = null!;
private bool _initialized = false;
private string _id = string.Empty;
public virtual IStateEnable StateEnable => _stateEnable;
public string Id public string Id
{ {
get => _id; get;
set set
{ {
if (IsInitialized) 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; return;
string previousId = _id; string previousId = field;
_id = value; field = value;
OnIdChanged?.Invoke(this, new(previousId)); OnIdChanged?.Invoke(this, new(previousId));
} }
} } = string.Empty;
public bool IsInitialized public bool IsInitialized
{ {
get => _initialized; get;
private set private set
{ {
if (value == _initialized) if (value == field)
return; return;
_initialized = value; field = value;
if (value) if (value)
OnInitialized?.Invoke(this); OnInitialized?.Invoke(this);
else else
OnFinalized?.Invoke(this); OnFinalized?.Invoke(this);
} }
} } = false;
protected virtual void OnAssign(IStateEnable stateEnable) { } protected virtual void OnAssign(IStateEnable stateEnable) { }
public bool Assign(IStateEnable stateEnable) public bool Assign(IStateEnable stateEnable)
@@ -57,8 +52,8 @@ public abstract class BaseEntity : IEntity
if (IsInitialized) if (IsInitialized)
return false; return false;
_stateEnable = stateEnable; StateEnable = stateEnable;
_stateEnable.Assign(this); StateEnable.Assign(this);
OnAssign(stateEnable); OnAssign(stateEnable);
OnStateEnableAssigned?.Invoke(this); OnStateEnableAssigned?.Invoke(this);
return true; return true;
@@ -72,8 +67,8 @@ public abstract class BaseEntity : IEntity
UnassignInternal(); UnassignInternal();
_stateEnable = null!; StateEnable = null!;
_stateEnable.Unassign(); StateEnable.Unassign();
OnUnassigned?.Invoke(this); OnUnassigned?.Invoke(this);
return true; return true;
} }
@@ -84,7 +79,7 @@ public abstract class BaseEntity : IEntity
if (IsInitialized) if (IsInitialized)
return false; return false;
_stateEnable ??= Factory.StateEnableFactory.Instantiate(this); StateEnable ??= Factory.StateEnableFactory.Instantiate(this);
InitializeInternal(); InitializeInternal();
@@ -104,6 +99,6 @@ public abstract class BaseEntity : IEntity
return true; return true;
} }
protected BaseEntity() => _id = Guid.NewGuid().ToString("D"); protected BaseEntity() => Id = Guid.NewGuid().ToString("D");
protected BaseEntity(string id) => _id = id; 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 IUniverse Universe => BehaviourController.UniverseObject.Universe;
public IUniverseObject UniverseObject => BehaviourController.UniverseObject; public IUniverseObject UniverseObject => BehaviourController.UniverseObject;
private IBehaviourController _behaviourController = null!; public IBehaviourController BehaviourController { get; private set; } = null!;
public IBehaviourController BehaviourController => _behaviourController;
private int _priority = 0;
public int Priority public int Priority
{ {
get => _priority; get;
set set
{ {
if (value == _priority) if (value == field)
return; return;
int previousPriority = _priority; int previousPriority = field;
_priority = value; field = value;
OnPriorityChanged?.Invoke(this, new(previousPriority)); OnPriorityChanged?.Invoke(this, new(previousPriority));
} }
} } = 0;
private bool _isActive = false; public bool IsActive { get; private set; } = false;
public bool IsActive => _isActive;
protected virtual void OnAssign(IBehaviourController behaviourController) { } protected virtual void OnAssign(IBehaviourController behaviourController) { }
public bool Assign(IBehaviourController behaviourController) public bool Assign(IBehaviourController behaviourController)
@@ -41,7 +38,7 @@ public abstract class Behaviour : BaseEntity, IBehaviour
if (IsInitialized) if (IsInitialized)
return false; return false;
_behaviourController = behaviourController; BehaviourController = behaviourController;
OnAssign(behaviourController); OnAssign(behaviourController);
behaviourController.OnUniverseObjectAssigned.AddListener(delegateOnUniverseObjectAssigned); behaviourController.OnUniverseObjectAssigned.AddListener(delegateOnUniverseObjectAssigned);
behaviourController.StateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged); behaviourController.StateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged);
@@ -71,7 +68,7 @@ public abstract class Behaviour : BaseEntity, IBehaviour
BehaviourController.OnUniverseObjectAssigned.RemoveListener(delegateOnUniverseObjectAssigned); BehaviourController.OnUniverseObjectAssigned.RemoveListener(delegateOnUniverseObjectAssigned);
BehaviourController.StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged); BehaviourController.StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged);
base.UnassignInternal(); base.UnassignInternal();
_behaviourController = null!; BehaviourController = null!;
} }
protected override void InitializeInternal() protected override void InitializeInternal()
@@ -88,7 +85,7 @@ public abstract class Behaviour : BaseEntity, IBehaviour
private void UpdateActive() private void UpdateActive()
{ {
bool previousActive = IsActive; bool previousActive = IsActive;
_isActive = StateEnable.Enabled && _behaviourController.StateEnable.Enabled && _behaviourController.UniverseObject.IsActive; IsActive = StateEnable.Enabled && BehaviourController.StateEnable.Enabled && BehaviourController.UniverseObject.IsActive;
if (previousActive != IsActive) if (previousActive != IsActive)
OnActiveChanged?.Invoke(this, new(previousActive)); 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 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 int Count => behaviours.Count;
public IBehaviour this[Index index] => behaviours[index]; public IBehaviour this[Index index] => behaviours[index];
@@ -58,9 +57,11 @@ public class BehaviourController : BaseEntity, IBehaviourController
return behaviours; return behaviours;
} }
public void GetBehaviours<T>(IList<T> results) public void GetBehaviours<T>(IList<T> results, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear)
{ {
results.Clear(); if (collectionMethod == IBehaviourController.CollectionMethod.Clear)
results.Clear();
foreach (IBehaviour behaviourItem in behaviours) foreach (IBehaviour behaviourItem in behaviours)
{ {
if (behaviourItem is not T behaviour) if (behaviourItem is not T behaviour)
@@ -101,7 +102,7 @@ public class BehaviourController : BaseEntity, IBehaviourController
if (UniverseObject is not null && UniverseObject.IsInitialized) if (UniverseObject is not null && UniverseObject.IsInitialized)
return false; return false;
_universeObject = universeObject; UniverseObject = universeObject;
OnAssign(universeObject); OnAssign(universeObject);
OnUniverseObjectAssigned?.Invoke(this); OnUniverseObjectAssigned?.Invoke(this);
return true; return true;

View File

@@ -29,7 +29,7 @@ public abstract class ActiveBehaviourCollectorBase<T> : IBehaviourCollector<T> w
if (Universe is not null) if (Universe is not null)
return false; return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects) foreach (IUniverseObject universeObject in universe)
OnUniverseObjectRegistered(universe, new(universeObject)); OnUniverseObjectRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered); universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered);
@@ -46,7 +46,7 @@ public abstract class ActiveBehaviourCollectorBase<T> : IBehaviourCollector<T> w
if (Universe is null) if (Universe is null)
return false; return false;
foreach (IUniverseObject universeObject in Universe.UniverseObjects) foreach (IUniverseObject universeObject in Universe)
OnUniverseObjectUnregistered(Universe, new(universeObject)); OnUniverseObjectUnregistered(Universe, new(universeObject));
Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered); 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 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 SortedDictionary<TIndex, FastList<TItem>> behaviours = null!;
private readonly Dictionary<TItem, TIndex> indexCache = [];
private readonly Func<TItem, TIndex> getIndexFunc = null!; private readonly Func<TItem, TIndex> getIndexFunc = null!;
private readonly IComparer<TIndex> sortBy = null!; private readonly IComparer<TIndex> sortBy = null!;
@@ -39,11 +38,10 @@ public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCol
protected override bool RemoveBehaviour(TItem tBehaviour) protected override bool RemoveBehaviour(TItem tBehaviour)
{ {
TIndex index = getIndexFunc(tBehaviour); if (!indexCache.TryGetValue(tBehaviour, out TIndex? index) || !behaviours.TryGetValue(index, out FastList<TItem>? list))
if (!behaviours.TryGetValue(index, out FastList<TItem>? list))
throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector"); 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; return false;
count--; count--;
@@ -58,21 +56,11 @@ public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCol
count++; count++;
list.Add(behaviour); list.Add(behaviour);
} indexCache.Add(behaviour, key);
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);
} }
public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
{ {
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc; this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy); this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.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) public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) : base(universe)
{ {
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc; this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy); this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy); behaviours = new(this.sortBy);
@@ -89,14 +76,12 @@ public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCol
public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
{ {
this.getIndexFunc = getIndexFunc; this.getIndexFunc = getIndexFunc;
delegateOnPriorityChanged = OnPriorityChanged;
this.sortBy = sortBy; this.sortBy = sortBy;
behaviours = new(sortBy); behaviours = new(sortBy);
} }
public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe) public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe)
{ {
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc; this.getIndexFunc = getIndexFunc;
this.sortBy = sortBy; this.sortBy = sortBy;
behaviours = new(sortBy); behaviours = new(sortBy);

View File

@@ -25,7 +25,7 @@ public abstract class BehaviourCollectorBase<T> : IBehaviourCollector<T> where T
if (Universe is not null) if (Universe is not null)
return false; return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects) foreach (IUniverseObject universeObject in universe)
OnUniverseObjectRegistered(universe, new(universeObject)); OnUniverseObjectRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered); universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered);
@@ -43,7 +43,7 @@ public abstract class BehaviourCollectorBase<T> : IBehaviourCollector<T> where T
if (Universe is null) if (Universe is null)
return false; return false;
foreach (IUniverseObject universeObject in Universe.UniverseObjects) foreach (IUniverseObject universeObject in Universe)
OnUniverseObjectUnregistered(Universe, new(universeObject)); OnUniverseObjectUnregistered(Universe, new(universeObject));
Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered); 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 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 SortedDictionary<TIndex, FastList<TItem>> behaviours = null!;
private readonly Dictionary<TItem, TIndex> indexCache = null!;
private readonly Func<TItem, TIndex> getIndexFunc = null!; private readonly Func<TItem, TIndex> getIndexFunc = null!;
private readonly IComparer<TIndex> sortBy = null!; private readonly IComparer<TIndex> sortBy = null!;
@@ -39,11 +38,10 @@ public class BehaviourCollectorOrdered<TIndex, TItem> : BehaviourCollectorBase<T
protected override bool RemoveBehaviour(TItem tBehaviour) protected override bool RemoveBehaviour(TItem tBehaviour)
{ {
TIndex index = getIndexFunc(tBehaviour); if (!indexCache.TryGetValue(tBehaviour, out TIndex? index) || !behaviours.TryGetValue(index, out FastList<TItem>? list))
if (!behaviours.TryGetValue(index, out FastList<TItem>? list))
throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector"); 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; return false;
count--; count--;
@@ -58,21 +56,11 @@ public class BehaviourCollectorOrdered<TIndex, TItem> : BehaviourCollectorBase<T
count++; count++;
list.Add(behaviour); list.Add(behaviour);
} indexCache.Add(behaviour, key);
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);
} }
public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
{ {
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc; this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy); this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.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) public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) : base(universe)
{ {
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc; this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy); this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.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) public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
{ {
this.getIndexFunc = getIndexFunc; this.getIndexFunc = getIndexFunc;
delegateOnPriorityChanged = OnPriorityChanged;
this.sortBy = sortBy; this.sortBy = sortBy;
behaviours = new(sortBy); behaviours = new(sortBy);
} }
public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe) public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe)
{ {
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc; this.getIndexFunc = getIndexFunc;
this.sortBy = sortBy; this.sortBy = sortBy;
behaviours = new(sortBy); behaviours = new(sortBy);

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
namespace Engine.Core.Config;
public class BasicConfiguration : IConfiguration
{
public Event<IConfiguration, IConfiguration.ConfigUpdateArguments> OnAdded { get; } = new();
public Event<IConfiguration, IConfiguration.ConfigUpdateArguments> OnSet { get; } = new();
public Event<IConfiguration, IConfiguration.ConfigUpdateArguments> OnRemoved { get; } = new();
private readonly Dictionary<string, object?> values = [];
public IReadOnlyDictionary<string, object?> Values => values;
public T? Get<T>(string key, T? defaultValue = default)
{
if (!values.TryGetValue(key, out object? value))
return defaultValue;
if (value is T castedObject)
return castedObject;
try { return (T?)System.Convert.ChangeType(value, typeof(T)); } catch { }
return defaultValue;
}
public object? Get(string key)
{
values.TryGetValue(key, out object? value);
return value;
}
public bool Has(string key) => values.ContainsKey(key);
public void Remove<T>(string key)
{
if (values.Remove(key))
OnRemoved.Invoke(this, new(key));
}
public void Set<T>(string key, T value)
{
if (!values.TryAdd(key, value))
values[key] = value;
else
OnAdded.Invoke(this, new(key));
OnSet.Invoke(this, new(key));
}
}

View File

@@ -0,0 +1,8 @@
using Engine.Core.Exceptions;
namespace Engine.Core.Config;
public static class ConfigurationExtensions
{
public static T GetRequired<T>(this IConfiguration configuration, string key) => configuration.Get<T>(key) ?? throw new NotFoundException($"Type of {typeof(T).FullName} with the key {key} was not present in the {configuration.GetType().FullName}");
}

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
namespace Engine.Core.Config;
public interface IConfiguration
{
static IConfiguration System { get; set; } = new SystemConfiguration();
static IConfiguration Shared { get; set; } = new BasicConfiguration();
Event<IConfiguration, ConfigUpdateArguments> OnAdded { get; }
Event<IConfiguration, ConfigUpdateArguments> OnSet { get; }
Event<IConfiguration, ConfigUpdateArguments> OnRemoved { get; }
IReadOnlyDictionary<string, object?> Values { get; }
bool Has(string key);
object? Get(string key);
T? Get<T>(string key, T? defaultValue = default);
void Set<T>(string key, T value);
void Remove<T>(string key);
readonly record struct ConfigUpdateArguments(string Key);
}

View File

@@ -0,0 +1,11 @@
namespace Engine.Core.Config;
public class SystemConfiguration : BasicConfiguration, IConfiguration
{
public SystemConfiguration()
{
foreach (System.Collections.DictionaryEntry entry in System.Environment.GetEnvironmentVariables())
if (entry is { Key: string key, Value: not null })
Set(key, entry.Value);
}
}

View File

@@ -11,7 +11,8 @@ public static class LoggerExtensions
logger.Log(body, level, force); 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) 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) 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) private static void RotateLastLogs(string directory, string prefix, int rotateLength)
{ {
IOrderedEnumerable<string> logs = System.IO.Directory.GetFiles(directory, $"{prefix}*.log") IOrderedEnumerable<string> logs = System.IO.Directory.GetFiles(directory, $"{prefix}*.log")
.OrderBy(File.GetCreationTime); .OrderByDescending(File.GetCreationTime);
foreach (string file in logs.Skip(rotateLength)) foreach (string file in logs.Skip(rotateLength))
try try

View File

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

View File

@@ -100,18 +100,16 @@ public static class BehaviourControllerExtensions
/// </summary> /// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam> /// <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> /// <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; if (collectionMethod == IBehaviourController.CollectionMethod.Clear)
List<T> cache = []; behavioursInParent.Clear();
behavioursInParent.Clear();
IBehaviourController? controller = behaviourController;
while (controller is not null) while (controller is not null)
{ {
controller.GetBehaviours(cache); controller.GetBehaviours(behavioursInParent, IBehaviourController.CollectionMethod.Append);
foreach (T behaviour in cache)
behavioursInParent.Add(behaviour);
controller = controller.UniverseObject.Parent?.BehaviourController; controller = controller.UniverseObject.Parent?.BehaviourController;
} }
} }
@@ -161,22 +159,20 @@ public static class BehaviourControllerExtensions
/// </summary> /// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam> /// <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> /// <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(); 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); universeObject.BehaviourController.GetBehaviours(behaviours, IBehaviourController.CollectionMethod.Append);
foreach (T behaviour in cache)
behaviours.Add(behaviour);
foreach (IUniverseObject child in universeObject.Children) foreach (IUniverseObject child in universeObject.Children)
TraverseChildrenForBehaviour(child, behaviours, cache); TraverseChildrenForBehaviour(child, behaviours);
} }
} }

View File

@@ -0,0 +1,12 @@
namespace Engine.Core;
public static class IdentifiableExtensions
{
public static bool IsIdentical(this IIdentifiable? left, IIdentifiable? right)
{
if (left == null || right == null)
return false;
return left?.Id?.CompareTo(right?.Id) == 0;
}
}

View File

@@ -32,5 +32,5 @@ public static class UniverseExtensions
/// <typeparam name="T">Type to be searched through the <see cref="IUniverse"/>.</typeparam> /// <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> /// <returns>The specified type if found; otherwise, throws <see cref="NotFoundException"/>.</returns>
public static T FindRequired<T>(this IUniverse universe) where T : class 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; 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 #region Universe Object Search
/// <summary> /// <summary>
/// Gets a <see cref="IUniverseObject"/> of the specified type. /// Gets a <see cref="IUniverseObject"/> of the specified type.

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,439 @@
using System;
namespace Engine.Core;
// TODO Comments
/// <summary>
/// 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.
/// </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
);
/// <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,
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 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 &&
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;
/// <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, 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(
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(
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(
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(
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(
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 f = (target - position).Normalized;
Vector3D s = f.Cross(up).Normalized;
Vector3D u = s.Cross(f);
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 CreateOrthographicView(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,
-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 + nearPlane) / zRange, -1f,
0f, 0f, -(2f * nearPlane * farPlane) / zRange, 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.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, 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);
/// <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, Vector3D up) => matrix * Matrix4x4.CreateLookMatrix(forward, up);
/// <inheritdoc cref="Matrix4x4.CreateLookMatrix(Vector3D, Vector3D, Vector3D)" />
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

@@ -156,6 +156,27 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
/// <returns>The normalized <see cref="Quaternion"/>.</returns> /// <returns>The normalized <see cref="Quaternion"/>.</returns>
public static Quaternion Normalize(Quaternion quaternion) => quaternion / Length(quaternion); 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> /// <summary>
/// Rotates a <see cref="Quaternion"/> around a axis by the specified angle (in radians). /// Rotates a <see cref="Quaternion"/> around a axis by the specified angle (in radians).
/// </summary> /// </summary>
@@ -212,8 +233,8 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
dot = -dot; dot = -dot;
} }
if (dot > 0.9995f) if (dot > 0.999999f)
return Lerp(from, to, t); return to;
float angle = Math.Acos(dot); float angle = Math.Acos(dot);
float sinAngle = Math.Sin(angle); float sinAngle = Math.Sin(angle);
@@ -276,12 +297,11 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
} }
/// <summary> /// <summary>
/// Calculates the <see cref="System.Numerics.Matrix4x4"/> from given <see cref="Quaternion"/>. /// Calculates the <see cref="Matrix4x4"/> from given <see cref="Quaternion"/>.
/// </summary> /// </summary>
/// <param name="axis">The axis of the rotation in <see cref="Vector3D"/>.</param> /// <param name="quaternion">The rotation <see cref="Quaternion"/>.</param>
/// <param name="angle">The angle in radians.</param> /// <returns>The rotation <see cref="Matrix4x4"/> calculated by the given <see cref="Quaternion"/>.</returns>
/// <returns>The rotation <see cref="System.Numerics.Matrix4x4"/> calculated by the given <see cref="Quaternion"/>.</returns> public static Matrix4x4 ToRotationMatrix4x4(Quaternion quaternion)
public static System.Numerics.Matrix4x4 ToRotationMatrix4x4(Quaternion quaternion)
{ {
float m00 = 1 - 2 * (quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z); float m00 = 1 - 2 * (quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z);
float m01 = 2 * (quaternion.X * quaternion.Y - quaternion.W * 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> /// <summary>
/// Checks if two <see cref="Quaternion"/>s are approximately equal within a specified epsilon range. /// Checks if two <see cref="Quaternion"/>s are approximately equal within a specified epsilon range.
/// </summary> /// </summary>
@@ -393,7 +459,7 @@ public static class QuaternionExtensions
public static float Dot(this Quaternion left, Quaternion right) => Quaternion.Dot(left, right); public static float Dot(this Quaternion left, Quaternion right) => Quaternion.Dot(left, right);
/// <inheritdoc cref="Quaternion.ToRotationMatrix4x4(Quaternion, Quaternion)" /> /// <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)" /> /// <inheritdoc cref="Quaternion.FromAxisAngle(Vector3D, float)" />
public static Quaternion CreateRotation(this Vector3D axis, float angle) => Quaternion.FromAxisAngle(axis, angle); public static Quaternion CreateRotation(this Vector3D axis, float angle) => Quaternion.FromAxisAngle(axis, angle);

View File

@@ -20,7 +20,7 @@ public class Shape2D(List<Vector2D> vertices) : IEnumerable<Vector2D>
public Event<Shape2D> OnShapeUpdated { get; } = new(); public Event<Shape2D> OnShapeUpdated { get; } = new();
private List<Vector2D> _vertices = vertices; private readonly List<Vector2D> _vertices = vertices;
/// <summary> /// <summary>
/// Gets the vertices of the <see cref="Shape2D"/>. /// 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 bool operator !=(Vector2D left, Vector2D right) => left.X != right.X || left.Y != right.Y; public static bool operator !=(Vector2D left, Vector2D right) => left.X != right.X || left.Y != right.Y;
public static implicit operator System.Numerics.Vector2(Vector2D vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector2DInt vector) => new(vector.X, vector.Y); public static implicit operator Vector2D(Vector2DInt vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector3D vector) => new(vector.X, vector.Y); public static implicit operator Vector2D(Vector3DInt vector) => new(vector.X, vector.Y);
public static implicit operator System.Numerics.Vector2(Vector2D vector) => new(vector.X, vector.Y);
public static implicit operator System.Numerics.Vector3(Vector2D vector) => new(vector.X, vector.Y, 0f);
public static implicit operator System.Numerics.Vector4(Vector2D vector) => new(vector.X, vector.Y, 0f, 0f);
public static implicit operator Vector2D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y); public static implicit operator Vector2D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(System.Numerics.Vector4 vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector3D vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector4D vector) => new(vector.X, vector.Y);
/// <summary> /// <summary>
/// Calculates the length of the <see cref="Vector2D"/>. /// Calculates the length of the <see cref="Vector2D"/>.

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -4,15 +4,15 @@ namespace Engine.Core.Serialization;
public interface ISerializer public interface ISerializer
{ {
object Deserialize(string configuration); object Deserialize(string content);
object Deserialize(string configuration, Type type); object Deserialize(string content, Type type);
T Deserialize<T>(string configuration); T Deserialize<T>(string content);
string Serialize(object instance); string Serialize(object instance);
ProgressiveTask<object> DeserializeAsync(string configuration); ProgressiveTask<object> DeserializeAsync(string content);
ProgressiveTask<object> DeserializeAsync(string configuration, Type type); ProgressiveTask<object> DeserializeAsync(string content, Type type);
ProgressiveTask<T> DeserializeAsync<T>(string configuration); ProgressiveTask<T> DeserializeAsync<T>(string content);
ProgressiveTask<string> SerializeAsync(object instance); ProgressiveTask<string> SerializeAsync(object instance);
} }

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

View File

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

View File

@@ -11,70 +11,54 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent
private readonly ActiveBehaviourCollectorOrdered<int, IEnterUniverse> enterUniverses = new(GetPriority(), SortByAscendingPriority()); private readonly ActiveBehaviourCollectorOrdered<int, IEnterUniverse> enterUniverses = new(GetPriority(), SortByAscendingPriority());
private readonly ActiveBehaviourCollectorOrdered<int, IExitUniverse> exitUniverses = new(GetPriority(), SortByAscendingPriority()); private readonly ActiveBehaviourCollectorOrdered<int, IExitUniverse> exitUniverses = new(GetPriority(), SortByAscendingPriority());
private readonly List<IEnterUniverse> toCallEnterUniverses = new(32); private bool isInitialCollectionDone = false;
private readonly List<IExitUniverse> toCallExitUniverses = new(32); private readonly FastListOrdered<int, IEnterUniverse> toCallEnterUniverses = new(GetPriority(), SortByAscendingPriority());
protected override void OnEnteredUniverse(IUniverse universe) 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);
exitUniverses.Assign(universe); exitUniverses.Assign(universe);
foreach (IUniverseObject universeObject in universe.UniverseObjects) // FIXME: the isInitialCollectionDone is for the sole reason of some behaviours
OnUniverseObjectRegistered(universe, new(universeObject)); // 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;
universe.OnUniverseObjectRegistered.AddListener(OnUniverseObjectRegistered); for (int i = toCallEnterUniverses.Count - 1; i >= 0; i--)
universe.OnUniverseObjectUnRegistered.AddListener(OnUniverseObjectUnRegistered); toCallEnterUniverses[i].EnterUniverse(universe);
toCallEnterUniverses.Clear();
} }
protected override void OnExitedUniverse(IUniverse universe) protected override void OnExitedUniverse(IUniverse universe)
{ {
enterUniverses.Unassign(); enterUniverses.Unassign();
exitUniverses.Unassign(); exitUniverses.Unassign();
foreach (IUniverseObject universeObject in universe.UniverseObjects)
OnUniverseObjectUnRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.RemoveListener(OnUniverseObjectRegistered);
universe.OnUniverseObjectUnRegistered.RemoveListener(OnUniverseObjectUnRegistered);
}
private void OnUniverseObjectUnRegistered(IUniverse sender, IUniverse.UniverseObjectUnRegisteredArguments args)
{
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);
}
} }
private void OnEnterUniverseCollected(IBehaviourCollector<IEnterUniverse> sender, IBehaviourCollector<IEnterUniverse>.BehaviourCollectedArguments args) private void OnEnterUniverseCollected(IBehaviourCollector<IEnterUniverse> sender, IBehaviourCollector<IEnterUniverse>.BehaviourCollectedArguments args)
{ {
toCallEnterUniverses.Add(args.BehaviourCollected); if (!isInitialCollectionDone)
{
toCallEnterUniverses.Add(args.BehaviourCollected);
return;
}
args.BehaviourCollected.EnterUniverse(Universe);
} }
private void OnExitUniverseCollected(IBehaviourCollector<IExitUniverse> sender, IBehaviourCollector<IExitUniverse>.BehaviourCollectedArguments args) private void OnExitUniverseRemoved(IBehaviourCollector<IExitUniverse> sender, IBehaviourCollector<IExitUniverse>.BehaviourRemovedArguments args)
{ => args.BehaviourRemoved.ExitUniverse(Universe);
toCallExitUniverses.Add(args.BehaviourCollected);
}
public UniverseEntranceManager() public UniverseEntranceManager()
{ {
enterUniverses.OnCollected.AddListener(OnEnterUniverseCollected); enterUniverses.OnCollected.AddListener(OnEnterUniverseCollected);
exitUniverses.OnCollected.AddListener(OnExitUniverseCollected); exitUniverses.OnRemoved.AddListener(OnExitUniverseRemoved);
} }
} }

View File

@@ -44,6 +44,17 @@ public class UpdateManager : Behaviour, IEnterUniverse, IExitUniverse
universe.OnPostUpdate.RemoveListener(OnPostUpdate); universe.OnPostUpdate.RemoveListener(OnPostUpdate);
} }
/// <summary>
/// Call the <see cref="IFirstFrameUpdate"/> early if it's in queue to be called by this the <see cref="UpdateManager"/>.
/// It will not be called in the next natural cycle.
/// </summary>
/// <param name="instance">The instance that will be called now rather than later.</param>
public void CallFirstActiveFrameImmediately(IFirstFrameUpdate instance)
{
if (toCallFirstFrameUpdates.Remove(instance))
instance.FirstActiveFrame();
}
private void OnFirstUpdate(IUniverse sender, IUniverse.UpdateArguments args) private void OnFirstUpdate(IUniverse sender, IUniverse.UpdateArguments args)
{ {
for (int i = toCallFirstFrameUpdates.Count - 1; i >= 0; i--) for (int i = toCallFirstFrameUpdates.Count - 1; i >= 0; i--)

View File

@@ -0,0 +1,35 @@
using System.Threading.Tasks;
using static Engine.Core.WaitForTaskYield;
namespace Engine.Core;
public class WaitForTaskYield(Task task, TaskCompletionStatus completionStatus = TaskCompletionStatus.Either) : ICoroutineYield
{
public bool Yield()
{
switch (completionStatus)
{
case TaskCompletionStatus.Successful:
if (task.IsCanceled)
throw new("Task has been canceled.");
if (task.IsFaulted)
throw new("Task has faulted.");
return task.IsCompletedSuccessfully;
case TaskCompletionStatus.Failed:
if (task.IsCompletedSuccessfully)
throw new("Task was completed successfully.");
return task.IsFaulted;
}
return task.IsCompleted;
}
public enum TaskCompletionStatus
{
Either,
Successful,
Failed
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace Engine.Core;
public class WaitForTimeYield(float seconds = 0f, float milliseconds = 0f, float minutes = 0f, float hours = 0f) : ICoroutineYield
{
private readonly DateTime triggerTime = DateTime.UtcNow
.AddHours(hours)
.AddMinutes(minutes)
.AddSeconds(seconds)
.AddMilliseconds(milliseconds);
public bool Yield() => DateTime.UtcNow < triggerTime;
}

View File

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

View File

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

View File

@@ -23,7 +23,6 @@ public class Universe : BaseEntity, IUniverse
private readonly Event<IUniverseObject, IUniverseObject.ExitedUniverseArguments>.EventHandler delegateOnUniverseObjectExitedUniverse = null!; private readonly Event<IUniverseObject, IUniverseObject.ExitedUniverseArguments>.EventHandler delegateOnUniverseObjectExitedUniverse = null!;
private readonly FastList<IUniverseObject> _universeObjects = new(Constants.UNIVERSE_OBJECTS_SIZE_INITIAL); private readonly FastList<IUniverseObject> _universeObjects = new(Constants.UNIVERSE_OBJECTS_SIZE_INITIAL);
private float _timeScale = 1f;
public Universe() public Universe()
{ {
@@ -31,24 +30,24 @@ public class Universe : BaseEntity, IUniverse
delegateOnUniverseObjectExitedUniverse = OnUniverseObjectExitedUniverse; 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 Time { get; private set; } = new();
public UniverseTime UnscaledTime { get; private set; } = new(); public UniverseTime UnscaledTime { get; private set; } = new();
public float TimeScale public float TimeScale
{ {
get => _timeScale; get;
set set
{ {
value = value.Max(0f); value = value.Max(0f);
if (value == _timeScale) if (value == field)
return; return;
float previousTimeScale = _timeScale; float previousTimeScale = field;
_timeScale = value; field = value;
OnTimeScaleChanged?.Invoke(this, new(previousTimeScale)); OnTimeScaleChanged?.Invoke(this, new(previousTimeScale));
} }
} } = 1f;
public void Register(IUniverseObject universeObject) public void Register(IUniverseObject universeObject)
{ {
@@ -63,14 +62,17 @@ public class Universe : BaseEntity, IUniverse
if (!universeObject.Initialize()) if (!universeObject.Initialize())
throw new Exception($"{universeObject.Name} can't be initialized"); throw new Exception($"{universeObject.Name} can't be initialized");
for (int i = 0; i < universeObject.Children.Count; i++) if (universeObject.Parent == null)
Register(universeObject.Children[i]); universeObject.Parent = Root;
_universeObjects.Add(universeObject); _universeObjects.Add(universeObject);
if (!universeObject.EnterUniverse(this)) if (!universeObject.EnterUniverse(this))
throw new Exception($"{universeObject.Name} can't enter the universe"); 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)); OnUniverseObjectRegistered?.Invoke(this, new(universeObject));
} }
@@ -113,15 +115,15 @@ public class Universe : BaseEntity, IUniverse
protected override void InitializeInternal() protected override void InitializeInternal()
{ {
foreach (IUniverseObject universeObject in UniverseObjects) foreach (IUniverseObject universeObject in _universeObjects)
universeObject.Initialize(); universeObject.Initialize();
} }
protected override void FinalizeInternal() protected override void FinalizeInternal()
{ {
base.FinalizeInternal(); base.FinalizeInternal();
for (int i = UniverseObjects.Count - 1; i >= 0; i--) for (int i = _universeObjects.Count - 1; i >= 0; i--)
Remove(UniverseObjects[i]); Remove(_universeObjects[i]);
} }
public void Update(UniverseTime engineTime) public void Update(UniverseTime engineTime)
@@ -158,6 +160,6 @@ public class Universe : BaseEntity, IUniverse
Remove(universeObject); Remove(universeObject);
} }
public IEnumerator<IUniverseObject> GetEnumerator() => _universeObjects.GetEnumerator(); public IEnumerator<IUniverseObject> GetEnumerator() => Root.TraverseChildren();
IEnumerator IEnumerable.GetEnumerator() => _universeObjects.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => Root.TraverseChildren();
} }

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ namespace Engine.Systems.Network;
public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicatorClient public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicatorClient
{ {
private readonly NetDataWriter netDataWriter = new(); private readonly NetDataWriter netDataWriter = new();
private readonly NetDataWriter netDataWriterEncrypted = new();
private CancellationTokenSource? cancellationTokenSource = null; 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() public INetworkCommunicatorClient SendToServer<T>(T packet, PacketDelivery packetDelivery) where T : class, new()
{ {
netDataWriter.Reset(); netDataWriter.Reset();
netPacketProcessor.Write(netDataWriter, packet); 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) switch (packetDelivery)
{ {
@@ -78,8 +92,9 @@ public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicator
{ {
while (true) while (true)
{ {
Manager.PollEvents();
Thread.Sleep(1); Thread.Sleep(1);
try { Manager.PollEvents(); }
catch (Exception e) { logger?.LogException(this, e, force: true); }
} }
}, cancellationToken); }, cancellationToken);
} }

View File

@@ -12,6 +12,7 @@ namespace Engine.Systems.Network;
public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IExitUniverse, INetworkCommunicator public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IExitUniverse, INetworkCommunicator
{ {
protected readonly NetPacketProcessor netPacketProcessor = new(); 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<Type, Event<IConnection, object>> listeners = [];
private readonly Dictionary<string, IConnection> _connections = []; 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) 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); } catch (Exception exception) { logger?.LogException(this, exception, force: true); }
} }
@@ -164,6 +178,7 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IE
netPacketProcessor.RegisterNestedType(Vector3DNetPacker.Write, Vector3DNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector3DNetPacker.Write, Vector3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector3DIntNetPacker.Write, Vector3DIntNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector3DIntNetPacker.Write, Vector3DIntNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector4DNetPacker.Write, Vector4DNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector4DNetPacker.Write, Vector4DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Matrix4x4NetPacker.Write, Matrix4x4NetPacker.Read);
} }
public INetworkCommunicator SubscribeToPackets<T>(Event<IConnection, T>.EventHandler callback) public INetworkCommunicator SubscribeToPackets<T>(Event<IConnection, T>.EventHandler callback)

View File

@@ -6,13 +6,14 @@ using Engine.Core.Debug;
namespace Engine.Systems.Network; namespace Engine.Systems.Network;
public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicatorServer public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicatorServer, IPostUpdate
{ {
public string Password { get; private set; } = string.Empty; public string Password { get; private set; } = string.Empty;
public int MaxConnectionCount { get; private set; } = 2; public int MaxConnectionCount { get; private set; } = 2;
public int Port { get; private set; } = 8888; public int Port { get; private set; } = 8888;
private readonly NetDataWriter netDataWriter = new(); private readonly NetDataWriter netDataWriter = new();
private readonly NetDataWriter netDataWriterEncrypted = new();
public LiteNetLibServer() : this(8888, 2) { } public LiteNetLibServer() : this(8888, 2) { }
public LiteNetLibServer(int port, int maxConnectionCount) : base() public LiteNetLibServer(int port, int maxConnectionCount) : base()
@@ -52,12 +53,11 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
public INetworkCommunicatorServer SendToClient<T>(IConnection connection, T packet, PacketDelivery packetDelivery) where T : class, new() public INetworkCommunicatorServer SendToClient<T>(IConnection connection, T packet, PacketDelivery packetDelivery) where T : class, new()
{ {
netDataWriter.Reset(); if (Manager.ConnectedPeerList.FirstOrDefault(p => p.Id.ToString().CompareTo(connection.Id) == 0) is not NetPeer netPeer)
netPacketProcessor.Write(netDataWriter, packet);
if (Manager.ConnectedPeerList.FirstOrDefault(p => p.Id.CompareTo(connection.Id) == 0) is not NetPeer netPeer)
throw new($"Peer {connection} couldn't be found."); throw new($"Peer {connection} couldn't be found.");
WritePacket(packet);
switch (packetDelivery) switch (packetDelivery)
{ {
case PacketDelivery.ReliableInOrder: netPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break; case PacketDelivery.ReliableInOrder: netPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break;
@@ -72,8 +72,7 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
public INetworkCommunicatorServer SendToAll<T>(T packet, PacketDelivery packetDelivery) where T : class, new() public INetworkCommunicatorServer SendToAll<T>(T packet, PacketDelivery packetDelivery) where T : class, new()
{ {
netDataWriter.Reset(); WritePacket(packet);
netPacketProcessor.Write(netDataWriter, packet);
switch (packetDelivery) switch (packetDelivery)
{ {
@@ -86,17 +85,27 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
return this; return this;
} }
private void PollEvents(IUniverse sender, IUniverse.UpdateArguments args) => Manager.PollEvents(); private void WritePacket<T>(T packet) where T : class, new()
public override void EnterUniverse(IUniverse universe)
{ {
base.EnterUniverse(universe); netDataWriter.Reset();
universe.OnPostUpdate.AddListener(PollEvents); 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);
}
} }
public override void ExitUniverse(IUniverse universe) public void PostUpdate()
{ {
base.ExitUniverse(universe); Manager.PollEvents();
universe.OnPostUpdate.RemoveListener(PollEvents);
} }
} }

View File

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

View File

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

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

@@ -5,101 +5,118 @@ using Engine.Core;
namespace Engine.Integration.MonoGame; 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> OnViewMatrixChanged { get; } = new();
public Event<MonoGameCamera2D> OnProjectionMatrixChanged { get; } = new();
public Event<MonoGameCamera2D> OnViewportChanged { get; } = new(); public Event<MonoGameCamera2D> OnViewportChanged { get; } = new();
public Event<MonoGameCamera2D> OnZoomChanged { 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 GraphicsDeviceManager Graphics { get; private set; } = null!;
public ITransform2D Transform { get; private set; } = null!; public ITransform2D Transform { get; private set; } = null!;
public Matrix MatrixTransform public Matrix4x4 ProjectionMatrix
{ {
get => _matrixTransform; get;
set private set
{ {
if (_matrixTransform == value) if (field == value)
return; return;
_matrixTransform = value; field = value;
OnMatrixTransformChanged.Invoke(this); OnProjectionMatrixChanged.Invoke(this);
} }
} } = Matrix4x4.Identity;
public Vector2D Position public Matrix4x4 ViewMatrix
{ {
get => Transform.Position; get;
set => Transform.Position = value; private set
} {
if (field == value)
return;
field = value;
OnViewMatrixChanged.Invoke(this);
}
} = Matrix4x4.Identity;
public Viewport Viewport public Viewport Viewport
{ {
get => _viewport; get;
set set
{ {
if (_viewport.Equals(value)) if (field.Equals(value))
return; return;
_viewport = value; field = value;
OnViewportChanged.Invoke(this); OnViewportChanged.Invoke(this);
} }
} } = default;
public float Zoom public float Zoom
{ {
get => _zoom; get;
set set
{ {
float newValue = Engine.Core.Math.Max(0.1f, value); float newValue = Math.Max(0.1f, value);
if (_zoom == newValue) if (field == newValue)
return; return;
_zoom = newValue; field = newValue;
OnZoomChanged.Invoke(this); OnZoomChanged.Invoke(this);
} }
} } = 1f;
public float Rotation
{
get => Transform.Rotation;
set => Transform.Rotation = value;
}
// TODO This causes delay since OnPreDraw calls assuming this is called in in Update // TODO This causes delay since OnPreDraw calls assuming this is called in in Update
public Vector2D ScreenToWorldPosition(Vector2D screenPosition) public Vector2D ScreenToWorldPosition(Vector2D screenPosition)
{ {
Vector2D worldPosition = Vector2.Transform(screenPosition.ToVector2(), Matrix.Invert(MatrixTransform)).ToVector2D(); float x = 2f * screenPosition.X / Viewport.Width - 1f;
return worldPosition.Scale(EngineConverterExtensions.screenScale); float y = 1f - 2f * screenPosition.Y / Viewport.Height;
} Vector4D normalizedCoordinates = new(x, y, 0f, 1f);
public Vector2D WorldToScreenPosition(Vector2D worldPosition)
{ Matrix4x4 invertedViewProjectionMatrix = (ProjectionMatrix * ViewMatrix).Inverse;
Vector2D screenPosition = Vector2.Transform(worldPosition.ToVector2(), MatrixTransform).ToVector2D();
return screenPosition.Scale(EngineConverterExtensions.screenScale); Vector4D worldPosition = invertedViewProjectionMatrix * normalizedCoordinates;
if (worldPosition.W != 0f)
worldPosition /= worldPosition.W;
return new(worldPosition.X, worldPosition.Y);
} }
public Vector2D WorldToScreenPosition(Vector2D worldPosition)
{
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!;
public void FirstActiveFrame() public void FirstActiveFrame()
{ {
Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics; Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics;
Viewport = Graphics.GraphicsDevice.Viewport; Viewport = Graphics.GraphicsDevice.Viewport;
Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
} }
public void PreDraw() public void PreDraw()
{ {
MatrixTransform = ProjectionMatrix = Matrix4x4.CreateOrthographicViewCentered(Viewport.Width, Viewport.Height);
Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) * ViewMatrix = Matrix4x4.Identity
Matrix.CreateRotationZ(Rotation * Engine.Core.Math.DegreeToRadian) * .ApplyScale(Transform.Scale.X.Max(Transform.Scale.Y))
Matrix.CreateScale(Transform.Scale.X.Max(Transform.Scale.Y)) * .ApplyScale(Zoom)
Matrix.CreateScale(Zoom) * .ApplyRotationZ(-Transform.Rotation * Math.DegreeToRadian)
Matrix.CreateTranslation(new Vector3(_viewport.Width * .5f, _viewport.Height * .5f, 0f)); .ApplyTranslation(new Vector3D(-Transform.Position.X, -Transform.Position.Y, 0f));
} }
protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
protected sealed override void FinalizeInternal() => Transform = null!;
} }

View File

@@ -5,127 +5,179 @@ using Engine.Core;
namespace Engine.Integration.MonoGame; 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, ViewChangedArguments> OnViewChanged { get; } = new();
public Event<MonoGameCamera3D, ProjectionChangedArguments> OnProjectionChanged { get; } = new(); public Event<MonoGameCamera3D, ProjectionChangedArguments> OnProjectionChanged { get; } = new();
public Event<MonoGameCamera3D, ViewportChangedArguments> OnViewportChanged { get; } = new(); public Event<MonoGameCamera3D, ViewportChangedArguments> OnViewportChanged { get; } = new();
public Event<MonoGameCamera3D, FieldOfViewChangedArguments> OnFieldOfViewChanged { get; } = new();
private Matrix _view = Matrix.Identity; public Event<ICamera3D, ICamera3D.NearPlaneChangedArguments> OnNearPlaneChanged { get; } = new();
private Matrix _projection = Matrix.Identity; public Event<ICamera3D, ICamera3D.FarPlaneChangedArguments> OnFarPlaneChanged { get; } = new();
private Viewport _viewport = default; public Event<ICamera3D, ICamera3D.FieldOfViewChangedArguments> OnFieldOfViewChanged { get; } = new();
private float _fieldOfView = 1f;
private float fieldOfView = Math.DegreeToRadian * 70f;
private bool isRecalculationNeeded = true;
public GraphicsDeviceManager Graphics { get; private set; } = null!; public GraphicsDeviceManager Graphics { get; private set; } = null!;
public ITransform3D Transform { get; private set; } = null!; public ITransform3D Transform { get; private set; } = null!;
public Matrix View public Matrix4x4 ViewMatrix
{ {
get => _view; get;
set set
{ {
if (_view == value) if (field == value)
return; return;
Matrix previousView = _view; Matrix4x4 previousView = field;
_view = value; field = value;
OnViewChanged.Invoke(this, new(previousView)); OnViewChanged.Invoke(this, new(previousView));
} }
} } = Matrix4x4.Identity;
public Matrix Projection
public Matrix4x4 ProjectionMatrix
{ {
get => _projection; get;
set set
{ {
if (_projection == value) if (field == value)
return; return;
Matrix previousProjection = _projection; Matrix4x4 previousProjection = field;
_projection = value; field = value;
OnProjectionChanged.Invoke(this, new(previousProjection)); OnProjectionChanged.Invoke(this, new(previousProjection));
} }
} } = Matrix4x4.Identity;
public Vector3D Position
{
get => Transform.Position;
set => Transform.Position = value;
}
public Viewport Viewport public Viewport Viewport
{ {
get => _viewport; get;
set set
{ {
if (_viewport.Equals(value)) if (field.Equals(value))
return; return;
Viewport previousViewport = _viewport; Viewport previousViewport = field;
_viewport = value; field = value;
SetForRecalculation();
OnViewportChanged.Invoke(this, new(previousViewport)); OnViewportChanged.Invoke(this, new(previousViewport));
} }
} }
public float FieldOfView public float NearPlane
{ {
get => _fieldOfView; get;
set set
{ {
float newValue = Math.Max(0.1f, value); float previousNearPlane = field;
field = value.Max(0.0001f);
SetForRecalculation();
OnNearPlaneChanged.Invoke(this, new(previousNearPlane));
}
} = 0.01f;
if (_fieldOfView == newValue) public float FarPlane
{
get;
set
{
float previousFarPlane = field;
field = value.Max(NearPlane);
SetForRecalculation();
OnFarPlaneChanged.Invoke(this, new(previousFarPlane));
}
} = 100f;
public float FieldOfView
{
get => fieldOfView * Math.RadianToDegree;
set
{
value = value.Max(0.1f) * Math.DegreeToRadian;
if (fieldOfView == value)
return; return;
float previousFieldOfView = _fieldOfView; float previousFieldOfView = fieldOfView;
_fieldOfView = newValue; fieldOfView = value;
SetForRecalculation();
OnFieldOfViewChanged.Invoke(this, new(previousFieldOfView)); 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 // TODO This causes delay since OnPreDraw calls assuming this is called in in Update
public Ray3D ScreenToWorldRay(Vector2D screenPosition) public Ray3D ScreenToWorldRay(Vector2D screenPosition)
{ {
Vector3 nearPoint = new(screenPosition.X, screenPosition.Y, 0f); Vector3 nearPoint = new(screenPosition.X, screenPosition.Y, 0f);
Vector3 farPoint = new(screenPosition.X, screenPosition.Y, 1f); Vector3 farPoint = new(screenPosition.X, screenPosition.Y, 1f);
Vector3 worldNear = Viewport.Unproject(nearPoint, _projection, _view, Matrix.Identity); Matrix projection = ProjectionMatrix.ToXnaMatrix();
Vector3 worldFar = Viewport.Unproject(farPoint, _projection, _view, Matrix.Identity); 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); Vector3 direction = Vector3.Normalize(worldFar - worldNear);
return new(worldNear.ToVector3D(), direction.ToVector3D()); 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() public void FirstActiveFrame()
{ {
Transform = BehaviourController.GetRequiredBehaviour<ITransform3D>();
Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics; Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics;
Viewport = Graphics.GraphicsDevice.Viewport; Viewport = Graphics.GraphicsDevice.Viewport;
Transform.OnTransformUpdated.AddListener(SetDirtyOnTransformUpdate);
} }
private void SetDirtyOnTransformUpdate(ITransform3D sender) => SetForRecalculation();
private void SetForRecalculation() => isRecalculationNeeded = true;
public void PreDraw() public void PreDraw()
{ {
Vector3 cameraPosition = Position.ToVector3(); if (!isRecalculationNeeded)
View = Matrix.CreateLookAt( return;
cameraPosition,
Transform.Forward.ToVector3() + cameraPosition, CalculateView();
Transform.Up.ToVector3() CalculateProjection();
); isRecalculationNeeded = false;
Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Viewport.AspectRatio, 0.1f, 100f);
} }
protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour<ITransform3D>(); private void CalculateView()
protected sealed override void FinalizeInternal() => Transform = null!; {
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();
public readonly record struct ViewChangedArguments(Matrix PreviousView); ViewMatrix = new Matrix4x4(
public readonly record struct ProjectionChangedArguments(Matrix PreviousProjection); 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;
ProjectionMatrix = new Matrix4x4(
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(Matrix4x4 PreviousView);
public readonly record struct ProjectionChangedArguments(Matrix4x4 PreviousProjection);
public readonly record struct ViewportChangedArguments(Viewport PreviousViewport); public readonly record struct ViewportChangedArguments(Viewport PreviousViewport);
public readonly record struct FieldOfViewChangedArguments(float PreviousFieldOfView);
} }

View File

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

View File

@@ -26,7 +26,7 @@ public class SpriteBatcher : Behaviour, IFirstFrameUpdate, IDraw
public void Draw() public void Draw()
{ {
spriteBatch.Begin(transformMatrix: camera2D.MatrixTransform); spriteBatch.Begin(transformMatrix: camera2D.ViewMatrix.ToXnaMatrix());
for (int i = 0; i < drawableSprites.Count; i++) for (int i = 0; i < drawableSprites.Count; i++)
drawableSprites[i].Draw(spriteBatch); drawableSprites[i].Draw(spriteBatch);
spriteBatch.End(); 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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings> <ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>Engine.Integration.MonoGame</RootNamespace> <RootNamespace>Engine.Integration.MonoGame</RootNamespace>

View File

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

View File

@@ -2,22 +2,29 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Engine.Core; using Engine.Core;
using Engine.Systems.Graphics;
namespace Engine.Integration.MonoGame; 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 VertexBuffer vertexBuffer = default!;
private readonly VertexPositionColor[] vertices = new VertexPositionColor[1024]; private readonly VertexPositionColor[] vertices = new VertexPositionColor[1024];
private int verticesIndex = 0; private int verticesIndex = 0;
private Matrix _view; private Matrix view = Matrix.Identity;
private Matrix _projection; private Matrix projection = Matrix.Identity;
private readonly BasicEffect basicEffect;
private BasicEffect basicEffect = null!;
private readonly RasterizerState rasterizerState = new() { CullMode = CullMode.None }; 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; this.graphicsDevice = graphicsDevice;
basicEffect = new(graphicsDevice); basicEffect = new(graphicsDevice);
basicEffect.VertexColorEnabled = true; basicEffect.VertexColorEnabled = true;
@@ -29,9 +36,9 @@ public class TriangleBatch : ITriangleBatch
if (verticesIndex + 3 >= vertices.Length) if (verticesIndex + 3 >= vertices.Length)
Flush(); Flush();
Vector2 A = triangle.A.ToDisplayVector2(); Vector2 A = triangle.A.ToVector2();
Vector2 B = triangle.B.ToDisplayVector2(); Vector2 B = triangle.B.ToVector2();
Vector2 C = triangle.C.ToDisplayVector2(); Vector2 C = triangle.C.ToVector2();
Color color = colorRGBA.ToColor(); Color color = colorRGBA.ToColor();
vertices[verticesIndex++] = new(new(A.X, A.Y, 0f), color); vertices[verticesIndex++] = new(new(A.X, A.Y, 0f), color);
@@ -39,20 +46,10 @@ public class TriangleBatch : ITriangleBatch
vertices[verticesIndex++] = new(new(C.X, C.Y, 0f), color); vertices[verticesIndex++] = new(new(C.X, C.Y, 0f), color);
} }
public void Begin(Matrix? view = null, Matrix? projection = null) public void Begin(Matrix4x4? view = null, Matrix4x4? projection = null)
{ {
if (view != null) this.view = (view ?? camera.ViewMatrix).Transposed.ToXnaMatrix();
_view = view.Value; this.projection = (projection ?? camera.ProjectionMatrix).Transposed.ToXnaMatrix();
else
_view = Matrix.Identity;
if (projection != null)
_projection = projection.Value;
else
{
Viewport viewport = graphicsDevice.Viewport;
_projection = Matrix.CreateOrthographicOffCenter(viewport.X, viewport.Width, viewport.Height, viewport.Y, 0, 1);
}
} }
public void End() => Flush(); public void End() => Flush();
@@ -63,8 +60,8 @@ public class TriangleBatch : ITriangleBatch
return; return;
graphicsDevice.RasterizerState = rasterizerState; graphicsDevice.RasterizerState = rasterizerState;
basicEffect.Projection = _projection; basicEffect.Projection = projection;
basicEffect.View = _view; basicEffect.View = view;
vertexBuffer.SetData(vertices); vertexBuffer.SetData(vertices);
graphicsDevice.SetVertexBuffer(vertexBuffer); graphicsDevice.SetVertexBuffer(vertexBuffer);

View File

@@ -1,4 +1,4 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Engine.Core; using Engine.Core;
@@ -19,10 +19,12 @@ public class MonoGameWindow : Game
IsMouseVisible = true; IsMouseVisible = true;
Universe = universe ?? new Universe(); 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); .BehaviourController.AddBehaviour<MonoGameWindowContainer>(this);
Universe.InstantiateUniverseObject().SetUniverseObject("Content Loader") Universe.InstantiateUniverseObject().SetUniverseObject("Content Loader", monogameParent)
.BehaviourController.AddBehaviour<LoadContentManager>(); .BehaviourController.AddBehaviour<LoadContentManager>();
} }

View File

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

View File

@@ -11,7 +11,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class BehaviourControllerConverter : EngineTypeYamlSerializerBase<IBehaviourController> public class BehaviourControllerConverter : EngineTypeYamlConverterBase<IBehaviourController>
{ {
private const string BEHAVIOURS_SCALAR_NAME = "Behaviours"; private const string BEHAVIOURS_SCALAR_NAME = "Behaviours";
@@ -36,7 +36,7 @@ public class BehaviourControllerConverter : EngineTypeYamlSerializerBase<IBehavi
throw new(); throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!; SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
ProgressionTracker.Set(isTrackingController ? .5f : ProgressionTracker.Progression, $"Creating {instanceSerializedClass.Type}"); 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; string value = parser.Consume<Scalar>().Value;
if (value.CompareTo(nameof(IBehaviourController.StateEnable)) != 0) if (value.CompareTo(nameof(IBehaviourController.StateEnable)) != 0)

View File

@@ -9,7 +9,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class BehaviourConverter : EngineTypeYamlSerializerBase<IBehaviour> public class BehaviourConverter : EngineTypeYamlConverterBase<IBehaviour>
{ {
public override IBehaviour? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override IBehaviour? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {
@@ -36,7 +36,7 @@ public class BehaviourConverter : EngineTypeYamlSerializerBase<IBehaviour>
throw new(); throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!; SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
ProgressionTracker.Set(isTrackingController ? .5f : ProgressionTracker.Progression, $"Creating {instanceSerializedClass.Type}"); 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) if (parser.Consume<Scalar>().Value.CompareTo(nameof(IBehaviour.StateEnable)) != 0)
throw new(); throw new();

View File

@@ -8,11 +8,11 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public abstract class EngineTypeYamlSerializerBase<T> : IEngineTypeYamlConverter public abstract class EngineTypeYamlConverterBase<T> : IEngineTypeYamlConverter
{ {
protected const string SERIALIZED_SCALAR_NAME = "Properties"; 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 YamlSerializer Serializer { get; set; } = null!;
public IProgressionTracker ProgressionTracker { 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); T? result = Read(parser, type, rootDeserializer);
if (result is IEntity entity) if (result is IEntity entity)
EntityRegistry.Add(entity); IdentifiableRegistry.Add(entity);
return result; return result;
} }

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class AABB2DConverter : EngineTypeYamlSerializerBase<AABB2D> public class AABB2DConverter : EngineTypeYamlConverterBase<AABB2D>
{ {
public override AABB2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override AABB2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class AABB3DConverter : EngineTypeYamlSerializerBase<AABB3D> public class AABB3DConverter : EngineTypeYamlConverterBase<AABB3D>
{ {
public override AABB3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override AABB3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class CircleConverter : EngineTypeYamlSerializerBase<Circle> public class CircleConverter : EngineTypeYamlConverterBase<Circle>
{ {
public override Circle Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override Circle Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class ColorHSVAConverter : EngineTypeYamlSerializerBase<ColorHSVA> public class ColorHSVAConverter : EngineTypeYamlConverterBase<ColorHSVA>
{ {
private static readonly int SUBSTRING_START_LENGTH = nameof(ColorHSVA).Length + 1; private static readonly int SUBSTRING_START_LENGTH = nameof(ColorHSVA).Length + 1;

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class ColorHSVConverter : EngineTypeYamlSerializerBase<ColorHSV> public class ColorHSVConverter : EngineTypeYamlConverterBase<ColorHSV>
{ {
private static readonly int SUBSTRING_START_LENGTH = nameof(ColorHSV).Length + 1; private static readonly int SUBSTRING_START_LENGTH = nameof(ColorHSV).Length + 1;

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class ColorRGBAConverter : EngineTypeYamlSerializerBase<ColorRGBA> public class ColorRGBAConverter : EngineTypeYamlConverterBase<ColorRGBA>
{ {
private static readonly int SUBSTRING_START_LENGTH = nameof(ColorRGBA).Length + 1; private static readonly int SUBSTRING_START_LENGTH = nameof(ColorRGBA).Length + 1;

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class ColorRGBConverter : EngineTypeYamlSerializerBase<ColorRGB> public class ColorRGBConverter : EngineTypeYamlConverterBase<ColorRGB>
{ {
private static readonly int SUBSTRING_START_LENGTH = nameof(ColorRGB).Length + 1; private static readonly int SUBSTRING_START_LENGTH = nameof(ColorRGB).Length + 1;

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Line2DConverter : EngineTypeYamlSerializerBase<Line2D> public class Line2DConverter : EngineTypeYamlConverterBase<Line2D>
{ {
public override Line2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override Line2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Line2DEquationConverter : EngineTypeYamlSerializerBase<Line2DEquation> public class Line2DEquationConverter : EngineTypeYamlConverterBase<Line2DEquation>
{ {
public override Line2DEquation Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override Line2DEquation Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Line3DConverter : EngineTypeYamlSerializerBase<Line3D> public class Line3DConverter : EngineTypeYamlConverterBase<Line3D>
{ {
public override Line3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override Line3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -0,0 +1,33 @@
using System;
using Engine.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class Matrix4x4Converter : EngineTypeYamlConverterBase<Matrix4x4>
{
private static readonly int SUBSTRING_START_LENGTH = nameof(Matrix4x4).Length + 1;
public override Matrix4x4 Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string value = parser.Consume<Scalar>().Value;
string insideParenthesis = value[SUBSTRING_START_LENGTH..^1];
string[] values = insideParenthesis.Split(", ");
return new Matrix4x4(
float.Parse(values[0]), float.Parse(values[1]), float.Parse(values[2]), float.Parse(values[3]),
float.Parse(values[4]), float.Parse(values[5]), float.Parse(values[6]), float.Parse(values[7]),
float.Parse(values[8]), float.Parse(values[9]), float.Parse(values[10]), float.Parse(values[11]),
float.Parse(values[12]), float.Parse(values[13]), float.Parse(values[14]), float.Parse(values[15])
);
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Matrix4x4 m = (Matrix4x4)value!;
emitter.Emit(new Scalar($"{nameof(Matrix4x4)}({m.M11}, {m.M12}, {m.M13}, {m.M14},{m.M21}, {m.M22}, {m.M23}, {m.M24},{m.M31}, {m.M32}, {m.M33}, {m.M34},{m.M41}, {m.M42}, {m.M43}, {m.M44})"));
}
}

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Projection1DConverter : EngineTypeYamlSerializerBase<Projection1D> public class Projection1DConverter : EngineTypeYamlConverterBase<Projection1D>
{ {
public override Projection1D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override Projection1D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class QuaternionConverter : EngineTypeYamlSerializerBase<Quaternion> public class QuaternionConverter : EngineTypeYamlConverterBase<Quaternion>
{ {
private static readonly int SUBSTRING_START_LENGTH = nameof(Quaternion).Length + 1; private static readonly int SUBSTRING_START_LENGTH = nameof(Quaternion).Length + 1;

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Ray2DConverter : EngineTypeYamlSerializerBase<Ray2D> public class Ray2DConverter : EngineTypeYamlConverterBase<Ray2D>
{ {
public override Ray2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override Ray2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Ray3DConverter : EngineTypeYamlSerializerBase<Ray3D> public class Ray3DConverter : EngineTypeYamlConverterBase<Ray3D>
{ {
public override Ray3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override Ray3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -9,7 +9,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Shape2DConverter : EngineTypeYamlSerializerBase<Shape2D> public class Shape2DConverter : EngineTypeYamlConverterBase<Shape2D>
{ {
public override Shape2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override Shape2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Sphere3DConverter : EngineTypeYamlSerializerBase<Sphere3D> public class Sphere3DConverter : EngineTypeYamlConverterBase<Sphere3D>
{ {
public override Sphere3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override Sphere3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class TriangleConverter : EngineTypeYamlSerializerBase<Triangle> public class TriangleConverter : EngineTypeYamlConverterBase<Triangle>
{ {
public override Triangle Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override Triangle Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Vector2DConverter : EngineTypeYamlSerializerBase<Vector2D> public class Vector2DConverter : EngineTypeYamlConverterBase<Vector2D>
{ {
private static readonly int SUBSTRING_START_LENGTH = nameof(Vector2D).Length + 1; private static readonly int SUBSTRING_START_LENGTH = nameof(Vector2D).Length + 1;

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Vector2DIntConverter : EngineTypeYamlSerializerBase<Vector2DInt> public class Vector2DIntConverter : EngineTypeYamlConverterBase<Vector2DInt>
{ {
private static readonly int SUBSTRING_START_LENGTH = nameof(Vector2DInt).Length + 1; private static readonly int SUBSTRING_START_LENGTH = nameof(Vector2DInt).Length + 1;

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Vector3DConverter : EngineTypeYamlSerializerBase<Vector3D> public class Vector3DConverter : EngineTypeYamlConverterBase<Vector3D>
{ {
private static readonly int SUBSTRING_START_LENGTH = nameof(Vector3D).Length + 1; private static readonly int SUBSTRING_START_LENGTH = nameof(Vector3D).Length + 1;

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Vector3DIntConverter : EngineTypeYamlSerializerBase<Vector3DInt> public class Vector3DIntConverter : EngineTypeYamlConverterBase<Vector3DInt>
{ {
private static readonly int SUBSTRING_START_LENGTH = nameof(Vector3DInt).Length + 1; private static readonly int SUBSTRING_START_LENGTH = nameof(Vector3DInt).Length + 1;

View File

@@ -8,7 +8,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class Vector4DConverter : EngineTypeYamlSerializerBase<Vector4D> public class Vector4DConverter : EngineTypeYamlConverterBase<Vector4D>
{ {
private static readonly int SUBSTRING_START_LENGTH = nameof(Vector4D).Length + 1; private static readonly int SUBSTRING_START_LENGTH = nameof(Vector4D).Length + 1;

View File

@@ -12,7 +12,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class SerializedClassConverter : EngineTypeYamlSerializerBase<SerializedClass> public class SerializedClassConverter : EngineTypeYamlConverterBase<SerializedClass>
{ {
public override SerializedClass? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override SerializedClass? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -9,7 +9,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class StateEnableConverter : EngineTypeYamlSerializerBase<IStateEnable> public class StateEnableConverter : EngineTypeYamlConverterBase<IStateEnable>
{ {
public override IStateEnable? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override IStateEnable? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {
@@ -29,7 +29,7 @@ public class StateEnableConverter : EngineTypeYamlSerializerBase<IStateEnable>
if (parser.Consume<Scalar>().Value.CompareTo(SERIALIZED_SCALAR_NAME) != 0) if (parser.Consume<Scalar>().Value.CompareTo(SERIALIZED_SCALAR_NAME) != 0)
throw new(); throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!; SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
stateEnable = (IStateEnable)instanceSerializedClass.CreateInstance(EntityRegistry); stateEnable = (IStateEnable)instanceSerializedClass.CreateInstance(IdentifiableRegistry);
parser.Consume<MappingEnd>(); parser.Consume<MappingEnd>();

View File

@@ -9,7 +9,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class TypeContainerConverter : EngineTypeYamlSerializerBase<TypeContainer> public class TypeContainerConverter : EngineTypeYamlConverterBase<TypeContainer>
{ {
public override TypeContainer Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override TypeContainer Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {

View File

@@ -11,7 +11,7 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml; namespace Engine.Serializers.Yaml;
public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse> public class UniverseConverter : EngineTypeYamlConverterBase<IUniverse>
{ {
public override IUniverse? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) public override IUniverse? Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{ {
@@ -22,7 +22,7 @@ public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse>
IUniverse universe; IUniverse universe;
IStateEnable stateEnable; IStateEnable stateEnable;
List<IUniverseObject> universeObjects; IUniverseObject rootUniverseObject;
parser.Consume<MappingStart>(); parser.Consume<MappingStart>();
@@ -34,16 +34,16 @@ public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse>
throw new(); throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!; SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
ProgressionTracker.Set(isTrackingController ? .2f : ProgressionTracker.Progression, $"Creating {instanceSerializedClass.Type}"); 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) if (parser.Consume<Scalar>().Value.CompareTo(nameof(IUniverse.StateEnable)) != 0)
throw new(); throw new();
stateEnable = (IStateEnable)rootDeserializer(typeof(IStateEnable))!; stateEnable = (IStateEnable)rootDeserializer(typeof(IStateEnable))!;
ProgressionTracker.Set(isTrackingController ? .5f : ProgressionTracker.Progression, $"Reading universe objects"); 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(); throw new();
universeObjects = (List<IUniverseObject>)rootDeserializer(typeof(List<IUniverseObject>))!; rootUniverseObject = (IUniverseObject)rootDeserializer(typeof(IUniverseObject))!;
parser.Consume<MappingEnd>(); parser.Consume<MappingEnd>();
@@ -52,13 +52,12 @@ public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse>
stateEnable.Assign(universe); stateEnable.Assign(universe);
universe.Assign(stateEnable); universe.Assign(stateEnable);
ProgressionTracker.Set(isTrackingController ? .9f : ProgressionTracker.Progression, "Registering universe objects"); ProgressionTracker.Set(isTrackingController ? .9f : ProgressionTracker.Progression, "Registering root universe object");
for (int i = 0; i < universeObjects.Count; i++) for (int i = rootUniverseObject.Children.Count - 1; i >= 0; i--)
{ {
IUniverseObject uo = universeObjects[i]; IUniverseObject uo = rootUniverseObject.Children[i];
ProgressionTracker.Set(isTrackingController ? .9f + .1f * ((float)i / universeObjects.Count) : ProgressionTracker.Progression, $"Registering {uo.Name}"); ProgressionTracker.Set(isTrackingController ? .9f + .1f * ((float)i / rootUniverseObject.Children.Count) : ProgressionTracker.Progression, $"Registering {uo.Name}");
universe.Root.AddChild(uo);
universe.Register(uo);
} }
ProgressionTracker.Set(isTrackingController ? 1f : ProgressionTracker.Progression, $"Created {instanceSerializedClass.Type}"); ProgressionTracker.Set(isTrackingController ? 1f : ProgressionTracker.Progression, $"Created {instanceSerializedClass.Type}");
@@ -72,7 +71,7 @@ public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse>
bool isTrackingController = ProgressionTracker.Progression.ApproximatelyEquals(0f); bool isTrackingController = ProgressionTracker.Progression.ApproximatelyEquals(0f);
ProgressionTracker.Set(isTrackingController ? .25f : ProgressionTracker.Progression, $"Serializing universe"); 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()); emitter.Emit(new MappingStart());
@@ -85,8 +84,8 @@ public class UniverseConverter : EngineTypeYamlSerializerBase<IUniverse>
emitter.Emit(new Scalar(nameof(IUniverse.StateEnable))); emitter.Emit(new Scalar(nameof(IUniverse.StateEnable)));
serializer(universe.StateEnable); serializer(universe.StateEnable);
emitter.Emit(new Scalar(nameof(IUniverse.UniverseObjects))); emitter.Emit(new Scalar(nameof(IUniverse.Root)));
serializer(rootUniverseObjects); serializer(rootUniverseObject);
ProgressionTracker.Set(isTrackingController ? 1f : ProgressionTracker.Progression, $"Serialized universe"); ProgressionTracker.Set(isTrackingController ? 1f : ProgressionTracker.Progression, $"Serialized universe");
emitter.Emit(new MappingEnd()); emitter.Emit(new MappingEnd());

Some files were not shown because too many files have changed in this diff Show More