change: Started rewriting parts of the project in Go

This commit is contained in:
Nicholas Novak 2023-10-28 21:40:29 -07:00
parent 7419412be4
commit c73a555b04
8 changed files with 275 additions and 0 deletions

View File

@ -0,0 +1,34 @@
package main
import (
"math/rand"
"testing"
"time"
"git.nicholasnovak.io/nnovak/spatial-db/storage"
"git.nicholasnovak.io/nnovak/spatial-db/world"
)
func BenchmarkInsertSomePoints(b *testing.B) {
var server storage.SimpleServer
points := make([]world.BlockPos, b.N)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < b.N; i++ {
points[i] = world.BlockPos{
X: int(r.NormFloat64()),
Y: uint(r.NormFloat64()),
Z: int(r.NormFloat64()),
}
}
b.ResetTimer()
for _, point := range points {
if err := server.ChangeBlock(point, world.Generic); err != nil {
b.Error(err)
}
}
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module git.nicholasnovak.io/nnovak/spatial-db
go 1.21.3
require github.com/deckarep/golang-set/v2 v2.3.1

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A=
github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=

10
main.go Normal file
View File

@ -0,0 +1,10 @@
package main
import (
"git.nicholasnovak.io/nnovak/spatial-db/storage"
)
func main() {
server := storage.SimpleServer{}
_ = server
}

View File

@ -0,0 +1,18 @@
package storage
import (
"encoding/json"
"os"
"git.nicholasnovak.io/nnovak/spatial-db/world"
)
func ReadChunkFromFile(chunkFile *os.File) (world.ChunkData, error) {
var chunkData world.ChunkData
if err := json.NewDecoder(chunkFile).Decode(&chunkData); err != nil {
return chunkData, err
}
return chunkData, nil
}

79
storage/simple_server.go Normal file
View File

@ -0,0 +1,79 @@
package storage
import (
"encoding/json"
"errors"
"io/fs"
"os"
"git.nicholasnovak.io/nnovak/spatial-db/world"
)
const fileCacheSize = 8
type SimpleServer struct {
}
// Filesystem operations
func (s *SimpleServer) FetchChunk(pos world.ChunkPos) (world.ChunkData, error) {
chunkFileName := pos.ToFileName()
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
}
} else {
return chunkData, err
}
}
return ReadChunkFromFile(chunkFile)
}
// Voxel server implementation
func (s *SimpleServer) ChangeBlock(
worldPosition world.BlockPos,
targetState world.BlockID,
) error {
chunk, err := s.FetchChunk(worldPosition.ToChunkPos())
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) {
chunk, err := s.FetchChunk(pos.ToChunkPos())
if err != nil {
return world.Empty, err
}
return chunk.SectionFor(pos).FetchBlock(pos), nil
}
func (s *SimpleServer) ReadChunkAt(pos world.ChunkPos) (world.ChunkData, error) {
return s.FetchChunk(pos)
}

13
storage/storage_server.go Normal file
View File

@ -0,0 +1,13 @@
package storage
import "git.nicholasnovak.io/nnovak/spatial-db/world"
type StorageServer interface {
// Individual block-level interactions
ChangeBlock(targetState world.BlockID, world_position world.BlockPos) error
ChangeBlockRange(targetState world.BlockID, start, end world.BlockPos) error
ReadBlockAt(pos world.BlockPos) error
// Network-level operations
ReadChunkAt(pos world.ChunkPos) error
}

114
world/data_format.go Normal file
View File

@ -0,0 +1,114 @@
package world
import (
"fmt"
)
const (
// Slice size is the total number of blocks in a horizontal slice of a chunk
sliceSize = 16 * 16
)
type BlockPos struct {
X int `json:"x"`
Y uint `json:"y"`
Z int `json:"z"`
}
func (b BlockPos) ToChunkPos() ChunkPos {
return ChunkPos{
X: b.X / 16,
Z: b.Z / 16,
}
}
type ChunkData struct {
Pos ChunkPos `json:"pos"`
Sections [16]ChunkSection `json:"sections"`
}
func (cd *ChunkData) SectionFor(pos BlockPos) *ChunkSection {
return &cd.Sections[pos.Y%16]
}
type ChunkPos struct {
X int `json:"x"`
Z int `json:"z"`
}
func (cp *ChunkPos) ToFileName() string {
return fmt.Sprintf("p.%d.%d.chunk", cp.X, cp.Z)
}
type ChunkSection struct {
// The count of full blocks in the chunk
BlockCount uint `json:"block_count"`
BlockStates [16 * 16 * 16]BlockID `json:"block_states"`
}
func rem_euclid(a, b int) int {
return (a%b + b) % b
}
func (cs *ChunkSection) IndexOfBlock(pos BlockPos) int {
baseX := rem_euclid(pos.X, 16)
baseY := rem_euclid(int(pos.Y), 16)
baseZ := rem_euclid(pos.Z, 16)
return (baseY * sliceSize) + (baseZ * 16) + baseX
}
func (cs *ChunkSection) UpdateBlockAtIndex(index int, targetState BlockID) {
// TODO: Keep track of the block count
cs.BlockStates[index] = targetState
}
func (cs *ChunkSection) UpdateBlock(pos BlockPos, targetState BlockID) {
cs.BlockStates[cs.IndexOfBlock(pos)] = targetState
}
func (cs *ChunkSection) FetchBlock(pos BlockPos) BlockID {
return cs.BlockStates[cs.IndexOfBlock(pos)]
}
type BlockID uint8
const (
Empty BlockID = iota
Generic
)
func (b *BlockID) UnmarshalJSON(data []byte) error {
idName := string(data)
if len(idName) < 2 {
return fmt.Errorf("error decoding blockid, input was too short")
}
switch idName[1 : len(idName)-1] {
case "Empty":
*b = Empty
case "Generic":
*b = Generic
default:
return fmt.Errorf("unknown block id: %s", string(data))
}
return nil
}
func (b BlockID) MarshalJSON() ([]byte, error) {
var encoded []byte
switch b {
case Empty:
encoded = []byte("\"Empty\"")
case Generic:
encoded = []byte("\"Generic\"")
default:
return []byte{}, fmt.Errorf("could not turn block id %d into data", b)
}
return encoded, nil
}