diff --git a/Directory.Packages.props b/Directory.Packages.props
index e23cfad..23f099b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,31 +1,26 @@
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs b/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs
index 32cc10e..9d8e5b4 100644
--- a/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs
+++ b/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs
@@ -2,7 +2,6 @@
using Hexecs.Benchmarks.Map.Common.Positions;
using Hexecs.Benchmarks.Map.Terrains;
using Hexecs.Benchmarks.Map.Utils;
-using Hexecs.Benchmarks.Map.Utils.Sprites;
using Hexecs.Threading;
using Hexecs.Worlds;
diff --git a/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs b/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs
index 39f3d6b..4b605c7 100644
--- a/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs
+++ b/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs
@@ -1,6 +1,8 @@
using Hexecs.Actors.Pipelines;
+using Hexecs.Benchmarks.Map.Common.Positions;
using Hexecs.Benchmarks.Map.Terrains.Assets;
using Hexecs.Benchmarks.Map.Terrains.ValueTypes;
+using Hexecs.Benchmarks.Map.Utils;
using Hexecs.Pipelines;
namespace Hexecs.Benchmarks.Map.Terrains.Commands.Generate;
@@ -9,7 +11,9 @@ internal sealed class GenerateTerrainHandler : ActorCommandHandler 45 and < 55) // river
{
- // river
- > 45 and < 55 => Context.BuildActor(river, args
- .Set(nameof(Terrain.Elevation), Elevation.FromValue(-10))
- .Set(nameof(Terrain.Moisture), Moisture.FromValue(35))),
- // urban concrete
- < 10 when y < 10 => Context.BuildActor(urbanConcrete, args),
- // just ground
- _ => Context.BuildActor(ground, args)
- };
+ Context.BuildActor(river,
+ args.Set(nameof(Terrain.Elevation), Elevation.FromValue(-10))
+ .Set(nameof(Terrain.Moisture), Moisture.FromValue(35)));
+ }
+ else if (x < 10 && y < 10) // urban concrete
+ {
+ Context.BuildActor(urbanConcrete, args);
+ }
+ else // just ground
+ {
+ Context.BuildActor(ground, args);
+ }
}
}
diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs
index e763f7e..6cbda7e 100644
--- a/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs
+++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs
@@ -2,7 +2,6 @@
using Hexecs.Benchmarks.Map.Common.Positions;
using Hexecs.Benchmarks.Map.Common.Visibles;
using Hexecs.Benchmarks.Map.Utils;
-using Hexecs.Benchmarks.Map.Utils.Sprites;
using Hexecs.Worlds;
using Microsoft.Xna.Framework.Graphics;
diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs
index a692019..f7e7967 100644
--- a/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs
+++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs
@@ -1,10 +1,10 @@
+using Hexecs.Benchmarks.Map.Common.Positions;
using Hexecs.Benchmarks.Map.Terrains.Assets;
using Hexecs.Benchmarks.Map.Terrains.Commands.Generate;
using Hexecs.Configurations;
using Hexecs.Dependencies;
using Hexecs.Worlds;
using Microsoft.Xna.Framework.Content;
-using Microsoft.Xna.Framework.Graphics;
namespace Hexecs.Benchmarks.Map.Terrains;
@@ -23,7 +23,7 @@ public static ActorContextBuilder AddTerrain(this ActorContextBuilder builder)
builder.CreateCommandHandler();
builder.CreateDrawSystem();
-
+
return builder;
}
@@ -32,6 +32,11 @@ public static WorldBuilder UseTerrain(this WorldBuilder builder)
builder
.UseAddAssetSource(new TerrainAssetSource());
+ builder
+ .UseScoped(ctx => new ActorDictionary(
+ context: ctx.GetRequiredService(),
+ keyExtractor: terrain => terrain.Grid));
+
builder
.UseSingleton(ctx => new TerrainSpriteAtlas(
contentManager: ctx.GetRequiredService(),
diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs
index d5a6998..fce1f14 100644
--- a/src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs
+++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs
@@ -1,7 +1,6 @@
using Hexecs.Benchmarks.Map.Terrains.ValueTypes;
using Hexecs.Benchmarks.Map.Utils.Sprites;
using Microsoft.Xna.Framework.Content;
-using Microsoft.Xna.Framework.Graphics;
namespace Hexecs.Benchmarks.Map.Terrains;
diff --git a/src/Hexecs.Benchmarks.City/Utils/PointExtensions.cs b/src/Hexecs.Benchmarks.City/Utils/PointExtensions.cs
index 77958bc..a3c46ea 100644
--- a/src/Hexecs.Benchmarks.City/Utils/PointExtensions.cs
+++ b/src/Hexecs.Benchmarks.City/Utils/PointExtensions.cs
@@ -2,8 +2,11 @@ namespace Hexecs.Benchmarks.Map.Utils;
public static class PointExtensions
{
- public static void GetNeighborPoints(int x, int y, ref Span neighbors)
+ public static void GetNeighborPoints(this in Point point, ref Span neighbors)
{
+ var x = point.X;
+ var y = point.Y;
+
neighbors[0] = new Point(x - 1, y);
neighbors[1] = new Point(x + 1, y);
neighbors[2] = new Point(x, y - 1);
diff --git a/src/Hexecs.Benchmarks/Actors/ActorCheckComponentExistsBenchmark.cs b/src/Hexecs.Benchmarks/Actors/ActorCheckComponentExistsBenchmark.cs
new file mode 100644
index 0000000..4670121
--- /dev/null
+++ b/src/Hexecs.Benchmarks/Actors/ActorCheckComponentExistsBenchmark.cs
@@ -0,0 +1,194 @@
+using System.Runtime.CompilerServices;
+using Friflo.Engine.ECS;
+using Hexecs.Benchmarks.Mocks.ActorComponents;
+using Hexecs.Worlds;
+using World = Hexecs.Worlds.World;
+
+namespace Hexecs.Benchmarks.Actors;
+
+// BenchmarkDotNet v0.15.8, Windows 11 (10.0.22621.4317/22H2/2022Update/SunValley2)
+// Intel Xeon CPU E5-2697 v3 2.60GHz, 2 CPU, 56 logical and 28 physical cores
+// .NET SDK 10.0.100
+// [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+// .NET 10.0 : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+//
+// Job=.NET 10.0 Runtime=.NET 10.0
+//
+// | Method | Count | Mean | Ratio | Allocated | Alloc Ratio |
+// |----------------- |------- |----------:|------:|----------:|------------:|
+// | Hexecs_Is | 10000 | 20.42 us | 0.96 | - | NA |
+// | Hexecs_Has | 10000 | 21.19 us | 1.00 | - | NA |
+// | Hexecs_Reference | 10000 | 24.30 us | 1.15 | - | NA |
+// | FriFlo_Has | 10000 | 40.28 us | 1.90 | - | NA |
+// | DefaultEcs_Has | 10000 | 73.24 us | 3.46 | - | NA |
+// | | | | | | |
+// | Hexecs_Is | 100000 | 204.98 us | 0.94 | - | NA |
+// | Hexecs_Has | 100000 | 219.12 us | 1.00 | - | NA |
+// | Hexecs_Reference | 100000 | 251.83 us | 1.15 | - | NA |
+// | FriFlo_Has | 100000 | 409.48 us | 1.87 | - | NA |
+// | DefaultEcs_Has | 100000 | 712.00 us | 3.25 | - | NA |
+//
+// ------------------------------------------------------------------------------------
+//
+// BenchmarkDotNet v0.15.8, macOS Tahoe 26.2 (25C56) [Darwin 25.2.0]
+// Apple M3 Max, 1 CPU, 16 logical and 16 physical cores
+// .NET SDK 10.0.101
+// [Host] : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+// .NET 10.0 : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+//
+// Job=.NET 10.0 Runtime=.NET 10.0
+//
+// | Method | Mean | Ratio | Allocated | Alloc Ratio |
+// |----------------- |----------:|------:|----------:|------------:|
+// | Hexecs_Is | 12.76 us | 0.93 | - | NA |
+// | Hexecs_Has | 13.79 us | 1.00 | - | NA |
+// | Hexecs_Reference | 15.44 us | 1.12 | - | NA |
+// | DefaultEcs_Has | 25.32 us | 1.84 | - | NA |
+// | | | | | |
+// | Hexecs_Is | 127.64 us | 0.92 | - | NA |
+// | Hexecs_Has | 139.17 us | 1.00 | - | NA |
+// | Hexecs_Reference | 155.12 us | 1.11 | - | NA |
+// | DefaultEcs_Has | 255.36 us | 1.83 | - | NA |
+
+[SimpleJob(RuntimeMoniker.Net10_0)]
+[Orderer(SummaryOrderPolicy.FastestToSlowest)]
+[MeanColumn, MemoryDiagnoser]
+[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
+[JsonExporterAttribute.Full]
+[JsonExporterAttribute.FullCompressed]
+[BenchmarkCategory("Actors")]
+public class ActorCheckComponentExistsBenchmark
+{
+ [Params(10_000, 100_000)] public int Count;
+
+ private ActorContext _context = null!;
+ private DefaultEcs.World _defaultWorld = null!;
+ private EntityStore _frifloWorld = null!;
+ private ArchetypeQuery _frifloAllEntitiesQuery = null!;
+ private World _world = null!;
+
+ [Benchmark(Baseline = true)]
+ public int Hexecs_Has()
+ {
+ var result = 0;
+
+ // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
+ foreach (var actor in _context)
+ {
+ if (actor.Has()) result++;
+ }
+
+ return result;
+ }
+
+ [Benchmark]
+ public int DefaultEcs_Has()
+ {
+ var result = 0;
+
+ // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
+ foreach (var entity in _defaultWorld)
+ {
+ if (entity.Has())
+ {
+ result++;
+ }
+ }
+
+ return result;
+ }
+
+ [Benchmark]
+ public int FriFlo_Has()
+ {
+ var result = 0;
+
+ // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
+ foreach (var entity in _frifloAllEntitiesQuery.Entities)
+ {
+ if (entity.HasComponent())
+ {
+ result++;
+ }
+ }
+
+ return result;
+ }
+
+ [Benchmark]
+ public int Hexecs_Is()
+ {
+ var result = 0;
+
+ // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
+ foreach (var actor in _context)
+ {
+ if (actor.Is(out _))
+ {
+ result++;
+ }
+ }
+
+ return result;
+ }
+
+ [Benchmark]
+ public int Hexecs_Reference()
+ {
+ var result = 0;
+
+ // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
+ foreach (var actor in _context)
+ {
+ ref var reference = ref actor.TryGetRef();
+ if (!Unsafe.IsNullRef(ref reference))
+ {
+ result++;
+ }
+ }
+
+ return result;
+ }
+
+ [GlobalCleanup]
+ public void Cleanup()
+ {
+ _defaultWorld.Dispose();
+ _defaultWorld = null!;
+
+ _frifloWorld = null!;
+
+ _world.Dispose();
+ _world = null!;
+ }
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _defaultWorld = new DefaultEcs.World();
+ _frifloWorld = new EntityStore();
+ _frifloAllEntitiesQuery = _frifloWorld.Query();
+ _world = new WorldBuilder().Build();
+ _context = _world.Actors;
+
+ for (var i = 0; i < Count; i++)
+ {
+ var actor = _context.CreateActor();
+ actor.Add(new Attack());
+ actor.Add(new Defence());
+
+ var defaultEntity = _defaultWorld.CreateEntity();
+ defaultEntity.Set();
+ defaultEntity.Set();
+
+ var frifloEntity = _frifloWorld.CreateEntity(new Attack(), new Defence());
+
+ if (i % 10 != 0) continue;
+
+ actor.Add(new Speed());
+
+ defaultEntity.Set();
+ frifloEntity.Add(new Speed());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Actors/ActorCreateAddComponentsDestroyBenchmark.cs b/src/Hexecs.Benchmarks/Actors/ActorCreateAddComponentsDestroyBenchmark.cs
new file mode 100644
index 0000000..38f1345
--- /dev/null
+++ b/src/Hexecs.Benchmarks/Actors/ActorCreateAddComponentsDestroyBenchmark.cs
@@ -0,0 +1,252 @@
+using DefaultEcs;
+using Friflo.Engine.ECS;
+using Hexecs.Benchmarks.Mocks.ActorComponents;
+using Hexecs.Worlds;
+using World = Hexecs.Worlds.World;
+
+namespace Hexecs.Benchmarks.Actors;
+
+// BenchmarkDotNet v0.15.8, Windows 11 (10.0.22621.4317/22H2/2022Update/SunValley2)
+// Intel Xeon CPU E5-2697 v3 2.60GHz, 2 CPU, 56 logical and 28 physical cores
+// .NET SDK 10.0.100
+// [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+// .NET 10.0 : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+//
+// Job=.NET 10.0 Runtime=.NET 10.0
+//
+// | Method | Count | Mean | Ratio | Gen0 | Allocated | Alloc Ratio |
+// |---------------------------- |------- |-------------:|------:|-------:|-----------:|------------:|
+// | FriFlo_CreateAddDestroy | 1000 | 153.5 us | 0.29 | - | - | NA |
+// | DefaultEcs_CreateAddDestroy | 1000 | 401.1 us | 0.77 | 1.4648 | 32000 B | NA |
+// | Hexecs_CreateAddDestroy | 1000 | 523.6 us | 1.00 | - | - | NA |
+// | | | | | | | |
+// | FriFlo_CreateAddDestroy | 100000 | 16,519.0 us | 0.25 | - | 40 B | 1.00 |
+// | Hexecs_CreateAddDestroy | 100000 | 65,924.0 us | 1.00 | - | 40 B | 1.00 |
+// | DefaultEcs_CreateAddDestroy | 100000 | 105,603.9 us | 1.60 | - | 3200040 B | 80,001.00 |
+// | | | | | | | |
+// | FriFlo_CreateAddDestroy | 500000 | 85,496.9 us | 0.18 | - | 40 B | 1.00 |
+// | Hexecs_CreateAddDestroy | 500000 | 474,476.8 us | 1.00 | - | 40 B | 1.00 |
+// | DefaultEcs_CreateAddDestroy | 500000 | 539,368.3 us | 1.14 | - | 16000040 B | 400,001.00 |
+//
+// ------------------------------------------------------------------------------------
+//
+// BenchmarkDotNet v0.15.8, macOS Tahoe 26.2 (25C56) [Darwin 25.2.0]
+// Apple M3 Max, 1 CPU, 16 logical and 16 physical cores
+// .NET SDK 10.0.101
+// [Host] : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+// .NET 10.0 : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+//
+// Job=.NET 10.0 Runtime=.NET 10.0
+//
+// | Method | Count | Mean | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
+// |---------------------------- |------- |-------------:|------:|----------:|---------:|-----------:|------------:|
+// | Hexecs_CreateAddDestroy | 1000 | 176.0 us | 1.00 | - | - | - | NA |
+// | DefaultEcs_CreateAddDestroy | 1000 | 207.3 us | 1.18 | 3.6621 | - | 32000 B | NA |
+// | | | | | | | | |
+// | Hexecs_CreateAddDestroy | 100000 | 19,069.0 us | 1.00 | - | - | 40 B | 1.00 |
+// | DefaultEcs_CreateAddDestroy | 100000 | 22,847.4 us | 1.20 | 375.0000 | 156.2500 | 3200040 B | 80,001.00 |
+// | | | | | | | | |
+// | Hexecs_CreateAddDestroy | 500000 | 113,318.9 us | 1.00 | - | - | 40 B | 1.00 |
+// | DefaultEcs_CreateAddDestroy | 500000 | 121,507.3 us | 1.07 | 1800.0000 | 800.0000 | 16000040 B | 400,001.00 |
+
+[SimpleJob(RuntimeMoniker.Net10_0)]
+[Orderer(SummaryOrderPolicy.FastestToSlowest)]
+[MeanColumn, MemoryDiagnoser]
+[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
+[JsonExporterAttribute.Full]
+[JsonExporterAttribute.FullCompressed]
+[BenchmarkCategory("Actors")]
+public class ActorCreateAddComponentsDestroyBenchmark
+{
+ [Params(1_000, 100_000, 500_000)] public int Count;
+
+ private List _defaultEntities = null!;
+ private List _defaultSets = null!;
+ private DefaultEcs.World _defaultWorld = null!;
+
+ private List _frifloEntities = null!;
+ private List _frifloQueries = null!;
+ private EntityStore _frifloWorld = null!;
+
+ private List _hexecsActors = null!;
+ private ActorContext _hexecsContext = null!;
+ private List _hexecsFilters = null!;
+ private World _hexecsWorld = null!;
+
+ [Benchmark(Baseline = true)]
+ public int Hexecs_CreateAddDestroy()
+ {
+ _hexecsActors.Clear();
+
+ for (var i = 0; i < Count; i++)
+ {
+ var actor = _hexecsContext.CreateActor();
+ actor.Add(new Attack { Value = i });
+ actor.Add(new Defence());
+ actor.Add(new Speed());
+
+ _hexecsActors.Add(actor);
+ }
+
+ foreach (var actor in _hexecsActors)
+ {
+ actor.Remove();
+ actor.Remove();
+ actor.Remove();
+
+ actor.Destroy();
+ }
+
+ return _hexecsFilters.Sum(static x => x.Length);
+ }
+
+ [Benchmark]
+ public int DefaultEcs_CreateAddDestroy()
+ {
+ _defaultEntities.Clear();
+
+ for (var i = 0; i < Count; i++)
+ {
+ var entity = _defaultWorld.CreateEntity();
+ entity.Set(new Attack { Value = i });
+ entity.Set(new Defence());
+ entity.Set(new Speed());
+
+ _defaultEntities.Add(entity);
+ }
+
+ foreach (var entity in _defaultEntities)
+ {
+ entity.Remove();
+ entity.Remove();
+ entity.Remove();
+
+ entity.Dispose();
+ }
+
+ return _defaultSets.Sum(static x => x.Count);
+ }
+
+ [Benchmark]
+ public int FriFlo_CreateAddDestroy()
+ {
+ _frifloEntities.Clear();
+
+ for (var i = 0; i < Count; i++)
+ {
+ var entity = _frifloWorld.CreateEntity();
+ entity.AddComponent(new Attack { Value = i });
+ entity.AddComponent(new Defence());
+ entity.AddComponent(new Speed());
+
+ _frifloEntities.Add(entity);
+ }
+
+ foreach (var entity in _frifloEntities)
+ {
+ entity.RemoveComponent();
+ entity.RemoveComponent();
+ entity.RemoveComponent();
+
+ entity.DeleteEntity();
+ }
+
+ return _frifloQueries.Sum(static x => x.Count);
+ }
+
+ [GlobalCleanup]
+ public void Cleanup()
+ {
+ _defaultWorld.Dispose();
+ _defaultWorld = null!;
+
+ _frifloWorld = null!;
+
+ _hexecsWorld.Dispose();
+ _hexecsWorld = null!;
+ }
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _defaultEntities = new List(Count);
+ _defaultWorld = new DefaultEcs.World();
+ _defaultSets =
+ [
+ _defaultWorld.GetEntities().With().AsSet(),
+ _defaultWorld.GetEntities().With().AsSet(),
+ _defaultWorld.GetEntities().With().AsSet(),
+ _defaultWorld.GetEntities().With().With().AsSet(),
+ _defaultWorld.GetEntities().With().With().AsSet(),
+ _defaultWorld.GetEntities().With().With().AsSet(),
+ _defaultWorld.GetEntities().With().With().With().AsSet()
+ ];
+
+ _frifloEntities = new List(Count);
+ _frifloWorld = new EntityStore();
+ _frifloQueries =
+ [
+ _frifloWorld.Query(),
+ _frifloWorld.Query(),
+ _frifloWorld.Query(),
+ _frifloWorld.Query(),
+ _frifloWorld.Query(),
+ _frifloWorld.Query(),
+ _frifloWorld.Query()
+ ];
+
+ _hexecsActors = new List(Count);
+ _hexecsWorld = new WorldBuilder().Build();
+ _hexecsContext = _hexecsWorld.Actors;
+ _hexecsFilters =
+ [
+ _hexecsContext.Filter(),
+ _hexecsContext.Filter(),
+ _hexecsContext.Filter(),
+ _hexecsContext.Filter(),
+ _hexecsContext.Filter(),
+ _hexecsContext.Filter(),
+ _hexecsContext.Filter()
+ ];
+
+ // warmup
+ for (var i = 0; i < Count; i++)
+ {
+ var defaultEntity = _defaultWorld.CreateEntity();
+ defaultEntity.Set();
+ defaultEntity.Set();
+ defaultEntity.Set();
+
+ _defaultEntities.Add(defaultEntity);
+
+ var frifloEntity = _frifloWorld.CreateEntity(new Attack(), new Defence(), new Speed());
+ _frifloEntities.Add(frifloEntity);
+
+ var actor = _hexecsContext.CreateActor();
+ actor.Add(new Attack());
+ actor.Add(new Defence());
+ actor.Add(new Speed());
+
+ _hexecsActors.Add(actor);
+ }
+
+ foreach (var entity in _defaultEntities)
+ {
+ entity.Dispose();
+ }
+
+ foreach (var entity in _frifloEntities)
+ {
+ entity.DeleteEntity();
+ }
+
+ foreach (var actor in _hexecsActors)
+ {
+ actor.Destroy();
+ }
+
+ _defaultEntities.Clear();
+ _frifloEntities.Clear();
+ _hexecsActors.Clear();
+ }
+}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Actors/ActorFilter2EnumerationBenchmark.cs b/src/Hexecs.Benchmarks/Actors/ActorFilter2EnumerationBenchmark.cs
index adef7f3..faaec4a 100644
--- a/src/Hexecs.Benchmarks/Actors/ActorFilter2EnumerationBenchmark.cs
+++ b/src/Hexecs.Benchmarks/Actors/ActorFilter2EnumerationBenchmark.cs
@@ -1,4 +1,4 @@
-using Hexecs.Benchmarks.Mocks;
+using Hexecs.Benchmarks.Mocks.ActorComponents;
using Hexecs.Worlds;
namespace Hexecs.Benchmarks.Actors;
@@ -15,6 +15,24 @@ namespace Hexecs.Benchmarks.Actors;
// |----------- |---------:|------:|----------:|------------:|
// | DefaultEcs | 165.8 us | 0.70 | - | NA |
// | Hexecs | 236.0 us | 1.00 | - | NA |
+//
+// ------------------------------------------------------------------------------------
+//
+// BenchmarkDotNet v0.15.8, macOS Tahoe 26.2 (25C56) [Darwin 25.2.0]
+// Apple M3 Max, 1 CPU, 16 logical and 16 physical cores
+// .NET SDK 10.0.101
+// [Host] : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+// .NET 10.0 : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+//
+// Job=.NET 10.0 Runtime=.NET 10.0
+//
+// | Method | Count | Mean | Ratio | Allocated | Alloc Ratio |
+// |----------- |------- |-----------:|------:|----------:|------------:|
+// | DefaultEcs | 10000 | 9.140 us | 0.88 | - | NA |
+// | Hexecs | 10000 | 10.444 us | 1.00 | - | NA |
+// | | | | | | |
+// | DefaultEcs | 100000 | 89.176 us | 0.88 | - | NA |
+// | Hexecs | 100000 | 101.793 us | 1.00 | - | NA |
[SimpleJob(RuntimeMoniker.Net10_0)]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
@@ -25,8 +43,7 @@ namespace Hexecs.Benchmarks.Actors;
[BenchmarkCategory("Actors")]
public class ActorFilter2EnumerationBenchmark
{
- [Params(10_000, 100_000)]
- public int Count;
+ [Params(10_000, 100_000)] public int Count;
private ActorFilter _filter = null!;
private World _world = null!;
@@ -81,7 +98,7 @@ public void Setup()
_defaultEntitySet = _defaultWorld.GetEntities().With().With().AsSet();
_filter = _world.Actors.Filter();
-
+
var context = _world.Actors;
for (var i = 0; i < Count; i++)
{
diff --git a/src/Hexecs.Benchmarks/Actors/ActorFilter3EnumerationBenchmark.cs b/src/Hexecs.Benchmarks/Actors/ActorFilter3EnumerationBenchmark.cs
index a97f5a9..a8903e4 100644
--- a/src/Hexecs.Benchmarks/Actors/ActorFilter3EnumerationBenchmark.cs
+++ b/src/Hexecs.Benchmarks/Actors/ActorFilter3EnumerationBenchmark.cs
@@ -1,4 +1,5 @@
-using Hexecs.Benchmarks.Mocks;
+using Friflo.Engine.ECS;
+using Hexecs.Benchmarks.Mocks.ActorComponents;
using Hexecs.Worlds;
using World = Hexecs.Worlds.World;
@@ -6,16 +7,41 @@ namespace Hexecs.Benchmarks.Actors;
// BenchmarkDotNet v0.15.8, Windows 11 (10.0.22621.4317/22H2/2022Update/SunValley2)
// Intel Xeon CPU E5-2697 v3 2.60GHz, 2 CPU, 56 logical and 28 physical cores
-// .NET SDK 10.0.100
-// [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
-// .NET 10.0 : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+// .NET SDK 10.0.100
+// [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+// .NET 10.0 : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
//
// Job=.NET 10.0 Runtime=.NET 10.0
//
-// | Method | Mean | Ratio | Allocated | Alloc Ratio |
-// |----------- |---------:|------:|----------:|------------:|
-// | Hexecs | 69.55 us | 1.00 | - | NA |
-// | DefaultEcs | 69.89 us | 1.00 | - | NA |
+// | Method | Count | Mean | Ratio | Allocated | Alloc Ratio |
+// |-------------- |------- |----------:|------:|----------:|------------:|
+// | FriFlo_Chunks | 10000 | 16.28 us | 0.63 | - | NA |
+// | Hexecs | 10000 | 25.99 us | 1.00 | - | NA |
+// | FriFlo | 10000 | 26.76 us | 1.03 | 88 B | NA |
+// | DefaultEcs | 10000 | 29.78 us | 1.15 | - | NA |
+// | | | | | | |
+// | FriFlo_Chunks | 100000 | 156.83 us | 0.58 | - | NA |
+// | FriFlo | 100000 | 263.76 us | 0.98 | 88 B | NA |
+// | Hexecs | 100000 | 268.25 us | 1.00 | - | NA |
+// | DefaultEcs | 100000 | 289.78 us | 1.08 | - | NA |
+//
+// ------------------------------------------------------------------------------------
+//
+// BenchmarkDotNet v0.15.8, macOS Tahoe 26.2 (25C56) [Darwin 25.2.0]
+// Apple M3 Max, 1 CPU, 16 logical and 16 physical cores
+// .NET SDK 10.0.101
+// [Host] : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+// .NET 10.0 : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+//
+// Job=.NET 10.0 Runtime=.NET 10.0
+//
+// | Method | Count | Mean | Ratio | Allocated | Alloc Ratio |
+// |----------- |------- |----------:|------:|----------:|------------:|
+// | DefaultEcs | 10000 | 13.52 us | 0.95 | - | NA |
+// | Hexecs | 10000 | 14.28 us | 1.00 | - | NA |
+// | | | | | | |
+// | DefaultEcs | 100000 | 125.76 us | 0.90 | - | NA |
+// | Hexecs | 100000 | 139.85 us | 1.00 | - | NA |
[SimpleJob(RuntimeMoniker.Net10_0)]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
@@ -26,8 +52,7 @@ namespace Hexecs.Benchmarks.Actors;
[BenchmarkCategory("Actors")]
public class ActorFilter3EnumerationBenchmark
{
- [Params(10_000, 100_000)]
- public int Count;
+ [Params(10_000, 100_000)] public int Count;
private ActorFilter _filter = null!;
private World _world = null!;
@@ -35,6 +60,9 @@ public class ActorFilter3EnumerationBenchmark
private DefaultEcs.World _defaultWorld = null!;
private DefaultEcs.EntitySet _defaultEntitySet = null!;
+ private EntityStore _frifloWorld = null!;
+ private ArchetypeQuery _frifloQuery = null!;
+
[Benchmark(Baseline = true)]
public int Hexecs()
{
@@ -67,6 +95,43 @@ public int DefaultEcs()
return result;
}
+ [Benchmark]
+ public int FriFlo()
+ {
+ var result = 0;
+
+ _frifloQuery.ForEachEntity((ref attack, ref defence, ref speed, _) =>
+ {
+ result += attack.Value +
+ defence.Value +
+ speed.Value;
+ });
+
+ return result;
+ }
+
+ [Benchmark]
+ public int FriFlo_Chunks()
+ {
+ var result = 0;
+
+ foreach (var queryChunk in _frifloQuery.Chunks)
+ {
+ var attacks = queryChunk.Chunk1;
+ var defences = queryChunk.Chunk2;
+ var speeds = queryChunk.Chunk3;
+
+ for (var i = 0; i < queryChunk.Length; i++)
+ {
+ result += attacks[i].Value +
+ defences[i].Value +
+ speeds[i].Value;
+ }
+ }
+
+ return result;
+ }
+
[GlobalCleanup]
public void Cleanup()
{
@@ -81,23 +146,29 @@ public void Cleanup()
public void Setup()
{
_defaultWorld = new DefaultEcs.World();
+ _frifloWorld = new EntityStore();
_world = new WorldBuilder().Build();
_defaultEntitySet = _defaultWorld.GetEntities().With().With().With().AsSet();
_filter = _world.Actors.Filter();
+ _frifloQuery = _frifloWorld.Query();
var context = _world.Actors;
for (var i = 0; i < Count; i++)
{
+ var attack = new Attack { Value = i };
+
var actor = context.CreateActor();
- actor.Add(new Attack());
+ actor.Add(in attack);
actor.Add(new Defence());
actor.Add(new Speed());
var defaultEntity = _defaultWorld.CreateEntity();
- defaultEntity.Set();
+ defaultEntity.Set(in attack);
defaultEntity.Set();
defaultEntity.Set();
+
+ _frifloWorld.CreateEntity(attack, new Defence(), new Speed());
}
}
}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Actors/ActorHierarchyBenchmark.cs b/src/Hexecs.Benchmarks/Actors/ActorHierarchyBenchmark.cs
new file mode 100644
index 0000000..98e5b5f
--- /dev/null
+++ b/src/Hexecs.Benchmarks/Actors/ActorHierarchyBenchmark.cs
@@ -0,0 +1,84 @@
+using Hexecs.Worlds;
+using System.Buffers;
+
+namespace Hexecs.Benchmarks.Actors;
+
+[SimpleJob(RuntimeMoniker.Net10_0)]
+[MeanColumn, MemoryDiagnoser]
+[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
+[BenchmarkCategory("Actors")]
+public class ActorHierarchyBenchmark
+{
+ [Params(100, 1_000, 2_000)] public int Count;
+
+ private ActorContext _actorContext = null!;
+ private Actor[] _parents = null!;
+ private Actor[] _children = null!;
+ private World _world = null!;
+
+ [Benchmark]
+ public int Do()
+ {
+ // Часть 1: Построение иерархии
+ // Для каждого родителя добавляем Count детей (аналогично бенчмарку отношений)
+ var childIdx = 0;
+ for (var i = 0; i < _parents.Length; i++)
+ {
+ var parent = _parents[i];
+ for (var j = 0; j < Count; j++)
+ {
+ parent.AddChild(_children[childIdx++]);
+ }
+ }
+
+ var result = 0;
+ var buffer = ArrayPool.Shared.Rent(Count);
+
+ // Часть 2: Итерация и удаление
+ for (var i = 0; i < _parents.Length; i++)
+ {
+ var parent = _parents[i];
+ var children = parent.Children();
+ var k = 0;
+
+ // Сбор детей в буфер
+ foreach (var child in children)
+ {
+ buffer[k++] = child.Id;
+ result++;
+ }
+
+ // Удаление детей
+ for (var j = 0; j < k; j++)
+ {
+ parent.RemoveChild(new Actor(_actorContext, buffer[j]));
+ }
+ }
+
+ ArrayPool.Shared.Return(buffer);
+ return result;
+ }
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _world = new WorldBuilder().Build();
+ _actorContext = _world.Actors;
+
+ _parents = new Actor[Count];
+ _children = new Actor[Count * Count];
+
+ for (var i = 0; i < Count; i++)
+ {
+ _parents[i] = _actorContext.CreateActor();
+ for (var j = 0; j < Count; j++)
+ {
+ var index = (i * Count) + j;
+ _children[index] = _actorContext.CreateActor();
+ }
+ }
+ }
+
+ [GlobalCleanup]
+ public void Cleanup() => _world.Dispose();
+}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Actors/ActorRelationBenchmark.cs b/src/Hexecs.Benchmarks/Actors/ActorRelationBenchmark.cs
new file mode 100644
index 0000000..933763b
--- /dev/null
+++ b/src/Hexecs.Benchmarks/Actors/ActorRelationBenchmark.cs
@@ -0,0 +1,187 @@
+using System.Buffers;
+using Friflo.Engine.ECS;
+using Hexecs.Benchmarks.Mocks.ActorComponents;
+using Hexecs.Worlds;
+
+namespace Hexecs.Benchmarks.Actors;
+
+// BenchmarkDotNet v0.15.8, Windows 11 (10.0.22621.4317/22H2/2022Update/SunValley2)
+// Intel Xeon CPU E5-2697 v3 2.60GHz, 2 CPU, 56 logical and 28 physical cores
+// .NET SDK 10.0.100
+// [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+// .NET 10.0 : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+//
+// Job=.NET 10.0 Runtime=.NET 10.0
+//
+// | Method | Count | Mean | Ratio | Allocated | Alloc Ratio |
+// |------- |------ |--------------:|------:|----------:|------------:|
+// | Hexecs | 10 | 13.32 us | 1.00 | - | NA |
+// | FriFlo | 10 | 15.66 us | 1.18 | - | NA |
+// | | | | | | |
+// | Hexecs | 100 | 1,929.54 us | 1.00 | - | NA |
+// | FriFlo | 100 | 2,301.57 us | 1.19 | - | NA |
+// | | | | | | |
+// | Hexecs | 1000 | 604,757.61 us | 1.00 | - | NA |
+// | FriFlo | 1000 | 807,112.05 us | 1.33 | - | NA |
+//
+// BenchmarkDotNet v0.15.8, macOS Tahoe 26.2 (25C56) [Darwin 25.2.0]
+// Apple M3 Max, 1 CPU, 16 logical and 16 physical cores
+// .NET SDK 10.0.101
+// [Host] : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+// .NET 10.0 : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+//
+// Job=.NET 10.0 Runtime=.NET 10.0
+//
+// | Method | Count | Mean | Allocated |
+// |------- |------ |---------------:|----------:|
+// | Do | 100 | 798.9 us | - |
+// | Do | 1000 | 261,046.6 us | - |
+// | Do | 2000 | 2,049,070.5 us | - |
+
+[SimpleJob(RuntimeMoniker.Net10_0)]
+[Orderer(SummaryOrderPolicy.FastestToSlowest)]
+[MeanColumn, MemoryDiagnoser]
+[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
+[JsonExporterAttribute.Full]
+[JsonExporterAttribute.FullCompressed]
+[BenchmarkCategory("Actors")]
+public class ActorRelationBenchmark
+{
+ [Params(10, 100, 1_000)] public int Count;
+
+ private ActorContext _actorContext = null!;
+ private Actor[] _actorBuffer = null!;
+ private ActorFilter _employeeFilter = null!;
+ private ActorFilter _employerFilter = null!;
+ private World _world = null!;
+
+ private EntityStore _frifloWorld = null!;
+ private Entity[] _frifloBuffer = null!;
+ private ArchetypeQuery _frifloEmployees = null!;
+ private ArchetypeQuery _frifloEmployers = null!;
+
+ [Benchmark(Baseline = true)]
+ public int Hexecs()
+ {
+ // Часть 1: Наполнение
+ using (var employeeEnumerator = _employeeFilter.GetEnumerator())
+ {
+ foreach (var employer in _employerFilter)
+ {
+ for (var i = 0; i < Count; i++)
+ {
+ if (!employeeEnumerator.MoveNext()) break;
+ var employee = employeeEnumerator.Current;
+ employer.AddRelation(employee, new EmployeeAgreement { Salary = i });
+ }
+ }
+ }
+
+ var result = 0;
+ var buffer = _actorBuffer;
+
+ // Часть 2: Удаление
+ foreach (var employer in _employerFilter)
+ {
+ var relations = employer.Relations();
+ var i = 0;
+
+ foreach (var relation in relations)
+ {
+ buffer[i++] = relation;
+ }
+
+ for (var j = 0; j < i; j++)
+ {
+ if (employer.RemoveRelation(buffer[j]))
+ {
+ result++;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [Benchmark]
+ public int FriFlo()
+ {
+ // Часть 1: Наполнение
+ using (var employeeEnumerator = _frifloEmployees.Entities.GetEnumerator())
+ {
+ foreach (var employer in _frifloEmployers.Entities)
+ {
+ for (var i = 0; i < Count; i++)
+ {
+ if (!employeeEnumerator.MoveNext()) break;
+ var employee = employeeEnumerator.Current;
+ employer.AddRelation(new EmployeeAgreement { Salary = i, Target = employee });
+ }
+ }
+ }
+
+ var result = 0;
+ var buffer = _frifloBuffer;
+
+ // Часть 2: Удаление
+ foreach (var employer in _frifloEmployers.Entities)
+ {
+ // Получаем все связи данного типа у сущности
+ var relations = employer.GetRelations();
+ var i = 0;
+
+ foreach (var relation in relations)
+ {
+ buffer[i++] = relation.Target;
+ }
+
+ for (var j = 0; j < i; j++)
+ {
+ if (employer.RemoveRelation(buffer[j]))
+ {
+ result++;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ [GlobalCleanup]
+ public void Cleanup()
+ {
+ _world.Dispose();
+ _world = null!;
+ }
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _world = new WorldBuilder().Build();
+ _actorContext = _world.Actors;
+ _actorBuffer = new Actor[Count];
+ _employeeFilter = _actorContext.Filter();
+ _employerFilter = _actorContext.Filter();
+
+ _frifloWorld = new EntityStore();
+ _frifloBuffer = new Entity[Count];
+ _frifloEmployees = _frifloWorld.Query();
+ _frifloEmployers = _frifloWorld.Query();
+
+ for (var i = 0; i < Count; i++)
+ {
+ var employer = _actorContext.CreateActor();
+ employer.Add(new Employer());
+
+ _frifloWorld.CreateEntity(new Employer());
+
+ for (var y = 0; y < Count; y++)
+ {
+ var employee = _actorContext.CreateActor();
+ employee.Add(new Employee());
+
+ _frifloWorld.CreateEntity(new Employee());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Actors/CheckComponentExistsBenchmark.cs b/src/Hexecs.Benchmarks/Actors/CheckComponentExistsBenchmark.cs
deleted file mode 100644
index c49b9d8..0000000
--- a/src/Hexecs.Benchmarks/Actors/CheckComponentExistsBenchmark.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-using System.Runtime.CompilerServices;
-using Hexecs.Benchmarks.Mocks;
-using Hexecs.Worlds;
-using World = Hexecs.Worlds.World;
-
-namespace Hexecs.Benchmarks.Actors;
-
-// BenchmarkDotNet v0.15.8, Windows 11 (10.0.22621.4317/22H2/2022Update/SunValley2)
-// Intel Xeon CPU E5-2697 v3 2.60GHz, 2 CPU, 56 logical and 28 physical cores
-// .NET SDK 10.0.100
-// [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
-// .NET 10.0 : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
-//
-// Job=.NET 10.0 Runtime=.NET 10.0
-//
-// | Method | Mean | Ratio | Allocated | Alloc Ratio |
-// |----------------- |---------:|------:|----------:|------------:|
-// | Hexecs_Is | 307.7 us | 0.90 | - | NA |
-// | Hexecs_Has | 342.4 us | 1.00 | - | NA |
-// | Hexecs_Reference | 380.7 us | 1.11 | - | NA |
-// | DefaultEcs_Has | 713.7 us | 2.08 | - | NA |
-
-[SimpleJob(RuntimeMoniker.Net10_0)]
-[Orderer(SummaryOrderPolicy.FastestToSlowest)]
-[MeanColumn, MemoryDiagnoser]
-[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD", "Count")]
-[JsonExporterAttribute.Full]
-[JsonExporterAttribute.FullCompressed]
-[BenchmarkCategory("Actors")]
-public class CheckComponentExistsBenchmark
-{
- [Params(10_000, 100_000)]
- public int Count;
-
- private ActorContext _context = null!;
- private DefaultEcs.World _defaultWorld = null!;
- private World _world = null!;
-
- [Benchmark(Baseline = true)]
- public int Hexecs_Has()
- {
- var result = 0;
-
- // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
- foreach (var actor in _context)
- {
- if (actor.Has()) result++;
- }
-
- return result;
- }
-
- [Benchmark]
- public int DefaultEcs_Has()
- {
- var result = 0;
-
- // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
- foreach (var entity in _defaultWorld)
- {
- if (entity.Has())
- {
- result++;
- }
- }
-
- return result;
- }
-
- [Benchmark]
- public int Hexecs_Is()
- {
- var result = 0;
-
- // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
- foreach (var actor in _context)
- {
- if (actor.Is(out _))
- {
- result++;
- }
- }
-
- return result;
- }
-
- [Benchmark]
- public int Hexecs_Reference()
- {
- var result = 0;
-
- // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
- foreach (var actor in _context)
- {
- ref var reference = ref actor.TryGetRef();
- if (!Unsafe.IsNullRef(ref reference))
- {
- result++;
- }
- }
-
- return result;
- }
-
- [GlobalCleanup]
- public void Cleanup()
- {
- _defaultWorld.Dispose();
- _defaultWorld = null!;
-
- _world.Dispose();
- _world = null!;
- }
-
- [GlobalSetup]
- public void Setup()
- {
- _defaultWorld = new DefaultEcs.World();
- _world = new WorldBuilder().Build();
- _context = _world.Actors;
-
- for (var i = 0; i < Count; i++)
- {
- var actor = _context.CreateActor();
- actor.Add(new Attack());
- actor.Add(new Defence());
-
- var defaultEntity = _defaultWorld.CreateEntity();
- defaultEntity.Set();
- defaultEntity.Set();
-
- if (i % 10 != 0) continue;
-
- actor.Add(new Speed());
- defaultEntity.Set();
- }
- }
-}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Actors/CreateAddComponentsDestroyBenchmark.cs b/src/Hexecs.Benchmarks/Actors/CreateAddComponentsDestroyBenchmark.cs
deleted file mode 100644
index 7c23f91..0000000
--- a/src/Hexecs.Benchmarks/Actors/CreateAddComponentsDestroyBenchmark.cs
+++ /dev/null
@@ -1,169 +0,0 @@
-using Hexecs.Benchmarks.Mocks;
-using Hexecs.Worlds;
-
-namespace Hexecs.Benchmarks.Actors;
-
-// BenchmarkDotNet v0.15.8, Windows 11 (10.0.22621.4317/22H2/2022Update/SunValley2)
-// Intel Xeon CPU E5-2697 v3 2.60GHz, 2 CPU, 56 logical and 28 physical cores
-// .NET SDK 10.0.100
-// [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
-// .NET 10.0 : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
-//
-// Job=.NET 10.0 Runtime=.NET 10.0
-//
-// | Method | Count | Mean | Ratio | Gen0 | Allocated | Alloc Ratio |
-// |---------------------------- |------- |-------------:|------:|---------:|-----------:|------------:|
-// | DefaultEcs_CreateAddDestroy | 1000 | 411.8 us | 0.72 | 1.4648 | 32000 B | NA |
-// | Hexecs_CreateAddDestroy | 1000 | 574.8 us | 1.00 | - | - | NA |
-// | | | | | | | |
-// | Hexecs_CreateAddDestroy | 100000 | 76,251.7 us | 1.00 | - | 40 B | 1.00 |
-// | DefaultEcs_CreateAddDestroy | 100000 | 90,755.1 us | 1.19 | 166.6667 | 3200040 B | 80,001.00 |
-// | | | | | | | |
-// | DefaultEcs_CreateAddDestroy | 500000 | 470,864.8 us | 0.86 | - | 16000040 B | 400,001.00 |
-// | Hexecs_CreateAddDestroy | 500000 | 548,102.0 us | 1.00 | - | 40 B | 1.00 |
-
-[SimpleJob(RuntimeMoniker.Net10_0)]
-[Orderer(SummaryOrderPolicy.FastestToSlowest)]
-[MeanColumn, MemoryDiagnoser]
-[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
-[JsonExporterAttribute.Full]
-[JsonExporterAttribute.FullCompressed]
-[BenchmarkCategory("Actors")]
-public class CreateAddComponentsDestroyBenchmark
-{
- [Params(1_000, 100_000, 500_000)]
- public int Count;
-
- private List _defaultSets = null!;
- private List _defaultEntities = null!;
- private DefaultEcs.World _defaultWorld = null!;
-
- private List _hexecsActors = null!;
- private ActorContext _hexecsContext = null!;
- private List _hexecsFilters = null!;
- private World _hexecsWorld = null!;
-
- [Benchmark(Baseline = true)]
- public int Hexecs_CreateAddDestroy()
- {
- _hexecsActors.Clear();
-
- for (var i = 0; i < Count; i++)
- {
- var actor = _hexecsContext.CreateActor();
- actor.Add(new Attack { Value = i });
- actor.Add(new Defence());
- actor.Add(new Speed());
-
- _hexecsActors.Add(actor);
- }
-
- foreach (var actor in _hexecsActors)
- {
- actor.Remove();
- actor.Remove();
- actor.Remove();
-
- actor.Destroy();
- }
-
- return _hexecsFilters.Sum(static x => x.Length);
- }
-
- [Benchmark]
- public int DefaultEcs_CreateAddDestroy()
- {
- _defaultEntities.Clear();
-
- for (var i = 0; i < Count; i++)
- {
- var entity = _defaultWorld.CreateEntity();
- entity.Set(new Attack { Value = i });
- entity.Set(new Defence());
- entity.Set(new Speed());
-
- _defaultEntities.Add(entity);
- }
-
- foreach (var entity in _defaultEntities)
- {
- entity.Remove();
- entity.Remove();
- entity.Remove();
-
- entity.Dispose();
- }
-
- return _defaultSets.Sum(static x => x.Count);
- }
-
- [GlobalCleanup]
- public void Cleanup()
- {
- _defaultWorld.Dispose();
- _defaultWorld = null!;
-
- _hexecsWorld.Dispose();
- _hexecsWorld = null!;
- }
-
- [GlobalSetup]
- public void Setup()
- {
- _defaultEntities = new List(Count);
- _defaultWorld = new DefaultEcs.World();
- _defaultSets =
- [
- _defaultWorld.GetEntities().With().AsSet(),
- _defaultWorld.GetEntities().With().AsSet(),
- _defaultWorld.GetEntities().With().AsSet(),
- _defaultWorld.GetEntities().With().With().AsSet(),
- _defaultWorld.GetEntities().With().With().AsSet(),
- _defaultWorld.GetEntities().With().With().AsSet(),
- _defaultWorld.GetEntities().With().With().With().AsSet()
- ];
-
- _hexecsActors = new List(Count);
- _hexecsWorld = new WorldBuilder().Build();
- _hexecsContext = _hexecsWorld.Actors;
- _hexecsFilters =
- [
- _hexecsContext.Filter(),
- _hexecsContext.Filter(),
- _hexecsContext.Filter(),
- _hexecsContext.Filter(),
- _hexecsContext.Filter(),
- _hexecsContext.Filter(),
- _hexecsContext.Filter()
- // warmup
- ];
-
- // warmup
- for (var i = 0; i < Count; i++)
- {
- var actor = _hexecsContext.CreateActor();
- actor.Add(new Attack());
- actor.Add(new Defence());
- actor.Add(new Speed());
-
- _hexecsActors.Add(actor);
-
- var defaultEntity = _defaultWorld.CreateEntity();
- defaultEntity.Set();
- defaultEntity.Set();
- defaultEntity.Set();
-
- _defaultEntities.Add(defaultEntity);
- }
-
- foreach (var actor in _hexecsActors)
- {
- actor.Destroy();
- }
-
- foreach (var entity in _defaultEntities)
- {
- entity.Dispose();
- }
- }
-}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs b/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs
index 2e96d88..073fa81 100644
--- a/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs
+++ b/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs
@@ -1,23 +1,47 @@
using DefaultEcs.System;
+using Friflo.Engine.ECS;
using Hexecs.Actors.Systems;
-using Hexecs.Benchmarks.Mocks;
+using Hexecs.Benchmarks.Mocks.ActorComponents;
using Hexecs.Threading;
using Hexecs.Worlds;
namespace Hexecs.Benchmarks.Actors;
+// BenchmarkDotNet v0.15.8, Windows 11 (10.0.22621.4317/22H2/2022Update/SunValley2)
+// Intel Xeon CPU E5-2697 v3 2.60GHz, 2 CPU, 56 logical and 28 physical cores
+// .NET SDK 10.0.100
+// [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+// .NET 10.0 : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+//
+// Job=.NET 10.0 Runtime=.NET 10.0
+//
+// | Method | Count | Mean | Ratio | Allocated | Alloc Ratio |
+// |-------------------- |-------- |------------:|------:|----------:|------------:|
+// | FriFlo_Parallel | 100000 | 94.42 us | 0.77 | - | NA |
+// | Hexecs_Parallel | 100000 | 122.17 us | 1.00 | - | NA |
+// | DefaultEcs_Parallel | 100000 | 221.83 us | 1.82 | - | NA |
+// | | | | | | |
+// | FriFlo_Parallel | 1000000 | 842.69 us | 0.91 | - | NA |
+// | Hexecs_Parallel | 1000000 | 931.08 us | 1.00 | - | NA |
+// | DefaultEcs_Parallel | 1000000 | 2,370.75 us | 2.55 | - | NA |
+//
+// ------------------------------------------------------------------------------------
+//
// BenchmarkDotNet v0.15.8, macOS Tahoe 26.2 (25C56) [Darwin 25.2.0]
// Apple M3 Max, 1 CPU, 16 logical and 16 physical cores
-// .NET SDK 10.0.101
-// [Host] : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
-// .NET 10.0 : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+// .NET SDK 10.0.101
+// [Host] : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
+// .NET 10.0 : .NET 10.0.1 (10.0.1, 10.0.125.57005), Arm64 RyuJIT armv8.0-a
//
// Job=.NET 10.0 Runtime=.NET 10.0
//
-// | Method | Mean | Ratio | Allocated | Alloc Ratio |
-// |-------------------- |-----------:|------:|----------:|------------:|
-// | Hexecs_Parallel | 769.8 us | 1.00 | - | NA |
-// | DefaultEcs_Parallel | 1,576.7 us | 2.05 | - | NA |
+// | Method | Count | Mean | Ratio | Allocated | Alloc Ratio |
+// |-------------------- |-------- |----------:|------:|----------:|------------:|
+// | Hexecs_Parallel | 100000 | 51.38 us | 1.00 | - | NA |
+// | DefaultEcs_Parallel | 100000 | 77.63 us | 1.51 | - | NA |
+// | | | | | | |
+// | Hexecs_Parallel | 1000000 | 390.26 us | 1.00 | - | NA |
+// | DefaultEcs_Parallel | 1000000 | 804.39 us | 2.06 | - | NA |
[SimpleJob(RuntimeMoniker.Net10_0)]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
@@ -28,8 +52,7 @@ namespace Hexecs.Benchmarks.Actors;
[BenchmarkCategory("Actors")]
public class UpdateSystemWithParallelWorkerBenchmark
{
- [Params(100_000, 1_000_000)]
- public int Count;
+ [Params(100_000, 1_000_000)] public int Count;
private DefaultEcs.World _defaultWorld = null!;
private DefaultEcsParallelSystem _defaultSystem = null!;
@@ -37,6 +60,10 @@ public class UpdateSystemWithParallelWorkerBenchmark
private World _hexecsWorld = null!;
private HexecsUpdateParallelSystem _hexecsSystem = null!;
+ private EntityStore _frifloWorld = null!;
+ private ArchetypeQuery _frifloQuery = null!;
+ private QueryJob _frifloJob = null!;
+
private WorldTime _state;
[Benchmark(Baseline = true)]
@@ -53,12 +80,21 @@ public int DefaultEcs_Parallel()
return _defaultSystem.Set.Count;
}
+ [Benchmark]
+ public int FriFlo_Parallel()
+ {
+ _frifloJob.RunParallel();
+ return _frifloQuery.Count;
+ }
+
[GlobalCleanup]
public void Cleanup()
{
_defaultSystem.Dispose();
_defaultWorld.Dispose();
_hexecsWorld.Dispose();
+
+ _frifloJob.JobRunner.Dispose();
}
[GlobalSetup]
@@ -75,6 +111,18 @@ public void Setup()
.Build();
_hexecsSystem = _hexecsWorld.Actors.GetUpdateSystem();
+ _frifloWorld = new EntityStore { JobRunner = new ParallelJobRunner(4) };
+ _frifloQuery = _frifloWorld.Query();
+ _frifloJob = _frifloQuery.ForEach((a, d, s, ch) =>
+ {
+ for (var i = 0; i < ch.Length; i++)
+ {
+ a[i].Value++;
+ d[i].Value++;
+ s[i].Value++;
+ }
+ });
+
_state = new WorldTime();
var context = _hexecsWorld.Actors;
@@ -89,6 +137,8 @@ public void Setup()
defaultEntity.Set();
defaultEntity.Set();
defaultEntity.Set();
+
+ _frifloWorld.CreateEntity(new Attack(), new Defence(), new Speed());
}
}
diff --git a/src/Hexecs.Benchmarks/Collections/ThreadLocalStackBenchmark.cs b/src/Hexecs.Benchmarks/Collections/ThreadLocalStackBenchmark.cs
index 761efee..3458161 100644
--- a/src/Hexecs.Benchmarks/Collections/ThreadLocalStackBenchmark.cs
+++ b/src/Hexecs.Benchmarks/Collections/ThreadLocalStackBenchmark.cs
@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
-using System.ComponentModel;
using Hexecs.Collections;
namespace Hexecs.Benchmarks.Collections;
diff --git a/src/Hexecs.Benchmarks/Hexecs.Benchmarks.csproj b/src/Hexecs.Benchmarks/Hexecs.Benchmarks.csproj
index 2cf767c..40fbedc 100644
--- a/src/Hexecs.Benchmarks/Hexecs.Benchmarks.csproj
+++ b/src/Hexecs.Benchmarks/Hexecs.Benchmarks.csproj
@@ -13,6 +13,16 @@
+
+
+
+
+
+ Attack.cs
+
+
+ Defence.cs
+
diff --git a/src/Hexecs.Benchmarks/Mocks/ActorComponents/Attack.cs b/src/Hexecs.Benchmarks/Mocks/ActorComponents/Attack.cs
new file mode 100644
index 0000000..8479b55
--- /dev/null
+++ b/src/Hexecs.Benchmarks/Mocks/ActorComponents/Attack.cs
@@ -0,0 +1,8 @@
+using Friflo.Engine.ECS;
+
+namespace Hexecs.Benchmarks.Mocks.ActorComponents;
+
+public struct Attack : IActorComponent, IComponent
+{
+ public int Value;
+}
\ No newline at end of file
diff --git a/src/Hexecs.Tests/Mocks/AttackBuilder.cs b/src/Hexecs.Benchmarks/Mocks/ActorComponents/AttackBuilder.cs
similarity index 84%
rename from src/Hexecs.Tests/Mocks/AttackBuilder.cs
rename to src/Hexecs.Benchmarks/Mocks/ActorComponents/AttackBuilder.cs
index bf06d23..d2d58c6 100644
--- a/src/Hexecs.Tests/Mocks/AttackBuilder.cs
+++ b/src/Hexecs.Benchmarks/Mocks/ActorComponents/AttackBuilder.cs
@@ -1,7 +1,7 @@
using Hexecs.Assets;
using Hexecs.Utils;
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Benchmarks.Mocks.ActorComponents;
internal sealed class AttackBuilder : IActorBuilder
{
diff --git a/src/Hexecs.Benchmarks/Mocks/ActorComponents/Defence.cs b/src/Hexecs.Benchmarks/Mocks/ActorComponents/Defence.cs
new file mode 100644
index 0000000..62c521a
--- /dev/null
+++ b/src/Hexecs.Benchmarks/Mocks/ActorComponents/Defence.cs
@@ -0,0 +1,8 @@
+using Friflo.Engine.ECS;
+
+namespace Hexecs.Benchmarks.Mocks.ActorComponents;
+
+public struct Defence : IActorComponent, IComponent
+{
+ public int Value;
+}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Mocks/DefenceBuilder.cs b/src/Hexecs.Benchmarks/Mocks/ActorComponents/DefenceBuilder.cs
similarity index 84%
rename from src/Hexecs.Benchmarks/Mocks/DefenceBuilder.cs
rename to src/Hexecs.Benchmarks/Mocks/ActorComponents/DefenceBuilder.cs
index 881b669..3e63e2d 100644
--- a/src/Hexecs.Benchmarks/Mocks/DefenceBuilder.cs
+++ b/src/Hexecs.Benchmarks/Mocks/ActorComponents/DefenceBuilder.cs
@@ -1,7 +1,7 @@
using Hexecs.Assets;
using Hexecs.Utils;
-namespace Hexecs.Benchmarks.Mocks;
+namespace Hexecs.Benchmarks.Mocks.ActorComponents;
internal sealed class DefenceBuilder : IActorBuilder
{
diff --git a/src/Hexecs.Benchmarks/Mocks/ActorComponents/Employee.cs b/src/Hexecs.Benchmarks/Mocks/ActorComponents/Employee.cs
new file mode 100644
index 0000000..55e9ce9
--- /dev/null
+++ b/src/Hexecs.Benchmarks/Mocks/ActorComponents/Employee.cs
@@ -0,0 +1,5 @@
+using Friflo.Engine.ECS;
+
+namespace Hexecs.Benchmarks.Mocks.ActorComponents;
+
+public struct Employee : IActorComponent, IComponent;
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Mocks/ActorComponents/EmployeeAgreement.cs b/src/Hexecs.Benchmarks/Mocks/ActorComponents/EmployeeAgreement.cs
new file mode 100644
index 0000000..6f153a8
--- /dev/null
+++ b/src/Hexecs.Benchmarks/Mocks/ActorComponents/EmployeeAgreement.cs
@@ -0,0 +1,11 @@
+using Friflo.Engine.ECS;
+
+namespace Hexecs.Benchmarks.Mocks.ActorComponents;
+
+public struct EmployeeAgreement : ILinkRelation
+{
+ public Entity Target;
+ public int Salary;
+
+ public Entity GetRelationKey() => Target;
+}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Mocks/ActorComponents/Employer.cs b/src/Hexecs.Benchmarks/Mocks/ActorComponents/Employer.cs
new file mode 100644
index 0000000..fe32ae2
--- /dev/null
+++ b/src/Hexecs.Benchmarks/Mocks/ActorComponents/Employer.cs
@@ -0,0 +1,5 @@
+using Friflo.Engine.ECS;
+
+namespace Hexecs.Benchmarks.Mocks.ActorComponents;
+
+public struct Employer : IActorComponent, IComponent;
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Mocks/ActorComponents/Speed.cs b/src/Hexecs.Benchmarks/Mocks/ActorComponents/Speed.cs
new file mode 100644
index 0000000..7483cdf
--- /dev/null
+++ b/src/Hexecs.Benchmarks/Mocks/ActorComponents/Speed.cs
@@ -0,0 +1,8 @@
+using Friflo.Engine.ECS;
+
+namespace Hexecs.Benchmarks.Mocks.ActorComponents;
+
+public struct Speed : IActorComponent, IComponent
+{
+ public int Value;
+}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Mocks/Attack.cs b/src/Hexecs.Benchmarks/Mocks/RelationMock.cs
similarity index 61%
rename from src/Hexecs.Benchmarks/Mocks/Attack.cs
rename to src/Hexecs.Benchmarks/Mocks/RelationMock.cs
index 358a8af..65454eb 100644
--- a/src/Hexecs.Benchmarks/Mocks/Attack.cs
+++ b/src/Hexecs.Benchmarks/Mocks/RelationMock.cs
@@ -1,6 +1,6 @@
namespace Hexecs.Benchmarks.Mocks;
-public struct Attack : IActorComponent
+public struct RelationMock
{
public int Value;
}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Mocks/Speed.cs b/src/Hexecs.Benchmarks/Mocks/Speed.cs
deleted file mode 100644
index 3142bab..0000000
--- a/src/Hexecs.Benchmarks/Mocks/Speed.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Hexecs.Benchmarks.Mocks;
-
-public struct Speed: IActorComponent
-{
- public int Value;
-}
\ No newline at end of file
diff --git a/src/Hexecs.Benchmarks/Program.cs b/src/Hexecs.Benchmarks/Program.cs
index af5ba4f..3c07ae5 100644
--- a/src/Hexecs.Benchmarks/Program.cs
+++ b/src/Hexecs.Benchmarks/Program.cs
@@ -1,3 +1,4 @@
using BenchmarkDotNet.Running;
+//BenchmarkRunner.Run();
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
\ No newline at end of file
diff --git a/src/Hexecs.Tests/Actors/ActorComponentShould.cs b/src/Hexecs.Tests/Actors/ActorComponentShould.cs
index 3817ab5..362987a 100644
--- a/src/Hexecs.Tests/Actors/ActorComponentShould.cs
+++ b/src/Hexecs.Tests/Actors/ActorComponentShould.cs
@@ -1,4 +1,5 @@
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
namespace Hexecs.Tests.Actors;
diff --git a/src/Hexecs.Tests/Actors/ActorConstraintShould.cs b/src/Hexecs.Tests/Actors/ActorConstraintShould.cs
index 913a4ee..1663309 100644
--- a/src/Hexecs.Tests/Actors/ActorConstraintShould.cs
+++ b/src/Hexecs.Tests/Actors/ActorConstraintShould.cs
@@ -1,4 +1,5 @@
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
namespace Hexecs.Tests.Actors;
diff --git a/src/Hexecs.Tests/Actors/ActorContextShould.cs b/src/Hexecs.Tests/Actors/ActorContextShould.cs
index 5da620e..54d5dee 100644
--- a/src/Hexecs.Tests/Actors/ActorContextShould.cs
+++ b/src/Hexecs.Tests/Actors/ActorContextShould.cs
@@ -1,6 +1,7 @@
using System.Runtime.CompilerServices;
using Hexecs.Assets;
-using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
+using Hexecs.Tests.Mocks.Assets;
namespace Hexecs.Tests.Actors;
@@ -264,7 +265,7 @@ public void AddComponentIfNotExists()
var actor = fixture.Actors.CreateActor();
// act
- ref var component = ref fixture.Actors.GetOrAddComponent(actor.Id, id => new Attack { Value = 30 });
+ ref var component = ref fixture.Actors.GetOrAddComponent(actor.Id, _ => new Attack { Value = 30 });
// assert
component.Value.Should().Be(30);
diff --git a/src/Hexecs.Tests/Actors/ActorDictionaryShould.cs b/src/Hexecs.Tests/Actors/ActorDictionaryShould.cs
deleted file mode 100644
index c469940..0000000
--- a/src/Hexecs.Tests/Actors/ActorDictionaryShould.cs
+++ /dev/null
@@ -1,172 +0,0 @@
-using Hexecs.Tests.Mocks;
-
-namespace Hexecs.Tests.Actors;
-
-public sealed class ActorDictionaryShould(ActorTestFixture fixture) : IClassFixture
-{
- [Fact(DisplayName = "Должен автоматически добавлять актора в словарь при добавлении компонента")]
- public void Should_Add_Actor_When_Component_Added()
- {
- // Arrange
- // Используем Name как ключ
- using var dict = new ActorDictionary(
- fixture.Actors,
- c => c.Name);
-
- // Act
- var actor = fixture.CreateActor();
- var expectedName = actor.Get().Name;
-
- // Assert
- dict
- .ContainsKey(expectedName)
- .Should()
- .BeTrue();
-
- dict[expectedName]
- .Id.Should()
- .Be(actor.Id);
- }
-
- [Fact(DisplayName = "Должен обновлять ключ в словаре при изменении компонента")]
- public void Should_Update_Key_When_Component_Updated()
- {
- // Arrange
- var actor = fixture.CreateActor();
- actor.Add(new Description { Name = "OldName" });
-
- using var dict = new ActorDictionary(
- fixture.Actors,
- c => c.Name);
-
- // Act
- // Обновляем компонент, что должно вызвать OnComponentUpdating
- actor.Update(new Description { Name = "NewName" });
-
- // Assert
- dict
- .ContainsKey("OldName")
- .Should()
- .BeFalse();
-
- dict
- .ContainsKey("NewName")
- .Should()
- .BeTrue();
-
- dict["NewName"]
- .Id
- .Should()
- .Be(actor.Id);
- }
-
- [Fact(DisplayName = "Должен удалять актора из словаря при удалении компонента")]
- public void Should_Remove_Actor_When_Component_Removed()
- {
- // Arrange
- var actor = fixture.CreateActor();
- actor.Add(new Description { Name = "ToRemove" });
-
- using var dict = new ActorDictionary(
- fixture.Actors,
- c => c.Name);
-
- // Act
- actor.Remove();
-
- // Assert
- dict
- .ContainsKey("ToRemove")
- .Should()
- .BeFalse();
-
- dict
- .Length
- .Should()
- .Be(0);
- }
-
- [Fact(DisplayName = "Должен генерировать события при добавлении и удалении")]
- public void Should_Raise_Events_On_Changes()
- {
- // Arrange
- using var dict = new ActorDictionary(
- fixture.Actors,
- c => c.Name);
-
- var addedRaised = false;
- var removedRaised = false;
- dict.Added += _ => addedRaised = true;
- dict.Removed += _ => removedRaised = true;
-
- // Act
- var actor = fixture.CreateActor();
- actor.Add(new Description { Name = "EventTest" });
- actor.Remove();
-
- // Assert
- addedRaised
- .Should()
- .BeTrue();
-
- removedRaised
- .Should()
- .BeTrue();
- }
-
- [Fact(DisplayName = "Должен возвращать ActorRef через TryGetActorRef")]
- public void Should_TryGet_ActorRef()
- {
- // Arrange
- var actor = fixture.CreateActor();
- actor.Add(new Description { Name = "Target" });
-
- using var dict = new ActorDictionary(
- fixture.Actors,
- c => c.Name);
-
- // Act
- var found = dict.TryGetActorRef("Target", out var actorRef);
- var notFound = dict.TryGetActorRef("Unknown", out _);
-
- // Assert
- found.Should().BeTrue();
- actorRef.Id.Should().Be(actor.Id);
- notFound.Should().BeFalse();
- }
-
- [Fact(DisplayName = "Должен выбрасывать исключение при обращении по несуществующему ключу через индексер")]
- public void Should_Throw_When_Key_Not_Found_In_Indexer()
- {
- // Arrange
- using var dict = new ActorDictionary(
- fixture.Actors,
- c => c.Name);
-
- // Act
- var action = () => { _ = dict["Missing"]; };
-
- // Assert
- // Исходя из кода ActorError.KeyNotFound()
- action.Should().Throw();
- }
-
- [Fact(DisplayName = "Должен очищаться при очистке контекста")]
- public void Should_Clear_When_Context_Cleared()
- {
- // Arrange
- var actor = fixture.CreateActor();
- actor.Add(new Description { Name = "ClearMe" });
-
- using var dict = new ActorDictionary(
- fixture.Actors,
- c => c.Name);
-
- // Act
- fixture.Actors.Clear();
-
- // Assert
- dict.Length.Should().Be(0);
- dict.ContainsKey("ClearMe").Should().BeFalse();
- }
-}
\ No newline at end of file
diff --git a/src/Hexecs.Tests/Actors/ActorFilter1Should.cs b/src/Hexecs.Tests/Actors/ActorFilter1Should.cs
index 8a95f45..357cd38 100644
--- a/src/Hexecs.Tests/Actors/ActorFilter1Should.cs
+++ b/src/Hexecs.Tests/Actors/ActorFilter1Should.cs
@@ -1,4 +1,5 @@
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
using Hexecs.Utils;
namespace Hexecs.Tests.Actors;
diff --git a/src/Hexecs.Tests/Actors/ActorFilter2Should.cs b/src/Hexecs.Tests/Actors/ActorFilter2Should.cs
index cf2691b..b37c729 100644
--- a/src/Hexecs.Tests/Actors/ActorFilter2Should.cs
+++ b/src/Hexecs.Tests/Actors/ActorFilter2Should.cs
@@ -1,4 +1,5 @@
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
namespace Hexecs.Tests.Actors;
diff --git a/src/Hexecs.Tests/Actors/ActorFilter3Should.cs b/src/Hexecs.Tests/Actors/ActorFilter3Should.cs
index 2304d26..18564ae 100644
--- a/src/Hexecs.Tests/Actors/ActorFilter3Should.cs
+++ b/src/Hexecs.Tests/Actors/ActorFilter3Should.cs
@@ -1,4 +1,5 @@
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
namespace Hexecs.Tests.Actors;
diff --git a/src/Hexecs.Tests/Actors/ActorListShould.cs b/src/Hexecs.Tests/Actors/ActorListShould.cs
index 6fbd27b..bdb98d1 100644
--- a/src/Hexecs.Tests/Actors/ActorListShould.cs
+++ b/src/Hexecs.Tests/Actors/ActorListShould.cs
@@ -1,4 +1,5 @@
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
namespace Hexecs.Tests.Actors;
diff --git a/src/Hexecs.Tests/Actors/ActorMarshalShould.cs b/src/Hexecs.Tests/Actors/ActorMarshalShould.cs
index 2d03e36..115745d 100644
--- a/src/Hexecs.Tests/Actors/ActorMarshalShould.cs
+++ b/src/Hexecs.Tests/Actors/ActorMarshalShould.cs
@@ -1,6 +1,7 @@
using System.Runtime.CompilerServices;
using Hexecs.Actors.Components;
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
namespace Hexecs.Tests.Actors;
diff --git a/src/Hexecs.Tests/Actors/ActorRelationShould.cs b/src/Hexecs.Tests/Actors/ActorRelationShould.cs
index 163c993..30f3b4f 100644
--- a/src/Hexecs.Tests/Actors/ActorRelationShould.cs
+++ b/src/Hexecs.Tests/Actors/ActorRelationShould.cs
@@ -1,4 +1,5 @@
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
namespace Hexecs.Tests.Actors;
@@ -23,7 +24,8 @@ public void CreateRelation()
.Should()
.BeTrue();
- actor2.HasRelation(actor1)
+ actor2
+ .HasRelation(actor1)
.Should()
.BeTrue();
}
diff --git a/src/Hexecs.Tests/Actors/ActorShould.cs b/src/Hexecs.Tests/Actors/ActorShould.cs
index d6b984c..6e8cc89 100644
--- a/src/Hexecs.Tests/Actors/ActorShould.cs
+++ b/src/Hexecs.Tests/Actors/ActorShould.cs
@@ -1,5 +1,7 @@
using System.Runtime.CompilerServices;
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
+using Hexecs.Tests.Mocks.Assets;
namespace Hexecs.Tests.Actors;
diff --git a/src/Hexecs.Tests/Actors/ActorSystemShould.cs b/src/Hexecs.Tests/Actors/ActorSystemShould.cs
index b28c92d..5bab88f 100644
--- a/src/Hexecs.Tests/Actors/ActorSystemShould.cs
+++ b/src/Hexecs.Tests/Actors/ActorSystemShould.cs
@@ -1,6 +1,7 @@
using Hexecs.Actors.Systems;
using Hexecs.Dependencies;
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
using Hexecs.Threading;
using Hexecs.Worlds;
diff --git a/src/Hexecs.Tests/Actors/ActorTestFixture.cs b/src/Hexecs.Tests/Actors/ActorTestFixture.cs
index 3f9d71a..bc25dc8 100644
--- a/src/Hexecs.Tests/Actors/ActorTestFixture.cs
+++ b/src/Hexecs.Tests/Actors/ActorTestFixture.cs
@@ -1,6 +1,8 @@
using Hexecs.Assets;
using Hexecs.Assets.Sources;
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.ActorComponents;
+using Hexecs.Tests.Mocks.Assets;
using Hexecs.Worlds;
namespace Hexecs.Tests.Actors;
diff --git a/src/Hexecs.Tests/Assets/AssetConstraintShould.cs b/src/Hexecs.Tests/Assets/AssetConstraintShould.cs
index 62c6e53..f510eec 100644
--- a/src/Hexecs.Tests/Assets/AssetConstraintShould.cs
+++ b/src/Hexecs.Tests/Assets/AssetConstraintShould.cs
@@ -1,6 +1,7 @@
using Hexecs.Assets;
using Hexecs.Tests.Actors;
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.Assets;
namespace Hexecs.Tests.Assets;
diff --git a/src/Hexecs.Tests/Assets/AssetFilter1Should.cs b/src/Hexecs.Tests/Assets/AssetFilter1Should.cs
index 4ddd7a8..c90c659 100644
--- a/src/Hexecs.Tests/Assets/AssetFilter1Should.cs
+++ b/src/Hexecs.Tests/Assets/AssetFilter1Should.cs
@@ -1,4 +1,5 @@
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.Assets;
namespace Hexecs.Tests.Assets;
diff --git a/src/Hexecs.Tests/Assets/AssetFilter2Should.cs b/src/Hexecs.Tests/Assets/AssetFilter2Should.cs
index 02a3c9f..6aee3c5 100644
--- a/src/Hexecs.Tests/Assets/AssetFilter2Should.cs
+++ b/src/Hexecs.Tests/Assets/AssetFilter2Should.cs
@@ -1,4 +1,5 @@
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.Assets;
namespace Hexecs.Tests.Assets;
diff --git a/src/Hexecs.Tests/Assets/AssetFilter3Should.cs b/src/Hexecs.Tests/Assets/AssetFilter3Should.cs
index 93d3769..d81b730 100644
--- a/src/Hexecs.Tests/Assets/AssetFilter3Should.cs
+++ b/src/Hexecs.Tests/Assets/AssetFilter3Should.cs
@@ -1,4 +1,5 @@
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.Assets;
namespace Hexecs.Tests.Assets;
diff --git a/src/Hexecs.Tests/Assets/AssetTestFixture.cs b/src/Hexecs.Tests/Assets/AssetTestFixture.cs
index 4f7e646..a0aa2ca 100644
--- a/src/Hexecs.Tests/Assets/AssetTestFixture.cs
+++ b/src/Hexecs.Tests/Assets/AssetTestFixture.cs
@@ -1,6 +1,7 @@
using Hexecs.Assets;
using Hexecs.Assets.Sources;
using Hexecs.Tests.Mocks;
+using Hexecs.Tests.Mocks.Assets;
using Hexecs.Worlds;
namespace Hexecs.Tests.Assets;
diff --git a/src/Hexecs.Tests/Collections/BucketShould.cs b/src/Hexecs.Tests/Collections/BucketShould.cs
index 25fc5a8..dba5a37 100644
--- a/src/Hexecs.Tests/Collections/BucketShould.cs
+++ b/src/Hexecs.Tests/Collections/BucketShould.cs
@@ -370,42 +370,6 @@ public void Remove_ItemExists_ShouldRemoveItemAndReturnTrue()
bucket.Has(2).Should().BeFalse();
}
- [Fact(DisplayName = "Remove: должен удалять первый элемент")]
- public void Remove_FirstItem_ShouldRemoveAndShift()
- {
- // Arrange
- var bucket = new Bucket(3);
- bucket.Add(10);
- bucket.Add(20);
- bucket.Add(30);
-
- // Act
- var removed = bucket.Remove(10);
-
- // Assert
- removed.Should().BeTrue();
- bucket.Length.Should().Be(2);
- bucket.ToArray().Should().Equal(20, 30);
- }
-
- [Fact(DisplayName = "Remove: должен удалять последний элемент")]
- public void Remove_LastItem_ShouldRemove()
- {
- // Arrange
- var bucket = new Bucket(3);
- bucket.Add(10);
- bucket.Add(20);
- bucket.Add(30);
-
- // Act
- var removed = bucket.Remove(30);
-
- // Assert
- removed.Should().BeTrue();
- bucket.Length.Should().Be(2);
- bucket.ToArray().Should().Equal(10, 20);
- }
-
[Fact(DisplayName = "Remove: должен возвращать false, если элемент не существует")]
public void Remove_ItemDoesNotExist_ShouldReturnFalse()
{
diff --git a/src/Hexecs.Tests/Hexecs.Tests.csproj b/src/Hexecs.Tests/Hexecs.Tests.csproj
index 1bb875f..572b682 100644
--- a/src/Hexecs.Tests/Hexecs.Tests.csproj
+++ b/src/Hexecs.Tests/Hexecs.Tests.csproj
@@ -12,4 +12,13 @@
+
+
+ Attack.cs
+
+
+ Defence.cs
+
+
+
diff --git a/src/Hexecs.Tests/Mocks/Attack.cs b/src/Hexecs.Tests/Mocks/ActorComponents/Attack.cs
similarity index 58%
rename from src/Hexecs.Tests/Mocks/Attack.cs
rename to src/Hexecs.Tests/Mocks/ActorComponents/Attack.cs
index 0fd35cd..67651a7 100644
--- a/src/Hexecs.Tests/Mocks/Attack.cs
+++ b/src/Hexecs.Tests/Mocks/ActorComponents/Attack.cs
@@ -1,4 +1,4 @@
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.ActorComponents;
public struct Attack : IActorComponent
{
diff --git a/src/Hexecs.Benchmarks/Mocks/AttackBuilder.cs b/src/Hexecs.Tests/Mocks/ActorComponents/AttackBuilder.cs
similarity index 77%
rename from src/Hexecs.Benchmarks/Mocks/AttackBuilder.cs
rename to src/Hexecs.Tests/Mocks/ActorComponents/AttackBuilder.cs
index 2d4572a..05917d2 100644
--- a/src/Hexecs.Benchmarks/Mocks/AttackBuilder.cs
+++ b/src/Hexecs.Tests/Mocks/ActorComponents/AttackBuilder.cs
@@ -1,7 +1,8 @@
using Hexecs.Assets;
+using Hexecs.Tests.Mocks.Assets;
using Hexecs.Utils;
-namespace Hexecs.Benchmarks.Mocks;
+namespace Hexecs.Tests.Mocks.ActorComponents;
internal sealed class AttackBuilder : IActorBuilder
{
diff --git a/src/Hexecs.Benchmarks/Mocks/Defence.cs b/src/Hexecs.Tests/Mocks/ActorComponents/Defence.cs
similarity index 58%
rename from src/Hexecs.Benchmarks/Mocks/Defence.cs
rename to src/Hexecs.Tests/Mocks/ActorComponents/Defence.cs
index efb89c3..078593e 100644
--- a/src/Hexecs.Benchmarks/Mocks/Defence.cs
+++ b/src/Hexecs.Tests/Mocks/ActorComponents/Defence.cs
@@ -1,4 +1,4 @@
-namespace Hexecs.Benchmarks.Mocks;
+namespace Hexecs.Tests.Mocks.ActorComponents;
public struct Defence : IActorComponent
{
diff --git a/src/Hexecs.Tests/Mocks/DefenceBuilder.cs b/src/Hexecs.Tests/Mocks/ActorComponents/DefenceBuilder.cs
similarity index 77%
rename from src/Hexecs.Tests/Mocks/DefenceBuilder.cs
rename to src/Hexecs.Tests/Mocks/ActorComponents/DefenceBuilder.cs
index b3832ff..b6192ef 100644
--- a/src/Hexecs.Tests/Mocks/DefenceBuilder.cs
+++ b/src/Hexecs.Tests/Mocks/ActorComponents/DefenceBuilder.cs
@@ -1,7 +1,8 @@
using Hexecs.Assets;
+using Hexecs.Tests.Mocks.Assets;
using Hexecs.Utils;
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.ActorComponents;
internal sealed class DefenceBuilder : IActorBuilder
{
diff --git a/src/Hexecs.Tests/Mocks/Description.cs b/src/Hexecs.Tests/Mocks/ActorComponents/Description.cs
similarity index 59%
rename from src/Hexecs.Tests/Mocks/Description.cs
rename to src/Hexecs.Tests/Mocks/ActorComponents/Description.cs
index f3c9bd1..7f86556 100644
--- a/src/Hexecs.Tests/Mocks/Description.cs
+++ b/src/Hexecs.Tests/Mocks/ActorComponents/Description.cs
@@ -1,4 +1,4 @@
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.ActorComponents;
public struct Description : IActorComponent
{
diff --git a/src/Hexecs.Tests/Mocks/DisposableComponent.cs b/src/Hexecs.Tests/Mocks/ActorComponents/DisposableComponent.cs
similarity index 75%
rename from src/Hexecs.Tests/Mocks/DisposableComponent.cs
rename to src/Hexecs.Tests/Mocks/ActorComponents/DisposableComponent.cs
index 91acf29..8419a0d 100644
--- a/src/Hexecs.Tests/Mocks/DisposableComponent.cs
+++ b/src/Hexecs.Tests/Mocks/ActorComponents/DisposableComponent.cs
@@ -1,4 +1,4 @@
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.ActorComponents;
public readonly struct DisposableComponent(IDisposable monitor) : IActorComponent, IDisposable
{
diff --git a/src/Hexecs.Tests/Mocks/NonExistentComponent.cs b/src/Hexecs.Tests/Mocks/ActorComponents/NonExistentComponent.cs
similarity index 69%
rename from src/Hexecs.Tests/Mocks/NonExistentComponent.cs
rename to src/Hexecs.Tests/Mocks/ActorComponents/NonExistentComponent.cs
index 98df604..3cd4af9 100644
--- a/src/Hexecs.Tests/Mocks/NonExistentComponent.cs
+++ b/src/Hexecs.Tests/Mocks/ActorComponents/NonExistentComponent.cs
@@ -1,4 +1,4 @@
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.ActorComponents;
public struct NonExistentComponent : IActorComponent;
diff --git a/src/Hexecs.Tests/Mocks/Speed.cs b/src/Hexecs.Tests/Mocks/ActorComponents/Speed.cs
similarity index 56%
rename from src/Hexecs.Tests/Mocks/Speed.cs
rename to src/Hexecs.Tests/Mocks/ActorComponents/Speed.cs
index 410a5e5..0ad5bec 100644
--- a/src/Hexecs.Tests/Mocks/Speed.cs
+++ b/src/Hexecs.Tests/Mocks/ActorComponents/Speed.cs
@@ -1,4 +1,4 @@
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.ActorComponents;
public struct Speed: IActorComponent
{
diff --git a/src/Hexecs.Tests/Mocks/BuildingAsset.cs b/src/Hexecs.Tests/Mocks/Assets/BuildingAsset.cs
similarity index 68%
rename from src/Hexecs.Tests/Mocks/BuildingAsset.cs
rename to src/Hexecs.Tests/Mocks/Assets/BuildingAsset.cs
index a124a1b..7a34d29 100644
--- a/src/Hexecs.Tests/Mocks/BuildingAsset.cs
+++ b/src/Hexecs.Tests/Mocks/Assets/BuildingAsset.cs
@@ -1,5 +1,5 @@
using Hexecs.Assets;
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.Assets;
public readonly struct BuildingAsset : IAssetComponent;
\ No newline at end of file
diff --git a/src/Hexecs.Tests/Mocks/CarAsset.cs b/src/Hexecs.Tests/Mocks/Assets/CarAsset.cs
similarity index 87%
rename from src/Hexecs.Tests/Mocks/CarAsset.cs
rename to src/Hexecs.Tests/Mocks/Assets/CarAsset.cs
index ac26ddc..845f48b 100644
--- a/src/Hexecs.Tests/Mocks/CarAsset.cs
+++ b/src/Hexecs.Tests/Mocks/Assets/CarAsset.cs
@@ -1,6 +1,6 @@
using Hexecs.Assets;
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.Assets;
public readonly struct CarAsset(int price, int speed) : IAssetComponent
{
diff --git a/src/Hexecs.Tests/Mocks/DecisionAsset.cs b/src/Hexecs.Tests/Mocks/Assets/DecisionAsset.cs
similarity index 82%
rename from src/Hexecs.Tests/Mocks/DecisionAsset.cs
rename to src/Hexecs.Tests/Mocks/Assets/DecisionAsset.cs
index 040d776..172bbd0 100644
--- a/src/Hexecs.Tests/Mocks/DecisionAsset.cs
+++ b/src/Hexecs.Tests/Mocks/Assets/DecisionAsset.cs
@@ -1,6 +1,6 @@
using Hexecs.Assets;
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.Assets;
public readonly struct DecisionAsset(int min, int max) : IAssetComponent
{
diff --git a/src/Hexecs.Tests/Mocks/NonExistentAsset.cs b/src/Hexecs.Tests/Mocks/Assets/NonExistentAsset.cs
similarity index 69%
rename from src/Hexecs.Tests/Mocks/NonExistentAsset.cs
rename to src/Hexecs.Tests/Mocks/Assets/NonExistentAsset.cs
index 39b823a..d3cf1a1 100644
--- a/src/Hexecs.Tests/Mocks/NonExistentAsset.cs
+++ b/src/Hexecs.Tests/Mocks/Assets/NonExistentAsset.cs
@@ -1,5 +1,5 @@
using Hexecs.Assets;
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.Assets;
public readonly struct NonExistentAsset : IAssetComponent;
\ No newline at end of file
diff --git a/src/Hexecs.Tests/Mocks/SubjectAsset.cs b/src/Hexecs.Tests/Mocks/Assets/SubjectAsset.cs
similarity index 68%
rename from src/Hexecs.Tests/Mocks/SubjectAsset.cs
rename to src/Hexecs.Tests/Mocks/Assets/SubjectAsset.cs
index 38fef95..7636156 100644
--- a/src/Hexecs.Tests/Mocks/SubjectAsset.cs
+++ b/src/Hexecs.Tests/Mocks/Assets/SubjectAsset.cs
@@ -1,5 +1,5 @@
using Hexecs.Assets;
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.Assets;
public readonly struct SubjectAsset : IAssetComponent;
\ No newline at end of file
diff --git a/src/Hexecs.Tests/Mocks/UnitAsset.cs b/src/Hexecs.Tests/Mocks/Assets/UnitAsset.cs
similarity index 88%
rename from src/Hexecs.Tests/Mocks/UnitAsset.cs
rename to src/Hexecs.Tests/Mocks/Assets/UnitAsset.cs
index e13be7a..247a54e 100644
--- a/src/Hexecs.Tests/Mocks/UnitAsset.cs
+++ b/src/Hexecs.Tests/Mocks/Assets/UnitAsset.cs
@@ -1,6 +1,6 @@
using Hexecs.Assets;
-namespace Hexecs.Tests.Mocks;
+namespace Hexecs.Tests.Mocks.Assets;
public readonly struct UnitAsset(int attack, int defence) : IAssetComponent
{
diff --git a/src/Hexecs.Tests/Mocks/Defence.cs b/src/Hexecs.Tests/Mocks/Defence.cs
deleted file mode 100644
index 602610d..0000000
--- a/src/Hexecs.Tests/Mocks/Defence.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Hexecs.Tests.Mocks;
-
-public struct Defence : IActorComponent
-{
- public int Value;
-}
\ No newline at end of file
diff --git a/src/Hexecs/Actors/ActorContext.Components.cs b/src/Hexecs/Actors/ActorContext.Components.cs
index f068d55..f893ab8 100644
--- a/src/Hexecs/Actors/ActorContext.Components.cs
+++ b/src/Hexecs/Actors/ActorContext.Components.cs
@@ -3,6 +3,7 @@
namespace Hexecs.Actors;
+[SuppressMessage("ReSharper", "InvertIf")]
public sealed partial class ActorContext
{
private IActorComponentPool?[] _componentPools;
@@ -26,8 +27,8 @@ public ref T AddComponent(uint actorId, in T component)
var pool = GetOrCreateComponentPool();
ref var result = ref pool.Add(actorId, in component);
- ref var entry = ref GetEntryExact(actorId);
- entry.Components.Add(ActorComponentType.Id);
+ ref var entry = ref GetEntryRefExact(actorId);
+ entry.Add(ActorComponentType.Id);
return ref result;
}
@@ -46,8 +47,8 @@ public ref T CloneComponent(uint ownerId, uint cloneId) where T : struct, IAc
var pool = GetComponentPool();
if (pool == null) ActorError.ComponentNotFound(ownerId);
- ref var clone = ref GetEntryExact(cloneId);
- if (clone.Components.TryAdd(ActorComponentType.Id))
+ ref var clone = ref GetEntryRefExact(cloneId);
+ if (clone.TryAdd(ActorComponentType.Id))
{
return ref pool.Clone(ownerId, cloneId);
}
@@ -64,11 +65,10 @@ public ref T CloneComponent(uint ownerId, uint cloneId) where T : struct, IAc
/// Перечислитель компонентов. Возвращает , если актёр не найден.
public ComponentEnumerator Components(uint actorId)
{
- ref var entry = ref GetEntry(actorId);
- if (Unsafe.IsNullRef(ref entry)) return ComponentEnumerator.Empty;
-
- ref readonly var entryComponents = ref entry.Components;
- return new ComponentEnumerator(actorId, _componentPools, entryComponents.ToArray());
+ ref var entry = ref GetEntryRef(actorId);
+ return Unsafe.IsNullRef(ref entry)
+ ? ComponentEnumerator.Empty
+ : new ComponentEnumerator(actorId, _componentPools, entry.ToArray());
}
///
@@ -101,8 +101,8 @@ public ref T GetOrAddComponent(uint actorId, Func factory)
// ReSharper disable once InvertIf
if (added)
{
- ref var entry = ref GetEntryExact(actorId);
- entry.Components.Add(ActorComponentType.Id);
+ ref var entry = ref GetEntryRefExact(actorId);
+ entry.Add(ActorComponentType.Id);
}
return ref component;
@@ -191,12 +191,14 @@ public bool RemoveComponent(uint actorId)
where T : struct, IActorComponent
{
var pool = GetComponentPool();
- if (pool == null || !pool.Remove(actorId)) return false;
-
- ref var entry = ref GetEntryExact(actorId);
- entry.Components.Remove(ActorComponentType.Id);
+ if (pool != null && pool.Remove(actorId))
+ {
+ ref var entry = ref GetEntryRefExact(actorId);
+ entry.Remove(ActorComponentType.Id);
+ return true;
+ }
- return true;
+ return false;
}
///
@@ -216,8 +218,8 @@ public bool RemoveComponent(uint actorId, out T component)
return false;
}
- ref var entry = ref GetEntryExact(actorId);
- entry.Components.Remove(ActorComponentType.Id);
+ ref var entry = ref GetEntryRefExact(actorId);
+ entry.Remove(ActorComponentType.Id);
return true;
}
@@ -237,8 +239,8 @@ public bool TryAdd(uint actorId, in T component)
if (!result) return false;
- ref var entry = ref GetEntryExact(actorId);
- entry.Components.Add(ActorComponentType.Id);
+ ref var entry = ref GetEntryRefExact(actorId);
+ entry.Add(ActorComponentType.Id);
return true;
}
@@ -253,8 +255,12 @@ public ref T TryGetComponentRef(uint actorId)
where T : struct, IActorComponent
{
var pool = GetComponentPool();
- if (pool == null) return ref Unsafe.NullRef();
- return ref pool.TryGet(actorId);
+ if (pool != null)
+ {
+ return ref pool.TryGet(actorId);
+ }
+
+ return ref Unsafe.NullRef();
}
///
@@ -293,15 +299,17 @@ public bool UpdateComponent(uint actorId, in T component, bool createIfNotExi
internal ActorComponentPool? GetComponentPool() where T : struct, IActorComponent
{
var id = ActorComponentType.Id;
-
var pools = _componentPools;
- if (id >= pools.Length) return null;
- var pool = pools[id];
+ if (id < pools.Length)
+ {
+ var pool = pools[id];
+ return pool == null
+ ? null
+ : Unsafe.As>(pool);
+ }
- return pool == null
- ? null
- : Unsafe.As>(pool);
+ return null;
}
///
@@ -311,21 +319,40 @@ public bool UpdateComponent(uint actorId, in T component, bool createIfNotExi
///
/// Пул компонентов указанного типа (существующий или вновь созданный).
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ActorComponentPool GetOrCreateComponentPool() where T : struct, IActorComponent
{
var id = ActorComponentType.Id;
- if (id < _componentPools.Length)
+ var pools = _componentPools;
+
+ if ((uint)id < (uint)pools.Length)
{
- var existsPool = _componentPools[id];
- if (existsPool != null) return Unsafe.As>(existsPool);
+ var existsPool = pools[id];
+ if (existsPool != null)
+ {
+ return Unsafe.As>(existsPool);
+ }
}
+ return CreateComponentPoolSlow(id);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private ActorComponentPool CreateComponentPoolSlow(ushort id) where T : struct, IActorComponent
+ {
#if NET9_0_OR_GREATER
using (_componentPoolLock.EnterScope())
#else
lock (_componentPoolLock)
#endif
{
+ // Повторная проверка под локом (Double-Check Locking)
+ if (id < _componentPools.Length)
+ {
+ var existsPool = _componentPools[id];
+ if (existsPool != null) return Unsafe.As>(existsPool);
+ }
+
ArrayUtils.EnsureCapacity(ref _componentPools, id);
ref var pool = ref _componentPools[id];
pool ??= new ActorComponentPool(this, GetOrCreateComponentConfiguration());
diff --git a/src/Hexecs/Actors/ActorContext.Dictionary.cs b/src/Hexecs/Actors/ActorContext.Dictionary.cs
index 12a719a..d5ebc85 100644
--- a/src/Hexecs/Actors/ActorContext.Dictionary.cs
+++ b/src/Hexecs/Actors/ActorContext.Dictionary.cs
@@ -5,70 +5,36 @@ namespace Hexecs.Actors;
[SuppressMessage("ReSharper", "InvertIf")]
public sealed partial class ActorContext
{
+ private const int PageBits = 12;
+ private const int PageSize = 1 << PageBits; // 4096
+ private const int PageMask = PageSize - 1;
+
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _length - _freeCount;
+ get => _count;
}
- private int[] _buckets;
- private Entry[] _entries;
- private int _freeCount;
- private int _freeList;
- private int _length;
+ private uint[]?[] _sparsePages;
+ private uint[] _dense;
+ private Entry[] _values;
+ private int _count;
- private ref Entry AddEntry(uint key)
+ private ref Entry AddEntry(uint actorId)
{
- if (_buckets.Length == 0) ResizeEntries();
-
- ref var bucket = ref GetBucket(key);
- var entries = _entries;
- var i = bucket - 1;
-
- while ((uint)i < (uint)entries.Length)
- {
- ref readonly var existsEntry = ref entries[i];
-
- if (existsEntry.Key == key) ActorError.AlreadyExists(key);
- i = existsEntry.Next;
- }
-
- int index;
- if (_freeCount > 0)
+ ref var entry = ref TryAddEntry(actorId);
+ if (!Unsafe.IsNullRef(ref entry))
{
- index = _freeList;
- _freeList = CollectionUtils.StartOfFreeList - entries[index].Next;
- _freeCount--;
+ Created?.Invoke(actorId);
+ return ref entry;
}
- else
- {
- index = _length;
- if (index == entries.Length)
- {
- ResizeEntries();
- bucket = ref GetBucket(key);
- entries = _entries;
- }
-
- _length++;
- }
-
- ref var entry = ref entries[index];
-
- entry.Key = key;
- entry.Next = bucket - 1;
-
- bucket = index + 1;
- Created?.Invoke(key);
-
- return ref entry;
+ ActorError.AlreadyExists(actorId); // выбрасывает ошибку
+ return ref Unsafe.NullRef();
}
- private void ClearEntry(ref Entry entry)
+ private void ClearEntry(uint actorId, ref Entry entry)
{
- var actorId = entry.Key;
-
ref var relationsComponent = ref TryGetComponentRef(actorId);
if (!Unsafe.IsNullRef(ref relationsComponent))
{
@@ -78,123 +44,203 @@ private void ClearEntry(ref Entry entry)
relationPool?.Remove(actorId);
}
}
-
- ref var components = ref entry.Components;
- foreach (var componentId in components)
+
+ foreach (var componentId in entry)
{
var componentPool = _componentPools[componentId];
componentPool?.Remove(actorId);
}
- components.Dispose();
+ entry.Dispose();
}
private void ClearEntries()
{
- var index = 0;
- while ((uint)index < (uint)_length)
+ var dense = _dense;
+ var values = _values;
+ var sparsePages = _sparsePages;
+
+ for (var i = 0; i < _count; i++)
{
- ref var entry = ref _entries[index];
- if (entry.Next >= -1)
- {
- entry.Components.Dispose();
- }
+ var key = dense[i];
- index++;
+ ref var entry = ref values[i];
+ entry.Dispose();
+
+ var pageIndex = (int)(key >> PageBits);
+ sparsePages[pageIndex]![key & PageMask] = 0;
}
- ArrayUtils.Clear(_buckets);
- ArrayUtils.Clear(_entries, _length);
+ _count = 0;
+ }
- _freeCount = 0;
- _freeList = 0;
- _length = 0;
+ private void EnsureDenseCapacity()
+ {
+ if (_count >= _dense.Length)
+ {
+ var newSize = _dense.Length * 2;
+ Array.Resize(ref _dense, newSize);
+ Array.Resize(ref _values, newSize);
+ }
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private ref int GetBucket(uint keyHash) => ref _buckets[keyHash % (uint)_buckets.Length];
+ private void EnsurePageArraySize(int pageIndex)
+ {
+ if (pageIndex >= _sparsePages.Length)
+ {
+ var newSize = Math.Max(_sparsePages.Length * 2, pageIndex + 1);
+ Array.Resize(ref _sparsePages, newSize);
+ }
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private ref Entry GetEntry(uint key)
+ private ref Entry GetEntryRef(uint actorId)
{
- if (_length > 0)
+ var pageIndex = (int)(actorId >> PageBits);
+ if ((uint)pageIndex < (uint)_sparsePages.Length)
{
- var i = _buckets[key % (uint)_buckets.Length] - 1;
- var entries = _entries;
- while ((uint)i < (uint)entries.Length)
+ var page = _sparsePages[pageIndex];
+ if (page != null)
{
- ref var entry = ref entries[i];
- if (entry.Key == key) return ref entry;
- i = entry.Next;
+ var denseIndexPlusOne = page[actorId & PageMask];
+ if (denseIndexPlusOne != 0)
+ {
+ var index = (int)denseIndexPlusOne - 1;
+ if (_dense[index] == actorId)
+ {
+ return ref _values[index];
+ }
+ }
}
}
return ref Unsafe.NullRef();
}
- private ref Entry GetEntryExact(uint key)
+ private ref Entry GetEntryRefExact(uint key)
{
- ref var entry = ref GetEntry(key);
- if (Unsafe.IsNullRef(ref entry)) ActorError.NotFound(key);
- return ref entry;
+ ref var entry = ref GetEntryRef(key);
+ if (!Unsafe.IsNullRef(ref entry))
+ {
+ return ref entry;
+ }
+
+ ActorError.NotFound(key); // exception
+ return ref Unsafe.NullRef();
}
- private bool RemoveEntry(uint key)
+ private bool RemoveEntry(uint actorId)
{
- if (_length == 0) return false;
+ var pageIndex = (int)(actorId >> PageBits);
+ if ((uint)pageIndex >= (uint)_sparsePages.Length) return false;
- ref var bucket = ref GetBucket(key);
- var entries = _entries;
- var i = bucket - 1;
- var last = -1;
+ var page = _sparsePages[pageIndex];
+ if (page == null) return false;
- while (i >= 0)
- {
- ref var entry = ref entries[i];
- if (entry.Key == key)
- {
- Destroying?.Invoke(key);
- ClearEntry(ref entry);
+ var offset = (int)(actorId & PageMask);
+ var denseIndexPlusOne = page[offset];
+ if (denseIndexPlusOne == 0) return false;
- if (last < 0) bucket = entry.Next + 1;
- else entries[last].Next = entry.Next;
+ var denseIndex = (int)denseIndexPlusOne - 1;
+ if (_dense[denseIndex] != actorId) return false;
- entry.Next = CollectionUtils.StartOfFreeList - _freeList;
+ // 1. Уведомляем системы ПЕРЕД какими-либо изменениями структуры
+ Destroying?.Invoke(actorId);
- _freeCount++;
- _freeList = i;
- return true;
- }
+ // 2. Очищаем данные сущности (пулы компонентов и т.д.)
+ ref var entryToRemove = ref _values[denseIndex];
+ ClearEntry(actorId, ref entryToRemove);
+
+ var lastIndex = _count - 1;
+ if (denseIndex != lastIndex)
+ {
+ var lastKey = _dense[lastIndex];
- last = i;
- i = entry.Next;
+ // Переносим ключ
+ _dense[denseIndex] = lastKey;
+
+ // ВАЖНО: Переносим данные.
+ // Поскольку мы уже вызвали ClearEntry для entryToRemove,
+ // мы можем просто перезаписать её.
+ _values[denseIndex] = _values[lastIndex];
+
+ // Обновляем индекс перемещенного ключа
+ var lastKeyPageIndex = (int)(lastKey >> PageBits);
+ _sparsePages[lastKeyPageIndex]![lastKey & PageMask] = (uint)denseIndex + 1;
}
- return false;
+ // 3. ОБЯЗАТЕЛЬНО обнуляем хвост, чтобы не было дубликатов ссылок на массивы
+ _values[lastIndex] = default;
+
+ // 4. Финализируем удаление
+ page[offset] = 0;
+ _count = lastIndex;
+
+ return true;
}
- private void ResizeEntries()
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ref Entry TryAddEntry(uint actorId)
{
- var length = _length;
- var newSize = HashHelper.GetPrime(length == 0 ? 4 : length << 1);
+ var pageIndex = (int)(actorId >> PageBits);
+ var pages = _sparsePages;
- var buckets = new int[newSize];
- var entries = new Entry[newSize];
+ // Максимально компактная проверка на готовность страницы и места
+ if ((uint)pageIndex < (uint)pages.Length)
+ {
+ var page = pages[pageIndex];
+ if (page != null && (uint)_count < (uint)_dense.Length)
+ {
+ ref var slot = ref page[actorId & PageMask];
+ if (slot == 0) // Чистая вставка (самый частый случай в ECS)
+ {
+ var idx = (uint)_count;
+ slot = idx + 1;
+ _dense[idx] = actorId;
+ _count++;
+
+ return ref _values[idx];
+ }
+
+ // Если не 0, проверяем на дубликат (чуть медленнее)
+ if (_dense[slot - 1] == actorId)
+ {
+ return ref Unsafe.NullRef();
+ }
+ }
+ }
- Array.Copy(_entries, entries, length);
+ return ref TryAddEntrySlow(actorId);
+ }
- for (var i = 0; i < length; i++)
- {
- ref var entry = ref entries[i];
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private ref Entry TryAddEntrySlow(uint actorId)
+ {
+ EnsureDenseCapacity();
+ var pageIndex = (int)(actorId >> PageBits);
+ EnsurePageArraySize(pageIndex);
- if (entry.Next < -1) continue;
+ ref var page = ref _sparsePages[pageIndex];
+ if (page == null)
+ {
+ page = ArrayUtils.Create(PageSize);
+ Array.Clear(page, 0, page.Length);
+ }
- ref var bucket = ref buckets[entry.Key % (uint)buckets.Length];
- entry.Next = bucket - 1;
- bucket = i + 1;
+ ref var denseIndexPlusOne = ref page[actorId & PageMask];
+ if (denseIndexPlusOne != 0)
+ {
+ if (_dense[denseIndexPlusOne - 1] == actorId)
+ {
+ return ref Unsafe.NullRef();
+ }
}
- _buckets = buckets;
- _entries = entries;
+ var denseIndex = (uint)_count;
+ denseIndexPlusOne = denseIndex + 1;
+ _dense[denseIndex] = actorId;
+ _count++;
+
+ return ref _values[denseIndex];
}
}
\ No newline at end of file
diff --git a/src/Hexecs/Actors/ActorContext.Entry.cs b/src/Hexecs/Actors/ActorContext.Entry.cs
index 166021b..03afa77 100644
--- a/src/Hexecs/Actors/ActorContext.Entry.cs
+++ b/src/Hexecs/Actors/ActorContext.Entry.cs
@@ -5,77 +5,53 @@ namespace Hexecs.Actors;
public sealed partial class ActorContext
{
- private struct Entry
- {
- public uint Key;
- public int Next;
- public ComponentBucket Components;
-
- public readonly void Serialize(Utf8JsonWriter writer)
- {
- writer.WriteStartObject();
-
- writer.WriteProperty("Key", Key);
- writer.WritePropertyName("Components");
-
- writer.WriteStartArray();
- foreach (var component in Components)
- {
- writer.WriteNumberValue(component);
- }
-
- writer.WriteStartArray();
-
- writer.WriteEndObject();
- }
- }
-
[DebuggerDisplay("Length = {Length}")]
[method: MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal struct ComponentBucket()
+ private struct Entry()
{
private const int InlineArraySize = 6;
+ private InlineItemArray _inlineArray;
+ private int _length = 0;
+ private ushort[]? _array;
+
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _length;
}
- private InlineItemArray _inlineArray;
- private int _length = 0;
- private ushort[] _array = [];
-
public void Add(ushort item)
{
if (_length < InlineArraySize) _inlineArray[_length] = item;
- else ArrayUtils.Insert(ref _array, ArrayPool.Shared, _length - InlineArraySize, item);
+ else
+ {
+ if (_array == null) _array = ArrayPool.Shared.Rent(InlineArraySize);
+ else ArrayUtils.Insert(ref _array, ArrayPool.Shared, _length - InlineArraySize, item);
+ }
_length++;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly bool Contains(ushort item) => IndexOf(item) > -1;
-
public void Dispose()
{
if (_array is { Length: > 0 }) ArrayPool.Shared.Return(_array);
- _array = [];
+ _array = null;
_length = 0;
}
- public ComponentBucketEnumerator GetEnumerator()
+ public readonly EntryComponentEnumerator GetEnumerator()
{
- ref var reference = ref Unsafe.As(ref _inlineArray);
+ ref var inlineRef = ref Unsafe.AsRef(in _inlineArray);
+ ref var reference = ref Unsafe.As(ref inlineRef);
var span = MemoryMarshal.CreateSpan(ref reference, InlineArraySize);
- return new ComponentBucketEnumerator(span, _array, _length);
+ return new EntryComponentEnumerator(span, _array ?? [], _length);
}
public readonly int IndexOf(ushort item)
{
if (_length == 0) return -1;
-
var inlineLength = Math.Min(_length, InlineArraySize);
for (var i = 0; i < inlineLength; i++)
{
@@ -116,7 +92,7 @@ public bool Remove(ushort item)
if (arraySize > 0)
{
const int lastInlineIndex = InlineArraySize - 1;
- _inlineArray[lastInlineIndex] = _array[0];
+ _inlineArray[lastInlineIndex] = _array![0];
ArrayUtils.Cut(_array, 0, arraySize);
}
@@ -124,7 +100,7 @@ public bool Remove(ushort item)
return true;
}
- if (_array.Length == 0 || arraySize <= 0) return false;
+ if (_array == null || _array.Length == 0 || arraySize <= 0) return false;
var span = _array.AsSpan(0, arraySize);
for (var i = 0; i < span.Length; i++)
@@ -139,15 +115,30 @@ public bool Remove(ushort item)
return false;
}
- public ushort this[int index]
+ public readonly void Serialize(uint actorId, Utf8JsonWriter writer)
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- readonly get => index < InlineArraySize ? _inlineArray[index] : _array[index - InlineArraySize];
- set
+ writer.WriteStartObject();
+
+ writer.WriteProperty("Key", actorId);
+ writer.WritePropertyName("Components");
+
+ writer.WriteStartArray();
+ foreach (var component in this)
{
- if (index < InlineArraySize) _inlineArray[index] = value;
- else _array[index - InlineArraySize] = value;
+ writer.WriteNumberValue(component);
}
+
+ writer.WriteStartArray();
+
+ writer.WriteEndObject();
+ }
+
+ public readonly ushort this[int index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => index < InlineArraySize
+ ? _inlineArray[index]
+ : _array![index - InlineArraySize];
}
public readonly ushort[] ToArray()
@@ -165,14 +156,14 @@ public readonly ushort[] ToArray()
public bool TryAdd(ushort item)
{
- var has = Contains(item);
+ var has = IndexOf(item) > -1;
if (has) return false;
Add(item);
return true;
}
- public ref struct ComponentBucketEnumerator
+ public ref struct EntryComponentEnumerator
{
public readonly ref ushort Current
{
@@ -188,7 +179,7 @@ public readonly ref ushort Current
private int _index;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal ComponentBucketEnumerator(Span inlineArray, ushort[] array, int length)
+ internal EntryComponentEnumerator(Span inlineArray, ushort[] array, int length)
{
_inlineArray = inlineArray;
_array = array;
diff --git a/src/Hexecs/Actors/ActorContext.Enumerator.cs b/src/Hexecs/Actors/ActorContext.Enumerator.cs
index d2e10cd..b8afbb5 100644
--- a/src/Hexecs/Actors/ActorContext.Enumerator.cs
+++ b/src/Hexecs/Actors/ActorContext.Enumerator.cs
@@ -2,51 +2,44 @@
public sealed partial class ActorContext
{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator GetEnumerator() => new(this);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
[SuppressMessage("ReSharper", "MemberHidesStaticFromOuterClass")]
public struct Enumerator : IEnumerator, IEnumerable
{
public readonly Actor Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => new(_context, _context._entries[_index].Key);
+ get => new(_context, _dense[_index]);
}
public readonly int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _context.Length;
+ get => _count;
}
private int _index;
private readonly ActorContext _context;
+ private readonly uint[] _dense;
+ private readonly int _count;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(ActorContext context)
{
_index = -1;
_context = context;
+ _dense = context._dense;
+ _count = context._count;
}
- public bool MoveNext()
- {
- var index = _index + 1;
- var length = _context._length;
- var entries = _context._entries;
- while ((uint)index < (uint)length)
- {
- ref readonly var entry = ref entries[index];
- if (entry.Next >= -1)
- {
- _index = index;
- return true;
- }
-
- index++;
- }
-
- _index = length;
- return false;
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext() => ++_index < _count;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Enumerator GetEnumerator() => this;
@@ -67,17 +60,11 @@ readonly void IDisposable.Dispose()
readonly IEnumerator IEnumerable.GetEnumerator() => this;
- readonly void IEnumerator.Reset()
+ void IEnumerator.Reset()
{
+ _index = -1;
}
#endregion
}
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Enumerator GetEnumerator() => new(this);
-
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
\ No newline at end of file
diff --git a/src/Hexecs/Actors/ActorContext.Relations.cs b/src/Hexecs/Actors/ActorContext.Relations.cs
index cf18b26..eaa3945 100644
--- a/src/Hexecs/Actors/ActorContext.Relations.cs
+++ b/src/Hexecs/Actors/ActorContext.Relations.cs
@@ -120,11 +120,17 @@ public bool RemoveRelation(uint subject, uint relative, out T relation) where
var relationId = ActorRelationType.Id;
- ref var subjectRelations = ref GetComponent(subject);
- subjectRelations.Remove(relationId);
+ if (pool.Count(subject) == 0)
+ {
+ ref var subjectRelations = ref GetComponent(subject);
+ subjectRelations.Remove(relationId);
+ }
- ref var relativeRelations = ref GetComponent(relative);
- relativeRelations.Remove(relationId);
+ if (pool.Count(relative) == 0)
+ {
+ ref var relativeRelations = ref GetComponent(relative);
+ relativeRelations.Remove(relationId);
+ }
return true;
}
diff --git a/src/Hexecs/Actors/ActorContext.Serialization.cs b/src/Hexecs/Actors/ActorContext.Serialization.cs
index 8e444d5..53190a7 100644
--- a/src/Hexecs/Actors/ActorContext.Serialization.cs
+++ b/src/Hexecs/Actors/ActorContext.Serialization.cs
@@ -26,32 +26,32 @@ public void Serialize(Utf8JsonWriter writer)
private void SerializeComponents(Utf8JsonWriter writer)
{
writer.WriteStartArray();
-
+
foreach (var pool in _componentPools)
{
pool?.Serialize(writer);
}
-
+
writer.WriteEndArray();
}
-
+
private void SerializeComponentTypes(Utf8JsonWriter writer)
{
writer.WriteStartArray();
-
+
foreach (var pool in _componentPools)
{
if (pool == null) continue;
-
+
writer.WriteStartObject();
writer
.WriteProperty(nameof(IActorComponentPool.Id), pool.Id)
.WriteProperty(nameof(IActorComponentPool.Type), pool.Type);
-
+
writer.WriteEndObject();
}
-
+
writer.WriteEndArray();
}
@@ -59,18 +59,18 @@ private void SerializeEntries(Utf8JsonWriter writer)
{
writer.WriteStartArray();
- var index = 0;
- var length = _length;
- var entries = _entries;
- while ((uint)index < (uint)length)
- {
- ref readonly var entry = ref entries[index];
- if (entry.Next >= -1)
- {
- entry.Serialize(writer);
- }
+ var count = _count;
+ var dense = _dense;
+ var values = _values;
- index++;
+ for (var i = 0; i < count; i++)
+ {
+ // Извлекаем ID из плотного массива ключей
+ var actorId = dense[i];
+ // Получаем ссылку на данные по тому же индексу
+ ref readonly var entry = ref values[i];
+
+ entry.Serialize(actorId, writer);
}
writer.WriteEndArray();
@@ -79,32 +79,32 @@ private void SerializeEntries(Utf8JsonWriter writer)
private void SerializeRelations(Utf8JsonWriter writer)
{
writer.WriteStartArray();
-
+
foreach (var pool in _relationPools)
{
pool?.Serialize(writer);
}
-
+
writer.WriteEndArray();
}
-
+
private void SerializeRelationTypes(Utf8JsonWriter writer)
{
writer.WriteStartArray();
-
+
foreach (var pool in _relationPools)
{
if (pool == null) continue;
-
+
writer.WriteStartObject();
writer
.WriteProperty(nameof(IActorRelationPool.Id), pool.Id)
.WriteProperty(nameof(IActorRelationPool.Type), pool.Type);
-
+
writer.WriteEndObject();
}
-
+
writer.WriteEndArray();
}
}
\ No newline at end of file
diff --git a/src/Hexecs/Actors/ActorContext.cs b/src/Hexecs/Actors/ActorContext.cs
index 027a955..13dc4dc 100644
--- a/src/Hexecs/Actors/ActorContext.cs
+++ b/src/Hexecs/Actors/ActorContext.cs
@@ -63,11 +63,9 @@ internal ActorContext(bool isDefault,
capacity = HashHelper.GetPrime(capacity);
- _buckets = new int[capacity];
- _entries = new Entry[capacity];
- _freeCount = 0;
- _freeList = 0;
- _length = 0;
+ _sparsePages = new uint[16][];
+ _dense = new uint[capacity];
+ _values = new Entry[capacity];
_builders = [];
@@ -102,11 +100,7 @@ internal ActorContext(bool isDefault,
/// Идентификатор актёра для проверки
/// Возвращает true, если актёр существует, иначе false
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool ActorAlive(uint actorId)
- {
- ref var entry = ref GetEntry(actorId);
- return !Unsafe.IsNullRef(ref entry) && entry.Key == actorId;
- }
+ public bool ActorAlive(uint actorId) => !Unsafe.IsNullRef(ref GetEntryRef(actorId));
///
/// Очищает контекст актёров, удаляя всех актёров и их компоненты.
@@ -144,13 +138,13 @@ public Actor Clone(uint actorId, bool withParent = true)
var cloneId = GetNextActorId();
ref var cloneEntry = ref AddEntry(cloneId);
- ref var entry = ref GetEntryExact(actorId);
- foreach (var componentId in entry.Components)
+ ref var entry = ref GetEntryRefExact(actorId);
+ foreach (var componentId in entry)
{
var componentPool = _componentPools[componentId]!;
componentPool.Clone(actorId, cloneId);
- cloneEntry.Components.Add(componentId);
+ cloneEntry.Add(componentId);
}
// ReSharper disable once InvertIf
@@ -352,7 +346,7 @@ public string GetDescription(uint actorId, int maxComponentDescription = 5)
public void GetDescription(uint actorId, ref ValueStringBuilder builder, int maxComponentDescription = 5)
{
- ref var entry = ref GetEntry(actorId);
+ ref var entry = ref GetEntryRef(actorId);
if (Unsafe.IsNullRef(ref entry))
{
builder.Append('\'');
@@ -362,9 +356,8 @@ public void GetDescription(uint actorId, ref ValueStringBuilder builder, int max
builder.Append("Id = ");
builder.Append(actorId);
-
- ref var components = ref entry.Components;
- var componentsLength = components.Length;
+
+ var componentsLength = entry.Length;
if (componentsLength == 0) return;
builder.Append(" (");
@@ -375,7 +368,7 @@ public void GetDescription(uint actorId, ref ValueStringBuilder builder, int max
var printMore = false;
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
- foreach (var componentId in components)
+ foreach (var componentId in entry)
{
if (maxComponentDescription == index)
{
diff --git a/src/Hexecs/Actors/ActorDictionary.cs b/src/Hexecs/Actors/ActorDictionary.cs
index 10d7ba2..9cfc59f 100644
--- a/src/Hexecs/Actors/ActorDictionary.cs
+++ b/src/Hexecs/Actors/ActorDictionary.cs
@@ -3,14 +3,10 @@
namespace Hexecs.Actors;
[DebuggerDisplay("Length = {Length}")]
-public sealed class ActorDictionary : IDisposable
+public sealed class ActorDictionary : IDisposable
where TKey : IEquatable
- where T : struct, IActorComponent
+ where T1 : struct, IActorComponent
{
- public event Action? Added;
- public event Action? Cleared;
- public event Action? Removed;
-
public readonly ActorContext Context;
public int Length
@@ -20,14 +16,14 @@ public int Length
}
private readonly Dictionary _actors;
- private readonly Func _keyExtractor;
- private readonly ActorComponentPool