#nullable enable using System; using Demo2.Domain; using Unity.Collections; using Unity.Entities; namespace Demo2.ECS { [DisableAutoCreation] public sealed partial class Demo2SimulationSystemGroup : ComponentSystemGroup { } [DisableAutoCreation] public sealed partial class Demo2BuildCommandSystem : SystemBase { protected override void OnUpdate() { var singletonQuery = GetEntityQuery(typeof(Demo2SimulationState), typeof(Demo2EconomyState), typeof(Demo2BuildCommandElement), typeof(Demo2ProcessedCommandToken)); if (singletonQuery.IsEmptyIgnoreFilter) return; var singleton = singletonQuery.GetSingletonEntity(); var simulation = EntityManager.GetComponentData(singleton); if (simulation.Phase != Demo2Phase.Build) return; var economy = EntityManager.GetComponentData(singleton); var commands = EntityManager.GetBuffer(singleton); var tokens = EntityManager.GetBuffer(singleton); var pending = commands.ToNativeArray(Allocator.Temp); var knownTokens = new NativeArray(tokens.Length + pending.Length, Allocator.Temp); var knownCount = tokens.Length; for (var i = 0; i < tokens.Length; i++) knownTokens[i] = tokens[i].Value; commands.Clear(); for (var index = 0; index < pending.Length; index++) { var command = pending[index]; if (Contains(knownTokens, knownCount, command.IdempotencyToken)) continue; knownTokens[knownCount++] = command.IdempotencyToken; if (command.X < 0 || command.X >= Demo2Protocol.GridWidth || command.Y < 0 || command.Y >= Demo2Protocol.GridHeight) continue; var existing = FindMachine(command.X, command.Y); if (command.Remove != 0) { if (existing != Entity.Null) { var machine = EntityManager.GetComponentData(existing); economy.Money = SaturatingMath.Add(economy.Money, Cost(machine.Kind)); EntityManager.DestroyEntity(existing); } continue; } var cost = Cost(command.Kind); if (existing != Entity.Null || economy.Money < cost) continue; economy.Money -= cost; simulation.NextStableId++; Demo2EcsFactory.CreateMachine(EntityManager, simulation.NextStableId, command.Kind, command.X, command.Y, command.Rotation, command.RecipeId); } tokens = EntityManager.GetBuffer(singleton); tokens.Clear(); for (var i = Math.Max(0, knownCount - 256); i < knownCount; i++) tokens.Add(new Demo2ProcessedCommandToken { Value = knownTokens[i] }); pending.Dispose(); knownTokens.Dispose(); EntityManager.SetComponentData(singleton, simulation); EntityManager.SetComponentData(singleton, economy); } private Entity FindMachine(int x, int y) { var query = GetEntityQuery(typeof(Demo2GridPosition), typeof(Demo2MachineState)); using var entities = query.ToEntityArray(Allocator.Temp); using var positions = query.ToComponentDataArray(Allocator.Temp); for (var i = 0; i < entities.Length; i++) if (positions[i].X == x && positions[i].Y == y) return entities[i]; return Entity.Null; } private static bool Contains(NativeArray tokens, int count, FixedString64Bytes token) { for (var i = 0; i < count; i++) if (tokens[i].Equals(token)) return true; return false; } public static int Cost(Demo2MachineKind kind) => kind switch { Demo2MachineKind.Miner => 40, Demo2MachineKind.Conveyor => 5, Demo2MachineKind.Splitter => 15, Demo2MachineKind.Smelter => 80, Demo2MachineKind.Assembler => 110, Demo2MachineKind.Loader => 70, Demo2MachineKind.Turret => 150, Demo2MachineKind.Generator => 60, Demo2MachineKind.NightShiftSmelter => 95, _ => 0 }; } [DisableAutoCreation] public sealed partial class Demo2ExtractionSystem : SystemBase { protected override void OnUpdate() { if (!Demo2SystemUtility.TryGetProductionState(EntityManager, out var singleton, out var simulation)) return; var stats = EntityManager.GetComponentData(singleton); var query = GetEntityQuery(typeof(Demo2StableId), typeof(Demo2GridPosition), typeof(Demo2MachineState), typeof(Demo2RecipeState)); using var entities = query.ToEntityArray(Allocator.Temp); using var positions = query.ToComponentDataArray(Allocator.Temp); for (var i = 0; i < entities.Length; i++) { var machine = EntityManager.GetComponentData(entities[i]); if (machine.Enabled == 0 || (machine.Kind != Demo2MachineKind.Miner && machine.Kind != Demo2MachineKind.Generator)) continue; machine.ProgressTicks++; if (machine.ProgressTicks < Math.Max(1, machine.ProcessTicks)) { EntityManager.SetComponentData(entities[i], machine); continue; } machine.ProgressTicks = 0; var recipe = EntityManager.GetComponentData(entities[i]); simulation.NextStableId++; Demo2EcsFactory.CreateMaterial(EntityManager, simulation.NextStableId, recipe.Output, positions[i].X, positions[i].Y, machine.Rotation, Math.Max(1, recipe.OutputCount)); stats.Extracted = SaturatingMath.Add(stats.Extracted, recipe.OutputCount); if (recipe.Output == Demo2MaterialKind.Energy) { var economy = EntityManager.GetComponentData(singleton); economy.Energy = SaturatingMath.Add(economy.Energy, recipe.OutputCount); EntityManager.SetComponentData(singleton, economy); } EntityManager.SetComponentData(entities[i], machine); } EntityManager.SetComponentData(singleton, simulation); EntityManager.SetComponentData(singleton, stats); } } [DisableAutoCreation] public sealed partial class Demo2ConveyorSystem : SystemBase { protected override void OnUpdate() { if (!Demo2SystemUtility.TryGetProductionState(EntityManager, out var singleton, out _)) return; var stats = EntityManager.GetComponentData(singleton); stats.BeltOccupancy = 0; stats.BeltCapacity = 0; var beltQuery = GetEntityQuery(typeof(Demo2ConveyorState)); using (var belts = beltQuery.ToComponentDataArray(Allocator.Temp)) for (var i = 0; i < belts.Length; i++) stats.BeltCapacity += Math.Max(1, belts[i].Capacity); var query = GetEntityQuery(typeof(Demo2GridPosition), typeof(Demo2MaterialState)); using var entities = query.ToEntityArray(Allocator.Temp); for (var i = 0; i < entities.Length; i++) { var position = EntityManager.GetComponentData(entities[i]); var material = EntityManager.GetComponentData(entities[i]); material.SubCellProgress += 250; stats.BeltOccupancy += material.Quantity; if (material.SubCellProgress >= 1000) { material.SubCellProgress -= 1000; Demo2SystemUtility.Move(ref position, material.Direction); stats.Transported = SaturatingMath.Add(stats.Transported, material.Quantity); } EntityManager.SetComponentData(entities[i], position); EntityManager.SetComponentData(entities[i], material); } EntityManager.SetComponentData(singleton, stats); } } [DisableAutoCreation] public sealed partial class Demo2InputAllocationSystem : SystemBase { protected override void OnUpdate() { if (!Demo2SystemUtility.TryGetProductionState(EntityManager, out var singleton, out _)) return; var stats = EntityManager.GetComponentData(singleton); var materialQuery = GetEntityQuery(typeof(Demo2GridPosition), typeof(Demo2MaterialState)); var machineQuery = GetEntityQuery(typeof(Demo2GridPosition), typeof(Demo2MachineState)); using var materials = materialQuery.ToEntityArray(Allocator.Temp); using var materialPositions = materialQuery.ToComponentDataArray(Allocator.Temp); using var materialStates = materialQuery.ToComponentDataArray(Allocator.Temp); using var machines = machineQuery.ToEntityArray(Allocator.Temp); using var machinePositions = machineQuery.ToComponentDataArray(Allocator.Temp); for (var m = 0; m < materials.Length; m++) { if (!EntityManager.Exists(materials[m])) continue; for (var i = 0; i < machines.Length; i++) { if (materialPositions[m].X != machinePositions[i].X || materialPositions[m].Y != machinePositions[i].Y) continue; var machine = EntityManager.GetComponentData(machines[i]); if (machine.Kind is Demo2MachineKind.Conveyor or Demo2MachineKind.Splitter or Demo2MachineKind.Miner or Demo2MachineKind.Generator or Demo2MachineKind.Turret) continue; if (machine.StoredInput + materialStates[m].Quantity > machine.Capacity) continue; machine.StoredInput += materialStates[m].Quantity; EntityManager.SetComponentData(machines[i], machine); EntityManager.DestroyEntity(materials[m]); break; } } stats.IronFuelAdjacent = HasAdjacentMaterial(materialPositions, materialStates, Demo2MaterialKind.IronOre, Demo2MaterialKind.Fuel) ? (byte)1 : (byte)0; EntityManager.SetComponentData(singleton, stats); } private static bool HasAdjacentMaterial(NativeArray positions, NativeArray materials, Demo2MaterialKind a, Demo2MaterialKind b) { for (var i = 0; i < positions.Length; i++) for (var j = i + 1; j < positions.Length; j++) if (((materials[i].Kind == a && materials[j].Kind == b) || (materials[i].Kind == b && materials[j].Kind == a)) && Math.Abs(positions[i].X - positions[j].X) + Math.Abs(positions[i].Y - positions[j].Y) == 1) return true; return false; } } [DisableAutoCreation] public sealed partial class Demo2ProcessingSystem : SystemBase { protected override void OnUpdate() { if (!Demo2SystemUtility.TryGetProductionState(EntityManager, out var singleton, out _)) return; var stats = EntityManager.GetComponentData(singleton); stats.Faults = 0; var query = GetEntityQuery(typeof(Demo2MachineState), typeof(Demo2RecipeState)); using var entities = query.ToEntityArray(Allocator.Temp); for (var i = 0; i < entities.Length; i++) { var machine = EntityManager.GetComponentData(entities[i]); if (machine.Kind is Demo2MachineKind.Miner or Demo2MachineKind.Generator or Demo2MachineKind.Conveyor or Demo2MachineKind.Splitter or Demo2MachineKind.Turret) continue; var recipe = EntityManager.GetComponentData(entities[i]); var needed = Math.Max(1, recipe.InputCountA + recipe.InputCountB); if (machine.StoredInput < needed) continue; machine.ProgressTicks++; if (machine.ProgressTicks >= Math.Max(1, machine.ProcessTicks)) { machine.ProgressTicks = 0; machine.StoredInput -= needed; machine.PendingOutputs += Math.Max(1, recipe.OutputCount); stats.Processed = SaturatingMath.Add(stats.Processed, 1); stats.FirstBatch = stats.Processed == 1 ? (byte)1 : stats.FirstBatch; if (stats.LastRecipeId == machine.RecipeId) stats.CompletedSameRecipeStreak++; else { stats.LastRecipeId = machine.RecipeId; stats.CompletedSameRecipeStreak = 1; } } stats.Faults += machine.Faults; EntityManager.SetComponentData(entities[i], machine); } EntityManager.SetComponentData(singleton, stats); } } [DisableAutoCreation] public sealed partial class Demo2OutputSystem : SystemBase { protected override void OnUpdate() { if (!Demo2SystemUtility.TryGetProductionState(EntityManager, out var singleton, out var simulation)) return; var stats = EntityManager.GetComponentData(singleton); var query = GetEntityQuery(typeof(Demo2GridPosition), typeof(Demo2MachineState), typeof(Demo2RecipeState)); using var entities = query.ToEntityArray(Allocator.Temp); using var positions = query.ToComponentDataArray(Allocator.Temp); for (var i = 0; i < entities.Length; i++) { var machine = EntityManager.GetComponentData(entities[i]); if (machine.PendingOutputs <= 0) continue; var recipe = EntityManager.GetComponentData(entities[i]); var quantity = machine.PendingOutputs; if (stats.DuplicateNextOutputs > 0) { quantity *= 2; stats.DuplicateNextOutputs--; } simulation.NextStableId++; Demo2EcsFactory.CreateMaterial(EntityManager, simulation.NextStableId, recipe.Output, positions[i].X, positions[i].Y, machine.Rotation, quantity); if (recipe.Output == Demo2MaterialKind.Ammo) stats.AmmoProduced = SaturatingMath.Add(stats.AmmoProduced, quantity); machine.PendingOutputs = 0; EntityManager.SetComponentData(entities[i], machine); } EntityManager.SetComponentData(singleton, simulation); EntityManager.SetComponentData(singleton, stats); } } [DisableAutoCreation] public sealed partial class Demo2RuleTriggerSystem : SystemBase { protected override void OnUpdate() { var query = GetEntityQuery(typeof(Demo2SimulationState), typeof(Demo2EconomyState), typeof(Demo2CycleStats), typeof(Demo2RuleInstruction)); if (query.IsEmptyIgnoreFilter) return; var singleton = query.GetSingletonEntity(); var simulation = EntityManager.GetComponentData(singleton); if (simulation.Phase != Demo2Phase.Defense || simulation.RuleAppliedCycle == simulation.Cycle) return; var economy = EntityManager.GetComponentData(singleton); var stats = EntityManager.GetComponentData(singleton); var context = new Demo2RuleContext { Payload = economy.Payload, MultiplierMilli = economy.MultiplierMilli, Energy = economy.Energy, Money = economy.Money, ExcessFirepower = economy.ExcessFirepower, Faults = stats.Faults, ThreeSameRecipes = stats.CompletedSameRecipeStreak >= 3, IronFuelAdjacent = stats.IronFuelAdjacent != 0, BeltBelowEightyPercent = stats.BeltCapacity <= 0 || stats.BeltOccupancy * 100 < stats.BeltCapacity * 80, FirstBatch = stats.FirstBatch != 0, CoreAtRisk = EntityManager.GetComponentData(singleton).Value <= 1, DuplicatedOutputs = stats.DuplicateNextOutputs }; var instructions = EntityManager.GetBuffer(singleton); for (var i = 0; i < instructions.Length; i++) { var instruction = instructions[i]; var data = new Demo2RuleInstructionData { Opcode = instruction.Opcode, Condition = instruction.Condition, Operand = instruction.Operand }; if (Demo2RuleEngine.Matches(data.Condition, context)) Demo2RuleEngine.Apply(ref context, data); } economy.Payload = context.Payload; economy.MultiplierMilli = context.MultiplierMilli; economy.Energy = context.Energy; economy.Money = context.Money; stats.Faults = context.Faults; stats.DuplicateNextOutputs = context.DuplicatedOutputs; simulation.RuleAppliedCycle = simulation.Cycle; EntityManager.SetComponentData(singleton, simulation); EntityManager.SetComponentData(singleton, economy); EntityManager.SetComponentData(singleton, stats); } } [DisableAutoCreation] public sealed partial class Demo2TurretLoadingSystem : SystemBase { protected override void OnUpdate() { if (!Demo2SystemUtility.TryGetProductionState(EntityManager, out _, out _)) return; var materialQuery = GetEntityQuery(typeof(Demo2MaterialState)); var turretQuery = GetEntityQuery(typeof(Demo2TurretAmmo), typeof(Demo2StableId)); if (turretQuery.IsEmptyIgnoreFilter) return; using var turretEntities = turretQuery.ToEntityArray(Allocator.Temp); using var turretIds = turretQuery.ToComponentDataArray(Allocator.Temp); var turret = turretEntities[0]; var lowestId = turretIds[0].Value; for (var i = 1; i < turretEntities.Length; i++) if (turretIds[i].Value < lowestId) { turret = turretEntities[i]; lowestId = turretIds[i].Value; } var ammo = EntityManager.GetComponentData(turret); using var entities = materialQuery.ToEntityArray(Allocator.Temp); using var materials = materialQuery.ToComponentDataArray(Allocator.Temp); for (var i = 0; i < entities.Length; i++) { if (materials[i].Kind != Demo2MaterialKind.Ammo || !EntityManager.Exists(entities[i])) continue; ammo.Value = SaturatingMath.Add(ammo.Value, materials[i].Quantity); EntityManager.DestroyEntity(entities[i]); } EntityManager.SetComponentData(turret, ammo); } } [DisableAutoCreation] public sealed partial class Demo2CycleStatisticsSystem : SystemBase { protected override void OnUpdate() { var query = GetEntityQuery(typeof(Demo2SimulationState), typeof(Demo2EconomyState), typeof(Demo2CycleStats)); if (query.IsEmptyIgnoreFilter) return; var singleton = query.GetSingletonEntity(); var simulation = EntityManager.GetComponentData(singleton); if (simulation.Phase != Demo2Phase.Production) return; simulation.Tick++; var stats = EntityManager.GetComponentData(singleton); var economy = EntityManager.GetComponentData(singleton); economy.Payload = SaturatingMath.Add(economy.Payload, stats.AmmoProduced); stats.AmmoProduced = 0; EntityManager.SetComponentData(singleton, simulation); EntityManager.SetComponentData(singleton, stats); EntityManager.SetComponentData(singleton, economy); } } [DisableAutoCreation] public sealed partial class Demo2StateHashSystem : SystemBase { protected override void OnUpdate() { var singletonQuery = GetEntityQuery(typeof(Demo2SimulationState), typeof(Demo2EconomyState), typeof(Demo2CoreHealth)); if (singletonQuery.IsEmptyIgnoreFilter) return; var singleton = singletonQuery.GetSingletonEntity(); var simulation = EntityManager.GetComponentData(singleton); var economy = EntityManager.GetComponentData(singleton); var core = EntityManager.GetComponentData(singleton); var stats = EntityManager.GetComponentData(singleton); var hash = Demo2Hash.Offset; Demo2Hash.Add(ref hash, simulation.Tick); Demo2Hash.Add(ref hash, simulation.Seed); Demo2Hash.Add(ref hash, (int)simulation.Phase); Demo2Hash.Add(ref hash, simulation.Cycle); Demo2Hash.Add(ref hash, simulation.RuleAppliedCycle); Demo2Hash.Add(ref hash, simulation.SpeedMultiplier); Demo2Hash.Add(ref hash, economy.Money); Demo2Hash.Add(ref hash, economy.Payload); Demo2Hash.Add(ref hash, economy.MultiplierMilli); Demo2Hash.Add(ref hash, economy.Energy); Demo2Hash.Add(ref hash, economy.ExcessFirepower); Demo2Hash.Add(ref hash, core.Value); Demo2Hash.Add(ref hash, stats.Extracted); Demo2Hash.Add(ref hash, stats.Transported); Demo2Hash.Add(ref hash, stats.Processed); Demo2Hash.Add(ref hash, stats.AmmoProduced); Demo2Hash.Add(ref hash, stats.CompletedSameRecipeStreak); Demo2Hash.Add(ref hash, stats.LastRecipeId); Demo2Hash.Add(ref hash, stats.Faults); Demo2Hash.Add(ref hash, stats.BeltOccupancy); Demo2Hash.Add(ref hash, stats.BeltCapacity); Demo2Hash.Add(ref hash, stats.IronFuelAdjacent); Demo2Hash.Add(ref hash, stats.FirstBatch); Demo2Hash.Add(ref hash, stats.DuplicateNextOutputs); var query = GetEntityQuery(typeof(Demo2StableId)); using var entities = query.ToEntityArray(Allocator.Temp); using var ids = query.ToComponentDataArray(Allocator.Temp); var rows = new NativeArray(entities.Length, Allocator.Temp); for (var i = 0; i < entities.Length; i++) { var row = new Demo2HashRow { StableId = ids[i].Value }; if (EntityManager.HasComponent(entities[i])) { var position = EntityManager.GetComponentData(entities[i]); row.X = position.X; row.Y = position.Y; } if (EntityManager.HasComponent(entities[i])) { var machine = EntityManager.GetComponentData(entities[i]); row.Kind = (int)machine.Kind; row.Rotation = machine.Rotation; row.Enabled = machine.Enabled; row.RecipeId = machine.RecipeId; row.StoredInput = machine.StoredInput; row.ProgressTicks = machine.ProgressTicks; row.ProcessTicks = machine.ProcessTicks; row.Capacity = machine.Capacity; row.PendingOutputs = machine.PendingOutputs; row.Faults = machine.Faults; if (EntityManager.HasComponent(entities[i])) row.TurretAmmo = EntityManager.GetComponentData(entities[i]).Value; } if (EntityManager.HasComponent(entities[i])) { var material = EntityManager.GetComponentData(entities[i]); row.Kind = (int)material.Kind; row.Quantity = material.Quantity; row.SubCellProgress = material.SubCellProgress; row.Rotation = material.Direction; } rows[i] = row; } rows.Sort(); for (var i = 0; i < rows.Length; i++) { var row = rows[i]; Demo2Hash.Add(ref hash, row.StableId); Demo2Hash.Add(ref hash, row.X); Demo2Hash.Add(ref hash, row.Y); Demo2Hash.Add(ref hash, row.Kind); Demo2Hash.Add(ref hash, row.Rotation); Demo2Hash.Add(ref hash, row.Enabled); Demo2Hash.Add(ref hash, row.RecipeId); Demo2Hash.Add(ref hash, row.StoredInput); Demo2Hash.Add(ref hash, row.ProgressTicks); Demo2Hash.Add(ref hash, row.ProcessTicks); Demo2Hash.Add(ref hash, row.Capacity); Demo2Hash.Add(ref hash, row.PendingOutputs); Demo2Hash.Add(ref hash, row.Faults); Demo2Hash.Add(ref hash, row.Quantity); Demo2Hash.Add(ref hash, row.SubCellProgress); Demo2Hash.Add(ref hash, row.TurretAmmo); } rows.Dispose(); simulation.StateHash = hash; EntityManager.SetComponentData(singleton, simulation); } private struct Demo2HashRow : IComparable { public int StableId; public int X; public int Y; public int Kind; public int Rotation; public int Enabled; public int RecipeId; public int StoredInput; public int ProgressTicks; public int ProcessTicks; public int Capacity; public int PendingOutputs; public int Faults; public int Quantity; public int SubCellProgress; public long TurretAmmo; public int CompareTo(Demo2HashRow other) => StableId.CompareTo(other.StableId); } } internal static class Demo2SystemUtility { public static bool TryGetProductionState(EntityManager manager, out Entity singleton, out Demo2SimulationState simulation) { var query = manager.CreateEntityQuery(typeof(Demo2SimulationState)); if (query.IsEmptyIgnoreFilter) { singleton = Entity.Null; simulation = default; return false; } singleton = query.GetSingletonEntity(); simulation = manager.GetComponentData(singleton); return simulation.Phase == Demo2Phase.Production; } public static void Move(ref Demo2GridPosition position, byte direction) { switch (direction & 3) { case 0: position.X++; break; case 1: position.Y--; break; case 2: position.X--; break; case 3: position.Y++; break; } if (position.X < 0) position.X = 0; if (position.X >= Demo2Protocol.GridWidth) position.X = Demo2Protocol.GridWidth - 1; if (position.Y < 0) position.Y = 0; if (position.Y >= Demo2Protocol.GridHeight) position.Y = Demo2Protocol.GridHeight - 1; } } internal static class Demo2Hash { public const ulong Offset = 14695981039346656037UL; private const ulong Prime = 1099511628211UL; public static void Add(ref ulong hash, long value) { unchecked { hash ^= (ulong)value; hash *= Prime; } } public static void Add(ref ulong hash, ulong value) { unchecked { hash ^= value; hash *= Prime; } } public static void Add(ref ulong hash, int value) => Add(ref hash, (long)value); } }