feat: World.SetBlock writes through to IChunkModificationStore
This commit is contained in:
@@ -6,6 +6,7 @@ using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using VoxelEngine.Rendering;
|
||||
using VoxelEngine.World.Blocks;
|
||||
using VoxelEngine.World.Chunk;
|
||||
using VoxelEngine.World.Persistence;
|
||||
using VoxelEngine.World.WorldGen;
|
||||
|
||||
namespace VoxelEngine.Core;
|
||||
@@ -40,7 +41,7 @@ public sealed class Game : GameWindow
|
||||
this._shader = new Shader("Assets/shaders/chunk.vert", "Assets/shaders/chunk.frag");
|
||||
this._texture = new Texture("Assets/textures/VoxelShadeAtlas.png");
|
||||
|
||||
this._world = new World.World(this._registry);
|
||||
this._world = new World.World(this._registry, new InMemoryChunkModificationStore());
|
||||
|
||||
int seed = Game.UseSeed ? Game.Seed : new Random().Next();
|
||||
|
||||
|
||||
88
VoxelEngine.Tests/WorldTests.cs
Normal file
88
VoxelEngine.Tests/WorldTests.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using OpenTK.Mathematics;
|
||||
using VoxelEngine.World;
|
||||
using VoxelEngine.World.Blocks;
|
||||
using VoxelEngine.World.Persistence;
|
||||
using Xunit;
|
||||
using VoxelChunk = VoxelEngine.World.Chunk.Chunk;
|
||||
|
||||
namespace VoxelEngine.Tests;
|
||||
|
||||
public class WorldTests
|
||||
{
|
||||
private static (IWorld, BlockRegistry, IChunkModificationStore) Create()
|
||||
{
|
||||
var registry = new BlockRegistry();
|
||||
var store = new InMemoryChunkModificationStore();
|
||||
IWorld world = new World.World(registry, store);
|
||||
return (world, registry, store);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetBlock_LoadedChunk_WritesBlockAndRecordsDiff()
|
||||
{
|
||||
var (world, registry, store) = Create();
|
||||
var chunkPos = new Vector3i(0, 0, 0);
|
||||
world.SetChunk(chunkPos, new VoxelChunk());
|
||||
|
||||
bool result = world.SetBlock(2, 3, 4, registry.Stone.Id);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(registry.Stone.Id, world.GetBlock(2, 3, 4).Id);
|
||||
Assert.True(store.HasDiff(chunkPos));
|
||||
Assert.True(store.TryGetDiff(chunkPos, out var diff));
|
||||
int expectedIndex = (2 * VoxelChunk.Size + 3) * VoxelChunk.Size + 4;
|
||||
Assert.Equal(registry.Stone.Id, diff![expectedIndex]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetBlock_UnloadedChunk_RecordsDiffButReturnsFalse()
|
||||
{
|
||||
var (world, registry, store) = Create();
|
||||
var chunkPos = new Vector3i(5, 0, 0);
|
||||
|
||||
bool result = world.SetBlock(5 * VoxelChunk.Size, 0, 0, registry.Stone.Id);
|
||||
|
||||
Assert.False(result);
|
||||
Assert.True(store.HasDiff(chunkPos));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetBlock_UnloadedChunk_ReturnsAir()
|
||||
{
|
||||
var (world, registry, _) = Create();
|
||||
Assert.Same(registry.Air, world.GetBlock(0, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetBlock_NegativeCoords_FloorsToCorrectChunk()
|
||||
{
|
||||
var (world, registry, store) = Create();
|
||||
var chunkPos = new Vector3i(-1, 0, 0);
|
||||
world.SetChunk(chunkPos, new VoxelChunk());
|
||||
|
||||
bool result = world.SetBlock(-1, 0, 0, registry.Stone.Id);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(registry.Stone.Id, world.GetBlock(-1, 0, 0).Id);
|
||||
Assert.True(store.HasDiff(chunkPos));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveChunk_RemovesAndGetBlockReturnsAir()
|
||||
{
|
||||
var (world, registry, _) = Create();
|
||||
var chunkPos = new Vector3i(0, 0, 0);
|
||||
world.SetChunk(chunkPos, new VoxelChunk());
|
||||
world.RemoveChunk(chunkPos);
|
||||
|
||||
Assert.Null(world.GetChunk(chunkPos));
|
||||
Assert.Same(registry.Air, world.GetBlock(0, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModificationStore_AccessorReturnsConstructorInjectedStore()
|
||||
{
|
||||
var (world, _, store) = Create();
|
||||
Assert.Same(store, world.ModificationStore);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Mathematics;
|
||||
using VoxelEngine.World.Blocks;
|
||||
using VoxelEngine.World.Persistence;
|
||||
|
||||
namespace VoxelEngine.World;
|
||||
|
||||
public interface IWorld
|
||||
{
|
||||
public void SetChunk(Vector3i chunkPos, Chunk.Chunk chunk);
|
||||
public Chunk.Chunk? GetChunk(Vector3i chunkPos);
|
||||
public IBlock GetBlock(int worldX, int worldY, int worldZ);
|
||||
IChunkModificationStore ModificationStore { get; }
|
||||
|
||||
void SetChunk(Vector3i chunkPos, Chunk.Chunk chunk);
|
||||
void RemoveChunk(Vector3i chunkPos);
|
||||
Chunk.Chunk? GetChunk(Vector3i chunkPos);
|
||||
IBlock GetBlock(int worldX, int worldY, int worldZ);
|
||||
bool SetBlock(int worldX, int worldY, int worldZ, ushort blockId);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections;
|
||||
using System.Collections;
|
||||
using OpenTK.Mathematics;
|
||||
using VoxelEngine.World.Blocks;
|
||||
using VoxelEngine.World.Persistence;
|
||||
|
||||
namespace VoxelEngine.World;
|
||||
|
||||
@@ -9,55 +10,69 @@ public sealed class World : IWorld, IEnumerable<KeyValuePair<Vector3i, Chunk.Chu
|
||||
private readonly Dictionary<Vector3i, Chunk.Chunk> _chunks = new();
|
||||
private readonly BlockRegistry _registry;
|
||||
|
||||
public World(BlockRegistry registry)
|
||||
public IChunkModificationStore ModificationStore { get; }
|
||||
|
||||
public World(BlockRegistry registry, IChunkModificationStore modStore)
|
||||
{
|
||||
this._registry = registry;
|
||||
this.ModificationStore = modStore;
|
||||
}
|
||||
|
||||
public void SetChunk(Vector3i chunkPos, Chunk.Chunk chunk)
|
||||
{
|
||||
this._chunks[chunkPos] = chunk;
|
||||
}
|
||||
public void SetChunk(Vector3i chunkPos, Chunk.Chunk chunk) => this._chunks[chunkPos] = chunk;
|
||||
|
||||
public Chunk.Chunk? GetChunk(Vector3i chunkPos)
|
||||
{
|
||||
return this._chunks.GetValueOrDefault(chunkPos);
|
||||
}
|
||||
public void RemoveChunk(Vector3i chunkPos) => this._chunks.Remove(chunkPos);
|
||||
|
||||
public Chunk.Chunk? GetChunk(Vector3i chunkPos) => this._chunks.GetValueOrDefault(chunkPos);
|
||||
|
||||
public IBlock GetBlock(int worldX, int worldY, int worldZ)
|
||||
{
|
||||
Vector3i chunkPos = new(
|
||||
World.FloorDiv(worldX, Chunk.Chunk.Size),
|
||||
World.FloorDiv(worldY, Chunk.Chunk.Size),
|
||||
World.FloorDiv(worldZ, Chunk.Chunk.Size)
|
||||
);
|
||||
World.FloorDiv(worldZ, Chunk.Chunk.Size));
|
||||
|
||||
Chunk.Chunk? chunk = this.GetChunk(chunkPos);
|
||||
if (chunk == null)
|
||||
{
|
||||
return this._registry.Air;
|
||||
}
|
||||
|
||||
int localX = worldX - chunkPos.X * Chunk.Chunk.Size;
|
||||
int localY = worldY - chunkPos.Y * Chunk.Chunk.Size;
|
||||
int localZ = worldZ - chunkPos.Z * Chunk.Chunk.Size;
|
||||
if (chunk == null) return this._registry.Air;
|
||||
|
||||
int localX = worldX - chunkPos.X * Chunk.Chunk.Size;
|
||||
int localY = worldY - chunkPos.Y * Chunk.Chunk.Size;
|
||||
int localZ = worldZ - chunkPos.Z * Chunk.Chunk.Size;
|
||||
return this._registry.Get(chunk[localX, localY, localZ]);
|
||||
}
|
||||
|
||||
public bool SetBlock(int worldX, int worldY, int worldZ, ushort blockId)
|
||||
{
|
||||
Vector3i chunkPos = new(
|
||||
World.FloorDiv(worldX, Chunk.Chunk.Size),
|
||||
World.FloorDiv(worldY, Chunk.Chunk.Size),
|
||||
World.FloorDiv(worldZ, Chunk.Chunk.Size));
|
||||
|
||||
Chunk.Chunk? chunk = this.GetChunk(chunkPos);
|
||||
|
||||
int localX = worldX - chunkPos.X * Chunk.Chunk.Size;
|
||||
int localY = worldY - chunkPos.Y * Chunk.Chunk.Size;
|
||||
int localZ = worldZ - chunkPos.Z * Chunk.Chunk.Size;
|
||||
int localIndex = (localX * Chunk.Chunk.Size + localY) * Chunk.Chunk.Size + localZ;
|
||||
|
||||
// For a loaded chunk, no-op writes shouldn't pollute the diff store.
|
||||
if (chunk != null && chunk[localX, localY, localZ] == blockId) return true;
|
||||
|
||||
this.ModificationStore.RecordEdit(chunkPos, localIndex, blockId);
|
||||
|
||||
if (chunk == null) return false;
|
||||
|
||||
chunk[localX, localY, localZ] = blockId;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int FloorDiv(int value, int size)
|
||||
{
|
||||
int quotient = value / size;
|
||||
if (value % size != 0 && value < 0)
|
||||
{
|
||||
quotient--;
|
||||
}
|
||||
return quotient;
|
||||
int q = value / size;
|
||||
if (value % size != 0 && value < 0) q--;
|
||||
return q;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<Vector3i, Chunk.Chunk>> GetEnumerator()
|
||||
=> this._chunks.GetEnumerator();
|
||||
public IEnumerator<KeyValuePair<Vector3i, Chunk.Chunk>> GetEnumerator() => this._chunks.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user