This commit is contained in:
2026-08-18 02:03:34 +08:00
parent d2cd3b9fa8
commit 517c4cf46e
157 changed files with 16536 additions and 37 deletions
+19
View File
@@ -0,0 +1,19 @@
{
"name": "Demo2.ECS",
"rootNamespace": "Demo2.ECS",
"references": [
"Demo2.Domain",
"Unity.Entities",
"Unity.Collections",
"Unity.Mathematics",
"Unity.Burst"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e97a30deb43db644d974377bb7c70d46
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+127
View File
@@ -0,0 +1,127 @@
#nullable enable
using Demo2.Domain;
using Unity.Collections;
using Unity.Entities;
namespace Demo2.ECS
{
public struct Demo2SimulationState : IComponentData
{
public long Tick;
public ulong Seed;
public Demo2Phase Phase;
public int Cycle;
public int NextStableId;
public int RuleAppliedCycle;
public ulong StateHash;
public int SpeedMultiplier;
}
public struct Demo2EconomyState : IComponentData
{
public long Money;
public long Payload;
public long MultiplierMilli;
public long Energy;
public long ExcessFirepower;
}
public struct Demo2StableId : IComponentData { public int Value; }
public struct Demo2GridPosition : IComponentData { public short X; public short Y; }
public struct Demo2MachineState : IComponentData
{
public Demo2MachineKind Kind;
public byte Rotation;
public byte Enabled;
public int RecipeId;
public int ProgressTicks;
public int ProcessTicks;
public int StoredInput;
public int Capacity;
public int PendingOutputs;
public int Faults;
}
public struct Demo2RecipeState : IComponentData
{
public Demo2MaterialKind InputA;
public Demo2MaterialKind InputB;
public Demo2MaterialKind Output;
public int InputCountA;
public int InputCountB;
public int OutputCount;
}
public struct Demo2ConveyorState : IComponentData
{
public byte Direction;
public int Occupancy;
public int Capacity;
}
public struct Demo2MaterialState : IComponentData
{
public Demo2MaterialKind Kind;
public int Quantity;
public int SubCellProgress;
public byte Direction;
}
public struct Demo2TurretAmmo : IComponentData { public long Value; }
public struct Demo2CoreHealth : IComponentData { public int Value; }
public struct Demo2CycleStats : IComponentData
{
public long Extracted;
public long Transported;
public long Processed;
public long AmmoProduced;
public int CompletedSameRecipeStreak;
public int LastRecipeId;
public int Faults;
public int BeltOccupancy;
public int BeltCapacity;
public byte IronFuelAdjacent;
public byte FirstBatch;
public int DuplicateNextOutputs;
}
[InternalBufferCapacity(32)]
public struct Demo2BuildCommandElement : IBufferElementData
{
public long Sequence;
public FixedString64Bytes IdempotencyToken;
public FixedString32Bytes PlayerId;
public Demo2MachineKind Kind;
public short X;
public short Y;
public byte Rotation;
public int RecipeId;
public byte Remove;
}
[InternalBufferCapacity(64)]
public struct Demo2ProcessedCommandToken : IBufferElementData { public FixedString64Bytes Value; }
[InternalBufferCapacity(32)]
public struct Demo2RuleInstruction : IBufferElementData
{
public Demo2RuleOpcode Opcode;
public Demo2RuleCondition Condition;
public long Operand;
public int Slot;
}
public struct Demo2RuleInstructionBlob
{
public Demo2RuleOpcode Opcode;
public Demo2RuleCondition Condition;
public long Operand;
public int Slot;
}
public struct Demo2RuleProgramBlob { public BlobArray<Demo2RuleInstructionBlob> Instructions; }
public struct Demo2RuleProgram : IComponentData { public BlobAssetReference<Demo2RuleProgramBlob> Value; }
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7e6df350c356cf046b48a756e864df3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+198
View File
@@ -0,0 +1,198 @@
#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)
};
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc27cb2d9904aa64fbdbbff14348aa1d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,145 @@
#nullable enable
using System.Collections.Generic;
using Demo2.Domain;
using Unity.Collections;
using Unity.Entities;
namespace Demo2.ECS
{
public static class Demo2SnapshotCodec
{
public static Demo2Snapshot Capture(EntityManager manager, Entity singleton, IReadOnlyList<string>? ruleIds = null)
{
var simulation = manager.GetComponentData<Demo2SimulationState>(singleton);
var economy = manager.GetComponentData<Demo2EconomyState>(singleton);
var core = manager.GetComponentData<Demo2CoreHealth>(singleton);
var stats = manager.GetComponentData<Demo2CycleStats>(singleton);
var snapshot = new Demo2Snapshot
{
Tick = simulation.Tick,
Seed = simulation.Seed,
Phase = simulation.Phase,
Cycle = simulation.Cycle,
CoreHealth = core.Value,
Money = economy.Money,
Payload = economy.Payload,
MultiplierMilli = economy.MultiplierMilli,
Energy = economy.Energy,
ExcessFirepower = economy.ExcessFirepower,
RuleAppliedCycle = simulation.RuleAppliedCycle,
SpeedMultiplier = simulation.SpeedMultiplier,
Extracted = stats.Extracted,
Transported = stats.Transported,
Processed = stats.Processed,
AmmoProduced = stats.AmmoProduced,
CompletedSameRecipeStreak = stats.CompletedSameRecipeStreak,
LastRecipeId = stats.LastRecipeId,
Faults = stats.Faults,
BeltOccupancy = stats.BeltOccupancy,
BeltCapacity = stats.BeltCapacity,
IronFuelAdjacent = stats.IronFuelAdjacent,
FirstBatch = stats.FirstBatch,
DuplicateNextOutputs = stats.DuplicateNextOutputs,
StateHash = simulation.StateHash,
RuleIds = ruleIds == null ? new List<string>() : new List<string>(ruleIds)
};
using var entities = manager.GetAllEntities(Allocator.Temp);
for (var i = 0; i < entities.Length; i++)
{
var entity = entities[i];
if (!manager.HasComponent<Demo2StableId>(entity) || !manager.HasComponent<Demo2GridPosition>(entity)) continue;
var row = new Demo2SnapshotEntity { StableId = manager.GetComponentData<Demo2StableId>(entity).Value };
var position = manager.GetComponentData<Demo2GridPosition>(entity);
row.X = position.X;
row.Y = position.Y;
if (manager.HasComponent<Demo2MachineState>(entity))
{
var machine = manager.GetComponentData<Demo2MachineState>(entity);
row.MachineKind = machine.Kind;
row.Rotation = machine.Rotation;
row.RecipeId = machine.RecipeId;
row.Progress = machine.ProgressTicks;
row.Quantity = machine.StoredInput;
row.Faults = machine.Faults;
row.PendingOutputs = machine.PendingOutputs;
if (manager.HasComponent<Demo2TurretAmmo>(entity)) row.TurretAmmo = manager.GetComponentData<Demo2TurretAmmo>(entity).Value;
}
else if (manager.HasComponent<Demo2MaterialState>(entity))
{
var material = manager.GetComponentData<Demo2MaterialState>(entity);
row.MaterialKind = material.Kind;
row.Rotation = material.Direction;
row.Progress = material.SubCellProgress;
row.Quantity = material.Quantity;
row.MachineKind = (Demo2MachineKind)255;
}
snapshot.Entities.Add(row);
}
snapshot.Entities.Sort((left, right) => left.StableId.CompareTo(right.StableId));
return snapshot;
}
public static void Restore(EntityManager manager, Entity singleton, Demo2Snapshot snapshot)
{
using (var all = manager.GetAllEntities(Allocator.Temp))
for (var i = 0; i < all.Length; i++) if (all[i] != singleton) manager.DestroyEntity(all[i]);
var simulation = manager.GetComponentData<Demo2SimulationState>(singleton);
simulation.Tick = snapshot.Tick;
simulation.Seed = snapshot.Seed;
simulation.Phase = snapshot.Phase;
simulation.Cycle = snapshot.Cycle;
simulation.StateHash = snapshot.StateHash;
simulation.RuleAppliedCycle = snapshot.RuleAppliedCycle;
simulation.SpeedMultiplier = snapshot.SpeedMultiplier;
simulation.NextStableId = 0;
manager.SetComponentData(singleton, simulation);
manager.SetComponentData(singleton, new Demo2CoreHealth { Value = snapshot.CoreHealth });
manager.SetComponentData(singleton, new Demo2EconomyState
{
Money = snapshot.Money, Payload = snapshot.Payload, MultiplierMilli = snapshot.MultiplierMilli,
Energy = snapshot.Energy,
ExcessFirepower = snapshot.ExcessFirepower
});
manager.SetComponentData(singleton, new Demo2CycleStats
{
Extracted = snapshot.Extracted,
Transported = snapshot.Transported,
Processed = snapshot.Processed,
AmmoProduced = snapshot.AmmoProduced,
CompletedSameRecipeStreak = snapshot.CompletedSameRecipeStreak,
LastRecipeId = snapshot.LastRecipeId,
Faults = snapshot.Faults,
BeltOccupancy = snapshot.BeltOccupancy,
BeltCapacity = snapshot.BeltCapacity,
IronFuelAdjacent = snapshot.IronFuelAdjacent,
FirstBatch = snapshot.FirstBatch,
DuplicateNextOutputs = snapshot.DuplicateNextOutputs
});
foreach (var row in snapshot.Entities)
{
if ((byte)row.MachineKind == 255)
Demo2EcsFactory.CreateMaterial(manager, row.StableId, row.MaterialKind, row.X, row.Y, row.Rotation, row.Quantity);
else
{
var entity = Demo2EcsFactory.CreateMachine(manager, row.StableId, row.MachineKind, row.X, row.Y, row.Rotation, row.RecipeId);
var machine = manager.GetComponentData<Demo2MachineState>(entity);
machine.ProgressTicks = row.Progress;
machine.StoredInput = row.Quantity;
machine.Faults = row.Faults;
machine.PendingOutputs = row.PendingOutputs;
manager.SetComponentData(entity, machine);
if (row.MachineKind == Demo2MachineKind.Turret && !manager.HasComponent<Demo2TurretAmmo>(entity))
manager.AddComponentData(entity, new Demo2TurretAmmo());
if (manager.HasComponent<Demo2TurretAmmo>(entity))
manager.SetComponentData(entity, new Demo2TurretAmmo { Value = row.TurretAmmo });
}
if (row.StableId > simulation.NextStableId) simulation.NextStableId = row.StableId;
}
manager.SetComponentData(singleton, simulation);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46554d8358fcb914bbe6b75006e83b57
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+538
View File
@@ -0,0 +1,538 @@
#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<Demo2SimulationState>(singleton);
if (simulation.Phase != Demo2Phase.Build) return;
var economy = EntityManager.GetComponentData<Demo2EconomyState>(singleton);
var commands = EntityManager.GetBuffer<Demo2BuildCommandElement>(singleton);
var tokens = EntityManager.GetBuffer<Demo2ProcessedCommandToken>(singleton);
var pending = commands.ToNativeArray(Allocator.Temp);
var knownTokens = new NativeArray<FixedString64Bytes>(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<Demo2MachineState>(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<Demo2ProcessedCommandToken>(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<Demo2GridPosition>(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<FixedString64Bytes> 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<Demo2CycleStats>(singleton);
var query = GetEntityQuery(typeof(Demo2StableId), typeof(Demo2GridPosition), typeof(Demo2MachineState), typeof(Demo2RecipeState));
using var entities = query.ToEntityArray(Allocator.Temp);
using var positions = query.ToComponentDataArray<Demo2GridPosition>(Allocator.Temp);
for (var i = 0; i < entities.Length; i++)
{
var machine = EntityManager.GetComponentData<Demo2MachineState>(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<Demo2RecipeState>(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<Demo2EconomyState>(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<Demo2CycleStats>(singleton);
stats.BeltOccupancy = 0;
stats.BeltCapacity = 0;
var beltQuery = GetEntityQuery(typeof(Demo2ConveyorState));
using (var belts = beltQuery.ToComponentDataArray<Demo2ConveyorState>(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<Demo2GridPosition>(entities[i]);
var material = EntityManager.GetComponentData<Demo2MaterialState>(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<Demo2CycleStats>(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<Demo2GridPosition>(Allocator.Temp);
using var materialStates = materialQuery.ToComponentDataArray<Demo2MaterialState>(Allocator.Temp);
using var machines = machineQuery.ToEntityArray(Allocator.Temp);
using var machinePositions = machineQuery.ToComponentDataArray<Demo2GridPosition>(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<Demo2MachineState>(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<Demo2GridPosition> positions, NativeArray<Demo2MaterialState> 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<Demo2CycleStats>(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<Demo2MachineState>(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<Demo2RecipeState>(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<Demo2CycleStats>(singleton);
var query = GetEntityQuery(typeof(Demo2GridPosition), typeof(Demo2MachineState), typeof(Demo2RecipeState));
using var entities = query.ToEntityArray(Allocator.Temp);
using var positions = query.ToComponentDataArray<Demo2GridPosition>(Allocator.Temp);
for (var i = 0; i < entities.Length; i++)
{
var machine = EntityManager.GetComponentData<Demo2MachineState>(entities[i]);
if (machine.PendingOutputs <= 0) continue;
var recipe = EntityManager.GetComponentData<Demo2RecipeState>(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<Demo2SimulationState>(singleton);
if (simulation.Phase != Demo2Phase.Defense || simulation.RuleAppliedCycle == simulation.Cycle) return;
var economy = EntityManager.GetComponentData<Demo2EconomyState>(singleton);
var stats = EntityManager.GetComponentData<Demo2CycleStats>(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<Demo2CoreHealth>(singleton).Value <= 1,
DuplicatedOutputs = stats.DuplicateNextOutputs
};
var instructions = EntityManager.GetBuffer<Demo2RuleInstruction>(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<Demo2StableId>(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<Demo2TurretAmmo>(turret);
using var entities = materialQuery.ToEntityArray(Allocator.Temp);
using var materials = materialQuery.ToComponentDataArray<Demo2MaterialState>(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<Demo2SimulationState>(singleton);
if (simulation.Phase != Demo2Phase.Production) return;
simulation.Tick++;
var stats = EntityManager.GetComponentData<Demo2CycleStats>(singleton);
var economy = EntityManager.GetComponentData<Demo2EconomyState>(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<Demo2SimulationState>(singleton);
var economy = EntityManager.GetComponentData<Demo2EconomyState>(singleton);
var core = EntityManager.GetComponentData<Demo2CoreHealth>(singleton);
var stats = EntityManager.GetComponentData<Demo2CycleStats>(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<Demo2StableId>(Allocator.Temp);
var rows = new NativeArray<Demo2HashRow>(entities.Length, Allocator.Temp);
for (var i = 0; i < entities.Length; i++)
{
var row = new Demo2HashRow { StableId = ids[i].Value };
if (EntityManager.HasComponent<Demo2GridPosition>(entities[i]))
{
var position = EntityManager.GetComponentData<Demo2GridPosition>(entities[i]);
row.X = position.X;
row.Y = position.Y;
}
if (EntityManager.HasComponent<Demo2MachineState>(entities[i]))
{
var machine = EntityManager.GetComponentData<Demo2MachineState>(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<Demo2TurretAmmo>(entities[i]))
row.TurretAmmo = EntityManager.GetComponentData<Demo2TurretAmmo>(entities[i]).Value;
}
if (EntityManager.HasComponent<Demo2MaterialState>(entities[i]))
{
var material = EntityManager.GetComponentData<Demo2MaterialState>(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<Demo2HashRow>
{
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<Demo2SimulationState>(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);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e5d69845118ea8f458a29670df8f6bb6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: