diff --git a/dev-assets/doodads/azulian/azulian.js b/dev-assets/doodads/azulian/azulian.js index 5178cc7..8636700 100644 --- a/dev-assets/doodads/azulian/azulian.js +++ b/dev-assets/doodads/azulian/azulian.js @@ -1,5 +1,5 @@ function main() { - log.Info("Azulian '%s' initialized!", Self.Doodad.Title); + Flash("Azulian '%s' initialized!", Self.Doodad.Title); var playerSpeed = 12; var gravity = 4; diff --git a/lib/render/canvas/canvas.go b/lib/render/canvas/canvas.go index c5eb82f..e492099 100644 --- a/lib/render/canvas/canvas.go +++ b/lib/render/canvas/canvas.go @@ -28,10 +28,12 @@ func GetCanvas(id string) Canvas { // ClientW returns the client width. func (c Canvas) ClientW() int { - return c.Value.Get("clientWidth").Int() + return js.Global().Get("window").Get("innerWidth").Int() + // return c.Value.Get("clientWidth").Int() } // ClientH returns the client height. func (c Canvas) ClientH() int { - return c.Value.Get("clientHeight").Int() + return js.Global().Get("window").Get("innerHeight").Int() + // return c.Value.Get("clientHeight").Int() } diff --git a/lib/render/canvas/engine.go b/lib/render/canvas/engine.go index 5368083..10d5563 100644 --- a/lib/render/canvas/engine.go +++ b/lib/render/canvas/engine.go @@ -1,13 +1,16 @@ package canvas import ( + "bytes" + "encoding/base64" "errors" + "image" + "image/png" "syscall/js" "time" "git.kirsle.net/apps/doodle/lib/events" "git.kirsle.net/apps/doodle/lib/render" - "git.kirsle.net/apps/doodle/pkg/wasm" ) // Engine implements a rendering engine targeting an HTML canvas for @@ -20,8 +23,9 @@ type Engine struct { ticks uint32 // Private fields. - events *events.State - running bool + events *events.State + running bool + textures map[string]*Texture // cached texture PNG images // Event channel. WASM subscribes to events asynchronously using the // JavaScript APIs, whereas SDL2 polls the event queue which orders them @@ -40,6 +44,7 @@ func New(canvasID string) (*Engine, error) { width: canvas.ClientW(), height: canvas.ClientH(), queue: make(chan Event, 1024), + textures: map[string]*Texture{}, } return engine, nil @@ -47,6 +52,14 @@ func New(canvasID string) (*Engine, error) { // WindowSize returns the size of the canvas window. func (e *Engine) WindowSize() (w, h int) { + // Good time to recompute it first? + var ( + window = js.Global().Get("window") + width = window.Get("innerWidth").Int() + height = window.Get("innerHeight").Int() + ) + e.canvas.Value.Set("width", width) + e.canvas.Value.Set("height", height) return e.canvas.ClientW(), e.canvas.ClientH() } @@ -70,10 +83,58 @@ func (e *Engine) Present() error { type Texture struct { data string // data:image/png URI image js.Value // DOM image element + canvas js.Value // Warmed up canvas element + ctx2d js.Value // 2D drawing context for the canvas. width int height int } +// NewTexture caches a texture from a bitmap. +func (e *Engine) NewTexture(filename string, img image.Image) (render.Texturer, error) { + var ( + fh = bytes.NewBuffer([]byte{}) + imageSize = img.Bounds().Size() + width = imageSize.X + height = imageSize.Y + ) + + // Encode to PNG format. + if err := png.Encode(fh, img); err != nil { + return nil, err + } + + var dataURI = "data:image/png;base64," + base64.StdEncoding.EncodeToString(fh.Bytes()) + + tex := &Texture{ + data: dataURI, + width: width, + height: height, + } + + // Preheat a cached Canvas object. + canvas := js.Global().Get("document").Call("createElement", "canvas") + canvas.Set("width", width) + canvas.Set("height", height) + tex.canvas = canvas + + ctx2d := canvas.Call("getContext", "2d") + tex.ctx2d = ctx2d + + // Load as a JS Image object. + image := js.Global().Call("eval", "new Image()") + image.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) interface{} { + ctx2d.Call("drawImage", image, 0, 0) + return nil + })) + image.Set("src", tex.data) + tex.image = image + + // Cache the texture in memory. + e.textures[filename] = tex + + return tex, nil +} + // Size returns the dimensions of the texture. func (t *Texture) Size() render.Rect { return render.NewRect(int32(t.width), int32(t.height)) @@ -82,37 +143,21 @@ func (t *Texture) Size() render.Rect { // NewBitmap initializes a texture from a bitmap image. The image is stored // in HTML5 Session Storage. func (e *Engine) NewBitmap(filename string) (render.Texturer, error) { - if data, ok := wasm.GetSession(filename); ok { - img := js.Global().Get("document").Call("createElement", "img") - img.Set("src", data) - return &Texture{ - data: data, - image: img, - width: 60, // TODO - height: 60, - }, nil + if tex, ok := e.textures[filename]; ok { + return tex, nil } + panic("no bitmap for " + filename) return nil, errors.New("no bitmap data stored for " + filename) - } -var TODO int - // Copy a texturer bitmap onto the canvas. func (e *Engine) Copy(t render.Texturer, src, dist render.Rect) { tex := t.(*Texture) - // image := js.Global().Get("document").Call("createElement", "img") - // image.Set("src", tex.data) + // e.canvas.ctx2d.Call("drawImage", tex.image, dist.X, dist.Y) + e.canvas.ctx2d.Call("drawImage", tex.canvas, dist.X, dist.Y) - // log.Info("drawing image just this once") - e.canvas.ctx2d.Call("drawImage", tex.image, dist.X, dist.Y) - // TODO++ - // if TODO > 200 { - // log.Info("I exited at engine.Copy for canvas engine") - // os.Exit(0) - // } } // Delay for a moment. diff --git a/lib/render/canvas/events.go b/lib/render/canvas/events.go index f21dade..4cc4905 100644 --- a/lib/render/canvas/events.go +++ b/lib/render/canvas/events.go @@ -15,6 +15,7 @@ const ( ClickEvent KeyEvent ResizeEvent + WindowEvent ) // Event object queues up asynchronous JavaScript events to be processed linearly. @@ -36,6 +37,19 @@ type Event struct { // AddEventListeners sets up bindings to collect events from the browser. func (e *Engine) AddEventListeners() { + // Window resize. + js.Global().Get("window").Call( + "addEventListener", + "resize", + js.FuncOf(func(this js.Value, args []js.Value) interface{} { + e.queue <- Event{ + Name: "resize", + Class: WindowEvent, + } + return nil + }), + ) + // Mouse movement. e.canvas.Value.Call( "addEventListener", @@ -149,6 +163,8 @@ func (e *Engine) Poll() (*events.State, error) { for event := e.PollEvent(); event != nil; event = e.PollEvent() { switch event.Class { + case WindowEvent: + s.Resized.Push(true) case MouseEvent: s.CursorX.Push(int32(event.X)) s.CursorY.Push(int32(event.Y)) diff --git a/lib/render/interface.go b/lib/render/interface.go index 2c265a5..5f912df 100644 --- a/lib/render/interface.go +++ b/lib/render/interface.go @@ -2,6 +2,7 @@ package render import ( "fmt" + "image" "math" "git.kirsle.net/apps/doodle/lib/events" @@ -32,6 +33,7 @@ type Engine interface { // Texture caching. NewBitmap(filename string) (Texturer, error) + NewTexture(filename string, img image.Image) (Texturer, error) Copy(t Texturer, src, dst Rect) // Delay for a moment using the render engine's delay method, diff --git a/lib/render/sdl/texture.go b/lib/render/sdl/texture.go index b7843cc..0fdad45 100644 --- a/lib/render/sdl/texture.go +++ b/lib/render/sdl/texture.go @@ -2,9 +2,12 @@ package sdl import ( "fmt" + "image" + "os" "git.kirsle.net/apps/doodle/lib/render" "github.com/veandco/go-sdl2/sdl" + "golang.org/x/image/bmp" ) // Copy a texture into the renderer. @@ -25,6 +28,18 @@ type Texture struct { height int32 } +// NewTexture caches an SDL texture from a bitmap. +func (r *Renderer) NewTexture(filename string, img image.Image) (render.Texturer, error) { + fh, err := os.Create(filename) + if err != nil { + return nil, err + } + defer fh.Close() + + err = bmp.Encode(fh, img) + return nil, err +} + // Size returns the dimensions of the texture. func (t *Texture) Size() render.Rect { return render.NewRect(t.width, t.height) diff --git a/pkg/doodle.go b/pkg/doodle.go index b7aaf31..d158638 100644 --- a/pkg/doodle.go +++ b/pkg/doodle.go @@ -11,6 +11,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/branding" "git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/apps/doodle/pkg/shmem" "github.com/kirsle/golog" ) @@ -56,6 +57,10 @@ func New(debug bool, engine render.Engine) *Doodle { } d.shell = NewShell(d) + // Make the render engine globally available. TODO: for wasm/ToBitmap + shmem.CurrentRenderEngine = engine + shmem.Flash = d.Flash + if debug { log.Logger.Config.Level = golog.DebugLevel // DebugOverlay = true // on by default in debug mode, F3 to disable diff --git a/pkg/level/chunk.go b/pkg/level/chunk.go index 434c1fe..39f7a09 100644 --- a/pkg/level/chunk.go +++ b/pkg/level/chunk.go @@ -11,8 +11,8 @@ import ( "git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/apps/doodle/pkg/shmem" "git.kirsle.net/apps/doodle/pkg/userdir" - "git.kirsle.net/apps/doodle/pkg/wasm" "github.com/satori/go.uuid" "github.com/vmihailenco/msgpack" ) @@ -187,8 +187,9 @@ func (c *Chunk) ToBitmap(filename string, mask render.Color) error { ) } - // Write the image to bitmap file. - return wasm.StoreBitmap(filename, img) + // Cache the texture data with the current renderer. + _, err := shmem.CurrentRenderEngine.NewTexture(filename, img) + return err } // Set proxies to the accessor and flags the texture as dirty. diff --git a/pkg/scripting/vm.go b/pkg/scripting/vm.go index 34b86e3..6dd4095 100644 --- a/pkg/scripting/vm.go +++ b/pkg/scripting/vm.go @@ -7,6 +7,7 @@ import ( "git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/apps/doodle/pkg/shmem" "github.com/robertkrimen/otto" ) @@ -67,6 +68,7 @@ func (vm *VM) Set(name string, v interface{}) error { func (vm *VM) RegisterLevelHooks() error { bindings := map[string]interface{}{ "log": log.Logger, + "Flash": shmem.Flash, "RGBA": render.RGBA, "Point": render.NewPoint, "Self": vm.Self, // i.e., the uix.Actor object diff --git a/pkg/shmem/globals.go b/pkg/shmem/globals.go new file mode 100644 index 0000000..64549af --- /dev/null +++ b/pkg/shmem/globals.go @@ -0,0 +1,26 @@ +package shmem + +import ( + "fmt" + + "git.kirsle.net/apps/doodle/lib/render" +) + +// Shared globals for easy access throughout the app. +// Not an ideal place to keep things but *shrug* +var ( + // Current render engine (i.e. SDL2 or HTML5 Canvas) + // The level.Chunk.ToBitmap() uses this to cache a texture image. + CurrentRenderEngine render.Engine + + // Globally available Flash() function so we can emit text to the Doodle UI. + Flash func(string, ...interface{}) +) + +func init() { + // Default Flash function in case the app misconfigures it. Output to the + // console in an obvious way. + Flash = func(tmpl string, v ...interface{}) { + fmt.Printf("[shmem.Flash] "+tmpl+"\n", v...) + } +} diff --git a/pkg/wasm/bitmap.go b/pkg/wasm/bitmap.go deleted file mode 100644 index addd855..0000000 --- a/pkg/wasm/bitmap.go +++ /dev/null @@ -1,20 +0,0 @@ -// +build !js - -package wasm - -import ( - "image" - "os" - - "golang.org/x/image/bmp" -) - -// StoreBitmap stores a bitmap image to disk. -func StoreBitmap(filename string, img image.Image) error { - fh, err := os.Create(filename) - if err != nil { - return err - } - defer fh.Close() - return bmp.Encode(fh, img) -} diff --git a/pkg/wasm/bitmap_js.go b/pkg/wasm/bitmap_js.go deleted file mode 100644 index ddd2144..0000000 --- a/pkg/wasm/bitmap_js.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build js,wasm - -package wasm - -import ( - "bytes" - "encoding/base64" - "image" - "image/png" -) - -// StoreBitmap stores a bitmap image to sessionStorage as a data URL for PNG -// base64 encoded image. -func StoreBitmap(filename string, img image.Image) error { - var fh = bytes.NewBuffer([]byte{}) - - if err := png.Encode(fh, img); err != nil { - return err - } - - var dataURI = "data:image/png;base64," + base64.StdEncoding.EncodeToString(fh.Bytes()) - - SetSession(filename, dataURI) - return nil -} diff --git a/pkg/wasm/localstorage_js.go b/pkg/wasm/localstorage_js.go index fbb70d4..eb8074d 100644 --- a/pkg/wasm/localstorage_js.go +++ b/pkg/wasm/localstorage_js.go @@ -9,11 +9,13 @@ import ( // SetSession sets a text value on sessionStorage. func SetSession(key string, value string) { // b64 := base64.StdEncoding.EncodeToString(value) + panic("SesSession: " + key) js.Global().Get("sessionStorage").Call("setItem", key, value) } // GetSession retrieves a text value from sessionStorage. func GetSession(key string) (string, bool) { + panic("GetSession: " + key) var value js.Value value = js.Global().Get("sessionStorage").Call("getItem", key) return value.String(), value.Type() == js.TypeString