From 922e1dbf377c94937986e591f1755236c0c2e62c Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Tue, 23 Dec 2025 13:25:43 +0300 Subject: [PATCH 01/14] Fix assets --- .../Assets/AssetConstraintShould.cs | 94 +++++++++++++++ src/Hexecs.Tests/Assets/AssetContextShould.cs | 11 +- src/Hexecs.Tests/Assets/AssetFilter1Should.cs | 109 +++++++++++++++++- src/Hexecs.Tests/Assets/AssetTestFixture.cs | 64 ++++++++-- src/Hexecs.Tests/Mocks/BuildingAsset.cs | 5 + src/Hexecs.Tests/Mocks/NonExistentAsset.cs | 5 + src/Hexecs/Assets/AssetConstraint.Builder.cs | 8 +- src/Hexecs/Assets/AssetConstraint.cs | 57 +++++++++ 8 files changed, 328 insertions(+), 25 deletions(-) create mode 100644 src/Hexecs.Tests/Assets/AssetConstraintShould.cs create mode 100644 src/Hexecs.Tests/Mocks/BuildingAsset.cs create mode 100644 src/Hexecs.Tests/Mocks/NonExistentAsset.cs diff --git a/src/Hexecs.Tests/Assets/AssetConstraintShould.cs b/src/Hexecs.Tests/Assets/AssetConstraintShould.cs new file mode 100644 index 0000000..62c6e53 --- /dev/null +++ b/src/Hexecs.Tests/Assets/AssetConstraintShould.cs @@ -0,0 +1,94 @@ +using Hexecs.Assets; +using Hexecs.Tests.Actors; +using Hexecs.Tests.Mocks; + +namespace Hexecs.Tests.Assets; + +public sealed class AssetConstraintShould(AssetTestFixture fixture) : IClassFixture +{ + [Fact(DisplayName = "Должен успешно проходить проверку Applicable, если все условия соблюдены")] + public void Should_Be_Applicable_When_Conditions_Met() + { + // Arrange + + var asset = fixture.CreateAsset(); + var constraint = AssetConstraint + .Include(fixture.Assets) + .Build(); + + // Assert + constraint + .Applicable(asset.Id) + .Should() + .BeTrue(); + } + + [Fact(DisplayName = "Должен возвращать false в Applicable, если компонент исключен")] + public void Should_Not_Be_Applicable_When_Excluded_Component_Exists() + { + // Arrange + var asset = fixture.CreateAsset(); + var constraint = AssetConstraint + .Exclude(fixture.Assets) + .Build(); + + // Assert + constraint + .Applicable(asset.Id) + .Should() + .BeFalse(); + } + + [Fact(DisplayName = "Builder должен выбрасывать исключение при добавлении дублирующегося компонента")] + public void Builder_Should_Throw_On_Duplicate_Component() + { + // Arrange + var builder = AssetConstraint.Include(fixture.Assets); + + // Act + var action = () => builder.Include(); + + // Assert + action + .Should() + .Throw(); + } + + [Fact(DisplayName = "Два одинаковых ограничения должны иметь одинаковый HashCode и быть равны")] + public void Should_Implement_Equality_Correctly() + { + // Arrange + var context = fixture.Assets; + var constraint1 = AssetConstraint.Include(context) + .Exclude() + .Build(); + + var constraint2 = AssetConstraint.Include(context) + .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.CreateAsset(); + var constraint = AssetConstraint + .Include(fixture.Assets) + .Build(); + + constraint + .Applicable(actor.Id) + .Should() + .BeTrue(); + } +} \ No newline at end of file diff --git a/src/Hexecs.Tests/Assets/AssetContextShould.cs b/src/Hexecs.Tests/Assets/AssetContextShould.cs index f9fd09f..5c211b2 100644 --- a/src/Hexecs.Tests/Assets/AssetContextShould.cs +++ b/src/Hexecs.Tests/Assets/AssetContextShould.cs @@ -9,7 +9,7 @@ public void GetAssetByAlias() var alias = fixture.RandomString(); uint? assetId = null; - var (assets, world) = fixture.CreateAssetContext(loader => + fixture.CreateAssetContext(loader => { var asset = loader.CreateAsset(alias); assetId = asset.Id; @@ -17,7 +17,7 @@ public void GetAssetByAlias() // act - var actual = assets.Invoking(ctx => ctx.GetAsset(alias)) + var actual = fixture.Assets.Invoking(ctx => ctx.GetAsset(alias)) .Should() .NotThrow() .Which; @@ -27,16 +27,15 @@ public void GetAssetByAlias() actual.Id .Should() .Be(assetId); - - world.Dispose(); } - + [Fact] public void Throw_IfAssetByAlias_NotFound() { // act && assert - fixture.Assets.Invoking(ctx => ctx.GetAsset(fixture.RandomString())) + var context = fixture.CreateAssetContext(); + context.Invoking(ctx => ctx.GetAsset(fixture.RandomString())) .Should() .Throw(); } diff --git a/src/Hexecs.Tests/Assets/AssetFilter1Should.cs b/src/Hexecs.Tests/Assets/AssetFilter1Should.cs index 87c08b6..c337b58 100644 --- a/src/Hexecs.Tests/Assets/AssetFilter1Should.cs +++ b/src/Hexecs.Tests/Assets/AssetFilter1Should.cs @@ -10,7 +10,7 @@ public void ContainsAllAssets() // arrange var assetIds = new List(); - var (context, world) = fixture.CreateAssetContext(loader => + var context = fixture.CreateAssetContext(loader => { for (int i = 1; i < 100; i++) { @@ -31,8 +31,6 @@ public void ContainsAllAssets() actualActors .Should() .Contain(expectedAssets); - - world.Dispose(); } [Fact(DisplayName = "Фильтр ассетов можно перебирать как AssetRef")] @@ -41,7 +39,7 @@ public void AssetFilterShouldEnumerable() // arrange var expectedIds = new Dictionary(); - var (context, world) = fixture.CreateAssetContext(loader => + var context = fixture.CreateAssetContext(loader => { for (var i = 0; i < 100; i++) { @@ -77,7 +75,108 @@ public void AssetFilterShouldEnumerable() actualIds .Should() .Contain(expectedIds.Keys); + } + + [Fact(DisplayName = "Фильтр должен быть пустым, если компоненты заданного типа отсутствуют")] + public void EmptyFilterWhenNoComponentsExist() + { + // arrange + var context = fixture.CreateAssetContext(loader => loader + .CreateAsset(new NonExistentAsset())); + + // act + var filter = context.Filter(); + + // assert + filter.Length + .Should() + .Be(0); + } + + [Fact(DisplayName = "Фильтр должен учитывать constraint")] + public void FilterWithConstraint() + { + var notExpectedIds = new List(); + uint expectedId = 0; + + // arrange + var context = fixture.CreateAssetContext(loader => + { + notExpectedIds.Add(loader.CreateAsset( + new CarAsset(10, 10), + new UnitAsset()).Id); + + notExpectedIds.Add(loader.CreateAsset( + new CarAsset(30, 30)).Id); - world.Dispose(); + expectedId = loader.CreateAsset( + new CarAsset(20, 20), + new BuildingAsset()).Id; + }); + + // act + + var filter = context.Filter(constraint => constraint + .Exclude() + .Include()); + + // assert + + filter.Length.Should().Be(1); + + filter + .Contains(expectedId) + .Should() + .BeTrue(); + + foreach (var notExpectedId in notExpectedIds) + { + filter.Contains(notExpectedId) + .Should() + .BeFalse(); + } + } + + [Fact(DisplayName = "Метод Get должен выбрасывать исключение, если ассет не найден в фильтре")] + public void GetThrowsExceptionWhenNotFound() + { + // arrange + var context = fixture.CreateAssetContext(loader => { loader.CreateAsset(new CarAsset(1, 1)); }); + + var filter = context.Filter(); + + // act + + Action act = () => filter.Get(999); // Несуществующий ID + + // assert + act + .Should() + .Throw(); + } + + [Fact(DisplayName = "Contains возвращает корректный статус наличия ассета")] + public void ContainsReturnsCorrectStatus() + { + // arrange + uint existingId = 0; + var context = fixture.CreateAssetContext(loader => + { + var asset = loader.CreateAsset(new CarAsset(1, 1)); + existingId = asset.Id; + }); + + var filter = context.Filter(); + + // act & assert + filter + .Contains(existingId) + .Should() + .BeTrue(); + + filter + .Contains(existingId + 100) + .Should() + .BeFalse(); } } \ No newline at end of file diff --git a/src/Hexecs.Tests/Assets/AssetTestFixture.cs b/src/Hexecs.Tests/Assets/AssetTestFixture.cs index 1ff2130..efd2866 100644 --- a/src/Hexecs.Tests/Assets/AssetTestFixture.cs +++ b/src/Hexecs.Tests/Assets/AssetTestFixture.cs @@ -7,31 +7,75 @@ namespace Hexecs.Tests.Assets; public sealed class AssetTestFixture : BaseFixture, IDisposable { - public ActorContext Actors => World.Actors; - public AssetContext Assets => World.Assets; - public readonly World World; + public AssetContext Assets => _assets ?? throw new Exception("Assets isn't configured"); - public AssetTestFixture() + public World World { - World = new WorldBuilder() + get => _world ?? throw new Exception("World isn't configured"); + set + { + if (_world != null) + { + _assets = null; + _world.Dispose(); + } + + _world = value; + } + } + + private AssetContext? _assets; + private World? _world; + + public Asset CreateAsset() where T : struct, IAssetComponent + { + var assetId = Asset.EmptyId; + _world = new WorldBuilder() .CreateAssetData(CreateAssets) + .CreateAssetData(loader => { assetId = loader.CreateAsset(CreateComponent()).Id; }) .Build(); + + _assets = _world.Assets; + return Assets.GetAsset(assetId); } - public (AssetContext, World) CreateAssetContext(Action assets) + public Asset CreateAsset() + where T1 : struct, IAssetComponent + where T2 : struct, IAssetComponent { - var world = new WorldBuilder() + var assetId = Asset.EmptyId; + _world = new WorldBuilder() .CreateAssetData(CreateAssets) - .CreateAssetData(assets) + .CreateAssetData(loader => + { + var asset = loader.CreateAsset(CreateComponent()); + asset.Set(CreateComponent()); + assetId = asset.Id; + }) .Build(); - return (world.Assets, world); + _assets = _world.Assets; + return Assets.GetAsset(assetId); + } + + public AssetContext CreateAssetContext(Action? assets = null) + { + var worldBuilder = new WorldBuilder(); + worldBuilder.CreateAssetData(CreateAssets); + + if (assets != null) worldBuilder.CreateAssetData(assets); + + _world = worldBuilder.Build(); + _assets = _world.Assets; + + return _assets; } public T CreateComponent() where T : struct, IAssetComponent { object? result = null; + if (typeof(T) == typeof(CarAsset)) result = new CarAsset(RandomInt(1, 10), RandomInt(11, 20)); if (typeof(T) == typeof(UnitAsset)) result = new UnitAsset(RandomInt(1, 10), RandomInt(11, 20)); return result == null @@ -50,6 +94,6 @@ private void CreateAssets(IAssetLoader loader) public void Dispose() { - World.Dispose(); + _world?.Dispose(); } } \ No newline at end of file diff --git a/src/Hexecs.Tests/Mocks/BuildingAsset.cs b/src/Hexecs.Tests/Mocks/BuildingAsset.cs new file mode 100644 index 0000000..a124a1b --- /dev/null +++ b/src/Hexecs.Tests/Mocks/BuildingAsset.cs @@ -0,0 +1,5 @@ +using Hexecs.Assets; + +namespace Hexecs.Tests.Mocks; + +public readonly struct BuildingAsset : IAssetComponent; \ No newline at end of file diff --git a/src/Hexecs.Tests/Mocks/NonExistentAsset.cs b/src/Hexecs.Tests/Mocks/NonExistentAsset.cs new file mode 100644 index 0000000..39b823a --- /dev/null +++ b/src/Hexecs.Tests/Mocks/NonExistentAsset.cs @@ -0,0 +1,5 @@ +using Hexecs.Assets; + +namespace Hexecs.Tests.Mocks; + +public readonly struct NonExistentAsset : IAssetComponent; \ No newline at end of file diff --git a/src/Hexecs/Assets/AssetConstraint.Builder.cs b/src/Hexecs/Assets/AssetConstraint.Builder.cs index 469d380..12f8eed 100644 --- a/src/Hexecs/Assets/AssetConstraint.Builder.cs +++ b/src/Hexecs/Assets/AssetConstraint.Builder.cs @@ -26,7 +26,7 @@ public AssetConstraint Build() var subscriptions = new Subscription[_length]; Array.Copy(_subscriptions, subscriptions, _length); - var instance = new AssetConstraint(_hash, _subscriptions); + var instance = new AssetConstraint(_hash, subscriptions); ArrayPool.Shared.Return(_subscriptions, true); @@ -78,7 +78,7 @@ private void AddSubscription(bool include, IAssetComponentPool pool, Func.Id; // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator - foreach (var exists in _subscriptions) + foreach (var exists in _subscriptions.AsSpan(0, _length)) { if (exists.ComponentId == id) AssetError.ConstraintExists(); } @@ -91,9 +91,9 @@ private void AddSubscription(bool include, IAssetComponentPool pool, Func { + /// + /// Создает построитель ограничений с исключением указанного компонента. + /// + /// Тип компонента, который должен отсутствовать у ассета + /// Контекст ассетов + /// Построитель ограничений + public static Builder Exclude(AssetContext context) where T1 : struct, IAssetComponent + { + return new Builder(context).Exclude(); + } + + /// + /// Создает построитель ограничений с включением указанного компонента. + /// + /// Тип компонента, который должен присутствовать у ассета + /// Контекст ассетов + /// Построитель ограничений + public static Builder Include(AssetContext context) where T1 : struct, IAssetComponent + { + return new Builder(context).Include(); + } + + /// + /// Создает построитель ограничений с включением двух указанных компонентов. + /// + /// Первый тип компонента, который должен присутствовать у ассета + /// Второй тип компонента, который должен присутствовать у ассета + /// Контекст актёров + /// Построитель ограничений + public static Builder Include(AssetContext context) + where T1 : struct, IAssetComponent + where T2 : struct, IAssetComponent + { + return new Builder(context) + .Include() + .Include(); + } + + /// + /// Создает построитель ограничений с включением трех указанных компонентов. + /// + /// Первый тип компонента, который должен присутствовать у ассета + /// Второй тип компонента, который должен присутствовать у ассета + /// Третий тип компонента, который должен присутствовать у ассета + /// Контекст актёров + /// Построитель ограничений + public static Builder Include(AssetContext context) + where T1 : struct, IAssetComponent + where T2 : struct, IAssetComponent + where T3 : struct, IAssetComponent + { + return new Builder(context) + .Include() + .Include() + .Include(); + } + private readonly int _hash; private readonly Subscription[] _subscriptions; From f8967e025789293a3e7946529d039bce05bcde75 Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Tue, 23 Dec 2025 13:41:56 +0300 Subject: [PATCH 02/14] Fix assets --- src/Hexecs.Tests/Assets/AssetFilter1Should.cs | 3 +- src/Hexecs.Tests/Assets/AssetFilter2Should.cs | 193 ++++++++++++++++++ src/Hexecs.Tests/Mocks/SubjectAsset.cs | 5 + 3 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 src/Hexecs.Tests/Assets/AssetFilter2Should.cs create mode 100644 src/Hexecs.Tests/Mocks/SubjectAsset.cs diff --git a/src/Hexecs.Tests/Assets/AssetFilter1Should.cs b/src/Hexecs.Tests/Assets/AssetFilter1Should.cs index c337b58..4ddd7a8 100644 --- a/src/Hexecs.Tests/Assets/AssetFilter1Should.cs +++ b/src/Hexecs.Tests/Assets/AssetFilter1Should.cs @@ -81,8 +81,7 @@ public void AssetFilterShouldEnumerable() public void EmptyFilterWhenNoComponentsExist() { // arrange - var context = fixture.CreateAssetContext(loader => loader - .CreateAsset(new NonExistentAsset())); + var context = fixture.CreateAssetContext(); // act var filter = context.Filter(); diff --git a/src/Hexecs.Tests/Assets/AssetFilter2Should.cs b/src/Hexecs.Tests/Assets/AssetFilter2Should.cs new file mode 100644 index 0000000..11ca2c6 --- /dev/null +++ b/src/Hexecs.Tests/Assets/AssetFilter2Should.cs @@ -0,0 +1,193 @@ +using Hexecs.Tests.Mocks; + +namespace Hexecs.Tests.Assets; + +public sealed class AssetFilter2Should(AssetTestFixture fixture) : IClassFixture +{ + [Fact(DisplayName = "Фильтр ассетов должен содержать все созданные ассеты")] + public void ContainsAllAssets() + { + // arrange + var assetIds = new List(); + + var context = fixture.CreateAssetContext(loader => + { + for (int i = 1; i < 100; i++) + { + var asset = loader.CreateAsset( + new CarAsset(i, i), + new UnitAsset(i, i)); + assetIds.Add(asset.Id); + } + }); + + var expectedAssets = assetIds.Select(id => context.GetAsset(id)).ToArray(); + + // act + + var filter = context.Filter(); + var actualActors = filter.ToArray(); + + // assert + + actualActors + .Should() + .Contain(expectedAssets); + } + + [Fact(DisplayName = "Фильтр ассетов можно перебирать как AssetRef")] + public void AssetFilterShouldEnumerable() + { + // arrange + var expectedIds = new Dictionary(); + + var context = fixture.CreateAssetContext(loader => + { + for (var i = 1; i < 100; i++) + { + var component1 = new CarAsset(i, i); + var component2 = new UnitAsset(i, i); + var asset = loader.CreateAsset(component1, component2); + + expectedIds.Add(asset.Id, (component1, component2)); + } + }); + + // act + + var filter = context.Filter(); + + // assert + + var actualIds = new List(); + foreach (var asset in filter) + { + actualIds.Add(asset.Id); + asset + .Component1 + .Should().Be(expectedIds[asset.Id].Item1); + + asset + .Component2 + .Should().Be(expectedIds[asset.Id].Item2); + } + + filter.Length + .Should().Be(expectedIds.Count); + + actualIds + .Should() + .HaveCount(expectedIds.Count); + + actualIds + .Should() + .Contain(expectedIds.Keys); + } + + [Fact(DisplayName = "Фильтр должен быть пустым, если компоненты заданного типа отсутствуют")] + public void EmptyFilterWhenNoComponentsExist() + { + // arrange + var context = fixture.CreateAssetContext(); + + // act + var filter = context.Filter(); + + // assert + filter.Length + .Should() + .Be(0); + } + + [Fact(DisplayName = "Фильтр должен учитывать constraint")] + public void FilterWithConstraint() + { + var notExpectedIds = new List(); + uint expectedId = 0; + + // arrange + var context = fixture.CreateAssetContext(loader => + { + notExpectedIds.Add(loader.CreateAsset( + new CarAsset(10, 10), + new UnitAsset(), + new BuildingAsset()).Id); + + notExpectedIds.Add(loader.CreateAsset( + new CarAsset(10, 10), + new UnitAsset(), + new NonExistentAsset()).Id); + + expectedId = loader.CreateAsset( + new CarAsset(20, 20), + new UnitAsset(), + new SubjectAsset()).Id; + }); + + // act + + var filter = context.Filter(constraint => constraint + .Exclude() + .Include()); + + // assert + + filter.Length.Should().Be(1); + + filter + .Contains(expectedId) + .Should() + .BeTrue(); + + foreach (var notExpectedId in notExpectedIds) + { + filter.Contains(notExpectedId) + .Should() + .BeFalse(); + } + } + + [Fact(DisplayName = "Метод Get должен выбрасывать исключение, если ассет не найден в фильтре")] + public void GetThrowsExceptionWhenNotFound() + { + // arrange + var context = fixture.CreateAssetContext(loader => loader + .CreateAsset(new CarAsset(1, 1), new UnitAsset())); + + var filter = context.Filter(); + + // act + + Action act = () => filter.Get(999); // Несуществующий ID + + // assert + act + .Should() + .Throw(); + } + + [Fact(DisplayName = "Contains возвращает корректный статус наличия ассета")] + public void ContainsReturnsCorrectStatus() + { + // arrange + uint existingId = 0; + var context = fixture.CreateAssetContext(loader => + { + var asset = loader.CreateAsset(new CarAsset(1, 1), new UnitAsset()); + existingId = asset.Id; + }); + + var filter = context.Filter(); + + // act & assert + filter + .Contains(existingId) + .Should() + .BeTrue(); + + filter + .Contains(existingId + 100) + .Should() + .BeFalse(); + } +} \ No newline at end of file diff --git a/src/Hexecs.Tests/Mocks/SubjectAsset.cs b/src/Hexecs.Tests/Mocks/SubjectAsset.cs new file mode 100644 index 0000000..38fef95 --- /dev/null +++ b/src/Hexecs.Tests/Mocks/SubjectAsset.cs @@ -0,0 +1,5 @@ +using Hexecs.Assets; + +namespace Hexecs.Tests.Mocks; + +public readonly struct SubjectAsset : IAssetComponent; \ No newline at end of file From 70ae30defeb3722aec4e0cc4d4547878594617e3 Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Tue, 23 Dec 2025 13:53:57 +0300 Subject: [PATCH 03/14] Fix assets --- src/Hexecs.Tests/Assets/AssetFilter2Should.cs | 2 +- src/Hexecs.Tests/Assets/AssetFilter3Should.cs | 196 ++++++++++++++++++ src/Hexecs.Tests/Mocks/DecisionAsset.cs | 9 + 3 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/Hexecs.Tests/Assets/AssetFilter3Should.cs create mode 100644 src/Hexecs.Tests/Mocks/DecisionAsset.cs diff --git a/src/Hexecs.Tests/Assets/AssetFilter2Should.cs b/src/Hexecs.Tests/Assets/AssetFilter2Should.cs index 11ca2c6..02a3c9f 100644 --- a/src/Hexecs.Tests/Assets/AssetFilter2Should.cs +++ b/src/Hexecs.Tests/Assets/AssetFilter2Should.cs @@ -177,7 +177,7 @@ public void ContainsReturnsCorrectStatus() existingId = asset.Id; }); - var filter = context.Filter(); + var filter = context.Filter(); // act & assert filter diff --git a/src/Hexecs.Tests/Assets/AssetFilter3Should.cs b/src/Hexecs.Tests/Assets/AssetFilter3Should.cs new file mode 100644 index 0000000..93d3769 --- /dev/null +++ b/src/Hexecs.Tests/Assets/AssetFilter3Should.cs @@ -0,0 +1,196 @@ +using Hexecs.Tests.Mocks; + +namespace Hexecs.Tests.Assets; + +public sealed class AssetFilter3Should(AssetTestFixture fixture) : IClassFixture +{ + [Fact(DisplayName = "Фильтр ассетов должен содержать все созданные ассеты")] + public void ContainsAllAssets() + { + // arrange + var assetIds = new List(); + + var context = fixture.CreateAssetContext(loader => + { + for (int i = 1; i < 100; i++) + { + var asset = loader.CreateAsset( + new CarAsset(i, i), + new DecisionAsset(i, i), + new UnitAsset(i, i)); + assetIds.Add(asset.Id); + } + }); + + var expectedAssets = assetIds.Select(id => context.GetAsset(id)).ToArray(); + + // act + + var filter = context.Filter(); + var actualActors = filter.ToArray(); + + // assert + + actualActors + .Should() + .Contain(expectedAssets); + } + + [Fact(DisplayName = "Фильтр ассетов можно перебирать как AssetRef")] + public void AssetFilterShouldEnumerable() + { + // arrange + var expectedIds = new Dictionary(); + + var context = fixture.CreateAssetContext(loader => + { + for (var i = 1; i < 100; i++) + { + var component1 = new CarAsset(i, i); + var component2 = new DecisionAsset(i, i); + var component3 = new UnitAsset(i, i); + var asset = loader.CreateAsset(component1, component2, component3); + + expectedIds.Add(asset.Id, (component1, component2, component3)); + } + }); + + // act + + var filter = context.Filter(); + + // assert + + var actualIds = new List(); + foreach (var asset in filter) + { + actualIds.Add(asset.Id); + asset + .Component1 + .Should().Be(expectedIds[asset.Id].Item1); + + asset + .Component2 + .Should().Be(expectedIds[asset.Id].Item2); + + asset + .Component3 + .Should().Be(expectedIds[asset.Id].Item3); + } + + filter.Length + .Should().Be(expectedIds.Count); + + actualIds + .Should() + .HaveCount(expectedIds.Count); + + actualIds + .Should() + .Contain(expectedIds.Keys); + } + + [Fact(DisplayName = "Фильтр должен быть пустым, если компоненты заданного типа отсутствуют")] + public void EmptyFilterWhenNoComponentsExist() + { + // arrange + var context = fixture.CreateAssetContext(); + + // act + var filter = context.Filter(); + + // assert + filter.Length + .Should() + .Be(0); + } + + [Fact(DisplayName = "Фильтр должен учитывать constraint")] + public void FilterWithConstraint() + { + var notExpectedIds = new List(); + uint expectedId = 0; + + // arrange + var context = fixture.CreateAssetContext(loader => + { + var asset = loader.CreateAsset(new CarAsset(10, 10), new DecisionAsset(), new UnitAsset()); + asset.Set(new BuildingAsset()); + notExpectedIds.Add(asset.Id); + + asset = loader.CreateAsset(new CarAsset(10, 10), new DecisionAsset(), new UnitAsset()); + asset.Set(new NonExistentAsset()); + notExpectedIds.Add(asset.Id); + + asset = loader.CreateAsset(new CarAsset(10, 10), new DecisionAsset(), new UnitAsset()); + asset.Set(new SubjectAsset()); + expectedId = asset.Id; + }); + + // act + + var filter = context.Filter(constraint => constraint + .Exclude() + .Include()); + + // assert + + filter.Length.Should().Be(1); + + filter + .Contains(expectedId) + .Should() + .BeTrue(); + + foreach (var notExpectedId in notExpectedIds) + { + filter.Contains(notExpectedId) + .Should() + .BeFalse(); + } + } + + [Fact(DisplayName = "Метод Get должен выбрасывать исключение, если ассет не найден в фильтре")] + public void GetThrowsExceptionWhenNotFound() + { + // arrange + var context = fixture.CreateAssetContext(loader => loader + .CreateAsset(new CarAsset(1, 1), new DecisionAsset(), new UnitAsset())); + + var filter = context.Filter(); + + // act + + Action act = () => filter.Get(999); // Несуществующий ID + + // assert + act + .Should() + .Throw(); + } + + [Fact(DisplayName = "Contains возвращает корректный статус наличия ассета")] + public void ContainsReturnsCorrectStatus() + { + // arrange + uint existingId = 0; + var context = fixture.CreateAssetContext(loader => + { + var asset = loader.CreateAsset(new CarAsset(1, 1), new DecisionAsset(), new UnitAsset()); + existingId = asset.Id; + }); + + var filter = context.Filter(); + + // act & assert + filter + .Contains(existingId) + .Should() + .BeTrue(); + + filter + .Contains(existingId + 100) + .Should() + .BeFalse(); + } +} \ No newline at end of file diff --git a/src/Hexecs.Tests/Mocks/DecisionAsset.cs b/src/Hexecs.Tests/Mocks/DecisionAsset.cs new file mode 100644 index 0000000..040d776 --- /dev/null +++ b/src/Hexecs.Tests/Mocks/DecisionAsset.cs @@ -0,0 +1,9 @@ +using Hexecs.Assets; + +namespace Hexecs.Tests.Mocks; + +public readonly struct DecisionAsset(int min, int max) : IAssetComponent +{ + public readonly int Min = min; + public readonly int Max = max; +} \ No newline at end of file From 80216b9a19e1eff8280881c0bbf3b8d35314406c Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Tue, 23 Dec 2025 15:05:28 +0300 Subject: [PATCH 04/14] Fix assets --- Directory.Packages.props | 9 ++ Hexecs.sln | 8 +- .../Hexecs.Benchmark.Map.csproj | 18 ++++ src/Hexecs.Benchmark.Map/MapGame.cs | 6 ++ src/Hexecs.Benchmark.Map/Program.cs | 3 + src/Hexecs.Benchmarks.MonoGame/Program.cs | 4 - .../Components/CircleColor.cs | 2 +- .../Components/Position.cs | 2 +- .../Components/Velocity.cs | 2 +- .../Content/Instancing.fx | 0 .../Content/Instancing.mgfx | Bin src/Hexecs.Benchmarks.Noise/FpsCounter.cs | 67 +++++++++++++++ .../Hexecs.Benchmarks.Noise.csproj} | 10 +-- .../NoiseGame.cs} | 79 ++++-------------- src/Hexecs.Benchmarks.Noise/Program.cs | 4 + .../Systems/InstanceData.cs | 2 +- .../Systems/MovementSystem.cs | 4 +- .../Systems/RenderSystem.cs | 4 +- .../Usings.cs | 0 19 files changed, 143 insertions(+), 81 deletions(-) create mode 100644 Directory.Packages.props create mode 100644 src/Hexecs.Benchmark.Map/Hexecs.Benchmark.Map.csproj create mode 100644 src/Hexecs.Benchmark.Map/MapGame.cs create mode 100644 src/Hexecs.Benchmark.Map/Program.cs delete mode 100644 src/Hexecs.Benchmarks.MonoGame/Program.cs rename src/{Hexecs.Benchmarks.MonoGame => Hexecs.Benchmarks.Noise}/Components/CircleColor.cs (92%) rename src/{Hexecs.Benchmarks.MonoGame => Hexecs.Benchmarks.Noise}/Components/Position.cs (87%) rename src/{Hexecs.Benchmarks.MonoGame => Hexecs.Benchmarks.Noise}/Components/Velocity.cs (86%) rename src/{Hexecs.Benchmarks.MonoGame => Hexecs.Benchmarks.Noise}/Content/Instancing.fx (100%) rename src/{Hexecs.Benchmarks.MonoGame => Hexecs.Benchmarks.Noise}/Content/Instancing.mgfx (100%) create mode 100644 src/Hexecs.Benchmarks.Noise/FpsCounter.cs rename src/{Hexecs.Benchmarks.MonoGame/Hexecs.Benchmarks.MonoGame.csproj => Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj} (72%) rename src/{Hexecs.Benchmarks.MonoGame/BenchmarkGame.cs => Hexecs.Benchmarks.Noise/NoiseGame.cs} (64%) create mode 100644 src/Hexecs.Benchmarks.Noise/Program.cs rename src/{Hexecs.Benchmarks.MonoGame => Hexecs.Benchmarks.Noise}/Systems/InstanceData.cs (94%) rename src/{Hexecs.Benchmarks.MonoGame => Hexecs.Benchmarks.Noise}/Systems/MovementSystem.cs (91%) rename src/{Hexecs.Benchmarks.MonoGame => Hexecs.Benchmarks.Noise}/Systems/RenderSystem.cs (97%) rename src/{Hexecs.Benchmarks.MonoGame => Hexecs.Benchmarks.Noise}/Usings.cs (100%) diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..729ad6c --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,9 @@ + + + true + true + + + + + \ No newline at end of file diff --git a/Hexecs.sln b/Hexecs.sln index b6b32c4..8ee2630 100644 --- a/Hexecs.sln +++ b/Hexecs.sln @@ -5,7 +5,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Tests", "src\Hexecs. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Benchmarks", "src\Hexecs.Benchmarks\Hexecs.Benchmarks.csproj", "{6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Benchmarks.MonoGame", "src\Hexecs.Benchmarks.MonoGame\Hexecs.Benchmarks.MonoGame.csproj", "{1F5BACA8-7AC3-48B4-9F28-532F7684C80B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Benchmarks.Noise", "src\Hexecs.Benchmarks.Noise\Hexecs.Benchmarks.Noise.csproj", "{1F5BACA8-7AC3-48B4-9F28-532F7684C80B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Benchmark.Map", "src\Hexecs.Benchmark.Map\Hexecs.Benchmark.Map.csproj", "{BAE6244E-6879-4D30-B105-49ABB05A8D63}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -29,5 +31,9 @@ Global {1F5BACA8-7AC3-48B4-9F28-532F7684C80B}.Debug|Any CPU.Build.0 = Debug|Any CPU {1F5BACA8-7AC3-48B4-9F28-532F7684C80B}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F5BACA8-7AC3-48B4-9F28-532F7684C80B}.Release|Any CPU.Build.0 = Release|Any CPU + {BAE6244E-6879-4D30-B105-49ABB05A8D63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAE6244E-6879-4D30-B105-49ABB05A8D63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAE6244E-6879-4D30-B105-49ABB05A8D63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAE6244E-6879-4D30-B105-49ABB05A8D63}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Hexecs.Benchmark.Map/Hexecs.Benchmark.Map.csproj b/src/Hexecs.Benchmark.Map/Hexecs.Benchmark.Map.csproj new file mode 100644 index 0000000..7668e84 --- /dev/null +++ b/src/Hexecs.Benchmark.Map/Hexecs.Benchmark.Map.csproj @@ -0,0 +1,18 @@ + + + + Exe + net10.0 + true + true + + + + + + + + + + + diff --git a/src/Hexecs.Benchmark.Map/MapGame.cs b/src/Hexecs.Benchmark.Map/MapGame.cs new file mode 100644 index 0000000..797718e --- /dev/null +++ b/src/Hexecs.Benchmark.Map/MapGame.cs @@ -0,0 +1,6 @@ +namespace Hexecs.Benchmark.Map; + +internal sealed class MapGame +{ + +} \ No newline at end of file diff --git a/src/Hexecs.Benchmark.Map/Program.cs b/src/Hexecs.Benchmark.Map/Program.cs new file mode 100644 index 0000000..e5dff12 --- /dev/null +++ b/src/Hexecs.Benchmark.Map/Program.cs @@ -0,0 +1,3 @@ +// See https://aka.ms/new-console-template for more information + +Console.WriteLine("Hello, World!"); \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.MonoGame/Program.cs b/src/Hexecs.Benchmarks.MonoGame/Program.cs deleted file mode 100644 index 51ac21d..0000000 --- a/src/Hexecs.Benchmarks.MonoGame/Program.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Hexecs.Benchmarks.MonoGame; - -using var game = new BenchmarkGame(); -game.Run(); diff --git a/src/Hexecs.Benchmarks.MonoGame/Components/CircleColor.cs b/src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs similarity index 92% rename from src/Hexecs.Benchmarks.MonoGame/Components/CircleColor.cs rename to src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs index 8b132ec..e5819ff 100644 --- a/src/Hexecs.Benchmarks.MonoGame/Components/CircleColor.cs +++ b/src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs @@ -1,7 +1,7 @@ using Hexecs.Actors; using Microsoft.Xna.Framework; -namespace Hexecs.Benchmarks.MonoGame.Components; +namespace Hexecs.Benchmarks.Noise.Components; public readonly struct CircleColor(Color value) : IActorComponent { diff --git a/src/Hexecs.Benchmarks.MonoGame/Components/Position.cs b/src/Hexecs.Benchmarks.Noise/Components/Position.cs similarity index 87% rename from src/Hexecs.Benchmarks.MonoGame/Components/Position.cs rename to src/Hexecs.Benchmarks.Noise/Components/Position.cs index 833c12e..03299cb 100644 --- a/src/Hexecs.Benchmarks.MonoGame/Components/Position.cs +++ b/src/Hexecs.Benchmarks.Noise/Components/Position.cs @@ -1,7 +1,7 @@ using Hexecs.Actors; using Microsoft.Xna.Framework; -namespace Hexecs.Benchmarks.MonoGame.Components; +namespace Hexecs.Benchmarks.Noise.Components; public struct Position(Vector2 value) : IActorComponent { diff --git a/src/Hexecs.Benchmarks.MonoGame/Components/Velocity.cs b/src/Hexecs.Benchmarks.Noise/Components/Velocity.cs similarity index 86% rename from src/Hexecs.Benchmarks.MonoGame/Components/Velocity.cs rename to src/Hexecs.Benchmarks.Noise/Components/Velocity.cs index 9f645a6..b999e31 100644 --- a/src/Hexecs.Benchmarks.MonoGame/Components/Velocity.cs +++ b/src/Hexecs.Benchmarks.Noise/Components/Velocity.cs @@ -1,7 +1,7 @@ using Hexecs.Actors; using Microsoft.Xna.Framework; -namespace Hexecs.Benchmarks.MonoGame.Components; +namespace Hexecs.Benchmarks.Noise.Components; public struct Velocity(Vector2 value) : IActorComponent { diff --git a/src/Hexecs.Benchmarks.MonoGame/Content/Instancing.fx b/src/Hexecs.Benchmarks.Noise/Content/Instancing.fx similarity index 100% rename from src/Hexecs.Benchmarks.MonoGame/Content/Instancing.fx rename to src/Hexecs.Benchmarks.Noise/Content/Instancing.fx diff --git a/src/Hexecs.Benchmarks.MonoGame/Content/Instancing.mgfx b/src/Hexecs.Benchmarks.Noise/Content/Instancing.mgfx similarity index 100% rename from src/Hexecs.Benchmarks.MonoGame/Content/Instancing.mgfx rename to src/Hexecs.Benchmarks.Noise/Content/Instancing.mgfx diff --git a/src/Hexecs.Benchmarks.Noise/FpsCounter.cs b/src/Hexecs.Benchmarks.Noise/FpsCounter.cs new file mode 100644 index 0000000..5683953 --- /dev/null +++ b/src/Hexecs.Benchmarks.Noise/FpsCounter.cs @@ -0,0 +1,67 @@ +using Microsoft.Xna.Framework; + +namespace Hexecs.Benchmarks.Noise; + +internal sealed class FpsCounter +{ + private readonly Func _countResolver; + private readonly int[] _fpsHistory; + private readonly GameWindow _window; + + // Поля для статистики + private double _frameTime; + private int _fps; + private int _frameCount; + private double _fpsTimer; + private int _secondsCounter; + + // Для среднего значения за минуту (Rolling Average) + private int _historyIndex; + private bool _historyFull; + private double _avgFps; + + public FpsCounter(Func countResolver, GameWindow window) + { + _countResolver = countResolver; + _window = window; + _fpsHistory = new int[60]; + } + + public void Draw(GameTime gameTime) + { + var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; + _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; + _fpsTimer += elapsedSeconds; + _frameCount++; + + // Считаем FPS каждую секунду для точности истории + if (_fpsTimer >= 1.0) + { + _fps = _frameCount; + + // Обновляем историю для Avg + _fpsHistory[_historyIndex] = _fps; + _historyIndex = (_historyIndex + 1) % 60; + if (_historyIndex == 0) _historyFull = true; + + // Считаем среднее за минуту + var historyCount = _historyFull ? 60 : _historyIndex; + var sum = 0; + for (var i = 0; i < historyCount; i++) sum += _fpsHistory[i]; + _avgFps = (double)sum / historyCount; + + _frameCount = 0; + _fpsTimer -= 1.0; + _secondsCounter++; + + if (_secondsCounter >= 1) + { + var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; + var count = _countResolver(); + _window.Title = $"FPS: {_fps} | Avg FPS: {_avgFps:F1} | Entities: {count:N0} | Frame Time: {_frameTime:F2}ms | Alloc: {alloc:F2}Mb"; + + _secondsCounter = 0; + } + } + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.MonoGame/Hexecs.Benchmarks.MonoGame.csproj b/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj similarity index 72% rename from src/Hexecs.Benchmarks.MonoGame/Hexecs.Benchmarks.MonoGame.csproj rename to src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj index f5b53ac..872e626 100644 --- a/src/Hexecs.Benchmarks.MonoGame/Hexecs.Benchmarks.MonoGame.csproj +++ b/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj @@ -8,7 +8,7 @@ - + @@ -16,13 +16,13 @@ - - PreserveNewest - + + PreserveNewest + - + diff --git a/src/Hexecs.Benchmarks.MonoGame/BenchmarkGame.cs b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs similarity index 64% rename from src/Hexecs.Benchmarks.MonoGame/BenchmarkGame.cs rename to src/Hexecs.Benchmarks.Noise/NoiseGame.cs index 3f5c73f..2da28ba 100644 --- a/src/Hexecs.Benchmarks.MonoGame/BenchmarkGame.cs +++ b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs @@ -1,6 +1,6 @@ using Hexecs.Actors; -using Hexecs.Benchmarks.MonoGame.Components; -using Hexecs.Benchmarks.MonoGame.Systems; +using Hexecs.Benchmarks.Noise.Components; +using Hexecs.Benchmarks.Noise.Systems; using Hexecs.Dependencies; using Hexecs.Threading; using Hexecs.Worlds; @@ -8,33 +8,22 @@ using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; -namespace Hexecs.Benchmarks.MonoGame; +namespace Hexecs.Benchmarks.Noise; -public class BenchmarkGame : Game +public class NoiseGame : Game { - private ActorFilter? _entitiesCountFilter; private readonly GraphicsDeviceManager _graphics; - private World _world = null!; - private ActorContext _context = null!; private readonly Random _random = new(); - // Поля для статистики - private double _frameTime; - private int _fps; - private int _frameCount; - private double _fpsTimer; - private int _secondsCounter; - - // Для среднего значения за минуту (Rolling Average) - private readonly int[] _fpsHistory = new int[60]; - private int _historyIndex; - private bool _historyFull; - private double _avgFps; + private ActorContext _context = null!; + private ActorFilter? _entitiesCountFilter; + private FpsCounter _fpsCounter = null!; + private World _world = null!; private const int InitialEntityCount = 2_000_000; private const int MaxEntityCount = 3_000_000; - public BenchmarkGame() + public NoiseGame() { _graphics = new GraphicsDeviceManager(this) { @@ -78,7 +67,7 @@ protected override void Initialize() .Build(); _context = _world.Actors; - _entitiesCountFilter = _context.Filter(); + _fpsCounter = new FpsCounter(() => _context.Length, Window); for (var i = 0; i < InitialEntityCount; i++) { @@ -93,23 +82,22 @@ private void SpawnEntity(CircleColor? color = null) { var actor = _context.CreateActor(); actor.Add(Position.Create( - x: _graphics.PreferredBackBufferWidth / 2, + x: _graphics.PreferredBackBufferWidth / 2, y: _graphics.PreferredBackBufferHeight / 2)); - + actor.Add(Velocity.Create( x: (float)(_random.NextDouble() * 200 - 100), y: (float)(_random.NextDouble() * 200 - 100))); - + actor.Add(color ?? CircleColor.CreateRgba(_random)); } protected override void Update(GameTime gameTime) { - var count = _entitiesCountFilter?.Length ?? 0; - var keyboard = Keyboard.GetState(); if (keyboard.IsKeyDown(Keys.Space)) { + var count = _entitiesCountFilter?.Length ?? 0; var color = CircleColor.CreateRgba(_random); for (var i = 0; i < 50; i++) { @@ -124,43 +112,6 @@ protected override void Update(GameTime gameTime) _world.Update(gameTime.ElapsedGameTime, gameTime.TotalGameTime); - // Сбор статистики - var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; - _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; - _fpsTimer += elapsedSeconds; - _frameCount++; - - // Считаем FPS каждую секунду для точности истории - if (_fpsTimer >= 1.0) - { - _fps = _frameCount; - - // Обновляем историю для Avg - _fpsHistory[_historyIndex] = _fps; - _historyIndex = (_historyIndex + 1) % 60; - if (_historyIndex == 0) _historyFull = true; - - // Считаем среднее за минуту - var historyCount = _historyFull ? 60 : _historyIndex; - var sum = 0; - for (var i = 0; i < historyCount; i++) sum += _fpsHistory[i]; - _avgFps = (double)sum / historyCount; - - _frameCount = 0; - _fpsTimer -= 1.0; - _secondsCounter++; - - if (_secondsCounter >= 1) - { - var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; - count = _entitiesCountFilter?.Length ?? 0; - Window.Title = - $"FPS: {_fps} | Avg FPS: {_avgFps:F1} | Entities: {count:N0} | Frame Time: {_frameTime:F2}ms | Alloc: {alloc:F2}Mb"; - - _secondsCounter = 0; - } - } - base.Update(gameTime); } @@ -168,6 +119,8 @@ protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.White); + _fpsCounter.Draw(gameTime); + _world.Draw(gameTime.ElapsedGameTime, gameTime.TotalGameTime); base.Draw(gameTime); diff --git a/src/Hexecs.Benchmarks.Noise/Program.cs b/src/Hexecs.Benchmarks.Noise/Program.cs new file mode 100644 index 0000000..7cfe2fd --- /dev/null +++ b/src/Hexecs.Benchmarks.Noise/Program.cs @@ -0,0 +1,4 @@ +using Hexecs.Benchmarks.Noise; + +using var game = new NoiseGame(); +game.Run(); diff --git a/src/Hexecs.Benchmarks.MonoGame/Systems/InstanceData.cs b/src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs similarity index 94% rename from src/Hexecs.Benchmarks.MonoGame/Systems/InstanceData.cs rename to src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs index 73c0395..176ec1c 100644 --- a/src/Hexecs.Benchmarks.MonoGame/Systems/InstanceData.cs +++ b/src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs @@ -2,7 +2,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -namespace Hexecs.Benchmarks.MonoGame.Systems; +namespace Hexecs.Benchmarks.Noise.Systems; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct InstanceData : IVertexType diff --git a/src/Hexecs.Benchmarks.MonoGame/Systems/MovementSystem.cs b/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs similarity index 91% rename from src/Hexecs.Benchmarks.MonoGame/Systems/MovementSystem.cs rename to src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs index a81bebd..2bab8de 100644 --- a/src/Hexecs.Benchmarks.MonoGame/Systems/MovementSystem.cs +++ b/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs @@ -1,11 +1,11 @@ using Hexecs.Actors; using Hexecs.Actors.Systems; -using Hexecs.Benchmarks.MonoGame.Components; +using Hexecs.Benchmarks.Noise.Components; using Hexecs.Threading; using Hexecs.Worlds; using Microsoft.Xna.Framework; -namespace Hexecs.Benchmarks.MonoGame.Systems; +namespace Hexecs.Benchmarks.Noise.Systems; public sealed class MovementSystem( ActorContext context, diff --git a/src/Hexecs.Benchmarks.MonoGame/Systems/RenderSystem.cs b/src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs similarity index 97% rename from src/Hexecs.Benchmarks.MonoGame/Systems/RenderSystem.cs rename to src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs index 3b8bb42..f0d80f3 100644 --- a/src/Hexecs.Benchmarks.MonoGame/Systems/RenderSystem.cs +++ b/src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs @@ -1,11 +1,11 @@ using Hexecs.Actors; using Hexecs.Actors.Systems; -using Hexecs.Benchmarks.MonoGame.Components; +using Hexecs.Benchmarks.Noise.Components; using Hexecs.Worlds; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -namespace Hexecs.Benchmarks.MonoGame.Systems; +namespace Hexecs.Benchmarks.Noise.Systems; public sealed class RenderSystem : DrawSystem { diff --git a/src/Hexecs.Benchmarks.MonoGame/Usings.cs b/src/Hexecs.Benchmarks.Noise/Usings.cs similarity index 100% rename from src/Hexecs.Benchmarks.MonoGame/Usings.cs rename to src/Hexecs.Benchmarks.Noise/Usings.cs From 95d66b32b1fbd84edc7dc5e26abecfdb4e77dacf Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Tue, 23 Dec 2025 16:44:32 +0300 Subject: [PATCH 05/14] 0.4.3 map bench --- Directory.Build.props | 24 +++--- Directory.Packages.props | 16 ++++ Hexecs.sln | 39 --------- Hexecs.slnx | 9 ++ src/Hexecs.Benchmark.Map/MapGame.cs | 6 -- .../Hexecs.Benchmarks.Map.csproj} | 6 +- src/Hexecs.Benchmarks.Map/MapGame.cs | 82 +++++++++++++++++++ .../Program.cs | 0 .../Hexecs.Benchmarks.Noise.csproj | 8 +- src/Hexecs.Benchmarks.Noise/NoiseGame.cs | 3 +- src/Hexecs.Benchmarks/Program.cs | 2 +- 11 files changed, 135 insertions(+), 60 deletions(-) delete mode 100644 Hexecs.sln create mode 100644 Hexecs.slnx delete mode 100644 src/Hexecs.Benchmark.Map/MapGame.cs rename src/{Hexecs.Benchmark.Map/Hexecs.Benchmark.Map.csproj => Hexecs.Benchmarks.Map/Hexecs.Benchmarks.Map.csproj} (73%) create mode 100644 src/Hexecs.Benchmarks.Map/MapGame.cs rename src/{Hexecs.Benchmark.Map => Hexecs.Benchmarks.Map}/Program.cs (100%) diff --git a/Directory.Build.props b/Directory.Build.props index 87cb637..5c3dccd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -17,7 +17,11 @@ - + + + + + @@ -28,21 +32,21 @@ - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Directory.Packages.props b/Directory.Packages.props index 729ad6c..caed005 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,4 +6,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Hexecs.sln b/Hexecs.sln deleted file mode 100644 index 8ee2630..0000000 --- a/Hexecs.sln +++ /dev/null @@ -1,39 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs", "src\Hexecs\Hexecs.csproj", "{90188DF9-5A78-4303-BF3F-EF4E648C4BC6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Tests", "src\Hexecs.Tests\Hexecs.Tests.csproj", "{2F69C623-5FAC-4696-B849-B42B7F6CE540}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Benchmarks", "src\Hexecs.Benchmarks\Hexecs.Benchmarks.csproj", "{6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Benchmarks.Noise", "src\Hexecs.Benchmarks.Noise\Hexecs.Benchmarks.Noise.csproj", "{1F5BACA8-7AC3-48B4-9F28-532F7684C80B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Benchmark.Map", "src\Hexecs.Benchmark.Map\Hexecs.Benchmark.Map.csproj", "{BAE6244E-6879-4D30-B105-49ABB05A8D63}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {90188DF9-5A78-4303-BF3F-EF4E648C4BC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90188DF9-5A78-4303-BF3F-EF4E648C4BC6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90188DF9-5A78-4303-BF3F-EF4E648C4BC6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90188DF9-5A78-4303-BF3F-EF4E648C4BC6}.Release|Any CPU.Build.0 = Release|Any CPU - {2F69C623-5FAC-4696-B849-B42B7F6CE540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F69C623-5FAC-4696-B849-B42B7F6CE540}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F69C623-5FAC-4696-B849-B42B7F6CE540}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F69C623-5FAC-4696-B849-B42B7F6CE540}.Release|Any CPU.Build.0 = Release|Any CPU - {6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}.Release|Any CPU.Build.0 = Release|Any CPU - {1F5BACA8-7AC3-48B4-9F28-532F7684C80B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F5BACA8-7AC3-48B4-9F28-532F7684C80B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F5BACA8-7AC3-48B4-9F28-532F7684C80B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F5BACA8-7AC3-48B4-9F28-532F7684C80B}.Release|Any CPU.Build.0 = Release|Any CPU - {BAE6244E-6879-4D30-B105-49ABB05A8D63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BAE6244E-6879-4D30-B105-49ABB05A8D63}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BAE6244E-6879-4D30-B105-49ABB05A8D63}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BAE6244E-6879-4D30-B105-49ABB05A8D63}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/Hexecs.slnx b/Hexecs.slnx new file mode 100644 index 0000000..963319e --- /dev/null +++ b/Hexecs.slnx @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Hexecs.Benchmark.Map/MapGame.cs b/src/Hexecs.Benchmark.Map/MapGame.cs deleted file mode 100644 index 797718e..0000000 --- a/src/Hexecs.Benchmark.Map/MapGame.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Hexecs.Benchmark.Map; - -internal sealed class MapGame -{ - -} \ No newline at end of file diff --git a/src/Hexecs.Benchmark.Map/Hexecs.Benchmark.Map.csproj b/src/Hexecs.Benchmarks.Map/Hexecs.Benchmarks.Map.csproj similarity index 73% rename from src/Hexecs.Benchmark.Map/Hexecs.Benchmark.Map.csproj rename to src/Hexecs.Benchmarks.Map/Hexecs.Benchmarks.Map.csproj index 7668e84..cb7acec 100644 --- a/src/Hexecs.Benchmark.Map/Hexecs.Benchmark.Map.csproj +++ b/src/Hexecs.Benchmarks.Map/Hexecs.Benchmarks.Map.csproj @@ -8,11 +8,15 @@ - + + + + + diff --git a/src/Hexecs.Benchmarks.Map/MapGame.cs b/src/Hexecs.Benchmarks.Map/MapGame.cs new file mode 100644 index 0000000..acb84ff --- /dev/null +++ b/src/Hexecs.Benchmarks.Map/MapGame.cs @@ -0,0 +1,82 @@ +using Hexecs.Worlds; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +namespace Hexecs.Benchmarks.Map; + +internal sealed class MapGame : Game +{ + private readonly GraphicsDeviceManager _graphics; + private World _world = null!; + + public MapGame() + { + _graphics = new GraphicsDeviceManager(this) + { + PreferredBackBufferWidth = 1280, + PreferredBackBufferHeight = 720, + GraphicsProfile = GraphicsProfile.HiDef, // Используем профиль HiDef для поддержки расширенных возможностей + PreferMultiSampling = true, + SynchronizeWithVerticalRetrace = false, + IsFullScreen = false, + HardwareModeSwitch = false // Используем borderless fullscreen для удобства + }; + + // Включаем поддержку сглаживания для устройства + _graphics.PreparingDeviceSettings += (_, e) => + { + e.GraphicsDeviceInformation.PresentationParameters.MultiSampleCount = 8; // 8x MSAA + }; + + _graphics.ApplyChanges(); + + IsFixedTimeStep = false; + } + + protected override void Initialize() + { + GraphicsDevice.SamplerStates[0] = SamplerState.AnisotropicClamp; + + _world = new WorldBuilder() + .Singleton(_ => GraphicsDevice) + .DefaultParallelWorker(Math.Min(6, Environment.ProcessorCount)) + .DefaultActorContext(context => context + .Capacity(500_000)) + .Build(); + + + base.Initialize(); + } + + protected override void Update(GameTime gameTime) + { + var keyboard = Keyboard.GetState(); + if (keyboard.IsKeyDown(Keys.Space)) + { + } + + _world.Update(gameTime.ElapsedGameTime, gameTime.TotalGameTime); + + base.Update(gameTime); + } + + protected override void Draw(GameTime gameTime) + { + GraphicsDevice.Clear(Color.White); + + _world.Draw(gameTime.ElapsedGameTime, gameTime.TotalGameTime); + + base.Draw(gameTime); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _world.Dispose(); + } + + base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmark.Map/Program.cs b/src/Hexecs.Benchmarks.Map/Program.cs similarity index 100% rename from src/Hexecs.Benchmark.Map/Program.cs rename to src/Hexecs.Benchmarks.Map/Program.cs diff --git a/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj b/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj index 872e626..33c856a 100644 --- a/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj +++ b/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj @@ -7,8 +7,14 @@ true + + Speed + true + link + + - + diff --git a/src/Hexecs.Benchmarks.Noise/NoiseGame.cs b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs index 2da28ba..3302a18 100644 --- a/src/Hexecs.Benchmarks.Noise/NoiseGame.cs +++ b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs @@ -16,7 +16,6 @@ public class NoiseGame : Game private readonly Random _random = new(); private ActorContext _context = null!; - private ActorFilter? _entitiesCountFilter; private FpsCounter _fpsCounter = null!; private World _world = null!; @@ -97,7 +96,7 @@ protected override void Update(GameTime gameTime) var keyboard = Keyboard.GetState(); if (keyboard.IsKeyDown(Keys.Space)) { - var count = _entitiesCountFilter?.Length ?? 0; + var count = _context.Length; var color = CircleColor.CreateRgba(_random); for (var i = 0; i < 50; i++) { diff --git a/src/Hexecs.Benchmarks/Program.cs b/src/Hexecs.Benchmarks/Program.cs index c1ae414..7a74751 100644 --- a/src/Hexecs.Benchmarks/Program.cs +++ b/src/Hexecs.Benchmarks/Program.cs @@ -1,5 +1,5 @@ using BenchmarkDotNet.Running; -using Hexecs.Benchmarks.Actors; + //BenchmarkRunner.Run(); BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); \ No newline at end of file From 3e55cd4fd3e2a38e5aaf3140109b7be746615d95 Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Tue, 23 Dec 2025 17:27:18 +0300 Subject: [PATCH 06/14] 0.4.3 map bench --- Directory.Build.props | 2 +- Directory.Packages.props | 4 - Hexecs.sln | 46 +++ Hexecs.slnx | 9 - .../CityGame.cs} | 54 +-- .../Common/CommonInstaller.cs | 13 + .../Common/Positions/Position.cs | 6 + .../Common/Positions/PositionAbility.cs | 3 + .../Common/Positions/PositionBuilder.cs | 12 + .../Common/Positions/PositionExtensions.cs | 12 + .../Common/Positions/PositionsInstaller.cs | 10 + .../Hexecs.Benchmarks.City.csproj} | 5 + src/Hexecs.Benchmarks.City/Program.cs | 4 + .../Terrains/Assets/TerrainAsset.cs | 15 + .../Terrains/Assets/TerrainAssetSource.cs | 29 ++ .../Generate/GenerateTerrainCommand.cs | 7 + .../Generate/GenerateTerrainHandler.cs | 48 +++ .../Terrains/Terrain.cs | 32 ++ .../Terrains/TerrainBuilder.cs | 22 ++ .../Terrains/TerrainDrawSystem.cs | 64 ++++ .../Terrains/TerrainGrid.cs | 43 +++ .../Terrains/TerrainInstaller.cs | 35 ++ .../Terrains/TerrainSettings.cs | 11 + .../Terrains/TerrainTextureStorage.cs | 23 ++ .../Terrains/ValueTypes/TerrainOverlay.cs | 62 ++++ .../Terrains/ValueTypes/TerrainType.cs | 67 ++++ src/Hexecs.Benchmarks.City/Usings.cs | 6 + src/Hexecs.Benchmarks.City/Utils/Camera.cs | 111 ++++++ .../Utils/PointExtensions.cs | 12 + .../ValueTypes/Elevation.cs | 57 +++ .../ValueTypes/Moisture.cs | 43 +++ .../ValueTypes/Temperature.cs | 39 +++ src/Hexecs.Benchmarks.Map/Program.cs | 3 - src/Hexecs.Benchmarks.Noise/NoiseGame.cs | 8 +- .../Systems/MovementSystem.cs | 7 +- ...UpdateSystemWithParallelWorkerBenchmark.cs | 4 +- src/Hexecs.Benchmarks/Program.cs | 5 +- src/Hexecs.Tests/Actors/ActorSystemShould.cs | 8 +- src/Hexecs.Tests/Actors/ActorTestFixture.cs | 2 +- src/Hexecs.Tests/Utils/MoneyShould.cs | 330 ------------------ .../Worlds/WordDependencyShould.cs | 8 +- src/Hexecs/Actors/Systems/DrawSystem.cs | 6 +- .../Dependencies/IDependencyCollection.cs | 12 +- src/Hexecs/Loggers/LogBuilder.cs | 3 +- .../Loggers/Writers/DefaultMoneyWriter.cs | 11 - src/Hexecs/Utils/Args.cs | 23 ++ src/Hexecs/Utils/Money.cs | 314 ----------------- src/Hexecs/Worlds/Dice.cs | 3 + src/Hexecs/Worlds/WorldBuilder.cs | 180 ++++++---- 49 files changed, 1041 insertions(+), 792 deletions(-) create mode 100644 Hexecs.sln delete mode 100644 Hexecs.slnx rename src/{Hexecs.Benchmarks.Map/MapGame.cs => Hexecs.Benchmarks.City/CityGame.cs} (64%) create mode 100644 src/Hexecs.Benchmarks.City/Common/CommonInstaller.cs create mode 100644 src/Hexecs.Benchmarks.City/Common/Positions/Position.cs create mode 100644 src/Hexecs.Benchmarks.City/Common/Positions/PositionAbility.cs create mode 100644 src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs create mode 100644 src/Hexecs.Benchmarks.City/Common/Positions/PositionExtensions.cs create mode 100644 src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs rename src/{Hexecs.Benchmarks.Map/Hexecs.Benchmarks.Map.csproj => Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj} (81%) create mode 100644 src/Hexecs.Benchmarks.City/Program.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAsset.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAssetSource.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainCommand.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/Terrain.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/TerrainBuilder.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/TerrainGrid.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/TerrainTextureStorage.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/ValueTypes/TerrainOverlay.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/ValueTypes/TerrainType.cs create mode 100644 src/Hexecs.Benchmarks.City/Usings.cs create mode 100644 src/Hexecs.Benchmarks.City/Utils/Camera.cs create mode 100644 src/Hexecs.Benchmarks.City/Utils/PointExtensions.cs create mode 100644 src/Hexecs.Benchmarks.City/ValueTypes/Elevation.cs create mode 100644 src/Hexecs.Benchmarks.City/ValueTypes/Moisture.cs create mode 100644 src/Hexecs.Benchmarks.City/ValueTypes/Temperature.cs delete mode 100644 src/Hexecs.Benchmarks.Map/Program.cs delete mode 100644 src/Hexecs.Tests/Utils/MoneyShould.cs delete mode 100644 src/Hexecs/Loggers/Writers/DefaultMoneyWriter.cs delete mode 100644 src/Hexecs/Utils/Money.cs diff --git a/Directory.Build.props b/Directory.Build.props index 5c3dccd..3fbe90a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,7 +21,7 @@ - + diff --git a/Directory.Packages.props b/Directory.Packages.props index caed005..f3ee5c5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,10 +6,6 @@ - - - - diff --git a/Hexecs.sln b/Hexecs.sln new file mode 100644 index 0000000..b4a90ad --- /dev/null +++ b/Hexecs.sln @@ -0,0 +1,46 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs", "src\Hexecs\Hexecs.csproj", "{90188DF9-5A78-4303-BF3F-EF4E648C4BC6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Tests", "src\Hexecs.Tests\Hexecs.Tests.csproj", "{2F69C623-5FAC-4696-B849-B42B7F6CE540}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Benchmarks", "src\Hexecs.Benchmarks\Hexecs.Benchmarks.csproj", "{6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Benchmarks.Noise", "src\Hexecs.Benchmarks.Noise\Hexecs.Benchmarks.Noise.csproj", "{0CA9A4D9-359D-4F10-8A45-DC3D1A3940AB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexecs.Benchmarks.City", "src\Hexecs.Benchmarks.City\Hexecs.Benchmarks.City.csproj", "{B95D5C8E-90D4-4719-A6B4-81120C3BAD39}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{9BB142FC-044B-48F4-A183-5B2BA50E4658}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {90188DF9-5A78-4303-BF3F-EF4E648C4BC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90188DF9-5A78-4303-BF3F-EF4E648C4BC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90188DF9-5A78-4303-BF3F-EF4E648C4BC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90188DF9-5A78-4303-BF3F-EF4E648C4BC6}.Release|Any CPU.Build.0 = Release|Any CPU + {2F69C623-5FAC-4696-B849-B42B7F6CE540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F69C623-5FAC-4696-B849-B42B7F6CE540}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F69C623-5FAC-4696-B849-B42B7F6CE540}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F69C623-5FAC-4696-B849-B42B7F6CE540}.Release|Any CPU.Build.0 = Release|Any CPU + {6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB}.Release|Any CPU.Build.0 = Release|Any CPU + {0CA9A4D9-359D-4F10-8A45-DC3D1A3940AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CA9A4D9-359D-4F10-8A45-DC3D1A3940AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CA9A4D9-359D-4F10-8A45-DC3D1A3940AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CA9A4D9-359D-4F10-8A45-DC3D1A3940AB}.Release|Any CPU.Build.0 = Release|Any CPU + {B95D5C8E-90D4-4719-A6B4-81120C3BAD39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B95D5C8E-90D4-4719-A6B4-81120C3BAD39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B95D5C8E-90D4-4719-A6B4-81120C3BAD39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B95D5C8E-90D4-4719-A6B4-81120C3BAD39}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {6B3B5C57-80EF-4CC7-A0CE-5533B7628FDB} = {9BB142FC-044B-48F4-A183-5B2BA50E4658} + {B95D5C8E-90D4-4719-A6B4-81120C3BAD39} = {9BB142FC-044B-48F4-A183-5B2BA50E4658} + {0CA9A4D9-359D-4F10-8A45-DC3D1A3940AB} = {9BB142FC-044B-48F4-A183-5B2BA50E4658} + EndGlobalSection +EndGlobal diff --git a/Hexecs.slnx b/Hexecs.slnx deleted file mode 100644 index 963319e..0000000 --- a/Hexecs.slnx +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Map/MapGame.cs b/src/Hexecs.Benchmarks.City/CityGame.cs similarity index 64% rename from src/Hexecs.Benchmarks.Map/MapGame.cs rename to src/Hexecs.Benchmarks.City/CityGame.cs index acb84ff..e078822 100644 --- a/src/Hexecs.Benchmarks.Map/MapGame.cs +++ b/src/Hexecs.Benchmarks.City/CityGame.cs @@ -1,26 +1,31 @@ -using Hexecs.Worlds; -using Microsoft.Xna.Framework; +using Hexecs.Benchmarks.Map.Common; +using Hexecs.Benchmarks.Map.Terrains; +using Hexecs.Benchmarks.Map.Terrains.Commands.Generate; +using Hexecs.Benchmarks.Map.Utils; +using Hexecs.Worlds; +using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Hexecs.Benchmarks.Map; -internal sealed class MapGame : Game +internal sealed class CityGame : Game { + private Camera2D _camera = null!; private readonly GraphicsDeviceManager _graphics; private World _world = null!; - public MapGame() + public CityGame() { _graphics = new GraphicsDeviceManager(this) { PreferredBackBufferWidth = 1280, PreferredBackBufferHeight = 720, - GraphicsProfile = GraphicsProfile.HiDef, // Используем профиль HiDef для поддержки расширенных возможностей + GraphicsProfile = GraphicsProfile.HiDef, PreferMultiSampling = true, SynchronizeWithVerticalRetrace = false, IsFullScreen = false, - HardwareModeSwitch = false // Используем borderless fullscreen для удобства + HardwareModeSwitch = false }; // Включаем поддержку сглаживания для устройства @@ -38,27 +43,32 @@ protected override void Initialize() { GraphicsDevice.SamplerStates[0] = SamplerState.AnisotropicClamp; + _camera = new Camera2D(GraphicsDevice); _world = new WorldBuilder() - .Singleton(_ => GraphicsDevice) - .DefaultParallelWorker(Math.Min(6, Environment.ProcessorCount)) - .DefaultActorContext(context => context - .Capacity(500_000)) + .UseDefaultParallelWorker(Math.Min(6, Environment.ProcessorCount)) + .UseSingleton(_ => Content) + .UseSingleton(_ => GraphicsDevice) + .UseSingleton(_camera) + .UseTerrain() + .UseDefaultActorContext(context => context + .Capacity(500_000) + .AddCommon() + .AddTerrain()) .Build(); + _world.Actors.Execute(new GenerateTerrainCommand()); base.Initialize(); } - protected override void Update(GameTime gameTime) + protected override void Dispose(bool disposing) { - var keyboard = Keyboard.GetState(); - if (keyboard.IsKeyDown(Keys.Space)) + if (disposing) { + _world.Dispose(); } - _world.Update(gameTime.ElapsedGameTime, gameTime.TotalGameTime); - - base.Update(gameTime); + base.Dispose(disposing); } protected override void Draw(GameTime gameTime) @@ -70,13 +80,17 @@ protected override void Draw(GameTime gameTime) base.Draw(gameTime); } - protected override void Dispose(bool disposing) + protected override void Update(GameTime gameTime) { - if (disposing) + var keyboard = Keyboard.GetState(); + if (keyboard.IsKeyDown(Keys.Space)) { - _world.Dispose(); } - base.Dispose(disposing); + _camera.Update(gameTime); + + _world.Update(gameTime.ElapsedGameTime, gameTime.TotalGameTime); + + base.Update(gameTime); } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/CommonInstaller.cs b/src/Hexecs.Benchmarks.City/Common/CommonInstaller.cs new file mode 100644 index 0000000..58aa310 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Common/CommonInstaller.cs @@ -0,0 +1,13 @@ +using Hexecs.Benchmarks.Map.Common.Positions; + +namespace Hexecs.Benchmarks.Map.Common; + +internal static class CommonInstaller +{ + public static ActorContextBuilder AddCommon(this ActorContextBuilder builder) + { + builder.AddPositions(); + + return builder; + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/Position.cs b/src/Hexecs.Benchmarks.City/Common/Positions/Position.cs new file mode 100644 index 0000000..b88373b --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Common/Positions/Position.cs @@ -0,0 +1,6 @@ +namespace Hexecs.Benchmarks.Map.Common.Positions; + +public struct Position : IActorComponent +{ + public Point Grid; +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/PositionAbility.cs b/src/Hexecs.Benchmarks.City/Common/Positions/PositionAbility.cs new file mode 100644 index 0000000..ca9afd8 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Common/Positions/PositionAbility.cs @@ -0,0 +1,3 @@ +namespace Hexecs.Benchmarks.Map.Common.Positions; + +public readonly struct PositionAbility: IAssetComponent; \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs b/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs new file mode 100644 index 0000000..a0f91e2 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs @@ -0,0 +1,12 @@ +namespace Hexecs.Benchmarks.Map.Common.Positions; + +internal sealed class PositionBuilder : IActorBuilder +{ + public void Build(in Actor actor, in AssetRef asset, Args args) + { + actor.Add(new Position + { + Grid = args.Get(nameof(Point)) + }); + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/PositionExtensions.cs b/src/Hexecs.Benchmarks.City/Common/Positions/PositionExtensions.cs new file mode 100644 index 0000000..3fdaca1 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Common/Positions/PositionExtensions.cs @@ -0,0 +1,12 @@ +using Hexecs.Assets.Sources; + +namespace Hexecs.Benchmarks.Map.Common.Positions; + +internal static class PositionExtensions +{ + public static AssetConfigurator WithPosition(this AssetConfigurator configurator) + { + configurator.Set(new PositionAbility()); + return configurator; + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs b/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs new file mode 100644 index 0000000..40ceacf --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs @@ -0,0 +1,10 @@ +namespace Hexecs.Benchmarks.Map.Common.Positions; + +internal static class PositionsInstaller +{ + public static ActorContextBuilder AddPositions(this ActorContextBuilder builder) + { + builder.AddBuilder(); + return builder; + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Map/Hexecs.Benchmarks.Map.csproj b/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj similarity index 81% rename from src/Hexecs.Benchmarks.Map/Hexecs.Benchmarks.Map.csproj rename to src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj index cb7acec..49dbbec 100644 --- a/src/Hexecs.Benchmarks.Map/Hexecs.Benchmarks.Map.csproj +++ b/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj @@ -5,6 +5,7 @@ net10.0 true true + Hexecs.Benchmarks.Map @@ -18,5 +19,9 @@ + + + + diff --git a/src/Hexecs.Benchmarks.City/Program.cs b/src/Hexecs.Benchmarks.City/Program.cs new file mode 100644 index 0000000..cc90eaf --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Program.cs @@ -0,0 +1,4 @@ +using Hexecs.Benchmarks.Map; + +using var game = new CityGame(); +game.Run(); \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAsset.cs b/src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAsset.cs new file mode 100644 index 0000000..12a7519 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAsset.cs @@ -0,0 +1,15 @@ +using System.Runtime.CompilerServices; +using Hexecs.Benchmarks.Map.Terrains.ValueTypes; + +namespace Hexecs.Benchmarks.Map.Terrains.Assets; + +[method: MethodImpl(MethodImplOptions.AggressiveInlining)] +public readonly struct TerrainAsset(string name, TerrainType type) : IAssetComponent +{ + public const string Ground = "Base"; + public const string River = "River"; + public const string UrbanConcrete = "UrbanConcrete"; + + public readonly string Name = name; + public readonly TerrainType Type = type; +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAssetSource.cs b/src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAssetSource.cs new file mode 100644 index 0000000..d24988b --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAssetSource.cs @@ -0,0 +1,29 @@ +using Hexecs.Assets.Sources; +using Hexecs.Benchmarks.Map.Common.Positions; +using Hexecs.Benchmarks.Map.Terrains.ValueTypes; + +namespace Hexecs.Benchmarks.Map.Terrains.Assets; + +internal sealed class TerrainAssetSource : IAssetSource +{ + private IAssetLoader _loader = null!; + + public void Load(IAssetLoader loader) + { + _loader = loader; + + Create(TerrainAsset.Ground, "Земля", TerrainType.Ground) + .WithPosition(); + + Create(TerrainAsset.River, "Река", TerrainType.WaterRiver) + .WithPosition(); + } + + private AssetConfigurator Create( + string alias, + string name, + TerrainType type) + { + return _loader.CreateAsset(alias, new TerrainAsset(name, type)); + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainCommand.cs b/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainCommand.cs new file mode 100644 index 0000000..4eced5a --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainCommand.cs @@ -0,0 +1,7 @@ +using System.Runtime.CompilerServices; +using Hexecs.Pipelines; + +namespace Hexecs.Benchmarks.Map.Terrains.Commands.Generate; + +[method: MethodImpl(MethodImplOptions.AggressiveInlining)] +public readonly struct GenerateTerrainCommand : ICommand; \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs b/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs new file mode 100644 index 0000000..4173edd --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs @@ -0,0 +1,48 @@ +using Hexecs.Actors.Pipelines; +using Hexecs.Benchmarks.Map.Terrains.Assets; +using Hexecs.Benchmarks.Map.ValueTypes; +using Hexecs.Pipelines; +using Hexecs.Worlds; + +namespace Hexecs.Benchmarks.Map.Terrains.Commands.Generate; + +internal sealed class GenerateTerrainHandler : ActorCommandHandler +{ + private readonly TerrainSettings _settings; + + public GenerateTerrainHandler(ActorContext context, TerrainSettings settings) : base(context) + { + _settings = settings; + } + + public override Result Handle(in GenerateTerrainCommand terrainCommand) + { + var ground = Assets.GetAsset(TerrainAsset.Ground); + var river = Assets.GetAsset(TerrainAsset.River); + var urbanConcrete = Assets.GetAsset(TerrainAsset.UrbanConcrete); + + var height = _settings.Height; + var width = _settings.Width; + + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + var args = Args.Rent(nameof(Point), new Point(x, y)); + var actor = x switch + { + // 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) + }; + } + } + + return Result.Ok(); + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/Terrain.cs b/src/Hexecs.Benchmarks.City/Terrains/Terrain.cs new file mode 100644 index 0000000..6e6ac61 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/Terrain.cs @@ -0,0 +1,32 @@ +using Hexecs.Benchmarks.Map.Terrains.ValueTypes; +using Hexecs.Benchmarks.Map.ValueTypes; + +namespace Hexecs.Benchmarks.Map.Terrains; + +public struct Terrain : IActorComponent +{ + /// + /// Высота (100 - уровень моря, 150 - холм, 250 - гора) + /// + public Elevation Elevation; + + /// + /// Влажность или загрязнение (100 - это 0) + /// + public Moisture Moisture; + + /// + /// Покрытие + /// + public TerrainOverlay Overlay; + + /// + /// Температура (100 - это 0) + /// + public Temperature Temperature; + + /// + /// Основной тип + /// + public TerrainType Type; +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainBuilder.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainBuilder.cs new file mode 100644 index 0000000..4eb41a4 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainBuilder.cs @@ -0,0 +1,22 @@ +using Hexecs.Benchmarks.Map.Terrains.Assets; +using Hexecs.Benchmarks.Map.Terrains.ValueTypes; +using Hexecs.Benchmarks.Map.ValueTypes; + +namespace Hexecs.Benchmarks.Map.Terrains; + +internal sealed class TerrainBuilder : IActorBuilder +{ + public void Build(in Actor actor, in AssetRef asset, Args args) + { + ref readonly var assetData = ref asset.Component1; + + actor.Add(new Terrain + { + Elevation = args.GetOrDefault(nameof(Terrain.Elevation), Elevation.Default), + Moisture = args.GetOrDefault(nameof(Terrain.Moisture), Moisture.Default), + Overlay = TerrainOverlay.None, + Temperature = args.GetOrDefault(nameof(Terrain.Temperature), Temperature.Default), + Type = assetData.Type + }); + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs new file mode 100644 index 0000000..8a9d2ee --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs @@ -0,0 +1,64 @@ +using Hexecs.Actors.Systems; +using Hexecs.Benchmarks.Map.Common.Positions; +using Hexecs.Benchmarks.Map.Utils; +using Hexecs.Worlds; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Map.Terrains; + +internal sealed class TerrainDrawSystem : DrawSystem +{ + private const int TileSize = 64; + + private readonly Camera2D _camera; + private readonly TerrainGrid _grid; + private readonly SpriteBatch _spriteBatch; + + public TerrainDrawSystem( + Camera2D camera, + ActorContext context, + GraphicsDevice graphicsDevice, + TerrainGrid grid) + : base(context) + { + _camera = camera; + _grid = grid; + _spriteBatch = new SpriteBatch(graphicsDevice); + } + + protected override void BeforeDraw(in WorldTime time) + { + _spriteBatch.Begin( + transformMatrix: _camera.GetTransformationMatrix(), + samplerState: SamplerState.PointClamp, + blendState: BlendState.AlphaBlend); + } + + protected override void Draw(in ActorRef actor, in WorldTime time) + { + ref readonly var gridPosition = ref actor.Component1.Grid; + ref readonly var terrain = ref actor.Component2; + + var drawPosition = new Vector2( + gridPosition.X * TileSize, + gridPosition.Y * TileSize); + + Texture2D texture = null!; //TODO: set it!!! + + _spriteBatch.Draw( + texture, + drawPosition, + Color.White); + } + + protected override void AfterDraw(in WorldTime time) + { + _spriteBatch.End(); + } + + public override void Dispose() + { + _spriteBatch.Dispose(); + base.Dispose(); + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainGrid.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainGrid.cs new file mode 100644 index 0000000..0d037b1 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainGrid.cs @@ -0,0 +1,43 @@ +using Hexecs.Benchmarks.Map.Common.Positions; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Map.Terrains; + +internal sealed class TerrainGrid +{ + private ActorContext _context; + + public TerrainGrid(ActorContext context) + { + _context = context; + _context.OnComponentAdded(OnAdded); + _context.OnComponentRemoving(OnRemoving); + _context.OnComponentUpdating(OnUpdating); + } + + public ReadOnlySpan.Enumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + private void OnAdded(uint actorId, int index, ref Terrain component) + { + } + + private void OnRemoving(uint actorId, ref Terrain component) + { + throw new NotImplementedException(); + } + + private void OnUpdating(uint actorId, ref Terrain exists, in Terrain expected) + { + throw new NotImplementedException(); + } + + public struct Entry + { + public Position Position; + public Terrain Terrain; + public Texture2D Texture; + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs new file mode 100644 index 0000000..ae10d39 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs @@ -0,0 +1,35 @@ +using Hexecs.Benchmarks.Map.Terrains.Commands.Generate; +using Hexecs.Configurations; +using Hexecs.Dependencies; +using Hexecs.Worlds; + +namespace Hexecs.Benchmarks.Map.Terrains; + +internal static class TerrainInstaller +{ + public static ActorContextBuilder AddTerrain(this ActorContextBuilder builder) + { + var terrainSettings = builder.World.GetRequiredService(); + + builder.AddBuilder(); + + builder + .ConfigureComponentPool(terrain => terrain + .Capacity(terrainSettings.Width * terrainSettings.Height)); + + builder.CreateCommandHandler(); + + builder.CreateDrawSystem(); + + return builder; + } + + public static WorldBuilder UseTerrain(this WorldBuilder builder) + { + builder.UseSingleton(ctx => ctx + .GetService()? + .GetValue(TerrainSettings.Key) ?? TerrainSettings.Default); + + return builder; + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs new file mode 100644 index 0000000..313ec60 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs @@ -0,0 +1,11 @@ +namespace Hexecs.Benchmarks.Map.Terrains; + +public sealed class TerrainSettings +{ + public static TerrainSettings Default { get; } = new() { Width = 256, Height = 256 }; + + public const string Key = "Map:Terrain"; + + public int Width { get; init; } + public int Height { get; init; } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainTextureStorage.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainTextureStorage.cs new file mode 100644 index 0000000..3084fc4 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainTextureStorage.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; +using Hexecs.Benchmarks.Map.Terrains.ValueTypes; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Map.Terrains; + +internal sealed class TerrainTextureStorage(ContentManager content) +{ + private readonly Dictionary _cache = new(); + + public Texture2D GetTexture(TerrainType type, TerrainOverlay overlay) + { + var key = new Key(type, overlay); + ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(_cache, key, out var exists); + if (exists) return value!; + + value = content.Load("Content/Textures/Terrain/" + type + "/" + overlay + ".png"); + return value; + } + + private readonly record struct Key(TerrainType Type, TerrainOverlay Overlay); +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/TerrainOverlay.cs b/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/TerrainOverlay.cs new file mode 100644 index 0000000..7ff7566 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/TerrainOverlay.cs @@ -0,0 +1,62 @@ +namespace Hexecs.Benchmarks.Map.Terrains.ValueTypes; + +public enum TerrainOverlay : byte +{ + None = 0, + + // --- Природные состояния (1-19) --- + /// + /// Снежный покров. + /// + Snow = 1, + /// + /// Тонкий слой льда (на воде или на дороге). + /// + Ice = 2, + /// + /// Лужи или затопление после дождя. + /// + Puddles = 3, + + // --- Растительность (20-39) --- + /// + /// Камыш или водные растения. + /// + Reeds = 20, + /// + /// Дикие кустарники или густая трава. + /// + Bushes = 21, + /// + /// Мох или лишайник (на камнях/бетоне). + /// + Moss = 22, + /// + /// Опавшие листья (городской декор). + /// + DeadLeaves = 23, + + // --- Городские и техногенные эффекты (40-59) --- + /// + /// Мусор или строительные обломки (например, после сноса). + /// + Debris = 40, + /// + /// Пятна масла, топлива или химикатов. + /// + PollutionSpill = 41, + /// + /// Следы износа или трещины на асфальте. + /// + Cracks = 42, + + // --- Следы событий (60-79) --- + /// + /// Следы гари после пожара. + /// + BurnMarks = 60, + /// + /// Кровь или следы происшествий. + /// + Blood = 61 +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/TerrainType.cs b/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/TerrainType.cs new file mode 100644 index 0000000..e14165d --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/TerrainType.cs @@ -0,0 +1,67 @@ +namespace Hexecs.Benchmarks.Map.Terrains.ValueTypes; + +// ReSharper disable ConvertToExtensionBlock +public enum TerrainType : byte +{ + None = 0, + + // Природная земля (1-19) + Ground = 1, // пустая земля (например, сняли дёрн) + GroundGrass = 2, + GroundClay = 3, + GroundSand = 4, + GroundDirt = 5, + + // Подготовленная городская почва (20-39) + UrbanGravel = 20, // Гравийная засыпка + UrbanPavement = 21, // Мощение + UrbanConcrete = 22, // Бетонное основание + + // Камни и минералы (40-59) + Rock = 40, + + // Болота (60-79) + Swamp = 60, // Глубокое болото (не проходимое) + + // Горы (вертикальные препятствия) (80-99) + Mountains = 80, + Cliff = 81, // Утёс, резкий перепад высоты + + // Вода (100-119) + WaterShallow = 100, + WaterRiver = 101, // (не проходимое) + WaterOcean = 102, // (не проходимое) +} + +public static class TerrainTypeExtensions +{ + public static bool IsGround(this TerrainType type) + { + return type is >= TerrainType.GroundGrass and < TerrainType.UrbanGravel; + } + + public static bool IsUrban(this TerrainType type) => type is >= TerrainType.UrbanGravel and < TerrainType.Rock; + + public static bool IsRock(this TerrainType type) => type is >= TerrainType.Rock and < TerrainType.Swamp; + + public static bool IsSwamp(this TerrainType type) => type is >= TerrainType.Swamp and < TerrainType.Mountains; + + public static bool IsElevationObstacle(this TerrainType type) + { + return type is >= TerrainType.Mountains and < TerrainType.WaterShallow; + } + + public static bool IsWater(this TerrainType type) => type >= TerrainType.WaterShallow; + + /// + /// Проверка на проходимость для пеших юнитов. + /// + public static bool IsWalkable(this TerrainType type) => type switch + { + TerrainType.Swamp => false, + TerrainType.Mountains => false, + TerrainType.WaterRiver => false, + TerrainType.WaterOcean => false, + _ => true + }; +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Usings.cs b/src/Hexecs.Benchmarks.City/Usings.cs new file mode 100644 index 0000000..28f20c2 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Usings.cs @@ -0,0 +1,6 @@ +// Global using directives + +global using Hexecs.Actors; +global using Hexecs.Assets; +global using Hexecs.Utils; +global using Microsoft.Xna.Framework; \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/Camera.cs b/src/Hexecs.Benchmarks.City/Utils/Camera.cs new file mode 100644 index 0000000..75e888d --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Utils/Camera.cs @@ -0,0 +1,111 @@ +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +namespace Hexecs.Benchmarks.Map.Utils; + +internal sealed class Camera2D +{ + private readonly GraphicsDevice _graphicsDevice; + + /// + /// Позиция камеры в мировых координатах (центр экрана). + /// + public Vector2 Position { get; private set; } + + /// + /// Текущий масштаб камеры (1.0 = без изменений). + /// + public float Zoom { get; private set; } + + private int _previousScrollValue; + + public Camera2D(GraphicsDevice graphicsDevice) + { + Position = Vector2.Zero; + Zoom = 1f; + + _graphicsDevice = graphicsDevice; + } + + /// + /// Изменяет текущий зум на множитель и ограничивает его допустимым диапазоном. + /// + /// Множитель масштаба (больше 1 для приближения, меньше 1 для отдаления). + public void AdjustZoom(float factor) + { + Zoom *= factor; + Zoom = MathHelper.Clamp(Zoom, 0.1f, 10f); + } + + /// + /// Создает матрицу трансформации для SpriteBatch, учитывая позицию, зум и размер экрана. + /// + public Matrix GetTransformationMatrix() + { + var viewport = _graphicsDevice.Viewport; + + return Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0)) * + Matrix.CreateScale(new Vector3(Zoom, Zoom, 1)) * + Matrix.CreateTranslation(new Vector3(viewport.Width * 0.5f, viewport.Height * 0.5f, 0)); + } + + /// + /// Смещает камеру на указанный вектор. + /// + public void Move(Vector2 direction) => Position += direction; + + /// + /// Обрабатывает ввод игрока для перемещения и масштабирования камеры. + /// + public void Update(GameTime gameTime) + { + var keyboard = Keyboard.GetState(); + var mouse = Mouse.GetState(); + + var dt = (float)gameTime.ElapsedGameTime.TotalSeconds; + + // Базовое управление камерой + var speed = 500f / Zoom; + var moveDir = Vector2.Zero; + + if (keyboard.IsKeyDown(Keys.W)) moveDir.Y -= 1; + if (keyboard.IsKeyDown(Keys.S)) moveDir.Y += 1; + if (keyboard.IsKeyDown(Keys.A)) moveDir.X -= 1; + if (keyboard.IsKeyDown(Keys.D)) moveDir.X += 1; + + if (moveDir != Vector2.Zero) + { + moveDir.Normalize(); + Move(moveDir * speed * dt); + } + + if (keyboard.IsKeyDown(Keys.Q)) Zoom += dt * Zoom; + if (keyboard.IsKeyDown(Keys.E)) Zoom -= dt * Zoom; + + var scrollDelta = mouse.ScrollWheelValue - _previousScrollValue; + if (scrollDelta != 0) + { + AdjustZoom(scrollDelta > 0 ? 1.1f : 0.9f); + } + + _previousScrollValue = mouse.ScrollWheelValue; + + Zoom = MathHelper.Clamp(Zoom, 0.1f, 10f); + } + + /// + /// Переводит экранные координаты (например, позицию мыши) в мировые координаты игрового поля. + /// + public Vector2 ScreenToWorld(Vector2 screenPosition) + { + return Vector2.Transform(screenPosition, Matrix.Invert(GetTransformationMatrix())); + } + + /// + /// Переводит мировые координаты в координаты экрана (например, для отрисовки UI над объектами). + /// + public Vector2 WorldToScreen(Vector2 worldPosition) + { + return Vector2.Transform(worldPosition, GetTransformationMatrix()); + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/PointExtensions.cs b/src/Hexecs.Benchmarks.City/Utils/PointExtensions.cs new file mode 100644 index 0000000..77958bc --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Utils/PointExtensions.cs @@ -0,0 +1,12 @@ +namespace Hexecs.Benchmarks.Map.Utils; + +public static class PointExtensions +{ + public static void GetNeighborPoints(int x, int y, ref Span neighbors) + { + neighbors[0] = new Point(x - 1, y); + neighbors[1] = new Point(x + 1, y); + neighbors[2] = new Point(x, y - 1); + neighbors[3] = new Point(x, y + 1); + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/ValueTypes/Elevation.cs b/src/Hexecs.Benchmarks.City/ValueTypes/Elevation.cs new file mode 100644 index 0000000..4c42f09 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/ValueTypes/Elevation.cs @@ -0,0 +1,57 @@ +using System.Runtime.CompilerServices; + +namespace Hexecs.Benchmarks.Map.ValueTypes; + +public readonly struct Elevation +{ + public static Elevation Default => FromValue(10); + + public static Elevation FromValue(int value) + { + var raw = (byte)Math.Clamp(value + Offset, 0, 255); + return new Elevation(raw); + } + + public static Elevation SeaLevel + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(Offset); + } + + private const byte Offset = 100; // sea level + private readonly byte _raw; + + private Elevation(byte raw) => _raw = raw; + + public int Value + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _raw - Offset; + } + + /// + /// Находится ли уровень ниже базового (уровня моря). + /// + public bool IsBelowSeaLevel + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _raw < 100; + } + + /// + /// Является ли это возвышенностью (холмом). + /// + public bool IsHighland + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _raw > 150; + } + + public bool IsSeaLevel + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _raw == Offset; + } + + public override string ToString() => $"{Value}m"; +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/ValueTypes/Moisture.cs b/src/Hexecs.Benchmarks.City/ValueTypes/Moisture.cs new file mode 100644 index 0000000..6e0faa8 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/ValueTypes/Moisture.cs @@ -0,0 +1,43 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Hexecs.Benchmarks.Map.ValueTypes; + +[DebuggerDisplay("{Value:F2}%")] +public readonly struct Moisture +{ + public static Moisture Default + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(Offset); + } + + public static Moisture FromValue(float value) + { + var raw = (byte)Math.Clamp(value + Offset, 0, 255); + return new Moisture(raw); + } + + private const byte Offset = 100; + private readonly byte _raw; + + private Moisture(byte raw) => _raw = raw; + + public float Value + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _raw - Offset; + } + + public bool IsDry + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Value < -50; + } + + public bool IsSoaked + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Value > 50; + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/ValueTypes/Temperature.cs b/src/Hexecs.Benchmarks.City/ValueTypes/Temperature.cs new file mode 100644 index 0000000..806ca0f --- /dev/null +++ b/src/Hexecs.Benchmarks.City/ValueTypes/Temperature.cs @@ -0,0 +1,39 @@ +using System.Runtime.CompilerServices; + +namespace Hexecs.Benchmarks.Map.ValueTypes; + +public readonly struct Temperature +{ + public static Temperature Default => FromCelsius(20); + + public static Temperature FromCelsius(byte celsius) + { + var raw = (byte)Math.Clamp(celsius + Offset, 0, 255); + return new Temperature(raw); + } + + private const byte Offset = 100; + private readonly byte _raw; + + private Temperature(byte raw) => _raw = raw; + + public float Celsius + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _raw - Offset; + } + + public bool IsFreezing + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Celsius <= 0; + } + + public bool IsBoiling + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Celsius >= 100; + } + + public override string ToString() => $"{Celsius}°C"; +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Map/Program.cs b/src/Hexecs.Benchmarks.Map/Program.cs deleted file mode 100644 index e5dff12..0000000 --- a/src/Hexecs.Benchmarks.Map/Program.cs +++ /dev/null @@ -1,3 +0,0 @@ -// See https://aka.ms/new-console-template for more information - -Console.WriteLine("Hello, World!"); \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Noise/NoiseGame.cs b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs index 3302a18..1b2af81 100644 --- a/src/Hexecs.Benchmarks.Noise/NoiseGame.cs +++ b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs @@ -28,11 +28,11 @@ public NoiseGame() { PreferredBackBufferWidth = 1280, PreferredBackBufferHeight = 720, - GraphicsProfile = GraphicsProfile.HiDef, // Используем профиль HiDef для поддержки расширенных возможностей + GraphicsProfile = GraphicsProfile.HiDef, PreferMultiSampling = true, SynchronizeWithVerticalRetrace = false, IsFullScreen = false, - HardwareModeSwitch = false // Используем borderless fullscreen для удобства + HardwareModeSwitch = false }; // Включаем поддержку сглаживания для устройства @@ -54,8 +54,8 @@ protected override void Initialize() var height = _graphics.PreferredBackBufferHeight; _world = new WorldBuilder() - .DefaultParallelWorker(Math.Min(6, Environment.ProcessorCount)) - .DefaultActorContext(builder => builder + .UseDefaultParallelWorker(Math.Min(6, Environment.ProcessorCount)) + .UseDefaultActorContext(builder => builder .Capacity(InitialEntityCount) .ConfigureComponentPool(color => color.Capacity(InitialEntityCount)) .ConfigureComponentPool(position => position.Capacity(InitialEntityCount)) diff --git a/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs b/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs index 2bab8de..428845a 100644 --- a/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs +++ b/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs @@ -21,8 +21,8 @@ protected override void Update( in ActorRef actor, in WorldTime time) { - ref var pos = ref actor.Component1; - ref var vel = ref actor.Component2; + var pos = actor.Component1; + var vel = actor.Component2; pos.Value += vel.Value * time.DeltaTime; @@ -36,5 +36,8 @@ protected override void Update( { vel.Value.Y *= -1; } + + actor.Update(pos); + actor.Update(vel); } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs b/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs index 2954373..2e96d88 100644 --- a/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs +++ b/src/Hexecs.Benchmarks/Actors/UpdateSystemWithParallelWorkerBenchmark.cs @@ -70,8 +70,8 @@ public void Setup() new DefaultEcs.Threading.DefaultParallelRunner(4)); _hexecsWorld = new WorldBuilder() - .DefaultParallelWorker(4) - .DefaultActorContext(ctx => ctx.CreateUpdateSystem()) + .UseDefaultParallelWorker(4) + .UseDefaultActorContext(ctx => ctx.CreateUpdateSystem()) .Build(); _hexecsSystem = _hexecsWorld.Actors.GetUpdateSystem(); diff --git a/src/Hexecs.Benchmarks/Program.cs b/src/Hexecs.Benchmarks/Program.cs index 7a74751..7617776 100644 --- a/src/Hexecs.Benchmarks/Program.cs +++ b/src/Hexecs.Benchmarks/Program.cs @@ -1,5 +1,6 @@ using BenchmarkDotNet.Running; +using Hexecs.Benchmarks.Actors; -//BenchmarkRunner.Run(); -BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); \ No newline at end of file +BenchmarkRunner.Run(); +//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); \ No newline at end of file diff --git a/src/Hexecs.Tests/Actors/ActorSystemShould.cs b/src/Hexecs.Tests/Actors/ActorSystemShould.cs index def401f..b28c92d 100644 --- a/src/Hexecs.Tests/Actors/ActorSystemShould.cs +++ b/src/Hexecs.Tests/Actors/ActorSystemShould.cs @@ -14,8 +14,8 @@ public void ConfigureAndRunSystemsInParallel() var systems = fixture.CreateArray(_ => new Mock()); using var world = new WorldBuilder() - .DefaultParallelWorker(degreeOfParallelism: 4) - .DefaultActorContext(cfg => cfg + .UseDefaultParallelWorker(degreeOfParallelism: 4) + .UseDefaultActorContext(cfg => cfg .CreateParallelUpdateSystem(systems.Select(mock => mock.Object))) .Build(); @@ -40,8 +40,8 @@ public void UpdateActorsInParallel(int degreeOfParallelism, int actorCount) // arrange using var world = new WorldBuilder() - .DefaultParallelWorker(degreeOfParallelism) - .DefaultActorContext(cfg => cfg + .UseDefaultParallelWorker(degreeOfParallelism) + .UseDefaultActorContext(cfg => cfg .CreateUpdateSystem(ctx => new ParallelUpdateSystem( ctx, ctx.GetRequiredService()))) diff --git a/src/Hexecs.Tests/Actors/ActorTestFixture.cs b/src/Hexecs.Tests/Actors/ActorTestFixture.cs index d880692..42de54c 100644 --- a/src/Hexecs.Tests/Actors/ActorTestFixture.cs +++ b/src/Hexecs.Tests/Actors/ActorTestFixture.cs @@ -15,7 +15,7 @@ public ActorTestFixture() { World = new WorldBuilder() .CreateAssetData(CreateAssets) - .DefaultActorContext(ctx => ctx + .UseDefaultActorContext(ctx => ctx .AddBuilder() .AddBuilder() .ConfigureComponentPool(c => c.AddDisposeHandler())) diff --git a/src/Hexecs.Tests/Utils/MoneyShould.cs b/src/Hexecs.Tests/Utils/MoneyShould.cs deleted file mode 100644 index fd48c19..0000000 --- a/src/Hexecs.Tests/Utils/MoneyShould.cs +++ /dev/null @@ -1,330 +0,0 @@ -using Hexecs.Utils; - -namespace Hexecs.Tests.Utils; - -public class MoneyTests -{ - [Fact(DisplayName = "Конструктор создает экземпляр с правильным значением")] - public void Constructor_ShouldCreateInstanceWithCorrectValue() - { - // Arrange & Act - var money = new Money(10050); - - // Assert - money.Value.Should().Be(10050); - money.Whole.Should().Be(100); - money.Fraction.Should().Be(50); - } - - [Fact(DisplayName = "Zero должен возвращать экземпляр с нулевым значением")] - public void Zero_ShouldReturnInstanceWithZeroValue() - { - // Act - var zero = Money.Zero; - - // Assert - zero.Value.Should().Be(0); - zero.Whole.Should().Be(0); - zero.Fraction.Should().Be(0); - } - - [Theory(DisplayName = "Create должен корректно создавать экземпляр с заданными значениями")] - [InlineData(100, 50, 10050)] - [InlineData(0, 0, 0)] - [InlineData(-100, 50, -10050)] - public void Create_ShouldCreateCorrectInstance(long whole, int fraction, long expectedValue) - { - // Act - var money = Money.Create(whole, fraction); - - // Assert - money.Value.Should().Be(expectedValue); - money.Whole.Should().Be(whole); - money.Fraction.Should().Be(fraction); - } - - [Fact(DisplayName = "Create должен вызывать исключение, если дробная часть вне диапазона")] - public void Create_ShouldThrowOverflowException_WhenFractionOutOfRange() - { - // Act & Assert - Action act1 = () => Money.Create(100, -1); - act1.Should().Throw() - .WithMessage("Fraction should be between 0 and 100"); - - Action act2 = () => Money.Create(100, 100); - act2.Should().Throw() - .WithMessage("Fraction should be between 0 and 100"); - } - - [Theory(DisplayName = "TryParse должен корректно преобразовывать строку в Money")] - [InlineData("100.50", 10050, true)] - [InlineData("0", 0, true)] - [InlineData("-100.50", -10050, true)] - [InlineData("invalid", 0, false)] - public void TryParse_ShouldCorrectlyParseString(string input, long expectedValue, bool expectedResult) - { - // Act - var success = Money.TryParse(input, out var result); - - // Assert - success.Should().Be(expectedResult); - if (expectedResult) - { - result.Value.Should().Be(expectedValue); - } - } - - [Fact(DisplayName = "Abs должен возвращать абсолютное значение")] - public void Abs_ShouldReturnAbsoluteValue() - { - // Arrange - var negative = new Money(-10050); - var positive = new Money(10050); - - // Act - var absNegative = negative.Abs(); - var absPositive = positive.Abs(); - - // Assert - absNegative.Value.Should().Be(10050); - absPositive.Value.Should().Be(10050); - } - - [Fact(DisplayName = "Min должен возвращать минимальное значение")] - public void Min_ShouldReturnMinimumValue() - { - // Arrange - var money1 = new Money(10050); - var money2 = new Money(20050); - - // Act - var min1 = money1.Min(money2); - var min2 = money2.Min(money1); - - // Assert - min1.Value.Should().Be(10050); - min2.Value.Should().Be(10050); - } - - [Fact(DisplayName = "Max должен возвращать максимальное значение")] - public void Max_ShouldReturnMaximumValue() - { - // Arrange - var money1 = new Money(10050); - var money2 = new Money(20050); - - // Act - var max1 = money1.Max(money2); - var max2 = money2.Max(money1); - - // Assert - max1.Value.Should().Be(20050); - max2.Value.Should().Be(20050); - } - - [Fact(DisplayName = "ToString должен возвращать корректное строковое представление")] - public void ToString_ShouldReturnCorrectStringRepresentation() - { - // Arrange - var money = new Money(10050); - var negMoney = new Money(-10050); - - // Act - var str = money.ToString(); - var negStr = negMoney.ToString(); - - // Assert - str.Should().Be("100.50"); - negStr.Should().Be("-100.50"); - } - - [Theory(DisplayName = "ToString с форматом должен корректно форматировать значение")] - [InlineData(null, null, "100.50")] - [InlineData("F4", null, "100.5000")] - [InlineData("N1", null, "100.5")] - public void ToString_WithFormat_ShouldFormatValueCorrectly(string? format, IFormatProvider? provider, string expected) - { - // Arrange - var money = new Money(10050); - - // Act - var result = money.ToString(format, provider); - - // Assert - result.Should().Be(expected); - } - - [Fact(DisplayName = "TryFormat должен корректно форматировать значение в буфер символов")] - public void TryFormat_ShouldFormatValueToCharSpan() - { - // Arrange - var money = new Money(10050); - var destination = new char[10]; - - // Act - var success = money.TryFormat(destination, out var charsWritten, "N2", null); - - // Assert - success.Should().BeTrue(); - charsWritten.Should().BeGreaterThan(0); - new string(destination, 0, charsWritten).Should().Be("100.50"); - } - - [Fact(DisplayName = "Операторы сложения должны работать корректно")] - public void AdditionOperators_ShouldWorkCorrectly() - { - // Arrange - var money1 = new Money(10050); - var money2 = new Money(20050); - var longValue = 100L; - - // Act & Assert - (money1 + money2).Value.Should().Be(30100); - (money1 + longValue).Value.Should().Be(10150); - (longValue + money1).Value.Should().Be(10150); - (+money1).Value.Should().Be(10050); - } - - [Fact(DisplayName = "Операторы вычитания должны работать корректно")] - public void SubtractionOperators_ShouldWorkCorrectly() - { - // Arrange - var money1 = new Money(10050); - var money2 = new Money(5050); - var longValue = 100L; - - // Act & Assert - (money1 - money2).Value.Should().Be(5000); - (money1 - longValue).Value.Should().Be(9950); - (longValue - money1).Value.Should().Be(-9950); - (-money1).Value.Should().Be(-10050); - } - - [Fact(DisplayName = "Операторы умножения должны работать корректно")] - public void MultiplicationOperators_ShouldWorkCorrectly() - { - // Arrange - var money1 = new Money(10050); - var money2 = new Money(2); - var longValue = 2L; - - // Act & Assert - (money1 * money2).Value.Should().Be(20100); - (money1 * longValue).Value.Should().Be(20100); - (longValue * money1).Value.Should().Be(20100); - } - - [Fact(DisplayName = "Операторы деления должны работать корректно")] - public void DivisionOperators_ShouldWorkCorrectly() - { - // Arrange - var money1 = new Money(10050); - var money2 = new Money(2); - var longValue = 2L; - - // Act & Assert - (money1 / money2).Value.Should().Be(5025); - (money1 / longValue).Value.Should().Be(5025); - (longValue / money1).Value.Should().Be(0); - } - - [Fact(DisplayName = "Операторы сравнения должны работать корректно")] - public void ComparisonOperators_ShouldWorkCorrectly() - { - // Arrange - var money1 = new Money(10050); - var money2 = new Money(20050); - var money3 = new Money(10050); - - // Act & Assert - (money1 == money3).Should().BeTrue(); - (money1 != money2).Should().BeTrue(); - (money1 < money2).Should().BeTrue(); - (money2 > money1).Should().BeTrue(); - (money1 <= money3).Should().BeTrue(); - (money1 >= money3).Should().BeTrue(); - (money1 <= money2).Should().BeTrue(); - (money2 >= money1).Should().BeTrue(); - } - - [Fact(DisplayName = "Equals должен корректно определять равенство")] - public void Equals_ShouldWorkCorrectly() - { - // Arrange - var money1 = new Money(10050); - var money2 = new Money(10050); - var money3 = new Money(20050); - var obj = new object(); - - // Act & Assert - money1.Equals(money2).Should().BeTrue(); - money1.Equals(money3).Should().BeFalse(); - money1.Equals(obj).Should().BeFalse(); - } - - [Fact(DisplayName = "CompareTo должен корректно сравнивать значения")] - public void CompareTo_ShouldCompareValuesCorrectly() - { - // Arrange - var money1 = new Money(10050); - var money2 = new Money(20050); - var money3 = new Money(10050); - - // Act & Assert - money1.CompareTo(money2).Should().BeLessThan(0); - money2.CompareTo(money1).Should().BeGreaterThan(0); - money1.CompareTo(money3).Should().Be(0); - } - - [Fact(DisplayName = "GetHashCode должен возвращать корректный хэш-код")] - public void GetHashCode_ShouldReturnCorrectHashCode() - { - // Arrange - var money = new Money(10050); - - // Act - var hashCode = money.GetHashCode(); - - // Assert - hashCode.Should().Be(10050.GetHashCode()); - } - - [Fact(DisplayName = "Неявное преобразование в числовые типы должно работать корректно")] - public void ImplicitConversionToNumericTypes_ShouldWorkCorrectly() - { - // Arrange - var money = new Money(10050); - - // Act - float floatValue = money; - double doubleValue = money; - decimal decimalValue = money; - - // Assert - floatValue.Should().BeApproximately(100.5f, 0.001f); - doubleValue.Should().BeApproximately(100.5, 0.001); - decimalValue.Should().Be(100.5m); - } - - [Fact(DisplayName = "Неявное преобразование из числовых типов должно работать корректно")] - public void ImplicitConversionFromNumericTypes_ShouldWorkCorrectly() - { - // Act - Money moneyFromFloat = 100.5f; - Money moneyFromDouble = 100.5; - Money moneyFromDecimal = 100.5m; - - // Assert - moneyFromFloat.Value.Should().Be(10050); - moneyFromDouble.Value.Should().Be(10050); - moneyFromDecimal.Value.Should().Be(10050); - } - - [Fact(DisplayName = "MaxValue и MinValue должны содержать правильные значения")] - public void MaxValueAndMinValue_ShouldHaveCorrectValues() - { - // Act & Assert - Money.MaxValue.Should().Be(Money.Create(long.MaxValue / 100L - 1, 99)); - Money.MinValue.Should().Be(Money.Create(long.MinValue / 100L + 1, 99)); - } -} \ No newline at end of file diff --git a/src/Hexecs.Tests/Worlds/WordDependencyShould.cs b/src/Hexecs.Tests/Worlds/WordDependencyShould.cs index 503d4e7..ad5f43d 100644 --- a/src/Hexecs.Tests/Worlds/WordDependencyShould.cs +++ b/src/Hexecs.Tests/Worlds/WordDependencyShould.cs @@ -7,10 +7,10 @@ namespace Hexecs.Tests.Worlds; public sealed class WordDependencyShould { private readonly World _world = new WorldBuilder() - .Singleton(_ => new Singleton()) - .Singleton(_ => new Singleton()) - .Scoped(_ => new Scoped()) - .Transient(_ => new Transient()) + .UseSingleton(_ => new Singleton()) + .UseSingleton(_ => new Singleton()) + .UseScoped(_ => new Scoped()) + .UseTransient(_ => new Transient()) .Build(); [Fact] diff --git a/src/Hexecs/Actors/Systems/DrawSystem.cs b/src/Hexecs/Actors/Systems/DrawSystem.cs index 551bff1..13b349a 100644 --- a/src/Hexecs/Actors/Systems/DrawSystem.cs +++ b/src/Hexecs/Actors/Systems/DrawSystem.cs @@ -6,7 +6,7 @@ namespace Hexecs.Actors.Systems; -public abstract class DrawSystem(ActorContext context) : IDrawSystem +public abstract class DrawSystem(ActorContext context) : IDrawSystem, IDisposable { public bool Enabled { get; set; } = true; @@ -52,6 +52,10 @@ private ContextLogger CreateLogger() => Context .CreateContext(GetType()); ActorContext IDrawSystem.Context => Context; + + public virtual void Dispose() + { + } } public abstract class DrawSystem : DrawSystem diff --git a/src/Hexecs/Dependencies/IDependencyCollection.cs b/src/Hexecs/Dependencies/IDependencyCollection.cs index 6eb05ea..e06a1c2 100644 --- a/src/Hexecs/Dependencies/IDependencyCollection.cs +++ b/src/Hexecs/Dependencies/IDependencyCollection.cs @@ -6,15 +6,15 @@ public interface IDependencyCollection IDependencyCollection AddRegistrar(IDependencyRegistrar registrar); - IDependencyCollection Singleton(Type contract, Func resolver); + IDependencyCollection UseSingleton(Type contract, Func resolver); - IDependencyCollection Singleton(Func resolver) where T : class; + IDependencyCollection UseSingleton(Func resolver) where T : class; - IDependencyCollection Scoped(Type contract, Func resolver); + IDependencyCollection UseScoped(Type contract, Func resolver); - IDependencyCollection Scoped(Func resolver) where T : class; + IDependencyCollection UseScoped(Func resolver) where T : class; - IDependencyCollection Transient(Type contract, Func resolver); + IDependencyCollection UseTransient(Type contract, Func resolver); - IDependencyCollection Transient(Func resolver) where T : class; + IDependencyCollection UseTransient(Func resolver) where T : class; } \ No newline at end of file diff --git a/src/Hexecs/Loggers/LogBuilder.cs b/src/Hexecs/Loggers/LogBuilder.cs index 01d026a..3560cf3 100644 --- a/src/Hexecs/Loggers/LogBuilder.cs +++ b/src/Hexecs/Loggers/LogBuilder.cs @@ -32,8 +32,7 @@ internal LogBuilder() new KeyValuePair(typeof(Actor), ActorLogWriter.Instance), new KeyValuePair(typeof(Asset), AssetLogWriter.Instance), new KeyValuePair(typeof(ActorId), ActorIdLogWriter.Instance), - new KeyValuePair(typeof(AssetId), AssetIdLogWriter.Instance), - new KeyValuePair(typeof(Money), new DefaultMoneyWriter()), + new KeyValuePair(typeof(AssetId), AssetIdLogWriter.Instance) ], ReferenceComparer.Instance); _valueFactories = new Queue(4); diff --git a/src/Hexecs/Loggers/Writers/DefaultMoneyWriter.cs b/src/Hexecs/Loggers/Writers/DefaultMoneyWriter.cs deleted file mode 100644 index fa32f64..0000000 --- a/src/Hexecs/Loggers/Writers/DefaultMoneyWriter.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Globalization; - -namespace Hexecs.Loggers.Writers; - -internal sealed class DefaultMoneyWriter : ILogValueWriter -{ - public void Write(ref ValueStringBuilder stringBuilder, Money arg) - { - stringBuilder.Append(arg.Value, "N2", CultureInfo.InvariantCulture); - } -} \ No newline at end of file diff --git a/src/Hexecs/Utils/Args.cs b/src/Hexecs/Utils/Args.cs index b533803..d0c0d15 100644 --- a/src/Hexecs/Utils/Args.cs +++ b/src/Hexecs/Utils/Args.cs @@ -45,6 +45,29 @@ public TValue Get(string name) return value; } + /// + /// Получает значение аргумента по имени. + /// Выбрасывает исключение, если значение не найдено. + /// + /// Тип значения. + /// Имя аргумента. + public TValue GetOrDefault(string name) + { + return TryGet(name, out var value) ? value : default!; + } + + /// + /// Получает значение аргумента по имени. + /// Выбрасывает исключение, если значение не найдено. + /// + /// Тип значения. + /// Имя аргумента. + /// Значение по умолчанию + public TValue GetOrDefault(string name, TValue defaultValue) + { + return TryGet(name, out var value) ? value : defaultValue; + } + /// /// Возвращает экземпляр Args в пул после использования. /// Очищает все хранилища значений и возвращает их в соответствующие пулы. diff --git a/src/Hexecs/Utils/Money.cs b/src/Hexecs/Utils/Money.cs deleted file mode 100644 index c8a4d04..0000000 --- a/src/Hexecs/Utils/Money.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System.Globalization; -using System.Numerics; - -namespace Hexecs.Utils; - -/// -/// Представляет денежную сумму с точностью до двух знаков после запятой. -/// Внутренне хранит значение в наименьших единицах валюты (копейках). -/// Обеспечивает арифметические операции, сравнение и преобразование между различными числовыми форматами. -/// -/// -/// Денежные значения хранятся как целое число (), представляющее сотые доли (копейки), -/// что позволяет избежать проблем с точностью при использовании чисел с плавающей точкой. -/// -[DebuggerDisplay("{ToString()}")] -[method: MethodImpl(MethodImplOptions.AggressiveInlining)] -public readonly struct Money(long value) : - IComparable, IEquatable, - IMinMaxValue, - ISpanFormattable, IUtf8SpanFormattable -{ - /// - /// Максимальное значение - /// - public static readonly Money MaxValue = Create(long.MaxValue / 100L - 1, 99); - - /// - /// Минимальное значение - /// - public static readonly Money MinValue = Create(long.MinValue / 100L + 1, 99); - - /// - /// Создает экземпляр структуры Money с указанными значениями целой и дробной части. - /// - /// Целая часть суммы денег. - /// Дробная часть суммы денег (от 0 до 99). - /// Новый экземпляр структуры Money. - /// Если дробная часть не находится в диапазоне от 0 до 99. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money Create(long whole, int? fraction = null) - { - var result = whole * 100; - - if (fraction is < 0 or > 99) ThrowOverflow(); - - // ReSharper disable once InvertIf - if (fraction.HasValue) - { - if (whole >= 0) result += fraction.Value; - else result -= fraction.Value; - } - - return new Money(result); - } - - /// - /// Пытается преобразовать строковое представление суммы денег в эквивалентный экземпляр структуры Money. - /// - /// Строка, содержащая сумму денег для преобразования. - /// При успешном выполнении содержит значение типа Money, эквивалентное строке s. - /// True, если s успешно преобразована; иначе false. - public static bool TryParse(ReadOnlySpan s, out Money result) - { - if (double.TryParse(s, CultureInfo.InvariantCulture, out var doubleValue)) - { - result = new Money((long)(doubleValue * 100)); - return true; - } - - result = Zero; - return false; - } - - /// - /// Получает экземпляр структуры Money со значением ноль. - /// - public static Money Zero - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(0); - } - - /// - /// Дробная часть - /// - /// - /// Это остаток от деления на 100 - /// - public int Fraction - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - var fraction = Value % 100; - return Value >= 0 ? (int)fraction : (int)-fraction; - } - } - - /// - /// Целая часть - /// - /// - /// Это результат целочисленного деления на 100 - /// - public long Whole - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Value / 100; - } - - /// - /// Внутреннее значение, представляющее деньги в наименьших единицах (копейках). - /// - public readonly long Value = value; - - /// - /// Возвращает абсолютное значение суммы. - /// - /// Абсолютное значение текущей суммы. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Money Abs() => new(Math.Abs(Value)); - - /// - /// Возвращает минимальное значение из текущей суммы и указанного параметра. - /// - /// Сумма для сравнения. - /// Минимальное из двух значений. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Money Min(in Money b) => Value < b.Value ? this : b; - - /// - /// Возвращает максимальное значение из текущей суммы и указанного параметра. - /// - /// Сумма для сравнения. - /// Максимальное из двух значений. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Money Max(in Money b) => Value > b.Value ? this : b; - - /// - /// Возвращает строковое представление суммы в формате с двумя десятичными знаками. - /// - /// Строковое представление суммы. - public override string ToString() => (Value / 100.0).ToString("N2", CultureInfo.InvariantCulture); - - /// - /// Форматирует значение в строковом представлении с использованием указанного формата и провайдера форматирования. - /// - /// Строка формата (поддерживаются "G", "F", "N" или null для формата по умолчанию). - /// Объект, который предоставляет информацию о форматировании. - /// Строка, представляющая форматированное значение Money. - public string ToString(string? format, IFormatProvider? formatProvider) - { - formatProvider ??= CultureInfo.InvariantCulture; - format ??= "N2"; - - return (Value / 100.0).ToString(format, formatProvider); - } - - /// - /// Пытается форматировать значение в буфере символов, используя указанный формат и провайдер форматирования. - /// - /// Буфер символов для форматирования результата. - /// Количество записанных символов в буфер. - /// Строка формата - /// Объект, который предоставляет информацию о форматировании. - /// True, если форматирование выполнено успешно; в противном случае — false. - public bool TryFormat( - Span destination, - out int charsWritten, - ReadOnlySpan format, - IFormatProvider? formatProvider) - { - formatProvider ??= CultureInfo.InvariantCulture; - var formatString = format.IsEmpty ? "N2" : format; - var decimalValue = Value / 100.0M; - - // Используем стандартный метод форматирования double для вывода в буфер - return decimalValue.TryFormat(destination, out charsWritten, formatString, formatProvider); - } - - /// - /// Пытается форматировать значение в виде последовательности байтов UTF-8 в предоставленном буфере. - /// - /// Буфер байтов UTF-8 для вывода форматированного значения. - /// Количество байтов, записанных в буфер. - /// Строка формата. - /// Объект, предоставляющий информацию о форматировании. - /// True, если форматирование выполнено успешно; в противном случае — false. - public bool TryFormat( - Span utf8Destination, - out int bytesWritten, - ReadOnlySpan format, - IFormatProvider? formatProvider) - { - formatProvider ??= CultureInfo.InvariantCulture; - var formatString = format.IsEmpty ? "N2" : format.ToString(); - var decimalValue = Value / 100.0M; - - return decimalValue.TryFormat(utf8Destination, out bytesWritten, formatString, formatProvider); - } - - [DoesNotReturn] - private static void ThrowOverflow() => throw new OverflowException("Fraction should be between 0 and 100"); - - #region + and - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator +(in Money left, in Money right) => new(left.Value + right.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator -(in Money left, in Money right) => new(left.Value - right.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator -(in Money money) => new(-money.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator +(in Money money) => money; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator +(in Money left, in long right) => new(left.Value + right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator -(in Money left, in long right) => new(left.Value - right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator +(in long left, in Money right) => new(left + right.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator -(in long left, in Money right) => new(left - right.Value); - - #endregion - - #region * and / - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator *(in Money left, in Money right) => new(left.Value * right.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator /(in Money left, in Money right) => new(left.Value / right.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator *(in long left, in Money right) => new(left * right.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator /(in long left, in Money right) => new(left / right.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator *(in Money left, in long right) => new(left.Value * right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Money operator /(in Money left, in long right) => new(left.Value / right); - - #endregion - - #region Equality - - public int CompareTo(Money other) => Value.CompareTo(other.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Money other) => Value == other.Value; - - public override bool Equals(object? obj) => obj is Money other && Equals(other); - - public override int GetHashCode() => Value.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(in Money left, in Money right) => left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(in Money left, in Money right) => !left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator >(in Money left, in Money right) => left.Value > right.Value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator >=(in Money left, in Money right) => left.Value >= right.Value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator <(in Money left, in Money right) => left.Value < right.Value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator <=(in Money left, in Money right) => left.Value <= right.Value; - - #endregion - - #region implicit - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator float(in Money money) => money.Value / 100f; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator double(in Money money) => money.Value / 100.0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator decimal(in Money money) => money.Value / 100m; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Money(float value) => new((long)(value * 100)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Money(double value) => new((long)(value * 100)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Money(decimal value) => new((long)(value * 100)); - - #endregion - - #region interfaces - - static Money IMinMaxValue.MaxValue => MaxValue; - static Money IMinMaxValue.MinValue => MinValue; - - #endregion -} \ No newline at end of file diff --git a/src/Hexecs/Worlds/Dice.cs b/src/Hexecs/Worlds/Dice.cs index 88eb8ab..f475ad0 100644 --- a/src/Hexecs/Worlds/Dice.cs +++ b/src/Hexecs/Worlds/Dice.cs @@ -41,6 +41,9 @@ public int GetNext(int start, int end) return (int)value + start; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double GetNextDouble() => GetNext() / 32768.0; + #region Roll /// diff --git a/src/Hexecs/Worlds/WorldBuilder.cs b/src/Hexecs/Worlds/WorldBuilder.cs index dff5b2b..d44e497 100644 --- a/src/Hexecs/Worlds/WorldBuilder.cs +++ b/src/Hexecs/Worlds/WorldBuilder.cs @@ -25,9 +25,9 @@ public WorldBuilder Add(DependencyLifetime lifetime, Type contract, Func Singleton(contract, resolver), - DependencyLifetime.Scoped => Scoped(contract, resolver), - DependencyLifetime.Transient => Transient(contract, resolver), + DependencyLifetime.Singleton => UseSingleton(contract, resolver), + DependencyLifetime.Scoped => UseScoped(contract, resolver), + DependencyLifetime.Transient => UseTransient(contract, resolver), _ => DependencyError.NotSupportedLifetime(lifetime) }; } @@ -38,16 +38,45 @@ public WorldBuilder AddRegistrar(IDependencyRegistrar registrar) return this; } + public World Build() + { + foreach (var registrar in _registrars) + { + registrar.TryRegister(this); + } + + _registrars.Clear(); + + var dependencyProvider = new DependencyProvider(_dependencies + .GroupBy(static dep => dep.Contract) + .Select(static group => new KeyValuePair(group.Key, group.ToArray())) + .ToDictionary(ReferenceComparer.Instance), null); + + var instance = new World( + _configurationService ?? ConfigurationService.Empty, + _logService ?? LogService.Empty, + dependencyProvider, + _defaultActorContextBuilder ?? DelegateUtils.EmptyAction, + _valueStorage ?? ValueService.Empty); + + if (_debugWorld) WorldDebug.World = instance; + + LoadAssets(instance); + + _dependencies.Clear(); + + return instance; + } #region AssetSource - public WorldBuilder AddAssetSource(IAssetSource source) + public WorldBuilder UseAddAssetSource(IAssetSource source) { _assetSourceBuilders.Add(_ => source); return this; } - public WorldBuilder AddAssetSource() where T : class, IAssetSource, new() + public WorldBuilder UseAddAssetSource() where T : class, IAssetSource, new() { _assetSourceBuilders.Add(static _ => new T()); return this; @@ -69,47 +98,57 @@ public WorldBuilder CreateAssetData(int order, Action source) #endregion - public World Build() + public WorldBuilder UseAsDebugWorld(bool value = true) { - foreach (var registrar in _registrars) - { - registrar.TryRegister(this); - } - - _registrars.Clear(); + _debugWorld = value; + return this; + } - var dependencyProvider = new DependencyProvider(_dependencies - .GroupBy(static dep => dep.Contract) - .Select(static group => new KeyValuePair(group.Key, group.ToArray())) - .ToDictionary(ReferenceComparer.Instance), null); + public WorldBuilder UseConfiguration(Action configuration) + { + var builder = new ConfigurationBuilder(); + configuration(builder); - var instance = new World( - _configurationService ?? ConfigurationService.Empty, - _logService ?? LogService.Empty, - dependencyProvider, - _defaultActorContextBuilder ?? DelegateUtils.EmptyAction, - _valueStorage ?? ValueService.Empty); + _configurationService = builder.Build(); - if (_debugWorld) WorldDebug.World = instance; + return this; + } - LoadAssets(instance); + public WorldBuilder UseDefaultActorContext(Action defaultActorContext) + { + _defaultActorContextBuilder = defaultActorContext; + return this; + } - _dependencies.Clear(); + #region ParallelWorker - return instance; + public WorldBuilder UseDefaultParallelWorker(int? degreeOfParallelism = null) + { + return UseDefaultParallelWorker(ctx => + { + if (degreeOfParallelism == null) + { + var configuration = ctx.GetService(); + degreeOfParallelism = configuration?.GetValue("ParallelWorker:DegreeOfParallelism"); + } + + return new DefaultParallelWorker(degreeOfParallelism ?? Environment.ProcessorCount); + }); } - public WorldBuilder CreateConfiguration(Action configuration) + public WorldBuilder UseDefaultParallelWorker(IParallelWorker worker) { - var builder = new ConfigurationBuilder(); - configuration(builder); - - _configurationService = builder.Build(); + return UseDefaultParallelWorker(_ => worker); + } - return this; + public WorldBuilder UseDefaultParallelWorker(Func worker) + { + return UseSingleton(worker); } - public WorldBuilder CreateLogger(Action logger) + #endregion + + public WorldBuilder UseLogger(Action logger) { var builder = new LogBuilder(); logger(builder); @@ -119,7 +158,7 @@ public WorldBuilder CreateLogger(Action logger) return this; } - public WorldBuilder CreateValues(Action values) + public WorldBuilder UseValues(Action values) { var builder = new ValueServiceBuilder(); values(builder); @@ -129,62 +168,59 @@ public WorldBuilder CreateValues(Action values) return this; } - public WorldBuilder DebugWorld(bool value = true) - { - _debugWorld = value; - return this; - } + #region Singleton - public WorldBuilder DefaultActorContext(Action defaultActorContext) + public WorldBuilder UseSingleton(T value) + where T : class { - _defaultActorContextBuilder = defaultActorContext; + _dependencies.Add(new Dependency(DependencyLifetime.Singleton, typeof(T), _ => value)); return this; } - public WorldBuilder DefaultParallelWorker(int? degreeOfParallelism = null) - { - var value = degreeOfParallelism ?? Environment.ProcessorCount; - return DefaultParallelWorker(new DefaultParallelWorker(value)); - } - public WorldBuilder DefaultParallelWorker(IParallelWorker worker) - { - return Singleton(typeof(IParallelWorker), _ => worker); - } - - public WorldBuilder Singleton(Type contract, Func resolver) + public WorldBuilder UseSingleton(Type contract, Func resolver) { _dependencies.Add(new Dependency(DependencyLifetime.Singleton, contract, resolver)); return this; } - public WorldBuilder Singleton(Func resolver) where T : class + public WorldBuilder UseSingleton(Func resolver) where T : class { - return Singleton(typeof(T), resolver); + return UseSingleton(typeof(T), resolver); } - public WorldBuilder Scoped(Type contract, Func resolver) + #endregion + + #region Scoped + + public WorldBuilder UseScoped(Type contract, Func resolver) { _dependencies.Add(new Dependency(DependencyLifetime.Scoped, contract, resolver)); return this; } - public WorldBuilder Scoped(Func resolver) where T : class + public WorldBuilder UseScoped(Func resolver) where T : class { - return Scoped(typeof(T), resolver); + return UseScoped(typeof(T), resolver); } - public WorldBuilder Transient(Type contract, Func resolver) + #endregion + + #region Transient + + public WorldBuilder UseTransient(Type contract, Func resolver) { _dependencies.Add(new Dependency(DependencyLifetime.Transient, contract, resolver)); return this; } - public WorldBuilder Transient(Func resolver) where T : class + public WorldBuilder UseTransient(Func resolver) where T : class { - return Transient(typeof(T), resolver); + return UseTransient(typeof(T), resolver); } + #endregion + private void LoadAssets(World world) { var sortedAssetSources = _assetSourceBuilders @@ -211,34 +247,34 @@ IDependencyCollection IDependencyCollection.Add( return Add(lifetime, contract, resolver); } - IDependencyCollection IDependencyCollection.Singleton(Type contract, Func resolver) + IDependencyCollection IDependencyCollection.UseSingleton(Type contract, Func resolver) { - return Singleton(contract, resolver); + return UseSingleton(contract, resolver); } - IDependencyCollection IDependencyCollection.Singleton(Func resolver) where T : class + IDependencyCollection IDependencyCollection.UseSingleton(Func resolver) where T : class { - return Singleton(resolver); + return UseSingleton(resolver); } - IDependencyCollection IDependencyCollection.Scoped(Type contract, Func resolver) + IDependencyCollection IDependencyCollection.UseScoped(Type contract, Func resolver) { - return Scoped(contract, resolver); + return UseScoped(contract, resolver); } - IDependencyCollection IDependencyCollection.Scoped(Func resolver) where T : class + IDependencyCollection IDependencyCollection.UseScoped(Func resolver) where T : class { - return Scoped(resolver); + return UseScoped(resolver); } - IDependencyCollection IDependencyCollection.Transient(Type contract, Func resolver) + IDependencyCollection IDependencyCollection.UseTransient(Type contract, Func resolver) { - return Transient(contract, resolver); + return UseTransient(contract, resolver); } - IDependencyCollection IDependencyCollection.Transient(Func resolver) where T : class + IDependencyCollection IDependencyCollection.UseTransient(Func resolver) where T : class { - return Transient(resolver); + return UseTransient(resolver); } #endregion From d99ead0466d29a7dd22e25014b129a717ce923a8 Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Wed, 24 Dec 2025 13:57:59 +0300 Subject: [PATCH 07/14] 0.4.3 map bench --- src/Hexecs.Benchmarks.City/CityGame.cs | 22 ++-- .../Common/Positions/Position.cs | 1 + .../Common/Positions/PositionBuilder.cs | 10 +- .../Common/Positions/PositionsInstaller.cs | 12 +- .../Common/Visibles/Visible.cs | 3 + .../Common/Visibles/VisibleInstaller.cs | 15 +++ .../Common/Visibles/VisibleSystem.cs | 47 ++++++++ .../Content/roguelikeSheet_transparent.png | Bin 0 -> 94579 bytes src/Hexecs.Benchmarks.City/FpsCounter.cs | 65 ++++++++++ .../Hexecs.Benchmarks.City.csproj | 6 +- .../Terrains/Assets/TerrainAssetSource.cs | 3 + .../Generate/GenerateTerrainCommand.cs | 2 - .../Generate/GenerateTerrainHandler.cs | 3 +- .../Terrains/Terrain.cs | 1 - .../Terrains/TerrainBuilder.cs | 1 - .../Terrains/TerrainDrawSystem.cs | 33 ++--- .../Terrains/TerrainGrid.cs | 43 ------- .../Terrains/TerrainInstaller.cs | 11 +- .../Terrains/TerrainSettings.cs | 2 +- .../Terrains/TerrainTextureStorage.cs | 23 ---- .../{ => Terrains}/ValueTypes/Elevation.cs | 2 +- .../{ => Terrains}/ValueTypes/Moisture.cs | 2 +- .../{ => Terrains}/ValueTypes/Temperature.cs | 2 +- .../Utils/AtlasTexture.cs | 14 +++ src/Hexecs.Benchmarks.City/Utils/Camera.cs | 59 +++++++-- .../Utils/CameraViewport.cs | 32 +++++ .../Utils/TextureStorage.cs | 69 +++++++++++ .../Hexecs.Benchmarks.Noise.csproj | 2 +- src/Hexecs.Benchmarks.Noise/NoiseGame.cs | 2 +- .../Actors/ActorContextBuilder.Extensions.cs | 99 +++++++-------- src/Hexecs/Actors/Systems/DrawSystem.cs | 114 ------------------ src/Hexecs/Actors/Systems/DrawSystem1.cs | 44 +++++++ src/Hexecs/Actors/Systems/DrawSystem2.cs | 45 +++++++ src/Hexecs/Actors/Systems/DrawSystem3.cs | 46 +++++++ src/Hexecs/Actors/Systems/UpdateSystem1.cs | 11 +- src/Hexecs/Actors/Systems/UpdateSystem2.cs | 9 +- src/Hexecs/Actors/Systems/UpdateSystem3.cs | 9 +- src/Hexecs/Hexecs.csproj | 3 + src/Hexecs/Worlds/WorldBuilder.Extensions.cs | 14 +++ 39 files changed, 585 insertions(+), 296 deletions(-) create mode 100644 src/Hexecs.Benchmarks.City/Common/Visibles/Visible.cs create mode 100644 src/Hexecs.Benchmarks.City/Common/Visibles/VisibleInstaller.cs create mode 100644 src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs create mode 100644 src/Hexecs.Benchmarks.City/Content/roguelikeSheet_transparent.png create mode 100644 src/Hexecs.Benchmarks.City/FpsCounter.cs delete mode 100644 src/Hexecs.Benchmarks.City/Terrains/TerrainGrid.cs delete mode 100644 src/Hexecs.Benchmarks.City/Terrains/TerrainTextureStorage.cs rename src/Hexecs.Benchmarks.City/{ => Terrains}/ValueTypes/Elevation.cs (96%) rename src/Hexecs.Benchmarks.City/{ => Terrains}/ValueTypes/Moisture.cs (94%) rename src/Hexecs.Benchmarks.City/{ => Terrains}/ValueTypes/Temperature.cs (94%) create mode 100644 src/Hexecs.Benchmarks.City/Utils/AtlasTexture.cs create mode 100644 src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs create mode 100644 src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs create mode 100644 src/Hexecs/Actors/Systems/DrawSystem1.cs create mode 100644 src/Hexecs/Actors/Systems/DrawSystem2.cs create mode 100644 src/Hexecs/Actors/Systems/DrawSystem3.cs create mode 100644 src/Hexecs/Worlds/WorldBuilder.Extensions.cs diff --git a/src/Hexecs.Benchmarks.City/CityGame.cs b/src/Hexecs.Benchmarks.City/CityGame.cs index e078822..d3156f7 100644 --- a/src/Hexecs.Benchmarks.City/CityGame.cs +++ b/src/Hexecs.Benchmarks.City/CityGame.cs @@ -1,9 +1,9 @@ using Hexecs.Benchmarks.Map.Common; +using Hexecs.Benchmarks.Map.Common.Visibles; using Hexecs.Benchmarks.Map.Terrains; using Hexecs.Benchmarks.Map.Terrains.Commands.Generate; using Hexecs.Benchmarks.Map.Utils; using Hexecs.Worlds; -using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; @@ -11,8 +11,9 @@ namespace Hexecs.Benchmarks.Map; internal sealed class CityGame : Game { - private Camera2D _camera = null!; + private Camera _camera = null!; private readonly GraphicsDeviceManager _graphics; + private FpsCounter _fpsCounter = null!; private World _world = null!; public CityGame() @@ -23,7 +24,7 @@ public CityGame() PreferredBackBufferHeight = 720, GraphicsProfile = GraphicsProfile.HiDef, PreferMultiSampling = true, - SynchronizeWithVerticalRetrace = false, + SynchronizeWithVerticalRetrace = true, IsFullScreen = false, HardwareModeSwitch = false }; @@ -43,21 +44,25 @@ protected override void Initialize() { GraphicsDevice.SamplerStates[0] = SamplerState.AnisotropicClamp; - _camera = new Camera2D(GraphicsDevice); + _camera = new Camera(GraphicsDevice); _world = new WorldBuilder() + .UseSingleton() .UseDefaultParallelWorker(Math.Min(6, Environment.ProcessorCount)) - .UseSingleton(_ => Content) - .UseSingleton(_ => GraphicsDevice) + .UseSingleton(Content) + .UseSingleton(GraphicsDevice) .UseSingleton(_camera) .UseTerrain() .UseDefaultActorContext(context => context - .Capacity(500_000) + .Capacity(3_000_000) .AddCommon() - .AddTerrain()) + .AddTerrain() + .AddVisible()) .Build(); _world.Actors.Execute(new GenerateTerrainCommand()); + _fpsCounter = new FpsCounter(() => _world.Actors.Length, Window); + base.Initialize(); } @@ -75,6 +80,7 @@ protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.White); + _fpsCounter.Draw(gameTime); _world.Draw(gameTime.ElapsedGameTime, gameTime.TotalGameTime); base.Draw(gameTime); diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/Position.cs b/src/Hexecs.Benchmarks.City/Common/Positions/Position.cs index b88373b..98c7e99 100644 --- a/src/Hexecs.Benchmarks.City/Common/Positions/Position.cs +++ b/src/Hexecs.Benchmarks.City/Common/Positions/Position.cs @@ -3,4 +3,5 @@ public struct Position : IActorComponent { public Point Grid; + public Point World; } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs b/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs index a0f91e2..0e69fd3 100644 --- a/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs +++ b/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs @@ -1,12 +1,18 @@ -namespace Hexecs.Benchmarks.Map.Common.Positions; +using Hexecs.Benchmarks.Map.Utils; + +namespace Hexecs.Benchmarks.Map.Common.Positions; internal sealed class PositionBuilder : IActorBuilder { + private const int TileSize = TextureStorage.TerrainTileSize; + public void Build(in Actor actor, in AssetRef asset, Args args) { + var grid = args.Get(nameof(Point)); actor.Add(new Position { - Grid = args.Get(nameof(Point)) + Grid = grid, + World = new Point(grid.X * TileSize, grid.Y * TileSize) }); } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs b/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs index 40ceacf..06080ec 100644 --- a/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs +++ b/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs @@ -1,10 +1,20 @@ -namespace Hexecs.Benchmarks.Map.Common.Positions; +using Hexecs.Benchmarks.Map.Terrains; +using Hexecs.Dependencies; + +namespace Hexecs.Benchmarks.Map.Common.Positions; internal static class PositionsInstaller { public static ActorContextBuilder AddPositions(this ActorContextBuilder builder) { + var terrainSettings = builder.World.GetRequiredService(); + builder.AddBuilder(); + + builder + .ConfigureComponentPool(terrain => terrain + .Capacity(terrainSettings.Width * terrainSettings.Height)); + return builder; } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Visibles/Visible.cs b/src/Hexecs.Benchmarks.City/Common/Visibles/Visible.cs new file mode 100644 index 0000000..1c0e4e9 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Common/Visibles/Visible.cs @@ -0,0 +1,3 @@ +namespace Hexecs.Benchmarks.Map.Common.Visibles; + +public struct Visible : IActorComponent; \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleInstaller.cs b/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleInstaller.cs new file mode 100644 index 0000000..7c57260 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleInstaller.cs @@ -0,0 +1,15 @@ +namespace Hexecs.Benchmarks.Map.Common.Visibles; + +internal static class VisibleInstaller +{ + public static ActorContextBuilder AddVisible(this ActorContextBuilder builder) + { + builder + .ConfigureComponentPool(terrain => terrain + .Capacity(4096)); + + builder.CreateUpdateSystem(); + + return builder; + } +} \ 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 new file mode 100644 index 0000000..88fcbb6 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs @@ -0,0 +1,47 @@ +using Hexecs.Actors.Systems; +using Hexecs.Benchmarks.Map.Common.Positions; +using Hexecs.Benchmarks.Map.Utils; +using Hexecs.Threading; +using Hexecs.Worlds; + +namespace Hexecs.Benchmarks.Map.Common.Visibles; + +internal sealed class VisibleSystem : UpdateSystem +{ + private const int TileSize = TextureStorage.TerrainTileSize; + + private readonly Camera _camera; + + private CameraViewport _currentViewport; + + public VisibleSystem(ActorContext context, Camera camera, IParallelWorker parallelWorker) + : base(context, parallelWorker: parallelWorker) + { + _camera = camera; + } + + protected override bool BeforeUpdate(in WorldTime time) + { + var currentViewport = _camera.Viewport; + + if (currentViewport.Equals(_currentViewport)) return false; // не обновляем, если камера не двигалась + + _currentViewport = currentViewport; + + return true; + } + + protected override void Update(in ActorRef actor, in WorldTime time) + { + ref readonly var position = ref actor.Component1.World; + + if (_currentViewport.Visible(position.X, position.Y, TileSize, TileSize)) + { + actor.TryAdd(new Visible()); + } + else + { + actor.Remove(); + } + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Content/roguelikeSheet_transparent.png b/src/Hexecs.Benchmarks.City/Content/roguelikeSheet_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..79b13326ceb8557a98b2b78aa5e8e7b9269f6693 GIT binary patch literal 94579 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 literal 0 HcmV?d00001 diff --git a/src/Hexecs.Benchmarks.City/FpsCounter.cs b/src/Hexecs.Benchmarks.City/FpsCounter.cs new file mode 100644 index 0000000..5ac865c --- /dev/null +++ b/src/Hexecs.Benchmarks.City/FpsCounter.cs @@ -0,0 +1,65 @@ +namespace Hexecs.Benchmarks.Map; + +internal sealed class FpsCounter +{ + private readonly Func _countResolver; + private readonly int[] _fpsHistory; + private readonly GameWindow _window; + + // Поля для статистики + private double _frameTime; + private int _fps; + private int _frameCount; + private double _fpsTimer; + private int _secondsCounter; + + // Для среднего значения за минуту (Rolling Average) + private int _historyIndex; + private bool _historyFull; + private double _avgFps; + + public FpsCounter(Func countResolver, GameWindow window) + { + _countResolver = countResolver; + _window = window; + _fpsHistory = new int[60]; + } + + public void Draw(GameTime gameTime) + { + var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; + _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; + _fpsTimer += elapsedSeconds; + _frameCount++; + + // Считаем FPS каждую секунду для точности истории + if (_fpsTimer >= 1.0) + { + _fps = _frameCount; + + // Обновляем историю для Avg + _fpsHistory[_historyIndex] = _fps; + _historyIndex = (_historyIndex + 1) % 60; + if (_historyIndex == 0) _historyFull = true; + + // Считаем среднее за минуту + var historyCount = _historyFull ? 60 : _historyIndex; + var sum = 0; + for (var i = 0; i < historyCount; i++) sum += _fpsHistory[i]; + _avgFps = (double)sum / historyCount; + + _frameCount = 0; + _fpsTimer -= 1.0; + _secondsCounter++; + + if (_secondsCounter >= 1) + { + var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; + var count = _countResolver(); + _window.Title = $"FPS: {_fps} | Avg FPS: {_avgFps:F1} | Entities: {count:N0} | Frame Time: {_frameTime:F2}ms | Alloc: {alloc:F2}Mb"; + + _secondsCounter = 0; + } + } + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj b/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj index 49dbbec..72c6725 100644 --- a/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj +++ b/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj @@ -9,7 +9,7 @@ - + @@ -21,7 +21,9 @@ - + + PreserveNewest + diff --git a/src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAssetSource.cs b/src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAssetSource.cs index d24988b..106b6b8 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAssetSource.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/Assets/TerrainAssetSource.cs @@ -17,6 +17,9 @@ public void Load(IAssetLoader loader) Create(TerrainAsset.River, "Река", TerrainType.WaterRiver) .WithPosition(); + + Create(TerrainAsset.UrbanConcrete, "Бетон", TerrainType.UrbanConcrete) + .WithPosition(); } private AssetConfigurator Create( diff --git a/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainCommand.cs b/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainCommand.cs index 4eced5a..5b5a57a 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainCommand.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainCommand.cs @@ -1,7 +1,5 @@ -using System.Runtime.CompilerServices; using Hexecs.Pipelines; namespace Hexecs.Benchmarks.Map.Terrains.Commands.Generate; -[method: MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly struct GenerateTerrainCommand : ICommand; \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs b/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs index 4173edd..39f3d6b 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/Commands/Generate/GenerateTerrainHandler.cs @@ -1,8 +1,7 @@ using Hexecs.Actors.Pipelines; using Hexecs.Benchmarks.Map.Terrains.Assets; -using Hexecs.Benchmarks.Map.ValueTypes; +using Hexecs.Benchmarks.Map.Terrains.ValueTypes; using Hexecs.Pipelines; -using Hexecs.Worlds; namespace Hexecs.Benchmarks.Map.Terrains.Commands.Generate; diff --git a/src/Hexecs.Benchmarks.City/Terrains/Terrain.cs b/src/Hexecs.Benchmarks.City/Terrains/Terrain.cs index 6e6ac61..b8c0f30 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/Terrain.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/Terrain.cs @@ -1,5 +1,4 @@ using Hexecs.Benchmarks.Map.Terrains.ValueTypes; -using Hexecs.Benchmarks.Map.ValueTypes; namespace Hexecs.Benchmarks.Map.Terrains; diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainBuilder.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainBuilder.cs index 4eb41a4..24ff904 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainBuilder.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainBuilder.cs @@ -1,6 +1,5 @@ using Hexecs.Benchmarks.Map.Terrains.Assets; using Hexecs.Benchmarks.Map.Terrains.ValueTypes; -using Hexecs.Benchmarks.Map.ValueTypes; namespace Hexecs.Benchmarks.Map.Terrains; diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs index 8a9d2ee..aca0d08 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs @@ -1,5 +1,6 @@ using Hexecs.Actors.Systems; using Hexecs.Benchmarks.Map.Common.Positions; +using Hexecs.Benchmarks.Map.Common.Visibles; using Hexecs.Benchmarks.Map.Utils; using Hexecs.Worlds; using Microsoft.Xna.Framework.Graphics; @@ -8,47 +9,39 @@ namespace Hexecs.Benchmarks.Map.Terrains; internal sealed class TerrainDrawSystem : DrawSystem { - private const int TileSize = 64; - - private readonly Camera2D _camera; - private readonly TerrainGrid _grid; + private readonly Camera _camera; private readonly SpriteBatch _spriteBatch; + private readonly TextureStorage _textureStorage; public TerrainDrawSystem( - Camera2D camera, + Camera camera, ActorContext context, GraphicsDevice graphicsDevice, - TerrainGrid grid) - : base(context) + TextureStorage textureStorage) + : base(context, constraint => constraint.Include()) { _camera = camera; - _grid = grid; + _textureStorage = textureStorage; _spriteBatch = new SpriteBatch(graphicsDevice); } - protected override void BeforeDraw(in WorldTime time) + protected override bool BeforeDraw(in WorldTime time) { _spriteBatch.Begin( transformMatrix: _camera.GetTransformationMatrix(), samplerState: SamplerState.PointClamp, blendState: BlendState.AlphaBlend); + + return true; } protected override void Draw(in ActorRef actor, in WorldTime time) { - ref readonly var gridPosition = ref actor.Component1.Grid; ref readonly var terrain = ref actor.Component2; + ref readonly var texture = ref _textureStorage.GetTerrainTexture(terrain.Type, terrain.Overlay); - var drawPosition = new Vector2( - gridPosition.X * TileSize, - gridPosition.Y * TileSize); - - Texture2D texture = null!; //TODO: set it!!! - - _spriteBatch.Draw( - texture, - drawPosition, - Color.White); + ref readonly var worldPosition = ref actor.Component1.World; + texture.Draw(_spriteBatch, new Vector2(worldPosition.X, worldPosition.Y)); } protected override void AfterDraw(in WorldTime time) diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainGrid.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainGrid.cs deleted file mode 100644 index 0d037b1..0000000 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainGrid.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Hexecs.Benchmarks.Map.Common.Positions; -using Microsoft.Xna.Framework.Graphics; - -namespace Hexecs.Benchmarks.Map.Terrains; - -internal sealed class TerrainGrid -{ - private ActorContext _context; - - public TerrainGrid(ActorContext context) - { - _context = context; - _context.OnComponentAdded(OnAdded); - _context.OnComponentRemoving(OnRemoving); - _context.OnComponentUpdating(OnUpdating); - } - - public ReadOnlySpan.Enumerator GetEnumerator() - { - throw new NotImplementedException(); - } - - private void OnAdded(uint actorId, int index, ref Terrain component) - { - } - - private void OnRemoving(uint actorId, ref Terrain component) - { - throw new NotImplementedException(); - } - - private void OnUpdating(uint actorId, ref Terrain exists, in Terrain expected) - { - throw new NotImplementedException(); - } - - public struct Entry - { - public Position Position; - public Terrain Terrain; - public Texture2D Texture; - } -} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs index ae10d39..ecaa090 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs @@ -1,3 +1,4 @@ +using Hexecs.Benchmarks.Map.Terrains.Assets; using Hexecs.Benchmarks.Map.Terrains.Commands.Generate; using Hexecs.Configurations; using Hexecs.Dependencies; @@ -26,9 +27,13 @@ public static ActorContextBuilder AddTerrain(this ActorContextBuilder builder) public static WorldBuilder UseTerrain(this WorldBuilder builder) { - builder.UseSingleton(ctx => ctx - .GetService()? - .GetValue(TerrainSettings.Key) ?? TerrainSettings.Default); + builder + .UseAddAssetSource(new TerrainAssetSource()); + + builder + .UseSingleton(ctx => ctx + .GetService()? + .GetValue(TerrainSettings.Key) ?? TerrainSettings.Default); return builder; } diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs index 313ec60..96b4be1 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs @@ -2,7 +2,7 @@ namespace Hexecs.Benchmarks.Map.Terrains; public sealed class TerrainSettings { - public static TerrainSettings Default { get; } = new() { Width = 256, Height = 256 }; + public static TerrainSettings Default { get; } = new() { Width = 768, Height = 768 }; public const string Key = "Map:Terrain"; diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainTextureStorage.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainTextureStorage.cs deleted file mode 100644 index 3084fc4..0000000 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainTextureStorage.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Runtime.InteropServices; -using Hexecs.Benchmarks.Map.Terrains.ValueTypes; -using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; - -namespace Hexecs.Benchmarks.Map.Terrains; - -internal sealed class TerrainTextureStorage(ContentManager content) -{ - private readonly Dictionary _cache = new(); - - public Texture2D GetTexture(TerrainType type, TerrainOverlay overlay) - { - var key = new Key(type, overlay); - ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(_cache, key, out var exists); - if (exists) return value!; - - value = content.Load("Content/Textures/Terrain/" + type + "/" + overlay + ".png"); - return value; - } - - private readonly record struct Key(TerrainType Type, TerrainOverlay Overlay); -} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/ValueTypes/Elevation.cs b/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/Elevation.cs similarity index 96% rename from src/Hexecs.Benchmarks.City/ValueTypes/Elevation.cs rename to src/Hexecs.Benchmarks.City/Terrains/ValueTypes/Elevation.cs index 4c42f09..90d99d1 100644 --- a/src/Hexecs.Benchmarks.City/ValueTypes/Elevation.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/Elevation.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -namespace Hexecs.Benchmarks.Map.ValueTypes; +namespace Hexecs.Benchmarks.Map.Terrains.ValueTypes; public readonly struct Elevation { diff --git a/src/Hexecs.Benchmarks.City/ValueTypes/Moisture.cs b/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/Moisture.cs similarity index 94% rename from src/Hexecs.Benchmarks.City/ValueTypes/Moisture.cs rename to src/Hexecs.Benchmarks.City/Terrains/ValueTypes/Moisture.cs index 6e0faa8..80b3752 100644 --- a/src/Hexecs.Benchmarks.City/ValueTypes/Moisture.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/Moisture.cs @@ -1,7 +1,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -namespace Hexecs.Benchmarks.Map.ValueTypes; +namespace Hexecs.Benchmarks.Map.Terrains.ValueTypes; [DebuggerDisplay("{Value:F2}%")] public readonly struct Moisture diff --git a/src/Hexecs.Benchmarks.City/ValueTypes/Temperature.cs b/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/Temperature.cs similarity index 94% rename from src/Hexecs.Benchmarks.City/ValueTypes/Temperature.cs rename to src/Hexecs.Benchmarks.City/Terrains/ValueTypes/Temperature.cs index 806ca0f..a2fde28 100644 --- a/src/Hexecs.Benchmarks.City/ValueTypes/Temperature.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/ValueTypes/Temperature.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -namespace Hexecs.Benchmarks.Map.ValueTypes; +namespace Hexecs.Benchmarks.Map.Terrains.ValueTypes; public readonly struct Temperature { diff --git a/src/Hexecs.Benchmarks.City/Utils/AtlasTexture.cs b/src/Hexecs.Benchmarks.City/Utils/AtlasTexture.cs new file mode 100644 index 0000000..cd6867d --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Utils/AtlasTexture.cs @@ -0,0 +1,14 @@ +using System.Runtime.CompilerServices; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Map.Utils; + +internal readonly struct AtlasTexture(Texture2D texture, Rectangle region) +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Draw(SpriteBatch spriteBatch, Vector2 position) => spriteBatch.Draw( + texture, + position, + region, + Color.White); +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/Camera.cs b/src/Hexecs.Benchmarks.City/Utils/Camera.cs index 75e888d..0981cf3 100644 --- a/src/Hexecs.Benchmarks.City/Utils/Camera.cs +++ b/src/Hexecs.Benchmarks.City/Utils/Camera.cs @@ -3,7 +3,7 @@ namespace Hexecs.Benchmarks.Map.Utils; -internal sealed class Camera2D +internal sealed class Camera { private readonly GraphicsDevice _graphicsDevice; @@ -12,6 +12,11 @@ internal sealed class Camera2D ///

public Vector2 Position { get; private set; } + /// + /// Viewport of world boundary + /// + public CameraViewport Viewport { get; private set; } + /// /// Текущий масштаб камеры (1.0 = без изменений). /// @@ -19,7 +24,7 @@ internal sealed class Camera2D private int _previousScrollValue; - public Camera2D(GraphicsDevice graphicsDevice) + public Camera(GraphicsDevice graphicsDevice) { Position = Vector2.Zero; Zoom = 1f; @@ -33,8 +38,10 @@ public Camera2D(GraphicsDevice graphicsDevice) /// Множитель масштаба (больше 1 для приближения, меньше 1 для отдаления). public void AdjustZoom(float factor) { - Zoom *= factor; - Zoom = MathHelper.Clamp(Zoom, 0.1f, 10f); + if (factor > 0) Zoom += 1f; + else Zoom -= 1f; + + Zoom = MathHelper.Clamp(Zoom, 1f, 10f); } /// @@ -43,12 +50,30 @@ public void AdjustZoom(float factor) public Matrix GetTransformationMatrix() { var viewport = _graphicsDevice.Viewport; + var zoom = MathF.Round(Zoom); + + var roundedPosition = new Vector2( + MathF.Round(Position.X), + MathF.Round(Position.Y) + ); - return Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0)) * - Matrix.CreateScale(new Vector3(Zoom, Zoom, 1)) * + return Matrix.CreateTranslation(new Vector3(-roundedPosition.X, -roundedPosition.Y, 0)) * + Matrix.CreateScale(new Vector3(zoom, zoom, 1)) * Matrix.CreateTranslation(new Vector3(viewport.Width * 0.5f, viewport.Height * 0.5f, 0)); } + public Rectangle GetViewportWorldBoundary() + { + var viewport = _graphicsDevice.Viewport; + var topLeft = ScreenToWorld(new Vector2(0, 0)); + var bottomRight = ScreenToWorld(new Vector2(viewport.Width, viewport.Height)); + + var width = (int)MathF.Ceiling(bottomRight.X - topLeft.X); + var height = (int)MathF.Ceiling(bottomRight.Y - topLeft.Y); + + return new Rectangle((int)topLeft.X, (int)topLeft.Y, width, height); + } + /// /// Смещает камеру на указанный вектор. /// @@ -79,18 +104,20 @@ public void Update(GameTime gameTime) Move(moveDir * speed * dt); } - if (keyboard.IsKeyDown(Keys.Q)) Zoom += dt * Zoom; - if (keyboard.IsKeyDown(Keys.E)) Zoom -= dt * Zoom; + if (keyboard.IsKeyDown(Keys.Q)) Zoom += dt * 5f; + if (keyboard.IsKeyDown(Keys.E)) Zoom -= dt * 5f; var scrollDelta = mouse.ScrollWheelValue - _previousScrollValue; if (scrollDelta != 0) { - AdjustZoom(scrollDelta > 0 ? 1.1f : 0.9f); + AdjustZoom(scrollDelta); } _previousScrollValue = mouse.ScrollWheelValue; - Zoom = MathHelper.Clamp(Zoom, 0.1f, 10f); + Zoom = MathHelper.Clamp(Zoom, 1f, 10f); + + UpdateViewportBoundary(); } /// @@ -108,4 +135,16 @@ public Vector2 WorldToScreen(Vector2 worldPosition) { return Vector2.Transform(worldPosition, GetTransformationMatrix()); } + + private void UpdateViewportBoundary() + { + var viewport = _graphicsDevice.Viewport; + var topLeft = ScreenToWorld(Vector2.Zero); + var bottomRight = ScreenToWorld(new Vector2(viewport.Width, viewport.Height)); + + var width = (int)MathF.Ceiling(bottomRight.X - topLeft.X); + var height = (int)MathF.Ceiling(bottomRight.Y - topLeft.Y); + + Viewport = new CameraViewport((int)topLeft.X, (int)topLeft.Y, width, height); + } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs b/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs new file mode 100644 index 0000000..2f4e758 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs @@ -0,0 +1,32 @@ +using System.Runtime.CompilerServices; + +namespace Hexecs.Benchmarks.Map.Utils; + +[method: MethodImpl(MethodImplOptions.AggressiveInlining)] +public readonly struct CameraViewport(int x, int y, int width, int height) : IEquatable +{ + public readonly int Left = x; + public readonly int Right = x + width; + public readonly int Top = y; + public readonly int Bottom = y + height; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Hidden(int x, int y, int width, int height) => !Visible(x, y, width, height); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Visible(int x, int y, int width, int height) => + x < Right && + Left < x + width + && y < Bottom && + Top < y + height; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(CameraViewport other) => Left == other.Left && + Right == other.Right && + Top == other.Top && + Bottom == other.Bottom; + + public override bool Equals(object? obj) => obj is CameraViewport other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(Left, Right, Top, Bottom); +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs b/src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs new file mode 100644 index 0000000..ebeac1b --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs @@ -0,0 +1,69 @@ +using System.Runtime.InteropServices; +using Hexecs.Benchmarks.Map.Terrains.ValueTypes; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Map.Utils; + +internal sealed class TextureStorage : IDisposable +{ + public const int TerrainTileSize = 16; // Размер одного тайла в пикселях (подставьте свой) + private const int TerrainSpacing = 1; // Между тайлами есть промежуток в 1 пиксель + + private readonly Texture2D _terrainAtlas; + private readonly Dictionary _terrainCache; + + private bool _disposed; + + public TextureStorage(GraphicsDevice graphicsDevice) + { + var contentRootPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Content"); + + _terrainAtlas = Texture2D.FromFile( + graphicsDevice, + Path.Combine(contentRootPath, "roguelikeSheet_transparent.png")); + + _terrainCache = new Dictionary(256); + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + + _terrainAtlas.Dispose(); + _terrainCache.Clear(); + } + + public ref readonly AtlasTexture GetTerrainTexture(TerrainType type, TerrainOverlay overlay) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var key = new TerrainKey(type, overlay); + ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(_terrainCache, key, out var exists); + if (exists) return ref value; + + var column = type switch + { + TerrainType.Ground => 6, + TerrainType.WaterRiver => 3, + TerrainType.UrbanConcrete => 7, + _ => 1 + }; + var row = type switch + { + TerrainType.Ground => 0, + TerrainType.WaterRiver => 1, + TerrainType.UrbanConcrete => 0, + _ => 1 + }; + + var x = column * (TerrainTileSize + TerrainSpacing); + var y = row * (TerrainTileSize + TerrainSpacing); + var sourceRect = new Rectangle(x, y, TerrainTileSize, TerrainTileSize); + + value = new AtlasTexture(_terrainAtlas, sourceRect); + return ref value; + } + + private readonly record struct TerrainKey(TerrainType Type, TerrainOverlay Overlay); +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj b/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj index 33c856a..ceb90bf 100644 --- a/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj +++ b/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Hexecs.Benchmarks.Noise/NoiseGame.cs b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs index 1b2af81..e1bf250 100644 --- a/src/Hexecs.Benchmarks.Noise/NoiseGame.cs +++ b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs @@ -30,7 +30,7 @@ public NoiseGame() PreferredBackBufferHeight = 720, GraphicsProfile = GraphicsProfile.HiDef, PreferMultiSampling = true, - SynchronizeWithVerticalRetrace = false, + SynchronizeWithVerticalRetrace = true, IsFullScreen = false, HardwareModeSwitch = false }; diff --git a/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs b/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs index e067fa1..4ae9711 100644 --- a/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs +++ b/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs @@ -6,59 +6,62 @@ namespace Hexecs.Actors; public static class ActorContextBuilderExtensions { - /// - /// Регистрирует метод создания обработчика команды указанного типа. - /// - /// - /// Использует рефлексию. - /// - public static ActorContextBuilder CreateCommandHandler< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | - DynamicallyAccessedMemberTypes.PublicConstructors)] - THandler> - (this ActorContextBuilder builder) - where THandler : class, ICommandHandler + extension(ActorContextBuilder builder) { - var commandType = PipelineUtils.GetCommandType(typeof(THandler)); - var commandId = CommandType.GetId(commandType); + /// + /// Регистрирует метод создания обработчика команды указанного типа. + /// + /// + /// Использует рефлексию. + /// + public ActorContextBuilder CreateCommandHandler< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicConstructors)] + THandler> + () + where THandler : class, ICommandHandler + { + var commandType = PipelineUtils.GetCommandType(typeof(THandler)); + var commandId = CommandType.GetId(commandType); - builder.InsertCommandHandlerEntry( - commandId, - commandType, - new ActorContextBuilder.Entry(static ctx => - (ICommandHandler)ctx.Activate(typeof(THandler)))); + builder.InsertCommandHandlerEntry( + commandId, + commandType, + new ActorContextBuilder.Entry(static ctx => + (ICommandHandler)ctx.Activate(typeof(THandler)))); - return builder; - } + return builder; + } - /// - /// Регистрирует метод создания системы отрисовки актёров. - /// - /// - /// Использует рефлексию. - /// - public static ActorContextBuilder CreateDrawSystem< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] - TSystem> - (this ActorContextBuilder builder) where TSystem : class, IDrawSystem - { - builder.CreateDrawSystem(static ctx => (IDrawSystem)ctx.Activate(typeof(TSystem))); - return builder; - } + /// + /// Регистрирует метод создания системы отрисовки актёров. + /// + /// + /// Использует рефлексию. + /// + public ActorContextBuilder CreateDrawSystem< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TSystem> + () where TSystem : class, IDrawSystem + { + builder.CreateDrawSystem(static ctx => (IDrawSystem)ctx.Activate(typeof(TSystem))); + return builder; + } - /// - /// Регистрирует метод создания системы обновления актёров. - /// - /// - /// Использует рефлексию. - /// - public static ActorContextBuilder CreateUpdateSystem< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] - TSystem> - (this ActorContextBuilder builder) where TSystem : class, IUpdateSystem - { - builder.CreateUpdateSystem(static ctx => (IUpdateSystem)ctx.Activate(typeof(TSystem))); - return builder; + /// + /// Регистрирует метод создания системы обновления актёров. + /// + /// + /// Использует рефлексию. + /// + public ActorContextBuilder CreateUpdateSystem< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TSystem> + () where TSystem : class, IUpdateSystem + { + builder.CreateUpdateSystem(static ctx => (IUpdateSystem)ctx.Activate(typeof(TSystem))); + return builder; + } } /// diff --git a/src/Hexecs/Actors/Systems/DrawSystem.cs b/src/Hexecs/Actors/Systems/DrawSystem.cs index 13b349a..ee47e11 100644 --- a/src/Hexecs/Actors/Systems/DrawSystem.cs +++ b/src/Hexecs/Actors/Systems/DrawSystem.cs @@ -56,118 +56,4 @@ private ContextLogger CreateLogger() => Context public virtual void Dispose() { } -} - -public abstract class DrawSystem : DrawSystem - where T1 : struct, IActorComponent -{ - private readonly ActorFilter _filter; - - protected DrawSystem(ActorContext context, Action? constraint = null) : base(context) - { - _filter = constraint == null - ? context.Filter() - : context.Filter(constraint); - } - - protected virtual void AfterDraw(in WorldTime time) - { - } - - protected virtual void BeforeDraw(in WorldTime time) - { - } - - public sealed override void Draw(in WorldTime time) - { - if (!Enabled) return; - - BeforeDraw(in time); - - foreach (var actor in _filter) - { - Draw(in actor, time); - } - - AfterDraw(in time); - } - - protected abstract void Draw(in ActorRef actor, in WorldTime time); -} - -public abstract class DrawSystem : DrawSystem - where T1 : struct, IActorComponent - where T2 : struct, IActorComponent -{ - private readonly ActorFilter _filter; - - protected DrawSystem(ActorContext context, Action? constraint = null) : base(context) - { - _filter = constraint == null - ? context.Filter() - : context.Filter(constraint); - } - - protected virtual void AfterDraw(in WorldTime time) - { - } - - protected virtual void BeforeDraw(in WorldTime time) - { - } - - public sealed override void Draw(in WorldTime time) - { - if (!Enabled) return; - - BeforeDraw(in time); - - foreach (var actor in _filter) - { - Draw(in actor, time); - } - - AfterDraw(in time); - } - - protected abstract void Draw(in ActorRef actor, in WorldTime time); -} - -public abstract class DrawSystem : DrawSystem - where T1 : struct, IActorComponent - where T2 : struct, IActorComponent - where T3 : struct, IActorComponent -{ - private readonly ActorFilter _filter; - - protected DrawSystem(ActorContext context, Action? constraint = null) : base(context) - { - _filter = constraint == null - ? context.Filter() - : context.Filter(constraint); - } - - protected virtual void AfterDraw(in WorldTime time) - { - } - - protected virtual void BeforeDraw(in WorldTime time) - { - } - - public sealed override void Draw(in WorldTime time) - { - if (!Enabled) return; - - BeforeDraw(in time); - - foreach (var actor in _filter) - { - Draw(in actor, time); - } - - AfterDraw(in time); - } - - protected abstract void Draw(in ActorRef actor, in WorldTime time); } \ No newline at end of file diff --git a/src/Hexecs/Actors/Systems/DrawSystem1.cs b/src/Hexecs/Actors/Systems/DrawSystem1.cs new file mode 100644 index 0000000..00aed6e --- /dev/null +++ b/src/Hexecs/Actors/Systems/DrawSystem1.cs @@ -0,0 +1,44 @@ +using Hexecs.Worlds; + +namespace Hexecs.Actors.Systems; + +public abstract class DrawSystem : DrawSystem + where T1 : struct, IActorComponent +{ + private readonly ActorFilter _filter; + + protected DrawSystem(ActorContext context, Action? constraint = null) : base(context) + { + _filter = constraint == null + ? context.Filter() + : context.Filter(constraint); + } + + protected virtual void AfterDraw(in WorldTime time) + { + } + + /// + /// Метод запускается до полного обновления + /// + /// Время мира + /// Если возвращает false, то обновление не происходит + protected virtual bool BeforeDraw(in WorldTime time) => true; + + public sealed override void Draw(in WorldTime time) + { + if (!Enabled) return; + + if (BeforeDraw(in time)) + { + foreach (var actor in _filter) + { + Draw(in actor, time); + } + + AfterDraw(in time); + } + } + + protected abstract void Draw(in ActorRef actor, in WorldTime time); +} \ No newline at end of file diff --git a/src/Hexecs/Actors/Systems/DrawSystem2.cs b/src/Hexecs/Actors/Systems/DrawSystem2.cs new file mode 100644 index 0000000..e0c3a43 --- /dev/null +++ b/src/Hexecs/Actors/Systems/DrawSystem2.cs @@ -0,0 +1,45 @@ +using Hexecs.Worlds; + +namespace Hexecs.Actors.Systems; + +public abstract class DrawSystem : DrawSystem + where T1 : struct, IActorComponent + where T2 : struct, IActorComponent +{ + private readonly ActorFilter _filter; + + protected DrawSystem(ActorContext context, Action? constraint = null) : base(context) + { + _filter = constraint == null + ? context.Filter() + : context.Filter(constraint); + } + + protected virtual void AfterDraw(in WorldTime time) + { + } + + /// + /// Метод запускается до полного обновления + /// + /// Время мира + /// Если возвращает false, то обновление не происходит + protected virtual bool BeforeDraw(in WorldTime time) => true; + + public sealed override void Draw(in WorldTime time) + { + if (!Enabled) return; + + if (BeforeDraw(in time)) + { + foreach (var actor in _filter) + { + Draw(in actor, time); + } + + AfterDraw(in time); + } + } + + protected abstract void Draw(in ActorRef actor, in WorldTime time); +} \ No newline at end of file diff --git a/src/Hexecs/Actors/Systems/DrawSystem3.cs b/src/Hexecs/Actors/Systems/DrawSystem3.cs new file mode 100644 index 0000000..5655877 --- /dev/null +++ b/src/Hexecs/Actors/Systems/DrawSystem3.cs @@ -0,0 +1,46 @@ +using Hexecs.Worlds; + +namespace Hexecs.Actors.Systems; + +public abstract class DrawSystem : DrawSystem + where T1 : struct, IActorComponent + where T2 : struct, IActorComponent + where T3 : struct, IActorComponent +{ + private readonly ActorFilter _filter; + + protected DrawSystem(ActorContext context, Action? constraint = null) : base(context) + { + _filter = constraint == null + ? context.Filter() + : context.Filter(constraint); + } + + protected virtual void AfterDraw(in WorldTime time) + { + } + + /// + /// Метод запускается до полного обновления + /// + /// Время мира + /// Если возвращает false, то обновление не происходит + protected virtual bool BeforeDraw(in WorldTime time) => true; + + public sealed override void Draw(in WorldTime time) + { + if (!Enabled) return; + + if (BeforeDraw(in time)) + { + foreach (var actor in _filter) + { + Draw(in actor, time); + } + + AfterDraw(in time); + } + } + + protected abstract void Draw(in ActorRef actor, in WorldTime time); +} \ No newline at end of file diff --git a/src/Hexecs/Actors/Systems/UpdateSystem1.cs b/src/Hexecs/Actors/Systems/UpdateSystem1.cs index e6431a5..519f970 100644 --- a/src/Hexecs/Actors/Systems/UpdateSystem1.cs +++ b/src/Hexecs/Actors/Systems/UpdateSystem1.cs @@ -35,9 +35,12 @@ protected virtual void AfterUpdate(in WorldTime time) { } - protected virtual void BeforeUpdate(in WorldTime time) - { - } + /// + /// Метод запускается до полного обновления + /// + /// Время мира + /// Если возвращает false, то обновление не происходит + protected virtual bool BeforeUpdate(in WorldTime time) => true; public sealed override void Update(in WorldTime time) { @@ -46,7 +49,7 @@ public sealed override void Update(in WorldTime time) var length = Filter.Length; if (length > 0) { - BeforeUpdate(in time); + if (!BeforeUpdate(in time)) return; if (_parallelWorker == null) { diff --git a/src/Hexecs/Actors/Systems/UpdateSystem2.cs b/src/Hexecs/Actors/Systems/UpdateSystem2.cs index 4ace729..b97e396 100644 --- a/src/Hexecs/Actors/Systems/UpdateSystem2.cs +++ b/src/Hexecs/Actors/Systems/UpdateSystem2.cs @@ -36,9 +36,12 @@ protected virtual void AfterUpdate(in WorldTime time) { } - protected virtual void BeforeUpdate(in WorldTime time) - { - } + /// + /// Метод запускается до полного обновления + /// + /// Время мира + /// Если возвращает false, то обновление не происходит + protected virtual bool BeforeUpdate(in WorldTime time) => true; public sealed override void Update(in WorldTime time) { diff --git a/src/Hexecs/Actors/Systems/UpdateSystem3.cs b/src/Hexecs/Actors/Systems/UpdateSystem3.cs index 6376adf..7e34e0b 100644 --- a/src/Hexecs/Actors/Systems/UpdateSystem3.cs +++ b/src/Hexecs/Actors/Systems/UpdateSystem3.cs @@ -38,9 +38,12 @@ protected virtual void AfterUpdate(in WorldTime time) { } - protected virtual void BeforeUpdate(in WorldTime time) - { - } + /// + /// Метод запускается до полного обновления + /// + /// Время мира + /// Если возвращает false, то обновление не происходит + protected virtual bool BeforeUpdate(in WorldTime time) => true; public sealed override void Update(in WorldTime time) { diff --git a/src/Hexecs/Hexecs.csproj b/src/Hexecs/Hexecs.csproj index 1799564..9e3c77c 100644 --- a/src/Hexecs/Hexecs.csproj +++ b/src/Hexecs/Hexecs.csproj @@ -240,6 +240,9 @@ ActorFilter1.cs + + WorldBuilder.cs + diff --git a/src/Hexecs/Worlds/WorldBuilder.Extensions.cs b/src/Hexecs/Worlds/WorldBuilder.Extensions.cs new file mode 100644 index 0000000..c551887 --- /dev/null +++ b/src/Hexecs/Worlds/WorldBuilder.Extensions.cs @@ -0,0 +1,14 @@ +using Hexecs.Dependencies; + +namespace Hexecs.Worlds; + +public static class WorldBuilderExtensions +{ + public static WorldBuilder UseSingleton< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicConstructors)] + TService>(this WorldBuilder builder) where TService : class + { + return builder.UseSingleton(ctx => (TService)ctx.Activate(typeof(TService))); + } +} \ No newline at end of file From 07607e62391f0442271d7d833362262f4cc58d87 Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Thu, 25 Dec 2025 13:44:01 +0300 Subject: [PATCH 08/14] 0.4.3 map bench --- .../BenchmarkCounter.cs | 90 ++++++++ src/Hexecs.Benchmarks.City/CityGame.cs | 10 +- .../Common/Positions/PositionBuilder.cs | 8 +- .../Common/Positions/PositionsInstaller.cs | 2 +- .../Common/Visibles/VisibleSystem.cs | 10 +- .../Content/Content.mgcb | 13 ++ .../Content/DebugFont.spritefont | 16 ++ .../Content/DebugFont.xnb | Bin 0 -> 21529 bytes ...heet_transparent.png => terrain_atlas.png} | Bin .../Content/terrain_atlas.xnb | Bin 0 -> 2036757 bytes src/Hexecs.Benchmarks.City/FpsCounter.cs | 65 ------ .../Hexecs.Benchmarks.City.csproj | 8 +- .../Terrains/TerrainDrawSystem.cs | 11 +- .../Terrains/TerrainInstaller.cs | 10 +- .../Terrains/TerrainSettings.cs | 15 +- .../Terrains/TerrainSpriteAtlas.cs | 37 +++ src/Hexecs.Benchmarks.City/Utils/Camera.cs | 134 ++++++----- .../Utils/CameraViewport.cs | 17 +- .../{AtlasTexture.cs => Sprites/Sprite.cs} | 5 +- .../Utils/Sprites/SpriteAtlas.cs | 63 +++++ .../Utils/TextureStorage.cs | 69 ------ .../BenchmarkCounter.cs | 90 ++++++++ .../Components/CircleColor.cs | 1 - .../Components/Position.cs | 1 - .../Components/Velocity.cs | 1 - .../Content/DebugFont.spritefont | 16 ++ .../Content/DebugFont.xnb | Bin 0 -> 21529 bytes src/Hexecs.Benchmarks.Noise/FpsCounter.cs | 67 ------ .../Hexecs.Benchmarks.Noise.csproj | 3 + src/Hexecs.Benchmarks.Noise/NoiseGame.cs | 13 +- .../Systems/InstanceData.cs | 1 - .../Systems/MovementSystem.cs | 1 - .../Systems/RenderSystem.cs | 1 - src/Hexecs.Benchmarks.Noise/Usings.cs | 3 +- src/Hexecs.Tests/Actors/ActorTestFixture.cs | 4 +- src/Hexecs/Actors/ActorContext.Entry.cs | 12 +- .../Actors/ActorContextBuilder.Extensions.cs | 15 ++ src/Hexecs/Actors/ActorContextBuilder.cs | 11 - .../Serializations/JsonWriterExtensions.cs | 218 +++++++++--------- 39 files changed, 612 insertions(+), 429 deletions(-) create mode 100644 src/Hexecs.Benchmarks.City/BenchmarkCounter.cs create mode 100644 src/Hexecs.Benchmarks.City/Content/Content.mgcb create mode 100644 src/Hexecs.Benchmarks.City/Content/DebugFont.spritefont create mode 100644 src/Hexecs.Benchmarks.City/Content/DebugFont.xnb rename src/Hexecs.Benchmarks.City/Content/{roguelikeSheet_transparent.png => terrain_atlas.png} (100%) create mode 100644 src/Hexecs.Benchmarks.City/Content/terrain_atlas.xnb delete mode 100644 src/Hexecs.Benchmarks.City/FpsCounter.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs rename src/Hexecs.Benchmarks.City/Utils/{AtlasTexture.cs => Sprites/Sprite.cs} (63%) create mode 100644 src/Hexecs.Benchmarks.City/Utils/Sprites/SpriteAtlas.cs delete mode 100644 src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs create mode 100644 src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs create mode 100644 src/Hexecs.Benchmarks.Noise/Content/DebugFont.spritefont create mode 100644 src/Hexecs.Benchmarks.Noise/Content/DebugFont.xnb delete mode 100644 src/Hexecs.Benchmarks.Noise/FpsCounter.cs diff --git a/src/Hexecs.Benchmarks.City/BenchmarkCounter.cs b/src/Hexecs.Benchmarks.City/BenchmarkCounter.cs new file mode 100644 index 0000000..cc4b84d --- /dev/null +++ b/src/Hexecs.Benchmarks.City/BenchmarkCounter.cs @@ -0,0 +1,90 @@ +using System.Globalization; +using System.Text; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Map; + +internal sealed class BenchmarkCounter +{ + private readonly Func _countResolver; + private readonly int[] _fpsHistory; + + private double _frameTime; + private int _fps; + private int _frameCount; + private double _fpsTimer; + + private int _historyIndex; + private bool _historyFull; + private double _avgFps; + private long _historySum; + + private readonly SpriteFont _font; + private readonly SpriteBatch _spriteBatch; + + // Используем StringBuilder как буфер + private readonly StringBuilder _stringBuilder = new(128); + private readonly Vector2 _textPos = new(10, 10); + private readonly Vector2 _shadowPos = new(11, 11); + + public BenchmarkCounter(Func countResolver, ContentManager contentManager, GraphicsDevice graphicsDevice) + { + _countResolver = countResolver; + _fpsHistory = new int[60]; + _font = contentManager.Load("DebugFont"); + _spriteBatch = new SpriteBatch(graphicsDevice); + } + + public void Draw(GameTime gameTime) + { + _frameCount++; + + _spriteBatch.Begin(); + + _spriteBatch.DrawString(_font, _stringBuilder, _shadowPos, Color.Black); + _spriteBatch.DrawString(_font, _stringBuilder, _textPos, Color.Yellow); + + _spriteBatch.End(); + } + + public void Update(GameTime gameTime) + { + var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; + _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; + _fpsTimer += elapsedSeconds; + + + if (_fpsTimer >= 1.0) + { + _fps = _frameCount; + + _historySum -= _fpsHistory[_historyIndex]; + _fpsHistory[_historyIndex] = _fps; + _historySum += _fps; + + _historyIndex = (_historyIndex + 1) % 60; + if (_historyIndex == 0) _historyFull = true; + + var historyCount = _historyFull ? 60 : _historyIndex; + _avgFps = (double)_historySum / historyCount; + + var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; + var count = _countResolver(); + + // Очищаем буфер и записываем новые данные без создания строк + var culture = CultureInfo.InvariantCulture; + + _stringBuilder.Clear(); + _stringBuilder + .Append($"{_fps} FPS") + .Append(culture, $" | Avg:{_avgFps:F1} fps") + .Append(culture, $" | Entities:{count:N0}") + .Append(culture, $" | Frame time:{_frameTime:F1}ms") + .Append(culture, $" | Alloc:{alloc:F3}mb"); + + _frameCount = 0; + _fpsTimer = 0; + } + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/CityGame.cs b/src/Hexecs.Benchmarks.City/CityGame.cs index d3156f7..928477d 100644 --- a/src/Hexecs.Benchmarks.City/CityGame.cs +++ b/src/Hexecs.Benchmarks.City/CityGame.cs @@ -11,9 +11,9 @@ namespace Hexecs.Benchmarks.Map; internal sealed class CityGame : Game { + private BenchmarkCounter _benchmarkCounter = null!; private Camera _camera = null!; private readonly GraphicsDeviceManager _graphics; - private FpsCounter _fpsCounter = null!; private World _world = null!; public CityGame() @@ -38,6 +38,7 @@ public CityGame() _graphics.ApplyChanges(); IsFixedTimeStep = false; + Content.RootDirectory = "Content"; } protected override void Initialize() @@ -46,7 +47,6 @@ protected override void Initialize() _camera = new Camera(GraphicsDevice); _world = new WorldBuilder() - .UseSingleton() .UseDefaultParallelWorker(Math.Min(6, Environment.ProcessorCount)) .UseSingleton(Content) .UseSingleton(GraphicsDevice) @@ -61,7 +61,7 @@ protected override void Initialize() _world.Actors.Execute(new GenerateTerrainCommand()); - _fpsCounter = new FpsCounter(() => _world.Actors.Length, Window); + _benchmarkCounter = new BenchmarkCounter(() => _world.Actors.Length, Content, GraphicsDevice); base.Initialize(); } @@ -80,8 +80,8 @@ protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.White); - _fpsCounter.Draw(gameTime); _world.Draw(gameTime.ElapsedGameTime, gameTime.TotalGameTime); + _benchmarkCounter.Draw(gameTime); base.Draw(gameTime); } @@ -94,7 +94,7 @@ protected override void Update(GameTime gameTime) } _camera.Update(gameTime); - + _benchmarkCounter.Update(gameTime); _world.Update(gameTime.ElapsedGameTime, gameTime.TotalGameTime); base.Update(gameTime); diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs b/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs index 0e69fd3..4fe897a 100644 --- a/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs +++ b/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs @@ -1,10 +1,10 @@ -using Hexecs.Benchmarks.Map.Utils; +using Hexecs.Benchmarks.Map.Terrains; namespace Hexecs.Benchmarks.Map.Common.Positions; -internal sealed class PositionBuilder : IActorBuilder +internal sealed class PositionBuilder(TerrainSettings terrainSettings) : IActorBuilder { - private const int TileSize = TextureStorage.TerrainTileSize; + private readonly int _terrainTileSize = terrainSettings.TileSize; public void Build(in Actor actor, in AssetRef asset, Args args) { @@ -12,7 +12,7 @@ public void Build(in Actor actor, in AssetRef asset, Args args) actor.Add(new Position { Grid = grid, - World = new Point(grid.X * TileSize, grid.Y * TileSize) + World = new Point(grid.X * _terrainTileSize, grid.Y * _terrainTileSize) }); } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs b/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs index 06080ec..a9b0dee 100644 --- a/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs +++ b/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs @@ -9,7 +9,7 @@ public static ActorContextBuilder AddPositions(this ActorContextBuilder builder) { var terrainSettings = builder.World.GetRequiredService(); - builder.AddBuilder(); + builder.CreateBuilder(); builder .ConfigureComponentPool(terrain => terrain diff --git a/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs b/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs index 88fcbb6..32cc10e 100644 --- a/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs +++ b/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs @@ -1,6 +1,8 @@ using Hexecs.Actors.Systems; 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; @@ -8,16 +10,16 @@ namespace Hexecs.Benchmarks.Map.Common.Visibles; internal sealed class VisibleSystem : UpdateSystem { - private const int TileSize = TextureStorage.TerrainTileSize; - private readonly Camera _camera; + private readonly int _tileSize; private CameraViewport _currentViewport; - public VisibleSystem(ActorContext context, Camera camera, IParallelWorker parallelWorker) + public VisibleSystem(ActorContext context, Camera camera, IParallelWorker parallelWorker, TerrainSettings settings) : base(context, parallelWorker: parallelWorker) { _camera = camera; + _tileSize = settings.TileSize; } protected override bool BeforeUpdate(in WorldTime time) @@ -35,7 +37,7 @@ protected override void Update(in ActorRef actor, in WorldTime time) { ref readonly var position = ref actor.Component1.World; - if (_currentViewport.Visible(position.X, position.Y, TileSize, TileSize)) + if (_currentViewport.Visible(position.X, position.Y, _tileSize, _tileSize)) { actor.TryAdd(new Visible()); } diff --git a/src/Hexecs.Benchmarks.City/Content/Content.mgcb b/src/Hexecs.Benchmarks.City/Content/Content.mgcb new file mode 100644 index 0000000..ad2226a --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Content/Content.mgcb @@ -0,0 +1,13 @@ +#----------------------------- Global Properties ----------------------------# +/outputDir:bin +/intermediateDir:obj +/platform:DesktopGL +/config: +/profile:Reach +/compress:False + +#---------------------------------- References -------------------------------# + +#---------------------------------- Content ----------------------------------# +/build:DebugFont.spritefont +/build:terrain_atlas.png \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Content/DebugFont.spritefont b/src/Hexecs.Benchmarks.City/Content/DebugFont.spritefont new file mode 100644 index 0000000..ba84b52 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Content/DebugFont.spritefont @@ -0,0 +1,16 @@ + + + + Consolas + 10 + 0 + true + + + + + ~ + + + + \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Content/DebugFont.xnb b/src/Hexecs.Benchmarks.City/Content/DebugFont.xnb new file mode 100644 index 0000000000000000000000000000000000000000..47451f4677c111d6f0012d9d4be29b774995dedd GIT binary patch literal 21529 zcmeHOU2I&(b)MyN>1b(kw@HQ2t%}IWQg)IxE!mRAI8=2bDUO}k%8gA*DotmX64$nB znq(--Klv#(h+gv0c7aBzQ3S!f?8_o3w@CXIpzAbm1rog&=|dm3NYI!1;ReA11z&Ez z@0^+4t4lGG02%ee&SLh?ch8wKXU;iuX71(k!r4LZq`n$)vj)BF0YmkhF34HUcU5|+S~K%uhrI0tX*AQ zH7JkXZv6S$TNL!z86BQ|ew4Xgr)Mu;aq3o&y!hf?+|So8Z7r|8vRb=`b9(8eJDOF0 z=)tUhc$c$!;q5D1wby4(zq$;cI1p}Leq(v72Ip5tSvp*ORzR5KyeV@nzI~7=szF9 zbsJ>fLjQ@;vXJBpSW7<4QvmScS&nbPt@Q5}XLrRrWEK2+nl0{LPj)ekqh2ed{GVM|`CHD!<`^1roH1p{6?626 z;qU5BoO1?>IvIl%`8R6)8=1h62*KDC3 z%6$I&6Xs#xTgE4__(aGU;O}&Ts`oxI+z;LPQU8_8H#(Wg4itv}ZxQcz>wnYi#qh@><#*4u+mWm_>VWZRAk0il99PkZa}|RgY{k?2 z&avT&kFQ1O%Yy#niSPrK+tzys_Np`57!WM5S2ly;tnvYmEg7?8TAaMl?>mVLj3p5C zppMo~>@|*0rTrvY|-R_^%pM+9pX#97}qYHg%N#OL2ua z5FWtSje*07BS^vgj9C$~%U>|3&tDKL{cnhCUH`*_{*WR4eY{}K!e7_fQ!9z@38xrO9NqS(+s7#u-fT^_ruSJ1m3dlXnh zj<|Ts{%L1&~3}~WXWKCjLsJQ zYM^|`lhuSB))Hqkvi?l)o1Zd%xyK59WCSLtU`cQ5c>8jX@H{TdmHo%Z zDbE$^i?U;pft#2i>^V2~H@a!4y{BOyoUQo*FZ?|cU6QU$Z#if>%zs952EFcb9(fX2?f6>ND{lL0UiOEg2u~DyHew-RnPwV&Qv! zC=chu-TqJI*F0>8$5G+WX2(nL{DL|#j;(FC7Z#!r7KKgYxOJ^wCqF;LS(HK^A6u}* zM~=Mvu6keN3GUzJ|0cAJ@?2HO_hUylR9l9I=!=!2A7a{D{f}>8_`eNCSJXeu=8RU- zC+4i9DdxA&o}}|}XHc|1_6N~M=yMxFb}9>=Y&Ubwlr+-ehWB(G=+jZVnek6JZ<{^vCN@T%jFj37nhc{$P#lBEX#hTL|+Xc1_YpM)tFZ7GRbT{*@OD zuvfL%*eQr@#GW4?fjuMTX8!<9^oo!pMBYLW70Ind&yzh2wH;E&I6jy8Yn%^!K!DE2 zahz{?h9f;Jjp2Mir1>s8HhM_?Nr~c5z(IbN^=V0Ck>>;MoIU?M9##9#;{*F{M|RQX zE%|T!;@|&G&nq%Vc|Red=XFD7hcU1r4;q!4}5%$;k+CQE>Mt6lRo-h!9v;X@} zSZTHLEovRd@5H}vr{zgDZNev@ce~wA6gC&~i|7G-?H_;od-|R_5ofa_BVomEN4<*Z z|IUA|e4p}YL(&nhJpWrGXA;c6Q~ytov+3hp2rzLt~meWX#yc; z$=@h8I%wkoR`<6?yEyH+b$r9KsJb8gbE5^un=2}>Who9;1pQZ;edmbu4H({E?8}Vk z>eu+d@}J+yQCq# z`*G~f(^FIZ^EA3Z9~82Cd&~Pup1$}29r>b59xcl6*wGjH2LteYr_UTXf3)$$q2l^< z3vBZoDvl2n=fr|H$&+UV-hq59NGQME*XO>oi; z$Eu28G(=()W@h=>Mv+%Oq(((EAFC|Anq! zH^m2TDLu*~LSPNzqf<18FOFZJ#y^_#YELRC_RKL7Q6l25`-_p>0oJQhN%!PjgiTZM zsed95B0~Bbb1ES2Rj`Q&{fDQCwWvHs21< z@cdwZ`5yKc{%MJ)CDOC_djkJ@pcBA(hkq|*Fko~Uzg-2t_mG?yIlpb$-qF8(|(_W|Ig;8*uQ10;$qZcqn~e>PuzOrU9LA8!WCU_9+5t1vvm(~NDV2k-ofh3Nln8m%x`_7iEAJ2S zY{T6_=!X!mqPxTT{O0V6{tLXX(uBWGsHb1S*NJ_(XZkyVLwVk^dL7zhXpf`?hVhWJ zhUBB0ZVc^nlR56?{wRWn_8riY zkhI1yKMdnx7!OGc4C5hbjbVNm#=|fkk`@@oL(&?<{4k7%VLT))FpP(!HHP_N7!M!D zc*s_u)d^JoXn{YsVdbx;i1BMA`H#)Va6OL7-!bss8Prdr-iP`T)Bu(Ji1CXh^9(Bg z)qt44@LWM9oxH@yQ0Gu7i}e$zq*MO=sQj5k60;A-A44S%X)~yl&&uEVkUr|_E4Xq_ zo)^Ds6wI;klN$2^DrK-Qe=qhNYH~i)z@I`TojP-@Bo2T%CiNkmV<+iP11ISmi#GN0 zIA#)mEQJrG@RPv&=^*vznEbs7<#WCqpFbDFPkn^OF-YV5*=8S($2qVs@BJAn{Y(4M zw%$&ZbwBE-QAsCe<-a+97S+#p7446s`uSWW9e#UE({`nN9KmG*O_g_OhIo>nC(|<`!|0OZ? zPug=4_(4=Z{-qSY2u%BOd{){!$$u1>`qBUN7k{`#O#e`~Cmr^5#wKHw}zcJlf9BkNr3;Uj$y~*$OeFN>}_?$caN8MQ;LFGN#fqZPU(s#54V~3c$q|^V& zd>~05_xg*ZNPxmlRAMj80sau3{b_)``rl6e?dVKq$?-W>5_7^yoTjJcr|0uw!(5Qb z`J{2$9t?;if0~}2e;R+-=0DWuW`CyrbvvB4pHC`#tm7XXlb&Uwi(A`Gs@mFZ{*gi%Tzky;5Dicxk2f@++@i z{>#@^Ute3__{Qdyt*dXm`PSRt{MNU>^H)Bu?&Da4(han2d3Eg$uJx0^D4w2fn=gRp zUxmda+k45m6PbJUP2TSYZcJSMzuHNClI;$3yPe&VJW0DH+ub{uV^U7CeYbt@g#S)> z?pFV#%sc737f)von_Jq@*>SJ2_TowMclMB<1j^do>o!89+cgt5F`|Auu`F7g%BaIV^HI6DK$H~8>u-+@`2S7 znrL$HK?4>Vj2QPxQV4ETs8N#Op9C8zE$OCgNvJ2=QlSU?@U6eK`rF^N#v1d_eLwel z*R$U5d0?(F=9pt#bIfti`+nxN?)P2mJO9()`zQXj&-|PJ#&7-1Z~1rs;D7NwU;MuR z>ihn)@Bi#~e&LUO_CNmOAN$_V|JUF5#sBiN|Izn-;rl=Th426DcYOZOe*d5O;^+VU z-}mjG|6_mR^I!Z7pWnhCfB#?o%xAvoUwPg6g@66Ge&&0A=^sDc(!jhWo>(w`>=(b~ z>6Z4=Fn<2e{o$uu8uj>@zwza#TN)Vu?Em;XPq(zI^7t#?@!OxW{rq41(Wh)VhMr^S zwutR()~|y^e$N_&h4`sKBEFj|x00@TkD^R^TuF=l|i;PMc-*lRx>BPdjaf z!PqK~RXpRXJa#Z1KG#&h^W!dB=ik|8cr$|p%z=YQ8zwwhOtq1SOR{=m_~jmxS?${?H$K>e`Wdbn)1Z zV7$+oGWXyA>5sPBXs^IWzxvg$ZZ&*Iwb{vKl!KT%xy*79=OM4D09=)G{npq9Pnf3t1`zj2X+0^Pn$BwjJs08i@*94-+ZVMlR3bJzx&G{9csiR2E6!Nf9^|% z8Zls8!ELX==y+n5BiZ#=&@e9u;_0;8B6I72wP}YVzaJxaZ~B_|6!)@A%DpuENMV z`aL`1L5^ok)%AG^7;@Tg`?ha;>YC%V2HDrAi^t}8##G&!j<}`cH7@)5oW&rX^L(y@ zerLls=T>Jw|GmHW_ujyu-`VgrE+yFPt(>>!K_8!49@HV{t$EPLXOu_HiMeV^pOs!? z?XTi;+?XoUr|S5ot;X7~KC8ZQDQ7Wo#ymsHA*-xDiFpMEcoiq-tUA7#gZeEP#AXxo zh?E=uFCYE3r)>3Ia*m$UM+j<6zYLsA8dRG#APZ(C6y)CDe zZ&c~{=>B<07(Bm|3(jvulU>Uq+BjuS(ypDQrHsyNPBB{|g`M!D2j`yGaBMdHUC*>Vo;+LUnI z%6;j}eeqlQz;WxDoW9%_jFk@@w{lC^eG#=)q#^fS(U$gRs6jLtr?o#Drs5BeE?T={6nS6NHtkhA`bAA`@F&Z9Js z?Ca|odE`BdUh`OugHi9bim@68qkiO}`TdHnz4~{3ecuPhANeDHfm>ISj(e%fKLo-8DTg#zUrArVt@nBlMgE8kSQHQ5<{$?D=@&SILuWWIdwUVa;fq3 zM;Pg=k2d<`XO7s>omaWyzWA+tR@@himCua(63=Jm)A7|KHt>)5cfMd${>Z+Ei^>H% z-kLTrvVTV3!7v|kBgdLI7}eHs;OdioFq}u(abU+f35NVqnH*HxOoCB4WXFN4Pxfmb z=Ad-`+Q*I!35NHlGUq_ouB_u=toZM09E|+zoIghA4sqT$2Hg0^fBeS}HDb&k$$fC9 zjWD?8BfM^`%c1iVvyP4O&>!h{e%ew`jMURdoNA+=G3uS4-|IQ1|4gQT)n{=zR^zgt zelz{g{O}JyjoK_O$8z4Pr{7GkaoL~6<=AK)*wZg{&*+@X(RZ3>c+c#K;n=RcHwi0}y zB=_s|5e9bN>sl|@U#0cHmza88#9(Z7T{;Z+2=iwC=22}m2lMg1Nq_H~YV$rZRtfe! z`#a1z9ejWG4Rxw6?9bY|S zW1b`aoi7;4583l_f9M_V4OgsZF?HTC{f zR{pD9S;xUx@!!=r82NeM+}2!f$>S=1JUhd6V}8H62Kt@f%yzFBN7q!>aaH^-&S-w) zk*n*og8^@xUC$Y>G?y##xB}zsS~z#Z0nRkf>@Nw!z2$kB{h?lEj@A7}Uw?GY)pFw0 ztDpPo9`nfcvNrD>*QpITs)w~mo>6sc(qX_KbDo@A-8}J~VVKL&>ypR7$wOap zvdt1es%56tMt0djlC|8D`$Q) z-fKK^<~QQK8IL~ls~mFHhZojyu&eE8AB>J4Cof%LUX{mcp3G^NALrV3bk@?n6Fy=C zW5qjH<6u;td9;5d4>_~a{J`-3RMtGSE9*EIEB?D02gA<}C-r%cuW>0m44ym2r96U> zajAD0SMg)N|8}L{Z`Q-_tlfD0?iGVIk(=j~)tc%$j@HDpk#SX;i)SSzd97l_x&mXw zDb3>uKV~o2c62Q@p8hU|YZ+mT)!Gx!qp@5IzQoo2fNf;2 z*F_9D5L^em#7O>wJu!?&?&gs(c|EZ2=20=g+2N#~E#-`rBVK{Ao69H%;`!|4GQx13 z$mGLd)-}JL?d?DFy61JNo-@B0?@gSU--!2SJo?CQ#Rpg8V2^w{KHd3(A#Y^w)_2EU zOtsDOsJ^5AE`}VrdxxBBaZN`h7)Q8p=L?4X@~i}-^30?CQ66w$@=7kOt~75jxKFeF z(_j19r))Wfo@3}>eA{3C<4@VDPmZD2aWMRFF0Rk?Q6FF9QjRcq27#qSKZ}t$br?tZ zF}}211G?WgelPAwViM=2VR%M4Qw!^Iq$bQ)Yg%#esjZ$_jH%LGYCQd23^*gcBa9KJ z%v`R>;|h$kYstBDZ&*j`(~S{JEw01>3+}tqoVVsdp9VkvfiM5wS5j^aKdu~^wTY4I z$u+O^jK2Z{K6FbXhg;&4kFhq^@197Rdisp&fjz$iI^Fpa1O1lfHMtc796sQ%-pt=U zQf>IYj(YP?Y77@nfBA8><@HFEd&q+v>OR1hE%ye#jCJc^ESoh=8#?x@F~AeIo6j}m zSjMoP?5CeO?_hw#SxY{c{IJtGUt+IZ@d}LHTt+z%&u1r>5eBi@ULzj+bH;lUY34WM zy@@gN8}Z(ZM<4l(xZ|NiH9~g2-_B;Go4tj)Ri@zJAzjAjinOl8_oNIASME0Kl(;njf25`l&xaq78nGNPM%6^2nH62lm}ODh4>7o8*igFV?0HFr#2&fxhoo%BZ-Bj3t<>5nivU%6q* zBm9!NGWLuPh8&W;dn-R4<*-^;UCYRSmmk;vx5+iGBzee%>;K!NeI>!*3`nb-RQpPT z;Tfm?zfIa#(mX2X?dH};sSgh{WITqhN6qN!oW`)F|@5P zsIBjYlOtW<&2K!?wRlgatmmGLOWk*_8LJM) zbMj;6bMBPydM(y5xd*oFd)6fvnqyw4D>1;@d3TJD^VU4*!+-O;fWGgJuY7-8b8vrY zw`Mb`FVc9q(=IPgzvRbhKcb64KIX@Dx49483)=1HS`q`h&;OBhzQoWrtK(=)oGUQA zK4Rq>oWI(f-&m#BIQ_jw)t1-8c;r3gLC#)xYe1jeXx$hw-8zWRwTsu;YF(`bF>)Z{ z5<~8zEisH&c5_ynzT#wmH;;_5CZ3(-jPlq{>O+k2$PtDdIk83wJ^VP0?~m#4 zaAd!m{7&1E#N;)-7mUnFJ#(vh>$4-pIg4>N-nh|q*~#Y$jI*3FYkNf=S74l7i*x50 zmg|!J)N_njYH>9N^R3Ytno|2;2v^4bzjc<#N_?=k}$5|wpUnJR(cmd%727) z6eIn`Ok3*RIJT9~YFu2oqP#WvXw~(p+%qxQPsX{ovOmHwpOoNc%dwHJ|7cwMuAlz9 zvg5n6*v`M>yEBZ=zw+H#JY&_r@=Be8G6|<8OJAD^rgZ|6PrP(VhQ}*uXf$ zi8JP^JivK;9u;^W72rG8vpV|O9K<<`aZ9|hu0wewmn$&jmCmo+;l|eeiaf5sII|Y# zMY^7qtTDB}if6pW)c*UzFgNl!+A`;(vHIU52Hf^c%e9<|Cnhnh(NTFdhIqSq=-)|r z?@sGtvF+xdUx%|ZfBJM758JgBfG5IV<*jnrPU=HUW--F3{5a>S7>V~G>mA>nt*7(v z`0fm&^RIk&7SGsG{}tY2{852N1s)Z6RNzs8M+F`gcvRp~fky=%6?jzOQGu6Gf$n|o z5gRo);@|m#;k#4*-tEl4(REcGU4GRz`nS1QL$1X&9hG3L-Vd+F!8r1+xbp=g-vyu1 z!8p49>JJ8g&sJOg|A}YgVDR@#wN;GSI2e!5eOKVfd&ncc_r2;zcAusS_-^W~q*iBR zjk^lt({$Y)YrU-k?!A?q&64kBXZj4|G4}E*z}Ye1>vr#rwY}^2$Mqb$>UURT6;sDM zj9YBJ4@U0d)EM6fBX==sjPHYyyBHZ$x!=(~a~_SU{$R+7c=kQ`@yqAO{O+;WTq#Gl zuVjC4Kl^-AR=%!Xnd4x1@8thEo<8U~hMwbKJU;hZfmc88`isA`_5TDN@x9*_KQepH z3arlMt8sFjoym8`z+j&Ioy#LSvG;^QE__zCtK+Yu-u$!rtom<8zwWJK>iE0DNZirS zN~ce!n=50tVu-bpvpiO@j*l>Q_c=Hn2KSdg>$;Nob{G%a%dCLsOS!LS$WH1*Ojcur z;a*Fu5e9hTr2j~-IhfBC7-F4~mFwJ&~xmi@ng>)qw@nbT7B*r;|S+H;=8KJ zWBk)m0rvjx8NK>>&vEYd-vl};J+oht$Gu>%bI#;qJ>7XP3BxtXY4hJjPTg_+Q(6xs zei@hg3d6JguI#SMZmfP4gLPr!-zv!5{kIFMt&0c4doTUH2dm9{jkx*&f2Y;B?1R74 zGG?TYa7O-}{e59DZ|1X;M;CJ|hUZIpYduS5U!TfNM;Pww9SqiyILtL_;ME+g!z~!Z zW)t&>tXy{|w#Il~zjuDz{h!6Y17_XoXC)Z&CZ0Q2y+5Ai-R0V?rNh|$`Re>Xn_OQ? z{~zSEp_7OFH~-HjZ7CV=e87-z!Zp=9$#~}jhWs)A&n9gt{ePa)h7Lxqug;%iBOMIy z_k70GxpE9W$HDM^OAeeq=sETh`7!7A(fNTItvh!uBn;Lx8308DTj84hC2woZa|aG3q?zlP%Y)U7NCUUF|bjV?3|lJ3sEYq&C*8zAG!| z1Oslwv*!#qX5KS-)O?R%j5xAu8F9Vj%sc!zxv+Zj;T?WlIVt_*!#n)A@<{s0nOFRn zIjBj^#X6aTKH8Dg)MrNCyEUgAL(g$Ayx)=orw@9L-7`PtUa_&ZosZw`Nc@er|Dj{R z_x|Sp|MS(kwob3Ub?uIWH){CQxa?nzv6?UE-qAC^tFd?He@>rn4;-@>+7O@rzZL)2D-IM9!vdc)mC#5%X=^Vy$7q!d(Bwoz2rfD@-EhZ zj!nBZ?dG3SUwrVV23e0Y7@z&IANdA`I`~~4%+DN0^Du_Bj4+%VU-ir*F~ISBDfg{s z$?WSx46%Mj7|y$c0oDj-H~v-aG|!sc^gCjIxnj3E#8vl0yM70xNhoZtC6nK3hr z#8Ve%-M<0Fk4epeB`o4~S{ik;4nB}6+yU61R zH+HS{{jY05>a&s^20X&GuIhnrR}byZpVuOFV)f1Ig6?|6K$=gr)f~ia(W=dR z%~<8V<-p^K^Y2Rxr=dBz=`%Hr2^DFO5=TPPt zx^^TO-fzi)(+54rM*LV#QRnOW;u9mkNnRq>t`viNg>wrs=jHk3oK zQ!&VAr_C^&XC+4%?5~|1@X;46){-CUT=#12do409^{X)48>{(p?j0Sx88&rUwVioJ zJ}Ydm%Z}g7hxr-b-51p_^WaQg$5(w~D#k3|ZtPuRz*~13d5!X@yx9JOAAj3Za=_GU zJ-tsJ{P@l1ad$uL*sQ1b|4vNxNepqg2a?Ot?`&Yzc=~d=#E?@~8@?3-Upbdt7>O?} zZRqwB#~i?_bB=5^m&A}8gOzKMBWp_x4TnQA1XiQ8CKUtpYLnV@_uh;zt*ny#mg9K zn*9||a6DrwhV!iC2t&^6Tr;UJSmcqPtGI35)3v|iyp_Y!?^@Ck%9(e_RK9Z9qy1fs5jTd`(Uhi%4!^pt`7b_a{gR(&CfV6xDUBEv;EUw``M>#IfkBNU~r#(+h6|U zZ?yD5pN)e7uaHyt%$P~*LkzJML#$mX2KNcy!^oUJYvA0a!j7$A$jL{z1$zv4jnmP`_@Um}3`1--<#1NMpWtg#oACY0N*$qw-?=NBHq>O*04gn7N(B;LLnW zBad6+iFGaaz>kmY&CZv#boL`Q>p{$s>-sMCN6jTUo4HjRc^j9KYYnGMfBKy*_k|eP zk{8PlvD>hvPsO3H4}LamBm31D=74m5cR=JA-&WPJ~XCcm5*ef?2VVF;1 zjWBj|nZ@6V(dDU)d0A&&J9Flza@`yo={4pa`EmDNwd0v`MAq7kAF+WU7vh=nY2nBD zZ*{qTB=wNzu4MjOUFi#kXWx~Cqjo-Etays!`EPY;LxPcOlNW0{!jHW#mGyU2wJUR+ zJh-#hGDL zZZhi6Fq~&4M;Pqm(Y)2JFIZgL{OnvKuSLeC&UklUrR@mDXpih*jAG5D!=Ns;X4&uR zmg`^XyZ9@=%&F?;h-B~A@m0UxFJd5f8u%Rs`5b8-#wzzZKEi;@?z}%9tp&RnRr)^K zchD7sJ>s=FQ&0EUtr(usJyY*W&c4Kw#}ydh+zUVUTJ2mHeTd;&uny#{7~+6+v=Qs) zXsrImE3vcIJ)P8t7~_#6jC8<(fx& z<+>xkwAYw>eJZ1E6RHskX!C-yPRo9w*eUy%Y;rG($ zI|rZD=L!sXgj^$^BO~2<5F-ak46$~l7`c|w+}h0{W2ja3^&QC_49-G3E$6LyIL}IU z7|hMy*~uY&!K!;_=Nfq}YOH<6-OD3+1Y@*Eb}&Y<=G9?XW6yt8c6D>@J8~C)oC|SUYpsqa7&i7&?lI~!FZ1&;epbiPn037Z<8?Nlw3D}WKqeQiW9c{3E7zU*I#y#o zG=7}CP@T1@E9H^95)8Qz&z~KBT(QBZwxj)B4A+T#@Z8aCR}!3eiD8~Qvdd$Ye>d(mvmTw#%626N`{YP2^_sZPZpGltd`lybTjGgz zE%(BYsp;9qTF%BAXFbGXUnXC2pH>_38LLi>;ZW%>Z>zSv9>(8O9^{uCdBl&k=Ni?n=DCvicJ;Uw!(5m@^EaPrt2w;BGN16V^d~o3wIz2lKJ&=uhj~=nDi3)R z(mbkdlm~WwiABezU7Pk?gF3#>i=+njQ^$s{aVc-XFjm>k-Q|(_7^^&k!FpzVw4Uuo z{B(rjbxNEOM%Fg*@@xkVW9eIUV@JNL@rrk}U%Bp1e2w{#`LUdkbyrR+cT6s+-bpav zLOf3@R_EW@!07BpY)3F$Bhp&11~BAxyR!ZsG8jksaQ!`GFg*M2O89B|g0bSib-ex_ zG8onYd~|uRGRO5H5A9&EH~1bJedpk_`dopLoF|`IBR%UW2T6<^-_gZzEq*s2k=F2~ zVX&UFt?r$jYvi@4vGzZ74EIN#`_yl}7pc)Me){e1bMnj_#n_EcKVoaXIX=@zd4PGO zF^74$zg~)DU9%c?W3?793FErkcRH7@)5)L8qDXAC-ad>prLT*~T0|0)b{D_f%#nkbNk@tZ26w>`sZCyMV@-O)c(z&Zm{`K7V!kPJ!JEndFE8|jsml)kV zX-nNas;&0%OIzwA4D516d28~~s_Rp^=Lo~Sl{g~|b4&?twj3Mj`j5u7@A~P#D=XLC ziK{UmB0o+p2%llyNS;Y&Ir0pn^3qWsjM3Px{RqZ`AJ2d9SRU=Q9dX|sI~eS*JZEA% z`ndu_&V%&XGL!ga3^`{XeP9{`cM?18>F0U)hy*J!>|3kLl7OdHkX z2-XPWNNujh5N|gJ{X3a_X{L9v58KPF0B1P(tGqS2Y}NH4CMz<+xRoF093P4f*QFiJ zH{;;Pw4)dq$G;CpJBo2O?&v%Z|3?KL6?jzOQGrJV9u;_0;8B4`1s)Z6RNzs8M+H8F z3hcgHUA-^m@Am5Z<6Ug(G&TzoKH3-K)_UasQ(WKI<`{Y%_wV`A&%a|*<`}y6l&t0IPvVdCtLOZ*<8|*L zKJ7Jmtp9l`(Cxh=HnKe8-}yezN*}X4DsXKDy1m@lX0y)T8Rysx40disEIln+;bFp^*I=p9DpRi9Zo zhF-^gj-}rp_?hp0%9dm3Iff2*Wi3~J!2Gtq{Kwy#>pAAWavdKU7SGfCoi+L&+8XX9 z-_duIHC>J0%Q~Jb59*P7?}(l?Jrb9`Vm#M+cjrlNvZHsnJ8|iA&lpF3CK}(m7Vo>WoYhsurd>*7Z?FdqE)yT8GksmF--!hv%v^#?zG{X5a027b(Qz{Y+# z^D!QICZ3pk!8npXIoC75k@$!2Cl#o*w65-{)%Yh!``VBH`9JkEYWpbHc(T51E7Vav ztlLZy3;tCYnfFoMJdVbtzqLBTTjVvBzq}+2IIaJd@~*5LnL4wVkY_RS`poLfSiK&` zD)sUDRq43?Rk9Aw!nqdD#?^*zji=8XoRQMsGgGzA)?;mV?$s_2bE!3`{hb`F!AQCX zx;$LRC|3K2fYGh*hz(ws?}qCcGW+l9_m9cpfB&=p!K=m`a?QF9?6%AcKl_f4Fz`(u z;}Qc~`Y={MjFYQ*J0HJC%>Mf7Pki%R&EGNZ#)$FvmU(|f|GU5Z(Ob=*wd}@-@qHG* zM@;`)f9^|fHGjvr8zYAC5ohh}l^@fmUe(^k&TL>j`0@SnW8UXx%Xh%_U2pAkZPR~8 z?=bTHuJ^Js$I!Jay=T*}zN5`C^g8bS#(KOrcO^01hxoJakGX$m-&@^$)%KDw>e^;~ z*X{X+e)Y$;(`sDycjCLbXBatlt6uXvg9nCnOzFB-<45O&7telQgz*(-$E{Z}bzBV3 zkV@BG{q2wHk+mD?V&5yq#~**(?uE|oTBwV>MXs_VD|dKF7+3LQ_9S&q?l;pb$M)LD z+uY+-I$p1ddmg(!HP*i4BMjDoZ{}}owdLM22iI~dKL(roGjpkV(8p(%2kXsT-1Dx5 zK0aMNGKWpZ&2Pd7_C?`yEDJtBbB-|F+lkd-7!TLXaqQVD4!$(poIqC!Pm)c z&sZcFqo13G|K(Wf$rHh-T=i&Q4C2lpu z#3ym9el^BwzB>P^pZT4|PK>TLtMOHiIX>gYzxoS5{SDqc~SDEuxT@0St zKGW2ZGYsmH&-x>}*XU{tzAsjJq)(Rz9Qf;B|N0X$eMfdNs4X0T7|)R_iErf%Vjy?g z3}ZFF<1>t%95UzJWB107-5Ys-r|vlG$#tk>*N5xgHOEI7#NZ=;L>g0Vxwp&#X}!~* zYe=iMd(NY)#i$xrn zSu%Y*S32hi!{VWSx~i?&9bNhWwIyu>QQZelrQiYI7bDky zh97&T8cFgP@m`+gjIA0NBi_q-kg-(*gBW3imuI3)K8XB4FTyOXTlmN6p?eMfSJ z!Pssy_fa0*TGCeY0B84OtYH5QxS(IeT#CC*hCvpjRYqdNKBik%o@A+617d^cw~ zX+E>y#L0^t<9@@x!|rg1^Bz@>;=vSyec`jO?vYUr)C1qFmAV-AQ!x2FaNW5!-F0CM><)wa zI_|ZV_YqTV_~tcHCpWHL`g@J5t*%3i_nn9H*L9ETk#_5n7?rzteSLm7uD!$XoPy-6 zqHjGbY40%Pzw#1&E5Fe$hS%j*$$5u!v9^JiIeQRHA_#GI2z`BATi(e(rv8)B( zDV$~eZppr}>EG!Y!#c8`=lPkQF%`o+R*`(0e(T!k^qGv_<1 zli#h_i6Iu!+N{Qpxj#l2)B`_il>XUIJ$gxu zfM=fWF{E{>l>N5l%uVL1qo^?3Wh|!&u zI*jDT@)t2W-jW!3jc?I8BXZuUXQu89q>txH=N)12te_=Uhe5nN)A5X1>EyBUS&g5; zAXoFYCUyVNw@y`#eCz(9Ut{f$eCz(9Z%mb)?+zcF`7p<9d}j=dU4A^myDETk(vU4;Yoh9_@pXTsQTKm1C)ck?W}Uc#fghaqrpm ztIzcuL$BlBoAo}deqCSe=j6vcv+6UJ??uis`8{dh9Bb`re})0pPMcwv$4XWV_Q>wX zSaYRcW3i3yPwib?;#`H1d4k!soVV(ozqZ7#aXWo*#7K#q_|^E%9Qss#oSZh_BkyqA zj73krO#fYBP!IMGwQ?WT{%9;~N(_CP+!%f=N6!0)=Z^D=VNI`)$&ul~$%W;})t>Xp z7d%IJ8zdqj+xI(!pLiUi$3DE_54HMbCu^x z=N(~qO%tobAYPv7c*d-B@>uz-#?N4ot9e_K5$Cm5DM!BbtnC>*W7S8#9p^Qs%FZ`= z-x-}b&ctO581iEz_wUi4vA!yg%&XI9+?d?SW5#`bhRuAym~mg9r86HeIOpX#sncgZ zU{nr!v@Zs6XB!y&?yimdF`IT}j-h+cI*#PMZMGci#=SS|ec18gYP0Uo#gB9S`8?#l z;`=hcC+(YK`gi&a!|&pioMD*9N>&W^$nM8j^Q2#6v3akKWEYn>S7VrOl{4Jzt}v_z_0QU8A6-9u9IG{@Pvg0g_X~aB2_ZRu zh$qIX)X8gdc{zv;@toU*A(heVt^yzY5jf2r)*Esw2 zcaJ|U{J8SmeAip~Gd0cenOMCRNS|}cd=}#C*&Cb=BmFylh5`0Yn_=WyW_rc2E<4g# zbERM7^yzAVE$dig?Srv8zjSjOk4%g%m)$(@>*CFCl}F~3dgdn-#LKl00}fywiNHRtbjhLRNCbL%^t<^=KaqdG1P9Z2iGF^I;FIo~0|?icz2UBVLpSAd>D%eM{5YRmonF_HeRJ&M(v~sQF8iF3c3Q?%4D(pYim|$mIZkZmNk7NU37=eh z>Wp=9iDQoS{?7gpjGSM6H;>U;%w?5Ft}FG?8pT_UC)P^e<)%B_cJ#Z;J}ci9hV|fj z)aJFQcKVFfn(7BXuFbKOiLt8n*>SjVa$|8ucE;rk^*gNV>sxt)_AaK{^i2$}i`U?Y ztk>m8tiEDUk6ZaMSgr>tM_NhdWqv=_$sA{rd8l*N6r3wCz;Qjf2J@*le2J+ab4h>q zMzz&-i1ohn=;k-~t{ypNUBq~a{1}dzyidJyS+A$^R&EQoVhpZJQbtzW8UvpPFrpM?Do6j$KSv&KX;zbLiD

|cSAXYrYy^HT5Z zo5xCKOuc5SKCzh}{Tic>&;Kfk@8S}NaqsDoJc3bkpud|3eqFqIt@6lqr9N7tc&qWm zTIurR{9WOy^E|tP+m3L|f$qtEq-^+UB2OIBWhP@?)MaT(`AowmIX1$&1%p`MA2XL&H5cr*ZXMVhA7O~E z-&q{-sY|{;_Gb@}BkXFgF`l)%_d_+-evR>G50Lt-WX5!S^@xqRM*eWn(OBBS==iZ5 znfqB@I^xP;q)+F!8V94}%^627v{AlbR6bn$Jjbw&8W@%P);`a5Y@-GS=QFwPhzsL0 zYG9C~JQcaq&vl8Bwxc>2XFh%pAkV}TQ!%*z{F&$-iN8Oy4d=@|81sIv9NWG=8JBsn zm*kT9UC`-%e_9XMb`=Kao1M0khie+iic#w`>$C36(|Tk~^+|vAF0Nvj=SUvGaNa6+ zFlvl_^IBo#x>6@jjFjT7#uIC$!%gAC`An-i<2(F#^vp|phm-s`eMfr5pdPn0YII9H zv8y^i_qe=8t}>JFiXXG)BYBu(!$GtY`?{%!Ux(;&SV_nn#IrHe|H>SA2#@Y8H zHQs*jJ+>N`5^ws{=E0Bu@mqYC`Xtvqqk}Qxo7%ajY{^&E!EjvZm{Pk^yAllbl=>sh zXC%RZ|M`qS*8a$53>cLU*FMkn&gSzT49;wMeysfwXEp{5azDzC(;tlNSG*h>>0q4s zaJ{H0@=QE29fq8BSI*{!JHr93d6o5@vsmWko;f0|$4kPnE<3WW+j?Yd_37%{#l2LF zTvzI&I$4|5cw((|xMsaSx_;%P;@-+_!MOs%d>-<31>`M$ckRf^UtSW%hsck4K62eg z*QI-YcD4?KUE*^@{)9BX+I;rZ`^!C&{_eGEtLqTUJpB7bqfc<7Y64NshTQ&%OB6 z-2HnMTi;*#B_-w{7XZFwNV2t=?XXpN|t;4836|0VeQEl{V z+^i2q<-@hlv#PWCd;_C$-`eL{+Sz>GgHic#?Q^#5Y@Rv6sC@QlAB^O^H9p5iIv8g@ zT)X^UcwZQ^d2i*%-F}_fvL0EVSxgzM0t9rQb&95+VkSdGg*zWN!L(y@%G zx)}P_cjK41UfHPLlpz9`cqBgLlGkk`;!$B{8x#sr#(& zp5f^?(&e}L{utZHzq233!2jqR-MsJ1mifD9kh}TR821cv7o*0wXOO!X8FPluW(;#? z|BMdCjC1ac-4jOTzO~Q&-PwGuu$IbwYoBL&XY+dsjLLm$|G|%`(){ChiQg@A@8hGq zJNDT5PpI@2NYJ6u7eK_l^&O0j~$9JUjR=E?aPmO)_->$$Bu8_Wol=dU~Jz~I* z&$L_%_nqwsR$?5fWBOi=A)f15N&P#SdCv4M_8x4ko#$)*oq)Q}U;8P$XL63Ke`pw^ zYr^xF^Dc7av$D(glq>&k+*oDj^Ac^9>+Y+Zzkb0%gN`{DPfz@q|> z3Op+CsKBEFj|x00@TkC}0*?wjD)6Ylhgg9#?|UM)&j7{(fHN#M)DWag+~Ne=w3?&-AQK z`hmfIr&jKJY|rso&CAb3XU$TVJHM+u$&H_jLhdwuGoP1&;X3>|G5WdAhyJL*Jyf9X zQ}^qR{Iu-p(a*iEuHPtbWIu~{HtuX5o};T=<)J@*qo1)}VT^Jf**mQ}z< zl~axQv$4kAf+4>K1D@ygsxmp?$PbKrHUgKQQXP$Mzhb)x5Xz zV}4)aNXs>w+s@ezBT?`bQ_6qYW7*`s_@U<*I-FS_ z&bsj5dalkhFnS#)Mj!dF|HrlxySbukSHhV)e!Swib(}fUHsZkPQ+4x9e*7!H@+(hA z8W>0XJKr-HpC*0`x1I6f%4_kbrrOU+Fnp%_jK}sIpV_?Ky;<*7c&+!U`NE6g$h7pw zH~Z+-mRRQI_u`JM^?NB8tRv6jb)DA!hy-KScCcpgJ2__j&U{CG-ZlQwd^-O>^TR*< zbW7_n-9aIu3L% zd2sIZJe=p%)E&n+Yl&_=vg1qDHsi;2PmKCAe(YXc$q~jqT-#dX_tE#BtJjad_u^>G zdrl1a2wWunI}CXYV-vfg)B zV6dKw!(6qQXU)NS+=7vFIgd8_V2h8v%fUTU`LEoUJXb#MnR+J9Gw{mCJtOZ`Vz*W^ zpUPFc{*3c7M;CL%W}eB9ne#{k4S0P_mAuEBhv>X z^BeKw^g+*e#9(A!J!7_OspMS4m9P z?FtNPG&(bl?Daa{nn%r{;^&%o^orN5V>QRs7_0eq)|Eb)-__VT&#gM#_;3E=FP&<{ zGC%9PrCo`^`L5ef*k@ziHNpm#u~jnG#no#fPM6mx9y<+g1+PJBtFrSk2RJG3WWm_s zw)j?VOaE35buW2%_VjFu>>6iNed-yNK4Zw_9W%XhWa_~+ne8w48$Kg$i=F-8+#h}P zQLE!K!f<~ecl5eH*lVt5XODI9?$`GzsS!1|mMOg!X;Wv6SaKKR!H++=lJ%aG4|Z~P z&*eSY#cCU2kkL*qv-n#v%&}XG^D;)Bv`?z|iMHatc&>a#=j%KJuY6|Q*KB7#M|f=K z%N(8kh|N4-5i_*EQ7r=-#Pu+26sCv#fmU z{Hs2AGwfU5*~&{-IqZyI#gAR<8A+aJ;xYyd_XPIb2iZ>@AN%$Cd;Jop(}}OKT{p_+$(-8W|cD^xX2aU_N8EO=HwjeSya7B&#&0)IaLg#9BC!* znIGSJZP|}6i647kru5!Vn>u4Y1CaC`OZlN;)aT6EKKF_D6LQwPFS~p0Rt)mr$z_y- z>%Rpf*X}&peAd*pJFl|izStaB&ip#=o7`B8nP10!u^Foz`5oc0oiF&E{fNyRJAS;2 zb5$O@^ZEYy==)=zKX5|zDp?C$xZ}NPoB4nN7nbXe7jm zY>uT4hR<}L@z|c@Gn==&FOwgmKlt&7&5ybMUE`iz?M3%ay}z@+gW>sQ<$Lsg0B?qU z1uwmVV_%U+uGKXkk<|8xf9DGZd2sz1&wY^n)YG?Kf3fiSzyF{A_YGbP&;9lP^PhcV zjv7lYe#EKVn0PT}@iXr095bH>KYsJy3FGX^IkcWlJ)@@7ubx-MK(2VP<1>ET?dNLa zY*OF5%1PNjJ8gt<6+ia<^GeorxR2kr^j^(p$FA-@{E{%b>+x5<E4`-J;y z_nyPIyXS7jAg7&N%(Ld;nr^|U&lj+=Imb-ScyGqd{JQh?gCD>C_p9ZfyL^?J@A{7X zj$oMIr;#5gudUp+_Ni(0KiUVwXS&aLY|rtT&D-6-&&iMZ4*2EI|E^c<=fCTBF8?ls zzayzXU!53Ytt2s7dumkAN~?aoj`#Zhm^{12nWU~EYl6=B>g=+EVJ*8nwAHz@uTLiv zJ7dtV;HCG1!P;Hx8A(0P#AOT^{kBsk~lVJQF-sKB*trs6ay*$UCHjcz*o8UJ8i^mufVuW5KW6{T-VO78L!0@4F?%=c`{bDq7?tnJZF#?&y6=0dKNya!WR2Ay z44>&f&iL(4F0**{b;YR9 zlUyslSKkYj_tGEDG4ieF>y8_XIr6PPH{JbgwHTf6=v`{H`@XbFu&d2}VqnXD>gM+@ zf4+J)zqycy`bxUS(feb$<4pQItCBq6zB^5>TqPKA-pfYy@w@){YM%GGecD`0?JZ;kt8Pv%Xa_w%UvC zoqB&~{|H9Sf&OkDqqUezm&X;n^eT*>`5Rw;I@6e+E6)0;!Py4J*;wPugPLQr_O(A6 z%WH&AT=H6U`D{u!G`<;6E_^CZ#;K3u<-TwiTjj_*KD*o&KfdQUZ=U7HD~wxt82Gnh za9*wF*-@YQ7|Sy*HZhPLU+QeP@MHGTt^9a~!8P~V@95Ov^$x%OPq25|?#vZy7U%ui zT|b{EqcLK^7UL!IVs6rrfjXlX2?Y^XPfs=e|h{eT_x>Jegr6 z-cfzTc{`kuZ#`ds2>e(+sARqBO1a~TV>)ilR{VIzalsJptUQ9@8eKEFyN=|0=sven zj_mk=Q@L^Fzw~$BA16Ofo}_-}{jt0iSvhU?(OFCS&vg7VUSBYLru&S?_8gztyxgy6 z{(Y|c{~yb%JFIHUX9T%%miGC-lK9s8U5NqyZX4y{zFf(=7T4kY760g75pPt}I)8@` zM&9$CzREK(x?Fbiz^{upr!Eh;*}eE`$XQDJhIcd-#T8I80-&Y-7DE2 zVeI6yiUlKcF;{FhY&$sSb5xEvZ-+DTy;pwR@zBZ#fAcqg^NGfK)#Zx%?($S?w&KS# zJTPYCJ7Y79S`*jkn$PfK`CyggvCEIEpM5Zr8;^K!^~pXMm9Osfb3J0D?Whih&vc*h z*q-Avo44M-^4yf&zt5FTC~woE7YKt~Yyvy_oy4)9cPul&jr4P14(qQ1M>=5~}HCuap?cfOI|5e(Ppnjif5 z55M|GB| z*Lk$g6|bx9YK{*blV{cO^fL!|K*ukVGgQ5DhnIxG*)`9@J9<4Yi-GL;QfJHS#$wS{=4Ega9PMR^~l>H+($!!y3w$2=@G1d#2e$TC&$A|-S zjYeacyEyXMl$G1Up}qEXy&cYxc<|s=+3FlQrk*}kcPQpLz}! z1KIJV&c?N=*9C5Sq;=QjNIbq3gPgD(X}OLob#frb+1_+7ck6I1XED5Ac}CR6J?`_P z8}s?`k}yWs_8#6J%W37r+27^ItdDy*+i1;c*FSBYzU$Y;&TJjuJ?j4<^JBQI?@ZNw zmnwJM<*3%}!H-}6{w?+J=cJY0cZ_451S5Y|8om3S&gzqWFe=|Y+6O~^j6B1U!SI>x zGalP>d{*;v53kz0%5#-l&O7oQ*)vvsIhm_-fBs^^xz$Zd~P<+Gn!H)c!2iu^LnR`psmGajnQ%+`Aq#j}ZsE zJt}ZT1=y!N|9mIxvoQOqr?2mB$U~fbe>{5kyZiq5O#Br!d0;#$@SGLMcio--9JzG! zcDCopW%b>84>_3QyUK;1x5)Rp`Tn=+qj$d}jHB<5^JhPH^sXj5ap}`x++y>3ot4ae zHrBW+Fe>jG^*cT&M@HVtZCT5$7^B>G?c}iQ+tq`3KAw4Yq`WYF6QdhX-_dwtr2j|< zr;c?PonOY)aWJZF=FeF52jfHfeDOQK^~v@w^Y%G_T+K7`9oaj)k?+XH{0_-)Oamj(*yFM2g(=UBA zT)2N{ZIvgnt7iy4E1X-#cXBYtca;nG%RTYq%E?E4^6qNydPZ?+A7OO3yEd+q-xBWmG(fGgUxr5ueVM*5HR%1QC- zFgm})tm9x*+svP_>JP@ff8@i+@A>#!o_^{Fzx|bbmw9vVdzMCDHGMOumxAFs{2o0b ziOrfk^hX8WRR!2gnq~6(i$!bunC@SgZR5{4N&T*=7#%1ti?9`?sU8 z$?J?unHZz^Y6mYd)WH%@o|4jW{Zr235+ikD6646()^lz>mKz-^~l`br@m9$ zTFcDWcz%EQvp@DDubTg^OZK@oD;&mGE$2?1xXgjMYaZHWd2rvY?%R6LW*)|KZ}V?i z(|nF(pE*`IjIUbGojP%uLzjoPSssabRQG=I-LUsnC-oWKbB-J9y>1_=PsJeb%mu$u z1E-6>6$8v{M{9{czkkfN`sz=7^IMHES~o@vV_@*_`q2OGFMsq_^WXgG#)x4Y4F10+ z`rrCIU;fF@yyC=2Fn-|6zxOoO z?)==t)NAz}={UZ`B>qdmU>&aWsH`=iKG=3z*0Sn5@!i~sQ!#SvR{dRK-j*KJXUjR(HJP zF0Mh}5k}f84rkNRnRsNc=VURE=D(6%J?`Z`q(;=8x+a&ceSB&?T?6*?=ln~bhMu@H z46px6&M*FB_^`{I{-%yBrgK5$(4Z~lL& z=yG2$(Cauc`pAF1x0Tq5=acuo<8Wr|$x~;#oY(*BSP8zmQr{U~j)B2v#*b(G8Z6=y zQ+*YuUxzV^b&b9=CwFq-({aEfwh@NUu9PF(5yr^BvyU)x{;F3TF{+<^{MD7!2FAPo z$Z6%Z`964OZg}z2KlrAnv;3Gi+Rj}1`2A+xc4YmIdMOxZ)?vMBO{g!nv)}i5eXhjl z@^r2yHaCXo^R>bdYX4)5W||qKo@5h!(7C$kKDyLn}^Q;c?F!p`z?7#z1OS- zGBJpu24bL#vx{LaV%SISVw}w**L6ncKH>h_x$pGp?zs-b`#ta95eB*Mo*cPwRxv5KwZ zU{qVq(T!Cb7(B~wX=41k6}>UW8mTZ}G`kN%y!3d0(%=Ie5p*}!{O*t2<$j%A+ayDP7* z+0opqKl!ka$UpZ}_ES$^*K$QYuEGGPHux+209nVfZ*Al)NY3Rut#ah6Fyyx8z&b`c zzRc@7kgj1CSMe$a=S|PQ$XVlj>X|k35CiFaD_Lurc<)>H`g(3uK&+ zW0;G1m;?5Aod@?3&jvXM_3(LuF89EOuQf<{HHNvEhdE$>*Lmc+&gk4H-b-@9k@Vgq zCg&Pqyfl7X>r(4LP4e1v{oNZ`ua#fNk9{wkI5WSzC%G@ZXEPpsa)+oA}6XtlGfXJr9rk zzf!SQU4DYi=SIDTFA1Zr?P#C-HtrKRU-CaWVU_xH_gshZ()cm!u4}IqPtJ>8$7{dyPtJ>8V`{(iPtJ>8V`{(p zSB|*qcieUrvl=(Ym&}ioAFj?K)I*<)Tj|Ci6C>^D;8a`J?=V(5RP2gT$7{dl!8jPB zGv}@y3^*^mxV|H%pYMrBn4>)M_k`7F7;s<4D<9TA8q=RV_{crrx4b(}9$a1 zu5xW>B^cC$nuB5OYkxF$bxoc3$a;t|57tvZa$|nJdiQ6neXoaHaX7~-m%9o>{9ReO z?U6M&N5$amxyy|?d*fTrt70IXXC-S*>$QEKeXs9>AHTi|K1Uw>`0ZWEdxP5E_IlI8 zebRB;%8k>fyXSJ;*n8l|^?8zc)^(V-K8$C3vvPfp{b}nx-nQzGWVH6*j(FQrV`~5H zh_@{nv*NC+anM&jt8p{DcYd6IH*oa*F+6X@1wAiO5A}>o-Em}RPmJ`{zuLNfhp}2i z9j_R5y!LAzjDvCH4A=RJk?)18-o>R&jIsFmLuGUlQQ0KI^*1nTRFh=?8V2onTsl&iFid)&my9&cRkIF0Z zV69x+)tXo1V2}@Mz&F>D{nXRh90ccSVO&V`{&PskRzZ`_;eeXUvMbuExP%`K-px@hCq|zPidIeMkAMFf#5aKjzF) zxgh;)o=eC>-EsS!?0gcV#^7IVUBAOv#n$nPQO9e)=D~Qys6IRWmxjT5ypMO*uMaWA z`guthuIUO%UEl+=R%==3-Kp&nj8QH-7^B$HzF;jo`5RxQIUz@Luk49Y@pt=)v6^o- zPCaJhJ7ZZ>{BsT2PaPloxt5GQqE`&DcQEu7gZ1R+!H=)u*qlo}zpiBW{`kR<-~2lZ z_vlOF*xZBMvk!jk)qVZ@X8CWhRGvKJ$Rlo>oKjuBNz5m+kAH9YC_e^smd|MZSuWsZ zV=X&4iFri#=cRXkwp!d85pz+NBME$ z)I4%*S3iO=7_O(g z1~Xed+xk5_i>YJxia|}T`mjd$z^v6e)VX(Rdjw;Y%MQjU)@$2g?Bs8JmEyew4A#cA zt!q5m2g7=BJu*J~spDh6)|CE{23ExY`$$U+){~zz{Fr!o3v%_2)A9PQ^C}GScO~;? z4UVkAIVuL{*cC5!yyM5@BL>oWR3v}*I&Q}@ypYXKAfJNr4f4~ZYM9`0RvtoQpar!|&%bnx=C8c*LddgZ!17&WGH z-5rb?lU#Qv9-oZa<&Gmi@J3_1b};z76n^aUVZ{9$N6HuV#YQdkNxQZx#YlT%q;P*JQf>^tWXDMmM^9QpD4$NT?A z*Dd_mb8|fd(`O91;>C`C2>kec^HTOa*K4PB_lNPf{;at7Z+%8^A6MJxeih>uj?H?& z*=eJCT$Km-aA4Yx|M@@l>gTMH+D7wZmoLgceOKH`eY$&2KAUmw*|d!?YL4cWv31@W zlk1uJiaqn`uy<^g>z;|JTz3bf#w6F>iN_~nb~)_G54_RXt{n_M_sWmEcjB}6$Gc|@ zxL?N)sYPORKCAJHk^Y_D&Cy|Weu-DdD@Gl!{lr3N9E`{3?F#t4vh&@PzKN9f9UQI+ ze)ouxIo+zS>eAJr&Uqz<^W`5e#E02K}86bB!?0<}sQVo@E>Pq}_LeNV(cdRxWoH#%kU=&b)Ok)o0bO7@Su* zKObrJj4K9mHNWGvrn#>7ty7~Tt;?7A?mcg3tJkE%J8Qclk0bXizFn?96DptPIZQ@W{{F-zz`v@_z8+*JpE{rP(Uq<=SGKwT!R&gCBc$ye<7++4=4o`Ht*w z&-NzQ$?|c3Op+CsKBEFj|x00@TkC}0*?wjD)6YlqXLf#JSuRk0x#wL@$6k`9rJx@ z+NoOh@yUMGyYY%qV`_gFqw`CwI!+$dR{a^DH89S+Ga)WNF9{>p_0S&`cvRp~fky=% z6?jx&U4hlR+cm;@58nx|a&VlPj5$B+JaY98c^-Y+D073b%E57cZ=3b)Xnt1XUVpy& z?QHMg;`KT!S>f4O<&E~M9E~SNjp_Q}RC|Zf`DI)k2cz0% z{)|@B6?pCn_+52YUX}0J`Hj0Ok6j$+t&;d^v%ibI@)K_+ zXL;1P)qFeS@`Wmit+wp%V6S`H_}&mWZcet`eaXZ#-cas7RY-Mo#%KlPn>eKJlR>G({hzcE}N&+EvYmNCqi{hj!n z&;4-)#%%4+IriYkV}8uFPhP3+zOk?5x~cET4!^_b{1U&8gHdfWf5xgm7`e~spZUBi zj4yuYw?5f+^133Aoq6zKu3g`e-=F($fBa^Q5%$CWvMa#;t7n|u{uO)I>fTEp<};Gy zIm?x}QT>b^Vei;ybLYIXJkk#x%$>PAA9#Trp>hZM#B_FT#&_5|w%Odyv6Dyog1Iwy z=i_=3FLh$FUv=X<>>b;4*TebDyRN79(+{0BB?kWf+tK|0O6awZPsUUX=h%^AiNRdN z<(yp}+Vn+&;r;2oUhlQqPe1R&#K52Pmj6Z@dhL4;E4w_LXC*5Ja}kqsl1t4)o4!c$ zxatG1d*APm$>Bfw<-hx?@o#|1HR~F&+x$1fkoehme1w5-`WTlO*wTlw`eB@0&D;6- zJz}oWSAXK0-)jDjaW_Vczqicq5!3(fFMsq_^LLE9F=G6^WqyyC{grj&V0e z%$eU?uJyUH|D61|`!}gsOZRVab+*~R&sF)K_qwuW}o@46BilP5V5>v-I zjLt9P>NptKW-a5~LH5pZ8&6 zU^iY4+SxLOdSu_{&PrxX#pvejY!w5XD?Xgz;Di3#)bhnn>LV9){t<@zB(XXSgkiQsi%)|iGeMBGFF{&{Ky#{I~Z_W_%Hu=ST?z@J~@U?j6QH&`0pS1 zneTn0$$h~@J^zU>yu#NHL#=HFZ$b4N6&bvF`2;&O7YZ=L%IYzPC zR~Y&sSH5S)XZSgGMkgQQR_CVCII+2=qdC;r(L5`A%_DKI)S1WUXU$1F^D#z$MNVubf?-E6=ys^c6SzjDtZ8d{7K@adt7xMGX7MU5ul7 zcz-Hy<+kkW4;h2~e#M8gn_MqBVW-O*>v?a4;r)?V9R~68Ovd%eICRJAICao>^@AMIdzVcz@(?9s8r=R`Y?|3EY z>jOr9uzO}i+NX}%v1Lrv<-)ux_I)w(m;TYOzv^qNaoKm?ZZ2%uGA?!V==d?VY#Dd8 z?pmy0C%biK9#>=SEWS^cn^b2rz_MqHOeonw|q^*xgdoQ8AdC>PgG9&nMBA6W9G+4}34+-ilt zn}@uq&a>)U58_!1GMhGV)RmndW4F{}hRuAL2mP7`>Bo!@nM-Ui3| z<~cjp?D{&4RUXbKhWDrPeREsZ$-S|YhrT`!T!Z#A7_0kccbv1Ed@%W6r^`#_g3d9* z;2P2rtHU53j!APISzF?yP9E7;ukrRBPdUQC$GDW*&D)x+xG(dpd{*2SjFnH`lissF zLx|_IorG4{^>9Mo>z8!!SG`}sF}L8(neqZPRdE)!s0Xb6$mE?N;+S@2&)IhMnX3?Mib+Qp4Ri{oK2`mLvH% zzwxs?yvEL*GV@6LQN8A4UP|*D)yf?4XYQ5XYCh+!(mZDCWA0TR#^ZY?7rDe}oz{Rn zJYPF!m0;i_$Ea(b_3teY*5P@(O2$|V_LbkQ^{8{(2e;<2%4K$4tiRH;?fc@#?>i6g zPvs7$)Ytnk``jBJGRExQ{r}i|zn9;>?XE9>ftOwt5iRINDYaC@3(KisErj+!O{pa+ zrB-jMaC$^&TFg0OL#&PVgmP#Du~>iQoRs+?K&XN~(dxnA<;<_+zSxXa zuKMZ!KIENiMBy|&4>u1gNT+>$!Yhk{-f5vusxYumWJXZJa z{KPPyDp}7SjCA5M=aJvheszE6x6Xt7&g3Ew8P%tqyy0KYM-m?}t#ehrv^=O|HmFN} zJoCHSk0b0`eao+Ts-ybU`H0(gd{XWz7ZhuR;c-r^4ug1k zrt2AVq?5;y&(ZoBj0ZpV47jhnH9xML_GnIZM?S7U7eDrzC#Q6dtTyLJYRP%(8JqU1 zcNj;pbsY?BR|XhfLy>tMUDZbz@Z<0O?*D8!Sa**OgKHYs2M2TgJK1622k-S6v7C4$ z!Kg3$lYPc!zmPSi&f%QNLFJ)x*BK{ufA_5OnpfSMx%(p>nM39_%caBKwblHyu3Vep z>X)@w9iJl%&!d_{&W|wCH}%A>82GuqD~SaUNzJ?Q#E4;DJNb06=8?yMyqu?=zGCgx zkp5y=OO>pfN0pfuan6<1pZU>#b${o#&f{z@@`A25Y}QWR-v4XsV(^h;SnG;>X?ajb zH{@j0q^|7zulA$E%>6PCd~>bF&W?-yS3aH}zr9ZQKK+oN{aSvdl9U>yvvp-Aq7cE=0@4m=vUPlv(vppJ~GdWWG8d^mOHe#aWS>tNKE z{h?nDZ1^c_Or6V5J07~iN6i75x;bb^*1esZ12T2iwJD83CiWRUvBaZ~9*-(no7IsR z9*<7$VALF(uVCm~r8#vN*j(S0;@twnW3z+h{xz2RvzDBvj*oM(n4cYDm`|0n+H#G! z%qwe2{b;|szw_KH@z8T^H4o#&UzPHqnuBw50S~_AKCV}Z&y0tS@_K1`RBq|qJgGx2 zs!g9Nuk@p0G0*)nkGfW4XL*RJeDLE3KlZ&|-?MPw%8PS8sxs*b7QI;`BvV`yv91; z@ipc-Wyg{LIfhG05FsRgTBp&+Bfj+Q8A4ajCnG?Cgn=zWP^N zH}5dmtMk=X*DFR{uXAyftb^hG5c14D>W7Gt+&F8dmhNSb=oxzFb7Yf~`rP)&zpk^d z<7^)CUvoj$ed?S&vqw96*Q*u0E-v$8ogU1_4#uda6^v1=xpf%kKZ+gM^SIvv!=A2U z<^El4)=Uk~(cJ@^YuLaU)w^OB!+fgDea%N{9?Z=n^V2Wo(SCJ*=eN#-oX+GTAIh3i zwy6^nW9mwD8+8avCw{ge-W z{NTsDr@Lzk{P)Ly->(~v4}l-cIpv`8*Oaay;nWV^bL#1nv8ktzafyK~eKJ`^A zfHC5{tku`Zcf@&XJoAz74(HwR?Q*!cE%~v$b5)Yts_)3}3dRvXhVSvdmiIaL^E=nb zLqFG@ce3+Ij2e@B)wP^=7)P;ny<*h$I?w&lSqEcw--gf8cx@QeQ(yGRKB-rqqq*Ev ze!Rj>E2evMOwjB4%Dm=r6vI9%Jf-vLa$Ldd;xbRxkIu^-j4pRFo^Z}v0O9vR>#j#_jm3((zq(q$G9rf zr^YiEpT0bvAJ^{Pb`Hn<`q#gHYLLmBtPyxpLcBJAx|{Uc_%v`FtneQnTPv= zrQXT9UNP$WN^W4}brF4cyfzH#$rt_L{?L0wb?oUk)8VAu-^JSD$nPtU+}GMyW!Iw> zye=;L%lgszy@N5TX9Z&vYknPu`Hx~pb}+@e1%^Fc#mfD=SZhX7gY(pLEo(vF9bk!J zK2@@A9y3W?&9n1A+OO{K{MLD_)b8FXgzS(mKb8?XMyj% zYSZ_Yd5qq_@Zzuiy)VDv$s?c6o_w$J!6R-g-dPOdEG$Q{vF1>Y;er4e)X%T205LLHSP*V@?&|U z*NG}!N3y0=Cl7s$b>2ziaxF2^KGIh)I={rM>ot$MUgwF0&N>+N+KO%W^_noc+Nm#V zMDP4&w))(5hZ~FgKJu_`&X?WM^=SpKi?avF4;^DA|52~)%`GtO=?<3r*VxJ}bDlbN zIj>k^u1Iq}!q9KjPwnh!UfMc&wBIQATjxP;XL6BSbhV|8I=uf+d)4ugr;KXv>@O`3 z>X;2`ke|)`uJ+>y`&N0_e`V#MJJ3ZVczk;q%gNziK;!K}(#l}vudZWX~Qq)Rqxg-MvbZSRgBIr zvFduwqpsI^VxhARhSyLe^E0p4gfXiB7B#|&{eJampCkKo=fQrtr}LTQ+;Y9vbA)AV zjm!BhG2G95#nzttR=xVJVi=eHD|+U13k>U7#mfC_Y~DMnX@zP8knQHNA?dfkJrYJ&vH_ax5rRf`84N9 zwdpIy==|8RCuX;2XUiD&;=Ocbh~fJZS+94U&FjLM_wuMmiP`Ph*)nE?i~R~eCSHD< z&ycaPw))z|z<+m-)m)y#FZ1vC?ktDSzvH_zjLv_T@9z4~^11aI{P^r3m$R|PUB$2; zmE(=(y#E<>9h|gvK1b^nBmFzQ+oQwi{1UIOgMn=}GQR4>fw6jB#CP<1O&IsA@k8Xt z^ke7v9{JAJXKQ&}W`47^4ukgrF|HY3?XIUB`Ht+yx{lO#Bx_8aAH}miiW%7xbL69a zb{!h65u1DL=ot#_DbY)AVXt)JPmi`jYKcD^0v8C!?FW1H=Hl^=sG-&4-^8?70u9QnOA zJABu%TAThzV&J=uRSf-+#K3nQs~GwtS7RQ2j}AOK@aVv!1CI_oI`HVgqXUl)JUZ~` zz@r0?4!q_L{1^Z8zw~rv(3>mq#CHAt?r;88PmXSV#b!Km#sA^+x;ntmB3GWT&8mJ) z7_&MbYmW}R<_`Ei9z7$T*@=D4y?NC3=)lL+f#-To+x3*au^;8&y1pIOt!!N1X5&`- zbU0pHXZzZDT-;F2MW8n9kk>K!unIY3Y(!r=}x4=lutS#;8*71-pqXW0N2JB#b_ILi~Cr5T* z2P5NBzXe9(q2B|u?s2dAhn)MpdU7vZdc89;kDa*A_X<{x<$fc7*G^q->^$Y@9wo8w zp?AH&#$L0vTjVmrPx~y7^h3WzPV$8+!LGKP-*fM}PtIrhJ#%`E{h*ijY}VK6XN)~j z=IRkIwW(+#}DZ9O^P2a@H_f&na)w#K43^wP;yxw(s`ihqrX*<%zGM@cb zaxgD_6X$5pm36r4AOD+gIWqbvMoz15`rQHpJon(UN4?5i%N*48Pd{zSTr=(=KiPrg zw&=+z>smJlE$zGU#F`O2Qx3}iSq48wY8(B(#n{1cUH=h=>quiNM%vZ!m$PeAlDl<0 z1BN@K6&G}}A(o8^&y=wP1B30@#asN8`$xSd^_@mKa2YbKBK$k^mMyLnkp z;-!9+hw;1n>Q8*`H`0&t$T+b(>3-&&ah;xi=wK!WG5j5@pZff-elyVP9G{G-81Atm z#S(+Ph|4|6rRJecUnCg5KYg#)_gbB&pYOxOz@KZK{qVY&bKk?tE)VxPk`;sfh{+t- zyXK)yUnF_l>IG+n^E%{^Rk}Xo$NJP*=dO1c)PrC8%yfLa9$djNPi2SyvDoCca>kBR z;^R8f7`bh(kK!|?>h@ZG3S3TnCJ|6vG1KuvaYWf%;Ck~_JhB5Y2e#oz>9g$ z&jvom`q^6RddzRz0s75-&C0+xci)Mv_*byHSk4#r@ZPJ=M{5-;>$|0%y~-R@XTO@K z>-O(TzQl?*le0W(+}V8K1n>(+#dJ;{a0};E8ZW=Vf9EAXskn6x<}45W@wt+V^~k5H zBoBP5-MR5sau91KkMhXaSuXY;X;Pi#*jzOiP4|G10)8zc5E3_G3?=yoMD{J z!}llB>wtNCj^!MmjG>mC6Qe&p5F`76En_N%*PSCd!#JAh+c^;L|loju7Xhc$OL*ItXX=W<5wi7PL~$91GJNcxcLqxg)ey1mS2Pr0A= zwE2G)E5Sh57n%Ofu_cdT&LCC{F_q$UwLSQ;NAKmydOfZg{)IpF*>~4BzZb34_XtBj zWS&=PKf>^NX~tCj2&4M1%oEekHaW38+2_dGk@~WSdUE6c^!xwyPw-^s)n;5`$cz8k z55MKfNOjkc#Afa*wXXPpd1bK2m3U%b5{5k^FOS!ew1zXW75fTS7t1{udw366=cBcX zWo@^X>|<`|IkdB)`qQ6&a-@AnHy*hY zcgB}`&vHs)*>e`}8or}p-uUVxCzZblCDu)qLI++ArN;PCQ1 z;aoWyS7Iyn6|7M#y~urXo;p6xE7set-1cAg$o;rK{ru-Y|K=W^HFIXyBR|&9=Ima` z%mLjsa>+cH>z^_DBX_$6r-A1s_NX6l5&L5v)fX)J(oQaNt1gf137F<#4qY$gO*=W@ zYd?%nY;$A{-^!0W->dy#&%{Mv$s=R*N0N8C66^hj!!1UyNFd zzG8rLi`k}`0`mKwjAaI$4o4Jx~BR>boEaTzDzwisc@P;G9lam{x!;ihscCixUDAs+E#5qqr*J?kk z?UvFWvoCrb=E$DrS$!(Tnf%1)uw5gU%wv?Z{>a^2=-r9o#Dud!TsqOjGEWcJb9kFGVihBgmS~QgO5Kp<5FtR_*_RfNBA*ZQ~czm*b+k@ z@y$bQ*Av6F#LbznX{K z53Y$WH-n!9%9`ikNE zlQqtDt_2Ryedk^$^!0ssOAN8m`yUURiBV)y5eKnsQKe-)zVc%@Cu^0P z*17v2!4NNH#*kmGscTORbL{fYIv@Plqjz7K*UnYl>#o=1jPv>NFaF{$KGgwVW*z?- zM&fav)Zu!?=y)f#&%KTn4KK3DF`5Y4ipUjOM%p>jQ1de)gVCrC` zt?I@l#who+qZ3k&dhVO`WFF|mn6t9_Ge6od z_fGxRd7RBfUeMK+b(6dI|M=!w#>zFa))oDwrtc)<0wn-=E5?#q&M;flXf|{m41%A2J5@+~T#uC*_Us zhK`%w0)tvsa7H=Uiz^t!<`DC&l-qI-$UQRWC;HRZzUhs=!fnMMmN;>kGgdIpX(PCGH3FL_+4#XP{_#oT+ZBRPsCPh-u^IcvFxzB8=! z7YDf#+xcg#>lKUn*->kG+u2|KcT^9#M*sf=7Im2T;w$F zmojrC4>*tOxuy*%Z~A1l$FrA~2X)k8j@YRvd1|h8amO9_q{k7Sd)z7y_fsa9b1&|X zAN=@J@7@F156*qx6TS~~&U*IL_soZa@yWCE?Tq^B=={>Q!Y9x0W8RB7jxfk+B^S6y z$I-2+Vk9>*R*A21_mFa1c&+?TeU;maK|I{}4WA62{2cuFDyOW+K>mr8Zz9u9jK?JT z@w+}YoZ9%@4~(3<-llv}OEZeu65 za>zQz2VdimiRu34Gn4v)q3-*2B2FSHJELE%#XP{_WnXF!>zrI?u~`eT&Qr(cd=*QaGqLWKvEsRA9?Z`UG2V9c8~_j?OR)6M4`+Z{uZ!NdZ<7_VSp{};9o2=nU`sJE7BtBq{>K@r&S{`!3l*xyw zBj>4S%+-EWEath_t@5z{%H(q9kX+8Xl;mNo@f`9jZ3^0j6B|}Iz4i%a7y{0l6};b;=wP~#YSp#-CW_vJKR(pBpA-GVwe|rv%Fci zW^%WVhrE3Uy!M}wUK`KE8uQXH>@az8omd&xkg?RagOU4ylk+qBSqySEck4-cR8#E* zvDii<&x4WAYs0Xg)Jy#w-`WS~uHkE({iv~ZZtPhs;}Z8Cd7L@Fjj8!$kFrl$OQ+*o z`_=g!?N|4APF#nFE%(hl(%!|M$we;H;pKkh0T`X z$!)nl=YaFBaNAp8oT-5xF;+RHeDLdE|N0YyJ=I+kQ{0@ZCkHie`LRAn+*Cg#7|s*> z78v3p6Pvj;Klrgn@jjAk+nM3#16iGkC*~z$*k$u0$D@I-arVR7j$*rYFgp8=?JS0M z*P6iN<$7L=)yZ!Z_cr>I|BAI6mna`HhPm6nk?i_2i}gI|@OH5h<0#gBk;FMqJ=ds# zW2xU6DMp?XsoSR`Y+`DjUChybxp(Tf&V$^}0 z%m(#vFFx~e?Mgqy7|C1ZVgIcuxtx0^AH=4w_1zL9vDnAG+&69d-ZBs0pX|pup8fEg z&wgOj*Z1KqF~q9ZU-qpweQ%jZzJI~ZJ*)1%8KYkLq};ZX-1o~ljYlSi=fOyhFh+Rz zJG#9yE^`_An(Ju2;$4};ZAX2;zRHiUVw|bLo+s~{aZ35%SHJqzCkA_}yC$X*Z1Gcf z-MrzLVCd_*a%5*64E>NJ-;sTUp+8bR_p#;&KlUiT99gf&XEJ-~80MGXMb6*h)z0Tt z{d_Q1iI3l__IHq-Bemhjpr7jbz3Tifb?WJ3Tw<83`8!u?C)S*6O&(*{-KWY?obgC} z9L76eiGM8^)N1`lvi4;swqjqw8pYOoCg$!tS`$mZGg6E^zfxy?1(%r2c@%TBU+$gy zt@9wiGr7nuM)heYcklnTbrK(WO4fU%zqCB4V>YOV`^cG(YghUq#z@{O5Bsmodt~>n zHhrxx=fotZ6eBq$wv3?`=2wOoiG3yy-=E67W_jLMo4&peb52a=Ax8ECTgFfe^D9G) z#6FV;IQcTCFMP*mo__L2{`?bCTl!U9zR0~joN+evAtrGWBhSfBA7LaOx|o?y-M7YM z+{{<(nNNqkYx6xelSi1dSl5m)&a5Zb>-Zn*X_pVmiP=-#wZvS}&0BsfM|NMN`Unq< zx|VUNS3l<+hHE2f%@2O;QG7X)>!R1+vst}#4D-v+#7^(}V5}0KF1Fh6W6)Fe>T@(V z?hFR8=8Wu)ZLXu|Fuse^hJV&zzG=fp|4zOZ4C=6F=%b-e_F^@*VqU?z51q!$7}7~{NDYz zMIPjMWpHdghRWnC_NChNMb6HTowdYd-@0dK%NX`rz2=Pk#PEHI%+EBhE7j(80l8E2 z%r`M_od>-5Yk%*{Z}{;n2iN3^+~+43?R0%AuS^V%t+5{GoR2WfCndN!a&4sRKU&wm z>Zkw5CM)kt9+|p+$p__x$mF*Au1b4;6=QY3kso=C)<*X1hjl#U%jiH}f6r$0(lN}> z&$XGvH~V0$au(aIfz{b}Y-cgdxz=QTqxF&9I3<27L;p^$#N*S&YI_M7ulV6pb6J`1V6Xa~$pv1pGN@_APoI?@q>ph& z@{z|UkJr8Xk;mvrKRR|t>qq#j>qq+>t*`ELx zvB4*h@Y7ixkF`e!9vyge;L(9c2Ob@Gbl}l}M+Y7qcy!>=fky`(9e8x$eRp8=T$JBI zcBIefd1?3T*LY-NRNWqQ80kC110#LU=qngChfzPQ>8j-CX+QSg{I2QW<>T-3;e+qh z{q7aNT8DpY@TJ#tuRc9+A02pf;L(9c2Ob@Gbl^T6c&_KPv+R#@aD9extF@zNwzt}6 zhI5pI>obfiYdpuz=ep>l0k`O${f;nppC9|3=T+I&y&9`uhw-qDIVC)hzo%2VZSq0* zEImYa+;)UfxvR&a!${v59vJC6(!p?4pB0RnL-xZus`R~`a^{n3#5@;2&iv5HquR7* zjw?EGM|J#}&;IszKL>d5*C>wFNq(IxOugc_pF}k-_1uD9qz|w zs~l18$UQ0i7^w|k`Jj@1sVns#VbneC`G_B{;DJ$byE#}L@5gtBG0TCv$=y19Z!6(? zb-1R?b#SyJS@XH-NaBO9IjqRs!+mNVIaklPm3{CLf0Tc15lbc~ES zlCyYnuKSJh8{zOC>)!uq!{;d8zZXKB?_2b0pWzrcle744Kd?Ic2$)Hq!CqdP3~c%WGKf z(d`3Hw_nDIwX?@8`izV?6q+`GYi^j^&)ix2l)Km51Kj({-6K%|9fH_{T;B12c{#} zK17{5=w;p$dXK84uGzZrV4dkf<{(xl&7)hZwzIj&7vK`sUnP0KGpgOW+zG5R{UC0J z?>S`wq$JB+Kg&AFaI8Pt;t+}t{;vQW*cvmoLo?zuL-_GV*m2z7C-!O-^>N}e2Z#+`lDCVkN zZiwXXRm&sggGm35HPZjfNy+}|$n+gy%eX<+$ZmH?3Eny`Ehc+ zGdi`5;`k17eh)eQ&(eS$^+(joiH#v__?Na-?SGt}D}irXFy_ zn8}*Q%oofvxxhulwLYbGaN!fqT~}%c>ue6% z*Oh6%(hu>f6#pm>C_49=;%4tO2x?AW?~jC@D-dtq=6)LO`) z%LCsokE^y7J}Ga6PoNW%`;IU;KN-d&M;PFZa8~25Vs!gxBX9CEN9^d?hpNM2lhan6 z^%_&>x4@{FD|6~~zysxxDRW(2$sX!RFr4pTfHUI9D|j`J+m-<6s^1b)o* z2JV^sHucI`>4)nxUVg-B!QpkZzk99nV>xIiEAM50{Hnc+)mD9XF^K=z`7zf*`gZ08 zuVp-a?VH~HuE#FUEil;gL+7^Hx3jhI{^cHDZtFAO9bTL9#3jZGPQ7OWF9-Ko_+-7E z{=0wo?|zz%F6N%LtP7p@&F4S=`8SfeHIhT#OOAY3`%r6gFJw1nd5rwA*L{xWavAm` ze%24}naykD%l#>sNBh=wV#Kf5R8<6CoJ-eF)H**gqs z>Gta|M*Zk~ck}3S+p)>_xaXG(j-)=77mhGEUl@ru!XW1n&T9NsjBX!o=ItC=`%wFy z+?M+>t^ql&@MG~-Flt^abLwTlCmHPLcM-L*o*dB_Bzxq1<~zdxr{l+~c;;cwqdHip zKBRe@ml)cS;-!DC6I;JuEzcXt%0sIkc~slVe8wxe-psghho5;I@nhm=`-8Qb!we^% zAIoD`crW{>|46T#IQ?cAUEB^o=6vl2d#=8I{Cj?hM>bMfx(_< zIc4ViA!E>gctFREvGGh;E|WETk7}eo{bs)SfVta`qde?M z=7FsnAeVL!_hgemLy^;s9_^;%FPu7L*-krUx>os9;e&uxl z-RoYpTf=kA7@vgdk!-0@g#GhWGcukzzt^Yq#N^tQ{v+*8hQM*KK= zY^Sq-+B-g*>z!UPI-hErJwNud2+8}d8|lOT^_A=V-at3fr^YkS^%cFts5Wr?%=UA; z`W($)3dUdj-p@Qe{qvuAH<%yg-mPEd$6x&77oUFq=YRg)VBYO>WIK}w$AbHNcxZj* zD_51DjP}2ZVZ8fQZk!w$-%%d&8tkHfWJ6s0t?%YYQ0Bu`!U01?bdmKXc01|M&i1E%3SaC6;3KAge!EsaD>6R$}k=|!XUR1&T9NsjBX!o=FOZu8C^fMCAXCi z`y41a?u^@34C2HP^N3@c&#t6ihJ3H%gRG_97$p1UywgXq`kHgckLd;R`i>+R)==}U zcJLV36AujM`p|!mhcbg^ zIU4L&bLi&yqN#l(>AsiGTKIfA;AtHwNoW9v+Jk zH-@WvkCgeQeg(tWBW^5zpXHML_}}=-Pe0{=FF!Xw_WpWB($5vY^vPpzq}%6w?{+#k zTr0Y@Y6J6V-(1h!k96XWeCm4cHPdU{Y<|x?=-=ufk5ym&W8qKFg1d_c`=g9G@Yd<;TYR9C{VU zW0~J&u3XjQG&;gpFpPE2%){r#a%0wJxxkO*!QJ<)S$ods$MwEl4$w(@+u1uC_hev? zjz_i2H&!IED}L!y_Z!WLA@{DPY6GL%M)NcMcnNuM?k5-G+#U`3F#qm^_ARKPWy&a#ov@U;T{gk3HWzIlGVXb5c$`k}DWohh}4jL4WIu z+_)#3eJ<6QI)4cmx8km8T-*sz=BA3Wl+{pSdW>!!gSR z?kh)z3)kU#?c2TP+*^-0UA2=#waw;WujXCv@n^U@dB|mU@|f+NYdd)mx7&~0Yo^~b z56}I|Z@W2pboP-AY+_bk?=z2jZhB6Q>|H%0-;w<#V{k0^JodPv`}th$qhs7*U>n)H ze(;{?_Ukb4?Jzpu-8{e*$6i#)dbdBe5w}gQs1DyiCnoVm7@VsN; zzxJ*UY>upPS5I#1c>)JhM>5B6gnbK)5%!Ut9y#QH%+-~0U8FW7_{OJ9pHZy-<_JID zi8CIVd5D2s-5lT_BhD=bF=88s)Rya4`Ehbfc_MkJD?2~ekRv|HHDQOBCT7j!D2JUq zj<81c?C|5{snnA5WlsO{2uLF{k!=~ zz<}4*(dBw%lV>B3e7kjcZ5^H8kxibBJo0tj;{X@!xH0ylV|@j~Sh={ISSc??lHV*B zxv?_&u{NZ&-&=BjzB=i!d2sHp4r*KV z)o-;Q`me}a=fN?AV|bpSdt5tvSJN#p*uUGa!^Zbsd2kH)Y=7VUxXZ)yqvnUb>&GoH z#C7j4e8*>=e)32D{1Z}}KAjwK+vJq$@Bs9VS7LWvf6f<%^Yp10<~GYm{820zHJ*8n zl^o3FiiF$3f91q^KdQdMkFR2!twGM$$>f0}p2)suV~sO+`LXuVp01}H<$;eJ8JU=A z$1gFm_S~o1JAMqeb0ptXha1lLA$iQ=#Ys8hlUy5EOW#%9JXZbnyPC@=o_t39c*IY& z6RUn!?qJBVkNB~lZ%2~j$o}<({V3M;dt%hQn6n?(z|p>4OtrlfjGEW(TyA{z`_;Sq zR)4wi+3#16xbY4@K7+w|zq&hedovzJDA+J z*K=Srmv5{{FjoB1XS8o;Ck}owtB*BeSN!k+v)gNCdue%4$81nb^0%4qZa+MqW|*$+ z<^lHXIDngVp_4zO+VSnMZh_%`?CHonv>~zID-UYP>wsK?K34mzAJv!m4r6qD^mTt^ zVpRL6AFFwcYN@#JV(t}@b*-CMKABv$>hJ+<#3a@T!*e2WMi?W!=aW?<#d#6`EsE>B`)mEiGM|N}f`_-#)iMyId@?+!OuVP?# zEp_9N9Y6N^fb6(w$B*%IkcSvuygal@e2F98SC_+#n}b>N*vY~8ng{uGxzrkF@nF0} zek>R6c=rlF&hJ$Fd&o-Gyw|1v`l6?-=l81pJ!BYOT`^A}$YghUq#z@{e z4|4TBScz?9?~bXy<_u0^RQm|Sm=!s~VDGx$D8CMa_i1uZ_0B%J4(RK9%+Ko1POe=Z z*zjK&v-rHG&GKNM5l-4)13#voRU0c%#-Vl z^yG%AMMDqzdG3JM;Q90?D({5W}O>d8~J6N}xsK1cJ68DWUk$rXO=XWK~f z`IX0mJbYnieH5$x-WcXnU(r|cNS>X#_$wItDsRb;?Sqo*23%3@dL%nuxr5QglgkbU zIbOvX#gj|&dt%hQ9`o1N0nVSJaa8qD z{LwioKb~=8d^qn%Smem_;rh(j`Ex%zCZCnQSkJ1j{@|~^R=fky`(9e8x$(Sb(?9vyge;L(9c2VQ#z?*07OXTBpz ze@AO0`{?;GKM&(M=+QG@*GJDxS8=Y!-ztyU^W$5bzpv@}@h$rCz<6}vW9R_aL7oZE ze&?9)Rr`GT==t%-(6dL4j}E-g4)~r$?u^y)_aKw-K+ggswSHY<^eUWvon|B!1 zuVUVdALr-E3eR5U$NrA5e7MSrV;(Cxh*dH27^91++*#k{$NqjXd+Tm+|Tk~V^a&&oCTlIHd{f_1tr;a>|V?F(kV*ET%uHaW6 zW0iINwa!=gF@MjudU;tsDA97n78J~9CPx3V>;xW%8uj8N$WYooHb)R zS@FcmJi54%E#tv3Cnf7Sw5QBh+!+T`p)qW(_l{~KY<4PX8d0+MC*g00Q^hXkd ztgcD@M1see`(1rLc9r)rKE`r_9(YSzE;d+gx^XFsD&2S8_PZE3=N5fZ=gD z+gEE)9vxG79h{f@eR=Rm>QlMk2*Vr_ufrhzzx&Bgy&0@me9s%~j>Kf$x$7egeACCc z#K4w5tkoCmBO+s*a8g3;lAD7MOv^I0vr_sU4uI5rGwRCjIU?>f?$ zilIGy(K}2yGP!4;J(lpi5s$2PIUzW8ZDnpe(p<=6CkOWt!+A>hbmi2!PAvPH1M?i| zi=J!h=_3v@`Elj1Grmh~w+_ZCPKQxr>->H2WB4S4bCsNTU!LPvFn0GG`J2nxTEu+` zwV2C?D!r!7kLAQlIdMvSiRrMkv0nFg?YX7(A-y;3BzblA zv?bPV9T}ULRlj#0atCDHJLfBTB-U;}GB)?E`n~hm&HJi9=fUpJjcQDuOX#a(_TJ@q zz&C3Dv9Wekx5u-_JKy1^HJ8;K%yE>gB~yvufs4Nu9@rDb5tCB85~FT^vT%N)5o~Pz?ME4tIj%p|xtlyVrE@p>;4IeqNGC8q2 zm`9(vnTz`(yS+#C+|q7+NbfB>$)24(ZHcv;W5yBoA*`!JP%f}YD}H4;2gzxJZk?}IpHW?yd4}kk8w(D?6FeoD3{e7*aM7;WBh6! z;t*#YVyxED^+K%Gp42*wU(JPh^Qg6eHOm8DC)Z0(*y(VCjth=3*q4!59R~68O!-H~ zsJkYHc8-<$$j5bL=aU#CpAG{XxmSMd`&mxxF;!}xeO})JW92-4=|1vfxF{p9@eh zJ-80$d`Bl2#?#ln=}kSmwXoL}jBo$!@BBpWNAs?>qnNt>QZf9DN=_>um8;G^9=my8_0RJFeRcSLV*guUWE~s}ct-8LbJp<5n5ug`jj3{l zi`^m*@*3r`l7n@bOXY(ry%3|zWjBZ1|7uSYOMRuD+E3;T^O@B&;&KxSx4fe&T-7SdX0DPddd+7KE|cgZr<4w-~U__JO(2PPtI!xdGHd0 z^--+$5yo4j5C7lf$KK!9=f!eUk5R{uT}yt9%{dqqmvuT=V>{n2hYyV(k9d0WRr)dF zwIlm3KOW(vojiP1eq8x(<;SiQf5d<5y0O}47}eL9BYvFs9oC2(+0#cIUw9zrWe#lO z=sS}7&9FwXojvi?JHMIDxRf*hZY^WgJHKlC%J+TOo1bkUSv#87_?4K7`I0f{9iLzF zPdRDw)$a4nnvBP1#ErpXy~8oy{oFrg=GI}53pny%eXxn|8gp}h4(a|#Kkqw9E}cDX ziM5-1#wKRf@12MDx_N(Fb6~!bM`G>vBV)V$wcR_9-Mp{*dmdc*-QincWE~z0dtT*? z|A=!HgLt@3-Mh~5!Pk8|Im%_F4)Jb*0anGiMGp341*`7Qobg;3BW|1AQ5}weo?MU~ zjxabE7{((z4C3XPu4l}VP7X&tN9$)W$kn{FC%*ra+j39pF-%;ea_mE*)7$ZEqrdg0&kW4N*$c@(SN zJ!d%ju1N6Y8c60Vaq06uF)DXGnrGhXM=^EXxRllBXr8g^M=^EXxRe|V_#_-uPAXSL zVwZ1boVxKdZVV3V9ggw(n1eF8@d$%lJg3-WG|rx{)_O)-AF_V_=N#;ucY0#&=8~}; zwzhlc;r(*nBfEFXl{^w_w;vgs`&RwldF5n7^$@}BMk3ZQ#&xd;a>--ES z6k{dUtjq`gWjy*023X>Vu@hfo#ZazbRSf2g=fW6q+m26i?~gsXAUzyma6A}^)nO1X z&vZRwj&yQ3@;O>RgF&w5ZBM*@a1HSoD&@1xQ?LA1jO5p1Ai2NIIeNvQp4ak{AJcC} za#L-|U9}HS!hnR)4=*K7=HXoTn}q-QmaJajsRqp17Sp%cJw} zxbZGVR~Hz*>ht5|qBEWNx<2E=)t7mP(fOrqg&*f};W*jbQ9td4(qk*P_4~*^OUAD6 zNP=O!apGWSeH7c-@lXD-8xQ9D#HgI{Xx{bUD5kEzWDJjme6(`o87IwSS$XU#PTk); zkR2xH-sqeY3!h*7#b10kJYPp~-SN4V&76?+Ge76-m-9|ftlj)Fw!_wT?>xL;&U<9{ zPPvjtV(s=LV{_lC-#d?;yg7!uKjWz}c^;szj(p$U0we48Sk&G-Kf}X}RUTm+<*J3a|Fz%CcGo(@B9tQ4=q zAReCSn!YnBPeGE0^I1%{W?W)m>wG&KzT|FCeD5c>om+8a)`N}nPt`l50;I(pF zuOs?qJi7LbuNdH_U&ZL=`g^~`yz`sc-X})ou1E7)$6x&3&pbW-^PhM(R^}Ch`O&_0 zy<&Xvi(h>D`Jey!cVlHVB zux??i=ehTUon*hxo;G_W?rM(u+k?cc`n~h;emU=vYYxm;@<=Rd-04TgcKd6)XCABh z>X*k~+mWm>b$%4jdX1^`GZ-1;v9RY=p5bA;7)LpXcO{pV*jZn8-~lUomn$^~65I zk1HpvypcSrE$2t^_<~VioNtwfC2rM;ueQ-#K3Dgw7%REgJrdLB$H_sH!&Y5CFzQ-2 z$KM#%Q|~aUf5n{f<60-j$I-22AJFmD&ufI&9HfIa{P17Js5mo>is_vFyw}#YE8iDJ z@=mzzY(A=qxPSWfzxIT5jOHVZ+!t;;n~yMv``MrU*(ao9G#_Dj97ddWl^=6H;5))$ zy*p>ze}x-A`0+2l@95j;m{vQ!_8IAYODBo#>}g9Z^t#_@j<5I2d5@gf>MMC9)`ytK zs81t%?K|_8k?*^gf{`_P96k0`p5bA;7)LpXcO{pV*jZn8%aKopW!=ajx9< z2tyq2b2|)jc`dKx$MViqNe@8Q8?R z7e>vC`5*q==bnD(M}O?;OF!^~ZzS{1Z)SU+81UJA{yWm~8`W?N4ESt5{~hW0jqYAxF~F)gd5@m;80(&+=f+}=a54{kd~S?nK8wqIZmnA%(tE;A zvTtWkTVn0znX!pk^?T>x{c_$T*BqFy@d^M`Byy$HpWM`Ut~1 zUW0^Ve*NoTKQ*ZTY^-rtFe*O=i_ZtS&k+Zd!yt)G|6Cj4RUa@$FY+=s<8nWB;?72c}Zgsiqb0psywnF*pu>4slGo z!TRi2i-8{_xv{nphOzMDBW`@e)y2v^Q=ipfKW{C~3F$pyC)u~Nr!BE|^UT=9topt4 z@P0Y(k!udjSMo@#-F{?jx4*V~=dqKw$MC_Azp-l`Pmf!bM_leGUc4h5G8*!x29|i(&4sLBcV=`qi(V8q|L_*0?JeSNJiUhR-wyc_h!#uFN%c*OXu! zz4S$jlalzHqh}2I(H^YVm?O-ZPwvt2;}w1j#){v}XM`aa?qWy$*qW$SK5T!H+E#j; z7{qmR_Z!JB#vH^P#g6P?e#rbd>w@3%40(kUXI$#Hz<}TK40(kU=iaHG#o%}zjohcx zjqg4qT^q&0VOO~E5kCgsy^!=H=iUDIwz0oI8}Od6ljPCa)0SAfd1q{5R{h?2c)y(Y z$TbJ%D|sZ=Za*@%+h5zg^VrEdk0JW%@cd)1mx7UXdo1jEm1lU^E`~Yy_m)QTN-it0 zU0=*)Hy*57*A7OFH4o(qmKYun<_zaM815^E^A!ww0pEig!})UfycD0K*BK0A@)_*W z9xrLV=FatlelzUW5{d$j`bOid3Xe!;x=+v2*N3z8up!M!wkb<^H|n)txQx^=G*B zgYQE#S}cg^~c-V=5b+|Hi1#QIS4@P0Y(k+Vkim3}1FZa*^CJv({tJa+OvbNp*e zov-X)W9s}2M#gw7M)%n({O2kL@jD~)ulgvLl{yk@lu!B`=~ppE@!D7RPcCJg$9lCV z#*O4r9_GD45hv~Jpmg>f+Zl`%e$272$6s5uf3h2IXCvQ{ z{azRy?rX5YZR^;rtHv|m#el=r@jfti_q>IFp36J>(can5Bj0qSARYvZjGsP{bsVp)cGvdwOLFa zimw| zz*yO*`sB0WGdg%@;xeY@^jhYx?fLO*IWCVnK8z0db8O{@k@X|XM+Y7qcy!>C9r(g` zeCCPqN$c^0vC#S4y-JBiMz8g=hS^GU_ zBsl!;0W$3)9gMnm3yj3f+S0CW9m!MA=-`}*%NTQ6mEHFkMzPhm`sF%nIajYStMgrc z?C%PL&v&Vv=l80sZ8V=@RBSPhzF!TdTxBHjIePu~|KYECnvJ7)*8i1n|F@rJV+A99 zs3+%VbZR;imoe0o^N&^Mc;xR>?fktFdNw*gwSP=~{hrVKTThIxX4gjRUChJw=)j`` z>m7hU{geOeC*KV8)yRx9zRDdu_QiiU#<;q-^Bp|*uX42~=2GK#a&WJTp?yb!ai$Ns z$1UlHIG#7c?3oIUO66Pm6F)>QBNO#ciOdz1xEF+ zG5D;$j`*>>H2E(&_|>*MKZ7yj$MTBGS?XNBu19j>pZff-ewq#aIt)4SH-G65KFx-H z9Y(d0^Dh6Y801V%S2!{`Q`5)Hkv)GrCV75gt9`F?<1?<)^{eqOZEl_`wU2U#`|@|e zDhFoHb+}s_VLj}R4m>)r-U0cOe3}^Kkgxnt&+7>EcP>(ogB<_l*^SI&hpBv z<0W8tT+a5@8k9T7)Z=qi%8CERkN)pZ4sBPlAMwxA0lBT5vE!xqxQ;YNZky|)_>8H# zmCBFdpp2)V_!Vy?dhUaAL!EVE7!?B_*DFq9h^0TiU2XE@EByG+eD=4$`~Q5n zw;~U@Ze7!UB*7TH5A?-k4s%_olq zdcF1@9h;1wotwFKg!w*uSnIixt6UiA;QFTiKI?q!{c(5T4}b1+PiMwa*H$sXS@AvE zudd7EL8iS;7xju@|n5x_R-}}Yi_~gj_I-CDz zu@Ve)eUTM|Iiusb#EK!NQk<@~Rerp~bIC&bNOeFa0VOm1Dy zX-j^53vLU?9`WpICy&vdBb*g}%=_UF{n=mtX4LlvbGb*omroA)BY*f`c>2m8_?2%4 zdL8oQ$}^Kge(HPwqo?2Ug>QW`(Cd&VAMwmvaFDF+>@(!7?aVV|>X?l@FGo6c%*H4# z?bI05R3_nb=Ib1v(vvCLWb{3GV4|LAXfGsH}(A2A)4c)4fQM{&2XTT9Kk%h$Ye z-;VcU%l}`BdB!~SM+cs}1Mt!x`q3XdHHaPMyMqhHN)EIAx;!cul!vWy!cqJg9Jrrx z%3VH4-o`3dYoR~XQ)@CFd)LFxHp1w9tBu&M7uK`dlbVC^)>HSb^UNdrs=k^>#XU2Z zAH$1(?~nccHzPUnbMa$%u^gq!e5QA%#*ZO}dxf){l302)i+3&WNvdw2@pVm3DyJRs zTK&O5A7QX2X2)|`7eh?(tigDsHYDd=o@+;(@o1eqj(m>R!Kg2}@lmYn#H*i`I~YDo zu6gI2YaN_(#8XFhFkDOCJL0warW|45Gs5Zkao(rHi#g9`{Mhpm{^7m6{83)4gd!vo>?jyhm|q&)Up=mUqU=jpTbHsb9vb%ZdHD z#7OFATuM2yKbIIu{W4aa96iU-Jtj(Q)`xB!(lva4{P+F3H?c@{F{;e-R6MZ6bH7gN zXWT6%*fm$?*mIxEGuPn0TnCc_YxDY;Yl->LuXVup3Gy1!>yMVl`g0q2s(O{VhCbt~ zJGgYY?!|m(zj~yS|Hz$EuJE4`2NP$7i`^m*Y8&OUl7qc57daqxps(~oj4qem9CCkq zsa(Ob4)f1g^p$#QKbbQUi+R?45542Ya%3btndity9Jzbuo3^9e@r9e@cVSbnyyXbb z^_AyX`c?^d%2C%><{6K^5_8sv`-7{TauzRs)>Q9N%$?(zSo#qQrycQH{J_w^!@%Yc z52+2A7-D7K8Rt52^*r-l>8vD=8p|`{?m2Pwa}GwncDXS)#EVzuYA#0@73YW}bFH26 z;}K6KkL0=|J-KuBIfHS;k3El8WbzK~6X9I)$5oj;v*VC*$(fw-Ou0qYlX=s_qgPj3 z=6w{;dRN;lZ!yik<8C8cawu@jf5eG9d%f>B{}Ctd?8I1ylH5ENF2>lA zBqp)M8TGHTXS}tfWW5{dQ+52ioO7@0lWX9?f4R?>2S506?eewxkAKfMJvl_;z3~sf z`!~EB^}V9sL%!|64v*d8t2?-4au0rd3m$d_1AYZplXH#a89pc03ZL{`C!emK9SpE4 z&Q5%dwGQP9R>fe>cy0{)dPR2pxIRZd;>U7ZxW#DTGs}~8_^{uDt&+7PjEWDB;C;Qz zk>yc*cQ=Rjx=%OPH{;Q@cQIFO-Y>xO)RoB{$pLhHHtV4gZS4TIZWFp1XOTRW6zH6`o5T8H2tOmoZ@EYsQZ&M-_*7^|Mlp zZjY{}U4C5oY~{zUlSk#dyYn*`BYx~Tg!K7d+D5wf>)azICq8;^oW9;)!l#l4A3Zlt z-w_vs>-a1@W#vrtvU`ym{L$}r7+o$q@#au_;CuzEVlZbsH->#he&=_7 z=TjZI-sy5)*4*kKs^q z+E0J_(@#0H<5Ty_`7JOKZ&lBGnyZ}Bcrewe%i7@A$@kscTscsjac)|^DOU|s@`$J>O&sYwmS#I*Nhv_aEYI-udJQHm~mvTwX^5P zBhE`6qqUJ;ZteORj1_*|os-p8ISluVa;#M;hn9=Mr{q|xQVu=hV(^ukJ9F$E-=L@Y z@?N;BY4zS%ZrRl|)4? zSzKbIE{7Hi=^m~j)iXBr(fN{i>90PD%X1721~K^8ft~e?QHSqx-OFcVc^yoCjGoV$ z#Tdz7`cFTt#$!+4nLfgp#kf{u?SayK^zUTFV9t1M40D4QS3b%dyFQ(){8)~hzk7^r zg&$WgTj$9+x?EV#m@j|%%TJ879bt$Ihj7S`o$J#{uK#d8IkCr6sa;-pFN_X%#pZoQ zN@|jq${lkZT|0Z!wVaCuUj19r`k5!k3cg83}doxCP z6SK>Y^ImGCQ_E;=)oxFbuaO^D?h1$PY%AP$7H2$?Ugo^&9nMvLT)CO|?c68Ht&XHT zO+W9kcKES8O)ggXF?qo=cu$PR5qBXkc%pfBGI=D&qrQ?`nroFa9(hzlS6kM#Qo~VQ zD>c-bn2+kBM;sqUm%ld8Eqi+8Zyd7nU9pf?dz$%-u=0J9$HUkvXL%jP%aOZlvHW zH<+*B(aYcQcmK}2afTzG-8=K6mhPE&`W)$8Q+B>z4nO|V5B%Vp=RDlYrtZ&mu-7&G zh#loK!xaz79$+BTzPn$|qjD(kv6W|dn6b(ujH4V@aj0#Cb(F(VUv}fWTy`)=|yG{MdM<*em=P{@``P_jJx#?|Q+t zUvBJi97%YxJTI|sfsuH-dfwxZi}}C{%@>gkW3te9%7+{L!8h+-S{1{(G$4xWV zJ;6{%y59&RebvPf|0r+buJYsVd&lI_-FuB}BMfkO-HRXBXW^qfy1YAI{Z^hIcU-jc zRp$E9hg{d5wvn#y8H}s^*mF}U@8F)P<5?>#E1)sGh9Py6SN`sweB~>Y_GYN}eI%=piJ%ddIEkCEC&G1U$qWPI=M`0H=@F;ZLg9nJaNWpMAnXK&s^ zjACYX_J^y(k#{&d{o`j1aOAT;b2z(q*7JrRH_yb==Sb(ez|RF&zh6G%vD^zV{t784<1(U*N=GNQ4ZoA;ZWNMYb7>&Q*n0VyIgiK>i+gXxq?+Om@}Rm z!@kOk>oerl=f}y9<-{pjhcmD6V>zvImD?siCZ@(?Tj9s_#OsFd>73iMD*YUQ`+6Kl za)%%D+1BBVFlt^qbMGgRE1Xh3sAMm7rI_$bb+M7!TsL2i)ebim2MLDrs~F}5-Yjp{ ztvSy#d7g5{1@%cuEjd4x=uQ%Eyv#Rb7AMk?^U?$C7VVUH`<$I^Yoa_E{D^Hq!*Yd*-t@%VMPGYs)MevD1~DnEw9G5C3~Ivme^ zj-=0x=^YqI#?EtO`5-(L4CD?cB}YFmyPr!R)sXA{d;lp&SEk=c&v^Bb@5t_cDM!8| zdp-+SANh{#To>vX`C^~(W1st+;j1%T@sR969zJWzwH-bVM!qVCs`toec-St+Q4Zo= z$z>&W)|cIQuxed97&X=&C|9s526M)9V`N|9!f;49GV+KU8{0iYPQHA^jlsDEx4i|1 zH7S!HYa2;E3;6lyh#x1<9dX+u49^eb2xFArirxEyRZb}%^yez}YTXBhCRmC<1&-^lmMNbh(DIUn)J zT*trWvdb$gm)xnN@+xcEsY70DUDmWyhrC$+Tx-f6b@iEd`gJ;fE2GACdydWvd9mv? zw+^cw2j?|5^XhQi6S;yRo;=uVqWlAi?T8=e=Po?XXPs$xuaQh&c``EXqgd_f3m=ph zr(NHXOyB(6P5US|?eyX5ccT;UW7Ai8EWCAxukPT|^W??qJNwn+RykDO8>64$VY?Vd zIf!>9mzCJ;P0eXH9;_=EHP#*|SMsSC%o)#(k$pvn6FchKXrBBSE?l|s$H0%d{?E9v z$2Dc;x3|C;9ivsd_XT{0spn^#HrM5Y{5*xhKI*O+qxAEUdFrm4D;yIH_i|l1va=3` ze#nvU$Uef*AE}=ESo1CTadJX*@43!Md)o2iRXi|OeMf$0F|rrdl9Frg&LDQjzwNlZj2+Yk?)n%lfUe6O1UIbK1nZjIHg<9_%U;xL;L3YAKU0z)+Z)#1Ya|mR z`SDqdx);yFeSYmignKPX4V7RXs&Q~z9uV;1nvA=UH zN0t|BAAPr)ec==GD&(2(Mpul)Q%~NqQt#^X<6Cgs6%2l+?m2?29DAjvnU9};$o!lg z+2w=w7i@LcGIm8bKaQ0cf}~xHgjJo99`*Pg3+`DTCmUJa_u!?z>n+5S|9qn13Nr+hp+D7 zl40^<^_~4{m2xQgPL(VC=PHJrYgN`7az4ssrH+cz<&!=~dKZuFD9-g!ymtLY5TDNwI{}nICnqnSabt411-HEgM(xW>VsT&`m5)>8e?^kdc&@LuDq-e=PT zY&*5S1Ppk^Xsp&EU+m;6hJ3M;D;T31s(psl<=@$67+wCIZHD0*x!3C$HrH3=m2<52 zZZtp2qpQQZcIp}7>|oS>?7nBMH9u?BU0apL(F^s}SpAH9-x%iekoz5w$HGN-2eG3a zcW}X2*?YEM&11xQs=db5dG)XJ>VMC9-P`#+_k4-i)_o*jV(zn?$Bb?ZoBgQ_z0us|Cm2I@aVv!1CI_oI`HVgqXUl)JUZ~`z@r0? z4m>*W=)isl{JDFTaCb+pbZJjjh4kP_%cwnUO8GQw#=3veFvz+=2 zb!FZ?L%#a#*S?;`A$}$Ina>%Ft{2wwkdF>LI`HVgOXg_n*k`$Ntx^ed6zbJa^7t`M&RZmxuau=kQwhgC~vt zK3D$T>U{oNe|LC>k#VV4jH~?}@knyx|7bv_Px3)IA+qDPBaFmDw}uWQeP?)Jr0+-v z!%=-!Flr9j59^rm#o1c&!n2%Kd~-pb#UfU`v+@i^_JR11<;C|pbDXKWet-82M*aTo z84O~qY_=b?`~@F#!&r=IEXEnQ?K0i78qA6svJ@7NNwqcSi#?nF#}o%I{$Kvf-|-Ct`)VKH!#o461N$ll*Rp^1!{0ctuVQ>CetiDQ ziOCuL-}$S5=*@V}&t|!C^5cK>5B};C<27?+I7;%Ad!9G&rPs=j;g5``pZFDTBs%rN zA^pFOmGoF$DF+>4RNm@wn(W_Vy!+?>z&t$s6%SxvX($M8_{ILk+k1H%`ey&XTU zeArke@#@N>82yxqF^kQ$BYq5b^=E#ok~~&@M}B88$d$O+tK%HI+#|WBXhpWkNtaC%EX@OaxwVP>KJjb)%cE!UCkpo?FyH~hdertyMi@}wbx+m4C9dY zg6AMRW7gLroG#X!$Y*C5SLJFyYTVKM2$%So&k_D?{lSlC-MRf5pS^NpBs(NOh9^JA z=W}vn?(@h4DL;O#92ssUKYI?23}0H|$N4wAR-Ye_zMsu!L&f0uUU|t4k$gtWBjtlg zKf96jZ>h0&+R}G~k$a+>f5(qk@xVyjtOL8boZ;9jIpjGfH%yuQFYTR9Eu9@3I4f9S z@Z$5ab`&A!tfD!vNErW*k(248lNlQ z_JhCm69ZfNjC8K+{GT~_&pFfaB_{F2u+EhJj4f?5jNklMzW*u5zx*fvt0zX<&?^S~ z2d+zBkGP=g_#W|NeXAVB+86Ty|7hTA9Fl(S)M0MKbjM}|tBW-!^TFq6)cwJbn;!MS zb^YCcB=s|HB#9rbVHan#PiN2XQ;(h>>(j+!^Lx~@=g0btFt~0xM$eC3A7Okbc0Pye zwTav-KfX6duH1OUk6$83hF>Jd*g5vZ$&Hm~u*^X@%K^UC@#7tCJHqIGjw;XQn##V! z&hlGb^BxDuXR|yqr9P~yE7?aKnLaxh;Lvx!qb=qtUd zJDgIS5x1;Z)WVC;z31S^=2s;-RNJVA^jqP_a?nmz-b)_%ReKk!t@`d_5P!svN1VfR zZ-skQE_sF{S1x&lBiEW%YM{6Isx>|MadzkV^?FP_Kb1$vST2g+Z0Ofj$o-se7BnKTBjZViotbEPW%tP z^vk+7WZLns7$3^K7<+w_Yt@=#PaAp`?9JU!h zmUD2Qp668R-oxX|{`Dn~;9`}Btzvbt_BS!={hM?0>M-P^v%E)Z#F_(g2dl=4BgPR=C#Q;$ zeX!n9?)1X7t}o_;+`+|P+>|@}f#K`HkL|?sNIq-fzz(>tIvI@k@vS*><;H#o@rWP4 z6pp;ZOY4!93nME(cCE&Y<~w=7mnuJo$HI+~4zH=J{8%oWGWqsB`7ykaQGf54&+8FS zq?g@2hM)JWjbe@Sc+mgk#~GJ+sV9bejq1=hC0Ne8TF3!BWbUhOzIm?A?w?lH$YaHC z=3`I!+`AV)UU?3Dww7IfJmb8qkw^N#^>V(VSB#aI6@Kjb_lN%MuYY$gYD2;$Mtp($ z_vFT3`2)Z58+axB5}$gnPd&W&97}HeTfXqE-@q&3m#>{4%bkw6+i0C$j@DM~S20HC z3Akrp_Qi9O&m;Ae)Y$EBJnQ)XmcQ`>Z$`$d+oul4{Ncs=RXK|zo^|&^j^1;ujj-Ur zIr2K3{8%6Ju9D9XIC2hb)vud#U59J(o@996nKJE_mx3vV99T|VrRy^co(;>3M{@N1 z_)9JBb-T;0!JAU_s|R~?yP^sa83VC8mp`r+G{L)DfuycR&LC9ztpv_@MC?E>fnGU&%PHwmhW|Z zkhQeu{RMr=d8dzJ^|iP3TE5BJQJnEeFsz~GTkYU7uvZM{`f!Zl$Mt!-e&$Q=){*y1 z>MLAQUvSjVN^{|JZ^n;jc^&03i#6{f4E?ISH$PtCw$+DPuyNn%n)j#9>-vZvbMGx* z@LZZneCz#s<;Ff|9m$R(S8nWc){&%MV(3NpvMzW>jxPT$9-DQ+^>cLjk1(tYuAgI; zzqwb=wK^x4Ro{`{Rg8L$jQX=`wY)knA$$9xm7iyg%g zPiz-6vt=#jkP;4@*Dg6Rl6!k(`d2+ao6zg}XwG#V4(9a-S-Ekp&A6@nxAJ1=uFo+1 ze)WtS&-n2!pM(cjj>{bT5mpy#pDTas_!+htKOV)5?6ba(VzhU$)*?UFcP2kpe*BOA zr*C_5oc(_FKm7VXd2*cne)ZEo{u57*mG4)NI52i|LC!d`{1@ptgJfwGU4Fd7kw<(n`9asSYQx{}R3qi3X`5lz_z?#79h}uX;7^qw%Zuf-NXIHa zt{BeY%N<7T>+ZbX8&H?eOIbVOiR@P~oFB#NXU-fa_~vSy@klU|@76l-VZF191BSYO z^rAjb*9UnEehkmddo1$Ed0o%7kq(Bh>3#Er3c{8AnXAC^asxG{F>UE#+o`EyLVSJsvF zboqDj*s`vyXO_R*i(a1@?0Y6IW3FP1_#Aczd#t|f3CEMqvY-B=zirAfsU>sB_@aFycIVz3B)8OtJmQJ$m;R|A#p-L$^jUrkFFoR>#v{#RR)_rtw|gF( z7~Z2;15#V=vC5BE`6PbivEyGkuNdI);IA<^t-|I5_=ZpJp&)>?A^Zr~PWW861N6H^7KW6S)N@6k(K6~Vkl^-*A zE#>UqHnF+Z^~HRUGu*7rd~zSxclOJ@D?d(d ztiH>SlZ&cv^W)&6{{6ute(YYz5eLrNtA2$8S1jjzt|kXo&*#S4h#~h8C+2#VYopI> z_fFYyWADR|Im~0kjp5U|cEpk8FmgsD+}NQ#^Bw8jl$jxvekIBjIyfwtp-?Ib9jMC1-c z?0_r@t38Mul&D*=lUSy0BBI7EBMhw`SoFmr&>CcRhhOyjj(+z(cipPmRkio`o%fu( zH|(=kty=4;wbtI>e(O2={myAgW0e&{dySQA!Fl1Wa^l`IsKsrsz^Hkxj@>69<$tMb z>+wX^OAK}6kiA%a&6!@W#2JswJjB4RZVn^PEe0`S8;8`E^N+xfb5EvjPmz4aT*Z&Y zpOvgX>#N^Y{5W|kdE_{GZ?*U4Dn`cjbf3$_=8=1S@2ewO_xvLsS$U=7$}dMevhqsD zqtB{QT{ZvgN9v=xYW^9Q`lzm6{-fNFt_}07KjSy@Ig2ssF>~^9^g7P|spB)sw>3x1 znRq#L;()i3OO0KPx2_hBwIM4%X6%`MGdiCua^fnPOAL8KZ@*FPEe`Yu7|EB&Sq{{b z`m}i15!NWy-X=yb2kfo&v!A2boHHk4%>g-s)r%G93`Xrka#GfHZF^x&t-hEKa)c`e z5`6q~9p^KbqxHb>kRSV=SWcW0e#|`IwZ`9ArSE6!d*bA)@MGUAx40|Zl)$t z5`N4)ezWgySt6OH;?4Nsl$&~I5Hdt4(a=1 z#&YAdVV~`zgfHcfR;=i*yD+; zvm0xixzhicHH}Nz%L5-UJ+lsNu9+Cxat*Q4cEpcY-?zdM$wRJ~as>+v4?ZVb{P+lu z_*{QSpTVek?C|3rKh;hi>br6ULyq0z$LzV`DS02w=)|~RhbQu`N8URtiIHRSnMR)S z-dRaZi(?+umHE@l)^Su<=HH6Pc2w6Wf38FConp`KwPS7dksRS1Vf13Pdw+ma59Y|r z>wr|RzKoBqXRpS@bj_Y`+U3RCYAr1+>o&%5jkTWY=eQ?ZIN7TfKhF6TozI-vf76<0 z`p%j8?8FmuB}VckdDoGIC$)Id3|24Jni4}k){?FD=?%UuPS?Un-&S99z7_8}HP%{Y z^66pJJ|ritx#V1{FXn?>!NtF>#du{sFg)P9ey6(ie;;duFXPwa$8ukIaPNfs;n(BG zey189+&iE7^5pOTvJUHcw$`-|Jqa&<&g*yAM9%W^Rc;J_CWje*%xCrNd*oGa%-+mA zk`t#cKc3~ta>(SwJ$;s+in+s&z1FXdA-9F^uAan__Z1k{zbkwD0_{gUk#+U0@+ek+ zbLSeuH)rFFN6zMv{MdNcs~FgwOWio+5kKy6%E}4Tk34d`ql3Z0XJm^T&tMWiee1l=xuzJilGwaQR1M)J`kj-T_?S2^x1Rx8$8 z&S2C&Bqy!cBj-lFC9f4+{OekbSJnf=1HPN@iq+x8>M48tINuek=Xa{LAzM84nTK}1 zQ=RXS)n|Ar>m03XA9}Lpb+j&P?r>xIF|o;gQ)f@*nRayIvajen+*p20Y;xk%d;Az5 z;xZ5VDi4(t`!2ak{Cd}++QodA_%SuWwRX;|e{XH*rcNwtk1*0d^^7rp z`LP@sfBbrJ6(h%07i(lcs-?y<-sQ(FPNt33%xo3w*&b~9Qe);}*_MLI{<1>nWXr|A5?{HmWAkyU@?$vyQl5;TXU4a5G2b z@_psUEk4?_(U0ETtbGUL^Tv<)xsY#E$=ng=koWuVja3Opp5e#xVE@grl+5Mlt3NB` zcIE`hfB1lwoV(e==R>}Lq)zerd?`2m{V;@QG^5Z}M&7XY!xg{Pwd2i}3&5wWQd*6D_ z1AdL3?~POU9dg>#iOp*wmz5vO4^ld(Ozx0a>SEsf*pA%t?!euFy90Lz?hf1?xI1un z;O@ZPfx82D2ks8s9r(O)!$Kceg9=ZF}D*_Fe#Q zbm~{-Y977=L4uXz9eo$0*N;d1{o~R5=g-UgW8S&>`>dm%xBT7Idav?cc;??68~qzy z{GBv^?_9qRUdQ;f-XYhVKF?#W>F8RH=7{|~_wKHJt^>ULJ9FkLt2h~Zg+BUxo|(%W zX8hA9W3SLxds+Q^IaI&V7>v<;&h^&2?Jw7Xo%hGQFXpqCto*F*y+iK3Kj!<|`Psem z{c7G9vzH_f;?aBWkbCcs+1tIw`(tvvkGlg${5XGCyY9{CUbT(b-uf@?)N{@K^FcI`&|4i-Taul=-oJa{J8afYiz&ySN`pf`Sj<3AM-ux-gl{;>&?H^9dHF_dQA*C z&M$xSA8nn)W*=9uYHZ@{>hvxD-K`a!*j#r7tHvhIuD;UCdi_SdUBM;yirpHI*4wTB zwd3;p)~~(x?s}{ISZ>VTh&AQMj7J36EX z@r`eMtk-vIc|Dlxo$1%pd$DQn#cEIAp04jLU#z28CRGq=7g?v3;Bpz~+<*57nMe%#{2_1fZ-e!Xk_ zKJ^yv+xqvou=7l7&An9pJm)!+lWCh>Q~%e*kT2z$Gdet}@}gO+R_tt#Mt-&bGj-@g zY+|oq)!4+@)$L{dJ?X=7ZfkK)A|;aq`H^SZ}-9ms3J{tGUe zr~luy|A#bl?$cON-I$)g^BGTUVpJ?+TA18t^&aChi*tq}%LQAXbE_D&^EJNm&=G$J zE9X~YjjI^i!5ZP&lUh&m5S7JQ%Y$EsPpl$2ULz^!sCeu6o?*=EwDJf4gJ( zvCqPk$%WId-n+(cx1#G;mkT33dB&+RdB)4L^52Tp*7{tjD2egU|NB4u!E_%T>K+vUeQT<|3@U1-e0S{k zsax{q$KFY|oauo47%s}$dRGfSWzPSf3CTVUd}P|?wE81CTeyjVKJsnl(E58EK6B*9 zBR)CegRhBEbEK!$wmROy81>_bAN!0=j*3nmNapL@?06M}wXWdD+>f5}j#YjvuSD+h z16(7nA-p%a zZ|}OMyLvgJB;>dH3e( z^;yBHvDR`1Bm3}!Klp=BGbgo;dP`m_xcHCOommfzmpJ5kbE01UG?63Ij>U-LryNCWcs#9!E}| z>Hi-rzmyw`!RJhmJ8Jj;4tAWMP2gCI`5sB??bTwwM>2VSt;KwgB=yu<81J6su^VgL zSqytIBe@2iJ%6ul-lOQrm(lb6a=o^UN1V7dS1~d_`S3>{ee{HmWL|sv_H?=Mm%si) zPxW-J6$|%eUoKBp=Cg5^AGQTefFhfFWGx{E1no|VR>;%?6VkfVR>;% z>@AGsOFiz`vbT8HELJPlUYx;L;iI)CV$A`$f>mS1IfIdX_~9S^;is9Cnnt}PuN7SU zN9)e42gcP$kK5Mwzdf#5<7=E;RnFYQfe-(q|K~d&pWFQSclX#wYqfgFXI6_BV;gZ} zo=@NrpU&acRy`Qv}^IUczZm%jX5kwfq2D}5h9=X&NIK|YI2yL&Zk>g2DU_Oyx7 z!xck6WY0Hod-ph>PiOe?D89vOd%SWL$Gsl?P+yT}zoXr&gM4^i@m!LB&Prlue0fIq z{AMvSew814Um@k0+A3%0@ngBL?^%;C&+=orFjAhWjn6fD;Xz*JI4gH*$+|}T*gR)s z)^hZDT|d9;n0{2->i8^1*5mbQN%}7@t=A&`XRd91zg+QhE$)xM_2a+%e75eDnVjIm zJe!f8jx+b3j&J3}tucNT1MbVSu=1OHJ|;hwKUbU_vrf%PTl%$hp6%~CKjwS9J11N# zd%}1Yr^bqt{HWNc+xpJ;HQ)XA@-QcBYSrM+jFS6UKI-Qk z()kq$FJ|9ymLK!8*ZcWe$>%kE=`sCs-;~@`oBM|JV~+gvLF!j^#ywmy61(Rse((O^ z^QOm*XSwYa7+$v#7JIwowC?qYRaaKOI@V9w!f>u7N8A{!EBP_pCAsX8&iY3_b-u@s z!J6S7-qV$H5YI8gk>!<@8^1w#^Y^$jToNBY+l-a_nCGm72U!z&@xIDDXC*P#c z9r|$(Sd$p5oK}B&qP{E7VpxmUrzO2+c@0{A+VG#fu3poOWgX&w@=yKGA4~NfZZ9{U zyKvd>eeZkE`&9TbpK*+_Gw<3xDMpJEr>%!2M{Yg8u*r#$?7fh?x%6@nkLUXPz>in? zBs{osT*lasuv)S9Gx;0Wu6MS0px2@o)3cK=vF3oB#cIWh14eJnYOm|h_Mzgm`eHuF z6&0tN3~d>YUhUqq z^*-hE<|X-YYi~B(M zIUez2@Zb|8{^2~ny#tQO+2fVY_u{ehImIb&tkU_4L4NX|`m-_`C;uV8n;*ZqkLa)W z)Q;pj)oaAq`$*|EW8CtuHsgBN)cIDNYxQDV_PjsDJF18rSbuuYSL}e#hG1KW^ob zoHqM5(!ptQ+!?H1tk+fy@Q+S>jmtigJL}Ax`c{7Gb#=al;WIe?{%^d4HaBFA z7puNMh99r~KJs%euK&C8nX|{Zw>CNOS>rPXIm3;4KKU8NwMEZoR!>ik%vxfQNB#Zd z8E(A8k1I!R@nhesuE@$As42N^3#-RFi1nx##3a^hVVIBiJu*LYN1t!Trj#4|y|tZp z$mZhD@8~|ywcnzt7B9R4 zBd_Nw9HLi3bEn0Pd$IDOR{pgf#~A~z{2kfj_|={v-e?~Tn1S#fy=fAD|*haZ2o%9G{AeC9Hb zUoW4E;pa4Vb>$J3YgB%m&&nP*CbpGJ&0{79p6hUjypPneGjFavDMoT*^;L}gy<_zr zM)IW=7a}fCeOf&12x}B;UlJqNt$J&Hd+0hfrZv{LUZWYTUaYkw2EM(Myz2V356MaK z6`R)Vs&!@B15%#nYuw${30@mV83p5?|om;8+4enj_KbtL7;BYr%~jd%HR za^x9)%)bR(&*Z4+NBmfh(UPrLuTS!+R;;#{#vq@(&$!nZ)4$q7b05k2_aS}{U=&m5 zuE406FFl^Uo?h3K;3jOEAj&sj-q_2qfovbF5^wJ>UY9nbP(uydb1+E?S-JKiP+d}ikC<&Hgh??@kE z@R>VvRt#c#TzbY1-i+VK=PU*_)VdsVZR)$+yk7bp$(4Hy{N%hdx*VF=nv0w=d9u8C zhD(B1IVkZxTV203&VKNWWbE1UPg||WJg8;1p5zd@$COVnpKG@C#E=&wcQNF}$Pq^J zCHbEIEvZk7haF*!V(m#{wARo~u%9vFFiyibSECcphXeyFX*3qOu6amMsyVjW?8%uR!DgPR!EnXg>4Ic}9Mo-yg4jcVB<{s=h1FV6^U2 zKA&cI=!hF$fl=={FvXRdwzw~G&MT{*c4eIhquLnjk2D{AkB$~U=6RaDw2pVVZDOus zn8W9ZALq48&VZhCv-)de@Hq{4<=-P~aoC<+U+PjH$q~*GMlV)7HT3%1;>Y<;xffUM zE7xU(AIpp7&@FyUE_|LQuarm5e$HlWenzXWa8vThe(-Etz2CDQa#*d09DIh#A&}ab zpE>k@O^oDA>{n+_@@w(18LVEc{Yi}ML)BaB(?9&Eq5Aj6`Vwmn$Qi6&tT<;dY9Er5 z)>?9|)fe+YuHfQd*J8Y~9vFEv`Ki1xcyEgr&c;@roLHa2Xslaf9VdqM`2H9^>^ozm z<7s|e{%(LZ<>{?;U7ydImG{Q%iHPC81p~>6jk(#AHS=>0?zO^=*+UXj^H|}>V6Y}J z_5%)3x$n)7-*o3w_x_&Kk1PN`j~T?vMIO8t@e%pNU%TzPD341UYLV(VeZgoX%)u1jl8 z`R|eJ@ndt`{d^_fSh=vgbcdT}KX&->74?X*`&>01`I;D&FSYnk%huvyvskTI>pz1* z-^kT{|EwgYeh$uJwPLO13`Xrka?*M|a&FXH@>;>gzpll2Wj!$R_|!ffej1!Ob^J0; z{ZsLs*b&Ys9!@GRO$_v2K5$^)7yG-%jAyuU*3^1`+*{MQRbGof_oVl_I&ufgJXU!v z{;my%I&ufgJX-wa3_qTE&)4D|na>p%GvCD>)J{k46>Jbn9 z1mUyqN@wm<@Ox{wII{k^?n~+Nhc)R?i#T{m` zTCvu36~)adNRVJ8mmoBOYpewH7Pa(JY<zt@2dHJ>QmnwWo<8 z?>&+k^XPi3A2WYv@aT2DR(|p&IM3=yZ05OwRb$0DgR#O#EAJ!L9FQwmHCCK67%P3K z*R9uE@>;>gzhbw>qxHb(9dgfQ-^W7T`U)vo_Y)|f{c^NxDu^X@436|8(ejt-}t zJ&*F3#lbK0kVovuniCi^@96NWv5p@F!+dUeci`^8-GRFUcL(kc+#R?(aChMDz}TKM>CDM}dM2LO_Ys48gLC%pfA!CPXxqiu`Mcwc;kx8_M}HqN{JZq8eeG*c zGpGLa&wgl|iN{BOG5(9c_TN68ImzQpJhAU1hTjYQnVt|WF=%ODjVIQOaxVzZANe~!_so&g-qXRTb5~#_X4aN=b?d0I^&F@^N8=fc z^brr+5kK~KnBmAr{5aQW>8|@y68^$>mS?{g?e7Yc4>682zS?@@5k|!pqxJo2_?SFw zN0P@*T*jQmXmQHKpVeO?k5z6Cv7K0kVc-ytK@>u1z^riM= z7XvOOuiBC1u@jduXEEf1Z@ZRO9;@7zzSMr~V(?ieuiBC1u@jduXECS&jJI7&E00xf zOJ8a~b}_(tgoD1!{+n&pFMC?&X2(}xuok^MQ;Qh0dH4Ltqc=BeKZ{{cXV+`_iGlQF z|3PjqeXx#ew(`)Hd8B`9K7HSHJ-ID_c_8dHDTSL2DbhwO*spg-`HFF*5Rq_)w& zqXvfa`u8xLM;e1njI^ucFK5@LbT0=+y?@}G^{hU<`%J%?`1HvHw%May40`ei2YqSrqZwQFbjH8sa|H&t^m4ZruO}F-99L{$ zh=(*bZK=2B6T|rxX-`*Ts;?OAIezQcf8qK2Z|ocE9)w)a9u?DdTo-)uv2W&<>mJRg z@4K$cZRLzDPKl56NMq!-Ip2%Vn5x@D_Cs<|cqsptA5u=5In;ks4Ge7~41AnN8iP!X zw7YJrwiZ9Gys^bes}JLPFEZAzVrWm9KGfn|+Glj*!0<4aqj+L#9b#M|lOOlEaEte{ zcFVqE+rc>E$MtzNGbWE2zmd-_h8*$?FU`E(^*m}V$th=fSo+bA9PjA67;?xnyfpKD z*Yl{gBtM?zVd+Oda=fGOV#p!S@Y2lpUC*P|!e^Fy!dV`ce)J>9JNhn$dHcEhwrY8l zJhJ!n^ooO?o;k^Z_{xoD$HYDY2KkQi@E*vzw50?ieRgy(5_3oIVPrnyrcK@J;&?}j z!CpeXs?OLm_ZlsoT3YtBiKTRoF*+FJW6kKTb2Jae`*O%t<%}&}D*u(I8iS+{Ip2%V zn5x^${699#2dO=6?&XwVpzDiFf5+HbJeOE8#8isYs;$M3k2qv+KDj0LB;%g%k)2wO ze2(V9;8@Ki>v)53-Ji*iui&>kgqcB<&D*~8(vL(bWO{C>61rxgj-ihs*@7vtu~ z{eLHup5FZUliv@fAIULib)UO$o2O^K)N=FVPkuj`ek8}7)gOhYXTH?JXPkS!$=#hS@|d!ap3A%#Vv1)C#v`>M;mGdgz&PTa`TM@3xN8#z2EBD9Ek8ggQ+$(kNj~wslyBLqcOCKeV>^(i@GtT{L@~c@Lj9K5F-!6uE zQv(=ptCmN}Bl~MV=*jLW#x7^5*Tp=(^$&jbiPJIi%U}PYk5A|6>k3BVT!~?x_QSle zyN*6e>#6f#un*_dH+@py#mKzQV1Uz#xBjDAtnUZ|zr+MLkKAjtbbMR(v?ZQ;U6=9h zy*|T_zx2f~Jb9YKs-!0N*2vY^6@NLPoG>{cHpe4QIpU<^gX8I1O7mrJnOM#r<$xa; z`nNE!d5VYBhD;2x%vnhv(48l4l^@F|t4v|R1_sA!E?LJLgzNr1 z;>SInN(OSW)APpfTa{3!2jA0?0M89lw< zAK&kflY6Dk{gLAxeHVj0b?&R(?_&Er=8-ki(=+>hVz0#TI)N)+Lif~$ls_6{9*%n$ zX}=P~9M9yD80MzU+8O`jANxbkC%X3PPaf5#ubAR!ul_R_y&Sv_S7ESDVuJe`M_rfk zOY&phbN=*Ke)Rc!yu0I^{n&K1V zi7heoL4so*Vmfa;(z(RZhJVFiuH~CC_xG#+#Am(({+j%_^4ZFdo#)yn*WJ-~Fm8T) z^W)@MM>_Y|k z^vr&rSTXGLs;n4{Ipu7Q)5e@!u61pVX<;PJl^Cv>{m?d>hyGxgo3_M|*D|NQ`X6Bs zE6y2=UJiL(YHqG)Z8I47BqsH}#!=U0{L=iGe!-J>>)YkWKljxyJ#o4&wdSc^Dc3`u z<&?&^u#UbPO+NBtI42mDo7S=GAc;rTGh@iF&NJ4Y7{qb5^3FQX@?*Y}-1=TJ*KGFv zaboy>m|E0RwtRCwbuc(;E=L%z$&Y7wE_KZM>L<5$yn}J`fAP(~`OJ~g z>3^QJ_btA-%PB`#+!OWRqt@R#4$pCTrce{9Sn||%MnJ_@dn|#KmC4n<*snpmTiXHj^d0*Qbmrd z-om;0@y(BuXC3L>V@Ezm^Sc<2!b=||kL)u&z4`IYkCS_)&i#?&9eo$W{hfO|q_)pv z9$7Oz&3@RISy_83$HDF?-;vX4N3ss`>O5xiProbj0Pk!qVq|VRI(h8GWz1O&dzzT5 zx)?}e9h2K@9CcmBJG|JKQO@#Oe`Xku{9AKpG33bbD*&SK1cJr7>~kBA0?0M zGd;ce@y(Bud!^3(k>eeG7sEZCdpo4I&to20Gd;B*a@*E>UVJLQO|DWg7<;-`t?|Yn zGY_xLimVuNky+WQWo7;JJzEP{U?k>_{s46Dd&V=s*8=(=DaC~GI=HUsQh>p2Y+(eiOU%4S%uJ&0B z;>Ec6@y(BuXC3L>V@Ezm^Sc<2!b=||kL)u&z4`IYkCS_)&i#?&9eo$Wy(*v6VC4Sx z^O#50oc)j=%V+6Ro|WGwSFxvJWSn?4-WX)&AwM_Hablv2*UF>COGdU9hU?Deq5qj$ zDh73GYvrLW`(dBV;|PO!aqMYgu)gC_F8YX3d&>MuJpL6U$JF;4M=uX@>D^Pz_2x%8 zpMCcW_8AO$u{nB8mD-i7oKimc;fEhSae|}noS4YunmxO@$dC0q;->l`!El^wUV$Ml zGO-z3bMj;OVV=o3)pK5*Yh*8wjGZ%1sXe80NaA~Q=~wlP0mH*ws(&35U+WO#Dk(?y z@1SNRYtQ(Ne0DI%wbp(A|Hq$jW_rWl2d@PjN`SHzV{!!^^-{+LJ3=`W_fJv$i8 z>1SNeKi8@~&AFL){3}L|$?Y|cULNGKbB=O`+tzdB-kKFhL}mY96DwoD<`v@(_PTQVXA3J9|N6z);_n zXECS&jI1TO;z-Y&YaXMxoD<`v@(_PTW4thhkm*S{SQ2 zv@pb<%|pNJ=~;}dQ+zR*)0TedoaT{p`X>hSJv$i8iD_KVKi8@~&AFL){3}L|$?Y|c zULNMcd_9@#&A&#@ueC;Uy%ztgTvI;i-hw>UolDFa-CEfXiIIFX^&TD=buQyluYQhO z7|!*i`MQ_0mX!70Vx03c`V2<;BqnyaY~{m_{rzG2amH3X*J$b0{~mv~12;c@^X?b! zNq3Ti-u(EJ=c&)tRY^}*W76Me8B%-l*O9J2vgR?0%Q-P#Di85jB()@`T;X5(f}y@E z&tkk3FHH_S(yjZY@(_PT(o;Um-1F^7Fm~cH<}3y^fRVK%e;w(gJVtRjC&o+VA^wW2 z{aA^`x7NaVj*V4ncP%h_bNVu`KYH33 z&tfD_3xhTA$u(TJ`lnC!bSIve#HiPa`d;Ix>wfh&zWedB5WVJbG++JS<#FYG$s<$O zFZp2ZU(lV?cUIcJvlz4M_58@AH`lXUv%RpEN0QwCBYrGTp5e!jRM)$j3;cNWJ?^79 z>OS&0nm>zi#e3UF!#L{M(L6oPXPD216$!?Qf6MnQhR-sj95VUqNY_8Ft+r8I&gp+e z9%8imv|^*DaJql?-QV6m!8j97>{*N}c`&RJP|>KNRk!|Py-YA*ZfrhB^ZMQL?!euFy90Lz?hf1?xI1un;O@ZP zfx82D2ks8s9eDRU(0eb+?;v}okALUx-^<$p#?I+)JW^Xrjxf@Hga=0Y?&vcZH3w_1 z{jjF9@~{36A3Xgp|L6bp^z*;;_2G#RzGv5asrcDCtn<<9InyWJt(`epn|QyM{5HOm zobLzUr##MH7oG!WPOdHSzC+FTsv}*0pF?jOBkvD$8|9yK`d?iOc)KUph4-dsPBHxM z@92BTIj8@ly|*PFbIW{3y8c(!a`w9L4&}_rwe>v=?}bM1YICml4(Z1D%z5p zgyT#d#8ShJ@yXBW`__*~xJU0CKHY&HkAw&E9sJ5|lMm+p1>HF@dl=3u&8UT;4|0SD zM*8-2Fg&Zz3`Whtn(c+Pq=XyRGw0EBKIdk6>a`9zKhjffjJ%?r z+1y^+FS)JUa#qSa(tnj76HCpR$0~+-oWU@UGqsq<)fkC4tJ71S1M<67=`$qbk~fa@ zjH!8y;&M(5^T=A#Z={QXB##`A;&RRy^KgtzzmaZC3&Xh;NiE3-<$1GGKA8R^9+`6) zQ}al_kuHXLWG$n(oD%~{9yw0Gk#3B6I7W`*a?Y3*hI1>DTHqaW)m15X$++aeBRykk z9;3LN6T>{Rmh>CxVj#&Q$D_EMGsZj|BhzoB8`HvYZbeedqa4(<^Br+w?8n>2AQ$?1 zCJ!-odtn@TsPD?N7+#m%^@$S$>B)YK++O-%9oIAu*Gzkz!{=yToOfAPjwpBJ-^_*| zBemfxA5>Dix>ElhMqShXv6m4eeP-~$$e5lER*U!JKf)O0K+f*Nz>r5O<$o!iQ|4U7 zU@p01OUDN+VrLK6R%Q<9;8dII7_+h#u{HO^&pFrW;m>jw{lHP*l{*-%{F76z=5hre zWKHtQJZ^q`&Hcf1V1;+3ug{RQjW}S=WenFg^GLsuE{1uW!2pXq#6*tP&N*Yu!|_!Z zi8rfLOLEE;{)KOg%O&R<>A6hhrht!_7)LZk3;rxp9x~#-hUoqH&{noGl z!t?)i$-i|{_hjUH_NbVysU$M z>dGUGx~9EjKh)#L9M9_hy#njMO^nQ!JkD@R`CxLz9sR02c52Cd$s@zRIBfsy%AOYQ}euZ{G&cO3c7%w3hoPA!=qdE|Ij zzoH*kU}V13a>YSU&z$5yeC0;7V`3ixgM3GMc>S_2Z7IP>pB){H#N5$)7@3c_X;b&Q zINp(Bu$N#@>Yk$RPqp_Xwe;q+_b^=-4D#``cE?q==D~Phj^u&-d)4wt`5@AN#~SIr zLrEXhk?GsR$TiXJCHtYuk7x0~NZhOgySeOe?3o*$=}~@Q zq)#hO8@RnSwYzp=w)9aQa^r9R=l{ynul&V7@O+{lo#fE+0i$Ja*?Jf)Uu`^hDo5Vo zlvnUUpOHvvxgw7pjG9M|XZ0)kaRo-^OHX$=rO!{FA<4^j^sDmNsU`Czj~vhHSM=iw zjLer>b~xoraJeh;*ukirGRL#}75%sZBlG3jvhQ=Rm;7p^>rX5gBj1_1tMb^XCG*>@ z<%)h>fl+Irp0fu%-QhmOcz@(|vCdifC>Z2RFVEB>#%%39Kl13!&DzgmcwJ`KYx#-6 zULxP)=H3Fma@#tlmTIfxib+geclSOw4$SxENKOe4lt-r2hk11+eNacH&k6=O?8jRC zSo{%Q%_Dth_$_Nx4A-5h$-ONY$@%1uX*<#@M$f-uk9Z}RNBmSgBx}|@oNwiGw03L# z9^MEGPQ%Z<{Ie%3?`3^_tGyTB@?FJXEpjDiVm)>^dyML({< z$b6|~hf}@;m%Ad59gNB;b3ChG(T^)IGGDIk4ySwxE_X#9I~bKy=6F`Wq90daWWHS6 z-0vs98tLwJ-QSLUXXdWTW2ctPk34cbt6$NND=;!&drD7sPchv8uS)L^ughv4^yCpR z%-epLm-ZDY#!5`}ecKq;e( z9C5&&A9>Wd9IIFC5wASr#$bwvWL)!b-JTq+4_3{=^~7`C5e}TD#gC78FYAxEuyZ4v z5k@QS=EpzLe2(X{o1W%)R=Eojr{)5683d_!)Bs!}YGj5OYVG$4;yH z2i)wB{=Hv%K6_lZ`a3qZ6-U0g?(EM-&(AeqLjK%Wzw|N3uYF!w58lRk6eG`*H@3ci z4350B@wd`=pm*8?&u1xDt}wcX*AFTv%m$YTei za>^Xf>R0sR3XIH`YwI47ePZ&fk&bWfD@VRFb64fDQ%mMY9yy-Xujt1W7@4mD6&Uhpk)==}Uc5pedR}9Dc-2E0;>VT8&Wz~?e#x!T zb6n?deth%e!-#H~sb1 zua48Vt~;B1&o8mn;lf0HMIRWTgv*NfH9eB{ToamFLTNWKe3)`3su%^5F- zHsir){X5?Jt?*+xVDdnDt^5*6EjdnogwcC_>R==Vj zS72nmwWs3GO0TUwMXsEzQ!!@OPQORNAg8lE6(j50(aB>cE@RGOcwG{6RTl$EtYdP^ zLmsumkHH~6@zm?OJ>ENO7dIum6mAR`t|#_cehe=*zV^A-x0;8%SP3tdBl9jf{pESd zEwv$!cp_NopZZa(zUGtsn0w%en;MTak5L`u<2{CC%qdP{fK&Ojwp?SxkCXq&9V-u% z52hrK9H(x43%kW9!D?~S7ACgD?d8$S!T44lM>%9ZBb>^Q<)ST~n!J}<^c~5HvxAZN zH$T4laq_H|{t{enhuhXXb}+I&e&%vKtJgec1!^RUJhS@W3XD*Cl*Sy?}Q&(;DK7>T)~KLUo=WhZ}Q#HjZR_07Q-o5P56i$RRo#v!%k{D>d7IG^0I&gGaqay+AhyTVJ82lsNSc^IR=RQ2mt3-^lZO~RpK4#> zr8TxUHjmzX&pw-rH6jzM7f(Jte%#}y+KE-)l`9x>?3*9o{5W}5OMeM2x5I5~9y=IW zA3rZSp4Dp}vvKKn1xDgg%MPb}2`+a<9y=J7Q|5S9zoH*kU}U~rTla|U_mW?YboYkD zf-&-)nY${Fomw)#-CC~b#}yd07W;u*Imre8ye`QNX7m=uN)Gt8FlN?HpBBby9{OiL zw5>`pR^!tDZDZ7)W^S{3#mF)BE+J< zT`UH%6~nm|3C41oAaymCHD z9_E}q)wU|dSdB~nw~bM+OXfDKSBxA}-)kJ?n(Jon*quwedTahQa(=BflI!*KR_uMVd$6AeBFmx3$n#c<&Y`mgZL+}9L38m zTR2BKBp*#3tjtB6numU_U2_q`cxB?$brZ7}4-Py8&IuPyUYdIHRPDspn3-|<^e}S0 zs^9$h6CN)gO-WC4ysBTpZLh!}Z))K=AU8|CHq!O?8R8f@ip#l-xhjvWCGlr<@|g8) z`CZYED=^57T9Q*HUmNK!!R4;VBQeZzMbeKI|LS`Mx4i;`yr{)JBKy4&C(gOt8-n3@ z2ZNZa^2k~;r&+z$G8?BK>sO5I+ep6>1FYIp@n@yA*i+=n$vPEdhMT8P3u7e*d|Mc~ z#)_UjU5QIyFmk-3zio`{=}!K{Bu2eX)b|=kZ{4r{#&@6dBS-X_%h7!Ge;3Ee^>%b> z-igZ?`^H{IzM1ng`b-|lvr`v;218$^`MM9Y7G&~4xnxWB_~2~3m@S+Yj8^254>SJz^XMt#gV z{mp~tfLt&6>qyt%XUHRA_#Vt8E~Q{QVGqjllHEpBXHNAvpK^6tRh zfx82D2ks8s9k@Gici`^8-GRFUcL(kc+#R?(a771rU*Nj~-_OoU)}Qt5`R!nsukSkS z!^(SG-n;hRna<*zjlb&M@2D1gb4K#Mj`xN$?|ajSHFLb9?_iKCIcLpRy|)FYcjkIk z_g!Z%HtjXm@f8?ijb!H1(}_2q7FI7V?ME2SXH3=cx$507IK7i~dWX-UUToT55~GKm z_ROWH9pnmUz!mj2JcETzYvJr*w^q)5-xK{f$pqG0a6Nrc%37 zyHY!Hl^-Ad{;@n+KgTV}`q*cD^?6Bt{H=fRvrn9?kte*3TziM}8Pn4BYvDN8ic6b* zEsPl(@rl>c!5OV{1%_+X^}PnJ&vU@SQw;+)f;Sk^)Q(g#2O zeAe$L>w{nGaK6UXv3@Uw(Za1Za;SOLF)>HE)|fgz!f?LEc@0ME6Z_i7Oy6oPj;-Zw zV~`JhJClbP*0w9jV>i~gvlw0%@phybNYDHoE_HG}dx(|NHO-^0k@GE|qxp(=kM}x| zd!5Ql8N*Ha|G!#%mlbQw$iMnBZedivis_!pJ(QKh?J9>H@k%)*abW0MF_MR-p7COU z)5;^`TDn-7NBWst+R)84v`b4Mq6 z=--pCg>ke_FF)<}bcCaCPtIU~U*o+7%z*)ylJAZICSI4h~ zakNg&tB&pI2&cx>@eBs|HO_0m92o5Jw-}a^0SZTnm$MK z>3i$@9mqWjyc8~)C;yih{1~ZS{>rNA&h`AAM;e1n4DIQQ-oj*G$y~w<(uWOBb#6c!MuKabxcZqGygE5QK!lj*oD5G0fZj?@QNmR0nlm zRSP(05BhQDWPRf6^_(3O`v@3bmtHOYOk`c!Qc_F$?C4-5=8oRO$b7_2o4VJ<@s1P& z9?L!>`E2TSZ&b(BQf+mdc&X#Z7!2~Mr#+~4$2kwieO%vx7C-iVF_QD?SAYMr)Gm)D z8+CZ>$lrOSG04Qwp1wUy_E+wqvMWd2w8wkF=*?9=+4I}UqjFa;s;#$P+KEYioO3&R z)*?q~aov#(jMn^Fe%!)L8_ykb&KlrS*f{eUHPZE$hrKkXH1GO+%`r9QxTTYKwO3C0 z(zUd5s5xC#3(o<#7U$@lan9-QGXyC&c}I;i`qTEIDb z(2sg8X2;o6@0nHkC>Z2TFUe=MhV%y`$2I8ItK3+O%&isIvKf=L$aRu~rrzVbVBlBZ?`G_gzKX$G z@Gx@D8g734=J%#wi|c#OR8E{@u1}6zdJCs=eVzmI-Q>U{U4Ne;NV(Z6uRX&Db1nLj ze7`?_^MAIs_Qq-BXD-K8hZ|H|9Uo!f-@<8eefI6U zCwUNC^P3&Z8Ia`rkALhBJ)ewy=kn#R|IiaBHa}DJBc@{LW4vShW--JU!*MGfn|}5q zYjJJ`LtkU9!7*#v7k#m*lZU!7I~c~gR>g3QYR~I(1qQVxCiTgCXZ2aX#I5Vj-q$TZ zu*hL_eCdl{c=8ybcUB>zDI5BEW#yXc+>RF2%MJ`+UZso_$ zgJI09G*38JGZ~2+aan)~r{MrA*_~?7d z^t65_yN+8J_xG#cyf>C3vzN{NEPF$_S=~G0S8a97b*Z*GKEm0>FmLy_FI~$~9n^hg zEwzucXvU|caJGIOPhYg^e~Lq7r!eo%qMYOi|fJ~S&MV&e}tj0vHCk^ z4Q={jQzs8~WAt}CgQ2gn71Ocv`sa1grjIe&9Xqc*G3+Ul+BoX_;5XJjZ#Jgqmw5Wr zb?g1RGS1w^uM*Do(_i_~=XaTPZf5+!2Om7ub0#i*YCPkde?%RQIeP!=ocvdAT)A!9 z)7N-;GEy6op5ULho^Rr^XRG|Ua$~ukQa+d*(K+%^-<4o^)LeRNYj;iJ)vMIE=FfPR zA19`G*wn?g4rJxW^3Wqm?2!-iJ)XdU>$4408ws5=#a($iy^4-xp5YvFheye?P0KmN6Ef9Hww zpMUr}pE_HdwDMMJsd06DgtLpW%8zSabxa<$z8q6Wjj7`!4DebwUIV`??jDi-s{Hpz z;#>E(zxucT+7stL{kvcN)QR8GK9gE%Tpb_b>|(6)Zmbwe1rjh3&(5V zUL85gWi*!Fuget}_7q8N9QmF%xou0=zcr`r8MNTmbw@uBM{}9GYgGxq zqL*^6IzJl2V?X-nqo?nG|N9@Gj92(AzFyOmD;T33Uuuqh2tAW~CO6fV+*KQroN}xm zw$vH-aK%{3!M&P$D0@>mX>!392h4de(kJI;b@Naj<dUx=(eg{1yoy+ltG-|T&9DEDPn`6mo-Ll3Hgc&kt#QUweHUYu zAGh*K8+p{+a-65eE3J zIIn?wb>t|Q(OhD%7Gt@PpJVQ^TXV)x3-gTiXI-)6UTM>BgkikC)>7@O80M3>Vzz2= zF8z-%^fgw0$JC-tUu^2+p>B-+j%P6RHMU|pc3%JNsWyF#(eBuJ?TO+2fuuGLcv|9E$jsfmtAty0oV!khxZ)%fA)^X3zJd{WI zh@X!-wjRF8AkfP|yKRg#0MkN7UJ`1O2xcCnEy4B~2Rj^$OvdffbY z?e~vcyfkg}HTiJW)3>J|VR#*D{LPO){oUxwDQ|xKh6BsPkaDvYZ)@?yv~i!d;;JoU zs=kYH^W)?1SIf=V_tx`hzsYr}F|F|kXBXpr=f|x+&e+`VCx?*R$aUmC_&BGHH7dr; zy4B}VFvyF0%zo57XUDVm=?L!;FuX3KJdftYu&0ix&-cDcY#cq_Jem_j`3&^Cf7EqH z&zGaQ%w7DH@G5_9&T?dU@hUeq7dbM#_+8+}hjVmiLbWSII&X4w_@ac z>WRC{kL8FhNggeG&(`9ZVAP|>k>#NkpLlF7{OXHOiyJ3KE5@~z)rWD`0Y8DOBnM3n zTXp@wsB^6`{>Ct$dJCiaS4??T*6`B&xPEuIH71|R_3F44m$qGu`kmzGessPo&U!Vk zIwp_ItEJbNI-bF3OaD5VbpwQ#x;*4o>J$p$^#s(3;E3C-SYvX zH#cj41PrgsY|bq|G3=>h>Z?3hZrhUjx8}6r!@>BC_Q|z$-FojZHt*J4&Wj;-m8_fZ zFEWqnI~v1pce$~+S73h%X?+gaHXOAiuo#B{rLvYl0W#*E)d5!p} zYtH0hT|I6*gW-BTnfb$O`1z_l^k@u5^-1pQ+=%bCFpTTTx51Aq_iK%*rDb2S?P63; z2|w1hvcA5%c^Hq}#V~Jb0E6d%JS%_4c%|oZIIv!zI zuQ9zb82XqCHu9K>sXi5>Vmq$arP{Nn&WY`K1;d_N8}%iBoY8k;>bk9ah*~*YbJ(2E z+{vXLyl1pWNXFIYXbi9QyVa||do1=97-GGaWKY$4KQLn>#~Hto&lwE&gV`&&D7;S| z+2erDBf&_&oI}q!_3D#(RGqxc%{XyV);yTgUny2yBggtGbBz`ze(94~aGA=FE6=TC zxGNYv4m)dK!7zuLAAk0DqbsMp`SF|IG4>gPlxMcM>yeH7^vLIEeiy^M-^W^Re*EV5 zrsb43KmP2$&m}i=zqlg#nOpI1`R-zvxBFY9w$?gFw%t4ucNfFFvlejWN!TlI!REf* zIbtdXT)=t##mzZwEsPo`o-vspdJCh+Q+oCmhU>Y$c{yfnPVM@0UCc|HG5R_lVHj(S zV`CkIp-o?Gx7Q?a&0j^*nU$I2e!YxIC1ZJ=M{^tv630y}4QY84UM>*-$xXa=p~$ zfk=5K5)8Ryof9+XTRxdbPtP?g9^;zF73;L}&pgyKpUl6-k7xKT7&Cq&pB{!>xD|WE zkMkYojP85N_lZ-!PqmzRhWQLZ$}?x5UFpYtn&TaP7sI^Y$6Asf=R3?9{e9$=?<+rE z-5a_mM9R%pG4SVSZYM5db}`KReXWHa$x*1W@)m3}+$DQGt7lJ%aUJt%<)V#PVk*Y$ z+UZv@>iXuDYZIegf9^5!(q@dlj`8bZ7;B7UV;vK(Of=~n$0H1KPE6{noHn^~)%DlTIFH0idtH}tJ-v^r-Eqz*_N`ynfirtcve?-NX3QB3 z_k-CDxnOeQmM-tCv2w+}&MzU`9l(FlWPvuaVA9@R8X6^K8 zVbt}_%Y0Zn`=USh7}#KHGiC+DSYsTE=#&@5uHT9xboTK^be~X1IdqjoU~;px@BLn?O^oyG4`GFet-N~zRh)+dG|i@*~O^e73W#f z`}Ylw?8K4>Ql7cWYw-tTCoW@lG0gjYtOb5t|1Id*@%{ezv;UVB_t?z4_mR&oM(g*F z*)Q*$?q`wmO!ktiC$+4`RR3KJ^L}4zsePIqXFrH>OrMVqd^5)J?Aqz)x?(s!%7OVB z<2dnG^u%0=OJ6W@yrVw?hSy;ye`Cb(x;Uo3*EnX@g#)*^vH2a%>vzk$19u1R4%{8M zJ8*a4?!euFy90Lz?hf1?xI1un;N9y${$E8qIu-B4Wz4(RwQi5 zv+u+E9tX^vwy~muHBabV7XLkpqzAv50xB66!ojekge&l#Z z-^Hl4_%3Hxk_Yc$d=I;Wvy0)HzIWPLyJB2X%T68{LqBpntJhkWDan)83~6aVH@J;7+%5_>ey+zdwI^>lga%y+BxO$ly}t3HF<%dKbkcYRaN z`c}V+kz@55GdqU+@V(^O??wB&!r&9*IOD6WHy&YBY%yBjuioKr8ABddaLOHw$_F#A zS-oQHZ8bR6f|tab&L;J9#8N{mAi- zzKc<70p~vM4%{8MJFwb;Lt!~W<0)(Xi%+2H}S4EOXJ;6vH_3Ecxne$-eSY2NvKC=hhXT*=? zrOAKM!LPQ}@ean#k3akS!j)6r{P@lP?*#Yb9=n25?qF0txWh#&#!eo&F8#>yj=qag zYuVv%8ABddaLOHw$_F#AS-oQHa;@i|U>V@LDM&0v^MFJJ7Hzsd=#y*I8H#P@vDo_?eJgHicl<~6HVjGa7kE&7q;9eo$0*0RIjGKM^^;FLQUl@DfK zvwFqY$s^aIA35I9cQI-$;JmK~*UA6SrzM$xUu(bXx;yaF9gsWD$QD;7znNHkTJhL> zHRe0t%5CcyjA~mQ&tO<9x%Kjv11jZ$j*ld`M?TJrjnszhZ8bR6dw_&FU3n zCy!iZGvBIFZam9z!NAw`jB~8ip1#h3fu24+ z9lst&PJYMbYgVrqJ9*?<^drYR`YuMTWrx3I40*7hl$+(;tiFrkn)0C8_0q3mTv1En@96Yn zC$824PCej%zxC_C@EKl-z54dXS77w8XYE`k>gb)`6SH{uQI~vsr5``@$N%6{@0?w~ z7ms}wgME4LoW=NouYCEbcg|waJ5OTNS9)4w>UbuX8dJv=gL~%8|8rOIatwB!aO2bz2;HmOJ9N z!;cf+_#=M2%8glrejKe;*G>P5G4gc|IpWAi{J6(csYPx(l9l(S?+(Vzk8ggw!s)oj zuHckA7?lsMl+oj=qagYuVv%8ABddaLOHw$_F#AS-oQH!n}CxT2QC-_ftC1)O@o5#)d=zx5A(_PK}E zzcs!BqlLR->*Zig%&*|#o0u!{aM=I#-}~1-bFOgP#GnT8RxsF;fBEY_^qI4@SMT)( zZv037&d+`3#NLZZJNbGIkoE6kX6hl|5ynhC)x*y|{CM?emKdKw*$Z`XlY3+H z{jpMiB|JE}u;Y(D`smr7bH-dHlVi&JkZB+3UMH_9vXFrSfJ<;P;x zoLcv&98u2acve!&tZ&b+_B8o%FRyB^c`)zUTDLVZ8bR6dw_ z&FU3nCy!iX?44#^7IT0jD1D;N%VJD}3-Q20dmyPw>={ zUeB6)j`1`1mSjHtXLK=FW97D)2mE%13!2vz80^X6$C(G?)?VFptCSxn27I~2Cu=NY z>d4slfgj6jdq0ySe$2gWe3i_N-Xp_*lM8?Ed*6GiryNEQG@GTevf&MLnpkJT8twQ)NbeeG7o*m)!{0K7Jg(rBI~bJ@W?r*;#n{Os*Pz3QoC$QTbrzHLF*Qojh_a`jO)ueHWwF(%bXR+R1}`p4@CUF8y{fTvI-kn6rAt zxT2QXwR?W_qc=CQ*IK}-ha50D4EhQuJcB`hsiCf)W9Gf4HGjvw9K>0vCoy;8uHd#} zz^k9H$M3z5#%_K`^5(~%bm+?W$MV|V&)y6_uCc}-d+(2_Mve?m=1hL9O>V6Gqzj+c zn6Ia6eA@F9ZVS(=ysz>?#`0r*!P8Hhyit3~7QfA$uI9(CZElW{HP5Ui{m{uk3Y}gHicl<~6HVjGa7kE&7q;9eo$0*0RIjGKM^^;FLQU zl@DfKvwFqY$s^aIA35I9cQI-$JNzwU$m0r5xr0&pVCFTeSB#xJaxMCi;~jk$qt;UQ zZL4GQxPnveU{pT1nmhg#V<(SXmwx1UN8iP$wSZHP$}4@px55i6#y|eCKlH>oi$Ra^ z^?Zc)$G$h6eNUQwO>XP=qIcgPU%`*-cdY9^J#(+ReW>5Dp6v_0@b4&)-e1~g_&)jS z-@=%whkQpEt$MgG;j8s$7F)}%A9KWJFX5p*JmQ!$9(}}ZnS&S0hf`uVR+)LIGxwkT z$RGa9X^c{TIbW4>zm`4uAvi7HhF0pk!$32YnnZ*#x_3M4w z;)zF`Fk{Fg#~EAgy)hUbEBrY5D)Bx1*_?V9FTsy1Uu7(3C6Aofp0=K@?+ymfCioh$ z9y|OkW5BtBQ|@3?KA3sU>J?)rk6ep>Z8bR6dw_&FU3nCy!iAo*!E_tCkYpW|0C*!qO?wt9r@MH0iVC0xway+9~3}O<;`l`e)IkKEG zxn}aoYS&*$9_mL@o>~3C@aXa58dJx_SKCqUbv`jNSM}=8df1NMANROy&rTk_xoVdW zSNkjm@nW?2v7cSLl01@A?%?cVR6e+~cE!kAUX$DATJ$5wvwE%NyWjop)0vYzl2h(r z?P63uxU+V}$XZ^L+vZyIBgeCPt>vG6_qU(UoaB+5atCV{qw>L>wJS!}@|xT>*PiJ*@ARIS!K0r1JYVSr_|)#-5u}`XS89G&V6YdLccjcc zy6g2Me>d<)|K2Zs=A8XIgI*o>Wu_;+I;?4?p89tlGkl+WruQ>>WY7BzF6uj8?5$J& z%$&YQ{8o-DH)d>%vUgpvx8k+exXK%o4>ISqsGLwg@OpC@U)Ra8IdQ$QCtmuglasm< z4D}hggOPcq&U$&TR!+Gxh8t4LY>Yg!`h&sI^GZ@5@$C(_&K*}>`d1?BQM~-*&T@3TKmal#7 zYfooR@_}pK)-@(Y7)z5IsE4b|y81lh0wTN-^<4-s>_sGqUf9=V4y!1nTSMFle_s3^AC9&jj z^W*P4`Hq)MjQalg45uWPJlI#B!8wZ|4?2^B7)W{M>OL8NYT1d)m|YC>wia;S z*Mk~IpTR3VNY14GzV_{|tKR|cnfy6C((&i4+}IW1qO+$lBPq9)V>2H4*SVE=`CgA# zGM`*f-kW@|#cTBwezezwzC|0gfWvJK48rH_WV{c z>Co^pL> zKQpG58NZRwE{1vAQ{MfaImzQpJh6|4Awx_du!JZ#^^yX&mkA@-7T;a6(Qj7Yo+{G|&`VPi@+#R?( zu+{-N?~GhSzP;}b+#R?(aChMDz}ZoNOQ7`?GQ69Yfj>%~+%7}d5qUcoTGRzK>y(3x@Ted*bEzxH((hxnP?M?O0k ztzKBm`yk)9cfV)q;d-8l_r2|-V7zbde$UjywLKH>d)r6BIP=cd_mnrrC-307KOUtY zv+sC&e$>*No3-Bd^ ztc*1WZD3TJbF&hR8DDF1J#r@2W5gFnbIA*LIj#8Sf}Fu(ZDW1Ym&(frKZ8H}u<$F*kdU|qo}cQ7g+oXt7?D#lJ8iAO(jyrb`8)LM3U zSjLdY6`XPhqw>MbYgVrqJ9*?<^drYR`YuMTWrv4l40&9^DR(d`AI!XF^@_2RN3KOb za=fGOV$@oC9BtN49#?S69gNCtXLC-!im{VN;?a*B@94W2wH9#h* z<(MN$9;5l0IWRmbKc0!lhxl3Fo?pdCF1*W+Uz7jjTGW!`Ssm_8tjBCz&kvj{IOPsT z<%7K(NA`-blSksyj~wslyBM{W9sZUveZ2nRA8TUV&l0JQwoR z{=Q1NV@t~aoJ(G)PG8ikzr54<9zV_Aw)nB}NY_dmIvCY98n4Ke zj*;Y%;}u>02?ocA8`rgsbv@$Mk##=jdV0l3Ki6;JVr#uahHJuq{@wrWKU+GPDw z?_iK?<;TDCy>C68ImrV~`JI3A%g^Ty)-Hx?vOX9)YgddbYT3ynW3H;@!w)}vI&+c- zobub>{`T{^gSCs{nye4T&e|2@iduH^$e62Y+2L;)LmqI-U;FlVp3j_{)ps#mll8%v zT`&DA#uc?B{*HcCE&ufIe)Z|hNgnJQfAw$wwdZpOYZt>cSs#p@wJXLIwd~}PF;~?B z&V4*~2jH!_H|*&ne)}jGbedd_&Z8^R_?@MRbOTUV7MJdKn>R*WlZ5qnqCkDb_B3pn@j*d37D{?t$X)Kl+d&xl{I5;1=HoB!yk zcg|uQ?N4X&IDm8qd(e`z7`+@uTvBX&*!%UkF?QxhSXW}W5ADgB zJbJO(M;P=4Zu@8d?4SM2cdL1SjGcM=*OOOZBsTYlKg;Mj_de>$9cTEhywEs$qCSfE z-K##vAnogw{MbB^xzj3JLJIOPsT<%5~mtX?s8^2oL5M~-*&U5r|bd%j&s9_$z8 zW;-~$7_RAlb!Y90aYZdVd1MUz$nmURYXPTsP$#(duIHC_d{_K4c153E2cFe>UwXv9 zG8fkJyf!$17Yh2g__VxQs1TtDvbj8$K~@BjK=|Laej zeg>m|lI@)Kr$6|&o;c^cU)Jk?`uG2(C(qtW$rt6W$h4;}FVqIE`b@k&#vsk@h#$*! zdl-pnp2pNU#uX=PVg3lid3;}zA6GspuT-}9r9JKOO5<$ z@Z*2y=l|oU(McYo`I)&H4BtiH?~mKu9`0H><;{=p_s4$bR>{v?wXKeKF>ZeRou}li zl~dmQ_)mTOE`@uHy{O!5mdmBzE{1E$WfF5%uNYU*@awZRBZ+`sA-621Q3%AZ$e$3x@aBW+1=K9Ku$;|r_ zyqVZfs`-7t4_^G`um8~ViT4N(`Y%6A z#@N#jJ$Ym5zVA(2OYdRK#)FwT9dTs*E3XwJ^OL)___2N!r`FQLsP^Q>VASUnnn!#8A)u58-oSEO71BiJ>s{-9{C*2 z&tRBO=19)OdhER4%ouR4;FLQUmD^@svwFqY$s^aIA35I9cQI-$tK6;nlgAbBTz4=k zAFR2}j4Q@Y9=Q(v$nlQ8i&1MyuDGL<#}%A%2cvS^o%}1tP9C`~{mAi-zKc<7X>o%U z8+ou_l$))@>br~Kn(kM{Tagvxidt6IZ~4-Xmi@?9YXPTsP$ziyuIE?nyKxn#HD1M* zTjo1t_E|gpINu$w_QgDs~^K5_oUpZR0YJlQpR68`$pM;|?X|NGzn%!$3lZz~o) za^jY(7;?p)lndrOywWl2tGDD)JTY0r3_otIGsh#&XxiD%Ay<-9%rmR*e2oVaxk`^}I0?_cTZ6`XPhqw>KWE?O~m^2l}RM~-*& zU5r}G4u8uS^0ct_vGsI~0yw~Qf=D>&s2M&*N<*Q{PK zcJj!z=tqus^j(Zvi+jFZNgnJMO&F zR6^IPk}ND+blu$C-#+(u>pA0&`SHGMopaaT=Y6Gl$C%^enPZN3t$pWn-nG|0vv$R} z$6n6Vkva4u=ev5{3pjbfUvhw(bM%ZK>GX(PWX<2f=yDQsv~IP7!x<|1B=emb#L9Ki zb8gH^-cm=GoBO%=@!QXkkMQG;WBa=8?rF!1jq`Oe%QrS<$B*H~a%7$*XMCs2kKw}I zsN8mj!L#E!I*whrrkqc^T+n%y)R4BL`Q+w4I)2>MP}iw-WKHTToFf>Cb(9}hK3M0} zQEf-(V0d->SZ+GwyyPEUx5ihDT+bTJ(T9A^{FrCUw+wokobn9TS&YgD&#YZBaxd?b z+vZyIBj>w%-An#mp))#lB&U3z7?ls6S-WE7Ufw6S&9&%h&Uf{?motBVD08SIIc55G z^|Kh25ALp)aTO!?@;0NvA@#mK#I7Ct(t z<7iHeKZ{ZK0*)6vET6MJ>+G+5=kbhNCI)Ah=ekQic)J*Ht^IK=&zPU!%`kFL_t|RfI~;k0!M)wZ$ouZ=#q(6{#<9I!-2p5{qKKf>~Lc-k6_3hcV&GR%bL5n#8qC4&AO4h{J5@3Znf35 zlGD~$>!|h-Mq-Wlv3zg#e(z$~3pt%nEPOLR7+$;lxZ;uTdR2Do*L-qj7!}hwIIo!> z|JpDA&8J%iJ%v+#o*1l89k6SrHIXixy7;>3gY7he{&wS>8aL5CLejJJK zV$Wh&w|fD{3l0Sz^{jzp?YHKe-1e=U&jBO%l(neOxTLvCdez;7u7~DSIpU`~b$DOi z`nQ5l{*54QGpyHyL2u=@@Yrq~;m2J*_j?B;?{mIRj=VO&fCG<4uCdbLw{@&?s=wwl zuQAN!gZY_hZ97tov-zxHT^%>>YN#Atj4pO&t2MiqS&lyWvHUqXvOk~BO6pkUcKkGR z>}ki1i8H_BwBlqPHO_ovM=~)+F`XU!jwAD2k7t(t?IeHS_a47Hd=Cuk$r`EI`+>ZL zQ<4kD#gBjZ`E&ZQ`@DA)cNU{QKfZ-il1m*IKYsiB%66akj^gO)$Tzds=f}5jN^+^= z;>SPy&M*40`@DA)cNU{QKfZ-il1m+&7jMD26~puDEj5UNlxKFlX=P(CtC*GlEQWQv z7jV2@8;4?F>}fRI_e#>M(OBi`AJu@}n30_2R^Nw!;U3|yqhZWSj`ELk<1haH_dfmH zJ?~v!FW6S=zhu9B@2q6+XLHT7f4ltHyp&l-<-Hj<(~n@B<;Rt0cHEFUI{OhD7+fQM zyn{vlPTVYJhB52K#gE6&nCa=ok1u{a;^MqMZsFKI`yJu$*u_A~GiO|M&^E zhIPA_iyyyvettba{_L}6USpgWZ{eo5Vz53Kx6~j8Ql7cuv7MhfI{OjZSq$rTFW`8= zwPxoPeKSt?C>ZYPl2-@bR|nw3Io{X0pVvAwzPRI~?wz<$azGsapnh z+>$Ssdn62b<}RPjIQo(EGx}K!>$a!x!dnJ)+>)QX^cI|Z*5Py5(evGzojPXz9p70D z>$azGsapnh+>$Ssdn62b<`GV7Ed5YFE6-wBw><^N>ruGvJ?d~zm%KV~b>Qm2)q$%6 zR|l>RTphSNaCPA7z}11P16K#G4qP3$I`AQM;I%wI=3X6rZaaEj>U{@qHjJzCXdU@X z7p$D0(a&Ox`oXin{M}xE-}fHBJA4le>&Y6aIqwV48o%`q|IYIu?&tvPXikkki(%b9 zFFmuD4#VfLJI{J+411}zqw}*E)@?s{*7)1M{oBvud*A!s)6qd4M{{cYSq$s;dFh$G zbQnH|eV@;Mb1nLj^IhG#?Z@Bvx&QX*@BP-do=1MK^R7-EyRoCVvl!OR9>6%Wmx{r& zSD(Y~JnOA7)KP6m=VvjjJD(r_`j>v{iE-xppQ+Y$ z*Pj??p8v9!GkKYF7Q?#T3pm$xb>Qm2)q$7Xf!EBB;d?WFIpTxdPdP3zqd`n}@6FA% zSG5=mp88DA@KzY3TSXW zzXit_#CNf`VqESDRx5fTvzgx_AUU%PnojEHEV#S&L{xLWmFRdIi zx#5Tp?%;rXOMccN7Lt89uQAwnh8W`P~3$8ELk)?mDx5-Ddz zXZZZr`Ao8xnSaN(i&5*!y1_W}%r&3?f|2tx`dN&Q+iKgb!+1F*QqGFb@cFItnPe|B z|Bi1Lqt=ymgK_4WYd!}CBj;!Ivlx}zp4m&q$mhrC44?lxpGo#I^Y8d}F=}0MZPoze z%rn<~9t%d!&**0{I&Q0Nw+`dwlt?)%I>TqX&S#Rn%=|mPU5r{+)(ysWT^+bOaCP9N zccA0Pm4_yuO}@+C<*&w&t1ho~u1xMN2EN4ZaB5xDp8bd09p$ziA3TbYHK333cla*( za@CAaz2m#-7bD~FXN+t1?%Kls7Z(E-NMoEm=?!@Ai67-#m< zVIF5CCXikkki(%dD0gN+ysTgp}ur|YR&qxZt8@u{q8US?DP1}SHJjlbbxU*r^cVfux|DM#+kiz7|An}gR;gWUF5Lb4T(IWLBbCV~v5h$83McUrlYQu$$-$J^xr=tUmqd7JHEQWQn2Qbd;rNfYCCievY5ij;^oz`tX z9o_E=S8ApbT z%1tW=rlx9hp3jKSVvr|}HGJOuxN_DJuN?7Xd8+)>xw!OHeI{#O#_nQd4tTGfANv{O z-OnUv96inX8T~AVbtga8wp)ksi6PJQxh(ZP@+C*MZubJe@-xP}pGnR*_MG!G`dJL? zPHwAhw+`bIL!Jpg*8a%%mKv$mx~&;r=x2=I=VzmHE&7)8UER9nwrBQIF)BZnYd-S5 zl_Pij*v}Yy4m={+^O5*2_AG{Vdu~N)>(=RP#wUh66P}{|k?$=vQmb{pgqrWsPySZa zXzb$CtI^oSxFybeH>x51qjikwUfx+fVderHM zdP?)srmj!S#Lz};jWb{0#gF^nJ%)qQ(_Jn)iaU$Zaa(PpwW{5CIVH04OzL~|tE(F> zB}d%llo`jKbACoYi(%dJ!83d5Fe=Y{^!rME?7h}~hEpbg1vlqs^s^Y1+n(7=#Yleq z61XwF%qK_m9C$>6dnCS#J&R%8o?FlCrNelMJd?e@#JainXD_(%*S`LZ&kXFl znDniCP5PSJ!69}uR{7+?AxHV?A7N$82*)*67#*KY8~E=MqhiBvfAmLx^yv&Y7V8!a zd%NnNYhCi)bl@KRxboK>ZcGe2+KH|ByPvO44wyPzvEzg9iXY2UXB?IK=ttJ6Zl3d$ z`dnj$VO+)N`T^E!`Fyn;v2w?qIeplPo5h^PuclIN;vltz>)wa97@t*sU zFOg>+t($nqqyJ1A)dvsG^OO2few_SIo!*Srt#)F&*V_&TF>){TZs@P;I=?4|`yWZx za@{JeW0b3XhLM=^TKMrwhxF$d5?jX!JC2-r>Z5$`-wsaYs^liV zdX<@n?jSBPGS8UI#m~I!gZsGn@jrO(Y4Xi8I{iG8*Ku2IyLiT5{P?qfADR0y)wZ)PF~(o~__O?y`;vU~jD8lQa@*_q@z_cDKm2qs$8O9@CdTjld;iok&QXlqlXV~E z$zS@?m!9gNc5JIX=qDCl{L{bjFFgJDFa2}RlKD{#c=6Nk{@JJR{L&lA{0N5id>*pn z$H_}a+%{v>lM}1s8(|nz`LTE@iFr-@IQi?0+t#?*oYznKD<4iD`iH`g{SN2Z_dJh$ z^mOFgwV%bX?&P-GcGosOF<#U2W52_>ey{Y-ocpp9H;XxoVcp4XwawN`pYe(Dnw}r~ z9nST8rFZ7smz}s-%vlWUh8Kf@)HYi$ea4H?eNGwdN58tdIrI4)&h>kxcjoMQCvFyV z7Q?zdx1QNchjH=aM%#}UNtN!qlf=l0JGqM? zC+_49MpxVWwyijF@>F!{t2XCyUgw=828Gds95|Bml0hIPwr&+MgQ zbUc {uX-=FI1JIM?r$-kG!Kow!-dSq$s;+ikL8v}p0$o%-(q$@&EkMR7MJvml<)Lsy8thrU{H>b*bV$8VlZXLH^orV?SfO z`qo2jFZh0(H+io4kCr0I&-MxP_+lU|g8ROm0Bxf9Z%=sDpEQWQ<2a(!# z>o7hsDrbJQ``Fb({k3j5rJqxNpP!A+wWu}cySjBJKh|~@qvM%fFCWd;)y?_O&ls;h z2i^JEV`o2NJBwl6>;a53d+9J?BkAGJl{x5%vy)GZ7@4c>2!`jgm(GuG z;greYW;ni2y8b>2hPA-OAwW-lGaYv;#)=QHOK zb-3y7Yl&F%K5vZfjCEu$yk1%4IC%0gbyYs%8)$^J#vfuFAI?}(R*L>zXdB)_u zo!tZDM^bKjgcDaRa_guut9i}8rJmJV@53)IRmyRd)SyqPPpMt0T?vMIO5>5l>|RH? zZN;tmaLMlHpfEc#?Fe?b@AhiAMd_a-gVsd{P?r{ z{(64=SzhV$SzeFoXXROp%5Bd)3$GX*&wSVKxqIMub#vyEBkr8jYYcT&+tK-14D0sX zdS)*j#)ra><+#WroHF_G=Z!%v-PnD<@R}Ko_kl55yR(n#>0(E=N5>e|_j%aA`1{{` zer@>gC-fBC(OTf3KJP`obWVD7|L-$CIqQhGu3|cWwT-ZM_u6sF$`^_0@XVF}b`mV@ z^3D!xWs|F>>~hkNUSm3cwRL$FXEc8;{MhfP+Wk(ejH5R>Kck<;u&^EhIO+CFwX3y!+7od z*mG0Lqx?8?)!#P;_x!r94!p|_z=w0Z%e5}-532(mKTO-G8^k34_2=(M-olSZH9VUA zweVy4;O=)?WgPb;=V$b@7}ni!+ub@k4DcL}_L>{jW4%h^l2`3~msX7dquP$n&tg;_ zduA^k1~?9CdkL5IDybP>=yz1@e3w>@0i)WE&d*|4x7_y3UMdDWGY7T3gez-Cr)D^% z-%&OD4y}<7jFE5Geip;JlOJo_UEBD?KzC5vOSr68IjXsP)~aokQ|-ogvc`_)U9PrK z&Sk$kaCPA7z}11P16K#G4qP3$I&gL1>cG{3s{>aDt`1xsxH|B@I>6tHzGYD7E&1fW zuf2YV>%6vS(68b7@y_eeXOf*Hzq4!G!CzsBHIw(k;GW(x9;uF<-uq0lljL`HZ9DiY z46$bNUKro}<~N^i8IM%QPVadxI~wGVeAsvJR~R#VefPrn#y|S*(=CHK>a*OVb1-hH zfm|?lE}uzulKjrDZ3lmaA=XUZ3&YhbTGs_E5X>g;HBN)OTte6%D=nD zSzelT+=_8F-#qr(_3DTX4DrrNFm^6DX!qS)^52zzca5{0FzdJ#<7~cp?6vFF5gQod zot0qhT=3HFJGZctzw+;{ah8*29k*hf%{Py|cD*`c14F#C5{yT=xIa0lE9c(7Gxpr$ z2-BF(-`S4VaSse@RE{ur{j)uYamlL#qYkXN?RWm8fA)zniUW7#!~X04_CNVNW-+7M z(w^M5!|LkD9Q2x(etbI^S<^^|V>&8-l&|WSk7Doe%+L7D(O+@F(noyd!s2yy^Rgao z=8dFrnR`zi?gIb$H~#nkBwJky7jOT z2N(RMU-{MPsOylEbuh0nE6l_|zh$tOy8h8Q7`N0wE*RZa&*scKeutg>nZIIn>xlsu z{Dr^r7pJ4vK~C1eyvD3B69fI0!Cvb6N9SPNQUkeQbeFxtSKoJaz8gba;xf+*->?0R zf9-jovrcCl*})pcXU(&i5ymK|v%gD>+{dU7#*FIFuk7}q4~$2=sCj41YUgiZ?~ED7 zhwhUz!ecvq#E*@qf8-^H{-gipFT90itoX>Z&F+C5w&s$b1B{#}zPU3$z8wtfnbn+} zv*VHJM>hs;nYhgLjdJuOGX@-eX;|2{I3{j3Ii>w0m-2F7Zgl^=}mf^R0LgKyqAt=4#S4#o(7*A7N^an4Fk2j9xMI&tLJc>Oic z_|clguehw(N^-2j9xMI&tLJ zc>Oic_|clguehw(N^-b*$buqt}@@ ztdsx0?Q~*{zvO2-Aa~r6@ZRLUWw9z`BrXQ=VVmd(fJOB zwNu-hDnI+*5qDhKlUvG-S2-g;RgHWjJL{*F_sRh{$eXiR{ zYx#+v_=%@(P{*o}4l^7PEsf zT3=i+xZss_B(H;WkJPcIpQGen#LqfT_Z8jcZO#MWX7|X zoS)HmFh=XU9vEDnt5PPPhv}ma^t0zWQ?kpVUBn<*qY4eB@*ubxmUur>>D0 z=sRP?Pub5-{w!t(W3;}wU~qNkzC16(xpJ;fp848R*1T?==K?;df%wW@XL$I?$vWzq z#w1Q%BQel-#)zM?pPl?!%nrtAeR0947w-Y?z3(ggR^xW(7_Mp*1cT&1zy+Qy^=gR9n(F`b_Kc!O}i|H4J!U9W*3Cx3Kb+L(9G zI^~qe&fnpPi8P-(f=+zpt{GqP@L>~2jLb16ZFP;rK(AxPMKhkg54op;Yb$uqz6cQ}b_z8J(;?waux4<9yhkfWHyscR$#dL1h+ zn(^!<=SOnQs~FmkNHDnSnGlYZXG7-S`RW%xGdvR_JAa3hxaL~}@s+z~e8t0uO&sJX zCUNQ-iGg0nii>7Ed&&8cT=Oc1_9GIEynNjw<%28Ul=)Ss*NKz(v)H^>=4&G_ad+&w z)+pDQ>gT;uZKK$ky~D5{JA0%K;#~vTVIJ`(hWpI?)LD!72XZ!y$z1gnCw&S0*ghP2 zpAkFji~V_NGC8Ka51ICv&VKcu$tSn=tmDVT&FacJtY;>V@?){;9^V%~?szCRIdApT zk7_eE`ElwM&--%m<2T>^ML$;G;RSC`?k-ofmr8hL@<(`Bop;=<^3pD*;^f?X>gc#_ zS4ZZf6C+NCVNB+AxMFmBIKpw+OJdaAI?q`3#Mp^9FC`dU@XF+m@US}XxLM_;T};Kv zx%t-6t()=Hj}MzT$WhD;Co$0LIKpw+OXkIVy(IM$JC7?ZY&lNjiA9O1a^CG%@;ooB3iV(i45ml6yv&xLYG z&wM%W&Wz-l-}yTnG3%KUtW|vVgMqEqk!u)}wu+M&=ye?7xb!3QYi^xqta@VX#G98A zjJ$l^Bk32PweGlE=8Sam%>{>f;@7;4N5|jU&e(IU5vDQK&wHWTMzJ&d2m^fkM9c_- zIQpj_Il?q%OHZ@%}*0hA}mt`G>%d{TY()GxS+sx?a5mo4gNMxnK1$ub+`- zkH+ofk9euQ8gXOS=sx3(FY7QCNxeCr=@rBM)Ewzhn9Sp8oOUBfE*K{9m5bpP1_z1i!IjEd;CaGZN-|-#6$Qr4+b^va; z;*ZI_)a8`&Qtc^cxaL!j7_)eA(~pi#oDRd7%&Rzwfqu(iFSGm|zMB|3@!fi0aKS5A z{4u$gdgZ0sQ_gVBw+8ZN@zqZ(HgS-nm=&%V55 z9iIC0wu-}C4D?$Ddzt0$@ZH4NiSO0}gUj=Ap8wR5 z^*rdDJoB|xS#ff1eqy8_tl1nNG2$R=jxjTw#6Z7gu$Ni>4&P0To%n7&FuZ)t`})OB zug-8}^5lGS zUBBzwLCTLm`skyl+2H>}#Aa^l;PYNZc0*t0RNY#9W?ki|2G{Q1 zPuTn!QvEwM)fyjtp4_kUzVxZfkBw!2`n1U#wWoaF{Mfav%{j8xnS04Nbn4b7mif2v zKTktZz&hcRrr^7I2hLafRw+!}jOFp^x!T<+u zSovf9ymZIGqrb^($?XMf$x^VUT(=J_g)y_aE7b= zv3_2<<6xfske$E7$aT!O1~AgUniETmIM$IE#-y#o6$9TbgT366Pwu@i!0}v|=f8S3 zu4h8#4=6(IXu^#8h z^ypK*)qEG%Y#sCLb7E>_hP8}%`ltqQ{dv*#!~Ikr_Ek;R_EMyruS&UJXHR~}KD*fDlNF=R zvsc~oW7n&Bxh@#hpYst$#_VE<<@y~zh9e&3rO7KRC*8ryHQCEf&MXEDuA{Z&KHebQ z@4s*vco%#$`PWLPf>q4Qe-y(SKr>ZaXJiR z#2C%R*xAD@hP}-EJH8I%=yPN+xZsABKkjfmIisLFHeb?v6#2KqjoE*14Hx`drUYq7>&s=o;oo!^d zPS+QM_#7ilVk(Apj&eGC#juVp*1fQf{CHMJ?!(;Cen)l8Fx;;(V0cmg>NTmKSo2cK zN%MWy=_76`)=n*>8pN;`Yp!xsgKKwnuWYNDtnH=9sR`a!xHd-_a`uIf>)`sw6Hs@{DuNe90@1a?*;+UaBqUtd)E^ed=Ivt!k+_ zxsNvp_xmqg2fkIG*RJMNu!>pv&tS+6YX{(ll|SzAI60-fbSEd*F`s(GNWcBnR_EBU ziPK>iBgSYhM#i1d*~^){%mJhJ^5Vz0f4(|-^ckJ|awacxz^EKpZj7wHZVm<)=c>vd zIdiQBdFHQT_2)X~Cr0|ona%MLBM!3W7&F63jEp;@vzIe@nF9tj<+%9qXP-fHuH}53 z=i6@X`8MOu=-ihxd6@%7J=4D{e#~0zU5@PggVz;4UtieaYa?zcrH%oZqvKtUYyV>gw28J8@E9^(OcD67%bc|12gA!+YJ8oOSN9>tJ!Hq%lIM(c)*ktG?PoElx$Yfq z*nJkda^Q_4%Lzs~C#=2MRtxqtF!b9}^z(_t7h!%2+#-PT9v?B!@qjR&Ll?&us0 zE}orspM~ytSK55$>T}!lb$VUHd}|;ldE#nLEHUC(M`9S0whmW}`rXz?=j`QZPK^hH z8gCh3aB=4CJ`3&nb0j@0dv+ehX|HRTZwsj&&r4F=^{?#i-wHeRR%V zj^@;OFsSjC0fv{ad0)TUQ?mbzGZvY4`;@ur8K(_NPJI^I*~stgBMfU(cDed0hCdTV zIogRs2PZMoZfrg$CLTL+X-|yje{cBYXOnd*E2j0%>M&QC7@02ywRjQxQ(ySP)7QTK zjc3Wcvl-t>IA>xo?`&PJHr8Cd?ujwJldM^Jd7TqebHDie-+SKkYW>c?s;RE^=<^we zOkd@O_~eA>)h};M4D{~%vh2|=KdzX}!9aGr7yHiVy~UW};A7-|ncE96*RIYk-&R&k@)OawC_OixEJd#*pi zG$#GNpVNl^RxCg7%I{fc{5>&TSJ|zj54rO1#v~@()}h@wQrk>+@ttjiL2Tvd?3Ize zQ9kyJlcOus*I~$!S?|dEjrb1lrP}0sGya%dP~Mw-u;aDHi9eI}uEUw-r4NjppV5zC zWKUQt`yoeEx<93MrS_RLR#{`UE7Ok+E?fDqbA3o+Gq>vEk7SL%=6wg`rj=u@=5oqg z^Qr1A1~uMVM`Bd2dUVcSj^@;OFjl=^`N812mEYc)e@hJ=KdxMHXMXYHw>h+Da?j3{ zqpjxVb8pXLc#gdnM&+tU=hSgDr^bV^>fOo@2G`NEpyx)#gLzayqu$pb<}R&4AYpz_Gd!cY8}Mr zf7ck|D_s|9t@@BF|87iTK7tN^b<}#aBdagxv$&a03^;P~ zj@#~XCOMdKr9!0MBaq{F9KYo-wz4SWZmuCliKbtGBy%j^8 zTk3c;j1~V~`F%~o4bKiR&gRNVZ^aPjmO35{V~5kvV!XGUea{Xs&gPPLj~c|er4BLv zv!DI>r&|X5xh0=mFjo9`EB_8g zuJ?KB_t?v8!uUMz#UrmRPu`J_ypOB4s{>aDt`1xsxH@ok;OfBDfvW>o2d)lW9k@Dh zb>LhFM$b~K-Djmf6RyuoyXVIhV>GvNF^F@$QBJjkQEf-(M=-2!)DQP`tNcH|`q!WS zzd!uWTi=|0%#HlMZDb?<>uF^|ky|CYg8 zx8#$1FAV#BR%YLLe(Li`zthS&GHpI<&3yEGU{uT_bJo9QxQ9o;$ljmPvu`}N^qHjJ zY2_T5HlJ~3KKeZ{D&~)$fm!y{m1@6YJjH=e=z?9uPEa*j-!&ucRu{T>(<^T?d_ zZyE045iqj%XY}mbBRuQ0r@T(pM>1n_AI7I&Ta_7;80z@dM{*Uj@`F2zd4&5i+WTey zusg8gkfZOfCQptmudSSs7;Uq4@(#?f5<_094UB5jw=2QeiFHq|XCKJ#F1TSGH7@<> z+vSz_;Kx}5I(1Z=_N;M7Cog$E93=Ul^NSyU z`O-ea+rjAat=rz(_g%^FFlTJ`?kEql1~Hf)#V}V1hTqqWoDE*1_`5o8!2r*}8r0@o zne`IW*|psZgWkHX(lwB*Q)5d7^z!F+N!?#TT?q2U}hhfSAJYMCA_eXSqycgUtZar@#P7`kVEe7m205a z8o{YH*D?3VTI5=3i)$$>|J9DHy~UQjxd*qMVQ|KWPs-us zfmuW9_*Wz2(TSI5R_@BW)mGHxheH9KZ@bnN^qXPmzwwXC*Nf~l%K@di+f?X7dbY2 zN*g-1wDW$^P94UlU7LMN4C2zB7+Hro%8Ch&zQo8n^eYo1^YMf6h!-_G@^5~lYe&v% z_q(c*6(fB!j1S#cIpl1fyilF~sVheqBYv9q;0Qmi9NTr%PEFcJ>-#sSsKeM12C?o_ z?8=WTr>y64=Cc^;NPm})&GWi=yZl&gn`@xgb*Q7-T+3Xsh@D-=)pgth(zS>&zG7ya z@jDWnow!-dSqy5f`(O?)bN{TXu1uR8uHwuv$WM+p!VnYxZ2w(8HhE^vWgpd6=d4|A z&Uf;Oonhb~;aIo(vhPZA>0kB`UuVN^ACd4fIf5L{IWlc>xXfQ+ke?iHgdrw=az>-e z$0pCLdG4XwbI#g1Pkkq!_!$QN5sr1cFXD2LOaHQm_&XbR`-p^>an54E;nbCBlfz~H z3WIz&vUnp5G4Yc#8eKj%d1lRHztvXftX*x+ck+pyVc;L(ShxGKPs+Tv+K}Gcw4vKa zBxg=J7aUGqnYPjSbcR8Ga=a0SHQ^^`G`f6j^30moanc&Y+STTKC!g3E2L2I_b-OS7 zt|XWKWe@RnHthBh2_NEq&v+5*W}LCev|C@|t7n`xC3y_$P#?*RF&}As`n6S=F=Al5 zWq@-_KDm#CVPCR$l}|d)81qsl?=+{%jLF>8S%=p;x!?KkfBdb14IeR?Q+3wi|AQk8 ze8kke_cgbd?`yp?>&OQ?DgV=#yilDr)oXk{lLlkNPqXfhAG zx$d{}W6$?zC3WoN<~g2tc_sOy3*ITWU9AB|#?1UW zgIocLz01e;?QehkdF0a4vRJBmAtVcqt{UZ-T= z_C$M~YqyWc8E?hUnmo(pd8VBD6T6C8`DYmHlRe~f z+q9vB&*C~5j8zTdAgO0GGHyo~$M-SwoxIN@X2u~$zT}70EKeN8 zuk7x-NA^sO^z59rl};V2n9e`rm0+&;skI`( zuXX4@vv#-s2yeCzYlFLXb&PCvFQd4b{V0YtQZu>rdghiN``nZLP*RJ!9PS7P{1yy3 z@(4pr{Kh9Y7N^U{_VwTR@1BRXB3(0WozB{weK()n5eB{)j&-}Qt`2SX#d(KoACd4f zxfSPfb!FP*aGAftkk`uPMi^q^mmiRm1ACW`?Kgk(H=l=fBV99XozB{weK()n5eB{) zj&-}Qt`2SX#d(KoACd4f&&$b$JDVIX^H&)1S~>CvLrnbCag-nb>TiDiiDBJH_MY>R z&f1-CH=o=Q2EG}Nb=#M&4sG6B=N+znL~>S>TXB9$whd>kxk>XX}GiYsj@+ zA34Ku|8mdzjCV9={gH3ieguPkfa|`3v0Z^<^(PGs(9&!>KFNMr`J)&oJ^_2%j5aB-ZE|E%q)Sn>^FHksW{RY^f@cH_p7OQ=6QPJZm*>v<`lAd98{ulEzegaAs@Z z=NiRW1M9K|v1!Zoj@W0h-Ci;tKlM^eUKxX)KJ=Bp#*Ef6j@OXZq~$ zk=5sX#w#l~C8uK5HM%-ntIMCQ4^~%0;;GMYtXb~)F1YOolRb=l)y`h9*(Y-A1uwJb z#yNMN&gF1tF~A>Th?(`cZptnnn>;gPSx+vh?DojonTJk%wax4uhWXZEeqvj<`!c7K zxrc5am}!qcVrAYYgZew)y8~e z?=Z}_4)YVky4{yKog|L|9;1t~k4ShK=Pbs%;KzvzzC18%NImPZu9RIqHhE^o+EaUC zUor-r^{b88YGXdKcNpedhxv(N-R{erPLjv)-gYtBOU_B2+wu(8*+yroU5uTxU1C{7 z%B}`%^3057J-MW^+avwRJapo#ZD#K<%(o8n6WhAompPrxJ#_mZC+}^?7g^^BHx`dP zd2Qv$-8z{^{PXJ&IUD?pY4|r-vo&066qB_-@BiaD&p~T1Q2>w}lt@9N(O-^b~m0+y6Y1RUt6+>=IowL2Lr>v#JSk;hy;r_8^ z9oScOh=U}yzL$^Wi&5)1fqV$bOWwSL2*#Z<3F8oGWc|xXedS49@tKx9wnvnOGIam@3JU zXJ(FkLz#7C9C$fLulrywPK{;WVPst9Q-}E0ZErIN-8}Tnt8vb=H_1ou!H@kM_4Aqe z<1-vGapi$oL+V*iVxVVS>g32XGsk8z{|P0X-jUJ zcJ&TJA2Rc;3A=UM+sw&4^vtVq&TDUyGqMK!v*T_xubY!^t~@sJM>)^ye*D@*&IYef z{S|Aq4)LQHFs&Qib<*Z~HFjnn#e%HZoQI^ z*1oz=`yz)MVaP*`S0?TVLrnZ~#+3RynK`bBq@I-NOC8Ldqf@h-)_vtZYJ7*0F=A*F z%RSl`{YZ1N7WEqEJo}S;lry?KOPMw~T;`*TAy+Y1xq~4lSp5I64*i`ZN1o}LNa{(M zzSP0YIl6o$xl`_=#&;MQQ+p$pb=%u6Cu>o!an7?p$w!mNa{gkZO%9j&=wiT)GyVvM zm|*#x&MEbGk{o%aYa;1q%JiiUX3o*=VZFCC&UuHS59z(t20prV+gtsaleMVVIOo}) zJbUFCD$i2tqcfct@=)WIi95ouCj4^Al=?eKjy%&fk>saLU+Q4y9G#kTedf838sA}L z3|N`3Zr%2!%OTcU)N7pc+Mnc%N6z>a$2oN-PU0I=<*hNS$>8;?K9U(@9mHBQI(Fi! z4SN?mvUjnrzk{89K_9Jy-K$$u9}*m5+#h=SQ^)7)W+g{Ba#CKGv$5i);3psN^id7& z$y!D{eN}@vtmFObiUoqU%iXU@s=1lGI99`}DmGnY=SC053SRFS_TC;l8rkC}Cm)T3=)s<G*I#9akujt7t=ryaPOh7KPzTdG&C4FiZQ)m( z&*fRlw8`NzA6*Q20@D17JHikXzj$Kk8%g5jndTs^L*9|T)WOZUx#ndLYMk>9L!Wp# zSGR8aq8*v*T8nn}=E%70k=&Lu*I8~WhWxlbLrz?IVAha2e(|V@k^W96UY=NzTH-FXTuG30>A z#M!~HCa}a4L*GaeFV8dwX&tPuo_=+3)s?xItY3eX9Y)5C*0*lR*JHC=OV{(t?6KCy6-w)cTp^Ig2ojq-09mR0p zPqH(~Il*-np{YVUF?6jYl4- z;|z{#%01AVU5&GFYA?Dl=$q2`_v|f8)0;OZ5Jakv!=9?UE zw>M+KRaY7hj<%WXFpRIYXh&MNy-i=$vli;>aI;6rMLC-%Ka-1Tlfz{`x)^z;H)j_k zF~uV{?IWFdd8X^ift+`H(+94)(s*#R&18pRe62-0(z@+!`l_C_$fK;&*z8eq(d4n7 z#nL8+%Y1Y(;Kg!e^N~9ki76hrX&>pt%QIcqembWI>Q#bUZN`IBecimnFs|039ckV6 zHhopkTJqjzT=s}_lxHW-OX|wB&CX)PcYF=|4#udaw2yS^l4n|X<&9Z`F-maNmBxdk zZ6-Sm<7+M2k=AW*(^vJZh59<&+9PoJEOc_GKWk0LCo$xRVXbE+IA?Rsdn63|f}~INbynxu%gj}mm%=|sgBasG zdCxj({myszF?A##mJ@4Jj(W1Q58k_u5sp0?;hB%@Ft8cp{;M=M^W~1avObHY?%iDC zDzC-n-jTcfxUNZVwbiwf)7Dt)sP+*?VpV<&M|5zOK04UTD4%nChj)aRcKMynIy&FX zwp&Ybki;A1Q;++Xd&)=e!ENt>VLj}}8mXDu>y=!T`0R)K)Fy}PFyv}yF%r|7(l*nH zmuFgQSBJhT!LByv;3O~AUgZj-#yhued#k-l`qLpjGxzZ+w%Y1a~@`Q|+ zv!!i>k(kz$wwX@6JhNL<+em^v@=-f`8~L@bFpR6xy6sE#JGZ~ik=a-6*{9^9$zwhH zrA-c(`RLf?#TkDDBQdQhZ8M#Cd8TW!Z}pV~dlcin9r?AdFpR6xy6tWCJNMo?M|OM6 zKIQp~^HO()(w66_iXm4!i&61dGi@`Sy5yPG+SQ@2O0cWVIXKBnwO6^qsPWFN+umxg z(*CBs+hf-!usPSuZ}auh>0osBw21|-wa?ZjS8g$q##B7!4E*XNnK9NwtZSlEduJQj z!Rg{V+ffYrs2pM9AJt*5a)n`E#QUi)eBtS9U;oAvl3Yf$Wlq)Mz8TNFv(>zuvu3`w zJDr%!sXA+p__=msYMyiV?rUWxjWrjEt$R;)dm)Z7+LQRmE??USW0gm~H6(_7u<~B* zD@h-SIh#)|7+f7U?$+;qx0(Ddc4o`ZiCf1kK7I0I`Ezn)p4mF!dYq+KgE$9!S>=0H zPuz}I*5}7vUSgovI+)j(oL9`Q4)>H;<{=Yrln;ina!>i_J-F>XFsz6DSR*x4d%fhM z?g^O#4CivV4#PO5Imon)FcNborpC)NbKPAXtXXWj>GxXee#Zk$rSR&hrdiMbO~yT?ye5jEVkQIjbCBZc<0vbp0!uGQv*5K7r7{B z&$HYX40Dy>InpK%%o?h`6O%c_%QIc`hy-gEPhV$#d@GDmzID6jQQS@q)?sh*9K|`w zbC)u0#G029yKzeMJ%>3TVN|@FuXHfwnXYq0f{mQ$<+Y2{R^=$>(;7Zr-+DV6ct`9LJ@x@5acSdI~XYw+~e(dt&%vH}iD(^LBC68dt__6!xxG87tI^=_L z;!%F)d6rL`IOZd}8pt&-b!=jgCr-s6F7s+FVwkT?oXoGajPk)TPwpuny$83w2Zr^q zCu^i;dyo=dmc3I~+Gpl+xXeet2S&v_GG~2xrga~YtTl_z9*p8v7^6Jvwzs3Wof^o^ zzQN1nVjTxdn;b6l(eHs#F^|kyU!Lh+j!4#;#b*yjaVv~bo^{*XQQS@q_#n!WiXSx4j+3?bKi$_KtIrXQu9Kmp0E( z<|^-jA=Vkm`tnToazwJ`EIxZMid$if@~qq5j^cJ|AoooF-enH^V(_|CAIXfVHI2A$ z#*Jd?dgOGrqxO4XWc{=CjT^10e+MIDA4P}1I%-YYk=2*;S=`K*7$3U6;*2Xl{aeMX z{N96m@#EEcBma@ToZ-iuv%4|kgUKhmylU%kj$m~8^yCNzHQkCc%BL212y4ha<)ioD zw)eoWo~*HQC-%X04{m!8j5pPs`|rEv$0N>~b}G;LuD;8Uck!}=_u#hoz{r}8>hGE# zk2q`EsX6Dn`Yu1-#mnB^gWKK%BWpUUziWOx;;d<>=A7^9yZm?;FMG#%TTa$-u(T1I zx$5`85bKO&eR-yPIU-r}NPHK27Q?#jZHIltcBX&tI%mXNbKOR zTphSNaCPA7z}11P16K#G4qP3$I&gL1>cG{3s{>aD9=QXr>G|=aJac{Db+{-sJo~)|hV^8P)SP?gnd>d1V)=ZNHPw}AW6jLHhkiXj{^9S8(@&nc-ZJu@ zT+ff+e6D%-8E?kDi|5BYbG>Ecy}h0vzx&TuA9;Sv^U7OB-dk{d#+lEXJDbl*Gyhf{ zOrOKL<`K#IN8-EKvl!NGUpnj~wp)AoTJz~IgZGH~NM=m-!}#=Tt1@FA5hH85^s57p z*n!v1kCU^&TXNLT6vJyB_%c=v7Z^0<1)|tp{{g&<~28KI;(@}=agM@=R2p3WnJ~N za)mLf$GYvy+4aq%4md5p_jqQI3x?Mt^5dQFoR+7u5B0Nhg)yqtnPFpkkP(LeI7^8aL z1wXD_E$>tMv>%a~k8W;f?`+23Q|_!o4Bto0`*Zfa#2G_Q*7=$+a$lEzb>I;@FyhBM z-?znnM!#2#o$uFPeaH3+1023Po3Z-+W4K-AwVtCpUL>a^k2;t;y7+3#d@;c3>ZrMO zE>_l&F{6E8Coc1?fjS)SOQ}7jJ|q}ku5t0>HyybbKL%$tyay@Ck!Sk481Yg4DZ$G5 z8GVH@s>iz7!wO@kE-)UEA5+t6PalM2u)^4>3k*2z z8E#C@yW+>-t_HQ&iyV2T9CpM<^``_c=V$a4#;6|awx?&-&K&CCtbc|Zlhg4AdF3tq z7~IvM_Ii;c&$NyaAJv}{yqurWR~VyutlOTRSvzy611vcpI=`387}@ocTQ7ae5k|Fx zQ*B}>>sq^W^Odu8#3bg-_h_>Q7|632)_^>Vaql{^rbp1}i^Kb>)Q+6h>UUKm%~hsv zhH?2Grvn{7t~@k3aPnPtA%8W7T=fw@?l8znd*bZ!W9otL!S9k=s&n2}SIP;=)pkUh zpPX`51K3?1*u~gczhaQfy6l51>q4)X)n8-YpuG97YhC>KO+Vmv^u)fXE4|0e<$>8x zb$s={(bBfy2dI?uK7o znYNny2I0+rS2c}tvCA`a%@HRxM#<{xXXOloIi99e9C6a*ljvYo+tK+9!+YCden|Xy#@R>bW9;%wxgt0tPHK#j`=WkU&M>H>`*#D@ zvTpZwc75|^ud(j6r41fIZoRM@b3}qOiyy65*YD;>YBHbxGN?&iIja*N7^@hul(~*I zAUl8BX6ukY^XHn0sZM@g+L83M8ySd8C1d~6wGUX`rFdxZG9M{M|rsd+hP9sbVo z2m>E6nKROJ-&G&wtnB~p-~98-J*+T3B>xUSmM_bDmGnt{C2z$rU*1gJ-F;ed)8rqi zgO&5D=h?mboM%0|b%1L=5{zm~|44`5CO;mrM)^qXWH(i0-`FfzA>dLgq1GAs% z_=ts1)`5MLi(Q`Sn#jtJ>)cq@SMTH~FJosIvwZ7zZ;7cs%Qr?z9V;#h2N?~y95`Ie z<#HVlOk2%;gYf3R)&%FM1B_8Fc6p|CAicy$@V;`bkACnc zPdTh<_TH%zuiUh9VER*S&Uxmmjy!`wo}cqJ|Izp{=XAIXBVR9-AA6q9c=J+%k@FqB za$_-8YjxP>#6 zb6$0Esy*i;jJl7^LAP%AR{hL();Etj4n#hXP)kYlaXI|CCsrH~eTlF*FS>HVBxEDVLhv9hz9;>cQoAK08 zFKepYnE4rgY)|Yjb8AXT9XYSMIMtr>5r%6kGY5T#AMdO$E_HYwt>?d;d7cl^8S*po z)s<;8o*L?fk68F%9oX^L9Ol^NnXZYfSXoELfsym7i&O16A7RveWDdG@ySM6RzO%l0 z)B#rVV{BX_`y21xe@7Udy|eADax7|R9by=~2U2QJX$-ck#F+GJQ|ePz4DjTmDdoSe zmp*MNM}F+-qYmbot4?jaw(wV8>l`03-9ErcPHIk-V63=l)-ceU+mX&bM!sG9F2-4Y?0LJs z#_F6p(pK|4k5_xHPRlzW=Sy{H$@mBT0^2 zPCiSleNDZyi7Q6-6&v`Y(dCejU7qQhNVuu%p{Fl(Fmm4M<|}JVhmq@#>acG2R&kxz zb(vQivD5({`@s)>FnmVLxxE|Y$mNj6$j_LoE7NAY7}VlrO{~$4E{9y~@=Vu6!dqMq zJ$1j9hl%r4G3$yz}TFw_0OVTlDm$4o1#9-F#(@=`eErytkQa-R>=Ysn>OxR~zxv!CC1$U;X0q z*_Sih?jXlA8qz%Z8FO`I+Kd;2TD+`@HM-H|kc(ZO>6%Emsq3MqFLf|--s$EmYfOic z>yPTNZueGko!51lR~xa^0oKKj>BJ8&d*b`W{&bQU?A{Ym5~Iu0mTOcDu;7cvpo^c< zm|Oj|3p+VytQqhg!IozGqW* zuM6`!>AwEc|Mj2w^ykm0mt8(KpFPX5kLH6xjyxT_m%@|p!H+9{{XF<_<)og`m7cwk z+LYWs^|KNTuUgA!ZSAf}zIv6$*7}d|6K?kGS>YTd4$h~Athtb8>I;`8hC1&d6<*<(# zgH9drG5A!+LuWQ}aHY5E{8PFa~E@UW!j7vgIc_-$-FAN z9CESCGc$)?I8R-h5)Ac`%zN9#bQt8+*jk5mySEzeJnt*I_g24>Iyf)Er#c=wvytQZ z4C&cX9>-i=nKt9apcXG{GOx-mhg|IP%*;vtn0m&bgHdgDPTgSSUNWY`=wfRf*6rRB zGj;NE*vE`Prw*{>YU0l9_;#-&*rV9pwRYon)*U_ZQ$T+5nL;v=T^!a9?aW?t%Gthi~`k~|xIRYO-x zhq0Qt9rWitfO1k+K~D? zZk!lhj=3poT<*p7UIRbQeeUXVIix)^m$|w!ZN`g{eN{KF%25t>d8Rp)%d)Qg(ikP{ zs?Q|(BmWLYtqCmac5fZNdG>Bq$BK(q-j#E?9MZGAe&*`Rv>7jk_cNt=RgQA7%QMZ9 zXEvnG)zIpa;RmX~p<{7B!ayg`Y*O=tOoy~YL@}8=j zSLG-NyFAkzK9fx^oaepOret09nWU#9{|<)tC^4+ty`@jxyu7#is-%wY+*Z$Vqq%1@ z&t06oj7`0>?VRt-tI~C;KQHX^OmixiWnKBDF-ox1XOjGpe+Q%11eSHXw+`Ptd$+0s zEYJ1gQs2mqZ}%EuboS1+yUvlgopp#|@Lo_K$&AT5j8DI|Dl;ZA(B-4Z>T|w|S^0O? zq2}4Yg{6;JYfC9V{^+BRo@QgkO~L2&?Y`)r0~@|k4c22VBaBrI;;@eIpP3xhgPj4d zg^T8UI-lXX-b#X%&!2aCk(lI@Lmg*1cH+Hnz2nEpOGo@TW7OroDe)cU$Ks^~^Pc>e z^Eu~m`i=Cw?%7;h&b67VuimpAer&FCR);YuSNu3*(YyVn-96=Aw7qnG3@3B{yOJEa z9MXQ8Uwdb-(oc*S+1J$NjVVVt*o||YjziX3jb&Z+nd~r)-@)ket=qkcha{enJ$0@x zswEC$RY9Pg>%y?@lL9UF6f+LhGd87|LmJGyydaPIQ#MSkkyrOkM7 zz!+U{z&b`b*yWk7*>OnMHbzMe>NDA48oz_l&tj_>Hy1gy|^vOR5%wx(DN|e#v}$hvGw&cniE^| znafFiZ;j-Nn}VNwywgWDSeLboc>1aaaahOq&oBPuk32D)Bk_-F!A=k4!O3gwEw#)yYTi@QqQ9xN%|_t4!Qc4aDX?jC;xanEk-Xl3U8vM$&kD!Q4I~ z?TbBCrq6hC#TiNSk#+6N!Dr3pz#r9D60B7W{u$;D279j<)@?tmgE)ItZOrvmJo^GC zV^khGnj4?_@)9KcOqsk*TgJ)<#p+~Y)U`7npFGnXIEMO4g0+glKf~O?VE+}vy6uN` z5NE%tjk&&xXJ6#Hm4}Yz#%I3#SYE8;?B>vxvGPH&I%z(#uATAt4_4d|jw3>5IA?&R#I*_mo$?tA27SmY7wJ z*3LMv6m&w04m#Zt&CWkYhIlrsy zd45-tQ?bOX(mH1G_~e=X?lc@keI;3Q6|=%Si(%dF&;4fK(?%W6ku@&+2QTBS#*oK4 zuQoZH`ONuUu;jfvIyn_f%qp#A7LQM!>H2=>wey)|&6yt`J)OnuV01NExBGLy+%t70 zHtKMWtZ~^t&R?FHx^q<8>UoYi&v}SBGRUb|SySrPHjBq6&-A$yyit86S#K4y!aIv$ z-R{r*X5Z6R`EZS+j=cA~y8Mg$yySYjIL3jY-pSE=x%NmuQj-|;mq9J+BWa9vgQ0#D zgLrE|KIi*8%Cp~L&X`Yr_L6TMpZ+fOCPu9TEH3Q6zD9DC`v&2M9C`Hb;XAzao^_1& zv0I1SxXOHvJnD)2kRQWQvmWjKyd2@!TR3dyqwipVOI-c=AeP)2IpWyX@JJGloIF{6 zn%ox~HS1Gm47zq@^@CAuHP3p8U0rg~JV&EO`z@-I1@`R1;;W%YwwvBd1;DjqDj#o57{XLHTF6~lgbFGjMv zM;V*_gO_alp5@G4am(swt%{X3Reco?7QFH7V7;@s=G}^6KRmyVWZsW1hC0CVY>y6~ zWQ^?k$*q?@RE?6zMuNS7oNWM^=~{&W2?>hPV(F~|1tcCY8O5o3HO zeSUnFr@MFW;Y=Evbzt-TDTmGau{kG>G1?RMdwNOT?CVUQ#eYcrxa(!wvWIe9I9A8K(k6#9KXa3# z?dTOt%#obM4vgVn*%()fA{q6Q|6qo(;9L0Ibvt6E}(pJw4vfi1z z%()fA{q6K_7DF9i`Tfk|&g}ShuOrx_*xj{u<962hH0I-W0nK1*fXA9YkSmra@G-VUBz_%Y8zqi^hGY1oVe5F zorx(=M5Y}*ed;yl=vm)RG1 znS3j`aA%XlnV-4F@5+iL<{1fAa@C_cYaY$1@wZ~wkKO)_;StQ^?5(&l<^Jp2D0VRZJ+w!hMTU09AiVUJaj)0GEj5r!9gl*M&w|hB+0*2>XZSJsXGhj?i+*-}^H^K`th^V-i1+T= zvyaJf&+udN&yK9)7X9q{=CQW=S$QuE&fd3->?7xTxz-teTzSEfdDd}@eq_xqmbE+k z5!<~myccKXEq#3Lx%c3duLRTphSNaCPA7z}11P16K#G4qP3$I&gL1 z>cG{3GadMif9HREx@EBDE&1f0sr!+0^4ahioxPpO%bZ8<&n>mRrsv0J`jF3D&*VR9 z&MnW7+0!lg+0(N)_vq_?^Y8!j&p!*5_rQDv4EA$&Q2*In^KQksM-O?<0*>+?n2&(L ze$Ed2aW>yx-kN`FA9=0{hVoXtN6A0(ytu~Nk7_?MzZK(A`uK=-cpvg}&)TZYnB0qT z>DN|e#ylcM)^zFf4t)7P{Fj~>^QhP1y$83w2gZ?Z{ulq^Pe0W`erHSEnIGQ{M&gZh zd8%B_`K|Pizw_JuUa+|}R=YC&*mACJ>{037U&HSoAKg3Ljs26y>qB05 z6z9?6*~hp3;oo^4v)BE|XFo>%9s5}f>%R3~z~z4PKmS*s?=9bx!u-+h)0ukM$KU_2 z{>|qx{Qj58-{fG;GkKYF7Q?!4y%+E|&WF^&=dtB=%wO{5zStZ4Iy%_<(VQB87K54@ zx84hQ0H6J(j@R(LF6@Jgef|2Ee(HI!ucHHuqd7JHEQWQn2QVsE!^YLwcV7?2br>V; zE>@i9g6{Yqal19GkMASqJuv%T;*0?+OJtt@8IJ2bDn{<>(&rt38*{He1dhDowoiZi z|9WDK;^^eahy7RntuH^1S{RuMdqM%Z-yC^SZc|m+kibUB-Rr{8(oqKa%M`ZTnNM6RC#qgfoavwPB`+Kp}A-}5} z0N+cy*n>RNeI1eP_egvfdltjGy$?va8#?!#(b;)j=!}l) zG3xr2+LhXoVB}ogSfuq3ySm)ths2MQ8?%3UWFL_)og;t9{5ZLB<;R>kZhdySdOy8u z|A)+vIYWMWe*E}+Nw1UJdDeW__uyUO=l*hEyL#=%ZeGTn#qhqIy$764<;MB^Se~~! zyS}S?axYx!muI@CBa%9f#CNf0F|6Br;Jx0FzNSa7i}df{8N*)GQzjlUw_b^t>!^!Q zZy4CsN79(YP(O-6yfq+Q(>0uT(iqn!U!8dUU9Pr_AwMr;kd;q5&zQ_rPu^)xl^J7R zm8`>ig50Oy{j+ZkZ1{+&c{yht{`T<*10OM&v(n|p@Z(!KvfrV-TJ!Vt%N=(l95{I} zIn=(BOUxhtwZHp3YW!&b`OLR++d2oM+K$e5Fsz;0Ms+85L5I`G>#DDtgWK6Vo7fe9 zR2y7WZrb5t6YEGFBdi?^>)CmhY;7M3Kd#)E{dYZm$sGAZ<;Rs9cl`LJbL0<|AA3&q z{D$Oxo7@haIP5R+%01S7?abNpPTVZ!EQa^xO#eBTCO7W*@y_}6k?%QslxKF_b!B63 ztC*GlEQWQT2N5+CtFS&8X&2;i-{vF>D44);} zx^wQ{KLmcgh}6$R8pk*$n<6erM`|S3$ zvW@z&%1i%Q4DZR&-g8E*+<3%~Ics~Sd@1+TI;mTpx#F>%ADqs9#C8_Ly6rtO`El;C zv+LiHN3ch+JL{Z{8Lf$(!TUgcBr_)KFh2d-s?307nHYsLD6UsESBwU;@6tCj|1RfAZp!~4YdF81Wc`geXG8Ez~u zR$_ClT`9*tlj|B{IPav~@T>&$Y_55=u3c_yKJsWC-RH(D#)unhXWfn?@9;)rM%-5a zN}4ZkG`{xqC^rTN4DpcNT6NuwuNbpfeJhS^+-v8@BW}$8>wU?dzeJAwq4DDpH=gn1 zm&%bpG=A(kjq_j~yl<1+r9L|wlJ|&L?lt$atFx!w*iqbB4DZd6zH<)jxbci1@0?>F z@!qmmd8X&$Ba%HGiSJ_1VpunO0K@Y;XMAllJHFlP2=*v;cdavVqcyejKJecNM$#B^ zvW9zLfalMV*?Vynv-0n(!+v-je#(#IdF%6MoImEMhxzdUW5rEBQ;Ym~qXzHMTl_fk z;o9tDRf9OzlIKiy`h}mt>u1H0;iU57U-|Xlcw!voxQTK0`Ez2Rk7`JcDept3eWuex z{b%yY^*$&&eoWk~uB^j)X7VUM7OU=Y#*gK4zLs_+d)bX0#o0?`^5aom)n4ns@95U; zd=;b1OWVbd-~6p2d)diVPhT2G{*d_bj2o}`@k`{$9}++Ij3)Q0lK1V1=V70n56OMB zEB8Cw*T~0yM!sG9Sq$&ZZqGUYjkxiOAA5#;wEH)!hyBVkJMOx&fxU`Z`OjilH+ulX zv-`!5f5>m?3I9LweekT0zNd?S>$~NU0J>fO^%#SOt z{bYWKA5Sn=-1Mzl;I?Aj!jB8ass`>2m+znM{hIG7xbY4@mKQ5W-1N=rpZ#xgTev@l)T~Ul#+0ef-!(_3 zMg}#f?jDXxF#J17(=*uMY{q(L@-pWvhIQMI+1@?BeR|&XOc;{<74I9J-DYd=`oI=H z_1*j$2ZsAy?W^;H*V&KQ&SF@%{iu7(Ip@O}H{RjLoGrmg+&|}oY*ZWA=$#$k?lr>b?450QowISHHL){%A2{zMG1{{ZZRyvRvaXRB z==pPG^nNtvRzJ0jFsS)EU;X0NAhzynxD4Z z|GoLK>sXs}WUVvzl5yzNtxYWRclmMho!NPvI;!7X?ab9XnLWK1Kd$T4nyR09w-@Z@ zcKi5H`7s{-VwZst3SVcqPZ?kne>-8=`5 zBsn|WZ*+dkHIL}vWsO;fz9V?XCg#q*YE0HqeY^9s7~UiH;C?#E*>A;-clj~r-PKvL zt6^pXFKZ-L-l=bvKk|_)&lK;7%sP(bW$alD>t+vNAU)S>8`)Q{2lBX#JA&QCit`5b ze*YD>ThmpDkuNIE>teK}OpIzzpZFPrPJImgDYd6G23z77lYVVVeaebqEj#aZV!C`V zW_RYZ>wMRSn{>j_;pN&g$r%)yhkiBW~*YDaA8aDgVycsvCne zFD3SxBj>A9?$_CqAA;A#CZDVrb)GeK{MhwsUakv9_2+zqkukd%V!8h3!H>H$e%i99 zNBD8_RWK5>>hia0&-n<$++5GRBmCH$l^oTv%1i%I44z4o&mPI0UCBGMH=Pbn)-}^t zylqyKJ}|oax;l(CcSn*V&z$XL7f#g6P}F|3*`qLRR38F>t+vNAU)Tk`&#epye{;UTQ7aabr{4{J2=%woH*!ptuymgeTiAu&-a8l zHHJADGru{xH}zZ}-Jan09$ANTr7`4W9UVsIn1@ZBoUWGL_j{KQo}9G4mg{`QO~GH) zGOEFSS<47#Rf9OJL+^g^Cx7I5*jwi4vpV3MKA%M)dB&NS z(mGrVnf$QR^E;=L7pjAyu1uWnJFn|!p26@s!jE$u@^eltIp5JM204l2{;DJ{IkIQ) z%Keh-7}H7W=K)!LKi>IFv@z77epa5rpg-)9 z+|ku>&+Jdt!Kr3QmBa$m~<{oiZm+X!i5omq%gj0GWuzQf7$JLmb`XPtG=%bJ;ey`1Zw4f|d*>*YUd)}Foh{O^5T*J;f$?gQP^ zjU*Tq&Zm!iz}DC!9dV=FMtK-J!q*nZFy|FH%A<<2i{W)*4$dda%KkL&JBiJE;8B0H zr;K^zHgTieMtK;UF-^lYO%dNEnQi(%f(0SvEoMY^VY>mu4Kc>476hK6RT79Y635f9JJMzv$ptYu1ybb zh7tL3D+hFrkFVoq-Wd5HHu-Vpg!+Ngvk~9o$9YV1X>sH1D`wV%k#(a!^UC%aMr)qd zp~a6Y`||7qHzbd&@Ac(Q*$xIrj~f$b%m_oDT}ckc_Qug3Wsf7ryrciD9rKv^_UyYD z_%QJiVkMxy1M)BBoG0eL)#}WVRab)uN z(LN!+<5R!K-@wWTzLmU)o8=)^9wYYgk!Q~ETJ}mFt+=S$#V~K?5a&eOedu(57WQ|< zRe7I&j6+=Iz_*eYSmO7jSfg>{SUsGPzLLi%9@{R4*Nr*kIac)? zp^tARFMKQG&c?g55xxo{s?o1WB%7%+*C*xGyX==A}Ecn|uV*lqV@^vQYbV3u8BV=ch&FxOQ{{Ax`0 zKfy5nDB)Cjj7lGjT(3&M#o=;3D><#?0d8-6^zzKgJhIP99y!+jE{1vMxm7r5izAaq z;#S7)jgMZQS;dQfD|y7Y*xto3?>sm5X6)5gPx6@Yy_Vg*An~mO z`$|q=w8m-a#O{r24O+1?-AW!ktX&NA&b6)VOY=UJK6ylLS8dPsxb8b!W43fFd5BF+ zKCsC%t8pS;YV+ zj@@gw@^FmILq~mLGL|@zzwTg+VzfDidB_t-d@yoBd2i%{Encgi_#*1a|OSn(g+0T5i$7@FzVj-<*#+k)OI~Za{y*gx1ua6knFZcCV=6X1v zk0RxS)M53srq-$|;c}VBfrXCqHS%z*cpcHnGplone!KN}0>iwSgL8sU{o}v!tJA;5 z^&~%Lyv*as2OVc?tdWk`AMKMS`zXgE)&re9a}^JNaCYKi%r1s`GY99xKJ0({=YP1& zjp1Wp^myJ#&v>o9BRcnm^1X`O$-}X%12T?1%D-pZ$s=R!Vpu2U00swpKklOz@0{s~ z-{F98ImRVE9_~YD<7PV;t9dw99$%ihiiiJB9(kNy4D+_G&QIw)?JL!6dnkJeh4>2~W8ad$CtP2p1PvEPCp zM~*C4eTzICo0`&=XGYFiZG%zu?fLCum^X6(gFP5r^)2{u61t1ca?sR%b~A~n|)65kkf!c+uSPi$UeJy zM4Vj=^JWfU+@oCq);{-v_dv^Q<}Ukjr(|Es$dxD~h+xD~h+xD~h+xD~h+ zxD~h+xD~h+xD~h+_^DT*_boN+{Z{HX;rR2!)UfsaIAiqsUIQ`kbG%+m)`O9CtNj%W z^J~>3ewV7YTi;7hzx`U*T^!=8`S<*GFj}>6E+0Y0@1?K(-Inj2j87f^4;{PtOG`^}o~ zEMWZXkN@P;$+??H%ohy5`|u4a+N$lR@~Dm%{od>M<4Byl|J(iuomzFaNDz!PAUY_U0m| z({Chu7^9e2hd7Uq(vE!4TCT{Qno{q~PjPvub^FZ$)0!Uz31 z%?gLiKFp=}bHXFNWBRkefAshM%BPc)JaTTUeK0ORex4t*Z$3G>E|Dwlw83!iOfI_^ zJ8>~)cP`dXZU8s&9?CznTC@zA*6h8vOxGU{7#EuAr7cw~M&iZdQbeC*pMeuNQiJq)nKiT>zE{5Zy} zw8>+|zvX*^L99pQgROC9I&i!eD>8Gr**G)%ZXPjbFk(HTt=fJnkLq~QFW2<)fvpU}OU#|e`#PiSp`9J?Dp6Zb!WdBw*GRD)6wh;z4#)gC=MLmfU5gE=@) z^(*{1?yY-m^61&BdN4dPKVHG8`u6yZdFC$0PF#%Hor`%h2Qb*K$$z1 zbBMgtm@K1D3j-bFaL*9?tH1iI-<;^M;nRyp&p7-$$2|;e_>3^HF;3*ePx0gb{J;OR zpZ`YpZ2Y&h%Mn{r{--VSLfhnSJNrkD3C1ozc6_8`MICMO$huL#g;T{a*7=V3apV?p zUu|2veI$9r{z@A>F~Avd<2<&pj+gVucKt>&V?=oNCMy~1Q$M^ejZ|}(( zi`=f-&fky0IX$Xl_xu=N9>9Eiej_~?BYVZRi{Ukr=dcI7{P^R3KW3e{UcKh%@-g$7 z*}nTs;l-T)%|H9IKl~fdvzWKCk2qw{7WpB0%M*M4Greynf7S0SK@_pl-d*sM*KOQ+x)Z5l>A4wjuAKUsPqdvF6;8@|u zEsU&d_06eeBZroLrmOJd8Ai14=5c}n$0nZoFuq6RgVt_Ef)n$qwlkMApA%UfyXVK) zu^v6YksgeZy<*$N$Tj^){P^VWdl5@b@Au=c-)g8wh4W>fm*B@If8UE(=E8njo|$v5 z^mpqKv3D`dn>m2N{_dmj{+W5L{TVlnoYa^s!I*K= zmEWkLp7# zI3u6P8R4RyQQvFB4Z+rKZ05Aud(!+`{J0mB^)ZiZtM*qgPWdr>sJCw?kDe{^sGbjc zFgQm1coZ+j%m=$UwS37T>qq|jR{3%3J8w%z9xeTfZUuvRfQ@9n#Ck+NXf0MGIM!)J zW-ezwC-RBE@fGW_G9P_+>tQ@{7bDm7Bk|*tzwbpXHNE`!eez@Z;jZM`y1xb^a@J}a zjH++XZx_RP%MIAueH4BizxC|2sVVi4XI6P>^aCUIciOuc<{kIzv^lx%wtF)Akb?*Q zPyLNA#&GS4AEozJ&u3-_YvwbvS1@AE-+PDl6n2Y=vIm^N8 z5czS87o(EHOYmbj@o)U@FMs;&-}@(@&sC1=w`V!_YCQEJMxG8{k0(d&SmC$wLgN_A z_9$M?%s1Hh9hO)yIG*Ik=7}`FnIwnkGs4KW-uL6U2d}otqvsoWRL=)J7#y#`k7s#g zyK_)a-?sAd<1c>$&00mtb+t|_GIKd_PLG(^PMh&BKmPV-lBa(^=70FhKl&Rp?(N~D zr`Oc@%a8xkr+@a}{-saWl>MaNa^+VmlDhVI-bnA5jPJEMl`DC-d>P;UHTF^dJsTK3 zTUEb{;hd}~dt>jRT~4{f0b5*-Ih`Ck`NtT>rXJ?DGcPc9;$qA$hIumwFgR$Ra$7Ls z-|reer+hs@!@6_rsgF{7&j&qgxMv4z<}8rjqVi&7{Dy1Z zGYMAw{=CbN8GpvHBX5lD{Eau-Vw<_dzU}<`zUo^t)}!(b`IH}sEe&ryup__lJDllIoM`U*Fd zi}z$N2lFzQ9#6025cx4}@|PdSKJ8XMeyf!SM_wyWMpylYES}s}4De?d*g3(-@$}JK zJEdH)C*^|C9=S6((U#k-cz@PfUvoiL__1+O5*z!m9rKO)+#X@XoSoAOKaP8G`B2ow zHhIMUP8$r589(mf6OYY&kv-p@UW}Gq+-$r2c+8ncHO;k*{mkXSc@M|s$M^ej@Bay~ zK6|#ReiwtEt^TM#U;V^=FIQj>UgfjVj~rrur@f0o&Ya8v4EA>ZKPL8it0x%leOF|Q z%YpUQ2lX(wmHFsP{8y%x7i?v*OblyQ*VeCqFArexBX~_FFRh zRP+4a{HSa7zRzpRxyngrYre`&NBQ(}$hq_|syWDqol~pN7_EHdvvA-RH-^`uN4EH> zb5V*XFII}tV3);^zW30x57xQWH<5muNoSa9_ll}~6 z1tVfrz8^=PGHa8^%x7i?!=uHI<)$OfOZ;q{nLT5~@ytOCZI>Tke(aj4$h^iXG1+Gq zBmX~#`~8?oem(B@dzou4QALH`RMVjS;t9Qw^uoR27}Q;$!o zkF{*oK#mOm?49_uV%0sFhk0kY!jIv^qZ8~_UEs5DQ28+&HF8q*aMsGtNyA}de8e7M zkOyP+xUt+B898=~M~-y)=cc)yBmZp6ycWA_0$Jt98JF0s%VR}On|;kA>w6dxtH+P! zdwDM&`z;^lf=|n4EVhvy438>5&UnN-UY4!#b38F4jErgj^5e^oT?-Ytv&J&+F2?1@ zRIYzqe*ASGN|ma-FZ#X2-{qc{j`$m8IE!_x%aWkLIeSAh3jM?Mo>hbAefFJKs#vk=_KGq;| z*vM_+tM*6Smi)kq-++x1+kTH@6Q6yJQAVzgKjX>=t3QLRzfz3dcyPerXmMl5iLx~o z@zJN!jz2Sv{|biuSpFP2a^8Ed_Q_)w)8eNw1`LiCH^$HS7N-@5c-z^}ccOWr&Y6=E^|5ad21Q#F`GlnzFUuo zwTqE!dcPlkyP7UPzWg}y+1O^K#Qsiu7lS>&`+hLk+g*N4CB7WgL!KG;#ML$^Ro|Z9 zE{1vM{d(koUuOB^|BigX`}|#&wX^Zv_Q#{`YsfRM-xku$XJ!Yp;?o-Yd)C2?Je`Mq zd6W8{Z27lzqjn@()XzRtJkA8A=WwiA$7?XQbUObcdM=ZyP{L^+%5)(0Ki^I3w z(Klc*dl=eVGIHZs+X{~y`H=s}Ua^6}QRT-iOybq&Z`|rFu<7^7NTe%$|tJgVvFT4XK<&U-j6Kfb?z+}m%mK6|#Rei!5Z z{_*(f;NF>i`fGCAI2IYkezkr1@%Y-Z*Oh1X`0Gdy`p90f?P8dB-mi~mJhy_!$=b2q zlhJ36yzb$>hwMeqd}elVD?YvPuYSJ*a6He9qQ1ogqwmOXWD|q69q-dh-`I~LGw)mN zQ-{@<>~Aff;>Q`k+FzN=EFRge-uF_k4|tPm`CBJ>sGE)3vXe(|tdZU^<98u-qqv@JH;;(7i(%f* z1)O_)TY;V0$K-xH>RWA&$^CD=U#|DWxEUk&UoQJShsWj}KC1IW9iE6Q9-(s!Z-&u}?bSnn zxu<;e32yrYhWSu0b0p_@ZFYGnoLDU6Do4&-ZZ{9cHjmM|=-Ebj^kPQ(*%}+gYTL!g zbJ^vkaNUv|2^yCpa zX0`2@?tfz*RgCDjo5vFvc`oE~kFOQr+EQn)^;b*3m1A ztmTPuGe+(|oAEi?N`CK!+dh%UdF7r#=<(y$->c5QQ%nX~7yJCZt=Zq)nqh#)-~Qqp z{r)jHEnezgTnWdp-Q$B5957DeV;*4HXCC&|nQv8!Ax<@}Hy`tmd&)a-m=E(b zM{=eP9y`2LZY&m(bA=;kF1M41BxEUk&pUwCjZ6&`acSwXhE{3r!JZ&-V^5d^Rcb&Ph*VyY) z<;a=KU4Hz(|8Nhq)~V}qBy&wy`ElmPUR(I_3P-;D_{Y3hj!aE$ugKHy$C(>@ZRN66 zj@;s<>cm=+(zvsd)|`>>od-xZ!C zR*uPi{YEm!c1l{gqoge4# z#$HpUe73@oGnZozs!p60Ne+=?R@;ur_+Z35suL;1>Hquhe&EPEd>L;1v%m8n{J@cS_%hs>e{CLKVy|!}N8Ar}sj(wv# zF;*nwM~+!-J0|0U5%Z{GM8DlUp1{a+A(z%6Hj!m$8kR%+dV$m`hF}&iSf2qaEK8LDMpmkBlc|@XFp19 zVnz&g_-DU}(VGWYVtjOdT;;~pgu{A{ICAE4yZo3jI2qS=yneIxD34ie%T69G{fcfE zBhRJ6kE`66oH!~R`6+%(Zk&v3d*x@6TY30%$}2efl85bGxr<@mTvIUMg>u9mAFXm@ z@HoU7ab(Agyi}d>70DQ!jB7jc-&xx+V;+t>i*4D-qorTb?P8d>b76nv+GP*eI;-56 zoH)c7apcV9$U~j+70DQ!jBDF{Xhk|EbL*KU;KgZW}n!{A75hP`fy!xU2P-1&RJ)_hhf}EK7kSOnP==*+c}R|uWH-t z02VP%U{>?*`H@G@W`4+?4|*`f|Vb*T7e{OZqF_x9^OKKN4nSdJ;DvY+##9$8161srZ{)L!VcyP#eUWQ7?gMSZIc+m$^)QC`*4{94 zIr30v>`2Om^&v0Y+FIjgontaS7%`9RTj_&Q@f-Q@iCy2G{DBAm z;%BPm$4czISoQK?zZuJqmDu&|NqMl}jOE8l?5$X?Eol@dYdAMe0 z@(GMM2K9;kYCGrAs`E@2uLHhMU{>?*`H@G@W`4?^9~hB?+Gf724dYxV==D*~FpNdE z;=$=*IEML*u)uh4M~@@>^USTkwZ$Cdwwb@GkJ7)h6?G8XFgr+|)&k9HVd4&*mZSN9D)6+}P`EOg* z7bDLFoOr;2<-_n~&dPV=i1C>t|KhtbKU@9z{KYrq=r?M^f#t*SWX{TW| zIeXuYjpN#~M&LPKZ*47!kDN$wpV4$qgA(=j`1Q! z?$6>|_SHFLe=xHy_RTlyavKaeC^FiK&nNc$GtU`AJbYt3>kOUtD0>+C5a;tp%!~!b zdplbEIP%gSKaM`OBmcFHZIvI37bSQfi66@ql&lNeyE0?IqefUiWqy3hjlGW}SBub2`R-zvw{z+7<5O;Its_^BJhjD3)fp3|TsP`y zJ5H3@$JjW&ZO8m6@#9l&Y^~jYWsaP=9Q!kM#zZOCjXK(n6J_=>);(dAj>-67#5|&3 zwGBqqx97Kuk>>(VJ|ahsT)Fc7Sgdb2a^T99@5f?9jvTpi<@>Q1kt0X0{2JenSz}zk zC|Nt4QKt{OxW;1N`HZBvCm19Asx9YHjf;L6Bln~4duXrZ*W#dY4RGB((SvOalKI9y zZG2i7=yQAzBVrO`4$6%49_rr8VaAaoPo)h;*4dA|SY4L*fPu`mR)55e!Kr+&b#IRB z@xmUCJVcCMJh^!Eu*5)uYo9pVyK)7iG6(bfXy1>Y;>KQU*9W*>yUgW!{8(-)hEnX2 z)K@8%IUq-QM89f#WsUVPcQNu@TKxDaZfxBnPn8e0c&R!$@$7rC_&w>k$`(&{OgZI8 z=f_WRV{0y71$V?t<*NE4<+f2rTkMg{F=Ckm(lLK3{P-zuY~9_1MUI@g9Q!kM`XlAG zQAbRN_h#VD;+~TF`PI<9oDji!Hd9q{v z6!`Je+}QfcRprPnUaAgXxvX(Y@m8d@{#7CP&U(jy;O$8uzU57|C4`u5uJ;LqQWfAO=`v0vrL zd?V)cXRBks`u&)1#GL+Yb?m>@_hYXm>x_1kwBs7HkFI(e!?hvCel3`rj<8zsnI9V;eQD>M zVh&=E%X>ZG$dM<@Pb2R|NB-KB(TBErWwwKnbve#_@SPoU(bj%E>U!Le_*D%2D|v}4 zH;wt&21mP6n^L_}y%G%DQR!LrBh@w-Ro|Z9E=I0ts_0SAtJ7;$BCSkHh$5k zN zV`p8&`4j&HW;OqwA9?g_=BMoWfg!(C$~#9=Ut|kIdk;gqvWL-&M{leWjPLP?oVCYW zXFe@^*7dNfHEVIo%op)#;ThZF!s_4~w&k5IteH-(8f7ac+G%H>mOblQaT%xAcfQ{{ zKZcvh6?=1iQtw!dE0;s6M?N`T4=aw>%OmhPrAu^2b+_q9DaUJuK$ki9&jPo>RR6~B?sDTZ_D&HJaS zr!LeTS*ekJ#!$D6q0dad7Yy@sF7{VsoXgHyVyuXf`@5JcbI884dYEsHi+wQUm%H*k zVUWu`UakTyei(JVk@11^%716_gdg{EcyD?*4t4n6`LSH>HS6n`ayj|&Yv$lsmpa$ikn%&hoO!&)*m(?c$$6OHsswvACi|aaIG4yv^PFF@FF%ya zna4|x*~2E6oQL_XO2$}?$^NGp&V~IQdq2+J++RZPn2Zm;dA!6}GfelsF%R=wm0+yK zWdBnPYYNUizE;3Bwo;!Qllx!M`>h<4`~AqTmK>A&Up-&8^~AUtBlll+2J7f6`R(#z z-U|29jcK`phxsS?TA0cX(8*_hREaq~9V(IpM?a5_9Hnp}oIB zrN8(ujgj+e^_gQY-;qAgWmK#8V&l6B=PVz5{M%6ZTd2R2nWg^XKZW7*Am>r(GtZo7 zrJv_AtJk&P3fu~O%nIy&^EHmD*xr>-VSLOh^mem~KRZo3XEL*AfBLhSo*jMs8ErVq zhMmvv_`>ouc)S3VH{PWeldcgI(HZ$J$(%jZOvdp}6lt+sXXC1Z3V^sP%k6umR ziypozH6Tl2^=^U_fs zEk2xe%rB2o>E}Fpb$Ty)_^RAc4%^G^DQ+vz%v{zyvee)FklxD~qta)7d5lUw=h3Uv zTk8EA)ykb*{2SHE9Sr|QwQ>i;zfrB+!MHKE0xwa4T~2EpIoaNoPhq@7eSXTAa7@q4 zALXms<)i3ZH8Vc?#~AGy7c6b~XU-u{$@+*L^NCnFm)O@Y#y;8psQfr`W3S1GBae8g z@mXe0`O+9%E5>xKwIpjJ=UM6JxjebXp2mV_W?m=so6`)ZRMHrRb|e@zW(Ng^jbk=RNd${Mhfw%2BR8e(d*T~dP;$jSDudhn{^tnTr%U*wi@3 zewm+n4OerC^AO`HX^tO-AH(mczueJl5oL=5j(BN|jqS`SUmAn)oEI|p(XlRKj@5Ra z%Tw#?NgVhkyinfQ;)rt5IBtcPW-Rx;;=Klj^FqdXs%K6)&r1J$;K%Sgc%i(p#SvQ^ zaKuYn80a#md}$2Eb6&{YN5{48>`=G4n~q|bASwRtaX`0qdZ>;K}1Q$FnXWF)##zMuK=zy4F7KK{KVPv zQAdupqZA|RdhMq$UZY+=r7_&&v;VhUk&#!fa#HaflX|P$m0q))i(^FGsEhLu<0lMoQD`s zNpt_+`7ykX`o(MF+j@lqWs5In9vJ&_QpO+g(ijul?sdf(Vf@>F`0JlIV?6B@jLeT?-!^lJW2je0 z-*%MmnIE^f@rWP84I@V$@zTg6V>@%o5ysE{_)k7@#(3H*7?~f(zHP2;97B9{^le9} zU#tmO@8N(~!u=wL9Pv_o<)q|KTaH-8&|jPp#vlFTzx|0b^V1fF_j1l7_VI~*+sq}7 zA-+2LwxiU~xq$N?j}|}9-;`&0H~De;raZ%F@#FkWd4|#A$N8Ib3uEUR<(S-Wjgxgb zCik;{tDj?XzZLTmx+liX7`guvd7ofb^OJW*8M(0>8Hqmn(I&^Nqn|NavBvcw89BV&w${q&1*maOOauvh)nPgp)<6_(W@C*Z9{6~N9uY5i;N3LQxpP8hVF~{hS-hT9<{a*PooDQxB z$CD>2BL|Fad7^x9#BF0-Y-di{!+;l?Pv*!~4C7~#Yun1v`8r_I+?Y zIG#LF8988V%M;x*BISt2Dq~!1>p#PQ7mJlSatp(I*_ai{^=Rejd>t?P&{pqU-otfm z<&gO?-;U+Sv!AUNFZ1Ki-;Tr2TkAKHnIH4*SbmIc6ss-sW4;~BkFn_&CD(`Rg{)jF z{fq%CW9Sp{qRw^?Bjz`1gL8^8!mrwL9@V($moaib`o4$uN`8@p+Lnj@_V4|ZPn>e( z%t`TQzQ#o8+F>0`q1~f z05tw^pz&eJ&c ztvS7Cek{Ms{4w)D`})fVd)yX0W3sGZL~d+ukt0_yjH^hlZ7)ytjA>h$^8n{P9PIt% zcbQAFA8(!d6U!W43L|o3bBi3gm52AT{wtEXUD_hxPE=f8#Hf`7yr4cx}Z%=k-@& z-_nsfvJzLCghHUc28a5>GIr>WE^>Llr2si?Ux__zu)l6 z%qcU+j@;O5E60^1&oK0_O2(1%M%m)T*^ZC@zrcLoTC(4h-?@)hy1&m-e`3l1B`_j4 zwnpY9M{ecey{zwwWSl%#`i#@ktIK(q?|YJPUw_wFUaVAK;m1Gxu5tL8>d$|#>lgp- z6Fhk|Z@90&Yb-BTs;}^4f7e)EtW-bZ$6Pnp?kHD$@MR6@kEAbej_b?y5OvWvV_-98 zCdE9#h+{DC*sr#89;5p8Y}ODge4fCp=HK%pkDkr^kUbytV8}7yn{rU3JT%J4agB=@ z>con%w9#i?E4MYaC5dTaMPKr`$FmiX6SSF(V(VvYJbrhZs*u z=P36a6=Eso>x7wE<*ZFaa8+&c#y7;^nKaO0u)y{dC>sw3qs`9(I4;<|U z^^bdXbx&ZlxUqE-A0Nkzd3Z1Dw;~y1wEykdavtXTo+R8?K8%zTM_q{@M_%l|g&cGh zek>nG%88?{!jI*{NI7xTy)-}0Yk_s9F1DjCu2Xzk9J{w(^*zCe*sRUiueNg@J8PUd zFdkUMKY>}zzvo9DJ)8L{dwyWZG3B&9ZVW$_A4iTGF)~)>w8V>{FS3WhIG!!6oCi4f zc&-BO;a6qkm8+aIkHu`VF81XtQJ33RWR?q9F*eRajHjey^}g};^o-xLRrNa<)Pn)IZSyy1{i+iD7XL*z`i6Y@@wY##ojE0YW^^NN?6q}& zDo2i-;idR7xyg01%yTi%_a-w(Ug5^hrS-Gb@!xyWZzSQrkq^iAj2nv!|BZY&wp-ko zIC&Y@&ov`nOX7ns>rMZN5!V4e_G3HhG6wyXvz5n6eDt**QCDrpJX&>|=@=(sSIi}{Uwz<{6f^UiS1%t3Qsek;c%P7L|(2&)yJ`LXfQmv$=`a>ic_a=FKI z6^Q$IxMDt|owjy(Xv{)hioF+OZ1w@CX9G(PGs11emh9QpF& zIX|v&W3R28mzdY$$32dG`SF||SGcj)mi;v+G3;5B{r`4k5=7gx;TG)pTMl<-}57np3VGbwFV>d zTDWHBp{+iCk(Y|0)ZU6kr(OS;2RiM&_#Os0_js-Xk(a_1^BL{($sVUPwilxv8GV>d zw)MD|`j`u`u_9u{F}T2xvjA=zfoKN zh#{`F70FmR&)5em_H8?+d5m&?P1}3o$8z54{Plkge$3BRUw-_5|M2&w;jp9cvGB^s zZRNbxYpZ|6kgIB2k&N5Rck~U}G0mfz|4aL^SC!wzec)&>sDIq=t2@CkwmN71V;(QX zkJ&pj$JQR&xtm8d|JU}D8_S7vzmm@n-1rxqIQJ_Ua$`Ah?pHA6#&Y7^Z((qKSTp>b zY~@Wge)XzyWYSN1S^@#u|Jf^m;$DVLvUWclxh;Hm zd?kNuB_?8s3zuCzL2vzj@l1EWAJ4xZ!)1Sb^~M@vIyH`myt&NxMy=r^B8CR zdRF$T^1Hn6bHAUZ{*g1NJHa4!b>_sxJiKpn9+f_0=ebt;&fPp#=kQwo@L^BKw2xFb zlJH?q{p=&vjU;^7Q$PDibt4HM_SDZlQeBi>AJ0nC73sKdjbX0lVSh!& zxq!2JFjmCK{Z%~tSMqQ9&M?fk6{`*m`Q@&BPZ;EKkB?h{89$bfRQU17tz5Mvw zDzdke-?0abXOA1|Pb_FpV0@d^UXMJyZ{s|wZAR_Qd8E(v*vtM#y>tawAFKt}ZDqV1 z6Z=)17{i#kf0{=n{wbfG-1FEOBlolaTlZJ;+vUZ^F<0BW@+pkBuKOMTR^V3PR^V3P zR^V3PR^V3PR^V3PR^V3PR^UBUfd4PBdy@ZdtZ}Cpv4+3(Tfg;b_XKA**0@uQI2XRn z?VjN5#u|5u5$6(r=4Z7H&T35dKgEc1`8)sdpZ>Iag0mZI+$lz!%fJ6${racf6P(>x z<4!T+T=F$v>4UT4-|{`h_^5O7Ilm*R$xd91ImL+ACE~8OPxD~BQ;ay5+kV*!w4M=B zR~ zp4d|i-<99%y82%7t~`&tM@DlR;Z)))&v>7qqdZ19mH5hg)A!OSj}cBKeuRB(w*t2U zw*s*O?q90Eo09#&%DUD52?pHTcfS1i5B}f}KJA?3u@e_#PB7rtnIBhvhqL8F9xeTf z?gRslo%wP6xA|7uE~DSPA0^rv}%e}X}*bKu#$`aX#MV8#AU z`xL|X3zB0e2Vze1IK?1$=Rz*`c)1F&{&{c2do}+5+W1cCW$^??>lvT_pQg{yULH?i zwBDQf{}cOO>gDkSMi2YyZv}1zZUu4$cwfRXzt?yE<;TDC!@p%5xi8l__IKJR7?&Tn z`7plerM<9C*L%zFSWDpyQH{?Hwtn zL~`t45ObQxDF(Sa7joem?VhLcu8-aEjVBL}T`b~HVjVw9@Gd|8T9GHX?GqSZGrFx? zf!C`5&)s-dMvngRxm}6v`SATMo;|;)kN$qYi}szizr(4lVCbu?#GLw2_sfsJd~c?f z@mugt`vl{DKYpLwCH}wu);oHI!7?&Tv&ou@ob%IAw-;dez zj!v$D_Si<3`#nGPJ28w=J!ZcX2#!9njV||le(HB(7^8alyL~+gj<(oFm-{_G^*b?) zv3kg{To1laW;nZXJzThLypC0_yBpWb1AZH?0sWqD)bGYcjMcW+;Cp@Nv*uiHUdJ6? zOANK!jWzBBgSG3LbuH|C_a%lrc4Lh@!C?Kn?pu3>xNn%X$z$d-v!7tFuW>Kade65w zY)?lXJzG_Of^qrr<;Syo1^dbULag@-&*t5C3vpnb#6N-I`vrOV@t5zST$jxER{N(I zt})*)tK;kcR36}Xm{Yvh98YQf#$@>f#uME335?cz^R4)?Jf$kRE>+*2-wsAz53PRW zJC!yV6~B?s4n`~Ytc#qf(k8y*H}cuR$a#Ke-8#>spP?Sk->(`o(K9YctNwXJ$Xah-;>`2I6&{ zwaH`VGqayy#C6wNM{#Y;+T=0wnb}V;;@WM!=i|O%)+Udc&&+;;!G6ZQOzS;g<+b>c z$BKW;_XOkeOy$iA(tgKt@pJKQtL0*1*`LX;~=~`!bn z`YT&;#v`A=c!JwLfzf(zUVc3Ph8@>Kt3BeeEgKju{fce}qm_Hsjd*O!Mtn=ZqT9j9 zdH4D~Zsq%=7pES-{H}|9N57fu@uDin#V_+7)=M?s{Cb?G%5m|_oP}#$jW>@K*}I1M zy7c;t)w5OgCm3pfrDqw7N+ zws++T2KyQJGOhP~i^KMGoOI@yI7Ip5V4mV6@(wmmgn#9M}5_uf>!zW_T-2YRrxJ`E&dbvN^gAiBR*5*xA?WV3)i%YVLnwkx{j@D zlXZ-fb*udojJQ@tYoT?0vyMEnZnb}c5!c&jJ+;mhYFC2lMf0gwdXE^B%aPuv5w-t#RE+tZOp&sNo+U|fED`EhHFXB}%S>#BWX z-TRN;@w4{=cCadbBcD?Y_cO@LkH6eM)U>)DdVVJu@tNG>k-afn`ls>$$D@^#yjbZy zVoa7#U_8NXpTKCnH{XgMQ)l*#)pOOqg}Z}6Oy%IaO7#S<>f7_%!KlmwpBbNx_ErBF zYkLQSn99+6_xV1VVVuT`-{Z)q{J0u#9zA}0%8#q@=CLBV2Hel1{l&<}SR;GIc7j3f zt~ah1_w?#FU;W9$_O3j^Aa~as*PMHJ^_#E$>$dftZ|zfhI`Zh*s`?X* zxEE`^=c~LHKk``dZ~2~JTz-7{apj&lxnGEN@85gJSKbTwfK~PF`JH0;enDP-eED&_ zf8x7=b>`7}=gEte-Xq3j`2@xj-1Z5K)_e1<_;HKBj&xj?k-cKu!N}{O)$eiGo*s;z zt*YO_Xyu-Da@MLOzUtfa+rh|rkNU0mpYM}V9JZ6#j18~IoJOvZeRKaLHe&QKx*Fm*U9#!9--w6h}yJlGn+|zk2 zRr};o_3incV351(zV)7udyH9|JZ3&K`w2$e1GV1sEe_k$kw?!~)t_Koeth}y>|Vis za=#Gk-oN#ZpS=&F9jw^jX`f>FenEaTek@m8mDF@KCi|aaxW>To9h1kX^iT4LIe_C~ zKJi}5x%6V6#CU?+K7rACZ(e>p{)X*37|AN9)ekJ&yK)C(GzV=x4%^cc*RxgiI~e9$ zmH8d$`(zfs8`s0lJXfyK^H;wcXMC2K=gJeZU+(Y5Wt^2h*WmKw`|lsOI5O*~rLX9C zKDq8&dxf}fn6=4c<}ZA8l1ms~@qpcjYQZ%y*^DyCa^J*yvBY5;OCG>&A2D zk3IU+uEflI;0E!W`D2g%a*Imbi4S$Z{P^^m5hxvX+rFjr%;{|-iFF4yN{RN(UCFWi`)m*$H0er~#|Kf&O+0AywtF)!o9&=v-D-aaqn-V?+RR;NeWGVRGy4w4w^`rb zN3Ou-$Cn@XI5X?MXPfDHE?s_n`SI*qGHYz+)3Zl@3=VtJ-SaeFKDax+@#MjAigOZg zE+-gYX7S~}ykmAx5O!mYJH_}ii!c8bb4Q-$!I-BQaW1$0auwj-xj$e1wZ4}hum6p& zuhr|fUW41_F~}zCX8ryQzFKBnHrt8Jy4C&;#@CE*>sH`a;8x&P;8x&P;8x&P;8x&P z;8x&P;8x&P;8x({SAhQ~eSiP>{{C_6doZiNrLXAt|9JQN@%?_>`}V7zHD-HN=I_UR zqu4#kV>i~gQw-ug#N3f!?8L>GQw-;U%zxiwrGFal@2jkgnSDR%T;jZL``HTI@5kTP zlV=M*PksMB*Z60uwlZ|?S145 zTz-6izPiVES@%8LOwTjs^5e^oXPlQcHuLG(BR_Wkm4BzL+9!{yZ_n=(gE@eqr2V}f z&H=8uy8nv)%s=*b+NT(iV}J9@z>6L7SPc1*`zeNV0p}hsSAolqzpbs$=gW^TKmK-Q zeC^G6X|@xYb*udyjISBr)<>_v<;RyF_qZ-=zh|52c}`t^eED&Y+p1@c*7@p2Wo{P^42UVeOk|M=S#@#EfWaN9fvS!CU;|KrTB=A%~N^5e^odt8?_-?Pp1 zJYz0DzWjK`d0AsKpPv2l;~%fDQ*J8<=epQlmBe0t{Pn#eN2V^ecjXC2=Ercj-IF|a zV~sn-AU+=Au1YXgWAu;X$2Ru+_qo1S)w6nk3k63}r zkH79?xW2pl7UNDZ_}M}K|32K3dws@3dv@~Z*{b>z406Bx_{aQ(ny&JV?0x;E=vOZjECm6)K#(19jY}}Dx?Zm~HQw*QUNREiT z(mu_Du}&}|KZd`7^*tYP4zc~3bLx$Y{_^9mSK{*H%a6aVhIZ>UxNRPTOtNm)Z)ZQ& z!m~Wbp5I!HL4Ir5>+ufrTtP4fxf0j&g&TPGd|G;A~~te zCm5F>|MI8b_}w3$)U>5v(Vbwh&v75rde6%#Dv~@Zej}d~jLVPTC#U3k%PEzkH88TB ztPgQdFo<;yyiZ%t$*K;ls&CKl6vI6M^73P<`92Px$;!9Nsx>~k%a6Zak=A=N{~ZUP z8DFEmZ9RbjV*DFemG(|d#@)dnW_GOpJqGYrW3vAa#%vzgXS_7qcVeEvsLUn$$gx-x zt33B4Hs(N^_m|hwcUt!8^-p3mCfwHRu*!2!VsjpqzV-k8zx8XMc236FiHk8O7;(*c z4SV~fnVvjmJ~R6X25Z-K>pG}@>&<@Tk#(#66AaeBYrnNucr9-0ILyWNs=WO8?>E0O zQ`6b^;hFse<9Ry-U+3cA)vV%3US8S&kK9iA`A5+cuamY)RZ#8rdthQw;Y4$jgtZ=KDD0m&&)wsx^M=ofo-re5dsCegfkOZu(<0$F^)cF&SqEgP6+E;#RBt7XQebSWBxI zEneh1tuhaM@Q?3W*2^kJi_@&~Tl^zmi9Dk^mleO(HH_C|)@H1k&&+;;5!Xv=9mH!o zYm>*!XJ$Xah-iPe&d-TUCF85%)5!cXW@ZwDjcB z(y!=FFz(M+zt3;3Tz@&Ga%D|K-;?zr&Ityw&LO{7XV1@U2P^AV`==Q01CW;=Q_c5r z$V-)Pl~rr});sU=C_;BkXEr&da^~N&pXLGX2?nvw zq4i!};kRI|__usdG2BZaFF&T5@8gh{D&H!r*7&V=-sQ)aANTlhOJ7;fnY+&VV9tDI z_8pAMTxMK0+riAb)&34fWiI+;etXJ~SL4kizlVLNX`ji{c=0nwmLq7N$p`pb{+zwgxJyR4g@ZKmfL%aE!64Q-#P@22%Vr<2vTn71is4=YdHFHbd>=>TrLp~*+4aV4z4IJCSe$&lMbFnF(AkeMvu?G2fzqSi^KMGyox z%q5N=+vLJ~Bfg(5KYkzlxW!*bIiUh;l| zA2U~R^0(*sP8!wZiGQ03d~xExn`|4~CN7AuE6ETmml}IF6+K$o9TH@xu0w872?mG&D!KK z^O@OCFyilewchjbXU1l2@|gL|>?ar>_2;YMdj7oNj-;kLaWUo;gE@eqr2V}f&VgsZ z>YgR~GymA%X`f>F97CQQ?wwG0p}hsSAo_$Z|CQ$!Rci=vKixZG5&wb_{^g1 zy=qKLj(kQov0CrVou97;N6aiow&=V1Gt$Ja#$^8;jF@w;{Tkdhk3lY3H|zKEkNVYT zJF%-V*?$Kk=6u^9y8@RVUw+)f6O6dGV~@5ozA-0xJcZ#tQz`DMeCxR|zH{IiFnd;v>|l-T727F>&oLxNWz3e( zX&zvnV350W0p}hsSAo_$Zqt@vc_I_op< zna|9=gE6YNwiz$YcH**bwZDU5z9X5r>#Psf%x7lb!5Gcq+CEkVEa|~k+vYLICF^GWUj9)(3L4M7i;YmS{$~gBafb~sz1TF{P^zi9^xg9-S7M{@ zX&zvmU=ZsZcm~X#6(c)XBYVYmis5q%$x#`z<#U<`m?s$I?p(M&`8(oDpFCFlTfV0l zt}$><=5vxq%mEya);sU=}{9?XxO6Fe-i{pA!snckQE(6RrKz)m6e#5&uJcDo?sB`9C!wdo-s2USTmoQ{S?FJ z7?NW)UeA7-2e>B~QJ8tIT4xKY<}Hiq}V;;W#t-1P1S( zd~JLm&BoXM1cq1@Y5nD@tCBHRW3vAV#^uMq)7-C7)80OsI>ivz05c(f2eDuud?Dbq+iOs?VF2A6PB@itZG{=NOV> zW$fsCng>`X8079;xIR7JTG5k7#c$+uis2f2>$#W*^YLiC^DaNW{5W!@S=+TVl9BUN z+F(`uMm{?jqd92H+-cS)Zss$y?_ikkNcMPD zN6%K(pJ2pw-`Xowc`bhAvEtwIJ;AvA`10e(KYMM~SkG3`yU$iuVp=|@d4PF>L9BD& zIaYn%wEVzo=~r~87{0%e94li--_tz6I>8`!=fd?Fao3rRJZ3&K`zeNN?5*cw9?Zw1 z_0GHe`10c!CysX4(nyYYY|92#OTVJq!5Ga!+loMH@z^2; zZB97V3Pz6;alLo=F)^I*p%o0?J6xOiKBSG#chamL5ra18D#mP#Y>(HV%4ee=c(K3J zKEa4BysJtLjfME zLGI3l>jQs{>t>}*9xMJW-xCaSca6Q}Tw)%~$D{SmyZrd_IcR$gZkxwo?5vyh&37bwyejgQQ9QPYL7NkPwSv*(L|jvBvmCHT4BDJcxx6K(d}577Uiy}EiFt5sJX-I( z%a1QVo^j%6cP))%=B~3oSTmoQeFtMS2W{*bS5D$q{9C>|80I^YJzf?0$|xS&6Bs>C z#5Kk?%K`fn7`%6=e||5b^PM!y zonXW@+gl5Hef9d}(X&qFLr+I*Nfz29LUX_uT zzU5qE9?Zw1_0GHe`10e(e`jsi(n!v@Y_@}yb*udyjL{slwfO5uN8HF>vF%`(?@Y?A z;&nzhi;MOrFyuw?I#;=|xjccvyC+{0-$#x!lTTpG#)x+7-{Y<`Jy0r`q+c zwcFY&w0LMwM;<*}ReyqU`SIn)GfsNSDT#Nlt*ngM@?ovF^eeg(3}T%F&w$nEPV@&W z_IKK+7(U0496LD>bDGB~2Dv*Ia^V{7o~QAykKOT&Cl8NZEaFdM9Y4y}JMVr!en0#; zUaQ-_6}T0+6=+r9^5d`nRxFtCi|aa_^rad zLjM2f)joMteS3bV7|a0--}C&e*v<*YPF##R#b6F#z+vLgJFd3DSdGd4rx@}uA?i3@=2Y$JG zg0mZI+$lz!3wxQ}6P(>x<4!T+TzHS~p5W}p8h45j=fXR9_XKA**0@uQI2ZOcyC*oi zvBsTZ+`0Vt`kmI4YjcVbuZz!<9SP1(T#Pxz_^5M<^SbTlE8x4QCubuP_k@qn+Mb+^ z;qzeRSMl-v)srK9Y{Wg`GsC#{TY+1FTY+1FTY+1FTY+1FTY+1FTY+1FTY+1FTY+1F zTY+1FuNCk+ZcpO5W6Lo;oSvTekxhJjJp7K^llbk}a!e1Wrzd`76CWRs_zk4h#;>JE zH?rlJo^MZI#V6jQ_y5q<<7Yd|k*(@G!Wh}a$H$}h|G(AaXFJQ0t?E0%7}>-TU*7`>P{20mcGG4t;O=l;kCjO^d*_i#oS zy_h%#K48E>^X~)a{>TT6?BDD6a7Gxtm^cPLVDO&r{akq;R9&14T}gwczMW8nh^ z@96luz^(Sk2aHxsOV`60Vf11mK0aWu@5#T5mir?gFtUHI-@_SU^kU)|_<(VbTY+1F zTY>jd0pAy+d}cl~JJ>Uy%JXsNGqaP&%*S^QzP)4SGqZzR@#&3!^|u1I0=ELO0`kx5 zzr~dOz{}p~C%X^f{R`Lpy}lYdmA^gJ@*$6wenoeJLGE#1 z#x>eK$zwOxxKj+`J;dCRVC=-jm{Sbrf#mtTd!EMoZrB~)c;|D9a}sYZCm7D-%9pJG z_tI>Hna|7){>&#nAA9-Cd}elVXFk4jdO4t<`ONGUjM4blb}Miza4Qfiz`GWXxyotv z2g~-ZJi)m9`0KwP68SAPjs2bW3C88eZT?J6<+;1^1mmOfWA=&hd;e;iny$uV|5FV2 zx=QfA*TXsZeu+Qtv(je%EB-CtQ;f(<m^?4y`BE_z+>wv_m$%@yj!AyDS0(!)-}(Oh^62~S$VMI` zd&PEwalaqmfB(3`nOS2Mzn0HO=EplfS6~b^wY@7(F?N7q1 zgBANb?NbcjFG!A^9Eds1;}nD3oy&~Rkn4Lt;v8c8HS_L`>-rz%GV__)!Jhf_@{4)R zd}emAW{vZTxcI*q(kT2CVAAJAHJ5qdm6K<$ljk{Z0&H zRFC+(Q>`{Q>SG&S?)UuE@5C_1>cKtg8bQwPS@Cz{jL(vLBVNbqy|NpZ$G6Y3B3^^p zv*R?*_#Mgn5{~)3zVk0XzTb~4Jc4_p;@9$t&orMEE$&&-kw?XE#` H4 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 Jm>E!J;D4};<*xt$ literal 0 HcmV?d00001 diff --git a/src/Hexecs.Benchmarks.City/FpsCounter.cs b/src/Hexecs.Benchmarks.City/FpsCounter.cs deleted file mode 100644 index 5ac865c..0000000 --- a/src/Hexecs.Benchmarks.City/FpsCounter.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace Hexecs.Benchmarks.Map; - -internal sealed class FpsCounter -{ - private readonly Func _countResolver; - private readonly int[] _fpsHistory; - private readonly GameWindow _window; - - // Поля для статистики - private double _frameTime; - private int _fps; - private int _frameCount; - private double _fpsTimer; - private int _secondsCounter; - - // Для среднего значения за минуту (Rolling Average) - private int _historyIndex; - private bool _historyFull; - private double _avgFps; - - public FpsCounter(Func countResolver, GameWindow window) - { - _countResolver = countResolver; - _window = window; - _fpsHistory = new int[60]; - } - - public void Draw(GameTime gameTime) - { - var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; - _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; - _fpsTimer += elapsedSeconds; - _frameCount++; - - // Считаем FPS каждую секунду для точности истории - if (_fpsTimer >= 1.0) - { - _fps = _frameCount; - - // Обновляем историю для Avg - _fpsHistory[_historyIndex] = _fps; - _historyIndex = (_historyIndex + 1) % 60; - if (_historyIndex == 0) _historyFull = true; - - // Считаем среднее за минуту - var historyCount = _historyFull ? 60 : _historyIndex; - var sum = 0; - for (var i = 0; i < historyCount; i++) sum += _fpsHistory[i]; - _avgFps = (double)sum / historyCount; - - _frameCount = 0; - _fpsTimer -= 1.0; - _secondsCounter++; - - if (_secondsCounter >= 1) - { - var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; - var count = _countResolver(); - _window.Title = $"FPS: {_fps} | Avg FPS: {_avgFps:F1} | Entities: {count:N0} | Frame Time: {_frameTime:F2}ms | Alloc: {alloc:F2}Mb"; - - _secondsCounter = 0; - } - } - } -} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj b/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj index 72c6725..b27b7ab 100644 --- a/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj +++ b/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj @@ -21,7 +21,13 @@ - + + Never + + + PreserveNewest + + PreserveNewest diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs index aca0d08..e763f7e 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs @@ -2,6 +2,7 @@ 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; @@ -10,25 +11,25 @@ namespace Hexecs.Benchmarks.Map.Terrains; internal sealed class TerrainDrawSystem : DrawSystem { private readonly Camera _camera; + private readonly TerrainSpriteAtlas _spriteAtlas; private readonly SpriteBatch _spriteBatch; - private readonly TextureStorage _textureStorage; public TerrainDrawSystem( Camera camera, ActorContext context, GraphicsDevice graphicsDevice, - TextureStorage textureStorage) + TerrainSpriteAtlas spriteAtlas) : base(context, constraint => constraint.Include()) { _camera = camera; - _textureStorage = textureStorage; + _spriteAtlas = spriteAtlas; _spriteBatch = new SpriteBatch(graphicsDevice); } protected override bool BeforeDraw(in WorldTime time) { _spriteBatch.Begin( - transformMatrix: _camera.GetTransformationMatrix(), + transformMatrix: _camera.TransformationMatrix, samplerState: SamplerState.PointClamp, blendState: BlendState.AlphaBlend); @@ -38,7 +39,7 @@ protected override bool BeforeDraw(in WorldTime time) protected override void Draw(in ActorRef actor, in WorldTime time) { ref readonly var terrain = ref actor.Component2; - ref readonly var texture = ref _textureStorage.GetTerrainTexture(terrain.Type, terrain.Overlay); + ref readonly var texture = ref _spriteAtlas.GetSprite(in terrain); ref readonly var worldPosition = ref actor.Component1.World; texture.Draw(_spriteBatch, new Vector2(worldPosition.X, worldPosition.Y)); diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs index ecaa090..a692019 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs @@ -3,6 +3,8 @@ using Hexecs.Configurations; using Hexecs.Dependencies; using Hexecs.Worlds; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; namespace Hexecs.Benchmarks.Map.Terrains; @@ -12,7 +14,7 @@ public static ActorContextBuilder AddTerrain(this ActorContextBuilder builder) { var terrainSettings = builder.World.GetRequiredService(); - builder.AddBuilder(); + builder.CreateBuilder(); builder .ConfigureComponentPool(terrain => terrain @@ -30,6 +32,12 @@ public static WorldBuilder UseTerrain(this WorldBuilder builder) builder .UseAddAssetSource(new TerrainAssetSource()); + builder + .UseSingleton(ctx => new TerrainSpriteAtlas( + contentManager: ctx.GetRequiredService(), + fileName: "terrain_atlas", + settings: ctx.GetRequiredService())); + builder .UseSingleton(ctx => ctx .GetService()? diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs index 96b4be1..9d4f8a3 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs @@ -2,10 +2,21 @@ namespace Hexecs.Benchmarks.Map.Terrains; public sealed class TerrainSettings { - public static TerrainSettings Default { get; } = new() { Width = 768, Height = 768 }; - public const string Key = "Map:Terrain"; + public static readonly TerrainSettings Default = new() + { + TileSize = 16, + TileSpacing = 1, + Width = 768, + Height = 768 + }; + + public int TileSize { get; init; } + + public int TileSpacing { get; init; } + public int Width { get; init; } + public int Height { get; init; } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs new file mode 100644 index 0000000..d5a6998 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs @@ -0,0 +1,37 @@ +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; + +internal sealed class TerrainSpriteAtlas : SpriteAtlas +{ + public TerrainSpriteAtlas(ContentManager contentManager, string fileName, TerrainSettings settings) + : base(contentManager, fileName, settings.TileSize, settings.TileSpacing) + { + } + + protected override AtlasKey CreateKey(in Terrain key) + { + var type = key.Type; + + var column = type switch + { + TerrainType.Ground => 6, + TerrainType.WaterRiver => 3, + TerrainType.UrbanConcrete => 7, + _ => 1 + }; + + var row = type switch + { + TerrainType.Ground => 0, + TerrainType.WaterRiver => 1, + TerrainType.UrbanConcrete => 0, + _ => 1 + }; + + return new AtlasKey(column, row); + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/Camera.cs b/src/Hexecs.Benchmarks.City/Utils/Camera.cs index 0981cf3..949742f 100644 --- a/src/Hexecs.Benchmarks.City/Utils/Camera.cs +++ b/src/Hexecs.Benchmarks.City/Utils/Camera.cs @@ -1,83 +1,73 @@ +using System.Runtime.CompilerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Hexecs.Benchmarks.Map.Utils; -internal sealed class Camera +internal sealed class Camera(GraphicsDevice graphicsDevice) { - private readonly GraphicsDevice _graphicsDevice; - ///

/// Позиция камеры в мировых координатах (центр экрана). /// - public Vector2 Position { get; private set; } - - /// - /// Viewport of world boundary - /// - public CameraViewport Viewport { get; private set; } + public ref readonly Vector2 Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _currentPosition; + } /// - /// Текущий масштаб камеры (1.0 = без изменений). + /// Матрица трансформации, учитывая позицию, зум и размер экрана. /// - public float Zoom { get; private set; } - - private int _previousScrollValue; - - public Camera(GraphicsDevice graphicsDevice) + public ref readonly Matrix TransformationMatrix { - Position = Vector2.Zero; - Zoom = 1f; - - _graphicsDevice = graphicsDevice; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _currentTransform; } /// - /// Изменяет текущий зум на множитель и ограничивает его допустимым диапазоном. + /// Viewport of world boundary /// - /// Множитель масштаба (больше 1 для приближения, меньше 1 для отдаления). - public void AdjustZoom(float factor) + public ref readonly CameraViewport Viewport { - if (factor > 0) Zoom += 1f; - else Zoom -= 1f; - - Zoom = MathHelper.Clamp(Zoom, 1f, 10f); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _currentViewport; } /// - /// Создает матрицу трансформации для SpriteBatch, учитывая позицию, зум и размер экрана. + /// Текущий масштаб камеры (1.0 = без изменений). /// - public Matrix GetTransformationMatrix() + public float Zoom { - var viewport = _graphicsDevice.Viewport; - var zoom = MathF.Round(Zoom); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _currentZoom; + } - var roundedPosition = new Vector2( - MathF.Round(Position.X), - MathF.Round(Position.Y) - ); + private Vector2 _currentPosition; + private Matrix _currentTransform; + private CameraViewport _currentViewport; + private float _currentZoom = 1f; - return Matrix.CreateTranslation(new Vector3(-roundedPosition.X, -roundedPosition.Y, 0)) * - Matrix.CreateScale(new Vector3(zoom, zoom, 1)) * - Matrix.CreateTranslation(new Vector3(viewport.Width * 0.5f, viewport.Height * 0.5f, 0)); - } + private Vector2 _previousPosition; + private float _previousZoom; + private int _previousScrollValue; - public Rectangle GetViewportWorldBoundary() + /// + /// Изменяет текущий зум на множитель и ограничивает его допустимым диапазоном. + /// + /// Множитель масштаба (больше 1 для приближения, меньше 1 для отдаления). + public void AdjustZoom(float factor) { - var viewport = _graphicsDevice.Viewport; - var topLeft = ScreenToWorld(new Vector2(0, 0)); - var bottomRight = ScreenToWorld(new Vector2(viewport.Width, viewport.Height)); + if (factor > 0) _currentZoom += 1f; + else _currentZoom -= 1f; - var width = (int)MathF.Ceiling(bottomRight.X - topLeft.X); - var height = (int)MathF.Ceiling(bottomRight.Y - topLeft.Y); - - return new Rectangle((int)topLeft.X, (int)topLeft.Y, width, height); + _currentZoom = MathHelper.Clamp(_currentZoom, 1f, 10f); } /// /// Смещает камеру на указанный вектор. /// - public void Move(Vector2 direction) => Position += direction; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Move(Vector2 direction) => _currentPosition += direction; /// /// Обрабатывает ввод игрока для перемещения и масштабирования камеры. @@ -90,7 +80,7 @@ public void Update(GameTime gameTime) var dt = (float)gameTime.ElapsedGameTime.TotalSeconds; // Базовое управление камерой - var speed = 500f / Zoom; + var speed = 500f / _currentZoom; var moveDir = Vector2.Zero; if (keyboard.IsKeyDown(Keys.W)) moveDir.Y -= 1; @@ -104,9 +94,6 @@ public void Update(GameTime gameTime) Move(moveDir * speed * dt); } - if (keyboard.IsKeyDown(Keys.Q)) Zoom += dt * 5f; - if (keyboard.IsKeyDown(Keys.E)) Zoom -= dt * 5f; - var scrollDelta = mouse.ScrollWheelValue - _previousScrollValue; if (scrollDelta != 0) { @@ -115,36 +102,67 @@ public void Update(GameTime gameTime) _previousScrollValue = mouse.ScrollWheelValue; - Zoom = MathHelper.Clamp(Zoom, 1f, 10f); + if (_currentPosition != _previousPosition || Math.Abs(_currentZoom - _previousZoom) > float.Epsilon) + { + UpdateTransformationMatrix(); + UpdateViewportBoundary(); + _previousPosition = _currentPosition; + _previousZoom = _currentZoom; + } + + UpdateTransformationMatrix(); UpdateViewportBoundary(); } /// /// Переводит экранные координаты (например, позицию мыши) в мировые координаты игрового поля. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ScreenToWorld(Vector2 screenPosition) { - return Vector2.Transform(screenPosition, Matrix.Invert(GetTransformationMatrix())); + return Vector2.Transform(screenPosition, Matrix.Invert(_currentTransform)); } /// /// Переводит мировые координаты в координаты экрана (например, для отрисовки UI над объектами). /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 WorldToScreen(Vector2 worldPosition) { - return Vector2.Transform(worldPosition, GetTransformationMatrix()); + return Vector2.Transform(worldPosition, _currentTransform); + } + + private void UpdateTransformationMatrix() + { + var viewport = graphicsDevice.Viewport; + var zoom = MathF.Round(_currentZoom); + + var roundedPosition = new Vector2( + MathF.Round(_currentPosition.X), + MathF.Round(_currentPosition.Y) + ); + + _currentTransform = Matrix.CreateTranslation(new Vector3(-roundedPosition.X, -roundedPosition.Y, 0)) * + Matrix.CreateScale(new Vector3(zoom, zoom, 1)) * + Matrix.CreateTranslation(new Vector3(viewport.Width * 0.5f, viewport.Height * 0.5f, 0)); } private void UpdateViewportBoundary() { - var viewport = _graphicsDevice.Viewport; + var deviceViewport = graphicsDevice.Viewport; var topLeft = ScreenToWorld(Vector2.Zero); - var bottomRight = ScreenToWorld(new Vector2(viewport.Width, viewport.Height)); + var bottomRight = ScreenToWorld(new Vector2(deviceViewport.Width, deviceViewport.Height)); var width = (int)MathF.Ceiling(bottomRight.X - topLeft.X); var height = (int)MathF.Ceiling(bottomRight.Y - topLeft.Y); - - Viewport = new CameraViewport((int)topLeft.X, (int)topLeft.Y, width, height); + var x = (int)topLeft.X; + var y = (int)topLeft.Y; + + ref var viewport = ref _currentViewport; + viewport.Left = x; + viewport.Right = x + width; + viewport.Top = y; + viewport.Bottom = y + height; } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs b/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs index 2f4e758..086648a 100644 --- a/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs +++ b/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs @@ -2,13 +2,12 @@ namespace Hexecs.Benchmarks.Map.Utils; -[method: MethodImpl(MethodImplOptions.AggressiveInlining)] -public readonly struct CameraViewport(int x, int y, int width, int height) : IEquatable +public struct CameraViewport : IEquatable { - public readonly int Left = x; - public readonly int Right = x + width; - public readonly int Top = y; - public readonly int Bottom = y + height; + public int Left; + public int Right; + public int Top; + public int Bottom; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Hidden(int x, int y, int width, int height) => !Visible(x, y, width, height); @@ -29,4 +28,10 @@ public bool Equals(CameraViewport other) => Left == other.Left && public override bool Equals(object? obj) => obj is CameraViewport other && Equals(other); public override int GetHashCode() => HashCode.Combine(Left, Right, Top, Bottom); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(in CameraViewport left, in CameraViewport right) => left.Equals(right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(in CameraViewport left, in CameraViewport right) => !left.Equals(right); } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/AtlasTexture.cs b/src/Hexecs.Benchmarks.City/Utils/Sprites/Sprite.cs similarity index 63% rename from src/Hexecs.Benchmarks.City/Utils/AtlasTexture.cs rename to src/Hexecs.Benchmarks.City/Utils/Sprites/Sprite.cs index cd6867d..67958d5 100644 --- a/src/Hexecs.Benchmarks.City/Utils/AtlasTexture.cs +++ b/src/Hexecs.Benchmarks.City/Utils/Sprites/Sprite.cs @@ -1,9 +1,10 @@ using System.Runtime.CompilerServices; using Microsoft.Xna.Framework.Graphics; -namespace Hexecs.Benchmarks.Map.Utils; +namespace Hexecs.Benchmarks.Map.Utils.Sprites; -internal readonly struct AtlasTexture(Texture2D texture, Rectangle region) +[method: MethodImpl(MethodImplOptions.AggressiveInlining)] +internal readonly struct Sprite(Texture2D texture, Rectangle region) { [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Draw(SpriteBatch spriteBatch, Vector2 position) => spriteBatch.Draw( diff --git a/src/Hexecs.Benchmarks.City/Utils/Sprites/SpriteAtlas.cs b/src/Hexecs.Benchmarks.City/Utils/Sprites/SpriteAtlas.cs new file mode 100644 index 0000000..30a8327 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Utils/Sprites/SpriteAtlas.cs @@ -0,0 +1,63 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Map.Utils.Sprites; + +internal abstract class SpriteAtlas : IDisposable + where TKey : struct +{ + private readonly Dictionary _sprites = new(); + private readonly Texture2D _texture; + + private readonly int _tileSize; + private readonly int _tileSpacing; + + private bool _disposed; + + protected SpriteAtlas(ContentManager contentManager, string fileName, int tileSize, int tileSpacing) + { + _texture = contentManager.Load(fileName); + _tileSize = tileSize; + _tileSpacing = tileSpacing; + } + + public ref Sprite GetSprite(in TKey key) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var atlasKey = CreateKey(in key); + ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(_sprites, key, out var exists); + if (exists) + { + return ref value; + } + + value = CreateSprite(atlasKey.Column, atlasKey.Row); + return ref value; + } + + protected abstract AtlasKey CreateKey(in TKey key); + + private Sprite CreateSprite(int column, int row) + { + var x = column * (_tileSize + _tileSpacing); + var y = row * (_tileSize + _tileSpacing); + var sourceRect = new Rectangle(x, y, _tileSize, _tileSize); + + return new Sprite(_texture, sourceRect); + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + + _sprites.Clear(); + _texture.Dispose(); + } + + [method: MethodImpl(MethodImplOptions.AggressiveInlining)] + protected readonly record struct AtlasKey(int Column, int Row); +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs b/src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs deleted file mode 100644 index ebeac1b..0000000 --- a/src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Runtime.InteropServices; -using Hexecs.Benchmarks.Map.Terrains.ValueTypes; -using Microsoft.Xna.Framework.Graphics; - -namespace Hexecs.Benchmarks.Map.Utils; - -internal sealed class TextureStorage : IDisposable -{ - public const int TerrainTileSize = 16; // Размер одного тайла в пикселях (подставьте свой) - private const int TerrainSpacing = 1; // Между тайлами есть промежуток в 1 пиксель - - private readonly Texture2D _terrainAtlas; - private readonly Dictionary _terrainCache; - - private bool _disposed; - - public TextureStorage(GraphicsDevice graphicsDevice) - { - var contentRootPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Content"); - - _terrainAtlas = Texture2D.FromFile( - graphicsDevice, - Path.Combine(contentRootPath, "roguelikeSheet_transparent.png")); - - _terrainCache = new Dictionary(256); - } - - public void Dispose() - { - if (_disposed) return; - _disposed = true; - - _terrainAtlas.Dispose(); - _terrainCache.Clear(); - } - - public ref readonly AtlasTexture GetTerrainTexture(TerrainType type, TerrainOverlay overlay) - { - ObjectDisposedException.ThrowIf(_disposed, this); - - var key = new TerrainKey(type, overlay); - ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(_terrainCache, key, out var exists); - if (exists) return ref value; - - var column = type switch - { - TerrainType.Ground => 6, - TerrainType.WaterRiver => 3, - TerrainType.UrbanConcrete => 7, - _ => 1 - }; - var row = type switch - { - TerrainType.Ground => 0, - TerrainType.WaterRiver => 1, - TerrainType.UrbanConcrete => 0, - _ => 1 - }; - - var x = column * (TerrainTileSize + TerrainSpacing); - var y = row * (TerrainTileSize + TerrainSpacing); - var sourceRect = new Rectangle(x, y, TerrainTileSize, TerrainTileSize); - - value = new AtlasTexture(_terrainAtlas, sourceRect); - return ref value; - } - - private readonly record struct TerrainKey(TerrainType Type, TerrainOverlay Overlay); -} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs b/src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs new file mode 100644 index 0000000..01faf6a --- /dev/null +++ b/src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs @@ -0,0 +1,90 @@ +using System.Globalization; +using System.Text; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Noise; + +internal sealed class BenchmarkCounter +{ + private readonly Func _countResolver; + private readonly int[] _fpsHistory; + + private double _frameTime; + private int _fps; + private int _frameCount; + private double _fpsTimer; + + private int _historyIndex; + private bool _historyFull; + private double _avgFps; + private long _historySum; + + private readonly SpriteFont _font; + private readonly SpriteBatch _spriteBatch; + + // Используем StringBuilder как буфер + private readonly StringBuilder _stringBuilder = new(128); + private readonly Vector2 _textPos = new(10, 10); + private readonly Vector2 _shadowPos = new(11, 11); + + public BenchmarkCounter(Func countResolver, ContentManager contentManager, GraphicsDevice graphicsDevice) + { + _countResolver = countResolver; + _fpsHistory = new int[60]; + _font = contentManager.Load("DebugFont"); + _spriteBatch = new SpriteBatch(graphicsDevice); + } + + public void Draw(GameTime gameTime) + { + _frameCount++; + + _spriteBatch.Begin(); + + _spriteBatch.DrawString(_font, _stringBuilder, _shadowPos, Color.Black); + _spriteBatch.DrawString(_font, _stringBuilder, _textPos, Color.Yellow); + + _spriteBatch.End(); + } + + public void Update(GameTime gameTime) + { + var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; + _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; + _fpsTimer += elapsedSeconds; + + + if (_fpsTimer >= 1.0) + { + _fps = _frameCount; + + _historySum -= _fpsHistory[_historyIndex]; + _fpsHistory[_historyIndex] = _fps; + _historySum += _fps; + + _historyIndex = (_historyIndex + 1) % 60; + if (_historyIndex == 0) _historyFull = true; + + var historyCount = _historyFull ? 60 : _historyIndex; + _avgFps = (double)_historySum / historyCount; + + var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; + var count = _countResolver(); + + // Очищаем буфер и записываем новые данные без создания строк + var culture = CultureInfo.InvariantCulture; + + _stringBuilder.Clear(); + _stringBuilder + .Append($"{_fps} FPS") + .Append(culture, $" | Avg:{_avgFps:F1} fps") + .Append(culture, $" | Entities:{count:N0}") + .Append(culture, $" | Frame time:{_frameTime:F1}ms") + .Append(culture, $" | Alloc:{alloc:F3}mb"); + + _frameCount = 0; + _fpsTimer = 0; + } + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs b/src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs index e5819ff..69190a8 100644 --- a/src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs +++ b/src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs @@ -1,5 +1,4 @@ using Hexecs.Actors; -using Microsoft.Xna.Framework; namespace Hexecs.Benchmarks.Noise.Components; diff --git a/src/Hexecs.Benchmarks.Noise/Components/Position.cs b/src/Hexecs.Benchmarks.Noise/Components/Position.cs index 03299cb..e7bbd0a 100644 --- a/src/Hexecs.Benchmarks.Noise/Components/Position.cs +++ b/src/Hexecs.Benchmarks.Noise/Components/Position.cs @@ -1,5 +1,4 @@ using Hexecs.Actors; -using Microsoft.Xna.Framework; namespace Hexecs.Benchmarks.Noise.Components; diff --git a/src/Hexecs.Benchmarks.Noise/Components/Velocity.cs b/src/Hexecs.Benchmarks.Noise/Components/Velocity.cs index b999e31..3e2d743 100644 --- a/src/Hexecs.Benchmarks.Noise/Components/Velocity.cs +++ b/src/Hexecs.Benchmarks.Noise/Components/Velocity.cs @@ -1,5 +1,4 @@ using Hexecs.Actors; -using Microsoft.Xna.Framework; namespace Hexecs.Benchmarks.Noise.Components; diff --git a/src/Hexecs.Benchmarks.Noise/Content/DebugFont.spritefont b/src/Hexecs.Benchmarks.Noise/Content/DebugFont.spritefont new file mode 100644 index 0000000..ba84b52 --- /dev/null +++ b/src/Hexecs.Benchmarks.Noise/Content/DebugFont.spritefont @@ -0,0 +1,16 @@ + + + + Consolas + 10 + 0 + true + + + + + ~ + + + + \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Noise/Content/DebugFont.xnb b/src/Hexecs.Benchmarks.Noise/Content/DebugFont.xnb new file mode 100644 index 0000000000000000000000000000000000000000..47451f4677c111d6f0012d9d4be29b774995dedd GIT binary patch literal 21529 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 _countResolver; - private readonly int[] _fpsHistory; - private readonly GameWindow _window; - - // Поля для статистики - private double _frameTime; - private int _fps; - private int _frameCount; - private double _fpsTimer; - private int _secondsCounter; - - // Для среднего значения за минуту (Rolling Average) - private int _historyIndex; - private bool _historyFull; - private double _avgFps; - - public FpsCounter(Func countResolver, GameWindow window) - { - _countResolver = countResolver; - _window = window; - _fpsHistory = new int[60]; - } - - public void Draw(GameTime gameTime) - { - var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; - _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; - _fpsTimer += elapsedSeconds; - _frameCount++; - - // Считаем FPS каждую секунду для точности истории - if (_fpsTimer >= 1.0) - { - _fps = _frameCount; - - // Обновляем историю для Avg - _fpsHistory[_historyIndex] = _fps; - _historyIndex = (_historyIndex + 1) % 60; - if (_historyIndex == 0) _historyFull = true; - - // Считаем среднее за минуту - var historyCount = _historyFull ? 60 : _historyIndex; - var sum = 0; - for (var i = 0; i < historyCount; i++) sum += _fpsHistory[i]; - _avgFps = (double)sum / historyCount; - - _frameCount = 0; - _fpsTimer -= 1.0; - _secondsCounter++; - - if (_secondsCounter >= 1) - { - var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; - var count = _countResolver(); - _window.Title = $"FPS: {_fps} | Avg FPS: {_avgFps:F1} | Entities: {count:N0} | Frame Time: {_frameTime:F2}ms | Alloc: {alloc:F2}Mb"; - - _secondsCounter = 0; - } - } - } -} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj b/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj index ceb90bf..80b5a75 100644 --- a/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj +++ b/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj @@ -25,6 +25,9 @@ PreserveNewest + + PreserveNewest + diff --git a/src/Hexecs.Benchmarks.Noise/NoiseGame.cs b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs index e1bf250..d7db849 100644 --- a/src/Hexecs.Benchmarks.Noise/NoiseGame.cs +++ b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs @@ -4,7 +4,6 @@ using Hexecs.Dependencies; using Hexecs.Threading; using Hexecs.Worlds; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; @@ -12,11 +11,10 @@ namespace Hexecs.Benchmarks.Noise; public class NoiseGame : Game { + private BenchmarkCounter _benchmarkCounter = null!; + private ActorContext _context = null!; private readonly GraphicsDeviceManager _graphics; private readonly Random _random = new(); - - private ActorContext _context = null!; - private FpsCounter _fpsCounter = null!; private World _world = null!; private const int InitialEntityCount = 2_000_000; @@ -44,6 +42,7 @@ public NoiseGame() _graphics.ApplyChanges(); IsFixedTimeStep = false; + Content.RootDirectory = "Content"; } protected override void Initialize() @@ -66,7 +65,7 @@ protected override void Initialize() .Build(); _context = _world.Actors; - _fpsCounter = new FpsCounter(() => _context.Length, Window); + _benchmarkCounter = new BenchmarkCounter(() => _context.Length, Content, GraphicsDevice); for (var i = 0; i < InitialEntityCount; i++) { @@ -109,6 +108,7 @@ protected override void Update(GameTime gameTime) } } + _benchmarkCounter.Update(gameTime); _world.Update(gameTime.ElapsedGameTime, gameTime.TotalGameTime); base.Update(gameTime); @@ -118,9 +118,8 @@ protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.White); - _fpsCounter.Draw(gameTime); - _world.Draw(gameTime.ElapsedGameTime, gameTime.TotalGameTime); + _benchmarkCounter.Draw(gameTime); base.Draw(gameTime); } diff --git a/src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs b/src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs index 176ec1c..b9dd6b9 100644 --- a/src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs +++ b/src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs @@ -1,5 +1,4 @@ using System.Runtime.InteropServices; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Hexecs.Benchmarks.Noise.Systems; diff --git a/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs b/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs index 428845a..82d3117 100644 --- a/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs +++ b/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs @@ -3,7 +3,6 @@ using Hexecs.Benchmarks.Noise.Components; using Hexecs.Threading; using Hexecs.Worlds; -using Microsoft.Xna.Framework; namespace Hexecs.Benchmarks.Noise.Systems; diff --git a/src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs b/src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs index f0d80f3..206e3d3 100644 --- a/src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs +++ b/src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs @@ -2,7 +2,6 @@ using Hexecs.Actors.Systems; using Hexecs.Benchmarks.Noise.Components; using Hexecs.Worlds; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Hexecs.Benchmarks.Noise.Systems; diff --git a/src/Hexecs.Benchmarks.Noise/Usings.cs b/src/Hexecs.Benchmarks.Noise/Usings.cs index e12d09f..a1b37ef 100644 --- a/src/Hexecs.Benchmarks.Noise/Usings.cs +++ b/src/Hexecs.Benchmarks.Noise/Usings.cs @@ -1,3 +1,4 @@ // Global using directives -global using System.Runtime.CompilerServices; \ No newline at end of file +global using System.Runtime.CompilerServices; +global using Microsoft.Xna.Framework; \ No newline at end of file diff --git a/src/Hexecs.Tests/Actors/ActorTestFixture.cs b/src/Hexecs.Tests/Actors/ActorTestFixture.cs index 42de54c..3f9d71a 100644 --- a/src/Hexecs.Tests/Actors/ActorTestFixture.cs +++ b/src/Hexecs.Tests/Actors/ActorTestFixture.cs @@ -16,8 +16,8 @@ public ActorTestFixture() World = new WorldBuilder() .CreateAssetData(CreateAssets) .UseDefaultActorContext(ctx => ctx - .AddBuilder() - .AddBuilder() + .CreateBuilder() + .CreateBuilder() .ConfigureComponentPool(c => c.AddDisposeHandler())) .Build(); } diff --git a/src/Hexecs/Actors/ActorContext.Entry.cs b/src/Hexecs/Actors/ActorContext.Entry.cs index be87629..166021b 100644 --- a/src/Hexecs/Actors/ActorContext.Entry.cs +++ b/src/Hexecs/Actors/ActorContext.Entry.cs @@ -16,7 +16,15 @@ public readonly void Serialize(Utf8JsonWriter writer) writer.WriteStartObject(); writer.WriteProperty("Key", Key); - //writer.WriteProperty("Components", in Components); + writer.WritePropertyName("Components"); + + writer.WriteStartArray(); + foreach (var component in Components) + { + writer.WriteNumberValue(component); + } + + writer.WriteStartArray(); writer.WriteEndObject(); } @@ -26,7 +34,7 @@ public readonly void Serialize(Utf8JsonWriter writer) [method: MethodImpl(MethodImplOptions.AggressiveInlining)] internal struct ComponentBucket() { - public const int InlineArraySize = 6; + private const int InlineArraySize = 6; public int Length { diff --git a/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs b/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs index 4ae9711..abe3a11 100644 --- a/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs +++ b/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs @@ -8,6 +8,21 @@ public static class ActorContextBuilderExtensions { extension(ActorContextBuilder builder) { + /// + /// Создаёт строитель актёров указанного типа. + /// + /// Тип строителя актёров. + /// Этот же экземпляр ActorContextBuilder для цепочки вызовов. + public ActorContextBuilder CreateBuilder< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicConstructors)] + T>() where T : class, IActorBuilder + { + builder.CreateBuilder(static ctx => (IActorBuilder)ctx.Activate(typeof(T))); + return builder; + } + + /// /// Регистрирует метод создания обработчика команды указанного типа. /// diff --git a/src/Hexecs/Actors/ActorContextBuilder.cs b/src/Hexecs/Actors/ActorContextBuilder.cs index 2d1673d..f30deeb 100644 --- a/src/Hexecs/Actors/ActorContextBuilder.cs +++ b/src/Hexecs/Actors/ActorContextBuilder.cs @@ -93,17 +93,6 @@ public ActorContextBuilder AddBuilder(IActorBuilder builder) return this; } - /// - /// Регистрирует строитель актёров указанного типа. - /// - /// Тип строителя актёров. - /// Этот же экземпляр ActorContextBuilder для цепочки вызовов. - public ActorContextBuilder AddBuilder() where T : class, IActorBuilder, new() - { - _builders.Add(new Entry(static _ => new T())); - return this; - } - /// /// Регистрирует функцию для создания строителя актёров с доступом к контексту актёров. /// diff --git a/src/Hexecs/Serializations/JsonWriterExtensions.cs b/src/Hexecs/Serializations/JsonWriterExtensions.cs index 749ca1f..3833b3b 100644 --- a/src/Hexecs/Serializations/JsonWriterExtensions.cs +++ b/src/Hexecs/Serializations/JsonWriterExtensions.cs @@ -7,140 +7,138 @@ namespace Hexecs.Serializations; public static class JsonWriterExtensions { - public static Utf8JsonWriter WriteProperty( - this Utf8JsonWriter writer, - string propertyName, - Action value) + extension(Utf8JsonWriter writer) { - writer.WritePropertyName(propertyName); - value(writer); - return writer; - } - - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, - string propertyName, - in TArray array, - Action value) - where TArray: struct, IArray - { - writer.WritePropertyName(propertyName); - writer.WriteStartArray(); - - for (var i = 0; i < array.Length; i++) + public Utf8JsonWriter WriteProperty(string propertyName, + Action value) { - value(writer, array[i]); + writer.WritePropertyName(propertyName); + value(writer); + return writer; } - writer.WriteEndArray(); + public Utf8JsonWriter WriteProperty(string propertyName, + in TArray array, + Action value) + where TArray: struct, IArray + { + writer.WritePropertyName(propertyName); + writer.WriteStartArray(); - return writer; - } + for (var i = 0; i < array.Length; i++) + { + value(writer, array[i]); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, ActorId value) - { - writer.WriteNumber(propertyName, value.Value); - return writer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, ActorId value) - where T : struct, IActorComponent - { - writer.WriteNumber(propertyName, value.Value); - return writer; - } + writer.WriteEndArray(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, AssetId value) - { - writer.WriteNumber(propertyName, value.Value); - return writer; - } + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, AssetId value) - where T : struct, IAssetComponent - { - writer.WriteNumber(propertyName, value.Value); - return writer; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, ActorId value) + { + writer.WriteNumber(propertyName, value.Value); + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, bool value) - { - writer.WriteBoolean(propertyName, value); - return writer; - } - - [SkipLocalsInit] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, DateOnly value) - { - writer.WritePropertyName(propertyName); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, ActorId value) + where T : struct, IActorComponent + { + writer.WriteNumber(propertyName, value.Value); + return writer; + } - Span buffer = stackalloc char[68]; - if (value.TryFormat(buffer, out var charsWritten)) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, AssetId value) { - writer.WriteStringValue(buffer[..charsWritten]); + writer.WriteNumber(propertyName, value.Value); + return writer; } - else + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, AssetId value) + where T : struct, IAssetComponent { - writer.WriteStringValue(string.Empty); + writer.WriteNumber(propertyName, value.Value); + return writer; } - return writer; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, bool value) + { + writer.WriteBoolean(propertyName, value); + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, int value) - { - writer.WriteNumber(propertyName, value); - return writer; - } + [SkipLocalsInit] + public Utf8JsonWriter WriteProperty(string propertyName, DateOnly value) + { + writer.WritePropertyName(propertyName); + + Span buffer = stackalloc char[68]; + if (value.TryFormat(buffer, out var charsWritten)) + { + writer.WriteStringValue(buffer[..charsWritten]); + } + else + { + writer.WriteStringValue(string.Empty); + } + + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, long value) - { - writer.WriteNumber(propertyName, value); - return writer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, string value) - { - writer.WriteString(propertyName, value); - return writer; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, int value) + { + writer.WriteNumber(propertyName, value); + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, Type value) - { - writer.WriteString(propertyName, value.FullName); - return writer; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, long value) + { + writer.WriteNumber(propertyName, value); + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, uint value) - { - writer.WriteNumber(propertyName, value); - return writer; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, string value) + { + writer.WriteString(propertyName, value); + return writer; + } - internal static Utf8JsonWriter WriteProperty( - this Utf8JsonWriter writer, - string propertyName, - ref readonly InlineBucket value) - { - writer.WritePropertyName(propertyName); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, Type value) + { + writer.WriteString(propertyName, value.FullName); + return writer; + } - writer.WriteStartArray(); - foreach (var element in value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, uint value) { - writer.WriteNumberValue(element); + writer.WriteNumber(propertyName, value); + return writer; } - writer.WriteEndArray(); + internal Utf8JsonWriter WriteProperty(string propertyName, + ref readonly InlineBucket value) + { + writer.WritePropertyName(propertyName); + + writer.WriteStartArray(); + foreach (var element in value) + { + writer.WriteNumberValue(element); + } + + writer.WriteEndArray(); - return writer; + return writer; + } } } \ No newline at end of file From c66738f1e067766bd71e4762081222558d289a41 Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Thu, 25 Dec 2025 13:44:01 +0300 Subject: [PATCH 09/14] 0.4.3 map bench --- .../BenchmarkCounter.cs | 90 ++++++++ src/Hexecs.Benchmarks.City/CityGame.cs | 10 +- .../Common/Positions/PositionBuilder.cs | 8 +- .../Common/Positions/PositionsInstaller.cs | 2 +- .../Common/Visibles/VisibleSystem.cs | 10 +- .../Content/Content.mgcb | 13 ++ .../Content/DebugFont.spritefont | 16 ++ .../Content/DebugFont.xnb | Bin 0 -> 21529 bytes ...heet_transparent.png => terrain_atlas.png} | Bin .../Content/terrain_atlas.xnb | Bin 0 -> 2036757 bytes src/Hexecs.Benchmarks.City/FpsCounter.cs | 65 ------ .../Hexecs.Benchmarks.City.csproj | 8 +- .../Terrains/TerrainDrawSystem.cs | 11 +- .../Terrains/TerrainInstaller.cs | 10 +- .../Terrains/TerrainSettings.cs | 15 +- .../Terrains/TerrainSpriteAtlas.cs | 37 +++ src/Hexecs.Benchmarks.City/Utils/Camera.cs | 134 ++++++----- .../Utils/CameraViewport.cs | 17 +- .../{AtlasTexture.cs => Sprites/Sprite.cs} | 5 +- .../Utils/Sprites/SpriteAtlas.cs | 63 +++++ .../Utils/TextureStorage.cs | 69 ------ .../BenchmarkCounter.cs | 90 ++++++++ .../Components/CircleColor.cs | 1 - .../Components/Position.cs | 1 - .../Components/Velocity.cs | 1 - .../Content/DebugFont.spritefont | 16 ++ .../Content/DebugFont.xnb | Bin 0 -> 21529 bytes src/Hexecs.Benchmarks.Noise/FpsCounter.cs | 67 ------ .../Hexecs.Benchmarks.Noise.csproj | 3 + src/Hexecs.Benchmarks.Noise/NoiseGame.cs | 13 +- .../Systems/InstanceData.cs | 1 - .../Systems/MovementSystem.cs | 1 - .../Systems/RenderSystem.cs | 1 - src/Hexecs.Benchmarks.Noise/Usings.cs | 3 +- src/Hexecs.Tests/Actors/ActorTestFixture.cs | 4 +- src/Hexecs.Tests/Assets/AssetTestFixture.cs | 1 + src/Hexecs/Actors/ActorContext.Entry.cs | 12 +- src/Hexecs/Actors/ActorContext.cs | 3 +- .../Actors/ActorContextBuilder.Extensions.cs | 15 ++ src/Hexecs/Actors/ActorContextBuilder.cs | 11 - src/Hexecs/Actors/Systems/UpdateSystem.cs | 1 - src/Hexecs/Assets/AssetContext.Components.cs | 2 +- src/Hexecs/Assets/AssetContext.Dictionary.cs | 10 + src/Hexecs/Assets/AssetContext.Entry.cs | 124 +++++++++- src/Hexecs/Assets/AssetContext.cs | 18 +- .../Serializations/JsonWriterExtensions.cs | 218 +++++++++--------- 46 files changed, 755 insertions(+), 445 deletions(-) create mode 100644 src/Hexecs.Benchmarks.City/BenchmarkCounter.cs create mode 100644 src/Hexecs.Benchmarks.City/Content/Content.mgcb create mode 100644 src/Hexecs.Benchmarks.City/Content/DebugFont.spritefont create mode 100644 src/Hexecs.Benchmarks.City/Content/DebugFont.xnb rename src/Hexecs.Benchmarks.City/Content/{roguelikeSheet_transparent.png => terrain_atlas.png} (100%) create mode 100644 src/Hexecs.Benchmarks.City/Content/terrain_atlas.xnb delete mode 100644 src/Hexecs.Benchmarks.City/FpsCounter.cs create mode 100644 src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs rename src/Hexecs.Benchmarks.City/Utils/{AtlasTexture.cs => Sprites/Sprite.cs} (63%) create mode 100644 src/Hexecs.Benchmarks.City/Utils/Sprites/SpriteAtlas.cs delete mode 100644 src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs create mode 100644 src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs create mode 100644 src/Hexecs.Benchmarks.Noise/Content/DebugFont.spritefont create mode 100644 src/Hexecs.Benchmarks.Noise/Content/DebugFont.xnb delete mode 100644 src/Hexecs.Benchmarks.Noise/FpsCounter.cs diff --git a/src/Hexecs.Benchmarks.City/BenchmarkCounter.cs b/src/Hexecs.Benchmarks.City/BenchmarkCounter.cs new file mode 100644 index 0000000..cc4b84d --- /dev/null +++ b/src/Hexecs.Benchmarks.City/BenchmarkCounter.cs @@ -0,0 +1,90 @@ +using System.Globalization; +using System.Text; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Map; + +internal sealed class BenchmarkCounter +{ + private readonly Func _countResolver; + private readonly int[] _fpsHistory; + + private double _frameTime; + private int _fps; + private int _frameCount; + private double _fpsTimer; + + private int _historyIndex; + private bool _historyFull; + private double _avgFps; + private long _historySum; + + private readonly SpriteFont _font; + private readonly SpriteBatch _spriteBatch; + + // Используем StringBuilder как буфер + private readonly StringBuilder _stringBuilder = new(128); + private readonly Vector2 _textPos = new(10, 10); + private readonly Vector2 _shadowPos = new(11, 11); + + public BenchmarkCounter(Func countResolver, ContentManager contentManager, GraphicsDevice graphicsDevice) + { + _countResolver = countResolver; + _fpsHistory = new int[60]; + _font = contentManager.Load("DebugFont"); + _spriteBatch = new SpriteBatch(graphicsDevice); + } + + public void Draw(GameTime gameTime) + { + _frameCount++; + + _spriteBatch.Begin(); + + _spriteBatch.DrawString(_font, _stringBuilder, _shadowPos, Color.Black); + _spriteBatch.DrawString(_font, _stringBuilder, _textPos, Color.Yellow); + + _spriteBatch.End(); + } + + public void Update(GameTime gameTime) + { + var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; + _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; + _fpsTimer += elapsedSeconds; + + + if (_fpsTimer >= 1.0) + { + _fps = _frameCount; + + _historySum -= _fpsHistory[_historyIndex]; + _fpsHistory[_historyIndex] = _fps; + _historySum += _fps; + + _historyIndex = (_historyIndex + 1) % 60; + if (_historyIndex == 0) _historyFull = true; + + var historyCount = _historyFull ? 60 : _historyIndex; + _avgFps = (double)_historySum / historyCount; + + var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; + var count = _countResolver(); + + // Очищаем буфер и записываем новые данные без создания строк + var culture = CultureInfo.InvariantCulture; + + _stringBuilder.Clear(); + _stringBuilder + .Append($"{_fps} FPS") + .Append(culture, $" | Avg:{_avgFps:F1} fps") + .Append(culture, $" | Entities:{count:N0}") + .Append(culture, $" | Frame time:{_frameTime:F1}ms") + .Append(culture, $" | Alloc:{alloc:F3}mb"); + + _frameCount = 0; + _fpsTimer = 0; + } + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/CityGame.cs b/src/Hexecs.Benchmarks.City/CityGame.cs index d3156f7..928477d 100644 --- a/src/Hexecs.Benchmarks.City/CityGame.cs +++ b/src/Hexecs.Benchmarks.City/CityGame.cs @@ -11,9 +11,9 @@ namespace Hexecs.Benchmarks.Map; internal sealed class CityGame : Game { + private BenchmarkCounter _benchmarkCounter = null!; private Camera _camera = null!; private readonly GraphicsDeviceManager _graphics; - private FpsCounter _fpsCounter = null!; private World _world = null!; public CityGame() @@ -38,6 +38,7 @@ public CityGame() _graphics.ApplyChanges(); IsFixedTimeStep = false; + Content.RootDirectory = "Content"; } protected override void Initialize() @@ -46,7 +47,6 @@ protected override void Initialize() _camera = new Camera(GraphicsDevice); _world = new WorldBuilder() - .UseSingleton() .UseDefaultParallelWorker(Math.Min(6, Environment.ProcessorCount)) .UseSingleton(Content) .UseSingleton(GraphicsDevice) @@ -61,7 +61,7 @@ protected override void Initialize() _world.Actors.Execute(new GenerateTerrainCommand()); - _fpsCounter = new FpsCounter(() => _world.Actors.Length, Window); + _benchmarkCounter = new BenchmarkCounter(() => _world.Actors.Length, Content, GraphicsDevice); base.Initialize(); } @@ -80,8 +80,8 @@ protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.White); - _fpsCounter.Draw(gameTime); _world.Draw(gameTime.ElapsedGameTime, gameTime.TotalGameTime); + _benchmarkCounter.Draw(gameTime); base.Draw(gameTime); } @@ -94,7 +94,7 @@ protected override void Update(GameTime gameTime) } _camera.Update(gameTime); - + _benchmarkCounter.Update(gameTime); _world.Update(gameTime.ElapsedGameTime, gameTime.TotalGameTime); base.Update(gameTime); diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs b/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs index 0e69fd3..4fe897a 100644 --- a/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs +++ b/src/Hexecs.Benchmarks.City/Common/Positions/PositionBuilder.cs @@ -1,10 +1,10 @@ -using Hexecs.Benchmarks.Map.Utils; +using Hexecs.Benchmarks.Map.Terrains; namespace Hexecs.Benchmarks.Map.Common.Positions; -internal sealed class PositionBuilder : IActorBuilder +internal sealed class PositionBuilder(TerrainSettings terrainSettings) : IActorBuilder { - private const int TileSize = TextureStorage.TerrainTileSize; + private readonly int _terrainTileSize = terrainSettings.TileSize; public void Build(in Actor actor, in AssetRef asset, Args args) { @@ -12,7 +12,7 @@ public void Build(in Actor actor, in AssetRef asset, Args args) actor.Add(new Position { Grid = grid, - World = new Point(grid.X * TileSize, grid.Y * TileSize) + World = new Point(grid.X * _terrainTileSize, grid.Y * _terrainTileSize) }); } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs b/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs index 06080ec..a9b0dee 100644 --- a/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs +++ b/src/Hexecs.Benchmarks.City/Common/Positions/PositionsInstaller.cs @@ -9,7 +9,7 @@ public static ActorContextBuilder AddPositions(this ActorContextBuilder builder) { var terrainSettings = builder.World.GetRequiredService(); - builder.AddBuilder(); + builder.CreateBuilder(); builder .ConfigureComponentPool(terrain => terrain diff --git a/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs b/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs index 88fcbb6..32cc10e 100644 --- a/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs +++ b/src/Hexecs.Benchmarks.City/Common/Visibles/VisibleSystem.cs @@ -1,6 +1,8 @@ using Hexecs.Actors.Systems; 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; @@ -8,16 +10,16 @@ namespace Hexecs.Benchmarks.Map.Common.Visibles; internal sealed class VisibleSystem : UpdateSystem { - private const int TileSize = TextureStorage.TerrainTileSize; - private readonly Camera _camera; + private readonly int _tileSize; private CameraViewport _currentViewport; - public VisibleSystem(ActorContext context, Camera camera, IParallelWorker parallelWorker) + public VisibleSystem(ActorContext context, Camera camera, IParallelWorker parallelWorker, TerrainSettings settings) : base(context, parallelWorker: parallelWorker) { _camera = camera; + _tileSize = settings.TileSize; } protected override bool BeforeUpdate(in WorldTime time) @@ -35,7 +37,7 @@ protected override void Update(in ActorRef actor, in WorldTime time) { ref readonly var position = ref actor.Component1.World; - if (_currentViewport.Visible(position.X, position.Y, TileSize, TileSize)) + if (_currentViewport.Visible(position.X, position.Y, _tileSize, _tileSize)) { actor.TryAdd(new Visible()); } diff --git a/src/Hexecs.Benchmarks.City/Content/Content.mgcb b/src/Hexecs.Benchmarks.City/Content/Content.mgcb new file mode 100644 index 0000000..ad2226a --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Content/Content.mgcb @@ -0,0 +1,13 @@ +#----------------------------- Global Properties ----------------------------# +/outputDir:bin +/intermediateDir:obj +/platform:DesktopGL +/config: +/profile:Reach +/compress:False + +#---------------------------------- References -------------------------------# + +#---------------------------------- Content ----------------------------------# +/build:DebugFont.spritefont +/build:terrain_atlas.png \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Content/DebugFont.spritefont b/src/Hexecs.Benchmarks.City/Content/DebugFont.spritefont new file mode 100644 index 0000000..ba84b52 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Content/DebugFont.spritefont @@ -0,0 +1,16 @@ + + + + Consolas + 10 + 0 + true + + + + + ~ + + + + \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Content/DebugFont.xnb b/src/Hexecs.Benchmarks.City/Content/DebugFont.xnb new file mode 100644 index 0000000000000000000000000000000000000000..47451f4677c111d6f0012d9d4be29b774995dedd GIT binary patch literal 21529 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

|cSAXYrYy^HT5Z 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 H4 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 Jm>E!J;D4};<*xt$ literal 0 HcmV?d00001 diff --git a/src/Hexecs.Benchmarks.City/FpsCounter.cs b/src/Hexecs.Benchmarks.City/FpsCounter.cs deleted file mode 100644 index 5ac865c..0000000 --- a/src/Hexecs.Benchmarks.City/FpsCounter.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace Hexecs.Benchmarks.Map; - -internal sealed class FpsCounter -{ - private readonly Func _countResolver; - private readonly int[] _fpsHistory; - private readonly GameWindow _window; - - // Поля для статистики - private double _frameTime; - private int _fps; - private int _frameCount; - private double _fpsTimer; - private int _secondsCounter; - - // Для среднего значения за минуту (Rolling Average) - private int _historyIndex; - private bool _historyFull; - private double _avgFps; - - public FpsCounter(Func countResolver, GameWindow window) - { - _countResolver = countResolver; - _window = window; - _fpsHistory = new int[60]; - } - - public void Draw(GameTime gameTime) - { - var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; - _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; - _fpsTimer += elapsedSeconds; - _frameCount++; - - // Считаем FPS каждую секунду для точности истории - if (_fpsTimer >= 1.0) - { - _fps = _frameCount; - - // Обновляем историю для Avg - _fpsHistory[_historyIndex] = _fps; - _historyIndex = (_historyIndex + 1) % 60; - if (_historyIndex == 0) _historyFull = true; - - // Считаем среднее за минуту - var historyCount = _historyFull ? 60 : _historyIndex; - var sum = 0; - for (var i = 0; i < historyCount; i++) sum += _fpsHistory[i]; - _avgFps = (double)sum / historyCount; - - _frameCount = 0; - _fpsTimer -= 1.0; - _secondsCounter++; - - if (_secondsCounter >= 1) - { - var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; - var count = _countResolver(); - _window.Title = $"FPS: {_fps} | Avg FPS: {_avgFps:F1} | Entities: {count:N0} | Frame Time: {_frameTime:F2}ms | Alloc: {alloc:F2}Mb"; - - _secondsCounter = 0; - } - } - } -} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj b/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj index 72c6725..b27b7ab 100644 --- a/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj +++ b/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj @@ -21,7 +21,13 @@ - + + Never + + + PreserveNewest + + PreserveNewest diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs index aca0d08..e763f7e 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainDrawSystem.cs @@ -2,6 +2,7 @@ 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; @@ -10,25 +11,25 @@ namespace Hexecs.Benchmarks.Map.Terrains; internal sealed class TerrainDrawSystem : DrawSystem { private readonly Camera _camera; + private readonly TerrainSpriteAtlas _spriteAtlas; private readonly SpriteBatch _spriteBatch; - private readonly TextureStorage _textureStorage; public TerrainDrawSystem( Camera camera, ActorContext context, GraphicsDevice graphicsDevice, - TextureStorage textureStorage) + TerrainSpriteAtlas spriteAtlas) : base(context, constraint => constraint.Include()) { _camera = camera; - _textureStorage = textureStorage; + _spriteAtlas = spriteAtlas; _spriteBatch = new SpriteBatch(graphicsDevice); } protected override bool BeforeDraw(in WorldTime time) { _spriteBatch.Begin( - transformMatrix: _camera.GetTransformationMatrix(), + transformMatrix: _camera.TransformationMatrix, samplerState: SamplerState.PointClamp, blendState: BlendState.AlphaBlend); @@ -38,7 +39,7 @@ protected override bool BeforeDraw(in WorldTime time) protected override void Draw(in ActorRef actor, in WorldTime time) { ref readonly var terrain = ref actor.Component2; - ref readonly var texture = ref _textureStorage.GetTerrainTexture(terrain.Type, terrain.Overlay); + ref readonly var texture = ref _spriteAtlas.GetSprite(in terrain); ref readonly var worldPosition = ref actor.Component1.World; texture.Draw(_spriteBatch, new Vector2(worldPosition.X, worldPosition.Y)); diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs index ecaa090..a692019 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainInstaller.cs @@ -3,6 +3,8 @@ using Hexecs.Configurations; using Hexecs.Dependencies; using Hexecs.Worlds; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; namespace Hexecs.Benchmarks.Map.Terrains; @@ -12,7 +14,7 @@ public static ActorContextBuilder AddTerrain(this ActorContextBuilder builder) { var terrainSettings = builder.World.GetRequiredService(); - builder.AddBuilder(); + builder.CreateBuilder(); builder .ConfigureComponentPool(terrain => terrain @@ -30,6 +32,12 @@ public static WorldBuilder UseTerrain(this WorldBuilder builder) builder .UseAddAssetSource(new TerrainAssetSource()); + builder + .UseSingleton(ctx => new TerrainSpriteAtlas( + contentManager: ctx.GetRequiredService(), + fileName: "terrain_atlas", + settings: ctx.GetRequiredService())); + builder .UseSingleton(ctx => ctx .GetService()? diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs index 96b4be1..9d4f8a3 100644 --- a/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainSettings.cs @@ -2,10 +2,21 @@ namespace Hexecs.Benchmarks.Map.Terrains; public sealed class TerrainSettings { - public static TerrainSettings Default { get; } = new() { Width = 768, Height = 768 }; - public const string Key = "Map:Terrain"; + public static readonly TerrainSettings Default = new() + { + TileSize = 16, + TileSpacing = 1, + Width = 768, + Height = 768 + }; + + public int TileSize { get; init; } + + public int TileSpacing { get; init; } + public int Width { get; init; } + public int Height { get; init; } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs b/src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs new file mode 100644 index 0000000..d5a6998 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Terrains/TerrainSpriteAtlas.cs @@ -0,0 +1,37 @@ +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; + +internal sealed class TerrainSpriteAtlas : SpriteAtlas +{ + public TerrainSpriteAtlas(ContentManager contentManager, string fileName, TerrainSettings settings) + : base(contentManager, fileName, settings.TileSize, settings.TileSpacing) + { + } + + protected override AtlasKey CreateKey(in Terrain key) + { + var type = key.Type; + + var column = type switch + { + TerrainType.Ground => 6, + TerrainType.WaterRiver => 3, + TerrainType.UrbanConcrete => 7, + _ => 1 + }; + + var row = type switch + { + TerrainType.Ground => 0, + TerrainType.WaterRiver => 1, + TerrainType.UrbanConcrete => 0, + _ => 1 + }; + + return new AtlasKey(column, row); + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/Camera.cs b/src/Hexecs.Benchmarks.City/Utils/Camera.cs index 0981cf3..949742f 100644 --- a/src/Hexecs.Benchmarks.City/Utils/Camera.cs +++ b/src/Hexecs.Benchmarks.City/Utils/Camera.cs @@ -1,83 +1,73 @@ +using System.Runtime.CompilerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Hexecs.Benchmarks.Map.Utils; -internal sealed class Camera +internal sealed class Camera(GraphicsDevice graphicsDevice) { - private readonly GraphicsDevice _graphicsDevice; - ///

/// Позиция камеры в мировых координатах (центр экрана). /// - public Vector2 Position { get; private set; } - - /// - /// Viewport of world boundary - /// - public CameraViewport Viewport { get; private set; } + public ref readonly Vector2 Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _currentPosition; + } /// - /// Текущий масштаб камеры (1.0 = без изменений). + /// Матрица трансформации, учитывая позицию, зум и размер экрана. /// - public float Zoom { get; private set; } - - private int _previousScrollValue; - - public Camera(GraphicsDevice graphicsDevice) + public ref readonly Matrix TransformationMatrix { - Position = Vector2.Zero; - Zoom = 1f; - - _graphicsDevice = graphicsDevice; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _currentTransform; } /// - /// Изменяет текущий зум на множитель и ограничивает его допустимым диапазоном. + /// Viewport of world boundary /// - /// Множитель масштаба (больше 1 для приближения, меньше 1 для отдаления). - public void AdjustZoom(float factor) + public ref readonly CameraViewport Viewport { - if (factor > 0) Zoom += 1f; - else Zoom -= 1f; - - Zoom = MathHelper.Clamp(Zoom, 1f, 10f); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _currentViewport; } /// - /// Создает матрицу трансформации для SpriteBatch, учитывая позицию, зум и размер экрана. + /// Текущий масштаб камеры (1.0 = без изменений). /// - public Matrix GetTransformationMatrix() + public float Zoom { - var viewport = _graphicsDevice.Viewport; - var zoom = MathF.Round(Zoom); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _currentZoom; + } - var roundedPosition = new Vector2( - MathF.Round(Position.X), - MathF.Round(Position.Y) - ); + private Vector2 _currentPosition; + private Matrix _currentTransform; + private CameraViewport _currentViewport; + private float _currentZoom = 1f; - return Matrix.CreateTranslation(new Vector3(-roundedPosition.X, -roundedPosition.Y, 0)) * - Matrix.CreateScale(new Vector3(zoom, zoom, 1)) * - Matrix.CreateTranslation(new Vector3(viewport.Width * 0.5f, viewport.Height * 0.5f, 0)); - } + private Vector2 _previousPosition; + private float _previousZoom; + private int _previousScrollValue; - public Rectangle GetViewportWorldBoundary() + /// + /// Изменяет текущий зум на множитель и ограничивает его допустимым диапазоном. + /// + /// Множитель масштаба (больше 1 для приближения, меньше 1 для отдаления). + public void AdjustZoom(float factor) { - var viewport = _graphicsDevice.Viewport; - var topLeft = ScreenToWorld(new Vector2(0, 0)); - var bottomRight = ScreenToWorld(new Vector2(viewport.Width, viewport.Height)); + if (factor > 0) _currentZoom += 1f; + else _currentZoom -= 1f; - var width = (int)MathF.Ceiling(bottomRight.X - topLeft.X); - var height = (int)MathF.Ceiling(bottomRight.Y - topLeft.Y); - - return new Rectangle((int)topLeft.X, (int)topLeft.Y, width, height); + _currentZoom = MathHelper.Clamp(_currentZoom, 1f, 10f); } /// /// Смещает камеру на указанный вектор. /// - public void Move(Vector2 direction) => Position += direction; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Move(Vector2 direction) => _currentPosition += direction; /// /// Обрабатывает ввод игрока для перемещения и масштабирования камеры. @@ -90,7 +80,7 @@ public void Update(GameTime gameTime) var dt = (float)gameTime.ElapsedGameTime.TotalSeconds; // Базовое управление камерой - var speed = 500f / Zoom; + var speed = 500f / _currentZoom; var moveDir = Vector2.Zero; if (keyboard.IsKeyDown(Keys.W)) moveDir.Y -= 1; @@ -104,9 +94,6 @@ public void Update(GameTime gameTime) Move(moveDir * speed * dt); } - if (keyboard.IsKeyDown(Keys.Q)) Zoom += dt * 5f; - if (keyboard.IsKeyDown(Keys.E)) Zoom -= dt * 5f; - var scrollDelta = mouse.ScrollWheelValue - _previousScrollValue; if (scrollDelta != 0) { @@ -115,36 +102,67 @@ public void Update(GameTime gameTime) _previousScrollValue = mouse.ScrollWheelValue; - Zoom = MathHelper.Clamp(Zoom, 1f, 10f); + if (_currentPosition != _previousPosition || Math.Abs(_currentZoom - _previousZoom) > float.Epsilon) + { + UpdateTransformationMatrix(); + UpdateViewportBoundary(); + _previousPosition = _currentPosition; + _previousZoom = _currentZoom; + } + + UpdateTransformationMatrix(); UpdateViewportBoundary(); } /// /// Переводит экранные координаты (например, позицию мыши) в мировые координаты игрового поля. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ScreenToWorld(Vector2 screenPosition) { - return Vector2.Transform(screenPosition, Matrix.Invert(GetTransformationMatrix())); + return Vector2.Transform(screenPosition, Matrix.Invert(_currentTransform)); } /// /// Переводит мировые координаты в координаты экрана (например, для отрисовки UI над объектами). /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 WorldToScreen(Vector2 worldPosition) { - return Vector2.Transform(worldPosition, GetTransformationMatrix()); + return Vector2.Transform(worldPosition, _currentTransform); + } + + private void UpdateTransformationMatrix() + { + var viewport = graphicsDevice.Viewport; + var zoom = MathF.Round(_currentZoom); + + var roundedPosition = new Vector2( + MathF.Round(_currentPosition.X), + MathF.Round(_currentPosition.Y) + ); + + _currentTransform = Matrix.CreateTranslation(new Vector3(-roundedPosition.X, -roundedPosition.Y, 0)) * + Matrix.CreateScale(new Vector3(zoom, zoom, 1)) * + Matrix.CreateTranslation(new Vector3(viewport.Width * 0.5f, viewport.Height * 0.5f, 0)); } private void UpdateViewportBoundary() { - var viewport = _graphicsDevice.Viewport; + var deviceViewport = graphicsDevice.Viewport; var topLeft = ScreenToWorld(Vector2.Zero); - var bottomRight = ScreenToWorld(new Vector2(viewport.Width, viewport.Height)); + var bottomRight = ScreenToWorld(new Vector2(deviceViewport.Width, deviceViewport.Height)); var width = (int)MathF.Ceiling(bottomRight.X - topLeft.X); var height = (int)MathF.Ceiling(bottomRight.Y - topLeft.Y); - - Viewport = new CameraViewport((int)topLeft.X, (int)topLeft.Y, width, height); + var x = (int)topLeft.X; + var y = (int)topLeft.Y; + + ref var viewport = ref _currentViewport; + viewport.Left = x; + viewport.Right = x + width; + viewport.Top = y; + viewport.Bottom = y + height; } } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs b/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs index 2f4e758..086648a 100644 --- a/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs +++ b/src/Hexecs.Benchmarks.City/Utils/CameraViewport.cs @@ -2,13 +2,12 @@ namespace Hexecs.Benchmarks.Map.Utils; -[method: MethodImpl(MethodImplOptions.AggressiveInlining)] -public readonly struct CameraViewport(int x, int y, int width, int height) : IEquatable +public struct CameraViewport : IEquatable { - public readonly int Left = x; - public readonly int Right = x + width; - public readonly int Top = y; - public readonly int Bottom = y + height; + public int Left; + public int Right; + public int Top; + public int Bottom; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Hidden(int x, int y, int width, int height) => !Visible(x, y, width, height); @@ -29,4 +28,10 @@ public bool Equals(CameraViewport other) => Left == other.Left && public override bool Equals(object? obj) => obj is CameraViewport other && Equals(other); public override int GetHashCode() => HashCode.Combine(Left, Right, Top, Bottom); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(in CameraViewport left, in CameraViewport right) => left.Equals(right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(in CameraViewport left, in CameraViewport right) => !left.Equals(right); } \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/AtlasTexture.cs b/src/Hexecs.Benchmarks.City/Utils/Sprites/Sprite.cs similarity index 63% rename from src/Hexecs.Benchmarks.City/Utils/AtlasTexture.cs rename to src/Hexecs.Benchmarks.City/Utils/Sprites/Sprite.cs index cd6867d..67958d5 100644 --- a/src/Hexecs.Benchmarks.City/Utils/AtlasTexture.cs +++ b/src/Hexecs.Benchmarks.City/Utils/Sprites/Sprite.cs @@ -1,9 +1,10 @@ using System.Runtime.CompilerServices; using Microsoft.Xna.Framework.Graphics; -namespace Hexecs.Benchmarks.Map.Utils; +namespace Hexecs.Benchmarks.Map.Utils.Sprites; -internal readonly struct AtlasTexture(Texture2D texture, Rectangle region) +[method: MethodImpl(MethodImplOptions.AggressiveInlining)] +internal readonly struct Sprite(Texture2D texture, Rectangle region) { [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Draw(SpriteBatch spriteBatch, Vector2 position) => spriteBatch.Draw( diff --git a/src/Hexecs.Benchmarks.City/Utils/Sprites/SpriteAtlas.cs b/src/Hexecs.Benchmarks.City/Utils/Sprites/SpriteAtlas.cs new file mode 100644 index 0000000..30a8327 --- /dev/null +++ b/src/Hexecs.Benchmarks.City/Utils/Sprites/SpriteAtlas.cs @@ -0,0 +1,63 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Map.Utils.Sprites; + +internal abstract class SpriteAtlas : IDisposable + where TKey : struct +{ + private readonly Dictionary _sprites = new(); + private readonly Texture2D _texture; + + private readonly int _tileSize; + private readonly int _tileSpacing; + + private bool _disposed; + + protected SpriteAtlas(ContentManager contentManager, string fileName, int tileSize, int tileSpacing) + { + _texture = contentManager.Load(fileName); + _tileSize = tileSize; + _tileSpacing = tileSpacing; + } + + public ref Sprite GetSprite(in TKey key) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var atlasKey = CreateKey(in key); + ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(_sprites, key, out var exists); + if (exists) + { + return ref value; + } + + value = CreateSprite(atlasKey.Column, atlasKey.Row); + return ref value; + } + + protected abstract AtlasKey CreateKey(in TKey key); + + private Sprite CreateSprite(int column, int row) + { + var x = column * (_tileSize + _tileSpacing); + var y = row * (_tileSize + _tileSpacing); + var sourceRect = new Rectangle(x, y, _tileSize, _tileSize); + + return new Sprite(_texture, sourceRect); + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + + _sprites.Clear(); + _texture.Dispose(); + } + + [method: MethodImpl(MethodImplOptions.AggressiveInlining)] + protected readonly record struct AtlasKey(int Column, int Row); +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs b/src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs deleted file mode 100644 index ebeac1b..0000000 --- a/src/Hexecs.Benchmarks.City/Utils/TextureStorage.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Runtime.InteropServices; -using Hexecs.Benchmarks.Map.Terrains.ValueTypes; -using Microsoft.Xna.Framework.Graphics; - -namespace Hexecs.Benchmarks.Map.Utils; - -internal sealed class TextureStorage : IDisposable -{ - public const int TerrainTileSize = 16; // Размер одного тайла в пикселях (подставьте свой) - private const int TerrainSpacing = 1; // Между тайлами есть промежуток в 1 пиксель - - private readonly Texture2D _terrainAtlas; - private readonly Dictionary _terrainCache; - - private bool _disposed; - - public TextureStorage(GraphicsDevice graphicsDevice) - { - var contentRootPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Content"); - - _terrainAtlas = Texture2D.FromFile( - graphicsDevice, - Path.Combine(contentRootPath, "roguelikeSheet_transparent.png")); - - _terrainCache = new Dictionary(256); - } - - public void Dispose() - { - if (_disposed) return; - _disposed = true; - - _terrainAtlas.Dispose(); - _terrainCache.Clear(); - } - - public ref readonly AtlasTexture GetTerrainTexture(TerrainType type, TerrainOverlay overlay) - { - ObjectDisposedException.ThrowIf(_disposed, this); - - var key = new TerrainKey(type, overlay); - ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(_terrainCache, key, out var exists); - if (exists) return ref value; - - var column = type switch - { - TerrainType.Ground => 6, - TerrainType.WaterRiver => 3, - TerrainType.UrbanConcrete => 7, - _ => 1 - }; - var row = type switch - { - TerrainType.Ground => 0, - TerrainType.WaterRiver => 1, - TerrainType.UrbanConcrete => 0, - _ => 1 - }; - - var x = column * (TerrainTileSize + TerrainSpacing); - var y = row * (TerrainTileSize + TerrainSpacing); - var sourceRect = new Rectangle(x, y, TerrainTileSize, TerrainTileSize); - - value = new AtlasTexture(_terrainAtlas, sourceRect); - return ref value; - } - - private readonly record struct TerrainKey(TerrainType Type, TerrainOverlay Overlay); -} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs b/src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs new file mode 100644 index 0000000..01faf6a --- /dev/null +++ b/src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs @@ -0,0 +1,90 @@ +using System.Globalization; +using System.Text; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace Hexecs.Benchmarks.Noise; + +internal sealed class BenchmarkCounter +{ + private readonly Func _countResolver; + private readonly int[] _fpsHistory; + + private double _frameTime; + private int _fps; + private int _frameCount; + private double _fpsTimer; + + private int _historyIndex; + private bool _historyFull; + private double _avgFps; + private long _historySum; + + private readonly SpriteFont _font; + private readonly SpriteBatch _spriteBatch; + + // Используем StringBuilder как буфер + private readonly StringBuilder _stringBuilder = new(128); + private readonly Vector2 _textPos = new(10, 10); + private readonly Vector2 _shadowPos = new(11, 11); + + public BenchmarkCounter(Func countResolver, ContentManager contentManager, GraphicsDevice graphicsDevice) + { + _countResolver = countResolver; + _fpsHistory = new int[60]; + _font = contentManager.Load("DebugFont"); + _spriteBatch = new SpriteBatch(graphicsDevice); + } + + public void Draw(GameTime gameTime) + { + _frameCount++; + + _spriteBatch.Begin(); + + _spriteBatch.DrawString(_font, _stringBuilder, _shadowPos, Color.Black); + _spriteBatch.DrawString(_font, _stringBuilder, _textPos, Color.Yellow); + + _spriteBatch.End(); + } + + public void Update(GameTime gameTime) + { + var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; + _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; + _fpsTimer += elapsedSeconds; + + + if (_fpsTimer >= 1.0) + { + _fps = _frameCount; + + _historySum -= _fpsHistory[_historyIndex]; + _fpsHistory[_historyIndex] = _fps; + _historySum += _fps; + + _historyIndex = (_historyIndex + 1) % 60; + if (_historyIndex == 0) _historyFull = true; + + var historyCount = _historyFull ? 60 : _historyIndex; + _avgFps = (double)_historySum / historyCount; + + var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; + var count = _countResolver(); + + // Очищаем буфер и записываем новые данные без создания строк + var culture = CultureInfo.InvariantCulture; + + _stringBuilder.Clear(); + _stringBuilder + .Append($"{_fps} FPS") + .Append(culture, $" | Avg:{_avgFps:F1} fps") + .Append(culture, $" | Entities:{count:N0}") + .Append(culture, $" | Frame time:{_frameTime:F1}ms") + .Append(culture, $" | Alloc:{alloc:F3}mb"); + + _frameCount = 0; + _fpsTimer = 0; + } + } +} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs b/src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs index e5819ff..69190a8 100644 --- a/src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs +++ b/src/Hexecs.Benchmarks.Noise/Components/CircleColor.cs @@ -1,5 +1,4 @@ using Hexecs.Actors; -using Microsoft.Xna.Framework; namespace Hexecs.Benchmarks.Noise.Components; diff --git a/src/Hexecs.Benchmarks.Noise/Components/Position.cs b/src/Hexecs.Benchmarks.Noise/Components/Position.cs index 03299cb..e7bbd0a 100644 --- a/src/Hexecs.Benchmarks.Noise/Components/Position.cs +++ b/src/Hexecs.Benchmarks.Noise/Components/Position.cs @@ -1,5 +1,4 @@ using Hexecs.Actors; -using Microsoft.Xna.Framework; namespace Hexecs.Benchmarks.Noise.Components; diff --git a/src/Hexecs.Benchmarks.Noise/Components/Velocity.cs b/src/Hexecs.Benchmarks.Noise/Components/Velocity.cs index b999e31..3e2d743 100644 --- a/src/Hexecs.Benchmarks.Noise/Components/Velocity.cs +++ b/src/Hexecs.Benchmarks.Noise/Components/Velocity.cs @@ -1,5 +1,4 @@ using Hexecs.Actors; -using Microsoft.Xna.Framework; namespace Hexecs.Benchmarks.Noise.Components; diff --git a/src/Hexecs.Benchmarks.Noise/Content/DebugFont.spritefont b/src/Hexecs.Benchmarks.Noise/Content/DebugFont.spritefont new file mode 100644 index 0000000..ba84b52 --- /dev/null +++ b/src/Hexecs.Benchmarks.Noise/Content/DebugFont.spritefont @@ -0,0 +1,16 @@ + + + + Consolas + 10 + 0 + true + + + + + ~ + + + + \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Noise/Content/DebugFont.xnb b/src/Hexecs.Benchmarks.Noise/Content/DebugFont.xnb new file mode 100644 index 0000000000000000000000000000000000000000..47451f4677c111d6f0012d9d4be29b774995dedd GIT binary patch literal 21529 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 _countResolver; - private readonly int[] _fpsHistory; - private readonly GameWindow _window; - - // Поля для статистики - private double _frameTime; - private int _fps; - private int _frameCount; - private double _fpsTimer; - private int _secondsCounter; - - // Для среднего значения за минуту (Rolling Average) - private int _historyIndex; - private bool _historyFull; - private double _avgFps; - - public FpsCounter(Func countResolver, GameWindow window) - { - _countResolver = countResolver; - _window = window; - _fpsHistory = new int[60]; - } - - public void Draw(GameTime gameTime) - { - var elapsedSeconds = gameTime.ElapsedGameTime.TotalSeconds; - _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; - _fpsTimer += elapsedSeconds; - _frameCount++; - - // Считаем FPS каждую секунду для точности истории - if (_fpsTimer >= 1.0) - { - _fps = _frameCount; - - // Обновляем историю для Avg - _fpsHistory[_historyIndex] = _fps; - _historyIndex = (_historyIndex + 1) % 60; - if (_historyIndex == 0) _historyFull = true; - - // Считаем среднее за минуту - var historyCount = _historyFull ? 60 : _historyIndex; - var sum = 0; - for (var i = 0; i < historyCount; i++) sum += _fpsHistory[i]; - _avgFps = (double)sum / historyCount; - - _frameCount = 0; - _fpsTimer -= 1.0; - _secondsCounter++; - - if (_secondsCounter >= 1) - { - var alloc = GC.GetTotalMemory(false) / 1024.0 / 1024.0; - var count = _countResolver(); - _window.Title = $"FPS: {_fps} | Avg FPS: {_avgFps:F1} | Entities: {count:N0} | Frame Time: {_frameTime:F2}ms | Alloc: {alloc:F2}Mb"; - - _secondsCounter = 0; - } - } - } -} \ No newline at end of file diff --git a/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj b/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj index ceb90bf..80b5a75 100644 --- a/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj +++ b/src/Hexecs.Benchmarks.Noise/Hexecs.Benchmarks.Noise.csproj @@ -25,6 +25,9 @@ PreserveNewest + + PreserveNewest + diff --git a/src/Hexecs.Benchmarks.Noise/NoiseGame.cs b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs index e1bf250..d7db849 100644 --- a/src/Hexecs.Benchmarks.Noise/NoiseGame.cs +++ b/src/Hexecs.Benchmarks.Noise/NoiseGame.cs @@ -4,7 +4,6 @@ using Hexecs.Dependencies; using Hexecs.Threading; using Hexecs.Worlds; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; @@ -12,11 +11,10 @@ namespace Hexecs.Benchmarks.Noise; public class NoiseGame : Game { + private BenchmarkCounter _benchmarkCounter = null!; + private ActorContext _context = null!; private readonly GraphicsDeviceManager _graphics; private readonly Random _random = new(); - - private ActorContext _context = null!; - private FpsCounter _fpsCounter = null!; private World _world = null!; private const int InitialEntityCount = 2_000_000; @@ -44,6 +42,7 @@ public NoiseGame() _graphics.ApplyChanges(); IsFixedTimeStep = false; + Content.RootDirectory = "Content"; } protected override void Initialize() @@ -66,7 +65,7 @@ protected override void Initialize() .Build(); _context = _world.Actors; - _fpsCounter = new FpsCounter(() => _context.Length, Window); + _benchmarkCounter = new BenchmarkCounter(() => _context.Length, Content, GraphicsDevice); for (var i = 0; i < InitialEntityCount; i++) { @@ -109,6 +108,7 @@ protected override void Update(GameTime gameTime) } } + _benchmarkCounter.Update(gameTime); _world.Update(gameTime.ElapsedGameTime, gameTime.TotalGameTime); base.Update(gameTime); @@ -118,9 +118,8 @@ protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.White); - _fpsCounter.Draw(gameTime); - _world.Draw(gameTime.ElapsedGameTime, gameTime.TotalGameTime); + _benchmarkCounter.Draw(gameTime); base.Draw(gameTime); } diff --git a/src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs b/src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs index 176ec1c..b9dd6b9 100644 --- a/src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs +++ b/src/Hexecs.Benchmarks.Noise/Systems/InstanceData.cs @@ -1,5 +1,4 @@ using System.Runtime.InteropServices; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Hexecs.Benchmarks.Noise.Systems; diff --git a/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs b/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs index 428845a..82d3117 100644 --- a/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs +++ b/src/Hexecs.Benchmarks.Noise/Systems/MovementSystem.cs @@ -3,7 +3,6 @@ using Hexecs.Benchmarks.Noise.Components; using Hexecs.Threading; using Hexecs.Worlds; -using Microsoft.Xna.Framework; namespace Hexecs.Benchmarks.Noise.Systems; diff --git a/src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs b/src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs index f0d80f3..206e3d3 100644 --- a/src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs +++ b/src/Hexecs.Benchmarks.Noise/Systems/RenderSystem.cs @@ -2,7 +2,6 @@ using Hexecs.Actors.Systems; using Hexecs.Benchmarks.Noise.Components; using Hexecs.Worlds; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Hexecs.Benchmarks.Noise.Systems; diff --git a/src/Hexecs.Benchmarks.Noise/Usings.cs b/src/Hexecs.Benchmarks.Noise/Usings.cs index e12d09f..a1b37ef 100644 --- a/src/Hexecs.Benchmarks.Noise/Usings.cs +++ b/src/Hexecs.Benchmarks.Noise/Usings.cs @@ -1,3 +1,4 @@ // Global using directives -global using System.Runtime.CompilerServices; \ No newline at end of file +global using System.Runtime.CompilerServices; +global using Microsoft.Xna.Framework; \ No newline at end of file diff --git a/src/Hexecs.Tests/Actors/ActorTestFixture.cs b/src/Hexecs.Tests/Actors/ActorTestFixture.cs index 42de54c..3f9d71a 100644 --- a/src/Hexecs.Tests/Actors/ActorTestFixture.cs +++ b/src/Hexecs.Tests/Actors/ActorTestFixture.cs @@ -16,8 +16,8 @@ public ActorTestFixture() World = new WorldBuilder() .CreateAssetData(CreateAssets) .UseDefaultActorContext(ctx => ctx - .AddBuilder() - .AddBuilder() + .CreateBuilder() + .CreateBuilder() .ConfigureComponentPool(c => c.AddDisposeHandler())) .Build(); } diff --git a/src/Hexecs.Tests/Assets/AssetTestFixture.cs b/src/Hexecs.Tests/Assets/AssetTestFixture.cs index efd2866..4f7e646 100644 --- a/src/Hexecs.Tests/Assets/AssetTestFixture.cs +++ b/src/Hexecs.Tests/Assets/AssetTestFixture.cs @@ -94,6 +94,7 @@ private void CreateAssets(IAssetLoader loader) public void Dispose() { + _assets?.Dispose(); _world?.Dispose(); } } \ No newline at end of file diff --git a/src/Hexecs/Actors/ActorContext.Entry.cs b/src/Hexecs/Actors/ActorContext.Entry.cs index be87629..166021b 100644 --- a/src/Hexecs/Actors/ActorContext.Entry.cs +++ b/src/Hexecs/Actors/ActorContext.Entry.cs @@ -16,7 +16,15 @@ public readonly void Serialize(Utf8JsonWriter writer) writer.WriteStartObject(); writer.WriteProperty("Key", Key); - //writer.WriteProperty("Components", in Components); + writer.WritePropertyName("Components"); + + writer.WriteStartArray(); + foreach (var component in Components) + { + writer.WriteNumberValue(component); + } + + writer.WriteStartArray(); writer.WriteEndObject(); } @@ -26,7 +34,7 @@ public readonly void Serialize(Utf8JsonWriter writer) [method: MethodImpl(MethodImplOptions.AggressiveInlining)] internal struct ComponentBucket() { - public const int InlineArraySize = 6; + private const int InlineArraySize = 6; public int Length { diff --git a/src/Hexecs/Actors/ActorContext.cs b/src/Hexecs/Actors/ActorContext.cs index f1bc97f..027a955 100644 --- a/src/Hexecs/Actors/ActorContext.cs +++ b/src/Hexecs/Actors/ActorContext.cs @@ -1,5 +1,4 @@ -using System.Collections.Concurrent; -using System.Collections.Frozen; +using System.Collections.Frozen; using Hexecs.Actors.Components; using Hexecs.Actors.Delegates; using Hexecs.Actors.Relations; diff --git a/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs b/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs index 4ae9711..abe3a11 100644 --- a/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs +++ b/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs @@ -8,6 +8,21 @@ public static class ActorContextBuilderExtensions { extension(ActorContextBuilder builder) { + /// + /// Создаёт строитель актёров указанного типа. + /// + /// Тип строителя актёров. + /// Этот же экземпляр ActorContextBuilder для цепочки вызовов. + public ActorContextBuilder CreateBuilder< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicConstructors)] + T>() where T : class, IActorBuilder + { + builder.CreateBuilder(static ctx => (IActorBuilder)ctx.Activate(typeof(T))); + return builder; + } + + /// /// Регистрирует метод создания обработчика команды указанного типа. /// diff --git a/src/Hexecs/Actors/ActorContextBuilder.cs b/src/Hexecs/Actors/ActorContextBuilder.cs index 2d1673d..f30deeb 100644 --- a/src/Hexecs/Actors/ActorContextBuilder.cs +++ b/src/Hexecs/Actors/ActorContextBuilder.cs @@ -93,17 +93,6 @@ public ActorContextBuilder AddBuilder(IActorBuilder builder) return this; } - /// - /// Регистрирует строитель актёров указанного типа. - /// - /// Тип строителя актёров. - /// Этот же экземпляр ActorContextBuilder для цепочки вызовов. - public ActorContextBuilder AddBuilder() where T : class, IActorBuilder, new() - { - _builders.Add(new Entry(static _ => new T())); - return this; - } - /// /// Регистрирует функцию для создания строителя актёров с доступом к контексту актёров. /// diff --git a/src/Hexecs/Actors/Systems/UpdateSystem.cs b/src/Hexecs/Actors/Systems/UpdateSystem.cs index d7243b0..e986b3f 100644 --- a/src/Hexecs/Actors/Systems/UpdateSystem.cs +++ b/src/Hexecs/Actors/Systems/UpdateSystem.cs @@ -1,7 +1,6 @@ using Hexecs.Assets; using Hexecs.Dependencies; using Hexecs.Loggers; -using Hexecs.Threading; using Hexecs.Values; using Hexecs.Worlds; diff --git a/src/Hexecs/Assets/AssetContext.Components.cs b/src/Hexecs/Assets/AssetContext.Components.cs index 3d53bd3..1344a18 100644 --- a/src/Hexecs/Assets/AssetContext.Components.cs +++ b/src/Hexecs/Assets/AssetContext.Components.cs @@ -21,7 +21,7 @@ public ComponentEnumerator Components(uint assetId) ref var entry = ref GetEntry(assetId); return Unsafe.IsNullRef(ref entry) ? ComponentEnumerator.Empty - : new ComponentEnumerator(assetId, _componentPools, entry.AsReadOnlySpan()); + : new ComponentEnumerator(assetId, _componentPools, entry.ToArray()); } /// diff --git a/src/Hexecs/Assets/AssetContext.Dictionary.cs b/src/Hexecs/Assets/AssetContext.Dictionary.cs index 46a8fda..fb6823b 100644 --- a/src/Hexecs/Assets/AssetContext.Dictionary.cs +++ b/src/Hexecs/Assets/AssetContext.Dictionary.cs @@ -18,6 +18,16 @@ private ref Entry AddEntry(uint id) return ref entry; } + private void ClearEntries() + { + foreach (var value in _entries.Values) + { + value.Dispose(); + } + + _entries.Clear(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private ref Entry GetEntry(uint id) => ref CollectionsMarshal.GetValueRefOrNullRef(_entries, id); diff --git a/src/Hexecs/Assets/AssetContext.Entry.cs b/src/Hexecs/Assets/AssetContext.Entry.cs index 48b8688..e587acf 100644 --- a/src/Hexecs/Assets/AssetContext.Entry.cs +++ b/src/Hexecs/Assets/AssetContext.Entry.cs @@ -1,21 +1,129 @@ namespace Hexecs.Assets; +[DebuggerDisplay("Length = {Length}")] public sealed partial class AssetContext { - private struct Entry + [method: MethodImpl(MethodImplOptions.AggressiveInlining)] + private struct Entry() { - private ushort[]? _array; - private int _length; + private const int InlineArraySize = 6; - public void Add(ushort componentId) + public int Length { - ArrayUtils.InsertOrCreate(ref _array, _length, componentId); + [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); + _length++; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ReadOnlySpan AsReadOnlySpan() => _length == 0 - ? ReadOnlySpan.Empty - : new ReadOnlySpan(_array, 0, _length); + public readonly bool Contains(ushort item) => IndexOf(item) > -1; + + public void Dispose() + { + if (_array is { Length: > 0 }) ArrayPool.Shared.Return(_array); + _array = []; + _length = 0; + } + + public ComponentBucketEnumerator GetEnumerator() + { + ref var reference = ref Unsafe.As(ref _inlineArray); + var span = MemoryMarshal.CreateSpan(ref reference, InlineArraySize); + return new ComponentBucketEnumerator(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++) + { + if (_inlineArray[i] == item) + { + return i; + } + } + + if (_array == null || _array.Length == 0) return -1; + + var span = _array.AsSpan(0, _length - InlineArraySize); + for (var i = 0; i < span.Length; i++) + { + if (span[i] == item) return InlineArraySize + i; + } + + return -1; + } + + public ushort this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => index < InlineArraySize ? _inlineArray[index] : _array[index - InlineArraySize]; + set + { + if (index < InlineArraySize) _inlineArray[index] = value; + else _array[index - InlineArraySize] = value; + } + } + + public readonly ushort[] ToArray() + { + if (_length == 0) return []; + + var result = ArrayUtils.Create(_length); + for (var i = 0; i < _length; i++) + { + result[i] = this[i]; + } + + return result; + } + + public ref struct ComponentBucketEnumerator + { + public readonly ref ushort Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _index < InlineArraySize + ? ref _inlineArray[_index] + : ref _array[_index - InlineArraySize]; + } + + private readonly Span _inlineArray; + private readonly ushort[] _array; + private readonly int _length; + private int _index; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ComponentBucketEnumerator(Span inlineArray, ushort[] array, int length) + { + _inlineArray = inlineArray; + _array = array; + _length = length; + _index = -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_index < _length; + } + + [InlineArray(InlineArraySize)] + private struct InlineItemArray + { + private ushort _item; + } } } \ No newline at end of file diff --git a/src/Hexecs/Assets/AssetContext.cs b/src/Hexecs/Assets/AssetContext.cs index ca9fb36..11f473e 100644 --- a/src/Hexecs/Assets/AssetContext.cs +++ b/src/Hexecs/Assets/AssetContext.cs @@ -7,11 +7,13 @@ namespace Hexecs.Assets; /// Контекст ассетов, управляющий их жизненным циклом и содержащий коллекции их компонентов. /// [DebuggerDisplay("Length = {Length}")] -public sealed partial class AssetContext : IEnumerable +public sealed partial class AssetContext : IEnumerable, IDisposable { public readonly World World; private readonly Dictionary _aliases; + + private bool _disposed; internal AssetContext(World world, int capacity = 256) { @@ -26,6 +28,16 @@ internal AssetContext(World world, int capacity = 256) _filtersWithConstraint = new List(8); } + public void Dispose() + { + if (_disposed) return; + _disposed = true; + + ClearEntries(); + + _aliases.Clear(); + } + /// /// Проверяет существование ассета с указанным идентификатором. /// @@ -134,8 +146,6 @@ public Asset GetAsset(string alias) public AssetRef GetAssetRef(uint assetId) where T1 : struct, IAssetComponent { - Debug.Assert(ExistsAsset(assetId), $"Asset {assetId} isn't found"); - var pool = GetComponentPool(); if (pool == null) AssetError.ComponentNotFound(assetId); @@ -172,7 +182,7 @@ public void GetDescription(uint assetId, ref ValueStringBuilder builder, int max builder.Append("Id = "); builder.Append(assetId); - var components = entry.AsReadOnlySpan(); + ref var components = ref entry; var componentsLength = components.Length; if (componentsLength == 0) return; diff --git a/src/Hexecs/Serializations/JsonWriterExtensions.cs b/src/Hexecs/Serializations/JsonWriterExtensions.cs index 749ca1f..3833b3b 100644 --- a/src/Hexecs/Serializations/JsonWriterExtensions.cs +++ b/src/Hexecs/Serializations/JsonWriterExtensions.cs @@ -7,140 +7,138 @@ namespace Hexecs.Serializations; public static class JsonWriterExtensions { - public static Utf8JsonWriter WriteProperty( - this Utf8JsonWriter writer, - string propertyName, - Action value) + extension(Utf8JsonWriter writer) { - writer.WritePropertyName(propertyName); - value(writer); - return writer; - } - - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, - string propertyName, - in TArray array, - Action value) - where TArray: struct, IArray - { - writer.WritePropertyName(propertyName); - writer.WriteStartArray(); - - for (var i = 0; i < array.Length; i++) + public Utf8JsonWriter WriteProperty(string propertyName, + Action value) { - value(writer, array[i]); + writer.WritePropertyName(propertyName); + value(writer); + return writer; } - writer.WriteEndArray(); + public Utf8JsonWriter WriteProperty(string propertyName, + in TArray array, + Action value) + where TArray: struct, IArray + { + writer.WritePropertyName(propertyName); + writer.WriteStartArray(); - return writer; - } + for (var i = 0; i < array.Length; i++) + { + value(writer, array[i]); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, ActorId value) - { - writer.WriteNumber(propertyName, value.Value); - return writer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, ActorId value) - where T : struct, IActorComponent - { - writer.WriteNumber(propertyName, value.Value); - return writer; - } + writer.WriteEndArray(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, AssetId value) - { - writer.WriteNumber(propertyName, value.Value); - return writer; - } + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, AssetId value) - where T : struct, IAssetComponent - { - writer.WriteNumber(propertyName, value.Value); - return writer; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, ActorId value) + { + writer.WriteNumber(propertyName, value.Value); + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, bool value) - { - writer.WriteBoolean(propertyName, value); - return writer; - } - - [SkipLocalsInit] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, DateOnly value) - { - writer.WritePropertyName(propertyName); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, ActorId value) + where T : struct, IActorComponent + { + writer.WriteNumber(propertyName, value.Value); + return writer; + } - Span buffer = stackalloc char[68]; - if (value.TryFormat(buffer, out var charsWritten)) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, AssetId value) { - writer.WriteStringValue(buffer[..charsWritten]); + writer.WriteNumber(propertyName, value.Value); + return writer; } - else + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, AssetId value) + where T : struct, IAssetComponent { - writer.WriteStringValue(string.Empty); + writer.WriteNumber(propertyName, value.Value); + return writer; } - return writer; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, bool value) + { + writer.WriteBoolean(propertyName, value); + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, int value) - { - writer.WriteNumber(propertyName, value); - return writer; - } + [SkipLocalsInit] + public Utf8JsonWriter WriteProperty(string propertyName, DateOnly value) + { + writer.WritePropertyName(propertyName); + + Span buffer = stackalloc char[68]; + if (value.TryFormat(buffer, out var charsWritten)) + { + writer.WriteStringValue(buffer[..charsWritten]); + } + else + { + writer.WriteStringValue(string.Empty); + } + + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, long value) - { - writer.WriteNumber(propertyName, value); - return writer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, string value) - { - writer.WriteString(propertyName, value); - return writer; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, int value) + { + writer.WriteNumber(propertyName, value); + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, Type value) - { - writer.WriteString(propertyName, value.FullName); - return writer; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, long value) + { + writer.WriteNumber(propertyName, value); + return writer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Utf8JsonWriter WriteProperty(this Utf8JsonWriter writer, string propertyName, uint value) - { - writer.WriteNumber(propertyName, value); - return writer; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, string value) + { + writer.WriteString(propertyName, value); + return writer; + } - internal static Utf8JsonWriter WriteProperty( - this Utf8JsonWriter writer, - string propertyName, - ref readonly InlineBucket value) - { - writer.WritePropertyName(propertyName); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, Type value) + { + writer.WriteString(propertyName, value.FullName); + return writer; + } - writer.WriteStartArray(); - foreach (var element in value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Utf8JsonWriter WriteProperty(string propertyName, uint value) { - writer.WriteNumberValue(element); + writer.WriteNumber(propertyName, value); + return writer; } - writer.WriteEndArray(); + internal Utf8JsonWriter WriteProperty(string propertyName, + ref readonly InlineBucket value) + { + writer.WritePropertyName(propertyName); + + writer.WriteStartArray(); + foreach (var element in value) + { + writer.WriteNumberValue(element); + } + + writer.WriteEndArray(); - return writer; + return writer; + } } } \ No newline at end of file From 930558441ae788f4168b3f549fa07ae54a1da86d Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Mon, 29 Dec 2025 13:50:23 +0300 Subject: [PATCH 10/14] 0.4.3 map bench --- src/Hexecs.Benchmarks.City/BenchmarkCounter.cs | 1 - src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Hexecs.Benchmarks.City/BenchmarkCounter.cs b/src/Hexecs.Benchmarks.City/BenchmarkCounter.cs index cc4b84d..cd80598 100644 --- a/src/Hexecs.Benchmarks.City/BenchmarkCounter.cs +++ b/src/Hexecs.Benchmarks.City/BenchmarkCounter.cs @@ -54,7 +54,6 @@ public void Update(GameTime gameTime) _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; _fpsTimer += elapsedSeconds; - if (_fpsTimer >= 1.0) { _fps = _frameCount; diff --git a/src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs b/src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs index 01faf6a..9413e83 100644 --- a/src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs +++ b/src/Hexecs.Benchmarks.Noise/BenchmarkCounter.cs @@ -54,7 +54,6 @@ public void Update(GameTime gameTime) _frameTime = gameTime.ElapsedGameTime.TotalMilliseconds; _fpsTimer += elapsedSeconds; - if (_fpsTimer >= 1.0) { _fps = _frameCount; From 65e32d446dc7992b5d3ca180470f0c189204d37b Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Mon, 29 Dec 2025 13:50:51 +0300 Subject: [PATCH 11/14] 0.4.3 map bench --- src/Hexecs.Benchmarks.Noise/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hexecs.Benchmarks.Noise/Program.cs b/src/Hexecs.Benchmarks.Noise/Program.cs index 7cfe2fd..9e6cfc0 100644 --- a/src/Hexecs.Benchmarks.Noise/Program.cs +++ b/src/Hexecs.Benchmarks.Noise/Program.cs @@ -1,4 +1,4 @@ using Hexecs.Benchmarks.Noise; using var game = new NoiseGame(); -game.Run(); +game.Run(); \ No newline at end of file From 30a1a504523c0da1a29bfeefc9729e78d9e15d3c Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Mon, 29 Dec 2025 13:51:16 +0300 Subject: [PATCH 12/14] 0.4.3 map bench --- src/Hexecs/Actors/ActorContextBuilder.Extensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs b/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs index abe3a11..41afff0 100644 --- a/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs +++ b/src/Hexecs/Actors/ActorContextBuilder.Extensions.cs @@ -22,7 +22,6 @@ public ActorContextBuilder CreateBuilder< return builder; } - /// /// Регистрирует метод создания обработчика команды указанного типа. /// From 4d779df760a6be8802f301e2d31abe530dbd8375 Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Mon, 29 Dec 2025 13:51:48 +0300 Subject: [PATCH 13/14] 0.4.3 map bench --- src/Hexecs/Assets/AssetContext.Entry.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Hexecs/Assets/AssetContext.Entry.cs b/src/Hexecs/Assets/AssetContext.Entry.cs index e587acf..ab8378d 100644 --- a/src/Hexecs/Assets/AssetContext.Entry.cs +++ b/src/Hexecs/Assets/AssetContext.Entry.cs @@ -47,7 +47,6 @@ public readonly int IndexOf(ushort item) { if (_length == 0) return -1; - var inlineLength = Math.Min(_length, InlineArraySize); for (var i = 0; i < inlineLength; i++) { From 97864b0137db7d6ea329dd836e5d8c6e141f9f9c Mon Sep 17 00:00:00 2001 From: Kirill Bazhaykin Date: Mon, 29 Dec 2025 14:27:33 +0300 Subject: [PATCH 14/14] 0.4.3 map bench --- Directory.Build.props | 82 +++++++++---------- Directory.Packages.props | 16 +++- .../Hexecs.Benchmarks.City.csproj | 24 +++--- .../Hexecs.Benchmarks.csproj | 3 +- src/Hexecs.Benchmarks/Program.cs | 5 +- src/Hexecs/Hexecs.csproj | 2 +- 6 files changed, 67 insertions(+), 65 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 3fbe90a..9e85214 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,49 +8,47 @@ true $(NoWarn);1591 - - - - Exe - false - false - - - - + + + + false + false + + + + + - - - - - - - false - true - false - + + + + + + + false + true + false + - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index f3ee5c5..e23cfad 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,14 +1,24 @@  - true - true + true + true + + + + + + + + + - + + diff --git a/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj b/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj index b27b7ab..e1d5f3a 100644 --- a/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj +++ b/src/Hexecs.Benchmarks.City/Hexecs.Benchmarks.City.csproj @@ -15,21 +15,17 @@ - - - - - + - - Never - - - PreserveNewest - - - PreserveNewest - + + Never + + + PreserveNewest + + + PreserveNewest + diff --git a/src/Hexecs.Benchmarks/Hexecs.Benchmarks.csproj b/src/Hexecs.Benchmarks/Hexecs.Benchmarks.csproj index cf3e358..2cf767c 100644 --- a/src/Hexecs.Benchmarks/Hexecs.Benchmarks.csproj +++ b/src/Hexecs.Benchmarks/Hexecs.Benchmarks.csproj @@ -1,6 +1,7 @@  + Exe net10.0 enable enable @@ -11,7 +12,7 @@ - + diff --git a/src/Hexecs.Benchmarks/Program.cs b/src/Hexecs.Benchmarks/Program.cs index 7617776..af5ba4f 100644 --- a/src/Hexecs.Benchmarks/Program.cs +++ b/src/Hexecs.Benchmarks/Program.cs @@ -1,6 +1,3 @@ using BenchmarkDotNet.Running; -using Hexecs.Benchmarks.Actors; - -BenchmarkRunner.Run(); -//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); \ No newline at end of file +BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); \ No newline at end of file diff --git a/src/Hexecs/Hexecs.csproj b/src/Hexecs/Hexecs.csproj index 9e3c77c..2d4a2d0 100644 --- a/src/Hexecs/Hexecs.csproj +++ b/src/Hexecs/Hexecs.csproj @@ -31,7 +31,7 @@ - +