2023-10-28 21:40:29 -07:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"io/fs"
|
|
|
|
"os"
|
2023-10-29 01:23:38 -07:00
|
|
|
"path/filepath"
|
2023-10-28 21:40:29 -07:00
|
|
|
|
|
|
|
"git.nicholasnovak.io/nnovak/spatial-db/world"
|
|
|
|
)
|
|
|
|
|
|
|
|
const fileCacheSize = 8
|
|
|
|
|
2023-10-29 19:32:23 -07:00
|
|
|
var (
|
|
|
|
ChunkNotFoundError = errors.New("chunk was not found in storage")
|
|
|
|
)
|
|
|
|
|
2023-10-28 21:40:29 -07:00
|
|
|
type SimpleServer struct {
|
2023-11-08 16:24:27 -08:00
|
|
|
StorageDir string
|
2023-10-28 21:40:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Filesystem operations
|
|
|
|
|
2023-10-29 19:32:23 -07:00
|
|
|
func (s *SimpleServer) FetchOrCreateChunk(pos world.ChunkPos) (world.ChunkData, error) {
|
2023-11-08 16:24:27 -08:00
|
|
|
chunkFileName := filepath.Join(s.StorageDir, pos.ToFileName())
|
2023-10-28 21:40:29 -07:00
|
|
|
|
|
|
|
var chunkData world.ChunkData
|
|
|
|
|
|
|
|
chunkFile, err := os.Open(chunkFileName)
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
|
|
// There was no chunk that exists, create a blank one
|
|
|
|
chunkFile, err = os.Create(chunkFileName)
|
|
|
|
if err != nil {
|
|
|
|
return chunkData, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initilize the file with some blank data
|
|
|
|
if err := json.NewEncoder(chunkFile).Encode(chunkData); err != nil {
|
|
|
|
return chunkData, err
|
|
|
|
}
|
2023-10-29 01:23:38 -07:00
|
|
|
|
|
|
|
if _, err := chunkFile.Seek(0, 0); err != nil {
|
|
|
|
return chunkData, err
|
|
|
|
}
|
2023-10-28 21:40:29 -07:00
|
|
|
} else {
|
|
|
|
return chunkData, err
|
|
|
|
}
|
|
|
|
}
|
2023-10-29 19:32:23 -07:00
|
|
|
defer chunkFile.Close()
|
|
|
|
|
|
|
|
return ReadChunkFromFile(chunkFile)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SimpleServer) FetchChunk(pos world.ChunkPos) (world.ChunkData, error) {
|
2023-11-08 16:24:27 -08:00
|
|
|
chunkFileName := filepath.Join(s.StorageDir, pos.ToFileName())
|
2023-10-29 19:32:23 -07:00
|
|
|
|
|
|
|
var chunkData world.ChunkData
|
|
|
|
|
|
|
|
chunkFile, err := os.Open(chunkFileName)
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
|
|
return chunkData, ChunkNotFoundError
|
|
|
|
} else {
|
|
|
|
return chunkData, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer chunkFile.Close()
|
2023-10-28 21:40:29 -07:00
|
|
|
|
|
|
|
return ReadChunkFromFile(chunkFile)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Voxel server implementation
|
|
|
|
|
|
|
|
func (s *SimpleServer) ChangeBlock(
|
|
|
|
worldPosition world.BlockPos,
|
|
|
|
targetState world.BlockID,
|
|
|
|
) error {
|
2023-10-29 19:32:23 -07:00
|
|
|
chunk, err := s.FetchOrCreateChunk(worldPosition.ToChunkPos())
|
2023-10-28 21:40:29 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
chunk.SectionFor(worldPosition).UpdateBlock(worldPosition, targetState)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SimpleServer) ChangeBlockRange(
|
|
|
|
targetState world.BlockID,
|
|
|
|
start, end world.BlockPos,
|
|
|
|
) error {
|
|
|
|
panic("ChangeBlockRange is unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SimpleServer) ReadBlockAt(pos world.BlockPos) (world.BlockID, error) {
|
2023-10-29 19:32:23 -07:00
|
|
|
chunk, err := s.FetchOrCreateChunk(pos.ToChunkPos())
|
2023-10-28 21:40:29 -07:00
|
|
|
if err != nil {
|
|
|
|
return world.Empty, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return chunk.SectionFor(pos).FetchBlock(pos), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SimpleServer) ReadChunkAt(pos world.ChunkPos) (world.ChunkData, error) {
|
2023-10-29 19:32:23 -07:00
|
|
|
return s.FetchOrCreateChunk(pos)
|
2023-10-28 21:40:29 -07:00
|
|
|
}
|