progress: Finished the chunk-level ui for the visualization
This commit is contained in:
parent
f941bf87f0
commit
e82ad34de5
@ -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)
|
||||
}
|
||||
|
@ -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).
|
||||
Align(lipgloss.Center, lipgloss.Center).
|
||||
BorderStyle(lipgloss.HiddenBorder())
|
||||
focusedModelStyle = lipgloss.NewStyle().
|
||||
Width(15).
|
||||
Height(5).
|
||||
missingChunkStyle = lipgloss.NewStyle().
|
||||
Width(chunkWidth).
|
||||
Height(chunkHeight).
|
||||
Align(lipgloss.Center, lipgloss.Center).
|
||||
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
|
||||
|
@ -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"`
|
||||
|
Loading…
Reference in New Issue
Block a user