diff --git a/Numos.slnx b/Numos.slnx
index 1d731ad..e25baa0 100644
--- a/Numos.slnx
+++ b/Numos.slnx
@@ -23,6 +23,7 @@
+
diff --git a/benchmarks/Numos.CoreSim.Benchmarks/Infrastructure/SimulationWorkload.cs b/benchmarks/Numos.CoreSim.Benchmarks/Infrastructure/SimulationWorkload.cs
index c0dccb9..779db3f 100644
--- a/benchmarks/Numos.CoreSim.Benchmarks/Infrastructure/SimulationWorkload.cs
+++ b/benchmarks/Numos.CoreSim.Benchmarks/Infrastructure/SimulationWorkload.cs
@@ -1,3 +1,4 @@
+using Numos.Chunks;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.GasReactions;
using Numos.CoreSim.Replay;
@@ -34,7 +35,7 @@ internal SimulationWorkload(ScalingWorkloadOptions options)
if (options.GasCount <= 0 || options.CondensingGasCount < 0 || options.CondensingGasCount > options.GasCount)
throw new ArgumentException("Gas dimensions are invalid.", nameof(options));
- if (options.ActiveVoxelCount is < 0 or > AtmosChunkConstants.MaximumVoxelCount)
+ if (options.ActiveVoxelCount is < 0 or > ChunkConstants.MaximumVoxelCount)
throw new ArgumentException("Active voxel count is invalid.", nameof(options));
Options = options;
@@ -159,7 +160,7 @@ private static IEnumerable CreateReactions(AtmosConfig config
private void CreateChunk(ScalingWorkloadOptions options, Int3 position, int chunkIndex, bool awake)
{
- Kernel.CreateAndRegisterChunk(position, options.ChunkWidth, options.ChunkHeight, options.ChunkDepth);
+ Kernel.CreateAndRegisterChunk(position);
var chunk = Kernel.GetChunkForDangerousAccess(position);
if (!awake)
{
diff --git a/benchmarks/Numos.CoreSim.Benchmarks/Scaling/ExplicitTopologyBenchmarks.cs b/benchmarks/Numos.CoreSim.Benchmarks/Scaling/ExplicitTopologyBenchmarks.cs
index 9b7b18a..462e25a 100644
--- a/benchmarks/Numos.CoreSim.Benchmarks/Scaling/ExplicitTopologyBenchmarks.cs
+++ b/benchmarks/Numos.CoreSim.Benchmarks/Scaling/ExplicitTopologyBenchmarks.cs
@@ -1,5 +1,7 @@
using BenchmarkDotNet.Attributes;
using Numos.API;
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim.Datatypes.Primitives;
namespace Numos.CoreSim.Benchmarks.Scaling;
@@ -15,8 +17,8 @@ namespace Numos.CoreSim.Benchmarks.Scaling;
[BenchmarkCategory("Scaling", "ExplicitTopology")]
public class ExplicitTopologyBenchmarks
{
- private AtmosChunkHandle _chunk;
- private ExplicitLinkDefinition[] _definitions = [];
+ private ChunkHandle _chunk;
+ private ExplicitLinkDefinition[] _definitions = [];
private ExplicitLinkSetHandle _links;
private AtmosSimulation _simulation = null!;
private AtmosWorld _world = null!;
@@ -56,13 +58,13 @@ public void Setup()
_simulation.AddGasToVoxel(_chunk, 0, "BenchmarkGas", EdgeCount, 300f);
var source = _simulation.GetCellRef(_chunk, 0);
- _definitions = new ExplicitLinkDefinition[EdgeCount];
+ _definitions = new ExplicitLinkDefinition[EdgeCount];
for (int index = 0; index < EdgeCount; index++)
{
- _definitions[index] = new ExplicitLinkDefinition(
+ _definitions[index] = new ExplicitLinkDefinition(
source,
_simulation.GetCellRef(_chunk, checked((ushort)(index + 1))),
- ExplicitLinkFlags.GasTransport);
+ AtmosLinkFlags.GasTransport);
}
_links = _world.CreateLinks(_definitions);
diff --git a/benchmarks/Numos.Replay.Benchmarks/Program.cs b/benchmarks/Numos.Replay.Benchmarks/Program.cs
index 4c5309e..227cffa 100644
--- a/benchmarks/Numos.Replay.Benchmarks/Program.cs
+++ b/benchmarks/Numos.Replay.Benchmarks/Program.cs
@@ -2,6 +2,7 @@
using System.Globalization;
using System.Runtime.InteropServices;
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Replay;
using Numos.Maths;
@@ -65,7 +66,7 @@
var timeline = new AtmosReplayTimeline(simulation);
for (int tick = 0; tick < 200; tick++)
{
- if (tick % 17 == 0) simulation.AddGasToVoxel(new AtmosChunkHandle(default), 0, 0, 0.25f, 300f);
+ if (tick % 17 == 0) simulation.AddGasToVoxel(new ChunkHandle(default), 0, 0, 0.25f, 300f);
simulation.Tick();
timeline.ObserveLiveState();
}
diff --git a/docs/deterministic_replay.md b/docs/deterministic_replay.md
index 7f099c1..57d3631 100644
--- a/docs/deterministic_replay.md
+++ b/docs/deterministic_replay.md
@@ -380,7 +380,7 @@ all of them, so don't skip steps on the assumption that a missing one will alway
pointing at the registration host class.
3. Most operations need nothing else here -- the field order in the record's primary constructor becomes the wire
layout, mapped through `Numos.Replay.SourceGen`'s fixed set of recognized field types (primitives, `Int3`,
- `VoxelClassification`, `AtmosSimulationId`, `ExplicitLinkSetHandle`, and a nested-operation kind for a world
+ `VoxelClassification`, `SimulationId`, `ExplicitLinkSetHandle`, and a nested-operation kind for a world
operation embedding a whole `AtmosOperation`). An unrecognized field type fails the build with
`NUMOSREPLAYGEN001` rather than silently miscoding.
diff --git a/eng/package_manifest.py b/eng/package_manifest.py
index 1d87985..dc778e8 100644
--- a/eng/package_manifest.py
+++ b/eng/package_manifest.py
@@ -11,7 +11,8 @@
"Numos.Maths": (),
"Numos.Units": (),
"Numos.Collections": ("Numos.Maths",),
- "Numos.CoreSim": ("Numos.Maths", "Numos.Collections", "Numos.Units",),
+ "Numos.Chunks": ("Numos.Maths", "Numos.Collections",),
+ "Numos.CoreSim": ("Numos.Maths", "Numos.Collections", "Numos.Chunks", "Numos.Units",),
"Numos.API": ("Numos.CoreSim",),
"Numos.API.Dangerous": ("Numos.API",),
"Numos.Serialization": ("Numos.API",),
@@ -22,6 +23,7 @@
PACKAGE_VERSION_FILES = {
"Numos.Maths": Path("src/Numos.CoreSim/Version.props"),
"Numos.Collections": Path("src/Numos.CoreSim/Version.props"),
+ "Numos.Chunks": Path("src/Numos.CoreSim/Version.props"),
"Numos.Units": Path("src/Numos.CoreSim/Version.props"),
"Numos.CoreSim": Path("src/Numos.CoreSim/Version.props"),
"Numos.API": Path("src/Numos.CoreSim/Version.props"),
@@ -35,6 +37,7 @@
"coresim": (
"Numos.Maths",
"Numos.Collections",
+ "Numos.Chunks",
"Numos.Units",
"Numos.CoreSim",
"Numos.API",
diff --git a/src/Numos.API.Dangerous/AtmosDangerousApi.cs b/src/Numos.API.Dangerous/AtmosDangerousApi.cs
index 6eb5769..33fbebb 100644
--- a/src/Numos.API.Dangerous/AtmosDangerousApi.cs
+++ b/src/Numos.API.Dangerous/AtmosDangerousApi.cs
@@ -1,3 +1,5 @@
+using Numos.Chunks;
+
namespace Numos.API.Dangerous;
///
@@ -34,7 +36,7 @@ internal AtmosDangerousApi(AtmosSimulation simulation)
///
/// No chunk is registered at the handle's position.
/// The simulation has been disposed.
- public AtmosDangerousChunk GetChunk(AtmosChunkHandle chunk)
+ public AtmosDangerousChunk GetChunk(ChunkHandle chunk)
{
return new AtmosDangerousChunk(_simulation.Kernel.GetChunkForDangerousAccess(chunk.Position));
}
diff --git a/src/Numos.API.Dangerous/AtmosWorldSolverDangerousExtensions.cs b/src/Numos.API.Dangerous/AtmosWorldSolverDangerousExtensions.cs
index 4063ed0..9b0a99d 100644
--- a/src/Numos.API.Dangerous/AtmosWorldSolverDangerousExtensions.cs
+++ b/src/Numos.API.Dangerous/AtmosWorldSolverDangerousExtensions.cs
@@ -1,3 +1,5 @@
+using Numos.Chunks.Topology;
+
namespace Numos.API.Dangerous;
///
@@ -35,7 +37,7 @@ internal AtmosDangerousWorldSolverApi(AtmosWorldSolverContext context)
/// A current cell reference from the callback's world.
/// The unchecked live chunk view.
/// The cell does not identify live storage in this world.
- public AtmosDangerousChunk GetChunk(AtmosCellRef cell)
+ public AtmosDangerousChunk GetChunk(VoxelRef cell)
{
if (!_context.World.TryGetSimulation(cell.Simulation, out var simulation) || simulation == null)
throw new ArgumentException("The cell does not identify a live simulation in this world.", nameof(cell));
diff --git a/src/Numos.API/AtmosChunkHandle.cs b/src/Numos.API/AtmosChunkHandle.cs
deleted file mode 100644
index 416ae4f..0000000
--- a/src/Numos.API/AtmosChunkHandle.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Numos.Maths;
-
-namespace Numos.API;
-
-///
-/// Identifies a chunk owned by an .
-///
-public readonly record struct AtmosChunkHandle(Int3 Position);
\ No newline at end of file
diff --git a/src/Numos.API/AtmosSimulation.GasMixtures.cs b/src/Numos.API/AtmosSimulation.GasMixtures.cs
index 42ee639..93d7270 100644
--- a/src/Numos.API/AtmosSimulation.GasMixtures.cs
+++ b/src/Numos.API/AtmosSimulation.GasMixtures.cs
@@ -1,4 +1,5 @@
using JetBrains.Annotations;
+using Numos.Chunks;
using Numos.CoreSim;
namespace Numos.API;
@@ -34,7 +35,7 @@ public GasMixture CreateGasMixture(
/// becomes stale if that chunk is removed or replaced at the same grid position.
///
[PublicAPI]
- public IGasMixture GetVoxelGasMixture(AtmosChunkHandle chunk, ushort localVoxelIndex)
+ public IGasMixture GetVoxelGasMixture(ChunkHandle chunk, ushort localVoxelIndex)
{
lock (_mixtureGate)
{
@@ -46,7 +47,7 @@ public IGasMixture GetVoxelGasMixture(AtmosChunkHandle chunk, ushort localVoxelI
/// Creates sandboxed live access to one voxel addressed by local coordinates.
[PublicAPI]
- public IGasMixture GetVoxelGasMixture(AtmosChunkHandle chunk, int x, int y, int z)
+ public IGasMixture GetVoxelGasMixture(ChunkHandle chunk, int x, int y, int z)
{
lock (_mixtureGate)
{
diff --git a/src/Numos.API/AtmosSimulation.SolverStorage.cs b/src/Numos.API/AtmosSimulation.SolverStorage.cs
index 08aeabd..bfaa8ad 100644
--- a/src/Numos.API/AtmosSimulation.SolverStorage.cs
+++ b/src/Numos.API/AtmosSimulation.SolverStorage.cs
@@ -1,4 +1,5 @@
using JetBrains.Annotations;
+using Numos.Chunks;
using Numos.Collections;
using Numos.CoreSim;
@@ -183,7 +184,7 @@ public T GetOrCreateGasSolverData(int gasId, object key, Func
[PublicAPI]
public T[] GetOrCreateChunkSolverArray(
- AtmosChunkHandle chunk, object key, bool captureForRollback, int? length = null)
+ ChunkHandle chunk, object key, bool captureForRollback, int? length = null)
{
ThrowIfDisposed();
return _kernel.GetOrCreateChunkSolverArray(chunk.Position, key, captureForRollback, length);
@@ -224,7 +225,7 @@ public T[] GetOrCreateChunkSolverArray(
///
///
[PublicAPI]
- public FlatArray GetOrCreateChunkSolverFlatArray(AtmosChunkHandle chunk, object key, bool captureForRollback)
+ public FlatArray GetOrCreateChunkSolverFlatArray(ChunkHandle chunk, object key, bool captureForRollback)
{
ThrowIfDisposed();
return _kernel.GetOrCreateChunkSolverFlatArray(chunk.Position, key, captureForRollback);
diff --git a/src/Numos.API/AtmosSimulation.cs b/src/Numos.API/AtmosSimulation.cs
index 066bf30..4fc9162 100644
--- a/src/Numos.API/AtmosSimulation.cs
+++ b/src/Numos.API/AtmosSimulation.cs
@@ -1,4 +1,6 @@
using JetBrains.Annotations;
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Datatypes.Snapshots;
@@ -20,7 +22,7 @@ namespace Numos.API;
/// the solver pipeline, but it must not recursively execute or dispose the simulation or change chunk ownership
/// during the current tick.
///
-public sealed partial class AtmosSimulation : IDisposable
+public sealed partial class AtmosSimulation : IChunkSimulation, IDisposable
{
///
/// The fixed simulation rate, in ticks per second.
@@ -47,9 +49,9 @@ public sealed partial class AtmosSimulation : IDisposable
/// .
///
public AtmosSimulation(
- int chunkWidth = AtmosChunkConstants.DefaultWidth,
- int chunkHeight = AtmosChunkConstants.DefaultHeight,
- int chunkDepth = AtmosChunkConstants.DefaultDepth)
+ int chunkWidth = ChunkConstants.DefaultWidth,
+ int chunkHeight = ChunkConstants.DefaultHeight,
+ int chunkDepth = ChunkConstants.DefaultDepth)
: this(new AtmosConfig(), chunkWidth, chunkHeight, chunkDepth)
{
}
@@ -71,9 +73,9 @@ public AtmosSimulation(
///
public AtmosSimulation(
AtmosConfig config,
- int chunkWidth = AtmosChunkConstants.DefaultWidth,
- int chunkHeight = AtmosChunkConstants.DefaultHeight,
- int chunkDepth = AtmosChunkConstants.DefaultDepth)
+ int chunkWidth = ChunkConstants.DefaultWidth,
+ int chunkHeight = ChunkConstants.DefaultHeight,
+ int chunkDepth = ChunkConstants.DefaultDepth)
: this(new AtmosWorld(config), true, chunkWidth, chunkHeight, chunkDepth)
{
}
@@ -87,30 +89,30 @@ internal AtmosSimulation(
int chunkWidth,
int chunkHeight,
int chunkDepth,
- AtmosSimulationId? requestedRegistration = null)
+ SimulationId? requestedRegistration = null)
{
ArgumentNullException.ThrowIfNull(world);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(chunkWidth);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(chunkHeight);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(chunkDepth);
- if (chunkWidth > AtmosChunkConstants.MaximumVoxelCount ||
- chunkHeight > AtmosChunkConstants.MaximumVoxelCount ||
- chunkDepth > AtmosChunkConstants.MaximumVoxelCount)
+ if (chunkWidth > ChunkConstants.MaximumVoxelCount ||
+ chunkHeight > ChunkConstants.MaximumVoxelCount ||
+ chunkDepth > ChunkConstants.MaximumVoxelCount)
{
throw new ArgumentOutOfRangeException(
nameof(chunkWidth),
chunkWidth,
- $"No chunk dimension may exceed {AtmosChunkConstants.MaximumVoxelCount}.");
+ $"No chunk dimension may exceed {ChunkConstants.MaximumVoxelCount}.");
}
long voxelCount = (long)chunkWidth * chunkHeight * chunkDepth;
- if (voxelCount > AtmosChunkConstants.MaximumVoxelCount)
+ if (voxelCount > ChunkConstants.MaximumVoxelCount)
{
throw new ArgumentOutOfRangeException(
nameof(chunkWidth),
chunkWidth,
$"Chunk dimensions contain {voxelCount} voxels, but at most " +
- $"{AtmosChunkConstants.MaximumVoxelCount} are supported.");
+ $"{ChunkConstants.MaximumVoxelCount} are supported.");
}
_chunkWidth = chunkWidth;
@@ -132,12 +134,18 @@ internal AtmosSimulation(
///
[PublicAPI]
public AtmosWorld World { get; }
+
+ ///
+ /// Gets the world that owns shared time, configuration, and cross-simulation topology.
+ ///
+ [PublicAPI]
+ public IChunkWorld ChunkWorld => World;
///
/// Gets this simulation's stable generational identifier within .
///
[PublicAPI]
- public AtmosSimulationId Id { get; }
+ public SimulationId Id { get; }
///
/// Gets the fixed dimensions used by every chunk owned by this simulation.
@@ -425,11 +433,11 @@ public AtmosRecording StopRecording()
///
/// The simulation has been disposed.
[PublicAPI]
- public AtmosChunkHandle CreateAndRegisterChunk(Int3 position)
+ public ChunkHandle CreateAndRegisterChunk(Int3 position)
{
ThrowIfDisposed();
- _kernel.CreateAndRegisterChunk(position, _chunkWidth, _chunkHeight, _chunkDepth);
- return new AtmosChunkHandle(position);
+ _kernel.CreateAndRegisterChunk(position);
+ return new ChunkHandle(position);
}
///
@@ -444,7 +452,7 @@ public AtmosChunkHandle CreateAndRegisterChunk(Int3 position)
/// The simulation has been disposed.
/// Called from a solver callback.
[PublicAPI]
- public bool UnregisterChunk(AtmosChunkHandle chunk)
+ public bool UnregisterChunk(ChunkHandle chunk)
{
ThrowIfDisposed();
bool removed = _kernel.UnregisterChunk(chunk.Position);
@@ -464,7 +472,7 @@ public bool UnregisterChunk(AtmosChunkHandle chunk)
/// The local voxel index is outside the chunk.
/// The simulation has been disposed.
[PublicAPI]
- public AtmosCellRef GetCellRef(AtmosChunkHandle chunk, ushort localVoxelIndex)
+ public VoxelRef GetCellRef(ChunkHandle chunk, ushort localVoxelIndex)
{
ThrowIfDisposed();
if (!_kernel.TryResolveExplicitEndpoint(chunk.Position, localVoxelIndex, out _))
@@ -475,7 +483,7 @@ public AtmosCellRef GetCellRef(AtmosChunkHandle chunk, ushort localVoxelIndex)
throw new ArgumentOutOfRangeException(nameof(localVoxelIndex));
}
- return new AtmosCellRef(Id, chunk, localVoxelIndex);
+ return new VoxelRef(Id, chunk, localVoxelIndex);
}
///
@@ -487,7 +495,7 @@ public AtmosCellRef GetCellRef(AtmosChunkHandle chunk, ushort localVoxelIndex)
///
/// The simulation has been disposed.
[PublicAPI]
- public AtmosChunkHandle[] GetChunkHandles()
+ public ChunkHandle[] GetChunkHandles()
{
ThrowIfDisposed();
Int3[] positions = _kernel.GetChunkPositions();
@@ -505,7 +513,7 @@ public AtmosChunkHandle[] GetChunkHandles()
public bool TryGetChunkHandles(
long knownRevision,
out long revision,
- out AtmosChunkHandle[] handles)
+ out ChunkHandle[] handles)
{
ThrowIfDisposed();
if (!_kernel.TryGetChunkPositions(knownRevision, out revision, out Int3[] positions))
@@ -518,7 +526,7 @@ public bool TryGetChunkHandles(
return true;
}
- private static AtmosChunkHandle[] CreateSortedHandles(Int3[] positions)
+ private static ChunkHandle[] CreateSortedHandles(Int3[] positions)
{
Array.Sort(
positions,
@@ -532,9 +540,9 @@ private static AtmosChunkHandle[] CreateSortedHandles(Int3[] positions)
return y != 0 ? y : left.Z.CompareTo(right.Z);
});
- var handles = new AtmosChunkHandle[positions.Length];
+ var handles = new ChunkHandle[positions.Length];
for (int index = 0; index < positions.Length; index++)
- handles[index] = new AtmosChunkHandle(positions[index]);
+ handles[index] = new ChunkHandle(positions[index]);
return handles;
}
@@ -550,7 +558,7 @@ private static AtmosChunkHandle[] CreateSortedHandles(Int3[] positions)
/// No chunk is registered at the handle's position.
/// The simulation has been disposed.
[PublicAPI]
- public AtmosChunkSnapshot GetChunkSnapshot(AtmosChunkHandle chunk)
+ public AtmosChunkSnapshot GetChunkSnapshot(ChunkHandle chunk)
{
ThrowIfDisposed();
return _kernel.GetChunkSnapshot(chunk.Position);
@@ -564,7 +572,7 @@ public AtmosChunkSnapshot GetChunkSnapshot(AtmosChunkHandle chunk)
/// Scalar values plus one moles value per active gas channel.
[PublicAPI]
public AtmosVoxelSnapshot GetVoxelSnapshot(
- AtmosChunkHandle chunk,
+ ChunkHandle chunk,
ushort localVoxelIndex)
{
ThrowIfDisposed();
@@ -586,7 +594,7 @@ public AtmosVoxelSnapshot GetVoxelSnapshot(
/// only when the expected version is still current.
[PublicAPI]
public bool TryGetVoxelSnapshot(
- AtmosChunkHandle chunk,
+ ChunkHandle chunk,
ushort localVoxelIndex,
AtmosChunkVersion expectedVersion,
out AtmosVoxelSnapshot snapshot)
@@ -608,7 +616,7 @@ public bool TryGetVoxelSnapshot(
/// when a new snapshot was created; otherwise .
[PublicAPI]
public bool TryGetChunkSnapshot(
- AtmosChunkHandle chunk,
+ ChunkHandle chunk,
AtmosChunkVersion knownVersion,
out AtmosChunkSnapshot snapshot)
{
@@ -633,7 +641,7 @@ public bool TryGetChunkSnapshot(
/// when a new snapshot was created; otherwise .
[PublicAPI]
public bool TryGetChunkSnapshot(
- AtmosChunkHandle chunk,
+ ChunkHandle chunk,
AtmosChunkVersion knownVersion,
AtmosChunkSnapshotFields fields,
out AtmosChunkSnapshot snapshot)
@@ -671,7 +679,7 @@ public AtmosChunkSnapshotBatch GetChangedChunkSnapshots(
/// No chunk is registered at the handle's position.
/// The simulation has been disposed.
[PublicAPI]
- public void SetChunkClassification(AtmosChunkHandle chunk, VoxelClassification classification)
+ public void SetChunkClassification(ChunkHandle chunk, VoxelClassification classification)
{
ThrowIfDisposed();
_kernel.SetChunkClassification(chunk.Position, classification);
@@ -691,7 +699,7 @@ public void SetChunkClassification(AtmosChunkHandle chunk, VoxelClassification c
/// The simulation has been disposed.
[PublicAPI]
public void SetChunkBoundaryClassification(
- AtmosChunkHandle chunk,
+ ChunkHandle chunk,
VoxelClassification classification)
{
ThrowIfDisposed();
@@ -710,7 +718,7 @@ public void SetChunkBoundaryClassification(
/// The simulation has been disposed.
[PublicAPI]
public void SetVoxelClassification(
- AtmosChunkHandle chunk, ushort localVoxelIndex,
+ ChunkHandle chunk, ushort localVoxelIndex,
VoxelClassification classification)
{
ThrowIfDisposed();
@@ -730,7 +738,7 @@ public void SetVoxelClassification(
/// The simulation has been disposed.
[PublicAPI]
public void SetVoxelClassification(
- AtmosChunkHandle chunk, int x, int y, int z,
+ ChunkHandle chunk, int x, int y, int z,
VoxelClassification classification)
{
ThrowIfDisposed();
@@ -753,7 +761,7 @@ public void SetVoxelClassification(
/// No chunk is registered at the handle's position.
/// The simulation has been disposed.
[PublicAPI]
- public void SetVoxelTemperature(AtmosChunkHandle chunk, ushort localVoxelIndex, float temperature)
+ public void SetVoxelTemperature(ChunkHandle chunk, ushort localVoxelIndex, float temperature)
{
ThrowIfDisposed();
_kernel.SetVoxelTemperature(chunk.Position, localVoxelIndex, temperature);
@@ -777,7 +785,7 @@ public void SetVoxelTemperature(AtmosChunkHandle chunk, ushort localVoxelIndex,
/// No chunk is registered at the handle's position.
/// The simulation has been disposed.
[PublicAPI]
- public void SetVoxelTemperature(AtmosChunkHandle chunk, int x, int y, int z, float temperature)
+ public void SetVoxelTemperature(ChunkHandle chunk, int x, int y, int z, float temperature)
{
ThrowIfDisposed();
_kernel.SetVoxelTemperature(chunk.Position, x, y, z, temperature);
@@ -811,7 +819,7 @@ public void SetVoxelTemperature(AtmosChunkHandle chunk, int x, int y, int z, flo
/// The simulation has been disposed.
[PublicAPI]
public void AddGasToVoxel(
- AtmosChunkHandle chunk, ushort localVoxelIndex, int gasId, float moles,
+ ChunkHandle chunk, ushort localVoxelIndex, int gasId, float moles,
float temperature)
{
lock (_mixtureGate)
@@ -851,7 +859,7 @@ public void AddGasToVoxel(
/// The simulation has been disposed.
[PublicAPI]
public void AddGasToVoxel(
- AtmosChunkHandle chunk, int x, int y, int z, int gasId, float moles,
+ ChunkHandle chunk, int x, int y, int z, int gasId, float moles,
float temperature)
{
lock (_mixtureGate)
@@ -877,7 +885,7 @@ public void AddGasToVoxel(
/// The simulation has been disposed.
[PublicAPI]
public void AddGasToVoxel(
- AtmosChunkHandle chunk, ushort localVoxelIndex,
+ ChunkHandle chunk, ushort localVoxelIndex,
string gasName, float moles, float temperature)
{
lock (_mixtureGate)
@@ -903,7 +911,7 @@ public void AddGasToVoxel(
/// The simulation has been disposed.
[PublicAPI]
public void AddGasToVoxel(
- AtmosChunkHandle chunk, int x, int y, int z,
+ ChunkHandle chunk, int x, int y, int z,
string gasName, float moles, float temperature)
{
lock (_mixtureGate)
@@ -919,7 +927,7 @@ public void AddGasToVoxel(
/// No chunk is registered at the handle's position.
/// The simulation has been disposed.
[PublicAPI]
- public void WakeChunk(AtmosChunkHandle chunk)
+ public void WakeChunk(ChunkHandle chunk)
{
ThrowIfDisposed();
_kernel.WakeChunk(chunk.Position);
@@ -933,7 +941,7 @@ public void WakeChunk(AtmosChunkHandle chunk)
/// No chunk is registered at the handle's position.
/// The simulation has been disposed.
[PublicAPI]
- public void SleepChunk(AtmosChunkHandle chunk)
+ public void SleepChunk(ChunkHandle chunk)
{
ThrowIfDisposed();
_kernel.SleepChunk(chunk.Position);
diff --git a/src/Numos.API/AtmosSolver.cs b/src/Numos.API/AtmosSolver.cs
index 77c4f4e..edc9a11 100644
--- a/src/Numos.API/AtmosSolver.cs
+++ b/src/Numos.API/AtmosSolver.cs
@@ -16,7 +16,7 @@ public static class AtmosBuiltInSolvers
///
/// Sparse gas transport across explicit portal, dock, and arbitrary links whose flags include
- /// .
+ /// .
///
public const string ExplicitGasTransport = "explicit-gas-transport";
@@ -32,7 +32,7 @@ public static class AtmosBuiltInSolvers
///
/// Sparse thermal transport across explicit portal, dock, and arbitrary links whose flags include
- /// . Runs on the same cadence as
+ /// . Runs on the same cadence as
/// (see AtmosSolverConstants.ThermodynamicsTickInterval).
///
public const string ExplicitThermalTransport = "explicit-thermal-transport";
diff --git a/src/Numos.API/AtmosWorld.Replay.cs b/src/Numos.API/AtmosWorld.Replay.cs
index 333b06e..a67dbe0 100644
--- a/src/Numos.API/AtmosWorld.Replay.cs
+++ b/src/Numos.API/AtmosWorld.Replay.cs
@@ -1,4 +1,6 @@
using System.Diagnostics;
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim.Replay;
namespace Numos.API;
@@ -241,7 +243,7 @@ public AtmosWorldStateHash ComputeStateHash()
///
/// The operation is atomic with respect to deterministic world state. If validation or application fails,
/// the state present before this call is restored. Simulation objects created after the source checkpoint
- /// may be replaced; reacquire them by after seeking.
+ /// may be replaced; reacquire them by after seeking.
///
/// A required argument is .
/// The history is malformed or incompatible with the checkpoint.
@@ -385,7 +387,7 @@ private void Apply(AtmosWorldSimulationOperation simulationOperation)
simulation.Kernel.ApplyRecordedOperation(simulationOperation.Operation);
if (simulationOperation.Operation is RemoveChunkOperation removed)
- InvalidateLinksForChunk(simulation, new AtmosChunkHandle(removed.Position));
+ InvalidateLinksForChunk(simulation, new ChunkHandle(removed.Position));
}
private void Apply(SetAtmosWorldConfigOperation config)
@@ -533,7 +535,7 @@ internal static AtmosWorldStateHash Hash(AtmosWorldCheckpoint checkpoint)
return new AtmosWorldStateHash(checkpoint.Position, hash.Value);
}
- private static void AddCell(ref AtmosStateHasher hash, AtmosCellRef cell)
+ private static void AddCell(ref AtmosStateHasher hash, VoxelRef cell)
{
hash.Add(cell.Simulation.Index);
hash.Add(cell.Simulation.Generation);
diff --git a/src/Numos.API/AtmosWorld.cs b/src/Numos.API/AtmosWorld.cs
index da1ad99..39f8b3e 100644
--- a/src/Numos.API/AtmosWorld.cs
+++ b/src/Numos.API/AtmosWorld.cs
@@ -1,5 +1,7 @@
using System.Buffers;
using JetBrains.Annotations;
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim;
using Numos.CoreSim.Replay;
using Numos.CoreSim.Solvers;
@@ -22,22 +24,22 @@ namespace Numos.API;
/// AtmosSimulation station = world.CreateSimulation(16, 16, 16);
/// AtmosSimulation shuttle = world.CreateSimulation(8, 8, 8);
///
-/// AtmosChunkHandle stationChunk = station.CreateAndRegisterChunk(new Int3(0, 0, 0));
-/// AtmosChunkHandle shuttleChunk = shuttle.CreateAndRegisterChunk(new Int3(0, 0, 0));
-/// AtmosCellRef stationCell = station.GetCellRef(stationChunk, 0);
-/// AtmosCellRef shuttleCell = shuttle.GetCellRef(shuttleChunk, 0);
-/// AtmosPortalHandle portal = world.CreatePortal(stationCell, shuttleCell);
+/// ChunkHandle stationChunk = station.CreateAndRegisterChunk(new Int3(0, 0, 0));
+/// ChunkHandle shuttleChunk = shuttle.CreateAndRegisterChunk(new Int3(0, 0, 0));
+/// VoxelRef stationCell = station.GetCellRef(stationChunk, 0);
+/// VoxelRef shuttleCell = shuttle.GetCellRef(shuttleChunk, 0);
+/// PortalHandle portal = world.CreatePortal(stationCell, shuttleCell);
/// world.Tick(); // Activates the portal, then advances both simulations.
///
///
-public sealed partial class AtmosWorld : IDisposable
+public sealed partial class AtmosWorld : IChunkWorld, IDisposable
{
private readonly ExplicitAtmosTransportSolver _explicitTransport = new();
private readonly SortedSet _freeLinkSlots = [];
private readonly SortedSet _freeSimulationSlots = [];
private readonly List _linkSlots = [];
private readonly Dictionary> _linkSlotsByChunk = [];
- private readonly Dictionary> _linkSlotsBySimulation = [];
+ private readonly Dictionary> _linkSlotsBySimulation = [];
private readonly SortedSet _pendingLinkSlots = [];
private readonly HashSet _reservedEdges = [];
private readonly List _simulationSlots = [];
@@ -131,6 +133,13 @@ public IReadOnlyList Simulations
}
}
+ ///
+ /// Gets a detached, stable-ID-ordered list of registered chunk simulations.
+ ///
+ /// The world has been disposed.
+ [PublicAPI]
+ public IReadOnlyList ChunkSimulations => Simulations;
+
///
/// Gets a monotonic revision that changes whenever simulation membership changes.
///
@@ -305,7 +314,7 @@ public IReadOnlyList GetLinkSets()
/// Canonical definitions ordered by their complete stable endpoint addresses.
/// The world has been disposed.
[PublicAPI]
- public IReadOnlyList GetActiveLinks()
+ public IReadOnlyList> GetActiveLinks()
{
lock (Gate)
{
@@ -330,9 +339,9 @@ public IReadOnlyList GetActiveLinks()
/// A chunk dimension or combined voxel count is invalid.
[PublicAPI]
public AtmosSimulation CreateSimulation(
- int chunkWidth = AtmosChunkConstants.DefaultWidth,
- int chunkHeight = AtmosChunkConstants.DefaultHeight,
- int chunkDepth = AtmosChunkConstants.DefaultDepth)
+ int chunkWidth = ChunkConstants.DefaultWidth,
+ int chunkHeight = ChunkConstants.DefaultHeight,
+ int chunkDepth = ChunkConstants.DefaultDepth)
{
lock (Gate)
{
@@ -403,13 +412,13 @@ public bool SetAtmosConfig(AtmosConfig config)
///
/// The world has been disposed.
[PublicAPI]
- public ExplicitLinkSetHandle CreateLinks(ReadOnlySpan links)
+ public ExplicitLinkSetHandle CreateLinks(ReadOnlySpan> links)
{
return CreateLinksCore(links, ExplicitLinkSetKind.Arbitrary);
}
private ExplicitLinkSetHandle CreateLinksCore(
- ReadOnlySpan links,
+ ReadOnlySpan> links,
ExplicitLinkSetKind kind)
{
lock (Gate)
@@ -499,14 +508,14 @@ public void DestroyLinks(ExplicitLinkSetHandle handle)
///
/// The world has been disposed.
[PublicAPI]
- public AtmosPortalHandle CreatePortal(
- AtmosCellRef first,
- AtmosCellRef second,
- ExplicitLinkFlags flags = ExplicitLinkFlags.All)
+ public PortalHandle CreatePortal(
+ VoxelRef first,
+ VoxelRef second,
+ AtmosLinkFlags flags = AtmosLinkFlags.All)
{
- ExplicitLinkDefinition definition = new(first, second, flags);
- return new AtmosPortalHandle(
- CreateLinksCore(new ReadOnlySpan(in definition), ExplicitLinkSetKind.Portal));
+ ExplicitLinkDefinition definition = new(first, second, flags);
+ return new PortalHandle(
+ CreateLinksCore(new ReadOnlySpan>(in definition), ExplicitLinkSetKind.Portal));
}
///
@@ -517,7 +526,7 @@ public AtmosPortalHandle CreatePortal(
/// Removal of this portal is already pending.
/// The world has been disposed.
[PublicAPI]
- public void DestroyPortal(AtmosPortalHandle portal)
+ public void DestroyPortal(PortalHandle portal)
{
DestroyLinks(portal.Links);
}
@@ -535,9 +544,9 @@ public void DestroyPortal(AtmosPortalHandle portal)
///
/// The world has been disposed.
[PublicAPI]
- public AtmosDockHandle CreateDock(ReadOnlySpan surfaceLinks)
+ public DockHandle CreateDock(ReadOnlySpan> surfaceLinks)
{
- return new AtmosDockHandle(CreateLinksCore(surfaceLinks, ExplicitLinkSetKind.Dock));
+ return new DockHandle(CreateLinksCore(surfaceLinks, ExplicitLinkSetKind.Dock));
}
///
@@ -548,7 +557,7 @@ public AtmosDockHandle CreateDock(ReadOnlySpan surfaceLi
/// Removal of this dock is already pending.
/// The world has been disposed.
[PublicAPI]
- public void DestroyDock(AtmosDockHandle dock)
+ public void DestroyDock(DockHandle dock)
{
DestroyLinks(dock.Links);
}
@@ -561,17 +570,17 @@ public void DestroyDock(AtmosDockHandle dock)
/// The handle is invalid or stale.
/// The world has been disposed.
[PublicAPI]
- public IReadOnlyList GetLinks(ExplicitLinkSetHandle handle)
+ public IReadOnlyList> GetLinks(ExplicitLinkSetHandle handle)
{
lock (Gate)
{
ThrowIfDisposed();
var slot = GetLinkSlot(handle);
- var result = new ExplicitLinkDefinition[slot.Edges.Length];
+ var result = new ExplicitLinkDefinition[slot.Edges.Length];
for (int index = 0; index < result.Length; index++)
{
var edge = slot.Edges[index];
- result[index] = new ExplicitLinkDefinition(edge.First, edge.Second, edge.Flags);
+ result[index] = new ExplicitLinkDefinition(edge.First, edge.Second, edge.Flags);
}
return result;
@@ -588,7 +597,7 @@ public IReadOnlyList GetLinks(ExplicitLinkSetHandle hand
/// is .
/// The world has been disposed.
[PublicAPI]
- public bool HasExplicitLinks(AtmosSimulation simulation, AtmosChunkHandle chunk)
+ public bool HasExplicitLinks(AtmosSimulation simulation, ChunkHandle chunk)
{
ArgumentNullException.ThrowIfNull(simulation);
lock (Gate)
@@ -633,7 +642,7 @@ public AtmosWorldCheckpoint CaptureCheckpoint()
for (int index = 0; index < _linkSlots.Count; index++)
{
var slot = _linkSlots[index];
- ExplicitLinkDefinition[] definitions = ToDefinitions(slot.Edges);
+ ExplicitLinkDefinition[] definitions = ToDefinitions(slot.Edges);
slotCheckpoints[index] = new AtmosWorldLinkSlotCheckpoint(
slot.Generation,
(byte)slot.State,
@@ -851,9 +860,9 @@ public bool DestroySimulation(AtmosSimulation simulation)
///
/// Registers a fully initialized simulation and returns its stable world identifier.
///
- internal AtmosSimulationId RegisterSimulation(
+ internal SimulationId RegisterSimulation(
AtmosSimulation simulation,
- AtmosSimulationId? requestedRegistration = null)
+ SimulationId? requestedRegistration = null)
{
lock (Gate)
{
@@ -899,7 +908,7 @@ internal AtmosSimulationId RegisterSimulation(
_simulationSlots[index] = slot;
RebuildOrderedSimulations();
IncrementSimulationCollectionRevision();
- return new AtmosSimulationId(index, slot.Generation);
+ return new SimulationId(index, slot.Generation);
}
}
@@ -937,7 +946,7 @@ private bool UnregisterSimulationCore(AtmosSimulation simulation, bool record)
///
/// Invalidates link sets that reference a chunk removed from its owning simulation.
///
- internal void InvalidateLinksForChunk(AtmosSimulation simulation, AtmosChunkHandle chunk)
+ internal void InvalidateLinksForChunk(AtmosSimulation simulation, ChunkHandle chunk)
{
lock (Gate)
{
@@ -1126,8 +1135,8 @@ private void ValidateCheckpoint(AtmosWorldCheckpoint checkpoint)
nameof(checkpoint));
}
- var validCells = new Dictionary>();
- AtmosSimulationId? previousSimulation = null;
+ var validCells = new Dictionary>();
+ SimulationId? previousSimulation = null;
foreach (var saved in checkpoint.Simulations)
{
if (!saved.Simulation.IsValid ||
@@ -1409,7 +1418,7 @@ private void WakeEndpoints(ExplicitAtmosEdge[] edges)
}
}
- private void WakeEndpoint(AtmosCellRef cell)
+ private void WakeEndpoint(VoxelRef cell)
{
var simulation = TryGetSimulationCore(cell.Simulation);
if (simulation != null &&
@@ -1511,7 +1520,7 @@ private void RemoveLinkSlotIndexes(int slotIndex, ExplicitAtmosEdge[] edges)
}
}
- private void IndexLinkSlot(int slotIndex, AtmosCellRef cell)
+ private void IndexLinkSlot(int slotIndex, VoxelRef cell)
{
AddIndex(_linkSlotsBySimulation, cell.Simulation, slotIndex);
AddIndex(_linkSlotsByChunk, new AtmosChunkKey(cell.Simulation, cell.Chunk.Position), slotIndex);
@@ -1543,7 +1552,7 @@ private static void UnindexLinkSlot(
index.Remove(key);
}
- private void InvalidateLinksForSimulation(AtmosSimulationId simulationId)
+ private void InvalidateLinksForSimulation(SimulationId simulationId)
{
if (!_linkSlotsBySimulation.TryGetValue(simulationId, out HashSet? slots))
return;
@@ -1553,7 +1562,7 @@ private void InvalidateLinksForSimulation(AtmosSimulationId simulationId)
DestroyLinkSlot(slotIndex);
}
- private void ValidateCell(AtmosCellRef cell, string parameterName)
+ private void ValidateCell(VoxelRef cell, string parameterName)
{
var simulation = TryGetSimulationCore(cell.Simulation);
if (simulation == null ||
@@ -1566,12 +1575,12 @@ private void ValidateCell(AtmosCellRef cell, string parameterName)
}
}
- private static void ValidateFlags(ExplicitLinkFlags flags, string parameterName)
+ private static void ValidateFlags(AtmosLinkFlags flags, string parameterName)
{
// Bits outside GasTransport|ThermalTransport are reserved for host-defined capabilities: a link can carry
- // them so a host-registered solver's AtmosExplicitLinkSelector can pick it out, without engaging Numos'
+ // them so a host-registered solver's ExplicitLinkSelector can pick it out, without engaging Numos'
// built-in transport stages, which only ever look at the bits they know about.
- if (flags == ExplicitLinkFlags.None)
+ if (flags == AtmosLinkFlags.None)
throw new ArgumentException("An explicit link must select at least one transport capability.", parameterName);
}
@@ -1602,7 +1611,7 @@ private bool ContainsSimulation(AtmosSimulation simulation)
///
/// The world has been disposed.
[PublicAPI]
- public bool TryGetSimulation(AtmosSimulationId id, out AtmosSimulation? simulation)
+ public bool TryGetSimulation(SimulationId id, out AtmosSimulation? simulation)
{
lock (Gate)
{
@@ -1611,8 +1620,15 @@ public bool TryGetSimulation(AtmosSimulationId id, out AtmosSimulation? simulati
return simulation != null;
}
}
+
+ public bool TryGetChunkSimulation(SimulationId id, out IChunkSimulation? simulation)
+ {
+ var success = TryGetSimulation(id, out var sim);
+ simulation = sim;
+ return success;
+ }
- private AtmosSimulation? TryGetSimulationCore(AtmosSimulationId id)
+ private AtmosSimulation? TryGetSimulationCore(SimulationId id)
{
if (!id.IsValid || (uint)id.Index >= (uint)_simulationSlots.Count)
return null;
@@ -1621,7 +1637,12 @@ public bool TryGetSimulation(AtmosSimulationId id, out AtmosSimulation? simulati
return slot.Generation == id.Generation ? slot.Simulation : null;
}
- internal bool TryResolveCell(AtmosCellRef cell, out AtmosSimulation? simulation)
+ public bool TryResolveCell(VoxelRef cell)
+ {
+ return TryResolveCell(cell, out _);
+ }
+
+ internal bool TryResolveCell(VoxelRef cell, out AtmosSimulation? simulation)
{
lock (Gate)
{
@@ -1634,7 +1655,7 @@ internal bool TryResolveCell(AtmosCellRef cell, out AtmosSimulation? simulation)
}
}
- internal IReadOnlyList GetActiveLinksCore()
+ internal IReadOnlyList> GetActiveLinksCore()
{
return ToDefinitions(_activeEdges);
}
@@ -1653,7 +1674,7 @@ private AtmosWorldLinkSetSnapshot[] CreateLinkSetSnapshots()
if (slot.State == LinkSetState.Free)
continue;
- ExplicitLinkDefinition[] definitions = ToDefinitions(slot.Edges);
+ ExplicitLinkDefinition[] definitions = ToDefinitions(slot.Edges);
result.Add(
new AtmosWorldLinkSetSnapshot(
new ExplicitLinkSetHandle(index, slot.Generation),
@@ -1665,7 +1686,7 @@ private AtmosWorldLinkSetSnapshot[] CreateLinkSetSnapshots()
return result.ToArray();
}
- private bool AreImplicitNeighbors(AtmosCellRef first, AtmosCellRef second)
+ private bool AreImplicitNeighbors(VoxelRef first, VoxelRef second)
{
if (first.Simulation != second.Simulation)
return false;
@@ -1727,7 +1748,7 @@ private void IncrementLinkSetCollectionRevision()
_linkSetCollectionRevision = checked(_linkSetCollectionRevision + 1);
}
- private static void AddChunkEdgeCount(Dictionary counts, AtmosCellRef cell)
+ private static void AddChunkEdgeCount(Dictionary counts, VoxelRef cell)
{
var key = new AtmosChunkKey(cell.Simulation, cell.Chunk.Position);
counts.TryGetValue(key, out int count);
@@ -1735,8 +1756,8 @@ private static void AddChunkEdgeCount(Dictionary counts, Atm
}
private static bool CheckpointContainsCell(
- Dictionary> validCells,
- AtmosCellRef cell)
+ Dictionary> validCells,
+ VoxelRef cell)
{
return validCells.TryGetValue(cell.Simulation, out Dictionary? chunks) &&
chunks.TryGetValue(cell.Chunk.Position, out int voxelCount) &&
@@ -1755,13 +1776,13 @@ private static bool IsSolverActive(LinkSetState state, bool applyPendingChanges)
: IsSolverActive(state);
}
- private static ExplicitLinkDefinition[] ToDefinitions(ExplicitAtmosEdge[] edges)
+ private static ExplicitLinkDefinition[] ToDefinitions(ExplicitAtmosEdge[] edges)
{
- var result = new ExplicitLinkDefinition[edges.Length];
+ var result = new ExplicitLinkDefinition[edges.Length];
for (int index = 0; index < result.Length; index++)
{
var edge = edges[index];
- result[index] = new ExplicitLinkDefinition(edge.First, edge.Second, edge.Flags);
+ result[index] = new ExplicitLinkDefinition(edge.First, edge.Second, edge.Flags);
}
return result;
@@ -1821,14 +1842,14 @@ private void ThrowIfDisposed()
private readonly record struct SimulationSlot(AtmosSimulation? Simulation, uint Generation);
- private readonly record struct AtmosChunkKey(AtmosSimulationId Simulation, Int3 ChunkPosition);
+ private readonly record struct AtmosChunkKey(SimulationId Simulation, Int3 ChunkPosition);
- private readonly record struct ExplicitEdgeKey(AtmosCellRef First, AtmosCellRef Second);
+ private readonly record struct ExplicitEdgeKey(VoxelRef First, VoxelRef Second);
private readonly record struct ExplicitAtmosEdge(
- AtmosCellRef First,
- AtmosCellRef Second,
- ExplicitLinkFlags Flags);
+ VoxelRef First,
+ VoxelRef Second,
+ AtmosLinkFlags Flags);
private sealed class ExplicitAtmosEdgeComparer : IComparer
{
diff --git a/src/Numos.API/AtmosWorldCheckpoint.cs b/src/Numos.API/AtmosWorldCheckpoint.cs
index a29c895..a19ecc0 100644
--- a/src/Numos.API/AtmosWorldCheckpoint.cs
+++ b/src/Numos.API/AtmosWorldCheckpoint.cs
@@ -1,3 +1,4 @@
+using Numos.Chunks.Topology;
using Numos.CoreSim;
using Numos.CoreSim.Replay;
@@ -110,7 +111,7 @@ public readonly record struct AtmosWorldSolverCheckpoint(
/// The stable simulation registration.
/// The simulation-owned chunk and solver continuation state.
public sealed record AtmosWorldSimulationCheckpoint(
- AtmosSimulationId Simulation,
+ SimulationId Simulation,
AtmosSimulationCheckpoint Checkpoint);
///
@@ -145,10 +146,10 @@ public sealed record AtmosWorldLinkSetCheckpoint(
ExplicitLinkSetHandle Handle,
ExplicitLinkSetKind Kind,
AtmosWorldLinkSetState State,
- IReadOnlyList Links);
+ IReadOnlyList> Links);
internal readonly record struct AtmosWorldLinkSlotCheckpoint(
uint Generation,
byte State,
ExplicitLinkSetKind Kind,
- ExplicitLinkDefinition[] Links);
\ No newline at end of file
+ ExplicitLinkDefinition[] Links);
diff --git a/src/Numos.API/AtmosWorldRecording.cs b/src/Numos.API/AtmosWorldRecording.cs
index f2980f7..6ab48fe 100644
--- a/src/Numos.API/AtmosWorldRecording.cs
+++ b/src/Numos.API/AtmosWorldRecording.cs
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
+using Numos.Chunks.Topology;
using Numos.CoreSim;
using Numos.CoreSim.Replay;
using Numos.Maths;
@@ -63,7 +64,7 @@ public abstract record AtmosWorldOperation
/// The exact simulation registration that receives the operation.
/// The immutable component operation payload.
public sealed record AtmosWorldSimulationOperation(
- AtmosSimulationId Simulation,
+ SimulationId Simulation,
AtmosOperation Operation) : AtmosWorldOperation
{
///
@@ -86,7 +87,7 @@ public sealed record SetAtmosWorldConfigOperation(AtmosConfigSnapshot Config) :
/// The identifier issued by the simulation registry.
/// The fixed dimensions used by chunks in the new simulation.
public sealed record CreateAtmosSimulationOperation(
- AtmosSimulationId Simulation,
+ SimulationId Simulation,
Int3 ChunkDimensions) : AtmosWorldOperation
{
///
@@ -97,7 +98,7 @@ public sealed record CreateAtmosSimulationOperation(
/// Records destruction of one simulation and the automatic invalidation of incident topology.
///
/// The registration that was removed.
-public sealed record DestroyAtmosSimulationOperation(AtmosSimulationId Simulation) : AtmosWorldOperation
+public sealed record DestroyAtmosSimulationOperation(SimulationId Simulation) : AtmosWorldOperation
{
///
public override AtmosWorldOperationCode Code => AtmosWorldOperationCode.DestroySimulation;
@@ -118,12 +119,12 @@ public sealed record CreateAtmosLinkSetOperation : AtmosWorldOperation
public CreateAtmosLinkSetOperation(
ExplicitLinkSetHandle handle,
ExplicitLinkSetKind kind,
- IEnumerable links)
+ IEnumerable> links)
{
ArgumentNullException.ThrowIfNull(links);
Handle = handle;
Kind = kind;
- Links = new ReadOnlyCollection(links.ToArray());
+ Links = new ReadOnlyCollection>(links.ToArray());
}
///
@@ -142,7 +143,7 @@ public CreateAtmosLinkSetOperation(
///
/// Gets immutable canonical links owned by the set.
///
- public IReadOnlyList Links { get; }
+ public IReadOnlyList> Links { get; }
}
///
diff --git a/src/Numos.API/AtmosWorldSolver.cs b/src/Numos.API/AtmosWorldSolver.cs
index 95a1251..f62d3d7 100644
--- a/src/Numos.API/AtmosWorldSolver.cs
+++ b/src/Numos.API/AtmosWorldSolver.cs
@@ -1,3 +1,5 @@
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim;
namespace Numos.API;
@@ -8,17 +10,6 @@ namespace Numos.API;
/// The tick-wide simulations, configuration, and compiled neighborhood view.
public delegate void AtmosWorldSolver(AtmosWorldSolverContext context);
-///
-/// Selects an explicit link while Numos compiles a custom solver's neighborhood view.
-///
-/// The canonical, value-only link being compiled.
-/// when the link participates in the solver.
-///
-/// Numos calls selectors only when topology or solver registration changes. Selectors must be deterministic and must
-/// not mutate their world.
-///
-public delegate bool AtmosExplicitLinkSelector(AtmosExplicitLinkInfo link);
-
///
/// Identifies whether a world solver stage is provided by Numos or its host.
///
@@ -35,67 +26,6 @@ public enum AtmosWorldSolverKind : byte
Custom
}
-///
-/// Describes one canonical explicit link to a topology selector.
-///
-/// The canonical first endpoint.
-/// The canonical second endpoint.
-/// The interactions enabled by the link.
-public readonly record struct AtmosExplicitLinkInfo(
- AtmosCellRef First,
- AtmosCellRef Second,
- ExplicitLinkFlags Flags);
-
-///
-/// Configures the neighborhood compiled for one custom world solver.
-///
-/// A stable identifier describing the selection policy for checkpoint compatibility.
-/// Whether ordinary Cartesian neighbors participate.
-/// The selector evaluated for active explicit links.
-///
-/// Set to for a solver that only interacts with
-/// portals, docks, or other explicit links: it keeps the compiled view limited to the sparse explicit edge set
-/// instead of re-deriving all six ordinary neighbors of every voxel in every chunk. Reach for
-/// only when the same stage genuinely needs to traverse ordinary walls too, such as fire
-/// or sound propagating through both open doorways and portals.
-///
-public sealed class AtmosNeighborSelection(
- string key,
- bool includeCartesian,
- AtmosExplicitLinkSelector explicitLinks)
-{
- ///
- /// Gets the stable compatibility key for this selection policy.
- ///
- ///
- /// This string is checkpointed alongside the solver's registration and folded into world state hashing, so a
- /// restored checkpoint compiles topology using the key it was captured with. Give a selection a new key when
- /// its changes what it matches; reusing a key for a semantically
- /// different selection lets a restored checkpoint silently compile the wrong edges for it.
- ///
- public string Key { get; } = string.IsNullOrWhiteSpace(key)
- ? throw new ArgumentException("A neighbor selection key cannot be empty.", nameof(key))
- : key;
-
- ///
- /// Gets whether ordinary Cartesian neighbors participate.
- ///
- public bool IncludeCartesian { get; } = includeCartesian;
-
- internal AtmosExplicitLinkSelector ExplicitLinks { get; } =
- explicitLinks ?? throw new ArgumentNullException(nameof(explicitLinks));
-
- ///
- /// Creates a selection that includes Cartesian neighbors and every active explicit link.
- ///
- /// The stable compatibility key for the consuming solver.
- /// A selection covering the complete world topology.
- public static AtmosNeighborSelection All(string key)
- {
- return new AtmosNeighborSelection(key, true, static _ => true);
- }
-}
-
///
/// Tick-scoped inputs supplied to a world solver callback.
///
@@ -108,7 +38,7 @@ public sealed class AtmosWorldSolverContext
internal AtmosWorldSolverContext(
AtmosWorld world,
IReadOnlyList simulations,
- AtmosWorldNeighborTopology topology)
+ WorldNeighborTopology topology)
{
World = world;
Simulations = simulations;
@@ -144,7 +74,7 @@ internal AtmosWorldSolverContext(
/// neighbors, no explicit edges. Use whenever
/// the callback needs this property.
///
- public AtmosWorldNeighborTopology Topology { get; }
+ public WorldNeighborTopology Topology { get; }
}
///
@@ -158,4 +88,4 @@ public readonly record struct AtmosWorldSolverStep(
string Name,
AtmosWorldSolverKind Kind,
bool IsEnabled,
- string? NeighborSelectionKey);
\ No newline at end of file
+ string? NeighborSelectionKey);
diff --git a/src/Numos.API/AtmosWorldSolverPipeline.cs b/src/Numos.API/AtmosWorldSolverPipeline.cs
index 454cfaa..168e75e 100644
--- a/src/Numos.API/AtmosWorldSolverPipeline.cs
+++ b/src/Numos.API/AtmosWorldSolverPipeline.cs
@@ -1,4 +1,6 @@
using JetBrains.Annotations;
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim.Replay;
using Numos.CoreSim.Solvers;
@@ -70,7 +72,7 @@ public void Register(string name, AtmosWorldSolver solver)
[PublicAPI]
public void RegisterNeighborSolver(
string name,
- AtmosNeighborSelection selection,
+ VoxelNeighborSelection selection,
AtmosWorldSolver solver)
{
ArgumentNullException.ThrowIfNull(selection);
@@ -100,7 +102,7 @@ public void RegisterBefore(string existingName, string name, AtmosWorldSolver so
public void RegisterNeighborSolverBefore(
string existingName,
string name,
- AtmosNeighborSelection selection,
+ VoxelNeighborSelection selection,
AtmosWorldSolver solver)
{
ArgumentNullException.ThrowIfNull(selection);
@@ -130,7 +132,7 @@ public void RegisterAfter(string existingName, string name, AtmosWorldSolver sol
public void RegisterNeighborSolverAfter(
string existingName,
string name,
- AtmosNeighborSelection selection,
+ VoxelNeighborSelection selection,
AtmosWorldSolver solver)
{
ArgumentNullException.ThrowIfNull(selection);
@@ -215,9 +217,9 @@ internal void Execute(WorldSolverRegistration[] steps, AtmosWorldExecutionContex
step.Execute(context);
}
- internal void RecompileTopology(IReadOnlyList links)
+ internal void RecompileTopology(IReadOnlyList> links)
{
- var compiled = new AtmosWorldNeighborTopology[_steps.Count];
+ var compiled = new WorldNeighborTopology[_steps.Count];
for (int index = 0; index < _steps.Count; index++)
compiled[index] = _steps[index].BuildTopology(_world, links);
@@ -233,7 +235,7 @@ internal AtmosSolverCheckpoint[] CaptureCheckpointSteps()
private void RegisterCore(
string name,
AtmosWorldSolver solver,
- AtmosNeighborSelection? selection,
+ VoxelNeighborSelection? selection,
int insertionIndex)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
@@ -257,7 +259,7 @@ private void RegisterRelative(
string existingName,
string name,
AtmosWorldSolver solver,
- AtmosNeighborSelection? selection,
+ VoxelNeighborSelection? selection,
bool before)
{
ArgumentException.ThrowIfNullOrWhiteSpace(existingName);
@@ -342,14 +344,14 @@ internal sealed class WorldSolverRegistration
{
private readonly Action? _builtInSolver;
private readonly AtmosWorldSolver? _customSolver;
- private AtmosWorldNeighborTopology _topology = AtmosWorldNeighborTopology.Empty;
+ private WorldNeighborTopology _topology = WorldNeighborTopology.Empty;
private WorldSolverRegistration(
string name,
AtmosWorldSolverKind kind,
Action? builtInSolver,
AtmosWorldSolver? customSolver,
- AtmosNeighborSelection? selection)
+ VoxelNeighborSelection? selection)
{
Name = name;
Kind = kind;
@@ -360,7 +362,7 @@ private WorldSolverRegistration(
internal string Name { get; }
internal AtmosWorldSolverKind Kind { get; }
- internal AtmosNeighborSelection? Selection { get; }
+ internal VoxelNeighborSelection? Selection { get; }
internal bool IsEnabled { get; set; } = true;
internal static WorldSolverRegistration CreateBuiltIn(
@@ -373,7 +375,7 @@ internal static WorldSolverRegistration CreateBuiltIn(
internal static WorldSolverRegistration CreateCustom(
string name,
AtmosWorldSolver solver,
- AtmosNeighborSelection? selection)
+ VoxelNeighborSelection? selection)
{
return new WorldSolverRegistration(name, AtmosWorldSolverKind.Custom, null, solver, selection);
}
@@ -389,16 +391,16 @@ internal void Execute(AtmosWorldExecutionContext context)
_builtInSolver!(context);
}
- internal AtmosWorldNeighborTopology BuildTopology(
+ internal WorldNeighborTopology BuildTopology(
AtmosWorld world,
- IReadOnlyList links)
+ IReadOnlyList> links)
{
return Selection == null
- ? AtmosWorldNeighborTopology.EmptyFor(world)
- : AtmosWorldNeighborTopology.Compile(world, Selection, links);
+ ? WorldNeighborTopology.EmptyFor(world)
+ : WorldNeighborTopology.Compile(world, Selection, links);
}
- internal void InstallTopology(AtmosWorldNeighborTopology topology)
+ internal void InstallTopology(WorldNeighborTopology topology)
{
_topology = topology;
}
diff --git a/src/Numos.API/ExplicitAtmosTopology.cs b/src/Numos.API/ExplicitAtmosTopology.cs
index b1c8d0b..854ffd5 100644
--- a/src/Numos.API/ExplicitAtmosTopology.cs
+++ b/src/Numos.API/ExplicitAtmosTopology.cs
@@ -1,82 +1,7 @@
-using Numos.Maths;
+using Numos.Chunks.Topology;
namespace Numos.API;
-///
-/// Identifies one simulation registration within an .
-///
-/// The stable registry slot.
-/// The slot generation that prevents a stale identifier from naming a replacement simulation.
-///
-/// The default value is invalid. Identifiers are meaningful only within the world that issued them.
-///
-public readonly record struct AtmosSimulationId(int Index, uint Generation) : IComparable
-{
- ///
- /// Gets whether this value could have been issued by a world registry.
- ///
- public bool IsValid => Index >= 0 && Generation != 0;
-
- ///
- /// Compares stable slot and generation values without depending on object identity or hash iteration.
- ///
- /// The identifier to compare.
- ///
- /// A negative value when this identifier sorts first, zero when the identifiers match, or a positive value when
- /// sorts first.
- ///
- public int CompareTo(AtmosSimulationId other)
- {
- int index = Index.CompareTo(other.Index);
- return index != 0 ? index : Generation.CompareTo(other.Generation);
- }
-}
-
-///
-/// Stably identifies one voxel within an .
-///
-/// The owning simulation registration.
-/// The owning chunk.
-/// The flat local voxel index within the chunk.
-///
-/// The value contains no object references and is suitable for deterministic ordering, snapshots, and replay data.
-/// It does not prove that the simulation, chunk, or voxel still exists; mutation methods validate the complete
-/// address against the receiving world.
-///
-public readonly record struct AtmosCellRef(
- AtmosSimulationId Simulation,
- AtmosChunkHandle Chunk,
- ushort LocalVoxelIndex) : IComparable
-{
- ///
- /// Compares the complete stable address in simulation, chunk-coordinate, and voxel order.
- ///
- /// The cell reference to compare.
- ///
- /// A negative value when this address sorts first, zero when the addresses match, or a positive value when
- /// sorts first.
- ///
- public int CompareTo(AtmosCellRef other)
- {
- int comparison = Simulation.CompareTo(other.Simulation);
- if (comparison != 0)
- return comparison;
-
- comparison = CompareChunkPositions(Chunk.Position, other.Chunk.Position);
- return comparison != 0 ? comparison : LocalVoxelIndex.CompareTo(other.LocalVoxelIndex);
- }
-
- private static int CompareChunkPositions(Int3 left, Int3 right)
- {
- int comparison = left.X.CompareTo(right.X);
- if (comparison != 0)
- return comparison;
-
- comparison = left.Y.CompareTo(right.Y);
- return comparison != 0 ? comparison : left.Z.CompareTo(right.Z);
- }
-}
-
///
/// Selects the physical interactions allowed to cross an explicit atmosphere link.
///
@@ -84,14 +9,14 @@ private static int CompareChunkPositions(Int3 left, Int3 right)
/// and are the only bits Numos' own built-in
/// /
/// stages act on. The remaining bits of this -backed flag set are reserved for hosts: a
-/// link can carry a host-defined bit (for example (ExplicitLinkFlags)(1 << 2)) purely so a
-/// host-registered can pick it out. Numos' built-in stages ignore bits
+/// link can carry a host-defined bit (for example (AtmosLinkFlags)(1 << 2)) purely so a
+/// host-registered can pick it out. Numos' built-in stages ignore bits
/// they do not recognize, so a link can mix built-in capabilities with host-defined ones, or use only
/// host-defined ones to opt out of default physics entirely while still participating in checkpointing,
/// recording, and topology enumeration like any other link.
///
[Flags]
-public enum ExplicitLinkFlags : byte
+public enum AtmosLinkFlags : byte
{
///
/// Allows no solver interaction and is therefore invalid for a registered link.
@@ -114,70 +39,6 @@ public enum ExplicitLinkFlags : byte
All = GasTransport | ThermalTransport
}
-///
-/// Records which world API created an explicit link set.
-///
-///
-/// The kind supports inspection, replay, and tooling. Every kind uses the same sparse solver representation, so it
-/// does not change transport physics.
-///
-public enum ExplicitLinkSetKind : byte
-{
- ///
- /// A generic batch created through .
- ///
- Arbitrary,
-
- ///
- /// A one-edge portal created through .
- ///
- Portal,
-
- ///
- /// A surface batch created through .
- ///
- Dock
-}
-
-///
-/// Defines one undirected sparse atmospheric adjacency.
-///
-/// One endpoint. Registration canonicalizes endpoint orientation.
-/// The other endpoint.
-/// The interactions allowed across the adjacency.
-public readonly record struct ExplicitLinkDefinition(
- AtmosCellRef First,
- AtmosCellRef Second,
- ExplicitLinkFlags Flags = ExplicitLinkFlags.All);
-
-///
-/// Identifies a batch of explicit links owned and removed as one lifecycle unit.
-///
-/// The link-set storage slot.
-/// The generation used to detect stale handles after slot reuse.
-///
-/// The default value is invalid.
-///
-public readonly record struct ExplicitLinkSetHandle(int Index, uint Generation)
-{
- ///
- /// Gets whether this value could identify a link set.
- ///
- public bool IsValid => Index >= 0 && Generation != 0;
-}
-
-///
-/// Identifies a one-edge portal in the world's explicit topology.
-///
-/// The underlying generic link set.
-public readonly record struct AtmosPortalHandle(ExplicitLinkSetHandle Links);
-
-///
-/// Identifies a dock surface in the world's explicit topology.
-///
-/// The underlying generic link set.
-public readonly record struct AtmosDockHandle(ExplicitLinkSetHandle Links);
-
///
/// Captures a detached inspection view of one current or pending explicit link set.
///
@@ -193,4 +54,4 @@ public sealed record AtmosWorldLinkSetSnapshot(
ExplicitLinkSetHandle Handle,
ExplicitLinkSetKind Kind,
AtmosWorldLinkSetState State,
- IReadOnlyList Links);
\ No newline at end of file
+ IReadOnlyList> Links);
diff --git a/src/Numos.Chunks/Chunk.cs b/src/Numos.Chunks/Chunk.cs
new file mode 100644
index 0000000..f79a964
--- /dev/null
+++ b/src/Numos.Chunks/Chunk.cs
@@ -0,0 +1,167 @@
+using JetBrains.Annotations;
+using Numos.Collections;
+using Numos.Maths;
+
+namespace Numos.Chunks;
+
+///
+/// Represents the simulation state for a fixed-size voxel chunk.
+///
+///
+/// Chunk-owned per-voxel data supports both flat-index and coordinate access.
+/// Use and when converting indices
+/// for scalar-indexed storage.
+///
+public abstract class Chunk
+{
+ ///
+ /// The number of voxels along the x-axis.
+ ///
+ public int Width;
+
+ ///
+ /// The number of voxels along the y-axis.
+ ///
+ public int Height;
+
+ ///
+ /// The number of voxels along the z-axis.
+ ///
+ public int Depth;
+
+ ///
+ /// The position of this chunk in the chunk grid.
+ ///
+ public Int3 GridPosition;
+
+ ///
+ /// Total number of voxels in this chunk, equal to Width * Height * Depth.
+ ///
+ public int VoxelCount;
+
+ ///
+ /// The number of voxels along each axis.
+ ///
+ public Int3 Dimensions => new(Width, Height, Depth);
+
+ ///
+ /// A process-local index assigned when this chunk is registered.
+ ///
+ ///
+ /// Lets hot per-tick solver lookups (e.g. boundary flow's cross-chunk injection buffer) use array
+ /// indexing instead of a dictionary keyed on . Unique only among currently
+ /// registered chunks, and not part of the simulation's observable state.
+ /// Do not use it for anything but indexing a solver-owned lookup table.
+ /// Ids are recycled after a chunk is unregistered, so a DenseId-keyed lookup table must track which
+ /// chunk currently owns each slot and validate that before trusting stale contents.
+ ///
+ public int DenseId;
+
+ ///
+ /// Converts local voxel coordinates to an index into the chunk's flat arrays.
+ ///
+ /// The local x coordinate, from zero through minus one.
+ /// The local y coordinate, from zero through minus one.
+ /// The local z coordinate, from zero through minus one.
+ /// The flat voxel index.
+ [PublicAPI]
+ public ushort GetIndex(int x, int y, int z)
+ {
+ return GetIndex(new Int3(x, y, z));
+ }
+
+ ///
+ [PublicAPI]
+ public ushort GetIndex(Int3 vec)
+ {
+ return (ushort)FlatArrayHelpers.GetIndex(vec, Dimensions);
+ }
+
+ ///
+ [PublicAPI]
+ public ushort GetIndexUnsafe(Int3 vec)
+ {
+ return (ushort)FlatArrayHelpers.GetIndexUnsafe(vec, Dimensions);
+ }
+
+ ///
+ /// Converts a flat voxel index to local x, y, and z coordinates.
+ ///
+ /// The flat voxel index.
+ /// The local coordinates as an (x, y, z) tuple.
+ [PublicAPI]
+ public (int x, int y, int z) GetXyz(ushort index)
+ {
+ var position = GetXyzInt3(index);
+ return (position.X, position.Y, position.Z);
+ }
+
+ ///
+ /// Converts a flat voxel index to local coordinates as an .
+ ///
+ /// The flat voxel index.
+ /// The local voxel coordinates.
+ [PublicAPI]
+ public Int3 GetXyzInt3(ushort index)
+ {
+ return FlatArrayHelpers.GetPosition(index, Dimensions);
+ }
+
+ ///
+ /// Method that is called before the chunk is released from the chunk map.
+ /// Here it must release all of its disposable resources.
+ ///
+ public virtual void Release() { }
+
+ ///
+ /// Ensures that a chunk's has specified dimensions.
+ ///
+ /// The target flat array.
+ /// Dimensions to ensure on the flat array.
+ /// Type parameter of the flat array.
+ protected void EnsureInitialized(ref FlatArray array, Int3 dimensions)
+ {
+ if (!array.IsInitialized || array.Length != VoxelCount)
+ array = new FlatArray(new T[VoxelCount], dimensions);
+ else if (array.Dimensions != dimensions)
+ array = array.Reshape(dimensions);
+ }
+
+ ///
+ /// Validates the specified dimensions and returns the total voxel count of the chunk.
+ ///
+ /// Width of the chunk.
+ /// Height of the chunk.
+ /// Depth of the chunk.
+ /// Total voxel count of the chunk.
+ ///
+ /// One of the dimensions is not in the supported size range.
+ ///
+ protected static int GetValidatedVoxelCount(int width, int height, int depth)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(width);
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(height);
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(depth);
+ if (width > ChunkConstants.MaximumVoxelCount ||
+ height > ChunkConstants.MaximumVoxelCount ||
+ depth > ChunkConstants.MaximumVoxelCount)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(width),
+ width,
+ $"No chunk dimension may exceed {ChunkConstants.MaximumVoxelCount}.");
+ }
+
+ long voxelCount = (long)width * height * depth;
+ if (voxelCount > ChunkConstants.MaximumVoxelCount)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(width),
+ width,
+ $"Chunk dimensions contain {voxelCount} voxels, but at most " +
+ $"{ChunkConstants.MaximumVoxelCount} are supported.");
+ }
+
+ return (int)voxelCount;
+ }
+}
diff --git a/src/Numos.Chunks/ChunkConstants.cs b/src/Numos.Chunks/ChunkConstants.cs
new file mode 100644
index 0000000..4465ab3
--- /dev/null
+++ b/src/Numos.Chunks/ChunkConstants.cs
@@ -0,0 +1,19 @@
+namespace Numos.Chunks;
+
+///
+/// Default constants for Numos chunks.
+///
+public static class ChunkConstants
+{
+ /// Default number of voxels along a chunk's x-axis.
+ public const int DefaultWidth = 16;
+
+ /// Default number of voxels along a chunk's y-axis.
+ public const int DefaultHeight = 16;
+
+ /// Default number of voxels along a chunk's z-axis.
+ public const int DefaultDepth = 16;
+
+ /// Maximum voxel count representable by the chunk's unsigned 16-bit flat indices.
+ public const int MaximumVoxelCount = ushort.MaxValue;
+}
diff --git a/src/Numos.Chunks/ChunkHandle.cs b/src/Numos.Chunks/ChunkHandle.cs
new file mode 100644
index 0000000..2ad524a
--- /dev/null
+++ b/src/Numos.Chunks/ChunkHandle.cs
@@ -0,0 +1,8 @@
+using Numos.Maths;
+
+namespace Numos.Chunks;
+
+///
+/// Identifies a chunk owned by a .
+///
+public readonly record struct ChunkHandle(Int3 Position);
diff --git a/src/Numos.Chunks/ChunkMap.cs b/src/Numos.Chunks/ChunkMap.cs
new file mode 100644
index 0000000..6503553
--- /dev/null
+++ b/src/Numos.Chunks/ChunkMap.cs
@@ -0,0 +1,210 @@
+using System.Collections.Concurrent;
+using Numos.Maths;
+
+namespace Numos.Chunks;
+
+///
+/// A chunk map of voxel chunks where each chunk has fixed dimensions.
+///
+///
+/// Chunk type that also implements an
+/// interface to initialize newly created instances of this chunk.
+///
+public sealed class ChunkMap(int x, int y, int z) : IDisposable where T : Chunk, IChunkInitializer
+{
+ ///
+ /// Dimensions of every chunk stored in this chunk map.
+ ///
+ public readonly Int3 Dimensions = new(x, y, z);
+
+ private ConcurrentDictionary _chunkMap = new();
+
+ public long CollectionRevision;
+
+ public int NextDenseId;
+
+ // Reused before minting a new id, so DenseId stays bounded by peak concurrent chunk count rather
+ // than growing forever. See AtmosChunk.DenseId's remarks on why recycling ids is safe.
+ public readonly Stack FreeDenseIds = new();
+
+ ///
+ /// Gets the number of chunks currently registered with the kernel.
+ ///
+ /// The count includes both awake and sleeping chunks.
+ public int ChunkCount => _chunkMap.Count;
+
+ ///
+ /// Returns a detached list of the currently registered chunk-grid positions.
+ ///
+ public Int3[] GetChunkPositions() => _chunkMap.Keys.ToArray();
+
+ ///
+ /// Returns live chunk storage for the opt-in Dangerous API.
+ ///
+ public T GetChunkForDangerousAccess(Int3 position) => GetChunk(position);
+
+ public bool TryGetChunk(Int3 position, out T chunk)
+ {
+ return _chunkMap.TryGetValue(position, out chunk!);
+ }
+
+ public bool HasPosition(Int3 position) => _chunkMap.ContainsKey(position);
+
+ ///
+ /// Returns registered positions only when the chunk collection changed.
+ ///
+ public bool TryGetChunkPositions(
+ long knownRevision,
+ out long revision,
+ out Int3[] positions)
+ {
+ revision = CollectionRevision;
+ if (revision == knownRevision)
+ {
+ positions = [];
+ return false;
+ }
+
+ positions = _chunkMap.Keys.ToArray();
+ return true;
+ }
+
+ ///
+ /// Adds the specified chunk to the chunk map internal dictionary and reserves a .
+ ///
+ /// The chunk object to register.
+ /// Chunk dimensions didn't match the dimensions of the chunk map.
+ /// A chunk is already registered at the specified position.
+ public void RegisterChunk(T chunk)
+ {
+ if (chunk.Dimensions != Dimensions)
+ throw new ArgumentException("Chunk dimensions must match the simulation.", nameof(chunk));
+
+ if (!_chunkMap.TryAdd(chunk.GridPosition, chunk))
+ throw new InvalidOperationException($"A chunk is already registered at {chunk.GridPosition}.");
+
+ chunk.DenseId = FreeDenseIds.Count > 0 ? FreeDenseIds.Pop() : NextDenseId++;
+ CollectionRevision++;
+ }
+
+ ///
+ /// Removes and releases the chunk at a grid position.
+ ///
+ /// The chunk-grid position to remove.
+ /// if a chunk was removed; otherwise, .
+ public bool UnregisterChunk(Int3 position)
+ {
+ if (!_chunkMap.TryRemove(position, out var chunk))
+ return false;
+
+ chunk.Release();
+ FreeDenseIds.Push(chunk.DenseId);
+ CollectionRevision++;
+ return true;
+ }
+
+ ///
+ /// Creates, initializes, and registers a chunk owned by this kernel.
+ ///
+ /// The chunk's position in the chunk grid.
+ /// A chunk is already registered at .
+ public T CreateAndRegisterChunk(Int3 position)
+ {
+ if (_chunkMap.ContainsKey(position))
+ throw new InvalidOperationException($"A chunk is already registered at {position}.");
+
+ return T.CreateInitializeChunk(position, Dimensions.X, Dimensions.Y, Dimensions.Z);
+ }
+
+ ///
+ /// Gets a chunk from the chunk map at a specific position.
+ ///
+ /// Position to get the chunk at.
+ /// Chunk object stored at that position.
+ /// Specified position doesn't have a chunk.
+ public T GetChunk(Int3 position)
+ {
+ if (_chunkMap.TryGetValue(position, out var chunk))
+ return chunk;
+
+ throw new KeyNotFoundException($"No atmospheric chunk is registered at ({position.X}, {position.Y}, {position.Z}).");
+ }
+
+ ///
+ /// Returns an array of all chunks ordered by their position.
+ ///
+ public T[] OrderedChunks()
+ {
+ return _chunkMap.Values
+ .OrderBy(static chunk => chunk.GridPosition.X)
+ .ThenBy(static chunk => chunk.GridPosition.Y)
+ .ThenBy(static chunk => chunk.GridPosition.Z).ToArray();
+ }
+
+ ///
+ /// An unsafe method to access the chunk map dictionary directly.
+ /// Used by replays to set a new state.
+ ///
+ public ConcurrentDictionary UnsafeGetStorage()
+ {
+ return _chunkMap;
+ }
+
+ ///
+ /// An unsafe method to replace the chunk map dictionary.
+ /// Used by replays to set a new state.
+ ///
+ public void UnsafeReplaceStorage(ConcurrentDictionary replacement)
+ {
+ _chunkMap = replacement;
+ }
+
+ ///
+ /// Gets a voxel index in the array while validating its position.
+ ///
+ /// A chunk to get the voxel index from.
+ /// The X coordinate of the voxel.
+ /// The Y coordinate of the voxel.
+ /// The Z coordinate of the voxel.
+ /// A voxel index that is guaranteed to be valid for this chunk.
+ /// X, Y, or Z coordinate isn't in the dimensions of the chunk.
+ public static ushort GetValidatedVoxelIndex(T chunk, int x, int y, int z)
+ {
+ if (x < 0 || x >= chunk.Width)
+ throw new ArgumentOutOfRangeException(nameof(x));
+
+ if (y < 0 || y >= chunk.Height)
+ throw new ArgumentOutOfRangeException(nameof(y));
+
+ if (z < 0 || z >= chunk.Depth)
+ throw new ArgumentOutOfRangeException(nameof(z));
+
+ return chunk.GetIndex(x, y, z);
+ }
+
+ ///
+ /// Validates that the given local voxel index is within the bounds of the chunk's voxel array.
+ ///
+ /// The chunk to validate against.
+ /// The local voxel index to validate.
+ /// Thrown if the local voxel index is out of bounds.
+ public static void ValidateVoxelIndex(T chunk, ushort localVoxelIndex)
+ {
+ if (localVoxelIndex >= chunk.VoxelCount)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(localVoxelIndex),
+ localVoxelIndex,
+ $"Voxel index must be less than the chunk's voxel count ({chunk.VoxelCount}).");
+ }
+ }
+
+ ///
+ /// Calls on all chunks on the map.
+ ///
+ public void Dispose()
+ {
+ foreach (var chunk in _chunkMap.Values)
+ chunk.Release();
+ }
+}
diff --git a/src/Numos.Chunks/IChunkInitializer.cs b/src/Numos.Chunks/IChunkInitializer.cs
new file mode 100644
index 0000000..80a9075
--- /dev/null
+++ b/src/Numos.Chunks/IChunkInitializer.cs
@@ -0,0 +1,24 @@
+using Numos.Maths;
+
+namespace Numos.Chunks;
+
+///
+/// Interface implemented by chunk types so that they can create
+/// and initialize new instances in static context.
+///
+/// Type of the chunk.
+public interface IChunkInitializer where T : Chunk
+{
+ ///
+ /// Creates a new instance of the chunk at a specified position.
+ ///
+ /// The position in the grid.
+ /// Width of the chunk.
+ /// Height of the chunk.
+ /// Depth of the chunk.
+ /// A newly created and initialized chunk.
+ abstract static T CreateInitializeChunk(Int3 position,
+ int width = ChunkConstants.DefaultWidth,
+ int height = ChunkConstants.DefaultHeight,
+ int depth = ChunkConstants.DefaultDepth);
+}
diff --git a/src/Numos.Chunks/IChunkSimulation.cs b/src/Numos.Chunks/IChunkSimulation.cs
new file mode 100644
index 0000000..1b06cb8
--- /dev/null
+++ b/src/Numos.Chunks/IChunkSimulation.cs
@@ -0,0 +1,15 @@
+using Numos.Chunks.Topology;
+using Numos.Maths;
+
+namespace Numos.Chunks;
+
+public interface IChunkSimulation
+{
+ SimulationId Id { get; }
+
+ IChunkWorld ChunkWorld { get; }
+
+ Int3 ChunkDimensions { get; }
+
+ ChunkHandle[] GetChunkHandles();
+}
diff --git a/src/Numos.Chunks/IChunkWorld.cs b/src/Numos.Chunks/IChunkWorld.cs
new file mode 100644
index 0000000..9d25bb8
--- /dev/null
+++ b/src/Numos.Chunks/IChunkWorld.cs
@@ -0,0 +1,16 @@
+using Numos.Chunks.Topology;
+
+namespace Numos.Chunks;
+
+///
+/// An interface for objects that hold multiple s
+/// with the same .
+///
+public interface IChunkWorld
+{
+ IReadOnlyList ChunkSimulations { get; }
+
+ bool TryResolveCell(VoxelRef cell);
+
+ bool TryGetChunkSimulation(SimulationId id, out IChunkSimulation? simulation);
+}
diff --git a/src/Numos.Chunks/Numos.Chunks.csproj b/src/Numos.Chunks/Numos.Chunks.csproj
new file mode 100644
index 0000000..7a9a10a
--- /dev/null
+++ b/src/Numos.Chunks/Numos.Chunks.csproj
@@ -0,0 +1,26 @@
+
+
+
+
+
+ net10.0
+ enable
+ enable
+ Numos.Chunks
+ Numos.Chunks
+ Numos Chunks
+ Voxel Chunk utilities used by Numos.
+ simulation;chunks;voxel;nativeaot
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Numos.Chunks/Topology/ExplicitLinkDefinition.cs b/src/Numos.Chunks/Topology/ExplicitLinkDefinition.cs
new file mode 100644
index 0000000..7b71f51
--- /dev/null
+++ b/src/Numos.Chunks/Topology/ExplicitLinkDefinition.cs
@@ -0,0 +1,12 @@
+namespace Numos.Chunks.Topology;
+
+///
+/// Defines one undirected sparse voxel adjacency.
+///
+/// One endpoint. Registration canonicalizes endpoint orientation.
+/// The other endpoint.
+/// The interactions allowed across the adjacency.
+public readonly record struct ExplicitLinkDefinition(
+ VoxelRef First,
+ VoxelRef Second,
+ T Flags) where T : Enum;
diff --git a/src/Numos.Chunks/Topology/ExplicitLinkHandles.cs b/src/Numos.Chunks/Topology/ExplicitLinkHandles.cs
new file mode 100644
index 0000000..4c405bf
--- /dev/null
+++ b/src/Numos.Chunks/Topology/ExplicitLinkHandles.cs
@@ -0,0 +1,29 @@
+namespace Numos.Chunks.Topology;
+
+///
+/// Identifies a batch of explicit links owned and removed as one lifecycle unit.
+///
+/// The link-set storage slot.
+/// The generation used to detect stale handles after slot reuse.
+///
+/// The default value is invalid.
+///
+public readonly record struct ExplicitLinkSetHandle(int Index, uint Generation)
+{
+ ///
+ /// Gets whether this value could identify a link set.
+ ///
+ public bool IsValid => Index >= 0 && Generation != 0;
+}
+
+///
+/// Identifies a one-edge portal in the world's explicit topology.
+///
+/// The underlying generic link set.
+public readonly record struct PortalHandle(ExplicitLinkSetHandle Links);
+
+///
+/// Identifies a dock surface in the world's explicit topology.
+///
+/// The underlying generic link set.
+public readonly record struct DockHandle(ExplicitLinkSetHandle Links);
diff --git a/src/Numos.Chunks/Topology/ExplicitLinkSelector.cs b/src/Numos.Chunks/Topology/ExplicitLinkSelector.cs
new file mode 100644
index 0000000..782cfba
--- /dev/null
+++ b/src/Numos.Chunks/Topology/ExplicitLinkSelector.cs
@@ -0,0 +1,12 @@
+namespace Numos.Chunks.Topology;
+
+///
+/// Selects an explicit link while Numos compiles a custom solver's neighborhood view.
+///
+/// The canonical, value-only link being compiled.
+/// when the link participates in the solver.
+///
+/// Numos calls selectors only when topology or solver registration changes. Selectors must be deterministic and must
+/// not mutate their world.
+///
+public delegate bool ExplicitLinkSelector(ExplicitLinkDefinition link) where T : struct, Enum;
diff --git a/src/Numos.Chunks/Topology/ExplicitLinkSetKind.cs b/src/Numos.Chunks/Topology/ExplicitLinkSetKind.cs
new file mode 100644
index 0000000..cec7c2d
--- /dev/null
+++ b/src/Numos.Chunks/Topology/ExplicitLinkSetKind.cs
@@ -0,0 +1,26 @@
+namespace Numos.Chunks.Topology;
+
+///
+/// Records which world API created an explicit link set.
+///
+///
+/// The kind supports inspection, replay, and tooling. Every kind uses the same sparse solver representation, so it
+/// does not change transport physics.
+///
+public enum ExplicitLinkSetKind : byte
+{
+ ///
+ /// A generic batch on a voxel.
+ ///
+ Arbitrary,
+
+ ///
+ /// A one-edge portal on a voxel.
+ ///
+ Portal,
+
+ ///
+ /// A surface batch on a voxel.
+ ///
+ Dock
+}
diff --git a/src/Numos.Chunks/Topology/SimulationId.cs b/src/Numos.Chunks/Topology/SimulationId.cs
new file mode 100644
index 0000000..4170f4f
--- /dev/null
+++ b/src/Numos.Chunks/Topology/SimulationId.cs
@@ -0,0 +1,31 @@
+namespace Numos.Chunks.Topology;
+
+///
+/// Identifies one simulation registration that owns a .
+///
+/// The stable registry slot.
+/// The slot generation that prevents a stale identifier from naming a replacement simulation.
+///
+/// The default value is invalid. Identifiers are meaningful only within the world that issued them.
+///
+public readonly record struct SimulationId(int Index, uint Generation) : IComparable
+{
+ ///
+ /// Gets whether this value could have been issued by a world registry.
+ ///
+ public bool IsValid => Index >= 0 && Generation != 0;
+
+ ///
+ /// Compares stable slot and generation values without depending on object identity or hash iteration.
+ ///
+ /// The identifier to compare.
+ ///
+ /// A negative value when this identifier sorts first, zero when the identifiers match, or a positive value when
+ /// sorts first.
+ ///
+ public int CompareTo(SimulationId other)
+ {
+ int index = Index.CompareTo(other.Index);
+ return index != 0 ? index : Generation.CompareTo(other.Generation);
+ }
+}
diff --git a/src/Numos.Chunks/Topology/VoxelRef.cs b/src/Numos.Chunks/Topology/VoxelRef.cs
new file mode 100644
index 0000000..b3413ed
--- /dev/null
+++ b/src/Numos.Chunks/Topology/VoxelRef.cs
@@ -0,0 +1,48 @@
+using Numos.Maths;
+
+namespace Numos.Chunks.Topology;
+
+///
+/// Stably identifies one voxel within some holder simulation of a .
+///
+/// The owning simulation registration.
+/// The owning chunk.
+/// The flat local voxel index within the chunk.
+///
+/// The value contains no object references and is suitable for deterministic ordering, snapshots, and replay data.
+/// It does not prove that the simulation, chunk, or voxel still exists; mutation methods validate the complete
+/// address against the receiving world.
+///
+public readonly record struct VoxelRef(
+ SimulationId Simulation,
+ ChunkHandle Chunk,
+ ushort LocalVoxelIndex) : IComparable
+{
+ ///
+ /// Compares the complete stable address in simulation, chunk-coordinate, and voxel order.
+ ///
+ /// The cell reference to compare.
+ ///
+ /// A negative value when this address sorts first, zero when the addresses match, or a positive value when
+ /// sorts first.
+ ///
+ public int CompareTo(VoxelRef other)
+ {
+ int comparison = Simulation.CompareTo(other.Simulation);
+ if (comparison != 0)
+ return comparison;
+
+ comparison = CompareChunkPositions(Chunk.Position, other.Chunk.Position);
+ return comparison != 0 ? comparison : LocalVoxelIndex.CompareTo(other.LocalVoxelIndex);
+ }
+
+ private static int CompareChunkPositions(Int3 left, Int3 right)
+ {
+ int comparison = left.X.CompareTo(right.X);
+ if (comparison != 0)
+ return comparison;
+
+ comparison = left.Y.CompareTo(right.Y);
+ return comparison != 0 ? comparison : left.Z.CompareTo(right.Z);
+ }
+}
diff --git a/src/Numos.API/AtmosWorldNeighborTopology.cs b/src/Numos.Chunks/WorldNeighborTopology.cs
similarity index 65%
rename from src/Numos.API/AtmosWorldNeighborTopology.cs
rename to src/Numos.Chunks/WorldNeighborTopology.cs
index efcb9b4..0c055ba 100644
--- a/src/Numos.API/AtmosWorldNeighborTopology.cs
+++ b/src/Numos.Chunks/WorldNeighborTopology.cs
@@ -1,11 +1,12 @@
+using Numos.Chunks.Topology;
using Numos.Maths;
-namespace Numos.API;
+namespace Numos.Chunks;
///
/// Identifies how a solver-facing neighboring cell was discovered.
///
-public enum AtmosNeighborKind : byte
+public enum VoxelNeighborKind : byte
{
///
/// The cells are ordinary Cartesian neighbors.
@@ -23,11 +24,11 @@ public enum AtmosNeighborKind : byte
///
/// The neighboring cell.
/// How the adjacency was discovered.
-/// The explicit-link capabilities, or for Cartesian adjacency.
-public readonly record struct AtmosNeighbor(
- AtmosCellRef Cell,
- AtmosNeighborKind Kind,
- ExplicitLinkFlags Flags);
+/// The explicit-link capabilities, or default enum value for Cartesian adjacency.
+public readonly record struct VoxelNeighbor(
+ VoxelRef Cell,
+ VoxelNeighborKind Kind,
+ T Flags) where T : struct, Enum;
///
/// Describes one canonically owned solver edge.
@@ -35,28 +36,78 @@ public readonly record struct AtmosNeighbor(
/// The canonical first endpoint.
/// The canonical second endpoint.
/// How the adjacency was discovered.
-/// The explicit-link capabilities, or for Cartesian adjacency.
-public readonly record struct AtmosNeighborEdge(
- AtmosCellRef First,
- AtmosCellRef Second,
- AtmosNeighborKind Kind,
- ExplicitLinkFlags Flags);
+/// The explicit-link capabilities, or default enum value for Cartesian adjacency.
+public readonly record struct VoxelNeighborEdge(
+ VoxelRef First,
+ VoxelRef Second,
+ VoxelNeighborKind Kind,
+ T Flags) where T : struct, Enum;
+
+///
+/// Configures the neighborhood compiled for one custom world solver.
+///
+/// A stable identifier describing the selection policy for checkpoint compatibility.
+/// Whether ordinary Cartesian neighbors participate.
+/// The selector evaluated for active explicit links.
+///
+/// Set to for a solver that only interacts with
+/// portals, docks, or other explicit links: it keeps the compiled view limited to the sparse explicit edge set
+/// instead of re-deriving all six ordinary neighbors of every voxel in every chunk. Reach for
+/// only when the same stage genuinely needs to traverse ordinary walls too, such as fire
+/// or sound propagating through both open doorways and portals.
+///
+public sealed class VoxelNeighborSelection(
+ string key,
+ bool includeCartesian,
+ ExplicitLinkSelector explicitLinks) where T : struct, Enum
+{
+ ///
+ /// Gets the stable compatibility key for this selection policy.
+ ///
+ ///
+ /// This string is checkpointed alongside the solver's registration and folded into world state hashing, so a
+ /// restored checkpoint compiles topology using the key it was captured with. Give a selection a new key when
+ /// its changes what it matches; reusing a key for a semantically
+ /// different selection lets a restored checkpoint silently compile the wrong edges for it.
+ ///
+ public string Key { get; } = string.IsNullOrWhiteSpace(key)
+ ? throw new ArgumentException("A neighbor selection key cannot be empty.", nameof(key))
+ : key;
+
+ ///
+ /// Gets whether ordinary Cartesian neighbors participate.
+ ///
+ public bool IncludeCartesian { get; } = includeCartesian;
+
+ internal ExplicitLinkSelector ExplicitLinks { get; } =
+ explicitLinks ?? throw new ArgumentNullException(nameof(explicitLinks));
+
+ ///
+ /// Creates a selection that includes Cartesian neighbors and every active explicit link.
+ ///
+ /// The stable compatibility key for the consuming solver.
+ /// A selection covering the complete world topology.
+ public static VoxelNeighborSelection All(string key)
+ {
+ return new VoxelNeighborSelection(key, true, static _ => true);
+ }
+}
///
/// Provides a solver-specific, immutable view of Cartesian and compiled explicit topology.
///
-public sealed class AtmosWorldNeighborTopology
+public sealed class WorldNeighborTopology where T : struct, Enum
{
- private readonly Dictionary _explicitByChunk;
- private readonly ExplicitLinkDefinition[] _explicitEdges;
+ private readonly Dictionary> _explicitByChunk;
+ private readonly ExplicitLinkDefinition[] _explicitEdges;
private readonly bool _includeCartesian;
- private readonly AtmosWorld? _world;
+ private readonly IChunkWorld? _world;
- private AtmosWorldNeighborTopology(
- AtmosWorld? world,
+ private WorldNeighborTopology(
+ IChunkWorld? world,
bool includeCartesian,
- Dictionary explicitByChunk,
- ExplicitLinkDefinition[] explicitEdges)
+ Dictionary> explicitByChunk,
+ ExplicitLinkDefinition[] explicitEdges)
{
_world = world;
_includeCartesian = includeCartesian;
@@ -64,12 +115,12 @@ private AtmosWorldNeighborTopology(
_explicitEdges = explicitEdges;
}
- internal static AtmosWorldNeighborTopology Empty { get; } =
+ public static WorldNeighborTopology Empty { get; } =
new(null, false, [], []);
- internal static AtmosWorldNeighborTopology EmptyFor(AtmosWorld world)
+ public static WorldNeighborTopology EmptyFor(IChunkWorld world)
{
- return new AtmosWorldNeighborTopology(world, false, [], []);
+ return new WorldNeighborTopology(world, false, [], []);
}
///
@@ -78,12 +129,12 @@ internal static AtmosWorldNeighborTopology EmptyFor(AtmosWorld world)
/// The simulation that owns the chunk.
/// The chunk to inspect.
/// An allocation-free chunk-local neighborhood view.
- public AtmosChunkNeighborView GetChunk(AtmosSimulation simulation, AtmosChunkHandle chunk)
+ public VoxelChunkNeighborView GetChunk(IChunkSimulation simulation, ChunkHandle chunk)
{
ArgumentNullException.ThrowIfNull(simulation);
var world = GetWorld();
- if (!ReferenceEquals(simulation.World, world) ||
- !world.TryResolveCell(new AtmosCellRef(simulation.Id, chunk, 0), out _))
+ if (!ReferenceEquals(simulation.ChunkWorld, world) ||
+ !world.TryResolveCell(new VoxelRef(simulation.Id, chunk, 0)))
{
throw new ArgumentException("The chunk is not registered in this world.", nameof(chunk));
}
@@ -97,14 +148,13 @@ public AtmosChunkNeighborView GetChunk(AtmosSimulation simulation, AtmosChunkHan
var adjacentPosition = chunk.Position + GetDirection(direction);
if (world.TryResolveCell(
- new AtmosCellRef(simulation.Id, new AtmosChunkHandle(adjacentPosition), 0),
- out _))
+ new VoxelRef(simulation.Id, new ChunkHandle(adjacentPosition), 0)))
{
adjacentChunkMask |= checked((byte)(1 << direction));
}
}
- return new AtmosChunkNeighborView(
+ return new VoxelChunkNeighborView(
simulation.Id,
chunk,
simulation.ChunkDimensions,
@@ -119,10 +169,10 @@ public AtmosChunkNeighborView GetChunk(AtmosSimulation simulation, AtmosChunkHan
/// A live cell in the callback's world.
/// The number of selected Cartesian and explicit neighbors.
/// Solid and void classifications do not remove structural adjacency.
- public int GetNeighborCount(AtmosCellRef cell)
+ public int GetNeighborCount(VoxelRef cell)
{
var world = GetWorld();
- if (!world.TryGetSimulation(cell.Simulation, out var simulation) || simulation == null)
+ if (!world.TryGetChunkSimulation(cell.Simulation, out var simulation) || simulation == null)
throw new ArgumentException("The cell does not identify a live simulation in this world.", nameof(cell));
return GetChunk(simulation, cell.Chunk).GetNeighborCount(cell.LocalVoxelIndex);
@@ -133,10 +183,10 @@ public int GetNeighborCount(AtmosCellRef cell)
///
/// A live cell in the callback's world.
/// An allocation-free incident-neighbor enumerable.
- public AtmosNeighborEnumerable GetNeighbors(AtmosCellRef cell)
+ public VoxelNeighborEnumerable GetNeighbors(VoxelRef cell)
{
var world = GetWorld();
- if (!world.TryGetSimulation(cell.Simulation, out var simulation) || simulation == null)
+ if (!world.TryGetChunkSimulation(cell.Simulation, out var simulation) || simulation == null)
throw new ArgumentException("The cell does not identify a live simulation in this world.", nameof(cell));
return GetChunk(simulation, cell.Chunk).GetNeighbors(cell.LocalVoxelIndex);
@@ -152,16 +202,16 @@ public AtmosNeighborEnumerable GetNeighbors(AtmosCellRef cell)
/// membership on every call, and when the selection includes Cartesian neighbors it walks all six directions
/// of every voxel in every chunk in the world to find them — for a selection built with
/// includeCartesian: false, it only walks the sparse explicit edge list instead. A
- /// performance-sensitive tiled solver should acquire once per chunk via
- /// and call per voxel instead of
+ /// performance-sensitive tiled solver should acquire once per chunk via
+ /// and call per voxel instead of
/// calling this every tick.
///
- public IEnumerable GetOwnedEdges()
+ public IEnumerable> GetOwnedEdges()
{
var world = GetWorld();
if (_includeCartesian)
{
- foreach (var simulation in world.Simulations)
+ foreach (var simulation in world.ChunkSimulations)
{
foreach (var chunk in simulation.GetChunkHandles())
{
@@ -173,17 +223,17 @@ public IEnumerable GetOwnedEdges()
for (ushort voxelIndex = 0; voxelIndex < voxelCount; voxelIndex++)
{
- AtmosCellRef first = new(simulation.Id, chunk, voxelIndex);
+ VoxelRef first = new(simulation.Id, chunk, voxelIndex);
for (int direction = 1; direction < 6; direction += 2)
{
if (!view.TryGetCartesianNeighbor(voxelIndex, direction, out var second))
continue;
- yield return new AtmosNeighborEdge(
+ yield return new VoxelNeighborEdge(
first,
second,
- AtmosNeighborKind.Cartesian,
- ExplicitLinkFlags.None);
+ VoxelNeighborKind.Cartesian,
+ default);
}
}
}
@@ -192,23 +242,23 @@ public IEnumerable GetOwnedEdges()
foreach (var edge in _explicitEdges)
{
- yield return new AtmosNeighborEdge(
+ yield return new VoxelNeighborEdge(
edge.First,
edge.Second,
- AtmosNeighborKind.Explicit,
+ VoxelNeighborKind.Explicit,
edge.Flags);
}
}
- internal static AtmosWorldNeighborTopology Compile(
- AtmosWorld world,
- AtmosNeighborSelection selection,
- IReadOnlyList links)
+ public static WorldNeighborTopology Compile(
+ IChunkWorld world,
+ VoxelNeighborSelection selection,
+ IReadOnlyList> links)
{
- var selected = new List(links.Count);
+ var selected = new List>(links.Count);
foreach (var link in links)
{
- if (selection.ExplicitLinks(new AtmosExplicitLinkInfo(link.First, link.Second, link.Flags)))
+ if (selection.ExplicitLinks(new ExplicitLinkDefinition(link.First, link.Second, link.Flags)))
selected.Add(link);
}
@@ -219,10 +269,10 @@ internal static AtmosWorldNeighborTopology Compile(
Add(entries, link.Second, link.First, link.Flags);
}
- var chunks = new Dictionary(entries.Count);
+ var chunks = new Dictionary>(entries.Count);
foreach ((var key, List neighbors) in entries)
{
- if (!world.TryGetSimulation(key.Simulation, out var simulation) || simulation == null)
+ if (!world.TryGetChunkSimulation(key.Simulation, out var simulation) || simulation == null)
continue;
int voxelCount = checked(
@@ -237,7 +287,7 @@ internal static AtmosWorldNeighborTopology Compile(
});
int[] starts = new int[voxelCount + 1];
- var values = new AtmosNeighbor[neighbors.Count];
+ var values = new VoxelNeighbor[neighbors.Count];
int neighborIndex = 0;
for (int voxelIndex = 0; voxelIndex < voxelCount; voxelIndex++)
{
@@ -250,17 +300,17 @@ internal static AtmosWorldNeighborTopology Compile(
}
starts[voxelCount] = neighborIndex;
- chunks.Add(key, new CompiledChunkAdjacency(starts, values));
+ chunks.Add(key, new CompiledChunkAdjacency(starts, values));
}
- return new AtmosWorldNeighborTopology(world, selection.IncludeCartesian, chunks, selected.ToArray());
+ return new WorldNeighborTopology(world, selection.IncludeCartesian, chunks, selected.ToArray());
}
private static void Add(
Dictionary> entries,
- AtmosCellRef source,
- AtmosCellRef neighbor,
- ExplicitLinkFlags flags)
+ VoxelRef source,
+ VoxelRef neighbor,
+ T flags)
{
var key = new CompiledChunkKey(source.Simulation, source.Chunk);
if (!entries.TryGetValue(key, out List? values))
@@ -272,10 +322,10 @@ private static void Add(
values.Add(
new CompiledNeighborEntry(
source.LocalVoxelIndex,
- new AtmosNeighbor(neighbor, AtmosNeighborKind.Explicit, flags)));
+ new VoxelNeighbor(neighbor, VoxelNeighborKind.Explicit, flags)));
}
- private AtmosWorld GetWorld()
+ private IChunkWorld GetWorld()
{
return _world ?? throw new InvalidOperationException("This solver was not registered with a neighbor selection.");
}
@@ -293,26 +343,34 @@ private static Int3 GetDirection(int direction)
_ => throw new ArgumentOutOfRangeException(nameof(direction))
};
}
+
+ private readonly record struct CompiledChunkKey(
+ SimulationId Simulation,
+ ChunkHandle Chunk);
+
+ private readonly record struct CompiledNeighborEntry(
+ ushort SourceIndex,
+ VoxelNeighbor Neighbor);
}
///
/// Reusable topology view for one chunk.
///
-public readonly struct AtmosChunkNeighborView
+public readonly struct VoxelChunkNeighborView where T : struct, Enum
{
- private readonly CompiledChunkAdjacency? _explicitAdjacency;
+ private readonly CompiledChunkAdjacency? _explicitAdjacency;
private readonly byte _adjacentChunkMask;
private readonly bool _includeCartesian;
- private readonly AtmosChunkHandle _chunk;
+ private readonly ChunkHandle _chunk;
private readonly Int3 _dimensions;
- private readonly AtmosSimulationId _simulation;
+ private readonly SimulationId _simulation;
- internal AtmosChunkNeighborView(
- AtmosSimulationId simulation,
- AtmosChunkHandle chunk,
+ internal VoxelChunkNeighborView(
+ SimulationId simulation,
+ ChunkHandle chunk,
Int3 dimensions,
bool includeCartesian,
- CompiledChunkAdjacency? explicitAdjacency,
+ CompiledChunkAdjacency? explicitAdjacency,
byte adjacentChunkMask)
{
_simulation = simulation;
@@ -354,18 +412,18 @@ public int GetNeighborCount(ushort localVoxelIndex)
///
/// The source voxel's chunk-local index.
/// An allocation-free incident-neighbor enumerable.
- public AtmosNeighborEnumerable GetNeighbors(ushort localVoxelIndex)
+ public VoxelNeighborEnumerable GetNeighbors(ushort localVoxelIndex)
{
ValidateIndex(localVoxelIndex);
int explicitStart = _explicitAdjacency?.Starts[localVoxelIndex] ?? 0;
int explicitEnd = _explicitAdjacency?.Starts[localVoxelIndex + 1] ?? 0;
- return new AtmosNeighborEnumerable(this, localVoxelIndex, explicitStart, explicitEnd);
+ return new VoxelNeighborEnumerable(this, localVoxelIndex, explicitStart, explicitEnd);
}
internal bool TryGetCartesianNeighbor(
ushort localVoxelIndex,
int direction,
- out AtmosCellRef neighbor)
+ out VoxelRef neighbor)
{
if (!_includeCartesian)
{
@@ -466,11 +524,11 @@ internal bool TryGetCartesianNeighbor(
}
ushort targetIndex = checked((ushort)(x + y * _dimensions.X + z * plane));
- neighbor = new AtmosCellRef(_simulation, new AtmosChunkHandle(chunkPosition), targetIndex);
+ neighbor = new VoxelRef(_simulation, new ChunkHandle(chunkPosition), targetIndex);
return true;
}
- internal AtmosNeighbor GetExplicitNeighbor(int index)
+ internal VoxelNeighbor GetExplicitNeighbor(int index)
{
return _explicitAdjacency!.Neighbors[index];
}
@@ -486,15 +544,15 @@ private void ValidateIndex(ushort localVoxelIndex)
///
/// Allocation-free enumerable over one cell's structural neighbors.
///
-public readonly struct AtmosNeighborEnumerable
+public readonly struct VoxelNeighborEnumerable where T : struct, Enum
{
private readonly int _explicitEnd;
private readonly int _explicitStart;
private readonly ushort _localVoxelIndex;
- private readonly AtmosChunkNeighborView _view;
+ private readonly VoxelChunkNeighborView _view;
- internal AtmosNeighborEnumerable(
- AtmosChunkNeighborView view,
+ internal VoxelNeighborEnumerable(
+ VoxelChunkNeighborView view,
ushort localVoxelIndex,
int explicitStart,
int explicitEnd)
@@ -509,25 +567,25 @@ internal AtmosNeighborEnumerable(
/// Creates an enumerator.
///
/// An allocation-free enumerator over this cell's neighbors.
- public AtmosNeighborEnumerator GetEnumerator()
+ public VoxelNeighborEnumerator GetEnumerator()
{
- return new AtmosNeighborEnumerator(_view, _localVoxelIndex, _explicitStart, _explicitEnd);
+ return new VoxelNeighborEnumerator(_view, _localVoxelIndex, _explicitStart, _explicitEnd);
}
}
///
/// Allocation-free enumerator over one cell's structural neighbors.
///
-public struct AtmosNeighborEnumerator
+public struct VoxelNeighborEnumerator where T : struct, Enum
{
private readonly int _explicitEnd;
private readonly ushort _localVoxelIndex;
- private readonly AtmosChunkNeighborView _view;
+ private readonly VoxelChunkNeighborView _view;
private int _direction;
private int _explicitIndex;
- internal AtmosNeighborEnumerator(
- AtmosChunkNeighborView view,
+ internal VoxelNeighborEnumerator(
+ VoxelChunkNeighborView view,
ushort localVoxelIndex,
int explicitStart,
int explicitEnd)
@@ -543,7 +601,7 @@ internal AtmosNeighborEnumerator(
///
/// Gets the current neighbor.
///
- public AtmosNeighbor Current { get; private set; }
+ public VoxelNeighbor Current { get; private set; }
///
/// Advances to the next neighbor.
@@ -557,7 +615,7 @@ public bool MoveNext()
if (!_view.TryGetCartesianNeighbor(_localVoxelIndex, direction, out var cell))
continue;
- Current = new AtmosNeighbor(cell, AtmosNeighborKind.Cartesian, ExplicitLinkFlags.None);
+ Current = new VoxelNeighbor(cell, VoxelNeighborKind.Cartesian, default);
return true;
}
@@ -569,23 +627,15 @@ public bool MoveNext()
}
}
-internal readonly record struct CompiledChunkKey(
- AtmosSimulationId Simulation,
- AtmosChunkHandle Chunk);
-
-internal readonly record struct CompiledNeighborEntry(
- ushort SourceIndex,
- AtmosNeighbor Neighbor);
-
-internal sealed class CompiledChunkAdjacency(
+internal sealed class CompiledChunkAdjacency(
int[] starts,
- AtmosNeighbor[] neighbors)
+ VoxelNeighbor[] neighbors) where T : struct, Enum
{
internal int[] Starts { get; } = starts;
- internal AtmosNeighbor[] Neighbors { get; } = neighbors;
+ internal VoxelNeighbor[] Neighbors { get; } = neighbors;
internal int GetCount(ushort localVoxelIndex)
{
return Starts[localVoxelIndex + 1] - Starts[localVoxelIndex];
}
-}
\ No newline at end of file
+}
diff --git a/src/Numos.Collections/FlatArray.cs b/src/Numos.Collections/FlatArray.cs
index bd0f874..5733824 100644
--- a/src/Numos.Collections/FlatArray.cs
+++ b/src/Numos.Collections/FlatArray.cs
@@ -112,10 +112,9 @@ public int GetIndex(Int3 position)
///
public int GetIndexUnsafe(Int3 position)
{
- return position.X + position.Y * _dimensions.X + position.Z * _dimensions.X * _dimensions.Y;
+ return FlatArrayHelpers.GetIndexUnsafe(position, _dimensions);
}
-
-
+
///
/// Converts a flat array index to its coordinate.
///
@@ -123,11 +122,8 @@ public Int3 GetPosition(int index)
{
if ((uint)index >= (uint)Length)
throw new IndexOutOfRangeException();
-
- return new Int3(
- index % _dimensions.X,
- index / _dimensions.X % _dimensions.Y,
- index / (_dimensions.X * _dimensions.Y));
+
+ return FlatArrayHelpers.GetPosition(index, _dimensions);
}
///
@@ -189,3 +185,36 @@ public T[] ToArray()
return [.. _data];
}
}
+
+public static class FlatArrayHelpers
+{
+ ///
+ /// Converts a coordinate to its flat array index.
+ ///
+ public static int GetIndex(Int3 position, Int3 dimensions)
+ {
+ if (!position.IsWithin(dimensions))
+ throw new IndexOutOfRangeException();
+
+ return position.X + position.Y * dimensions.X + position.Z * dimensions.X * dimensions.Y;
+ }
+
+ ///
+ /// Converts a coordinate to its flat array index.
+ ///
+ public static int GetIndexUnsafe(Int3 position, Int3 dimensions)
+ {
+ return position.X + position.Y * dimensions.X + position.Z * dimensions.X * dimensions.Y;
+ }
+
+ ///
+ /// Converts a flat array index to its coordinate.
+ ///
+ public static Int3 GetPosition(int index, Int3 dimensions)
+ {
+ return new Int3(
+ index % dimensions.X,
+ index / dimensions.X % dimensions.Y,
+ index / (dimensions.X * dimensions.Y));
+ }
+}
diff --git a/src/Numos.CoreSim/AtmosChunk.cs b/src/Numos.CoreSim/AtmosChunk.cs
index 377ca72..a8f2eed 100644
--- a/src/Numos.CoreSim/AtmosChunk.cs
+++ b/src/Numos.CoreSim/AtmosChunk.cs
@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
+using Numos.Chunks;
using Numos.Collections;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Datatypes.Snapshots;
@@ -16,10 +17,10 @@ namespace Numos.CoreSim;
///
///
/// Chunk-owned per-voxel data supports both flat-index and coordinate access.
-/// Use and when converting indices
+/// Use and when converting indices
/// for scalar-indexed storage such as gas channels (because... you know.... they aren't physical).
///
-internal class AtmosChunk
+internal sealed class AtmosChunk : Chunk, IChunkInitializer
{
private static long _nextGeneration;
@@ -50,36 +51,7 @@ internal class AtmosChunk
/// one moles value for every voxel in the chunk.
///
public GasChannel[] ActiveGases;
-
- ///
- /// A process-local index assigned when this chunk is registered.
- ///
- ///
- /// Lets hot per-tick solver lookups (e.g. boundary flow's cross-chunk injection buffer) use array
- /// indexing instead of a dictionary keyed on . Unique only among currently
- /// registered chunks, and not part of the simulation's observable state, so it is absent from
- /// and the state hash. Do not use it for anything but indexing a
- /// solver-owned lookup table.
- /// Ids are recycled after a chunk is unregistered, so a DenseId-keyed lookup table must track which
- /// chunk currently owns each slot and validate that before trusting stale contents.
- ///
- public int DenseId;
-
- ///
- /// The number of voxels along the z-axis.
- ///
- public int Depth;
-
- ///
- /// The position of this chunk in the chunk grid.
- ///
- public Int3 GridPosition;
-
- ///
- /// The number of voxels along the y-axis.
- ///
- public int Height;
-
+
///
/// Whether this chunk is eligible to be processed by the simulation.
/// A sleeping chunk is skipped during simulation ticks.
@@ -126,11 +98,6 @@ internal class AtmosChunk
[ElementQuantity("pressure")]
public FlatArray TotalPressure;
- ///
- /// Total number of voxels in this chunk, equal to Width * Height * Depth.
- ///
- public int VoxelCount;
-
///
/// Room classification for each voxel, indexed by flat voxel index or local coordinate.
///
@@ -145,11 +112,6 @@ internal class AtmosChunk
///
public FlatArray VoxelRoomMap;
- ///
- /// The number of voxels along the x-axis.
- ///
- public int Width;
-
private long _generation;
private long _revision;
private Dictionary
public AtmosChunkVersion Version => new(_generation, Interlocked.Read(ref _revision));
- ///
- /// The number of voxels along each axis.
- ///
- public Int3 Dimensions => new(Width, Height, Depth);
-
///
/// Ensures that the chunk's per-voxel arrays are initialized for its current dimensions.
///
@@ -223,7 +180,7 @@ public void EnsureInitialized()
/// The depth of the chunk.
///
/// A dimension is non-positive or the combined voxel count exceeds
- /// .
+ /// .
///
///
/// Initialization puts the chunk to sleep, resets all active counts and timers, and clears
@@ -232,9 +189,9 @@ public void EnsureInitialized()
[PublicAPI]
public void Initialize(
Int3 position,
- int width = AtmosChunkConstants.DefaultWidth,
- int height = AtmosChunkConstants.DefaultHeight,
- int depth = AtmosChunkConstants.DefaultDepth)
+ int width = ChunkConstants.DefaultWidth,
+ int height = ChunkConstants.DefaultHeight,
+ int depth = ChunkConstants.DefaultDepth)
{
int voxelCount = GetValidatedVoxelCount(width, height, depth);
_solverArrays = null;
@@ -277,7 +234,7 @@ public void MarkChanged()
///
/// After releasing a chunk, do not use its active gas channels until they have been initialized again.
///
- public void Release()
+ public override void Release()
{
_solverArrays = null;
if (ActiveGases != null)
@@ -381,7 +338,7 @@ internal void RestoreSolverArrays(IReadOnlyList snapsh
/// Every non-solid, non-void voxel participates while the chunk is awake. Classification IDs do not
/// partition simulation work.
///
- public virtual void Wake()
+ public void Wake()
{
IsAwake = true;
SleepTimer = 0;
@@ -413,7 +370,7 @@ public void RebuildActiveAirIndices()
///
/// Marks the chunk as sleeping so that it is skipped by simulation ticks.
///
- public virtual void Sleep()
+ public void Sleep()
{
IsAwake = false;
MarkChanged();
@@ -508,7 +465,6 @@ public bool TryGetThermalState(
return true;
}
-
///
/// Sets a specific voxel to a vacuum. This sets TotalPressure, ActiveGases, and TotalHeatCapacity to 0 and IsVacuum to
/// true.
@@ -544,7 +500,6 @@ public void SetChunkToVacuum()
IsVacuum.Fill(true);
}
-
///
/// Sets a specific voxel classification. Solid and void classifications clear the voxel to vacuum.
///
@@ -573,7 +528,6 @@ public void SetVoxelClassification(ushort idx, VoxelClassification classificatio
VoxelRoomMap[idx] = classification.RoomId;
}
-
///
/// Sets every voxel classification. Solid and void classifications clear the chunk to vacuum.
///
@@ -587,7 +541,6 @@ public void SetChunkClassification(int roomId)
VoxelRoomMap.Fill(roomId);
}
-
///
/// Sets the entire chunk classification. Solid and void classifications clear the chunk to vacuum.
///
@@ -685,90 +638,14 @@ public AtmosChunkSnapshot GetNetworkSnapshot(
return snapshot;
}
- ///
- /// Converts local voxel coordinates to an index into the chunk's flat arrays.
- ///
- /// The local x coordinate, from zero through minus one.
- /// The local y coordinate, from zero through minus one.
- /// The local z coordinate, from zero through minus one.
- /// The flat voxel index.
- [PublicAPI]
- public ushort GetIndex(int x, int y, int z)
- {
- return GetIndex(new Int3(x, y, z));
- }
-
- ///
- [PublicAPI]
- public ushort GetIndex(Int3 vec)
- {
- return (ushort)VoxelRoomMap.GetIndex(vec);
- }
-
- ///
- [PublicAPI]
- public ushort GetIndexUnsafe(Int3 vec)
- {
- return (ushort)VoxelRoomMap.GetIndexUnsafe(vec);
- }
-
-
- ///
- /// Converts a flat voxel index to local x, y, and z coordinates.
- ///
- /// The flat voxel index.
- /// The local coordinates as an (x, y, z) tuple.
- [PublicAPI]
- public (int x, int y, int z) GetXyz(ushort index)
- {
- var position = GetXyzInt3(index);
- return (position.X, position.Y, position.Z);
- }
-
- ///
- /// Converts a flat voxel index to local coordinates as an .
- ///
- /// The flat voxel index.
- /// The local voxel coordinates.
- [PublicAPI]
- public Int3 GetXyzInt3(ushort index)
- {
- return VoxelRoomMap.GetPosition(index);
- }
-
- private void EnsureInitialized(ref FlatArray array, Int3 dimensions)
- {
- if (!array.IsInitialized || array.Length != VoxelCount)
- array = new FlatArray(new T[VoxelCount], dimensions);
- else if (array.Dimensions != dimensions)
- array = array.Reshape(dimensions);
- }
-
- private static int GetValidatedVoxelCount(int width, int height, int depth)
+ public static AtmosChunk CreateInitializeChunk(
+ Int3 position,
+ int width = ChunkConstants.DefaultWidth,
+ int height = ChunkConstants.DefaultHeight,
+ int depth = ChunkConstants.DefaultDepth)
{
- ArgumentOutOfRangeException.ThrowIfNegativeOrZero(width);
- ArgumentOutOfRangeException.ThrowIfNegativeOrZero(height);
- ArgumentOutOfRangeException.ThrowIfNegativeOrZero(depth);
- if (width > AtmosChunkConstants.MaximumVoxelCount ||
- height > AtmosChunkConstants.MaximumVoxelCount ||
- depth > AtmosChunkConstants.MaximumVoxelCount)
- {
- throw new ArgumentOutOfRangeException(
- nameof(width),
- width,
- $"No chunk dimension may exceed {AtmosChunkConstants.MaximumVoxelCount}.");
- }
-
- long voxelCount = (long)width * height * depth;
- if (voxelCount > AtmosChunkConstants.MaximumVoxelCount)
- {
- throw new ArgumentOutOfRangeException(
- nameof(width),
- width,
- $"Chunk dimensions contain {voxelCount} voxels, but at most " +
- $"{AtmosChunkConstants.MaximumVoxelCount} are supported.");
- }
-
- return (int)voxelCount;
+ var chunk = new AtmosChunk(width, height, depth);
+ chunk.Initialize(position, width, height, depth);
+ return chunk;
}
-}
\ No newline at end of file
+}
diff --git a/src/Numos.CoreSim/AtmosChunkConstants.cs b/src/Numos.CoreSim/AtmosChunkConstants.cs
index 64eeced..dd9787e 100644
--- a/src/Numos.CoreSim/AtmosChunkConstants.cs
+++ b/src/Numos.CoreSim/AtmosChunkConstants.cs
@@ -5,20 +5,7 @@ namespace Numos.CoreSim;
///
public static class AtmosChunkConstants
{
- /// Default number of voxels along a chunk's x-axis.
- public const int DefaultWidth = 16;
-
- /// Default number of voxels along a chunk's y-axis.
- public const int DefaultHeight = 16;
-
- /// Default number of voxels along a chunk's z-axis.
- public const int DefaultDepth = 16;
-
-
/// Initial number of distinct gas-channel slots allocated by a chunk.
/// The channel table grows when a mixture introduces additional gas IDs.
public const int InitialGasChannelCapacity = 16;
-
- /// Maximum voxel count representable by the chunk's unsigned 16-bit flat indices.
- public const int MaximumVoxelCount = ushort.MaxValue;
}
\ No newline at end of file
diff --git a/src/Numos.CoreSim/AtmosKernel.API.cs b/src/Numos.CoreSim/AtmosKernel.API.cs
index d8d7458..5b0266e 100644
--- a/src/Numos.CoreSim/AtmosKernel.API.cs
+++ b/src/Numos.CoreSim/AtmosKernel.API.cs
@@ -1,3 +1,4 @@
+using Numos.Chunks;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Datatypes.Snapshots;
using Numos.CoreSim.Replay;
@@ -18,7 +19,7 @@ internal int ChunkCount
{
lock (StateGate)
{
- return _chunkMap.Count;
+ return _chunkMap.ChunkCount;
}
}
}
@@ -41,7 +42,7 @@ internal Int3[] GetChunkPositions()
{
lock (StateGate)
{
- return _chunkMap.Keys.ToArray();
+ return _chunkMap.GetChunkPositions();
}
}
@@ -66,14 +67,14 @@ internal bool TryGetChunkPositions(
{
lock (StateGate)
{
- revision = _chunkCollectionRevision;
+ revision = _chunkMap.CollectionRevision;
if (revision == knownRevision)
{
positions = [];
return false;
}
- positions = _chunkMap.Keys.ToArray();
+ positions = _chunkMap.GetChunkPositions();
return true;
}
}
@@ -105,7 +106,7 @@ internal void Update(Second elapsedSeconds)
// One elapsed-time update is one externally atomic batch. Solver callbacks may edit the pipeline, but
// chunk lifecycle changes are rejected until the batch completes.
- AtmosChunk[] chunks = OrderedChunks();
+ AtmosChunk[] chunks = _chunkMap.OrderedChunks();
int steps = 0;
while (_accumulator >= AtmosSolverConstants.FixedTimeStep &&
steps < AtmosSolverConstants.MaximumStepsPerUpdate)
@@ -260,15 +261,8 @@ internal void RegisterChunk(AtmosChunk chunk)
lock (StateGate)
{
ThrowIfTickExecuting("register a chunk during the current tick");
- if (chunk.Dimensions != _dimensions)
- throw new ArgumentException("Chunk dimensions must match the simulation.", nameof(chunk));
-
- if (!_chunkMap.TryAdd(chunk.GridPosition, chunk))
- throw new InvalidOperationException($"A chunk is already registered at {chunk.GridPosition}.");
-
- chunk.DenseId = _freeChunkDenseIds.Count > 0 ? _freeChunkDenseIds.Pop() : _nextChunkDenseId++;
+ _chunkMap.RegisterChunk(chunk);
WakeSleepingNeighbors(chunk.GridPosition);
- _chunkCollectionRevision++;
if (ShouldRecord) RecordOperation(new CreateChunkOperation(chunk.GridPosition));
}
}
@@ -283,12 +277,9 @@ internal bool UnregisterChunk(Int3 position)
lock (StateGate)
{
ThrowIfTickExecuting("unregister a chunk used by the current tick");
- if (!_chunkMap.TryRemove(position, out var chunk))
+ if (!_chunkMap.UnregisterChunk(position))
return false;
-
- chunk.Release();
- _freeChunkDenseIds.Push(chunk.DenseId);
- _chunkCollectionRevision++;
+
if (ShouldRecord) RecordOperation(new RemoveChunkOperation(position));
return true;
}
@@ -298,20 +289,13 @@ internal bool UnregisterChunk(Int3 position)
/// Creates, initializes, and registers a chunk owned by this kernel.
///
/// The chunk's position in the chunk grid.
- /// The number of voxels along the local x-axis.
- /// The number of voxels along the local y-axis.
- /// The number of voxels along the local z-axis.
/// A chunk is already registered at .
- internal void CreateAndRegisterChunk(Int3 position, int width, int height, int depth)
+ internal void CreateAndRegisterChunk(Int3 position)
{
lock (StateGate)
{
ThrowIfTickExecuting("register a chunk during the current tick");
- if (_chunkMap.ContainsKey(position))
- throw new InvalidOperationException($"A chunk is already registered at {position}.");
-
- var chunk = new AtmosChunk(width, height, depth);
- chunk.Initialize(position, width, height, depth);
+ var chunk = _chunkMap.CreateAndRegisterChunk(position);
RegisterChunk(chunk);
}
}
@@ -370,7 +354,7 @@ private static AtmosVoxelSnapshot CreateVoxelSnapshot(
Int3 position,
ushort localVoxelIndex)
{
- ValidateVoxelIndex(chunk, localVoxelIndex);
+ ChunkMap.ValidateVoxelIndex(chunk, localVoxelIndex);
var gases = new VoxelGasSnapshot[chunk.ActiveGasCount];
for (int gas = 0; gas < gases.Length; gas++)
{
@@ -461,7 +445,7 @@ internal AtmosChunkSnapshotBatch GetChangedChunkSnapshots(
var request = requests[index];
// Handle lists are detached. A concurrent unregistration between enumeration
// and this batch is represented by the chunk simply not being returned.
- if (!_chunkMap.TryGetValue(request.Position, out var chunk) ||
+ if (!_chunkMap.TryGetChunk(request.Position, out var chunk) ||
chunk.Version == request.KnownVersion &&
!(request.Fields.HasFlag(AtmosChunkSnapshotFields.SolverArrays) && chunk.HasCapturedSolverArrays))
{
@@ -546,7 +530,7 @@ internal void SetVoxelClassification(
lock (StateGate)
{
var chunk = GetChunk(position);
- ValidateVoxelIndex(chunk, localVoxelIndex);
+ ChunkMap.ValidateVoxelIndex(chunk, localVoxelIndex);
chunk.SetVoxelClassification(localVoxelIndex, classification);
RebuildActiveTopology(chunk);
chunk.MarkChanged();
@@ -571,7 +555,7 @@ internal void SetVoxelClassification(
lock (StateGate)
{
var chunk = GetChunk(position);
- SetVoxelClassification(position, GetValidatedVoxelIndex(chunk, x, y, z), classification);
+ SetVoxelClassification(position, ChunkMap.GetValidatedVoxelIndex(chunk, x, y, z), classification);
}
}
@@ -588,7 +572,7 @@ internal void SetVoxelTemperature(Int3 position, ushort localVoxelIndex, Kelvin
lock (StateGate)
{
var chunk = GetChunk(position);
- ValidateVoxelIndex(chunk, localVoxelIndex);
+ ChunkMap.ValidateVoxelIndex(chunk, localVoxelIndex);
chunk.Temperature[localVoxelIndex] = temperature;
chunk.TotalPressure[localVoxelIndex] =
AtmosSolverMath.CalculatePressureAtVoxel(_config, chunk, localVoxelIndex);
@@ -613,7 +597,7 @@ internal void SetVoxelTemperature(Int3 position, int x, int y, int z, Kelvin tem
lock (StateGate)
{
var chunk = GetChunk(position);
- SetVoxelTemperature(position, GetValidatedVoxelIndex(chunk, x, y, z), temperature);
+ SetVoxelTemperature(position, ChunkMap.GetValidatedVoxelIndex(chunk, x, y, z), temperature);
}
}
@@ -635,7 +619,7 @@ internal void AddGasToVoxel(
lock (StateGate)
{
var chunk = GetChunk(position);
- ValidateVoxelIndex(chunk, localVoxelIndex);
+ ChunkMap.ValidateVoxelIndex(chunk, localVoxelIndex);
ValidateGasInjection(gasId, moles, temperature);
int classification = chunk.VoxelRoomMap[localVoxelIndex];
@@ -668,7 +652,7 @@ internal void AddGasToVoxel(
lock (StateGate)
{
var chunk = GetChunk(position);
- AddGasToVoxel(position, GetValidatedVoxelIndex(chunk, x, y, z), gasId, moles, temperature);
+ AddGasToVoxel(position, ChunkMap.GetValidatedVoxelIndex(chunk, x, y, z), gasId, moles, temperature);
}
}
@@ -708,14 +692,14 @@ internal void Tick()
{
lock (StateGate)
{
- AtmosChunk[] chunks = OrderedChunks();
+ AtmosChunk[] chunks = _chunkMap.OrderedChunks();
TickSimulation(chunks);
}
}
private AtmosChunk GetChunk(Int3 position)
{
- if (_chunkMap.TryGetValue(position, out var chunk))
+ if (_chunkMap.TryGetChunk(position, out var chunk))
return chunk;
throw new KeyNotFoundException($"No atmospheric chunk is registered at ({position.X}, {position.Y}, {position.Z}).");
@@ -739,41 +723,10 @@ private void WakeSleepingNeighbors(Int3 position)
private void WakeSleepingChunk(Int3 position)
{
- if (_chunkMap.TryGetValue(position, out var chunk) && !chunk.IsAwake)
+ if (_chunkMap.TryGetChunk(position, out var chunk) && !chunk.IsAwake)
chunk.Wake();
}
- private static ushort GetValidatedVoxelIndex(AtmosChunk chunk, int x, int y, int z)
- {
- if (x < 0 || x >= chunk.Width)
- throw new ArgumentOutOfRangeException(nameof(x));
-
- if (y < 0 || y >= chunk.Height)
- throw new ArgumentOutOfRangeException(nameof(y));
-
- if (z < 0 || z >= chunk.Depth)
- throw new ArgumentOutOfRangeException(nameof(z));
-
- return chunk.GetIndex(x, y, z);
- }
-
- ///
- /// Validates that the given local voxel index is within the bounds of the chunk's voxel array.
- ///
- /// The chunk to validate against.
- /// The local voxel index to validate.
- /// Thrown if the local voxel index is out of bounds.
- private static void ValidateVoxelIndex(AtmosChunk chunk, ushort localVoxelIndex)
- {
- if (localVoxelIndex >= chunk.VoxelCount)
- {
- throw new ArgumentOutOfRangeException(
- nameof(localVoxelIndex),
- localVoxelIndex,
- $"Voxel index must be less than the chunk's voxel count ({chunk.VoxelCount}).");
- }
- }
-
private void ValidateGasInjection(int gasId, Mole moles, Kelvin temperature)
{
if ((uint)gasId >= (uint)_config.GasRegistry.Count)
@@ -790,4 +743,4 @@ private void ValidateGasInjection(int gasId, Mole moles, Kelvin temperature)
"Temperature must be nonnegative and finite.");
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Numos.CoreSim/AtmosKernel.GasMixtures.cs b/src/Numos.CoreSim/AtmosKernel.GasMixtures.cs
index cb59360..1fd7a14 100644
--- a/src/Numos.CoreSim/AtmosKernel.GasMixtures.cs
+++ b/src/Numos.CoreSim/AtmosKernel.GasMixtures.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+using Numos.Chunks;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Solvers;
using Numos.Maths;
@@ -48,7 +49,7 @@ internal void ExecuteMixtureTransaction(Action transaction)
lock (StateGate)
{
var chunk = GetChunk(position);
- ValidateVoxelIndex(chunk, localVoxelIndex);
+ ChunkMap.ValidateVoxelIndex(chunk, localVoxelIndex);
return (chunk.Version.Generation, localVoxelIndex);
}
}
@@ -65,7 +66,7 @@ internal void ExecuteMixtureTransaction(Action transaction)
lock (StateGate)
{
var chunk = GetChunk(position);
- ushort localVoxelIndex = GetValidatedVoxelIndex(chunk, x, y, z);
+ ushort localVoxelIndex = ChunkMap.GetValidatedVoxelIndex(chunk, x, y, z);
return (chunk.Version.Generation, localVoxelIndex);
}
}
@@ -580,7 +581,7 @@ private AtmosChunk GetMixtureChunk(Int3 position, long generation, ushort localV
"The voxel gas mixture is stale because its original chunk was unregistered or replaced.");
}
- ValidateVoxelIndex(chunk, localVoxelIndex);
+ ChunkMap.ValidateVoxelIndex(chunk, localVoxelIndex);
return chunk;
}
diff --git a/src/Numos.CoreSim/AtmosKernel.Replay.cs b/src/Numos.CoreSim/AtmosKernel.Replay.cs
index e8afca2..66648de 100644
--- a/src/Numos.CoreSim/AtmosKernel.Replay.cs
+++ b/src/Numos.CoreSim/AtmosKernel.Replay.cs
@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using System.Diagnostics;
+using Numos.Chunks;
using Numos.CoreSim.Replay;
using Numos.Maths;
@@ -7,7 +8,6 @@ namespace Numos.CoreSim;
internal sealed partial class AtmosKernel
{
- private readonly Int3 _dimensions;
private Action? _externalOperationSink;
private bool _isApplyingOperation;
private bool _isReplaying;
@@ -94,11 +94,11 @@ internal AtmosSimulationCheckpoint CaptureCheckpoint()
{
ThrowIfTickExecuting("capture a checkpoint during a tick");
return new AtmosSimulationCheckpoint(
- _dimensions,
+ _chunkMap.Dimensions,
TimelinePosition,
_config,
_solverCheckpointProvider(),
- OrderedChunks().Select(static chunk => new AtmosChunkCheckpoint(chunk)).ToArray());
+ _chunkMap.OrderedChunks().Select(static chunk => new AtmosChunkCheckpoint(chunk)).ToArray());
}
}
@@ -160,7 +160,7 @@ internal void ValidateCheckpoint(AtmosSimulationCheckpoint checkpoint)
ArgumentNullException.ThrowIfNull(checkpoint);
if (checkpoint.FormatVersion != AtmosSimulationCheckpoint.CurrentFormatVersion ||
checkpoint.CompatibilityVersion != AtmosSimulationCheckpoint.CurrentCompatibilityVersion ||
- checkpoint.Dimensions != _dimensions ||
+ checkpoint.Dimensions != _chunkMap.Dimensions ||
checkpoint.Position.Tick > int.MaxValue)
{
throw new ArgumentException(
@@ -185,7 +185,7 @@ internal void ValidateCheckpoint(AtmosSimulationCheckpoint checkpoint)
var positions = new HashSet();
foreach (var chunk in checkpoint.Chunks)
{
- if (chunk.Dimensions != _dimensions || !positions.Add(chunk.Position))
+ if (chunk.Dimensions != _chunkMap.Dimensions || !positions.Add(chunk.Position))
throw new ArgumentException("The checkpoint contains incompatible or duplicate chunks.", nameof(checkpoint));
}
}
@@ -213,13 +213,13 @@ private void InstallCheckpoint(AtmosSimulationCheckpoint checkpoint)
throw;
}
- ConcurrentDictionary previous = _chunkMap;
- _chunkMap = replacement;
+ ConcurrentDictionary previous = _chunkMap.UnsafeGetStorage();
+ _chunkMap.UnsafeReplaceStorage(replacement);
// Rebase on the restored chunk count instead of continuing to grow across repeated restores
// (e.g. replay scrubbing), which would otherwise leave DenseId-indexed lookup tables oversized.
// Ids are reassigned densely as 0..replacement.Count above, so none are free.
- _nextChunkDenseId = replacement.Count;
- _freeChunkDenseIds.Clear();
+ _chunkMap.NextDenseId = replacement.Count;
+ _chunkMap.FreeDenseIds.Clear();
_config = checkpoint.Config;
CurrentTickConfig.Capture(_config);
CurrentTickConfig.ClearGasSolverData();
@@ -231,7 +231,7 @@ private void InstallCheckpoint(AtmosSimulationCheckpoint checkpoint)
_setWorldSolverEnabled(step.Name, step.Enabled);
_defaultSolvers.ClearTransientState();
- _chunkCollectionRevision++;
+ _chunkMap.CollectionRevision++;
LastBoundaryTicks = 0;
foreach (var chunk in previous.Values)
chunk.Release();
@@ -357,7 +357,7 @@ private void Apply(SetAtmosConfigOperation op)
private void Apply(CreateChunkOperation op)
{
- CreateAndRegisterChunk(op.Position, _dimensions.X, _dimensions.Y, _dimensions.Z);
+ CreateAndRegisterChunk(op.Position);
}
private void Apply(RemoveChunkOperation op)
@@ -409,7 +409,7 @@ private void Apply(SetSolverEnabledOperation op)
private void Apply(SetVoxelMixtureOperation op)
{
var chunk = GetChunk(op.Position);
- ValidateVoxelIndex(chunk, op.LocalVoxelIndex);
+ ChunkMap.ValidateVoxelIndex(chunk, op.LocalVoxelIndex);
chunk.Wake();
foreach (var gas in op.Gases)
chunk.ActiveGases[chunk.GetOrCreateGasChannel(gas.GasId)].Moles[op.LocalVoxelIndex] = gas.Moles;
@@ -425,12 +425,4 @@ private void ApplyOperation(AtmosOperation operation)
{
ApplyRecordedOperation(operation);
}
-
- private AtmosChunk[] OrderedChunks()
- {
- return _chunkMap.Values
- .OrderBy(static chunk => chunk.GridPosition.X)
- .ThenBy(static chunk => chunk.GridPosition.Y)
- .ThenBy(static chunk => chunk.GridPosition.Z).ToArray();
- }
-}
\ No newline at end of file
+}
diff --git a/src/Numos.CoreSim/AtmosKernel.cs b/src/Numos.CoreSim/AtmosKernel.cs
index 5a7f91d..3c19dd5 100644
--- a/src/Numos.CoreSim/AtmosKernel.cs
+++ b/src/Numos.CoreSim/AtmosKernel.cs
@@ -1,5 +1,5 @@
-using System.Collections.Concurrent;
using System.Diagnostics;
+using Numos.Chunks;
using Numos.CoreSim.Replay;
using Numos.CoreSim.Solvers;
using Numos.Maths;
@@ -12,9 +12,6 @@ namespace Numos.CoreSim;
internal sealed partial class AtmosKernel : IDisposable
{
private readonly DefaultAtmosSolvers _defaultSolvers;
- // Reused before minting a new id, so DenseId stays bounded by peak concurrent chunk count rather
- // than growing forever. See AtmosChunk.DenseId's remarks on why recycling ids is safe.
- private readonly Stack _freeChunkDenseIds = new();
private readonly List _recordedOperations = [];
private readonly SolverDataStorage _solverData = new();
@@ -27,25 +24,23 @@ internal sealed partial class AtmosKernel : IDisposable
/// Number of completed fixed ticks on the owning world timeline.
///
internal int TickCount;
-
private Second _accumulator;
- private long _chunkCollectionRevision;
- private ConcurrentDictionary _chunkMap = new();
+ private ChunkMap _chunkMap;
private AtmosConfigSnapshot _config = new AtmosConfig().CreateSnapshot();
private bool _hasRecording;
private bool _isRecording;
private bool _isTickExecuting;
private ulong _lastOperationSequence;
- private int _nextChunkDenseId;
+
private AtmosTimelinePosition _recordingHead;
private AtmosTimelinePosition _recordingStart;
internal AtmosKernel(
- int chunkWidth = AtmosChunkConstants.DefaultWidth,
- int chunkHeight = AtmosChunkConstants.DefaultHeight,
- int chunkDepth = AtmosChunkConstants.DefaultDepth)
+ int chunkWidth = ChunkConstants.DefaultWidth,
+ int chunkHeight = ChunkConstants.DefaultHeight,
+ int chunkDepth = ChunkConstants.DefaultDepth)
{
- _dimensions = new Int3(chunkWidth, chunkHeight, chunkDepth);
+ _chunkMap = new ChunkMap(chunkWidth, chunkHeight, chunkDepth);
_defaultSolvers = new DefaultAtmosSolvers(chunkWidth, chunkHeight, chunkDepth);
CurrentTickConfig.Capture(_config);
}
@@ -65,10 +60,8 @@ public void Dispose()
lock (StateGate)
{
ThrowIfTickExecuting("dispose the simulation");
- foreach (var chunk in _chunkMap.Values)
- chunk.Release();
-
- _chunkMap.Clear();
+
+ _chunkMap.Dispose();
CurrentTickConfig.ClearGasSolverData();
_solverData.Clear();
_defaultSolvers.Dispose();
@@ -77,7 +70,7 @@ public void Dispose()
internal bool TryGetChunk(Int3 position, out AtmosChunk chunk)
{
- return _chunkMap.TryGetValue(position, out chunk!);
+ return _chunkMap.TryGetChunk(position, out chunk!);
}
///
@@ -94,7 +87,7 @@ internal bool TryResolveExplicitEndpoint(
{
lock (StateGate)
{
- if (!_chunkMap.TryGetValue(chunkPosition, out var chunk) ||
+ if (!_chunkMap.TryGetChunk(chunkPosition, out var chunk) ||
localVoxelIndex >= chunk.VoxelCount)
{
endpoint = default;
@@ -131,7 +124,7 @@ internal AtmosWorldTickExecution BeginWorldTick()
{
lock (StateGate)
{
- return new AtmosWorldTickExecution(BeginTickSimulation(OrderedChunks()));
+ return new AtmosWorldTickExecution(BeginTickSimulation(_chunkMap.OrderedChunks()));
}
}
@@ -214,4 +207,4 @@ private readonly record struct ThermalBoundaryConductance(
JoulePerKelvin Conductance);
private readonly record struct ThermalBoundaryState(Kelvin Temperature, JoulePerKelvin HeatCapacity);
-}
\ No newline at end of file
+}
diff --git a/src/Numos.CoreSim/Numos.CoreSim.csproj b/src/Numos.CoreSim/Numos.CoreSim.csproj
index e5a806d..d194155 100644
--- a/src/Numos.CoreSim/Numos.CoreSim.csproj
+++ b/src/Numos.CoreSim/Numos.CoreSim.csproj
@@ -27,6 +27,7 @@
+
_batches = [];
///
- /// Maps to this tick's batch index for that chunk, or -1 if the
+ /// Maps to this tick's batch index for that chunk, or -1 if the
/// chunk has no batch yet. A flat array indexed by dense id is a direct replacement for a
/// dictionary keyed on grid position — this lookup runs once per boundary-flow transfer and the
/// dictionary hash/probe was the dominant cost of that hot loop.
@@ -397,7 +398,7 @@ private sealed class InjectionBuffer
///
/// The chunk each slot was last resolved for. Since
- /// is recycled, a slot's batch index is only trusted when this
+ /// is recycled, a slot's batch index is only trusted when this
/// still matches the chunk being looked up.
///
private AtmosChunk?[] _ownerByChunkDenseId = [];
diff --git a/src/Numos.Headless/Diagnostics/SimulationStateAnalyzer.cs b/src/Numos.Headless/Diagnostics/SimulationStateAnalyzer.cs
index 659986e..b1a7ec4 100644
--- a/src/Numos.Headless/Diagnostics/SimulationStateAnalyzer.cs
+++ b/src/Numos.Headless/Diagnostics/SimulationStateAnalyzer.cs
@@ -1,4 +1,5 @@
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Datatypes.Snapshots;
@@ -26,7 +27,7 @@ public static SimulationStateReport Analyze(
0,
SimulationObservationOptions.MaximumMaxIssueLocations);
- AtmosChunkHandle[] handles = SelectHandles(simulation.GetChunkHandles(), options.Chunk);
+ ChunkHandle[] handles = SelectHandles(simulation.GetChunkHandles(), options.Chunk);
AtmosChunkSnapshotRequest[] requests = handles
.Select(static handle => new AtmosChunkSnapshotRequest(
handle.Position,
@@ -99,8 +100,8 @@ public static SimulationStateReport Analyze(
issues.Truncated);
}
- private static AtmosChunkHandle[] SelectHandles(
- AtmosChunkHandle[] handles,
+ private static ChunkHandle[] SelectHandles(
+ ChunkHandle[] handles,
Coordinate? selectedChunk)
{
if (!selectedChunk.HasValue)
diff --git a/src/Numos.Headless/SimulationSession.cs b/src/Numos.Headless/SimulationSession.cs
index 442265e..51ec034 100644
--- a/src/Numos.Headless/SimulationSession.cs
+++ b/src/Numos.Headless/SimulationSession.cs
@@ -1,4 +1,5 @@
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.Headless.Diagnostics;
@@ -341,9 +342,9 @@ private static HeadlessRequestException Missing(string property)
return new HeadlessRequestException("missingProperty", $"The '{property}' property is required.");
}
- private static AtmosChunkHandle Handle(Coordinate position)
+ private static ChunkHandle Handle(Coordinate position)
{
- return new AtmosChunkHandle(ToInt3(position));
+ return new ChunkHandle(ToInt3(position));
}
private static Int3 ToInt3(Coordinate value)
diff --git a/src/Numos.Replay.SourceGen/ReplayCodecGenerator.cs b/src/Numos.Replay.SourceGen/ReplayCodecGenerator.cs
index 5d62fe9..3da034a 100644
--- a/src/Numos.Replay.SourceGen/ReplayCodecGenerator.cs
+++ b/src/Numos.Replay.SourceGen/ReplayCodecGenerator.cs
@@ -446,14 +446,14 @@ private static bool TryGetWireKind(ITypeSymbol type, out string writeTemplate, o
writeTemplate = "writer.Write({0}.RoomId);";
readExpression = "new global::Numos.CoreSim.Datatypes.Primitives.VoxelClassification(reader.ReadInt32())";
return true;
- case "global::Numos.API.AtmosSimulationId":
+ case "global::Numos.Chunks.Topology.SimulationId":
// WriteSimulationId/ReadSimulationId are private helpers already declared directly on
// NumosWorldReplaySerializer (shared with its hand-written checkpoint codec) -- only world
// operations ever have a field of this type, so calling them unqualified is safe.
writeTemplate = "WriteSimulationId(writer, {0});";
readExpression = "ReadSimulationId(reader)";
return true;
- case "global::Numos.API.ExplicitLinkSetHandle":
+ case "global::Numos.Chunks.Topology.ExplicitLinkSetHandle":
writeTemplate = "WriteHandle(writer, {0});";
readExpression = "ReadHandle(reader)";
return true;
diff --git a/src/Numos.Serialization/NumosWorldReplaySerializer.cs b/src/Numos.Serialization/NumosWorldReplaySerializer.cs
index 4f4cbdc..b1b8137 100644
--- a/src/Numos.Serialization/NumosWorldReplaySerializer.cs
+++ b/src/Numos.Serialization/NumosWorldReplaySerializer.cs
@@ -1,4 +1,6 @@
using Numos.API;
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim.Replay;
using Numos.Replay.SourceGen;
@@ -336,7 +338,7 @@ private static WorldCheckpointPayload ReadCheckpoint(BinaryReader reader, NumosR
byte state = reader.ReadByte();
var kind = (ExplicitLinkSetKind)reader.ReadByte();
int linkCount = NumosReplaySerializer.ReadCount(reader, 10_000_000, "explicit link");
- var links = new ExplicitLinkDefinition[linkCount];
+ var links = new ExplicitLinkDefinition[linkCount];
for (int linkIndex = 0; linkIndex < linkCount; linkIndex++)
links[linkIndex] = ReadLink(reader);
@@ -510,49 +512,49 @@ private static CreateAtmosLinkSetOperation ReadCreateAtmosLinkSetOperation(Binar
var handle = ReadHandle(reader);
var kind = (ExplicitLinkSetKind)reader.ReadByte();
int count = NumosReplaySerializer.ReadCount(reader, 10_000_000, "explicit link");
- var links = new ExplicitLinkDefinition[count];
+ var links = new ExplicitLinkDefinition[count];
for (int index = 0; index < count; index++)
links[index] = ReadLink(reader);
return new CreateAtmosLinkSetOperation(handle, kind, links);
}
- private static void WriteLink(BinaryWriter writer, ExplicitLinkDefinition link)
+ private static void WriteLink(BinaryWriter writer, ExplicitLinkDefinition link)
{
WriteCell(writer, link.First);
WriteCell(writer, link.Second);
writer.Write((byte)link.Flags);
}
- private static ExplicitLinkDefinition ReadLink(BinaryReader reader)
+ private static ExplicitLinkDefinition ReadLink(BinaryReader reader)
{
- return new ExplicitLinkDefinition(ReadCell(reader), ReadCell(reader), (ExplicitLinkFlags)reader.ReadByte());
+ return new ExplicitLinkDefinition(ReadCell(reader), ReadCell(reader), (AtmosLinkFlags)reader.ReadByte());
}
- private static void WriteCell(BinaryWriter writer, AtmosCellRef cell)
+ private static void WriteCell(BinaryWriter writer, VoxelRef cell)
{
WriteSimulationId(writer, cell.Simulation);
NumosReplaySerializer.WriteInt3(writer, cell.Chunk.Position);
writer.Write(cell.LocalVoxelIndex);
}
- private static AtmosCellRef ReadCell(BinaryReader reader)
+ private static VoxelRef ReadCell(BinaryReader reader)
{
- return new AtmosCellRef(
+ return new VoxelRef(
ReadSimulationId(reader),
- new AtmosChunkHandle(NumosReplaySerializer.ReadInt3(reader)),
+ new ChunkHandle(NumosReplaySerializer.ReadInt3(reader)),
reader.ReadUInt16());
}
- private static void WriteSimulationId(BinaryWriter writer, AtmosSimulationId id)
+ private static void WriteSimulationId(BinaryWriter writer, SimulationId id)
{
writer.Write(id.Index);
writer.Write(id.Generation);
}
- private static AtmosSimulationId ReadSimulationId(BinaryReader reader)
+ private static SimulationId ReadSimulationId(BinaryReader reader)
{
- return new AtmosSimulationId(reader.ReadInt32(), reader.ReadUInt32());
+ return new SimulationId(reader.ReadInt32(), reader.ReadUInt32());
}
private static void WriteHandle(BinaryWriter writer, ExplicitLinkSetHandle handle)
diff --git a/src/Numos.Viewer/SimulationViewer.Project.cs b/src/Numos.Viewer/SimulationViewer.Project.cs
index 48343a0..0f72d44 100644
--- a/src/Numos.Viewer/SimulationViewer.Project.cs
+++ b/src/Numos.Viewer/SimulationViewer.Project.cs
@@ -1,4 +1,5 @@
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.GasReactions;
@@ -237,7 +238,7 @@ private void AddProjectChunk(Int3 position, int roomId)
}
}
- private void RemoveProjectChunk(AtmosChunkHandle chunk)
+ private void RemoveProjectChunk(ChunkHandle chunk)
{
if (_simulation == null)
return;
@@ -251,7 +252,7 @@ private void RemoveProjectChunk(AtmosChunkHandle chunk)
SetProjectMessage($"Chunk {FormatChunkPosition(chunk.Position)} no longer exists.", true);
}
- private void SealProjectChunk(AtmosChunkHandle chunk)
+ private void SealProjectChunk(ChunkHandle chunk)
{
if (_simulation == null)
return;
@@ -269,7 +270,7 @@ private void SealProjectChunk(AtmosChunkHandle chunk)
}
}
- private void UnsleepProjectChunk(AtmosChunkHandle chunk)
+ private void UnsleepProjectChunk(ChunkHandle chunk)
{
if (_simulation == null)
return;
@@ -346,7 +347,7 @@ private void RemoveProjectGas(int gasId)
}
private void InjectProjectGas(
- AtmosChunkHandle chunk,
+ ChunkHandle chunk,
int x,
int y,
int z,
diff --git a/src/Numos.Viewer/SimulationViewer.ProjectUi.cs b/src/Numos.Viewer/SimulationViewer.ProjectUi.cs
index c3271ca..6b92098 100644
--- a/src/Numos.Viewer/SimulationViewer.ProjectUi.cs
+++ b/src/Numos.Viewer/SimulationViewer.ProjectUi.cs
@@ -1,6 +1,7 @@
using System.Numerics;
using ImGuiNET;
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.Maths;
using Numos.Viewer.Ui;
@@ -38,8 +39,8 @@ public partial class SimulationViewer
private string _newGasName = "New Gas";
private int _projectChunkDepthDraft = 1;
- private int _projectChunkHeightDraft = AtmosChunkConstants.DefaultHeight;
- private int _projectChunkWidthDraft = AtmosChunkConstants.DefaultWidth;
+ private int _projectChunkHeightDraft = ChunkConstants.DefaultHeight;
+ private int _projectChunkWidthDraft = ChunkConstants.DefaultWidth;
private string _projectNameDraft = "Untitled Simulation";
private bool _requestOpenCloseProject;
@@ -55,11 +56,11 @@ private void RequestCreateProject()
_projectNameDraft = _world == null ? "Untitled Simulation" : $"{_projectName} Copy";
_projectChunkWidthDraft = _chunkDimensions.X > 0
? _chunkDimensions.X
- : AtmosChunkConstants.DefaultWidth;
+ : ChunkConstants.DefaultWidth;
_projectChunkHeightDraft = _chunkDimensions.Y > 0
? _chunkDimensions.Y
- : AtmosChunkConstants.DefaultHeight;
+ : ChunkConstants.DefaultHeight;
_projectChunkDepthDraft = _chunkDimensions.Z > 0 ? _chunkDimensions.Z : 1;
_includeDefaultGasesDraft = true;
@@ -300,9 +301,9 @@ private void RenderProjectChunkControls()
ImGui.TextDisabled($"Fixed size: {_chunkDimensions.X} x {_chunkDimensions.Y} x {_chunkDimensions.Z}");
ImGui.TextDisabled("Right-click a chunk coordinate for options.");
- AtmosChunkHandle? chunkToRemove = null;
- AtmosChunkHandle? chunkToSeal = null;
- AtmosChunkHandle? chunkToUnsleep = null;
+ ChunkHandle? chunkToRemove = null;
+ ChunkHandle? chunkToSeal = null;
+ ChunkHandle? chunkToUnsleep = null;
foreach (var handle in _liveChunkHandles)
{
ImGui.PushID($"chunk-{handle.Position.X}-{handle.Position.Y}-{handle.Position.Z}");
@@ -509,7 +510,7 @@ private void RenderProjectInjectionControls()
_injectionChunkPosition.HasValue)
{
InjectProjectGas(
- new AtmosChunkHandle(_injectionChunkPosition.Value),
+ new ChunkHandle(_injectionChunkPosition.Value),
_injectionX,
_injectionY,
_injectionZ,
diff --git a/src/Numos.Viewer/SimulationViewer.RenderUi.cs b/src/Numos.Viewer/SimulationViewer.RenderUi.cs
index 5bcdd36..d5ca1c6 100644
--- a/src/Numos.Viewer/SimulationViewer.RenderUi.cs
+++ b/src/Numos.Viewer/SimulationViewer.RenderUi.cs
@@ -1,6 +1,7 @@
using System.Numerics;
using ImGuiNET;
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Datatypes.Snapshots;
@@ -980,7 +981,7 @@ private void RenderVoxelTools()
try
{
_simulation!.SetChunkClassification(
- new AtmosChunkHandle(_toolChunkPosition.Value),
+ new ChunkHandle(_toolChunkPosition.Value),
new VoxelClassification(_toolClassificationDraft));
SetProjectMessage(
@@ -1503,7 +1504,7 @@ private bool TryGetVoxelDetails(VoxelAddress address, out AtmosVoxelSnapshot sna
try
{
bool available = _simulation.TryGetVoxelSnapshot(
- new AtmosChunkHandle(address.Chunk.Position),
+ new ChunkHandle(address.Chunk.Position),
address.LocalIndex,
presentedVersion,
out snapshot);
diff --git a/src/Numos.Viewer/SimulationViewer.TopologyUi.cs b/src/Numos.Viewer/SimulationViewer.TopologyUi.cs
index cce66a2..6146ec5 100644
--- a/src/Numos.Viewer/SimulationViewer.TopologyUi.cs
+++ b/src/Numos.Viewer/SimulationViewer.TopologyUi.cs
@@ -1,6 +1,8 @@
using System.Numerics;
using ImGuiNET;
using Numos.API;
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.Maths;
using Numos.Viewer.Ui;
using Raylib_cs;
@@ -18,8 +20,8 @@ public partial class SimulationViewer
private int _dockSecondChunk;
private int _dockSecondFace;
private int _dockSecondSimulation;
- private AtmosCellRef? _portalFirst;
- private AtmosCellRef? _portalSecond;
+ private VoxelRef? _portalFirst;
+ private VoxelRef? _portalSecond;
private bool _removeTopologyModalOpen;
private bool _requestRemoveTopology;
private ExplicitLinkSetHandle? _selectedLinkSet;
@@ -189,7 +191,7 @@ private void RenderTopologyControls()
ImGui.EndTable();
}
- ImGui.BeginDisabled(!_portalFirst.HasValue || !_portalSecond.HasValue || GetTopologyFlags() == ExplicitLinkFlags.None);
+ ImGui.BeginDisabled(!_portalFirst.HasValue || !_portalSecond.HasValue || GetTopologyFlags() == AtmosLinkFlags.None);
if (ImGui.Button("Create Portal"))
CreatePortalFromCapturedEndpoints();
@@ -218,12 +220,12 @@ private void RenderTopologyControls()
if (dockInputChanged)
_topologyFeedback = null;
- ImGui.BeginDisabled(GetTopologyFlags() == ExplicitLinkFlags.None || _simulationSurfaces.Count == 0);
+ ImGui.BeginDisabled(GetTopologyFlags() == AtmosLinkFlags.None || _simulationSurfaces.Count == 0);
if (ImGui.Button("Create Dock"))
{
try
{
- ExplicitLinkDefinition[] links = BuildDockLinks();
+ ExplicitLinkDefinition[] links = BuildDockLinks();
var dock = _world.CreateDock(links);
_selectedLinkSet = dock.Links;
SetTopologyFeedback(
@@ -274,7 +276,7 @@ private bool DrawDockEndpoint(string label, ref int simulationIndex, ref int chu
ImGui.EndCombo();
}
- AtmosChunkHandle[] chunks = _simulationSurfaces[simulationIndex].Simulation.GetChunkHandles().ToArray();
+ ChunkHandle[] chunks = _simulationSurfaces[simulationIndex].Simulation.GetChunkHandles().ToArray();
chunkIndex = chunks.Length == 0 ? 0 : Math.Clamp(chunkIndex, 0, chunks.Length - 1);
string chunkLabel = chunks.Length == 0 ? "No chunks" : FormatChunkPosition(chunks[chunkIndex].Position);
if (ImGui.BeginCombo($"{label} chunk", chunkLabel))
@@ -359,12 +361,12 @@ private void DrawRemoveTopologyModal()
ImGui.SetItemDefaultFocus();
}
- private ExplicitLinkDefinition[] BuildDockLinks()
+ private ExplicitLinkDefinition[] BuildDockLinks()
{
var firstSurface = _simulationSurfaces[Math.Clamp(_dockFirstSimulation, 0, _simulationSurfaces.Count - 1)];
var secondSurface = _simulationSurfaces[Math.Clamp(_dockSecondSimulation, 0, _simulationSurfaces.Count - 1)];
- AtmosChunkHandle[] firstChunks = firstSurface.Simulation.GetChunkHandles().ToArray();
- AtmosChunkHandle[] secondChunks = secondSurface.Simulation.GetChunkHandles().ToArray();
+ ChunkHandle[] firstChunks = firstSurface.Simulation.GetChunkHandles().ToArray();
+ ChunkHandle[] secondChunks = secondSurface.Simulation.GetChunkHandles().ToArray();
if (firstChunks.Length == 0 || secondChunks.Length == 0)
throw new InvalidOperationException("Each dock endpoint needs a chunk.");
@@ -377,7 +379,7 @@ private ExplicitLinkDefinition[] BuildDockLinks()
firstSize.Height != (quarterTurn ? secondSize.Width : secondSize.Height))
throw new InvalidOperationException("The selected faces do not have matching dimensions after rotation.");
- var links = new ExplicitLinkDefinition[firstSize.Width * firstSize.Height];
+ var links = new ExplicitLinkDefinition[firstSize.Width * firstSize.Height];
int destination = 0;
for (int v = 0; v < firstSize.Height; v++)
for (int u = 0; u < firstSize.Width; u++)
@@ -392,30 +394,30 @@ private ExplicitLinkDefinition[] BuildDockLinks()
secondU,
secondV);
- links[destination++] = new ExplicitLinkDefinition(
- new AtmosCellRef(firstSurface.Simulation.Id, firstChunk, firstIndex),
- new AtmosCellRef(secondSurface.Simulation.Id, secondChunk, secondIndex),
+ links[destination++] = new ExplicitLinkDefinition(
+ new VoxelRef(firstSurface.Simulation.Id, firstChunk, firstIndex),
+ new VoxelRef(secondSurface.Simulation.Id, secondChunk, secondIndex),
GetTopologyFlags());
}
return links;
}
- private AtmosCellRef? GetSelectedAtmosCell()
+ private VoxelRef? GetSelectedAtmosCell()
{
if (_simulation == null || !_selectedCell.HasValue)
return null;
- return new AtmosCellRef(
+ return new VoxelRef(
_simulation.Id,
- new AtmosChunkHandle(_selectedCell.Value.Chunk.Position),
+ new ChunkHandle(_selectedCell.Value.Chunk.Position),
_selectedCell.Value.LocalIndex);
}
- private ExplicitLinkFlags GetTopologyFlags()
+ private AtmosLinkFlags GetTopologyFlags()
{
- return (_topologyGas ? ExplicitLinkFlags.GasTransport : ExplicitLinkFlags.None) |
- (_topologyThermal ? ExplicitLinkFlags.ThermalTransport : ExplicitLinkFlags.None);
+ return (_topologyGas ? AtmosLinkFlags.GasTransport : AtmosLinkFlags.None) |
+ (_topologyThermal ? AtmosLinkFlags.ThermalTransport : AtmosLinkFlags.None);
}
private void CreatePortalFromCapturedEndpoints()
@@ -464,7 +466,7 @@ private void DrawTopologyOverlay()
}
}
- private bool TryGetCellCenter(AtmosCellRef cell, out Vector3 center)
+ private bool TryGetCellCenter(VoxelRef cell, out Vector3 center)
{
if (_drawData!.Chunks.TryGetValue(cell.Chunk.Position, out var chunk) && cell.LocalVoxelIndex < chunk.CellCount)
{
@@ -486,7 +488,7 @@ private static Color GetLinkColor(ExplicitLinkSetHandle handle, AtmosWorldLinkSe
return color;
}
- private string FormatCell(AtmosCellRef cell)
+ private string FormatCell(VoxelRef cell)
{
return $"{GetSimulationName(cell.Simulation)}\n{FormatChunkPosition(cell.Chunk.Position)}, voxel {cell.LocalVoxelIndex}";
}
diff --git a/src/Numos.Viewer/SimulationViewer.VoxelEditing.cs b/src/Numos.Viewer/SimulationViewer.VoxelEditing.cs
index cd8e3f0..0f83ec8 100644
--- a/src/Numos.Viewer/SimulationViewer.VoxelEditing.cs
+++ b/src/Numos.Viewer/SimulationViewer.VoxelEditing.cs
@@ -1,6 +1,8 @@
using System.Numerics;
using ImGuiNET;
using Numos.API;
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.SimDrawer;
using Numos.Viewer.Rendering.Viewport;
@@ -468,7 +470,7 @@ private void RenderVoxelContextMenu()
private void RenderContextPortalMenu()
{
- AtmosCellRef? selected = GetSelectedAtmosCell();
+ VoxelRef? selected = GetSelectedAtmosCell();
if (!selected.HasValue || !ImGui.BeginMenu("Portal"))
return;
@@ -479,7 +481,7 @@ private void RenderContextPortalMenu()
_topologyFeedback = null;
}
- bool canCreate = _portalFirst.HasValue && GetTopologyFlags() != ExplicitLinkFlags.None;
+ bool canCreate = _portalFirst.HasValue && GetTopologyFlags() != AtmosLinkFlags.None;
ImGui.BeginDisabled(!canCreate);
if (ImGui.MenuItem("Create Portal to Here"))
{
@@ -495,7 +497,7 @@ private void RenderContextPortalMenu()
else
ImGui.TextDisabled("Start a portal at its first endpoint.");
- if (GetTopologyFlags() == ExplicitLinkFlags.None)
+ if (GetTopologyFlags() == AtmosLinkFlags.None)
ImGui.TextDisabled("Enable a transport mode in World & Topology.");
ImGui.EndMenu();
@@ -553,7 +555,7 @@ private void ApplyClassification(IEnumerable addresses, int classi
ApplyVoxelMutation(
addresses,
address => _simulation!.SetVoxelClassification(
- new AtmosChunkHandle(address.Chunk.Position),
+ new ChunkHandle(address.Chunk.Position),
address.LocalIndex,
new VoxelClassification(classification)),
$"Set classification {classification}");
@@ -580,7 +582,7 @@ private void ApplyGasInjection(
ApplyVoxelMutation(
addresses,
address => _simulation!.AddGasToVoxel(
- new AtmosChunkHandle(address.Chunk.Position),
+ new ChunkHandle(address.Chunk.Position),
address.LocalIndex,
gasId,
moles,
@@ -593,7 +595,7 @@ private void ApplyClearGas(IEnumerable addresses)
ApplyVoxelMutation(
addresses,
address => _simulation!.GetVoxelGasMixture(
- new AtmosChunkHandle(address.Chunk.Position),
+ new ChunkHandle(address.Chunk.Position),
address.LocalIndex).Clear(),
"Cleared gas from");
}
@@ -604,7 +606,7 @@ private void ApplyClearCell(IEnumerable addresses)
addresses,
address =>
{
- var handle = new AtmosChunkHandle(address.Chunk.Position);
+ var handle = new ChunkHandle(address.Chunk.Position);
_simulation!.GetVoxelGasMixture(handle, address.LocalIndex).Clear();
_simulation.SetVoxelClassification(
handle,
@@ -619,7 +621,7 @@ private void ApplyTemperature(IEnumerable addresses, float tempera
ApplyVoxelMutation(
addresses,
address => _simulation!.SetVoxelTemperature(
- new AtmosChunkHandle(address.Chunk.Position),
+ new ChunkHandle(address.Chunk.Position),
address.LocalIndex,
temperature),
$"Set temperature to {temperature:F1} K for");
diff --git a/src/Numos.Viewer/SimulationViewer.World.cs b/src/Numos.Viewer/SimulationViewer.World.cs
index fc0d106..eebb587 100644
--- a/src/Numos.Viewer/SimulationViewer.World.cs
+++ b/src/Numos.Viewer/SimulationViewer.World.cs
@@ -1,7 +1,8 @@
using System.Numerics;
using ImGuiNET;
using Numos.API;
-using Numos.CoreSim;
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim.Datatypes.Snapshots;
using Numos.Maths;
using Numos.SimDrawer;
@@ -13,19 +14,19 @@ namespace Numos.Viewer;
public partial class SimulationViewer
{
- private readonly Dictionary _simulationNames = [];
+ private readonly Dictionary _simulationNames = [];
private readonly List _simulationSurfaces = [];
- private AtmosSimulationId? _activeSimulationId;
+ private SimulationId? _activeSimulationId;
private long _knownSimulationRevision = -1;
private int _newSimulationDepth = 1;
- private int _newSimulationHeight = AtmosChunkConstants.DefaultHeight;
- private int _newSimulationWidth = AtmosChunkConstants.DefaultWidth;
+ private int _newSimulationHeight = ChunkConstants.DefaultHeight;
+ private int _newSimulationWidth = ChunkConstants.DefaultWidth;
private bool _removeSimulationModalOpen;
private bool _requestRemoveSimulation;
private bool _showWorldPanel = true;
private string? _simulationFeedback;
private bool _simulationFeedbackIsError;
- private AtmosSimulationId? _simulationPendingRemoval;
+ private SimulationId? _simulationPendingRemoval;
private void ReconcileSimulationSurfaces()
{
@@ -33,7 +34,7 @@ private void ReconcileSimulationSurfaces()
return;
_knownSimulationRevision = revision;
- HashSet liveIds = simulations.Select(static simulation => simulation.Id).ToHashSet();
+ HashSet liveIds = simulations.Select(static simulation => simulation.Id).ToHashSet();
if (_portalFirst is { } firstPortal && !liveIds.Contains(firstPortal.Simulation))
_portalFirst = null;
@@ -69,14 +70,14 @@ private void ReconcileSimulationSurfaces()
}
_simulationSurfaces.Sort(static (left, right) => left.Simulation.Id.CompareTo(right.Simulation.Id));
- AtmosSimulationId? nextActive = _activeSimulationId is { } active && liveIds.Contains(active)
+ SimulationId? nextActive = _activeSimulationId is { } active && liveIds.Contains(active)
? active
: simulations.FirstOrDefault()?.Id;
SetActiveSimulation(nextActive);
}
- private void SetActiveSimulation(AtmosSimulationId? id)
+ private void SetActiveSimulation(SimulationId? id)
{
if (_world == null || id == null || !_world.TryGetSimulation(id.Value, out var simulation))
{
@@ -161,7 +162,7 @@ private void RefreshSimulationSurfaces()
private void RefreshSimulationSurface(SimulationSurface surface)
{
var fields = _frameBuilder!.GetRequiredSnapshotFields(_currentVisualizationId);
- if (surface.Simulation.TryGetChunkHandles(surface.ChunkRevision, out long revision, out AtmosChunkHandle[] handles))
+ if (surface.Simulation.TryGetChunkHandles(surface.ChunkRevision, out long revision, out ChunkHandle[] handles))
{
surface.ChunkRevision = revision;
surface.Handles = handles;
@@ -204,7 +205,7 @@ private void RenderSimulationViewports()
if (!_show3DViewport)
return;
- AtmosSimulationId? requestedActive = null;
+ SimulationId? requestedActive = null;
foreach (var surface in _simulationSurfaces)
{
if (surface.Viewport == null)
@@ -433,7 +434,7 @@ _simulationPendingRemoval is not { } id ||
ImGui.SetItemDefaultFocus();
}
- private string GetSimulationName(AtmosSimulationId id)
+ private string GetSimulationName(SimulationId id)
{
return _simulationNames.GetValueOrDefault(id, $"Simulation {id.Index + 1}");
}
@@ -462,7 +463,7 @@ internal SimulationSurface(AtmosSimulation simulation)
Projection = CameraProjection.Perspective
};
internal long ChunkRevision { get; set; } = -1;
- internal AtmosChunkHandle[] Handles { get; set; } = [];
+ internal ChunkHandle[] Handles { get; set; } = [];
internal Dictionary Snapshots { get; } = [];
internal SimulationDrawData? DrawData { get; set; }
diff --git a/src/Numos.Viewer/SimulationViewer.cs b/src/Numos.Viewer/SimulationViewer.cs
index 1990722..f8794d4 100644
--- a/src/Numos.Viewer/SimulationViewer.cs
+++ b/src/Numos.Viewer/SimulationViewer.cs
@@ -2,6 +2,7 @@
using System.Numerics;
using ImGuiNET;
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Snapshots;
using Numos.Maths;
@@ -25,7 +26,7 @@ public partial class SimulationViewer : IDisposable
private readonly Action? _configureVisualizations;
private readonly List _highlights = [];
- private readonly List _liveChunkHandles = [];
+ private readonly List _liveChunkHandles = [];
private readonly HashSet _liveChunkPositions = [];
private readonly List _orderedSnapshots = [];
private readonly HashSet _paintedCells = [];
@@ -302,7 +303,7 @@ private bool RefreshSnapshotCache()
if (_simulation.TryGetChunkHandles(
_chunkCollectionRevision,
out long collectionRevision,
- out AtmosChunkHandle[] handles))
+ out ChunkHandle[] handles))
{
_chunkCollectionRevision = collectionRevision;
_liveChunkHandles.Clear();
diff --git a/tests/Numos.API.Dangerous.Tests/AtmosDangerousApiTests.cs b/tests/Numos.API.Dangerous.Tests/AtmosDangerousApiTests.cs
index b54810b..30fd121 100644
--- a/tests/Numos.API.Dangerous.Tests/AtmosDangerousApiTests.cs
+++ b/tests/Numos.API.Dangerous.Tests/AtmosDangerousApiTests.cs
@@ -1,3 +1,4 @@
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.Maths;
@@ -54,7 +55,7 @@ public void GetChunk_WithUnknownHandle_Throws()
using var simulation = new AtmosSimulation();
Assert.That(
- () => simulation.Dangerous().GetChunk(new AtmosChunkHandle(Int3.PosX)),
+ () => simulation.Dangerous().GetChunk(new ChunkHandle(Int3.PosX)),
Throws.TypeOf());
}
@@ -100,7 +101,7 @@ public void WorldSolver_CanResolvePortalNeighborStorage()
world.Solvers.RegisterNeighborSolver(
"dangerous-neighbor",
- AtmosNeighborSelection.All("tests/dangerous-neighbor-v1"),
+ VoxelNeighborSelection.All("tests/dangerous-neighbor-v1"),
context =>
{
foreach (var neighbor in context.Topology.GetNeighbors(source))
@@ -160,7 +161,7 @@ public void StatefulDangerousSolver_RetainsEditableConfiguration()
Assert.That(simulation.GetChunkSnapshot(chunk).Gases.Single().Moles[0], Is.EqualTo(3f));
}
- private sealed class ConfiguredDangerousWriter(AtmosChunkHandle chunk)
+ private sealed class ConfiguredDangerousWriter(ChunkHandle chunk)
{
public DangerousWriterConfig Config { get; } = new();
diff --git a/tests/Numos.API.Tests/AtmosChunkVersionTests.cs b/tests/Numos.API.Tests/AtmosChunkVersionTests.cs
index d8ead17..7e6fdc7 100644
--- a/tests/Numos.API.Tests/AtmosChunkVersionTests.cs
+++ b/tests/Numos.API.Tests/AtmosChunkVersionTests.cs
@@ -1,3 +1,4 @@
+using Numos.Chunks;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Datatypes.Snapshots;
using Numos.Maths;
@@ -186,9 +187,9 @@ public void GetChunkHandles_DiscoversAdditionsAndRemovalsWithoutCallerRegistry()
var first = simulation.CreateAndRegisterChunk(new Int3(2, 0, 0));
var second = simulation.CreateAndRegisterChunk(new Int3(-1, 0, 0));
- AtmosChunkHandle[] before = simulation.GetChunkHandles();
+ ChunkHandle[] before = simulation.GetChunkHandles();
simulation.UnregisterChunk(first);
- AtmosChunkHandle[] after = simulation.GetChunkHandles();
+ ChunkHandle[] after = simulation.GetChunkHandles();
Assert.Multiple(() =>
{
@@ -206,8 +207,8 @@ public void TryGetChunkHandles_UnchangedCollection_DoesNotAllocateAnotherHandleL
using var simulation = new AtmosSimulation(1, 1, 1);
simulation.CreateAndRegisterChunk(default);
- bool firstCreated = simulation.TryGetChunkHandles(-1, out long revision, out AtmosChunkHandle[] first);
- bool secondCreated = simulation.TryGetChunkHandles(revision, out long unchangedRevision, out AtmosChunkHandle[] second);
+ bool firstCreated = simulation.TryGetChunkHandles(-1, out long revision, out ChunkHandle[] first);
+ bool secondCreated = simulation.TryGetChunkHandles(revision, out long unchangedRevision, out ChunkHandle[] second);
Assert.Multiple(() =>
{
@@ -228,7 +229,7 @@ public void TryGetChunkHandles_RemoveAndRecreateSamePosition_StillAdvancesCollec
simulation.UnregisterChunk(original);
simulation.CreateAndRegisterChunk(default);
- bool changed = simulation.TryGetChunkHandles(firstRevision, out long secondRevision, out AtmosChunkHandle[] handles);
+ bool changed = simulation.TryGetChunkHandles(firstRevision, out long secondRevision, out ChunkHandle[] handles);
Assert.Multiple(() =>
{
diff --git a/tests/Numos.API.Tests/AtmosReplayTests.cs b/tests/Numos.API.Tests/AtmosReplayTests.cs
index 27eb505..a38871d 100644
--- a/tests/Numos.API.Tests/AtmosReplayTests.cs
+++ b/tests/Numos.API.Tests/AtmosReplayTests.cs
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Replay;
@@ -292,7 +293,7 @@ public void Hash_UsesCanonicalChunkOrderAndRawFloatingPointBits()
second.CreateAndRegisterChunk(new Int3(2, 0, 0));
second.CreateAndRegisterChunk(default);
Assert.That(first.ComputeStateHash(), Is.EqualTo(second.ComputeStateHash()));
- second.SetVoxelTemperature(new AtmosChunkHandle(default), 0, BitConverter.Int32BitsToSingle(unchecked((int)0x80000000)));
+ second.SetVoxelTemperature(new ChunkHandle(default), 0, BitConverter.Int32BitsToSingle(unchecked((int)0x80000000)));
Assert.That(first.ComputeStateHash(), Is.Not.EqualTo(second.ComputeStateHash()));
}
diff --git a/tests/Numos.API.Tests/AtmosSimulationContractTests.cs b/tests/Numos.API.Tests/AtmosSimulationContractTests.cs
index da36151..a380b01 100644
--- a/tests/Numos.API.Tests/AtmosSimulationContractTests.cs
+++ b/tests/Numos.API.Tests/AtmosSimulationContractTests.cs
@@ -1,3 +1,4 @@
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.Maths;
@@ -186,7 +187,7 @@ public void CreateAndRegisterChunk_ReturnsPositionHandleAndUpdatesCount()
Assert.Multiple(() =>
{
- Assert.That(handle, Is.EqualTo(new AtmosChunkHandle(position)));
+ Assert.That(handle, Is.EqualTo(new ChunkHandle(position)));
Assert.That(handle.Position, Is.EqualTo(position));
Assert.That(simulation.ChunkCount, Is.EqualTo(1));
Assert.That(simulation.GetChunkSnapshot(handle).GridPosition, Is.EqualTo(position));
@@ -653,7 +654,7 @@ public void GetChunkSnapshot_ReturnsDeepDetachedCopies()
public void ChunkOperations_WithMissingPosition_ThrowKeyNotFoundException()
{
using var simulation = new AtmosSimulation(new TestAtmosConfig(), 2, 2, 1);
- var missing = new AtmosChunkHandle(new Int3(91, -37, 12));
+ var missing = new ChunkHandle(new Int3(91, -37, 12));
Assert.Multiple(() =>
{
diff --git a/tests/Numos.API.Tests/AtmosSolverPipelineTests.cs b/tests/Numos.API.Tests/AtmosSolverPipelineTests.cs
index a5451c3..8a62d36 100644
--- a/tests/Numos.API.Tests/AtmosSolverPipelineTests.cs
+++ b/tests/Numos.API.Tests/AtmosSolverPipelineTests.cs
@@ -1,5 +1,6 @@
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
+using Numos.Chunks;
namespace Numos.API.Tests;
@@ -154,8 +155,8 @@ public void ResetToDefaults_RemovesCustomStagesAndRestoresBuiltIns()
}
private static AtmosWorld CreateTransportWorld(
- out (AtmosSimulation Simulation, AtmosChunkHandle Chunk) source,
- out (AtmosSimulation Simulation, AtmosChunkHandle Chunk) target)
+ out (AtmosSimulation Simulation, ChunkHandle Chunk) source,
+ out (AtmosSimulation Simulation, ChunkHandle Chunk) target)
{
var config = new AtmosConfig
{
@@ -179,7 +180,7 @@ private static AtmosWorld CreateTransportWorld(
return world;
}
- private static float TotalMoles(AtmosSimulation simulation, AtmosChunkHandle chunk)
+ private static float TotalMoles(AtmosSimulation simulation, ChunkHandle chunk)
{
return simulation.GetVoxelSnapshot(chunk, 0).Gases.Sum(static gas => gas.Moles);
}
diff --git a/tests/Numos.API.Tests/AtmosSolverStorageTests.cs b/tests/Numos.API.Tests/AtmosSolverStorageTests.cs
index bebc562..847fb30 100644
--- a/tests/Numos.API.Tests/AtmosSolverStorageTests.cs
+++ b/tests/Numos.API.Tests/AtmosSolverStorageTests.cs
@@ -1,3 +1,4 @@
+using Numos.Chunks;
using Numos.Collections;
using Numos.Maths;
@@ -108,7 +109,7 @@ public void InvalidRequests_RejectNullKeysNegativeLengthsAndMissingChunks()
using var simulation = new AtmosSimulation(1, 1, 1);
var chunk = simulation.CreateAndRegisterChunk(default);
object key = new();
- var missing = new AtmosChunkHandle(Int3.PosX);
+ var missing = new ChunkHandle(Int3.PosX);
Assert.Multiple(() =>
{
diff --git a/tests/Numos.API.Tests/AtmosWorldReplayTests.cs b/tests/Numos.API.Tests/AtmosWorldReplayTests.cs
index e5c7518..184011f 100644
--- a/tests/Numos.API.Tests/AtmosWorldReplayTests.cs
+++ b/tests/Numos.API.Tests/AtmosWorldReplayTests.cs
@@ -1,3 +1,5 @@
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.GasReactions;
@@ -197,7 +199,7 @@ private static AtmosConfig CreateConfig()
};
}
- private static AtmosChunkHandle CreateOpenChunk(AtmosSimulation simulation, Int3 position)
+ private static ChunkHandle CreateOpenChunk(AtmosSimulation simulation, Int3 position)
{
var chunk = simulation.CreateAndRegisterChunk(position);
simulation.SetChunkClassification(chunk, new VoxelClassification(1));
diff --git a/tests/Numos.API.Tests/AtmosWorldSolverPipelineTests.cs b/tests/Numos.API.Tests/AtmosWorldSolverPipelineTests.cs
index 9629109..73bbdb1 100644
--- a/tests/Numos.API.Tests/AtmosWorldSolverPipelineTests.cs
+++ b/tests/Numos.API.Tests/AtmosWorldSolverPipelineTests.cs
@@ -1,3 +1,5 @@
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.Maths;
@@ -19,16 +21,16 @@ public void NeighborSolver_SeesCartesianAndPortalNeighborsThroughOneView()
second.SetChunkClassification(secondChunk, new VoxelClassification(1));
var source = first.GetCellRef(firstChunk, 0);
var target = second.GetCellRef(secondChunk, 0);
- world.CreatePortal(source, target, ExplicitLinkFlags.GasTransport);
+ world.CreatePortal(source, target, AtmosLinkFlags.GasTransport);
int neighborCount = -1;
- AtmosNeighbor[] neighbors = [];
+ VoxelNeighbor[] neighbors = [];
world.Solvers.RegisterNeighborSolver(
"inspect-neighbors",
- new AtmosNeighborSelection(
+ new VoxelNeighborSelection(
"tests/gas-neighbors-v1",
true,
- static link => (link.Flags & ExplicitLinkFlags.GasTransport) != 0),
+ static link => (link.Flags & AtmosLinkFlags.GasTransport) != 0),
context =>
{
neighborCount = context.Topology.GetNeighborCount(source);
@@ -40,9 +42,9 @@ public void NeighborSolver_SeesCartesianAndPortalNeighborsThroughOneView()
Assert.Multiple(() =>
{
Assert.That(neighborCount, Is.EqualTo(3));
- Assert.That(neighbors.Count(static neighbor => neighbor.Kind == AtmosNeighborKind.Cartesian), Is.EqualTo(2));
- Assert.That(neighbors.Count(static neighbor => neighbor.Kind == AtmosNeighborKind.Explicit), Is.EqualTo(1));
- Assert.That(neighbors.Single(static neighbor => neighbor.Kind == AtmosNeighborKind.Explicit).Cell, Is.EqualTo(target));
+ Assert.That(neighbors.Count(static neighbor => neighbor.Kind == VoxelNeighborKind.Cartesian), Is.EqualTo(2));
+ Assert.That(neighbors.Count(static neighbor => neighbor.Kind == VoxelNeighborKind.Explicit), Is.EqualTo(1));
+ Assert.That(neighbors.Single(static neighbor => neighbor.Kind == VoxelNeighborKind.Explicit).Cell, Is.EqualTo(target));
});
}
@@ -57,11 +59,11 @@ public void NeighborSolver_SeesCartesianNeighborAcrossChunkBoundary()
simulation.SetChunkClassification(second, new VoxelClassification(1));
var source = simulation.GetCellRef(first, 0);
var target = simulation.GetCellRef(second, 0);
- AtmosNeighbor[] neighbors = [];
+ VoxelNeighbor[] neighbors = [];
world.Solvers.RegisterNeighborSolver(
"inspect-chunk-boundary",
- AtmosNeighborSelection.All("tests/chunk-boundary-v1"),
+ VoxelNeighborSelection.All("tests/chunk-boundary-v1"),
context => neighbors = context.Topology.GetNeighbors(source).ToArray());
world.Tick();
@@ -71,7 +73,7 @@ public void NeighborSolver_SeesCartesianNeighborAcrossChunkBoundary()
Is.EqualTo(
new[]
{
- new AtmosNeighbor(target, AtmosNeighborKind.Cartesian, ExplicitLinkFlags.None)
+ new VoxelNeighbor(target, VoxelNeighborKind.Cartesian, AtmosLinkFlags.None)
}));
}
@@ -88,7 +90,7 @@ public void Selector_IsReevaluatedOnlyWhenCompiledTopologyChanges()
world.Solvers.RegisterNeighborSolver(
"compiled-selector",
- new AtmosNeighborSelection(
+ new VoxelNeighborSelection(
"tests/compiled-selector-v1",
false,
_ =>
@@ -121,7 +123,7 @@ public void SelectorFailure_LeavesTopologyBoundaryPendingForRetry()
world.Solvers.RegisterNeighborSolver(
"fallible-selector",
- new AtmosNeighborSelection(
+ new VoxelNeighborSelection(
"tests/fallible-selector-v1",
false,
_ => failCompilation
@@ -131,9 +133,10 @@ public void SelectorFailure_LeavesTopologyBoundaryPendingForRetry()
var links = world.CreateLinks(
[
- new ExplicitLinkDefinition(
+ new ExplicitLinkDefinition(
simulation.GetCellRef(first, 0),
- simulation.GetCellRef(second, 0))
+ simulation.GetCellRef(second, 0),
+ AtmosLinkFlags.All)
]);
Assert.That(() => world.Tick(), Throws.InvalidOperationException);
@@ -222,19 +225,19 @@ public void OwnedEdges_VisitsCartesianAndExplicitEdgesOnce()
first.SetChunkClassification(firstChunk, new VoxelClassification(1));
second.SetChunkClassification(secondChunk, new VoxelClassification(1));
world.CreatePortal(first.GetCellRef(firstChunk, 0), second.GetCellRef(secondChunk, 0));
- AtmosNeighborEdge[] edges = [];
+ VoxelNeighborEdge[] edges = [];
world.Solvers.RegisterNeighborSolver(
"owned-edges",
- AtmosNeighborSelection.All("tests/owned-edges-v1"),
+ VoxelNeighborSelection.All("tests/owned-edges-v1"),
context => edges = context.Topology.GetOwnedEdges().ToArray());
world.Tick();
Assert.Multiple(() =>
{
- Assert.That(edges.Count(static edge => edge.Kind == AtmosNeighborKind.Cartesian), Is.EqualTo(1));
- Assert.That(edges.Count(static edge => edge.Kind == AtmosNeighborKind.Explicit), Is.EqualTo(1));
+ Assert.That(edges.Count(static edge => edge.Kind == VoxelNeighborKind.Cartesian), Is.EqualTo(1));
+ Assert.That(edges.Count(static edge => edge.Kind == VoxelNeighborKind.Explicit), Is.EqualTo(1));
});
}
@@ -244,7 +247,7 @@ public void WorldSolverMetadata_ParticipatesInCheckpointHashAndCompatibility()
using var world = new AtmosWorld(CreateConfig());
world.Solvers.RegisterNeighborSolver(
"authoritative-custom",
- AtmosNeighborSelection.All("tests/authoritative-custom-v1"),
+ VoxelNeighborSelection.All("tests/authoritative-custom-v1"),
_ => { });
var checkpoint = world.CaptureCheckpoint();
@@ -309,7 +312,7 @@ public void HostDefinedFlagBit_IsInvisibleToBuiltInTransportButVisibleToCustomSe
// A capability bit outside GasTransport/ThermalTransport: Numos' built-in transport stages must ignore it,
// while a host-registered selector can still pick the link out by it.
- const ExplicitLinkFlags customCapability = (ExplicitLinkFlags)(1 << 2);
+ const AtmosLinkFlags customCapability = (AtmosLinkFlags)(1 << 2);
world.CreatePortal(
first.GetCellRef(firstChunk, 0),
second.GetCellRef(secondChunk, 0),
@@ -318,7 +321,7 @@ public void HostDefinedFlagBit_IsInvisibleToBuiltInTransportButVisibleToCustomSe
bool customSolverSawLink = false;
world.Solvers.RegisterNeighborSolver(
"custom-capability-observer",
- new AtmosNeighborSelection(
+ new VoxelNeighborSelection(
"tests/custom-capability-v1",
false,
link => (link.Flags & customCapability) != 0),
@@ -344,14 +347,14 @@ public void CreateLinksPortalAndDock_AcceptHostDefinedFlagBits()
var simulation = world.CreateSimulation(2, 2, 1);
var chunk = simulation.CreateAndRegisterChunk(default);
simulation.SetChunkClassification(chunk, new VoxelClassification(1));
- const ExplicitLinkFlags customCapability = (ExplicitLinkFlags)(1 << 3);
+ const AtmosLinkFlags customCapability = (AtmosLinkFlags)(1 << 3);
Assert.Multiple(() =>
{
Assert.That(
() => world.CreateLinks(
[
- new ExplicitLinkDefinition(
+ new ExplicitLinkDefinition(
simulation.GetCellRef(chunk, 0),
simulation.GetCellRef(chunk, 3),
customCapability)
@@ -366,7 +369,7 @@ public void CreateLinksPortalAndDock_AcceptHostDefinedFlagBits()
Throws.Nothing);
Assert.That(
- () => world.CreatePortal(simulation.GetCellRef(chunk, 0), simulation.GetCellRef(chunk, 1), ExplicitLinkFlags.None),
+ () => world.CreatePortal(simulation.GetCellRef(chunk, 0), simulation.GetCellRef(chunk, 1), AtmosLinkFlags.None),
Throws.ArgumentException);
});
}
@@ -399,14 +402,14 @@ private static AtmosWorld CreateDeterministicPortalWorld(bool reversePortal)
world.CreatePortal(
reversePortal ? target : source,
reversePortal ? source : target,
- ExplicitLinkFlags.GasTransport);
+ AtmosLinkFlags.GasTransport);
world.Solvers.RegisterNeighborSolver(
"deterministic-observer",
- new AtmosNeighborSelection(
+ new VoxelNeighborSelection(
"tests/deterministic-observer-v1",
true,
- static link => (link.Flags & ExplicitLinkFlags.GasTransport) != 0),
+ static link => (link.Flags & AtmosLinkFlags.GasTransport) != 0),
context =>
{
// Traverse the complete view so ordering bugs are observable to this fixture without mutating state.
@@ -421,9 +424,9 @@ private static AtmosWorld CreateDeterministicPortalWorld(bool reversePortal)
internal static class AtmosNeighborTestExtensions
{
- internal static AtmosNeighbor[] ToArray(this AtmosNeighborEnumerable neighbors)
+ internal static VoxelNeighbor[] ToArray(this VoxelNeighborEnumerable neighbors)
{
- var result = new List();
+ var result = new List>();
foreach (var neighbor in neighbors)
result.Add(neighbor);
diff --git a/tests/Numos.API.Tests/AtmosWorldTests.cs b/tests/Numos.API.Tests/AtmosWorldTests.cs
index af1b1da..ccff90c 100644
--- a/tests/Numos.API.Tests/AtmosWorldTests.cs
+++ b/tests/Numos.API.Tests/AtmosWorldTests.cs
@@ -1,3 +1,5 @@
+using Numos.Chunks;
+using Numos.Chunks.Topology;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.Maths;
@@ -70,7 +72,7 @@ public void InterSimulationPortal_ActivatesAtTickBoundaryAndConservesGas()
var portal = world.CreatePortal(
first.GetCellRef(source, 0),
second.GetCellRef(target, 0),
- ExplicitLinkFlags.GasTransport);
+ AtmosLinkFlags.GasTransport);
Assert.That(world.ActiveLinkCount, Is.Zero);
world.Tick();
@@ -98,7 +100,7 @@ public void LinkRegistration_CanonicalizesEndpointsAndRejectsDuplicates()
var first = simulation.GetCellRef(firstChunk, 0);
var second = simulation.GetCellRef(secondChunk, 0);
- var handle = world.CreateLinks([new ExplicitLinkDefinition(second, first, ExplicitLinkFlags.GasTransport)]);
+ var handle = world.CreateLinks([new ExplicitLinkDefinition(second, first, AtmosLinkFlags.GasTransport)]);
var link = world.GetLinks(handle).Single();
Assert.Multiple(() =>
@@ -106,7 +108,7 @@ public void LinkRegistration_CanonicalizesEndpointsAndRejectsDuplicates()
Assert.That(link.First, Is.EqualTo(first));
Assert.That(link.Second, Is.EqualTo(second));
Assert.That(
- () => world.CreateLinks([new ExplicitLinkDefinition(first, second)]),
+ () => world.CreateLinks([new ExplicitLinkDefinition(first, second, AtmosLinkFlags.All)]),
Throws.ArgumentException);
});
}
@@ -120,7 +122,13 @@ public void DestroyedLinkSet_BecomesStaleAndStopsFutureTransfer()
var source = CreateOpenChunk(first);
var target = CreateOpenChunk(second);
first.AddGasToVoxel(source, 0, GasName, 1f, 300f);
- var handle = world.CreateLinks([new ExplicitLinkDefinition(first.GetCellRef(source, 0), second.GetCellRef(target, 0))]);
+ var handle = world.CreateLinks(
+ [
+ new ExplicitLinkDefinition(
+ first.GetCellRef(source, 0),
+ second.GetCellRef(target, 0),
+ AtmosLinkFlags.All)
+ ]);
world.Tick();
world.DestroyLinks(handle);
@@ -151,8 +159,8 @@ public void MultiplePortalsFromOneCell_CannotOverdrawAnySpecies()
var sourceCell = simulation.GetCellRef(source, 0);
world.CreateLinks(
[
- new ExplicitLinkDefinition(sourceCell, simulation.GetCellRef(firstTarget, 0)),
- new ExplicitLinkDefinition(sourceCell, simulation.GetCellRef(secondTarget, 0))
+ new ExplicitLinkDefinition(sourceCell, simulation.GetCellRef(firstTarget, 0), AtmosLinkFlags.All),
+ new ExplicitLinkDefinition(sourceCell, simulation.GetCellRef(secondTarget, 0), AtmosLinkFlags.All)
]);
world.Tick();
@@ -175,7 +183,7 @@ public void RemovingChunk_InvalidatesOnlyIncidentSparseTopology()
var simulation = world.CreateSimulation(1, 1, 1);
var first = CreateOpenChunk(simulation);
var second = CreateOpenChunk(simulation, new Int3(2, 0, 0));
- var handle = world.CreateLinks([new ExplicitLinkDefinition(simulation.GetCellRef(first, 0), simulation.GetCellRef(second, 0))]);
+ var handle = world.CreateLinks([new ExplicitLinkDefinition(simulation.GetCellRef(first, 0), simulation.GetCellRef(second, 0), AtmosLinkFlags.All)]);
world.Tick();
@@ -198,7 +206,7 @@ public void WorldCheckpoint_RestoresTopologyHandlesAndSimulationState()
var source = CreateOpenChunk(first);
var target = CreateOpenChunk(second);
first.AddGasToVoxel(source, 0, GasName, 1f, 300f);
- var handle = world.CreateLinks([new ExplicitLinkDefinition(first.GetCellRef(source, 0), second.GetCellRef(target, 0))]);
+ var handle = world.CreateLinks([new ExplicitLinkDefinition(first.GetCellRef(source, 0), second.GetCellRef(target, 0), AtmosLinkFlags.All)]);
world.Tick();
var checkpoint = world.CaptureCheckpoint();
@@ -234,7 +242,7 @@ public void WorldCheckpoint_RestoresPendingLinkActivation()
var source = CreateOpenChunk(first);
var target = CreateOpenChunk(second);
first.AddGasToVoxel(source, 0, GasName, 1f, 300f);
- var handle = world.CreateLinks([new ExplicitLinkDefinition(first.GetCellRef(source, 0), second.GetCellRef(target, 0))]);
+ var handle = world.CreateLinks([new ExplicitLinkDefinition(first.GetCellRef(source, 0), second.GetCellRef(target, 0), AtmosLinkFlags.All)]);
var checkpoint = world.CaptureCheckpoint();
@@ -266,7 +274,7 @@ public void WorldCheckpoint_RestoresPendingLinkRemoval()
var source = CreateOpenChunk(first);
var target = CreateOpenChunk(second);
first.AddGasToVoxel(source, 0, GasName, 1f, 300f);
- var handle = world.CreateLinks([new ExplicitLinkDefinition(first.GetCellRef(source, 0), second.GetCellRef(target, 0))]);
+ var handle = world.CreateLinks([new ExplicitLinkDefinition(first.GetCellRef(source, 0), second.GetCellRef(target, 0), AtmosLinkFlags.All)]);
world.Tick();
world.DestroyLinks(handle);
@@ -298,14 +306,14 @@ public void EquivalentRegistrationSequences_CompileToTheSameEdgeOrder()
using var secondWorld = new AtmosWorld(CreateConfig());
var firstSimulation = firstWorld.CreateSimulation(1, 1, 1);
var secondSimulation = secondWorld.CreateSimulation(1, 1, 1);
- AtmosChunkHandle[] firstChunks =
+ ChunkHandle[] firstChunks =
[
CreateOpenChunk(firstSimulation),
CreateOpenChunk(firstSimulation, new Int3(2, 0, 0)),
CreateOpenChunk(firstSimulation, new Int3(4, 0, 0))
];
- AtmosChunkHandle[] secondChunks =
+ ChunkHandle[] secondChunks =
[
CreateOpenChunk(secondSimulation),
CreateOpenChunk(secondSimulation, new Int3(2, 0, 0)),
@@ -314,18 +322,18 @@ public void EquivalentRegistrationSequences_CompileToTheSameEdgeOrder()
firstWorld.CreateLinks(
[
- new ExplicitLinkDefinition(firstSimulation.GetCellRef(firstChunks[1], 0), firstSimulation.GetCellRef(firstChunks[2], 0)),
- new ExplicitLinkDefinition(firstSimulation.GetCellRef(firstChunks[0], 0), firstSimulation.GetCellRef(firstChunks[1], 0))
+ new ExplicitLinkDefinition(firstSimulation.GetCellRef(firstChunks[1], 0), firstSimulation.GetCellRef(firstChunks[2], 0), AtmosLinkFlags.All),
+ new ExplicitLinkDefinition(firstSimulation.GetCellRef(firstChunks[0], 0), firstSimulation.GetCellRef(firstChunks[1], 0), AtmosLinkFlags.All)
]);
secondWorld.CreateLinks(
[
- new ExplicitLinkDefinition(secondSimulation.GetCellRef(secondChunks[0], 0), secondSimulation.GetCellRef(secondChunks[1], 0))
+ new ExplicitLinkDefinition(secondSimulation.GetCellRef(secondChunks[0], 0), secondSimulation.GetCellRef(secondChunks[1], 0), AtmosLinkFlags.All)
]);
secondWorld.CreateLinks(
[
- new ExplicitLinkDefinition(secondSimulation.GetCellRef(secondChunks[2], 0), secondSimulation.GetCellRef(secondChunks[1], 0))
+ new ExplicitLinkDefinition(secondSimulation.GetCellRef(secondChunks[2], 0), secondSimulation.GetCellRef(secondChunks[1], 0), AtmosLinkFlags.All)
]);
firstWorld.Tick();
@@ -349,7 +357,7 @@ public void PortalAndOrdinaryNeighbor_DoNotOverdrawTheirSharedSource()
world.CreatePortal(
local.GetCellRef(localChunk, 0),
remote.GetCellRef(remoteChunk, 0),
- ExplicitLinkFlags.GasTransport);
+ AtmosLinkFlags.GasTransport);
world.Tick();
@@ -384,8 +392,8 @@ public void DockBatch_TransfersAcrossEveryPairAndUndocksAsOneUnit()
var dock = world.CreateDock(
[
- new ExplicitLinkDefinition(first.GetCellRef(firstChunk, 0), second.GetCellRef(secondChunk, 0)),
- new ExplicitLinkDefinition(first.GetCellRef(firstChunk, 1), second.GetCellRef(secondChunk, 1))
+ new ExplicitLinkDefinition(first.GetCellRef(firstChunk, 0), second.GetCellRef(secondChunk, 0), AtmosLinkFlags.All),
+ new ExplicitLinkDefinition(first.GetCellRef(firstChunk, 1), second.GetCellRef(secondChunk, 1), AtmosLinkFlags.All)
]);
world.Tick();
@@ -411,7 +419,7 @@ public void DestroyedSimulation_InvalidatesIncidentLinksAndReusedIdGeneration()
var second = world.CreateSimulation(1, 1, 1);
var firstChunk = CreateOpenChunk(first);
var secondChunk = CreateOpenChunk(second);
- var links = world.CreateLinks([new ExplicitLinkDefinition(first.GetCellRef(firstChunk, 0), second.GetCellRef(secondChunk, 0))]);
+ var links = world.CreateLinks([new ExplicitLinkDefinition(first.GetCellRef(firstChunk, 0), second.GetCellRef(secondChunk, 0), AtmosLinkFlags.All)]);
world.Tick();
var removedId = second.Id;
@@ -441,7 +449,7 @@ public void ExplicitTransport_RunsAtTheSharedAdvectionBarrierBeforeLaterStages()
world.CreatePortal(
first.GetCellRef(source, 0),
second.GetCellRef(target, 0),
- ExplicitLinkFlags.GasTransport);
+ AtmosLinkFlags.GasTransport);
float observedMoles = 0f;
world.Solvers.RegisterAfter(
@@ -472,7 +480,7 @@ public void ThermalOnlyPortal_UsesThermodynamicsCadenceAndConservesEnergy()
world.CreatePortal(
first.GetCellRef(firstChunk, 0),
second.GetCellRef(secondChunk, 0),
- ExplicitLinkFlags.ThermalTransport);
+ AtmosLinkFlags.ThermalTransport);
float heatCapacity = world.Config.GetMolarHeatCapacityAtConstantVolume(0);
float initialEnergy = heatCapacity * 600f;
@@ -515,7 +523,7 @@ public void DisabledExplicitThermalTransport_StopsThermalPortalTransport()
world.CreatePortal(
first.GetCellRef(firstChunk, 0),
second.GetCellRef(secondChunk, 0),
- ExplicitLinkFlags.ThermalTransport);
+ AtmosLinkFlags.ThermalTransport);
world.Solvers.SetEnabled(AtmosBuiltInSolvers.ExplicitThermalTransport, false);
@@ -547,7 +555,7 @@ public void DisabledThermodynamics_DoesNotStopThermalPortalTransport()
world.CreatePortal(
first.GetCellRef(firstChunk, 0),
second.GetCellRef(secondChunk, 0),
- ExplicitLinkFlags.ThermalTransport);
+ AtmosLinkFlags.ThermalTransport);
// Disabling intra-chunk thermodynamics no longer disables portal thermal transport: the two stages are
// independent now, unlike the pre-split fused domain.
@@ -583,7 +591,7 @@ private static AtmosConfig CreateConfig()
return config;
}
- private static AtmosChunkHandle CreateOpenChunk(
+ private static ChunkHandle CreateOpenChunk(
AtmosSimulation simulation,
Int3 position = default)
{
@@ -593,7 +601,7 @@ private static AtmosChunkHandle CreateOpenChunk(
return chunk;
}
- private static float TotalMoles(AtmosSimulation simulation, AtmosChunkHandle chunk)
+ private static float TotalMoles(AtmosSimulation simulation, ChunkHandle chunk)
{
return simulation.GetChunkSnapshot(chunk).Gases.Sum(gas => gas.Moles.Sum());
}
diff --git a/tests/Numos.CoreSim.IntegrationTests/CrossChunkFlowTests.cs b/tests/Numos.CoreSim.IntegrationTests/CrossChunkFlowTests.cs
index 3d8df34..85ba916 100644
--- a/tests/Numos.CoreSim.IntegrationTests/CrossChunkFlowTests.cs
+++ b/tests/Numos.CoreSim.IntegrationTests/CrossChunkFlowTests.cs
@@ -1,4 +1,5 @@
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Datatypes.Snapshots;
using Numos.Maths;
@@ -348,7 +349,7 @@ public void BoundaryFlow_ParallelInjectionBatchesRemainDeterministic()
Int3[] sourcePositions = [Int3.NegX, Int3.PosX, Int3.NegY, Int3.PosY];
float[] sourceTemperatures = [250f, 300f, 350f, 400f];
- var sources = new AtmosChunkHandle[sourcePositions.Length];
+ var sources = new ChunkHandle[sourcePositions.Length];
for (int sourceIndex = 0; sourceIndex < sourcePositions.Length; sourceIndex++)
{
sources[sourceIndex] = SimTestHelpers.CreateOpenChunk(simulation, sourcePositions[sourceIndex]);
@@ -861,7 +862,7 @@ public void VacuumCleanup_UsesPressurizedNeighborAcrossChunkBoundary()
Is.EqualTo(0.1f).Within(SimTestHelpers.Tolerance));
}
- private static AtmosChunkHandle CreateIsolatedVoxel(
+ private static ChunkHandle CreateIsolatedVoxel(
AtmosSimulation simulation, Int3 position,
int x, int y, int z, VoxelClassification classification)
{
diff --git a/tests/Numos.CoreSim.IntegrationTests/SimTestHelpers.cs b/tests/Numos.CoreSim.IntegrationTests/SimTestHelpers.cs
index 6e9c2f3..19da073 100644
--- a/tests/Numos.CoreSim.IntegrationTests/SimTestHelpers.cs
+++ b/tests/Numos.CoreSim.IntegrationTests/SimTestHelpers.cs
@@ -1,4 +1,5 @@
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Datatypes.Snapshots;
using Numos.Maths;
@@ -40,7 +41,7 @@ internal static AtmosConfig CreateDeterministicConfig()
};
}
- internal static AtmosChunkHandle CreateOpenChunk(
+ internal static ChunkHandle CreateOpenChunk(
AtmosSimulation simulation, Int3 position,
VoxelClassification? classification = null)
{
@@ -50,7 +51,7 @@ internal static AtmosChunkHandle CreateOpenChunk(
}
internal static void SetAllTemperatures(
- AtmosSimulation simulation, AtmosChunkHandle chunk,
+ AtmosSimulation simulation, ChunkHandle chunk,
int width, int height, int depth, float temperature = DefaultTemperature)
{
for (int z = 0; z < depth; z++)
diff --git a/tests/Numos.CoreSim.IntegrationTests/ThermodynamicsIntegrationTests.cs b/tests/Numos.CoreSim.IntegrationTests/ThermodynamicsIntegrationTests.cs
index 31df70a..4e8abf7 100644
--- a/tests/Numos.CoreSim.IntegrationTests/ThermodynamicsIntegrationTests.cs
+++ b/tests/Numos.CoreSim.IntegrationTests/ThermodynamicsIntegrationTests.cs
@@ -1,4 +1,5 @@
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Datatypes.Snapshots;
using Numos.Maths;
@@ -177,7 +178,7 @@ public void CrossChunkThermalDiffusion_MultipleFacesUseOneSymmetricSnapshot()
using var simulation = new AtmosSimulation(config, 1, 1, 1);
var center = SimTestHelpers.CreateOpenChunk(simulation, new Int3(0, 0, 0));
Int3[] neighborPositions = [Int3.NegX, Int3.PosX, Int3.NegY, Int3.PosY];
- AtmosChunkHandle[] neighbors = neighborPositions
+ ChunkHandle[] neighbors = neighborPositions
.Select(position => SimTestHelpers.CreateOpenChunk(simulation, position))
.ToArray();
@@ -1086,7 +1087,7 @@ private static AtmosConfig CreateCondensationConfig()
return config;
}
- private static AtmosChunkHandle CreateIsolatedVoxel(
+ private static ChunkHandle CreateIsolatedVoxel(
AtmosSimulation simulation, Int3 position,
int x, int y, int z, VoxelClassification classification, float temperature)
{
diff --git a/tests/Numos.CoreSim.Tests/AtmosChunkTopologyTests.cs b/tests/Numos.CoreSim.Tests/AtmosChunkTopologyTests.cs
index 5339889..e050cfa 100644
--- a/tests/Numos.CoreSim.Tests/AtmosChunkTopologyTests.cs
+++ b/tests/Numos.CoreSim.Tests/AtmosChunkTopologyTests.cs
@@ -1,3 +1,4 @@
+using Numos.Chunks;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.Maths;
diff --git a/tests/Numos.Serialization.Tests/NumosReplaySerializerTests.cs b/tests/Numos.Serialization.Tests/NumosReplaySerializerTests.cs
index 63a451b..442f9ce 100644
--- a/tests/Numos.Serialization.Tests/NumosReplaySerializerTests.cs
+++ b/tests/Numos.Serialization.Tests/NumosReplaySerializerTests.cs
@@ -1,4 +1,5 @@
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Replay;
@@ -98,7 +99,7 @@ public void RoundTrip_PreservesReplayAndBuildsImportedTimeline()
operation.Sequence <= imported.Position.OperationSequence);
imported.SimulateFromHere();
- replaySimulation.SetVoxelTemperature(new AtmosChunkHandle(default), 1, 350f);
+ replaySimulation.SetVoxelTemperature(new ChunkHandle(default), 1, 350f);
var branch = imported.CaptureReplay();
Assert.Multiple(() =>
{
diff --git a/tests/Numos.Serialization.Tests/ReplayWireFormatGoldenTests.cs b/tests/Numos.Serialization.Tests/ReplayWireFormatGoldenTests.cs
index 4854636..18d1e01 100644
--- a/tests/Numos.Serialization.Tests/ReplayWireFormatGoldenTests.cs
+++ b/tests/Numos.Serialization.Tests/ReplayWireFormatGoldenTests.cs
@@ -1,4 +1,5 @@
using Numos.API;
+using Numos.Chunks;
using Numos.CoreSim;
using Numos.CoreSim.Datatypes.Primitives;
using Numos.CoreSim.Replay;
@@ -213,7 +214,7 @@ private static NumosWorldReplayDocument BuildGoldenWorldReplayDocument()
return new NumosWorldReplayDocument(metadata, archive);
}
- private static AtmosChunkHandle CreateOpenChunk(AtmosSimulation simulation, Int3 position)
+ private static ChunkHandle CreateOpenChunk(AtmosSimulation simulation, Int3 position)
{
var chunk = simulation.CreateAndRegisterChunk(position);
simulation.SetChunkClassification(chunk, new VoxelClassification(1));