WASM Event Queue
* Refactor the event system in the WASM render engine to serialize the async JavaScript events into a channel, so that queued events are read off serially in the main loop similar to SDL. This fixes keyboard input issues, altho if you type really fast some input keys get lost.
This commit is contained in:
parent
af67b20d9b
commit
c5c85330de
|
@ -19,6 +19,11 @@ type Engine struct {
|
||||||
// Private fields.
|
// Private fields.
|
||||||
events *events.State
|
events *events.State
|
||||||
running bool
|
running bool
|
||||||
|
|
||||||
|
// Event channel. WASM subscribes to events asynchronously using the
|
||||||
|
// JavaScript APIs, whereas SDL2 polls the event queue which orders them
|
||||||
|
// all up for processing. This channel will order and queue the events.
|
||||||
|
queue chan Event
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates the Canvas Engine.
|
// New creates the Canvas Engine.
|
||||||
|
@ -31,6 +36,7 @@ func New(canvasID string) (*Engine, error) {
|
||||||
events: events.New(),
|
events: events.New(),
|
||||||
width: canvas.ClientW(),
|
width: canvas.ClientW(),
|
||||||
height: canvas.ClientH(),
|
height: canvas.ClientH(),
|
||||||
|
queue: make(chan Event, 1024),
|
||||||
}
|
}
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
|
@ -41,9 +47,10 @@ func (e *Engine) WindowSize() (w, h int) {
|
||||||
return e.canvas.ClientW(), e.canvas.ClientH()
|
return e.canvas.ClientW(), e.canvas.ClientH()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTicks returns the current tick count.
|
// GetTicks returns the number of milliseconds since the engine started.
|
||||||
func (e *Engine) GetTicks() uint32 {
|
func (e *Engine) GetTicks() uint32 {
|
||||||
return e.ticks
|
ms := time.Since(e.startTime) * time.Millisecond
|
||||||
|
return uint32(ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TO BE IMPLEMENTED...
|
// TO BE IMPLEMENTED...
|
||||||
|
|
|
@ -7,10 +7,35 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// EventClass to categorize JavaScript events.
|
||||||
|
type EventClass int
|
||||||
|
|
||||||
|
// EventClass values.
|
||||||
|
const (
|
||||||
|
MouseEvent EventClass = iota
|
||||||
|
KeyEvent
|
||||||
|
ResizeEvent
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event object queues up asynchronous JavaScript events to be processed linearly.
|
||||||
|
type Event struct {
|
||||||
|
Name string // mouseup, keydown, etc.
|
||||||
|
Class EventClass
|
||||||
|
|
||||||
|
// Mouse events.
|
||||||
|
X int
|
||||||
|
Y int
|
||||||
|
LeftClick bool
|
||||||
|
RightClick bool
|
||||||
|
|
||||||
|
// Key events.
|
||||||
|
KeyName string
|
||||||
|
State bool
|
||||||
|
Repeat bool
|
||||||
|
}
|
||||||
|
|
||||||
// AddEventListeners sets up bindings to collect events from the browser.
|
// AddEventListeners sets up bindings to collect events from the browser.
|
||||||
func (e *Engine) AddEventListeners() {
|
func (e *Engine) AddEventListeners() {
|
||||||
s := e.events
|
|
||||||
|
|
||||||
// Mouse movement.
|
// Mouse movement.
|
||||||
e.canvas.Value.Call(
|
e.canvas.Value.Call(
|
||||||
"addEventListener",
|
"addEventListener",
|
||||||
|
@ -21,8 +46,12 @@ func (e *Engine) AddEventListeners() {
|
||||||
y = args[0].Get("pageY").Int()
|
y = args[0].Get("pageY").Int()
|
||||||
)
|
)
|
||||||
|
|
||||||
s.CursorX.Push(int32(x))
|
e.queue <- Event{
|
||||||
s.CursorY.Push(int32(y))
|
Name: "mousemove",
|
||||||
|
Class: MouseEvent,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -42,9 +71,6 @@ func (e *Engine) AddEventListeners() {
|
||||||
|
|
||||||
log.Info("Clicked at %d,%d", x, y)
|
log.Info("Clicked at %d,%d", x, y)
|
||||||
|
|
||||||
s.CursorX.Push(int32(x))
|
|
||||||
s.CursorY.Push(int32(y))
|
|
||||||
|
|
||||||
// Is a mouse button pressed down?
|
// Is a mouse button pressed down?
|
||||||
checkDown := func(number int) bool {
|
checkDown := func(number int) bool {
|
||||||
if which == number {
|
if which == number {
|
||||||
|
@ -53,8 +79,14 @@ func (e *Engine) AddEventListeners() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Button1.Push(checkDown(1))
|
e.queue <- Event{
|
||||||
s.Button2.Push(checkDown(3))
|
Name: ev,
|
||||||
|
Class: MouseEvent,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
LeftClick: checkDown(1),
|
||||||
|
RightClick: checkDown(3),
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -71,32 +103,110 @@ func (e *Engine) AddEventListeners() {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Keyboard keys
|
// Keyboard keys
|
||||||
// js.Global().Get("document").Call(
|
for _, ev := range []string{"keydown", "keyup"} {
|
||||||
// "addEventListener",
|
ev := ev
|
||||||
// "keydown",
|
js.Global().Get("document").Call(
|
||||||
// js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
"addEventListener",
|
||||||
// log.Info("key: %+v", args)
|
ev,
|
||||||
// var (
|
js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
// event = args[0]
|
var (
|
||||||
// charCode = event.Get("charCode")
|
event = args[0]
|
||||||
// key = event.Get("key").String()
|
key = event.Get("key").String()
|
||||||
// )
|
repeat = event.Get("repeat").Bool()
|
||||||
//
|
|
||||||
// switch key {
|
pressed = ev == "keydown"
|
||||||
// case "Enter":
|
)
|
||||||
// s.EnterKey.Push(true)
|
|
||||||
// // default:
|
if key == "F3" {
|
||||||
// // s.KeyName.Push(key)
|
args[0].Call("preventDefault")
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// log.Info("keypress: code=%s key=%s", charCode, key)
|
e.queue <- Event{
|
||||||
//
|
Name: ev,
|
||||||
// return nil
|
Class: KeyEvent,
|
||||||
// }),
|
KeyName: key,
|
||||||
// )
|
Repeat: repeat,
|
||||||
|
State: pressed,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PollEvent returns the next event in the queue, or null.
|
||||||
|
func (e *Engine) PollEvent() *Event {
|
||||||
|
select {
|
||||||
|
case event := <-e.queue:
|
||||||
|
return &event
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll for events.
|
// Poll for events.
|
||||||
func (e *Engine) Poll() (*events.State, error) {
|
func (e *Engine) Poll() (*events.State, error) {
|
||||||
|
s := e.events
|
||||||
|
|
||||||
|
if e.events.EnterKey.Now {
|
||||||
|
log.Info("saw enter key here, good")
|
||||||
|
}
|
||||||
|
if e.events.KeyName.Now == "h" {
|
||||||
|
log.Info("saw letter h here, good")
|
||||||
|
}
|
||||||
|
|
||||||
|
for event := e.PollEvent(); event != nil; event = e.PollEvent() {
|
||||||
|
switch event.Class {
|
||||||
|
case MouseEvent:
|
||||||
|
s.CursorX.Push(int32(event.X))
|
||||||
|
s.CursorY.Push(int32(event.Y))
|
||||||
|
s.Button1.Push(event.LeftClick)
|
||||||
|
s.Button2.Push(event.RightClick)
|
||||||
|
case KeyEvent:
|
||||||
|
switch event.KeyName {
|
||||||
|
case "Enter":
|
||||||
|
if event.Repeat {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.State {
|
||||||
|
s.EnterKey.Push(true)
|
||||||
|
}
|
||||||
|
case "F3":
|
||||||
|
if event.State {
|
||||||
|
s.KeyName.Push("F3")
|
||||||
|
}
|
||||||
|
case "ArrowUp":
|
||||||
|
s.Up.Push(event.State)
|
||||||
|
case "ArrowLeft":
|
||||||
|
s.Left.Push(event.State)
|
||||||
|
case "ArrowRight":
|
||||||
|
s.Right.Push(event.State)
|
||||||
|
case "ArrowDown":
|
||||||
|
s.Down.Push(event.State)
|
||||||
|
case "Shift":
|
||||||
|
s.ShiftActive.Push(event.State)
|
||||||
|
continue
|
||||||
|
case "Alt":
|
||||||
|
case "Control":
|
||||||
|
continue
|
||||||
|
case "Backspace":
|
||||||
|
if event.State {
|
||||||
|
s.KeyName.Push(`\b`)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Info("default handler, push key %s", event.KeyName)
|
||||||
|
if event.State {
|
||||||
|
s.KeyName.Push(event.KeyName)
|
||||||
|
} else {
|
||||||
|
s.KeyName.Push("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("event end, stored key=%s", s.KeyName.Now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return e.events, nil
|
return e.events, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ var (
|
||||||
DebugWindowEvents = false
|
DebugWindowEvents = false
|
||||||
DebugMouseEvents = false
|
DebugMouseEvents = false
|
||||||
DebugClickEvents = false
|
DebugClickEvents = false
|
||||||
DebugKeyEvents = false
|
DebugKeyEvents = true
|
||||||
)
|
)
|
||||||
|
|
||||||
// Poll for events.
|
// Poll for events.
|
||||||
|
|
6
pkg/balance/workarounds.go
Normal file
6
pkg/balance/workarounds.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package balance
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Disable chunk texture caching (SLOW!)
|
||||||
|
DisableChunkTextureCache = false
|
||||||
|
)
|
|
@ -105,9 +105,18 @@ func (d *Doodle) Run() error {
|
||||||
|
|
||||||
start := time.Now() // Record how long this frame took.
|
start := time.Now() // Record how long this frame took.
|
||||||
d.ticks++
|
d.ticks++
|
||||||
|
if d.ticks%100 == 0 {
|
||||||
|
log.Info("...tick...%d", d.ticks)
|
||||||
|
}
|
||||||
|
|
||||||
// Poll for events.
|
// Poll for events.
|
||||||
ev, err := d.Engine.Poll()
|
ev, err := d.Engine.Poll()
|
||||||
|
if ev.EnterKey.Now {
|
||||||
|
log.Info("MainLoop sees enter key now")
|
||||||
|
}
|
||||||
|
if ev.KeyName.Now != "" {
|
||||||
|
log.Info("MainLoop sees key %s", ev.KeyName.Now)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("event poll error: %s", err)
|
log.Error("event poll error: %s", err)
|
||||||
d.running = false
|
d.running = false
|
||||||
|
|
|
@ -74,6 +74,8 @@ func NewChunk() *Chunk {
|
||||||
|
|
||||||
// Texture will return a cached texture for the rendering engine for this
|
// Texture will return a cached texture for the rendering engine for this
|
||||||
// chunk's pixel data. If the cache is dirty it will be rebuilt in this func.
|
// chunk's pixel data. If the cache is dirty it will be rebuilt in this func.
|
||||||
|
//
|
||||||
|
// Texture cache can be disabled with balance.DisableChunkTextureCache=true.
|
||||||
func (c *Chunk) Texture(e render.Engine) render.Texturer {
|
func (c *Chunk) Texture(e render.Engine) render.Texturer {
|
||||||
if c.texture == nil || c.dirty {
|
if c.texture == nil || c.dirty {
|
||||||
// Generate the normal bitmap and one with a color mask if applicable.
|
// Generate the normal bitmap and one with a color mask if applicable.
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/lib/render"
|
"git.kirsle.net/apps/doodle/lib/render"
|
||||||
"git.kirsle.net/apps/doodle/lib/render/canvas"
|
"git.kirsle.net/apps/doodle/lib/render/canvas"
|
||||||
doodle "git.kirsle.net/apps/doodle/pkg"
|
doodle "git.kirsle.net/apps/doodle/pkg"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
)
|
)
|
||||||
|
@ -16,6 +17,9 @@ func main() {
|
||||||
fmt.Printf("Hello world\n")
|
fmt.Printf("Hello world\n")
|
||||||
// testRawCanvas()
|
// testRawCanvas()
|
||||||
|
|
||||||
|
// Enable workarounds.
|
||||||
|
balance.DisableChunkTextureCache = true
|
||||||
|
|
||||||
// HTML5 Canvas engine.
|
// HTML5 Canvas engine.
|
||||||
engine, _ := canvas.New("canvas")
|
engine, _ := canvas.New("canvas")
|
||||||
engine.AddEventListeners()
|
engine.AddEventListeners()
|
||||||
|
@ -23,6 +27,8 @@ func main() {
|
||||||
game := doodle.New(true, engine)
|
game := doodle.New(true, engine)
|
||||||
game.SetupEngine()
|
game.SetupEngine()
|
||||||
|
|
||||||
|
doodle.DebugOverlay = true
|
||||||
|
|
||||||
// Manually inform Doodle of the canvas size since it can't control
|
// Manually inform Doodle of the canvas size since it can't control
|
||||||
// the size on its own.
|
// the size on its own.
|
||||||
w, h := engine.WindowSize()
|
w, h := engine.WindowSize()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user