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
var (
ChunkNotFoundError = errors.New("chunk was not found in storage")
)
type SimpleServer struct {
}
// 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())
var chunkData world.ChunkData
@ -43,6 +47,25 @@ func (s *SimpleServer) FetchChunk(pos world.ChunkPos) (world.ChunkData, error) {
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)
}
@ -53,7 +76,7 @@ func (s *SimpleServer) ChangeBlock(
worldPosition world.BlockPos,
targetState world.BlockID,
) error {
chunk, err := s.FetchChunk(worldPosition.ToChunkPos())
chunk, err := s.FetchOrCreateChunk(worldPosition.ToChunkPos())
if err != nil {
return err
}
@ -71,7 +94,7 @@ func (s *SimpleServer) ChangeBlockRange(
}
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 {
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) {
return s.FetchChunk(pos)
return s.FetchOrCreateChunk(pos)
}

View File

@ -1,66 +1,138 @@
package visualization
import (
"fmt"
"errors"
"strings"
"git.nicholasnovak.io/nnovak/spatial-db/storage"
"git.nicholasnovak.io/nnovak/spatial-db/world"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
)
const (
chunkWidth = 15
chunkHeight = 5
borderPadding = 2
)
var (
modelStyle = lipgloss.NewStyle().
Width(15).
Height(5).
missingChunkStyle = lipgloss.NewStyle().
Width(chunkWidth).
Height(chunkHeight).
Align(lipgloss.Center, lipgloss.Center).
BorderStyle(lipgloss.HiddenBorder())
focusedModelStyle = lipgloss.NewStyle().
Width(15).
Height(5).
BorderStyle(lipgloss.HiddenBorder()).
BorderForeground(lipgloss.Color("69"))
presentChunkStyle = lipgloss.NewStyle().
Width(chunkWidth).
Height(chunkHeight).
Align(lipgloss.Center, lipgloss.Center).
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("69"))
spinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
selectedChunkStyle = lipgloss.NewStyle().
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 {
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 {
return nil
return tea.EnterAltScreen
}
func (m chunkViewerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
case "ctrl+c", "q", "esc":
return m, tea.Quit
}
switch msg.Type {
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
}
func (m chunkViewerModel) View() string {
var s strings.Builder
if m.index == 0 {
s.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, focusedModelStyle.Render(fmt.Sprintf("%4s", "No")), modelStyle.Render("NO")))
} else {
s.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, modelStyle.Render(fmt.Sprintf("%4s", "Yes")), focusedModelStyle.Render("YES")))
midRow := m.visibleChunkRows / 2
midCol := m.visibleChunkCols / 2
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()
}
func initChunkViewer() chunkViewerModel {
func initChunkViewer(chunkServer *storage.SimpleServer) chunkViewerModel {
var model chunkViewerModel
model.chunkServer = chunkServer
return model
}
@ -70,7 +142,13 @@ var VisualizeChunkCommand = &cobra.Command{
Args: cobra.ExactArgs(1),
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 {
return err

View File

@ -36,10 +36,14 @@ type ChunkPos struct {
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)
}
func (cp ChunkPos) StringCoords() string {
return fmt.Sprintf("%d, %d", cp.X, cp.Z)
}
type ChunkSection struct {
// The count of full blocks in the chunk
BlockCount uint `json:"block_count"`