Added Arch Persitance and Relationships, styackable items, hot fixed Tags.containsAll. AutoList in Ordered!DList

This commit is contained in:
2026-05-16 12:44:51 -04:00
parent bf4d856608
commit 75c93f0c43
40 changed files with 52609 additions and 197 deletions

View File

@@ -37,10 +37,10 @@
<ItemGroup> <ItemGroup>
<!-- Production dependencies go here! --> <!-- Production dependencies go here! -->
<PackageReference Include="Arch" Version="2.1.0" /> <PackageReference Include="Arch" Version="2.1.0" />
<PackageReference Include="Arch-Events" Version="2.1.0" />
<PackageReference Include="Arch.LowLevel" Version="1.1.5" /> <PackageReference Include="Arch.LowLevel" Version="1.1.5" />
<PackageReference Include="Arch.System" Version="1.1.0" /> <PackageReference Include="Arch.System" Version="1.1.0" />
<PackageReference Include="Chickensoft.GameTools" Version="3.1.18" /> <PackageReference Include="Chickensoft.GameTools" Version="3.1.18" />
<PackageReference Include="MessagePack" Version="3.1.4" />
<PackageReference Include="NCalc.LambdaCompilation" Version="5.12.0" /> <PackageReference Include="NCalc.LambdaCompilation" Version="5.12.0" />
<PackageReference Include="NCalcSync" Version="5.12.0" /> <PackageReference Include="NCalcSync" Version="5.12.0" />
<PackageReference Include="SjkScripts" Version="1.0.17" /> <PackageReference Include="SjkScripts" Version="1.0.17" />
@@ -59,8 +59,11 @@
<PackageReference Include="Chickensoft.LogicBlocks.DiagramGenerator" Version="5.20.0" PrivateAssets="all" OutputItemType="analyzer" /> <PackageReference Include="Chickensoft.LogicBlocks.DiagramGenerator" Version="5.20.0" PrivateAssets="all" OutputItemType="analyzer" />
<PackageReference Include="Chickensoft.UMLGenerator" Version="1.1.0" /> <PackageReference Include="Chickensoft.UMLGenerator" Version="1.1.0" />
<PackageReference Include="Chickensoft.Sync" Version="2.2.0" /> <PackageReference Include="Chickensoft.Sync" Version="2.2.0" />
<PackageReference Include="Utf8Json" Version="1.3.7" />
</ItemGroup>
<ItemGroup>
<Compile Remove="/mods" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(RunTests)' == 'true'"> <ItemGroup Condition="'$(RunTests)' == 'true'">
<!-- Test dependencies go here! --> <!-- Test dependencies go here! -->
<!-- Dependencies added here will not be included in release builds. --> <!-- Dependencies added here will not be included in release builds. -->

8
GlobalSuppressions.cs Normal file
View File

@@ -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 = "<Pending>")]

49
export_presets.cfg Normal file
View File

@@ -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

0
mods/.gdignore Normal file
View File

View File

@@ -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<T>() 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<Type, object> _otherApi = [];
public T? GetApi<T>() where T : class
{
if (_otherApi.TryGetValue(typeof(T), out var api))
{
return api as T;
}
return null;
}
}

View File

@@ -0,0 +1 @@
uid://dbmq2c5nn8r1q

View File

@@ -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;
/// <summary>
/// The <see cref="SingleEntityFormatter"/> class
/// is a <see cref="IJsonFormatter"/> to (de)serialize a single <see cref="Entity"/>to or from json.
/// </summary>
public partial class SingleEntityFormatter : IMessagePackFormatter<Entity>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
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);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
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<ComponentType>(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;
}
}
/// <summary>
/// The <see cref="EntityFormatter"/> class
/// is a formatter that (de)serializes <see cref="Entity"/> structs.
/// </summary>
public partial class EntityFormatter : IMessagePackFormatter<Entity>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, Entity value, MessagePackSerializerOptions options)
{
writer.WriteInt32(value.Id);
writer.WriteInt32(value.Version);
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public Entity Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
// Read id
var id = reader.ReadInt32();
var version = reader.ReadInt32();
return DangerousEntityExtensions.CreateEntityStruct(id, WorldId, version);
}
}
/// <summary>
/// The <see cref="ArrayFormatter"/> class
/// is a <see cref="IJsonFormatter{Array}"/> to (de)serialize <see cref="Array"/>s to or from json.
/// </summary>
public partial class ArrayFormatter : IMessagePackFormatter<Array>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
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);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public Array Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
// Write type and size
var type = MessagePackSerializer.Deserialize<Type>(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;
}
}
/// <summary>
/// The <see cref="JaggedArrayFormatter{T}"/> class
/// (de)serializes a <see cref="JaggedArray{T}"/>.
/// </summary>
/// <typeparam name="T">The type stored in the <see cref="JaggedArray{T}"/>.</typeparam>
public partial class JaggedArrayFormatter<T> : IMessagePackFormatter<JaggedArray<T>>
{
private const int CpuL1CacheSize = 16_384;
private readonly T _filler;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="filler">Filler.</param>
public JaggedArrayFormatter(T filler)
{
_filler = filler;
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, JaggedArray<T> 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);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public JaggedArray<T> Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var capacity = reader.ReadInt32();
var jaggedArray = new JaggedArray<T>(CpuL1CacheSize / Unsafe.SizeOf<T>(), _filler, capacity);
for (var index = 0; index < capacity; index++)
{
var item = MessagePackSerializer.Deserialize<T>(ref reader, options);
jaggedArray.Add(index, item);
}
return jaggedArray;
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="ComponentType"/>s to or from json.
/// </summary>
public partial class ComponentTypeFormatter : IMessagePackFormatter<ComponentType>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, ComponentType value, MessagePackSerializerOptions options)
{
// Write id
writer.WriteUInt32((uint)value.Id);
// Write bytesize
writer.WriteUInt32((uint)value.ByteSize);
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public ComponentType Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var id = reader.ReadUInt32();
var bytesize = reader.ReadUInt32();
return new ComponentType((int)id, (int)bytesize);
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="Signature"/>s to or from json.
/// </summary>
public partial class SignatureFormatter : IMessagePackFormatter<Signature>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, Signature value, MessagePackSerializerOptions options)
{
var componentTypeFormatter = options.Resolver.GetFormatter<ComponentType>() as ComponentTypeFormatter;
// Write count and types
writer.WriteUInt32((uint)value.Count);
foreach (var type in value.Components)
{
componentTypeFormatter!.Serialize(ref writer, type, options);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public Signature Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var componentTypeFormatter = options.Resolver.GetFormatter<ComponentType>() 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);
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="ComponentType"/>s to or from json.
/// </summary>
public partial class EntitySlotFormatter : IMessagePackFormatter<EntityData>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
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);
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
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);
}
}
/// <summary>
/// The <see cref="WorldFormatter"/> class
/// is a <see cref="IJsonFormatter{World}"/> to (de)serialize <see cref="World"/>s to or from json.
/// </summary>
public partial class WorldFormatter : IMessagePackFormatter<World>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
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);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
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<Archetype>() as ArchetypeFormatter;
var entityFormatter = options.Resolver.GetFormatter<Entity>() as EntityFormatter;
entityFormatter!.WorldId = world.Id;
archetypeFormatter!.World = world;
// Read slots
var slots = MessagePackSerializer.Deserialize<JaggedArray<EntityData>>(ref reader, options);
//Read recycled entity ids
var recycledEntityIDs = MessagePackSerializer.Deserialize<List<(int, int)>>(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<Archetype> 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;
}
}
/// <summary>
/// The <see cref="ArchetypeFormatter"/> class
/// is a <see cref="IJsonFormatter{Archetype}"/> to (de)serialize <see cref="Archetype"/>s to or from json.
/// </summary>
public partial class ArchetypeFormatter : IMessagePackFormatter<Archetype>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
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<Chunk>() 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);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public Archetype Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var chunkFormatter = options.Resolver.GetFormatter<Chunk>() as ChunkFormatter;
// Types
var types = MessagePackSerializer.Deserialize<Signature>(ref reader, options);
// Archetype lookup array
var lookupArray = MessagePackSerializer.Deserialize<int[]>(ref reader, options);
// Archetype chunk size and list
var chunkSize = reader.ReadUInt32();
// Create archetype
var chunks = new List<Chunk>((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;
}
}
/// <summary>
/// The <see cref="ChunkFormatter"/> class
/// is a <see cref="IJsonFormatter{Chunk}"/> to (de)serialize <see cref="Chunk"/>s to or from json.
/// </summary>
public partial class ChunkFormatter : IMessagePackFormatter<Chunk>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
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);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
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<Entity[]>(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<Array>(ref reader, options);
var chunkArray = chunk.GetArray(array.GetType().GetElementType()!);
Array.Copy(array, chunkArray, (int)size);
}
return chunk;
}
}

View File

@@ -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;
/// <summary>
/// The <see cref="SingleEntityFormatter"/> class
/// is a <see cref="IJsonFormatter{Entity}"/> to (de)serialize a single <see cref="Entity"/>to or from json.
/// </summary>
public partial class SingleEntityFormatter : IJsonFormatter<Entity>
{
/// <summary>
/// The <see cref="EntityWorld"/> the entity belongs to.
/// </summary>
internal World EntityWorld { get; set; } = null!;
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
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();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
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<ComponentType>(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<Entity>
{
/// <summary>
/// The <see cref="World.Id"/> all deserialized <see cref="Entity"/>s will belong to.
/// <remarks>Due to the nature of deserialisation and changing world landscape we need to assign new WorldIds to the deserialized entities.</remarks>
/// </summary>
internal int WorldId { get; set; }
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, Entity value, IJsonFormatterResolver formatterResolver)
{
writer.WriteInt32(value.Id);
writer.WriteValueSeparator();
writer.WriteInt32(value.Version);
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
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);
}
}
/// <summary>
/// The <see cref="ArrayFormatter"/> class
/// is a <see cref="IJsonFormatter{Array}"/> to (de)serialize <see cref="Array"/>s to or from json.
/// </summary>
public partial class ArrayFormatter : IJsonFormatter<Array>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
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();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public Array Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
// Write type and size
reader.ReadIsBeginObject();
reader.ReadPropertyName();
var type = JsonSerializer.Deserialize<Type>(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;
}
}
/// <summary>
/// The <see cref="JaggedArrayFormatter{T}"/> class
/// (de)serializes a <see cref="JaggedArray{T}"/>.
/// </summary>
public partial class JaggedArrayFormatter<T> : IJsonFormatter<JaggedArray<T>>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, JaggedArray<T> 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();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public JaggedArray<T> Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
reader.ReadIsBeginObject();
// Read capacity;
reader.ReadPropertyName();
var capacity = reader.ReadInt32();
reader.ReadIsValueSeparator();
// Read items
var jaggedArray = new JaggedArray<T>(CpuL1CacheSize / Unsafe.SizeOf<T>(), _filler, capacity);
reader.ReadPropertyName();
reader.ReadIsBeginArray();
for (var index = 0; index < capacity; index++)
{
var item = JsonSerializer.Deserialize<T>(ref reader, formatterResolver);
jaggedArray.Add(index, item);
reader.ReadIsValueSeparator();
}
reader.ReadIsEndArray();
reader.ReadIsEndObject();
return jaggedArray;
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="ComponentType"/>s to or from json.
/// </summary>
public partial class ComponentTypeFormatter : IJsonFormatter<ComponentType>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
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();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
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);
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="Signature"/>s to or from json.
/// </summary>
public partial class SignatureFormatter : IJsonFormatter<Signature>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, Signature value, IJsonFormatterResolver formatterResolver)
{
var componentTypeFormatter = formatterResolver.GetFormatter<ComponentType>() 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();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public Signature Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
var componentTypeFormatter = formatterResolver.GetFormatter<ComponentType>() 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);
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="ComponentType"/>s to or from json.
/// </summary>
public partial class EntitySlotFormatter : IJsonFormatter<EntityData>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
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();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
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);
}
}
/// <summary>
/// The <see cref="WorldFormatter"/> class
/// is a <see cref="IJsonFormatter{World}"/> to (de)serialize <see cref="World"/>s to or from json.
/// </summary>
public partial class WorldFormatter : IJsonFormatter<World>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, World value, IJsonFormatterResolver formatterResolver)
{
//var archetypeFormatter = formatterResolver.GetFormatter<Archetype>();
//var versionsFormatter = formatterResolver.GetFormatter<int[][]>();
//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();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public World Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
// Create world and setup formatter
var archetypeFormatter = formatterResolver.GetFormatter<Archetype>() as ArchetypeFormatter;
var entityFormatter = formatterResolver.GetFormatter<Entity>() 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<JaggedArray<EntityData>>(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<Archetype> 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;
}
}
/// <summary>
/// The <see cref="ArchetypeFormatter"/> class
/// is a <see cref="IJsonFormatter{Archetype}"/> to (de)serialize <see cref="Archetype"/>s to or from json.
/// </summary>
public partial class ArchetypeFormatter : IJsonFormatter<Archetype>
{
/// <summary>
/// The <see cref="World"/> which is being used by this formatter during serialisation/deserialisation.
/// </summary>
internal World World { get; set; } = null!;
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, Archetype value, IJsonFormatterResolver formatterResolver)
{
// Setup formatters
var types = value.Signature;
var chunks = value.Chunks;
var chunkFormatter = formatterResolver.GetFormatter<Chunk>() 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();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public Archetype Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
var chunkFormatter = formatterResolver.GetFormatter<Chunk>() as ChunkFormatter;
reader.ReadIsBeginObject();
// Types
reader.ReadPropertyName();
var types = JsonSerializer.Deserialize<Signature>(ref reader, formatterResolver);
reader.ReadIsValueSeparator();
// Archetype lookup array
reader.ReadPropertyName();
var lookupArray = JsonSerializer.Deserialize<int[]>(ref reader, formatterResolver);
reader.ReadIsValueSeparator();
// Archetype chunk size and list
reader.ReadPropertyName();
var chunkCount = reader.ReadUInt32();
reader.ReadIsValueSeparator();
// Create archetype
var chunks = new List<Chunk>((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;
}
}
/// <summary>
/// The <see cref="ChunkFormatter"/> class
/// is a <see cref="IJsonFormatter{Chunk}"/> to (de)serialize <see cref="Chunk"/>s to or from json.
/// </summary>
public partial class ChunkFormatter : IJsonFormatter<Chunk>
{
/// <summary>
/// The <see cref="Archetype"/> the current (de)serialized <see cref="Chunk"/> belongs to.
/// Since chunks do not know this, we need to pass this information along it.
/// </summary>
internal World World { get; set; } = null!;
/// <summary>
/// The <see cref="Archetype"/> the current (de)serialized <see cref="Chunk"/> belongs to.
/// Since chunks do not know this, we need to pass this information along it.
/// </summary>
internal Archetype Archetype { get; set; } = null!;
/// <summary>
/// The types used in the <see cref="Chunk"/> in each <see cref="Chunk"/> (de)serialized by this formatter.
/// <remarks>Since <see cref="Chunk"/> does not have a reference to them and its controlled by its <see cref="Archetype"/>.</remarks>
/// </summary>
internal Signature Signature { get; set; } = Signature.Null;
/// <summary>
/// The lookup array used by each <see cref="Chunk"/> (de)serialized by this formatter.
/// <remarks>Since <see cref="Chunk"/> does not have a reference to them and its controlled by its <see cref="Archetype"/>.</remarks>
/// </summary>
internal int[] LookupArray { get; set; } = Array.Empty<int>();
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
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();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
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<Entity[]>(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<Array>(ref reader, formatterResolver);
var chunkArray = chunk.GetArray(array.GetType().GetElementType()!);
Array.Copy(array, chunkArray, (int)size);
reader.ReadIsValueSeparator();
}
reader.ReadIsEndArray();
reader.ReadIsEndObject();
return chunk;
}
}

View File

@@ -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;
/// <summary>
/// The <see cref="IArchSerializer"/> interface
/// represents an interface with shared methods to (de)serialize worlds and entities.
/// <remarks>It might happen that the serialized object is too large to fit into a regular c# byte-array. In this case use the <see cref="IBufferWriter{T}"/>-API.</remarks>
/// </summary>
public interface IArchSerializer
{
/// <summary>
/// Serializes an <see cref="Entity"/> to a <see cref="byte"/>-array.
/// </summary>
/// <param name="world">The <see cref="World"/>.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
byte[] Serialize(World world, Entity entity);
/// <summary>
/// Serializes an <see cref="Entity"/> to a <see cref="Stream"/> e.g. a File or existing array.
/// </summary>
/// <param name="stream">The <see cref="Stream"/>.</param>
/// <param name="world">The <see cref="World"/>.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
void Serialize(Stream stream, World world, Entity entity);
/// <summary>
/// Serializes an <see cref="Entity"/> to a <see cref="IBufferWriter{T}"/> e.g. a File or existing array.
/// </summary>
/// <param name="writer">The <see cref="IBufferWriter{T}"/>.</param>
/// <param name="world">The <see cref="World"/>.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
void Serialize(IBufferWriter<byte> writer, World world, Entity entity);
/// <summary>
/// Deserializes an <see cref="Entity"/> from its bytes to an real <see cref="Entity"/> in a <see cref="World"/>.
/// <remarks>The new <see cref="Entity.Id"/> and <see cref="Entity.WorldId"/> will differ.</remarks>
/// </summary>
/// <param name="world">The <see cref="World"/>.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <returns></returns>
Entity Deserialize(World world, byte[] entity);
/// <summary>
/// Deserializes an <see cref="Entity"/> from its bytes to an real <see cref="Entity"/> in a <see cref="World"/>.
/// <remarks>The new <see cref="Entity.Id"/> and <see cref="Entity.WorldId"/> will differ.</remarks>
/// </summary>
/// <param name="stream">The <see cref="Stream"/>.</param>
/// <param name="world">The <see cref="World"/>.</param>
/// <returns></returns>
Entity Deserialize(Stream stream, World world);
/// <summary>
/// Serializes a <see cref="World"/> to a <see cref="byte"/>-array.
/// </summary>
/// <param name="world">The <see cref="World"/>.</param>
byte[] Serialize(World world);
/// <summary>
/// Serializes a <see cref="World"/> to a <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/>.</param>
/// <param name="world">The <see cref="World"/>.</param>
void Serialize(Stream stream, World world);
/// <summary>
/// Serializes a <see cref="World"/> to a <see cref="IBufferWriter{T}"/>.
/// </summary>
/// <param name="writer">The <see cref="IBufferWriter{T}"/>.</param>
/// <param name="world">The <see cref="World"/>.</param>
void Serialize(IBufferWriter<byte> writer, World world);
/// <summary>
/// Deserializes a byte-array into a <see cref="World"/>.
/// </summary>
/// <param name="world">The <see cref="World"/> as an byte-array.</param>
/// <returns>The new <see cref="World"/>.</returns>
World Deserialize(byte[] world);
/// <summary>
/// Deserializes a byte-array into a <see cref="World"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/>.</param>
/// <returns>The new <see cref="World"/>.</returns>
World Deserialize(Stream stream);
}
/// <summary>
/// The <see cref="ArchBinarySerializer"/> class
/// represents a binary serializer for arch to (de)serialize single entities and whole worlds by binary.
/// </summary>
public class ArchBinarySerializer : IArchSerializer
{
/// <summary>
/// The default formatters used to (de)serialize the <see cref="World"/>.
/// </summary>
private readonly IMessagePackFormatter[] _formatters =
[
new WorldFormatter(),
new ArchetypeFormatter(),
new ChunkFormatter(),
new ArrayFormatter(),
new ComponentTypeFormatter(),
new SignatureFormatter(),
new EntitySlotFormatter(),
new EntityFormatter(),
new JaggedArrayFormatter<int>(-1),
new JaggedArrayFormatter<(int,int)>((-1,-1)),
new JaggedArrayFormatter<EntityData>(new EntityData(null!, new Slot(-1,-1), -1))
];
/// <summary>
/// The default formatters used to (de)serialize a single <see cref="Entity"/>.
/// </summary>
private readonly IMessagePackFormatter[] _singleEntityFormatters =
[
new ComponentTypeFormatter(),
new SignatureFormatter(),
new SingleEntityFormatter()
];
/// <summary>
/// The standard <see cref="MessagePackSerializerOptions"/> for world (de)serialization.
/// </summary>
private readonly MessagePackSerializerOptions _options;
/// <summary>
/// The standard <see cref="MessagePackSerializerOptions"/> for single entity (de)serialization.
/// </summary>
private readonly MessagePackSerializerOptions _singleEntityOptions;
/// <summary>
/// The static constructor gets called during compile time to setup the serializer.
/// </summary>
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
]
)
);
}
/// <inheritdoc/>
public byte[] Serialize(World world, Entity entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return MessagePackSerializer.Serialize(entity, _singleEntityOptions);
}
/// <inheritdoc/>
public void Serialize(Stream stream, World world, Entity entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
MessagePackSerializer.Serialize(stream, entity, _singleEntityOptions);
}
/// <inheritdoc/>
public void Serialize(IBufferWriter<byte> writer, World world, Entity entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
MessagePackSerializer.Serialize(writer, entity, _singleEntityOptions);
}
/// <inheritdoc/>
public Entity Deserialize(World world, byte[] entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return MessagePackSerializer.Deserialize<Entity>(entity, _singleEntityOptions);
}
/// <inheritdoc/>
public Entity Deserialize(Stream stream, World world)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return MessagePackSerializer.Deserialize<Entity>(stream, _singleEntityOptions);
}
/// <inheritdoc/>
public byte[] Serialize(World world)
{
return MessagePackSerializer.Serialize(world, _options);
;
}
/// <inheritdoc/>
public void Serialize(Stream stream, World world) => MessagePackSerializer.Serialize(stream, world, _options);
/// <inheritdoc/>
public void Serialize(IBufferWriter<byte> writer, World world) => MessagePackSerializer.Serialize(writer, world, _options);
/// <inheritdoc/>
public World Deserialize(byte[] world) => MessagePackSerializer.Deserialize<World>(world, _options);
/// <inheritdoc/>
public World Deserialize(Stream stream) => MessagePackSerializer.Deserialize<World>(stream, _options);
}
/// <summary>
/// The <see cref="ArchJsonSerializer"/> class
/// represents a json serializer for arch to (de)serialize single entities and whole worlds by binary.
/// </summary>
public class ArchJsonSerializer : IArchSerializer
{
/// <summary>
/// The default formatters used to (de)serialize the <see cref="World"/>.
/// </summary>
private readonly IJsonFormatter[] _formatters = [
new WorldFormatter(),
new ArchetypeFormatter(),
new ChunkFormatter(),
new ArrayFormatter(),
new ComponentTypeFormatter(),
new SignatureFormatter(),
new EntitySlotFormatter(),
new EntityFormatter(),
new JaggedArrayFormatter<int>(-1),
new JaggedArrayFormatter<(int,int)>((-1,-1)),
new JaggedArrayFormatter<EntityData>(new EntityData(null!, new Slot(-1, -1), -1)),
new DateTimeFormatter("yyyy-MM-dd HH:mm:ss"),
new NullableDateTimeFormatter("yyyy-MM-dd HH:mm:ss")
];
/// <summary>
/// The default formatters used to (de)serialize a single <see cref="Entity"/>.
/// </summary>
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;
/// <summary>
/// The static constructor gets called during compile time to setup the serializer.
/// </summary>
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,
]
);
}
/// <summary>
/// The static constructor gets called during compile time to setup the serializer.
/// This variant allows custom resolvers to be passed in as well.
/// </summary>
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,
},
]);
}
/// <summary>
/// Serializes the given <see cref="World"/> to a json-string.
/// </summary>
/// <param name="world">The <see cref="World"/> to serialize.</param>
/// <returns>Its json-string.</returns>
public string ToJson(World world) => JsonSerializer.ToJsonString(world, _formatterResolver);
/// <summary>
/// Serializes the given <see cref="Entity"/> to a json-string.
/// </summary>
/// <param name="world">The <see cref="World"/> the entity belongs to..</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <returns>Its json-string.</returns>
/// <returns>A json-string of the entity with all its components.</returns>
public string ToJson(World world, Entity entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return JsonSerializer.ToJsonString(entity, _singleEntityFormatterResolver);
}
/// <summary>
/// Deserializes the given json <see cref="string"/> to a <see cref="World"/>.
/// </summary>
/// <param name="jsonWorld">The json <see cref="string"/> to deserialize.</param>
/// <returns>A new <see cref="World"/>.</returns>
public World FromJson(string jsonWorld) => JsonSerializer.Deserialize<World>(jsonWorld, _formatterResolver);
/// <summary>
/// Deserializes the given json <see cref="string"/> to a <see cref="World"/>.
/// <remarks>The deserialized <see cref="Entity"/> will receive a new id and a new worldId.</remarks>
/// </summary>
/// <param name="world">The <see cref="World"/> to deserialize the entity into.</param>
/// <param name="jsonEntity">The json <see cref="string"/> of the entity to deserialize.</param>
/// <returns>A new <see cref="Entity"/>.</returns>
public Entity FromJson(World world, string jsonEntity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return JsonSerializer.Deserialize<Entity>(jsonEntity, _singleEntityFormatterResolver);
}
/// <inheritdoc/>
public byte[] Serialize(World world, Entity entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return JsonSerializer.Serialize(entity, _singleEntityFormatterResolver);
}
/// <inheritdoc/>
public void Serialize(Stream stream, World world, Entity entity)
{
(_singleEntityFormatters[1] as SingleEntityFormatter)!.EntityWorld = world;
JsonSerializer.Serialize(stream, entity, _singleEntityFormatterResolver);
}
/// <inheritdoc/>
public void Serialize(IBufferWriter<byte> writer, World world, Entity entity) => throw new NotImplementedException();
/// <inheritdoc/>
public Entity Deserialize(World world, byte[] entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return JsonSerializer.Deserialize<Entity>(entity, _singleEntityFormatterResolver);
}
/// <inheritdoc/>
public Entity Deserialize(Stream stream, World world)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return JsonSerializer.Deserialize<Entity>(stream, _singleEntityFormatterResolver);
}
/// <inheritdoc/>
public byte[] Serialize(World world) => JsonSerializer.Serialize(world, _formatterResolver);
/// <inheritdoc/>
public void Serialize(Stream stream, World world) => JsonSerializer.Serialize(stream, world, _formatterResolver);
/// <inheritdoc/>
public void Serialize(IBufferWriter<byte> writer, World world) => throw new NotImplementedException();
/// <inheritdoc/>
public World Deserialize(byte[] world) => JsonSerializer.Deserialize<World>(world, _formatterResolver);
/// <inheritdoc/>
public World Deserialize(Stream stream) => JsonSerializer.Deserialize<World>(stream, _formatterResolver);
}

View File

@@ -0,0 +1,139 @@
using System;
using System.Buffers;
using System.IO;
namespace Arch.Persistence;
/// <summary>
/// The <see cref="StreamBufferWriter"/> class
/// is a small wrapper around a <see cref="Stream"/> implementing a <see cref="IBufferWriter{T}"/>.
/// It buffers incoming bytes in an internally stored array and flushes it regulary into the <see cref="_destination"/>-<see cref="Stream"/>.
/// </summary>
public sealed class StreamBufferWriter : IBufferWriter<byte>, IDisposable
{
/// <summary>
/// The buffer.
/// </summary>
private byte[] _buffer;
/// <summary>
/// The <see cref="Stream"/>.
/// </summary>
private readonly Stream _destination;
/// <summary>
// / If this instance owns the <see cref="_destination"/> stream.
/// </summary>
private readonly bool _ownsStream;
/// <summary>
/// The current position and the amount of total leased bytes.
/// </summary>
private int _position, _leased;
/// <summary>
/// Creates a new <see cref="StreamBufferWriter"/> instance.
/// </summary>
/// <param name="destination">The <see cref="Stream"/>.</param>
/// <param name="bufferSize">The buffer-size of the <see cref="_buffer"/>.</param>
/// <param name="ownsStream">If it owns the stream.</param>
public StreamBufferWriter(Stream destination, int bufferSize = 1024, bool ownsStream = true)
{
const int minBufferSize = 128;
if (bufferSize < minBufferSize)
{
bufferSize = minBufferSize;
}
_buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
_ownsStream = ownsStream;
_destination = destination;
}
/// <summary>
/// Leases an amount of bytes from the <see cref="_buffer"/>.
/// </summary>
/// <param name="sizeHint">The total amount.</param>
/// <returns>The leased amount.</returns>
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;
}
/// <summary>
/// Flushes the buffered bytes to the <see cref="_destination"/>.
/// </summary>
/// <param name="flushUnderlyingStream">If it also should flush the <see cref="Stream"/>.</param>
public void Flush(bool flushUnderlyingStream = false)
{
if (_position != 0)
{
_destination.Write(_buffer, 0, _position);
_position = 0;
}
if (flushUnderlyingStream)
{
_destination.Flush();
}
}
/// <summary>
/// Advances the buffer, notifies this instance that there was something new written into the <see cref="_buffer"/> memory.
/// </summary>
/// <param name="count">The amount of bytes written.</param>
/// <exception cref="ArgumentOutOfRangeException">Throws if we are out of memory.</exception>
void IBufferWriter<byte>.Advance(int count)
{
if (count > _leased || count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
_position += count;
_leased = 0;
}
/// <summary>
/// Returns a partion of the <see cref="_buffer"/> as a <see cref="Memory{T}"/>.
/// </summary>
/// <param name="sizeHint">The total amount.</param>
/// <returns>The new <see cref="Memory{T}"/> instance.</returns>
Memory<byte> IBufferWriter<byte>.GetMemory(int sizeHint)
{
var actual = Lease(sizeHint);
return new Memory<byte>(_buffer, _position, actual);
}
/// <summary>
/// Returns a partion of the <see cref="_buffer"/> as a <see cref="Span{T}"/>.
/// </summary>
/// <param name="sizeHint">The total amount.</param>
/// <returns>The new <see cref="Span{T}"/> instance.</returns>
Span<byte> IBufferWriter<byte>.GetSpan(int sizeHint)
{
var actual = Lease(sizeHint);
return new Span<byte>(_buffer, _position, actual);
}
/// <summary>
/// Disposes this instance, flushes and releases all memory.
/// </summary>
public void Dispose()
{
Flush(true);
var tmp = _buffer;
_buffer = null!;
ArrayPool<byte>.Shared.Return(tmp);
if (_ownsStream)
{
_destination.Dispose();
}
}
}

View File

@@ -0,0 +1,125 @@
namespace Arch.Relationships;
using global::System.Diagnostics.Contracts;
using global::System.Runtime.CompilerServices;
using Arch.Core;
#if !PURE_ECS
/// <summary>
/// The <see cref="EntityRelationshipExtensions"/> class
/// stores several methods to forward relationship methods from the <see cref="World"/> to the <see cref="Entity"/>.
/// </summary>
public static class EntityRelationshipExtensions
{
/// <summary>
/// Adds a new relationship to the <see cref="Entity"/>.
/// </summary>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
// / <typeparam name="T">The relationship type.</typeparam>
/// <param name="relationship">The relationship instance.</param>
public static void AddRelationship<T>(this in Entity source, Entity target, T relationship = default!)
{
var world = World.Worlds[source.WorldId];
world.AddRelationship(source, target, relationship);
}
/// <summary>
/// Sets a relationship to the <see cref="Entity"/> by updating its relationship data.
/// </summary>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="relationship">The relationship instance.</param>
public static void SetRelationship<T>(this in Entity source, Entity target, T relationship = default!)
{
var world = World.Worlds[source.WorldId];
world.SetRelationship(source, target, relationship);
}
/// <summary>
/// Checks if an <see cref="Entity"/> has a certain relationship.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <returns>True if it has the desired relationship, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool HasRelationship<T>(this in Entity source, Entity target)
{
var world = World.Worlds[source.WorldId];
return world.HasRelationship<T>(source, target);
}
/// <summary>
/// Checks if an <see cref="Entity"/> has a certain relationship.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <returns>True if it has the desired relationship, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool HasRelationship<T>(this in Entity source)
{
var world = World.Worlds[source.WorldId];
return world.HasRelationship<T>(source);
}
/// <summary>
/// Returns a relationship of an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <returns>The relationship.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static T GetRelationship<T>(this in Entity source, Entity target)
{
var world = World.Worlds[source.WorldId];
return world.GetRelationship<T>(source, target);
}
/// <summary>
/// Returns a relationship of an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <returns>The <see cref="Relationship{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static ref Relationship<T> GetRelationships<T>(this in Entity source)
{
var world = World.Worlds[source.WorldId];
return ref world.GetRelationships<T>(source);
}
/// <summary>
/// Tries to return an <see cref="Entity"/>s relationship of the specified type.
/// Will copy the relationship if its a struct.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <param name="relationship">The found relationship.</param>
/// <returns>True if it exists, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool TryGetRelationship<T>(this in Entity source, Entity target, out T relationship)
{
var world = World.Worlds[source.WorldId];
return world.TryGetRelationship(source, target, out relationship);
}
/// <summary>
/// Removes a relationship from an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The <see cref="Entity"/> to remove the relationship from.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
public static void RemoveRelationship<T>(this in Entity source, Entity target)
{
var world = World.Worlds[source.WorldId];
world.RemoveRelationship<T>(source, target);
}
}
#endif

View File

@@ -0,0 +1 @@
uid://574q5d6c3s6y

View File

@@ -0,0 +1,58 @@
namespace Arch.Relationships;
using global::System;
using global::System.Collections.Generic;
using Arch.Core;
/// <summary>
/// The <see cref="SortedListEnumerator{TValue}"/> struct
/// is a enumerator to enumerate a passed <see cref="SortedList{TKey,TValue}"/> in an efficient way.
/// </summary>
/// <typeparam name="TValue"></typeparam>
public struct SortedListEnumerator<TValue>
{
private readonly SortedList<Entity, TValue> _sortedList;
private int _currentIndex;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="list">List.</param>
public SortedListEnumerator(SortedList<Entity, TValue> list)
{
_sortedList = list;
_currentIndex = -1;
}
/// <summary>
/// Current.
/// </summary>
public readonly KeyValuePair<Entity, TValue> Current
{
get
{
if (_currentIndex == -1 || _currentIndex >= _sortedList.Count)
{
throw new InvalidOperationException();
}
var key = _sortedList.Keys[_currentIndex];
var value = _sortedList.Values[_currentIndex];
return new KeyValuePair<Entity, TValue>(key, value);
}
}
/// <summary>
/// Moves to the next element in the enumerator.
/// </summary>
public bool MoveNext()
{
_currentIndex++;
return _currentIndex < _sortedList.Count;
}
/// <summary>
/// Resets the enumerator to its initial position.
/// </summary>
public void Reset() => _currentIndex = -1;
}

View File

@@ -0,0 +1 @@
uid://br3hmo40b0kmx

View File

@@ -0,0 +1,38 @@
namespace Arch.Relationships;
using Arch.Core;
/// <summary>
/// The struct <see cref="InRelationship"/>
/// represents a reference to a <see cref="Relationship{T}"/>.
/// It sits on an <see cref="Entity"/> to indicate in which other <see cref="Relationship{T}"/>s it is involved in.
/// </summary>
internal readonly struct InRelationship
{
/// <summary>
/// The id of the <see cref="Relationship{T}"/>-Component that this <see cref="InRelationship"/> points to.
/// Basically the <see cref="Relationship{T}"/> the <see cref="Entity"/> is in.
/// TODO: Uhmm... how the heck do we convert the Id back to the <see cref="ComponentType"/>?
/// </summary>
public readonly int ComponentTypeId;
/// <summary>
/// Creates a new <see cref="InRelationship"/> instance.
/// </summary>
/// <param name="targetRelation">The <see cref="ComponentType"/> that represents the relation.</param>
internal InRelationship(ComponentType targetRelation)
{
ComponentTypeId = targetRelation.Id;
}
/// <summary>
/// Creates a new <see cref="InRelationship"/> instance.
/// <remarks>Mostly for binary serialization.</remarks>
/// </summary>
/// <param name="componentTypeId">The <see cref="ComponentTypeId"/>.</param>
internal InRelationship(int componentTypeId)
{
ComponentTypeId = componentTypeId;
}
}

View File

@@ -0,0 +1 @@
uid://cu62xtv73hjq7

View File

@@ -0,0 +1,146 @@
namespace Arch.Relationships;
using global::System.Collections.Generic;
using global::System.Runtime.CompilerServices;
using Arch.Core;
/// <summary>
/// The <see cref="IRelationship"/> interface
/// is an interface that provides all methods required to act as a relationship.
/// </summary>
internal interface IRelationship
{
/// <summary>
/// The amount of relationships currently in the buffer.
/// </summary>
int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Removes the buffer as a component from the given world and entity.
/// </summary>
/// <param name="world"></param>
/// <param name="source"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Destroy(World world, Entity source);
/// <summary>
/// Removes the relationship targeting <paramref name="target"/> from this buffer.
/// </summary>
/// <param name="target">The <see cref="Entity"/> in the relationship to remove.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Remove(Entity target);
}
/// <summary>
/// A buffer storing relationships of <see cref="Entity"/> and <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the second relationship element.</typeparam>
public class Relationship<T> : IRelationship
{
/// <summary>
/// Its relations.
/// </summary>
internal readonly SortedList<Entity, T> _elements;
/// <summary>
/// Initializes a new instance of an <see cref="Relationship{T}"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Relationship()
{
_elements = [];
}
/// <summary>
/// Initializes a new instance of an <see cref="Relationship{T}"/>.
/// <remarks>Mostly for binary serialization.</remarks>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Relationship(SortedList<Entity, T> elements)
{
_elements = elements;
}
/// <inheritdoc/>
int IRelationship.Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _elements.Count;
}
/// <inheritdoc cref="IRelationship.Count"/>
internal int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ((IRelationship)this).Count;
}
/// <summary>
/// Adds a relationship to this buffer.
/// </summary>
/// <param name="relationship">The instance of the relationship.</param>
/// <param name="target">The target of the relationship.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Add(in T relationship, Entity target) => _elements.Add(target, relationship);
/// <summary>
/// Sets the stored <typeparamref name="T"/> for the given <see cref="Entity"/>.
/// </summary>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <param name="data">The data to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Set(Entity entity, T data = default!) => _elements[entity] = data;
/// <summary>
/// Determines whether the given <see cref="Relationship{T}"/> contains the passed <see cref="Entity"/> or not.
/// </summary>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <returns>True or false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(Entity entity) => _elements.ContainsKey(entity);
/// <summary>
/// Returns the stored <typeparamref name="T"/> for the given <see cref="Entity"/>.
/// </summary>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <returns>The stored <typeparamref name="T"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Get(Entity entity) => _elements[entity];
/// <summary>
/// Returns the stored <typeparamref name="T"/> for the given <see cref="Entity"/>.
/// </summary>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <param name="value">The stored <typeparamref name="T"/>.</param>
/// <returns>The stored <typeparamref name="T"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetValue(Entity entity, out T value) => _elements.TryGetValue(entity, out value!);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void IRelationship.Remove(Entity target) => _elements.Remove(target);
/// <inheritdoc cref="IRelationship.Remove(Entity)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Remove(Entity target) => ((IRelationship)this).Remove(target);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void IRelationship.Destroy(World world, Entity source) => world.Remove<Relationship<T>>(source);
/// <inheritdoc cref="IRelationship.Destroy(World, Entity)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Destroy(World world, Entity source) => ((IRelationship)this).Destroy(world, source);
/// <summary>
/// Creates a new <see cref="SortedListEnumerator{TValue}"/>.
/// </summary>
/// <returns>The new <see cref="SortedListEnumerator{TValue}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SortedListEnumerator<T> GetEnumerator() => new(_elements);
};

View File

@@ -0,0 +1 @@
uid://d2oi4itpntwl3

View File

@@ -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;
/// <summary>
/// The <see cref="WorldRelationshipExtensions"/> class
/// stores several extension methods for relationships handling.
/// </summary>
public static class WorldRelationshipExtensions
{
#if EVENTS
/// <summary>
/// Subscribes to entity destruction events to cleanup their relations.
/// </summary>
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.
/// <summary>
/// Cleans up all relations of the passed <see cref="Entity"/>.
/// </summary>
/// <param name="world"></param>
/// <param name="entity"></param>
public static void CleanupRelationships(this World world, in Entity entity)
{
ref var relationships = ref world.TryGetRefRelationships<InRelationship>(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<IRelationship[]>(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<InRelationship>(target, out exists);
if (!exists)
{
continue;
}
targetRelationships.Remove(entity);
}
}
#endif
/// <summary>
/// Adds a new relationship to the <see cref="Entity"/>.
/// </summary>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="relationship">The relationship instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AddRelationship<T>(this World world, Entity source, Entity target, in T relationship = default!)
{
ref var buffer = ref world.AddOrGetRelationships<T>(source);
buffer.Add(in relationship, target);
var targetComp = new InRelationship(Component<Relationship<T>>.ComponentType);
ref var targetBuffer = ref world.AddOrGetRelationships<InRelationship>(target);
targetBuffer.Add(in targetComp, source);
}
/// <summary>
/// Ensures the existence of a relationship on an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <param name="relationship">The relationship value used if its being added.</param>
/// <returns>The relationship.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T AddOrGetRelationship<T>(this World world, Entity source, Entity target, in T relationship = default!)
{
ref var relationships = ref world.TryGetRefRelationships<T>(source, out var exists);
if (exists)
{
return relationships.Get(target);
}
world.AddRelationship(source, target, in relationship);
return world.GetRelationship<T>(source, target);
}
/// <summary>
/// Ensures the existence of a buffer of relationships on an <see cref="Entity"/>.
/// </summary>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationships.</param>
/// <typeparam name="T">The relationship type.</typeparam>
/// <returns>The relationships.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ref Relationship<T> AddOrGetRelationships<T>(this World world, Entity source)
{
ref var component = ref world.TryGetRef<Relationship<T>>(source, out var exists);
if (exists)
{
return ref component!;
}
world.Add(source, new Relationship<T>());
return ref world.Get<Relationship<T>>(source);
}
/// <summary>
/// Sets the existing relationship data.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <param name="relationship">The new data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetRelationship<T>(this World world, Entity source, Entity target, in T relationship = default!)
{
ref var relationships = ref world.GetRelationships<T>(source);
relationships.Set(target, relationship);
}
/// <summary>
/// Checks if an <see cref="Entity"/> has a certain relationship.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <returns>True if it has the desired relationship, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool HasRelationship<T>(this World world, Entity source, Entity target)
{
ref var relationships = ref world.TryGetRefRelationships<T>(source, out var exists);
if (!exists)
{
return false;
}
return relationships.Contains(target);
}
/// <summary>
/// Checks if an <see cref="Entity"/> has a certain relationship.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <returns>True if it has the desired relationship, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool HasRelationship<T>(this World world, Entity source) => world.Has<Relationship<T>>(source);
/// <summary>
/// Returns a relationship of an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <returns>The relationship.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static T GetRelationship<T>(this World world, Entity source, Entity target)
{
ref var relationships = ref world.GetRelationships<T>(source);
return relationships.Get(target);
}
/// <summary>
/// Tries to return an <see cref="Entity"/>s relationship of the specified type.
/// Will copy the relationship if its a struct.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <param name="relationship">The found relationship.</param>
/// <returns>True if it exists, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool TryGetRelationship<T>(this World world, Entity source, Entity target, out T relationship)
{
ref var relationships = ref world.TryGetRefRelationships<T>(source, out var exists);
if (!exists)
{
relationship = default!;
return false;
}
return relationships.TryGetValue(target, out relationship);
}
/// <summary>
/// Returns all relationships of the given type of an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <returns>A reference to the relationships.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static ref Relationship<T> GetRelationships<T>(this World world, Entity source) => ref world.Get<Relationship<T>>(source);
/// <summary>
/// Tries to return an <see cref="Entity"/>s relationships of the specified type.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The <see cref="Entity"/>.</param>
/// <param name="relationships">The found relationships.</param>
/// <returns>True if it exists, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
internal static bool TryGetRelationships<T>(this World world, Entity source, out Relationship<T> relationships) => world.TryGet(source, out relationships!);
/// <summary>
/// Tries to return a reference to an <see cref="Entity"/>s relationships of the
/// specified type.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The <see cref="Entity"/>.</param>
/// <param name="exists">True if it exists, otherwise false.</param>
/// <returns>A reference to the relationships.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
internal static ref Relationship<T> TryGetRefRelationships<T>(this World world, Entity source, out bool exists) => ref world.TryGetRef<Relationship<T>>(source, out exists);
/// <summary>
/// Removes a relationship from an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The <see cref="Entity"/> to remove the relationship from.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RemoveRelationship<T>(this World world, Entity source, Entity target)
{
ref var buffer = ref world.GetRelationships<T>(source);
buffer.Remove(target);
if (buffer.Count == 0)
{
world.Remove<Relationship<T>>(source);
}
ref var targetBuffer = ref world.GetRelationships<InRelationship>(target);
targetBuffer.Remove(source);
if (targetBuffer.Count == 0)
{
world.Remove<Relationship<InRelationship>>(target);
}
}
}

View File

@@ -0,0 +1 @@
uid://dfnift6mppxam

View File

@@ -12,8 +12,8 @@ public partial class ConveyorBeltStraight : Node3D, IProvide<IVoxelGridRegistry>
{ {
public override void _Notification(int what) => this.Notify(what); public override void _Notification(int what) => this.Notify(what);
[Export] public int Width { get; set; } = default; [Export] public int Width { get; set; } = default;
public IVoxelGridRegistry Value() => Grid; public IVoxelGridRegistry Value() => FoodFactoryApi.GridRegistry;
[Dependency] protected IVoxelGridRegistry Grid => this.DependOn<IVoxelGridRegistry>(); [Dependency] protected IFoodFactoryApi FoodFactoryApi => this.DependOn<IFoodFactoryApi>();
private VoxelGuid _guid = default; private VoxelGuid _guid = default;
public GridTransform3D VoxelTransform public GridTransform3D VoxelTransform
{ {
@@ -62,13 +62,13 @@ public partial class ConveyorBeltStraight : Node3D, IProvide<IVoxelGridRegistry>
} }
public void OnResolved() public void OnResolved()
{ {
_guid = Grid.Register(this, VoxelTransform.Origin); _guid = FoodFactoryApi.GridRegistry.Register(this, VoxelTransform.Origin);
this.Provide(); this.Provide();
} }
public override void _ExitTree() public override void _ExitTree()
{ {
Grid.UnRegister(_guid); FoodFactoryApi.GridRegistry.UnRegister(_guid);
base._ExitTree(); base._ExitTree();
} }
} }

View File

@@ -1,10 +1,10 @@
[gd_scene format=3 uid="uid://c4h7mwnfrdesg"] [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://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://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="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"] [sub_resource type="Curve3D" id="Curve3D_x5qdr"]
_data = { _data = {

View File

@@ -3,10 +3,14 @@ namespace FoodFactory.Conveyors;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Arch.Core;
using Chickensoft.AutoInject; using Chickensoft.AutoInject;
using Chickensoft.Introspection; using Chickensoft.Introspection;
using Chickensoft.Sync.Primitives;
using CommunityToolkit.HighPerformance.Helpers;
using FoodFactory.Items; using FoodFactory.Items;
using Godot; using Godot;
using SJK.Math;
[Meta(typeof(IAutoNode))] [Meta(typeof(IAutoNode))]
public partial class ConveyorItemRender : Node public partial class ConveyorItemRender : Node
@@ -15,48 +19,69 @@ public partial class ConveyorItemRender : Node
[Export] protected Path3D Path3D { get; set; } = default!; [Export] protected Path3D Path3D { get; set; } = default!;
[Export] protected TestItemConveyor ItemConveyor { get; set; } = default!; [Export] protected TestItemConveyor ItemConveyor { get; set; } = default!;
[Chickensoft.AutoInject.Dependency] protected IItemRenderer Items => this.DependOn<IItemRenderer>(); [Chickensoft.AutoInject.Dependency] protected IItemRenderer Items => this.DependOn<IItemRenderer>();
[Chickensoft.AutoInject.Dependency] protected IFoodFactoryApi FoodFactoryApi => this.DependOn<IFoodFactoryApi>();
// private Chickensoft.Sync.Primitives.AutoList<ConveyorSlice>.Binding _binding = default!; // private Chickensoft.Sync.Primitives.AutoList<ConveyorSlice>.Binding _binding = default!;
public override async void _Ready() private AutoList<Ordered1DList<IBeltItem>.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<Timer>().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)));
_binding = ItemConveyor.Items.Values.Bind().OnUpdate((old, updated) =>
{
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<Timer>().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; float offset = 0;
public override void _Process(double delta) double delta = 0;
{
offset += (float)delta; // public void Process(double delta)
base._Process(delta); // {
var e = ItemConveyor.EnumerateTowardStart(); // offset += (float)delta;
while (e.MoveNext()) // base._Process(delta);
{ // var e = ItemConveyor.EnumerateTowardStart();
Items.UpdateTransform(e.Current.Value, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(e.Current.Position + (ItemConveyor.SignedSpeed * offset))); // 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) protected override void Dispose(bool disposing)
{ {
// _binding.Dispose(); _binding.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
} }
} }
@@ -68,6 +93,58 @@ public interface IItemRenderer
void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time); void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time);
void Remove(IBeltItem beltItem); 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<ItemEntry> _current = [];
private List<ItemEntry> _next = [];
private Dictionary<IBeltItem, Node3D> _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 public partial class ItemRenderSimple : Node3D, IItemRenderer
{ {
private Dictionary<IBeltItem, Node3D> _items = []; private Dictionary<IBeltItem, Node3D> _items = [];

View File

@@ -25,6 +25,7 @@ public interface IMovementConveyor
float SignedSpeed { get; set; } float SignedSpeed { get; set; }
bool IsReversed { get; set; } bool IsReversed { get; set; }
float Length { get; set; } float Length { get; set; }
bool HasItems();
// float GetAvailableTravel(ItemConveyor.BeltDirection beltDirection, LaneSpanT itemSpan, float maxDistance); // float GetAvailableTravel(ItemConveyor.BeltDirection beltDirection, LaneSpanT itemSpan, float maxDistance);
// IBeltSlotProfile GetPortFacingStart(); // IBeltSlotProfile GetPortFacingStart();
// IBeltSlotProfile GetPortFacingEnd(); // IBeltSlotProfile GetPortFacingEnd();
@@ -41,25 +42,6 @@ public interface IMovementConveyor
BeltObstacle GetDistanceToNextItem(BeltDirection beltDirection, float itemBeltT, float maxDistToCheck, LaneSpan laneSpan, HashSet<IMovementConveyor>? visted = null); BeltObstacle GetDistanceToNextItem(BeltDirection beltDirection, float itemBeltT, float maxDistToCheck, LaneSpan laneSpan, HashSet<IMovementConveyor>? visted = null);
ConveyorPort CreatePort(BeltPortProfile profile, BeltT beltT, LaneSpan laneSpan); ConveyorPort CreatePort(BeltPortProfile profile, BeltT beltT, LaneSpan laneSpan);
} }
public readonly struct ConveyorItemHandle
{
private readonly Action _remove;
private readonly Action<ConveyorSlice> _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<ConveyorSlice> replace)
{
Index = index;
Slice = slice;
_remove = remove;
_replace = replace;
}
public void Remove() => _remove();
public void Replace(ConveyorSlice slice) => _replace(slice);
}
public interface IBeltMovement public interface IBeltMovement
{ {
void AdvanceBelt(IMovementConveyor conveyor, float delta); void AdvanceBelt(IMovementConveyor conveyor, float delta);
@@ -112,7 +94,7 @@ public sealed class IndividualMovement : IBeltMovement
} }
} }
} }
enumerator.SortAll(); // enumerator.SortAll();
} }
} }
public sealed class StrictMovement : IBeltMovement 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) public struct ConveyorSlice(IBeltItem item, float beltT = 0)
@@ -258,7 +309,12 @@ public record BeltTOffset(float T) : BeltT();
public static class ConveyorExtensions 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<IBeltPort> GetPortFacing(this IBeltPort slot, IVoxelGridRegistry gridRegistry) public static IOption<IBeltPort> GetPortFacing(this IBeltPort slot, IVoxelGridRegistry gridRegistry)
{ {
var toCheck = new List<Vector3I>(); var toCheck = new List<Vector3I>();

View File

@@ -19,10 +19,12 @@ public partial class TestItemConveyor : Node, IMovementConveyor
public override void _Notification(int what) => this.Notify(what); public override void _Notification(int what) => this.Notify(what);
[Dependency] public IBeltMovement MovementSystem => this.DependOn<IBeltMovement>(() => new IndividualMovement()); [Dependency] public IBeltMovement MovementSystem => this.DependOn<IBeltMovement>(() => new IndividualMovement());
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>(); [Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
[Dependency] public IFoodFactoryApi FoodFactoryApi => this.DependOn<IFoodFactoryApi>();
// private readonly AutoList<ConveyorSlice> _items = []; // private readonly AutoList<ConveyorSlice> _items = [];
// public IAutoList<ConveyorSlice> Items => _items; // public IAutoList<ConveyorSlice> Items => _items;
// public readonly Sorted1DList<ConveyorSlice> Items = new(pos => pos.BeltT); // public readonly Sorted1DList<ConveyorSlice> Items = new(pos => pos.BeltT);
private readonly Ordered1DList<IBeltItem> _items = new(); private readonly Ordered1DList<IBeltItem> _items = new();
public Ordered1DList<IBeltItem> Items => _items;
// private AutoList<ConveyorSlice>.Binding _itemsBinding; // private AutoList<ConveyorSlice>.Binding _itemsBinding;
public IBeltPort StartPort { get; set; } public IBeltPort StartPort { get; set; }
// public IBeltSlotProfile StartPort => new ItemConveyor.ConveyorPort() { // public IBeltSlotProfile StartPort => new ItemConveyor.ConveyorPort() {
@@ -52,7 +54,7 @@ public partial class TestItemConveyor : Node, IMovementConveyor
public IList<IBeltPort> OtherPorts = []; public IList<IBeltPort> OtherPorts = [];
// [Export] public Vector3I Position { get; set; } = default!; // [Export] public Vector3I Position { get; set; } = default!;
//Speed per unit time //Speed per unit time
private readonly AutoValue<float> _speed = new(1); private readonly AutoValue<float> _speed = new(.5f);
public IAutoValue<float> SpeedValue => _speed; public IAutoValue<float> SpeedValue => _speed;
public float SignedSpeed public float SignedSpeed
@@ -82,9 +84,9 @@ public partial class TestItemConveyor : Node, IMovementConveyor
{ {
return; return;
} }
var timer = new Timer() { WaitTime = .05f, Autostart = true }; // var timer = new Timer() { WaitTime = .05f, Autostart = true };
AddChild(timer); // AddChild(timer);
timer.Timeout += () => MovementSystem.AdvanceBelt(this, (float)timer.WaitTime); FoodFactoryApi.TickManger.GameTick += args => MovementSystem.AdvanceBelt(this, (float)args.Delta);
if (StartPort is null || EndPort is null) if (StartPort is null || EndPort is null)
{ {
throw new Exception(); throw new Exception();
@@ -127,13 +129,15 @@ public partial class TestItemConveyor : Node, IMovementConveyor
} }
// 2⃣ No local item → try crossing into adjacent conveyor // 2⃣ No local item → try crossing into adjacent conveyor
return GetDistanceAcrossPort( var recursiveDistance = GetDistanceAcrossPort(
towardStart, towardStart,
itemBeltT, itemBeltT,
maxDistToCheck, maxDistToCheck,
laneSpan, laneSpan,
visited visited
); );
return recursiveDistance;// with { Distance = recursiveDistance.Distance + Length };
} }
//ChatGPT Assisted //ChatGPT Assisted
private IOption<ConveyorSlice> FindNextLocalItem( private IOption<ConveyorSlice> FindNextLocalItem(
@@ -412,6 +416,7 @@ public partial class TestItemConveyor : Node, IMovementConveyor
public Ordered1DList<IBeltItem>.Enumerator EnumerateTowardEnd() => _items.EnumerateTowardEnd(-1); public Ordered1DList<IBeltItem>.Enumerator EnumerateTowardEnd() => _items.EnumerateTowardEnd(-1);
public Ordered1DList<IBeltItem>.Enumerator EnumerateTowardStart() => _items.EnumerateTowardStart(_items.Count); public Ordered1DList<IBeltItem>.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 //TODO Should liklely account for max search distance where the conveyorm may be needed to know
public record class BeltObstacle(float Distance); public record class BeltObstacle(float Distance);

View File

@@ -18,7 +18,8 @@ public partial class Balancer : Node3D, IProvide<IBeltPortHost>, IProvide<IVoxel
private BeltPortHost _insertLogic = default!; private BeltPortHost _insertLogic = default!;
public IBeltPortHost Value() => _insertLogic; public IBeltPortHost Value() => _insertLogic;
IVoxelGridRegistry IProvide<IVoxelGridRegistry>.Value() => GridRegistry; IVoxelGridRegistry IProvide<IVoxelGridRegistry>.Value() => GridRegistry;
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>(); public IVoxelGridRegistry GridRegistry => FoodFactory.GridRegistry;
[Dependency] public IFoodFactoryApi FoodFactory => this.DependOn<IFoodFactoryApi>();
private List<BeltPort> _outPuts = new(); private List<BeltPort> _outPuts = new();
public GridTransform3D VoxelTransform public GridTransform3D VoxelTransform
{ {

View File

@@ -9,7 +9,7 @@ using FoodFactory.Math;
using FoodFactory.Voxel; using FoodFactory.Voxel;
using Godot; using Godot;
[Tool] // [Tool]
[Meta(typeof(IAutoNode))] [Meta(typeof(IAutoNode))]
public partial class BeltPort : Node3D, IBeltPort public partial class BeltPort : Node3D, IBeltPort
{ {

View File

@@ -21,6 +21,7 @@ public partial class OvenTest : Node3D, IProvide<IBeltPortHost>, IProvide<IVoxel
private BeltPortHost _insertLogic = default!; private BeltPortHost _insertLogic = default!;
public IBeltPortHost Value() => _insertLogic; public IBeltPortHost Value() => _insertLogic;
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>(); [Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
[Dependency] public IFoodFactoryApi FoodFactory => this.DependOn<IFoodFactoryApi>();
[Dependency] public IRecipes Recipes => this.DependOn<IRecipes>(); [Dependency] public IRecipes Recipes => this.DependOn<IRecipes>();
IVoxelGridRegistry IProvide<IVoxelGridRegistry>.Value() => GridRegistry; IVoxelGridRegistry IProvide<IVoxelGridRegistry>.Value() => GridRegistry;
public GridTransform3D VoxelTransform public GridTransform3D VoxelTransform
@@ -43,7 +44,7 @@ public partial class OvenTest : Node3D, IProvide<IBeltPortHost>, IProvide<IVoxel
{ {
_itemBeingHeld = itemData.GetItem(); _itemBeingHeld = itemData.GetItem();
item.Dispose(); item.Dispose();
GD.Print("Added Item"); // GD.Print("Added Item");
return true; return true;
} }
throw new NotImplementedException(); throw new NotImplementedException();
@@ -51,10 +52,10 @@ public partial class OvenTest : Node3D, IProvide<IBeltPortHost>, IProvide<IVoxel
_guid = GridRegistry.Register(this, VoxelTransform.Origin); _guid = GridRegistry.Register(this, VoxelTransform.Origin);
this.Provide(); this.Provide();
var time = new Timer() { Autostart = true, OneShot = false, WaitTime = .1 }; // var time = new Timer() { Autostart = true, OneShot = false, WaitTime = .1 };
AddChild(time); // AddChild(time);
time.Timeout += () => FoodFactory.TickManger.GameTick += _ =>
{ {
if (_itemBeingHeld.Id == -1 && !_itemBeingHeld.IsAlive())//TODO have a better way of dertming if item is valid, possibly nullable 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<IBeltPortHost>, IProvide<IVoxel
unsafe unsafe
{ {
while (RecipeProcessor.TryProcess( while (RecipeProcessor.TryProcess(
ref recipes, recipes: ref recipes,
ref context, context: ref context,
ref builder, builder: ref builder,
&DestroyEntity, destroyEntity: &DestroyEntity,
out var created filter: &RecipeProcessor.FilterByPass,
resultEntity: out var created
)) ))
{ {
Debug.Assert(created.Length <= 1); Debug.Assert(created.Length <= 1);
@@ -178,11 +180,13 @@ public unsafe ref struct RecipeEnumerator : IRecipeEnumerator
} }
public static class RecipeProcessor public static class RecipeProcessor
{ {
public static bool FilterByPass(Recipe recipe) => true;
public static unsafe bool TryProcess<TRecipeEnumerator>( public static unsafe bool TryProcess<TRecipeEnumerator>(
ref TRecipeEnumerator recipes, ref TRecipeEnumerator recipes,
scoped ref RecipeContext context, scoped ref RecipeContext context,
scoped ref RecipeResultBuilder builder, scoped ref RecipeResultBuilder builder,
delegate*<Entity, void> destroyEntity, delegate*<Entity, void> destroyEntity,
delegate*<Recipe, bool> filter,
out Span<Entity> resultEntity) out Span<Entity> resultEntity)
where TRecipeEnumerator : IRecipeEnumerator, allows ref struct where TRecipeEnumerator : IRecipeEnumerator, allows ref struct
{ {
@@ -198,6 +202,10 @@ public static class RecipeProcessor
{ {
continue; continue;
} }
if (!filter(recipe))
{
continue;
}
var result = recipe.Process(context, ref builder); var result = recipe.Process(context, ref builder);

View File

@@ -23,6 +23,7 @@ public partial class SlicerTest : Node3D, IProvide<IBeltPortHost>, IProvide<IVox
private BeltPortHost _insertLogic = default!; private BeltPortHost _insertLogic = default!;
public IBeltPortHost Value() => _insertLogic; public IBeltPortHost Value() => _insertLogic;
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>(); [Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
[Dependency] public IFoodFactoryApi FoodFactory => this.DependOn<IFoodFactoryApi>();
[Dependency] public IRecipes Recipes => this.DependOn<IRecipes>(); [Dependency] public IRecipes Recipes => this.DependOn<IRecipes>();
IVoxelGridRegistry IProvide<IVoxelGridRegistry>.Value() => GridRegistry; IVoxelGridRegistry IProvide<IVoxelGridRegistry>.Value() => GridRegistry;
public GridTransform3D VoxelTransform public GridTransform3D VoxelTransform
@@ -46,7 +47,7 @@ public partial class SlicerTest : Node3D, IProvide<IBeltPortHost>, IProvide<IVox
{ {
_itemBeingHeld = itemData.GetItem(); _itemBeingHeld = itemData.GetItem();
item.Dispose(); item.Dispose();
GD.Print("Added Item to slicer"); // GD.Print("Added Item to slicer");
return true; return true;
} }
throw new NotImplementedException(); throw new NotImplementedException();
@@ -54,10 +55,9 @@ public partial class SlicerTest : Node3D, IProvide<IBeltPortHost>, IProvide<IVox
_guid = GridRegistry.Register(this, VoxelTransform.Origin); _guid = GridRegistry.Register(this, VoxelTransform.Origin);
this.Provide(); this.Provide();
var time = new Timer() { Autostart = true, OneShot = false, WaitTime = .1 }; // var time = new Timer() { Autostart = true, OneShot = false, WaitTime = .1 };
AddChild(time); // AddChild(time);
FoodFactory.TickManger.GameTick += _ =>
time.Timeout += () =>
{ {
if (sliced.Any()) if (sliced.Any())
@@ -83,11 +83,12 @@ public partial class SlicerTest : Node3D, IProvide<IBeltPortHost>, IProvide<IVox
unsafe unsafe
{ {
while (RecipeProcessor.TryProcess( while (RecipeProcessor.TryProcess(
ref recipes, recipes: ref recipes,
ref context, context: ref context,
ref builder, builder: ref builder,
&DestroyEntity, destroyEntity: &DestroyEntity,
out var created filter: &RecipeProcessor.FilterByPass,
resultEntity: out var created
)) ))
{ {
_itemBeingHeld = Entity.Null; _itemBeingHeld = Entity.Null;

View File

@@ -0,0 +1,118 @@
namespace FoodFactory.Equipment;
using System;
using System.Collections.Generic;
using System.Linq;
using Arch.Core;
using Arch.Core.Extensions;
using Arch.Relationships;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using FoodFactory;
using FoodFactory.Conveyors;
using FoodFactory.Items;
using FoodFactory.Math;
using FoodFactory.Recipes;
using FoodFactory.Voxel;
using Godot;
using SJK.Functional;
[Meta(typeof(IAutoNode))]
public partial class StackerTest : Node3D, IProvide<IBeltPortHost>, IProvide<IVoxelGridRegistry>
{
public override void _Notification(int what) => this.Notify(what);
private BeltPortHost _insertLogic = default!;
public IBeltPortHost Value() => _insertLogic;
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
[Dependency] public IFoodFactoryApi FoodFactory => this.DependOn<IFoodFactoryApi>();
[Dependency] public IRecipes Recipes => this.DependOn<IRecipes>();
IVoxelGridRegistry IProvide<IVoxelGridRegistry>.Value() => GridRegistry;
public GridTransform3D VoxelTransform
{
get => GridTransform3D.FromGodot(GlobalTransform);
set => GlobalTransform = value.ToGodot();
}
private Entity _output = Entity.Null;
private List<Entity> _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<Entity> 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<Entity> items = [_inputs[0], _inputs[1]];
var context = new RecipeContext(World.Worlds[0], items);
var builder = new RecipeResultBuilder(stackalloc bool[10], new ItemBuilder[10]);
Span<int> mapping = stackalloc int[2];
// var recipe = new PotatoCookRecipe();
var recipes = Recipes.GetRecipes("stack", 2, [_inputs[0].Get<Tags>(), _inputs[1].Get<Tags>()], [], 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<ParentOf>();
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();
}
}

View File

@@ -0,0 +1 @@
uid://culjdbwllmsyk

View File

@@ -8,15 +8,15 @@ using System.Linq;
public readonly struct Tags : IEquatable<Tags>, IReadOnlyCollection<Tag> public readonly struct Tags : IEquatable<Tags>, IReadOnlyCollection<Tag>
{ {
private readonly Tag[] _values; private readonly int _index;
public Tags() public Tags()
{ {
_values = []; _index = 0;
} }
/// <summary> /// <summary>
/// Will error if there is not tags in contained. /// Will error if there is not tags in contained.
/// </summary> /// </summary>
public readonly Tag First => _values[0]; public readonly Tag First => _tags[_index][0];
public Tags(params string[] tags) public Tags(params string[] tags)
{ {
var arr = new Tag[tags.Length]; var arr = new Tag[tags.Length];
@@ -25,27 +25,29 @@ public readonly struct Tags : IEquatable<Tags>, IReadOnlyCollection<Tag>
arr[i] = TagRegistry.GetTag(tags[i]); arr[i] = TagRegistry.GetTag(tags[i]);
} }
_values = SortAndDeduplicate(arr); _index = SortAndDeduplicate(arr);
} }
public override string ToString() => $"Tags: [{string.Join(", ", _values.Select(t => t.Name))}]"; public override string ToString() => $"Tags: [{string.Join(", ", _tags[_index].Select(t => t.Name))}]";
private static readonly Dictionary<TagArrayKey, Tag[]> _internedTags = []; private static readonly Dictionary<TagArrayKey, int> _internedTags = new(){[new TagArrayKey([])] = 0};
private static readonly List<Tag[]> _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); Array.Sort(tags);
var key = new TagArrayKey(tags); var key = new TagArrayKey(tags);
if (!_internedTags.TryGetValue(key, out var interned)) if (!_internedTags.TryGetValue(key, out var interned))
{ {
_internedTags[key] = interned = tags; _tags.Add(tags);
_internedTags[key] = _tags.Count - 1;
} }
return interned; return interned;
} }
public Tags(params Tag[] tags) public Tags(params Tag[] tags)
{ {
_values = SortAndDeduplicate(tags); _index = SortAndDeduplicate(tags);
} }
public Tags(ISet<Tag> tags) : this(tags.ToArray()) public Tags(ISet<Tag> tags) : this(tags.ToArray())
{ {
@@ -53,30 +55,30 @@ public readonly struct Tags : IEquatable<Tags>, IReadOnlyCollection<Tag>
} }
public Tags With(params Tag[] tags) public Tags With(params Tag[] tags)
{ {
HashSet<Tag> set = [.. _values, .. tags]; HashSet<Tag> set = [.. _tags[_index], .. tags];
return new Tags(set); return new Tags(set);
} }
public Tags With(params string[] tags) public Tags With(params string[] tags)
{ {
HashSet<Tag> set = [.. _values, .. TagRegistry.GetTags(tags)]; HashSet<Tag> set = [.. _tags[_index], .. TagRegistry.GetTags(tags)];
return new Tags(set); return new Tags(set);
} }
public Tags WithOut(params string[] tags) public Tags WithOut(params string[] tags)
{ {
HashSet<Tag> set = [.. _values]; HashSet<Tag> set = [.. _tags[_index]];
for (int i = 0; i < tags.Length; i++) for (int i = 0; i < tags.Length; i++)
{ {
set.Remove(TagRegistry.GetTag(tags[i])); set.Remove(TagRegistry.GetTag(tags[i]));
} }
return new Tags(set); return new Tags(set);
} }
public readonly bool Contains(Tag tag) => Array.BinarySearch(_values, tag) >= 0; public readonly bool Contains(Tag tag) => Array.BinarySearch(_tags[_index], tag) >= 0;
public readonly bool Equals(Tags other) => _values == other._values; public readonly bool Equals(Tags other) => _index == other._index;
public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is Tags tags && Equals(tags); public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is Tags tags && Equals(tags);
public override readonly int GetHashCode() => _values.GetHashCode(); public override readonly int GetHashCode() => _tags[_index].GetHashCode();
public readonly IEnumerator<Tag> GetEnumerator() public readonly IEnumerator<Tag> GetEnumerator()
{ {
foreach (var item in _values) foreach (var item in _tags[_index])
{ {
yield return item; yield return item;
} }
@@ -140,32 +142,40 @@ public readonly struct Tags : IEquatable<Tags>, IReadOnlyCollection<Tag>
} }
public readonly bool ContainsAll(Tags other) public readonly bool ContainsAll(Tags other)
{ {
var i = 0; foreach (var item in other)
var j = 0;
while (i < _values.Length && j < other._values.Length)
{ {
if (_values[i] == other._values[j]) if (!Contains(item))
{
i++;
j++;
}
else if (_values[i] < other._values[j])
{ {
return false; return false;
} }
else
{
j++;
} }
} return true;
return j == other._values.Length; // var i = 0;
// var j = 0;
// while (i < _tags[_index].Length && j < _tags[other._index].Length)
// {
// if (_tags[_index][i] == _tags[other._index][j])
// {
// i++;
// j++;
// }
// else if (_tags[_index][i] < _tags[other._index][j])
// {
// return false;
// }
// else
// {
// j++;
// }
// }
// return j == _tags[other._index].Length;
} }
public readonly bool ContainsAny(Tags other) public readonly bool ContainsAny(Tags other)
{ {
for (var i = 0; i < _values.Length; i++) for (var i = 0; i < _tags[_index].Length; i++)
{ {
if (other.Contains(_values[i])) if (other.Contains(_tags[_index][i]))
{ {
return true; return true;
} }

View File

@@ -50,7 +50,7 @@ public class TestItem() : IBeltItem, IBeltItemData<Entity>
} }
else else
{ {
node = new MeshInstance3D() { Mesh = new BoxMesh() }; node = new MeshInstance3D() { Mesh = new BoxMesh() { Size = new Vector3(.2f, .2f, .2f) } };
} }
node.GetChildren().OfType<Node3D>().ToList().ForEach(i => i.Scale *= new Vector3(.2f, .2f, .2f)); node.GetChildren().OfType<Node3D>().ToList().ForEach(i => i.Scale *= new Vector3(.2f, .2f, .2f));
if (Item.TryGet<Color>(out var color)) if (Item.TryGet<Color>(out var color))
@@ -81,23 +81,12 @@ public class TestItem() : IBeltItem, IBeltItemData<Entity>
public Entity Item; public Entity Item;
} }
public interface IFluidItem
{
float Volume { get; }
}
public interface IConveyorProfile
{
int LaneCount { get; }
bool LaneSwitching { get; }
}
public readonly record struct BeltPortProfile( public readonly record struct BeltPortProfile(
GridTransform3D LocalOffset, GridTransform3D LocalOffset,
// Direction Face, // Direction Face,
int Width, int Width,
PortAccess Access PortAccess Access
) : IBeltSlotProfile )
{ {
public Vector3I Position => LocalOffset.Origin; public Vector3I Position => LocalOffset.Origin;
// Direction IBeltSlotProfile.Direction => Face; // Direction IBeltSlotProfile.Direction => Face;
@@ -186,15 +175,6 @@ public sealed class ConveyorPort : IBeltPort
return _accept!(item, beltT, laneSpan); return _accept!(item, beltT, laneSpan);
} }
} }
public interface IBeltSlotProfile
{
Vector3I Position { get; }
int Width { get; }
PortAccess Access { get; }
}
// public record LaneId(int Index);
[Flags] [Flags]
public enum PortAccess : byte public enum PortAccess : byte
{ {
@@ -204,14 +184,6 @@ public enum PortAccess : byte
InOut = In | Out, InOut = In | Out,
BiDirectional = InOut BiDirectional = InOut
} }
[Flags]
public enum TransferMode : byte//Need Better Name
{
Passive = 0,
Push = 1,
Pull = 2,
PushPull = Push | Pull
}
public static class SlotExtension public static class SlotExtension
{ {
public static LaneSpan MapLaneSpanToFacingPort(this IBeltPort self, IBeltPort other) => MapSlotToFacingSlot(self.Profile.LocalOffset, self.Profile.Width, other.Profile.LocalOffset, other.Profile.Width); public static LaneSpan MapLaneSpanToFacingPort(this IBeltPort self, IBeltPort other) => MapSlotToFacingSlot(self.Profile.LocalOffset, self.Profile.Width, other.Profile.LocalOffset, other.Profile.Width);

View File

@@ -3,10 +3,13 @@ namespace FoodFactory;
using System.Collections.Generic; using System.Collections.Generic;
using Arch.Core; using Arch.Core;
using Arch.Core.Extensions; using Arch.Core.Extensions;
using Arch.Core.Utils;
using Arch.LowLevel; using Arch.LowLevel;
using Arch.Persistence;
using FoodFactory.Items; using FoodFactory.Items;
using FoodFactory.Math; using FoodFactory.Math;
using Godot; using Godot;
using Utf8Json;
public class Test public class Test
{ {
@@ -51,22 +54,63 @@ public class Test
GD.Print(type); GD.Print(type);
var flour = world.Create(new Name("flour"), new Temperature(71, TemperatureUnit.Fahrenheit), new Tags("flour", "wheat")); var flour = world.Create(new Name("flour"), new Temperature(71, TemperatureUnit.Fahrenheit), new Tags("flour", "wheat"));
var tag2 = new Tags(); // var tag2 = new Tags();
var serializer = new ArchJsonSerializer(new TagsSerializer(), new TemperatureSerializer());
world.TrimExcess();
var worldJson = serializer.ToJson(world);
// GD.Print(worldJson);
var otherWorld = serializer.FromJson(worldJson);
world.SubscribeEntityDestroyed((in entity) =>
{
foreach (var item in entity.GetAllComponents())
{
if (item is IEntityContainer container)
{
foreach (var item2 in container.GetEntities())
{
world.Destroy(item2);
} }
} }
public class TagsSerializer : IJsonFormatter<Tags>
{
public Tags Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
List<string> names = [];
reader.ReadIsBeginObject();
reader.ReadIsBeginArray();
while (!reader.ReadIsEndArray())
{
names.Add(reader.ReadString());
}
reader.ReadIsEndObject();
return new Tags([.. names]);
}
public void Serialize(ref JsonWriter writer, Tags value, IJsonFormatterResolver formatterResolver)
{
writer.WriteBeginObject();
writer.WriteBeginArray();
if (value.Count > 0)
{
foreach (var item in value)
{
writer.WriteString(item.Name);
} }
} }
); writer.WriteEndArray();
writer.WriteEndObject();
}
}
public class TemperatureSerializer : IJsonFormatter<Temperature>
{
public Temperature Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
reader.ReadIsBeginObject();
reader.ReadPropertyName();
var temp = reader.ReadDouble();
reader.ReadIsEndObject();
return new(temp);
}
public void Serialize(ref JsonWriter writer, Temperature value, IJsonFormatterResolver formatterResolver)
{
writer.WriteBeginObject();
writer.WritePropertyName(nameof(value.Kelvin));
writer.WriteDouble(value.Kelvin);
writer.WriteEndObject();
} }
} }
@@ -79,6 +123,7 @@ public record struct Name(string Value);
public record struct BurnableTemp(Temperature Temperature); public record struct BurnableTemp(Temperature Temperature);
public record struct BurnedItem(Temperature BurnedAt, string Description);//Info about how burnted item is public record struct BurnedItem(Temperature BurnedAt, string Description);//Info about how burnted item is
public record struct MarcoNutrients(Weight Fat, Weight Protein, Carbohydrates Carbohydrate); public record struct MarcoNutrients(Weight Fat, Weight Protein, Carbohydrates Carbohydrate);
public record struct ParentOf();
public readonly record struct Carbohydrates( public readonly record struct Carbohydrates(
Weight Fiber, Weight Fiber,
Weight Starch, Weight Starch,

View File

@@ -2,13 +2,15 @@ namespace SJK.Math;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Chickensoft.Sync.Primitives;
public sealed class Ordered1DList<T>// : IDisposable where T : unmanaged public sealed class Ordered1DList<T>// : IDisposable where T : unmanaged
{ {
private readonly List<Entry> _values; private readonly AutoList<Entry> _values;
public Ordered1DList(int capacity = 8) public IAutoList<Entry> Values => _values;
public Ordered1DList()
{ {
_values = new(capacity); _values = [];
} }
public record struct Entry(float Position, T Value) : IComparable<Entry> public record struct Entry(float Position, T Value) : IComparable<Entry>
{ {
@@ -109,17 +111,19 @@ public sealed class Ordered1DList<T>// : IDisposable where T : unmanaged
_list._values.RemoveAt(_index); _list._values.RemoveAt(_index);
_index -= _towardsEnd ? 1 : -1; _index -= _towardsEnd ? 1 : -1;
} }
public void SortAll() public readonly void MoveAllBy(float offset) => _list.MoveAllBy(offset * (_towardsEnd ?-1:1));
{ // public readonly void SortAll()
if (_list.Count > 0) // {
{ // if (_list.Count > 0)
_list._values.Sort(); // {
} // _list._values.Sort();
} // }
// }
public readonly void Set(float newPosition) public readonly void Set(float newPosition, bool sortLocal = true)
{ {
_list._values[_index] = new(newPosition, _list._values[_index].Value); _list._values[_index] = new(newPosition, _list._values[_index].Value);
_list.SortFromIndex(_index);
// _list.SortFromIndex(_index); // _list.SortFromIndex(_index);
// _list.SortFromIndex(_index); // _list.SortFromIndex(_index);
} }

View File

@@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis;
using Arch.Core; using Arch.Core;
using Arch.Core.Extensions; using Arch.Core.Extensions;
using Arch.Core.Extensions.Dangerous; using Arch.Core.Extensions.Dangerous;
using Arch.Relationships;
using FoodFactory.Items; using FoodFactory.Items;
using FoodFactory.Math; using FoodFactory.Math;
using Godot; using Godot;
@@ -66,7 +67,7 @@ public static class RecipeCompiler
{ {
RecipeRef = recipe, RecipeRef = recipe,
ItemEntries = BuildEntries(recipe), ItemEntries = BuildEntries(recipe),
// IsOrdered = recipe.InputsOrdered, IsOrdered = recipe.InputsOrdered,
}; };
private static ItemEntry[] BuildEntries(Recipe recipe) private static ItemEntry[] BuildEntries(Recipe recipe)
{ {
@@ -147,8 +148,9 @@ public record OnionSliceRecipe() : Recipe(Name: "Onion_Slice",
private Entity Slice(in RecipeContext context) private Entity Slice(in RecipeContext context)
{ {
Span<Entity> span = stackalloc Entity[1]; Span<Entity> span = stackalloc Entity[1];
var signature = context.World.GetSignature(context.Items[0]); span[0] = context.World.Create(context.Items[0].GetComponentTypes());
context.World.Create(span, signature, 1); // var signature = context.World.GetSignature(context.Items[0]);
// context.World.Create(span, signature, 1);
PotatoCookRecipe.CopyEntities(context.Items, span, context.World); PotatoCookRecipe.CopyEntities(context.Items, span, context.World);
span[0].Set(new Name("Sliced " + context.Items[0].Get<Name>().Value)); span[0].Set(new Name("Sliced " + context.Items[0].Get<Name>().Value));
span[0].Set(new Mass(context.Items[0].Get<Mass>().Value * .5)); span[0].Set(new Mass(context.Items[0].Get<Mass>().Value * .5));
@@ -156,6 +158,32 @@ public record OnionSliceRecipe() : Recipe(Name: "Onion_Slice",
return span[0]; return span[0];
} }
} }
public record StackRecipe() : Recipe(Name: "Stack_Recipe",
BlueprintIds: null,
RequireAll: [false, false],
Inputs: [new Tags("raw"), new Tags()],
Exclude: [new Tags("raw"), new Tags()],
Action: "stack",
RecipeOutput: new RecipeOutput(1)
)
{
public override bool CanProcess(in RecipeContext context) => true;
public override RecipeResult Process(in RecipeContext context, ref RecipeResultBuilder builder)
{
builder.AddRemove(false);
builder.AddRemove(false);
builder.AddCreate(Stack);
return builder.Build();
}
private Entity Stack(in RecipeContext context)
{
var newItem = context.World.Create(new Name("Stack of " + context.Items[0].Get<Name>().Value + " and " + context.Items[1].Get<Name>().Value), new Tags("stack"));
newItem.AddRelationship<ParentOf>(context.Items[0]);
newItem.AddRelationship<ParentOf>(context.Items[1]);
return newItem;
}
}
public record PotatoCookRecipe() : Recipe(Name: "Potato_Cook", public record PotatoCookRecipe() : Recipe(Name: "Potato_Cook",
BlueprintIds: null, BlueprintIds: null,
RequireAll: [false], RequireAll: [false],
@@ -183,8 +211,9 @@ public record PotatoCookRecipe() : Recipe(Name: "Potato_Cook",
private Entity NewPotato(in RecipeContext context) private Entity NewPotato(in RecipeContext context)
{ {
Span<Entity> span = stackalloc Entity[1]; Span<Entity> span = stackalloc Entity[1];
var signature = context.World.GetSignature(context.Items[0]); span[0] = context.World.Create(context.Items[0].GetComponentTypes());
context.World.Create(span, signature, 1); // var signature = context.World.GetSignature(context.Items[0]);
// context.World.Create(span, signature, 1);
CopyEntity(context.Items[0], span[0], context.World); CopyEntity(context.Items[0], span[0], context.World);
span[0].Set(new Name("Cooked_Potato")); span[0].Set(new Name("Cooked_Potato"));

View File

@@ -28,6 +28,7 @@ public class Recipes : IRecipes
builder.Add(new PotatoCookRecipe()); builder.Add(new PotatoCookRecipe());
builder.Add(new BurnItemRecipe()); builder.Add(new BurnItemRecipe());
builder.Add(new OnionSliceRecipe()); builder.Add(new OnionSliceRecipe());
builder.Add(new StackRecipe());
_sortedRecipes = builder.Build(); _sortedRecipes = builder.Build();
_recipes = builder.CompiledRecipes.Select(f => f.RecipeRef).ToDictionary(k => new RecipeName(k.Name)); _recipes = builder.CompiledRecipes.Select(f => f.RecipeRef).ToDictionary(k => new RecipeName(k.Name));
} }
@@ -80,7 +81,8 @@ public class Recipes : IRecipes
{ {
return false; return false;
} }
if (tags.ContainsAny(compiled.ExcludeTags))
if (compiled.ExcludeTags.Count != 0 && tags.ContainsAny(compiled.ExcludeTags))
{ {
return false; return false;
} }

49365
src/VoxelGrid/Stress.tscn Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -43,10 +43,12 @@ public class BacterialSystem : BaseSystem<World, float>
//Will Liklely be the game instead of a node like this //Will Liklely be the game instead of a node like this
[Meta(typeof(IAutoNode))]// [Tool] [Meta(typeof(IAutoNode))]// [Tool]
public partial class VoxelGridNode : Node3D, IProvide<IVoxelGridRegistry>, IProvide<IItemRenderer>, IProvide<IRecipes>, IProvide<IBlueprintManger>, IProvide<World> public partial class VoxelGridNode : Node3D, IProvide<IVoxelGridRegistry>, IProvide<IItemRenderer>, IProvide<IRecipes>, IProvide<IBlueprintManger>, IProvide<World>, IProvide<IFoodFactoryApi>
{ {
public override void _Notification(int what) => this.Notify(what); public override void _Notification(int what) => this.Notify(what);
private IFoodFactoryApi _api = default!;
IFoodFactoryApi IProvide<IFoodFactoryApi>.Value() => _api;
private World _world = default!; private World _world = default!;
World IProvide<World>.Value() => _world; World IProvide<World>.Value() => _world;
private IVoxelGridRegistry _voxelGridRegistry = default!; private IVoxelGridRegistry _voxelGridRegistry = default!;
@@ -63,24 +65,40 @@ public partial class VoxelGridNode : Node3D, IProvide<IVoxelGridRegistry>, IProv
// GD.Print(); // GD.Print();
base._Ready(); base._Ready();
_voxelGridRegistry = new VoxelRegistry(); _voxelGridRegistry = new VoxelRegistry();
_itemRenderer = new ItemRenderSimple(); _itemRenderer = new ItemRenderBuffered();
_blueprintManger = new BlueprintManger(); _blueprintManger = new BlueprintManger();
_recipes = new Recipes(); _recipes = new Recipes();
_world = World.Create(); _world = World.Create();
_systems = new Group<float>("Items", new BacterialSystem(_world)); _systems = new Group<float>("Items", new BacterialSystem(_world));
var tickManger = new TickManger();
AddChild(_itemRenderer as Node); AddChild(_itemRenderer as Node);
Timer timer = new Timer() { WaitTime = .25f, Autostart = true };//TEST var delta = .2f;
Timer timer = new Timer() { WaitTime = delta, Autostart = true };//TEST
AddChild(timer);//TEST AddChild(timer);//TEST
// timer.Timeout += _itemRenderer.Tick;//TEST // timer.Timeout += _itemRenderer.Tick;//TEST
_api = new FoodFactoryApi()
{
BlueprintManger = _blueprintManger,
Recipes = _recipes,
ItemRenderer = _itemRenderer,
TickManger = tickManger,
GridRegistry = _voxelGridRegistry
};
int tick = 0;
timer.Timeout += () => timer.Timeout += () =>
{ {
_systems.BeforeUpdate(.25f); _systems.BeforeUpdate(delta);
_systems.Update(.25f); _systems.Update(delta);
_systems.AfterUpdate(.25f); _systems.AfterUpdate(delta);
tickManger.BroadCast(new(tick, delta));
tick++;
}; };
_systems.Initialize(); _systems.Initialize();
this.Provide(); this.Provide();
} }

View File

@@ -1,13 +1,14 @@
[gd_scene format=3 uid="uid://dfacxkkkc0v10"] [gd_scene format=3 uid="uid://dfacxkkkc0v10"]
[ext_resource type="Script" uid="uid://dcrb286hmpli" path="res://src/VoxelGrid/VoxelGridNode.cs" id="1_tsdpe"] [ext_resource type="Script" uid="uid://dcrb286hmpli" path="res://src/VoxelGrid/VoxelGridNode.cs" id="1_tsdpe"]
[ext_resource type="Script" uid="uid://cnkblltup5guy" path="res://src/VoxelGrid/OvenTest.cs" id="3_r7dgx"] [ext_resource type="Script" uid="uid://cnkblltup5guy" path="res://src/Equipment/OvenTest.cs" id="3_r7dgx"]
[ext_resource type="PackedScene" uid="uid://bktqs1lw6go4" path="res://src/VoxelGrid/ItemSpawner.tscn" id="5_mxaon"] [ext_resource type="PackedScene" uid="uid://bktqs1lw6go4" path="res://src/VoxelGrid/ItemSpawner.tscn" id="5_mxaon"]
[ext_resource type="PackedScene" uid="uid://h00mq2srsbfa" path="res://assets/kenney_conveyor-kit/Models/GLB format/door.glb" id="5_wk2t5"] [ext_resource type="PackedScene" uid="uid://h00mq2srsbfa" path="res://assets/kenney_conveyor-kit/Models/GLB format/door.glb" id="5_wk2t5"]
[ext_resource type="Script" uid="uid://ee5aoxi8mjnw" path="res://src/VoxelGrid/BeltPort.cs" id="6_2wkfx"] [ext_resource type="Script" uid="uid://ee5aoxi8mjnw" path="res://src/Equipment/BeltPort.cs" id="6_2wkfx"]
[ext_resource type="PackedScene" uid="uid://c4h7mwnfrdesg" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn" id="6_mxaon"] [ext_resource type="PackedScene" uid="uid://c4h7mwnfrdesg" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn" id="6_mxaon"]
[ext_resource type="Script" uid="uid://opbkqoaa7x2n" path="res://src/VoxelGrid/Balancer.cs" id="7_2wkfx"] [ext_resource type="Script" uid="uid://opbkqoaa7x2n" path="res://src/Equipment/Balancer.cs" id="7_2wkfx"]
[ext_resource type="Script" uid="uid://yec84plemjv1" path="res://src/VoxelGrid/SlicerTest.cs" id="8_2lg7i"] [ext_resource type="Script" uid="uid://yec84plemjv1" path="res://src/Equipment/SlicerTest.cs" id="8_2lg7i"]
[ext_resource type="Script" uid="uid://culjdbwllmsyk" path="res://src/Equipment/StackerTest.cs" id="8_e2skk"]
[sub_resource type="Curve3D" id="Curve3D_mxaon"] [sub_resource type="Curve3D" id="Curve3D_mxaon"]
_data = { _data = {
@@ -64,6 +65,12 @@ transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 5, 0
[node name="ConveyorBeltStraight27" parent="." unique_id=1546024538 instance=ExtResource("6_mxaon")] [node name="ConveyorBeltStraight27" parent="." unique_id=1546024538 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 5, 0, 6) transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 5, 0, 6)
[node name="ConveyorBeltStraight42" parent="." unique_id=2038052188 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 4, 0, 6)
[node name="ConveyorBeltStraight43" parent="." unique_id=131250040 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 3, 0, 6)
[node name="ConveyorBeltStraight28" parent="." unique_id=1945842047 instance=ExtResource("6_mxaon")] [node name="ConveyorBeltStraight28" parent="." unique_id=1945842047 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 5, 0, 4) transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 5, 0, 4)
@@ -189,14 +196,14 @@ transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0
script = ExtResource("6_2wkfx") script = ExtResource("6_2wkfx")
Face = 4 Face = 4
Width = 1 Width = 1
Access = 1 Access = 2
[node name="Node3D3" type="Node3D" parent="Balancer" unique_id=1846433120] [node name="Node3D3" type="Node3D" parent="Balancer" unique_id=1846433120]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 0) transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 0)
script = ExtResource("6_2wkfx") script = ExtResource("6_2wkfx")
Face = 4 Face = 4
Width = 1 Width = 1
Access = 2 Access = 1
[node name="Node3D4" type="Node3D" parent="Balancer" unique_id=1579207766] [node name="Node3D4" type="Node3D" parent="Balancer" unique_id=1579207766]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 1) transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 1)
@@ -216,6 +223,66 @@ Access = 2
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0.5) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0.5)
mesh = SubResource("BoxMesh_2wkfx") mesh = SubResource("BoxMesh_2wkfx")
[node name="Balancer3" type="Node3D" parent="." unique_id=457806568]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 2)
script = ExtResource("7_2wkfx")
[node name="Node3D" type="Node3D" parent="Balancer3" unique_id=1019210964]
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0, 0)
script = ExtResource("6_2wkfx")
Face = 4
Width = 1
Access = 2
[node name="Node3D3" type="Node3D" parent="Balancer3" unique_id=1520385636]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 0)
script = ExtResource("6_2wkfx")
Face = 4
Width = 1
Access = 1
[node name="Node3D4" type="Node3D" parent="Balancer3" unique_id=2034092939]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 1)
script = ExtResource("6_2wkfx")
Face = 4
Width = 1
Access = 1
[node name="Node3D2" type="Node3D" parent="Balancer3" unique_id=1013256336]
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0, 1)
script = ExtResource("6_2wkfx")
Face = 4
Width = 1
Access = 2
[node name="MeshInstance3D" type="MeshInstance3D" parent="Balancer3" unique_id=920267076]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0.5)
mesh = SubResource("BoxMesh_2wkfx")
[node name="Balancer2" type="Node3D" parent="." unique_id=620786657]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 6)
script = ExtResource("8_e2skk")
[node name="Node3D" type="Node3D" parent="Balancer2" unique_id=1570745328]
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0, 0)
script = ExtResource("6_2wkfx")
Face = 4
Width = 1
Access = 3
PortName = "OutPut"
[node name="Node3D3" type="Node3D" parent="Balancer2" unique_id=1793642533]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 0)
script = ExtResource("6_2wkfx")
Face = 4
Width = 1
Access = 3
PortName = "Input"
[node name="MeshInstance3D" type="MeshInstance3D" parent="Balancer2" unique_id=1733942784]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0.5)
mesh = SubResource("BoxMesh_2wkfx")
[node name="Balancer8" type="Node3D" parent="." unique_id=1783081047] [node name="Balancer8" type="Node3D" parent="." unique_id=1783081047]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 7, 0, -1) transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 7, 0, -1)
script = ExtResource("7_2wkfx") script = ExtResource("7_2wkfx")
@@ -225,14 +292,14 @@ transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0
script = ExtResource("6_2wkfx") script = ExtResource("6_2wkfx")
Face = 4 Face = 4
Width = 1 Width = 1
Access = 1 Access = 2
[node name="Node3D3" type="Node3D" parent="Balancer8" unique_id=51764402] [node name="Node3D3" type="Node3D" parent="Balancer8" unique_id=51764402]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 0) transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 0)
script = ExtResource("6_2wkfx") script = ExtResource("6_2wkfx")
Face = 4 Face = 4
Width = 1 Width = 1
Access = 2 Access = 1
[node name="Node3D4" type="Node3D" parent="Balancer8" unique_id=233486636] [node name="Node3D4" type="Node3D" parent="Balancer8" unique_id=233486636]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 1) transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 1)
@@ -258,6 +325,11 @@ script = ExtResource("8_2lg7i")
[node name="Node3D4" type="Node3D" parent="Slicer" unique_id=1206144064] [node name="Node3D4" type="Node3D" parent="Slicer" unique_id=1206144064]
transform = Transform3D(-1, 0, 8.742277e-08, 0, 1, 0, -8.742277e-08, 0, -1, 0, 0, 0) transform = Transform3D(-1, 0, 8.742277e-08, 0, 1, 0, -8.742277e-08, 0, -1, 0, 0, 0)
script = ExtResource("6_2wkfx")
Face = 4
Width = 1
Access = 1
PortName = "Input"
[node name="Path3D" type="Path3D" parent="Slicer/Node3D4" unique_id=894348025] [node name="Path3D" type="Path3D" parent="Slicer/Node3D4" unique_id=894348025]
curve = SubResource("Curve3D_mxaon") curve = SubResource("Curve3D_mxaon")
@@ -267,6 +339,11 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.870017e-08, 0, -0.6001501)
[node name="Node3D5" type="Node3D" parent="Slicer" unique_id=1113082191] [node name="Node3D5" type="Node3D" parent="Slicer" unique_id=1113082191]
transform = Transform3D(1, 0, -1.7484555e-07, 0, 1, 0, 1.7484555e-07, 0, 1, 0, 0, 0) transform = Transform3D(1, 0, -1.7484555e-07, 0, 1, 0, 1.7484555e-07, 0, 1, 0, 0, 0)
script = ExtResource("6_2wkfx")
Face = 4
Width = 1
Access = 2
PortName = "OutPut"
[node name="Path3D2" type="Path3D" parent="Slicer/Node3D5" unique_id=1352328131] [node name="Path3D2" type="Path3D" parent="Slicer/Node3D5" unique_id=1352328131]
curve = SubResource("Curve3D_mxaon") curve = SubResource("Curve3D_mxaon")