Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 69 additions & 6 deletions Obsidian.API/ChunkData/DataContainer.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
using Obsidian.API.Utilities;
using System.Threading;

namespace Obsidian.API.ChunkData;

public abstract class DataContainer<T>
{
public bool IsEmpty { get; }
public byte BitsPerEntry => (byte)DataArray.BitsPerEntry;
private readonly Lock storageLock = new();

public int EntryCount { get; protected set; }

public virtual bool IsEmpty { get; }
public byte BitsPerEntry => (byte)this.Palette.BitCount;

public abstract IPalette<T> Palette { get; internal set; }

internal abstract DataArray DataArray { get; private protected set; }
internal virtual DataArray? DataArray { get; private protected set; }

public virtual int GetIndex(int x, int y, int z) => (y << this.BitsPerEntry | z) << this.BitsPerEntry | x;

public DataContainer(int entryCount, IPalette<T> palette)
{
this.EntryCount = entryCount;
this.Palette = palette;
this.DataArray = BitsPerEntry != 0 ? new DataArray(BitsPerEntry, entryCount) : null;
}

public DataContainer(int entryCount, IPalette<T> palette, T defaultValue) : this(entryCount, palette)
{
this.Palette.GetOrAddId(defaultValue);
}

public void GrowDataArray()
{
if (Palette.BitCount <= DataArray.BitsPerEntry)
Expand All @@ -20,11 +38,56 @@ public void GrowDataArray()
DataArray = DataArray.Grow(Palette.BitCount);
}

public abstract void Set(int x, int y, int z, T blockState);
public void InitializeDataArray()
{
if (this.Palette.BitCount == 0)
return;

this.DataArray = new DataArray(this.Palette.BitCount, this.EntryCount);
}

public virtual void Set(int x, int y, int z, T blockState)
{
lock (this.storageLock)
{
var blockIndex = GetIndex(x, y, z);

public abstract T Get(int x, int y, int z);
int paletteId = Palette.GetOrAddId(blockState);

public abstract void WriteTo(INetStreamWriter writer);
if (this.Palette.BitCount == 0)
return;

DataArray ??= new DataArray(this.Palette.BitCount, this.EntryCount);

this.GrowDataArray();

DataArray[blockIndex] = paletteId;
}
}

public virtual T Get(int x, int y, int z)
{
lock (this.storageLock)
{
if (this.Palette.BitCount == 0)
return Palette.GetValueFromIndex(0);

var index = GetIndex(x, y, z);
int storageId = DataArray[index];

return Palette.GetValueFromIndex(storageId);
}
}

public virtual void WriteTo(INetStreamWriter writer)
{
writer.WriteByte(this.BitsPerEntry);

this.Palette.WriteTo(writer);

if (this.DataArray != null)
writer.WriteLongArray(this.DataArray.storage);
}

public abstract DataContainer<T> Clone();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;

namespace Obsidian.ChunkData;
namespace Obsidian.API.ChunkData.Palettes;

public abstract class BaseIndirectPalette<T> : IPalette<T>
{
Expand Down Expand Up @@ -81,10 +81,16 @@ public virtual IPalette<T> Clone()

public void WriteTo(INetStreamWriter writer)
{
writer.WriteVarInt(Count);

ReadOnlySpan<int> values = GetSpan();

if (this.BitCount == 0)
{
writer.WriteVarInt(values[0]);
return;
}

writer.WriteVarInt(Count);

for (int i = 0; i < values.Length; ++i)
writer.WriteVarInt(values[i]);
}
Expand All @@ -95,3 +101,4 @@ protected ReadOnlySpan<int> GetSpan()
return MemoryMarshal.CreateReadOnlySpan(ref first, Count);
}
}

6 changes: 3 additions & 3 deletions Obsidian.API/_Enums/HeightmapType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ public enum HeightmapType : int
WorldSurfaceWG,
WorldSurface,

OceanFloorWG,
OceanFloor,
//OceanFloorWG,
//OceanFloor,

MotionBlocking,
MotionBlockingNoLeaves,
//MotionBlockingNoLeaves,
}
26 changes: 2 additions & 24 deletions Obsidian/ChunkData/BiomeContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,14 @@ public sealed class BiomeContainer : DataContainer<Biome>

internal override DataArray DataArray { get; private protected set; }

internal BiomeContainer(byte bitsPerEntry = 2)
{
this.Palette = bitsPerEntry.DetermineBiomePalette();
this.DataArray = new(bitsPerEntry, 64);
}
internal BiomeContainer(byte bitsPerEntry = 0) : base(64, bitsPerEntry.DetermineBiomePalette(), Biome.Plains) { }

private BiomeContainer(IPalette<Biome> palette, DataArray dataArray)
private BiomeContainer(IPalette<Biome> palette, DataArray dataArray) : base(64, palette)
{
Palette = palette;
DataArray = dataArray;
}

public override void Set(int x, int y, int z, Biome biome)
{
var index = this.GetIndex(x, y, z);

var paletteIndex = this.Palette.GetOrAddId(biome);

this.GrowDataArray();

this.DataArray[index] = paletteIndex;
}

public override Biome Get(int x, int y, int z)
{
var storageId = this.DataArray[this.GetIndex(x, y, z)];

return this.Palette.GetValueFromIndex(storageId);
}

public override void WriteTo(INetStreamWriter writer)
{
writer.WriteByte(this.BitsPerEntry);
Expand Down
72 changes: 9 additions & 63 deletions Obsidian/ChunkData/BlockStateContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,30 @@

public sealed class BlockStateContainer : DataContainer<IBlock>
{
private const int MaxEntryCount = 4096;
public override IPalette<IBlock> Palette { get; internal set; }

public bool IsEmpty => DataArray.storage.Length == 0;
public override bool IsEmpty => this.Palette.Count == 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Verify IsEmpty logic for single-value air palettes.

The IsEmpty check now uses Palette.Count == 0, but a section filled with only air would have Palette.Count == 1 (containing just the air block entry). This means an empty section would incorrectly return IsEmpty = false.

Verify whether:

  1. The palette can ever have Count == 0 (since air is added by default in ChunkSection constructor line 27-28 of relevant snippet)
  2. The intended behavior for sections containing only air blocks
  3. Whether IsEmpty should check if all entries are air rather than palette size

Consider this logic instead:

-public override bool IsEmpty => this.Palette.Count == 0;
+public override bool IsEmpty => this.Palette.Count == 1 && this.Palette.TryGetId(BlocksRegistry.Air, out _);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public override bool IsEmpty => this.Palette.Count == 0;
public override bool IsEmpty => this.Palette.Count == 1
&& this.Palette.TryGetId(BlocksRegistry.Air, out _);
🤖 Prompt for AI Agents
In Obsidian/ChunkData/BlockStateContainer.cs around line 8, the IsEmpty getter
uses Palette.Count == 0 which misses the common case where the palette contains
a single air entry; change IsEmpty to return true when the palette is empty OR
when it contains exactly one entry that represents air (e.g. Palette.Count == 1
&& Palette[0].IsAir / Palette[0].Equals(BlockState.Air) or equivalent in your
codebase). Also confirm the constructor behavior that ensures air is added by
default and adjust the check accordingly so sections filled only with air return
IsEmpty = true.


internal override DataArray DataArray { get; private protected set; }
internal BlockStateContainer(byte bitsPerEntry = 0) : base(MaxEntryCount, bitsPerEntry.DetermineBlockPalette(), BlocksRegistry.Air) { }


#if CACHE_VALID_BLOCKS
private readonly DirtyCache<short> validBlockCount;
#endif

internal BlockStateContainer(byte bitsPerEntry = 4)
{
DataArray = new DataArray(bitsPerEntry, 4096);
Palette = bitsPerEntry.DetermineBlockPalette();

#if CACHE_VALID_BLOCKS
validBlockCount = new(GetNonAirBlocks);
#endif
}

private BlockStateContainer(IPalette<IBlock> palette, DataArray dataArray)
private BlockStateContainer(IPalette<IBlock> palette, DataArray dataArray) : base(MaxEntryCount, palette)
{
Palette = palette;
DataArray = dataArray;

#if CACHE_VALID_BLOCKS
validBlockCount = new(GetNonAirBlocks);
#endif
}

public override void Set(int x, int y, int z, IBlock blockState)
{
#if CACHE_VALID_BLOCKS
validBlockCount.SetDirty();
#endif
var blockIndex = GetIndex(x, y, z);

int paletteId = Palette.GetOrAddId(blockState);

this.GrowDataArray();

DataArray[blockIndex] = paletteId;
}

public override IBlock Get(int x, int y, int z)
{
int storageId = DataArray[GetIndex(x, y, z)];

return Palette.GetValueFromIndex(storageId);
}

public override void WriteTo(INetStreamWriter writer)
{
#if CACHE_VALID_BLOCKS
var validBlocks = validBlockCount.GetValue();
#else
var validBlocks = GetNonAirBlocks();
#endif

writer.WriteShort(validBlocks);
writer.WriteByte(BitsPerEntry);

Palette.WriteTo(writer);

writer.WriteLongArray(DataArray.storage);
}

public void Fill(IBlock block)
{
#if CACHE_VALID_BLOCKS
validBlockCount.SetDirty();
#endif
int index = Palette.GetOrAddId(block);
for (int i = 0; i < 16 * 16 * 16; i++)
{
DataArray[i] = index;
}
if (this.DataArray != null)
writer.WriteLongArray(DataArray.storage);
}

private short GetNonAirBlocks()
Expand All @@ -94,7 +40,7 @@ private short GetNonAirBlocks()
goto TWO_INDEXES;

// 1 1 1
for (int i = 0; i < 16 * 16 * 16; i++)
for (int i = 0; i < MaxEntryCount; i++)
{
int index = DataArray[i];
if (index != indexOne && index != indexTwo && index != indexThree)
Expand Down Expand Up @@ -124,7 +70,7 @@ private short GetNonAirBlocks()

// 1 0 0
ONE_INDEX:
for (int i = 0; i < 16 * 16 * 16; i++)
for (int i = 0; i < MaxEntryCount; i++)
{
int index = DataArray[i];
if (index != indexOne)
Expand All @@ -134,7 +80,7 @@ private short GetNonAirBlocks()

// 1 1 0
TWO_INDEXES:
for (int i = 0; i < 16 * 16 * 16; i++)
for (int i = 0; i < MaxEntryCount; i++)
{
int index = DataArray[i];
if (index != indexOne && index != indexTwo)
Expand Down
2 changes: 1 addition & 1 deletion Obsidian/ChunkData/ChunkSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public sealed class ChunkSection : IChunkSection

private byte[] blockLight = new byte[2048];

public ChunkSection(byte bitsPerBlock = 4, byte bitsPerBiome = 2, int? yBase = null)
public ChunkSection(byte bitsPerBlock = 0, byte bitsPerBiome = 0, int? yBase = null)
{
this.BlockStateContainer = new BlockStateContainer(bitsPerBlock);
this.BiomeContainer = new BiomeContainer(bitsPerBiome);
Expand Down
1 change: 0 additions & 1 deletion Obsidian/ChunkData/DataContainer.cs

This file was deleted.

7 changes: 1 addition & 6 deletions Obsidian/ChunkData/GlobalBiomePalette.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,10 @@
public class GlobalBiomePalette : IPalette<Biome>
{
public int[] Values => throw new NotSupportedException();
public int BitCount { get; }
public int BitCount { get; } = CodecRegistry.Biomes.GlobalBitsPerEntry;
public int Count => throw new NotSupportedException();
public bool IsFull => false;

public GlobalBiomePalette(int bitCount)
{
this.BitCount = bitCount;
}

public bool TryGetId(Biome biome, out int id)
{
id = (int)biome;
Expand Down
14 changes: 3 additions & 11 deletions Obsidian/ChunkData/GlobalBlockStatePalette.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
using Obsidian.Net;
using Obsidian.Registries;
namespace Obsidian.ChunkData;

namespace Obsidian.ChunkData;

public class GlobalBlockStatePalette : IPalette<IBlock>
public class GlobalBlockStatePalette() : IPalette<IBlock>
{
public int[] Values => throw new NotSupportedException();
public int BitCount { get; }
public int BitCount { get; } = 15;
public int Count => throw new NotSupportedException();

public bool IsFull => false;

public GlobalBlockStatePalette(int bitCount)
{
this.BitCount = bitCount;
}

public bool TryGetId(IBlock block, out int id)
{
id = block.GetHashCode();
Expand Down
6 changes: 0 additions & 6 deletions Obsidian/ChunkData/Heightmap.cs

This file was deleted.

5 changes: 3 additions & 2 deletions Obsidian/ChunkData/IndirectPalette.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using Obsidian.Exceptions;
using Obsidian.API.ChunkData.Palettes;
using Obsidian.Exceptions;

namespace Obsidian.ChunkData;

public sealed class IndirectPalette : BaseIndirectPalette<IBlock>, IPalette<IBlock>
public sealed class IndirectPalette : BaseIndirectPalette<IBlock>
{
public IndirectPalette(byte bitCount) : base(bitCount)
{
Expand Down
6 changes: 4 additions & 2 deletions Obsidian/ChunkData/InternalIndirectPalette.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Runtime.CompilerServices;
using Obsidian.API.ChunkData.Palettes;
using System.Runtime.CompilerServices;

namespace Obsidian.ChunkData;
internal sealed class InternalIndirectPalette<T> : BaseIndirectPalette<T>, IPalette<T> where T : struct

internal sealed class InternalIndirectPalette<T> : BaseIndirectPalette<T> where T : struct
{
public InternalIndirectPalette(byte bitCount) : base(bitCount)
{
Expand Down
Loading
Loading