#nullable enable using System; using System.Collections.Generic; using Demo2.Domain; using Unity.Collections; using Unity.Entities; namespace Demo2.ECS { public sealed class Demo2EcsWorld : IDisposable { private readonly World _world; private readonly Demo2SimulationSystemGroup _group; private BlobAssetReference _ruleBlob; public World World => _world; public EntityManager EntityManager => _world.EntityManager; public Entity Singleton { get; } public Demo2EcsWorld(ulong seed, IReadOnlyList? orderedRules = null) { _world = new World("Demo2 Rule Factory", WorldFlags.Game); _group = _world.CreateSystemManaged(); Add(); Add(); Add(); Add(); Add(); Add(); Add(); Add(); Add(); Add(); _group.SortSystems(); Singleton = EntityManager.CreateEntity( typeof(Demo2SimulationState), typeof(Demo2EconomyState), typeof(Demo2CoreHealth), typeof(Demo2CycleStats), typeof(Demo2BuildCommandElement), typeof(Demo2ProcessedCommandToken), typeof(Demo2RuleInstruction), typeof(Demo2RuleProgram)); EntityManager.SetName(Singleton, "Demo2 Simulation Singleton"); EntityManager.SetComponentData(Singleton, new Demo2SimulationState { Seed = seed, Phase = Demo2Phase.Build, Cycle = 1, SpeedMultiplier = 1, RuleAppliedCycle = -1 }); EntityManager.SetComponentData(Singleton, new Demo2EconomyState { Money = 500, MultiplierMilli = SaturatingMath.Scale }); EntityManager.SetComponentData(Singleton, new Demo2CoreHealth { Value = 3 }); SetRules(orderedRules ?? Array.Empty()); CreateStarterLayout(); Tick(); } public void Tick() => _group.Update(); public void QueueBuild(Demo2BuildCommand command) { EntityManager.GetBuffer(Singleton).Add(new Demo2BuildCommandElement { Sequence = command.Sequence, IdempotencyToken = command.IdempotencyToken, PlayerId = command.PlayerId, Kind = command.Kind, X = (short)command.X, Y = (short)command.Y, Rotation = command.Rotation, RecipeId = command.RecipeId, Remove = command.Remove ? (byte)1 : (byte)0 }); } public void SetPhase(Demo2Phase phase) { var state = EntityManager.GetComponentData(Singleton); state.Phase = phase; EntityManager.SetComponentData(Singleton, state); } public void SetCycle(int cycle) { var state = EntityManager.GetComponentData(Singleton); state.Cycle = cycle; EntityManager.SetComponentData(Singleton, state); } public void SetRules(IReadOnlyList orderedRules) { if (_ruleBlob.IsCreated) _ruleBlob.Dispose(); var flattened = new List(); var buffer = EntityManager.GetBuffer(Singleton); buffer.Clear(); for (var slot = 0; slot < orderedRules.Count; slot++) foreach (var instruction in orderedRules[slot].Instructions) { var value = new Demo2RuleInstructionBlob { Opcode = instruction.Opcode, Condition = instruction.Condition, Operand = instruction.Operand, Slot = slot }; flattened.Add(value); buffer.Add(new Demo2RuleInstruction { Opcode = value.Opcode, Condition = value.Condition, Operand = value.Operand, Slot = value.Slot }); } using var builder = new BlobBuilder(Allocator.Temp); ref var root = ref builder.ConstructRoot(); var blobArray = builder.Allocate(ref root.Instructions, flattened.Count); for (var i = 0; i < flattened.Count; i++) blobArray[i] = flattened[i]; _ruleBlob = builder.CreateBlobAssetReference(Allocator.Persistent); EntityManager.SetComponentData(Singleton, new Demo2RuleProgram { Value = _ruleBlob }); } public Demo2Snapshot CaptureSnapshot(IReadOnlyList? ruleIds = null) => Demo2SnapshotCodec.Capture(EntityManager, Singleton, ruleIds); public void RestoreSnapshot(Demo2Snapshot snapshot) => Demo2SnapshotCodec.Restore(EntityManager, Singleton, snapshot); public void Dispose() { if (_ruleBlob.IsCreated) _ruleBlob.Dispose(); if (_world.IsCreated) _world.Dispose(); } private void Add() where T : ComponentSystemBase, new() => _group.AddSystemToUpdateList(_world.CreateSystemManaged()); private void CreateStarterLayout() { var state = EntityManager.GetComponentData(Singleton); Demo2EcsFactory.CreateMachine(EntityManager, ++state.NextStableId, Demo2MachineKind.Miner, 3, 7, 0, 1); for (var x = 4; x <= 7; x++) Demo2EcsFactory.CreateMachine(EntityManager, ++state.NextStableId, Demo2MachineKind.Conveyor, x, 7, 0, 0); Demo2EcsFactory.CreateMachine(EntityManager, ++state.NextStableId, Demo2MachineKind.Smelter, 8, 7, 0, 3); for (var x = 9; x <= 11; x++) Demo2EcsFactory.CreateMachine(EntityManager, ++state.NextStableId, Demo2MachineKind.Conveyor, x, 7, 0, 0); Demo2EcsFactory.CreateMachine(EntityManager, ++state.NextStableId, Demo2MachineKind.Assembler, 12, 7, 0, 4); Demo2EcsFactory.CreateMachine(EntityManager, ++state.NextStableId, Demo2MachineKind.Loader, 15, 7, 0, 5); var turret = Demo2EcsFactory.CreateMachine(EntityManager, ++state.NextStableId, Demo2MachineKind.Turret, 20, 10, 0, 0); EntityManager.AddComponentData(turret, new Demo2TurretAmmo()); EntityManager.SetComponentData(Singleton, state); } } public static class Demo2EcsFactory { public static Entity CreateMachine(EntityManager manager, int stableId, Demo2MachineKind kind, int x, int y, byte rotation, int recipeId) { var entity = manager.CreateEntity(typeof(Demo2StableId), typeof(Demo2GridPosition), typeof(Demo2MachineState), typeof(Demo2RecipeState)); manager.SetName(entity, $"Machine {stableId} {kind}"); manager.SetComponentData(entity, new Demo2StableId { Value = stableId }); manager.SetComponentData(entity, new Demo2GridPosition { X = (short)x, Y = (short)y }); var recipe = ResolveRecipe(recipeId == 0 ? DefaultRecipe(kind) : recipeId); manager.SetComponentData(entity, new Demo2MachineState { Kind = kind, Rotation = rotation, Enabled = 1, RecipeId = recipeId == 0 ? DefaultRecipe(kind) : recipeId, ProcessTicks = recipe.processTicks, Capacity = Capacity(kind) }); manager.SetComponentData(entity, recipe.state); if (kind is Demo2MachineKind.Conveyor or Demo2MachineKind.Splitter) manager.AddComponentData(entity, new Demo2ConveyorState { Direction = rotation, Capacity = Capacity(kind) }); return entity; } public static Entity CreateMaterial(EntityManager manager, int stableId, Demo2MaterialKind kind, int x, int y, byte direction, int quantity) { var entity = manager.CreateEntity(typeof(Demo2StableId), typeof(Demo2GridPosition), typeof(Demo2MaterialState)); manager.SetName(entity, $"Material {stableId} {kind}"); manager.SetComponentData(entity, new Demo2StableId { Value = stableId }); manager.SetComponentData(entity, new Demo2GridPosition { X = (short)x, Y = (short)y }); manager.SetComponentData(entity, new Demo2MaterialState { Kind = kind, Quantity = quantity, Direction = direction }); return entity; } private static int DefaultRecipe(Demo2MachineKind kind) => kind switch { Demo2MachineKind.Miner => 1, Demo2MachineKind.Generator => 2, Demo2MachineKind.Smelter => 3, Demo2MachineKind.Assembler => 4, Demo2MachineKind.Loader => 5, Demo2MachineKind.NightShiftSmelter => 100, _ => 0 }; private static int Capacity(Demo2MachineKind kind) => kind switch { Demo2MachineKind.Conveyor => 4, Demo2MachineKind.Splitter => 8, Demo2MachineKind.Turret => 200, Demo2MachineKind.Loader => 24, Demo2MachineKind.NightShiftSmelter => 16, _ => 12 }; private static (Demo2RecipeState state, int processTicks) ResolveRecipe(int id) => id switch { 1 => (new Demo2RecipeState { Output = Demo2MaterialKind.IronOre, OutputCount = 1 }, 20), 2 => (new Demo2RecipeState { Output = Demo2MaterialKind.Energy, OutputCount = 1 }, 30), 3 => (new Demo2RecipeState { InputA = Demo2MaterialKind.IronOre, InputB = Demo2MaterialKind.Fuel, Output = Demo2MaterialKind.Ingot, InputCountA = 2, InputCountB = 1, OutputCount = 1 }, 40), 4 => (new Demo2RecipeState { InputA = Demo2MaterialKind.Ingot, Output = Demo2MaterialKind.Gear, InputCountA = 2, OutputCount = 1 }, 50), 5 => (new Demo2RecipeState { InputA = Demo2MaterialKind.Gear, InputB = Demo2MaterialKind.Energy, Output = Demo2MaterialKind.Ammo, InputCountA = 1, InputCountB = 1, OutputCount = 4 }, 35), 100 => (new Demo2RecipeState { InputA = Demo2MaterialKind.IronOre, InputB = Demo2MaterialKind.Energy, Output = Demo2MaterialKind.Ingot, InputCountA = 2, InputCountB = 1, OutputCount = 2 }, 34), _ => (default, 1) }; } }