Bindata: Embedding Doodads and Levels (for WASM)
* Use `go-bindata` to embed built-in doodads and levels directly into the Doodle binary. `make bindata` produces the bindata source file. * Add `FromJSON()` method to Levels and Doodads to load objects from JSON strings in memory (for bindata built-ins or WASM ajax requests) * Update file loading functions to check the embedded bindata files. * pkg/config.go#EditFile: * Supports editing a level from bindata (TODO: remove this support) * If the "assets/levels/%(simple-name.level)" exists in bindata, edits that drawing. * No such support for editing built-in doodads. * WASM has no filesystem access to edit files except built-in levels (yet) * pkg/doodads#ListDoodads: * Prepends built-in doodads from bindata to the returned list. * WASM: no filesystem access so gets only the built-ins. * pkg/doodads#LoadFile: * Checks built-in bindata store first for doodad files. * WASM: tries an HTTP request if not found in bindata but can go no further if not found (no filesystem access) * pkg/filesystem#FindFile: * This function finds a level/doodad by checking all the places. * If the level or doodad exists in bindata built-in, always returns its system path like "assets/doodads/test.doodad" * WASM: always returns the built-in candidate path even if not found in bindata so that ajax GET can be attempted. * pkg/level#ListSystemLevels: * New function that lists the system level files, similar to the equivalent doodads function. * Prepends the bindata built-in level files. * WASM: only returns the built-ins (no filesystem support) * Desktop: also lists and returns the assets/levels/ directory. * pkg/level#LoadFile: * Like the doodads.LoadFile, tries from built-in bindata first, then ajax request (WASM) before accessing the filesystem (desktop) * Menu Scene: TODO, list the built-in levels in the Load Level menu. This feature will soon go away when WASM gets its own storage for user levels (localStorage instead of filesystem)
This commit is contained in:
parent
c7cc40a339
commit
b17ca34de2
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
pkg/bindata/bindata.go
|
||||
fonts/
|
||||
maps/
|
||||
bin/
|
||||
|
|
10
Makefile
10
Makefile
|
@ -34,6 +34,16 @@ build-debug:
|
|||
go build $(LDFLAGS) -tags="developer" -i -o bin/doodle cmd/doodle/main.go
|
||||
go build $(LDFLAGS) -tags="developer" -i -o bin/doodad cmd/doodad/main.go
|
||||
|
||||
# `make bindata` generates the embedded binary assets package.
|
||||
.PHONY: bindata
|
||||
bindata:
|
||||
go-bindata -pkg bindata -o pkg/bindata/bindata.go assets/... fonts/
|
||||
|
||||
# `make bindata-dev` generates the debug version of bindata package.
|
||||
.PHONY: bindata-dev
|
||||
bindata-dev:
|
||||
go-bindata -debug -pkg bindata -o pkg/bindata/bindata.go assets/... fonts/
|
||||
|
||||
# `make wasm` builds the WebAssembly port.
|
||||
.PHONY: wasm
|
||||
wasm:
|
||||
|
|
|
@ -5,8 +5,10 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/bindata"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||
)
|
||||
|
@ -34,14 +36,35 @@ func (d *Doodle) EditFile(filename string) error {
|
|||
if m := reSimpleFilename.FindStringSubmatch(filename); len(m) > 0 {
|
||||
log.Debug("EditFile: simple filename %s", filename)
|
||||
extension := strings.ToLower(filepath.Ext(filename))
|
||||
|
||||
// Check the system level storage. TODO: no editing of system levels
|
||||
if _, err := bindata.Asset("assets/levels/" + filename); err == nil {
|
||||
log.Info("Found level %s in bindata", filename)
|
||||
return d.EditDrawing(filename)
|
||||
}
|
||||
|
||||
// WASM: no filesystem access, can go no further.
|
||||
if runtime.GOOS == "js" {
|
||||
return fmt.Errorf("EditFile(%s): not found for WASM and can go no further", filename)
|
||||
}
|
||||
|
||||
// Check the user's levels directory.
|
||||
if foundFilename := userdir.ResolvePath(filename, extension, false); foundFilename != "" {
|
||||
log.Info("EditFile: resolved name '%s' to path %s", filename, foundFilename)
|
||||
absPath = foundFilename
|
||||
} else {
|
||||
return fmt.Errorf("EditFile: %s: no level or doodad found", filename)
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Debug("Not a simple: %s %+v", filename, reSimpleFilename)
|
||||
|
||||
// WASM: no filesystem access.
|
||||
if runtime.GOOS == "js" {
|
||||
log.Error("EditFile(%s): wasm can't open file paths", filename)
|
||||
return fmt.Errorf("EditFile(%s): wasm can't open file paths", filename)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filename); !os.IsNotExist(err) {
|
||||
log.Debug("EditFile: verified path %s exists", filename)
|
||||
absPath = filename
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
package doodads
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/bindata"
|
||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||
"git.kirsle.net/apps/doodle/pkg/filesystem"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||
"git.kirsle.net/apps/doodle/pkg/wasm"
|
||||
)
|
||||
|
||||
// ListDoodads returns a listing of all available doodads between all locations,
|
||||
|
@ -18,6 +20,18 @@ import (
|
|||
func ListDoodads() ([]string, error) {
|
||||
var names []string
|
||||
|
||||
// List doodads embedded into the binary.
|
||||
if files, err := bindata.AssetDir("assets/doodads"); err == nil {
|
||||
names = append(names, files...)
|
||||
}
|
||||
|
||||
// WASM
|
||||
if runtime.GOOS == "js" {
|
||||
// Return the array of doodads embedded in the bindata.
|
||||
// TODO: append user doodads to the list.
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// Read system-level doodads first. Ignore errors, if the system path is
|
||||
// empty we still go on to read the user directory.
|
||||
files, _ := ioutil.ReadDir(filesystem.SystemDoodadsPath)
|
||||
|
@ -47,14 +61,23 @@ func LoadFile(filename string) (*Doodad, error) {
|
|||
return nil, fmt.Errorf("doodads.LoadFile(%s): %s", filename, err)
|
||||
}
|
||||
|
||||
// Load the JSON format.
|
||||
if doodad, err := LoadJSON(filename); err == nil {
|
||||
return doodad, nil
|
||||
} else {
|
||||
log.Warn(err.Error())
|
||||
// Do we have the file in bindata?
|
||||
if jsonData, err := bindata.Asset(filename); err == nil {
|
||||
return FromJSON(filename, jsonData)
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid file type")
|
||||
// WASM: try the file over HTTP ajax request.
|
||||
if runtime.GOOS == "js" {
|
||||
jsonData, err := wasm.HTTPGet(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return FromJSON(filename, jsonData)
|
||||
}
|
||||
|
||||
// Load the JSON file from the filesystem.
|
||||
return LoadJSON(filename)
|
||||
}
|
||||
|
||||
// WriteFile saves a doodad to disk in the user's config directory.
|
||||
|
|
|
@ -18,6 +18,18 @@ func (d *Doodad) ToJSON() ([]byte, error) {
|
|||
return out.Bytes(), err
|
||||
}
|
||||
|
||||
// FromJSON loads a doodad from JSON string.
|
||||
func FromJSON(filename string, data []byte) (*Doodad, error) {
|
||||
var doodad = &Doodad{}
|
||||
err := json.Unmarshal(data, doodad)
|
||||
|
||||
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
||||
doodad.Filename = filepath.Base(filename)
|
||||
doodad.Inflate()
|
||||
|
||||
return doodad, err
|
||||
}
|
||||
|
||||
// WriteJSON writes a Doodad to JSON on disk.
|
||||
func (d *Doodad) WriteJSON(filename string) error {
|
||||
json, err := d.ToJSON()
|
||||
|
|
|
@ -167,6 +167,7 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra
|
|||
// NOTE: The drag target is the EditorUI.Canvas in
|
||||
// editor_ui.go#SetupCanvas()
|
||||
btn.Handle(ui.MouseDown, func(e render.Point) {
|
||||
log.Warn("MouseDown on doodad %s (%s)", doodad.Filename, doodad.Title)
|
||||
u.startDragActor(doodad)
|
||||
})
|
||||
u.Supervisor.Add(btn)
|
||||
|
|
|
@ -6,8 +6,10 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/bindata"
|
||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||
)
|
||||
|
@ -24,8 +26,8 @@ const (
|
|||
|
||||
// Paths to system-level assets bundled with the application.
|
||||
var (
|
||||
SystemDoodadsPath = filepath.Join(".", "assets", "doodads")
|
||||
SystemLevelsPath = filepath.Join(".", "assets", "levels")
|
||||
SystemDoodadsPath = filepath.Join("assets", "doodads")
|
||||
SystemLevelsPath = filepath.Join("assets", "levels")
|
||||
)
|
||||
|
||||
// MakeHeader creates the binary file header.
|
||||
|
@ -94,6 +96,19 @@ func FindFile(filename string) (string, error) {
|
|||
if filetype == enum.LevelExt || filetype == "" {
|
||||
// system levels
|
||||
candidate := filepath.Join(SystemLevelsPath, filename)
|
||||
|
||||
// embedded system doodad?
|
||||
if _, err := bindata.Asset(candidate); err == nil {
|
||||
return candidate, nil
|
||||
}
|
||||
|
||||
// WASM: can't check the filesystem. Let the caller go ahead and try
|
||||
// loading via ajax request.
|
||||
if runtime.GOOS == "js" {
|
||||
return candidate, nil
|
||||
}
|
||||
|
||||
// external system level?
|
||||
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
||||
return candidate, nil
|
||||
}
|
||||
|
@ -107,8 +122,21 @@ func FindFile(filename string) (string, error) {
|
|||
|
||||
// Search doodad directories.
|
||||
if filetype == enum.DoodadExt || filetype == "" {
|
||||
// system doodads
|
||||
// system doodads path
|
||||
candidate := filepath.Join(SystemDoodadsPath, filename)
|
||||
|
||||
// embedded system doodad?
|
||||
if _, err := bindata.Asset(candidate); err == nil {
|
||||
return candidate, nil
|
||||
}
|
||||
|
||||
// WASM: can't check the filesystem. Let the caller go ahead and try
|
||||
// loading via ajax request.
|
||||
if runtime.GOOS == "js" {
|
||||
return candidate, nil
|
||||
}
|
||||
|
||||
// external system doodad?
|
||||
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
||||
return candidate, nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,26 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
)
|
||||
|
||||
// FromJSON loads a level from JSON string.
|
||||
func FromJSON(filename string, data []byte) (*Level, error) {
|
||||
var m = &Level{}
|
||||
err := json.Unmarshal(data, m)
|
||||
|
||||
// Fill in defaults.
|
||||
if m.Wallpaper == "" {
|
||||
m.Wallpaper = DefaultWallpaper
|
||||
}
|
||||
|
||||
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
||||
m.Chunker.Inflate(m.Palette)
|
||||
m.Actors.Inflate()
|
||||
|
||||
// Inflate the private instance values.
|
||||
m.Palette.Inflate()
|
||||
|
||||
return m, err
|
||||
}
|
||||
|
||||
// ToJSON serializes the level as JSON.
|
||||
func (m *Level) ToJSON() ([]byte, error) {
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
|
|
|
@ -4,15 +4,45 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/bindata"
|
||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||
"git.kirsle.net/apps/doodle/pkg/filesystem"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||
"git.kirsle.net/apps/doodle/pkg/wasm"
|
||||
)
|
||||
|
||||
// ListSystemLevels returns a list of built-in levels.
|
||||
func ListSystemLevels() ([]string, error) {
|
||||
var names = []string{}
|
||||
|
||||
// Add the levels embedded inside the binary.
|
||||
if levels, err := bindata.AssetDir("assets/levels"); err == nil {
|
||||
names = append(names, levels...)
|
||||
}
|
||||
|
||||
// WASM
|
||||
if runtime.GOOS == "js" {
|
||||
// Return just the embedded ones, no filesystem access.
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// Read filesystem for system levels.
|
||||
files, err := ioutil.ReadDir(filesystem.SystemLevelsPath)
|
||||
for _, file := range files {
|
||||
name := file.Name()
|
||||
if strings.HasSuffix(strings.ToLower(name), enum.DoodadExt) {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
|
||||
return names, err
|
||||
}
|
||||
|
||||
// LoadFile reads a level file from disk, checking a few locations.
|
||||
func LoadFile(filename string) (*Level, error) {
|
||||
if !strings.HasSuffix(filename, enum.LevelExt) {
|
||||
|
@ -25,6 +55,22 @@ func LoadFile(filename string) (*Level, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Do we have the file in bindata?
|
||||
if jsonData, err := bindata.Asset(filename); err == nil {
|
||||
log.Info("loaded from embedded bindata")
|
||||
return FromJSON(filename, jsonData)
|
||||
}
|
||||
|
||||
// WASM: try the file over HTTP ajax request.
|
||||
if runtime.GOOS == "js" {
|
||||
jsonData, err := wasm.HTTPGet(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return FromJSON(filename, jsonData)
|
||||
}
|
||||
|
||||
// Try the binary format.
|
||||
if level, err := LoadBinary(filename); err == nil {
|
||||
return level, nil
|
||||
|
|
|
@ -337,7 +337,13 @@ func (s *MenuScene) setupLoadWindow(d *Doodle) error {
|
|||
FillX: true,
|
||||
})
|
||||
|
||||
// Get the user's levels.
|
||||
levels, _ := userdir.ListLevels()
|
||||
|
||||
// Embedded levels, TODO
|
||||
sysLevels, _ := level.ListSystemLevels()
|
||||
levels = append(levels, sysLevels...)
|
||||
|
||||
lvlRow := ui.NewFrame("Level Row 0")
|
||||
frame.Pack(lvlRow, ui.Pack{
|
||||
Anchor: ui.N,
|
||||
|
|
|
@ -104,7 +104,7 @@ func (s *PlayScene) Setup(d *Doodle) error {
|
|||
}
|
||||
|
||||
// Load in the player character.
|
||||
player, err := doodads.LoadFile("./assets/doodads/azu-blu.doodad")
|
||||
player, err := doodads.LoadFile("azu-blu.doodad")
|
||||
if err != nil {
|
||||
log.Error("PlayScene.Setup: failed to load player doodad: %s", err)
|
||||
player = doodads.NewDummy(32)
|
||||
|
|
|
@ -15,9 +15,14 @@ var (
|
|||
|
||||
// Globally available Flash() function so we can emit text to the Doodle UI.
|
||||
Flash func(string, ...interface{})
|
||||
|
||||
// Ajax file cache for WASM use.
|
||||
AjaxCache map[string][]byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
AjaxCache = map[string][]byte{}
|
||||
|
||||
// Default Flash function in case the app misconfigures it. Output to the
|
||||
// console in an obvious way.
|
||||
Flash = func(tmpl string, v ...interface{}) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"github.com/kirsle/configdir"
|
||||
)
|
||||
|
||||
|
@ -132,6 +133,12 @@ func resolvePath(directory, filename, extension string) string {
|
|||
// existed. So the filename should have a ".level" or ".doodad" extension and
|
||||
// then this path will resolve the ProfileDirectory of the file.
|
||||
func ResolvePath(filename, extension string, one bool) string {
|
||||
// WASM has no file system.
|
||||
if runtime.GOOS == "js" {
|
||||
log.Error("userdir.ResolvePath: not supported in WASM build")
|
||||
return ""
|
||||
}
|
||||
|
||||
// If the filename exists outright, return it.
|
||||
if _, err := os.Stat(filename); !os.IsNotExist(err) {
|
||||
return filename
|
||||
|
|
44
pkg/wasm/ajax.go
Normal file
44
pkg/wasm/ajax.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package wasm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
)
|
||||
|
||||
// HTTPGet fetches a path via ajax request.
|
||||
func HTTPGet(filename string) ([]byte, error) {
|
||||
// Already cached?
|
||||
jsonData, ok := shmem.AjaxCache[filename]
|
||||
if ok {
|
||||
return jsonData, nil
|
||||
}
|
||||
|
||||
// Fetch the URI.
|
||||
resp, err := http.Get(filename)
|
||||
if err != nil {
|
||||
log.Error("http error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Error?
|
||||
if !(resp.StatusCode >= 200 && resp.StatusCode < 300) {
|
||||
return nil, fmt.Errorf("failed to load URI %s: HTTP %d response",
|
||||
filename,
|
||||
resp.StatusCode,
|
||||
)
|
||||
}
|
||||
|
||||
// Parse and store the response in cache.
|
||||
jsonData, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shmem.AjaxCache[filename] = jsonData
|
||||
|
||||
return jsonData, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user