diff --git a/.gitignore b/.gitignore
index adcee675..fc81be36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,13 @@
.vscode/
.idea/
+.junie/
bits/
cmake-build-*/
bin/
build/
graph/
+obj/
CMakeUserPresets.json
+*.DotSettings.user
diff --git a/Draconic.slnx b/Draconic.slnx
new file mode 100644
index 00000000..23dd1ad7
--- /dev/null
+++ b/Draconic.slnx
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Engine/csharp/Draconic.Engine.Tests/Draconic.Engine.Tests.csproj b/Engine/csharp/Draconic.Engine.Tests/Draconic.Engine.Tests.csproj
new file mode 100644
index 00000000..f46f40bd
--- /dev/null
+++ b/Engine/csharp/Draconic.Engine.Tests/Draconic.Engine.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Engine/csharp/Draconic.Engine.Tests/WorldTests.cs b/Engine/csharp/Draconic.Engine.Tests/WorldTests.cs
new file mode 100644
index 00000000..a5851989
--- /dev/null
+++ b/Engine/csharp/Draconic.Engine.Tests/WorldTests.cs
@@ -0,0 +1,305 @@
+using Draconic.Engine;
+
+namespace Draconic.Engine.Tests;
+
+public class WorldTests
+{
+ private readonly struct TestComponent(int value) : IComponent
+ {
+ public int Value { get; } = value;
+ }
+
+ private sealed class TrackingBehavior : Behavior
+ {
+ public int UpdateCalls { get; private set; }
+ public int FixedUpdateCalls { get; private set; }
+ public int LateUpdateCalls { get; private set; }
+ public float LastDeltaTime { get; private set; }
+ public Entity AssignedEntity => Entity;
+ public World AssignedWorld => World;
+
+ public TrackingBehavior() : base(new Entity(), new World())
+ {
+ }
+
+ public override void Update(float deltaTime)
+ {
+ UpdateCalls++;
+ LastDeltaTime = deltaTime;
+ }
+
+ public override void FixedUpdate(float deltaTime)
+ {
+ FixedUpdateCalls++;
+ LastDeltaTime = deltaTime;
+ }
+
+ public override void LateUpdate(float deltaTime)
+ {
+ LateUpdateCalls++;
+ LastDeltaTime = deltaTime;
+ }
+ }
+
+ private sealed class ThrowingBehavior : Behavior
+ {
+ public ThrowingBehavior() : base(new Entity(), new World())
+ {
+ }
+
+ public override void Update(float deltaTime)
+ {
+ throw new InvalidOperationException("Expected test exception.");
+ }
+ }
+
+ [Fact]
+ public void CreateEntity_WithoutId_CreatesEntityAndAllowsComponentAndBehaviorOperations()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+
+ world.CreateComponent(entity.EntityId);
+ var behavior = new TrackingBehavior();
+ world.AddBehavior(entity.EntityId, behavior);
+
+ Assert.True(world.TryGetComponent(entity.EntityId, out _));
+ Assert.True(world.TryGetBehavior(entity.EntityId, out var fetchedBehavior));
+ Assert.Same(behavior, fetchedBehavior);
+ }
+
+ [Fact]
+ public void CreateEntity_WithProvidedId_UsesProvidedId()
+ {
+ var world = new World();
+ const ulong entityId = 42;
+
+ var entity = world.CreateEntity(entityId);
+
+ Assert.Equal(entityId, entity.EntityId);
+ }
+
+ [Fact]
+ public void CreateEntity_WithDuplicateProvidedId_ThrowsInvalidOperationException()
+ {
+ var world = new World();
+ const ulong entityId = 42;
+ world.CreateEntity(entityId);
+
+ Assert.Throws(() => world.CreateEntity(entityId));
+ }
+
+ [Fact]
+ public void CreateEntity_WithProvidedId_AdvancesGeneratedEntityIdPastProvidedId()
+ {
+ var world = new World();
+ const ulong providedId = 1_000_000;
+
+ world.CreateEntity(providedId);
+ var generatedEntity = world.CreateEntity();
+
+ Assert.True(generatedEntity.EntityId > providedId);
+ }
+
+ [Fact]
+ public void DestroyEntity_ExistingEntity_RemovesEntityDataAndReturnsTrue()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+ world.CreateComponent(entity.EntityId);
+ world.AddBehavior(entity.EntityId, new TrackingBehavior());
+
+ var destroyed = world.DestroyEntity(entity.EntityId);
+
+ Assert.True(destroyed);
+ Assert.False(world.TryGetComponent(entity.EntityId, out _));
+ Assert.False(world.TryGetBehavior(entity.EntityId, out _));
+ Assert.False(world.RemoveComponent(entity.EntityId));
+ Assert.False(world.RemoveBehavior(entity.EntityId));
+ }
+
+ [Fact]
+ public void DestroyEntity_NonExistentEntity_ReturnsFalse()
+ {
+ var world = new World();
+
+ var destroyed = world.DestroyEntity(1234);
+
+ Assert.False(destroyed);
+ }
+
+ [Fact]
+ public void CreateComponent_ExistingEntity_CanBeRetrieved()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+
+ world.CreateComponent(entity.EntityId);
+
+ Assert.True(world.TryGetComponent(entity.EntityId, out var component));
+ Assert.Equal(0, component.Value);
+ }
+
+ [Fact]
+ public void CreateComponent_DuplicateType_ThrowsInvalidOperationException()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+ world.CreateComponent(entity.EntityId);
+
+ Assert.Throws(() => world.CreateComponent(entity.EntityId));
+ }
+
+ [Fact]
+ public void CreateComponent_ForMissingEntity_ThrowsKeyNotFoundException()
+ {
+ var world = new World();
+
+ Assert.Throws(() => world.CreateComponent(999));
+ }
+
+ [Fact]
+ public void TryUpdateComponent_ExistingEntity_UpdatesStoredValue()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+ world.CreateComponent(entity.EntityId);
+ var updated = new TestComponent(77);
+
+ var updateResult = world.TryUpdateComponent(entity.EntityId, ref updated);
+ var getResult = world.TryGetComponent(entity.EntityId, out var component);
+
+ Assert.True(updateResult);
+ Assert.True(getResult);
+ Assert.Equal(77, component.Value);
+ }
+
+ [Fact]
+ public void TryUpdateComponent_MissingEntity_ReturnsFalse()
+ {
+ var world = new World();
+ var component = new TestComponent(11);
+
+ var result = world.TryUpdateComponent(444, ref component);
+
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void RemoveComponent_ExistingComponent_RemovesAndReturnsTrue()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+ world.CreateComponent(entity.EntityId);
+
+ var removed = world.RemoveComponent(entity.EntityId);
+
+ Assert.True(removed);
+ Assert.False(world.TryGetComponent(entity.EntityId, out _));
+ }
+
+ [Fact]
+ public void AddBehavior_ExistingEntity_SetsWorldAndEntityAndCanBeRetrieved()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+ var behavior = new TrackingBehavior();
+
+ world.AddBehavior(entity.EntityId, behavior);
+
+ Assert.True(world.TryGetBehavior(entity.EntityId, out var fetched));
+ Assert.Same(behavior, fetched);
+ Assert.Same(entity, behavior.AssignedEntity);
+ Assert.Same(world, behavior.AssignedWorld);
+ }
+
+ [Fact]
+ public void AddBehavior_DuplicateType_ThrowsInvalidOperationException()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+ world.AddBehavior(entity.EntityId, new TrackingBehavior());
+
+ Assert.Throws(() => world.AddBehavior(entity.EntityId, new TrackingBehavior()));
+ }
+
+ [Fact]
+ public void AddBehavior_ForMissingEntity_ThrowsKeyNotFoundException()
+ {
+ var world = new World();
+
+ Assert.Throws(() => world.AddBehavior(99, new TrackingBehavior()));
+ }
+
+ [Fact]
+ public void RemoveBehavior_ExistingBehavior_RemovesAndReturnsTrue()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+ world.AddBehavior(entity.EntityId, new TrackingBehavior());
+
+ var removed = world.RemoveBehavior(entity.EntityId);
+
+ Assert.True(removed);
+ Assert.False(world.TryGetBehavior(entity.EntityId, out _));
+ }
+
+ [Fact]
+ public void ExecuteUpdate_CallsUpdateForRegisteredBehaviors()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+ var behavior = new TrackingBehavior();
+ world.AddBehavior(entity.EntityId, behavior);
+
+ world.ExecuteUpdate(0.25f);
+
+ Assert.Equal(1, behavior.UpdateCalls);
+ Assert.Equal(0.25f, behavior.LastDeltaTime);
+ }
+
+ [Fact]
+ public void ExecuteFixedUpdate_CallsFixedUpdateForRegisteredBehaviors()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+ var behavior = new TrackingBehavior();
+ world.AddBehavior(entity.EntityId, behavior);
+
+ world.ExecuteFixedUpdate(0.5f);
+
+ Assert.Equal(1, behavior.FixedUpdateCalls);
+ Assert.Equal(0.5f, behavior.LastDeltaTime);
+ }
+
+ [Fact]
+ public void ExecuteLateUpdate_CallsLateUpdateForRegisteredBehaviors()
+ {
+ var world = new World();
+ var entity = world.CreateEntity();
+ var behavior = new TrackingBehavior();
+ world.AddBehavior(entity.EntityId, behavior);
+
+ world.ExecuteLateUpdate(1f);
+
+ Assert.Equal(1, behavior.LateUpdateCalls);
+ Assert.Equal(1f, behavior.LastDeltaTime);
+ }
+
+ [Fact]
+ public void ExecuteUpdate_WhenBehaviorThrows_ContinuesProcessingOthers()
+ {
+ var world = new World();
+ var first = world.CreateEntity();
+ var second = world.CreateEntity();
+ var trackingBehavior = new TrackingBehavior();
+
+ world.AddBehavior(first.EntityId, new ThrowingBehavior());
+ world.AddBehavior(second.EntityId, trackingBehavior);
+
+ var exception = Record.Exception(() => world.ExecuteUpdate(0.16f));
+
+ Assert.Null(exception);
+ Assert.Equal(1, trackingBehavior.UpdateCalls);
+ }
+}
\ No newline at end of file
diff --git a/Engine/csharp/Draconic.Engine/Behavior.cs b/Engine/csharp/Draconic.Engine/Behavior.cs
new file mode 100644
index 00000000..77ed890c
--- /dev/null
+++ b/Engine/csharp/Draconic.Engine/Behavior.cs
@@ -0,0 +1,104 @@
+namespace Draconic.Engine;
+
+///
+/// Represents the base class for all behaviors that can be associated with an entity within a game world.
+///
+///
+/// A behavior encapsulates logic and functionality that can be attached to an entity in the world.
+/// It provides methods for handling lifecycle events such as initialization, updates, and destruction,
+/// which can be overridden by derived classes to implement specific behavior.
+///
+public abstract class Behavior
+{
+ ///
+ /// Represents a fundamental unit within the game engine architecture.
+ ///
+ ///
+ /// The class serves as the cornerstone for organizing objects in the game world.
+ /// Each entity is uniquely identified by its .
+ /// Entities can contain components and behaviors to define their characteristics and functionality.
+ ///
+ protected internal Entity Entity { get; internal set; }
+
+ ///
+ /// Provides the primary context for managing entities, components, and behaviors in the gaming engine.
+ ///
+ ///
+ /// The World property links behaviors to the World instance that governs the lifecycle and interaction
+ /// of all entities and components in the system. It acts as an interface for behaviors to access and
+ /// interact with the overarching simulation environment.
+ ///
+ protected internal World World { get; internal set; }
+
+ ///
+ /// Represents an abstract base class for behaviors attached to entities within the game world.
+ /// A behavior defines specific functionality or actions that can be applied to an entity.
+ ///
+ protected Behavior(Entity entity, World world)
+ {
+ Entity = entity;
+ World = world;
+ }
+
+ /// Updates the behavior with the logic defined by derived classes.
+ /// This method is called during the world's update cycle and should
+ /// be overridden by subclasses to implement specific behavior logic.
+ ///
+ /// The time, in seconds, since the last update.
+ public virtual void Update(float deltaTime)
+ {
+
+ }
+
+ ///
+ /// Invoked when a behavior is initialized and attached to an entity within the game world.
+ /// This method is primarily intended for performing setup logic, initializing state, or registering any required resources
+ /// specific to the behavior instance.
+ /// Override this method in derived classes to implement custom initialization logic.
+ ///
+ public virtual void Awake()
+ {
+
+ }
+
+ ///
+ /// Executes logic for a fixed update cycle. This method is used in for processing
+ /// physics-related updates or other time-dependent logic intended to run
+ /// at a consistent interval, independent of the frame rate.
+ ///
+ ///
+ /// The fixed time-step duration since the last fixed update cycle, typically used for
+ /// calculations requiring a consistent time interval.
+ ///
+ public virtual void FixedUpdate(float deltaTime)
+ {
+
+ }
+
+ ///
+ /// Invoked during the LateUpdate phase of the game loop for this behavior.
+ /// This method is called after all Update methods have run in the current frame,
+ /// allowing behaviors to perform operations that should occur later in the frame.
+ ///
+ ///
+ /// The time elapsed, in seconds, since the last frame. Use this value to perform
+ /// time-based calculations and ensure frame-rate independent behavior.
+ ///
+ public virtual void LateUpdate(float deltaTime)
+ {
+
+ }
+
+ ///
+ /// Called when the behavior's associated entity is being destroyed.
+ /// This method provides an opportunity to perform cleanup or finalization tasks
+ /// related to the behavior before the entity's destruction is completed.
+ /// By default, this method performs no operation, but it can be overridden
+ /// in derived classes to implement specific destruction logic.
+ ///
+ public virtual void OnDestroyed()
+ {
+
+ }
+
+}
\ No newline at end of file
diff --git a/Engine/csharp/Draconic.Engine/Draconic.Engine.csproj b/Engine/csharp/Draconic.Engine/Draconic.Engine.csproj
new file mode 100644
index 00000000..237d6616
--- /dev/null
+++ b/Engine/csharp/Draconic.Engine/Draconic.Engine.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
diff --git a/Engine/csharp/Draconic.Engine/Entity.cs b/Engine/csharp/Draconic.Engine/Entity.cs
new file mode 100644
index 00000000..6c9d12d3
--- /dev/null
+++ b/Engine/csharp/Draconic.Engine/Entity.cs
@@ -0,0 +1,44 @@
+using System.Runtime.InteropServices;
+
+namespace Draconic.Engine;
+
+///
+/// Represents a fundamental unit within the game engine.
+/// Each instance of the class is uniquely identified by an .
+/// Entities can serve as containers for components and behaviors, enabling flexible and modular interaction within the game world.
+///
+public class Entity
+{
+ ///
+ /// Gets or sets the unique identifier for an entity within the game engine.
+ ///
+ public ulong EntityId { get; set; }
+
+ private static ulong _lastEntityId = 0;
+ private static readonly Lock _lock = new Lock();
+
+ public Entity()
+ {
+ lock (_lock)
+ {
+ EntityId = ++_lastEntityId;
+ }
+ }
+ public Entity(ulong entityId)
+ {
+ lock (_lock)
+ {
+ EntityId = entityId;
+
+ if (entityId > _lastEntityId)
+ {
+ _lastEntityId = entityId;
+ }
+ }
+ }
+
+ public static Entity Create()
+ {
+ return new Entity();
+ }
+}
\ No newline at end of file
diff --git a/Engine/csharp/Draconic.Engine/IComponent.cs b/Engine/csharp/Draconic.Engine/IComponent.cs
new file mode 100644
index 00000000..1092d740
--- /dev/null
+++ b/Engine/csharp/Draconic.Engine/IComponent.cs
@@ -0,0 +1,14 @@
+namespace Draconic.Engine;
+
+///
+/// Represents a marker interface for defining components in the Draconic Engine.
+///
+///
+/// Components are used to store data in the entity-component-system (ECS) architecture.
+/// Implementing this interface allows a structure or class to represent an element of data
+/// associated with an entity in the world.
+///
+public interface IComponent
+{
+
+}
\ No newline at end of file
diff --git a/Engine/csharp/Draconic.Engine/Transform.cs b/Engine/csharp/Draconic.Engine/Transform.cs
new file mode 100644
index 00000000..b17ff74c
--- /dev/null
+++ b/Engine/csharp/Draconic.Engine/Transform.cs
@@ -0,0 +1,27 @@
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace Draconic.Engine;
+
+///
+/// Represents the transformation data for an entity, including its position, scale, and rotation.
+///
+[StructLayout(LayoutKind.Sequential)]
+public struct Transform : IComponent
+{
+ ///
+ /// Represents the position of a transform in 3D space.
+ ///
+ public Vector3 Position { get; set; }
+
+ ///
+ /// Represents the scaling factor applied to an object in 3D space.
+ ///
+ public Vector3 Scale { get; set; }
+
+ ///
+ /// Represents the rotational orientation of the transform in 3D space.
+ ///
+ public Quaternion Rotation { get; set; }
+
+}
\ No newline at end of file
diff --git a/Engine/csharp/Draconic.Engine/World.cs b/Engine/csharp/Draconic.Engine/World.cs
new file mode 100644
index 00000000..8f30eff9
--- /dev/null
+++ b/Engine/csharp/Draconic.Engine/World.cs
@@ -0,0 +1,423 @@
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Draconic.Engine;
+
+///
+/// Represents the main context for managing entities, components, and behaviors within the Draconic Engine.
+///
+///
+/// The World class provides functionality for creating and destroying entities, attaching and managing components,
+/// and adding, retrieving, or removing behaviors associated with entities. It also handles the update cycles
+/// (Update, FixedUpdate, LateUpdate) for behaviors.
+///
+public class World
+{
+ //ToDo: refactor as a sparse set.
+
+ #region Fields
+
+ ///
+ /// Stores a collection of all entities in the current world context,
+ /// providing fast, thread-safe access to entity instances by their unique identifiers.
+ ///
+ private readonly ConcurrentDictionary _entities = new();
+
+ ///
+ /// Stores all components associated with entities in the world.
+ ///
+ private readonly ConcurrentDictionary> _components = new();
+
+ ///
+ /// Maintains a thread-safe collection of behaviors associated with each entity in the .
+ ///
+ private readonly ConcurrentDictionary> _behaviors = new();
+
+ #endregion
+
+ #region Entity Management
+
+ /// Creates a new entity within the world and initializes its associated data structures
+ /// for components and behaviors.
+ /// The method generates a new entity, adds it to the world's collection of entities,
+ /// and registers empty sets of components and behaviors associated with the entity's ID.
+ ///
+ /// An instance of the newly created entity.
+ ///
+ public Entity CreateEntity()
+ {
+ var entity = Entity.Create();
+
+ _entities[entity.EntityId] = entity;
+ _components.TryAdd(entity.EntityId, new());
+ _behaviors.TryAdd(entity.EntityId, new());
+ return entity;
+ }
+
+ ///
+ /// Creates a new entity with the specified unique identifier.
+ ///
+ /// The unique identifier to associate with the new entity.
+ /// A newly created with the specified identifier.
+ public Entity CreateEntity(ulong entityId)
+ {
+ if (_entities.ContainsKey(entityId))
+ {
+ throw new InvalidOperationException($"Entity {entityId} already exists in world.");
+ }
+
+ var entity = new Entity(entityId);
+ _entities[entity.EntityId] = entity;
+ _components.TryAdd(entity.EntityId, new());
+ _behaviors.TryAdd(entity.EntityId, new());
+ return entity;
+ }
+
+ ///
+ /// Removes the specified entity and all associated components and behaviors from the world.
+ ///
+ /// The unique identifier of the entity to be removed.
+ ///
+ /// A boolean value indicating whether the entity was successfully removed.
+ /// Returns true if the entity existed and was removed; otherwise, false.
+ ///
+ public bool DestroyEntity(ulong entityId)
+ {
+ var removedEntity = _entities.TryRemove(entityId, out _);
+ _components.TryRemove(entityId, out _);
+ _behaviors.TryRemove(entityId, out _);
+
+ return removedEntity;
+ }
+
+ #endregion
+
+ #region Component Management
+
+ /// Adds a component of the specified type to the given entity within the world.
+ /// This method registers the provided component instance and associates it with the entity ID.
+ /// If a component of the same type already exists for the entity, an exception is thrown.
+ /// The type of the component to add, implementing IComponent.
+ /// The unique identifier of the entity to which the component will be added.
+ /// A reference to the component instance that will be added to the entity.
+ /// Thrown if the entity with the specified ID does not exist in the world.
+ /// Thrown if a component of the same type already exists for the entity.
+ internal void AddComponent(ulong entityId, ref T component) where T : struct, IComponent
+ {
+ if (!_entities.ContainsKey(entityId))
+ {
+ throw new KeyNotFoundException($"Entity {entityId} does not exist in world.");
+ }
+
+ if (!_components.TryGetValue(entityId, out var components))
+ {
+ components = new();
+
+ _components[entityId] = components;
+ }
+
+
+ if (!components.TryAdd(component.GetType(), component))
+ {
+ throw new InvalidOperationException($"Component of type {component.GetType()} already exists for entity {entityId}");
+ }
+ }
+
+ /// Creates and registers a new component of the specified type for the given entity.
+ /// The method initializes the component with its default values and associates it with
+ /// the provided entity ID. This allows the component to be included in the entity's data
+ /// structure, making it accessible for subsequent operations such as retrieval or updates.
+ /// Throws an exception if a component of the same type is already associated with the entity.
+ ///
+ /// The type of the component to create and associate with the entity. The type must
+ /// implement the IComponent interface and be a value type.
+ ///
+ ///
+ /// The unique identifier of the entity to which the new component will be associated.
+ ///
+ public void CreateComponent(ulong entityId) where T : struct, IComponent
+ {
+ T component = new T();
+ AddComponent(entityId, ref component);
+ }
+
+ /// Removes a specified component from the entity identified by the given entity ID.
+ /// This method ensures that the component of the specified type is removed only if the entity exists
+ /// and the component of the given type is currently associated with the entity.
+ ///
+ /// The type of the component to be removed. Must implement the IComponent interface.
+ ///
+ ///
+ /// The unique identifier of the entity from which the component is to be removed.
+ ///
+ ///
+ /// True if the component was successfully removed; otherwise, false. Returns false if the entity
+ /// does not exist or if the specified component is not associated with the entity.
+ ///
+ public bool RemoveComponent(ulong entityId) where T : IComponent
+ {
+ if (!_entities.ContainsKey(entityId))
+ {
+ return false;
+ }
+
+ if (_components.TryGetValue(entityId, out var components))
+ {
+
+ return components.TryRemove(typeof(T), out _);
+ }
+
+ return false;
+ }
+
+ /// Attempts to retrieve a component of the specified type associated with the given entity ID.
+ /// If the entity exists and has a component of the specified type, the method outputs the component
+ /// and returns true. Otherwise, the method outputs the default value for the component type and returns false.
+ ///
+ /// The type of the component to retrieve. Must be a struct and implement the IComponent interface.
+ ///
+ ///
+ /// The ID of the entity from which the component should be retrieved.
+ ///
+ ///
+ /// When the method returns, contains the component of the specified type if retrieval was successful;
+ /// otherwise, contains the default value for the specified type.
+ ///
+ ///
+ /// True if the entity exists and has a component of the specified type; otherwise, false.
+ ///
+ public bool TryGetComponent(ulong entityId, out T component) where T : struct, IComponent
+ {
+ if (!_entities.ContainsKey(entityId))
+ {
+ component = default;
+ return false;
+ }
+
+ if (_components.TryGetValue(entityId, out var components))
+ {
+ components.TryGetValue(typeof(T), out var componentObj);
+ if (componentObj is not null && componentObj is T typedComponent)
+ {
+ component = typedComponent;
+ return true;
+ }
+
+ }
+
+ component = default;
+ return false;
+ }
+
+ /// Attempts to update a component of the specified type for the given entity with the provided value.
+ /// If the entity exists and the component type is associated with the entity, the component value is updated,
+ /// and the method returns true. If the entity does not exist or the component type is not associated with the entity,
+ /// the method returns false.
+ ///
+ /// The unique identifier of the entity for which the component is to be updated.
+ ///
+ ///
+ /// The new value of the component to update. Passed by reference.
+ ///
+ ///
+ /// The type of the component to be updated. Must implement the IComponent interface.
+ ///
+ ///
+ /// true if the component was successfully updated; otherwise, false.
+ ///
+ public bool TryUpdateComponent(ulong entityId, ref T component) where T : struct, IComponent
+ {
+ if (!_entities.ContainsKey(entityId))
+ {
+ return false;
+ }
+
+ if (_components.TryGetValue(entityId, out var components))
+ {
+ components[typeof(T)] = component;
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ #region Behavior Management
+
+ /// Adds a behavior to the specified entity within the world.
+ /// The behavior is associated with the provided entity ID and stored in the world's behavior collection.
+ /// The method ensures that the behavior is assigned to both the entity and the world,
+ /// and validates that there are no duplicate behaviors of the same type for the entity.
+ ///
+ /// The type of the behavior to be added. Must be a subclass of Behavior.
+ ///
+ ///
+ /// The unique identifier of the entity to which the behavior is added.
+ ///
+ ///
+ /// The behavior instance to be added to the entity.
+ ///
+ ///
+ /// Thrown if no entity with the specified ID exists in the world.
+ ///
+ ///
+ /// Thrown if a behavior of the same type already exists for the specified entity.
+ ///
+ public void AddBehavior(ulong entityId, T behavior) where T : Behavior
+ {
+ if (!_entities.TryGetValue(entityId, out var entity))
+ {
+ throw new KeyNotFoundException($"Entity {entityId} does not exist in world.");
+ }
+
+ if (!_behaviors.TryGetValue(entityId, out var behaviors))
+ {
+ behaviors = new();
+ _behaviors[entityId] = behaviors;
+ }
+
+ behavior.Entity = entity;
+ behavior.World = this;
+
+ if (!behaviors.TryAdd(behavior.GetType(), behavior))
+ {
+ throw new InvalidOperationException($"Behavior of type {behavior.GetType()} already exists for entity {entityId}");
+ }
+ }
+
+ /// Attempts to retrieve a behavior of the specified type associated with a given entity ID.
+ /// If the behavior exists and is of the requested type, it is returned in the output parameter.
+ /// Otherwise, the output parameter is set to null, and the method returns false.
+ /// The type of the behavior to retrieve. Must inherit from the Behavior class.
+ /// The unique identifier for the entity whose behavior is being retrieved.
+ ///
+ /// When the method completes, this parameter contains the behavior of the requested type if it exists;
+ /// otherwise, it is set to null.
+ ///
+ ///
+ /// True if the specified behavior is found and is of the requested type; otherwise, false.
+ ///
+ public bool TryGetBehavior(ulong entityId, [NotNullWhen(true)] out T? behavior) where T : Behavior
+ {
+ if (!_entities.ContainsKey(entityId))
+ {
+ behavior = null;
+ return false;
+ }
+
+ if (_behaviors.TryGetValue(entityId, out var behaviors))
+ {
+ behaviors.TryGetValue(typeof(T), out var behaviorObj);
+ if (behaviorObj is T typedBehavior)
+ {
+ behavior = typedBehavior;
+ return true;
+ }
+ }
+
+ behavior = null;
+ return false;
+ }
+
+ /// Removes a behavior of the specified type from the given entity.
+ /// If the entity exists and the behavior is found, it is removed from the
+ /// collection of behaviors associated with the entity.
+ ///
+ /// The type of behavior to be removed, which must inherit from the Behavior class.
+ ///
+ ///
+ /// The unique identifier of the entity from which the behavior should be removed.
+ ///
+ ///
+ /// True if the behavior was successfully removed; otherwise, false.
+ ///
+ public bool RemoveBehavior(ulong entityId) where T : Behavior
+ {
+ if (!_entities.ContainsKey(entityId))
+ {
+ return false;
+ }
+
+ if (_behaviors.TryGetValue(entityId, out var behaviors))
+ {
+ return behaviors.TryRemove(typeof(T), out _);
+ }
+
+ return false;
+ }
+
+ /// Executes the update process for all behaviors registered within the world.
+ /// Each behavior's `Update` method is called with the provided delta time to
+ /// perform time-based logic or operations. If a behavior throws an exception
+ /// during its update, the process continues with the remaining behaviors.
+ /// The time, in seconds, since the last update.
+ public void ExecuteUpdate(float deltaTime)
+ {
+ foreach (var behaviors in _behaviors.Values)
+ foreach (var behavior in behaviors.Values)
+ {
+ try
+ {
+ behavior.Update(deltaTime);
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine(e);
+ }
+
+ }
+ }
+
+ /// Executes the FixedUpdate lifecycle step for all registered behaviors in the world.
+ /// This method iterates through all behaviors associated with entities and invokes their
+ /// FixedUpdate method with the provided delta time.
+ /// If an exception is thrown during the execution of a behavior's FixedUpdate method,
+ /// it is caught and logged to the error output stream without interrupting the remaining updates.
+ ///
+ /// The time interval, in seconds, that has elapsed since the previous FixedUpdate call.
+ /// This value is passed to each behavior's FixedUpdate method to support time-dependent updates.
+ ///
+ public void ExecuteFixedUpdate(float deltaTime)
+ {
+ foreach (var behaviors in _behaviors.Values)
+ foreach (var behavior in behaviors.Values)
+ {
+ try
+ {
+ behavior.FixedUpdate(deltaTime);
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine(e);
+ }
+ }
+ }
+
+ /// Executes the LateUpdate phase for all registered behaviors in the world.
+ /// This method iterates through all behaviors associated with entities in the world
+ /// and invokes their LateUpdate method, passing the provided delta time.
+ /// Any exceptions thrown during the execution of a behavior's LateUpdate method
+ /// are caught and logged to the error output.
+ ///
+ /// The time interval, in seconds, since the last LateUpdate cycle.
+ /// This value is provided to each behavior to adjust their logic based on the elapsed time.
+ ///
+ public void ExecuteLateUpdate(float deltaTime)
+ {
+ foreach (var behaviors in _behaviors.Values)
+ foreach (var behavior in behaviors.Values)
+ {
+ try
+ {
+ behavior.LateUpdate(deltaTime);
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine(e);
+ }
+ }
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/SourceGenerators/Draconic.Dynamics/Draconic.Dynamics.Sourcegen/Draconic.Dynamics.Sourcegen.csproj b/SourceGenerators/Draconic.Dynamics/Draconic.Dynamics.Sourcegen/Draconic.Dynamics.Sourcegen.csproj
new file mode 100644
index 00000000..1894925d
--- /dev/null
+++ b/SourceGenerators/Draconic.Dynamics/Draconic.Dynamics.Sourcegen/Draconic.Dynamics.Sourcegen.csproj
@@ -0,0 +1,27 @@
+
+
+
+ netstandard2.0
+ false
+ enable
+ latest
+
+ true
+ true
+ Draconic.Dynamics.Sourcegen
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
diff --git a/SourceGenerators/Draconic.Dynamics/Draconic.Dynamics.Tests/Draconic.Dynamics.Tests.csproj b/SourceGenerators/Draconic.Dynamics/Draconic.Dynamics.Tests/Draconic.Dynamics.Tests.csproj
new file mode 100644
index 00000000..f8ae0a4d
--- /dev/null
+++ b/SourceGenerators/Draconic.Dynamics/Draconic.Dynamics.Tests/Draconic.Dynamics.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net10.0
+ enable
+
+ false
+
+ Draconic.Dynamics.Tests
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+