diff --git a/level/rrrr.go b/level/rrrr.go index 267e5ae..cd935f9 100644 --- a/level/rrrr.go +++ b/level/rrrr.go @@ -16,8 +16,17 @@ var BpeNotAvailableErr = errors.New("bpe not available") var OutOfBoundsErr = errors.New("out of bounds") var AvailableBpeMustBeLenOneErr = errors.New("available bpe must be 1") var NoAvailableBpeErr = errors.New("no available bpe") +var Bpe0PalletMustBeLenOneErr = errors.New("bpe 0 pallet must be 1") +var RealInvalidPaletteIndexErr = errors.New("invalid palette index") +var AvailableBpeMustBeSortedErr = errors.New("available bpe must be sorted") +var DirectModeCantResizeErr = errors.New("direct mode cannot resize") func (r RealStorageFormat) Import(data []uint64, bpe uint8, palette []int32) (s *RealStorage, err error) { + if !slices.IsSorted(r.AvailableBpes) { + err = AvailableBpeMustBeSortedErr + return + } + if !slices.Contains(r.AvailableBpes, bpe) { err = BpeNotAvailableErr return @@ -25,7 +34,13 @@ func (r RealStorageFormat) Import(data []uint64, bpe uint8, palette []int32) (s s = new(RealStorage) s.format = r s.resize(bpe) - s.data = data + if bpe == 0 && len(palette) != 1 { + err = Bpe0PalletMustBeLenOneErr + return + } + if data != nil { + s.data = data + } s.palette = palette return } @@ -66,15 +81,15 @@ func (r *RealStorage) resize(bpe uint8) { r.maxAtCurrentBpe = 1 return } - elementsPerLong := int32(64 / int(bpe)) - numberOfLongs := int(math.Ceil(float64(r.format.Len) / float64(elementsPerLong))) + numberOfElements := int32(64 / int(bpe)) + numberOfLongs := int(math.Ceil(float64(r.format.Len) / float64(numberOfElements))) newData := make([]uint64, numberOfLongs) mask := (uint64(1) << uint32(bpe)) - 1 maxAtCurrentBpe := int32(1 << bpe) if r.bpe == 0 { - r.data = newData r.bpe = bpe - r.elementsPerLong = elementsPerLong + r.elementsPerLong = numberOfElements + r.data = newData r.mask = mask r.maxAtCurrentBpe = maxAtCurrentBpe return @@ -87,15 +102,17 @@ func (r *RealStorage) resize(bpe uint8) { } else { v = int32((r.data[i/r.elementsPerLong] >> ((i % r.elementsPerLong) * int32(r.bpe))) & r.mask) } - newData[i/elementsPerLong] |= uint64(v) << ((i % elementsPerLong) * int32(bpe)) + r.data[i/r.elementsPerLong] |= uint64(v) << ((i % r.elementsPerLong) * int32(bpe)) } + if resizeDirect { + r.palette = nil + } + r.elementsPerLong = numberOfElements r.data = newData - r.bpe = bpe - r.elementsPerLong = elementsPerLong r.mask = mask r.maxAtCurrentBpe = maxAtCurrentBpe - return + return } func (r *RealStorage) Get(i int32) (s int32, err error) { @@ -103,12 +120,26 @@ func (r *RealStorage) Get(i int32) (s int32, err error) { err = OutOfBoundsErr return } + if r.bpe == 0 { // single mode + if len(r.palette) != 1 { + err = Bpe0PalletMustBeLenOneErr + return + } + s = r.palette[0] + return + } + v := int32((r.data[i/r.elementsPerLong] >> ((i % r.elementsPerLong) * int32(r.bpe))) & r.mask) if r.palette == nil { // direct s = v return } - s = r.palette[v] // should allways be true cause data checking + if v < 0 || v >= int32(len(r.palette)) { + err = RealInvalidPaletteIndexErr + return + } + + s = r.palette[v] return } @@ -127,13 +158,19 @@ func (r *RealStorage) Set(i int32, v int32) (err error) { if iv == -1 { iv, err = r.appendPalette(v) } + v = int32(iv) } + r.set(i, v) return } func (r *RealStorage) appendPalette(v int32) (i int, err error) { - i = len(r.palette) + 1 - if i <= int(r.maxAtCurrentBpe) { + i = len(r.palette) + if r.palette == nil { + err = DirectModeCantResizeErr + return + } + if i < int(r.maxAtCurrentBpe) { r.palette = append(r.palette, v) return } @@ -144,10 +181,17 @@ func (r *RealStorage) appendPalette(v int32) (i int, err error) { bpei := slices.Index(r.format.AvailableBpes, r.bpe) if bpei == len(r.format.AvailableBpes)-1 { - err = CannotGrowPalletErr + err = NoAvailableBpeErr return } newBpe := r.format.AvailableBpes[bpei+1] r.resize(newBpe) + if r.palette == nil { + i = int(v) // direct mode + return + } + + r.palette = append(r.palette, v) + i = len(r.palette) - 1 return } diff --git a/level/rrrr_test.go b/level/rrrr_test.go new file mode 100644 index 0000000..a5edd1c --- /dev/null +++ b/level/rrrr_test.go @@ -0,0 +1,423 @@ +package level_test + +import ( + "testing" + + "github.com/admin-else/strom/level" +) + +func heightmapFormat() level.RealStorageFormat { + return level.RealStorageFormat{ + AvailableBpes: []uint8{9}, + BiggestDirect: true, + Len: 256, + } +} + +func blockStorageFormat() level.RealStorageFormat { + return level.RealStorageFormat{ + AvailableBpes: []uint8{0, 4, 5, 6, 7, 8, 15}, + BiggestDirect: true, + Len: 4096, + } +} + +func TestRealStorageImportDataDirect(t *testing.T) { + format := heightmapFormat() + data := make([]uint64, 256) + data[0] = 42 + + s, err := format.ImportDataDirect(data) + if err != nil { + t.Fatalf("ImportDataDirect failed: %v", err) + } + + got, err := s.Get(0) + if err != nil { + t.Fatalf("Get failed: %v", err) + } + if got != 42 { + t.Errorf("Get(0) = %d, want 42", got) + } +} + +func TestRealStorageImportDataDirectMultipleBpe(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{4, 8}, + BiggestDirect: true, + Len: 256, + } + + _, err := format.ImportDataDirect([]uint64{1}) + if err != level.AvailableBpeMustBeLenOneErr { + t.Errorf("expected AvailableBpeMustBeLenOneErr, got %v", err) + } +} + +func TestRealStorageImportUnsortedBpe(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{9, 4}, + BiggestDirect: true, + Len: 256, + } + + _, err := format.Import([]uint64{1}, 4, nil) + if err != level.AvailableBpeMustBeSortedErr { + t.Errorf("expected AvailableBpeMustBeSortedErr, got %v", err) + } +} + +func TestRealStorageImportBpeNotAvailable(t *testing.T) { + format := heightmapFormat() + + _, err := format.Import([]uint64{1}, 4, nil) + if err != level.BpeNotAvailableErr { + t.Errorf("expected BpeNotAvailableErr, got %v", err) + } +} + +func TestRealStorageGetSet(t *testing.T) { + format := blockStorageFormat() + s, err := format.Import(nil, 4, []int32{0, 1, 2, 3}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + s.Set(0, 1) + s.Set(1, 2) + s.Set(2, 3) + + for i, want := range []int32{1, 2, 3} { + got, err := s.Get(int32(i)) + if err != nil { + t.Fatalf("Get(%d) failed: %v", i, err) + } + if got != want { + t.Errorf("Get(%d) = %d, want %d", i, got, want) + } + } +} + +func TestRealStorageOutOfBounds(t *testing.T) { + format := blockStorageFormat() + s, err := format.Import(nil, 4, []int32{0, 1}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + _, err = s.Get(4096) + if err != level.OutOfBoundsErr { + t.Errorf("expected OutOfBoundsErr, got %v", err) + } + + _, err = s.Get(-1) + if err != level.OutOfBoundsErr { + t.Errorf("expected OutOfBoundsErr, got %v", err) + } +} + +func TestRealStorageBpeZero(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{0, 4, 8}, + BiggestDirect: true, + Len: 64, + } + + s, err := format.Import(nil, 0, []int32{42}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + for i := int32(0); i < 64; i++ { + got, err := s.Get(i) + if err != nil { + t.Fatalf("Get(%d) failed: %v", i, err) + } + if got != 42 { + t.Errorf("Get(%d) = %d, want 42", i, got) + } + } +} + +func TestRealStorageBpeZeroPaletteLen(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{0, 4, 8}, + BiggestDirect: true, + Len: 64, + } + + _, err := format.Import(nil, 0, []int32{1, 2}) + if err != level.Bpe0PalletMustBeLenOneErr { + t.Errorf("expected Bpe0PalletMustBeLenOneErr, got %v", err) + } +} + +func TestRealStorageResize(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{4, 8}, + BiggestDirect: false, + Len: 16, + } + + s, err := format.Import(nil, 4, []int32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + for i := int32(0); i < 16; i++ { + s.Set(i, i) + } + + for i := int32(0); i < 16; i++ { + got, err := s.Get(i) + if err != nil { + t.Fatalf("Get(%d) failed: %v", i, err) + } + if got != i { + t.Errorf("Get(%d) = %d, want %d", i, got, i) + } + } +} + +func TestRealStorageResizePreservesData(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{4, 5, 6, 8}, + BiggestDirect: false, + Len: 8, + } + + s, err := format.Import(nil, 4, []int32{0, 1}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + s.Set(0, 10) + s.Set(1, 20) + s.Set(2, 30) + s.Set(3, 40) + + for i, want := range []int32{10, 20, 30, 40} { + got, err := s.Get(int32(i)) + if err != nil { + t.Fatalf("Get(%d) failed: %v", i, err) + } + if got != want { + t.Errorf("Get(%d) = %d, want %d", i, got, want) + } + } +} + +func TestRealStoragePaletteGrowth(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{4, 5, 6, 8}, + BiggestDirect: false, + Len: 16, + } + + s, err := format.Import(nil, 4, []int32{0}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + s.Set(0, 10) + s.Set(1, 20) + + got, err := s.Get(0) + if err != nil { + t.Fatalf("Get(0) failed: %v", err) + } + if got != 10 { + t.Errorf("Get(0) = %d, want 10", got) + } + + got, err = s.Get(1) + if err != nil { + t.Fatalf("Get(1) failed: %v", err) + } + if got != 20 { + t.Errorf("Get(1) = %d, want 20", got) + } +} + +func TestRealStorageNoAvailableBpe(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{4}, + BiggestDirect: false, + Len: 16, + } + + s, err := format.Import(nil, 4, []int32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + err = s.Set(0, 16) + if err != level.NoAvailableBpeErr { + t.Errorf("expected NoAvailableBpeErr, got %v", err) + } +} + +func TestRealStorageBiggestDirect(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{4, 8}, + BiggestDirect: true, + Len: 64, + } + + s, err := format.Import(nil, 8, nil) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + for i := int32(0); i < 64; i++ { + s.Set(i, i) + } + + for i := int32(0); i < 64; i++ { + got, err := s.Get(i) + if err != nil { + t.Fatalf("Get(%d) failed: %v", i, err) + } + if got != i { + t.Errorf("Get(%d) = %d, want %d", i, got, i) + } + } +} + +func TestRealStorageDirectModeNoPalette(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{9}, + BiggestDirect: true, + Len: 256, + } + + data := make([]uint64, 36) + data[0] = 0x1FFFF + + s, err := format.ImportDataDirect(data) + if err != nil { + t.Fatalf("ImportDataDirect failed: %v", err) + } + + got, err := s.Get(0) + if err != nil { + t.Fatalf("Get(0) failed: %v", err) + } + if got != 0x1FFFF { + t.Errorf("Get(0) = %d, want %d", got, 0x1FFFF) + } +} + +func TestRealStorageInvalidPaletteIndex(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{4, 8}, + BiggestDirect: false, + Len: 16, + } + + data := []uint64{0xFFFF} + s, err := format.Import(data, 4, []int32{0, 1}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + _, err = s.Get(0) + if err != level.RealInvalidPaletteIndexErr { + t.Errorf("expected RealInvalidPaletteIndexErr, got %v", err) + } +} + +func TestRealStorageOverwrite(t *testing.T) { + format := blockStorageFormat() + s, err := format.Import(nil, 4, []int32{0, 1, 2}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + s.Set(0, 1) + s.Set(0, 2) + s.Set(0, 1) + + got, err := s.Get(0) + if err != nil { + t.Fatalf("Get(0) failed: %v", err) + } + if got != 1 { + t.Errorf("Get(0) = %d, want 1", got) + } +} + +func TestRealStorageSetOutOfBounds(t *testing.T) { + format := blockStorageFormat() + s, err := format.Import(nil, 4, []int32{0, 1}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + err = s.Set(4096, 1) + if err != level.OutOfBoundsErr { + t.Errorf("expected OutOfBoundsErr, got %v", err) + } + + err = s.Set(-1, 1) + if err != level.OutOfBoundsErr { + t.Errorf("expected OutOfBoundsErr, got %v", err) + } +} + +func TestRealStorageGetAfterResize(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{4, 5, 6, 8}, + BiggestDirect: false, + Len: 32, + } + + s, err := format.Import(nil, 4, []int32{0}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + for i := int32(0); i < 16; i++ { + s.Set(i, i) + } + + for i := int32(0); i < 16; i++ { + got, err := s.Get(i) + if err != nil { + t.Fatalf("Get(%d) failed: %v", i, err) + } + if got != i { + t.Errorf("Get(%d) = %d, want %d", i, got, i) + } + } +} + +func TestRealStorageResizeDirect(t *testing.T) { + format := level.RealStorageFormat{ + AvailableBpes: []uint8{4, 8}, + BiggestDirect: true, + Len: 64, + } + + s, err := format.Import(nil, 4, []int32{}) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + for i := int32(0); i < 64; i++ { + err = s.Set(i, i) + if err != nil { + t.Fatalf("Set(%d) failed: %v", i, err) + } + } + + for i := int32(0); i < 64; i++ { + got, err := s.Get(i) + if err != nil { + t.Fatalf("Get(%d) failed: %v", i, err) + } + if got != i { + t.Errorf("Get(%d) = %d, want %d", i, got, i) + } + } +}