change: Started rewriting parts of the project in Go
This commit is contained in:
		
							
								
								
									
										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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user