2023-12-11 01:33:39 -08:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2023-12-11 09:22:50 -08:00
|
|
|
"io"
|
2023-12-13 23:33:10 -08:00
|
|
|
"io/fs"
|
2023-12-11 01:33:39 -08:00
|
|
|
"os"
|
|
|
|
|
2023-12-13 23:48:53 -08:00
|
|
|
"github.com/NickyBoy89/spatial-db/world"
|
2023-12-11 01:33:39 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
2023-12-11 09:22:50 -08:00
|
|
|
metadata map[world.ChunkPos]fileMetadata
|
|
|
|
}
|
|
|
|
|
|
|
|
type fileMetadata struct {
|
2023-12-11 09:57:11 -08:00
|
|
|
StartOffset int `json:"start_offset"`
|
|
|
|
FileSize int `json:"file_size"`
|
2023-12-11 01:33:39 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func CreateUnityFile(fileName string) (UnityFile, error) {
|
|
|
|
var u UnityFile
|
|
|
|
|
|
|
|
f, err := os.Create(fileName)
|
|
|
|
if err != nil {
|
|
|
|
return u, err
|
|
|
|
}
|
|
|
|
|
|
|
|
u.fd = f
|
2023-12-11 09:22:50 -08:00
|
|
|
u.metadata = make(map[world.ChunkPos]fileMetadata)
|
2023-12-11 01:33:39 -08:00
|
|
|
|
|
|
|
return u, nil
|
|
|
|
}
|
|
|
|
|
2023-12-11 16:38:46 -08:00
|
|
|
func OpenUnityFile(fileName, metadataName string) (UnityFile, error) {
|
|
|
|
var u UnityFile
|
|
|
|
|
|
|
|
// Read the file
|
|
|
|
f, err := os.Open(fileName)
|
|
|
|
if err != nil {
|
|
|
|
return u, err
|
|
|
|
}
|
|
|
|
u.fd = f
|
|
|
|
|
|
|
|
if err := u.ReadMetadataFile(metadataName); err != nil {
|
|
|
|
return u, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return u, nil
|
|
|
|
}
|
|
|
|
|
2023-12-11 01:33:39 -08:00
|
|
|
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
|
2023-12-11 16:38:46 -08:00
|
|
|
u.fd.Seek(int64(u.fileSize), io.SeekStart)
|
2023-12-11 01:33:39 -08:00
|
|
|
// 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
|
2023-12-11 09:22:50 -08:00
|
|
|
u.metadata[data.Pos] = fileMetadata{
|
2023-12-11 09:57:11 -08:00
|
|
|
StartOffset: u.fileSize,
|
|
|
|
FileSize: encodedSize,
|
2023-12-11 09:22:50 -08:00
|
|
|
}
|
2023-12-11 01:33:39 -08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-12-11 09:22:50 -08:00
|
|
|
func (u UnityFile) ReadChunk(pos world.ChunkPos) (world.ChunkData, error) {
|
2023-12-13 23:33:10 -08:00
|
|
|
m, contains := u.metadata[pos]
|
|
|
|
if !contains {
|
|
|
|
return world.ChunkData{}, fs.ErrNotExist
|
|
|
|
}
|
2023-12-11 09:22:50 -08:00
|
|
|
|
2023-12-11 16:38:46 -08:00
|
|
|
u.fd.Seek(int64(m.StartOffset), io.SeekStart)
|
2023-12-11 09:22:50 -08:00
|
|
|
|
2023-12-11 09:57:11 -08:00
|
|
|
fileReader := io.LimitReader(u.fd, int64(m.FileSize))
|
2023-12-11 09:22:50 -08:00
|
|
|
|
|
|
|
var data world.ChunkData
|
|
|
|
if err := json.NewDecoder(fileReader).Decode(&data); err != nil {
|
|
|
|
return world.ChunkData{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return data, nil
|
2023-12-11 01:33:39 -08:00
|
|
|
}
|
2023-12-11 09:57:11 -08:00
|
|
|
|
2023-12-11 16:38:46 -08:00
|
|
|
func (u UnityFile) ReadAllChunks() ([]world.ChunkData, error) {
|
|
|
|
chunks := []world.ChunkData{}
|
|
|
|
|
|
|
|
for pos := range u.metadata {
|
|
|
|
chunk, err := u.ReadChunk(pos)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
chunks = append(chunks, chunk)
|
|
|
|
}
|
|
|
|
|
|
|
|
return chunks, nil
|
|
|
|
}
|
|
|
|
|
2023-12-11 09:57:11 -08:00
|
|
|
func (u *UnityFile) Close() error {
|
|
|
|
return u.fd.Close()
|
|
|
|
}
|