diff --git a/README.md b/README.md index 240b370..242e39f 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,10 @@ DI необходим для того, чтобы всё это работало Так как всё в этом мире завязано на "мир" (`World`), то мы начнём с его создания. ```csharp var world = new WorldBuilder() + .DefaultParallelWorker(4) // чтобы обрабатывать сущности в 4 потока .DebugWorld() // чтобы получать больше информации в Debug-режиме - .CreateLogger(logger => logger.UseConsoleSink()); // чтобы логи выводились в консоль - .Build(); + .CreateLogger(logger => logger.UseConsoleSink()) // чтобы логи выводились в консоль + .Build(); ``` Этот код создаёт сконфигурированный объект World. Так как библиотека AOT-friendly, тут нет сканеров assembly для регистрации отдельных компонентов. Всё нужно регистрировать вручную. @@ -29,8 +30,8 @@ var world = new WorldBuilder() ```csharp public struct FlyComponent(int current, int max) : IActorComponent { // необходимо указать маркерный интерфейс - public int CurrentSpeed; - public int MaxSpeed; + public int CurrentSpeed = current; + public int MaxSpeed = max; } ``` Компоненты должны быть `struct`, чтобы всё в вашем мире могло работать эффективно и быстро. @@ -125,4 +126,10 @@ Actor passenger = ...; plane.AddChild(passenger); ``` -Если повторить это много раз, то в самолёте будут сидеть 2-400 пассажиров (в зависимости от типа самолёта, конечно). +Если повторить это много раз, то в самолёте будут сидеть множество пассажиров (в зависимости от типа самолёта, конечно). + +## Системы + +У нас же ecS, а значит у нас есть системы. +Системы - это классы, которые обрабатывают компоненты актёров. +Они могут быть зарегистрированы в мире и запущены для обработки компонентов. \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.MonoGame/BenchmarkGame.cs b/src/Hexecs.Benchmarks.MonoGame/BenchmarkGame.cs index a4e7531..fe7b7f9 100644 --- a/src/Hexecs.Benchmarks.MonoGame/BenchmarkGame.cs +++ b/src/Hexecs.Benchmarks.MonoGame/BenchmarkGame.cs @@ -66,7 +66,10 @@ protected override void Initialize() _world = new WorldBuilder() .DefaultParallelWorker(Math.Min(6, Environment.ProcessorCount)) .DefaultActorContext(builder => builder - // Передаем зависимости явно через фабрику или DI + .Capacity(InitialEntityCount) + .ConfigureComponentPool(color => color.Capacity(InitialEntityCount)) + .ConfigureComponentPool(position => position.Capacity(InitialEntityCount)) + .ConfigureComponentPool(velocity => velocity.Capacity(InitialEntityCount)) .CreateUpdateSystem(ctx => new MovementSystem(ctx, ctx.GetRequiredService(), width, height)) .CreateDrawSystem(ctx => new RenderSystem(ctx, GraphicsDevice, MaxEntityCount * 2))) diff --git a/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs b/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs index 0a8848f..0859d6f 100644 --- a/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs +++ b/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs @@ -11,13 +11,13 @@ namespace Hexecs.Benchmarks.Actors; // .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 | 48.63 us | 1.00 | - | NA | -// | DefaultEcs_Parallel | 117.87 us | 2.42 | - | NA | +// +// | Method | Mean | Ratio | Allocated | Alloc Ratio | +// |-------------------- |-----------:|------:|----------:|------------:| +// | Hexecs_Parallel | 769.8 us | 1.00 | - | NA | +// | DefaultEcs_Parallel | 1,576.7 us | 2.05 | - | NA | [SimpleJob(RuntimeMoniker.Net10_0)] [Orderer(SummaryOrderPolicy.FastestToSlowest)] @@ -25,7 +25,7 @@ namespace Hexecs.Benchmarks.Actors; [HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")] public class UpdateSystemWithParallelWorkerBenchmark { - private const int Count = 100_000; + private const int Count = 2_000_000; private DefaultEcs.World _defaultWorld = null!; private DefaultEcsParallelSystem _defaultSystem = null!; diff --git a/src/Hexecs.Benchmarks/Program.cs b/src/Hexecs.Benchmarks/Program.cs index bc8ad08..4288fbf 100644 --- a/src/Hexecs.Benchmarks/Program.cs +++ b/src/Hexecs.Benchmarks/Program.cs @@ -1,4 +1,4 @@ using BenchmarkDotNet.Running; -using Hexecs.Benchmarks.Collections; +using Hexecs.Benchmarks.Actors; -BenchmarkRunner.Run(); \ No newline at end of file +BenchmarkRunner.Run(); \ No newline at end of file diff --git a/src/Hexecs.Tests/Actors/ActorConstraintShould.cs b/src/Hexecs.Tests/Actors/ActorConstraintShould.cs new file mode 100644 index 0000000..913a4ee --- /dev/null +++ b/src/Hexecs.Tests/Actors/ActorConstraintShould.cs @@ -0,0 +1,134 @@ +using Hexecs.Tests.Mocks; + +namespace Hexecs.Tests.Actors; + +public sealed class ActorConstraintShould(ActorTestFixture fixture) : IClassFixture +{ + [Fact(DisplayName = "Должен успешно проходить проверку Applicable, если все условия соблюдены")] + public void Should_Be_Applicable_When_Conditions_Met() + { + // Arrange + var actor = fixture.CreateActor(); // Создаем актора с компонентом + var constraint = ActorConstraint.Include(fixture.Actors).Build(); + + // Act + var result = constraint.Applicable(actor.Id); + + // Assert + result.Should().BeTrue(); + } + + [Fact(DisplayName = "Должен возвращать false в Applicable, если компонент исключен")] + public void Should_Not_Be_Applicable_When_Excluded_Component_Exists() + { + // Arrange + var actor = fixture.CreateActor(); + var constraint = ActorConstraint.Exclude(fixture.Actors).Build(); + + // Act + var result = constraint.Applicable(actor.Id); + + // Assert + result.Should().BeFalse(); + } + + [Fact(DisplayName = "Должен вызывать событие Added, когда актор начинает соответствовать фильтру")] + public void Should_Raise_Added_When_Actor_Becomes_Valid() + { + // Arrange + var actor = fixture.CreateActor(); // Пустой актор + var constraint = ActorConstraint.Include(fixture.Actors).Build(); + + using var monitoredSubject = constraint.Monitor(); + + // Act + actor.Add(new Description()); // Теперь актор соответствует Include + + // Assert + monitoredSubject.Should().Raise(nameof(constraint.Added)) + .WithArgs(id => id == actor.Id); + } + + [Fact(DisplayName = "Должен вызывать событие Removing, когда актор перестает соответствовать фильтру")] + public void Should_Raise_Removing_When_Actor_Becomes_Invalid() + { + // Arrange + var actor = fixture.CreateActor(); + var constraint = ActorConstraint.Include(fixture.Actors).Build(); + + using var monitoredSubject = constraint.Monitor(); + + // Act + actor.Remove(); + + // Assert + monitoredSubject.Should().Raise(nameof(constraint.Removing)) + .WithArgs(id => id == actor.Id); + } + + [Fact(DisplayName = "Builder должен выбрасывать исключение при добавлении дублирующегося компонента")] + public void Builder_Should_Throw_On_Duplicate_Component() + { + // Arrange + var builder = ActorConstraint.Include(fixture.Actors); + + // Act + var action = () => builder.Include(); + + // Assert + action.Should().Throw(); + } + + [Fact(DisplayName = "Два одинаковых ограничения должны иметь одинаковый HashCode и быть равны")] + public void Should_Implement_Equality_Correctly() + { + // Arrange + var constraint1 = ActorConstraint.Include(fixture.Actors) + .Exclude() + .Build(); + + var constraint2 = ActorConstraint.Include(fixture.Actors) + .Exclude() + .Build(); + + // Assert + constraint1 + .Should() + .Be(constraint2); + + constraint1.GetHashCode() + .Should() + .Be(constraint2.GetHashCode()); + } + + [Fact(DisplayName = "Должен корректно работать с несколькими Include компонентами")] + public void Should_Work_With_Multiple_Includes() + { + // Arrange + var actor = fixture.CreateActor(); + var constraint = ActorConstraint.Include(fixture.Actors).Build(); + + // Act & Assert + constraint.Applicable(actor.Id).Should().BeFalse("Нужны оба компонента"); + + actor.Add(new Defence()); + constraint.Applicable(actor.Id).Should().BeTrue("Теперь оба компонента на месте"); + } + + [Fact(DisplayName = "Должен отписаться от всех событий пула при Dispose")] + public void Should_Unsubscribe_On_Dispose() + { + // Arrange + var actor = fixture.CreateActor(); + var constraint = ActorConstraint.Include(fixture.Actors).Build(); + + using var monitoredSubject = constraint.Monitor(); + + // Act + ((IDisposable)constraint).Dispose(); + actor.Add(new Description()); + + // Assert + monitoredSubject.Should().NotRaise(nameof(constraint.Added)); + } +} \ No newline at end of file diff --git a/src/Hexecs.Tests/Actors/ActorDictionaryShould.cs b/src/Hexecs.Tests/Actors/ActorDictionaryShould.cs new file mode 100644 index 0000000..c469940 --- /dev/null +++ b/src/Hexecs.Tests/Actors/ActorDictionaryShould.cs @@ -0,0 +1,172 @@ +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/ActorListShould.cs b/src/Hexecs.Tests/Actors/ActorListShould.cs new file mode 100644 index 0000000..6fbd27b --- /dev/null +++ b/src/Hexecs.Tests/Actors/ActorListShould.cs @@ -0,0 +1,126 @@ +using Hexecs.Tests.Mocks; + +namespace Hexecs.Tests.Actors; + +public sealed class ActorListShould(ActorTestFixture fixture) : IClassFixture +{ + [Fact(DisplayName = "Должен успешно добавлять акторов и увеличивать размер списка")] + public void Should_Add_Actors_And_Increase_Length() + { + // Arrange + using var list = new ActorList(fixture.Actors); + var actor = fixture.CreateActor(); + + // Act + list.Add(actor); + + // Assert + list.Length.Should().Be(1); + list.Contains(actor).Should().BeTrue(); + } + + [Fact(DisplayName = "Должен возвращать false при попытке удалить несуществующего актора")] + public void Should_Return_False_When_Removing_Non_Existent_Actor() + { + // Arrange + using var list = new ActorList(fixture.Actors); + uint fakeId = 999; + + // Act + var result = list.Remove(fakeId); + + // Assert + result.Should().BeFalse(); + } + + [Fact(DisplayName = "Должен очищать список и освобождать ресурсы при вызове Clear")] + public void Should_Clear_All_Actors() + { + // Arrange + using var list = new ActorList(fixture.Actors); + list.Add(fixture.CreateActor()); + list.Add(fixture.CreateActor()); + + // Act + list.Clear(); + + // Assert + list.Length.Should().Be(0); + list.GetEnumerator().MoveNext().Should().BeFalse(); + } + + [Fact(DisplayName = "Должен автоматически удалять актора из списка, если его компонент удален из пула")] + public void Should_Automatically_Remove_Actor_When_Component_Is_Removed() + { + // Arrange + using var list = new ActorList(fixture.Actors); + var actor = fixture.CreateActor(); + list.Add(actor); + + // Act + actor.Remove(); // Это вызывает событие Removing в пуле + + // Assert + list.Contains(actor).Should().BeFalse(); + list.Length.Should().Be(0); + } + + [Fact(DisplayName = "Должен корректно итерироваться по добавленным акторам")] + public void Should_Enumerate_Added_Actors() + { + // Arrange + using var list = new ActorList(fixture.Actors); + var actor1 = fixture.CreateActor(); + var actor2 = fixture.CreateActor(); + list.Add(actor1); + list.Add(actor2); + + // Act + var resultIds = new List(); + foreach (var actor in list) + { + resultIds.Add(actor.Id); + } + + // Assert + resultIds + .Should() + .HaveCount(2) + .And.Contain([actor1.Id, actor2.Id]); + } + + [Fact(DisplayName = "Должен выбрасывать исключение при попытке добавления в Dispose объект")] + public void Should_Throw_When_Adding_To_Disposed_List() + { + // Arrange + var list = new ActorList(fixture.Actors); + list.Dispose(); + var actor = fixture.CreateActor(); + + // Act + var action = () => list.Add(actor); + + // Assert + action.Should().Throw() + .WithMessage("*disposed*"); + } + + [Theory(DisplayName = "Должен поддерживать добавление акторов через разные типы ссылок")] + [InlineData(true)] + [InlineData(false)] + public void Should_Add_Using_Different_References(bool useActorRef) + { + // Arrange + using var list = new ActorList(fixture.Actors); + var actor = fixture.CreateActor(); + + // Act + if (useActorRef) + list.Add(actor.AsRef()); + else + list.Add(actor); + + // Assert + list.Contains(actor.Id).Should().BeTrue(); + } +} \ No newline at end of file diff --git a/src/Hexecs.Tests/Actors/ActorMethodsConsistencyTests.cs b/src/Hexecs.Tests/Actors/ActorMethodsConsistencyTests.cs index af24339..82f249b 100644 --- a/src/Hexecs.Tests/Actors/ActorMethodsConsistencyTests.cs +++ b/src/Hexecs.Tests/Actors/ActorMethodsConsistencyTests.cs @@ -26,6 +26,7 @@ public class ActorMethodsConsistencyTests /// private static readonly HashSet ExcludedMethods = [ + "AsRef", "GetType", "ToString", "Equals", diff --git a/src/Hexecs.Tests/Actors/ActorSystemShould.cs b/src/Hexecs.Tests/Actors/ActorSystemShould.cs index 2eddec1..def401f 100644 --- a/src/Hexecs.Tests/Actors/ActorSystemShould.cs +++ b/src/Hexecs.Tests/Actors/ActorSystemShould.cs @@ -16,7 +16,7 @@ public void ConfigureAndRunSystemsInParallel() using var world = new WorldBuilder() .DefaultParallelWorker(degreeOfParallelism: 4) .DefaultActorContext(cfg => cfg - .CreateParallelUpdateSystem(parallel => parallel.Add(systems.Select(mock => mock.Object)))) + .CreateParallelUpdateSystem(systems.Select(mock => mock.Object))) .Build(); world.Update(); diff --git a/src/Hexecs.Tests/Actors/ActorTestFixture.cs b/src/Hexecs.Tests/Actors/ActorTestFixture.cs index bc3f9ed..d880692 100644 --- a/src/Hexecs.Tests/Actors/ActorTestFixture.cs +++ b/src/Hexecs.Tests/Actors/ActorTestFixture.cs @@ -129,6 +129,7 @@ public T CreateComponent() where T : struct, IActorComponent if (typeof(T) == typeof(Attack)) result = new Attack { Value = RandomInt() }; if (typeof(T) == typeof(Defence)) result = new Defence { Value = RandomInt() }; + if (typeof(T) == typeof(Description)) result = new Description { Name = RandomString(10) }; if (typeof(T) == typeof(Speed)) result = new Speed { Value = RandomInt() }; return result == null diff --git a/src/Hexecs.Tests/Mocks/Description.cs b/src/Hexecs.Tests/Mocks/Description.cs new file mode 100644 index 0000000..f3c9bd1 --- /dev/null +++ b/src/Hexecs.Tests/Mocks/Description.cs @@ -0,0 +1,6 @@ +namespace Hexecs.Tests.Mocks; + +public struct Description : IActorComponent +{ + public string Name; +} \ No newline at end of file diff --git a/src/Hexecs/Actors/Actor1.cs b/src/Hexecs/Actors/Actor1.cs index 7ddeea9..6257dc4 100644 --- a/src/Hexecs/Actors/Actor1.cs +++ b/src/Hexecs/Actors/Actor1.cs @@ -31,7 +31,7 @@ namespace Hexecs.Actors; /// Уникальный идентификатор актёра. /// public readonly uint Id; - + /// /// Пустой актёр, используемый по умолчанию. /// @@ -120,6 +120,14 @@ public ref T AddRelation(in Actor relative, in T relation) where T : struct [MethodImpl(MethodImplOptions.AggressiveInlining)] public Actor As() where T : struct, IActorComponent => Context.GetActor(Id); + /// + /// Преобразует актёра в ссылку на типизированного актёра. + /// + /// Ссылка на типизированного актёра. + /// Выбрасывается, если актёр не найден или не содержит указанный компонент. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ActorRef AsRef() => new(Context, Id, ref Context.GetComponent(Id)); + /// /// Преобразует актёра в ссылку на типизированного актёра. /// diff --git a/src/Hexecs/Actors/ActorConstraint.Builder.cs b/src/Hexecs/Actors/ActorConstraint.Builder.cs index 0a42d55..a81b9fb 100644 --- a/src/Hexecs/Actors/ActorConstraint.Builder.cs +++ b/src/Hexecs/Actors/ActorConstraint.Builder.cs @@ -96,7 +96,7 @@ private void AddSubscription(bool include, IActorComponentPool pool, Func private readonly bool _include; - + /// /// Пул компонентов, на события которого подписывается ограничение. /// @@ -108,7 +108,8 @@ public int CompareTo(Subscription other) /// /// Подписка для сравнения /// Возвращает true, если подписки равны; иначе false - public bool Equals(Subscription other) => _include == other._include && _pool.Id.Equals(other._pool.Id); + public bool Equals(Subscription other) => _include == other._include && + _pool.Id == other._pool.Id; /// /// Определяет, равен ли указанный объект текущей подписке. @@ -121,7 +122,7 @@ public int CompareTo(Subscription other) /// Возвращает хеш-код для текущей подписки. /// /// Хеш-код - public override int GetHashCode() => HashCode.Combine(Check, _include ? 2 : 3); + public override int GetHashCode() => HashCode.Combine(_pool.Id, _include ? 2 : 3); #endregion } diff --git a/src/Hexecs/Actors/ActorConstraint.cs b/src/Hexecs/Actors/ActorConstraint.cs index a06efcc..2f56054 100644 --- a/src/Hexecs/Actors/ActorConstraint.cs +++ b/src/Hexecs/Actors/ActorConstraint.cs @@ -157,7 +157,7 @@ public override bool Equals(object? obj) public override int GetHashCode() => _hash; #endregion - + /// /// Освобождает ресурсы, используемые экземпляром ActorConstraint. /// diff --git a/src/Hexecs/Actors/ActorContext.Entry.cs b/src/Hexecs/Actors/ActorContext.Entry.cs index e61c738..be87629 100644 --- a/src/Hexecs/Actors/ActorContext.Entry.cs +++ b/src/Hexecs/Actors/ActorContext.Entry.cs @@ -26,7 +26,7 @@ public readonly void Serialize(Utf8JsonWriter writer) [method: MethodImpl(MethodImplOptions.AggressiveInlining)] internal struct ComponentBucket() { - public const int InlineArraySize = 10; + public const int InlineArraySize = 6; public int Length { @@ -35,8 +35,8 @@ public int Length } private InlineItemArray _inlineArray; - private ushort[] _array = []; private int _length = 0; + private ushort[] _array = []; public void Add(ushort item) { diff --git a/src/Hexecs/Actors/ActorContextBuilder.cs b/src/Hexecs/Actors/ActorContextBuilder.cs index ec4c887..2d1673d 100644 --- a/src/Hexecs/Actors/ActorContextBuilder.cs +++ b/src/Hexecs/Actors/ActorContextBuilder.cs @@ -437,6 +437,19 @@ public ActorContextBuilder CreateParallelUpdateSystem(int order, Action /// Регистрирует систему параллельного обновления актёров с максимальным порядком выполнения. /// + /// Множество систем, которые будут выполняться параллельно + public ActorContextBuilder CreateParallelUpdateSystem(IEnumerable parallelSystems) + { + var builder = new ParallelSystemBuilder(int.MaxValue); + builder.Add(parallelSystems); + + _updateSystems.Add(new Entry(builder.Build())); + return this; + } + + /// + /// Регистрирует систему параллельного обновления актёров. + /// /// Действие для конфигурации параллельной системы. public ActorContextBuilder CreateParallelUpdateSystem(Action parallelSystem) { diff --git a/src/Hexecs/Actors/ActorDictionary.cs b/src/Hexecs/Actors/ActorDictionary.cs index 05a6f1f..10d7ba2 100644 --- a/src/Hexecs/Actors/ActorDictionary.cs +++ b/src/Hexecs/Actors/ActorDictionary.cs @@ -4,7 +4,7 @@ namespace Hexecs.Actors; [DebuggerDisplay("Length = {Length}")] public sealed class ActorDictionary : IDisposable - where TKey : struct, IEquatable + where TKey : IEquatable where T : struct, IActorComponent { public event Action? Added; diff --git a/src/Hexecs/Assets/AssetConstraint.Builder.cs b/src/Hexecs/Assets/AssetConstraint.Builder.cs index 4a8df86..469d380 100644 --- a/src/Hexecs/Assets/AssetConstraint.Builder.cs +++ b/src/Hexecs/Assets/AssetConstraint.Builder.cs @@ -96,7 +96,7 @@ private void AddSubscription(bool include, IAssetComponentPool pool, Func che public int CompareTo(Subscription other) { - var componentIdComparison = _pool.Id.CompareTo((uint)other._pool.Id); + var componentIdComparison = _pool.Id.CompareTo(other._pool.Id); return componentIdComparison != 0 ? componentIdComparison - : _include.CompareTo((bool)other._include); + : _include.CompareTo(other._include); } public bool Equals(Subscription other) => _include == other._include && - _pool.Id.Equals(other._pool.Id); + _pool.Id == other._pool.Id; public override bool Equals(object? obj) => obj is Subscription other && Equals(other); - public override int GetHashCode() => HashCode.Combine(Check, _include ? 2 : 3); + public override int GetHashCode() => HashCode.Combine(_pool.Id, _include ? 2 : 3); #endregion }