diff --git a/storage/unity_file.go b/storage/unity_file.go index 83a898a..a674e72 100644 --- a/storage/unity_file.go +++ b/storage/unity_file.go @@ -3,6 +3,7 @@ package storage import ( "bytes" "encoding/json" + "io" "os" "git.nicholasnovak.io/nnovak/spatial-db/world" @@ -16,7 +17,12 @@ type UnityFile struct { fd *os.File fileSize int // metadata maps the position of a chunk to its start index within the file - metadata map[world.ChunkPos]int + metadata map[world.ChunkPos]fileMetadata +} + +type fileMetadata struct { + startOffset int + fileSize int } func CreateUnityFile(fileName string) (UnityFile, error) { @@ -28,7 +34,7 @@ func CreateUnityFile(fileName string) (UnityFile, error) { } u.fd = f - u.metadata = make(map[world.ChunkPos]int) + u.metadata = make(map[world.ChunkPos]fileMetadata) return u, nil } @@ -55,7 +61,10 @@ func (u *UnityFile) WriteChunk(data world.ChunkData) error { } // Update the metadata with the new file - u.metadata[data.Pos] = u.fileSize + u.metadata[data.Pos] = fileMetadata{ + startOffset: u.fileSize, + fileSize: encodedSize, + } u.fileSize += encodedSize return nil @@ -88,6 +97,17 @@ func (u *UnityFile) ReadMetadataFile(fileName string) error { return nil } -func (u UnityFile) ReadChunk() world.ChunkData { - return world.ChunkData{} +func (u UnityFile) ReadChunk(pos world.ChunkPos) (world.ChunkData, error) { + m := u.metadata[pos] + + u.fd.Seek(0, m.startOffset) + + fileReader := io.LimitReader(u.fd, int64(m.fileSize)) + + var data world.ChunkData + if err := json.NewDecoder(fileReader).Decode(&data); err != nil { + return world.ChunkData{}, err + } + + return data, nil } diff --git a/storage/unity_file_test.go b/storage/unity_file_test.go index 00bac95..709e1ba 100644 --- a/storage/unity_file_test.go +++ b/storage/unity_file_test.go @@ -3,7 +3,10 @@ package storage import ( "os" "path" + "reflect" "testing" + + "git.nicholasnovak.io/nnovak/spatial-db/world" ) func TestCreateUnityFile(t *testing.T) { @@ -28,3 +31,91 @@ func TestCreateUnityFile(t *testing.T) { t.Fatalf("Got an error saving the empty metadata: %v", err) } } + +func TestWriteSingleFile(t *testing.T) { + tempDir, err := os.MkdirTemp("", "unity") + if err != nil { + t.Fatalf("Error creating temporary directory: %v", err) + } + defer os.RemoveAll(tempDir) + + u, err := CreateUnityFile(path.Join(tempDir, "test-unity")) + if err != nil { + t.Fatalf("Error creating unity file: %v", err) + } + + // Write a single file + var data world.ChunkData + data.Sections[0].BlockStates[0] = 2 + + if err := u.WriteChunk(data); err != nil { + t.Fatalf("Error writing chunk: %v", err) + } + + // Read the chunk back + readChunk, err := u.ReadChunk(data.Pos) + if err != nil { + t.Fatalf("Error reading chunk: %v", err) + } + + // Compare the chunks directly + if !reflect.DeepEqual(data, readChunk) { + t.Fatalf("Chunks differed, sent %v, received %v", data, readChunk) + } +} + +func TestWriteMultipleFiles(t *testing.T) { + tempDir, err := os.MkdirTemp("", "unity") + if err != nil { + t.Fatalf("Error creating temporary directory: %v", err) + } + defer os.RemoveAll(tempDir) + + u, err := CreateUnityFile(path.Join(tempDir, "test-unity")) + if err != nil { + t.Fatalf("Error creating unity file: %v", err) + } + + var ( + chunk1 world.ChunkData + chunk2 world.ChunkData + chunk3 world.ChunkData + ) + chunk1.Pos = world.ChunkPos{ + X: 0, + Z: 0, + } + chunk1.Sections[0].BlockStates[0] = 2 + chunk2.Sections[0].BlockStates[0] = 3 + chunk2.Pos = world.ChunkPos{ + X: 1, + Z: 0, + } + chunk3.Sections[0].BlockStates[0] = 4 + chunk3.Pos = world.ChunkPos{ + X: 2, + Z: 0, + } + + chunks := []world.ChunkData{chunk1, chunk2, chunk3} + + // Write all chunks + for _, data := range chunks { + if err := u.WriteChunk(data); err != nil { + t.Fatalf("Error writing chunk: %v", err) + } + } + + // Read the chunks back + for _, data := range chunks { + readChunk, err := u.ReadChunk(data.Pos) + if err != nil { + t.Fatalf("Error reading chunk: %v", err) + } + + // Compare the chunks directly + if !reflect.DeepEqual(data, readChunk) { + t.Fatalf("Chunks differed, sent %v, received %v", data, readChunk) + } + } +} diff --git a/world/world_position.go b/world/world_position.go index 51e6af6..cd985f1 100644 --- a/world/world_position.go +++ b/world/world_position.go @@ -3,6 +3,8 @@ package world import ( "fmt" "math/rand" + "strconv" + "strings" ) type BlockPos struct { @@ -36,7 +38,25 @@ type ChunkPos struct { } func (cp ChunkPos) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf("%d %d", cp.Z, cp.Z)), nil + return []byte(fmt.Sprintf("%d %d", cp.X, cp.Z)), nil +} + +func (cp *ChunkPos) UnmarshalText(text []byte) error { + words := strings.Split(string(text), " ") + x, err := strconv.Atoi(words[0]) + if err != nil { + return err + } + + z, err := strconv.Atoi(words[1]) + if err != nil { + return err + } + + cp.X = x + cp.Z = z + + return nil } func (cp ChunkPos) ToFileName() string {