115 Commits

Author SHA1 Message Date
8d31372c24 refactor: universe and objects now use fast list 2025-10-13 12:40:43 +03:00
a2e704916e feat: fast list now implements IList 2025-10-13 12:39:49 +03:00
c7d170fad9 perf: significant performance optimizations on ordered behaviour collectors by using a sorted dictionary 2025-10-13 09:58:58 +03:00
9ccf7b754d perf: ordered behaviour collectors now use linked lists for performance 2025-10-11 16:07:26 +03:00
e3d4899112 refactor: renamed behaviour collectors from sorted to ordered 2025-10-11 16:05:47 +03:00
566c16d09c refactor: active behaviour collector base added 2025-10-11 15:36:58 +03:00
ae9d4f02ef chore: moved behaviour collectors into subdirectory 2025-10-11 15:36:06 +03:00
e77772cbc2 refactor: behaviour collector base added 2025-10-11 15:08:02 +03:00
4c542df401 perf: implemented fast list with index mapping 2025-10-10 14:58:40 +03:00
28ca343b43 perf: improved pool return method by using a hashset for searching if the returning item is already queued 2025-10-10 14:21:54 +03:00
651b0614c4 fix: index check on triangle batch flush 2025-10-10 11:43:04 +03:00
f47488c6f1 fix: registering/unregistering objects during universe enter/exit causing stack overflows 2025-10-10 10:59:39 +03:00
6d159330a1 refactor: moved client and server interfaces into their files 2025-08-31 23:09:02 +03:00
8e314f3269 feat: networking type hasher added 2025-08-19 21:17:47 +03:00
f5a7077570 perf: improved garbage created by tweens slightly
They still do generate a lot of garbage but with boxed value pools I made the boxes reusable, it still does generate garbage through the delegate creation, gotta find a solution for them later
2025-08-14 20:31:46 +03:00
746d29fb7a refactor: shortened IButtonInputs event declaration 2025-08-10 14:42:47 +03:00
cf68f6ca6f fix: first frame updates not calling first, they are now set to be a high priority 2025-08-09 22:29:44 +03:00
a4b83679b1 chore: added todo for a rare bug 2025-08-09 21:41:24 +03:00
a31b39fd1d fix: universe finalize not working properly 2025-08-09 21:36:28 +03:00
0205354202 fix: universe entrance manager using the wrong reference on universe exit 2025-08-09 21:09:36 +03:00
949dfeb3d9 fix: universe reverse for loop index doesn't start with count - 1 2025-08-09 21:03:45 +03:00
620ef911fa fix: parameter name typo 2025-08-09 21:01:25 +03:00
efed24de20 feat: rotating file logger added 2025-08-08 16:28:22 +03:00
3912706d27 chore: force .log extension to log files 2025-08-08 16:27:57 +03:00
d78c42a653 feat: update manager now calls last frames listeners on process exit as well 2025-08-05 20:57:03 +03:00
b04e0f81cd fix: triangle batch not drawing shapes because not setting rasterizer state properly 2025-08-05 20:43:54 +03:00
65dcb0c564 BREAKING CHANGE: moved yaml serialization from Engine.Serialization to Engine.Integration 2025-08-05 20:10:30 +03:00
3d183b21cd BREAKING CHANGE: renamed namespace & assembly names 2025-08-05 19:41:35 +03:00
1644a751bb feat: added LiteNetLib networking integration 2025-08-05 19:27:47 +03:00
6631cae7b0 feat: added networking system 2025-08-05 19:27:27 +03:00
3452194941 BREAKING CHANGE: removed IUniverseObject.SetParent and made Parent property settable 2025-08-05 10:46:25 +03:00
11612ff0db feat: removed IEnumerable from IUniverseObject for intellisense clarity
Use IUniverseObject.Children to access children
2025-08-05 09:11:46 +03:00
63bc94c7a6 fix: some factories not assigning fields correctly 2025-08-04 22:02:48 +03:00
e00319d7ff fix: active checks on behaviour base and universe object not working properly 2025-08-04 22:01:16 +03:00
11719440dc fix: behaviour controller extensions not null checking in case of uninitialized state 2025-08-04 20:26:45 +03:00
f246d68aa7 fix: remove behaviour not starting the reverse for loop from count - 1 2025-08-04 14:56:43 +03:00
6e87c67096 fix: wrong assert messages are corrected 2025-08-04 14:45:00 +03:00
b8217f2106 feat: last active frame interface 2025-08-03 09:43:18 +03:00
9824980cbf chore!: behaviour collector now removes behaviours on pre unregister 2025-08-03 09:23:30 +03:00
93a79cd075 feat: universe pre register and unregister events 2025-08-03 09:22:22 +03:00
f6e52abcc1 feat: testing universe entrance manager 2025-08-02 23:24:59 +03:00
03232f72e8 fix: LogTrace not having an optional stack trace parameter 2025-07-27 19:01:50 +03:00
37aca44e45 feat: monogame premultiplied color extension method added 2025-07-26 12:03:28 +03:00
9f4d95a57b perf: removed unnecessary operations on hsv colors 2025-07-26 11:59:36 +03:00
65eac57fce fix: color lerp methods fixed 2025-07-26 11:59:04 +03:00
08311acc9a chore!: removed FromTo methods from colors 2025-07-26 11:58:23 +03:00
f8fbae6130 feat: added HSVA 2025-07-26 11:56:58 +03:00
df06e8d134 feat: ticker is decoupled from stopwatch and added timer and stopwatch tickers 2025-07-25 23:24:08 +03:00
ad365dc722 feat: monogame content loader interface added 2025-07-25 21:40:57 +03:00
200e8ae7da feat: ILogger WrapWith extension method added 2025-07-21 10:25:33 +03:00
65cfaf1b4a feat: ILogger.Shared for global access 2025-07-21 10:18:00 +03:00
83b155fc5e feat: trace log level added 2025-07-12 18:31:35 +03:00
7db56e7f3e refactor: moved event log calls to a shared method 2025-07-12 17:34:56 +03:00
42064875a0 chore: added extra line for LoggerExtensions.LogException for better clarity 2025-07-12 17:34:23 +03:00
41245c0c1c refactor: added class restriction to generic type for event senders 2025-07-12 17:05:18 +03:00
0e5cc8f898 feat: added loggers to event classes 2025-07-12 16:53:12 +03:00
c8bb991865 refactor!: removed noise from class names
Renamed classes with names XBehaviour to X
2025-07-09 22:20:42 +03:00
bc1c76d746 feat: added priorities to events 2025-07-06 22:22:57 +03:00
8f03628bd6 fix: invocation loop inversed 2025-07-06 22:21:20 +03:00
a1feb0bad3 docs: added documentation for events 2025-07-06 21:04:22 +03:00
978cba96c8 refactor!: event methods renamed for better clarity 2025-07-06 20:39:45 +03:00
7212094a3d chore: updated misleading comment 2025-06-28 14:15:58 +03:00
14843ddeba refactor: removed unnecessary linq call 2025-06-28 12:50:03 +03:00
5315db0077 refactor!: renamed Math.PI to Math.Pi 2025-06-27 14:44:20 +03:00
026f343d43 docs: removed unnecessary comment lines from math constants 2025-06-27 14:42:45 +03:00
da5f31f9d7 refactor: made equality operators consistent in primitives & added missing ones 2025-06-27 12:00:50 +03:00
fa1614f238 feat: added approximately equals methods to projection 1D and ray 2D 2025-06-27 11:44:52 +03:00
0c096d39db docs: line equation XML comments updated 2025-06-27 11:43:54 +03:00
dae6549bad refactor: Equals methods to use equality operators on primitives 2025-06-27 11:37:20 +03:00
767fc28488 refactor: file logger relative path to full path conversion 2025-06-21 00:27:01 +03:00
c3be8f60b7 feat: added logger wrapper class 2025-06-18 17:39:23 +03:00
33cb44bf36 fix: file logger ensure directory exists 2025-06-18 17:39:11 +03:00
4c1018ddec feat: added logger container behaviour 2025-06-18 17:18:08 +03:00
cf7061fd58 fix: shape2D triangulation order changed 2025-06-15 15:14:06 +03:00
e6b7b9953f feat: ensured all primitives have ToString, GetHashCode & Equals methods 2025-06-15 14:44:50 +03:00
4a3775a0de perf: double copy in shape collider's world shape field 2025-06-15 14:34:52 +03:00
4d353662a1 feat: xna color to engine color rgba extension method 2025-06-15 13:32:13 +03:00
ca0b2de917 docs: fixed typo on Shape2D parameter 2025-06-15 13:29:53 +03:00
2335c3ec62 docs: added ray 2d comments 2025-06-13 22:17:39 +03:00
30ccab1b93 refactor: list pool initial count and capacity parameters added 2025-06-09 20:36:39 +03:00
f56d6a7fc8 chore: standalone physics engine not having pooled lists fixed 2025-06-09 20:27:29 +03:00
29a7f5880f feat: transform up, down, left & right properties added 2025-06-09 18:59:15 +03:00
eee3056614 fix: events not having default parameterless constructor 2025-06-09 18:34:20 +03:00
152b0e93db feat: added list pools 2025-06-09 18:33:47 +03:00
3f914fe46f refactor: extracted interface from pool and added events 2025-06-09 18:19:32 +03:00
62b54ee836 feat: event listener counts as constructor parameters 2025-06-09 18:19:08 +03:00
6a41407005 feat: added raycasting support for physics engine 2D 2025-06-09 18:11:20 +03:00
adfa6c6ba0 feat: Vector2D.Reversed property added 2025-06-09 18:04:41 +03:00
a53766f472 fix: forgotten extension method for Line2D.IntersectionPoint 2025-06-09 17:51:34 +03:00
40735c713a feat: added basic pool helper 2025-06-09 17:51:06 +03:00
2054ae3a35 feat: added Ray2D primitive 2025-06-09 16:55:42 +03:00
9066e11c12 perf: simplified Line2D.ClosestPointTo method 2025-06-08 23:40:00 +03:00
f16a7e55c9 chore: fixed record struct arguments' naming 2025-06-08 21:12:16 +03:00
e3b32b3c4a chore: removed unused variables 2025-06-08 21:11:47 +03:00
a02584f3b6 chore: removed DelegateExtensions.InvokeSafe 2025-06-07 18:19:56 +03:00
45524e474e refactor: updated systems to use the update interfaces 2025-06-06 20:26:19 +03:00
fbdea47dc7 docs: updated physics interface delta parameter comment 2025-06-05 23:28:08 +03:00
f5fbd4e5ef feat: IPhysicsIteration interface added 2025-06-05 23:23:34 +03:00
c7f63dc638 refactor: rewritten MonoGameWindow to take in a universe as a constructor parameter 2025-06-04 20:13:01 +03:00
beecefec1c refactor: switched from universe objects to behaviours on all managers like update, draw & physics etc. 2025-06-03 23:59:40 +03:00
24d1a1d764 feat: ISpriteBatch added for MonoGame integration 2025-06-03 23:38:25 +03:00
9edf3b0aa6 feat: one time listeners for events added 2025-06-03 11:43:46 +03:00
8d49fb467c fix: sprite batcher not collecting drawables 2025-06-01 18:36:20 +03:00
2caa042317 feat: basic MonoGame integration implementations added 2025-06-01 15:02:25 +03:00
fe8bde855d fix: draw and update call orders being reverted 2025-06-01 14:45:28 +03:00
ac620264b1 refactor: removed unnecessary overrides from Behaviour class 2025-06-01 14:31:05 +03:00
f31b84f519 refactor: renamed sort comparer names to be more readable 2025-06-01 14:18:50 +03:00
efb7cc7452 refactor: moved behaviour shortcut properties to base 2025-06-01 14:18:25 +03:00
7a3202a053 chore: simplified type names on physics engine 2D 2025-06-01 10:26:38 +03:00
86c9ed2ba9 feat: parameterless Event type 2025-05-31 20:24:45 +03:00
56321864fb fix: tween manager not returning the cancelled tweens back into the pool 2025-05-31 12:10:57 +03:00
6adc002f1a chore: renamed tween manager queue to pool for better readability 2025-05-31 12:08:44 +03:00
1acc8bdb8f perf!: improved sorted behaviour collector by using binary insertion to reduce performance impact 2025-05-31 12:00:32 +03:00
61e2761580 perf!: events refactored throughout all the project to use Event<> class
All delegate events are refactored to use the Event<TSender> and Event<TSender, TArgument> for performance issues regarding delegate events creating garbage, also this gives us better control on event invocation since C# Delegates did also create unnecessary garbage during Delegate.DynamicInvoke
2025-05-31 00:32:58 +03:00
996e61d0ad perf: tween manager pooling 2025-05-30 23:53:18 +03:00
278 changed files with 5718 additions and 1620 deletions

4
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "Engine.Serializers/YamlDotNet"]
path = Engine.Serializers/YamlDotNet
[submodule "Engine.Integration/YamlDotNet"]
path = Engine.Integration/YamlDotNet
url = git@github.com:Syntriax/YamlDotNet.git

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Indicates the class implementing it has Assignable fields that are necessary for the engine to work properly.
@@ -8,7 +8,7 @@ public interface IAssignable
/// <summary>
/// Event triggered when the <see cref="IAssignable"/>'s fields are unassigned and completely ready to recycle.
/// </summary>
event UnassignEventHandler? OnUnassigned;
Event<IAssignable>? OnUnassigned { get; }
/// <summary>
/// Unassign <see cref="IAssignable"/>'s all fields and make it ready to recycle.
@@ -17,6 +17,4 @@ public interface IAssignable
/// <see cref="true"/>, if the fields are unsigned successfully, <see cref="false"/> if not.
/// </returns>
bool Unassign();
delegate void UnassignEventHandler(IAssignable sender);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IBehaviourController"/> field.
@@ -8,7 +8,7 @@ public interface IHasBehaviourController : IAssignable
/// <summary>
/// Event triggered when the <see cref="IBehaviourController"/> value has has been assigned a new value.
/// </summary>
event BehaviourControllerAssignedEventHandler? OnBehaviourControllerAssigned;
Event<IHasBehaviourController> OnBehaviourControllerAssigned { get; }
/// <inheritdoc cref="IBehaviourController" />
IBehaviourController BehaviourController { get; }
@@ -21,6 +21,4 @@ public interface IHasBehaviourController : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(IBehaviourController behaviourController);
delegate void BehaviourControllerAssignedEventHandler(IHasBehaviourController sender);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IEntity"/> field.
@@ -8,7 +8,7 @@ public interface IHasEntity : IAssignable
/// <summary>
/// Event triggered when the <see cref="IEntity"/> value has has been assigned a new value.
/// </summary>
event EntityAssignedEventHandler? OnEntityAssigned;
Event<IHasEntity> OnEntityAssigned { get; }
/// <inheritdoc cref="IEntity" />
IEntity Entity { get; }
@@ -21,6 +21,4 @@ public interface IHasEntity : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(IEntity entity);
delegate void EntityAssignedEventHandler(IHasEntity sender);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IStateEnable"/> field.
@@ -8,7 +8,7 @@ public interface IHasStateEnable : IAssignable
/// <summary>
/// Event triggered when the <see cref="IStateEnable"/> value has has been assigned a new value.
/// </summary>
event StateEnableAssignedEventHandler? OnStateEnableAssigned;
Event<IHasStateEnable> OnStateEnableAssigned { get; }
/// <inheritdoc cref="IStateEnable" />
IStateEnable StateEnable { get; }
@@ -21,6 +21,4 @@ public interface IHasStateEnable : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(IStateEnable stateEnable);
delegate void StateEnableAssignedEventHandler(IHasStateEnable sender);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IUniverse"/> field.
@@ -8,7 +8,7 @@ public interface IHasUniverse : IAssignable
/// <summary>
/// Event triggered when the <see cref="IUniverse"/> value has has been assigned a new value.
/// </summary>
event UniverseAssignedEventHandler? OnUniverseAssigned;
Event<IHasUniverse> OnUniverseAssigned { get; }
/// <inheritdoc cref="IUniverse" />
IUniverse Universe { get; }
@@ -21,6 +21,4 @@ public interface IHasUniverse : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(IUniverse universe);
delegate void UniverseAssignedEventHandler(IHasUniverse sender);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IUniverseObject"/> field.
@@ -8,7 +8,7 @@ public interface IHasUniverseObject : IAssignable
/// <summary>
/// Event triggered when the <see cref="IUniverseObject"/> value has has been assigned a new value.
/// </summary>
event UniverseObjectAssignedEventHandler? OnUniverseObjectAssigned;
Event<IHasUniverseObject> OnUniverseObjectAssigned { get; }
/// <inheritdoc cref="IUniverseObject" />
IUniverseObject UniverseObject { get; }
@@ -21,6 +21,4 @@ public interface IHasUniverseObject : IAssignable
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(IUniverseObject universeObject);
delegate void UniverseObjectAssignedEventHandler(IHasUniverseObject sender);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents an entity which can be active or not.
@@ -8,12 +8,12 @@ public interface IActive
/// <summary>
/// Event triggered when the <see cref="IsActive"/> state of the <see cref="IActive"/> changes.
/// </summary>
event ActiveChangedEventHandler? OnActiveChanged;
Event<IActive, ActiveChangedArguments> OnActiveChanged { get; }
/// <summary>
/// The value indicating whether the <see cref="IActive"/> is enabled.
/// </summary>
bool IsActive { get; }
delegate void ActiveChangedEventHandler(IActive sender, bool previousState);
readonly record struct ActiveChangedArguments(bool PreviousState);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a behaviour that any object in the engine that might use to interact with itself or other objects.
@@ -8,12 +8,12 @@ public interface IBehaviour : IEntity, IActive, IHasBehaviourController, IHasSta
/// <summary>
/// Event triggered when the priority of the <see cref="IBehaviour"/> changes.
/// </summary>
event PriorityChangedEventHandler? OnPriorityChanged;
Event<IBehaviour, PriorityChangedArguments> OnPriorityChanged { get; }
/// <summary>
/// The priority of the <see cref="IBehaviour"/>.
/// </summary>
int Priority { get; set; }
delegate void PriorityChangedEventHandler(IBehaviour sender, int previousPriority);
readonly record struct PriorityChangedArguments(int PreviousPriority);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public interface IBehaviour2D : IBehaviour
{

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a collector for the class type of <typeparamref name="T"/>.
@@ -10,12 +10,12 @@ public interface IBehaviourCollector<T> : IHasUniverse where T : class
/// <summary>
/// Event triggered when an object of type <typeparamref name="T"/> is added to the collector.
/// </summary>
event CollectedEventHandler? OnCollected;
Event<IBehaviourCollector<T>, BehaviourCollectedArguments> OnCollected { get; }
/// <summary>
/// Event triggered when an object of type <typeparamref name="T"/> is removed from the collector.
/// </summary>
event RemovedEventHandler? OnRemoved;
Event<IBehaviourCollector<T>, BehaviourRemovedArguments> OnRemoved { get; }
/// <summary>
/// Amount of <typeparamref name="T"/> collected.
@@ -32,12 +32,11 @@ public interface IBehaviourCollector<T> : IHasUniverse where T : class
/// </summary>
/// <param name="sender">The instance of the <see cref="IBehaviourCollector{T}"/> that triggered the event.</param>
/// <param name="behaviourCollected">The object of type <typeparamref name="T"/> that was added to the collector.</param>
delegate void CollectedEventHandler(IBehaviourCollector<T> sender, T behaviourCollected);
readonly record struct BehaviourCollectedArguments(T BehaviourCollected);
/// <summary>
/// Delegate for handling the <see cref="OnRemoved"/> event.
/// </summary>
/// <param name="sender">The instance of the <see cref="IBehaviourCollector{T}"/> that triggered the event.</param>
/// <param name="behaviourRemoved">The object of type <typeparamref name="T"/> that was removed from the collector.</param>
delegate void RemovedEventHandler(IBehaviourCollector<T> sender, T behaviourRemoved);
/// <param name="BehaviourRemoved">The object of type <typeparamref name="T"/> that was removed from the collector.</param>
readonly record struct BehaviourRemovedArguments(T BehaviourRemoved);
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a controller for managing <see cref="IBehaviour"/>s. Connected to an <see cref="IUniverseObject"/>.
@@ -10,12 +10,12 @@ public interface IBehaviourController : IEntity, IHasUniverseObject
/// <summary>
/// Event triggered when a <see cref="IBehaviour"/> is added to the <see cref="IBehaviourController"/>.
/// </summary>
event BehaviourAddedEventHandler? OnBehaviourAdded;
Event<IBehaviourController, BehaviourAddedArguments> OnBehaviourAdded { get; }
/// <summary>
/// Event triggered when a <see cref="IBehaviour"/> is removed from the <see cref="IBehaviourController"/>.
/// </summary>
event BehaviourRemovedEventHandler? OnBehaviourRemoved;
Event<IBehaviourController, BehaviourRemovedArguments> OnBehaviourRemoved { get; }
/// <summary>
/// Amount of <see cref="IBehaviour"/> collected.
@@ -78,6 +78,6 @@ public interface IBehaviourController : IEntity, IHasUniverseObject
/// <param name="behaviour">The <see cref="IBehaviour"/> to remove.</param>
void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour;
delegate void BehaviourAddedEventHandler(IBehaviourController sender, IBehaviour behaviourAdded);
delegate void BehaviourRemovedEventHandler(IBehaviourController sender, IBehaviour behaviourRemoved);
readonly record struct BehaviourAddedArguments(IBehaviour BehaviourAdded);
readonly record struct BehaviourRemovedArguments(IBehaviour BehaviourRemoved);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a 2D camera in the engine.

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public interface ICoroutineYield
{

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a basic entity in the engine.
@@ -9,12 +9,12 @@ public interface IEntity : IInitializable, IHasStateEnable
/// 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 IdChangedEventHandler? OnIdChanged;
Event<IEntity, IdChangedArguments> OnIdChanged { get; }
/// <summary>
/// The ID of the <see cref="IEntity"/>.
/// </summary>
string Id { get; set; }
delegate void IdChangedEventHandler(IEntity sender, string previousId);
readonly record struct IdChangedArguments(string PreviousId);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents an entity that can be initialized and finalized. This information is useful for objects we know that are not in use and can be either recycled or dropped for garbage collection.
@@ -8,12 +8,12 @@ public interface IInitializable
/// <summary>
/// Event triggered when the <see cref="Initialize"/> method is called successfully.
/// </summary>
event InitializedEventHandler? OnInitialized;
Event<IInitializable> OnInitialized { get; }
/// <summary>
/// Event triggered when the <see cref="IInitializable"/> method is called successfully.
/// </summary>
event FinalizedEventHandler? OnFinalized;
Event<IInitializable> OnFinalized { get; }
/// <summary>
/// The value indicating whether the entity has been initialized.
@@ -31,7 +31,4 @@ public interface IInitializable
/// </summary>
/// <returns><see cref="true"/> if finalization is successful, otherwise <see cref="false"/>.</returns>
bool Finalize();
delegate void InitializedEventHandler(IInitializable sender);
delegate void FinalizedEventHandler(IInitializable sender);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents an entity with a name.
@@ -8,12 +8,12 @@ public interface INameable
/// <summary>
/// Event triggered when the name of the entity changes.
/// </summary>
event NameChangedEventHandler? OnNameChanged;
Event<INameable, NameChangedArguments> OnNameChanged { get; }
/// <summary>
/// The name of the entity.
/// </summary>
string Name { get; set; }
delegate void NameChangedEventHandler(INameable sender, string previousName);
readonly record struct NameChangedArguments(string PreviousName);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents an entity with an enable state that can be toggled.
@@ -8,12 +8,12 @@ public interface IStateEnable : IHasEntity
/// <summary>
/// Event triggered when the <see cref="Enabled"/> state of the <see cref="IStateEnable"/> changes.
/// </summary>
event EnabledChangedEventHandler? OnEnabledChanged;
Event<IStateEnable, EnabledChangedArguments> OnEnabledChanged { get; }
/// <summary>
/// The value indicating whether the <see cref="IStateEnable"/> is enabled.
/// </summary>
bool Enabled { get; set; }
delegate void EnabledChangedEventHandler(IStateEnable sender, bool previousState);
readonly record struct EnabledChangedArguments(bool PreviousState);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents the transformation properties of an object such as position, scale, and rotation in 2D space.
@@ -8,17 +8,37 @@ public interface ITransform2D : IBehaviour
/// <summary>
/// Event triggered when the <see cref="Position"/> of the <see cref="ITransform2D"/> changes.
/// </summary>
event PositionChangedEventHandler? OnPositionChanged;
Event<ITransform2D, PositionChangedArguments> OnPositionChanged { get; }
/// <summary>
/// Event triggered when the <see cref="Scale"/> of the <see cref="ITransform2D"/> changes.
/// </summary>
event ScaleChangedEventHandler? OnScaleChanged;
Event<ITransform2D, ScaleChangedArguments> OnScaleChanged { get; }
/// <summary>
/// Event triggered when the <see cref="Rotation"/> of the <see cref="ITransform"/> changes.
/// </summary>
event RotationChangedEventHandler? OnRotationChanged;
Event<ITransform2D, RotationChangedArguments> OnRotationChanged { get; }
/// <summary>
/// The <see cref="Vector2D"/> pointing upwards in world space.
/// </summary>
Vector2D Up { get; }
/// <summary>
/// The <see cref="Vector2D"/> pointing upwards in world space.
/// </summary>
Vector2D Down { get; }
/// <summary>
/// The <see cref="Vector2D"/> pointing upwards in world space.
/// </summary>
Vector2D Left { get; }
/// <summary>
/// The <see cref="Vector2D"/> pointing upwards in world space.
/// </summary>
Vector2D Right { get; }
/// <summary>
/// The world position of the <see cref="ITransform2D"/> in 2D space.
@@ -51,23 +71,20 @@ public interface ITransform2D : IBehaviour
float LocalRotation { get; set; }
/// <summary>
/// Delegate for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
/// Arguments for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
/// </summary>
/// <param name="sender">The <see cref="ITransform2D"/> that the parent has changed.</param>
/// <param name="previousPosition">The previous <see cref="Position"/> of the <see cref="ITransform2D"/>.</param>
delegate void PositionChangedEventHandler(ITransform2D sender, Vector2D previousPosition);
/// <param name="PreviousPosition">The previous <see cref="Position"/> of the <see cref="ITransform2D"/>.</param>
readonly record struct PositionChangedArguments(Vector2D PreviousPosition);
/// <summary>
/// Delegate for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
/// Arguments for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
/// </summary>
/// <param name="sender">The <see cref="ITransform2D"/> that the parent has changed.</param>
/// <param name="previousScale">The previous <see cref="Scale"/> of the <see cref="ITransform2D"/>.</param>
delegate void ScaleChangedEventHandler(ITransform2D sender, Vector2D previousScale);
/// <param name="PreviousScale">The previous <see cref="Scale"/> of the <see cref="ITransform2D"/>.</param>
readonly record struct ScaleChangedArguments(Vector2D PreviousScale);
/// <summary>
/// Delegate for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
/// Arguments for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
/// </summary>
/// <param name="sender">The <see cref="ITransform2D"/> that the parent has changed.</param>
/// <param name="previousRotation">The previous <see cref="Rotation"/> of the <see cref="ITransform2D"/>.</param>
delegate void RotationChangedEventHandler(ITransform2D sender, float previousRotation);
/// <param name="PreviousRotation">The previous <see cref="Rotation"/> of the <see cref="ITransform2D"/>.</param>
readonly record struct RotationChangedArguments(float PreviousRotation);
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a universe responsible for managing <see cref="IUniverseObject"/>s.
@@ -10,47 +10,57 @@ public interface IUniverse : IEntity, IEnumerable<IUniverseObject>
/// <summary>
/// Event triggered when <see cref="Update(UniverseTime)"/> is about to be called called on the <see cref="IUniverse"/>.
/// </summary>
event UpdateEventHandler? OnPreUpdate;
Event<IUniverse, UpdateArguments> OnPreUpdate { get; }
/// <summary>
/// Event triggered when <see cref="Update(UniverseTime)"/> is called on the <see cref="IUniverse"/>.
/// </summary>
event UpdateEventHandler? OnUpdate;
Event<IUniverse, UpdateArguments> OnUpdate { get; }
/// <summary>
/// Event triggered after <see cref="Update(UniverseTime)"/> is called on the <see cref="IUniverse"/>.
/// </summary>
event UpdateEventHandler? OnPostUpdate;
Event<IUniverse, UpdateArguments> OnPostUpdate { get; }
/// <summary>
/// Event triggered when <see cref="Draw"/> is about to be called called on the <see cref="IUniverse"/>.
/// </summary>
event DrawEventHandler? OnPreDraw;
Event<IUniverse> OnPreDraw { get; }
/// <summary>
/// Event triggered when <see cref="Draw"/> is called on the <see cref="IUniverse"/>.
/// </summary>
event DrawEventHandler? OnDraw;
Event<IUniverse> OnDraw { get; }
/// <summary>
/// Event triggered after <see cref="Draw"/> is called on the <see cref="IUniverse"/>.
/// </summary>
event DrawEventHandler? OnPostDraw;
Event<IUniverse> OnPostDraw { get; }
/// <summary>
/// Event triggered when a <see cref="IUniverseObject"/> is about to be registered to the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse, UniverseObjectRegisteredArguments> OnPreUniverseObjectRegistered { get; }
/// <summary>
/// Event triggered when a <see cref="IUniverseObject"/> is registered to the <see cref="IUniverse"/>.
/// </summary>
event UniverseObjectRegisteredEventHandler? OnUniverseObjectRegistered;
Event<IUniverse, UniverseObjectRegisteredArguments> OnUniverseObjectRegistered { get; }
/// <summary>
/// Event triggered when a <see cref="IUniverseObject"/> is about to be unregistered from the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse, UniverseObjectUnRegisteredArguments> OnPreUniverseObjectUnRegistered { get; }
/// <summary>
/// Event triggered when a <see cref="IUniverseObject"/> is unregistered from the <see cref="IUniverse"/>.
/// </summary>
event UniverseObjectUnRegisteredEventHandler? OnUniverseObjectUnRegistered;
Event<IUniverse, UniverseObjectUnRegisteredArguments> OnUniverseObjectUnRegistered { get; }
/// <summary>
/// Event triggered when <see cref="TimeScale"/> is changed on the <see cref="IUniverse"/>.
/// </summary>
event TimeScaleChangedEventHandler? OnTimeScaleChanged;
Event<IUniverse, TimeScaleChangedArguments> OnTimeScaleChanged { get; }
/// <summary>
/// Current time scale the <see cref="IUniverse"/> operates on.
@@ -103,11 +113,8 @@ public interface IUniverse : IEntity, IEnumerable<IUniverseObject>
/// </summary>
void Draw();
delegate void TimeScaleChangedEventHandler(IUniverse sender, float previousTimeScale);
delegate void UpdateEventHandler(IUniverse sender, UniverseTime engineTime);
delegate void DrawEventHandler(IUniverse sender);
delegate void UniverseObjectRegisteredEventHandler(IUniverse sender, IUniverseObject universeObjectRegistered);
delegate void UniverseObjectUnRegisteredEventHandler(IUniverse sender, IUniverseObject universeObjectUnregistered);
readonly record struct TimeScaleChangedArguments(float PreviousTimeScale);
readonly record struct UpdateArguments(UniverseTime EngineTime);
readonly record struct UniverseObjectRegisteredArguments(IUniverseObject UniverseObjectRegistered);
readonly record struct UniverseObjectUnRegisteredArguments(IUniverseObject UniverseObjectUnregistered);
}

View File

@@ -1,38 +1,38 @@
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents an <see cref="IEntity"/> that can enter and exit a universe within the <see cref="IUniverse"/> system.
/// This interface allows for tracking the object's presence in the universe and provides events
/// for notifying when the see enters or exits the universe.
/// </summary>
public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourController, IEnumerable<IUniverseObject>
public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourController
{
/// <summary>
/// Event triggered when the <see cref="IUniverseObject"/> enters the universe.
/// </summary>
event EnteredUniverseEventHandler? OnEnteredUniverse;
Event<IUniverseObject, EnteredUniverseArguments> OnEnteredUniverse { get; }
/// <summary>
/// Event triggered when the <see cref="IUniverseObject"/> exits the universe.
/// </summary>
event ExitedUniverseEventHandler? OnExitedUniverse;
Event<IUniverseObject, ExitedUniverseArguments> OnExitedUniverse { get; }
/// <summary>
/// Event triggered when the <see cref="Parent"/> of the <see cref="IUniverseObject"/> changes. The second parameter is the old <see cref="IUniverseObject"/>.
/// </summary>
event ParentChangedEventHandler? OnParentChanged;
Event<IUniverseObject, ParentChangedArguments> OnParentChanged { get; }
/// <summary>
/// Event triggered when a new <see cref="IUniverseObject"/> is added to the <see cref="Children"/>.
/// </summary>
event ChildrenAddedEventHandler? OnChildrenAdded;
Event<IUniverseObject, ChildrenAddedArguments> OnChildrenAdded { get; }
/// <summary>
/// Event triggered when an <see cref="IUniverseObject"/> is removed from the <see cref="Children"/>.
/// </summary>
event ChildrenRemovedEventHandler? OnChildrenRemoved;
Event<IUniverseObject, ChildrenRemovedArguments> OnChildrenRemoved { get; }
/// <summary>
/// Gets the <see cref="IUniverse"/> this <see cref="IUniverseObject"/> is connected to, if any.
@@ -47,7 +47,7 @@ public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourCon
/// <summary>
/// The parent <see cref="IUniverseObject"/> of the <see cref="IUniverseObject"/>.
/// </summary>
IUniverseObject? Parent { get; }
IUniverseObject? Parent { get; set; }
/// <summary>
/// The <see cref="IUniverseObject"/>s that have this <see cref="IUniverseObject"/> as their <see cref="Parent"/>.
@@ -75,12 +75,6 @@ public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourCon
/// </returns>
internal bool ExitUniverse();
/// <summary>
/// Sets the parent <see cref="IUniverseObject"/> of this <see cref="IUniverseObject"/>.
/// </summary>
/// <param name="universeObject">The parent <see cref="IUniverseObject"/> to set.</param>
void SetParent(IUniverseObject? universeObject);
/// <summary>
/// Adds a child <see cref="IUniverseObject"/> to this <see cref="IUniverseObject"/>.
/// </summary>
@@ -94,38 +88,38 @@ public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourCon
void RemoveChild(IUniverseObject universeObject);
/// <summary>
/// EventHandler delegate for the event triggered when the <see cref="IUniverseObject"/> enters the universe of a <see cref="IUniverse">.
/// Arguments for the event triggered when the <see cref="IUniverseObject"/> enters the universe of a <see cref="IUniverse">.
/// </summary>
/// <param name="sender">The <see cref="IUniverseObject"/> that entered the universe.</param>
/// <param name="universe">The <see cref="IUniverse"/> that the <see cref="IUniverseObject"/> has entered it's universe.</param>
delegate void EnteredUniverseEventHandler(IUniverseObject sender, IUniverse universe);
readonly record struct EnteredUniverseArguments(IUniverse Universe);
/// <summary>
/// EventHandler delegate for the event triggered when the <see cref="IUniverseObject"/> exits the universe of a <see cref="IUniverse">.
/// Arguments for the event triggered when the <see cref="IUniverseObject"/> exits the universe of a <see cref="IUniverse">.
/// </summary>
/// <param name="sender">The <see cref="IUniverseObject"/> that exited the universe.</param>
/// <param name="universe">The <see cref="IUniverse"/> that the <see cref="IUniverseObject"/> has exited it's universe.</param>
delegate void ExitedUniverseEventHandler(IUniverseObject sender, IUniverse universe);
readonly record struct ExitedUniverseArguments(IUniverse Universe);
/// <summary>
/// Delegate for the event triggered when the <see cref="IUniverseObject"/>'s parent changes.
/// Arguments for the event triggered when the <see cref="IUniverseObject"/>'s parent changes.
/// </summary>
/// <param name="sender">The <see cref="IUniverseObject"/> that the parent has changed.</param>
/// <param name="previousParent">The previous <see cref="IUniverseObject"/> the sender was a child of.</param>
/// <param name="newParent">The new and current <see cref="IUniverseObject"/> the sender is a child of.</param>
delegate void ParentChangedEventHandler(IUniverseObject sender, IUniverseObject? previousParent, IUniverseObject? newParent);
readonly record struct ParentChangedArguments(IUniverseObject? PreviousParent, IUniverseObject? CurrentParent);
/// <summary>
/// Delegate for the event triggered when a new <see cref="IUniverseObject"/> added as a child.
/// Arguments for the event triggered when a new <see cref="IUniverseObject"/> added as a child.
/// </summary>
/// <param name="sender">The parent <see cref="IUniverseObject"/> this event is being called from.</param>
/// <param name="childrenAdded">The <see cref="IUniverseObject"/> that got removed as a children of the sender <see cref="IUniverseObject"/>.</param>
delegate void ChildrenAddedEventHandler(IUniverseObject sender, IUniverseObject childrenAdded);
readonly record struct ChildrenAddedArguments(IUniverseObject ChildrenAdded);
/// <summary>
/// Delegate for the event triggered when a new <see cref="IUniverseObject"/> removed from being a child.
/// </summary>
/// <param name="sender">The parent <see cref="IUniverseObject"/> this event is being called from.</param>
/// <param name="childrenAdded">The <see cref="IUniverseObject"/> that got removed as a children of the sender <see cref="IUniverseObject"/>.</param>
delegate void ChildrenRemovedEventHandler(IUniverseObject sender, IUniverseObject childrenRemoved);
readonly record struct ChildrenRemovedArguments(IUniverseObject ChildrenRemoved);
}

View File

@@ -1,121 +0,0 @@
using System.Collections;
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
public class ActiveBehaviourCollector<T> : IBehaviourCollector<T> where T : class, IBehaviour
{
public event IAssignable.UnassignEventHandler? OnUnassigned = null;
public event IHasUniverse.UniverseAssignedEventHandler? OnUniverseAssigned = null;
public event IBehaviourCollector<T>.CollectedEventHandler? OnCollected = null;
public event IBehaviourCollector<T>.RemovedEventHandler? OnRemoved = null;
private readonly List<T> monitoringBehaviours = new(32);
protected readonly List<T> activeBehaviours = new(32);
protected readonly Dictionary<IActive, T> monitoringActiveToBehaviour = new(32);
public IUniverse Universe { get; private set; } = null!;
public ActiveBehaviourCollector() { }
public ActiveBehaviourCollector(IUniverse universe) => Assign(universe);
private void OnUniverseObjectRegistered(IUniverse manager, IUniverseObject universeObject)
{
universeObject.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
universeObject.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourAdded(universeObject.BehaviourController, universeObject.BehaviourController[i]);
}
private void OnUniverseObjectUnregistered(IUniverse manager, IUniverseObject universeObject)
{
universeObject.BehaviourController.OnBehaviourAdded -= OnBehaviourAdded;
universeObject.BehaviourController.OnBehaviourRemoved -= OnBehaviourRemoved;
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourRemoved(universeObject.BehaviourController, universeObject.BehaviourController[i]);
}
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is not T tBehaviour)
return;
monitoringBehaviours.Add(tBehaviour);
monitoringActiveToBehaviour.Add(tBehaviour, tBehaviour);
tBehaviour.OnActiveChanged += OnBehaviourStateChanged;
OnBehaviourStateChanged(tBehaviour, !tBehaviour.IsActive);
}
private void OnBehaviourStateChanged(IActive sender, bool previousState)
{
T behaviour = monitoringActiveToBehaviour[sender];
if (sender.IsActive)
{
activeBehaviours.Add(behaviour);
OnBehaviourAdd(behaviour);
OnCollected?.Invoke(this, behaviour);
}
else if (activeBehaviours.Remove(behaviour))
{
OnBehaviourRemove(behaviour);
OnRemoved?.Invoke(this, behaviour);
}
}
protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is not T tBehaviour)
return;
if (!monitoringBehaviours.Remove(tBehaviour) || !monitoringActiveToBehaviour.Remove(tBehaviour))
return;
tBehaviour.OnActiveChanged -= OnBehaviourStateChanged;
if (activeBehaviours.Remove(tBehaviour))
{
OnBehaviourRemove(tBehaviour);
OnRemoved?.Invoke(this, tBehaviour);
}
}
public bool Assign(IUniverse universe)
{
if (Universe is not null)
return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects)
OnUniverseObjectRegistered(universe, universeObject);
universe.OnUniverseObjectRegistered += OnUniverseObjectRegistered;
universe.OnUniverseObjectUnRegistered += OnUniverseObjectUnregistered;
Universe = universe;
OnUniverseAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (Universe is null)
return false;
foreach (IUniverseObject universeObject in Universe.UniverseObjects)
OnUniverseObjectUnregistered(Universe, universeObject);
Universe.OnUniverseObjectRegistered -= OnUniverseObjectRegistered;
Universe.OnUniverseObjectUnRegistered -= OnUniverseObjectUnregistered;
Universe = null!;
OnUnassigned?.Invoke(this);
return true;
}
public int Count => activeBehaviours.Count;
public T this[System.Index index] => activeBehaviours[index];
}

View File

@@ -1,28 +0,0 @@
using System;
namespace Syntriax.Engine.Core;
public class ActiveBehaviourCollectorSorted<T> : ActiveBehaviourCollector<T> where T : class, IBehaviour
{
private Comparison<T>? _sortBy = null;
public Comparison<T>? SortBy
{
get => _sortBy;
set
{
_sortBy = value;
if (value is not null)
activeBehaviours.Sort(value);
}
}
protected override void OnBehaviourAdd(IBehaviour behaviour)
{
if (SortBy is not null)
activeBehaviours.Sort(SortBy);
}
public ActiveBehaviourCollectorSorted() { }
public ActiveBehaviourCollectorSorted(IUniverse universe, Comparison<T> sortBy) : base(universe) => SortBy = sortBy;
}

View File

@@ -1,16 +1,14 @@
using System;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public abstract class BaseEntity : IEntity
{
public event IEntity.IdChangedEventHandler? OnIdChanged = null;
public event IInitializable.InitializedEventHandler? OnInitialized = null;
public event IInitializable.FinalizedEventHandler? OnFinalized = null;
public event IHasStateEnable.StateEnableAssignedEventHandler? OnStateEnableAssigned = null;
public event IAssignable.UnassignEventHandler? OnUnassigned = null;
public Event<IEntity, IEntity.IdChangedArguments> OnIdChanged { get; } = new();
public Event<IInitializable> OnInitialized { get; } = new();
public Event<IInitializable> OnFinalized { get; } = new();
public Event<IHasStateEnable> OnStateEnableAssigned { get; } = new();
public Event<IAssignable> OnUnassigned { get; } = new();
private IStateEnable _stateEnable = null!;
@@ -33,7 +31,7 @@ public abstract class BaseEntity : IEntity
string previousId = _id;
_id = value;
OnIdChanged?.Invoke(this, previousId);
OnIdChanged?.Invoke(this, new(previousId));
}
}

View File

@@ -1,140 +1,50 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public abstract class Behaviour : BehaviourBase, IFirstFrameUpdate,
IPreUpdate, IUpdate, IPostUpdate,
IPreDraw, IDraw, IPostDraw
public abstract class Behaviour : BehaviourBase
{
protected IUniverse Universe => BehaviourController.UniverseObject.Universe;
protected IUniverseObject UniverseObject => BehaviourController.UniverseObject;
private readonly Event<IUniverseObject, IUniverseObject.EnteredUniverseArguments>.EventHandler delegateEnteredUniverse = null!;
private readonly Event<IUniverseObject, IUniverseObject.ExitedUniverseArguments>.EventHandler delegateExitedUniverse = null!;
public Behaviour()
{
OnInitialized += OnInitialize;
OnFinalized += OnFinalize;
OnUnassigned += OnUnassign;
OnInitialized.AddListener(OnInitialize);
OnFinalized.AddListener(OnFinalize);
OnUnassigned.AddListener(OnUnassign);
delegateEnteredUniverse = EnteredUniverse;
delegateExitedUniverse = ExitedUniverse;
}
protected virtual void OnUnassign() { }
protected virtual void OnUnassign(IAssignable assignable) => OnUnassign();
protected void OnUnassign(IAssignable assignable) => OnUnassign();
protected virtual void OnInitialize() { }
protected virtual void OnInitialize(IInitializable _)
protected void OnInitialize(IInitializable _)
{
BehaviourController.UniverseObject.OnEnteredUniverse += EnteredUniverse;
BehaviourController.UniverseObject.OnExitedUniverse += ExitedUniverse;
BehaviourController.UniverseObject.OnEnteredUniverse.AddListener(delegateEnteredUniverse);
BehaviourController.UniverseObject.OnExitedUniverse.AddListener(delegateExitedUniverse);
OnInitialize();
if (UniverseObject.IsInUniverse)
EnteredUniverse(UniverseObject, Universe);
EnteredUniverse(UniverseObject, new(Universe));
}
protected virtual void OnFinalize() { }
protected virtual void OnFinalize(IInitializable _)
protected void OnFinalize(IInitializable _)
{
BehaviourController.UniverseObject.OnEnteredUniverse -= EnteredUniverse;
BehaviourController.UniverseObject.OnExitedUniverse -= ExitedUniverse;
BehaviourController.UniverseObject.OnEnteredUniverse.RemoveListener(delegateEnteredUniverse);
BehaviourController.UniverseObject.OnExitedUniverse.RemoveListener(delegateExitedUniverse);
OnFinalize();
if (UniverseObject.IsInUniverse)
ExitedUniverse(UniverseObject, Universe);
}
protected virtual void OnFirstActiveFrame() { }
void IFirstFrameUpdate.FirstActiveFrame()
{
Debug.Assert.AssertInitialized(this);
OnFirstActiveFrame();
}
protected virtual void OnPreUpdatePreActiveCheck() { }
protected virtual void OnPreUpdate() { }
void IPreUpdate.PreUpdate()
{
Debug.Assert.AssertInitialized(this);
OnPreUpdatePreActiveCheck();
if (!IsActive)
return;
OnPreUpdate();
}
protected virtual void OnUpdatePreActiveCheck() { }
protected virtual void OnUpdate() { }
void IUpdate.Update()
{
Debug.Assert.AssertInitialized(this);
OnUpdatePreActiveCheck();
if (!IsActive)
return;
OnUpdate();
}
protected virtual void OnPostUpdatePreActiveCheck() { }
protected virtual void OnPostUpdate() { }
void IPostUpdate.PostUpdate()
{
Debug.Assert.AssertInitialized(this);
OnPostUpdatePreActiveCheck();
if (!StateEnable.Enabled)
return;
OnPostUpdate();
}
protected virtual void OnPreDrawPreActiveCheck() { }
protected virtual void OnPreDraw() { }
void IPreDraw.PreDraw()
{
Debug.Assert.AssertInitialized(this);
OnPreDrawPreActiveCheck();
if (!StateEnable.Enabled)
return;
OnPreDraw();
}
protected virtual void OnDrawPreActiveCheck() { }
protected virtual void OnDraw() { }
void IDraw.Draw()
{
Debug.Assert.AssertInitialized(this);
OnDrawPreActiveCheck();
if (!StateEnable.Enabled)
return;
OnDraw();
}
protected virtual void OnPostDrawPreActiveCheck() { }
protected virtual void OnPostDraw() { }
void IPostDraw.PostDraw()
{
Debug.Assert.AssertInitialized(this);
OnPostDrawPreActiveCheck();
if (!StateEnable.Enabled)
return;
OnPostDraw();
ExitedUniverse(UniverseObject, new(Universe));
}
protected virtual void OnEnteredUniverse(IUniverse universe) { }
protected virtual void EnteredUniverse(IUniverseObject sender, IUniverse universe) => OnEnteredUniverse(universe);
protected void EnteredUniverse(IUniverseObject sender, IUniverseObject.EnteredUniverseArguments args) => OnEnteredUniverse(args.Universe);
protected virtual void OnExitedUniverse(IUniverse universe) { }
protected virtual void ExitedUniverse(IUniverseObject sender, IUniverse universe) => OnExitedUniverse(universe);
protected void ExitedUniverse(IUniverseObject sender, IUniverseObject.ExitedUniverseArguments args) => OnExitedUniverse(args.Universe);
}

View File

@@ -1,22 +1,9 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public abstract class Behaviour2D : Behaviour, IBehaviour2D
{
public ITransform2D Transform { get; private set; } = null!;
protected sealed override void OnInitialize(IInitializable _)
{
Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
base.OnInitialize(_);
}
protected sealed override void OnFinalize(IInitializable _)
{
Transform = null!;
base.OnFinalize(_);
}
protected sealed override void OnUnassign(IAssignable assignable) => base.OnUnassign(assignable);
protected sealed override void EnteredUniverse(IUniverseObject sender, IUniverse universe) => base.EnteredUniverse(sender, universe);
protected sealed override void ExitedUniverse(IUniverseObject sender, IUniverse universe) => base.ExitedUniverse(sender, universe);
protected override void OnInitialize() => Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
protected override void OnFinalize() => Transform = null!;
}

View File

@@ -1,11 +1,18 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
[System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")]
public abstract class BehaviourBase : BaseEntity, IBehaviour
{
public event IHasBehaviourController.BehaviourControllerAssignedEventHandler? OnBehaviourControllerAssigned = null;
public event IBehaviour.PriorityChangedEventHandler? OnPriorityChanged = null;
public event IActive.ActiveChangedEventHandler? OnActiveChanged = null;
public Event<IBehaviour, IBehaviour.PriorityChangedArguments> OnPriorityChanged { get; } = new();
public Event<IActive, IActive.ActiveChangedArguments> OnActiveChanged { get; } = new();
public Event<IHasBehaviourController> OnBehaviourControllerAssigned { get; } = new();
private readonly Event<IHasUniverseObject>.EventHandler delegateOnUniverseObjectAssigned = null!;
private readonly Event<IActive, IActive.ActiveChangedArguments>.EventHandler delegateOnUniverseObjectActiveChanged = null!;
private readonly Event<IStateEnable, IStateEnable.EnabledChangedArguments>.EventHandler delegateOnStateEnabledChanged = null!;
public IUniverse Universe => BehaviourController.UniverseObject.Universe;
public IUniverseObject UniverseObject => BehaviourController.UniverseObject;
private IBehaviourController _behaviourController = null!;
public IBehaviourController BehaviourController => _behaviourController;
@@ -21,7 +28,7 @@ public abstract class BehaviourBase : BaseEntity, IBehaviour
int previousPriority = _priority;
_priority = value;
OnPriorityChanged?.Invoke(this, previousPriority);
OnPriorityChanged?.Invoke(this, new(previousPriority));
}
}
@@ -36,7 +43,8 @@ public abstract class BehaviourBase : BaseEntity, IBehaviour
_behaviourController = behaviourController;
OnAssign(behaviourController);
behaviourController.OnUniverseObjectAssigned += OnUniverseObjectAssigned;
behaviourController.OnUniverseObjectAssigned.AddListener(delegateOnUniverseObjectAssigned);
behaviourController.StateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged);
if (behaviourController.UniverseObject is not null)
OnUniverseObjectAssigned(behaviourController);
OnBehaviourControllerAssigned?.Invoke(this);
@@ -45,7 +53,7 @@ public abstract class BehaviourBase : BaseEntity, IBehaviour
private void OnUniverseObjectAssigned(IHasUniverseObject sender)
{
sender.UniverseObject.OnActiveChanged += OnUniverseObjectActiveChanged;
sender.UniverseObject.OnActiveChanged.AddListener(delegateOnUniverseObjectActiveChanged);
UpdateActive();
}
@@ -53,13 +61,15 @@ public abstract class BehaviourBase : BaseEntity, IBehaviour
{
base.OnAssign(stateEnable);
stateEnable.OnEnabledChanged += OnStateEnabledChanged;
stateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged);
}
protected override void UnassignInternal()
{
StateEnable.OnEnabledChanged -= OnStateEnabledChanged;
BehaviourController.OnUniverseObjectAssigned -= OnUniverseObjectAssigned;
BehaviourController.UniverseObject.OnActiveChanged.RemoveListener(delegateOnUniverseObjectActiveChanged);
StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged);
BehaviourController.OnUniverseObjectAssigned.RemoveListener(delegateOnUniverseObjectAssigned);
BehaviourController.StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged);
base.UnassignInternal();
_behaviourController = null!;
}
@@ -68,17 +78,26 @@ public abstract class BehaviourBase : BaseEntity, IBehaviour
{
Debug.Assert.AssertBehaviourControllerAssigned(this);
Debug.Assert.AssertStateEnableAssigned(this);
UpdateActive();
}
private void OnStateEnabledChanged(IStateEnable sender, bool previousState) => UpdateActive();
private void OnUniverseObjectActiveChanged(IActive sender, bool previousState) => UpdateActive();
private void OnStateEnabledChanged(IStateEnable sender, IStateEnable.EnabledChangedArguments args) => UpdateActive();
private void OnUniverseObjectActiveChanged(IActive sender, IActive.ActiveChangedArguments args) => UpdateActive();
private void UpdateActive()
{
bool previousActive = IsActive;
_isActive = StateEnable.Enabled && _behaviourController.UniverseObject.IsActive;
_isActive = StateEnable.Enabled && _behaviourController.StateEnable.Enabled && _behaviourController.UniverseObject.IsActive;
if (previousActive != IsActive)
OnActiveChanged?.Invoke(this, previousActive);
OnActiveChanged?.Invoke(this, new(previousActive));
}
protected BehaviourBase()
{
delegateOnUniverseObjectAssigned = OnUniverseObjectAssigned;
delegateOnUniverseObjectActiveChanged = OnUniverseObjectActiveChanged;
delegateOnStateEnabledChanged = OnStateEnabledChanged;
}
}

View File

@@ -1,100 +0,0 @@
using System.Collections;
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
public class BehaviourCollector<T> : IBehaviourCollector<T> where T : class
{
public event IAssignable.UnassignEventHandler? OnUnassigned = null;
public event IHasUniverse.UniverseAssignedEventHandler? OnUniverseAssigned = null;
public event IBehaviourCollector<T>.CollectedEventHandler? OnCollected = null;
public event IBehaviourCollector<T>.RemovedEventHandler? OnRemoved = null;
protected readonly List<T> behaviours = new(32);
public IUniverse Universe { get; private set; } = null!;
public BehaviourCollector() { }
public BehaviourCollector(IUniverse universe) => Assign(universe);
private void OnUniverseObjectRegistered(IUniverse manager, IUniverseObject universeObject)
{
universeObject.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
universeObject.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourAdded(universeObject.BehaviourController, universeObject.BehaviourController[i]);
}
private void OnUniverseObjectUnregistered(IUniverse manager, IUniverseObject universeObject)
{
universeObject.BehaviourController.OnBehaviourAdded -= OnBehaviourAdded;
universeObject.BehaviourController.OnBehaviourRemoved -= OnBehaviourRemoved;
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourRemoved(universeObject.BehaviourController, universeObject.BehaviourController[i]);
}
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is not T tBehaviour)
return;
behaviours.Add(tBehaviour);
OnBehaviourAdd(behaviour);
OnCollected?.Invoke(this, tBehaviour);
}
protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is not T tBehaviour)
return;
if (!behaviours.Remove(tBehaviour))
return;
OnBehaviourRemove(behaviour);
OnRemoved?.Invoke(this, tBehaviour);
}
protected virtual void OnAssign(IUniverse universe) { }
public bool Assign(IUniverse universe)
{
if (Universe is not null)
return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects)
OnUniverseObjectRegistered(universe, universeObject);
universe.OnUniverseObjectRegistered += OnUniverseObjectRegistered;
universe.OnUniverseObjectUnRegistered += OnUniverseObjectUnregistered;
Universe = universe;
OnAssign(universe);
OnUniverseAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (Universe is null)
return false;
foreach (IUniverseObject universeObject in Universe.UniverseObjects)
OnUniverseObjectUnregistered(Universe, universeObject);
Universe.OnUniverseObjectRegistered -= OnUniverseObjectRegistered;
Universe.OnUniverseObjectUnRegistered -= OnUniverseObjectUnregistered;
Universe = null!;
OnUnassigned?.Invoke(this);
return true;
}
public int Count => behaviours.Count;
public T this[System.Index index] => behaviours[index];
}

View File

@@ -1,28 +0,0 @@
using System;
namespace Syntriax.Engine.Core;
public class BehaviourCollectorSorted<T> : BehaviourCollector<T> where T : class
{
private Comparison<T>? _sortBy = null;
public Comparison<T>? SortBy
{
get => _sortBy;
set
{
_sortBy = value;
if (value is not null)
behaviours.Sort(value);
}
}
protected override void OnBehaviourAdd(IBehaviour behaviour)
{
if (SortBy is not null)
behaviours.Sort(SortBy);
}
public BehaviourCollectorSorted() { }
public BehaviourCollectorSorted(IUniverse universe, Comparison<T> sortBy) : base(universe) => SortBy = sortBy;
}

View File

@@ -1,18 +1,16 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
[System.Diagnostics.DebuggerDisplay("Behaviour Count: {behaviours.Count}")]
public class BehaviourController : BaseEntity, IBehaviourController
{
public event IBehaviourController.BehaviourAddedEventHandler? OnBehaviourAdded = null;
public event IBehaviourController.BehaviourRemovedEventHandler? OnBehaviourRemoved = null;
public event IHasUniverseObject.UniverseObjectAssignedEventHandler? OnUniverseObjectAssigned = null;
public Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments> OnBehaviourAdded { get; } = new();
public Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments> OnBehaviourRemoved { get; } = new();
public Event<IHasUniverseObject> OnUniverseObjectAssigned { get; } = new();
private readonly IList<IBehaviour> behaviours = new List<IBehaviour>(Constants.BEHAVIOURS_SIZE_INITIAL);
private readonly FastList<IBehaviour> behaviours = new(Constants.BEHAVIOURS_SIZE_INITIAL);
private IUniverseObject _universeObject = null!;
@@ -28,8 +26,9 @@ public class BehaviourController : BaseEntity, IBehaviourController
if (IsInitialized)
behaviour.Initialize();
behaviour.OnPriorityChanged += OnPriorityChange;
OnBehaviourAdded?.Invoke(this, behaviour);
behaviour.OnPriorityChanged.AddListener(OnPriorityChange);
OnBehaviourAdded?.Invoke(this, new(behaviour));
return behaviour;
}
@@ -50,17 +49,13 @@ public class BehaviourController : BaseEntity, IBehaviourController
public IReadOnlyList<T> GetBehaviours<T>()
{
List<T>? behaviours = null;
List<T> behaviours = [];
foreach (IBehaviour behaviourItem in this.behaviours)
{
if (behaviourItem is not T behaviour)
continue;
behaviours ??= [];
if (behaviourItem is T behaviour)
behaviours.Add(behaviour);
}
return behaviours ?? Enumerable.Empty<T>().ToList();
return behaviours;
}
public void GetBehaviours<T>(IList<T> results)
@@ -77,7 +72,7 @@ public class BehaviourController : BaseEntity, IBehaviourController
public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour
{
for (int i = behaviours.Count; i >= 0; i--)
for (int i = behaviours.Count - 1; i >= 0; i--)
{
if (behaviours[i] is not T behaviour)
continue;
@@ -94,10 +89,10 @@ public class BehaviourController : BaseEntity, IBehaviourController
if (!behaviours.Contains(behaviour))
throw new Exception($"{behaviour.GetType().Name} does not exist in {UniverseObject.Name}'s {nameof(IBehaviourController)}.");
behaviour.OnPriorityChanged -= OnPriorityChange;
behaviour.OnPriorityChanged.RemoveListener(OnPriorityChange);
behaviour.Finalize();
behaviours.Remove(behaviour);
OnBehaviourRemoved?.Invoke(this, behaviour);
OnBehaviourRemoved?.Invoke(this, new(behaviour));
}
protected virtual void OnAssign(IUniverseObject universeObject) { }
@@ -146,7 +141,7 @@ public class BehaviourController : BaseEntity, IBehaviourController
behaviours.Add(behaviour);
}
private void OnPriorityChange(IBehaviour sender, int previousPriority)
private void OnPriorityChange(IBehaviour sender, IBehaviour.PriorityChangedArguments args)
{
behaviours.Remove(sender);
InsertBehaviourByPriority(sender);

View File

@@ -0,0 +1,16 @@
using System;
namespace Engine.Core;
public class ActiveBehaviourCollector<T> : ActiveBehaviourCollectorBase<T> where T : class, IBehaviour
{
protected readonly FastList<T> activeBehaviours = new(32);
public override T this[Index index] => activeBehaviours[index];
public override int Count => activeBehaviours.Count;
public ActiveBehaviourCollector() { }
public ActiveBehaviourCollector(IUniverse universe) : base(universe) { }
protected override void AddBehaviour(T behaviour) => activeBehaviours.Add(behaviour);
protected override bool RemoveBehaviour(T tBehaviour) => activeBehaviours.Remove(tBehaviour);
}

View File

@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
namespace Engine.Core;
public abstract class ActiveBehaviourCollectorBase<T> : IBehaviourCollector<T> where T : class, IBehaviour
{
protected readonly Dictionary<IActive, T> monitoringActiveToBehaviour = new(32);
protected readonly FastList<T> monitoringBehaviours = new(32);
private readonly Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments>.EventHandler delegateOnBehaviourAdded = null!;
private readonly Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments>.EventHandler delegateOnBehaviourRemoved = null!;
private readonly Event<IActive, IActive.ActiveChangedArguments>.EventHandler delegateOnBehaviourStateChanged = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectRegisteredArguments>.EventHandler delegateOnUniverseObjectRegistered = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectUnRegisteredArguments>.EventHandler delegateOnUniverseObjectUnregistered = null!;
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourCollectedArguments> OnCollected { get; } = new();
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourRemovedArguments> OnRemoved { get; } = new();
public Event<IHasUniverse> OnUniverseAssigned { get; } = new();
public Event<IAssignable>? OnUnassigned { get; } = new();
public abstract int Count { get; }
public abstract T this[Index index] { get; }
public IUniverse Universe { get; private set; } = null!;
public bool Assign(IUniverse universe)
{
if (Universe is not null)
return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects)
OnUniverseObjectRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered);
universe.OnUniverseObjectUnRegistered.AddListener(delegateOnUniverseObjectUnregistered);
Universe = universe;
OnUniverseAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (Universe is null)
return false;
foreach (IUniverseObject universeObject in Universe.UniverseObjects)
OnUniverseObjectUnregistered(Universe, new(universeObject));
Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered);
Universe.OnUniverseObjectUnRegistered.RemoveListener(delegateOnUniverseObjectUnregistered);
Universe = null!;
OnUnassigned?.Invoke(this);
return true;
}
protected abstract void AddBehaviour(T behaviour);
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
private void OnBehaviourAdded(IBehaviourController controller, IBehaviourController.BehaviourAddedArguments args)
{
if (args.BehaviourAdded is not T tBehaviour)
return;
monitoringBehaviours.Add(tBehaviour);
monitoringActiveToBehaviour.Add(tBehaviour, tBehaviour);
tBehaviour.OnActiveChanged.AddListener(delegateOnBehaviourStateChanged);
OnBehaviourStateChanged(tBehaviour, new(!tBehaviour.IsActive));
}
protected abstract bool RemoveBehaviour(T behaviour);
protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviourController.BehaviourRemovedArguments args)
{
if (args.BehaviourRemoved is not T tBehaviour)
return;
if (!monitoringBehaviours.Remove(tBehaviour) || !monitoringActiveToBehaviour.Remove(tBehaviour))
return;
tBehaviour.OnActiveChanged.RemoveListener(delegateOnBehaviourStateChanged);
if (!RemoveBehaviour(tBehaviour))
return;
OnBehaviourRemove(tBehaviour);
OnRemoved?.Invoke(this, new(tBehaviour));
}
private void OnBehaviourStateChanged(IActive sender, IActive.ActiveChangedArguments args)
{
T behaviour = monitoringActiveToBehaviour[sender];
if (sender.IsActive)
{
AddBehaviour(behaviour);
OnBehaviourAdd(behaviour);
OnCollected?.Invoke(this, new(behaviour));
}
else if (RemoveBehaviour(behaviour))
{
OnBehaviourRemove(behaviour);
OnRemoved?.Invoke(this, new(behaviour));
}
}
private void OnUniverseObjectRegistered(IUniverse manager, IUniverse.UniverseObjectRegisteredArguments args)
{
IUniverseObject universeObject = args.UniverseObjectRegistered;
universeObject.BehaviourController.OnBehaviourAdded.AddListener(delegateOnBehaviourAdded);
universeObject.BehaviourController.OnBehaviourRemoved.AddListener(delegateOnBehaviourRemoved);
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourAdded(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
}
private void OnUniverseObjectUnregistered(IUniverse manager, IUniverse.UniverseObjectUnRegisteredArguments args)
{
IUniverseObject universeObject = args.UniverseObjectUnregistered;
universeObject.BehaviourController.OnBehaviourAdded.RemoveListener(delegateOnBehaviourAdded);
universeObject.BehaviourController.OnBehaviourRemoved.RemoveListener(delegateOnBehaviourRemoved);
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourRemoved(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
}
public ActiveBehaviourCollectorBase()
{
delegateOnBehaviourAdded = OnBehaviourAdded;
delegateOnBehaviourRemoved = OnBehaviourRemoved;
delegateOnBehaviourStateChanged = OnBehaviourStateChanged;
delegateOnUniverseObjectRegistered = OnUniverseObjectRegistered;
delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
}
public ActiveBehaviourCollectorBase(IUniverse universe)
{
delegateOnBehaviourAdded = OnBehaviourAdded;
delegateOnBehaviourRemoved = OnBehaviourRemoved;
delegateOnBehaviourStateChanged = OnBehaviourStateChanged;
delegateOnUniverseObjectRegistered = OnUniverseObjectRegistered;
delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
Assign(universe);
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
namespace Engine.Core;
public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCollector<TItem> where TItem : class, IBehaviour where TIndex : IComparable
{
private readonly Event<IBehaviour, IBehaviour.PriorityChangedArguments>.EventHandler delegateOnPriorityChanged = null!;
private readonly SortedDictionary<TIndex, FastList<TItem>> behaviours = null!;
private readonly Func<TItem, TIndex> getIndexFunc = null!;
private readonly IComparer<TIndex> sortBy = null!;
private int count = 0;
public override int Count => count;
public override TItem this[Index index]
{
get
{
int actualIndex = index.IsFromEnd
? count - index.Value
: index.Value;
if (actualIndex < 0 || actualIndex >= count)
throw new IndexOutOfRangeException();
int leftIndex = actualIndex;
foreach ((TIndex i, FastList<TItem> list) in behaviours)
{
if (leftIndex < list.Count)
return list[leftIndex];
leftIndex -= list.Count;
}
throw new IndexOutOfRangeException();
}
}
protected override bool RemoveBehaviour(TItem tBehaviour)
{
TIndex index = getIndexFunc(tBehaviour);
if (!behaviours.TryGetValue(index, out FastList<TItem>? list))
throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector");
if (!list.Remove(tBehaviour))
return false;
count--;
return true;
}
protected override void AddBehaviour(TItem behaviour)
{
TIndex key = getIndexFunc(behaviour);
if (!behaviours.TryGetValue(key, out FastList<TItem>? list))
behaviours[key] = list = [];
count++;
list.Add(behaviour);
}
protected override void OnBehaviourAdd(IBehaviour behaviour) => behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged);
protected override void OnBehaviourRemove(IBehaviour behaviour) => behaviour.OnPriorityChanged.RemoveListener(delegateOnPriorityChanged);
private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args)
{
TItem behaviour = (TItem)sender;
RemoveBehaviour(behaviour);
AddBehaviour(behaviour);
}
public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
}
public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
}
public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
{
this.getIndexFunc = getIndexFunc;
delegateOnPriorityChanged = OnPriorityChanged;
this.sortBy = sortBy;
behaviours = new(sortBy);
}
public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = sortBy;
behaviours = new(sortBy);
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace Engine.Core;
public class BehaviourCollector<T> : BehaviourCollectorBase<T> where T : class
{
protected readonly FastList<T> behaviours = new(32);
public override T this[Index index] => behaviours[index];
public override int Count => behaviours.Count;
protected override void AddBehaviour(T behaviour) => behaviours.Add(behaviour);
protected override bool RemoveBehaviour(T tBehaviour) => behaviours.Remove(tBehaviour);
public BehaviourCollector() { }
public BehaviourCollector(IUniverse universe) : base(universe) { }
}

View File

@@ -0,0 +1,124 @@
using System;
namespace Engine.Core;
public abstract class BehaviourCollectorBase<T> : IBehaviourCollector<T> where T : class
{
private readonly Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments>.EventHandler delegateOnBehaviourAdded = null!;
private readonly Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments>.EventHandler delegateOnBehaviourRemoved = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectRegisteredArguments>.EventHandler delegateOnUniverseObjectRegistered = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectUnRegisteredArguments>.EventHandler delegateOnUniverseObjectUnregistered = null!;
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourCollectedArguments> OnCollected { get; } = new();
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourRemovedArguments> OnRemoved { get; } = new();
public Event<IHasUniverse> OnUniverseAssigned { get; } = new();
public Event<IAssignable>? OnUnassigned { get; } = new();
public IUniverse Universe { get; private set; } = null!;
public abstract int Count { get; }
public abstract T this[Index index] { get; }
public bool Assign(IUniverse universe)
{
if (Universe is not null)
return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects)
OnUniverseObjectRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered);
universe.OnPreUniverseObjectUnRegistered.AddListener(delegateOnUniverseObjectUnregistered);
Universe = universe;
OnAssign(universe);
OnUniverseAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (Universe is null)
return false;
foreach (IUniverseObject universeObject in Universe.UniverseObjects)
OnUniverseObjectUnregistered(Universe, new(universeObject));
Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered);
Universe.OnPreUniverseObjectUnRegistered.RemoveListener(delegateOnUniverseObjectUnregistered);
Universe = null!;
OnUnassigned?.Invoke(this);
return true;
}
protected virtual void OnAssign(IUniverse universe) { }
protected abstract void AddBehaviour(T behaviour);
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
private void OnBehaviourAdded(IBehaviourController controller, IBehaviourController.BehaviourAddedArguments args)
{
if (args.BehaviourAdded is not T tBehaviour)
return;
AddBehaviour(tBehaviour);
OnBehaviourAdd(args.BehaviourAdded);
OnCollected?.Invoke(this, new(tBehaviour));
}
protected abstract bool RemoveBehaviour(T tBehaviour);
protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviourController.BehaviourRemovedArguments args)
{
if (args.BehaviourRemoved is not T tBehaviour)
return;
if (!RemoveBehaviour(tBehaviour))
return;
OnBehaviourRemove(args.BehaviourRemoved);
OnRemoved?.Invoke(this, new(tBehaviour));
}
private void OnUniverseObjectRegistered(IUniverse manager, IUniverse.UniverseObjectRegisteredArguments args)
{
IUniverseObject universeObject = args.UniverseObjectRegistered;
universeObject.BehaviourController.OnBehaviourAdded.AddListener(delegateOnBehaviourAdded);
universeObject.BehaviourController.OnBehaviourRemoved.AddListener(delegateOnBehaviourRemoved);
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourAdded(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
}
private void OnUniverseObjectUnregistered(IUniverse manager, IUniverse.UniverseObjectUnRegisteredArguments args)
{
IUniverseObject universeObject = args.UniverseObjectUnregistered;
universeObject.BehaviourController.OnBehaviourAdded.RemoveListener(delegateOnBehaviourAdded);
universeObject.BehaviourController.OnBehaviourRemoved.RemoveListener(delegateOnBehaviourRemoved);
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourRemoved(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
}
public BehaviourCollectorBase()
{
delegateOnBehaviourAdded = OnBehaviourAdded;
delegateOnBehaviourRemoved = OnBehaviourRemoved;
delegateOnUniverseObjectRegistered = OnUniverseObjectRegistered;
delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
}
public BehaviourCollectorBase(IUniverse universe)
{
delegateOnBehaviourAdded = OnBehaviourAdded;
delegateOnBehaviourRemoved = OnBehaviourRemoved;
delegateOnUniverseObjectRegistered = OnUniverseObjectRegistered;
delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
Assign(universe);
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
namespace Engine.Core;
public class BehaviourCollectorOrdered<TIndex, TItem> : BehaviourCollectorBase<TItem> where TItem : class where TIndex : IComparable
{
private readonly Event<IBehaviour, IBehaviour.PriorityChangedArguments>.EventHandler delegateOnPriorityChanged = null!;
private readonly SortedDictionary<TIndex, FastList<TItem>> behaviours = null!;
private readonly Func<TItem, TIndex> getIndexFunc = null!;
private readonly IComparer<TIndex> sortBy = null!;
private int count = 0;
public override int Count => count;
public override TItem this[Index index]
{
get
{
int actualIndex = index.IsFromEnd
? count - index.Value
: index.Value;
if (actualIndex < 0 || actualIndex >= count)
throw new IndexOutOfRangeException();
int leftIndex = actualIndex;
foreach ((TIndex i, FastList<TItem> list) in behaviours)
{
if (leftIndex < list.Count)
return list[leftIndex];
leftIndex -= list.Count;
}
throw new IndexOutOfRangeException();
}
}
protected override bool RemoveBehaviour(TItem tBehaviour)
{
TIndex index = getIndexFunc(tBehaviour);
if (!behaviours.TryGetValue(index, out FastList<TItem>? list))
throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector");
if (!list.Remove(tBehaviour))
return false;
count--;
return true;
}
protected override void AddBehaviour(TItem behaviour)
{
TIndex key = getIndexFunc(behaviour);
if (!behaviours.TryGetValue(key, out FastList<TItem>? list))
behaviours[key] = list = [];
count++;
list.Add(behaviour);
}
protected override void OnBehaviourAdd(IBehaviour behaviour) => behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged);
protected override void OnBehaviourRemove(IBehaviour behaviour) => behaviour.OnPriorityChanged.RemoveListener(delegateOnPriorityChanged);
private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args)
{
TItem behaviour = (TItem)sender;
RemoveBehaviour(behaviour);
AddBehaviour(behaviour);
}
public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
}
public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
}
public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
{
this.getIndexFunc = getIndexFunc;
delegateOnPriorityChanged = OnPriorityChanged;
this.sortBy = sortBy;
behaviours = new(sortBy);
}
public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = sortBy;
behaviours = new(sortBy);
}
}

View File

@@ -1,9 +1,9 @@
using System.Collections;
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public class CoroutineManager : UniverseObject
public class CoroutineManager : Behaviour, IUpdate
{
private readonly List<IEnumerator> enumerators = [];
@@ -18,17 +18,7 @@ public class CoroutineManager : UniverseObject
enumerators.Remove(enumerator);
}
protected override void OnEnteringUniverse(IUniverse universe)
{
universe.OnUpdate += OnUpdate;
}
protected override void OnExitingUniverse(IUniverse universe)
{
universe.OnUpdate -= OnUpdate;
}
private void OnUpdate(IUniverse sender, UniverseTime time)
void IUpdate.Update()
{
for (int i = enumerators.Count - 1; i >= 0; i--)
{
@@ -39,4 +29,6 @@ public class CoroutineManager : UniverseObject
enumerators.RemoveAt(i);
}
}
public CoroutineManager() => Priority = int.MinValue;
}

View File

@@ -1,6 +1,6 @@
using System;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public class CoroutineYield(Func<bool> condition) : ICoroutineYield
{

View File

@@ -1,6 +1,6 @@
using System.Runtime.CompilerServices;
namespace Syntriax.Engine.Core.Debug;
namespace Engine.Core.Debug;
public static class Assert
{
@@ -10,21 +10,21 @@ public static class Assert
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertBehaviourControllerAssigned(IHasBehaviourController assignable)
=> System.Diagnostics.Debug.Assert(assignable.BehaviourController is not null, $"{assignable.GetType().Name} must be initialized");
=> System.Diagnostics.Debug.Assert(assignable.BehaviourController is not null, $"{assignable.GetType().Name} must be assigned an {nameof(IBehaviourController)}");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertEntityAssigned(IHasEntity assignable)
=> System.Diagnostics.Debug.Assert(assignable.Entity is not null, $"{assignable.GetType().Name} must be initialized");
=> System.Diagnostics.Debug.Assert(assignable.Entity is not null, $"{assignable.GetType().Name} must be assigned an {nameof(IEntity)}");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertUniverseAssigned(IHasUniverse assignable)
=> System.Diagnostics.Debug.Assert(assignable.Universe is not null, $"{assignable.GetType().Name} must be initialized");
=> System.Diagnostics.Debug.Assert(assignable.Universe is not null, $"{assignable.GetType().Name} must be assigned an {nameof(IUniverse)}");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertUniverseObjectAssigned(IHasUniverseObject assignable)
=> System.Diagnostics.Debug.Assert(assignable.UniverseObject is not null, $"{assignable.GetType().Name} must be initialized");
=> System.Diagnostics.Debug.Assert(assignable.UniverseObject is not null, $"{assignable.GetType().Name} must be assigned an {nameof(IUniverseObject)}");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertStateEnableAssigned(IHasStateEnable assignable)
=> System.Diagnostics.Debug.Assert(assignable.StateEnable is not null, $"{assignable.GetType().Name} must be initialized");
=> System.Diagnostics.Debug.Assert(assignable.StateEnable is not null, $"{assignable.GetType().Name} must be assigned an {nameof(IStateEnable)}");
}

View File

@@ -1,6 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Debug;
namespace Engine.Core.Debug;
public class ConsoleLogger : LoggerBase
{

View File

@@ -1,20 +1,32 @@
using System;
using System.IO;
namespace Syntriax.Engine.Core.Debug;
namespace Engine.Core.Debug;
public class FileLogger : LoggerBase
{
public readonly string FilePath;
public FileLogger(string filePath)
{
FilePath = filePath;
File.Open(filePath, FileMode.Create).Close();
}
protected override void Write(string message)
{
File.AppendAllTextAsync(FilePath, $"{message}{Environment.NewLine}");
}
public FileLogger(string filePath)
{
if (!filePath.EndsWith(".log"))
filePath += ".log";
FilePath = filePath;
bool isRelativePath = Path.GetFullPath(filePath).CompareTo(filePath) != 0;
if (isRelativePath)
FilePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePath));
if (Path.GetDirectoryName(FilePath) is string directoryPath)
Directory.CreateDirectory(directoryPath);
File.Open(FilePath, FileMode.Create).Close();
}
}

View File

@@ -1,13 +1,16 @@
namespace Syntriax.Engine.Core.Debug;
namespace Engine.Core.Debug;
public interface ILogger
{
static ILogger Shared { get; set; } = new ConsoleLogger();
Level FilterLevel { get; set; }
void Log(string message, Level level = Level.Info, bool force = false);
enum Level
{
Trace,
Info,
Warning,
Error,

View File

@@ -1,10 +1,10 @@
using System;
namespace Syntriax.Engine.Core.Debug;
namespace Engine.Core.Debug;
public abstract class LoggerBase : ILogger
{
public ILogger.Level FilterLevel { get; set; } = ILogger.Level.Info;
public ILogger.Level FilterLevel { get; set; } = ILogger.Level.Trace;
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false)
{

View File

@@ -0,0 +1,9 @@
namespace Engine.Core.Debug;
public class LoggerContainer : Behaviour, ILogger
{
public ILogger Logger { get; set; } = ILogger.Shared;
public ILogger.Level FilterLevel { get => Logger.FilterLevel; set => Logger.FilterLevel = value; }
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false) => Logger.Log(message, level, force);
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Diagnostics;
namespace Syntriax.Engine.Core.Debug;
namespace Engine.Core.Debug;
public static class LoggerExtensions
{
@@ -16,13 +16,21 @@ public static class LoggerExtensions
public static void LogError<T>(this ILogger logger, T caller, string message, bool force = false)
{
Log(logger, caller, message, ILogger.Level.Error, force);
Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{new StackTrace()}");
LogTrace(logger, caller, new StackTrace(), force);
}
public static void LogException<T>(this ILogger logger, T caller, Exception exception, bool force = false)
{
Log(logger, caller, $"Exception of type {exception.GetType().Name} occured", ILogger.Level.Error, force);
Log(logger, caller, $"Message: {exception.Message}", ILogger.Level.Error, force);
Log(logger, caller, $"InnerException: {exception.InnerException}", ILogger.Level.Error, force);
Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{exception.StackTrace}");
// Not using LogTrace because exception.StackTrace is a type of string
Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{exception.StackTrace}", ILogger.Level.Trace);
}
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);
}
}

View File

@@ -0,0 +1,23 @@
namespace Engine.Core.Debug;
public class LoggerWrapper(ILogger firstLogger, ILogger secondLogger) : ILogger
{
private readonly ILogger firstLogger = firstLogger;
private readonly ILogger secondLogger = secondLogger;
public ILogger.Level FilterLevel
{
get => firstLogger.FilterLevel;
set
{
firstLogger.FilterLevel = value;
secondLogger.FilterLevel = value;
}
}
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false)
{
firstLogger.Log(message, level, force);
secondLogger.Log(message, level, force);
}
}

View File

@@ -0,0 +1,6 @@
namespace Engine.Core.Debug;
public static class LoggerWrapperExtensions
{
public static ILogger WrapWith(this ILogger thisLogger, ILogger logger) => new LoggerWrapper(thisLogger, logger);
}

View File

@@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Linq;
namespace Engine.Core.Debug;
public class RotatingFileLogger : ILogger
{
public readonly FileLogger FileLogger = null!;
public readonly string Directory = string.Empty;
public readonly int RotateLength = 3;
public RotatingFileLogger(string directory, string namePrefix, string nameSuffix = "", int rotateLength = 3)
{
RotateLength = rotateLength;
string fileName = Path.Combine(directory, namePrefix);
if (!string.IsNullOrWhiteSpace(nameSuffix))
fileName += $"_{nameSuffix}";
bool isRelativePath = Path.GetFullPath(fileName).CompareTo(fileName) != 0;
if (isRelativePath)
fileName = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName));
if (File.Exists($"{fileName}.log"))
RenameExistingLogs(fileName, RotateLength);
FileLogger = new(fileName);
Directory = Path.GetDirectoryName(fileName) ?? throw new("Unexpected error on getting directory of logger path");
RotateLastLogs(Directory, namePrefix, RotateLength);
}
private static void RenameExistingLogs(string filePath, int rotateLength)
{
for (int i = rotateLength - 1; i >= 0; i--)
{
string source = i == 0
? $"{filePath}.log"
: $"{filePath}_{i}.log";
string dest = $"{filePath}_{i + 1}.log";
if (!File.Exists(source))
continue;
if (File.Exists(dest))
File.Delete(dest);
File.Move(source, dest);
}
}
private static void RotateLastLogs(string directory, string prefix, int rotateLength)
{
IOrderedEnumerable<string> logs = System.IO.Directory.GetFiles(directory, $"{prefix}*.log")
.OrderBy(File.GetCreationTime);
foreach (string file in logs.Skip(rotateLength))
try
{
ILogger.Shared.Log($"Removing log file located at \"{file}\" during rotation.");
File.Delete(file);
}
catch (Exception e) { ILogger.Shared.LogException($"Failed to rotate log file at \"{file}\"", e); }
}
public ILogger.Level FilterLevel { get => FileLogger.FilterLevel; set => FileLogger.FilterLevel = value; }
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false) => FileLogger.Log(message, level, force);
}

View File

@@ -4,7 +4,8 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>false</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Syntriax.Engine.Core</RootNamespace>
<RootNamespace>Engine.Core</RootNamespace>
<AssemblyName>Engine.Core</AssemblyName>
</PropertyGroup>
</Project>

View File

@@ -1,6 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Exceptions;
namespace Engine.Core.Exceptions;
public class AssignFailedException(string? message) : Exception(message)
{

View File

@@ -1,3 +1,3 @@
namespace Syntriax.Engine.Core.Exceptions;
namespace Engine.Core.Exceptions;
public class BehaviourNotFoundException(string? message) : NotFoundException(message);

View File

@@ -1,6 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Exceptions;
namespace Engine.Core.Exceptions;
public class NotAssignedException(string? message) : Exception(message)
{

View File

@@ -1,6 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Exceptions;
namespace Engine.Core.Exceptions;
public class NotFoundException(string? message) : Exception(message)
{

View File

@@ -1,3 +1,3 @@
namespace Syntriax.Engine.Core.Exceptions;
namespace Engine.Core.Exceptions;
public class UniverseObjectNotFoundException(string? message) : NotFoundException(message);

View File

@@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core.Exceptions;
using Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public static class BehaviourControllerExtensions
{
@@ -27,7 +27,7 @@ public static class BehaviourControllerExtensions
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
public static T GetRequiredBehaviour<T>(this IBehaviourController behaviourController) where T : class
=> behaviourController.GetBehaviour<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName}");
=> behaviourController.GetBehaviour<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject?.Name ?? "NULL"}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName}");
/// <summary>
/// Gets an existing <see cref="IBehaviour"/> of the specified type, or adds and returns a new one if it doesn't exist.
@@ -93,7 +93,7 @@ public static class BehaviourControllerExtensions
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
public static T GetRequiredBehaviourInParent<T>(this IBehaviourController behaviourController) where T : class
=> behaviourController.GetBehaviourInParent<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any parent");
=> behaviourController.GetBehaviourInParent<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject?.Name ?? "NULL"}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any parent");
/// <summary>
/// Gets all <see cref="IBehaviour"/>s of the specified type in it's <see cref="IUniverseObject"/>'s parents recursively and stores them in the provided list.
@@ -140,7 +140,7 @@ public static class BehaviourControllerExtensions
if (behaviourController.GetBehaviour<T>() is T localBehaviour)
return localBehaviour;
foreach (IUniverseObject child in behaviourController.UniverseObject)
foreach (IUniverseObject child in behaviourController.UniverseObject.Children)
if (GetBehaviourInChildren<T>(child.BehaviourController) is T behaviour)
return behaviour;
@@ -154,7 +154,7 @@ public static class BehaviourControllerExtensions
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
public static T GetRequiredBehaviourInChildren<T>(this IBehaviourController behaviourController) where T : class
=> behaviourController.GetBehaviourInChildren<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any children ");
=> behaviourController.GetBehaviourInChildren<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject?.Name ?? "NULL"}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any children ");
/// <summary>
/// Gets all <see cref="IBehaviour"/>s of the specified type in it's <see cref="IUniverseObject"/>'s children recursively and stores them in the provided list.
@@ -176,7 +176,7 @@ public static class BehaviourControllerExtensions
foreach (T behaviour in cache)
behaviours.Add(behaviour);
foreach (IUniverseObject child in universeObject)
foreach (IUniverseObject child in universeObject.Children)
TraverseChildrenForBehaviour(child, behaviours, cache);
}
}

View File

@@ -1,6 +1,6 @@
using System;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public static class EnumExtensions
{

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public static class FloatExtensions
{

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public static class TransformExtensions
{

View File

@@ -1,6 +1,6 @@
using Syntriax.Engine.Core.Exceptions;
using Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public static class UniverseExtensions
{

View File

@@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core.Exceptions;
using Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public static class UniverseObjectExtensions
{
@@ -12,7 +12,7 @@ public static class UniverseObjectExtensions
if (!string.IsNullOrWhiteSpace(name))
universeObject.Name = name;
if (parent is not null)
universeObject.SetParent(parent);
universeObject.Parent = parent;
return universeObject;
}
@@ -81,7 +81,7 @@ public static class UniverseObjectExtensions
/// <returns>The <see cref="IUniverseObject"/> of the specified type if found; otherwise, null.</returns>
public static T? GetUniverseObjectInParent<T>(this IUniverseObject universeObject) where T : class
{
if (universeObject.GetUniverseObject<T>() is T localUniverseObject)
if (universeObject.Children.GetUniverseObject<T>() is T localUniverseObject)
return localUniverseObject;
IUniverseObject? parent = universeObject;
@@ -129,10 +129,10 @@ public static class UniverseObjectExtensions
/// <returns>The <see cref="IUniverseObject"/> of the specified type if found; otherwise, null.</returns>
public static T? GetUniverseObjectInChildren<T>(this IUniverseObject universeObject) where T : class
{
if (universeObject.GetUniverseObject<T>() is T localUniverseObject)
if (universeObject.Children.GetUniverseObject<T>() is T localUniverseObject)
return localUniverseObject;
foreach (IUniverseObject child in universeObject)
foreach (IUniverseObject child in universeObject.Children)
if (GetUniverseObjectInChildren<T>(child) is T behaviour)
return behaviour;
@@ -246,7 +246,7 @@ public static class UniverseObjectExtensions
foreach (IUniverseObject universeObject in universeObjects)
{
universeObject.Find(cache);
universeObject.Children.Find(cache);
foreach (T behaviour in cache)
instances.Add(behaviour);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core.Factory.Abstract;
namespace Engine.Core.Factory.Abstract;
public interface IFactory<TInterface> where TInterface : class
{

View File

@@ -1,13 +1,13 @@
using Syntriax.Engine.Core.Exceptions;
using Engine.Core.Exceptions;
namespace Syntriax.Engine.Core.Factory;
namespace Engine.Core.Factory;
public class BehaviourControllerFactory
{
public static IBehaviourController Instantiate(IUniverseObject universeObject)
=> Instantiate<BehaviourController>(universeObject);
public static IBehaviourController Instantiate(IUniverseObject universeObject, IStateEnable? stateEnable = null)
=> Instantiate<BehaviourController>(universeObject, stateEnable);
public static T Instantiate<T>(IUniverseObject universeObject, params object?[]? args)
public static T Instantiate<T>(IUniverseObject universeObject, IStateEnable? stateEnable = null, params object?[]? args)
where T : class, IBehaviourController
{
T behaviourController = TypeFactory.Get<T>(args);
@@ -18,6 +18,17 @@ public class BehaviourControllerFactory
if (!behaviourController.Assign(universeObject))
throw AssignFailedException.From(behaviourController, universeObject);
if (stateEnable is not null)
{
if (!stateEnable.Assign(behaviourController))
throw AssignFailedException.From(stateEnable, behaviourController);
if (!behaviourController.Assign(stateEnable))
throw AssignFailedException.From(behaviourController, stateEnable);
}
else
StateEnableFactory.Instantiate(behaviourController);
return behaviourController;
}
}

View File

@@ -1,6 +1,6 @@
using Syntriax.Engine.Core.Exceptions;
using Engine.Core.Exceptions;
namespace Syntriax.Engine.Core.Factory;
namespace Engine.Core.Factory;
public class BehaviourFactory
{
@@ -12,12 +12,15 @@ public class BehaviourFactory
{
T behaviour = TypeFactory.Get<T>(args);
stateEnable ??= TypeFactory.Get<StateEnable>();
if (stateEnable is not null)
{
if (!stateEnable.Assign(behaviour))
throw AssignFailedException.From(stateEnable, behaviour);
if (!behaviour.Assign(stateEnable))
throw AssignFailedException.From(behaviour, stateEnable);
}
else
StateEnableFactory.Instantiate(behaviour);
return behaviour;
}

View File

@@ -1,7 +1,7 @@
using System;
using Syntriax.Engine.Core.Factory.Abstract;
using Engine.Core.Factory.Abstract;
namespace Syntriax.Engine.Core.Factory;
namespace Engine.Core.Factory;
public abstract class FactoryBase<TInterface> : IFactory<TInterface>
where TInterface : class

View File

@@ -1,6 +1,6 @@
using Syntriax.Engine.Core.Exceptions;
using Engine.Core.Exceptions;
namespace Syntriax.Engine.Core.Factory;
namespace Engine.Core.Factory;
public class StateEnableFactory
{

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core.Factory;
namespace Engine.Core.Factory;
public class TransformFactory
{

View File

@@ -3,7 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Syntriax.Engine.Core.Factory;
namespace Engine.Core.Factory;
public static class TypeFactory
{

View File

@@ -1,6 +1,6 @@
using Syntriax.Engine.Core.Exceptions;
using Engine.Core.Exceptions;
namespace Syntriax.Engine.Core.Factory;
namespace Engine.Core.Factory;
public class UniverseObjectFactory
{
@@ -18,18 +18,25 @@ public class UniverseObjectFactory
{
T universeObject = TypeFactory.Get<T>(args);
behaviourController ??= TypeFactory.Get<BehaviourController>();
stateEnable ??= TypeFactory.Get<StateEnable>();
if (behaviourController is not null)
{
if (!behaviourController.Assign(universeObject))
throw AssignFailedException.From(behaviourController, universeObject);
if (!stateEnable.Assign(universeObject))
throw AssignFailedException.From(stateEnable, universeObject);
if (!universeObject.Assign(behaviourController))
throw AssignFailedException.From(universeObject, behaviourController);
}
else
BehaviourControllerFactory.Instantiate(universeObject);
if (stateEnable is not null)
{
if (!stateEnable.Assign(universeObject))
throw AssignFailedException.From(stateEnable, universeObject);
if (!universeObject.Assign(stateEnable))
throw AssignFailedException.From(universeObject, stateEnable);
}
else
StateEnableFactory.Instantiate(universeObject);
return universeObject;
}

View File

@@ -1,18 +0,0 @@
using System;
namespace Syntriax.Engine.Core;
public static class DelegateExtensions
{
[Obsolete($"{nameof(InvokeSafe)} causes memory allocation, please use Invoke() instead.")]
public static void InvokeSafe(this Delegate @delegate, params object?[] args)
{
foreach (Delegate invocation in Delegate.EnumerateInvocationList(@delegate))
try { invocation.DynamicInvoke(args); }
catch (Exception exception)
{
string methodCallRepresentation = $"{invocation.Method.DeclaringType?.FullName}.{invocation.Method.Name}({string.Join(", ", args)})";
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
}
}
}

View File

@@ -0,0 +1,506 @@
using System;
using System.Collections.Generic;
using Engine.Core.Debug;
namespace Engine.Core;
// TODO!: every reverse loop has a chance to have more than 1 unsubscription,
// for (int i = listeners.Count - 1; i >= 0; i--)
// can be replaced with
// for (int i = listeners.Count - 1; i >= 0; i = Math.Min(i - 1, listeners.Count - 1))
// but this would causes possible double calls on already called callbacks, find a better method.
/// <summary>
/// Represents a simple event with no parameters.
/// <para>Example usage:</para>
/// <code>
/// public class MyBehaviour : Behaviour, IUpdate
/// {
/// public readonly Event MyEvent = new();
///
/// public MyBehaviour()
/// {
/// MyEvent.AddListener(OnEventTriggered);
/// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
/// }
///
/// public void Update()
/// {
/// MyEvent.Invoke();
/// }
///
/// private void OnEventTriggered()
/// {
/// Console.WriteLine($"Event occurred!");
/// }
///
/// private static void OnEventTriggeredOneTime()
/// {
/// Console.WriteLine($"Event called once!");
/// }
/// }
/// </code>
/// The output of the example code above would be:
/// <code>
/// Event occurred!
/// Event called once!
/// Event occurred!
/// Event occurred!
/// Event occurred!
/// ...
/// </code>
/// </summary>
public class Event
{
// We use Ascending order because draw calls are running from last to first
private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
private ILogger _logger = ILogger.Shared;
public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!;
/// <summary>
/// Subscribes the callback to be invoked whenever the event is triggered.
/// </summary>
/// <param name="listener">The callback to be called when the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
listeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
/// </summary>
/// <param name="listener">The callback to be called the next time the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddOneTimeListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
onceListeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
public void RemoveListener(EventHandler listener)
{
for (int i = listeners.Count - 1; i >= 0; i--)
if (listeners[i].Callback == listener)
{
listeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
public void RemoveOneTimeListener(EventHandler listener)
{
for (int i = 0; i < onceListeners.Count; i++)
if (onceListeners[i].Callback == listener)
{
onceListeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
public void Clear() { listeners.Clear(); onceListeners.Clear(); }
/// <summary>
/// Triggers the event.
/// </summary>
public void Invoke()
{
for (int i = listeners.Count - 1; i >= 0; i--)
try { listeners[i].Callback.Invoke(); }
catch (Exception exception)
{
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}()";
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
}
for (int i = onceListeners.Count - 1; i >= 0; i--)
{
try { onceListeners[i].Callback.Invoke(); }
catch (Exception exception)
{
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}()";
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
}
onceListeners.RemoveAt(i);
}
}
public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2)
{
listeners = new(initialListenerCount);
onceListeners = new(initialOnceListenerCount);
}
public Event()
{
listeners = new(4);
onceListeners = new(2);
}
public delegate void EventHandler();
private record struct ListenerData(EventHandler Callback, int Priority);
}
/// <summary>
/// Represents an event with only sender parameters.
/// <para>Example usage:</para>
/// <code>
/// public class MyBehaviour : Behaviour, IUpdate
/// {
/// public readonly Event&lt;MyBehaviour&gt; MyEvent = new();
///
/// public MyBehaviour()
/// {
/// MyEvent.AddListener(OnEventTriggered);
/// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
/// }
///
/// public void Update()
/// {
/// MyEvent.Invoke(this);
/// }
///
/// private void OnEventTriggered(MyBehaviour sender)
/// {
/// Console.WriteLine($"{sender.Id}'s event occurred!");
/// }
///
/// private static void OnEventTriggeredOneTime(MyBehaviour sender)
/// {
/// Console.WriteLine($"{sender.Id}'s event called once!");
/// }
/// }
/// </code>
/// The output of the example code above would be:
/// <code>
/// [Id]'s event occurred!
/// [Id]'s event called once!
/// [Id]'s event occurred!
/// [Id]'s event occurred!
/// [Id]'s event occurred!
/// ...
/// </code>
///
/// </summary>
/// <typeparam name="TSender">Sender type</typeparam>
public class Event<TSender> where TSender : class
{
// We use Ascending order because draw calls are running from last to first
private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
private ILogger _logger = ILogger.Shared;
public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!;
/// <summary>
/// Subscribes the callback to be invoked whenever the event is triggered.
/// </summary>
/// <param name="listener">The callback to be called when the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
listeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
/// </summary>
/// <param name="listener">The callback to be called the next time the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddOneTimeListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
onceListeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
public void RemoveListener(EventHandler listener)
{
for (int i = listeners.Count - 1; i >= 0; i--)
if (listeners[i].Callback == listener)
{
listeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
public void RemoveOneTimeListener(EventHandler listener)
{
for (int i = 0; i < onceListeners.Count; i++)
if (onceListeners[i].Callback == listener)
{
onceListeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
public void Clear() { listeners.Clear(); onceListeners.Clear(); }
/// <summary>
/// Triggers the event.
/// </summary>
/// <param name="sender">The caller that's triggering this event.</param>
public void Invoke(TSender sender)
{
for (int i = listeners.Count - 1; i >= 0; i--)
try { listeners[i].Callback.Invoke(sender); }
catch (Exception exception)
{
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender})";
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
}
for (int i = onceListeners.Count - 1; i >= 0; i--)
{
try { onceListeners[i].Callback.Invoke(sender); }
catch (Exception exception)
{
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender})";
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
}
onceListeners.RemoveAt(i);
}
}
public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2)
{
listeners = new(initialListenerCount);
onceListeners = new(initialOnceListenerCount);
}
public Event()
{
listeners = new(4);
onceListeners = new(2);
}
public delegate void EventHandler(TSender sender);
private record struct ListenerData(EventHandler Callback, int Priority);
}
/// <summary>
/// Represents an event with sender and argument parameters.
/// <para>Example usage:</para>
/// <code>
/// public class MyBehaviour : Behaviour, IUpdate
/// {
/// public readonly Event&lt;MyBehaviour, MyArguments&gt; MyEvent = new();
///
/// private int myInt = 0;
/// private bool myBool = false;
///
/// public MyBehaviour()
/// {
/// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
/// MyEvent.AddListener(OnEventTriggered);
/// }
///
/// public void Update()
/// {
/// MyEvent.Invoke(this, new MyArguments(myInt, myBool));
/// myInt++;
/// myBool = !myBool;
/// }
///
/// private void OnEventTriggered(MyBehaviour sender, MyArguments args)
/// {
/// Console.WriteLine($"{sender.Id}'s event occurred with MyInt: {args.MyInt} and MyBool {args.MyBool}!");
/// }
///
/// private static void OnEventTriggeredOneTime(MyBehaviour sender, MyArguments args)
/// {
/// Console.WriteLine($"{sender.Id}'s event called once with MyInt: {args.MyInt} and MyBool {args.MyBool}!");
/// }
///
/// public readonly record struct MyArguments(int MyInt, bool MyBool);
/// }
/// </code>
/// The output of the example code above would be:
/// <code>
/// [Id]'s event occurred with MyInt: 0 and MyBool False!
/// [Id]'s event called once with MyInt: 0 and MyBool False!
/// [Id]'s event occurred with MyInt: 1 and MyBool True!
/// [Id]'s event occurred with MyInt: 2 and MyBool False!
/// [Id]'s event occurred with MyInt: 3 and MyBool True!
/// ...
/// </code>
///
/// </summary>
/// <typeparam name="TSender">Sender type</typeparam>
public class Event<TSender, TArguments> where TSender : class
{
// We use Ascending order because draw calls are running from last to first
private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
private ILogger _logger = ILogger.Shared;
public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
private readonly List<ListenerData> listeners = null!;
private readonly List<ListenerData> onceListeners = null!;
/// <summary>
/// Subscribes the callback to be invoked whenever the event is triggered.
/// </summary>
/// <param name="listener">The callback to be called when the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
listeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
/// </summary>
/// <param name="listener">The callback to be called the next time the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddOneTimeListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
onceListeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
public void RemoveListener(EventHandler listener)
{
for (int i = listeners.Count - 1; i >= 0; i--)
if (listeners[i].Callback == listener)
{
listeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
public void RemoveOneTimeListener(EventHandler listener)
{
for (int i = 0; i < onceListeners.Count; i++)
if (onceListeners[i].Callback == listener)
{
onceListeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
public void Clear() { listeners.Clear(); onceListeners.Clear(); }
/// <summary>
/// Triggers the event.
/// </summary>
/// <param name="sender">The caller that's triggering this event.</param>
/// <param name="args">The arguments provided for this event.</param>
public void Invoke(TSender sender, TArguments args)
{
for (int i = listeners.Count - 1; i >= 0; i--)
try { listeners[i].Callback.Invoke(sender, args); }
catch (Exception exception)
{
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender}, {args})";
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
}
for (int i = onceListeners.Count - 1; i >= 0; i--)
{
try { onceListeners[i].Callback.Invoke(sender, args); }
catch (Exception exception)
{
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender}, {args})";
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
}
onceListeners.RemoveAt(i);
}
}
public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2)
{
listeners = new(initialListenerCount);
onceListeners = new(initialOnceListenerCount);
}
public Event()
{
listeners = new(4);
onceListeners = new(2);
}
public delegate void EventHandler(TSender sender, TArguments args);
private record struct ListenerData(EventHandler Callback, int Priority);
}
internal static class EventHelpers
{
public static void LogInvocationException(object sender, ILogger logger, Exception exception, string methodCallRepresentation)
{
logger.LogException(sender, exception);
logger.LogError(sender, $"Unexpected exception on invocation of method {methodCallRepresentation}");
}
}

View File

@@ -0,0 +1,78 @@
using System.Collections;
using System.Collections.Generic;
namespace Engine.Core;
public class FastList<T> : IList<T>, IReadOnlyList<T>, IEnumerable<T> where T : notnull
{
private readonly List<T> items = [];
private readonly Dictionary<T, int> indexMap = [];
public int Count => items.Count;
public bool IsReadOnly { get; set; } = false;
public T this[int index] { get => items[index]; set => items[index] = value; }
public void Add(T item)
{
indexMap[item] = items.Count;
items.Add(item);
}
public void RemoveAt(int i) => Remove(items[i], i);
public bool Remove(T item)
{
if (!indexMap.TryGetValue(item, out int index))
return false;
Remove(item, index);
return true;
}
private void Remove(T item, int index)
{
int lastIndex = items.Count - 1;
T lastItem = items[lastIndex];
items[index] = lastItem;
indexMap[lastItem] = index;
items.RemoveAt(lastIndex);
indexMap.Remove(item);
}
public void Insert(int index, T item)
{
items.Insert(index, item);
for (int i = index; i < items.Count; i++)
indexMap[items[i]] = i;
}
public void Clear()
{
items.Clear();
indexMap.Clear();
}
public bool Contains(T item) => indexMap.ContainsKey(item);
public int BinarySearch(T item, IComparer<T>? comparer = null) => items.BinarySearch(item, comparer);
public void Sort(IComparer<T> comparer)
{
items.Sort(comparer);
for (int i = 0; i < items.Count; i++)
indexMap[items[i]] = i;
}
public int IndexOf(T item) => items.IndexOf(item);
public void CopyTo(T[] array, int arrayIndex) => items.CopyTo(array, arrayIndex);
public IEnumerator<T> GetEnumerator() => items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public FastList() { }
public FastList(int count) { items.Capacity = count; }
}

View File

@@ -0,0 +1,10 @@
namespace Engine.Core;
public interface IPool<T>
{
Event<IPool<T>, T> OnRemoved { get; }
Event<IPool<T>, T> OnReturned { get; }
T Get();
void Return(T item);
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
namespace Engine.Core;
public class ListPool<T> : IPool<List<T>>
{
public Event<IPool<List<T>>, List<T>> OnReturned { get; } = new();
public Event<IPool<List<T>>, List<T>> OnRemoved { get; } = new();
private readonly Func<List<T>> generator = null!;
private readonly Queue<List<T>> queue = new();
public List<T> Get()
{
if (!queue.TryDequeue(out List<T>? result))
result = generator();
result.Clear();
OnRemoved?.Invoke(this, result);
return result;
}
public void Return(List<T> list)
{
if (queue.Contains(list))
return;
list.Clear();
queue.Enqueue(list);
OnReturned?.Invoke(this, list);
}
public ListPool(int initialListCount = 1, int initialListCapacity = 32)
{
generator = () => new(initialListCapacity);
for (int i = 0; i < initialListCount; i++)
queue.Enqueue(generator());
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
namespace Engine.Core;
public class Pool<T> : IPool<T>
{
public Event<IPool<T>, T> OnRemoved { get; } = new();
public Event<IPool<T>, T> OnReturned { get; } = new();
private readonly Func<T> generator = null!;
private readonly Queue<T> queue = new();
private readonly HashSet<T> queuedHashes = [];
public T Get()
{
if (!queue.TryDequeue(out T? result))
result = generator();
queuedHashes.Remove(result);
OnRemoved?.Invoke(this, result);
return result;
}
public void Return(T item)
{
if (queuedHashes.Contains(item))
return;
queue.Enqueue(item);
queuedHashes.Add(item);
OnReturned?.Invoke(this, item);
}
public Pool(Func<T> generator, int initialCapacity = 1)
{
this.generator = generator;
for (int i = 0; i < initialCapacity; i++)
queue.Enqueue(generator());
}
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public interface IProgressionTracker : IReadOnlyProgressionTracker
{

View File

@@ -1,13 +1,12 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public interface IReadOnlyProgressionTracker
{
event ProgressionUpdatedEventHandler? OnUpdated;
event ProgressionEndedEventHandler? OnEnded;
Event<IReadOnlyProgressionTracker, ProgressionUpdatedArguments> OnUpdated { get; }
Event<IReadOnlyProgressionTracker> OnEnded { get; }
float Progression { get; }
string Status { get; }
delegate void ProgressionUpdatedEventHandler(IReadOnlyProgressionTracker sender, float previousProgression, string previousStatus);
delegate void ProgressionEndedEventHandler(IReadOnlyProgressionTracker sender);
readonly record struct ProgressionUpdatedArguments(float PreviousProgression, string PreviousStatus);
}

View File

@@ -1,9 +1,9 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public class ProgressionTracker : IProgressionTracker
{
public event IReadOnlyProgressionTracker.ProgressionUpdatedEventHandler? OnUpdated = null;
public event IReadOnlyProgressionTracker.ProgressionEndedEventHandler? OnEnded = null;
public Event<IReadOnlyProgressionTracker, IReadOnlyProgressionTracker.ProgressionUpdatedArguments> OnUpdated { get; } = new();
public Event<IReadOnlyProgressionTracker> OnEnded { get; } = new();
public float Progression { get; private set; } = 0f;
public string Status { get; private set; } = "Default";
@@ -19,7 +19,7 @@ public class ProgressionTracker : IProgressionTracker
Progression = progression.Clamp(Progression, 1f);
Status = status;
OnUpdated?.Invoke(this, previousProgression, previousStatus);
OnUpdated?.Invoke(this, new(previousProgression, previousStatus));
if (progression >= 1f)
OnEnded?.Invoke(this);
@@ -30,7 +30,7 @@ public class ProgressionTracker : IProgressionTracker
Progression = 0f;
Status = "Default";
OnUpdated = null;
OnEnded = null;
OnUpdated.Clear();
OnEnded.Clear();
}
}

View File

@@ -1,6 +1,6 @@
using System.Threading.Tasks;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public record struct ProgressiveTask<T>(IReadOnlyProgressionTracker ProgressionTracker, Task<T> Task)
{

View File

@@ -1,34 +1,34 @@
using System;
using System.Numerics;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public static class Math
{
/// <summary>
/// The value of Pi (π), a mathematical constant approximately equal to 3.14159.
/// The value of Pi (π).
/// </summary>
public const float PI = 3.1415926535897932f;
public const float Pi = 3.1415926535897932f;
/// <summary>
/// The value of Tau (τ), a mathematical constant equal to 2π, approximately equal to 6.28319.
/// The value of Tau (τ), mathematical constant equal to 2π.
/// </summary>
public const float Tau = 2f * PI;
public const float Tau = 2f * Pi;
/// <summary>
/// The base of the natural logarithm, approximately equal to 2.71828.
/// The base of the natural logarithm.
/// </summary>
public const float E = 2.718281828459045f;
/// <summary>
/// The conversion factor from radians to degrees.
/// </summary>
public const float RadianToDegree = 180f / PI;
public const float RadianToDegree = 180f / Pi;
/// <summary>
/// The conversion factor from degrees to radians.
/// </summary>
public const float DegreeToRadian = PI / 180f;
public const float DegreeToRadian = Pi / 180f;
/// <summary>
/// Gets one minus of given <see cref="T"/>.

View File

@@ -1,7 +1,7 @@
using System;
using System.Numerics;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
public static class MathExtensions
{

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core
namespace Engine.Core
{
// This is pretty much so the assembly gets loaded automatically because
// the builds include the assembly but sometimes doesn't link load it at startup.

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents an Axis-Aligned Bounding Box (AABB) in 2D space.
@@ -38,6 +38,9 @@ public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
/// </summary>
public readonly Vector2D SizeHalf => Size * .5f;
public static bool operator ==(AABB left, AABB right) => left.UpperBoundary == right.UpperBoundary && left.LowerBoundary == right.LowerBoundary;
public static bool operator !=(AABB left, AABB right) => left.UpperBoundary != right.UpperBoundary || left.LowerBoundary != right.LowerBoundary;
/// <summary>
/// Creates an <see cref="AABB"/> from a collection of <see cref="Vector2D"/>s.
/// </summary>
@@ -63,12 +66,6 @@ public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
return new(lowerBoundary, upperBoundary);
}
/// <summary>
/// Converts the <see cref="AABB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="AABB"/>.</returns>
public override string ToString() => $"{nameof(AABB)}({LowerBoundary}, {UpperBoundary})";
/// <summary>
/// Checks if two <see cref="AABB"/>s are approximately equal.
/// </summary>
@@ -78,6 +75,25 @@ public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
/// <returns><see cref="true"/> if the <see cref="AABB"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(AABB left, AABB right, float epsilon = float.Epsilon)
=> left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary, epsilon) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="AABB"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="AABB"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="AABB"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is AABB aabb && this == aabb;
/// <summary>
/// Generates a hash code for the <see cref="AABB"/>.
/// </summary>
/// <returns>A hash code for the <see cref="AABB"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(LowerBoundary, UpperBoundary);
/// <summary>
/// Converts the <see cref="AABB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="AABB"/>.</returns>
public override string ToString() => $"{nameof(AABB)}({LowerBoundary}, {UpperBoundary})";
}
/// <summary>

View File

@@ -1,6 +1,6 @@
using System.Diagnostics;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a 2D circle.
@@ -38,6 +38,9 @@ public readonly struct Circle(Vector2D center, float radius)
/// </summary>
public static readonly Circle UnitCircle = new(Vector2D.Zero, 1f);
public static bool operator ==(Circle left, Circle right) => left.Center == right.Center && left.Radius == right.Radius;
public static bool operator !=(Circle left, Circle right) => left.Center != right.Center || left.Radius != right.Radius;
/// <summary>
/// Sets the center of the <see cref="Circle"/>.
/// </summary>
@@ -77,6 +80,25 @@ public readonly struct Circle(Vector2D center, float radius)
/// <returns><see cref="true"/> if the <see cref="Circle"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Circle left, Circle right, float epsilon = float.Epsilon)
=> left.Center.ApproximatelyEquals(right.Center, epsilon) && left.Radius.ApproximatelyEquals(right.Radius, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Circle"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Circle"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Circle"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Circle circle && this == circle;
/// <summary>
/// Generates a hash code for the <see cref="Circle"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Circle"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Center, Radius);
/// <summary>
/// Converts the <see cref="Circle"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Circle"/>.</returns>
public override string ToString() => $"{nameof(Circle)}({Center}, {Radius})";
}
/// <summary>

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents an HSV color.
@@ -27,49 +27,19 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
/// </summary>
public readonly float Value = value.Clamp(0f, 1f);
public static ColorHSV operator -(ColorHSV color) => new(color.Hue.OneMinus().Clamp(0f, 1f), color.Saturation.OneMinus().Clamp(0f, 1f), color.Value.OneMinus().Clamp(0f, 1f));
public static ColorHSV operator +(ColorHSV left, ColorHSV right) => new((left.Hue + right.Hue).Clamp(0f, 1f), (left.Saturation + right.Saturation).Clamp(0f, 1f), (left.Value + right.Value).Clamp(0f, 1f));
public static ColorHSV operator -(ColorHSV left, ColorHSV right) => new((left.Hue - right.Hue).Clamp(0f, 1f), (left.Saturation - right.Saturation).Clamp(0f, 1f), (left.Value - right.Value).Clamp(0f, 1f));
public static ColorHSV operator *(ColorHSV left, ColorHSV right) => new((left.Hue * right.Hue).Clamp(0f, 1f), (left.Saturation * right.Saturation).Clamp(0f, 1f), (left.Value * right.Value).Clamp(0f, 1f));
public static ColorHSV operator *(ColorHSV color, float value) => new((color.Hue * value).Clamp(0f, 1f), (color.Saturation * value).Clamp(0f, 1f), (color.Value * value).Clamp(0f, 1f));
public static ColorHSV operator *(float value, ColorHSV color) => new((color.Hue * value).Clamp(0f, 1f), (color.Saturation * value).Clamp(0f, 1f), (color.Value * value).Clamp(0f, 1f));
public static ColorHSV operator /(ColorHSV color, float value) => new((color.Hue / value).Clamp(0f, 1f), (color.Saturation / value).Clamp(0f, 1f), (color.Value / value).Clamp(0f, 1f));
public static bool operator ==(ColorHSV left, ColorHSV right) => left.Hue.ApproximatelyEquals(right.Hue) && left.Saturation.ApproximatelyEquals(right.Saturation) && left.Value.ApproximatelyEquals(right.Value);
public static bool operator !=(ColorHSV left, ColorHSV right) => !left.Hue.ApproximatelyEquals(right.Hue) || !left.Saturation.ApproximatelyEquals(right.Saturation) || !left.Value.ApproximatelyEquals(right.Value);
public static ColorHSV operator -(ColorHSV color) => new(color.Hue.OneMinus(), color.Saturation.OneMinus(), color.Value.OneMinus());
public static ColorHSV operator +(ColorHSV left, ColorHSV right) => new(left.Hue + right.Hue, left.Saturation + right.Saturation, left.Value + right.Value);
public static ColorHSV operator -(ColorHSV left, ColorHSV right) => new(left.Hue - right.Hue, left.Saturation - right.Saturation, left.Value - right.Value);
public static ColorHSV operator *(ColorHSV left, ColorHSV right) => new(left.Hue * right.Hue, left.Saturation * right.Saturation, left.Value * right.Value);
public static ColorHSV operator *(ColorHSV color, float value) => new(color.Hue * value, color.Saturation * value, color.Value * value);
public static ColorHSV operator *(float value, ColorHSV color) => new(color.Hue * value, color.Saturation * value, color.Value * value);
public static ColorHSV operator /(ColorHSV color, float value) => new(color.Hue / value, color.Saturation / value, color.Value / value);
public static bool operator ==(ColorHSV left, ColorHSV right) => left.Hue == right.Hue && left.Saturation == right.Saturation && left.Value == right.Value;
public static bool operator !=(ColorHSV left, ColorHSV right) => left.Hue != right.Hue || left.Saturation != right.Saturation || left.Value != right.Value;
public static implicit operator ColorHSV(ColorRGBA rgba) => (ColorRGB)rgba;
public static implicit operator ColorHSV(ColorRGB rgb)
{
float hue;
float saturation;
float value;
float rd = rgb.R / 255f;
float gd = rgb.G / 255f;
float bd = rgb.B / 255f;
float max = Math.Max(rd, Math.Max(gd, bd));
float min = Math.Min(rd, Math.Min(gd, bd));
float delta = max - min;
if (delta.ApproximatelyEquals(0))
hue = 0f;
else if (max.ApproximatelyEquals(rd))
hue = 60f * ((gd - bd) / delta % 6f);
else if (max.ApproximatelyEquals(gd))
hue = 60f * (((bd - rd) / delta) + 2f);
else
hue = 60f * (((rd - gd) / delta) + 4f);
if (hue < 0f)
hue += 360f;
hue /= 360f;
saturation = max.ApproximatelyEquals(0f) ? 0f : delta / max;
value = max;
return new(hue, saturation, value);
}
public static implicit operator ColorHSV(ColorHSVA hsva) => new(hsva.Hue, hsva.Saturation, hsva.Value);
public static implicit operator ColorHSV(ColorRGBA rgba) => (ColorHSVA)rgba;
public static implicit operator ColorHSV(ColorRGB rgb) => (ColorHSVA)rgb;
/// <summary>
/// Inverts the given <see cref="ColorHSV"/>.
@@ -110,14 +80,6 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
/// <returns>The result of dividing the <see cref="ColorHSV"/> by the scalar value.</returns>
public static ColorHSV Divide(ColorHSV color, float value) => color / value;
/// <summary>
/// Calculates the <see cref="ColorHSV"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="ColorHSV"/> from the starting point to the ending point.</returns>
public static ColorHSV FromTo(ColorHSV from, ColorHSV to) => to - from;
/// <summary>
/// Performs linear interpolation between two <see cref="ColorHSV"/>s.
/// </summary>
@@ -125,13 +87,14 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
/// <param name="to">The ending <see cref="ColorHSV"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="ColorHSV"/>.</returns>
public static ColorHSV Lerp(ColorHSV from, ColorHSV to, float t) => from + FromTo(from, to) * t;
public static ColorHSV Lerp(ColorHSV from, ColorHSV to, float t)
{
float hueDiff = to.Hue - from.Hue;
float saturationDiff = to.Saturation - from.Saturation;
float valueDiff = to.Value - from.Value;
/// <summary>
/// Converts the <see cref="ColorHSV"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorHSV"/>.</returns>
public override string ToString() => $"{nameof(ColorHSV)}({Hue}, {Saturation}, {Value})";
return from + new ColorHSV(hueDiff * t, saturationDiff * t, valueDiff * t);
}
/// <summary>
/// Checks if two <see cref="ColorHSV"/>s are approximately equal within a specified epsilon range.
@@ -148,13 +111,19 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="ColorHSV"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorHSV"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is ColorHSV objVec && Hue.Equals(objVec.Hue) && Saturation.Equals(objVec.Saturation) && Value.Equals(objVec.Value);
public override bool Equals(object? obj) => obj is ColorHSV colorHSV && this == colorHSV;
/// <summary>
/// Generates a hash code for the <see cref="ColorHSV"/>.
/// </summary>
/// <returns>A hash code for the <see cref="ColorHSV"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Hue, Saturation, Value);
/// <summary>
/// Converts the <see cref="ColorHSV"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorHSV"/>.</returns>
public override string ToString() => $"{nameof(ColorHSV)}({Hue}, {Saturation}, {Value})";
}
/// <summary>
@@ -174,9 +143,6 @@ public static class ColorHSVExtensions
/// <inheritdoc cref="ColorHSV.Divide(ColorHSV, ColorHSV)" />
public static ColorHSV Divide(this ColorHSV color, float value) => ColorHSV.Divide(color, value);
/// <inheritdoc cref="ColorHSV.FromTo(ColorHSV, ColorHSV)" />
public static ColorHSV FromTo(this ColorHSV from, ColorHSV to) => ColorHSV.FromTo(from, to);
/// <inheritdoc cref="ColorHSV.Lerp(ColorHSV, ColorHSV, float)" />
public static ColorHSV Lerp(this ColorHSV from, ColorHSV to, float t) => ColorHSV.Lerp(from, to, t);

View File

@@ -0,0 +1,189 @@
namespace Engine.Core;
/// <summary>
/// Represents an HSV color.
/// </summary>
/// <param name="hue">Hue of the <see cref="ColorHSVA"/>.</param>
/// <param name="saturation">Saturation of the <see cref="ColorHSVA"/>.</param>
/// <param name="value">Value of the <see cref="ColorHSVA"/>.</param>
/// <param name="alpha">Alpha of the <see cref="ColorHSVA"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="ColorHSVA"/> struct with the specified values.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
public readonly struct ColorHSVA(float hue, float saturation, float value, float alpha = 1)
{
/// <summary>
/// The Hue value of the <see cref="ColorHSVA"/>.
/// </summary>
public readonly float Hue = hue.Clamp(0f, 1f);
/// <summary>
/// The Saturation value of the <see cref="ColorHSVA"/>.
/// </summary>
public readonly float Saturation = saturation.Clamp(0f, 1f);
/// <summary>
/// The Value value of the <see cref="ColorHSVA"/>.
/// </summary>
public readonly float Value = value.Clamp(0f, 1f);
/// <summary>
/// The Alpha value of the <see cref="ColorHSVA"/>.
/// </summary>
public readonly float Alpha = alpha;
public static ColorHSVA operator -(ColorHSVA color) => new(color.Hue.OneMinus(), color.Saturation.OneMinus(), color.Value.OneMinus(), color.Alpha);
public static ColorHSVA operator +(ColorHSVA left, ColorHSVA right) => new(left.Hue + right.Hue, left.Saturation + right.Saturation, left.Value + right.Value, left.Alpha + right.Alpha);
public static ColorHSVA operator -(ColorHSVA left, ColorHSVA right) => new(left.Hue - right.Hue, left.Saturation - right.Saturation, left.Value - right.Value, left.Alpha - right.Alpha);
public static ColorHSVA operator *(ColorHSVA left, ColorHSVA right) => new(left.Hue * right.Hue, left.Saturation * right.Saturation, left.Value * right.Value, left.Alpha * right.Alpha);
public static ColorHSVA operator *(ColorHSVA color, float value) => new(color.Hue * value, color.Saturation * value, color.Value * value, color.Alpha * value);
public static ColorHSVA operator *(float value, ColorHSVA color) => new(color.Hue * value, color.Saturation * value, color.Value * value, color.Alpha * value);
public static ColorHSVA operator /(ColorHSVA color, float value) => new(color.Hue / value, color.Saturation / value, color.Value / value, color.Alpha / value);
public static bool operator ==(ColorHSVA left, ColorHSVA right) => left.Hue == right.Hue && left.Saturation == right.Saturation && left.Value == right.Value;
public static bool operator !=(ColorHSVA left, ColorHSVA right) => left.Hue != right.Hue || left.Saturation != right.Saturation || left.Value != right.Value;
public static implicit operator ColorHSVA(ColorHSV hsv) => new(hsv.Hue, hsv.Saturation, hsv.Value, 1f);
public static implicit operator ColorHSVA(ColorRGB rgb) => (ColorRGBA)rgb;
public static implicit operator ColorHSVA(ColorRGBA rgba)
{
float hue;
float saturation;
float value;
float rd = rgba.R / 255f;
float gd = rgba.G / 255f;
float bd = rgba.B / 255f;
float max = Math.Max(rd, Math.Max(gd, bd));
float min = Math.Min(rd, Math.Min(gd, bd));
float delta = max - min;
if (delta.ApproximatelyEquals(0))
hue = 0f;
else if (max.ApproximatelyEquals(rd))
hue = 60f * ((gd - bd) / delta % 6f);
else if (max.ApproximatelyEquals(gd))
hue = 60f * (((bd - rd) / delta) + 2f);
else
hue = 60f * (((rd - gd) / delta) + 4f);
if (hue < 0f)
hue += 360f;
hue /= 360f;
saturation = max.ApproximatelyEquals(0f) ? 0f : delta / max;
value = max;
return new(hue, saturation, value, rgba.A / 255f);
}
/// <summary>
/// Inverts the given <see cref="ColorHSVA"/>.
/// </summary>
/// <param name="color">The <see cref="ColorHSVA"/>.</param>
/// <returns>The inverted <see cref="ColorHSVA"/>.</returns>
public static ColorHSVA Invert(ColorHSVA color) => -color;
/// <summary>
/// Adds two <see cref="ColorHSVA"/>s.
/// </summary>
/// <param name="left">The first <see cref="ColorHSVA"/>.</param>
/// <param name="right">The second <see cref="ColorHSVA"/>.</param>
/// <returns>The sum of the two <see cref="ColorHSVA"/>s.</returns>
public static ColorHSVA Add(ColorHSVA left, ColorHSVA right) => left + right;
/// <summary>
/// Subtracts one <see cref="ColorHSVA"/> from another.
/// </summary>
/// <param name="left">The <see cref="ColorHSVA"/> to subtract from.</param>
/// <param name="right">The <see cref="ColorHSVA"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="ColorHSVA"/> from the first.</returns>
public static ColorHSVA Subtract(ColorHSVA left, ColorHSVA right) => left - right;
/// <summary>
/// Multiplies a <see cref="ColorHSVA"/> by a scalar value.
/// </summary>
/// <param name="color">The <see cref="ColorHSVA"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="ColorHSVA"/> by the scalar value.</returns>
public static ColorHSVA Multiply(ColorHSVA color, float value) => color * value;
/// <summary>
/// Divides a <see cref="ColorHSVA"/> by a scalar value.
/// </summary>
/// <param name="color">The <see cref="ColorHSVA"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="ColorHSVA"/> by the scalar value.</returns>
public static ColorHSVA Divide(ColorHSVA color, float value) => color / value;
/// <summary>
/// Performs linear interpolation between two <see cref="ColorHSVA"/>s.
/// </summary>
/// <param name="from">The starting <see cref="ColorHSVA"/> (t = 0).</param>
/// <param name="to">The ending <see cref="ColorHSVA"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="ColorHSVA"/>.</returns>
public static ColorHSVA Lerp(ColorHSVA from, ColorHSVA to, float t)
{
float hueDiff = to.Hue - from.Hue;
float saturationDiff = to.Saturation - from.Saturation;
float valueDiff = to.Value - from.Value;
float alphaDiff = to.Alpha - from.Alpha;
return from + new ColorHSVA(hueDiff * t, saturationDiff * t, valueDiff * t, alphaDiff * t);
}
/// <summary>
/// Checks if two <see cref="ColorHSVA"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="ColorHSVA"/>.</param>
/// <param name="right">The second <see cref="ColorHSVA"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="ColorHSVA"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(ColorHSVA left, ColorHSVA right, float epsilon = float.Epsilon)
=> left.Hue.ApproximatelyEquals(right.Hue, epsilon) && left.Saturation.ApproximatelyEquals(right.Saturation, epsilon) && left.Value.ApproximatelyEquals(right.Value, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="ColorHSVA"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="ColorHSVA"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorHSVA"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is ColorHSVA colorHSVA && this == colorHSVA;
/// <summary>
/// Generates a hash code for the <see cref="ColorHSVA"/>.
/// </summary>
/// <returns>A hash code for the <see cref="ColorHSVA"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Hue, Saturation, Value);
/// <summary>
/// Converts the <see cref="ColorHSVA"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorHSVA"/>.</returns>
public override string ToString() => $"{nameof(ColorHSVA)}({Hue}, {Saturation}, {Value})";
}
/// <summary>
/// Provides extension methods for <see cref="ColorHSVA"/> type.
/// </summary>
public static class ColorHSVAExtensions
{
/// <inheritdoc cref="ColorHSVA.Add(ColorHSVA, ColorHSVA)" />
public static ColorHSVA Add(this ColorHSVA color, ColorHSVA value) => ColorHSVA.Add(color, value);
/// <inheritdoc cref="ColorHSVA.Subtract(ColorHSVA, ColorHSVA)" />
public static ColorHSVA Subtract(this ColorHSVA color, ColorHSVA value) => ColorHSVA.Subtract(color, value);
/// <inheritdoc cref="ColorHSVA.Multiply(ColorHSVA, ColorHSVA)" />
public static ColorHSVA Multiply(this ColorHSVA color, float value) => ColorHSVA.Multiply(color, value);
/// <inheritdoc cref="ColorHSVA.Divide(ColorHSVA, ColorHSVA)" />
public static ColorHSVA Divide(this ColorHSVA color, float value) => ColorHSVA.Divide(color, value);
/// <inheritdoc cref="ColorHSVA.Lerp(ColorHSVA, ColorHSVA, float)" />
public static ColorHSVA Lerp(this ColorHSVA from, ColorHSVA to, float t) => ColorHSVA.Lerp(from, to, t);
/// <inheritdoc cref="ColorHSVA.ApproximatelyEquals(ColorHSVA, ColorHSVA, float) " />
public static bool ApproximatelyEquals(this ColorHSVA left, ColorHSVA right, float epsilon = float.Epsilon) => ColorHSVA.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents an RGB color.
@@ -38,30 +38,8 @@ public readonly struct ColorRGB(byte r, byte g, byte b)
public static bool operator !=(ColorRGB left, ColorRGB right) => left.R != right.R || left.G != right.G || left.B != right.B;
public static implicit operator ColorRGB(ColorRGBA rgba) => new(rgba.R, rgba.G, rgba.B);
public static implicit operator ColorRGB(ColorHSV hsv)
{
float hue = hsv.Hue * 360f;
float chroma = hsv.Value * hsv.Saturation;
float x = chroma * (1f - Math.Abs(hue / 60f % 2f - 1f));
float m = hsv.Value - chroma;
float r1 = 0f;
float g1 = 0f;
float b1 = 0f;
if (hue < 60) { r1 = chroma; g1 = x; b1 = 0; }
else if (hue < 120) { r1 = x; g1 = chroma; b1 = 0; }
else if (hue < 180) { r1 = 0; g1 = chroma; b1 = x; }
else if (hue < 240) { r1 = 0; g1 = x; b1 = chroma; }
else if (hue < 300) { r1 = x; g1 = 0; b1 = chroma; }
else if (hue <= 360) { r1 = chroma; g1 = 0; b1 = x; }
byte r = (byte)Math.RoundToInt((r1 + m) * 255);
byte g = (byte)Math.RoundToInt((g1 + m) * 255);
byte b = (byte)Math.RoundToInt((b1 + m) * 255);
return new(r, g, b);
}
public static implicit operator ColorRGB(ColorHSVA hsva) => (ColorRGBA)hsva;
public static implicit operator ColorRGB(ColorHSV hsv) => (ColorRGBA)hsv;
/// <summary>
/// Inverts the given <see cref="ColorRGB"/>.
@@ -102,14 +80,6 @@ public readonly struct ColorRGB(byte r, byte g, byte b)
/// <returns>The result of dividing the <see cref="ColorRGB"/> by the scalar value.</returns>
public static ColorRGB Divide(ColorRGB color, float value) => color / value;
/// <summary>
/// Calculates the <see cref="ColorRGB"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="ColorRGB"/> from the starting point to the ending point.</returns>
public static ColorRGB FromTo(ColorRGB from, ColorRGB to) => to - from;
/// <summary>
/// Performs linear interpolation between two <see cref="ColorRGB"/>s.
/// </summary>
@@ -117,26 +87,33 @@ public readonly struct ColorRGB(byte r, byte g, byte b)
/// <param name="to">The ending <see cref="ColorRGB"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="ColorRGB"/>.</returns>
public static ColorRGB Lerp(ColorRGB from, ColorRGB to, float t) => from + FromTo(from, to) * t;
public static ColorRGB Lerp(ColorRGB from, ColorRGB to, float t)
{
int redDiff = to.R - from.R;
int greenDiff = to.G - from.G;
int blueDiff = to.B - from.B;
/// <summary>
/// Converts the <see cref="ColorRGB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorRGB"/>.</returns>
public override string ToString() => $"{nameof(ColorRGB)}({R}, {G}, {B})";
return from + new ColorRGB((byte)(redDiff * t), (byte)(greenDiff * t), (byte)(blueDiff * t));
}
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="ColorRGB"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="ColorRGB"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGB"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is ColorRGB objVec && R.Equals(objVec.R) && G.Equals(objVec.G) && B.Equals(objVec.B);
public override bool Equals(object? obj) => obj is ColorRGB colorRGB && this == colorRGB;
/// <summary>
/// Generates a hash code for the <see cref="ColorRGB"/>.
/// </summary>
/// <returns>A hash code for the <see cref="ColorRGB"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(R, G, B);
/// <summary>
/// Converts the <see cref="ColorRGB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorRGB"/>.</returns>
public override string ToString() => $"{nameof(ColorRGB)}({R}, {G}, {B})";
}
/// <summary>
@@ -156,9 +133,6 @@ public static class ColorRGBExtensions
/// <inheritdoc cref="ColorRGB.Divide(ColorRGB, ColorRGB)" />
public static ColorRGB Divide(this ColorRGB color, float value) => ColorRGB.Divide(color, value);
/// <inheritdoc cref="ColorRGB.FromTo(ColorRGB, ColorRGB)" />
public static ColorRGB FromTo(this ColorRGB from, ColorRGB to) => ColorRGB.FromTo(from, to);
/// <inheritdoc cref="ColorRGB.Lerp(ColorRGB, ColorRGB, float)" />
public static ColorRGB Lerp(this ColorRGB from, ColorRGB to, float t) => ColorRGB.Lerp(from, to, t);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents an RGBA color.
@@ -44,7 +44,31 @@ public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255)
public static bool operator !=(ColorRGBA left, ColorRGBA right) => left.R != right.R || left.G != right.G || left.B != right.B || left.A != right.A;
public static implicit operator ColorRGBA(ColorRGB rgb) => new(rgb.R, rgb.G, rgb.B, 255);
public static implicit operator ColorRGBA(ColorHSV hsv) => (ColorRGB)hsv;
public static implicit operator ColorRGBA(ColorHSV hsv) => (ColorHSVA)hsv;
public static implicit operator ColorRGBA(ColorHSVA hsva)
{
float hue = hsva.Hue * 360f;
float chroma = hsva.Value * hsva.Saturation;
float x = chroma * (1f - Math.Abs(hue / 60f % 2f - 1f));
float m = hsva.Value - chroma;
float r1 = 0f;
float g1 = 0f;
float b1 = 0f;
if (hue < 60) { r1 = chroma; g1 = x; b1 = 0; }
else if (hue < 120) { r1 = x; g1 = chroma; b1 = 0; }
else if (hue < 180) { r1 = 0; g1 = chroma; b1 = x; }
else if (hue < 240) { r1 = 0; g1 = x; b1 = chroma; }
else if (hue < 300) { r1 = x; g1 = 0; b1 = chroma; }
else if (hue <= 360) { r1 = chroma; g1 = 0; b1 = x; }
byte r = (byte)Math.RoundToInt((r1 + m) * 255);
byte g = (byte)Math.RoundToInt((g1 + m) * 255);
byte b = (byte)Math.RoundToInt((b1 + m) * 255);
return new(r, g, b, (byte)(hsva.Alpha * 255));
}
/// <summary>
/// Inverts the given <see cref="ColorRGBA"/>.
@@ -85,14 +109,6 @@ public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255)
/// <returns>The result of dividing the <see cref="ColorRGBA"/> by the scalar value.</returns>
public static ColorRGBA Divide(ColorRGBA color, float value) => color / value;
/// <summary>
/// Calculates the <see cref="ColorRGBA"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="ColorRGBA"/> from the starting point to the ending point.</returns>
public static ColorRGBA FromTo(ColorRGBA from, ColorRGBA to) => to - from;
/// <summary>
/// Performs linear interpolation between two <see cref="ColorRGBA"/>s.
/// </summary>
@@ -100,26 +116,34 @@ public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255)
/// <param name="to">The ending <see cref="ColorRGBA"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="ColorRGBA"/>.</returns>
public static ColorRGBA Lerp(ColorRGBA from, ColorRGBA to, float t) => from + FromTo(from, to) * t;
public static ColorRGBA Lerp(ColorRGBA from, ColorRGBA to, float t)
{
int redDiff = to.R - from.R;
int greenDiff = to.G - from.G;
int blueDiff = to.B - from.B;
int alphaDiff = to.A - from.A;
/// <summary>
/// Converts the <see cref="ColorRGBA"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorRGBA"/>.</returns>
public override string ToString() => $"{nameof(ColorRGBA)}({R}, {G}, {B}, {A})";
return from + new ColorRGBA((byte)(redDiff * t), (byte)(greenDiff * t), (byte)(blueDiff * t), (byte)(alphaDiff * t));
}
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="ColorRGBA"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="ColorRGBA"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGBA"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is ColorRGBA objVec && R.Equals(objVec.R) && G.Equals(objVec.G) && B.Equals(objVec.B) && A.Equals(objVec.A);
public override bool Equals(object? obj) => obj is ColorRGBA colorRGBA && this == colorRGBA;
/// <summary>
/// Generates a hash code for the <see cref="ColorRGBA"/>.
/// </summary>
/// <returns>A hash code for the <see cref="ColorRGBA"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(R, G, B, A);
/// <summary>
/// Converts the <see cref="ColorRGBA"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorRGBA"/>.</returns>
public override string ToString() => $"{nameof(ColorRGBA)}({R}, {G}, {B}, {A})";
}
/// <summary>
@@ -139,9 +163,6 @@ public static class ColorRGBAExtensions
/// <inheritdoc cref="ColorRGBA.Divide(ColorRGBA, ColorRGBA)" />
public static ColorRGBA Divide(this ColorRGBA color, float value) => ColorRGBA.Divide(color, value);
/// <inheritdoc cref="ColorRGBA.FromTo(ColorRGBA, ColorRGBA)" />
public static ColorRGBA FromTo(this ColorRGBA from, ColorRGBA to) => ColorRGBA.FromTo(from, to);
/// <inheritdoc cref="ColorRGBA.Lerp(ColorRGBA, ColorRGBA, float)" />
public static ColorRGBA Lerp(this ColorRGBA from, ColorRGBA to, float t) => ColorRGBA.Lerp(from, to, t);
}

View File

@@ -1,6 +1,6 @@
using System.Diagnostics.CodeAnalysis;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a 2D line segment defined by two endpoints.
@@ -43,6 +43,9 @@ public readonly struct Line2D(Vector2D from, Vector2D to)
/// </summary>
public readonly float LengthSquared => From.FromTo(To).LengthSquared();
public static bool operator ==(Line2D left, Line2D right) => left.From == right.From && left.To == right.To;
public static bool operator !=(Line2D left, Line2D right) => left.From != right.From || left.To != right.To;
/// <summary>
/// The equation of the <see cref="Line2D"/> defined by this <see cref="Line2D"/> segment.
/// </summary>
@@ -167,17 +170,14 @@ public readonly struct Line2D(Vector2D from, Vector2D to)
/// </summary>
public static Vector2D ClosestPointTo(Line2D line, Vector2D point)
{
Vector2D edgeVector = line.From.FromTo(line.To);
Vector2D pointVector = point - line.From;
Vector2D lineRelativeVector = line.From.FromTo(line.To);
float t = (pointVector.X * edgeVector.X + pointVector.Y * edgeVector.Y) / (edgeVector.X * edgeVector.X + edgeVector.Y * edgeVector.Y);
Vector2D lineDirection = lineRelativeVector.Normalized;
Vector2D pointVector = line.From.FromTo(point);
t = Math.Max(0, Math.Min(1, t));
float dot = lineDirection.Dot(pointVector).Clamp(0f, lineRelativeVector.Magnitude);
float closestX = line.From.X + t * edgeVector.X;
float closestY = line.From.Y + t * edgeVector.Y;
return new Vector2D((float)closestX, (float)closestY);
return lineDirection * dot;
}
/// <summary>
@@ -189,6 +189,25 @@ public readonly struct Line2D(Vector2D from, Vector2D to)
/// <returns><see cref="true"/> if the <see cref="Line2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Line2D left, Line2D right, float epsilon = float.Epsilon)
=> left.From.ApproximatelyEquals(right.From, epsilon) && left.To.ApproximatelyEquals(right.To, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Line2D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Line2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Line2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Line2D line2D && this == line2D;
/// <summary>
/// Generates a hash code for the <see cref="Line2D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Line2D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(From, To);
/// <summary>
/// Converts the <see cref="Line2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Line2D"/>.</returns>
public override string ToString() => $"{nameof(Line2D)}({From}, {To})";
}
/// <summary>
@@ -205,6 +224,9 @@ public static class Line2DExtensions
/// <inheritdoc cref="Line2D.Intersects(Line2D, Vector2D)" />
public static bool Intersects(this Line2D line, Vector2D point) => Line2D.Intersects(line, point);
/// <inheritdoc cref="Line2D.IntersectionPoint(Line2D, Line2D)" />
public static Vector2D IntersectionPoint(this Line2D left, Line2D right) => Line2D.IntersectionPoint(left, right);
/// <inheritdoc cref="Line2D.GetT(Line2D, Vector2D)" />
public static float GetT(this Line2D line, Vector2D point) => Line2D.GetT(line, point);
@@ -214,6 +236,9 @@ public static class Line2DExtensions
/// <inheritdoc cref="Line2D.Intersects(Line2D, Line2D, out Vector2D?)" />
public static bool Intersects(this Line2D left, Line2D right, [NotNullWhen(returnValue: true)] out Vector2D? point) => Line2D.Intersects(left, right, out point);
/// <inheritdoc cref="Line2D.ClosestPointTo(Line2D, Vector2D)" />
public static Vector2D ClosestPointTo(this Line2D line, Vector2D point) => Line2D.ClosestPointTo(line, point);
/// <inheritdoc cref="Line2D.ApproximatelyEquals(Line2D, Line2D, float)" />
public static bool ApproximatelyEquals(this Line2D left, Line2D right, float epsilon = float.Epsilon) => Line2D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -1,47 +1,69 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a line equation in the form y = mx + b.
/// Represents a <see cref="Line2DEquation"/> in the form y = mx + b.
/// </summary>
/// <param name="slope">The slope of the line.</param>
/// <param name="offsetY">The y-intercept of the line.</param>
/// <param name="offsetY">The Y intercept of the line.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Line2DEquation"/> struct with the specified slope and y-intercept.
/// Initializes a new instance of the <see cref="Line2DEquation"/> struct with the specified slope and Y intercept.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")]
public readonly struct Line2DEquation(float slope, float offsetY)
{
/// <summary>
/// The slope of the line equation.
/// The slope of the <see cref="Line2DEquation"/>.
/// </summary>
public readonly float Slope = slope;
/// <summary>
/// The y-intercept of the line equation.
/// The Y intercept of the <see cref="Line2DEquation"/>.
/// </summary>
public readonly float OffsetY = offsetY;
public static bool operator ==(Line2DEquation left, Line2DEquation right) => left.Slope == right.Slope && left.OffsetY == right.OffsetY;
public static bool operator !=(Line2DEquation left, Line2DEquation right) => left.Slope != right.Slope || left.OffsetY != right.OffsetY;
/// <summary>
/// Resolves the y-coordinate for a given x-coordinate using the line equation.
/// Resolves the Y coordinate for a given X coordinate using the <see cref="Line2DEquation"/>.
/// </summary>
/// <param name="lineEquation">The line equation to resolve.</param>
/// <param name="x">The x-coordinate for which to resolve the y-coordinate.</param>
/// <returns>The y-coordinate resolved using the line equation.</returns>
/// <param name="lineEquation">The <see cref="Line2DEquation"/> to resolve.</param>
/// <param name="x">The X coordinate for which to resolve the Y coordinate.</param>
/// <returns>The Y coordinate resolved using the <see cref="Line2DEquation"/>.</returns>
public static float Resolve(Line2DEquation lineEquation, float x) => lineEquation.Slope * x + lineEquation.OffsetY; // y = mx + b
/// <summary>
/// Checks if two line equations are approximately equal.
/// Checks if two <see cref="Line2DEquation"/> are approximately equal.
/// </summary>
/// <param name="left">The first line equation to compare.</param>
/// <param name="right">The second line equation to compare.</param>
/// <param name="left">The first <see cref="Line2DEquation"/> to compare.</param>
/// <param name="right">The second <see cref="Line2DEquation"/> to compare.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns>True if the line equations are approximately equal; otherwise, false.</returns>
/// <returns>True if the <see cref="Line2DEquation"/> are approximately equal; otherwise, false.</returns>
public static bool ApproximatelyEquals(Line2DEquation left, Line2DEquation right, float epsilon = float.Epsilon)
=> left.Slope.ApproximatelyEquals(right.Slope, epsilon) && left.OffsetY.ApproximatelyEquals(right.OffsetY, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Line2DEquation"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Line2DEquation"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Line2DEquation"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Line2DEquation lineEquation && this == lineEquation;
/// <summary>
/// Generates a hash code for the <see cref="Line2DEquation"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Line2DEquation"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Slope, OffsetY);
/// <summary>
/// Converts the <see cref="Line2DEquation"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Line2DEquation"/>.</returns>
public override string ToString() => $"{nameof(Line2DEquation)}({Slope}, {OffsetY})";
}
/// <summary>
/// Provides extension methods for the LineEquation struct.
/// Provides extension methods for the <see cref="Line2DEquation"/> struct.
/// </summary>
public static class Line2DEquationExtensions
{

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a range of values along a single axis.
@@ -21,6 +21,9 @@ public readonly struct Projection1D(float min, float max)
/// </summary>
public readonly float Max = max;
public static bool operator ==(Projection1D left, Projection1D right) => left.Min == right.Min && left.Max == right.Max;
public static bool operator !=(Projection1D left, Projection1D right) => left.Min != right.Min || left.Max != right.Max;
/// <summary>
/// Checks if two projections overlap.
/// </summary>
@@ -70,6 +73,35 @@ public readonly struct Projection1D(float min, float max)
depth = 0f;
return false;
}
/// <summary>
/// Checks if two <see cref="Projection1D"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Projection1D"/>.</param>
/// <param name="right">The second <see cref="Projection1D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Projection1D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Projection1D left, Projection1D right, float epsilon = float.Epsilon)
=> left.Min.ApproximatelyEquals(right.Min, epsilon) && left.Max.ApproximatelyEquals(right.Max, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Projection1D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Projection1D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Projection1D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Projection1D projection1D && this == projection1D;
/// <summary>
/// Generates a hash code for the <see cref="Projection1D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Projection1D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Min, Max);
/// <summary>
/// Converts the <see cref="Projection1D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Projection1D"/>.</returns>
public override string ToString() => $"{nameof(Projection1D)}({Min}, {Max})";
}
/// <summary>
@@ -82,4 +114,7 @@ public static class Projection1DExtensions
/// <inheritdoc cref="Projection1D.Overlaps(Projection1D, Projection1D, out float)" />
public static bool Overlaps(this Projection1D left, Projection1D right, out float depth) => Projection1D.Overlaps(left, right, out depth);
/// <inheritdoc cref="Projection1D.ApproximatelyEquals(Projection1D, Projection1D, float)" />
public static bool ApproximatelyEquals(this Projection1D left, Projection1D right, float epsilon = float.Epsilon) => Projection1D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a 3D space rotation.
@@ -282,24 +282,24 @@ public readonly struct Quaternion(float x, float y, float z, float w)
public static bool ApproximatelyEquals(Quaternion left, Quaternion right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon) && left.W.ApproximatelyEquals(right.W, epsilon);
/// <summary>
/// Converts the <see cref="Quaternion"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Quaternion"/>.</returns>
public override string ToString() => $"{nameof(Quaternion)}({W}, {X}, {Y}, {Z})";
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Quaternion"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Quaternion"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Quaternion"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Quaternion objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y) && Z.Equals(objVec.Z) && W.Equals(objVec.W);
public override bool Equals(object? obj) => obj is Quaternion quaternion && this == quaternion;
/// <summary>
/// Generates a hash code for the <see cref="Quaternion"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Quaternion"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z);
public override int GetHashCode() => System.HashCode.Combine(W, X, Y, Z);
/// <summary>
/// Converts the <see cref="Quaternion"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Quaternion"/>.</returns>
public override string ToString() => $"{nameof(Quaternion)}({W}, {X}, {Y}, {Z})";
}
/// <summary>

View File

@@ -0,0 +1,105 @@
namespace Engine.Core;
/// <summary>
/// Represents an infinite ray in 2D space.
/// </summary>
/// <param name="Origin">The <see cref="Vector2D"/> in 2D space where the ray starts from.</param>
/// <param name="Direction">Normalized <see cref="Vector2D"/> indicating the ray's is direction.</param>
public readonly struct Ray2D(Vector2D Origin, Vector2D Direction)
{
/// <summary>
/// The starting point of the <see cref="Ray2D"/>.
/// </summary>
public readonly Vector2D Origin = Origin;
/// <summary>
/// The direction in which the <see cref="Ray2D"/> points. Should be a normalized vector.
/// </summary>
public readonly Vector2D Direction = Direction;
/// <summary>
/// Gets a <see cref="Ray2D"/> with the same origin but with the direction reversed.
/// </summary>
public readonly Ray2D Reversed => new(Origin, -Direction);
public static bool operator ==(Ray2D left, Ray2D right) => left.Origin == right.Origin && left.Direction == right.Direction;
public static bool operator !=(Ray2D left, Ray2D right) => left.Origin != right.Origin || left.Direction != right.Direction;
public static implicit operator Ray2D(Line2D line) => new(line.From, line.From.FromTo(line.To).Normalized);
/// <summary>
/// Constructs a <see cref="Line2D"/> from a <see cref="Ray2D"/>, extending from its origin in the <see cref="Ray2D"/>'s direction for a given distance.
/// </summary>
/// <param name="ray">The source <see cref="Ray2D"/>.</param>
/// <param name="distance">The length of the line segment to create from the <see cref="Ray2D"/>.</param>
/// <returns>A <see cref="Line2D"/> representing the segment of the <see cref="Ray2D"/>.</returns>
public static Line2D GetLine(Ray2D ray, float distance)
=> new(ray.Origin, ray.Origin + ray.Direction * distance);
/// <summary>
/// Evaluates the point on the <see cref="Ray2D"/> at a specified distance from its origin.
/// </summary>
/// <param name="ray">The <see cref="Ray2D"/> to evaluate.</param>
/// <param name="distanceFromOrigin">The distance from the origin along the <see cref="Ray2D"/>'s direction.</param>
/// <returns>A <see cref="Vector2D"/> representing the point at the given distance on the <see cref="Ray2D"/>.</returns>
public static Vector2D Evaluate(Ray2D ray, float distanceFromOrigin)
=> ray.Origin + ray.Direction * distanceFromOrigin;
/// <summary>
/// Calculates the closest point on the <see cref="Ray2D"/> to the specified point.
/// </summary>
public static Vector2D ClosestPointTo(Ray2D ray, Vector2D point)
{
Vector2D originToPoint = ray.Origin.FromTo(point);
float dot = ray.Direction.Dot(originToPoint);
return ray.Origin + ray.Direction * dot;
}
/// <summary>
/// Checks if two <see cref="Ray2D"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Ray2D"/>.</param>
/// <param name="right">The second <see cref="Ray2D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Ray2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Ray2D left, Ray2D right, float epsilon = float.Epsilon)
=> left.Origin.ApproximatelyEquals(right.Origin, epsilon) && left.Direction.ApproximatelyEquals(right.Direction, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Ray2D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Ray2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Ray2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Ray2D ray2D && this == ray2D;
/// <summary>
/// Generates a hash code for the <see cref="Ray2D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Ray2D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Origin, Direction);
/// <summary>
/// Converts the <see cref="Ray2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Ray2D"/>.</returns>
public override string ToString() => $"{nameof(Ray2D)}({Origin}, {Direction})";
}
/// <summary>
/// Provides extension methods for the <see cref="Ray2D"/> struct.
/// </summary>
public static class Ray2DExtensions
{
/// <inheritdoc cref="Ray2D.GetLine(Ray2D, float) />
public static Line2D ToLine(this Ray2D ray, float distance) => Ray2D.GetLine(ray, distance);
/// <inheritdoc cref="Ray2D.Evaluate(Ray2D, float) />
public static Vector2D Evaluate(this Ray2D ray, float distanceFromOrigin) => Ray2D.Evaluate(ray, distanceFromOrigin);
/// <inheritdoc cref="Ray2D.ClosestPointTo(Ray2D, Vector2D) />
public static Vector2D ClosestPointTo(this Ray2D ray, Vector2D point) => Ray2D.ClosestPointTo(ray, point);
/// <inheritdoc cref="Ray2D.ApproximatelyEquals(Ray2D, Ray2D, float)" />
public static bool ApproximatelyEquals(this Ray2D left, Ray2D right, float epsilon = float.Epsilon) => Ray2D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -1,7 +1,7 @@
using System.Collections;
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a shape defined by a collection of vertices.
@@ -18,7 +18,7 @@ public class Shape2D(List<Vector2D> vertices) : IEnumerable<Vector2D>
public static Shape2D Pentagon => CreateNgon(5, Vector2D.Up);
public static Shape2D Hexagon => CreateNgon(6, Vector2D.Right);
public event ShapeUpdatedEventHandler? OnShapeUpdated = null;
public Event<Shape2D> OnShapeUpdated { get; } = new();
private List<Vector2D> _vertices = vertices;
@@ -122,7 +122,7 @@ public class Shape2D(List<Vector2D> vertices) : IEnumerable<Vector2D>
triangles.Clear();
for (int i = 2; i < shape.Vertices.Count; i++)
triangles.Add(new Triangle(shape[0], shape[i - 1], shape[i]));
triangles.Add(new Triangle(shape[0], shape[i], shape[i - 1]));
}
/// <summary>
@@ -251,13 +251,39 @@ public class Shape2D(List<Vector2D> vertices) : IEnumerable<Vector2D>
return true;
}
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Shape2D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Shape2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Shape2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Shape2D shape2D && _vertices.Equals(shape2D._vertices);
/// <summary>
/// Generates a hash code for the <see cref="Shape2D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Shape2D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Vertices);
/// <summary>
/// Converts the <see cref="Shape2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Shape2D"/>.</returns>
public override string ToString()
{
System.Text.StringBuilder stringBuilder = new(Vertices[0].ToString());
for (int i = 1; i < Vertices.Count; i++)
{
stringBuilder.Append(", ");
stringBuilder.Append(Vertices[i].ToString());
}
return $"{nameof(Shape2D)}({stringBuilder})";
}
/// <inheritdoc/>
public IEnumerator<Vector2D> GetEnumerator() => Vertices.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => Vertices.GetEnumerator();
public delegate void ShapeUpdatedEventHandler(Shape2D shape2D);
}
/// <summary>
@@ -272,7 +298,7 @@ public static class Shape2DExtensions
public static Triangle ToSuperTriangle(this Shape2D shape) => Shape2D.GetSuperTriangle(shape);
/// <inheritdoc cref="Shape2D.TriangulateConvex(Shape2D, IList{Triangle})" />
public static void ToTrianglesConvex(this Shape2D shape, IList<Triangle> lines) => Shape2D.TriangulateConvex(shape, lines);
public static void ToTrianglesConvex(this Shape2D shape, IList<Triangle> triangles) => Shape2D.TriangulateConvex(shape, triangles);
/// <inheritdoc cref="Shape2D.TriangulateConvex(Shape2D)" />
public static List<Triangle> ToTrianglesConvex(this Shape2D shape) => Shape2D.TriangulateConvex(shape);

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
[System.Diagnostics.DebuggerDisplay("A: {A.ToString(), nq}, B: {B.ToString(), nq}, B: {C.ToString(), nq}")]
public readonly struct Triangle(Vector2D A, Vector2D B, Vector2D C)
@@ -7,6 +7,9 @@ public readonly struct Triangle(Vector2D A, Vector2D B, Vector2D C)
public readonly Vector2D B { get; init; } = B;
public readonly Vector2D C { get; init; } = C;
public static bool operator ==(Triangle left, Triangle right) => left.A == right.A && left.B == right.B && left.C == right.C;
public static bool operator !=(Triangle left, Triangle right) => left.A != right.A || left.B != right.B || left.C != right.C;
public readonly float Area
=> .5f * Math.Abs(
A.X * (B.Y - C.Y) +
@@ -44,6 +47,25 @@ public readonly struct Triangle(Vector2D A, Vector2D B, Vector2D C)
/// <returns><c>true</c> if the <see cref="Triangle"/>s are approximately equal; otherwise, <c>false</c>.</returns>
public static bool ApproximatelyEquals(Triangle left, Triangle right, float epsilon = float.Epsilon)
=> left.A.ApproximatelyEquals(right.A, epsilon) && left.B.ApproximatelyEquals(right.B, epsilon) && left.C.ApproximatelyEquals(right.C, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Triangle"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Triangle"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Triangle"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Triangle triangle && this == triangle;
/// <summary>
/// Generates a hash code for the <see cref="Triangle"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Triangle"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(A, B, C);
/// <summary>
/// Converts the <see cref="Triangle"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Triangle"/>.</returns>
public override string ToString() => $"{nameof(Triangle)}({A}, {B}, {C})";
}
public static class TriangleExtensions

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a two-dimensional vector.
@@ -31,6 +31,11 @@ public readonly struct Vector2D(float x, float y)
/// </summary>
public float MagnitudeSquared => LengthSquared(this);
/// <summary>
/// Gets a <see cref="Vector2D"/> with the direction reversed.
/// </summary>
public readonly Vector2D Reversed => -this;
/// <summary>
/// The normalized form of the <see cref="Vector2D"/> (a <see cref="Vector2D"/> with the same direction and a magnitude of 1).
/// </summary>
@@ -297,24 +302,24 @@ public readonly struct Vector2D(float x, float y)
public static bool ApproximatelyEquals(Vector2D left, Vector2D right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon);
/// <summary>
/// Converts the <see cref="Vector2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector2D"/>.</returns>
public override string ToString() => $"{nameof(Vector2D)}({X}, {Y})";
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Vector2D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Vector2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector2D objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y);
public override bool Equals(object? obj) => obj is Vector2D vector2D && this == vector2D;
/// <summary>
/// Generates a hash code for the <see cref="Vector2D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Vector2D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y);
/// <summary>
/// Converts the <see cref="Vector2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector2D"/>.</returns>
public override string ToString() => $"{nameof(Vector2D)}({X}, {Y})";
}
/// <summary>

View File

@@ -1,4 +1,4 @@
namespace Syntriax.Engine.Core;
namespace Engine.Core;
/// <summary>
/// Represents a three-dimensional vector.
@@ -271,24 +271,24 @@ public readonly struct Vector3D(float x, float y, float z)
public static bool ApproximatelyEquals(Vector3D left, Vector3D right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon);
/// <summary>
/// Converts the <see cref="Vector3D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector3D"/>.</returns>
public override string ToString() => $"{nameof(Vector3D)}({X}, {Y}, {Z})";
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Vector3D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Vector3D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector3D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector3D objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y) && Z.Equals(objVec.Z);
public override bool Equals(object? obj) => obj is Vector3D vector3D && this == vector3D;
/// <summary>
/// Generates a hash code for the <see cref="Vector3D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Vector3D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z);
/// <summary>
/// Converts the <see cref="Vector3D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector3D"/>.</returns>
public override string ToString() => $"{nameof(Vector3D)}({X}, {Y}, {Z})";
}
/// <summary>

View File

@@ -1,6 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Serialization;
namespace Engine.Core.Serialization;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class)]
public class IgnoreSerializationAttribute : Attribute;

View File

@@ -1,6 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Serialization;
namespace Engine.Core.Serialization;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class SerializeAllAttribute : Attribute;

View File

@@ -1,6 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Serialization;
namespace Engine.Core.Serialization;
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class SerializeAttribute : Attribute;

View File

@@ -1,3 +1,3 @@
namespace Syntriax.Engine.Core.Serialization;
namespace Engine.Core.Serialization;
public record class EntityReference(string? Id = null);

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