progress: Finished the chunk-level ui for the visualization

This commit is contained in:
Nicholas Novak 2023-10-29 19:32:23 -07:00
parent f941bf87f0
commit e82ad34de5
3 changed files with 131 additions and 26 deletions

View File

@ -12,12 +12,16 @@ import (
const fileCacheSize = 8 const fileCacheSize = 8
var (
ChunkNotFoundError = errors.New("chunk was not found in storage")
)
type SimpleServer struct { type SimpleServer struct {
} }
// Filesystem operations // Filesystem operations
func (s *SimpleServer) FetchChunk(pos world.ChunkPos) (world.ChunkData, error) { func (s *SimpleServer) FetchOrCreateChunk(pos world.ChunkPos) (world.ChunkData, error) {
chunkFileName := filepath.Join(ChunkFileDirectory, pos.ToFileName()) chunkFileName := filepath.Join(ChunkFileDirectory, pos.ToFileName())
var chunkData world.ChunkData var chunkData world.ChunkData
@ -43,6 +47,25 @@ func (s *SimpleServer) FetchChunk(pos world.ChunkPos) (world.ChunkData, error) {
return chunkData, err return chunkData, err
} }
} }
defer chunkFile.Close()
return ReadChunkFromFile(chunkFile)
}
func (s *SimpleServer) FetchChunk(pos world.ChunkPos) (world.ChunkData, error) {
chunkFileName := filepath.Join(ChunkFileDirectory, pos.ToFileName())
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()
return ReadChunkFromFile(chunkFile) return ReadChunkFromFile(chunkFile)
} }
@ -53,7 +76,7 @@ func (s *SimpleServer) ChangeBlock(
worldPosition world.BlockPos, worldPosition world.BlockPos,
targetState world.BlockID, targetState world.BlockID,
) error { ) error {
chunk, err := s.FetchChunk(worldPosition.ToChunkPos()) chunk, err := s.FetchOrCreateChunk(worldPosition.ToChunkPos())
if err != nil { if err != nil {
return err return err
} }
@ -71,7 +94,7 @@ func (s *SimpleServer) ChangeBlockRange(
} }
func (s *SimpleServer) ReadBlockAt(pos world.BlockPos) (world.BlockID, error) { func (s *SimpleServer) ReadBlockAt(pos world.BlockPos) (world.BlockID, error) {
chunk, err := s.FetchChunk(pos.ToChunkPos()) chunk, err := s.FetchOrCreateChunk(pos.ToChunkPos())
if err != nil { if err != nil {
return world.Empty, err return world.Empty, err
} }
@ -80,5 +103,5 @@ func (s *SimpleServer) ReadBlockAt(pos world.BlockPos) (world.BlockID, error) {
} }
func (s *SimpleServer) ReadChunkAt(pos world.ChunkPos) (world.ChunkData, error) { func (s *SimpleServer) ReadChunkAt(pos world.ChunkPos) (world.ChunkData, error) {
return s.FetchChunk(pos) return s.FetchOrCreateChunk(pos)
} }

View File

@ -1,66 +1,138 @@
package visualization package visualization
import ( import (
"fmt" "errors"
"strings" "strings"
"git.nicholasnovak.io/nnovak/spatial-db/storage"
"git.nicholasnovak.io/nnovak/spatial-db/world"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const (
chunkWidth = 15
chunkHeight = 5
borderPadding = 2
)
var ( var (
modelStyle = lipgloss.NewStyle(). missingChunkStyle = lipgloss.NewStyle().
Width(15). Width(chunkWidth).
Height(5). Height(chunkHeight).
Align(lipgloss.Center, lipgloss.Center). Align(lipgloss.Center, lipgloss.Center).
BorderStyle(lipgloss.HiddenBorder()) BorderStyle(lipgloss.HiddenBorder()).
focusedModelStyle = lipgloss.NewStyle(). BorderForeground(lipgloss.Color("69"))
Width(15). presentChunkStyle = lipgloss.NewStyle().
Height(5). Width(chunkWidth).
Height(chunkHeight).
Align(lipgloss.Center, lipgloss.Center). Align(lipgloss.Center, lipgloss.Center).
BorderStyle(lipgloss.NormalBorder()). BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("69")) BorderForeground(lipgloss.Color("69"))
spinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69")) selectedChunkStyle = lipgloss.NewStyle().
helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")) Width(chunkWidth).
Height(chunkHeight).
Align(lipgloss.Center, lipgloss.Center).
BorderStyle(lipgloss.DoubleBorder()).
BorderForeground(lipgloss.Color("202"))
loadedChunkCache = make(map[world.ChunkPos]bool)
) )
type chunkViewerModel struct { type chunkViewerModel struct {
index int chunkServer *storage.SimpleServer
visibleChunkRows int
visibleChunkCols int
currentPos world.ChunkPos
}
func (m *chunkViewerModel) updateShownChunks(newWidth, newHeight int) {
m.visibleChunkRows = newHeight / (chunkHeight + borderPadding)
m.visibleChunkCols = newWidth / (chunkWidth + borderPadding)
} }
func (m chunkViewerModel) Init() tea.Cmd { func (m chunkViewerModel) Init() tea.Cmd {
return nil return tea.EnterAltScreen
} }
func (m chunkViewerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m chunkViewerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
switch msg.String() { switch msg.String() {
case "ctrl+c", "q": case "ctrl+c", "q", "esc":
return m, tea.Quit return m, tea.Quit
} }
switch msg.Type { switch msg.Type {
case tea.KeyRight: case tea.KeyRight:
// pass m.currentPos.X += 1
case tea.KeyLeft:
m.currentPos.X -= 1
case tea.KeyUp:
m.currentPos.Z -= 1
case tea.KeyDown:
m.currentPos.Z += 1
} }
case tea.WindowSizeMsg:
m.updateShownChunks(msg.Width, msg.Height)
} }
return m, nil return m, nil
} }
func (m chunkViewerModel) View() string { func (m chunkViewerModel) View() string {
var s strings.Builder var s strings.Builder
if m.index == 0 {
s.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, focusedModelStyle.Render(fmt.Sprintf("%4s", "No")), modelStyle.Render("NO"))) midRow := m.visibleChunkRows / 2
} else { midCol := m.visibleChunkCols / 2
s.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, modelStyle.Render(fmt.Sprintf("%4s", "Yes")), focusedModelStyle.Render("YES")))
for rowIndex := 0; rowIndex < m.visibleChunkRows; rowIndex++ {
renderedRow := make([]string, m.visibleChunkCols)
for colIndex := 0; colIndex < m.visibleChunkCols; colIndex++ {
currentChunkPos := world.ChunkPos{
X: midCol - colIndex - m.currentPos.X,
Z: midRow - rowIndex - m.currentPos.Z,
} }
var fetchChunkErr error
if isPresent, cached := loadedChunkCache[currentChunkPos]; cached {
if isPresent {
fetchChunkErr = nil
} else {
fetchChunkErr = storage.ChunkNotFoundError
}
} else {
_, fetchChunkErr = m.chunkServer.FetchChunk(currentChunkPos)
loadedChunkCache[currentChunkPos] = fetchChunkErr == nil
}
chunkDisplay := currentChunkPos.StringCoords()
if rowIndex == midRow && colIndex == midCol {
renderedRow[colIndex] = selectedChunkStyle.Render(chunkDisplay)
} else {
if fetchChunkErr == nil {
renderedRow[colIndex] = presentChunkStyle.Render(chunkDisplay)
} else if errors.Is(fetchChunkErr, storage.ChunkNotFoundError) {
renderedRow[colIndex] = missingChunkStyle.Render(chunkDisplay)
} else {
panic(fetchChunkErr)
}
}
}
s.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, renderedRow...) + "\n")
}
return s.String() return s.String()
} }
func initChunkViewer() chunkViewerModel { func initChunkViewer(chunkServer *storage.SimpleServer) chunkViewerModel {
var model chunkViewerModel var model chunkViewerModel
model.chunkServer = chunkServer
return model return model
} }
@ -70,7 +142,13 @@ var VisualizeChunkCommand = &cobra.Command{
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
prog := tea.NewProgram(initChunkViewer()) // Initialize the server in the specified directory
storage.ChunkFileDirectory = args[0]
// Create a new server to read from those files
var chunkServer storage.SimpleServer
prog := tea.NewProgram(initChunkViewer(&chunkServer), tea.WithAltScreen())
if _, err := prog.Run(); err != nil { if _, err := prog.Run(); err != nil {
return err return err

View File

@ -36,10 +36,14 @@ type ChunkPos struct {
Z int `json:"z"` Z int `json:"z"`
} }
func (cp *ChunkPos) ToFileName() string { func (cp ChunkPos) ToFileName() string {
return fmt.Sprintf("p.%d.%d.chunk", cp.X, cp.Z) return fmt.Sprintf("p.%d.%d.chunk", cp.X, cp.Z)
} }
func (cp ChunkPos) StringCoords() string {
return fmt.Sprintf("%d, %d", cp.X, cp.Z)
}
type ChunkSection struct { type ChunkSection struct {
// The count of full blocks in the chunk // The count of full blocks in the chunk
BlockCount uint `json:"block_count"` BlockCount uint `json:"block_count"`