diff --git a/storage/unity_file.go b/storage/unity_file.go new file mode 100644 index 0000000..83a898a --- /dev/null +++ b/storage/unity_file.go @@ -0,0 +1,93 @@ +package storage + +import ( + "bytes" + "encoding/json" + "os" + + "git.nicholasnovak.io/nnovak/spatial-db/world" +) + +// A `UnityFile` is a collection of chunks, stored as a single file on disk +// +// This is done because with worlds that span millions of chunks, the speed to +// access individual files slows down +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 +} + +func CreateUnityFile(fileName string) (UnityFile, error) { + var u UnityFile + + f, err := os.Create(fileName) + if err != nil { + return u, err + } + + u.fd = f + u.metadata = make(map[world.ChunkPos]int) + + return u, nil +} + +func (u UnityFile) Size() int { + return u.fileSize +} + +func (u *UnityFile) WriteChunk(data world.ChunkData) error { + var encoded bytes.Buffer + + // Encode the chunk first + if err := json.NewEncoder(&encoded).Encode(data); err != nil { + return err + } + + encodedSize := encoded.Len() + + // Go to the end of the file + u.fd.Seek(0, u.fileSize) + // Write the encoded contents to the file + if _, err := u.fd.Write(encoded.Bytes()); err != nil { + return err + } + + // Update the metadata with the new file + u.metadata[data.Pos] = u.fileSize + u.fileSize += encodedSize + + return nil +} + +func (u UnityFile) WriteMetadataFile(fileName string) error { + fd, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + return err + } + defer fd.Close() + + if err := json.NewEncoder(fd).Encode(&u.metadata); err != nil { + return err + } + + return nil +} + +func (u *UnityFile) ReadMetadataFile(fileName string) error { + fd, err := os.Open(fileName) + if err != nil { + return err + } + + if err := json.NewDecoder(fd).Decode(&u.metadata); err != nil { + return err + } + + return nil +} + +func (u UnityFile) ReadChunk() world.ChunkData { + return world.ChunkData{} +} diff --git a/storage/unity_file_test.go b/storage/unity_file_test.go new file mode 100644 index 0000000..00bac95 --- /dev/null +++ b/storage/unity_file_test.go @@ -0,0 +1,30 @@ +package storage + +import ( + "os" + "path" + "testing" +) + +func TestCreateUnityFile(t *testing.T) { + tempDir, err := os.MkdirTemp("", "unity") + if err != nil { + t.Fatalf("Error creating temporary directory: %v", err) + } + defer os.RemoveAll(tempDir) + + // Create an empty file + u, err := CreateUnityFile(path.Join(tempDir, "test-unity")) + if err != nil { + t.Fatalf("Error creating unity file: %v", err) + } + + if u.Size() != 0 { + t.Fatalf("Expected size of file to be %v, got %v", 0, u.Size()) + } + + // Save the metadata + if err := u.WriteMetadataFile(path.Join(tempDir, "test-unity.metadata")); err != nil { + t.Fatalf("Got an error saving the empty metadata: %v", err) + } +}