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
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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"`
|
||||||
|
Loading…
Reference in New Issue
Block a user