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