diff --git a/hashserver.txt b/hashserver.txt new file mode 100644 index 0000000..4530252 --- /dev/null +++ b/hashserver.txt @@ -0,0 +1,26 @@ +goos: linux +goarch: amd64 +pkg: git.nicholasnovak.io/nnovak/spatial-db +cpu: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz +BenchmarkInsertClusteredPoints-8 7030724 294.0 ns/op 139 B/op 0 allocs/op +BenchmarkInsertClusteredPoints-8 5987259 198.1 ns/op 10 B/op 0 allocs/op +BenchmarkInsertClusteredPoints-8 5793637 484.0 ns/op 337 B/op 0 allocs/op +BenchmarkInsertClusteredPoints-8 5644594 207.5 ns/op 0 B/op 0 allocs/op +BenchmarkInsertClusteredPoints-8 5815582 211.0 ns/op 0 B/op 0 allocs/op +BenchmarkInsertClusteredPoints-8 5618958 211.7 ns/op 2 B/op 0 allocs/op +BenchmarkInsertClusteredPoints-8 5603455 215.2 ns/op 10 B/op 0 allocs/op +BenchmarkInsertClusteredPoints-8 5454136 223.0 ns/op 13 B/op 0 allocs/op +BenchmarkInsertClusteredPoints-8 5391891 694.1 ns/op 718 B/op 0 allocs/op +BenchmarkInsertClusteredPoints-8 5574357 228.4 ns/op 0 B/op 0 allocs/op +BenchmarkInsertSparserPoints-8 5355165 218.3 ns/op 0 B/op 0 allocs/op +BenchmarkInsertSparserPoints-8 5385463 221.0 ns/op 1 B/op 0 allocs/op +BenchmarkInsertSparserPoints-8 5265614 224.1 ns/op 2 B/op 0 allocs/op +BenchmarkInsertSparserPoints-8 5269447 229.2 ns/op 7 B/op 0 allocs/op +BenchmarkInsertSparserPoints-8 5167845 231.7 ns/op 19 B/op 0 allocs/op +BenchmarkInsertSparserPoints-8 4997976 240.3 ns/op 23 B/op 0 allocs/op +BenchmarkInsertSparserPoints-8 4914524 772.6 ns/op 1584 B/op 0 allocs/op +BenchmarkInsertSparserPoints-8 1535679 692.3 ns/op 0 B/op 0 allocs/op +BenchmarkInsertSparserPoints-8 1852569 617.0 ns/op 0 B/op 0 allocs/op +BenchmarkInsertSparserPoints-8 2007783 568.2 ns/op 1 B/op 0 allocs/op +PASS +ok git.nicholasnovak.io/nnovak/spatial-db 114.742s diff --git a/server/hashserver.go b/server/hashserver.go index dafdedc..f520875 100644 --- a/server/hashserver.go +++ b/server/hashserver.go @@ -25,7 +25,7 @@ func (hs *HashServer) SetStorageRoot(path string) { for _, section := range data.Sections { for blockIndex, blockState := range section.BlockStates { pos := data.IndexToBlockPos(blockIndex) - hs.blocks[pos] = blockState + hs.blocks[pos] = section.Palette.State(blockState) } } } diff --git a/world/chunk.go b/world/chunk.go index 943d08a..960d7d2 100644 --- a/world/chunk.go +++ b/world/chunk.go @@ -16,15 +16,17 @@ const ( // `ChunkData` represents the contents of a "chunk", which is a column of voxels // in world space type ChunkData struct { - Pos ChunkPos `json:"pos"` + // The position of the chunk, in world space + Pos ChunkPos `json:"pos"` + // The column of sections Sections [ChunkSectionCount]ChunkSection `json:"sections"` } // `ChunkSection' is a fixed-size cube that stores the data in a chunk type ChunkSection struct { - // The count of full blocks in the chunk - BlockCount uint `json:"block_count"` - BlockStates [16 * 16 * 16]BlockID `json:"block_states"` + // A look-up-table of each section index to its value + Palette SectionPalette `json:"palette"` + BlockStates [16 * 16 * 16]PaletteIndex `json:"block_states"` } func rem_euclid(a, b int) int { @@ -39,18 +41,12 @@ func IndexOfBlock(pos BlockPos) int { return (baseY * chunkSliceSize) + (baseZ * 16) + baseX } -func (cs *ChunkSection) UpdateBlockAtIndex(index int, targetState BlockID) { - // TODO: Keep track of the block count - - cs.BlockStates[index] = targetState -} - func (cs *ChunkSection) UpdateBlock(pos BlockPos, targetState BlockID) { - cs.BlockStates[IndexOfBlock(pos)] = targetState + cs.BlockStates[IndexOfBlock(pos)] = cs.Palette.IndexFor(targetState) } func (cs *ChunkSection) FetchBlock(pos BlockPos) BlockID { - return cs.BlockStates[IndexOfBlock(pos)] + return cs.Palette.State(cs.BlockStates[IndexOfBlock(pos)]) } func (cd *ChunkData) SectionFor(pos BlockPos) *ChunkSection { @@ -68,6 +64,8 @@ func (cd *ChunkData) IndexToBlockPos(index int) BlockPos { } } +// Conversion from Minecraft chunks + func extractPaletteIndexes(compressed int64) [16]byte { var outputs [16]byte var outputIndex int @@ -113,13 +111,12 @@ func (cd *ChunkData) FromMCAChunk(other save.Chunk) { if section.BlockStates.Palette[paletteIndex].Name == "minecraft:air" { state = Empty } else { - cd.Sections[sectionIndex].BlockCount += 1 state = Generic } // TODO: Remove this workaround for larger bit sizes in palettes if blockIndex < 4096 { - currentSection.BlockStates[blockIndex] = state + currentSection.BlockStates[blockIndex] = currentSection.Palette.IndexFor(state) } } diff --git a/world/palette.go b/world/palette.go new file mode 100644 index 0000000..a057c77 --- /dev/null +++ b/world/palette.go @@ -0,0 +1,51 @@ +package world + +// `SectionPalette` is a "palette", which is a sort of look-up-table (LUT) between +// an index into the LUT, and the resulting `BlockID` +// +// This palette is unique to each section, and allows ranges of blocks to be +// changed in constant time, with the downside of having to "compact" the palette +type SectionPalette []BlockID + +// `Compact` removes all duplicate states from a palette and returns a new palette +func (p SectionPalette) Compact() SectionPalette { + ids := make(map[BlockID]bool) + + // Filter out the duplicate block ids + for _, blockId := range p { + ids[blockId] = true + } + + var np SectionPalette + + for blockId := range ids { + np = append(np, blockId) + } + + return np +} + +// `IndexFor` returns the palette index for a specified block id +// +// If the block id does not exist, it is placed in the palette and the index +// is returned +func (p SectionPalette) IndexFor(state BlockID) PaletteIndex { + // If the state is already in the palette, return it + for index, blockId := range p { + if state == blockId { + return PaletteIndex(index) + } + } + + // Otherwise, insert it into the palette and return the index + p = append(p, state) + + return PaletteIndex(len(p) - 1) +} + +// `State` returns the state for a palette's index +func (p SectionPalette) State(index PaletteIndex) BlockID { + return p[index] +} + +type PaletteIndex byte