diff --git a/world/palette.go b/world/palette.go index a057c77..81c95ac 100644 --- a/world/palette.go +++ b/world/palette.go @@ -5,47 +5,82 @@ package world // // 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 +type SectionPalette struct { + ids map[PaletteIndex]BlockID +} -// `Compact` removes all duplicate states from a palette and returns a new palette -func (p SectionPalette) Compact() SectionPalette { - ids := make(map[BlockID]bool) +func NewSectionPalette() SectionPalette { + return SectionPalette{ + ids: make(map[PaletteIndex]BlockID), + } +} + +// `Compact` removes all duplicate states from a palette and returns if the +// palette was modified +func (p *SectionPalette) Compact() bool { + newIds := make(map[BlockID]PaletteIndex) + + var wasResized bool // Filter out the duplicate block ids - for _, blockId := range p { - ids[blockId] = true + for index, blockId := range p.ids { + newIds[blockId] = index } - var np SectionPalette - - for blockId := range ids { - np = append(np, blockId) + // If there was not a resize, return instantly + if len(newIds) != len(p.ids) { + wasResized = true + } else { + return false } - return np + ids := make(map[PaletteIndex]BlockID) + + for blockId, index := range newIds { + ids[index] = blockId + } + + p.ids = ids + + return wasResized } // `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 { +func (p *SectionPalette) IndexFor(state BlockID) PaletteIndex { + var maxIndex PaletteIndex + // If the state is already in the palette, return it - for index, blockId := range p { + for index, blockId := range p.ids { + if index > maxIndex { + maxIndex = index + } + if state == blockId { return PaletteIndex(index) } } // Otherwise, insert it into the palette and return the index - p = append(p, state) + p.ids[maxIndex+1] = state - return PaletteIndex(len(p) - 1) + return maxIndex + 1 } // `State` returns the state for a palette's index func (p SectionPalette) State(index PaletteIndex) BlockID { - return p[index] + return p.ids[index] +} + +// `ReplaceIndex` replaces the block state at the given palette' index +// +// This function is used to quickly zero a section with a given block state in +// constant time, and should not be used for any other purpose. Otherwise, set +// the block state within the section to the result of `IndexFor`. +func (p *SectionPalette) ReplaceIndex(index PaletteIndex, state BlockID) { + p.ids[index] = state } type PaletteIndex byte diff --git a/world/palette_test.go b/world/palette_test.go new file mode 100644 index 0000000..d81964e --- /dev/null +++ b/world/palette_test.go @@ -0,0 +1,64 @@ +package world + +import ( + "runtime/debug" + "testing" +) + +func newPaletteWith(ids []BlockID) *SectionPalette { + p := NewSectionPalette() + + for _, id := range ids { + p.IndexFor(id) + } + + return &p +} + +func checkPaletteEqual(t *testing.T, p *SectionPalette, index PaletteIndex, expected BlockID) { + if p.State(index) != expected { + debug.PrintStack() + t.Fatalf("Expected to get state %v at index %v, got %v", expected, index, p.State(index)) + } +} + +func TestInsertPalette(t *testing.T) { + p := NewSectionPalette() + + ids := []BlockID{Empty, Generic} + + for _, id := range ids { + index := p.IndexFor(id) + if id != p.State(index) { + t.Fatalf("Fetching index for id %v: got index %v which returned %v", id, index, p.State(index)) + } + } +} + +func TestReplaceCompactPalettte(t *testing.T) { + p := newPaletteWith([]BlockID{Empty, Generic}) + + zeroIndex := p.IndexFor(Empty) + checkPaletteEqual(t, p, zeroIndex, Empty) + + // Zero out the chunk + genIndex := p.IndexFor(Generic) + checkPaletteEqual(t, p, genIndex, Generic) + p.ReplaceIndex(genIndex, Empty) + + // Check that everything now returns zero + checkPaletteEqual(t, p, zeroIndex, Empty) + checkPaletteEqual(t, p, genIndex, Empty) + + // Test for compaction + wasCompacted := p.Compact() + if !wasCompacted { + t.Fatalf("Palette should have been compacted") + } + + // Test to see that we don't compact again + wasCompacted = p.Compact() + if wasCompacted { + t.Fatalf("Palette should have not been compacted again") + } +}