Files
Workspace/Assets/Demos/Demo2/ECS/Demo2EcsWorld.cs
T
2026-08-18 02:03:34 +08:00

199 lines
10 KiB
C#

#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<Demo2RuleProgramBlob> _ruleBlob;
public World World => _world;
public EntityManager EntityManager => _world.EntityManager;
public Entity Singleton { get; }
public Demo2EcsWorld(ulong seed, IReadOnlyList<Demo2RuleDefinition>? orderedRules = null)
{
_world = new World("Demo2 Rule Factory", WorldFlags.Game);
_group = _world.CreateSystemManaged<Demo2SimulationSystemGroup>();
Add<Demo2BuildCommandSystem>();
Add<Demo2ExtractionSystem>();
Add<Demo2ConveyorSystem>();
Add<Demo2InputAllocationSystem>();
Add<Demo2ProcessingSystem>();
Add<Demo2OutputSystem>();
Add<Demo2RuleTriggerSystem>();
Add<Demo2TurretLoadingSystem>();
Add<Demo2CycleStatisticsSystem>();
Add<Demo2StateHashSystem>();
_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<Demo2RuleDefinition>());
CreateStarterLayout();
Tick();
}
public void Tick() => _group.Update();
public void QueueBuild(Demo2BuildCommand command)
{
EntityManager.GetBuffer<Demo2BuildCommandElement>(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<Demo2SimulationState>(Singleton);
state.Phase = phase;
EntityManager.SetComponentData(Singleton, state);
}
public void SetCycle(int cycle)
{
var state = EntityManager.GetComponentData<Demo2SimulationState>(Singleton);
state.Cycle = cycle;
EntityManager.SetComponentData(Singleton, state);
}
public void SetRules(IReadOnlyList<Demo2RuleDefinition> orderedRules)
{
if (_ruleBlob.IsCreated) _ruleBlob.Dispose();
var flattened = new List<Demo2RuleInstructionBlob>();
var buffer = EntityManager.GetBuffer<Demo2RuleInstruction>(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<Demo2RuleProgramBlob>();
var blobArray = builder.Allocate(ref root.Instructions, flattened.Count);
for (var i = 0; i < flattened.Count; i++) blobArray[i] = flattened[i];
_ruleBlob = builder.CreateBlobAssetReference<Demo2RuleProgramBlob>(Allocator.Persistent);
EntityManager.SetComponentData(Singleton, new Demo2RuleProgram { Value = _ruleBlob });
}
public Demo2Snapshot CaptureSnapshot(IReadOnlyList<string>? 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<T>() where T : ComponentSystemBase, new() => _group.AddSystemToUpdateList(_world.CreateSystemManaged<T>());
private void CreateStarterLayout()
{
var state = EntityManager.GetComponentData<Demo2SimulationState>(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)
};
}
}