diff --git a/README.md b/README.md index 5c6c8c7..1bab07a 100644 --- a/README.md +++ b/README.md @@ -2,277 +2,80 @@ [![Test](https://github.com/simulation-tree/simulation/actions/workflows/test.yml/badge.svg)](https://github.com/simulation-tree/simulation/actions/workflows/test.yml) -Library providing a way to organize systems, and executing programs. +Library providing a way to organize and update systems with support for message handling. ### Running simulators -Simulators contain and update systems, and iterate over programs found -in the world that the simulator is created with: +Simulators contain and update systems: ```cs -public static int Main() +public static void Main() { - StatusCode statusCode; - using (World world = new()) - { - using (Simulator simulator = new(world)) - { - simulator.AddSystem(new AllSystems()); - using (Program program = Program.Create(world, new ExampleProgram(100))) - { - while (!program.IsFinished(out statusCode)) - { - simulator.Update(); - } - - Console.WriteLine(program.Read().value); - } - - simulator.RemoveSystem(); - } - } + using World world = new(); + using Simulator simulator = new(world); - return statusCode.IsSuccess ? 0 : 1; + simulator.Add(new ProgramSystems()); + simulator.Update(world); + simulator.Remove(); } -``` - -### Order of operations - -Systems take precedence over programs: - -**Starting and Updating** - -Systems will always be started and updated before programs. Iterating with the simulator -world first, and then with each program world in the order they were created. - -**Finishing** - -Same as starting/updating but flipped. Systems finish before programs are, and with -program worlds in reverse order, then with simulator world last. -**Messages** - -When messages are given to a simulator to handle, systems will be called with the -simulator world, and then program worlds last. - -### Systems - -Systems are defined by implementing the `ISystem` interface, and are responsible -for initializing, updating, and finalizing the system with all worlds that the -simulator is aware of. - -```cs -public readonly partial struct ExampleSystem : ISystem +public class ProgramSystems : ISystem, IDisposable { - void IDisposable.Dispose() + public ProgramSystems() { + //initialize } - void ISystem.Start(in SystemContext context, in World world) + public void Dispose() { + //clean up } - void ISystem.Update(in SystemContext context, in World world, in TimeSpan delta) - { - } - - void ISystem.Finish(in SystemContext context, in World world) + void ISystem.Update(Simulator simulator, double deltaTime) { + //do work } } ``` -### Initializing systems with dependent data +### Receiving messages -When systems are added to a simulator world, their constructor can be used to customize -the initial data. In the case that they need information from the simulator itself, the -state of the system can be overwritten using the `Write()` function in `Start()`: +A system that is partial, and implementing the `IListener` interface will +allow it to receive messages broadcast by the simulator: ```cs -public readonly partial struct ExampleSystem : ISystem -{ - private readonly int initialData; - private readonly World simulatorWorld; - private readonly List numbers; - - [Obsolete("Default constructor not supported", true)] - public ExampleSystem() { } //doing this to enforce the public constructor below +simulator.Broadcast(32f); +simulator.Broadcast(32f); +simulator.Broadcast(32f); - public ExampleSystem(int initialData) - { - this.initialData = initialData; - } - - private ExampleSystem(int initialData, World simulatorWorld) - { - this.initialData = initialData; - this.simulatorWorld = simulatorWorld; - numbers = new(); - } - - void ISystem.Start(in SystemContext context, in World world) - { - if (context.IsSimulatorWorld(world)) - { - context.Write(new ExampleSystem(initialData, context.SimulatorWorld)); - } - } - - ... -} -``` - -### Sub systems - -When a system intends to have sub systems, and to act as a high level wrapper. -The `Start()` and `Finish()` functions are used to add and remove the sub systems: -```cs -public readonly partial struct AllSystems : ISystem +public partial class ListenerSystem : IListener { - void IDisposable.Dispose() - { - } - - void ISystem.Start(in SystemContext context, in World world) - { - if (context.IsSimulatorWorld(world)) - { - context.AddSystem(new ExampleSystem(100)); - } - } - - void ISystem.Update(in SystemContext context, in World world, in TimeSpan delta) + void IListener.Receive(ref float message) { - } - - void ISystem.Finish(in SystemContext context, in World world) - { - if (context.IsSimulatorWorld(world)) - { - context.RemoveSystem(); - } + //do something with this } } ``` -Notice that the systems aren't added/removed in the constructor/dispose. But instead, -they're added through the start/finish. This is because the `SystemContext` has functionality -for checking if initializing world is the simulator world, which will always be first and last. - -### Programs - -Programs are entities created in the simulator's world, and are defined by implementing -the `IProgram` interface. They expose functions that are called by the simulator, and -contain their own world separate from the simulator's: +Messages can also be broadcast by reference, allowing systems to modify them, +and use it to communicate between different projects: ```cs -public partial struct ExampleProgram : IProgram -{ - public readonly int initialValue; - public int value; - - public ExampleProgram(int initialValue) - { - this.initialValue = initialValue; - value = initialValue; - } - - void IProgram.Start(ref ExampleProgram program, in Simulator simulator, in World world) - { - } - - StatusCode IProgram.Update(in TimeSpan delta) - { - if (value > 200) - { - return StatusCode.Success(0); - } - - value++; - return StatusCode.Continue; - } +LoadRequest request = new(); +simulator.Broadcast(ref request); +Assert.That(request.loaded, Is.True); - void IProgram.Finish(in StatusCode statusCode) - { - } -} -``` - -### Finishing programs - -A program is finished when the status code from `Update()` is neither `default` or `StatusCode.Continue`. -This status code will then be available in `Finish()`. If the program is finished because -the simulator is disposed before the program decides to, the status code will be `StatusCode.Termination`. - -### Message handling - -Systems can also be message handlers. This is done by implementing the `ISystem.CollectMessageHandlers()` -function, and then adding all of the handler functions for each type of message: - -```cs -public struct StoreNumber +public partial class LoadSystem : IListener { - public double number; - - public StoreNumber(double number) + void IListener.Receive(ref LoadRequest message) { - this.number = number; + message.loaded = true; } } -public readonly struct ExampleSystem : ISystem +public struct LoadRequest { - unsafe readonly void ISystem.CollectMessageHandlers(MessageHandlerCollector collectors) - { - collectors.Add(&HandleStoreNumber); - } - - [UnmanagedCallersOnly] - private static StatusCode HandleStoreNumber(HandleMessage.Input input) - { - ref StoreNumber message = ref input.ReadMessage(); - if (message.number < 0) - { - return StatusCode.Failure(0); //cant store negative numbers - } - else - { - ref ExampleSystem system = ref input.ReadSystem(); - system.numbers.Add(message.number); - return StatusCode.Success(0); //successfully stored the number - } - } + public bool loaded; } ``` -The intended return values are: -- `StatusCode.Continue`: The message was received, but no handling was done -- `StatusCode.Success`: Message was handled and completed the objective -- `StatusCode.Failure`: Message was handled and attempting to complete it failed -- `default`: Reserved to indicate that the message was not received by the system - -### Message requesting - -Assuming there's a system that handles `StoreNumber` and none to handle `double` -messages: -```cs -//positive numbers do get added -StatusCode result = contextOrSimulator.TryHandleMessage(new StoreNumber(13.37)); -Assert.That(result.IsSuccess, Is.True); -Assert.That(result.IsFailure, Is.False); - -//negative numbers not allowed -result = contextOrSimulator.TryHandleMessage(new StoreNumber(-1.0)); -Assert.That(result.IsSuccess, Is.False); -Assert.That(result.IsFailure, Is.True); - -//when no system is registered to handle the message type of double -result = contextOrSimulator.TryHandleMessage(13.37); -Assert.That(result.IsSuccess, Is.False); -Assert.That(result.IsFailure, Is.False); -Assert.That(result == default, Is.True); -``` - -Messages can also be passed by referenced in case the message contains -mutable data. - ### Contributing and design This library is created for composing behaviour of programs using systems, ideally created by diff --git a/core/Components/IsProgram.cs b/core/Components/IsProgram.cs deleted file mode 100644 index a9d76b8..0000000 --- a/core/Components/IsProgram.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Simulation.Functions; -using Unmanaged; -using Worlds; - -namespace Simulation.Components -{ - /// - /// Stores functions to start, update, and finish a program. - /// - public struct IsProgram - { - /// - /// Starts the program. - /// - public readonly StartProgram start; - - /// - /// Updates the program. - /// - public readonly UpdateProgram update; - - /// - /// Finishes the program. - /// - public readonly FinishProgram finish; - - /// - /// Size of the type that the program operates on. - /// - public readonly ushort typeSize; - - /// - /// The current or last status code of the program. - /// - public StatusCode statusCode; - - /// - /// State of the program. - /// - public State state; - - /// - /// The allocation of the program. - /// - public readonly MemoryAddress allocation; - - /// - /// The world that was created for and belongs to the program. - /// - public readonly World world; - - /// - /// Initializes a new instance of the struct. - /// - public IsProgram(StartProgram start, UpdateProgram update, FinishProgram finish, ushort typeSize, MemoryAddress allocation, World world) - { - statusCode = default; - this.start = start; - this.update = update; - this.finish = finish; - this.typeSize = typeSize; - this.state = State.Uninitialized; - this.allocation = allocation; - this.world = world; - } - - /// - /// Describes the state of a program. - /// - public enum State : byte - { - /// - /// A has not initialized the program. - /// - Uninitialized, - - /// - /// The program is currently active and running. - /// - Active, - - /// - /// The program has finished running. - /// - Finished - } - } -} \ No newline at end of file diff --git a/core/Exceptions/SystemMissingFunctionsException.cs b/core/Exceptions/SystemMissingFunctionsException.cs deleted file mode 100644 index 4f89cdf..0000000 --- a/core/Exceptions/SystemMissingFunctionsException.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Simulation.Functions; -using System; - -namespace Simulation.Exceptions -{ - /// - /// Exception thrown when a system is being added, and is missing one or more - /// needed functions. - /// - public class SystemMissingFunctionsException : Exception - { - /// - public SystemMissingFunctionsException(Type systemType, StartSystem start, UpdateSystem update, FinishSystem finish, DisposeSystem dispose) - : base(GetMessage(systemType, start, update, finish, dispose)) - { - } - - private static string GetMessage(Type systemType, StartSystem start, UpdateSystem update, FinishSystem finish, DisposeSystem dispose) - { - if (start == default && update == default && finish == default && dispose == default) - { - return $"The system `{systemType.Name}` is missing all required functions"; - } - else - { - string message = $"The system `{systemType.Name}` is missing the required function(s): "; - if (start == default) - { - message += $"`{nameof(StartSystem)}`, "; - } - - if (update == default) - { - message += $"`{nameof(UpdateSystem)}`, "; - } - - if (finish == default) - { - message += $"`{nameof(FinishSystem)}`, "; - } - - if (dispose == default) - { - message += $"`{nameof(DisposeSystem)}`, "; - } - - return message.TrimEnd(',', ' '); - } - } - } -} \ No newline at end of file diff --git a/core/Extensions/ProgramExtensions.cs b/core/Extensions/ProgramExtensions.cs deleted file mode 100644 index 80986d4..0000000 --- a/core/Extensions/ProgramExtensions.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Simulation.Components; -using System; -using Worlds; - -namespace Simulation -{ - /// - /// Extensions for instances. - /// - public static class ProgramExtensions - { - /// - public static void Start(ref T program, in Simulator simulator, in World world) where T : unmanaged, IProgram - { - program.Start(ref program, in simulator, in world); - } - - /// - public static StatusCode Update(ref T program, in TimeSpan delta) where T : unmanaged, IProgram - { - return program.Update(in delta); - } - - /// - public static void Finish(ref T program, in StatusCode statusCode) where T : unmanaged, IProgram - { - program.Finish(statusCode); - } - - /// - /// Checks if the program has finished running - /// and outputs the if finished. - /// - public static bool IsFinished(this T program, out StatusCode statusCode) where T : unmanaged, IProgramEntity - { - ref IsProgram component = ref program.AsEntity().GetComponent(); - if (component.state == IsProgram.State.Finished) - { - statusCode = component.statusCode; - return true; - } - else - { - statusCode = default; - return false; - } - } - - /// - /// Marks this program as uninitialized, and to have itself restarted - /// by a . - /// - public static void Restart(this ref T program) where T : unmanaged, IProgramEntity - { - ref IsProgram component = ref program.AsEntity().GetComponent(); - component.state = IsProgram.State.Uninitialized; - } - } -} \ No newline at end of file diff --git a/core/Extensions/SystemExtensions.cs b/core/Extensions/SystemExtensions.cs deleted file mode 100644 index ef4a914..0000000 --- a/core/Extensions/SystemExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Worlds; - -namespace Simulation -{ - /// - /// Extensions for systems. - /// - public static class SystemExtensions - { - /// - public static void Start(ref T system, in SystemContainer systemContainer, in World world) where T : unmanaged, ISystem - { - system.Start(new SystemContext(systemContainer), in world); - } - - /// - public static void Update(ref T system, in SystemContainer systemContainer, in World world, in TimeSpan delta) where T : unmanaged, ISystem - { - system.Update(new SystemContext(systemContainer), in world, in delta); - } - - /// - public static void Finish(ref T system, in SystemContainer systemContainer, in World world) where T : unmanaged, ISystem - { - system.Finish(new SystemContext(systemContainer), in world); - } - - /// - public static void Dispose(ref T system) where T : unmanaged, ISystem - { - system.Dispose(); - } - } -} \ No newline at end of file diff --git a/core/Functions/DisposeSystem.cs b/core/Functions/DisposeSystem.cs deleted file mode 100644 index 6451964..0000000 --- a/core/Functions/DisposeSystem.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using Worlds; - -namespace Simulation.Functions -{ - /// - /// The function pointer. - /// - public unsafe readonly struct DisposeSystem : IEquatable - { -#if NET - private readonly delegate* unmanaged value; - - /// - public DisposeSystem(delegate* unmanaged value) - { - this.value = value; - } -#else - private readonly delegate* value; - - /// - public DisposeSystem(delegate* value) - { - this.value = value; - } -#endif - /// - public override string ToString() - { - if ((nint)value == default) - { - return "Default"; - } - else - { - return nameof(DisposeSystem); - } - } - - /// - public readonly void Invoke(SystemContainer container, World world) - { - value(container, world); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is DisposeSystem system && Equals(system); - } - - /// - public readonly bool Equals(DisposeSystem other) - { - return (nint)value == (nint)other.value; - } - - /// - public readonly override int GetHashCode() - { - return ((nint)value).GetHashCode(); - } - - /// - public static bool operator ==(DisposeSystem left, DisposeSystem right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(DisposeSystem left, DisposeSystem right) - { - return !(left == right); - } - } -} \ No newline at end of file diff --git a/core/Functions/FinishProgram.cs b/core/Functions/FinishProgram.cs deleted file mode 100644 index a5f5ab1..0000000 --- a/core/Functions/FinishProgram.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Diagnostics; -using Unmanaged; -using Worlds; - -namespace Simulation.Functions -{ - /// - /// A function that finishes a program. - /// - public unsafe readonly struct FinishProgram : IEquatable - { -#if NET - private readonly delegate* unmanaged function; - - /// - /// Creates a new . - /// - public FinishProgram(delegate* unmanaged function) - { - this.function = function; - } -#else - private readonly delegate* function; - - public FinishProgram(delegate* function) - { - this.function = function; - } -#endif - - /// - /// Invokes the function. - /// - public readonly void Invoke(Simulator simulator, MemoryAddress allocation, World world, StatusCode statusCode) - { - ThrowIfDefault(); - - function(simulator, allocation, world, statusCode); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is FinishProgram function && Equals(function); - } - - /// - public readonly bool Equals(FinishProgram other) - { - nint a = (nint)function; - nint b = (nint)other.function; - return a == b; - } - - /// - public readonly override int GetHashCode() - { - return ((nint)function).GetHashCode(); - } - - [Conditional("DEBUG")] - private readonly void ThrowIfDefault() - { - if (function == default) - { - throw new InvalidOperationException("Finish program function is not initialized"); - } - } - - /// - public static bool operator ==(FinishProgram left, FinishProgram right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(FinishProgram left, FinishProgram right) - { - return !(left == right); - } - } -} \ No newline at end of file diff --git a/core/Functions/FinishSystem.cs b/core/Functions/FinishSystem.cs deleted file mode 100644 index 244dd99..0000000 --- a/core/Functions/FinishSystem.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using Worlds; - -namespace Simulation.Functions -{ - /// - /// The function pointer. - /// - public unsafe readonly struct FinishSystem : IEquatable - { -#if NET - private readonly delegate* unmanaged value; - - /// - public FinishSystem(delegate* unmanaged value) - { - this.value = value; - } -#else - private readonly delegate* value; - - /// - public FinishSystem(delegate* value) - { - this.value = value; - } -#endif - /// - public override string ToString() - { - if ((nint)value == default) - { - return "Default"; - } - else - { - return nameof(FinishSystem); - } - } - - /// - /// Calls this function. - /// - public readonly void Invoke(SystemContainer container, World world) - { - value(container, world); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is FinishSystem system && Equals(system); - } - - /// - public readonly bool Equals(FinishSystem other) - { - return (nint)value == (nint)other.value; - } - - /// - public readonly override int GetHashCode() - { - return ((nint)value).GetHashCode(); - } - - /// - public static bool operator ==(FinishSystem left, FinishSystem right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(FinishSystem left, FinishSystem right) - { - return !(left == right); - } - } -} \ No newline at end of file diff --git a/core/Functions/HandleMessage.cs b/core/Functions/HandleMessage.cs deleted file mode 100644 index b6fbda3..0000000 --- a/core/Functions/HandleMessage.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Simulation.Exceptions; -using System; -using System.Diagnostics; -using Types; -using Unmanaged; -using Worlds; - -namespace Simulation.Functions -{ - /// - /// A function that handles a sent message. - /// - public unsafe readonly struct HandleMessage : IEquatable - { -#if NET - private readonly delegate* unmanaged value; - - /// - /// Creates a new instance. - /// - public HandleMessage(delegate* unmanaged value) - { - this.value = value; - } -#else - private readonly delegate* value; - - public HandleMessage(delegate* value) - { - this.value = value; - } -#endif - /// - /// Invokes the function. - /// - public readonly StatusCode Invoke(SystemContainer container, World world, MemoryAddress message, TypeMetadata messageType) - { - return value(new(container, world, message, messageType)); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is HandleMessage function && Equals(function); - } - - /// - public readonly bool Equals(HandleMessage other) - { - return ((nint)value) == ((nint)other.value); - } - - /// - public readonly override int GetHashCode() - { - return ((nint)value).GetHashCode(); - } - - /// - public static bool operator ==(HandleMessage left, HandleMessage right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(HandleMessage left, HandleMessage right) - { - return !(left == right); - } - - /// - /// Input for the function. - /// - public readonly struct Input - { - /// - /// The system that is handling the message, and registered - /// the handler. - /// - public readonly SystemContainer system; - - /// - /// The world the message is being handled with. - /// - public readonly World world; - - private readonly MemoryAddress data; - private readonly TypeMetadata messageType; - - /// - /// The simulator where the message is being handled from. - /// - public readonly Simulator Simulator => system.simulator; - - /// - public Input(SystemContainer system, World world, MemoryAddress data, TypeMetadata messageType) - { - this.system = system; - this.world = world; - this.data = data; - this.messageType = messageType; - } - - [Conditional("DEBUG")] - private readonly void ThrowIfMessageTypeMismatch() where T : unmanaged - { - if (!messageType.Is()) - { - throw new InvalidOperationException($"The message type {typeof(T)} does not match the expected type {messageType}"); - } - } - - [Conditional("DEBUG")] - private readonly void ThrowIfSystemTypeMismatch() where T : unmanaged - { - if (!system.type.Is()) - { - throw new SystemTypeMismatchException(typeof(T), system.type); - } - } - - /// - /// Reads the message being handled. - /// - public readonly ref T ReadMessage() where T : unmanaged - { - ThrowIfMessageTypeMismatch(); - - return ref data.Read(); - } - - /// - /// Reads the system instance that is handling the message. - /// - public readonly ref T ReadSystem() where T : unmanaged, ISystem - { - ThrowIfSystemTypeMismatch(); - - return ref system.Read(); - } - } - } -} \ No newline at end of file diff --git a/core/Functions/StartProgram.cs b/core/Functions/StartProgram.cs deleted file mode 100644 index 66340f9..0000000 --- a/core/Functions/StartProgram.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Diagnostics; -using Unmanaged; -using Worlds; - -namespace Simulation.Functions -{ - /// - /// A function that starts a program. - /// - public unsafe readonly struct StartProgram : IEquatable - { -#if NET - private readonly delegate* unmanaged function; - - /// - /// Initializes a new instance of the struct. - /// - public StartProgram(delegate* unmanaged function) - { - this.function = function; - } -#else - private readonly delegate* function; - - public StartProgram(delegate* function) - { - this.function = function; - } -#endif - /// - /// Invokes the function. - /// - public readonly void Invoke(Simulator simulator, MemoryAddress allocation, World world) - { - ThrowIfDefault(); - - function(simulator, allocation, world); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is StartProgram function && Equals(function); - } - - /// - public readonly bool Equals(StartProgram other) - { - nint a = (nint)function; - nint b = (nint)other.function; - return a == b; - } - - /// - public readonly override int GetHashCode() - { - return ((nint)function).GetHashCode(); - } - - [Conditional("DEBUG")] - private readonly void ThrowIfDefault() - { - if (function == default) - { - throw new InvalidOperationException("Start program function is not initialized"); - } - } - - /// - public static bool operator ==(StartProgram left, StartProgram right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(StartProgram left, StartProgram right) - { - return !(left == right); - } - } -} \ No newline at end of file diff --git a/core/Functions/StartSystem.cs b/core/Functions/StartSystem.cs deleted file mode 100644 index 836c05f..0000000 --- a/core/Functions/StartSystem.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using Worlds; - -namespace Simulation.Functions -{ - /// - /// Describes a function that initializes a system. - /// - public unsafe readonly struct StartSystem : IEquatable - { -#if NET - private readonly delegate* unmanaged value; - - /// - /// Creates a new with the given . - /// - public StartSystem(delegate* unmanaged value) - { - this.value = value; - } - -#else - private readonly delegate* value; - - public StartSystem(delegate* value) - { - this.value = value; - } -#endif - /// - public override string ToString() - { - if ((nint)value == default) - { - return "Default"; - } - else - { - return nameof(StartSystem); - } - } - - /// - /// Invokes the function. - /// - public readonly void Invoke(SystemContainer container, World world) - { - value(container, world); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is StartSystem system && Equals(system); - } - - /// - public readonly bool Equals(StartSystem other) - { - return (nint)value == (nint)other.value; - } - - /// - public readonly override int GetHashCode() - { - return ((nint)value).GetHashCode(); - } - - /// - public static bool operator ==(StartSystem left, StartSystem right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(StartSystem left, StartSystem right) - { - return !(left == right); - } - } -} \ No newline at end of file diff --git a/core/Functions/UpdateProgram.cs b/core/Functions/UpdateProgram.cs deleted file mode 100644 index 254d0e4..0000000 --- a/core/Functions/UpdateProgram.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Diagnostics; -using Unmanaged; -using Worlds; - -namespace Simulation.Functions -{ - /// - /// A function that updates a program. - /// - public unsafe readonly struct UpdateProgram : IEquatable - { -#if NET - private readonly delegate* unmanaged function; - - /// - /// Creates a new . - /// - public UpdateProgram(delegate* unmanaged function) - { - this.function = function; - } -#else - private readonly delegate* function; - - public UpdateProgram(delegate* function) - { - this.function = function; - } -#endif - - /// - /// Invokes the function. - /// - public readonly StatusCode Invoke(Simulator simulator, MemoryAddress allocation, World world, TimeSpan delta) - { - ThrowIfDefault(); - - return function(simulator, allocation, world, delta); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is UpdateProgram function && Equals(function); - } - - /// - public readonly bool Equals(UpdateProgram other) - { - nint a = (nint)function; - nint b = (nint)other.function; - return a == b; - } - - /// - public readonly override int GetHashCode() - { - return ((nint)function).GetHashCode(); - } - - [Conditional("DEBUG")] - private readonly void ThrowIfDefault() - { - if (function == default) - { - throw new InvalidOperationException("Update program function is not initialized"); - } - } - - /// - public static bool operator ==(UpdateProgram left, UpdateProgram right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(UpdateProgram left, UpdateProgram right) - { - return !(left == right); - } - } -} \ No newline at end of file diff --git a/core/Functions/UpdateSystem.cs b/core/Functions/UpdateSystem.cs deleted file mode 100644 index f8cfce9..0000000 --- a/core/Functions/UpdateSystem.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using Worlds; - -namespace Simulation.Functions -{ - /// - /// A function that iterates over a system. - /// - public unsafe readonly struct UpdateSystem : IEquatable - { -#if NET - private readonly delegate* unmanaged value; - - /// - /// Creates a new iterate function. - /// - public UpdateSystem(delegate* unmanaged value) - { - this.value = value; - } -#else - private readonly delegate* value; - - public UpdateSystem(delegate* value) - { - this.value = value; - } -#endif - - /// - public override string ToString() - { - if ((nint)value == default) - { - return "Default"; - } - else - { - return nameof(UpdateSystem); - } - } - - /// - /// Invokes the function. - /// - public readonly void Invoke(SystemContainer container, World world, TimeSpan delta) - { - value(container, world, delta); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is UpdateSystem system && Equals(system); - } - - /// - public readonly bool Equals(UpdateSystem other) - { - return (nint)value == (nint)other.value; - } - - /// - public readonly override int GetHashCode() - { - return ((nint)value).GetHashCode(); - } - - /// - public static bool operator ==(UpdateSystem left, UpdateSystem right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(UpdateSystem left, UpdateSystem right) - { - return !(left == right); - } - } -} \ No newline at end of file diff --git a/core/IListener.cs b/core/IListener.cs new file mode 100644 index 0000000..e1fd5c4 --- /dev/null +++ b/core/IListener.cs @@ -0,0 +1,18 @@ +using System; + +namespace Simulation +{ + public interface IListener + { + public int Count => 0; + + public void CollectMessageHandlers(Span receivers) + { + } + } + + public interface IListener : IListener where T : unmanaged + { + void Receive(ref T message); + } +} \ No newline at end of file diff --git a/core/ISystem.cs b/core/ISystem.cs new file mode 100644 index 0000000..f105f84 --- /dev/null +++ b/core/ISystem.cs @@ -0,0 +1,7 @@ +namespace Simulation +{ + public interface ISystem + { + void Update(Simulator simulator, double deltaTime); + } +} \ No newline at end of file diff --git a/core/Message.cs b/core/Message.cs new file mode 100644 index 0000000..be0d804 --- /dev/null +++ b/core/Message.cs @@ -0,0 +1,30 @@ +using System; +using Types; +using Unmanaged; + +namespace Simulation +{ + public readonly struct Message : IDisposable + { + public readonly TypeMetadata type; + public readonly MemoryAddress data; + + public Message(MemoryAddress data, TypeMetadata type) + { + this.data = data; + this.type = type; + } + + public void Dispose() + { + MemoryAddress.ThrowIfDefault(data); + + data.Dispose(); + } + + public static Message Create(T message) where T : unmanaged + { + return new(MemoryAddress.AllocateValue(message), TypeMetadata.GetOrRegister()); + } + } +} \ No newline at end of file diff --git a/core/MessageHandler.cs b/core/MessageHandler.cs index d2f9c44..9f653aa 100644 --- a/core/MessageHandler.cs +++ b/core/MessageHandler.cs @@ -1,49 +1,21 @@ -using Simulation.Functions; -using System; -using Types; +using Types; namespace Simulation { - internal readonly struct MessageHandler : IEquatable + public readonly struct MessageHandler { - public readonly TypeMetadata systemType; - public readonly HandleMessage function; + public readonly TypeMetadata type; + public readonly MessageReceiver receiver; - public MessageHandler(TypeMetadata systemType, HandleMessage function) + public MessageHandler(TypeMetadata type, MessageReceiver receiver) { - this.systemType = systemType; - this.function = function; + this.type = type; + this.receiver = receiver; } - public readonly override bool Equals(object? obj) + public static MessageHandler Get(L listener) where L : IListener where M : unmanaged { - return obj is MessageHandler handler && Equals(handler); - } - - public readonly bool Equals(MessageHandler other) - { - return systemType == other.systemType && function == other.function; - } - - public readonly override int GetHashCode() - { - unchecked - { - int hash = 17; - hash = hash * 23 + systemType.GetHashCode(); - hash = hash * 23 + function.GetHashCode(); - return hash; - } - } - - public static bool operator ==(MessageHandler left, MessageHandler right) - { - return left.Equals(right); - } - - public static bool operator !=(MessageHandler left, MessageHandler right) - { - return !(left == right); + return new(TypeMetadata.GetOrRegister(), MessageReceiver.Get(listener)); } } } \ No newline at end of file diff --git a/core/MessageHandlerCollector.cs b/core/MessageHandlerCollector.cs deleted file mode 100644 index 619139e..0000000 --- a/core/MessageHandlerCollector.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Simulation.Functions; -using Types; - -namespace Simulation -{ - /// - /// Collector of message handlers. - /// - public readonly struct MessageHandlerCollector - { - private readonly TypeMetadata systemType; - private readonly MessageHandlers messageHandlers; - - internal MessageHandlerCollector(TypeMetadata systemType, MessageHandlers messageHandlers) - { - this.systemType = systemType; - this.messageHandlers = messageHandlers; - } - - /// - /// Adds the given to the list of message handlers for the given type . - /// - public readonly void Add(HandleMessage function) where T : unmanaged - { - TypeMetadata messageType = TypeMetadata.GetOrRegister(); - messageHandlers.Add(systemType, messageType, function); - } - -#if NET - /// - /// Adds the given to the list of message handlers for the given type . - /// - public unsafe readonly void Add(delegate* unmanaged function) where T : unmanaged - { - TypeMetadata messageType = TypeMetadata.GetOrRegister(); - messageHandlers.Add(systemType, messageType, new(function)); - } -#else - /// - /// Adds the given to the list of message handlers for the given type . - /// - public unsafe readonly void Add(delegate* function) where T : unmanaged - { - TypeMetadata messageType = TypeMetadata.GetOrRegister(); - messageHandlers.Add(systemType, messageType, new(function)); - } -#endif - } -} \ No newline at end of file diff --git a/core/MessageHandlers.cs b/core/MessageHandlers.cs deleted file mode 100644 index c03daef..0000000 --- a/core/MessageHandlers.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Collections.Generic; -using Simulation.Functions; -using System; -using Types; - -namespace Simulation -{ - internal readonly struct MessageHandlers : IDisposable - { - private readonly Dictionary> map; - - public MessageHandlers(int initialCapacity) - { - map = new(initialCapacity); - } - - public readonly void Dispose() - { - foreach (Array handlers in map.Values) - { - handlers.Dispose(); - } - - map.Dispose(); - } - - public readonly void Add(TypeMetadata systemType, TypeMetadata messageType, HandleMessage function) - { - ref Array handlers = ref map.TryGetValue(messageType, out bool contains); - if (!contains) - { - handlers = ref map.Add(messageType); - handlers = new(0); - } - - int length = handlers.Length; - handlers.Length = length + 1; - handlers[length] = new(systemType, function); - } - - public readonly bool TryGetValue(TypeMetadata messageType, out Array handlers) - { - return map.TryGetValue(messageType, out handlers); - } - } -} \ No newline at end of file diff --git a/core/MessageReceiver.cs b/core/MessageReceiver.cs new file mode 100644 index 0000000..1aa040f --- /dev/null +++ b/core/MessageReceiver.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.InteropServices; + +namespace Simulation +{ + public struct MessageReceiver : IDisposable + { + private GCHandle function; + + public readonly bool IsDisposed => !function.IsAllocated; + + public MessageReceiver(Action function) + { + this.function = GCHandle.Alloc(function, GCHandleType.Normal); + } + + public void Dispose() + { + function.Free(); + } + + public readonly void Invoke(Message message) + { + Action function = (Action)this.function.Target!; + function.Invoke(message); + } + + public static MessageReceiver Get(L listener) where L : IListener where M : unmanaged + { + return new MessageReceiver(Handle); + + void Handle(Message message) + { + listener.Receive(ref message.data.Read()); + } + } + } +} \ No newline at end of file diff --git a/core/MessageReceiverLocator.cs b/core/MessageReceiverLocator.cs new file mode 100644 index 0000000..8d69910 --- /dev/null +++ b/core/MessageReceiverLocator.cs @@ -0,0 +1,16 @@ +using Types; + +namespace Simulation +{ + internal struct MessageReceiverLocator + { + public TypeMetadata type; + public int index; + + public MessageReceiverLocator(TypeMetadata type, int index) + { + this.type = type; + this.index = index; + } + } +} \ No newline at end of file diff --git a/core/Pointers/SimulatorPointer.cs b/core/Pointers/SimulatorPointer.cs deleted file mode 100644 index 7f8ae47..0000000 --- a/core/Pointers/SimulatorPointer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Collections.Generic; -using System; -using Worlds; - -namespace Simulation.Pointers -{ - internal struct SimulatorPointer - { - public DateTime lastUpdateTime; - public int programComponent; - public World world; - public List systems; - public List programs; - public List activePrograms; - public Dictionary programsMap; - public MessageHandlers handlers; - } -} \ No newline at end of file diff --git a/core/Program.Entity.cs b/core/Program.Entity.cs deleted file mode 100644 index 89ee0d0..0000000 --- a/core/Program.Entity.cs +++ /dev/null @@ -1,31 +0,0 @@ -#if !NET -using Worlds; - -namespace Simulation -{ - /// - /// An entity that represents a program running in a , - /// operated by a . - /// - public readonly partial struct Program : IProgramEntity - { - public readonly World world; - public readonly uint value; - - public readonly void Dispose() - { - world.DestroyEntity(value); - } - - public readonly ref T GetComponent() where T : unmanaged - { - return ref world.GetComponent(value); - } - - public static implicit operator Entity(Program program) - { - return program.AsEntity(); - } - } -} -#endif \ No newline at end of file diff --git a/core/Program.cs b/core/Program.cs deleted file mode 100644 index f0e10d8..0000000 --- a/core/Program.cs +++ /dev/null @@ -1,163 +0,0 @@ -using Simulation.Components; -using Simulation.Functions; -using System; -using System.Diagnostics; -using Unmanaged; -using Worlds; - -namespace Simulation -{ - /// - /// An entity that represents a program running in a , - /// operated by a . - /// - public readonly partial struct Program : IProgramEntity - { - /// - /// State of the program. - /// - public readonly ref IsProgram.State State => ref GetComponent().state; - - /// - /// The world that belongs to this program. - /// - public readonly World ProgramWorld => GetComponent().world; - - /// - /// Retrieves the allocation that contains the program value. - /// - public readonly MemoryAddress Allocation => GetComponent().allocation; - - readonly void IEntity.Describe(ref Archetype archetype) - { - archetype.AddComponentType(); - } - - /// - /// Creates a new program in the given . - /// - public Program(World hostWorld, StartProgram start, UpdateProgram update, FinishProgram finish, ushort typeSize, MemoryAddress allocation) - { - Schema programSchema = hostWorld.Schema.Clone(); - World programWorld = new(programSchema); - world = hostWorld; - value = world.CreateEntity(new IsProgram(start, update, finish, typeSize, allocation, programWorld)); - } - - /// - /// Reads the program's data. - /// - public readonly ref T Read() where T : unmanaged - { - ThrowIfNotInitialized(); - - ref IsProgram program = ref GetComponent(); - return ref program.allocation.Read(); - } - - /// - /// Throws an if the program hans't been initialized - /// by a . - /// - /// - [Conditional("DEBUG")] - public readonly void ThrowIfNotInitialized() - { - if (State == IsProgram.State.Uninitialized) - { - throw new InvalidOperationException($"Program `{value}` is not yet initialized"); - } - } - - /// - /// Creates a new program in the given - /// initialized with the given . - /// - public static Program Create(World world, T program) where T : unmanaged, IProgram - { - (StartProgram start, UpdateProgram update, FinishProgram finish) = program.Functions; - if (start == default || update == default || finish == default) - { - throw new InvalidOperationException($"Program `{typeof(T)}` does not have all functions defined"); - } - - MemoryAddress allocation = MemoryAddress.AllocateValue(program); - return new(world, start, update, finish, allocation); - } - - /// - /// Creates a new uninitialized program in the given . - /// - public static Program Create(World world) where T : unmanaged, IProgram - { - return Create(world, default); - } - } - - /// - /// An entity that represents a program running in a , - /// operated by a . - /// - public unsafe readonly struct Program : IProgramEntity where T : unmanaged, IProgram - { - private readonly Program program; - - /// - /// State of the program. - /// - public readonly ref IsProgram.State State => ref program.State; - - /// - /// The value that represents this program type. - /// - public readonly ref T Value => ref program.Read(); - - readonly void IEntity.Describe(ref Archetype archetype) - { - archetype.Add(); - } - - /// - public Program(World world, StartProgram start, UpdateProgram update, FinishProgram finish, MemoryAddress allocation) - { - program = new(world, start, update, finish, (ushort)sizeof(T), allocation); - } - - /// - public Program(World world, T program) - { - ushort typeSize = (ushort)sizeof(T); - (StartProgram start, UpdateProgram update, FinishProgram finish) = program.Functions; - MemoryAddress allocation = MemoryAddress.AllocateValue(program); - this.program = new(world, start, update, finish, typeSize, allocation); - } - - /// - public Program(World world) - { - T program = new(); - ushort typeSize = (ushort)sizeof(T); - (StartProgram start, UpdateProgram update, FinishProgram finish) = program.Functions; - MemoryAddress allocation = MemoryAddress.AllocateValue(program); - this.program = new(world, start, update, finish, typeSize, allocation); - } - - /// - public readonly void Dispose() - { - program.Dispose(); - } - - /// - public static implicit operator Program(Program program) - { - return program.program; - } - - /// - public static implicit operator Entity(Program program) - { - return program.program; - } - } -} \ No newline at end of file diff --git a/core/ProgramContainer.cs b/core/ProgramContainer.cs deleted file mode 100644 index 7caef4d..0000000 --- a/core/ProgramContainer.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Simulation.Components; -using Simulation.Functions; -using System; -using Unmanaged; -using Worlds; - -namespace Simulation -{ - /// - /// Container for a program running in a , - /// operated by a . - /// - public struct ProgramContainer : IDisposable, IEquatable - { - /// - /// The function to start the program. - /// - public readonly StartProgram start; - - /// - /// The function to finish the program. - /// - public readonly FinishProgram finish; - - /// - /// The function to update the program. - /// - public readonly UpdateProgram update; - - /// - /// The that belongs to this program. - /// - public readonly World world; - - /// - /// The entity in the world that initialized this program. - /// - public readonly uint entity; - - /// - /// Native memory containing the program's data. - /// - public readonly MemoryAddress allocation; - - /// - /// State of the executing program. - /// - public IsProgram.State state; - - /// - /// Initializes a new instance of the struct. - /// - public ProgramContainer(uint entity, IsProgram.State state, IsProgram component, World world, MemoryAddress allocation) - { - this.entity = entity; - this.state = state; - start = component.start; - finish = component.finish; - update = component.update; - this.world = world; - this.allocation = allocation; - } - - /// - public readonly void Dispose() - { - world.Dispose(); - allocation.Dispose(); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is ProgramContainer container && Equals(container); - } - - /// - public readonly bool Equals(ProgramContainer other) - { - return world == other.world; - } - - /// - public readonly override int GetHashCode() - { - return world.GetHashCode(); - } - - /// - public static bool operator ==(ProgramContainer left, ProgramContainer right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(ProgramContainer left, ProgramContainer right) - { - return !(left == right); - } - } -} \ No newline at end of file diff --git a/core/Simulation.Core.csproj b/core/Simulation.Core.csproj index 658b4f7..778ab98 100644 --- a/core/Simulation.Core.csproj +++ b/core/Simulation.Core.csproj @@ -1,43 +1,36 @@ - + - - net9.0 - disable - enable - True - True - True - True - popcron - simulation-tree - Logic upon data - https://github.com/simulation-tree/simulation - README.md - ecs - Simulation Core - Simulation - - True - + + net9.0 + disable + enable + True + True + True + True + popcron + simulation-tree + Logic upon data + https://github.com/simulation-tree/simulation + README.md + ecs + Simulation Core + Simulation + + False + - - - True - \ - - + + + True + \ + + - - - - - Analyzer - false - - - Analyzer - false - - + + + + + \ No newline at end of file diff --git a/core/Simulator.cs b/core/Simulator.cs index c1dc6b3..66e578a 100644 --- a/core/Simulator.cs +++ b/core/Simulator.cs @@ -1,731 +1,400 @@ using Collections.Generic; -using Simulation.Components; -using Simulation.Exceptions; -using Simulation.Functions; -using Simulation.Pointers; using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Types; -using Unmanaged; using Worlds; namespace Simulation { /// - /// A simulator that manages systems and programs. + /// Contains systems for updating and broadcasting messages to. /// [SkipLocalsInit] - public unsafe struct Simulator : IDisposable, IEquatable + public struct Simulator : IDisposable { - private SimulatorPointer* simulator; - /// - /// The world that this simulator was created for. + /// The world this simulator is created for. /// - public readonly World World - { - get - { - MemoryAddress.ThrowIfDefault(simulator); + public readonly World world; - return simulator->world; - } - } + private Dictionary> receiversMap; + private List systems; + private DateTime lastUpdateTime; + private double runTime; /// - /// Checks if this simulator is disposed. + /// Checks if the simulator has been disposed. /// - public readonly bool IsDisposed => simulator is null; + public readonly bool IsDisposed => systems.IsDisposed; /// - /// Native address of this simulator. + /// The total amount of time the simulator has progressed. /// - public readonly nint Address => (nint)simulator; + public readonly double Time => runTime; /// - /// All active programs. + /// Amount of systems added. /// - public readonly ReadOnlySpan Programs - { - get - { - MemoryAddress.ThrowIfDefault(simulator); - - return simulator->activePrograms.AsSpan(); - } - } + public readonly int Count => systems.Count; /// - /// All added systems. + /// All systems added. /// - public readonly ReadOnlySpan Systems + public readonly System.Collections.Generic.IEnumerable Systems { get { - MemoryAddress.ThrowIfDefault(simulator); - - return simulator->systems.AsSpan(); + int count = systems.Count; + for (int i = 0; i < count; i++) + { + yield return GetSystem(i); + } } } #if NET - /// - /// Not supported. - /// [Obsolete("Default constructor not supported", true)] public Simulator() { } #endif - - /// - /// Initializes an existing simulator from the given . - /// - public Simulator(nint address) - { - simulator = (SimulatorPointer*)address; - } - /// - /// Initializes an existing simulator from the given . - /// - public Simulator(void* pointer) - { - simulator = (SimulatorPointer*)pointer; - } - - /// - /// Creates a new simulator with the given . + /// Creates a new simulator. /// public Simulator(World world) { - if (world.IsDisposed) - { - throw new ArgumentException("Attempting to create a simulator without a world"); - } - - simulator = MemoryAddress.AllocatePointer(); - simulator->world = world; - simulator->programComponent = world.Schema.GetComponentType(); - simulator->lastUpdateTime = DateTime.MinValue; - simulator->systems = new(4); - simulator->programs = new(4); - simulator->activePrograms = new(4); - simulator->programsMap = new(4); - simulator->handlers = new(4); - } - - /// - public readonly override string ToString() - { - return $"Simulator ({World})"; + this.world = world; + receiversMap = new(4); + systems = new(4); + runTime = 0; + lastUpdateTime = DateTime.UtcNow; } /// - /// Finalizes programs, disposes systems and the simulator itself. + /// Disposes the simulator. /// public void Dispose() { - MemoryAddress.ThrowIfDefault(simulator); - - World hostWorld = simulator->world; - StatusCode statusCode = StatusCode.Termination; - StartSystemsWithWorld(hostWorld); - FinishDestroyedPrograms(hostWorld, statusCode); - StartPrograms(hostWorld); - TerminateAllPrograms(hostWorld, statusCode); - - //dispose systems - Span systems = simulator->systems.AsSpan(); - for (int i = systems.Length - 1; i >= 0; i--) + Span systemsSpan = systems.AsSpan(); + foreach (SystemContainer system in systemsSpan) { - SystemContainer systemContainer = systems[i]; - if (systemContainer.parent == -1) - { - systemContainer.FinalizeAndDispose(); - } + system.Dispose(); } - //dispose programs - Span programs = simulator->programs.AsSpan(); - for (int i = programs.Length - 1; i >= 0; i--) + systems.Dispose(); + foreach (List receivers in receiversMap.Values) { - programs[i].Dispose(); + receivers.Dispose(); } - simulator->handlers.Dispose(); - simulator->programsMap.Dispose(); - simulator->activePrograms.Dispose(); - simulator->programs.Dispose(); - simulator->systems.Dispose(); - MemoryAddress.Free(ref simulator); + receiversMap.Dispose(); } - private readonly void TerminateAllPrograms(World hostWorld, StatusCode statusCode) + /// + /// Adds the given . + /// + public readonly void Add(T system) where T : ISystem { - int programComponent = simulator->programComponent; - foreach (Chunk chunk in hostWorld.Chunks) + SystemContainer container = SystemContainer.Create(system); + if (system is IListener listener) { - if (chunk.Definition.ContainsComponent(programComponent)) + int count = listener.Count; + Span handlers = stackalloc MessageHandler[count]; + listener.CollectMessageHandlers(handlers); + foreach (MessageHandler handler in handlers) { - ComponentEnumerator programs = chunk.GetComponents(programComponent); - for (int i = 0; i < programs.length; i++) + ref List receivers = ref receiversMap.TryGetValue(handler.type, out bool contains); + if (!contains) { - ref IsProgram program = ref programs[i]; - if (program.state != IsProgram.State.Finished) - { - program.state = IsProgram.State.Finished; - program.finish.Invoke(this, program.allocation, program.world, statusCode); - } + receivers = ref receiversMap.Add(handler.type); + receivers = new(4); } - } - } - } - - private readonly void FinishDestroyedPrograms(World hostWorld, StatusCode statusCode) - { - for (int p = simulator->programs.Count - 1; p >= 0; p--) - { - ref ProgramContainer containerInList = ref simulator->programs[p]; - if (containerInList.state != IsProgram.State.Finished && !hostWorld.ContainsEntity(containerInList.entity)) - { - containerInList.state = IsProgram.State.Finished; - containerInList.finish.Invoke(this, containerInList.allocation, containerInList.world, statusCode); - - ref ProgramContainer containerInMap = ref simulator->programsMap[containerInList.entity]; - containerInMap.state = IsProgram.State.Finished; - simulator->activePrograms.TryRemove(containerInList); + container.receiverLocators.Add(new(handler.type, receivers.Count)); + receivers.Add(handler.receiver); } } - } - /// - /// Submits a for a potential system to handle. - /// - /// if no handler was found. - public readonly StatusCode TryHandleMessage(T message) where T : unmanaged - { - return TryHandleMessage(ref message); + systems.Add(container); } /// - /// Submits a for a potential system to handle. + /// Removes the first system found of type , + /// and disposes it by default. /// - /// if no handler was found. - public readonly StatusCode TryHandleMessage(ref T message) where T : unmanaged + /// The removed system. + public readonly T Remove(bool dispose = true) where T : ISystem { - World simulatorWorld = World; - StartSystemsWithWorld(simulatorWorld); - StartPrograms(simulatorWorld); + ThrowIfSystemIsMissing(); - using MemoryAddress messageContainer = MemoryAddress.AllocateValue(message); - TypeMetadata messageType = TypeMetadata.GetOrRegister(); - StatusCode statusCode = default; - if (simulator->handlers.TryGetValue(messageType, out Array handlers)) + ReadOnlySpan systemsSpan = systems.AsSpan(); + for (int i = 0; i < systemsSpan.Length; i++) { - Span handlersSpan = handlers.AsSpan(); - Span programs = simulator->activePrograms.AsSpan(); - Span systems = simulator->systems.AsSpan(); - - //tell with simulator world first - for (int s = 0; s < systems.Length; s++) + SystemContainer container = systemsSpan[i]; + if (container.System is T system) { - ref SystemContainer system = ref systems[s]; - for (int f = 0; f < handlersSpan.Length; f++) + Remove(container, i); + if (dispose && system is IDisposable disposableSystem) { - MessageHandler handler = handlersSpan[f]; - if (handler.systemType == system.type) - { - statusCode = handler.function.Invoke(system, simulatorWorld, messageContainer, messageType); - if (statusCode != default) - { - message = messageContainer.Read(); - return statusCode; - } - } + disposableSystem.Dispose(); } - } - //tell with program worlds second - for (int s = 0; s < systems.Length; s++) - { - ref SystemContainer system = ref systems[s]; - for (int p = 0; p < programs.Length; p++) - { - ref ProgramContainer program = ref programs[p]; - World programWorld = program.world; - for (int f = 0; f < handlersSpan.Length; f++) - { - ref MessageHandler handler = ref handlersSpan[f]; - if (handler.systemType == system.type) - { - statusCode = handler.function.Invoke(system, programWorld, messageContainer, messageType); - if (statusCode != default) - { - message = messageContainer.Read(); - return statusCode; - } - } - } - } + return system; } } - return statusCode; + throw new InvalidOperationException($"System of type {typeof(T)} not found"); } /// - /// Updates all systems with the given . + /// Removes the given without disposing it. + /// + /// Throws an exception if the system is not found. + /// /// - private readonly void UpdateSystemsWithWorld(World world, TimeSpan delta) + public readonly void Remove(object system) { - Span systems = simulator->systems.AsSpan(); - for (int s = 0; s < systems.Length; s++) - { - ref SystemContainer system = ref systems[s]; - system.Update(world, delta); - } - } + ThrowIfSystemIsMissing(system); - /// - /// Updates all systems, all programs by advancing their time. - /// - public readonly void Update() - { - ref DateTime lastUpdateTime = ref simulator->lastUpdateTime; - DateTime now = DateTime.UtcNow; - if (lastUpdateTime == DateTime.MinValue) + ReadOnlySpan systemsSpan = systems.AsSpan(); + for (int i = 0; i < systemsSpan.Length; i++) { - lastUpdateTime = now; + SystemContainer container = systemsSpan[i]; + if (container.System == system) + { + Remove(container, i); + return; + } } - TimeSpan delta = now - lastUpdateTime; - lastUpdateTime = now; - Update(delta); + throw new InvalidOperationException($"System instance {system} not found"); } - /// - /// Updates all systems, and all programs forward by - /// amount of time. - /// - public readonly void Update(TimeSpan delta) + private readonly void Remove(SystemContainer container, int index) { - World simulatorWorld = World; - FinishDestroyedPrograms(simulatorWorld, StatusCode.Termination); - StartSystemsWithWorld(simulatorWorld); - StartPrograms(simulatorWorld); - StartSystemsWithPrograms(simulatorWorld); - UpdateSystemsWithWorld(simulatorWorld, delta); - UpdateSystemsWithPrograms(delta, simulatorWorld); - UpdatePrograms(simulatorWorld, delta); - } - - /// - /// Updates only the systems forward with the simulator world first, then program worlds. - /// - public readonly void UpdateSystems(TimeSpan delta) - { - World simulatorWorld = World; - StartSystemsWithWorld(simulatorWorld); - StartSystemsWithPrograms(simulatorWorld); - UpdateSystemsWithWorld(simulatorWorld, delta); - UpdateSystemsWithPrograms(delta, simulatorWorld); - } - - /// - /// Initializes programs not yet started and adds them to the active - /// programs list. - /// - private readonly void StartPrograms(World simulatorWorld) - { - int programComponent = simulator->programComponent; - foreach (Chunk chunk in simulatorWorld.Chunks) + ReadOnlySpan receiverLocators = container.receiverLocators.AsSpan(); + for (int i = 0; i < receiverLocators.Length; i++) { - if (chunk.Definition.ContainsComponent(programComponent)) + MessageReceiverLocator locator = receiverLocators[i]; + ref List receivers = ref receiversMap[locator.type]; + receivers.RemoveAt(locator.index); + + if (receivers.Count != locator.index) { - ReadOnlySpan entities = chunk.Entities; - ComponentEnumerator components = chunk.GetComponents(programComponent); - for (int i = 0; i < components.length; i++) + //the removed listener wasnt last, so need to shift the rest of the list + Span systemsSpan = systems.AsSpan(); + for (int c = 0; c < systemsSpan.Length; c++) { - ref IsProgram program = ref components[i]; - if (program.state == IsProgram.State.Uninitialized) + SystemContainer system = systemsSpan[c]; + Span locators = system.receiverLocators.AsSpan(); + for (int j = 0; j < locators.Length; j++) { - uint entity = entities[i]; - program.state = IsProgram.State.Active; - ref ProgramContainer containerInMap = ref simulator->programsMap.TryGetValue(entity, out bool contains); - if (contains) + ref MessageReceiverLocator otherLocator = ref locators[j]; + if (otherLocator.type == locator.type && otherLocator.index > locator.index) { - ref ProgramContainer containerInList = ref simulator->programs[simulator->programs.IndexOf(containerInMap)]; - containerInMap.state = program.state; - containerInList.state = program.state; - - program.world.Clear(); - program.start.Invoke(this, program.allocation, program.world); - simulator->activePrograms.Add(containerInMap); - } - else - { - program.start.Invoke(this, program.allocation, program.world); - ProgramContainer newContainer = new(entity, program.state, program, program.world, program.allocation); - simulator->programsMap.Add(entity, newContainer); - simulator->programs.Add(newContainer); - simulator->activePrograms.Add(newContainer); + otherLocator.index--; } } } } } + + systems.RemoveAt(index); + container.Dispose(); } - private readonly void UpdatePrograms(World simulatorWorld, TimeSpan delta) + /// + /// Checks if the simulator contains a system of type . + /// + public readonly bool Contains() where T : ISystem { - int programComponent = simulator->programComponent; - Span programs = simulator->activePrograms.AsSpan(); - Span finishedPrograms = stackalloc int[programs.Length]; - int finishedProgramCount = 0; - for (int p = 0; p < programs.Length; p++) + ReadOnlySpan systemsSpan = systems.AsSpan(); + for (int i = 0; i < systemsSpan.Length; i++) { - ref ProgramContainer program = ref programs[p]; - ref IsProgram component = ref simulatorWorld.GetComponent(program.entity, programComponent); - component.statusCode = program.update.Invoke(this, program.allocation, program.world, delta); - if (component.statusCode != StatusCode.Continue) + if (systemsSpan[i].System is T) { - program.state = IsProgram.State.Finished; - component.state = IsProgram.State.Finished; - program.finish.Invoke(this, program.allocation, program.world, component.statusCode); - - uint entity = program.entity; - ref ProgramContainer containerInMap = ref simulator->programsMap[entity]; - ref ProgramContainer containerInList = ref simulator->programs[simulator->programs.IndexOf(containerInMap)]; - containerInMap.state = program.state; - containerInList.state = program.state; - - finishedPrograms[finishedProgramCount++] = p; + return true; } } - for (int i = finishedProgramCount - 1; i >= 0; i--) - { - int index = finishedPrograms[i]; - simulator->activePrograms.RemoveAt(index); - } + return false; } /// - /// Updates all systems only with the given . + /// Retrieves the first system of type . + /// + /// An exception will be thrown if a system is not found. + /// /// - public readonly void UpdateSystems(TimeSpan delta, World world) + public readonly T GetFirst() where T : ISystem { - StartSystemsWithWorld(world); + ThrowIfSystemIsMissing(); - Span systems = simulator->systems.AsSpan(); - for (int s = 0; s < systems.Length; s++) + ReadOnlySpan systemsSpan = systems.AsSpan(); + for (int i = 0; i < systemsSpan.Length; i++) { - ref SystemContainer systemContainer = ref systems[s]; - systemContainer.Update(world, delta); - } - } - - private readonly void UpdateSystemsWithPrograms(TimeSpan delta, World simulatorWorld) - { - Span systems = simulator->systems.AsSpan(); - Span programs = simulator->activePrograms.AsSpan(); - for (int s = 0; s < systems.Length; s++) - { - ref SystemContainer systemContainer = ref systems[s]; - for (int p = 0; p < programs.Length; p++) + if (systemsSpan[i].System is T system) { - ref ProgramContainer program = ref programs[p]; - if (program.state != IsProgram.State.Finished) - { - World programWorld = program.world; - systemContainer.Update(programWorld, delta); - } + return system; } } + + throw new InvalidOperationException($"System of type {typeof(T)} not found"); } /// - /// Starts all systems with the given . + /// Tries to retrieve the first system of type . /// - private readonly void StartSystemsWithWorld(World world) + public readonly bool TryGetFirst([NotNullWhen(true)] out T? system) where T : notnull, ISystem { - Span systems = simulator->systems.AsSpan(); - for (int s = 0; s < systems.Length; s++) + ReadOnlySpan systemsSpan = systems.AsSpan(); + for (int i = 0; i < systemsSpan.Length; i++) { - ref SystemContainer container = ref systems[s]; - if (!container.IsInitializedWith(world)) + if (systemsSpan[i].System is T foundSystem) { - container.Start(world); + system = foundSystem; + return true; } } - } - private readonly void StartSystemsWithPrograms(World simulatorWorld) - { - Span systems = simulator->systems.AsSpan(); - Span programs = simulator->activePrograms.AsSpan(); - for (int s = 0; s < systems.Length; s++) - { - ref SystemContainer container = ref systems[s]; - for (int p = 0; p < programs.Length; p++) - { - ref ProgramContainer program = ref programs[p]; - if (program.state != IsProgram.State.Finished) - { - World programWorld = program.world; - if (!container.IsInitializedWith(programWorld)) - { - container.Start(programWorld); - } - } - } - } + system = default; + return false; } /// - /// Adds a system to the simulator without initializing it. + /// Retrieves the system at the given . /// - internal readonly SystemContainer AddSystem(T system, int parent) where T : unmanaged, ISystem + public readonly ISystem GetSystem(int index) { - MemoryAddress.ThrowIfDefault(simulator); + ThrowIfIndexIsOutOfBounds(index); - (StartSystem start, UpdateSystem update, FinishSystem finish, DisposeSystem dispose) = system.Functions; - if (start == default || update == default || finish == default || dispose == default) - { - throw new SystemMissingFunctionsException(typeof(T), start, update, finish, dispose); - } - - World simulatorWorld = simulator->world; - TypeMetadata systemType = TypeMetadata.GetOrRegister(); - Trace.WriteLine($"Adding system `{typeof(T)}` to `{simulatorWorld}`"); - - system.CollectMessageHandlers(new(systemType, simulator->handlers)); - MemoryAddress systemAllocation = MemoryAddress.AllocateValue(system); - SystemContainer systemContainer = new(simulator->systems.Count, parent, this, systemAllocation, systemType, start, update, finish, dispose); - simulator->systems.Add(systemContainer); - systemContainer.Start(simulatorWorld); - return systemContainer.As(); + return systems[index].System; } /// - /// Adds a system to the simulator without initializing it. + /// Updates all systems forward. /// - public readonly SystemContainer AddSystem(T system) where T : unmanaged, ISystem + public void Update() { - return AddSystem(system, -1); - } + DateTime timeNow = DateTime.UtcNow; + double deltaTime = (timeNow - lastUpdateTime).TotalSeconds; + lastUpdateTime = timeNow; + runTime += deltaTime; - /// - /// Inserts the at the specified . - /// - public readonly SystemContainer InsertSystem(int index, T system) where T : unmanaged, ISystem - { - MemoryAddress.ThrowIfDefault(simulator); - - (StartSystem start, UpdateSystem update, FinishSystem finish, DisposeSystem dispose) = system.Functions; - if (start == default || update == default || finish == default || dispose == default) + ReadOnlySpan systemsSpan = systems.AsSpan(); + for (int i = 0; i < systemsSpan.Length; i++) { - throw new SystemMissingFunctionsException(typeof(T), start, update, finish, dispose); + systemsSpan[i].Update(this, deltaTime); } - - World simulatorWorld = simulator->world; - TypeMetadata systemType = TypeMetadata.GetOrRegister(); - Trace.WriteLine($"Adding system `{typeof(T)}` to `{simulatorWorld}`"); - - system.CollectMessageHandlers(new(systemType, simulator->handlers)); - MemoryAddress systemAllocation = MemoryAddress.AllocateValue(system); - SystemContainer systemContainer = new(index, -1, this, systemAllocation, systemType, start, update, finish, dispose); - simulator->systems.Insert(index, systemContainer); - systemContainer.Start(simulatorWorld); - return systemContainer.As(); } /// - /// Inserts the given before . + /// Updates all systems forward and retreives the delta time. /// - public readonly SystemContainer AddSystemBefore(T system) where T : unmanaged, ISystem where O : unmanaged, ISystem + public void Update(out double deltaTime) { - MemoryAddress.ThrowIfDefault(simulator); + DateTime timeNow = DateTime.UtcNow; + deltaTime = (timeNow - lastUpdateTime).TotalSeconds; + lastUpdateTime = timeNow; + runTime += deltaTime; - TypeMetadata otherSystemType = TypeMetadata.GetOrRegister(); - Span systems = simulator->systems.AsSpan(); - for (int i = 0; i < systems.Length; i++) + ReadOnlySpan systemsSpan = systems.AsSpan(); + for (int i = 0; i < systemsSpan.Length; i++) { - ref SystemContainer systemContainer = ref systems[i]; - if (systemContainer.type == otherSystemType) - { - return InsertSystem(i, system); - } + systemsSpan[i].Update(this, deltaTime); } - - throw new InvalidOperationException($"System `{typeof(O)}` is not registered in the simulator"); } /// - /// Inserts the given after . + /// Updates all systems forward with the specified . /// - public readonly SystemContainer AddSystemAfter(T system) where T : unmanaged, ISystem where O : unmanaged, ISystem + public void Update(double deltaTime) { - MemoryAddress.ThrowIfDefault(simulator); + lastUpdateTime = DateTime.UtcNow; + runTime += deltaTime; - TypeMetadata otherSystemType = TypeMetadata.GetOrRegister(); - Span systems = simulator->systems.AsSpan(); - for (int i = 0; i < systems.Length; i++) + ReadOnlySpan systemsSpan = systems.AsSpan(); + for (int i = 0; i < systemsSpan.Length; i++) { - ref SystemContainer systemContainer = ref systems[i]; - if (systemContainer.type == otherSystemType) - { - return InsertSystem(i + 1, system); - } + systemsSpan[i].Update(this, deltaTime); } - - throw new InvalidOperationException($"System `{typeof(O)}` is not registered in the simulator"); } /// - /// Removes the first system of type and disposes it. + /// Broadcasts the given to all systems. /// - public readonly void RemoveSystem() where T : unmanaged, ISystem + public readonly void Broadcast(T message) where T : unmanaged { - MemoryAddress.ThrowIfDefault(simulator); - ThrowIfSystemIsMissing(); - - TypeMetadata systemType = TypeMetadata.GetOrRegister(); - Trace.WriteLine($"Removing system `{typeof(T)}` from `{simulator->world}`"); - - Span systems = simulator->systems.AsSpan(); - for (int i = 0; i < systems.Length; i++) + TypeMetadata messageType = TypeMetadata.GetOrRegister(); + if (receiversMap.TryGetValue(messageType, out List receivers)) { - ref SystemContainer systemContainer = ref systems[i]; - if (systemContainer.type == systemType) + using Message container = Message.Create(message); + Span receiversSpan = receivers.AsSpan(); + for (int i = 0; i < receiversSpan.Length; i++) { - systemContainer.FinalizeAndDispose(); - simulator->systems.RemoveAt(i); - return; + receiversSpan[i].Invoke(container); } } } - internal readonly void RemoveSystem(int index) - { - MemoryAddress.ThrowIfDefault(simulator); - ThrowIfSystemIsMissing(index); - - Span systems = simulator->systems.AsSpan(); - ref SystemContainer systemContainer = ref systems[index]; - Trace.WriteLine($"Removing system `{systemContainer.type}` from `{simulator->world}`"); - - systemContainer.FinalizeAndDispose(); - simulator->systems.RemoveAt(index); - } - /// - /// Checks if the given system type is registered in the simulator. + /// Broadcasts the given to all systems. /// - public readonly bool ContainsSystem() where T : unmanaged, ISystem + public readonly void Broadcast(ref T message) where T : unmanaged { - MemoryAddress.ThrowIfDefault(simulator); - - TypeMetadata systemType = TypeMetadata.GetOrRegister(); - Span systems = simulator->systems.AsSpan(); - for (int i = 0; i < systems.Length; i++) + TypeMetadata messageType = TypeMetadata.GetOrRegister(); + if (receiversMap.TryGetValue(messageType, out List receivers)) { - ref SystemContainer systemContainer = ref systems[i]; - if (systemContainer.type == systemType) + using Message container = Message.Create(message); + Span receiversSpan = receivers.AsSpan(); + for (int i = 0; i < receiversSpan.Length; i++) { - return true; + receiversSpan[i].Invoke(container); } - } - return false; + message = container.data.Read(); + } } - /// - /// Retrieves the system container of the given . - /// - /// May throw an if the system is not registered. - /// - /// - /// - public readonly ref T GetSystem() where T : unmanaged, ISystem + [Conditional("DEBUG")] + private readonly void ThrowIfSystemIsMissing() where T : ISystem { - MemoryAddress.ThrowIfDefault(simulator); - - TypeMetadata systemType = TypeMetadata.GetOrRegister(); - Span systems = simulator->systems.AsSpan(); - for (int i = 0; i < systems.Length; i++) + ReadOnlySpan systemsSpan = systems.AsSpan(); + for (int i = 0; i < systemsSpan.Length; i++) { - ref SystemContainer systemContainer = ref systems[i]; - if (systemContainer.type == systemType) + if (systemsSpan[i].System is T) { - return ref systemContainer.Read(); + return; } } - throw new NullReferenceException($"System `{typeof(T)}` is not registered in the simulator"); + throw new InvalidOperationException($"System of type {typeof(T)} not found"); } - /// - /// Throws an if the given has already been registered. - /// - /// [Conditional("DEBUG")] - public readonly void ThrowIfSystemIsMissing() where T : unmanaged, ISystem + private readonly void ThrowIfSystemIsMissing(object system) { - TypeMetadata systemType = TypeMetadata.GetOrRegister(); - Span systems = simulator->systems.AsSpan(); - for (int i = 0; i < systems.Length; i++) + ReadOnlySpan systemsSpan = systems.AsSpan(); + for (int i = 0; i < systemsSpan.Length; i++) { - ref SystemContainer systemContainer = ref systems[i]; - if (systemContainer.type == systemType) + if (systemsSpan[i].System == system) { return; } } - throw new InvalidOperationException($"System `{typeof(T)}` is not registered in the simulator"); + throw new InvalidOperationException($"System instance {system} not found"); } [Conditional("DEBUG")] - private readonly void ThrowIfSystemIsMissing(int index) + private readonly void ThrowIfIndexIsOutOfBounds(int index) { - if (index < 0 || index >= simulator->systems.Count) + if (index < 0 || index >= systems.Count) { - throw new InvalidOperationException($"System at index `{index}` is not registered in the simulator"); + throw new ArgumentOutOfRangeException(nameof(index), $"Index {index} is out of bounds. Count: {systems.Count}"); } } - - /// - public readonly override bool Equals(object? obj) - { - return obj is Simulator simulator && Equals(simulator); - } - - /// - public readonly bool Equals(Simulator other) - { - return simulator == other.simulator; - } - - /// - public readonly override int GetHashCode() - { - return ((nint)simulator).GetHashCode(); - } - - /// - public static bool operator ==(Simulator left, Simulator right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(Simulator left, Simulator right) - { - return !(left == right); - } } } \ No newline at end of file diff --git a/core/StatusCode.cs b/core/StatusCode.cs deleted file mode 100644 index cbc16b2..0000000 --- a/core/StatusCode.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Diagnostics; - -namespace Simulation -{ - /// - /// Represents a status code for the result of an operation. - /// - public readonly struct StatusCode : IEquatable - { - /// - /// Maximum allowed value for status codes. - /// - public const byte MaxCode = 63; - - /// - /// Indicates that the program should continue running. - /// - public static readonly StatusCode Continue = new(1); - - /// - /// Indicates that the program was terminated externally, and not on its own. - /// - public static readonly StatusCode Termination = Failure(MaxCode); - - private readonly byte value; - - /// - /// Checks if the status code represents continuation. - /// - public readonly bool IsContinue => (value & 1) != 0 && (value & 2) == 0; - - /// - /// Checks if the status code represents a successful operation. - /// - public readonly bool IsSuccess => (value & 1) == 0 && (value & 2) != 0; - - /// - /// Checks if the status code represents a failure. - /// - public readonly bool IsFailure => (value & 1) != 0 && (value & 2) != 0; - - /// - /// The underlying status code. - /// - public readonly byte Code => (byte)(value >> 2); - -#if NET - /// - [Obsolete("Default constructor not supported", true)] - public StatusCode() - { - throw new NotSupportedException(); - } -#endif - - private StatusCode(byte value) - { - this.value = value; - } - - /// - public override string ToString() - { - if (HasSuccess(out byte code)) - { - return $"Success {code}"; - } - else if (HasFailure(out code)) - { - return $"Failure {code}"; - } - else if (value == 0) - { - return "Default"; - } - else - { - return "Continue"; - } - } - - /// - public readonly int ToString(Span destination) - { - int length = 0; - if (HasSuccess(out byte code)) - { - destination[length++] = 'S'; - destination[length++] = 'u'; - destination[length++] = 'c'; - destination[length++] = 'c'; - destination[length++] = 'e'; - destination[length++] = 's'; - destination[length++] = 's'; - destination[length++] = ' '; - length += code.ToString(destination.Slice(length)); - } - else if (HasFailure(out code)) - { - destination[length++] = 'F'; - destination[length++] = 'a'; - destination[length++] = 'i'; - destination[length++] = 'l'; - destination[length++] = 'u'; - destination[length++] = 'r'; - destination[length++] = 'e'; - destination[length++] = ' '; - length += code.ToString(destination.Slice(length)); - } - else if (value == 0) - { - destination[length++] = 'D'; - destination[length++] = 'e'; - destination[length++] = 'f'; - destination[length++] = 'a'; - destination[length++] = 'u'; - destination[length++] = 'l'; - destination[length++] = 't'; - } - else - { - destination[length++] = 'C'; - destination[length++] = 'o'; - destination[length++] = 'n'; - destination[length++] = 't'; - destination[length++] = 'i'; - destination[length++] = 'n'; - destination[length++] = 'u'; - destination[length++] = 'e'; - } - - return length; - } - - /// - /// Tries to retrieve the code if the status code indicates success. - /// - public readonly bool HasSuccess(out byte code) - { - if (IsSuccess) - { - code = Code; - return true; - } - else - { - code = default; - return false; - } - } - - /// - /// Tries to retrieve the code if the status code indicates failure. - /// - public readonly bool HasFailure(out byte code) - { - if (IsFailure) - { - code = Code; - return true; - } - else - { - code = default; - return false; - } - } - - /// - /// Gets a status code indicating that the operation was successful - /// with the given . - /// - public static StatusCode Success(byte code) - { - ThrowIfCodeIsOutOfRange(code); - - byte value = default; - value |= 2; - value |= (byte)(code << 2); - return new StatusCode(value); - } - - /// - /// Gets a status code indicating that the operation was a failure - /// with the given . - /// - public static StatusCode Failure(byte code) - { - ThrowIfCodeIsOutOfRange(code); - - byte value = default; - value |= 3; - value |= (byte)(code << 2); - return new StatusCode(value); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is StatusCode code && Equals(code); - } - - /// - public readonly bool Equals(StatusCode other) - { - return value == other.value; - } - - /// - public readonly override int GetHashCode() - { - return value; - } - - /// - public static bool operator ==(StatusCode left, StatusCode right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(StatusCode left, StatusCode right) - { - return !(left == right); - } - - [Conditional("DEBUG")] - private static void ThrowIfCodeIsOutOfRange(byte code) - { - if (code > MaxCode) - { - throw new ArgumentOutOfRangeException(nameof(code), code, $"Status code must be between 0 and {MaxCode}"); - } - } - } -} \ No newline at end of file diff --git a/core/SystemContainer.cs b/core/SystemContainer.cs index f0482f8..9c8d202 100644 --- a/core/SystemContainer.cs +++ b/core/SystemContainer.cs @@ -1,303 +1,37 @@ using Collections.Generic; -using Simulation.Functions; using System; -using System.Diagnostics; -using Types; -using Unmanaged; -using Worlds; +using System.Runtime.InteropServices; namespace Simulation { - /// - /// Contains a system added to a . - /// - public readonly unsafe struct SystemContainer : IDisposable, IEquatable + internal readonly struct SystemContainer : IDisposable { - /// - /// The type of the system. - /// - public readonly TypeMetadata type; + public readonly GCHandle handle; + public readonly List receiverLocators; - /// - /// The simulator that this system was created in. - /// - public readonly Simulator simulator; + public readonly ISystem System => (ISystem)handle.Target!; - private readonly MemoryAddress allocation; - private readonly List worlds; - private readonly StartSystem start; - private readonly UpdateSystem update; - private readonly FinishSystem finish; - private readonly DisposeSystem dispose; - internal readonly int index; - internal readonly int parent; - - /// - /// The world of the simulator that this system was created in. - /// - public readonly World World => simulator.World; - - /// - /// Creates a new instance. - /// - internal SystemContainer(int index, int parent, Simulator simulator, MemoryAddress allocation, TypeMetadata type, StartSystem start, UpdateSystem update, FinishSystem finish, DisposeSystem dispose) - { - this.index = index; - this.parent = parent; - this.simulator = simulator; - this.allocation = allocation; - this.type = type; - this.start = start; - this.update = update; - this.finish = finish; - this.dispose = dispose; - worlds = new(); - } - - [Conditional("DEBUG")] - private readonly void ThrowIfDisposed() + public SystemContainer(GCHandle handle) { - if (worlds.IsDisposed) - { - throw new ObjectDisposedException($"System `{this}` has been disposed"); - } + this.handle = handle; + receiverLocators = new(4); } - /// - public readonly override string ToString() - { - return type.ToString(); - } - - /// - /// Removes the system from the simulator and diposes it. - /// public readonly void Dispose() { - ThrowIfDisposed(); - - simulator.RemoveSystem(index); - } - - internal readonly void FinalizeAndDispose() - { - ThrowIfDisposed(); - - for (int i = worlds.Count - 1; i >= 0; i--) - { - Finalize(worlds[i]); - } - - dispose.Invoke(this, simulator.World); - allocation.Dispose(); - worlds.Dispose(); - } - - /// - /// Reads the system data of the given type. - /// - public readonly ref T Read() where T : unmanaged, ISystem - { - ThrowIfNotSameType(); - - return ref allocation.Read(); - } - - /// - /// Writes the system data. - /// - public readonly void Write(T value) where T : unmanaged, ISystem - { - ThrowIfNotSameType(); - - allocation.Write(value); - } - - /// - /// Checks if this system is initialized with the given . - /// - public readonly bool IsInitializedWith(World world) - { - return worlds.Contains(world); - } - - /// - /// Checks if the given is the world that the simulator - /// has been created with. - /// - public readonly bool IsSimulatorWorld(World world) - { - return simulator.World == world; - } - - /// - /// Initializes this system with the given world as its context. - /// - public readonly void Start(World world) - { - ThrowIfAlreadyInitializedWith(world); - - worlds.Add(world); - start.Invoke(this, world); - } - - /// - /// Updates this system with the given world as its context. - /// - public readonly void Update(World world, TimeSpan delta) - { - ThrowIfNotInitializedWith(world); - - update.Invoke(this, world, delta); - } - - /// - /// Finalizes this system with the given world as its context. - /// - public readonly void Finalize(World world) - { - ThrowIfNotInitializedWith(world); - - finish.Invoke(this, world); - worlds.TryRemoveBySwapping(world); - } - - /// - /// Throws an if this system is not initialized with the given . - /// - /// - [Conditional("DEBUG")] - public readonly void ThrowIfNotInitializedWith(World world) - { - if (!IsInitializedWith(world)) - { - throw new InvalidOperationException($"System `{this}` is not initialized with world `{world}`"); - } - } - - [Conditional("DEBUG")] - private readonly void ThrowIfAlreadyInitializedWith(World world) - { - if (IsInitializedWith(world)) - { - throw new InvalidOperationException($"System `{this}` is already initialized with world `{world}`"); - } - } - - [Conditional("DEBUG")] - private readonly void ThrowIfNotSameType() where T : unmanaged, ISystem - { - if (!type.Is()) - { - throw new InvalidOperationException($"System `{this}` is not of type `{typeof(T)}`"); - } - } - - internal readonly SystemContainer As() where T : unmanaged, ISystem - { - return new(simulator, index); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is SystemContainer container && Equals(container); - } - - /// - public readonly bool Equals(SystemContainer other) - { - return allocation.Equals(other.allocation); - } - - /// - public readonly override int GetHashCode() - { - return allocation.GetHashCode(); - } - - /// - public static bool operator ==(SystemContainer left, SystemContainer right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(SystemContainer left, SystemContainer right) - { - return !(left == right); - } - } - - /// - /// Generic container for of a system added to a . - /// - public unsafe readonly struct SystemContainer : IDisposable where T : unmanaged, ISystem - { - /// - /// The simulator that the system belongs to. - /// - public readonly Simulator simulator; - - private readonly int index; - - /// - /// The system's data. - /// - public readonly ref T Value - { - get - { - ThrowIfSystemIsDifferent(); - - return ref Container.Read(); - } - } - - /// - /// The world that this system was created in. - /// - public readonly World World => simulator.World; - - private readonly SystemContainer Container => simulator.Systems[index]; - - /// - /// Initializes a new instance with an - /// existing system index. - /// - internal SystemContainer(Simulator simulator, int index) - { - this.simulator = simulator; - this.index = index; - } - - /// - /// Removes the system from the simulator. - /// - public readonly void Dispose() - { - simulator.RemoveSystem(index); - } - - [Conditional("DEBUG")] - private readonly void ThrowIfSystemIsDifferent() - { - if (!Container.type.Is()) - { - throw new InvalidOperationException($"System at index `{index}` is not of expected type `{typeof(T)}`"); - } + receiverLocators.Dispose(); + handle.Free(); } - /// - public static implicit operator SystemContainer(SystemContainer container) + public readonly void Update(Simulator simulator, double deltaTime) { - return container.Container; + ((ISystem)handle.Target!).Update(simulator, deltaTime); } - /// - public static implicit operator T(SystemContainer container) + public static SystemContainer Create(T system) where T : ISystem { - return container.Value; + GCHandle handle = GCHandle.Alloc(system, GCHandleType.Normal); + return new SystemContainer(handle); } } } \ No newline at end of file diff --git a/core/SystemContext.cs b/core/SystemContext.cs deleted file mode 100644 index d2f53f9..0000000 --- a/core/SystemContext.cs +++ /dev/null @@ -1,145 +0,0 @@ -using Simulation.Exceptions; -using System; -using System.Diagnostics; -using Worlds; - -namespace Simulation -{ - /// - /// Represents a piece of the . - /// - public readonly struct SystemContext : IEquatable - { - private readonly SystemContainer systemContainer; - - /// - /// The world that the was created with. - /// - public readonly World SimulatorWorld => systemContainer.World; - - /// - /// The simulator that the context originates from. - /// - public readonly Simulator Simulator => systemContainer.simulator; - - internal SystemContext(SystemContainer systemContainer) - { - this.systemContainer = systemContainer; - } - - /// - public readonly override string ToString() - { - return systemContainer.ToString(); - } - - /// - /// Checks if the given is the world that the - /// simulator was created with. - /// - public readonly bool IsSimulatorWorld(World world) - { - return systemContainer.IsSimulatorWorld(world); - } - - /// - /// Adds the given to the simulator. - /// - public readonly SystemContainer AddSystem(T system) where T : unmanaged, ISystem - { - return systemContainer.simulator.AddSystem(system, systemContainer.index); - } - - /// - /// Removes the system of type from the simulator - /// and disposes it. - /// - public readonly void RemoveSystem() where T : unmanaged, ISystem - { - systemContainer.simulator.RemoveSystem(); - } - - /// - /// Submits a for a potential system to handle. - /// - /// if no handler was found. - public readonly StatusCode TryHandleMessage(T message) where T : unmanaged - { - return systemContainer.simulator.TryHandleMessage(message); - } - - /// - /// Submits a for a potential system to handle. - /// - /// if no handler was found. - public readonly StatusCode TryHandleMessage(ref T message) where T : unmanaged - { - return systemContainer.simulator.TryHandleMessage(ref message); - } - - /// - /// Only updates the systems forward with the simulator world first, then program worlds. - /// - public readonly void UpdateSystems(TimeSpan delta) - { - systemContainer.simulator.UpdateSystems(delta); - } - - /// - /// Updates all systems only with the given . - /// - public readonly void UpdateSystems(TimeSpan delta, World world) - { - systemContainer.simulator.UpdateSystems(delta, world); - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is SystemContext context && Equals(context); - } - - /// - public readonly bool Equals(SystemContext other) - { - return systemContainer == other.systemContainer; - } - - /// - public readonly override int GetHashCode() - { - return systemContainer.GetHashCode(); - } - - [Conditional("DEBUG")] - private readonly void ThrowIfSystemTypeMismatch() where T : unmanaged - { - if (!systemContainer.type.Is()) - { - throw new SystemTypeMismatchException(typeof(T), systemContainer.type); - } - } - - /// - /// Overwrites the system value. - /// - public readonly void Write(T newSystem) where T : unmanaged, ISystem - { - ThrowIfSystemTypeMismatch(); - - systemContainer.Write(newSystem); - } - - /// - public static bool operator ==(SystemContext left, SystemContext right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(SystemContext left, SystemContext right) - { - return !(left == right); - } - } -} \ No newline at end of file diff --git a/core/SystemOrderAttribute.cs b/core/SystemOrderAttribute.cs deleted file mode 100644 index 1661822..0000000 --- a/core/SystemOrderAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Simulation -{ - /// - /// Defines the ordering hint for the generated system bank. - /// - public class SystemOrderAttribute : Attribute - { - /// - /// Hint value. - /// - public readonly int order; - - /// - /// Creates an instance of this attribute. - /// - public SystemOrderAttribute(int order) - { - this.order = order; - } - } -} \ No newline at end of file diff --git a/core/Types/IProgram.cs b/core/Types/IProgram.cs deleted file mode 100644 index 7c35612..0000000 --- a/core/Types/IProgram.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Simulation.Functions; -using System; -using Worlds; - -namespace Simulation -{ - /// - /// Managed by a after all - /// s performed their behaviour. - /// - public interface IProgram - { - /// - /// The underlying pointers for all control functions. - /// - public (StartProgram start, UpdateProgram update, FinishProgram finish) Functions - { - get - { - return default; - } - } - } - - /// - /// Managed by a after all - /// s performed their behaviour. - /// - public interface IProgram : IProgram where T : unmanaged, IProgram - { - /// - /// Starts a program with its own . - /// - void Start(ref T program, in Simulator simulator, in World world); - - /// - /// Updates a program forward after all systems. - /// - StatusCode Update(in TimeSpan delta); - - /// - /// Finishes the program after returns a non - /// continue and default status code. - /// - void Finish(in StatusCode statusCode); - } -} \ No newline at end of file diff --git a/core/Types/IProgramEntity.cs b/core/Types/IProgramEntity.cs deleted file mode 100644 index e3e3646..0000000 --- a/core/Types/IProgramEntity.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Worlds; - -namespace Simulation -{ - /// - /// An entity type that represents a state. - /// - public interface IProgramEntity : IEntity - { - - } -} \ No newline at end of file diff --git a/core/Types/ISystem.cs b/core/Types/ISystem.cs deleted file mode 100644 index 3cde563..0000000 --- a/core/Types/ISystem.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Simulation.Functions; -using System; -using Worlds; - -namespace Simulation -{ - /// - /// Describes a system type added to instances. - /// - public interface ISystem : IDisposable - { - /// - /// Exposes function pointers for a to use. - /// - public (StartSystem start, UpdateSystem update, FinishSystem finish, DisposeSystem dispose) Functions - { - get - { - return default; - } - } - - /// - /// Collects all message handling functions. - /// - public void CollectMessageHandlers(MessageHandlerCollector collector) - { - } - - /// - /// Called to notify that the system has been started. - /// - /// The will be the 's world first, - /// and then with each program in the order they were added. - /// - /// - void Start(in SystemContext context, in World world); - - /// - /// Called when the simulator updates the simulation forward. - /// - /// The will be the 's world first, - /// and then with each program in the order they were added. - /// - /// - void Update(in SystemContext context, in World world, in TimeSpan delta); - - /// - /// Called after the system has been removed from the simulation, or when - /// the has been disposed. - /// - /// The will be with each program in the reverse added - /// order, and then with the 's world last. - /// - /// - void Finish(in SystemContext context, in World world); - } -} \ No newline at end of file diff --git a/generator tests/GeneratorTests.cs b/generator tests/GeneratorTests.cs deleted file mode 100644 index e21ce24..0000000 --- a/generator tests/GeneratorTests.cs +++ /dev/null @@ -1,151 +0,0 @@ -using Collections; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Simulation.Generators; -using System.Collections.Generic; -using Unmanaged; -using Worlds; - -namespace Simulation.Generator.Tests -{ - public class GeneratorTests - { - [Test] - public void GenerateWithDispose() - { - const string Source = @" -public readonly partial struct SimpleSystem : ISystem -{ - void ISystem.Start(in SystemCollector collector, in World world) - { - } - - void ISystem.Update(in SystemCollector collector, in World world, in TimeSpan delta) - { - } - - void ISystem.Finish(in SystemCollector collector, in World world) - { - } - - readonly void IDisposable.Dispose() - { - } -}"; - - GeneratorDriverRunResult results = Generate(Source); - GeneratorRunResult result = results.Results[0]; - string generatedSource = result.GeneratedSources[0].SourceText.ToString(); - - SyntaxTree generatedSyntaxTree = CSharpSyntaxTree.ParseText(generatedSource); - foreach (SyntaxNode descendant in generatedSyntaxTree.GetRoot().DescendantNodes()) - { - //type has been generated - if (descendant is StructDeclarationSyntax structDeclaration) - { - Assert.That(structDeclaration.Identifier.Text, Is.EqualTo("SimpleSystem")); - List properties = new(); - List methods = new(); - foreach (SyntaxNode grandDescendant in descendant.DescendantNodes()) - { - if (grandDescendant is PropertyDeclarationSyntax propertyDeclarationSyntax) - { - properties.Add(propertyDeclarationSyntax); - } - - if (grandDescendant is MethodDeclarationSyntax methodDeclarationSyntax) - { - methods.Add(methodDeclarationSyntax); - } - } - - //property has been generated - Assert.That(properties.Count, Is.EqualTo(1)); - Assert.That(methods.Count, Is.EqualTo(0)); - return; - } - } - - throw new("Generated code does not contain expected struct declaration"); - } - - [Test] - public void GenerateWithoutDispose() - { - const string Source = @" -public readonly partial struct SimpleSystem : ISystem -{ - void ISystem.Start(in SystemCollector collector, in World world) - { - } - - void ISystem.Update(in SystemCollector collector, in World world, in TimeSpan delta) - { - } - - void ISystem.Finish(in SystemCollector collector, in World world) - { - } -}"; - - GeneratorDriverRunResult results = Generate(Source); - GeneratorRunResult result = results.Results[0]; - string generatedSource = result.GeneratedSources[0].SourceText.ToString(); - - SyntaxTree generatedSyntaxTree = CSharpSyntaxTree.ParseText(generatedSource); - foreach (SyntaxNode descendant in generatedSyntaxTree.GetRoot().DescendantNodes()) - { - //type has been generated - if (descendant is StructDeclarationSyntax structDeclaration) - { - Assert.That(structDeclaration.Identifier.Text, Is.EqualTo("SimpleSystem")); - List properties = new(); - List methods = new(); - foreach (SyntaxNode grandDescendant in descendant.DescendantNodes()) - { - if (grandDescendant is PropertyDeclarationSyntax propertyDeclarationSyntax) - { - properties.Add(propertyDeclarationSyntax); - } - - if (grandDescendant is MethodDeclarationSyntax methodDeclarationSyntax) - { - methods.Add(methodDeclarationSyntax); - } - } - - //property has been generated - Assert.That(properties.Count, Is.EqualTo(1)); - //Assert.That(methods.Count, Is.EqualTo(1)); works in testing, but not when applied - //Assert.That(methods[0].Identifier.Text, Is.EqualTo("Dispose")); - return; - } - } - - throw new("Generated code does not contain expected struct declaration"); - } - - protected GeneratorDriverRunResult Generate(string sourceCode) - { - SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(sourceCode); - SyntaxTree[] syntaxTrees = [syntaxTree]; - CSharpCompilation compilation = CSharpCompilation.Create(assemblyName: "Tests", syntaxTrees, GetReferences()); - ImplementationGenerator generator = new(); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - driver = driver.RunGenerators(compilation); - return driver.GetRunResult(); - } - - protected virtual List GetReferences() - { - List references = new(); - references.Add(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)); - references.Add(MetadataReference.CreateFromFile(typeof(Simulator).Assembly.Location)); - references.Add(MetadataReference.CreateFromFile(typeof(MemoryAddress).Assembly.Location)); - references.Add(MetadataReference.CreateFromFile(typeof(Array).Assembly.Location)); - references.Add(MetadataReference.CreateFromFile(typeof(World).Assembly.Location)); - return references; - } - } -} \ No newline at end of file diff --git a/generator tests/Simulation.Generator.Tests.csproj b/generator tests/Simulation.Generator.Tests.csproj deleted file mode 100644 index 2394643..0000000 --- a/generator tests/Simulation.Generator.Tests.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - net9.0 - Simulation.Generator.Tests - disable - enable - false - true - true - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - diff --git a/generator/Constants.cs b/generator/Constants.cs index 704cc60..83b1b48 100644 --- a/generator/Constants.cs +++ b/generator/Constants.cs @@ -6,9 +6,8 @@ namespace Simulation internal static class Constants { public const string Namespace = "Simulation"; - public const string SystemInterfaceTypeName = Namespace + ".ISystem"; public const string MemoryAddressTypeName = "Unmanaged.MemoryAddress"; - public const string ProgramInterfaceTypeName = Namespace + ".IProgram"; + public const string ListenerInterfaceTypeName = Namespace + ".IListener"; public const string SystemBankTypeNameFormat = "{0}SystemBank"; public const string PluralSystemBankTypeNameFormat = "{0}SystemsBank"; } diff --git a/generator/Generators/ImplementationGenerator.cs b/generator/Generators/ImplementationGenerator.cs deleted file mode 100644 index dd78b0e..0000000 --- a/generator/Generators/ImplementationGenerator.cs +++ /dev/null @@ -1,271 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System; -using System.Collections.Immutable; -using System.Threading; -using static Simulation.Constants; - -namespace Simulation.Generators -{ - [Generator(LanguageNames.CSharp)] - internal class ImplementationGenerator : IIncrementalGenerator - { - private static readonly SourceBuilder source = new(); - - void IIncrementalGenerator.Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValuesProvider filter = context.SyntaxProvider.CreateSyntaxProvider(Predicate, Transform); - context.RegisterSourceOutput(filter, Generate); - } - - private static bool Predicate(SyntaxNode node, CancellationToken token) - { - return node.IsKind(SyntaxKind.StructDeclaration); - } - - private static Input? Transform(GeneratorSyntaxContext context, CancellationToken token) - { - if (context.Node is TypeDeclarationSyntax typeDeclaration) - { - if (context.SemanticModel.GetDeclaredSymbol(typeDeclaration) is ITypeSymbol typeSymbol) - { - ImmutableArray interfaces = typeSymbol.AllInterfaces; - foreach (INamedTypeSymbol interfaceSymbol in interfaces) - { - string interfaceTypeName = interfaceSymbol.ToDisplayString(); - if (interfaceTypeName == ProgramInterfaceTypeName || ProgramInterfaceTypeName.EndsWith("." + interfaceTypeName)) - { - return new ProgramInput(typeDeclaration, typeSymbol); - } - else if (interfaceTypeName == SystemInterfaceTypeName || SystemInterfaceTypeName.EndsWith("." + interfaceTypeName)) - { - return new SystemInput(typeDeclaration, typeSymbol); - } - } - } - } - - return null; - } - - private static void Generate(SourceProductionContext context, Input? input) - { - if (input is not null) - { - try - { - context.AddSource($"{input.fullTypeName}.generated.cs", Generate(input)); - } - catch (Exception ex) - { - context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("GEN001", "Generation error in {0}", "An error occurred while generating the source code: {1}", "Generator", DiagnosticSeverity.Error, true), Location.None, input.typeName, ex.Message)); - } - } - } - - public static string Generate(Input input) - { - source.Clear(); - bool hasDisposeMethod = false; - foreach (ISymbol typeMember in input.typeSymbol.GetMembers()) - { - if (typeMember is IMethodSymbol method) - { - if (method.MethodKind == MethodKind.Constructor) - { - continue; - } - - if (method.Parameters.Length == 0 && method.ReturnType.SpecialType == SpecialType.System_Void) - { - if (method.Name == "System.IDisposable.Dispose" || method.Name == "IDisposable.Dispose" || method.Name == "Dispose") - { - hasDisposeMethod = true; - } - } - - source.AppendLine($"//{typeMember.Kind} = {method.Name}, {method.Parameters.Length}, {method.ReturnType}"); - } - else - { - source.AppendLine($"//{typeMember.Kind} = {typeMember.Name}"); - } - } - - source.AppendLine("using System;"); - source.AppendLine("using Unmanaged;"); - source.AppendLine("using Worlds;"); - source.AppendLine("using Simulation;"); - source.AppendLine("using Simulation.Functions;"); - source.AppendLine("using System.Runtime.InteropServices;"); - source.AppendLine("using System.ComponentModel;"); - source.AppendLine(); - - if (input.containingNamespace is not null) - { - source.AppendLine($"namespace {input.containingNamespace}"); - source.BeginGroup(); - } - - if (input.typeSymbol.IsReadOnly) - { - source.AppendLine($"public readonly partial struct {input.typeName}"); - } - else - { - source.AppendLine($"public partial struct {input.typeName}"); - } - - source.BeginGroup(); - { - if (input is ProgramInput) - { - source.AppendLine("unsafe readonly (StartProgram start, UpdateProgram update, FinishProgram finish) IProgram.Functions"); - } - else if (input is SystemInput) - { - source.AppendLine("unsafe readonly (StartSystem start, UpdateSystem update, FinishSystem finish, DisposeSystem dispose) ISystem.Functions"); - } - else - { - throw new("Unreachable code"); - } - - source.BeginGroup(); - { - source.AppendLine("get"); - source.BeginGroup(); - { - if (input is ProgramInput) - { - source.AppendLine("return (new(&StartProgram), new(&UpdateProgram), new(&FinishProgram));"); - } - else if (input is SystemInput) - { - source.AppendLine("return (new(&StartSystem), new(&UpdateSystem), new(&FinishSystem), new(&DisposeSystem));"); - } - - source.AppendLine(); - - //start - source.AppendLine("[UnmanagedCallersOnly]"); - if (input is ProgramInput) - { - source.Append("static void StartProgram(Simulator simulator, "); - source.Append(MemoryAddressTypeName); - source.Append(" allocation, World world)"); - source.AppendLine(); - - source.BeginGroup(); - { - source.AppendLine($"ref {input.typeName} program = ref allocation.Read<{input.typeName}>();"); - source.AppendLine("ProgramExtensions.Start(ref program, in simulator, in world);"); - } - source.EndGroup(); - } - else if (input is SystemInput) - { - source.AppendLine("static void StartSystem(SystemContainer systemContainer, World world)"); - source.BeginGroup(); - { - source.AppendLine($"ref {input.typeName} system = ref systemContainer.Read<{input.typeName}>();"); - source.AppendLine("SystemExtensions.Start(ref system, in systemContainer, in world);"); - } - source.EndGroup(); - } - source.AppendLine(); - - //update - source.AppendLine("[UnmanagedCallersOnly]"); - if (input is ProgramInput) - { - source.Append("static StatusCode UpdateProgram(Simulator simulator, "); - source.Append(MemoryAddressTypeName); - source.Append(" allocation, World world, TimeSpan delta)"); - source.AppendLine(); - - source.BeginGroup(); - { - source.AppendLine($"ref {input.typeName} program = ref allocation.Read<{input.typeName}>();"); - source.AppendLine("return ProgramExtensions.Update(ref program, in delta);"); - } - source.EndGroup(); - } - else if (input is SystemInput) - { - source.AppendLine("static void UpdateSystem(SystemContainer systemContainer, World world, TimeSpan delta)"); - source.BeginGroup(); - { - source.AppendLine($"ref {input.typeName} system = ref systemContainer.Read<{input.typeName}>();"); - source.AppendLine("SystemExtensions.Update(ref system, in systemContainer, in world, in delta);"); - } - source.EndGroup(); - } - source.AppendLine(); - - //finish - source.AppendLine("[UnmanagedCallersOnly]"); - if (input is ProgramInput) - { - source.Append("static void FinishProgram(Simulator simulator, "); - source.Append(MemoryAddressTypeName); - source.Append(" allocation, World world, StatusCode statusCode)"); - source.AppendLine(); - - source.BeginGroup(); - { - source.AppendLine($"ref {input.typeName} program = ref allocation.Read<{input.typeName}>();"); - source.AppendLine("ProgramExtensions.Finish(ref program, in statusCode);"); - } - source.EndGroup(); - } - else if (input is SystemInput) - { - source.AppendLine("static void FinishSystem(SystemContainer systemContainer, World world)"); - source.BeginGroup(); - { - source.AppendLine($"ref {input.typeName} system = ref systemContainer.Read<{input.typeName}>();"); - source.AppendLine("SystemExtensions.Finish(ref system, in systemContainer, in world);"); - } - source.EndGroup(); - } - - //dispose - if (input is SystemInput) - { - source.AppendLine(); - source.AppendLine("[UnmanagedCallersOnly]"); - source.Append("static void DisposeSystem(SystemContainer systemContainer, World world)"); - source.AppendLine(); - - source.BeginGroup(); - { - source.AppendLine($"ref {input.typeName} system = ref systemContainer.Read<{input.typeName}>();"); - source.AppendLine("SystemExtensions.Dispose(ref system);"); - } - source.EndGroup(); - } - } - source.EndGroup(); - } - source.EndGroup(); - - //define dispose function if original declaration doesnt - if (!hasDisposeMethod && input is SystemInput) - { - //source.AppendLine(); - //source.AppendLine("public readonly void Dispose() { }"); - } - } - source.EndGroup(); - - if (input.containingNamespace is not null) - { - source.EndGroup(); - } - - return source.ToString(); - } - } -} \ No newline at end of file diff --git a/generator/Generators/ListenerCollectorGenerator.cs b/generator/Generators/ListenerCollectorGenerator.cs new file mode 100644 index 0000000..338742d --- /dev/null +++ b/generator/Generators/ListenerCollectorGenerator.cs @@ -0,0 +1,138 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using static Simulation.Constants; + +namespace Simulation.Generators +{ + [Generator(LanguageNames.CSharp)] + internal class ListenerCollectorGenerator : IIncrementalGenerator + { + private static readonly SourceBuilder source = new(); + + void IIncrementalGenerator.Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValuesProvider filter = context.SyntaxProvider.CreateSyntaxProvider(Predicate, Transform); + context.RegisterSourceOutput(filter, Generate); + } + + private static bool Predicate(SyntaxNode node, CancellationToken token) + { + return node.IsKind(SyntaxKind.ClassDeclaration); + } + + private static SystemType? Transform(GeneratorSyntaxContext context, CancellationToken token) + { + if (context.Node is TypeDeclarationSyntax typeDeclaration) + { + if (context.SemanticModel.GetDeclaredSymbol(typeDeclaration) is ITypeSymbol typeSymbol) + { + ImmutableArray interfaces = typeSymbol.AllInterfaces; + List listenedMessageTypes = new(interfaces.Length); + foreach (INamedTypeSymbol interfaceSymbol in interfaces) + { + string interfaceTypeName = interfaceSymbol.ToDisplayString(); + if (interfaceTypeName.StartsWith(ListenerInterfaceTypeName + "<")) + { + ITypeSymbol messageType = interfaceSymbol.TypeArguments[0]; + listenedMessageTypes.Add(messageType); + } + } + + if (listenedMessageTypes.Count > 0) + { + return new SystemType(typeDeclaration, typeSymbol, listenedMessageTypes); + } + } + } + + return null; + } + + private static void Generate(SourceProductionContext context, SystemType? input) + { + if (input is not null) + { + try + { + context.AddSource($"{input.fullTypeName}.generated.cs", Generate(input)); + } + catch (Exception ex) + { + context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("GEN001", "Generation error in {0}", "An error occurred while generating the source code: {1}", "Generator", DiagnosticSeverity.Error, true), Location.None, input.typeName, ex.Message)); + } + } + } + + public static string Generate(SystemType input) + { + source.Clear(); + source.AppendLine("using System;"); + source.AppendLine("using Unmanaged;"); + source.AppendLine("using Simulation;"); + source.AppendLine("using System.Runtime.InteropServices;"); + source.AppendLine("using System.ComponentModel;"); + source.AppendLine(); + + if (input.containingNamespace is not null) + { + source.AppendLine($"namespace {input.containingNamespace}"); + source.BeginGroup(); + } + + source.Append($"public partial class {input.typeName}"); + source.Append(" : "); + source.Append(ListenerInterfaceTypeName); + source.AppendLine(); + + source.BeginGroup(); + { + source.Append("int "); + source.Append(ListenerInterfaceTypeName); + source.Append('.'); + source.Append("Count => "); + source.Append(input.listenedMessageTypes.Count); + source.Append(';'); + source.AppendLine(); + + source.AppendLine(); + + source.Append("void "); + source.Append(ListenerInterfaceTypeName); + source.Append('.'); + source.Append("CollectMessageHandlers(Span receivers)"); + source.AppendLine(); + source.BeginGroup(); + { + for (int i = 0; i < input.listenedMessageTypes.Count; i++) + { + ITypeSymbol messageType = input.listenedMessageTypes[i]; + source.Append("receivers["); + source.Append(i); + source.Append("] = MessageHandler.Get<"); + source.Append(ListenerInterfaceTypeName); + source.Append('<'); + source.Append(messageType.ToDisplayString()); + source.Append(">, "); + source.Append(messageType.ToDisplayString()); + source.Append(">(this);"); + source.AppendLine(); + } + } + source.EndGroup(); + } + source.EndGroup(); + + if (input.containingNamespace is not null) + { + source.EndGroup(); + } + + return source.ToString(); + } + } +} \ No newline at end of file diff --git a/generator/Generators/SystemBankGenerator.cs b/generator/Generators/SystemBankGenerator.cs deleted file mode 100644 index a106399..0000000 --- a/generator/Generators/SystemBankGenerator.cs +++ /dev/null @@ -1,336 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using static Simulation.Constants; - -namespace Simulation.Generators -{ - [Generator(LanguageNames.CSharp)] - internal class SystemBankGenerator : IIncrementalGenerator - { - void IIncrementalGenerator.Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValuesProvider types = context.SyntaxProvider.CreateSyntaxProvider(Predicate, Transform); - context.RegisterSourceOutput(types.Collect(), Generate); - } - - private static bool Predicate(SyntaxNode node, CancellationToken token) - { - return node.IsKind(SyntaxKind.StructDeclaration); - } - - private void Generate(SourceProductionContext context, ImmutableArray typesArray) - { - List types = new(); - foreach (ITypeSymbol? type in typesArray) - { - if (type is not null) - { - //iterate through the types constructors, and skip this type if the default one is obsolete - bool skip = false; - foreach (ISymbol member in type.GetMembers()) - { - if (member is IMethodSymbol methodSymbol) - { - if (methodSymbol.MethodKind == MethodKind.Constructor && methodSymbol.Parameters.Length == 0) - { - foreach (AttributeData attribute in methodSymbol.GetAttributes()) - { - if (attribute.AttributeClass?.ToDisplayString() == "System.ObsoleteAttribute") - { - skip = true; - break; - } - } - - if (skip) - { - break; - } - } - } - } - - if (skip) - { - continue; - } - - types.Add(type); - } - } - - if (types.Count > 1) //dont generate if just 1 - { - //sort types by their declared order - ITypeSymbol[] sortedTypes = new ITypeSymbol[types.Count]; - for (int i = 0; i < types.Count; i++) - { - sortedTypes[i] = types[i]; - } - - sortedTypes = sortedTypes.OrderBy(GetSortOrderHint).ToArray(); - string source = Generate(sortedTypes, out string typeName); - context.AddSource($"{typeName}.generated.cs", source); - } - } - - private static ITypeSymbol? Transform(GeneratorSyntaxContext context, CancellationToken token) - { - StructDeclarationSyntax node = (StructDeclarationSyntax)context.Node; - ITypeSymbol? type = context.SemanticModel.GetDeclaredSymbol(node); - if (type is null) - { - return null; - } - - if (type is INamedTypeSymbol namedType) - { - if (namedType.IsGenericType) - { - return null; - } - } - - if (type.IsRefLikeType) - { - return null; - } - - if (type.DeclaredAccessibility != Accessibility.Public && type.DeclaredAccessibility != Accessibility.Internal) - { - return null; - } - - foreach (INamedTypeSymbol interfaceSymbol in type.AllInterfaces) - { - string interfaceTypeName = interfaceSymbol.ToDisplayString(); - if (interfaceTypeName == SystemInterfaceTypeName || SystemInterfaceTypeName.EndsWith("." + interfaceTypeName)) - { - return type; - } - } - - return null; - } - - public static string Generate(IReadOnlyList types, out string typeName) - { - string? assemblyName = types[0].ContainingAssembly?.Name; - if (assemblyName is not null) - { - if (assemblyName.EndsWith(".Core")) - { - assemblyName = assemblyName.Substring(0, assemblyName.Length - 5); - } - } - - SourceBuilder source = new(); - source.Append("using "); - source.Append(Namespace); - source.Append(';'); - source.AppendLine(); - - source.Append("using "); - source.Append(Namespace); - source.Append('.'); - source.Append("Functions"); - source.Append(';'); - source.AppendLine(); - - source.AppendLine("using Worlds;"); - source.AppendLine("using System;"); - source.AppendLine("using System.Runtime.InteropServices;"); - source.AppendLine(); - - if (assemblyName is not null) - { - source.Append("namespace "); - source.AppendLine(assemblyName); - source.BeginGroup(); - } - - source.AppendLine("/// "); - source.AppendLine("/// Contains all types declared by this project."); - source.AppendLine("/// "); - - if (assemblyName is not null && assemblyName.EndsWith(".Systems")) - { - typeName = PluralSystemBankTypeNameFormat.Replace("{0}", assemblyName.Substring(0, assemblyName.Length - 8)); - } - else - { - typeName = SystemBankTypeNameFormat.Replace("{0}", assemblyName ?? ""); - } - - typeName = typeName.Replace(".", ""); - source.Append("public readonly struct "); - source.Append(typeName); - source.Append(" : "); - source.Append(SystemInterfaceTypeName); - source.AppendLine(); - source.BeginGroup(); - { - source.Append("unsafe readonly (StartSystem start, UpdateSystem update, FinishSystem finish, DisposeSystem dispose) ISystem.Functions"); - source.AppendLine(); - source.BeginGroup(); - { - source.Append("get"); - source.AppendLine(); - source.BeginGroup(); - { - source.Append("return (new(&StartSystem), new(&UpdateSystem), new(&FinishSystem), new(&DisposeSystem));"); - source.AppendLine(); - source.AppendLine(); - - //start - source.Append("[UnmanagedCallersOnly]"); - source.AppendLine(); - source.Append("static void StartSystem(SystemContainer systemContainer, World world)"); - source.AppendLine(); - source.BeginGroup(); - { - source.Append("if (systemContainer.IsSimulatorWorld(world))"); - source.AppendLine(); - source.BeginGroup(); - { - foreach (ITypeSymbol systemType in types) - { - source.Append("systemContainer.simulator.AddSystem(new "); - source.Append(systemType.ToDisplayString()); - source.Append("());"); - source.AppendLine(); - } - } - source.EndGroup(); - } - source.EndGroup(); - source.AppendLine(); - - //update - source.Append("[UnmanagedCallersOnly]"); - source.AppendLine(); - source.Append("static void UpdateSystem(SystemContainer systemContainer, World world, TimeSpan delta)"); - source.AppendLine(); - source.BeginGroup(); - { - } - source.EndGroup(); - source.AppendLine(); - - //finish - source.Append("[UnmanagedCallersOnly]"); - source.AppendLine(); - source.Append("static void FinishSystem(SystemContainer systemContainer, World world)"); - source.AppendLine(); - source.BeginGroup(); - { - source.Append("if (systemContainer.IsSimulatorWorld(world))"); - source.AppendLine(); - source.BeginGroup(); - { - for (int i = types.Count - 1; i >= 0; i--) - { - ITypeSymbol systemType = types[i]; - source.Append("systemContainer.simulator.RemoveSystem<"); - source.Append(systemType.ToDisplayString()); - source.Append(">();"); - source.AppendLine(); - } - } - source.EndGroup(); - } - source.EndGroup(); - source.AppendLine(); - - //dispose - source.Append("[UnmanagedCallersOnly]"); - source.AppendLine(); - source.Append("static void DisposeSystem(SystemContainer systemContainer, World world)"); - source.AppendLine(); - source.BeginGroup(); - { - } - source.EndGroup(); - } - source.EndGroup(); - } - source.EndGroup(); - source.AppendLine(); - - string shortTypeName = SystemInterfaceTypeName.Substring(SystemInterfaceTypeName.IndexOf('.') + 1); - source.Append("readonly void IDisposable.Dispose()"); - source.AppendLine(); - source.BeginGroup(); - { - } - source.EndGroup(); - source.AppendLine(); - - //start function - source.Append("readonly void "); - source.Append(shortTypeName); - source.Append(".Start(in SystemContext context, in World world)"); - source.AppendLine(); - - source.BeginGroup(); - { - } - source.EndGroup(); - - //update function - source.Append("readonly void "); - source.Append(shortTypeName); - source.Append(".Update(in SystemContext context, in World world, in TimeSpan delta)"); - source.AppendLine(); - - source.BeginGroup(); - { - } - source.EndGroup(); - - //finish function - source.Append("readonly void "); - source.Append(shortTypeName); - source.Append(".Finish(in SystemContext context, in World world)"); - source.AppendLine(); - - source.BeginGroup(); - { - } - source.EndGroup(); - } - source.EndGroup(); - - if (assemblyName is not null) - { - source.EndGroup(); - } - - return source.ToString(); - } - - private static int GetSortOrderHint(ITypeSymbol typeSymbol) - { - foreach (AttributeData attribute in typeSymbol.GetAttributes()) - { - if (attribute.AttributeClass?.ToDisplayString() == "Simulation.SystemOrderAttribute") - { - foreach (TypedConstant argument in attribute.ConstructorArguments) - { - if (argument.Kind == TypedConstantKind.Primitive && argument.Type?.SpecialType == SpecialType.System_Int32) - { - return (int)argument.Value!; - } - } - } - } - - return 0; - } - } -} \ No newline at end of file diff --git a/generator/ProgramInput.cs b/generator/ProgramInput.cs deleted file mode 100644 index 92a897a..0000000 --- a/generator/ProgramInput.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Simulation -{ - public class ProgramInput : Input - { - public ProgramInput(TypeDeclarationSyntax typeDeclaration, ITypeSymbol typeSymbol) : base(typeDeclaration, typeSymbol) - { - } - } -} \ No newline at end of file diff --git a/generator/SystemInput.cs b/generator/SystemInput.cs deleted file mode 100644 index 9165a6e..0000000 --- a/generator/SystemInput.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Simulation -{ - public class SystemInput : Input - { - public SystemInput(TypeDeclarationSyntax typeDeclaration, ITypeSymbol typeSymbol) : base(typeDeclaration, typeSymbol) - { - } - } -} \ No newline at end of file diff --git a/generator/Input.cs b/generator/SystemType.cs similarity index 71% rename from generator/Input.cs rename to generator/SystemType.cs index 37c3e75..5ffd06e 100644 --- a/generator/Input.cs +++ b/generator/SystemType.cs @@ -1,20 +1,24 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; namespace Simulation { - public abstract class Input + public class SystemType { public readonly TypeDeclarationSyntax typeDeclaration; public readonly ITypeSymbol typeSymbol; public readonly string? containingNamespace; public readonly string typeName; public readonly string fullTypeName; + public readonly IReadOnlyList listenedMessageTypes; - public Input(TypeDeclarationSyntax typeDeclaration, ITypeSymbol typeSymbol) + public SystemType(TypeDeclarationSyntax typeDeclaration, ITypeSymbol typeSymbol, IReadOnlyList listenedMessageTypes) { this.typeDeclaration = typeDeclaration; this.typeSymbol = typeSymbol; + this.listenedMessageTypes = listenedMessageTypes; + if (!typeSymbol.ContainingNamespace.IsGlobalNamespace) { containingNamespace = typeSymbol.ContainingNamespace.ToDisplayString(); diff --git a/tests/Messages/AppendCharacter.cs b/tests/Messages/AppendCharacter.cs new file mode 100644 index 0000000..a5eb185 --- /dev/null +++ b/tests/Messages/AppendCharacter.cs @@ -0,0 +1,12 @@ +namespace Simulation.Tests +{ + public readonly struct AppendCharacter + { + public readonly char character; + + public AppendCharacter(char character) + { + this.character = character; + } + } +} \ No newline at end of file diff --git a/tests/Messages/UpdateMessage.cs b/tests/Messages/UpdateMessage.cs new file mode 100644 index 0000000..099a68b --- /dev/null +++ b/tests/Messages/UpdateMessage.cs @@ -0,0 +1,12 @@ +namespace Simulation.Tests +{ + public readonly struct UpdateMessage + { + public readonly double deltaTime; + + public UpdateMessage(double deltaTime) + { + this.deltaTime = deltaTime; + } + } +} \ No newline at end of file diff --git a/tests/ProgramTests.cs b/tests/ProgramTests.cs deleted file mode 100644 index 8199e5f..0000000 --- a/tests/ProgramTests.cs +++ /dev/null @@ -1,155 +0,0 @@ -using Collections.Generic; -using Simulation.Components; -using System; -using System.Threading; -using Unmanaged; -using Worlds; - -namespace Simulation.Tests -{ - public class ProgramTests : SimulationTests - { - [Test] - [CancelAfter(1000)] - public void SimpleProgram(CancellationToken token) - { - using List values = new(); - Program program = Program.Create(world, new Calculator(4, 2)); - Assert.That(program.State, Is.EqualTo(IsProgram.State.Uninitialized)); - - StatusCode statusCode; - do - { - simulator.Update(); - - ref Calculator calculator = ref program.Read(); - values.Add(calculator.value); - Assert.That(program.State, Is.Not.EqualTo(IsProgram.State.Uninitialized)); - token.ThrowIfCancellationRequested(); - } - while (!program.IsFinished(out statusCode)); - - Calculator finishedCalculator = program.Read(); - Assert.That(finishedCalculator.value, Is.EqualTo(finishedCalculator.limit * finishedCalculator.additive)); - Assert.That(program.State, Is.EqualTo(IsProgram.State.Finished)); - Assert.That(statusCode, Is.EqualTo(StatusCode.Success(0))); - Assert.That(values.Count, Is.EqualTo(4)); - Assert.That(values[0], Is.EqualTo(2)); - Assert.That(values[1], Is.EqualTo(4)); - Assert.That(values[2], Is.EqualTo(6)); - Assert.That(values[3], Is.EqualTo(8)); - } - - [Test] - public void ExitEarly() - { - Program program = Program.Create(world, new Calculator(4, 2)); - - Assert.That(program.State, Is.EqualTo(IsProgram.State.Uninitialized)); - - simulator.Update(); //to invoke the initializer and update - ref Calculator calculator = ref program.Read(); - - Assert.That(calculator.text.ToString(), Is.EqualTo("Running2")); - program.Dispose(); - - simulator.Update(); //to invoke the finisher - - Assert.That(calculator.value, Is.EqualTo(calculator.additive)); - Assert.That(calculator.text.ToString(), Is.EqualTo(StatusCode.Termination.ToString())); - } - - [Test] - [CancelAfter(1000)] - public void ReRunProgram(CancellationToken token) - { - Program program = Program.Create(world, new Calculator(4, 2)); - - while (!program.IsFinished(out StatusCode statusCode)) - { - simulator.Update(); - token.ThrowIfCancellationRequested(); - } - - ref Calculator calculator = ref program.Read(); - Assert.That(program.State, Is.EqualTo(IsProgram.State.Finished)); - Assert.That(calculator.value, Is.EqualTo(calculator.limit * calculator.additive)); - program.Restart(); - - while (!program.IsFinished(out StatusCode statusCode)) - { - simulator.Update(); - token.ThrowIfCancellationRequested(); - } - - Assert.That(program.State, Is.EqualTo(IsProgram.State.Finished)); - Assert.That(calculator.value, Is.EqualTo(calculator.limit * calculator.additive)); - } - - [Test, CancelAfter(3000)] - public void SystemsInitializeWithProgram(CancellationToken token) - { - using List startedWorlds = new(); - using List updatedWorlds = new(); - using List finishedWorlds = new(); - using MemoryAddress disposed = MemoryAddress.AllocateValue(false); - - simulator.AddSystem(new DummySystem(startedWorlds, updatedWorlds, finishedWorlds, disposed)); - using (Program program = Program.Create(world, new TimedProgram(2))) - { - StatusCode statusCode; - while (!program.IsFinished(out statusCode)) - { - simulator.Update(); - token.ThrowIfCancellationRequested(); - } - - Assert.That(statusCode, Is.EqualTo(StatusCode.Success(0))); - } - simulator.RemoveSystem(); - - Assert.That(startedWorlds.Count, Is.EqualTo(2)); - Assert.That(updatedWorlds.Count, Is.GreaterThan(2)); - Assert.That(finishedWorlds.Count, Is.EqualTo(2)); - } - - [Test, CancelAfter(3000)] - public void ProgramAskingSimulatorUpdateSystems(CancellationToken token) - { - using List startedWorlds = new(); - using List updatedWorlds = new(); - using List finishedWorlds = new(); - using MemoryAddress disposed = MemoryAddress.AllocateValue(false); - - simulator.AddSystem(new DummySystem(startedWorlds, updatedWorlds, finishedWorlds, disposed)); - World programWorld; - using (Program program = new Program(world)) - { - programWorld = program.ProgramWorld; - simulator.Update(); - if (program.IsFinished(out StatusCode statusCode)) - { - Assert.That(statusCode, Is.EqualTo(StatusCode.Success(0))); - } - else - { - throw new InvalidOperationException("Program did not finish as expected"); - } - } - simulator.RemoveSystem(); - - Assert.That(startedWorlds.Count, Is.EqualTo(2)); - Assert.That(startedWorlds[0], Is.EqualTo(world)); - Assert.That(startedWorlds[1], Is.EqualTo(programWorld)); - - Assert.That(updatedWorlds.Count, Is.EqualTo(3)); - Assert.That(updatedWorlds[0], Is.EqualTo(world)); - Assert.That(updatedWorlds[1], Is.EqualTo(world)); - Assert.That(updatedWorlds[2], Is.EqualTo(programWorld)); - - Assert.That(finishedWorlds.Count, Is.EqualTo(2)); - Assert.That(finishedWorlds[0], Is.EqualTo(programWorld)); - Assert.That(finishedWorlds[1], Is.EqualTo(world)); - } - } -} \ No newline at end of file diff --git a/tests/Programs/Calculator.cs b/tests/Programs/Calculator.cs deleted file mode 100644 index e5ac628..0000000 --- a/tests/Programs/Calculator.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Unmanaged; -using Worlds; - -namespace Simulation.Tests -{ - public partial struct Calculator : IProgram - { - public int value; - public ASCIIText256 text; - - public readonly int limit; - public readonly int additive; - private readonly World world; - - private Calculator(World world, int limit, int additive) - { - this.limit = limit; - this.additive = additive; - this.world = world; - } - - public Calculator(int limit, int additive) - { - this.limit = limit; - this.additive = additive; - } - - readonly void IProgram.Start(ref Calculator calculator, in Simulator simulator, in World world) - { - calculator = new(world, calculator.limit, calculator.additive); - calculator.text = "Not running"; - } - - StatusCode IProgram.Update(in TimeSpan delta) - { - value += additive; - text = "Running"; - text.Append(value); - - uint newEntity = world.CreateEntity(); - world.AddComponent(newEntity, true); - if (world.Count >= limit) - { - return StatusCode.Success(0); - } - else - { - return StatusCode.Continue; - } - } - - void IProgram.Finish(in StatusCode statusCode) - { - Span buffer = stackalloc char[64]; - int length = statusCode.ToString(buffer); - text = new(buffer.Slice(0, length)); - } - } -} \ No newline at end of file diff --git a/tests/Programs/DummyProgram.cs b/tests/Programs/DummyProgram.cs deleted file mode 100644 index cc6b1d2..0000000 --- a/tests/Programs/DummyProgram.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Collections.Generic; -using System; -using Worlds; - -namespace Simulation.Tests -{ - public partial struct DummyProgram : IProgram - { - private readonly List startedWorlds; - private readonly int limit; - private StatusCode exitCode; - private int iteration; - - public readonly StatusCode ExitCode => exitCode; - - [Obsolete("Default constructor not supported", true)] - public DummyProgram() - { - throw new NotSupportedException(); - } - - public DummyProgram(int limit, List startedWorlds) - { - this.limit = limit; - this.startedWorlds = startedWorlds; - } - - void IProgram.Finish(in StatusCode statusCode) - { - exitCode = statusCode; - } - - readonly void IProgram.Start(ref DummyProgram program, in Simulator simulator, in World world) - { - startedWorlds.Add(world); - } - - StatusCode IProgram.Update(in TimeSpan delta) - { - iteration++; - if (iteration >= limit) - { - return StatusCode.Success(0); - } - else - { - return StatusCode.Continue; - } - } - } -} \ No newline at end of file diff --git a/tests/Programs/ProgramThatUpdatesSystemsOnStart.cs b/tests/Programs/ProgramThatUpdatesSystemsOnStart.cs deleted file mode 100644 index ce919e6..0000000 --- a/tests/Programs/ProgramThatUpdatesSystemsOnStart.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Worlds; - -namespace Simulation.Tests -{ - public partial struct ProgramThatUpdatesSystemsOnStart : IProgram - { - private ProgramThatUpdatesSystemsOnStart(Simulator simulator) - { - simulator.UpdateSystems(TimeSpan.MinValue); - } - - readonly void IProgram.Start(ref ProgramThatUpdatesSystemsOnStart program, in Simulator simulator, in World world) - { - program = new(simulator); - } - - readonly StatusCode IProgram.Update(in TimeSpan delta) - { - return StatusCode.Success(0); - } - - readonly void IProgram.Finish(in StatusCode statusCode) - { - } - } -} \ No newline at end of file diff --git a/tests/Programs/TimedProgram.cs b/tests/Programs/TimedProgram.cs deleted file mode 100644 index 9ba3a53..0000000 --- a/tests/Programs/TimedProgram.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Worlds; - -namespace Simulation.Tests -{ - public partial struct TimedProgram : IProgram - { - private float time; - private readonly float duration; - - public TimedProgram(float duration) - { - this.duration = duration; - time = 0f; - } - - readonly void IProgram.Start(ref TimedProgram program, in Simulator simulator, in World world) - { - } - - StatusCode IProgram.Update(in TimeSpan delta) - { - time += (float)delta.TotalSeconds; - if (time >= duration) - { - return StatusCode.Success(0); - } - else - { - return StatusCode.Continue; - } - } - - readonly void IProgram.Finish(in StatusCode statusCode) - { - } - } -} \ No newline at end of file diff --git a/tests/Simulation.Tests.csproj b/tests/Simulation.Tests.csproj index 39ffac7..345bdf3 100644 --- a/tests/Simulation.Tests.csproj +++ b/tests/Simulation.Tests.csproj @@ -34,14 +34,6 @@ Analyzer false - - Analyzer - false - - - Analyzer - false - diff --git a/tests/SimulationTests.cs b/tests/SimulationTests.cs index 733aed0..f11cf90 100644 --- a/tests/SimulationTests.cs +++ b/tests/SimulationTests.cs @@ -1,31 +1,26 @@ -using Types; -using Unmanaged.Tests; +using Unmanaged.Tests; using Worlds; namespace Simulation.Tests { public abstract class SimulationTests : UnmanagedTests { - protected World world; - protected Simulator simulator; - - static SimulationTests() - { - MetadataRegistry.Load(); - MetadataRegistry.Load(); - } + public Simulator simulator; + public World world; protected override void SetUp() { base.SetUp(); - world = CreateWorld(); + world = new(CreateSchema()); simulator = new(world); } protected override void TearDown() { simulator.Dispose(); + simulator = default; world.Dispose(); + world = default; base.TearDown(); } @@ -34,18 +29,24 @@ protected void Update() simulator.Update(); } - protected virtual Schema CreateSchema() + protected void Update(double deltaTime) { - Schema schema = new(); - schema.Load(); - schema.Load(); - return schema; + simulator.Update(deltaTime); } - protected World CreateWorld() + protected void Broadcast(T message) where T : unmanaged + { + simulator.Broadcast(message); + } + + protected void Broadcast(ref T message) where T : unmanaged + { + simulator.Broadcast(ref message); + } + + protected virtual Schema CreateSchema() { - World world = new(CreateSchema()); - return world; + return new(); } } } \ No newline at end of file diff --git a/tests/SimulatorTests.cs b/tests/SimulatorTests.cs index bc545ec..18f977a 100644 --- a/tests/SimulatorTests.cs +++ b/tests/SimulatorTests.cs @@ -1,5 +1,4 @@ -using Collections.Generic; -using Unmanaged; +using System.Threading; using Worlds; namespace Simulation.Tests @@ -7,61 +6,107 @@ namespace Simulation.Tests public class SimulatorTests : SimulationTests { [Test] - public void VerifyOrderOfOperations() + public void CreatingAndDisposing() { - using World simulatorWorld = CreateWorld(); - using (Simulator simulator = new(simulatorWorld)) - { - using List systemStartedWorlds = new(); - using List programStartedWorlds = new(); - using List systemUpdatedWorlds = new(); - using List systemFinishedWorlds = new(); - using MemoryAddress disposed = MemoryAddress.AllocateValue(false); - simulator.AddSystem(new DummySystem(systemStartedWorlds, systemUpdatedWorlds, systemFinishedWorlds, disposed)); - World programWorld; - using (Program program = Program.Create(simulatorWorld, new DummyProgram(4, programStartedWorlds))) - { - programWorld = program.ProgramWorld; - simulator.Update(); + using World world = new(); + Simulator simulator = new(world); + Assert.That(simulator.IsDisposed, Is.False); + simulator.Dispose(); + Assert.That(simulator.IsDisposed, Is.True); - Assert.That(systemStartedWorlds.Count, Is.EqualTo(2)); - Assert.That(systemStartedWorlds[0], Is.EqualTo(simulatorWorld)); - Assert.That(systemStartedWorlds[1], Is.EqualTo(programWorld)); - - Assert.That(programStartedWorlds.Count, Is.EqualTo(1)); - Assert.That(programStartedWorlds[0], Is.EqualTo(programWorld)); + simulator = new(world); + Assert.That(simulator.IsDisposed, Is.False); + simulator.Dispose(); + Assert.That(simulator.IsDisposed, Is.True); + } - Assert.That(systemUpdatedWorlds.Count, Is.EqualTo(2)); - Assert.That(systemUpdatedWorlds[0], Is.EqualTo(simulatorWorld)); - Assert.That(systemUpdatedWorlds[1], Is.EqualTo(programWorld)); + [Test] + public void AddingAndRemovingSystems() + { + Assert.That(simulator.Count, Is.EqualTo(0)); + EmptySystem system = new(); + simulator.Add(system); + Assert.That(simulator.Count, Is.EqualTo(1)); + Assert.That(simulator.Contains(), Is.True); + Assert.That(simulator.Systems, Has.Exactly(1).EqualTo(system)); + EmptySystem removedSystem = simulator.Remove(); + Assert.That(simulator.Count, Is.EqualTo(0)); + Assert.That(removedSystem, Is.SameAs(system)); + } - simulator.Update(); + [Test] + public void BroadcastingMessages() + { + TimeSystem system = new(); + Broadcast(new UpdateMessage(1f)); + Assert.That(system.time, Is.EqualTo(0f)); + simulator.Add(system); + Broadcast(new UpdateMessage(1f)); + Assert.That(system.time, Is.EqualTo(1f)); + Broadcast(new UpdateMessage(1f)); + Assert.That(system.time, Is.EqualTo(2f)); + simulator.Remove(); + Broadcast(new UpdateMessage(1f)); + Assert.That(system.time, Is.EqualTo(2f)); + } - Assert.That(systemUpdatedWorlds.Count, Is.EqualTo(4)); - Assert.That(systemUpdatedWorlds[2], Is.EqualTo(simulatorWorld)); - Assert.That(systemUpdatedWorlds[3], Is.EqualTo(programWorld)); + [Test] + public void BroadcastingWithSystemsOutOfOrder() + { + TimeSystem system = new(); + using TextSystem text = new(); + Broadcast(new UpdateMessage(1f)); + Assert.That(system.time, Is.EqualTo(0f)); + simulator.Add(system); + simulator.Add(text); + Broadcast(new UpdateMessage(1f)); + Assert.That(system.time, Is.EqualTo(1f)); + Assert.That(text.Text.ToString(), Is.EqualTo("1")); + Broadcast(new UpdateMessage(1f)); + Assert.That(system.time, Is.EqualTo(2f)); + Assert.That(text.Text.ToString(), Is.EqualTo("11")); + simulator.Remove(); + Broadcast(new UpdateMessage(1f)); + Assert.That(system.time, Is.EqualTo(2f)); + Assert.That(text.Text.ToString(), Is.EqualTo("111")); + simulator.Remove(false); + Broadcast(new UpdateMessage(1f)); + Assert.That(system.time, Is.EqualTo(2f)); + Assert.That(text.Text.ToString(), Is.EqualTo("111")); + } - simulator.Update(); + [Test] + public void UpdatingSimulatorForward() + { + TimeSystem system = new(); + simulator.Add(system); - Assert.That(systemUpdatedWorlds.Count, Is.EqualTo(6)); - Assert.That(systemUpdatedWorlds[4], Is.EqualTo(simulatorWorld)); - Assert.That(systemUpdatedWorlds[5], Is.EqualTo(programWorld)); + Update(20); + Assert.That(simulator.Time, Is.EqualTo(20)); + Assert.That(system.time, Is.EqualTo(20)); - simulator.Update(); + Update(10); + Assert.That(simulator.Time, Is.EqualTo(30)); + Assert.That(system.time, Is.EqualTo(30)); - Assert.That(systemUpdatedWorlds.Count, Is.EqualTo(8)); - Assert.That(systemUpdatedWorlds[6], Is.EqualTo(simulatorWorld)); - Assert.That(systemUpdatedWorlds[7], Is.EqualTo(programWorld)); - } + Update(); + Thread.Sleep(1000); + Update(); + Assert.That(simulator.Time, Is.EqualTo(31).Within(0.01)); + Assert.That(system.time, Is.EqualTo(31).Within(0.01)); - simulator.RemoveSystem(); + Update(5); + Assert.That(simulator.Time, Is.EqualTo(36).Within(0.01)); + Assert.That(system.time, Is.EqualTo(36).Within(0.01)); - Assert.That(disposed.Read(), Is.True); + Update(0); + Assert.That(simulator.Time, Is.EqualTo(36).Within(0.01)); + Assert.That(system.time, Is.EqualTo(36).Within(0.01)); - Assert.That(systemFinishedWorlds.Count, Is.EqualTo(2)); - Assert.That(systemFinishedWorlds[0], Is.EqualTo(programWorld)); - Assert.That(systemFinishedWorlds[1], Is.EqualTo(simulatorWorld)); - } + simulator.Remove(system); + Update(10); + Assert.That(simulator.Time, Is.EqualTo(46).Within(0.01)); + Assert.That(system.time, Is.EqualTo(36).Within(0.01)); } } } \ No newline at end of file diff --git a/tests/StatusCodeTests.cs b/tests/StatusCodeTests.cs deleted file mode 100644 index ed83b35..0000000 --- a/tests/StatusCodeTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; - -namespace Simulation.Tests -{ - public class StatusCodeTests - { - [Test] - public void VerifySuccessCode() - { - StatusCode a = StatusCode.Success(0); - StatusCode b = StatusCode.Success(1); - Assert.That(a.IsSuccess, Is.True); - Assert.That(b.IsSuccess, Is.True); - Assert.That(a.Code, Is.EqualTo(0)); - Assert.That(b.Code, Is.EqualTo(1)); - } - - [Test] - public void VerifyFailure() - { - StatusCode a = StatusCode.Failure(0); - StatusCode b = StatusCode.Failure(1); - Assert.That(a.IsSuccess, Is.False); - Assert.That(b.IsSuccess, Is.False); - Assert.That(a.Code, Is.EqualTo(0)); - Assert.That(b.Code, Is.EqualTo(1)); - } - - [Test] - public void MaxCodes() - { - StatusCode a = StatusCode.Success(StatusCode.MaxCode); - Assert.That(a.Code, Is.EqualTo(StatusCode.MaxCode)); - Assert.That(a.IsSuccess, Is.True); - - StatusCode b = StatusCode.Failure(StatusCode.MaxCode); - Assert.That(b.Code, Is.EqualTo(StatusCode.MaxCode)); - Assert.That(b.IsSuccess, Is.False); - } - - [Test] - public void DefaultNotTheSame() - { - StatusCode defaultCode = default; - StatusCode continueCode = StatusCode.Continue; - StatusCode success = StatusCode.Success(0); - StatusCode failure = StatusCode.Failure(0); - - Assert.That(defaultCode, Is.Not.EqualTo(continueCode)); - Assert.That(continueCode, Is.Not.EqualTo(defaultCode)); - Assert.That(defaultCode, Is.Not.EqualTo(success)); - Assert.That(defaultCode, Is.Not.EqualTo(failure)); - Assert.That(continueCode, Is.Not.EqualTo(success)); - Assert.That(continueCode, Is.Not.EqualTo(failure)); - - Assert.That(StatusCode.Termination, Is.Not.EqualTo(defaultCode)); - Assert.That(StatusCode.Termination, Is.Not.EqualTo(continueCode)); - Assert.That(StatusCode.Termination, Is.Not.EqualTo(success)); - Assert.That(StatusCode.Termination, Is.Not.EqualTo(failure)); - } - -#if DEBUG - [Test] - public void ThrowIfOutOfRange() - { - StatusCode a = StatusCode.Success(StatusCode.MaxCode); - Assert.Throws(() => StatusCode.Success(StatusCode.MaxCode + 1)); - Assert.Throws(() => StatusCode.Failure(StatusCode.MaxCode + 1)); - } -#endif - } -} \ No newline at end of file diff --git a/tests/SystemTests.cs b/tests/SystemTests.cs deleted file mode 100644 index 1340375..0000000 --- a/tests/SystemTests.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Unmanaged; -using Worlds; - -namespace Simulation.Tests -{ - public class SystemTests : SimulationTests - { - [Test] - public void SimpleTest() - { - using (World simulatorWorld = CreateWorld()) - { - using (Simulator simulator = new(simulatorWorld)) - { - simulator.AddSystem(new SimpleSystem(4)); - - Assert.That(simulator.ContainsSystem(), Is.True); - Assert.That(simulator.Systems.Length, Is.EqualTo(1)); - - simulator.Update(); - - Assert.That(simulatorWorld.Count, Is.EqualTo(2)); - - Entity firstEntity = new(simulatorWorld, 1); - Assert.That(firstEntity.GetComponent(), Is.EqualTo(4)); - - Entity beforeFinalizeEntity = new(simulatorWorld, 2); - Assert.That(beforeFinalizeEntity.GetComponent(), Is.EqualTo(false)); - - Assert.That(simulator.ContainsSystem(), Is.True); - simulator.RemoveSystem(); - } - - Entity secondEntity = new(simulatorWorld, 2); - Assert.That(secondEntity.GetComponent(), Is.EqualTo(true)); - } - } - - [Test] - public void ReceiveMessages() - { - using (World world = CreateWorld()) - { - using (Simulator simulator = new(world)) - { - simulator.AddSystem(new MessageHandlerSystem()); - - Assert.That(simulator.Systems.Length, Is.EqualTo(1)); - - StatusCode statusCode = simulator.TryHandleMessage(new ASCIIText256("test message")); - Assert.That(statusCode, Is.Not.EqualTo(default(StatusCode))); - - statusCode = simulator.TryHandleMessage(new ASCIIText256("and another one")); - Assert.That(statusCode, Is.Not.EqualTo(default(StatusCode))); - - Assert.That(world.Count, Is.EqualTo(2)); - - Entity firstEntity = new(world, 1); - Entity secondEntity = new(world, 2); - Assert.That(firstEntity.GetComponent(), Is.EqualTo(new ASCIIText256("test message"))); - Assert.That(secondEntity.GetComponent(), Is.EqualTo(new ASCIIText256("and another one"))); - } - } - } - - [Test, CancelAfter(1000)] - public void SystemInsideSystem() - { - using (World world = CreateWorld()) - { - using (Simulator simulator = new(world)) - { - simulator.AddSystem(new StackedSystem()); - - Assert.That(simulator.Systems.Length, Is.EqualTo(2)); - - simulator.Update(); - - Assert.That(simulator.Systems.Length, Is.EqualTo(2)); - Assert.That(world.Count, Is.EqualTo(2)); - - Entity firstEntity = new(world, 1); - Assert.That(firstEntity.GetComponent(), Is.EqualTo(4)); - - Entity beforeFinalizeEntity = new(world, 2); - Assert.That(beforeFinalizeEntity.GetComponent(), Is.EqualTo(false)); - - simulator.RemoveSystem(); - - Assert.That(simulator.Systems.Length, Is.EqualTo(0)); - } - - Entity secondEntity = new(world, 2); - Assert.That(secondEntity.GetComponent(), Is.EqualTo(true)); - } - } - - [Test, CancelAfter(1000)] - public void SystemDisposesWhenSimulatorIsInCorrectOrder() - { - using (World world = CreateWorld()) - { - using (Simulator simulator = new(world)) - { - simulator.AddSystem(new StackedSystem()); - - Assert.That(simulator.Systems.Length, Is.EqualTo(2)); - - simulator.Update(); - } - - Entity secondEntity = new(world, 2); - Assert.That(secondEntity.GetComponent(), Is.EqualTo(true)); - } - } - } -} \ No newline at end of file diff --git a/tests/Systems/DummySystem.cs b/tests/Systems/DummySystem.cs deleted file mode 100644 index 8e6be95..0000000 --- a/tests/Systems/DummySystem.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Collections.Generic; -using System; -using Unmanaged; -using Worlds; - -namespace Simulation.Tests -{ - public readonly partial struct DummySystem : ISystem - { - public readonly List startedWorlds; - public readonly List updatedWorlds; - public readonly List finishedWorlds; - public readonly MemoryAddress disposed; - - [Obsolete("Default constructor not supported", true)] - public DummySystem() - { - //todo: make the implementation generator create this constructor, if another - //public constructor exists - } - - public DummySystem(List startedWorlds, List updatedWorlds, List finishedWorlds, MemoryAddress disposed) - { - this.startedWorlds = startedWorlds; - this.updatedWorlds = updatedWorlds; - this.finishedWorlds = finishedWorlds; - this.disposed = disposed; - } - - public readonly void Dispose() - { - disposed.Write(true); - } - - void ISystem.Start(in SystemContext context, in World world) - { - startedWorlds.Add(world); - } - - void ISystem.Update(in SystemContext context, in World world, in TimeSpan delta) - { - updatedWorlds.Add(world); - } - - void ISystem.Finish(in SystemContext context, in World world) - { - finishedWorlds.Add(world); - } - } -} \ No newline at end of file diff --git a/tests/Systems/EmptySystem.cs b/tests/Systems/EmptySystem.cs new file mode 100644 index 0000000..9b948dc --- /dev/null +++ b/tests/Systems/EmptySystem.cs @@ -0,0 +1,16 @@ +using System.Diagnostics; + +namespace Simulation.Tests +{ + public partial class EmptySystem : ISystem, IListener + { + void IListener.Receive(ref char message) + { + Trace.WriteLine($"received a {message}"); + } + + void ISystem.Update(Simulator simulator, double deltaTime) + { + } + } +} \ No newline at end of file diff --git a/tests/Systems/MessageHandlerSystem.cs b/tests/Systems/MessageHandlerSystem.cs deleted file mode 100644 index f959221..0000000 --- a/tests/Systems/MessageHandlerSystem.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Simulation.Functions; -using System; -using System.Runtime.InteropServices; -using Unmanaged; -using Worlds; - -namespace Simulation.Tests -{ - public readonly partial struct MessageHandlerSystem : ISystem - { - [UnmanagedCallersOnly] - private static StatusCode ReceiveEvent(HandleMessage.Input input) - { - if (input.Simulator.World == input.world) - { - Entity messageEntity = new(input.world); - messageEntity.AddComponent(input.ReadMessage()); - return StatusCode.Success(0); - } - - return StatusCode.Continue; - } - - readonly void IDisposable.Dispose() - { - } - - unsafe void ISystem.CollectMessageHandlers(MessageHandlerCollector collector) - { - collector.Add(&ReceiveEvent); - } - - void ISystem.Start(in SystemContext context, in World world) - { - } - - void ISystem.Update(in SystemContext context, in World world, in TimeSpan delta) - { - } - - void ISystem.Finish(in SystemContext context, in World world) - { - } - } -} \ No newline at end of file diff --git a/tests/Systems/SimpleSystem.cs b/tests/Systems/SimpleSystem.cs deleted file mode 100644 index e3c47ec..0000000 --- a/tests/Systems/SimpleSystem.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using Worlds; - -namespace Simulation.Tests -{ - public readonly partial struct SimpleSystem : ISystem - { - private readonly int initialData; - - [Obsolete("Default constructor not supported", true)] - public SimpleSystem() - { - } - - public SimpleSystem(int initialData) - { - this.initialData = initialData; - } - - readonly void IDisposable.Dispose() - { - } - - void ISystem.Start(in SystemContext context, in World world) - { - if (context.SimulatorWorld == world) - { - Entity entity = new(world); //1 - entity.AddComponent(initialData); - } - } - - void ISystem.Update(in SystemContext context, in World world, in TimeSpan delta) - { - if (context.SimulatorWorld == world) - { - Entity entity = new(world); //2 - entity.AddComponent(false); - } - } - - void ISystem.Finish(in SystemContext context, in World world) - { - if (context.SimulatorWorld == world) - { - Entity entity = new(world, 2); - entity.SetComponent(true); - } - } - } -} \ No newline at end of file diff --git a/tests/Systems/StackedSystem.cs b/tests/Systems/StackedSystem.cs deleted file mode 100644 index 323f6c6..0000000 --- a/tests/Systems/StackedSystem.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Worlds; - -namespace Simulation.Tests -{ - public readonly partial struct StackedSystem : ISystem - { - public readonly void Dispose() - { - } - - void ISystem.Start(in SystemContext context, in World world) - { - if (context.IsSimulatorWorld(world)) - { - context.AddSystem(new SimpleSystem(4)); - } - } - - void ISystem.Update(in SystemContext context, in World world, in TimeSpan delta) - { - } - - void ISystem.Finish(in SystemContext context, in World world) - { - if (context.IsSimulatorWorld(world)) - { - context.RemoveSystem(); - } - } - } -} \ No newline at end of file diff --git a/tests/Systems/TextSystem.cs b/tests/Systems/TextSystem.cs new file mode 100644 index 0000000..05c175d --- /dev/null +++ b/tests/Systems/TextSystem.cs @@ -0,0 +1,36 @@ +using System; +using Unmanaged; + +namespace Simulation.Tests +{ + public partial class TextSystem : IDisposable, ISystem, IListener, IListener + { + private readonly Text text; + + public ReadOnlySpan Text => text.AsSpan(); + + public TextSystem() + { + text = new(); + } + + public void Dispose() + { + text.Dispose(); + } + + void IListener.Receive(ref AppendCharacter message) + { + text.Append(message.character); + } + + void IListener.Receive(ref UpdateMessage message) + { + text.Append(message.deltaTime); + } + + void ISystem.Update(Simulator simulator, double deltaTime) + { + } + } +} \ No newline at end of file diff --git a/tests/Systems/TimeSystem.cs b/tests/Systems/TimeSystem.cs new file mode 100644 index 0000000..51dbc4f --- /dev/null +++ b/tests/Systems/TimeSystem.cs @@ -0,0 +1,17 @@ +namespace Simulation.Tests +{ + public partial class TimeSystem : ISystem, IListener + { + public double time; + + void IListener.Receive(ref UpdateMessage message) + { + time += message.deltaTime; + } + + void ISystem.Update(Simulator simulator, double deltaTime) + { + time += deltaTime; + } + } +} \ No newline at end of file