change: Started rewriting parts of the project in Go
This commit is contained in:
parent
7419412be4
commit
c73a555b04
34
basic_functionality_test.go
Normal file
34
basic_functionality_test.go
Normal 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
5
go.mod
Normal 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
2
go.sum
Normal 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
10
main.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.nicholasnovak.io/nnovak/spatial-db/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server := storage.SimpleServer{}
|
||||||
|
_ = server
|
||||||
|
}
|
18
storage/file_operations.go
Normal file
18
storage/file_operations.go
Normal 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
79
storage/simple_server.go
Normal 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
13
storage/storage_server.go
Normal 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
114
world/data_format.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user