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)
|
||||
}
|
||||
|
||||
// SaveGobz takes an object, gob-encodes it, gzips it, and writes to disk.
|
||||
func SaveGobz(src interface{}, name string) error {
|
||||
// saveGobz takes an object, gob-encodes it, gzips it, and writes to disk.
|
||||
func saveGobz(src interface{}, name string) error {
|
||||
f, err := os.CreateTemp(".", name)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -3,6 +3,7 @@ package engine
|
|||
import (
|
||||
"encoding/gob"
|
||||
"io/fs"
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
@ -15,15 +16,21 @@ func init() {
|
|||
// One component must be the designated root component - usually a
|
||||
// scene of some kind.
|
||||
type Game struct {
|
||||
Disabled
|
||||
Hidden
|
||||
ScreenWidth int
|
||||
ScreenHeight int
|
||||
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.
|
||||
func (g *Game) Draw(screen *ebiten.Image) {
|
||||
if g.Hidden {
|
||||
return
|
||||
}
|
||||
g.Root.Draw(screen, ebiten.DrawImageOptions{})
|
||||
}
|
||||
|
||||
|
@ -33,7 +40,12 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (w, h int) {
|
|||
}
|
||||
|
||||
// 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
|
||||
// only necessary for components with IDs.
|
||||
|
@ -46,7 +58,9 @@ func (g *Game) RegisterComponent(c interface{}) {
|
|||
if id == "" {
|
||||
return
|
||||
}
|
||||
g.componentsByID[id] = c
|
||||
g.dbmu.Lock()
|
||||
g.db[id] = c
|
||||
g.dbmu.Unlock()
|
||||
}
|
||||
|
||||
// UnregisterComponent tells the game the component is no more.
|
||||
|
@ -60,11 +74,17 @@ func (g *Game) UnregisterComponent(c interface{}) {
|
|||
if id == "" {
|
||||
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.
|
||||
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.
|
||||
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
|
||||
// fastidiously calling RegisterComponent/UnregisterComponent).
|
||||
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 {
|
||||
g.RegisterComponent(c)
|
||||
return nil
|
||||
|
|
|
@ -113,6 +113,11 @@ type Scener interface {
|
|||
Updater
|
||||
}
|
||||
|
||||
// Saver components can be saved to disk.
|
||||
type Saver interface {
|
||||
Save() error
|
||||
}
|
||||
|
||||
// Updater components can update themselves. Update is called repeatedly.
|
||||
// Each component is responsible for calling Update on its child components
|
||||
// (so that disabling the parent prevents updates to the children, etc).
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/gob"
|
||||
"image"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
@ -43,6 +44,9 @@ func (r *SceneRef) Load(assets fs.FS) error {
|
|||
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
|
||||
// panic if the scene isn't loaded.
|
||||
|
||||
|
|
Binary file not shown.
47
main.go
47
main.go
|
@ -1,8 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
_ "image/png"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"drjosh.dev/gurgle/engine"
|
||||
"drjosh.dev/gurgle/game"
|
||||
|
@ -34,7 +37,51 @@ func main() {
|
|||
}
|
||||
g.Prepare()
|
||||
|
||||
// Run a repl on the console.
|
||||
go repl(g)
|
||||
|
||||
// ... while the game also runs
|
||||
if err := ebiten.RunGame(g); err != nil {
|
||||
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