repl, save
This commit is contained in:
parent
cd4cbbdef9
commit
01468798d4
6 changed files with 87 additions and 8 deletions
|
@ -39,8 +39,8 @@ func loadGobz(dst interface{}, assets fs.FS, path string) error {
|
||||||
return gob.NewDecoder(gz).Decode(dst)
|
return gob.NewDecoder(gz).Decode(dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveGobz takes an object, gob-encodes it, gzips it, and writes to disk.
|
// saveGobz takes an object, gob-encodes it, gzips it, and writes to disk.
|
||||||
func SaveGobz(src interface{}, name string) error {
|
func saveGobz(src interface{}, name string) error {
|
||||||
f, err := os.CreateTemp(".", name)
|
f, err := os.CreateTemp(".", name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -3,6 +3,7 @@ package engine
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
@ -15,15 +16,21 @@ func init() {
|
||||||
// One component must be the designated root component - usually a
|
// One component must be the designated root component - usually a
|
||||||
// scene of some kind.
|
// scene of some kind.
|
||||||
type Game struct {
|
type Game struct {
|
||||||
|
Disabled
|
||||||
|
Hidden
|
||||||
ScreenWidth int
|
ScreenWidth int
|
||||||
ScreenHeight int
|
ScreenHeight int
|
||||||
Root DrawUpdater // typically a *Scene or SceneRef though
|
Root DrawUpdater // typically a *Scene or SceneRef though
|
||||||
|
|
||||||
componentsByID map[string]interface{}
|
dbmu sync.RWMutex
|
||||||
|
db map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw draws the entire thing, with default draw options.
|
// Draw draws the entire thing, with default draw options.
|
||||||
func (g *Game) Draw(screen *ebiten.Image) {
|
func (g *Game) Draw(screen *ebiten.Image) {
|
||||||
|
if g.Hidden {
|
||||||
|
return
|
||||||
|
}
|
||||||
g.Root.Draw(screen, ebiten.DrawImageOptions{})
|
g.Root.Draw(screen, ebiten.DrawImageOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +40,12 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (w, h int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates the scene.
|
// Update updates the scene.
|
||||||
func (g *Game) Update() error { return g.Root.Update() }
|
func (g *Game) Update() error {
|
||||||
|
if g.Disabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return g.Root.Update()
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterComponent tells the game there is a new component. Currently this is
|
// RegisterComponent tells the game there is a new component. Currently this is
|
||||||
// only necessary for components with IDs.
|
// only necessary for components with IDs.
|
||||||
|
@ -46,7 +58,9 @@ func (g *Game) RegisterComponent(c interface{}) {
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.componentsByID[id] = c
|
g.dbmu.Lock()
|
||||||
|
g.db[id] = c
|
||||||
|
g.dbmu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnregisterComponent tells the game the component is no more.
|
// UnregisterComponent tells the game the component is no more.
|
||||||
|
@ -60,11 +74,17 @@ func (g *Game) UnregisterComponent(c interface{}) {
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(g.componentsByID, id)
|
g.dbmu.Lock()
|
||||||
|
delete(g.db, id)
|
||||||
|
g.dbmu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component returns the component with a given ID, or nil if there is none.
|
// Component returns the component with a given ID, or nil if there is none.
|
||||||
func (g *Game) Component(id string) interface{} { return g.componentsByID[id] }
|
func (g *Game) Component(id string) interface{} {
|
||||||
|
g.dbmu.RLock()
|
||||||
|
defer g.dbmu.RUnlock()
|
||||||
|
return g.db[id]
|
||||||
|
}
|
||||||
|
|
||||||
// Scan implements Scanner.
|
// Scan implements Scanner.
|
||||||
func (g *Game) Scan() []interface{} { return []interface{}{g.Root} }
|
func (g *Game) Scan() []interface{} { return []interface{}{g.Root} }
|
||||||
|
@ -104,7 +124,10 @@ func (g *Game) Load(assets fs.FS) error {
|
||||||
// to Component. You may call Prepare again (e.g. as an alternative to
|
// to Component. You may call Prepare again (e.g. as an alternative to
|
||||||
// fastidiously calling RegisterComponent/UnregisterComponent).
|
// fastidiously calling RegisterComponent/UnregisterComponent).
|
||||||
func (g *Game) Prepare() {
|
func (g *Game) Prepare() {
|
||||||
g.componentsByID = make(map[string]interface{})
|
g.dbmu.Lock()
|
||||||
|
g.db = make(map[string]interface{})
|
||||||
|
g.dbmu.Unlock()
|
||||||
|
// Moment in time where db is empty... whatev.
|
||||||
Walk(g.Root, func(c interface{}) error {
|
Walk(g.Root, func(c interface{}) error {
|
||||||
g.RegisterComponent(c)
|
g.RegisterComponent(c)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -113,6 +113,11 @@ type Scener interface {
|
||||||
Updater
|
Updater
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Saver components can be saved to disk.
|
||||||
|
type Saver interface {
|
||||||
|
Save() error
|
||||||
|
}
|
||||||
|
|
||||||
// Updater components can update themselves. Update is called repeatedly.
|
// Updater components can update themselves. Update is called repeatedly.
|
||||||
// Each component is responsible for calling Update on its child components
|
// Each component is responsible for calling Update on its child components
|
||||||
// (so that disabling the parent prevents updates to the children, etc).
|
// (so that disabling the parent prevents updates to the children, etc).
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"image"
|
"image"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
@ -43,6 +44,9 @@ func (r *SceneRef) Load(assets fs.FS) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save saves the scene to a file in the current directory.
|
||||||
|
func (r *SceneRef) Save() error { return saveGobz(r.scene, filepath.Base(r.Path)) }
|
||||||
|
|
||||||
// The rest of the methods forward to r.scene, as such they will
|
// The rest of the methods forward to r.scene, as such they will
|
||||||
// panic if the scene isn't loaded.
|
// panic if the scene isn't loaded.
|
||||||
|
|
||||||
|
|
Binary file not shown.
47
main.go
47
main.go
|
@ -1,8 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"drjosh.dev/gurgle/engine"
|
"drjosh.dev/gurgle/engine"
|
||||||
"drjosh.dev/gurgle/game"
|
"drjosh.dev/gurgle/game"
|
||||||
|
@ -34,7 +37,51 @@ func main() {
|
||||||
}
|
}
|
||||||
g.Prepare()
|
g.Prepare()
|
||||||
|
|
||||||
|
// Run a repl on the console.
|
||||||
|
go repl(g)
|
||||||
|
|
||||||
|
// ... while the game also runs
|
||||||
if err := ebiten.RunGame(g); err != nil {
|
if err := ebiten.RunGame(g); err != nil {
|
||||||
log.Fatalf("Game error: %v", err)
|
log.Fatalf("Game error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func repl(g *engine.Game) {
|
||||||
|
sc := bufio.NewScanner(os.Stdin)
|
||||||
|
for sc.Scan() {
|
||||||
|
tok := strings.Split(sc.Text(), " ")
|
||||||
|
if len(tok) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch tok[0] {
|
||||||
|
case "quit":
|
||||||
|
os.Exit(0)
|
||||||
|
case "pause":
|
||||||
|
g.Disable()
|
||||||
|
case "resume", "unpause":
|
||||||
|
g.Enable()
|
||||||
|
case "save":
|
||||||
|
if len(tok) != 2 {
|
||||||
|
log.Print("Usage: save ID")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
id := tok[1]
|
||||||
|
c := g.Component(id)
|
||||||
|
if c == nil {
|
||||||
|
log.Printf("Component %q not found", id)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s, ok := c.(engine.Saver)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("Component %q not a Saver (type %T)", id, c)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := s.Save(); err != nil {
|
||||||
|
log.Printf("Couldn't save %q: %v", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := sc.Err(); err != nil {
|
||||||
|
log.Fatalf("Couldn't scan stdin: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue