2023-10-29 01:24:35 -07:00
|
|
|
package visualization
|
|
|
|
|
|
|
|
import (
|
2023-10-29 19:32:23 -07:00
|
|
|
"errors"
|
2023-10-29 01:24:35 -07:00
|
|
|
"strings"
|
|
|
|
|
2023-12-13 23:48:53 -08:00
|
|
|
"github.com/NickyBoy89/spatial-db/server"
|
|
|
|
"github.com/NickyBoy89/spatial-db/storage"
|
|
|
|
"github.com/NickyBoy89/spatial-db/world"
|
2023-10-29 01:24:35 -07:00
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
2023-10-29 19:32:23 -07:00
|
|
|
const (
|
|
|
|
chunkWidth = 15
|
|
|
|
chunkHeight = 5
|
|
|
|
)
|
|
|
|
|
2023-10-29 01:24:35 -07:00
|
|
|
var (
|
2023-10-29 19:32:23 -07:00
|
|
|
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).
|
2023-10-29 01:24:35 -07:00
|
|
|
Align(lipgloss.Center, lipgloss.Center).
|
|
|
|
BorderStyle(lipgloss.NormalBorder()).
|
|
|
|
BorderForeground(lipgloss.Color("69"))
|
2023-10-29 19:32:23 -07:00
|
|
|
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)
|
2023-10-29 01:24:35 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
type chunkViewerModel struct {
|
2023-12-10 21:27:39 -08:00
|
|
|
chunkServer *server.SimpleServer
|
2023-10-29 19:32:23 -07:00
|
|
|
|
|
|
|
visibleChunkRows int
|
|
|
|
visibleChunkCols int
|
|
|
|
|
|
|
|
currentPos world.ChunkPos
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *chunkViewerModel) updateShownChunks(newWidth, newHeight int) {
|
2023-11-06 15:27:03 -08:00
|
|
|
horiz, vert := missingChunkStyle.GetFrameSize()
|
|
|
|
m.visibleChunkRows = newHeight / (chunkHeight + vert)
|
|
|
|
m.visibleChunkCols = newWidth / (chunkWidth + horiz)
|
2023-10-29 01:24:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m chunkViewerModel) Init() tea.Cmd {
|
2023-10-29 19:32:23 -07:00
|
|
|
return tea.EnterAltScreen
|
2023-10-29 01:24:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m chunkViewerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
|
switch msg := msg.(type) {
|
|
|
|
case tea.KeyMsg:
|
|
|
|
switch msg.String() {
|
2023-10-29 19:32:23 -07:00
|
|
|
case "ctrl+c", "q", "esc":
|
2023-10-29 01:24:35 -07:00
|
|
|
return m, tea.Quit
|
|
|
|
}
|
|
|
|
switch msg.Type {
|
|
|
|
case tea.KeyRight:
|
2023-10-29 19:32:23 -07:00
|
|
|
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
|
2023-10-29 01:24:35 -07:00
|
|
|
}
|
2023-10-29 19:32:23 -07:00
|
|
|
case tea.WindowSizeMsg:
|
|
|
|
m.updateShownChunks(msg.Width, msg.Height)
|
2023-10-29 01:24:35 -07:00
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m chunkViewerModel) View() string {
|
|
|
|
var s strings.Builder
|
2023-10-29 19:32:23 -07:00
|
|
|
|
|
|
|
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")
|
2023-10-29 01:24:35 -07:00
|
|
|
}
|
2023-10-29 19:32:23 -07:00
|
|
|
|
2023-10-29 01:24:35 -07:00
|
|
|
return s.String()
|
|
|
|
}
|
|
|
|
|
2023-12-10 21:27:39 -08:00
|
|
|
func initChunkViewer(chunkServer *server.SimpleServer) chunkViewerModel {
|
2023-10-29 01:24:35 -07:00
|
|
|
var model chunkViewerModel
|
|
|
|
|
2023-10-29 19:32:23 -07:00
|
|
|
model.chunkServer = chunkServer
|
|
|
|
|
2023-10-29 01:24:35 -07:00
|
|
|
return model
|
|
|
|
}
|
|
|
|
|
|
|
|
var VisualizeChunkCommand = &cobra.Command{
|
|
|
|
Use: "chunk",
|
|
|
|
Short: "Visualize a single chunk",
|
|
|
|
Args: cobra.ExactArgs(1),
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
2023-10-29 19:32:23 -07:00
|
|
|
// Create a new server to read from those files
|
2023-12-10 21:27:39 -08:00
|
|
|
var chunkServer server.SimpleServer
|
2023-11-08 16:24:27 -08:00
|
|
|
chunkServer.StorageDir = args[0]
|
2023-10-29 19:32:23 -07:00
|
|
|
|
|
|
|
prog := tea.NewProgram(initChunkViewer(&chunkServer), tea.WithAltScreen())
|
2023-10-29 01:24:35 -07:00
|
|
|
|
|
|
|
if _, err := prog.Run(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|