diff --git a/ChickenGameTest.csproj b/ChickenGameTest.csproj
index c3fe914..88056f2 100644
--- a/ChickenGameTest.csproj
+++ b/ChickenGameTest.csproj
@@ -37,10 +37,10 @@
-
+
@@ -59,8 +59,11 @@
+
+
+
+
-
diff --git a/GlobalSuppressions.cs b/GlobalSuppressions.cs
new file mode 100644
index 0000000..72960b3
--- /dev/null
+++ b/GlobalSuppressions.cs
@@ -0,0 +1,8 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage("Style", "IDE0065:Misplaced using directive", Justification = "")]
diff --git a/export_presets.cfg b/export_presets.cfg
new file mode 100644
index 0000000..453c606
--- /dev/null
+++ b/export_presets.cfg
@@ -0,0 +1,49 @@
+[preset.0]
+
+name="Linux"
+platform="Linux"
+runnable=true
+dedicated_server=false
+custom_features=""
+export_filter="all_resources"
+include_filter=""
+exclude_filter="/mods"
+export_path="../../Builds/ChickenGameTest.x86_64"
+patches=PackedStringArray()
+patch_delta_encoding=false
+patch_delta_compression_level_zstd=19
+patch_delta_min_reduction=0.1
+patch_delta_include_filters="*"
+patch_delta_exclude_filters=""
+encryption_include_filters=""
+encryption_exclude_filters=""
+seed=0
+encrypt_pck=false
+encrypt_directory=false
+script_export_mode=2
+
+[preset.0.options]
+
+custom_template/debug=""
+custom_template/release=""
+debug/export_console_wrapper=1
+binary_format/embed_pck=false
+texture_format/s3tc_bptc=true
+texture_format/etc2_astc=false
+shader_baker/enabled=false
+binary_format/architecture="x86_64"
+ssh_remote_deploy/enabled=false
+ssh_remote_deploy/host="user@host_ip"
+ssh_remote_deploy/port="22"
+ssh_remote_deploy/extra_args_ssh=""
+ssh_remote_deploy/extra_args_scp=""
+ssh_remote_deploy/run_script="#!/usr/bin/env bash
+export DISPLAY=:0
+unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
+\"{temp_dir}/{exe_name}\" {cmd_args}"
+ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
+pkill -x -f \"{temp_dir}/{exe_name} {cmd_args}\"
+rm -rf \"{temp_dir}\""
+dotnet/include_scripts_content=false
+dotnet/include_debug_symbols=true
+dotnet/embed_build_outputs=false
diff --git a/mods/.gdignore b/mods/.gdignore
new file mode 100644
index 0000000..e69de29
diff --git a/src/Api/IFoodFactoryApi.cs b/src/Api/IFoodFactoryApi.cs
new file mode 100644
index 0000000..0bc9d11
--- /dev/null
+++ b/src/Api/IFoodFactoryApi.cs
@@ -0,0 +1,50 @@
+namespace FoodFactory;
+
+using System;
+using System.Collections.Generic;
+using FoodFactory.Conveyors;
+using FoodFactory.Recipes;
+using FoodFactory.Voxel;
+
+public interface IFoodFactoryApi
+{
+ T? GetApi() where T : class;
+ IRecipes Recipes { get; }
+ IBlueprintManger BlueprintManger { get; }
+ ITickManger TickManger { get; }
+ IItemRenderer ItemRenderer { get; }
+ IVoxelGridRegistry GridRegistry { get; }
+}
+public interface ITickManger
+{
+ delegate void Tick(TickArgs args);
+ event Tick GameTick;
+}
+public record struct TickArgs(int Tick, double Delta);
+public class TickManger : ITickManger
+{
+ public event ITickManger.Tick? GameTick;
+ public void BroadCast(TickArgs args) => GameTick?.Invoke(args);
+}
+public class FoodFactoryApi : IFoodFactoryApi
+{
+ public required IRecipes Recipes { get; set; }
+
+ public required IBlueprintManger BlueprintManger { get; set; }
+
+ public required ITickManger TickManger { get; set; }
+
+ public required IItemRenderer ItemRenderer { get; set; }
+
+ public required IVoxelGridRegistry GridRegistry { get; set; }
+
+ private readonly Dictionary _otherApi = [];
+ public T? GetApi() where T : class
+ {
+ if (_otherApi.TryGetValue(typeof(T), out var api))
+ {
+ return api as T;
+ }
+ return null;
+ }
+}
diff --git a/src/Api/IFoodFactoryApi.cs.uid b/src/Api/IFoodFactoryApi.cs.uid
new file mode 100644
index 0000000..2abba0b
--- /dev/null
+++ b/src/Api/IFoodFactoryApi.cs.uid
@@ -0,0 +1 @@
+uid://dbmq2c5nn8r1q
diff --git a/src/Arch.Extended/Arch.Persistence/Binary.cs b/src/Arch.Extended/Arch.Persistence/Binary.cs
new file mode 100644
index 0000000..af7cff9
--- /dev/null
+++ b/src/Arch.Extended/Arch.Persistence/Binary.cs
@@ -0,0 +1,497 @@
+using Arch.Core;
+using Arch.Core.Extensions;
+using Arch.Core.Extensions.Dangerous;
+using Arch.Core.Utils;
+using Arch.LowLevel.Jagged;
+using MessagePack;
+using MessagePack.Formatters;
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Utf8Json;
+
+namespace Arch.Persistence;
+
+
+///
+/// The class
+/// is a to (de)serialize a single to or from json.
+///
+public partial class SingleEntityFormatter : IMessagePackFormatter
+{
+
+ ///
+ public void Serialize(ref MessagePackWriter writer, Entity value, MessagePackSerializerOptions options)
+ {
+ // Write id
+ writer.WriteInt32(value.Id);
+
+#if !PURE_ECS
+
+ // Write world
+ writer.WriteInt32(value.WorldId);
+#endif
+
+ // Write size
+ var componentTypes = value.GetComponentTypes();
+ writer.WriteInt32(componentTypes.Count);
+
+ // Write components
+ foreach (ref var type in componentTypes.Components)
+ {
+ // Write type
+ MessagePackSerializer.Serialize(ref writer, type, options);
+
+ // Write component
+ var cmp = value.Get(type);
+ MessagePackSerializer.Serialize(ref writer, cmp, options);
+ }
+ }
+
+ ///
+ public Entity Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+ // Read id
+ var entityId = reader.ReadInt32();
+
+#if !PURE_ECS
+
+ // Read world id
+ var worldId = reader.ReadInt32();
+#endif
+
+ // Read size
+ var size = reader.ReadInt32();
+ var components = new object[size];
+
+ // Read components
+ for (var index = 0; index < size; index++)
+ {
+ // Read type
+ var type = MessagePackSerializer.Deserialize(ref reader, options);
+ var cmp = MessagePackSerializer.Deserialize(type, ref reader, options);
+ components[index] = cmp!;
+ }
+
+ // Create the entity
+ var entity = EntityWorld.Create();
+ EntityWorld.AddRange(entity, components.AsSpan());
+ return entity;
+ }
+}
+
+///
+/// The class
+/// is a formatter that (de)serializes structs.
+///
+public partial class EntityFormatter : IMessagePackFormatter
+{
+ ///
+ public void Serialize(ref MessagePackWriter writer, Entity value, MessagePackSerializerOptions options)
+ {
+ writer.WriteInt32(value.Id);
+ writer.WriteInt32(value.Version);
+ }
+
+ ///
+ public Entity Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+ // Read id
+ var id = reader.ReadInt32();
+ var version = reader.ReadInt32();
+ return DangerousEntityExtensions.CreateEntityStruct(id, WorldId, version);
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class ArrayFormatter : IMessagePackFormatter
+{
+ ///
+ public void Serialize(ref MessagePackWriter writer, Array value, MessagePackSerializerOptions options)
+ {
+ var type = value.GetType().GetElementType();
+
+ // Write type and size
+ MessagePackSerializer.Serialize(ref writer, type, options);
+ writer.WriteUInt32((uint)value.Length);
+
+ // Write array
+ for (var index = 0; index < value.Length; index++)
+ {
+ var obj = value.GetValue(index);
+ MessagePackSerializer.Serialize(ref writer, obj, options);
+ }
+ }
+
+ ///
+ public Array Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+ // Write type and size
+ var type = MessagePackSerializer.Deserialize(ref reader, options);
+ var size = reader.ReadUInt32();
+
+ // Create array
+ var array = Array.CreateInstance(type, size);
+
+ // Read array
+ for (var index = 0; index < size; index++)
+ {
+ var obj = MessagePackSerializer.Deserialize(type, ref reader, options);
+ array.SetValue(obj, index);
+ }
+ return array;
+ }
+}
+
+///
+/// The class
+/// (de)serializes a .
+///
+/// The type stored in the .
+public partial class JaggedArrayFormatter : IMessagePackFormatter>
+{
+ private const int CpuL1CacheSize = 16_384;
+ private readonly T _filler;
+
+ ///
+ /// Constructor.
+ ///
+ /// Filler.
+ public JaggedArrayFormatter(T filler)
+ {
+ _filler = filler;
+ }
+
+ ///
+ public void Serialize(ref MessagePackWriter writer, JaggedArray value, MessagePackSerializerOptions options)
+ {
+ // Write length/capacity and items
+ writer.WriteInt32(value.Capacity);
+ for (var index = 0; index < value.Capacity; index++)
+ {
+ var item = value[index];
+ MessagePackSerializer.Serialize(ref writer, item, options);
+ }
+ }
+
+ ///
+ public JaggedArray Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+ var capacity = reader.ReadInt32();
+ var jaggedArray = new JaggedArray(CpuL1CacheSize / Unsafe.SizeOf(), _filler, capacity);
+
+ for (var index = 0; index < capacity; index++)
+ {
+ var item = MessagePackSerializer.Deserialize(ref reader, options);
+ jaggedArray.Add(index, item);
+ }
+
+ return jaggedArray;
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class ComponentTypeFormatter : IMessagePackFormatter
+{
+ ///
+ public void Serialize(ref MessagePackWriter writer, ComponentType value, MessagePackSerializerOptions options)
+ {
+ // Write id
+ writer.WriteUInt32((uint)value.Id);
+
+ // Write bytesize
+ writer.WriteUInt32((uint)value.ByteSize);
+ }
+
+ ///
+ public ComponentType Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+ var id = reader.ReadUInt32();
+ var bytesize = reader.ReadUInt32();
+
+ return new ComponentType((int)id, (int)bytesize);
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class SignatureFormatter : IMessagePackFormatter
+{
+ ///
+ public void Serialize(ref MessagePackWriter writer, Signature value, MessagePackSerializerOptions options)
+ {
+ var componentTypeFormatter = options.Resolver.GetFormatter() as ComponentTypeFormatter;
+
+ // Write count and types
+ writer.WriteUInt32((uint)value.Count);
+ foreach (var type in value.Components)
+ {
+ componentTypeFormatter!.Serialize(ref writer, type, options);
+ }
+ }
+
+ ///
+ public Signature Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+ var componentTypeFormatter = options.Resolver.GetFormatter() as ComponentTypeFormatter;
+
+ // Read count
+ var count = reader.ReadUInt32();
+
+ // Read types
+ var componentTypes = new ComponentType[count];
+ for (var index = 0; index < count; index++)
+ {
+ var componentType = componentTypeFormatter!.Deserialize(ref reader, options);
+ componentTypes[index] = componentType;
+ }
+ return new Signature(componentTypes);
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class EntitySlotFormatter : IMessagePackFormatter
+{
+ ///
+ public void Serialize(ref MessagePackWriter writer, EntityData value, MessagePackSerializerOptions options)
+ {
+ // Write chunk index
+ writer.WriteUInt32((uint)value.Slot.ChunkIndex);
+
+ // Write entity index
+ writer.WriteUInt32((uint)value.Slot.Index);
+ }
+
+ ///
+ public EntityData Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+
+ // Read chunk index and entity index
+ var chunkIndex = reader.ReadUInt32();
+ var entityIndex = reader.ReadUInt32();
+
+ return new EntityData(null!, new Slot((int)entityIndex, (int)chunkIndex), 0);
+ }
+}
+
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class WorldFormatter : IMessagePackFormatter
+{
+ ///
+ public void Serialize(ref MessagePackWriter writer, World value, MessagePackSerializerOptions options)
+ {
+ // Write important meta data
+ writer.WriteUInt32((uint)value.BaseChunkSize);
+ writer.WriteUInt32((uint)value.BaseChunkEntityCount);
+
+ // Write slots
+ MessagePackSerializer.Serialize(ref writer, value.GetEntityDataArray(), options);
+
+ //Write recycled entity ids
+ var recycledEntityIDs = value.GetRecycledEntityIds();
+ MessagePackSerializer.Serialize(ref writer, recycledEntityIDs, options);
+
+ // Write archetypes
+ writer.WriteUInt32((uint)value.Archetypes.Count);
+ foreach (var archetype in value)
+ {
+ MessagePackSerializer.Serialize(ref writer, archetype, options);
+ }
+ }
+
+ ///
+ public World Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+ // Read important metadata
+ var baseChunkSize = reader.ReadUInt32();
+ var baseChunkEntityCount = reader.ReadUInt32();
+
+ // Create world and setup formatter
+ var world = World.Create(chunkSizeInBytes: (int)baseChunkSize, minimumAmountOfEntitiesPerChunk: (int)baseChunkEntityCount);
+ var archetypeFormatter = options.Resolver.GetFormatter() as ArchetypeFormatter;
+ var entityFormatter = options.Resolver.GetFormatter() as EntityFormatter;
+ entityFormatter!.WorldId = world.Id;
+ archetypeFormatter!.World = world;
+
+ // Read slots
+ var slots = MessagePackSerializer.Deserialize>(ref reader, options);
+
+ //Read recycled entity ids
+ var recycledEntityIDs = MessagePackSerializer.Deserialize>(ref reader, options);
+
+ // Forward values to the world
+ world.SetRecycledEntityIds(recycledEntityIDs);
+ world.SetEntityDataArray(slots);
+ world.EnsureCapacity(slots.Capacity);
+
+ // Read archetypes
+ var size = reader.ReadInt32();
+ List archetypes = new();
+
+ for (var index = 0; index < size; index++)
+ {
+ var archetype = archetypeFormatter.Deserialize(ref reader, options);
+ archetypes.Add(archetype);
+ }
+
+ // Set archetypes
+ world.SetArchetypes(archetypes);
+ return world;
+ }
+}
+
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class ArchetypeFormatter : IMessagePackFormatter
+{
+ ///
+ public void Serialize(ref MessagePackWriter writer, Archetype value, MessagePackSerializerOptions options)
+ {
+ // Setup formatters
+ var types = value.Signature;
+ var chunks = value.Chunks;
+ var chunkFormatter = options.Resolver.GetFormatter() as ChunkFormatter;
+ chunkFormatter!.Signature = types;
+
+ // Write type array
+ MessagePackSerializer.Serialize(ref writer, types, options);
+
+ // Write lookup array
+ MessagePackSerializer.Serialize(ref writer, value.GetLookupArray(), options);
+
+ // Write chunk size
+ writer.WriteUInt32((uint)value.ChunkCount);
+
+ // Write chunks
+ for (var index = 0; index < value.ChunkCount; index++)
+ {
+ ref var chunk = ref chunks[index];
+ chunkFormatter.Serialize(ref writer, chunk, options);
+ }
+ }
+
+ ///
+ public Archetype Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+
+ var chunkFormatter = options.Resolver.GetFormatter() as ChunkFormatter;
+
+ // Types
+ var types = MessagePackSerializer.Deserialize(ref reader, options);
+
+ // Archetype lookup array
+ var lookupArray = MessagePackSerializer.Deserialize(ref reader, options);
+
+ // Archetype chunk size and list
+ var chunkSize = reader.ReadUInt32();
+
+ // Create archetype
+ var chunks = new List((int)chunkSize);
+ var archetype = DangerousArchetypeExtensions.CreateArchetype(World.BaseChunkSize, World.BaseChunkEntityCount, types);
+ archetype.Chunks.Clear(true);
+ archetype.SetCount((int)chunkSize - 1);
+
+ // Pass types and lookup array to the chunk formatter for saving performance and memory
+ chunkFormatter!.World = World;
+ chunkFormatter.Archetype = archetype;
+ chunkFormatter.Signature = types;
+ chunkFormatter.LookupArray = lookupArray;
+
+ // Deserialise each chunk and put it into the archetype.
+ var entities = 0;
+ for (var index = 0; index < chunkSize; index++)
+ {
+ var chunk = chunkFormatter.Deserialize(ref reader, options);
+ chunks.Add(chunk);
+ entities += chunk.Count;
+ }
+
+ archetype.SetChunks(chunks);
+ archetype.SetEntities(entities);
+ return archetype;
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class ChunkFormatter : IMessagePackFormatter
+{
+ ///
+ public void Serialize(ref MessagePackWriter writer, Chunk value, MessagePackSerializerOptions options)
+ {
+ // Write size
+ writer.WriteUInt32((uint)value.Count);
+
+ // Write capacity
+ writer.WriteUInt32((uint)value.Capacity);
+
+ // Write entitys
+ MessagePackSerializer.Serialize(ref writer, value.Entities, options);
+
+ // Persist arrays as an array...
+ foreach (var type in Signature.Components)
+ {
+ // Write array itself
+ var array = value.GetArray(type);
+ MessagePackSerializer.Serialize(ref writer, array, options);
+ }
+ }
+
+ ///
+ public Chunk Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+ // Read chunk size
+ var size = reader.ReadUInt32();
+
+ // Read chunk size
+ var capacity = reader.ReadUInt32();
+
+ // Read entities
+ var entities = MessagePackSerializer.Deserialize(ref reader, options);
+
+ // Create chunk
+ var chunk = DangerousChunkExtensions.CreateChunk((int)capacity, LookupArray, Signature);
+ entities.CopyTo(chunk.Entities, 0);
+ chunk.SetSize((int)size);
+
+ // Updating World.EntityInfoStorage to their new archetype
+ for (var index = 0; index < size; index++)
+ {
+ ref var entity = ref chunk.Entity(index);
+ entity = DangerousEntityExtensions.CreateEntityStruct(entity.Id, World.Id, entity.Version);
+ World.SetArchetype(entity, Archetype);
+ }
+
+ // Persist arrays as an array...
+ foreach (var type in Signature.Components)
+ {
+ // Read array of the type
+ var array = MessagePackSerializer.Deserialize(ref reader, options);
+ var chunkArray = chunk.GetArray(array.GetType().GetElementType()!);
+ Array.Copy(array, chunkArray, (int)size);
+ }
+
+ return chunk;
+ }
+}
+
diff --git a/src/Arch.Extended/Arch.Persistence/Json.cs b/src/Arch.Extended/Arch.Persistence/Json.cs
new file mode 100644
index 0000000..950ccfe
--- /dev/null
+++ b/src/Arch.Extended/Arch.Persistence/Json.cs
@@ -0,0 +1,822 @@
+namespace Arch.Persistence;
+
+using global::System;
+using global::System.Collections.Generic;
+using Arch.Core;
+using Arch.Core.Extensions;
+using Arch.Core.Extensions.Dangerous;
+using Arch.LowLevel.Jagged;
+using CommunityToolkit.HighPerformance;
+using global::System.Runtime.CompilerServices;
+using Utf8Json;
+
+
+///
+/// The class
+/// is a to (de)serialize a single to or from json.
+///
+public partial class SingleEntityFormatter : IJsonFormatter
+{
+
+ ///
+ /// The the entity belongs to.
+ ///
+ internal World EntityWorld { get; set; } = null!;
+
+ ///
+ public void Serialize(ref JsonWriter writer, Entity value, IJsonFormatterResolver formatterResolver)
+ {
+ writer.WriteBeginObject();
+
+ // Write id
+ writer.WritePropertyName("id");
+ writer.WriteInt32(value.Id);
+ writer.WriteValueSeparator();
+
+#if !PURE_ECS
+
+ // Write world
+ writer.WritePropertyName("worldId");
+ writer.WriteInt32(value.WorldId);
+ writer.WriteValueSeparator();
+
+#endif
+
+ // Write size
+ var componentTypes = value.GetComponentTypes();
+ writer.WritePropertyName("size");
+ writer.WriteInt32(componentTypes.Count);
+ writer.WriteValueSeparator();
+
+ // Write components
+ writer.WritePropertyName("components");
+ writer.WriteBeginArray();
+ foreach (ref var type in componentTypes.Components)
+ {
+ // Write type
+ writer.WriteBeginObject();
+ writer.WritePropertyName("type");
+ JsonSerializer.Serialize(ref writer, type);
+ writer.WriteValueSeparator();
+
+ // Write component
+ writer.WritePropertyName("component");
+ var cmp = value.Get(type);
+ JsonSerializer.NonGeneric.Serialize(ref writer, cmp, formatterResolver);
+ writer.WriteEndObject();
+ writer.WriteValueSeparator();
+ }
+ writer.AdvanceOffset(-1);
+
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ }
+
+ ///
+ public Entity Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
+ {
+ reader.ReadIsBeginObject();
+
+ // Read id
+ reader.ReadPropertyName();
+ var entityId = reader.ReadInt32();
+ reader.ReadIsValueSeparator();
+
+#if !PURE_ECS
+
+ // Read world id
+ reader.ReadPropertyName();
+ var worldId = reader.ReadInt32();
+ reader.ReadIsValueSeparator();
+
+#endif
+
+ // Read size
+ reader.ReadPropertyName();
+ var size = reader.ReadInt32();
+ reader.ReadIsValueSeparator();
+
+ var components = new object[size];
+ var count = 0;
+
+ // Read components
+ reader.ReadPropertyName();
+ reader.ReadIsBeginArray();
+ while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count))
+ {
+ reader.ReadIsBeginObject();
+
+ // Read type
+ reader.ReadPropertyName();
+ var type = JsonSerializer.Deserialize(ref reader);
+ reader.ReadIsValueSeparator();
+
+ reader.ReadPropertyName();
+ var cmp = JsonSerializer.NonGeneric.Deserialize(type.Type, ref reader, formatterResolver);
+ components[count - 1] = cmp;
+ reader.ReadIsEndObject();
+ }
+
+ // Creat the entity
+ var entity = EntityWorld.Create();
+ EntityWorld.AddRange(entity, components.AsSpan());
+
+ reader.ReadIsEndObject();
+ return entity;
+ }
+}
+
+public partial class EntityFormatter : IJsonFormatter
+{
+
+ ///
+ /// The all deserialized s will belong to.
+ /// Due to the nature of deserialisation and changing world landscape we need to assign new WorldIds to the deserialized entities.
+ ///
+ internal int WorldId { get; set; }
+
+ ///
+ public void Serialize(ref JsonWriter writer, Entity value, IJsonFormatterResolver formatterResolver)
+ {
+ writer.WriteInt32(value.Id);
+ writer.WriteValueSeparator();
+ writer.WriteInt32(value.Version);
+ }
+
+ ///
+ public Entity Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
+ {
+ // Read id
+ var id = reader.ReadInt32();
+ reader.ReadIsValueSeparator();
+ var version = reader.ReadInt32();
+ return DangerousEntityExtensions.CreateEntityStruct(id, WorldId, version);
+ }
+}
+
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class ArrayFormatter : IJsonFormatter
+{
+ ///
+ public void Serialize(ref JsonWriter writer, Array value, IJsonFormatterResolver formatterResolver)
+ {
+ var type = value.GetType().GetElementType();
+
+ // Write type and size
+ writer.WriteBeginObject();
+ writer.WritePropertyName("type");
+ JsonSerializer.Serialize(ref writer, type, formatterResolver);
+ writer.WriteValueSeparator();
+
+ writer.WritePropertyName("length");
+ writer.WriteUInt32((uint)value.Length);
+ writer.WriteValueSeparator();
+
+ // Write array
+ writer.WritePropertyName("items");
+ writer.WriteBeginArray();
+ for (var index = 0; index < value.Length; index++)
+ {
+ var obj = value.GetValue(index);
+ JsonSerializer.NonGeneric.Serialize(ref writer, obj, formatterResolver);
+ writer.WriteValueSeparator();
+ }
+ writer.AdvanceOffset(-1);
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ }
+
+ ///
+ public Array Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
+ {
+ // Write type and size
+ reader.ReadIsBeginObject();
+ reader.ReadPropertyName();
+ var type = JsonSerializer.Deserialize(ref reader, formatterResolver);
+ reader.ReadIsValueSeparator();
+
+ reader.ReadPropertyName();
+ var size = reader.ReadUInt32();
+ reader.ReadIsValueSeparator();
+
+ // Create array
+ var array = Array.CreateInstance(type, size);
+
+ // Read array
+ reader.ReadPropertyName();
+ reader.ReadIsBeginArray();
+ for (var index = 0; index < size; index++)
+ {
+ var obj = JsonSerializer.NonGeneric.Deserialize(type, ref reader, formatterResolver);
+ array.SetValue(obj, index);
+ reader.ReadIsValueSeparator();
+ }
+ reader.ReadIsEndArray();
+ reader.ReadIsEndObject();
+ return array;
+ }
+}
+
+///
+/// The class
+/// (de)serializes a .
+///
+public partial class JaggedArrayFormatter : IJsonFormatter>
+{
+ ///
+ public void Serialize(ref JsonWriter writer, JaggedArray value, IJsonFormatterResolver formatterResolver)
+ {
+ writer.WriteBeginObject();
+
+ // Write length/capacity and items
+ writer.WritePropertyName("capacity");
+ writer.WriteInt32(value.Capacity);
+ writer.WriteValueSeparator();
+
+ // Write items
+ writer.WritePropertyName("items");
+ writer.WriteBeginArray();
+
+ for (var index = 0; index < value.Capacity; index++)
+ {
+ var item = value[index];
+ JsonSerializer.Serialize(ref writer, item, formatterResolver);
+ writer.WriteValueSeparator();
+ }
+
+ // Cut last value seperator
+ if (value.Capacity > 0)
+ {
+ writer.AdvanceOffset(-1);
+ }
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ }
+
+ ///
+ public JaggedArray Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
+ {
+ reader.ReadIsBeginObject();
+
+ // Read capacity;
+ reader.ReadPropertyName();
+ var capacity = reader.ReadInt32();
+ reader.ReadIsValueSeparator();
+
+ // Read items
+ var jaggedArray = new JaggedArray(CpuL1CacheSize / Unsafe.SizeOf(), _filler, capacity);
+ reader.ReadPropertyName();
+ reader.ReadIsBeginArray();
+ for (var index = 0; index < capacity; index++)
+ {
+ var item = JsonSerializer.Deserialize(ref reader, formatterResolver);
+ jaggedArray.Add(index, item);
+ reader.ReadIsValueSeparator();
+ }
+ reader.ReadIsEndArray();
+ reader.ReadIsEndObject();
+
+ return jaggedArray;
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class ComponentTypeFormatter : IJsonFormatter
+{
+ ///
+ public void Serialize(ref JsonWriter writer, ComponentType value, IJsonFormatterResolver formatterResolver)
+ {
+ writer.WriteBeginObject();
+
+ // Write id
+ writer.WritePropertyName("id");
+ writer.WriteUInt32((uint)value.Id);
+ writer.WriteValueSeparator();
+
+ // Write bytesize
+ writer.WritePropertyName("byteSize");
+ writer.WriteUInt32((uint)value.ByteSize);
+
+ writer.WriteEndObject();
+ }
+
+ ///
+ public ComponentType Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
+ {
+ reader.ReadIsBeginObject();
+
+ reader.ReadPropertyName();
+ var id = reader.ReadUInt32();
+ reader.ReadIsValueSeparator();
+
+ reader.ReadPropertyName();
+ var bytesize = reader.ReadUInt32();
+ reader.ReadIsValueSeparator();
+
+ reader.ReadIsEndObject();
+
+ return new ComponentType((int)id, (int)bytesize);
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class SignatureFormatter : IJsonFormatter
+{
+ ///
+ public void Serialize(ref JsonWriter writer, Signature value, IJsonFormatterResolver formatterResolver)
+ {
+ var componentTypeFormatter = formatterResolver.GetFormatter() as ComponentTypeFormatter;
+
+ writer.WriteBeginObject();
+
+ // write Count
+ writer.WritePropertyName("count");
+ writer.WriteUInt32((uint)value.Count);
+ writer.WriteValueSeparator();
+
+ // Write components
+ writer.WritePropertyName("components");
+ writer.WriteBeginArray();
+
+ foreach (var type in value.Components)
+ {
+ componentTypeFormatter!.Serialize(ref writer, type, formatterResolver);
+ writer.WriteValueSeparator();
+ }
+
+ // Cut last value seperator
+ if (value.Count > 0)
+ {
+ writer.AdvanceOffset(-1);
+ }
+
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ }
+
+ ///
+ public Signature Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
+ {
+ var componentTypeFormatter = formatterResolver.GetFormatter() as ComponentTypeFormatter;
+
+ reader.ReadIsBeginObject();
+ reader.ReadPropertyName();
+
+ // Read count
+ var count = (int)reader.ReadUInt32();
+ reader.ReadIsValueSeparator();
+
+ // Read types
+ reader.ReadPropertyName();
+ reader.ReadIsBeginArray();
+
+ var componentTypes = new ComponentType[count];
+ count = 0;
+ while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count))
+ {
+ var archetype = componentTypeFormatter!.Deserialize(ref reader, formatterResolver);
+ componentTypes[count - 1] = (archetype);
+ }
+
+ // Set archetypes
+ reader.ReadIsEndObject();
+ return new Signature(componentTypes);
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class EntitySlotFormatter : IJsonFormatter
+{
+ ///
+ public void Serialize(ref JsonWriter writer, EntityData value, IJsonFormatterResolver options)
+ {
+ writer.WriteBeginObject();
+
+ // Write chunk index
+ writer.WritePropertyName("chunkIndex");
+ writer.WriteUInt32((uint)value.Slot.ChunkIndex);
+ writer.WriteValueSeparator();
+
+ // Write entity index
+ writer.WritePropertyName("index");
+ writer.WriteUInt32((uint)value.Slot.Index);
+
+ writer.WriteEndObject();
+ }
+
+ ///
+ public EntityData Deserialize(ref JsonReader reader, IJsonFormatterResolver options)
+ {
+ reader.ReadIsBeginObject();
+
+ // Read chunk index
+ reader.ReadPropertyName();
+ var chunkIndex = reader.ReadUInt32();
+ reader.ReadIsValueSeparator();
+
+ // Read entity index
+ reader.ReadPropertyName();
+ var entityIndex = reader.ReadUInt32();
+
+ reader.ReadIsEndObject();
+ return new EntityData(null!, new Slot((int)entityIndex, (int)chunkIndex), 0);
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class WorldFormatter : IJsonFormatter
+{
+ ///
+ public void Serialize(ref JsonWriter writer, World value, IJsonFormatterResolver formatterResolver)
+ {
+ //var archetypeFormatter = formatterResolver.GetFormatter();
+ //var versionsFormatter = formatterResolver.GetFormatter();
+ //var slotFormatter = formatterResolver.GetFormatter<(int,int)[][]>();
+
+ writer.WriteBeginObject();
+
+ // Write meta data
+ writer.WritePropertyName("baseChunkSize");
+ writer.WriteUInt32((uint)value.BaseChunkSize);
+ writer.WriteValueSeparator();
+
+ writer.WritePropertyName("baseChunkEntityCount");
+ writer.WriteUInt32((uint)value.BaseChunkEntityCount);
+ writer.WriteValueSeparator();
+
+ // Write slots
+ writer.WritePropertyName("slots");
+ JsonSerializer.Serialize(ref writer, value.GetEntityDataArray(), formatterResolver);
+ writer.WriteValueSeparator();
+
+ //Write recycled entity ids
+ writer.WritePropertyName("recycledEntityIDs");
+ writer.WriteBeginArray();
+ var recycledEntityIDs = value.GetRecycledEntityIds();
+ foreach (var recycledId in recycledEntityIDs)
+ {
+ writer.WriteBeginObject();
+ writer.WritePropertyName("id");
+ writer.WriteInt32(recycledId.Item1);
+ writer.WriteValueSeparator();
+ writer.WritePropertyName("version");
+ writer.WriteInt32(recycledId.Item2);
+ writer.WriteEndObject();
+
+ writer.WriteValueSeparator();
+ }
+ // Cut last value seperator
+ if (recycledEntityIDs.Count > 0)
+ {
+ writer.AdvanceOffset(-1);
+ }
+ writer.WriteEndArray();
+
+ writer.WriteValueSeparator();
+
+ //Write archetypes
+ writer.WritePropertyName("archetypes");
+ writer.WriteBeginArray();
+ foreach (var archetype in value)
+ {
+ JsonSerializer.Serialize(ref writer, archetype, formatterResolver);
+ writer.WriteValueSeparator();
+ }
+
+ // Cut last value seperator
+ if (value.Archetypes.Count > 0)
+ {
+ writer.AdvanceOffset(-1);
+ }
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ }
+
+ ///
+ public World Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
+ {
+ // Create world and setup formatter
+ var archetypeFormatter = formatterResolver.GetFormatter() as ArchetypeFormatter;
+ var entityFormatter = formatterResolver.GetFormatter() as EntityFormatter;
+
+ reader.ReadIsBeginObject();
+
+ // Read meta data
+ reader.ReadPropertyName();
+ var baseChunkSize = reader.ReadUInt32();
+ reader.ReadIsValueSeparator();
+
+ reader.ReadPropertyName();
+ var baseChunkEntityCount = reader.ReadUInt32();
+ reader.ReadIsValueSeparator();
+
+ // Construct world
+ var world = World.Create(chunkSizeInBytes: (int)baseChunkSize, minimumAmountOfEntitiesPerChunk: (int)baseChunkEntityCount);
+ entityFormatter!.WorldId = world.Id;
+ archetypeFormatter!.World = world;
+
+ // Read slots
+ reader.ReadPropertyName();
+ var slots = JsonSerializer.Deserialize>(ref reader, formatterResolver);
+ reader.ReadIsValueSeparator();
+
+ // Read recycled ids
+ var count = 0;
+ List<(int, int)> recycledIds = new();
+
+ reader.ReadPropertyName();
+ reader.ReadIsBeginArray();
+
+ while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count))
+ {
+ reader.ReadIsBeginObject();
+ reader.ReadPropertyName();
+ var id = reader.ReadInt32();
+ reader.ReadIsValueSeparator();
+ reader.ReadPropertyName();
+ var value = reader.ReadInt32();
+ reader.ReadIsEndObject();
+
+ (int, int) recycledId = new(id, value);
+
+ recycledIds.Add(recycledId);
+ }
+
+ reader.ReadIsValueSeparator();
+
+ // Forward values to the world
+ world.SetRecycledEntityIds(recycledIds);
+ world.SetEntityDataArray(slots);
+ world.EnsureCapacity(slots.Capacity);
+
+ // Read archetypes
+ count = 0;
+ List archetypes = new();
+ reader.ReadPropertyName();
+ reader.ReadIsBeginArray();
+ while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count))
+ {
+ var archetype = archetypeFormatter.Deserialize(ref reader, formatterResolver);
+ archetypes.Add(archetype);
+ }
+
+ // Set archetypes
+ world.SetArchetypes(archetypes);
+ reader.ReadIsEndObject();
+ return world;
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class ArchetypeFormatter : IJsonFormatter
+{
+
+ ///
+ /// The which is being used by this formatter during serialisation/deserialisation.
+ ///
+ internal World World { get; set; } = null!;
+
+ ///
+ public void Serialize(ref JsonWriter writer, Archetype value, IJsonFormatterResolver formatterResolver)
+ {
+ // Setup formatters
+ var types = value.Signature;
+ var chunks = value.Chunks;
+ var chunkFormatter = formatterResolver.GetFormatter() as ChunkFormatter;
+ chunkFormatter!.Signature = types;
+
+ writer.WriteBeginObject();
+
+ // Write type array
+ writer.WritePropertyName("types");
+ JsonSerializer.Serialize(ref writer, types, formatterResolver);
+ writer.WriteValueSeparator();
+
+ // Write lookup array
+ writer.WritePropertyName("lookup");
+ JsonSerializer.Serialize(ref writer, value.GetLookupArray(), formatterResolver);
+ writer.WriteValueSeparator();
+
+ // Write chunk size
+ writer.WritePropertyName("chunkCount");
+ writer.WriteUInt32((uint)value.ChunkCount);
+ writer.WriteValueSeparator();
+
+ // Write chunks
+ writer.WritePropertyName("chunks");
+ writer.WriteBeginArray();
+ for (var index = 0; index < value.ChunkCount; index++)
+ {
+ ref var chunk = ref chunks[index];
+ chunkFormatter.Serialize(ref writer, chunk, formatterResolver);
+ writer.WriteValueSeparator();
+ }
+
+ // Trim last value separator
+ if (value.ChunkCount > 0)
+ {
+ writer.AdvanceOffset(-1);
+ }
+
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ }
+
+ ///
+ public Archetype Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
+ {
+ var chunkFormatter = formatterResolver.GetFormatter() as ChunkFormatter;
+
+ reader.ReadIsBeginObject();
+
+ // Types
+ reader.ReadPropertyName();
+ var types = JsonSerializer.Deserialize(ref reader, formatterResolver);
+ reader.ReadIsValueSeparator();
+
+ // Archetype lookup array
+ reader.ReadPropertyName();
+ var lookupArray = JsonSerializer.Deserialize(ref reader, formatterResolver);
+ reader.ReadIsValueSeparator();
+
+ // Archetype chunk size and list
+ reader.ReadPropertyName();
+ var chunkCount = reader.ReadUInt32();
+ reader.ReadIsValueSeparator();
+
+ // Create archetype
+ var chunks = new List((int)chunkCount);
+ var archetype = DangerousArchetypeExtensions.CreateArchetype(World.BaseChunkSize, World.BaseChunkEntityCount, types);
+ archetype.Chunks.Clear(true);
+ archetype.SetCount((int)chunkCount - 1);
+
+ // Pass types and lookup array to the chunk formatter for saving performance and memory
+ chunkFormatter!.World = World;
+ chunkFormatter.Archetype = archetype;
+ chunkFormatter.Signature = types;
+ chunkFormatter.LookupArray = lookupArray;
+
+ // Deserialise each chunk and put it into the archetype.
+ reader.ReadPropertyName();
+ reader.ReadIsBeginArray();
+
+ var entities = 0;
+ for (var index = 0; index < chunkCount; index++)
+ {
+ var chunk = chunkFormatter.Deserialize(ref reader, formatterResolver);
+ chunks.Add(chunk);
+ entities += chunk.Count;
+ reader.ReadIsValueSeparator();
+ }
+
+ archetype.SetChunks(chunks);
+ archetype.SetEntities(entities);
+
+ reader.ReadIsEndArray();
+ reader.ReadIsEndObject();
+ return archetype;
+ }
+}
+
+///
+/// The class
+/// is a to (de)serialize s to or from json.
+///
+public partial class ChunkFormatter : IJsonFormatter
+{
+
+ ///
+ /// The the current (de)serialized belongs to.
+ /// Since chunks do not know this, we need to pass this information along it.
+ ///
+ internal World World { get; set; } = null!;
+
+ ///
+ /// The the current (de)serialized belongs to.
+ /// Since chunks do not know this, we need to pass this information along it.
+ ///
+ internal Archetype Archetype { get; set; } = null!;
+
+ ///
+ /// The types used in the in each (de)serialized by this formatter.
+ /// Since does not have a reference to them and its controlled by its .
+ ///
+ internal Signature Signature { get; set; } = Signature.Null;
+
+ ///
+ /// The lookup array used by each (de)serialized by this formatter.
+ /// Since does not have a reference to them and its controlled by its .
+ ///
+ internal int[] LookupArray { get; set; } = Array.Empty();
+
+ ///
+ public void Serialize(ref JsonWriter writer, Chunk value, IJsonFormatterResolver formatterResolver)
+ {
+ writer.WriteBeginObject();
+
+ // Write size
+ writer.WritePropertyName("count");
+ writer.WriteUInt32((uint)value.Count);
+ writer.WriteValueSeparator();
+
+ // Write capacity
+ writer.WritePropertyName("capacity");
+ writer.WriteUInt32((uint)value.Capacity);
+ writer.WriteValueSeparator();
+
+ // Write entitys
+ writer.WritePropertyName("entities");
+ JsonSerializer.NonGeneric.Serialize(ref writer, value.Entities, formatterResolver);
+ writer.WriteValueSeparator();
+
+ // Persist arrays as an array...
+ writer.WritePropertyName("arrays");
+ writer.WriteBeginArray();
+ foreach (var type in Signature.Components)
+ {
+ // Write array itself
+ var array = value.GetArray(type);
+ JsonSerializer.Serialize(ref writer, array, formatterResolver);
+ writer.WriteValueSeparator();
+ }
+
+ // Remove trailing
+ if (Signature.Count > 0)
+ {
+ writer.AdvanceOffset(-1);
+ }
+
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ }
+
+ ///
+ public Chunk Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
+ {
+ reader.ReadIsBeginObject();
+
+ // Read chunk size
+ reader.ReadPropertyName();
+ var size = reader.ReadUInt32();
+ reader.ReadIsValueSeparator();
+
+ // Read chunk size
+ reader.ReadPropertyName();
+ var capacity = reader.ReadUInt32();
+ reader.ReadIsValueSeparator();
+
+ // Read entities
+ reader.ReadPropertyName();
+ var entities = JsonSerializer.Deserialize(ref reader, formatterResolver);
+ reader.ReadIsValueSeparator();
+
+ // Create chunk
+ var chunk = DangerousChunkExtensions.CreateChunk((int)capacity, LookupArray, Signature);
+ entities.CopyTo(chunk.Entities, 0);
+ chunk.SetSize((int)size);
+
+ // Updating World.EntityInfoStorage to their new archetype
+ for (var index = 0; index < size; index++)
+ {
+ ref var entity = ref chunk.Entity(index);
+ entity = DangerousEntityExtensions.CreateEntityStruct(entity.Id, World.Id, entity.Version);
+ World.SetArchetype(entity, Archetype);
+ }
+
+ // Persist arrays as an array...
+ reader.ReadPropertyName();
+ reader.ReadIsBeginArray();
+ foreach (var type in Signature)
+ {
+ // Read array of the type
+ var array = JsonSerializer.Deserialize(ref reader, formatterResolver);
+ var chunkArray = chunk.GetArray(array.GetType().GetElementType()!);
+ Array.Copy(array, chunkArray, (int)size);
+ reader.ReadIsValueSeparator();
+ }
+
+ reader.ReadIsEndArray();
+ reader.ReadIsEndObject();
+ return chunk;
+ }
+}
+
diff --git a/src/Arch.Extended/Arch.Persistence/Serializer.cs b/src/Arch.Extended/Arch.Persistence/Serializer.cs
new file mode 100644
index 0000000..c19c63c
--- /dev/null
+++ b/src/Arch.Extended/Arch.Persistence/Serializer.cs
@@ -0,0 +1,410 @@
+namespace Arch.Persistence;
+
+using Arch.Core;
+using MessagePack;
+using MessagePack.Formatters;
+using global::System;
+using global::System.Buffers;
+using global::System.IO;
+using Utf8Json;
+using Utf8Json.Resolvers;
+using DateTimeFormatter = Utf8Json.Formatters.DateTimeFormatter;
+using NullableDateTimeFormatter = Utf8Json.Formatters.NullableDateTimeFormatter;
+
+///
+/// The interface
+/// represents an interface with shared methods to (de)serialize worlds and entities.
+/// It might happen that the serialized object is too large to fit into a regular c# byte-array. In this case use the -API.
+///
+public interface IArchSerializer
+{
+ ///
+ /// Serializes an to a -array.
+ ///
+ /// The .
+ /// The .
+ byte[] Serialize(World world, Entity entity);
+
+ ///
+ /// Serializes an to a e.g. a File or existing array.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ void Serialize(Stream stream, World world, Entity entity);
+
+ ///
+ /// Serializes an to a e.g. a File or existing array.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ void Serialize(IBufferWriter writer, World world, Entity entity);
+
+ ///
+ /// Deserializes an from its bytes to an real in a .
+ /// The new and will differ.
+ ///
+ /// The .
+ /// The .
+ ///
+ Entity Deserialize(World world, byte[] entity);
+
+ ///
+ /// Deserializes an from its bytes to an real in a .
+ /// The new and will differ.
+ ///
+ /// The .
+ /// The .
+ ///
+ Entity Deserialize(Stream stream, World world);
+
+ ///
+ /// Serializes a to a -array.
+ ///
+ /// The .
+ byte[] Serialize(World world);
+
+ ///
+ /// Serializes a to a .
+ ///
+ /// The .
+ /// The .
+ void Serialize(Stream stream, World world);
+
+ ///
+ /// Serializes a to a .
+ ///
+ /// The .
+ /// The .
+ void Serialize(IBufferWriter writer, World world);
+
+ ///
+ /// Deserializes a byte-array into a .
+ ///
+ /// The as an byte-array.
+ /// The new .
+ World Deserialize(byte[] world);
+
+ ///
+ /// Deserializes a byte-array into a .
+ ///
+ /// The .
+ /// The new .
+ World Deserialize(Stream stream);
+}
+
+///
+/// The class
+/// represents a binary serializer for arch to (de)serialize single entities and whole worlds by binary.
+///
+public class ArchBinarySerializer : IArchSerializer
+{
+ ///
+ /// The default formatters used to (de)serialize the .
+ ///
+ private readonly IMessagePackFormatter[] _formatters =
+ [
+ new WorldFormatter(),
+ new ArchetypeFormatter(),
+ new ChunkFormatter(),
+ new ArrayFormatter(),
+ new ComponentTypeFormatter(),
+ new SignatureFormatter(),
+ new EntitySlotFormatter(),
+ new EntityFormatter(),
+ new JaggedArrayFormatter(-1),
+ new JaggedArrayFormatter<(int,int)>((-1,-1)),
+ new JaggedArrayFormatter(new EntityData(null!, new Slot(-1,-1), -1))
+ ];
+
+ ///
+ /// The default formatters used to (de)serialize a single .
+ ///
+ private readonly IMessagePackFormatter[] _singleEntityFormatters =
+ [
+ new ComponentTypeFormatter(),
+ new SignatureFormatter(),
+ new SingleEntityFormatter()
+ ];
+
+ ///
+ /// The standard for world (de)serialization.
+ ///
+ private readonly MessagePackSerializerOptions _options;
+
+ ///
+ /// The standard for single entity (de)serialization.
+ ///
+ private readonly MessagePackSerializerOptions _singleEntityOptions;
+
+ ///
+ /// The static constructor gets called during compile time to setup the serializer.
+ ///
+ public ArchBinarySerializer(params IMessagePackFormatter[] custFormatters)
+ {
+ // Register all important jsonformatters
+ _options = MessagePackSerializerOptions.Standard.WithResolver(
+ MessagePack.Resolvers.CompositeResolver.Create(
+ [.. _formatters, .. custFormatters],
+ [
+ MessagePack.Resolvers.BuiltinResolver.Instance,
+ MessagePack.Resolvers.ContractlessStandardResolverAllowPrivate.Instance
+ ]
+ )
+ );
+
+ _singleEntityOptions = MessagePackSerializerOptions.Standard.WithResolver(
+ MessagePack.Resolvers.CompositeResolver.Create(
+ [.. _singleEntityFormatters, .. custFormatters],
+ [
+ MessagePack.Resolvers.BuiltinResolver.Instance,
+ MessagePack.Resolvers.ContractlessStandardResolverAllowPrivate.Instance
+ ]
+ )
+ );
+ }
+
+ ///
+ public byte[] Serialize(World world, Entity entity)
+ {
+ (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
+ return MessagePackSerializer.Serialize(entity, _singleEntityOptions);
+ }
+
+ ///
+ public void Serialize(Stream stream, World world, Entity entity)
+ {
+ (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
+ MessagePackSerializer.Serialize(stream, entity, _singleEntityOptions);
+ }
+
+ ///
+ public void Serialize(IBufferWriter writer, World world, Entity entity)
+ {
+ (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
+ MessagePackSerializer.Serialize(writer, entity, _singleEntityOptions);
+ }
+
+ ///
+ public Entity Deserialize(World world, byte[] entity)
+ {
+ (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
+ return MessagePackSerializer.Deserialize(entity, _singleEntityOptions);
+ }
+
+ ///
+ public Entity Deserialize(Stream stream, World world)
+ {
+ (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
+ return MessagePackSerializer.Deserialize(stream, _singleEntityOptions);
+ }
+
+ ///
+ public byte[] Serialize(World world)
+ {
+ return MessagePackSerializer.Serialize(world, _options);
+ ;
+ }
+
+ ///
+ public void Serialize(Stream stream, World world) => MessagePackSerializer.Serialize(stream, world, _options);
+
+ ///
+ public void Serialize(IBufferWriter writer, World world) => MessagePackSerializer.Serialize(writer, world, _options);
+
+ ///
+ public World Deserialize(byte[] world) => MessagePackSerializer.Deserialize(world, _options);
+
+ ///
+ public World Deserialize(Stream stream) => MessagePackSerializer.Deserialize(stream, _options);
+}
+
+///
+/// The class
+/// represents a json serializer for arch to (de)serialize single entities and whole worlds by binary.
+///
+public class ArchJsonSerializer : IArchSerializer
+{
+
+ ///
+ /// The default formatters used to (de)serialize the .
+ ///
+ private readonly IJsonFormatter[] _formatters = [
+ new WorldFormatter(),
+ new ArchetypeFormatter(),
+ new ChunkFormatter(),
+ new ArrayFormatter(),
+ new ComponentTypeFormatter(),
+ new SignatureFormatter(),
+ new EntitySlotFormatter(),
+ new EntityFormatter(),
+ new JaggedArrayFormatter(-1),
+ new JaggedArrayFormatter<(int,int)>((-1,-1)),
+ new JaggedArrayFormatter(new EntityData(null!, new Slot(-1, -1), -1)),
+ new DateTimeFormatter("yyyy-MM-dd HH:mm:ss"),
+ new NullableDateTimeFormatter("yyyy-MM-dd HH:mm:ss")
+ ];
+
+ ///
+ /// The default formatters used to (de)serialize a single .
+ ///
+ private readonly IJsonFormatter[] _singleEntityFormatters =
+ [
+ new ComponentTypeFormatter(),
+ new SignatureFormatter(),
+ new SingleEntityFormatter(),
+ new DateTimeFormatter("yyyy-MM-dd HH:mm:ss"),
+ new NullableDateTimeFormatter("yyyy-MM-dd HH:mm:ss")
+ ];
+
+ // It can `not` garbage collect and create is slightly high cost.
+ // so you should store to static field.
+ private readonly IJsonFormatterResolver _formatterResolver;
+
+ // CompositeResolver.Create can create dynamic composite resolver.
+ // It can `not` garbage collect and create is slightly high cost.
+ // so you should store to static field.
+ private readonly IJsonFormatterResolver _singleEntityFormatterResolver;
+
+ ///
+ /// The static constructor gets called during compile time to setup the serializer.
+ ///
+ public ArchJsonSerializer(params IJsonFormatter[] custFormatters)
+ {
+ // Register all important jsonformatters
+ _formatterResolver = CompositeResolver.Create(
+ [.. _formatters, .. custFormatters],
+ [
+ EnumResolver.UnderlyingValue,
+ StandardResolver.AllowPrivateExcludeNullSnakeCase,
+ BuiltinResolver.Instance,
+ DynamicGenericResolver.Instance,
+ ]
+ );
+
+ _singleEntityFormatterResolver = CompositeResolver.Create(
+ [.. _singleEntityFormatters, .. custFormatters],
+ [
+ EnumResolver.UnderlyingValue,
+ StandardResolver.AllowPrivateExcludeNullSnakeCase,
+ ]
+ );
+ }
+
+ ///
+ /// The static constructor gets called during compile time to setup the serializer.
+ /// This variant allows custom resolvers to be passed in as well.
+ ///
+ public ArchJsonSerializer(IJsonFormatter[] custFormatters, IJsonFormatterResolver[] custResolvers)
+ {
+ // Register all important jsonformatters
+ _formatterResolver = CompositeResolver.Create(
+ [.. _formatters, .. custFormatters],
+ [
+ .. custResolvers,
+ .. new[] {
+ EnumResolver.UnderlyingValue,
+ StandardResolver.AllowPrivateExcludeNullSnakeCase,
+ BuiltinResolver.Instance,
+ DynamicGenericResolver.Instance,
+ },
+ ]);
+
+ _singleEntityFormatterResolver = CompositeResolver.Create(
+ [.. _singleEntityFormatters, .. custFormatters],
+ [
+ .. custResolvers,
+ .. new[] {
+ EnumResolver.UnderlyingValue,
+ StandardResolver.AllowPrivateExcludeNullSnakeCase,
+ },
+ ]);
+ }
+
+ ///
+ /// Serializes the given to a json-string.
+ ///
+ /// The to serialize.
+ /// Its json-string.
+ public string ToJson(World world) => JsonSerializer.ToJsonString(world, _formatterResolver);
+
+ ///
+ /// Serializes the given to a json-string.
+ ///
+ /// The the entity belongs to..
+ /// The .
+ /// Its json-string.
+ /// A json-string of the entity with all its components.
+ public string ToJson(World world, Entity entity)
+ {
+ (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
+ return JsonSerializer.ToJsonString(entity, _singleEntityFormatterResolver);
+ }
+
+ ///
+ /// Deserializes the given json to a .
+ ///
+ /// The json to deserialize.
+ /// A new .
+ public World FromJson(string jsonWorld) => JsonSerializer.Deserialize(jsonWorld, _formatterResolver);
+
+ ///
+ /// Deserializes the given json to a .
+ /// The deserialized will receive a new id and a new worldId.
+ ///
+ /// The to deserialize the entity into.
+ /// The json of the entity to deserialize.
+ /// A new .
+ public Entity FromJson(World world, string jsonEntity)
+ {
+ (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
+ return JsonSerializer.Deserialize(jsonEntity, _singleEntityFormatterResolver);
+ }
+
+ ///
+ public byte[] Serialize(World world, Entity entity)
+ {
+ (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
+ return JsonSerializer.Serialize(entity, _singleEntityFormatterResolver);
+ }
+
+ ///
+ public void Serialize(Stream stream, World world, Entity entity)
+ {
+ (_singleEntityFormatters[1] as SingleEntityFormatter)!.EntityWorld = world;
+ JsonSerializer.Serialize(stream, entity, _singleEntityFormatterResolver);
+ }
+
+ ///
+ public void Serialize(IBufferWriter writer, World world, Entity entity) => throw new NotImplementedException();
+
+ ///
+ public Entity Deserialize(World world, byte[] entity)
+ {
+ (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
+ return JsonSerializer.Deserialize(entity, _singleEntityFormatterResolver);
+ }
+
+ ///
+ public Entity Deserialize(Stream stream, World world)
+ {
+ (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
+ return JsonSerializer.Deserialize(stream, _singleEntityFormatterResolver);
+ }
+
+ ///
+ public byte[] Serialize(World world) => JsonSerializer.Serialize(world, _formatterResolver);
+
+ ///
+ public void Serialize(Stream stream, World world) => JsonSerializer.Serialize(stream, world, _formatterResolver);
+
+ ///
+ public void Serialize(IBufferWriter writer, World world) => throw new NotImplementedException();
+
+ ///
+ public World Deserialize(byte[] world) => JsonSerializer.Deserialize(world, _formatterResolver);
+
+ ///
+ public World Deserialize(Stream stream) => JsonSerializer.Deserialize(stream, _formatterResolver);
+}
diff --git a/src/Arch.Extended/Arch.Persistence/StreamBufferWriter.cs b/src/Arch.Extended/Arch.Persistence/StreamBufferWriter.cs
new file mode 100644
index 0000000..6660512
--- /dev/null
+++ b/src/Arch.Extended/Arch.Persistence/StreamBufferWriter.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Buffers;
+using System.IO;
+
+namespace Arch.Persistence;
+
+///
+/// The class
+/// is a small wrapper around a implementing a .
+/// It buffers incoming bytes in an internally stored array and flushes it regulary into the -.
+///
+public sealed class StreamBufferWriter : IBufferWriter, IDisposable
+{
+ ///
+ /// The buffer.
+ ///
+ private byte[] _buffer;
+
+ ///
+ /// The .
+ ///
+ private readonly Stream _destination;
+
+ ///
+ // / If this instance owns the stream.
+ ///
+ private readonly bool _ownsStream;
+
+ ///
+ /// The current position and the amount of total leased bytes.
+ ///
+ private int _position, _leased;
+
+ ///
+ /// Creates a new instance.
+ ///
+ /// The .
+ /// The buffer-size of the .
+ /// If it owns the stream.
+ public StreamBufferWriter(Stream destination, int bufferSize = 1024, bool ownsStream = true)
+ {
+ const int minBufferSize = 128;
+ if (bufferSize < minBufferSize)
+ {
+ bufferSize = minBufferSize;
+ }
+
+ _buffer = ArrayPool.Shared.Rent(bufferSize);
+ _ownsStream = ownsStream;
+ _destination = destination;
+ }
+
+ ///
+ /// Leases an amount of bytes from the .
+ ///
+ /// The total amount.
+ /// The leased amount.
+ private int Lease(int sizeHint)
+ {
+ var available = _buffer.Length - _position;
+ if (available < sizeHint && _position != 0)
+ { // try to get more
+ Flush();
+ available = _buffer.Length - _position;
+ }
+
+ _leased = available;
+ return available;
+ }
+
+ ///
+ /// Flushes the buffered bytes to the .
+ ///
+ /// If it also should flush the .
+ public void Flush(bool flushUnderlyingStream = false)
+ {
+ if (_position != 0)
+ {
+ _destination.Write(_buffer, 0, _position);
+ _position = 0;
+ }
+ if (flushUnderlyingStream)
+ {
+ _destination.Flush();
+ }
+ }
+
+ ///
+ /// Advances the buffer, notifies this instance that there was something new written into the memory.
+ ///
+ /// The amount of bytes written.
+ /// Throws if we are out of memory.
+ void IBufferWriter.Advance(int count)
+ {
+ if (count > _leased || count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count));
+ _position += count;
+ _leased = 0;
+ }
+
+ ///
+ /// Returns a partion of the as a .
+ ///
+ /// The total amount.
+ /// The new instance.
+ Memory IBufferWriter.GetMemory(int sizeHint)
+ {
+ var actual = Lease(sizeHint);
+ return new Memory(_buffer, _position, actual);
+ }
+
+ ///
+ /// Returns a partion of the as a .
+ ///
+ /// The total amount.
+ /// The new instance.
+ Span IBufferWriter.GetSpan(int sizeHint)
+ {
+ var actual = Lease(sizeHint);
+ return new Span(_buffer, _position, actual);
+ }
+
+ ///
+ /// Disposes this instance, flushes and releases all memory.
+ ///
+ public void Dispose()
+ {
+ Flush(true);
+
+ var tmp = _buffer;
+ _buffer = null!;
+ ArrayPool.Shared.Return(tmp);
+
+ if (_ownsStream)
+ {
+ _destination.Dispose();
+ }
+ }
+}
diff --git a/src/Arch.Extended/Arch.Relationships/EntityRelationshipExtensions.cs b/src/Arch.Extended/Arch.Relationships/EntityRelationshipExtensions.cs
new file mode 100644
index 0000000..c01c988
--- /dev/null
+++ b/src/Arch.Extended/Arch.Relationships/EntityRelationshipExtensions.cs
@@ -0,0 +1,125 @@
+namespace Arch.Relationships;
+
+using global::System.Diagnostics.Contracts;
+using global::System.Runtime.CompilerServices;
+using Arch.Core;
+
+#if !PURE_ECS
+
+///
+/// The class
+/// stores several methods to forward relationship methods from the to the .
+///
+public static class EntityRelationshipExtensions
+{
+
+ ///
+ /// Adds a new relationship to the .
+ ///
+ /// The source of the relationship.
+ /// The target of the relationship.
+ // / The relationship type.
+ /// The relationship instance.
+ public static void AddRelationship(this in Entity source, Entity target, T relationship = default!)
+ {
+ var world = World.Worlds[source.WorldId];
+ world.AddRelationship(source, target, relationship);
+ }
+
+ ///
+ /// Sets a relationship to the by updating its relationship data.
+ ///
+ /// The source of the relationship.
+ /// The target of the relationship.
+ /// The relationship type.
+ /// The relationship instance.
+ public static void SetRelationship(this in Entity source, Entity target, T relationship = default!)
+ {
+ var world = World.Worlds[source.WorldId];
+ world.SetRelationship(source, target, relationship);
+ }
+
+ ///
+ /// Checks if an has a certain relationship.
+ ///
+ /// The relationship type.
+ /// The source of the relationship.
+ /// The target of the relationship.
+ /// True if it has the desired relationship, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ public static bool HasRelationship(this in Entity source, Entity target)
+ {
+ var world = World.Worlds[source.WorldId];
+ return world.HasRelationship(source, target);
+ }
+
+ ///
+ /// Checks if an has a certain relationship.
+ ///
+ /// The relationship type.
+ /// The source of the relationship.
+ /// True if it has the desired relationship, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ public static bool HasRelationship(this in Entity source)
+ {
+ var world = World.Worlds[source.WorldId];
+ return world.HasRelationship(source);
+ }
+
+ ///
+ /// Returns a relationship of an .
+ ///
+ /// The relationship type.
+ /// The source of the relationship.
+ /// The target of the relationship.
+ /// The relationship.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ public static T GetRelationship(this in Entity source, Entity target)
+ {
+ var world = World.Worlds[source.WorldId];
+ return world.GetRelationship(source, target);
+ }
+
+ ///
+ /// Returns a relationship of an .
+ ///
+ /// The relationship type.
+ /// The source of the relationship.
+ /// The .
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ public static ref Relationship GetRelationships(this in Entity source)
+ {
+ var world = World.Worlds[source.WorldId];
+ return ref world.GetRelationships(source);
+ }
+
+ ///
+ /// Tries to return an s relationship of the specified type.
+ /// Will copy the relationship if its a struct.
+ ///
+ /// The relationship type.
+ /// The source of the relationship.
+ /// The target of the relationship.
+ /// The found relationship.
+ /// True if it exists, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ public static bool TryGetRelationship(this in Entity source, Entity target, out T relationship)
+ {
+ var world = World.Worlds[source.WorldId];
+ return world.TryGetRelationship(source, target, out relationship);
+ }
+
+ ///
+ /// Removes a relationship from an .
+ ///
+ /// The relationship type.
+ /// The to remove the relationship from.
+ /// The target of the relationship.
+ public static void RemoveRelationship(this in Entity source, Entity target)
+ {
+ var world = World.Worlds[source.WorldId];
+ world.RemoveRelationship(source, target);
+ }
+}
+
+#endif
diff --git a/src/Arch.Extended/Arch.Relationships/EntityRelationshipExtensions.cs.uid b/src/Arch.Extended/Arch.Relationships/EntityRelationshipExtensions.cs.uid
new file mode 100644
index 0000000..4cf2c25
--- /dev/null
+++ b/src/Arch.Extended/Arch.Relationships/EntityRelationshipExtensions.cs.uid
@@ -0,0 +1 @@
+uid://574q5d6c3s6y
diff --git a/src/Arch.Extended/Arch.Relationships/Enumerators.cs b/src/Arch.Extended/Arch.Relationships/Enumerators.cs
new file mode 100644
index 0000000..3fc139e
--- /dev/null
+++ b/src/Arch.Extended/Arch.Relationships/Enumerators.cs
@@ -0,0 +1,58 @@
+namespace Arch.Relationships;
+
+using global::System;
+using global::System.Collections.Generic;
+using Arch.Core;
+
+///
+/// The struct
+/// is a enumerator to enumerate a passed in an efficient way.
+///
+///
+public struct SortedListEnumerator
+{
+ private readonly SortedList _sortedList;
+ private int _currentIndex;
+
+ ///
+ /// Constructor.
+ ///
+ /// List.
+ public SortedListEnumerator(SortedList list)
+ {
+ _sortedList = list;
+ _currentIndex = -1;
+ }
+
+ ///
+ /// Current.
+ ///
+ public readonly KeyValuePair Current
+ {
+ get
+ {
+ if (_currentIndex == -1 || _currentIndex >= _sortedList.Count)
+ {
+ throw new InvalidOperationException();
+ }
+
+ var key = _sortedList.Keys[_currentIndex];
+ var value = _sortedList.Values[_currentIndex];
+ return new KeyValuePair(key, value);
+ }
+ }
+
+ ///
+ /// Moves to the next element in the enumerator.
+ ///
+ public bool MoveNext()
+ {
+ _currentIndex++;
+ return _currentIndex < _sortedList.Count;
+ }
+
+ ///
+ /// Resets the enumerator to its initial position.
+ ///
+ public void Reset() => _currentIndex = -1;
+}
diff --git a/src/Arch.Extended/Arch.Relationships/Enumerators.cs.uid b/src/Arch.Extended/Arch.Relationships/Enumerators.cs.uid
new file mode 100644
index 0000000..e365866
--- /dev/null
+++ b/src/Arch.Extended/Arch.Relationships/Enumerators.cs.uid
@@ -0,0 +1 @@
+uid://br3hmo40b0kmx
diff --git a/src/Arch.Extended/Arch.Relationships/InRelationship.cs b/src/Arch.Extended/Arch.Relationships/InRelationship.cs
new file mode 100644
index 0000000..00df7d3
--- /dev/null
+++ b/src/Arch.Extended/Arch.Relationships/InRelationship.cs
@@ -0,0 +1,38 @@
+namespace Arch.Relationships;
+
+using Arch.Core;
+
+
+///
+/// The struct
+/// represents a reference to a .
+/// It sits on an to indicate in which other s it is involved in.
+///
+internal readonly struct InRelationship
+{
+ ///
+ /// The id of the -Component that this points to.
+ /// Basically the the is in.
+ /// TODO: Uhmm... how the heck do we convert the Id back to the ?
+ ///
+ public readonly int ComponentTypeId;
+
+ ///
+ /// Creates a new instance.
+ ///
+ /// The that represents the relation.
+ internal InRelationship(ComponentType targetRelation)
+ {
+ ComponentTypeId = targetRelation.Id;
+ }
+
+ ///
+ /// Creates a new instance.
+ /// Mostly for binary serialization.
+ ///
+ /// The .
+ internal InRelationship(int componentTypeId)
+ {
+ ComponentTypeId = componentTypeId;
+ }
+}
diff --git a/src/Arch.Extended/Arch.Relationships/InRelationship.cs.uid b/src/Arch.Extended/Arch.Relationships/InRelationship.cs.uid
new file mode 100644
index 0000000..9c7b6ca
--- /dev/null
+++ b/src/Arch.Extended/Arch.Relationships/InRelationship.cs.uid
@@ -0,0 +1 @@
+uid://cu62xtv73hjq7
diff --git a/src/Arch.Extended/Arch.Relationships/Relationship.cs b/src/Arch.Extended/Arch.Relationships/Relationship.cs
new file mode 100644
index 0000000..6f1be81
--- /dev/null
+++ b/src/Arch.Extended/Arch.Relationships/Relationship.cs
@@ -0,0 +1,146 @@
+namespace Arch.Relationships;
+
+using global::System.Collections.Generic;
+using global::System.Runtime.CompilerServices;
+using Arch.Core;
+
+///
+/// The interface
+/// is an interface that provides all methods required to act as a relationship.
+///
+internal interface IRelationship
+{
+ ///
+ /// The amount of relationships currently in the buffer.
+ ///
+ int Count
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ /// Removes the buffer as a component from the given world and entity.
+ ///
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void Destroy(World world, Entity source);
+
+ ///
+ /// Removes the relationship targeting from this buffer.
+ ///
+ /// The in the relationship to remove.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void Remove(Entity target);
+}
+
+///
+/// A buffer storing relationships of and .
+///
+/// The type of the second relationship element.
+public class Relationship : IRelationship
+{
+
+ ///
+ /// Its relations.
+ ///
+ internal readonly SortedList _elements;
+
+ ///
+ /// Initializes a new instance of an .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Relationship()
+ {
+ _elements = [];
+ }
+
+ ///
+ /// Initializes a new instance of an .
+ /// Mostly for binary serialization.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Relationship(SortedList elements)
+ {
+ _elements = elements;
+ }
+
+ ///
+ int IRelationship.Count
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _elements.Count;
+ }
+
+ ///
+ internal int Count
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => ((IRelationship)this).Count;
+ }
+
+ ///
+ /// Adds a relationship to this buffer.
+ ///
+ /// The instance of the relationship.
+ /// The target of the relationship.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void Add(in T relationship, Entity target) => _elements.Add(target, relationship);
+
+ ///
+ /// Sets the stored for the given .
+ ///
+ /// The .
+ /// The data to store.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Set(Entity entity, T data = default!) => _elements[entity] = data;
+
+ ///
+ /// Determines whether the given contains the passed or not.
+ ///
+ /// The .
+ /// True or false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Contains(Entity entity) => _elements.ContainsKey(entity);
+
+ ///
+ /// Returns the stored for the given .
+ ///
+ /// The .
+ /// The stored .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public T Get(Entity entity) => _elements[entity];
+
+ ///
+ /// Returns the stored for the given .
+ ///
+ /// The .
+ /// The stored .
+ /// The stored .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryGetValue(Entity entity, out T value) => _elements.TryGetValue(entity, out value!);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void IRelationship.Remove(Entity target) => _elements.Remove(target);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void Remove(Entity target) => ((IRelationship)this).Remove(target);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void IRelationship.Destroy(World world, Entity source) => world.Remove>(source);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void Destroy(World world, Entity source) => ((IRelationship)this).Destroy(world, source);
+
+ ///
+ /// Creates a new .
+ ///
+ /// The new .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public SortedListEnumerator GetEnumerator() => new(_elements);
+};
diff --git a/src/Arch.Extended/Arch.Relationships/Relationship.cs.uid b/src/Arch.Extended/Arch.Relationships/Relationship.cs.uid
new file mode 100644
index 0000000..89a000e
--- /dev/null
+++ b/src/Arch.Extended/Arch.Relationships/Relationship.cs.uid
@@ -0,0 +1 @@
+uid://d2oi4itpntwl3
diff --git a/src/Arch.Extended/Arch.Relationships/WorldRelationshipExtensions.cs b/src/Arch.Extended/Arch.Relationships/WorldRelationshipExtensions.cs
new file mode 100644
index 0000000..cc4d435
--- /dev/null
+++ b/src/Arch.Extended/Arch.Relationships/WorldRelationshipExtensions.cs
@@ -0,0 +1,272 @@
+// [assembly:InternalsVisibleTo("Arch.Relationships.Tests")]
+namespace Arch.Relationships;
+
+using global::System.Diagnostics.Contracts;
+using global::System.Runtime.CompilerServices;
+using Arch.Core;
+
+///
+/// The class
+/// stores several extension methods for relationships handling.
+///
+public static class WorldRelationshipExtensions
+{
+
+#if EVENTS
+
+ ///
+ /// Subscribes to entity destruction events to cleanup their relations.
+ ///
+ public static void HandleRelationshipCleanup(this World world)
+ {
+ world.SubscribeEntityDestroyed((in Entity entity) => CleanupRelationships(world, in entity));
+ }
+
+ // TODO: Probably someone will kill me for the dark magic that happens down below.
+ ///
+ /// Cleans up all relations of the passed .
+ ///
+ ///
+ ///
+ public static void CleanupRelationships(this World world, in Entity entity)
+ {
+ ref var relationships = ref world.TryGetRefRelationships(entity, out var exists);
+ if (!exists)
+ {
+ return;
+ }
+
+ foreach (var (target, inRelationship) in relationships.Elements)
+ {
+ var id = inRelationship.ComponentTypeId;
+ var componentType = new ComponentType(id, 0);
+
+ // Get slots, chunk and array to prevent entity.Get(type) object allocation
+ ref readonly var chunk = ref world.GetChunk(target);
+ var array = chunk.GetArray(componentType);
+ var relationshipsArray = Unsafe.As(array);
+
+ var slot = world.GetSlot(target);
+ var relationship = relationshipsArray[slot.Index];
+ relationship.Remove(entity);
+
+ if (relationship.Count == 0)
+ {
+ relationship.Destroy(world, target);
+ }
+
+ ref var targetRelationships = ref world.TryGetRefRelationships(target, out exists);
+ if (!exists)
+ {
+ continue;
+ }
+
+ targetRelationships.Remove(entity);
+ }
+ }
+#endif
+
+ ///
+ /// Adds a new relationship to the .
+ ///
+ /// World.
+ /// The source of the relationship.
+ /// The target of the relationship.
+ /// The relationship type.
+ /// The relationship instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void AddRelationship(this World world, Entity source, Entity target, in T relationship = default!)
+ {
+ ref var buffer = ref world.AddOrGetRelationships(source);
+ buffer.Add(in relationship, target);
+
+ var targetComp = new InRelationship(Component>.ComponentType);
+ ref var targetBuffer = ref world.AddOrGetRelationships(target);
+ targetBuffer.Add(in targetComp, source);
+ }
+
+ ///
+ /// Ensures the existence of a relationship on an .
+ ///
+ /// The relationship type.
+ /// World.
+ /// The source of the relationship.
+ /// The target of the relationship.
+ /// The relationship value used if its being added.
+ /// The relationship.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T AddOrGetRelationship(this World world, Entity source, Entity target, in T relationship = default!)
+ {
+ ref var relationships = ref world.TryGetRefRelationships(source, out var exists);
+ if (exists)
+ {
+ return relationships.Get(target);
+ }
+
+ world.AddRelationship(source, target, in relationship);
+ return world.GetRelationship(source, target);
+ }
+
+ ///
+ /// Ensures the existence of a buffer of relationships on an .
+ ///
+ /// World.
+ /// The source of the relationships.
+ /// The relationship type.
+ /// The relationships.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static ref Relationship AddOrGetRelationships(this World world, Entity source)
+ {
+ ref var component = ref world.TryGetRef>(source, out var exists);
+ if (exists)
+ {
+ return ref component!;
+ }
+
+ world.Add(source, new Relationship());
+ return ref world.Get>(source);
+ }
+
+ ///
+ /// Sets the existing relationship data.
+ ///
+ /// The relationship type.
+ /// World.
+ /// The source of the relationship.
+ /// The target of the relationship.
+ /// The new data.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void SetRelationship(this World world, Entity source, Entity target, in T relationship = default!)
+ {
+ ref var relationships = ref world.GetRelationships(source);
+ relationships.Set(target, relationship);
+ }
+
+ ///
+ /// Checks if an has a certain relationship.
+ ///
+ /// The relationship type.
+ /// World.
+ /// The source of the relationship.
+ /// The target of the relationship.
+ /// True if it has the desired relationship, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ public static bool HasRelationship(this World world, Entity source, Entity target)
+ {
+ ref var relationships = ref world.TryGetRefRelationships(source, out var exists);
+ if (!exists)
+ {
+ return false;
+ }
+
+ return relationships.Contains(target);
+ }
+
+ ///
+ /// Checks if an has a certain relationship.
+ ///
+ /// The relationship type.
+ /// World.
+ /// The source of the relationship.
+ /// True if it has the desired relationship, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ public static bool HasRelationship(this World world, Entity source) => world.Has>(source);
+
+ ///
+ /// Returns a relationship of an .
+ ///
+ /// The relationship type.
+ /// World.
+ /// The source of the relationship.
+ /// The target of the relationship.
+ /// The relationship.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ public static T GetRelationship(this World world, Entity source, Entity target)
+ {
+ ref var relationships = ref world.GetRelationships(source);
+ return relationships.Get(target);
+ }
+
+ ///
+ /// Tries to return an s relationship of the specified type.
+ /// Will copy the relationship if its a struct.
+ ///
+ /// The relationship type.
+ /// World.
+ /// The source of the relationship.
+ /// The target of the relationship.
+ /// The found relationship.
+ /// True if it exists, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ public static bool TryGetRelationship(this World world, Entity source, Entity target, out T relationship)
+ {
+ ref var relationships = ref world.TryGetRefRelationships(source, out var exists);
+ if (!exists)
+ {
+ relationship = default!;
+ return false;
+ }
+
+ return relationships.TryGetValue(target, out relationship);
+ }
+
+ ///
+ /// Returns all relationships of the given type of an .
+ ///
+ /// The relationship type.
+ /// World.
+ /// The source of the relationship.
+ /// A reference to the relationships.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ public static ref Relationship GetRelationships(this World world, Entity source) => ref world.Get>(source);
+
+ ///
+ /// Tries to return an s relationships of the specified type.
+ ///
+ /// The relationship type.
+ /// World.
+ /// The .
+ /// The found relationships.
+ /// True if it exists, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ internal static bool TryGetRelationships(this World world, Entity source, out Relationship relationships) => world.TryGet(source, out relationships!);
+
+ ///
+ /// Tries to return a reference to an s relationships of the
+ /// specified type.
+ ///
+ /// The relationship type.
+ /// World.
+ /// The .
+ /// True if it exists, otherwise false.
+ /// A reference to the relationships.
+ [MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
+ internal static ref Relationship TryGetRefRelationships(this World world, Entity source, out bool exists) => ref world.TryGetRef>(source, out exists);
+
+ ///
+ /// Removes a relationship from an .
+ ///
+ /// The relationship type.
+ /// World.
+ /// The to remove the relationship from.
+ /// The target of the relationship.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RemoveRelationship(this World world, Entity source, Entity target)
+ {
+ ref var buffer = ref world.GetRelationships(source);
+ buffer.Remove(target);
+
+ if (buffer.Count == 0)
+ {
+ world.Remove>(source);
+ }
+
+ ref var targetBuffer = ref world.GetRelationships(target);
+ targetBuffer.Remove(source);
+
+ if (targetBuffer.Count == 0)
+ {
+ world.Remove>(target);
+ }
+ }
+}
diff --git a/src/Arch.Extended/Arch.Relationships/WorldRelationshipExtensions.cs.uid b/src/Arch.Extended/Arch.Relationships/WorldRelationshipExtensions.cs.uid
new file mode 100644
index 0000000..7173e24
--- /dev/null
+++ b/src/Arch.Extended/Arch.Relationships/WorldRelationshipExtensions.cs.uid
@@ -0,0 +1 @@
+uid://dfnift6mppxam
diff --git a/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs b/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs
index 3a8016d..6f3e689 100644
--- a/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs
+++ b/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs
@@ -12,8 +12,8 @@ public partial class ConveyorBeltStraight : Node3D, IProvide
{
public override void _Notification(int what) => this.Notify(what);
[Export] public int Width { get; set; } = default;
- public IVoxelGridRegistry Value() => Grid;
- [Dependency] protected IVoxelGridRegistry Grid => this.DependOn();
+ public IVoxelGridRegistry Value() => FoodFactoryApi.GridRegistry;
+ [Dependency] protected IFoodFactoryApi FoodFactoryApi => this.DependOn();
private VoxelGuid _guid = default;
public GridTransform3D VoxelTransform
{
@@ -62,13 +62,13 @@ public partial class ConveyorBeltStraight : Node3D, IProvide
}
public void OnResolved()
{
- _guid = Grid.Register(this, VoxelTransform.Origin);
+ _guid = FoodFactoryApi.GridRegistry.Register(this, VoxelTransform.Origin);
this.Provide();
}
public override void _ExitTree()
{
- Grid.UnRegister(_guid);
+ FoodFactoryApi.GridRegistry.UnRegister(_guid);
base._ExitTree();
}
}
diff --git a/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn b/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn
index 4690c42..a5c58ef 100644
--- a/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn
+++ b/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn
@@ -1,10 +1,10 @@
[gd_scene format=3 uid="uid://c4h7mwnfrdesg"]
[ext_resource type="Script" uid="uid://dgyuh6un1qoj4" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs" id="1_mn8ro"]
-[ext_resource type="Script" uid="uid://j24fuotdwwx4" path="res://src/VoxelGrid/TestItemConveyor.cs" id="2_1vr1d"]
+[ext_resource type="Script" uid="uid://j24fuotdwwx4" path="res://src/Conveyors/TestItemConveyor.cs" id="2_1vr1d"]
[ext_resource type="PackedScene" uid="uid://bapdy1c6rb4qr" path="res://assets/kenney_conveyor-kit/Models/GLB format/conveyor-sides.glb" id="2_mn8ro"]
[ext_resource type="PackedScene" uid="uid://ccuhhy2oa8xvm" path="res://assets/kenney_conveyor-kit/Models/GLB format/arrow-basic.glb" id="3_1vr1d"]
-[ext_resource type="Script" uid="uid://bjuntmf2sjynp" path="res://src/VoxelGrid/ConveyorItemRender.cs" id="4_g7n1f"]
+[ext_resource type="Script" uid="uid://bjuntmf2sjynp" path="res://src/Conveyors/ConveyorItemRender.cs" id="4_g7n1f"]
[sub_resource type="Curve3D" id="Curve3D_x5qdr"]
_data = {
diff --git a/src/Conveyors/ConveyorItemRender.cs b/src/Conveyors/ConveyorItemRender.cs
index a132dc3..c7a53c1 100644
--- a/src/Conveyors/ConveyorItemRender.cs
+++ b/src/Conveyors/ConveyorItemRender.cs
@@ -3,10 +3,14 @@ namespace FoodFactory.Conveyors;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
+using Arch.Core;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
+using Chickensoft.Sync.Primitives;
+using CommunityToolkit.HighPerformance.Helpers;
using FoodFactory.Items;
using Godot;
+using SJK.Math;
[Meta(typeof(IAutoNode))]
public partial class ConveyorItemRender : Node
@@ -15,48 +19,69 @@ public partial class ConveyorItemRender : Node
[Export] protected Path3D Path3D { get; set; } = default!;
[Export] protected TestItemConveyor ItemConveyor { get; set; } = default!;
[Chickensoft.AutoInject.Dependency] protected IItemRenderer Items => this.DependOn();
+ [Chickensoft.AutoInject.Dependency] protected IFoodFactoryApi FoodFactoryApi => this.DependOn();
// private Chickensoft.Sync.Primitives.AutoList.Binding _binding = default!;
- public override async void _Ready()
+ private AutoList.Entry>.Binding _binding = default!;
+ public void OnResolved()
{
- base._Ready();
- if (!ItemConveyor.IsNodeReady())
- {
- await ToSignal(ItemConveyor, Node.SignalName.Ready);
- }
- await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
- ItemConveyor.GetChildren().OfType().First().Timeout += () => offset = 0;
- return;
- // _binding = ItemConveyor.Items.Items.Bind();
- // binding.OnRemove(callback =>
- // {
- // Items.Remove(callback.Item);
- // // GD.PrintS(callback.Item, callback.BeltT);
- // // if (itemsRenders.TryGetValue(callback.Item, out var node))
- // // {
- // // GD.PrintS(callback.Item, callback.BeltT,node);
- // // itemsRenders.Remove(callback.Item);
- // // node.QueueFree();
- // // }
- // });
- // _binding.OnAdd((i, v) => Items.UpdateTransform(i.Item, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(i.BeltT)));
- // _binding.OnUpdate((a, b) => Items.UpdateTransform(a.Item, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(b.BeltT).Translated(-Path3D.Curve.SampleBakedWithRotation(b.BeltT).Basis.X * b.LaneSpan.Start)));
- }
- float offset = 0;
- public override void _Process(double delta)
- {
- offset += (float)delta;
- base._Process(delta);
- var e = ItemConveyor.EnumerateTowardStart();
- while (e.MoveNext())
+ _binding = ItemConveyor.Items.Values.Bind().OnUpdate((old, updated) =>
{
- Items.UpdateTransform(e.Current.Value, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(e.Current.Position + (ItemConveyor.SignedSpeed * offset)));
- }
+ if (old.Position == updated.Position)
+ {
+ return;
+ }
+
+ Items.UpdateTransform(updated.Value, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(updated.Position), (float)delta);
+
+ });
+ // FoodFactoryApi.TickManger.GameTick += _ =>Process(_.Delta);// offset = 0;
+ FoodFactoryApi.TickManger.GameTick += _ => delta = _.Delta;// offset = 0;
}
+ // public override async void _Ready()
+ // {
+ // base._Ready();
+ // if (!ItemConveyor.IsNodeReady())
+ // {
+ // await ToSignal(ItemConveyor, Node.SignalName.Ready);
+ // }
+ // await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
+ // // ItemConveyor.GetChildren().OfType().First().Timeout += () => offset = 0;
+ // return;
+ // // _binding = ItemConveyor.Items.Items.Bind();
+ // // binding.OnRemove(callback =>
+ // // {
+ // // Items.Remove(callback.Item);
+ // // // GD.PrintS(callback.Item, callback.BeltT);
+ // // // if (itemsRenders.TryGetValue(callback.Item, out var node))
+ // // // {
+ // // // GD.PrintS(callback.Item, callback.BeltT,node);
+ // // // itemsRenders.Remove(callback.Item);
+ // // // node.QueueFree();
+ // // // }
+ // // });
+ // // _binding.OnAdd((i, v) => Items.UpdateTransform(i.Item, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(i.BeltT)));
+ // // _binding.OnUpdate((a, b) => Items.UpdateTransform(a.Item, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(b.BeltT).Translated(-Path3D.Curve.SampleBakedWithRotation(b.BeltT).Basis.X * b.LaneSpan.Start)));
+
+ // }
+ float offset = 0;
+ double delta = 0;
+
+ // public void Process(double delta)
+ // {
+ // offset += (float)delta;
+ // base._Process(delta);
+ // var e = ItemConveyor.EnumerateTowardStart();
+ // while (e.MoveNext())
+ // {
+ // // Items.UpdateTransform(e.Current.Value, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(e.Current.Position + (ItemConveyor.SignedSpeed * offset)));
+ // Items.UpdateTransform(e.Current.Value, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(e.Current.Position), (float)delta);
+ // }
+ // }
protected override void Dispose(bool disposing)
{
- // _binding.Dispose();
+ _binding.Dispose();
base.Dispose(disposing);
}
}
@@ -68,6 +93,58 @@ public interface IItemRenderer
void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time);
void Remove(IBeltItem beltItem);
}
+public partial class ItemRenderBuffered : Node3D, IItemRenderer
+{
+ private record struct ItemEntry(IBeltItem Item, Transform3D PreviousTransform, Transform3D NewTransform, float Elapsed, float Duration);
+ private List _current = [];
+ private List _next = [];
+ private Dictionary _visuals = [];
+ public void Remove(IBeltItem beltItem) => throw new System.NotImplementedException();
+ public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform) => throw new System.NotImplementedException();
+ public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time)
+ {
+ var index = _current.FindIndex(f => f.Item == beltItem);
+ if (index >= 0)
+ {
+ _current[index] = _current[index] with { PreviousTransform = _current[index].NewTransform, NewTransform = newTransform, Elapsed = 0, Duration = time };
+ }
+ else
+ {
+ if (!_visuals.TryGetValue(beltItem, out var node3D))
+ {
+ _visuals[beltItem] = node3D = beltItem.CreateItemVisual();
+ node3D.Transform = newTransform;
+ AddChild(node3D);
+ beltItem.Disposed += _ => node3D.QueueFree();
+ }
+ // if (node3D.Transform.Origin.DistanceTo(newTransform.Origin) < .00001f)
+ // {
+ // return;
+ // }
+ _current.Add(new(beltItem, node3D.Transform, newTransform, 0, time));
+ }
+ }
+ public override void _Process(double delta)
+ {
+ _next.Clear();
+ GD.Print(_current.Count);
+ for (int i = 0; i < _current.Count; i++)
+ {
+ var entry = _current[i];
+ if (!IsInstanceValid(_visuals[entry.Item])){
+ _visuals.Remove(entry.Item);
+ continue;
+ }
+ entry.Elapsed = (float)(entry.Elapsed + delta);
+ _visuals[entry.Item].Transform = entry.PreviousTransform.InterpolateWith(entry.NewTransform, entry.Elapsed / entry.Duration);
+ if ((entry.Elapsed / entry.Duration) < 1f)
+ {
+ _next.Add(entry);
+ }
+ }
+ (_current, _next) = (_next, _current);
+ }
+}
public partial class ItemRenderSimple : Node3D, IItemRenderer
{
private Dictionary _items = [];
diff --git a/src/Conveyors/ItemConveyor.cs b/src/Conveyors/ItemConveyor.cs
index ef9fccd..37d7191 100644
--- a/src/Conveyors/ItemConveyor.cs
+++ b/src/Conveyors/ItemConveyor.cs
@@ -25,6 +25,7 @@ public interface IMovementConveyor
float SignedSpeed { get; set; }
bool IsReversed { get; set; }
float Length { get; set; }
+ bool HasItems();
// float GetAvailableTravel(ItemConveyor.BeltDirection beltDirection, LaneSpanT itemSpan, float maxDistance);
// IBeltSlotProfile GetPortFacingStart();
// IBeltSlotProfile GetPortFacingEnd();
@@ -41,25 +42,6 @@ public interface IMovementConveyor
BeltObstacle GetDistanceToNextItem(BeltDirection beltDirection, float itemBeltT, float maxDistToCheck, LaneSpan laneSpan, HashSet? visted = null);
ConveyorPort CreatePort(BeltPortProfile profile, BeltT beltT, LaneSpan laneSpan);
}
-public readonly struct ConveyorItemHandle
-{
- private readonly Action _remove;
- private readonly Action _replace;
- public int Index { get; }
- public ConveyorSlice Slice { get; }
- public IBeltItem Item => Slice.Item;
- public LaneSpan Span => Slice.LaneSpan;
- public float BeltT => Slice.BeltT;
- public ConveyorItemHandle(int index, ConveyorSlice slice, Action remove, Action replace)
- {
- Index = index;
- Slice = slice;
- _remove = remove;
- _replace = replace;
- }
- public void Remove() => _remove();
- public void Replace(ConveyorSlice slice) => _replace(slice);
-}
public interface IBeltMovement
{
void AdvanceBelt(IMovementConveyor conveyor, float delta);
@@ -112,7 +94,7 @@ public sealed class IndividualMovement : IBeltMovement
}
}
}
- enumerator.SortAll();
+ // enumerator.SortAll();
}
}
public sealed class StrictMovement : IBeltMovement
@@ -208,9 +190,78 @@ public sealed class StrictMovement : IBeltMovement
// }
// }
- public void AdvanceBelt(IMovementConveyor conveyor, float delta) => throw new NotImplementedException();
-}
+ public void AdvanceBelt(IMovementConveyor conveyor, float delta)
+ {
+ // GD.Print(conveyor.Items);
+ if (conveyor.GetBeltDirection() == BeltDirection.NotMoving)
+ {
+ return;
+ }
+ if (!conveyor.HasItems())
+ {
+ return;
+ }
+ // GD.Print("hello");
+ var towardStart = conveyor.GetBeltDirection() == BeltDirection.TowardStart;
+ float maxMove = conveyor.SpeedMagnitude * delta;
+ var enumerator = !towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd();
+ BeltObstacle freeSpace = default!;
+ float lastPos = 0;
+ if (enumerator.MoveNext())
+ {
+ lastPos = enumerator.Current.Position;
+ freeSpace = conveyor.GetDistanceToNextItem(conveyor.GetBeltDirection(),lastPos,maxMove,LaneSpan.One,[]);
+ }
+ var move = MathF.Min(maxMove, freeSpace.Distance);
+ if (lastPos > conveyor.Length || lastPos < 0)
+ {
+ enumerator = !towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd();
+ while (enumerator.MoveNext() && (enumerator.Current.Position >= conveyor.Length || enumerator.Current.Position <= 0))
+ {
+ if (towardStart ? enumerator.Current.Position <= 0 : enumerator.Current.Position >= conveyor.Length)
+ {
+ var facing = conveyor.GetPortFacing(towardStart ? conveyor.StartPort : conveyor.EndPort);
+ // GD.Print(facing.HasValue(out var slot2),slot2);// , slot2.CanAccept(new TestItem(),LaneSpan.One,0));
+ if (facing.HasValue(out var slot) && slot.TryInsert(enumerator.Current.Value, LaneSpan.One, 0))
+ {
+ // GD.Print(space is PortBeltObstacle port?port.LaneSpan:itemRef.Value.LaneSpan);
+ // GD.PrintS(itemRef.Value.LaneSpan,itemRef.Value.LaneSpan);
+ // GD.Print(itemRef.Item);
+ enumerator.Remove();
+ }
+ }
+ }
+ return;
+ }
+ // var freeSpace = conveyor.GetDistanceToNextItem(conveyor.GetBeltDirection(),conveyor.GetLastItemPosition(),maxMove,LaneSpan.One,[]);
+ if (freeSpace.Distance <= 0)
+ {
+ return;
+ }
+ enumerator = !towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd();
+ if (enumerator.MoveNext())
+ {
+ enumerator.MoveAllBy(MathF.Min(maxMove, freeSpace.Distance));
+ }
+ enumerator = !towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd();
+ while (enumerator.MoveNext() && (enumerator.Current.Position >= conveyor.Length || enumerator.Current.Position <= 0))
+ {
+ if (towardStart ? enumerator.Current.Position <= 0 : enumerator.Current.Position >= conveyor.Length)
+ {
+ var facing = conveyor.GetPortFacing(towardStart ? conveyor.StartPort : conveyor.EndPort);
+ // GD.Print(facing.HasValue(out var slot2),slot2);// , slot2.CanAccept(new TestItem(),LaneSpan.One,0));
+ if (facing.HasValue(out var slot) && slot.TryInsert(enumerator.Current.Value, LaneSpan.One, 0))
+ {
+ // GD.Print(space is PortBeltObstacle port?port.LaneSpan:itemRef.Value.LaneSpan);
+ // GD.PrintS(itemRef.Value.LaneSpan,itemRef.Value.LaneSpan);
+ // GD.Print(itemRef.Item);
+ enumerator.Remove();
+ }
+ }
+ }
+ }
+}
public struct ConveyorSlice(IBeltItem item, float beltT = 0)
@@ -258,7 +309,12 @@ public record BeltTOffset(float T) : BeltT();
public static class ConveyorExtensions
{
-
+ public static BeltDirection Reverse(this BeltDirection beltDirection) => beltDirection switch
+ {
+ BeltDirection.TowardStart => BeltDirection.TowardEnd,
+ BeltDirection.TowardEnd => BeltDirection.TowardStart,
+ _ => throw new NotSupportedException(),
+ };
public static IOption GetPortFacing(this IBeltPort slot, IVoxelGridRegistry gridRegistry)
{
var toCheck = new List();
diff --git a/src/Conveyors/TestItemConveyor.cs b/src/Conveyors/TestItemConveyor.cs
index 20d09b3..466cce0 100644
--- a/src/Conveyors/TestItemConveyor.cs
+++ b/src/Conveyors/TestItemConveyor.cs
@@ -19,10 +19,12 @@ public partial class TestItemConveyor : Node, IMovementConveyor
public override void _Notification(int what) => this.Notify(what);
[Dependency] public IBeltMovement MovementSystem => this.DependOn(() => new IndividualMovement());
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn();
+ [Dependency] public IFoodFactoryApi FoodFactoryApi => this.DependOn();
// private readonly AutoList _items = [];
// public IAutoList Items => _items;
// public readonly Sorted1DList Items = new(pos => pos.BeltT);
private readonly Ordered1DList _items = new();
+ public Ordered1DList Items => _items;
// private AutoList.Binding _itemsBinding;
public IBeltPort StartPort { get; set; }
// public IBeltSlotProfile StartPort => new ItemConveyor.ConveyorPort() {
@@ -52,7 +54,7 @@ public partial class TestItemConveyor : Node, IMovementConveyor
public IList OtherPorts = [];
// [Export] public Vector3I Position { get; set; } = default!;
//Speed per unit time
- private readonly AutoValue _speed = new(1);
+ private readonly AutoValue _speed = new(.5f);
public IAutoValue SpeedValue => _speed;
public float SignedSpeed
@@ -82,9 +84,9 @@ public partial class TestItemConveyor : Node, IMovementConveyor
{
return;
}
- var timer = new Timer() { WaitTime = .05f, Autostart = true };
- AddChild(timer);
- timer.Timeout += () => MovementSystem.AdvanceBelt(this, (float)timer.WaitTime);
+ // var timer = new Timer() { WaitTime = .05f, Autostart = true };
+ // AddChild(timer);
+ FoodFactoryApi.TickManger.GameTick += args => MovementSystem.AdvanceBelt(this, (float)args.Delta);
if (StartPort is null || EndPort is null)
{
throw new Exception();
@@ -127,13 +129,15 @@ public partial class TestItemConveyor : Node, IMovementConveyor
}
// 2️⃣ No local item → try crossing into adjacent conveyor
- return GetDistanceAcrossPort(
+ var recursiveDistance = GetDistanceAcrossPort(
towardStart,
itemBeltT,
maxDistToCheck,
laneSpan,
visited
);
+
+ return recursiveDistance;// with { Distance = recursiveDistance.Distance + Length };
}
//ChatGPT Assisted
private IOption FindNextLocalItem(
@@ -412,6 +416,7 @@ public partial class TestItemConveyor : Node, IMovementConveyor
public Ordered1DList.Enumerator EnumerateTowardEnd() => _items.EnumerateTowardEnd(-1);
public Ordered1DList.Enumerator EnumerateTowardStart() => _items.EnumerateTowardStart(_items.Count);
+ public bool HasItems() => _items.Count > 0;
}
//TODO Should liklely account for max search distance where the conveyorm may be needed to know
public record class BeltObstacle(float Distance);
diff --git a/src/Equipment/Balancer.cs b/src/Equipment/Balancer.cs
index 6099a15..62bb93f 100644
--- a/src/Equipment/Balancer.cs
+++ b/src/Equipment/Balancer.cs
@@ -18,7 +18,8 @@ public partial class Balancer : Node3D, IProvide, IProvide _insertLogic;
IVoxelGridRegistry IProvide.Value() => GridRegistry;
- [Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn();
+ public IVoxelGridRegistry GridRegistry => FoodFactory.GridRegistry;
+ [Dependency] public IFoodFactoryApi FoodFactory => this.DependOn();
private List _outPuts = new();
public GridTransform3D VoxelTransform
{
diff --git a/src/Equipment/BeltPort.cs b/src/Equipment/BeltPort.cs
index ef7b8f1..4023b4b 100644
--- a/src/Equipment/BeltPort.cs
+++ b/src/Equipment/BeltPort.cs
@@ -9,7 +9,7 @@ using FoodFactory.Math;
using FoodFactory.Voxel;
using Godot;
-[Tool]
+// [Tool]
[Meta(typeof(IAutoNode))]
public partial class BeltPort : Node3D, IBeltPort
{
diff --git a/src/Equipment/OvenTest.cs b/src/Equipment/OvenTest.cs
index 972aa58..7f3c429 100644
--- a/src/Equipment/OvenTest.cs
+++ b/src/Equipment/OvenTest.cs
@@ -21,6 +21,7 @@ public partial class OvenTest : Node3D, IProvide, IProvide _insertLogic;
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn();
+ [Dependency] public IFoodFactoryApi FoodFactory => this.DependOn();
[Dependency] public IRecipes Recipes => this.DependOn();
IVoxelGridRegistry IProvide.Value() => GridRegistry;
public GridTransform3D VoxelTransform
@@ -43,7 +44,7 @@ public partial class OvenTest : Node3D, IProvide, IProvide, IProvide
+ FoodFactory.TickManger.GameTick += _ =>
{
if (_itemBeingHeld.Id == -1 && !_itemBeingHeld.IsAlive())//TODO have a better way of dertming if item is valid, possibly nullable
{
@@ -77,11 +78,12 @@ public partial class OvenTest : Node3D, IProvide, IProvide true;
public static unsafe bool TryProcess(
ref TRecipeEnumerator recipes,
scoped ref RecipeContext context,
scoped ref RecipeResultBuilder builder,
delegate* destroyEntity,
+ delegate* filter,
out Span resultEntity)
where TRecipeEnumerator : IRecipeEnumerator, allows ref struct
{
@@ -198,6 +202,10 @@ public static class RecipeProcessor
{
continue;
}
+ if (!filter(recipe))
+ {
+ continue;
+ }
var result = recipe.Process(context, ref builder);
diff --git a/src/Equipment/SlicerTest.cs b/src/Equipment/SlicerTest.cs
index 435055c..091cd6e 100644
--- a/src/Equipment/SlicerTest.cs
+++ b/src/Equipment/SlicerTest.cs
@@ -23,6 +23,7 @@ public partial class SlicerTest : Node3D, IProvide, IProvide _insertLogic;
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn();
+ [Dependency] public IFoodFactoryApi FoodFactory => this.DependOn();
[Dependency] public IRecipes Recipes => this.DependOn();
IVoxelGridRegistry IProvide.Value() => GridRegistry;
public GridTransform3D VoxelTransform
@@ -46,7 +47,7 @@ public partial class SlicerTest : Node3D, IProvide, IProvide, IProvide
+ // var time = new Timer() { Autostart = true, OneShot = false, WaitTime = .1 };
+ // AddChild(time);
+ FoodFactory.TickManger.GameTick += _ =>
{
if (sliced.Any())
@@ -83,11 +83,12 @@ public partial class SlicerTest : Node3D, IProvide, IProvide, IProvide
+{
+ public override void _Notification(int what) => this.Notify(what);
+ private BeltPortHost _insertLogic = default!;
+ public IBeltPortHost Value() => _insertLogic;
+ [Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn();
+ [Dependency] public IFoodFactoryApi FoodFactory => this.DependOn();
+ [Dependency] public IRecipes Recipes => this.DependOn();
+ IVoxelGridRegistry IProvide.Value() => GridRegistry;
+ public GridTransform3D VoxelTransform
+ {
+ get => GridTransform3D.FromGodot(GlobalTransform);
+ set => GlobalTransform = value.ToGodot();
+ }
+ private Entity _output = Entity.Null;
+ private List _inputs = [];
+ private VoxelGuid _guid;
+
+ public void OnResolved()
+ {
+ _insertLogic = new BeltPortHost();
+ _insertLogic.Bind(port => port is BeltPort beltPort && beltPort.PortName == "Input", () => new DelegateInsertBeltItemLogic(canAccept, canInsert));
+ _insertLogic.Bind(port => port is BeltPort beltPort && beltPort.PortName == "OutPut", () => new DelegateInsertBeltItemLogic((_, _) => false, (_, _) => false));
+ bool canAccept(IBeltPort port, IBeltItem item) => _inputs.Count < 2;
+ bool canInsert(IBeltPort beltPort, IBeltItem item)
+ {
+ if (item is IBeltItemData itemData)
+ {
+ _inputs.Add(itemData.GetItem());
+ item.Dispose();
+ // GD.Print("Added Item to Stacker");
+ return true;
+ }
+ throw new NotImplementedException();
+ }
+ _guid = GridRegistry.Register(this, VoxelTransform.Origin);
+ this.Provide();
+
+ // var time = new Timer() { Autostart = true, OneShot = false, WaitTime = .1 };
+ // AddChild(time);
+
+ FoodFactory.TickManger.GameTick += _ =>
+ {
+
+ if (_output.Id != -1)
+ {
+ var port = _insertLogic.GetPorts().FirstOrNone(port => port is BeltPort beltPort && beltPort.PortName == "OutPut").Bind(f => f.GetPortFacing(GridRegistry));
+ if (port.HasValue(out var v) && v.TryInsert(new TestItem() { Item = _output }, LaneSpan.One, 0))
+ {
+ _output = Entity.Null;
+ }
+ }
+ if (_inputs.Count < 2)//TODO have a better way of dertming if item is valid, possibly nullable
+ {
+ return;
+ }
+
+ Span items = [_inputs[0], _inputs[1]];
+ var context = new RecipeContext(World.Worlds[0], items);
+ var builder = new RecipeResultBuilder(stackalloc bool[10], new ItemBuilder[10]);
+ Span mapping = stackalloc int[2];
+ // var recipe = new PotatoCookRecipe();
+ var recipes = Recipes.GetRecipes("stack", 2, [_inputs[0].Get(), _inputs[1].Get()], [], new RecipeOutput(1), mapping);
+ unsafe
+ {
+ while (RecipeProcessor.TryProcess(
+ recipes: ref recipes,
+ context: ref context,
+ builder: ref builder,
+ destroyEntity: &DestroyEntity,
+ filter: &RecipeProcessor.FilterByPass,
+ resultEntity: out var created
+ ))
+ {
+ _output = created[0];
+ ref var relation = ref _output.GetRelationships();
+ var count = relation.Count;
+ GD.Print(string.Join(',', relation._elements));
+ foreach (var item in relation)
+ {
+ GD.Print(string.Join(',', item.Key.GetAllComponents()));
+ }
+ _inputs.Clear();
+ break;
+ }
+ }
+
+ };
+ }
+ private static void DestroyEntity(Entity entity) => World.Worlds[entity.WorldId].Destroy(entity);
+
+
+ public override void _ExitTree()
+ {
+ GridRegistry.UnRegister(_guid);
+ base._ExitTree();
+ }
+}
diff --git a/src/Equipment/StackerTest.cs.uid b/src/Equipment/StackerTest.cs.uid
new file mode 100644
index 0000000..670562f
--- /dev/null
+++ b/src/Equipment/StackerTest.cs.uid
@@ -0,0 +1 @@
+uid://culjdbwllmsyk
diff --git a/src/Items/Components/Tags.cs b/src/Items/Components/Tags.cs
index 753d84b..481707f 100644
--- a/src/Items/Components/Tags.cs
+++ b/src/Items/Components/Tags.cs
@@ -8,15 +8,15 @@ using System.Linq;
public readonly struct Tags : IEquatable, IReadOnlyCollection
{
- private readonly Tag[] _values;
+ private readonly int _index;
public Tags()
{
- _values = [];
+ _index = 0;
}
///
/// Will error if there is not tags in contained.
///
- public readonly Tag First => _values[0];
+ public readonly Tag First => _tags[_index][0];
public Tags(params string[] tags)
{
var arr = new Tag[tags.Length];
@@ -25,27 +25,29 @@ public readonly struct Tags : IEquatable, IReadOnlyCollection
arr[i] = TagRegistry.GetTag(tags[i]);
}
- _values = SortAndDeduplicate(arr);
+ _index = SortAndDeduplicate(arr);
}
- public override string ToString() => $"Tags: [{string.Join(", ", _values.Select(t => t.Name))}]";
- private static readonly Dictionary _internedTags = [];
+ public override string ToString() => $"Tags: [{string.Join(", ", _tags[_index].Select(t => t.Name))}]";
+ private static readonly Dictionary _internedTags = new(){[new TagArrayKey([])] = 0};
+ private static readonly List _tags = [[]];
- public readonly int Count => _values.Length;
+ public readonly int Count => _tags[_index].Length;
- private static Tag[] SortAndDeduplicate(Tag[] tags)
+ private static int SortAndDeduplicate(Tag[] tags)
{
Array.Sort(tags);
var key = new TagArrayKey(tags);
if (!_internedTags.TryGetValue(key, out var interned))
{
- _internedTags[key] = interned = tags;
+ _tags.Add(tags);
+ _internedTags[key] = _tags.Count - 1;
}
return interned;
}
public Tags(params Tag[] tags)
{
- _values = SortAndDeduplicate(tags);
+ _index = SortAndDeduplicate(tags);
}
public Tags(ISet tags) : this(tags.ToArray())
{
@@ -53,30 +55,30 @@ public readonly struct Tags : IEquatable, IReadOnlyCollection
}
public Tags With(params Tag[] tags)
{
- HashSet set = [.. _values, .. tags];
+ HashSet set = [.. _tags[_index], .. tags];
return new Tags(set);
}
public Tags With(params string[] tags)
{
- HashSet