199 lines
10 KiB
C#
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)
|
|
};
|
|
}
|
|
}
|