feat!: GetRequiredBehaviour/HierarchyObject methods added for cleaner null handling

This commit is contained in:
Syntriax 2025-04-13 12:52:27 +03:00
parent bfbcfdce4f
commit 86b8cd9b55
21 changed files with 140 additions and 73 deletions

View File

@ -1,4 +1,5 @@
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Helpers;
namespace Syntriax.Engine.Core;
@ -55,6 +56,8 @@ public abstract class Behaviour : BehaviourBase
protected virtual void OnPreUpdate() { }
protected virtual void PreUpdate(IBehaviourController _)
{
AssertHelpers.AssertInitialized(this);
OnPreUpdatePreActiveCheck();
if (!IsActive)
@ -77,6 +80,8 @@ public abstract class Behaviour : BehaviourBase
protected virtual void OnUpdate() { }
protected virtual void Update(IBehaviourController _)
{
AssertHelpers.AssertInitialized(this);
OnUpdatePreActiveCheck();
if (!IsActive)
@ -89,6 +94,8 @@ public abstract class Behaviour : BehaviourBase
protected virtual void OnPreDraw() { }
protected virtual void PreDraw(IBehaviourController _)
{
AssertHelpers.AssertInitialized(this);
OnPreDrawPreActiveCheck();
if (!StateEnable.Enabled)

View File

@ -8,7 +8,7 @@ public abstract class Behaviour2D : Behaviour, IBehaviour2D
protected sealed override void OnInitialize(IInitializable _)
{
Transform = BehaviourController.GetBehaviourInChildren<ITransform2D>() ?? throw new($"{HierarchyObject.Name} does not contain any {nameof(ITransform2D)}");
Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
base.OnInitialize(_);
}

View File

@ -1,5 +1,6 @@
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
using Syntriax.Engine.Core.Helpers;
namespace Syntriax.Engine.Core;
@ -69,9 +70,8 @@ public abstract class BehaviourBase : BaseEntity, IBehaviour
protected override void InitializeInternal()
{
base.InitializeInternal();
NotAssignedException.Check(this, _behaviourController);
NotAssignedException.Check(this, StateEnable);
AssertHelpers.AssertBehaviourControllerAssigned(this);
AssertHelpers.AssertStateEnableAssigned(this);
}
private void OnStateEnabledChanged(IStateEnable sender, bool previousState) => UpdateActive();

View File

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
using Syntriax.Engine.Core.Helpers;
namespace Syntriax.Engine.Core;
@ -141,7 +141,7 @@ public class BehaviourController : IBehaviourController
if (IsInitialized)
return false;
NotAssignedException.Check(this, _hierarchyObject);
AssertHelpers.AssertHierarchyObjectAssigned(this);
foreach (IBehaviour behaviour in behaviours)
behaviour.Initialize();

View File

@ -1,12 +0,0 @@
using System;
namespace Syntriax.Engine.Core.Exceptions;
public class AssignException : Exception
{
public AssignException() : base("Assign operation has failed.") { }
public AssignException(string? message) : base(message) { }
public static AssignException From<T, T2>(T to, T2? value)
=> new($"Assign operation has failed on T: {to?.GetType().FullName ?? "\"null\""}, value: {value?.GetType().ToString() ?? "\"null\""}");
}

View File

@ -0,0 +1,9 @@
using System;
namespace Syntriax.Engine.Core.Exceptions;
public class AssignFailedException(string? message) : Exception(message)
{
public static AssignFailedException From<T, T2>(T to, T2? value)
=> new($"Assign operation has failed on T: {to?.GetType().FullName ?? "\"null\""}, value: {value?.GetType().ToString() ?? "\"null\""}");
}

View File

@ -0,0 +1,9 @@
using System;
namespace Syntriax.Engine.Core.Exceptions;
public class BehaviourNotFoundException(string? message) : Exception(message)
{
public static NotAssignedException FromType<TBehaviour>()
=> new($"{typeof(TBehaviour).FullName} was not found");
}

View File

@ -0,0 +1,9 @@
using System;
namespace Syntriax.Engine.Core.Exceptions;
public class HierarchyObjectNotFoundException(string? message) : Exception(message)
{
public static NotAssignedException FromType<THierarchyObject>()
=> new($"{typeof(THierarchyObject).FullName} was not found");
}

View File

@ -1,21 +1,9 @@
using System;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core.Exceptions;
public class NotAssignedException : Exception
public class NotAssignedException(string? message) : Exception(message)
{
public NotAssignedException() : base("The object has not been assigned.") { }
public NotAssignedException(string? message) : base(message) { }
public static NotAssignedException From<T1, T2>(T1 to, T2? value)
=> new($"{value?.GetType().FullName ?? "\"null\""} has not been assigned to {to?.GetType().FullName ?? "\"null\""}");
public static void Check<T1, T2>(T1 to, T2? value)
{
if (value is not null)
return;
throw From(to, value);
}
}

View File

@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
@ -19,6 +20,15 @@ public static class BehaviourControllerExtensions
return behaviour is not null;
}
/// <summary>
/// Gets a <see cref="IBehaviour"/> of the specified type in the provided <see cref="IBehaviourController"/>. Throws an error if not found.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <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.HierarchyObject.Name}'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.
/// </summary>
@ -30,7 +40,7 @@ public static class BehaviourControllerExtensions
=> behaviourController.GetBehaviour<T>() ?? behaviourController.AddBehaviour<T>(args);
/// <summary>
/// Tries to get a <see cref="IBehaviour"/> of the specified type in the parent hierarchy.
/// Tries to get a <see cref="IBehaviour"/> of the specified type in it's <see cref="IHierarchyObject"/>'s parents recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
@ -43,7 +53,7 @@ public static class BehaviourControllerExtensions
}
/// <summary>
/// Gets a <see cref="IBehaviour"/> of the specified type in the parent hierarchy.
/// Gets a <see cref="IBehaviour"/> of the specified type in it's <see cref="IHierarchyObject"/>'s parents recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
@ -64,7 +74,16 @@ public static class BehaviourControllerExtensions
}
/// <summary>
/// Tries to get a <see cref="IBehaviour"/> of the specified type in the child hierarchy.
/// Gets a <see cref="IBehaviour"/> of the specified type in it's <see cref="IHierarchyObject"/>'s parents recursively. Throws an error if not found.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <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.HierarchyObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any parent");
/// <summary>
/// Tries to get a <see cref="IBehaviour"/> of the specified type in it's <see cref="IHierarchyObject"/>'s children recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
@ -77,7 +96,7 @@ public static class BehaviourControllerExtensions
}
/// <summary>
/// Gets a <see cref="IBehaviour"/> of the specified type in the child hierarchy.
/// Gets a <see cref="IBehaviour"/> of the specified type in it's <see cref="IHierarchyObject"/>'s children recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
@ -93,4 +112,13 @@ public static class BehaviourControllerExtensions
return default;
}
/// <summary>
/// Gets a <see cref="IBehaviour"/> of the specified type in the children recursively. Throws an error if not found.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <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.HierarchyObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any children ");
}

View File

@ -1,4 +1,5 @@
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
@ -6,4 +7,13 @@ public static class GameManagerExtensions
{
public static IHierarchyObject InstantiateHierarchyObject(this IGameManager gameManager, params object?[]? args)
=> gameManager.InstantiateHierarchyObject<HierarchyObject>(args);
public static T GetRequiredHierarchyObject<T>(this IGameManager gameManager) where T : class
{
foreach (IHierarchyObject hierarchyObject in gameManager)
if (hierarchyObject is T @object)
return @object;
throw new HierarchyObjectNotFoundException($"{gameManager.GetType().FullName}({gameManager.Id}) does not contain any {typeof(T).FullName}");
}
}

View File

@ -16,7 +16,7 @@ public static class HierarchyObjectExtensions
return hierarchyObject;
}
public static T? FindHierarchyObject<T>(this IEnumerable<IHierarchyObject> hierarchyObjects) where T : class
public static T? GetHierarchyObject<T>(this IEnumerable<IHierarchyObject> hierarchyObjects) where T : class
{
foreach (IHierarchyObject hierarchyObject in hierarchyObjects)
if (hierarchyObject is T @object)
@ -25,13 +25,13 @@ public static class HierarchyObjectExtensions
return default;
}
public static bool TryFindHierarchyObject<T>(this IEnumerable<IHierarchyObject> hierarchyObjects, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
public static bool TryGetHierarchyObject<T>(this IEnumerable<IHierarchyObject> hierarchyObjects, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = FindHierarchyObject<T>(hierarchyObjects);
behaviour = GetHierarchyObject<T>(hierarchyObjects);
return behaviour is not null;
}
public static void FindHierarchyObjects<T>(this IEnumerable<IHierarchyObject> hierarchyObjects, List<T> behaviours) where T : class
public static void GetHierarchyObjects<T>(this IEnumerable<IHierarchyObject> hierarchyObjects, List<T> behaviours) where T : class
{
behaviours.Clear();
foreach (IHierarchyObject hierarchyObject in hierarchyObjects)

View File

@ -14,10 +14,10 @@ public class BehaviourControllerFactory
T behaviourController = TypeFactory.Get<T>(args);
if (!hierarchyObject.Assign(behaviourController))
throw AssignException.From(hierarchyObject, behaviourController);
throw AssignFailedException.From(hierarchyObject, behaviourController);
if (!behaviourController.Assign(hierarchyObject))
throw AssignException.From(behaviourController, hierarchyObject);
throw AssignFailedException.From(behaviourController, hierarchyObject);
return behaviourController;
}

View File

@ -15,12 +15,12 @@ public class BehaviourFactory
stateEnable ??= TypeFactory.Get<StateEnable>();
if (!stateEnable.Assign(behaviour))
throw AssignException.From(stateEnable, behaviour);
throw AssignFailedException.From(stateEnable, behaviour);
if (!behaviour.Assign(stateEnable))
throw AssignException.From(behaviour, stateEnable);
throw AssignFailedException.From(behaviour, stateEnable);
if (!behaviour.Assign(hierarchyObject.BehaviourController))
throw AssignException.From(behaviour, hierarchyObject.BehaviourController);
throw AssignFailedException.From(behaviour, hierarchyObject.BehaviourController);
return behaviour;
}

View File

@ -23,14 +23,14 @@ public class HierarchyObjectFactory
stateEnable ??= TypeFactory.Get<StateEnable>();
if (!behaviourController.Assign(hierarchyObject))
throw AssignException.From(behaviourController, hierarchyObject);
throw AssignFailedException.From(behaviourController, hierarchyObject);
if (!stateEnable.Assign(hierarchyObject))
throw AssignException.From(stateEnable, hierarchyObject);
throw AssignFailedException.From(stateEnable, hierarchyObject);
if (!hierarchyObject.Assign(behaviourController))
throw AssignException.From(hierarchyObject, behaviourController);
throw AssignFailedException.From(hierarchyObject, behaviourController);
if (!hierarchyObject.Assign(stateEnable))
throw AssignException.From(hierarchyObject, stateEnable);
throw AssignFailedException.From(hierarchyObject, stateEnable);
return hierarchyObject;
}

View File

@ -12,10 +12,10 @@ public class StateEnableFactory
T stateEnable = TypeFactory.Get<T>(args);
if (!entity.Assign(stateEnable))
throw AssignException.From(entity, stateEnable);
throw AssignFailedException.From(entity, stateEnable);
if (!stateEnable.Assign(entity))
throw AssignException.From(stateEnable, entity);
throw AssignFailedException.From(stateEnable, entity);
return stateEnable;
}

View File

@ -3,7 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
using Syntriax.Engine.Core.Helpers;
namespace Syntriax.Engine.Core;
@ -22,21 +22,6 @@ public class GameManager : BaseEntity, IGameManager
public IReadOnlyList<IHierarchyObject> HierarchyObjects => _hierarchyObjects;
public override IStateEnable StateEnable
{
get
{
if (base.StateEnable is null)
{
Assign(Factory.StateEnableFactory.Instantiate(this));
if (base.StateEnable is null)
throw NotAssignedException.From(this, base.StateEnable);
}
return base.StateEnable;
}
}
public EngineTime Time { get; private set; } = new();
public EngineTime UnscaledTime { get; private set; } = new();
public float TimeScale
@ -104,9 +89,6 @@ public class GameManager : BaseEntity, IGameManager
protected override void InitializeInternal()
{
base.InitializeInternal();
NotAssignedException.Check(this, StateEnable);
foreach (IHierarchyObject hierarchyObject in HierarchyObjects)
hierarchyObject.Initialize();
}
@ -120,6 +102,8 @@ public class GameManager : BaseEntity, IGameManager
public void Update(EngineTime engineTime)
{
AssertHelpers.AssertInitialized(this);
UnscaledTime = engineTime;
Time = new(TimeSpan.FromTicks((long)(Time.TimeSinceStart.Ticks + engineTime.DeltaSpan.Ticks * TimeScale)), TimeSpan.FromTicks((long)(engineTime.DeltaSpan.Ticks * TimeScale)));
@ -133,6 +117,8 @@ public class GameManager : BaseEntity, IGameManager
public void PreDraw()
{
AssertHelpers.AssertInitialized(this);
for (int i = 0; i < HierarchyObjects.Count; i++)
HierarchyObjects[i].BehaviourController.UpdatePreDraw();

View File

@ -0,0 +1,33 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core.Helpers;
public class AssertHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertInitialized(IInitializable initializable)
=> Debug.Assert(initializable.IsInitialized, $"{initializable.GetType().Name} must be initialized");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertBehaviourControllerAssigned(IHasBehaviourController assignable)
=> Debug.Assert(assignable.BehaviourController is not null, $"{assignable.GetType().Name} must be initialized");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertEntityAssigned(IHasEntity assignable)
=> Debug.Assert(assignable.Entity is not null, $"{assignable.GetType().Name} must be initialized");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertGameManagerAssigned(IHasGameManager assignable)
=> Debug.Assert(assignable.GameManager is not null, $"{assignable.GetType().Name} must be initialized");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertHierarchyObjectAssigned(IHasHierarchyObject assignable)
=> Debug.Assert(assignable.HierarchyObject is not null, $"{assignable.GetType().Name} must be initialized");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertStateEnableAssigned(IHasStateEnable assignable)
=> Debug.Assert(assignable.StateEnable is not null, $"{assignable.GetType().Name} must be initialized");
}

View File

@ -71,7 +71,7 @@ public class HierarchyObject : BaseEntity, IHierarchyObject
public void SetParent(IHierarchyObject? parent)
{
if (parent == this)
throw new Exceptions.AssignException($"{Name} can not parent itself");
throw new Exceptions.AssignFailedException($"{Name} can not parent itself");
if (Parent == parent)
return;

View File

@ -25,7 +25,7 @@ public class PhysicsCoroutineManager : HierarchyObject
protected override void OnEnteringHierarchy(IGameManager gameManager)
{
physicsEngine = gameManager.FindHierarchyObject<IPhysicsEngine2D>();
physicsEngine = gameManager.GetHierarchyObject<IPhysicsEngine2D>();
if (physicsEngine is IPhysicsEngine2D foundPhysicsEngine)
foundPhysicsEngine.OnPhysicsStep += OnPhysicsStep;
else
@ -56,7 +56,7 @@ public class PhysicsCoroutineManager : HierarchyObject
if (GameManager is not IGameManager gameManager)
return;
physicsEngine = gameManager.FindHierarchyObject<IPhysicsEngine2D>();
physicsEngine = gameManager.GetHierarchyObject<IPhysicsEngine2D>();
if (physicsEngine is IPhysicsEngine2D foundPhysicsEngine)
{
foundPhysicsEngine.OnPhysicsStep += OnPhysicsStep;

View File

@ -54,7 +54,7 @@ public class TweenManager : HierarchyObject
protected override void OnEnteringHierarchy(IGameManager gameManager)
{
coroutineManager = gameManager.FindHierarchyObject<CoroutineManager>() ?? throw new($"No {nameof(CoroutineManager)} was found in the game manager");
coroutineManager = gameManager.GetRequiredHierarchyObject<CoroutineManager>();
}
protected override void OnExitingHierarchy(IGameManager gameManager)