io/router: avoid a decode round for key ops

Like a previous change for pointer ops, process key ops during the
router decode of ops. This is a performance optimization and preparation
for processing future accessibility ops without without another decode
loop.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-10-22 14:38:08 +02:00
parent 60846d112b
commit e4b96cb779
3 changed files with 81 additions and 48 deletions
+53 -39
View File
@@ -6,7 +6,6 @@ import (
"gioui.org/internal/ops" "gioui.org/internal/ops"
"gioui.org/io/event" "gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/op"
) )
type TextInputState uint8 type TextInputState uint8
@@ -14,7 +13,6 @@ type TextInputState uint8
type keyQueue struct { type keyQueue struct {
focus event.Tag focus event.Tag
handlers map[event.Tag]*keyHandler handlers map[event.Tag]*keyHandler
reader ops.Reader
state TextInputState state TextInputState
hint key.InputHint hint key.InputHint
} }
@@ -27,6 +25,14 @@ type keyHandler struct {
hint key.InputHint hint key.InputHint
} }
// keyCollector tracks state required to update a keyQueue
// from key ops.
type keyCollector struct {
q *keyQueue
focus event.Tag
changed bool
}
const ( const (
TextInputKeep TextInputState = iota TextInputKeep TextInputState = iota
TextInputClose TextInputClose
@@ -53,46 +59,46 @@ func (q *keyQueue) InputHint() (key.InputHint, bool) {
return q.hint, old != q.hint return q.hint, old != q.hint
} }
func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) { func (q *keyQueue) Reset() {
if q.handlers == nil { if q.handlers == nil {
q.handlers = make(map[event.Tag]*keyHandler) q.handlers = make(map[event.Tag]*keyHandler)
} }
for _, h := range q.handlers { for _, h := range q.handlers {
h.visible, h.new = false, false h.visible, h.new = false, false
} }
q.reader.Reset(&root.Internal) q.state = TextInputKeep
}
focus, changed, state := q.resolveFocus(events) func (q *keyQueue) Frame(events *handlerEvents, collector keyCollector) {
for k, h := range q.handlers { for k, h := range q.handlers {
if !h.visible { if !h.visible {
delete(q.handlers, k) delete(q.handlers, k)
if q.focus == k { if q.focus == k {
// Remove the focus from the handler that is no longer visible. // Remove the focus from the handler that is no longer visible.
q.focus = nil q.focus = nil
state = TextInputClose q.state = TextInputClose
} }
} else if h.new && k != focus { } else if h.new && k != collector.focus {
// Reset the handler on (each) first appearance, but don't trigger redraw. // Reset the handler on (each) first appearance, but don't trigger redraw.
events.AddNoRedraw(k, key.FocusEvent{Focus: false}) events.AddNoRedraw(k, key.FocusEvent{Focus: false})
} }
} }
if changed && focus != nil { if collector.changed && collector.focus != nil {
if _, exists := q.handlers[focus]; !exists { if _, exists := q.handlers[collector.focus]; !exists {
focus = nil collector.focus = nil
} }
} }
if changed && focus != q.focus { if collector.changed && collector.focus != q.focus {
if q.focus != nil { if q.focus != nil {
events.Add(q.focus, key.FocusEvent{Focus: false}) events.Add(q.focus, key.FocusEvent{Focus: false})
} }
q.focus = focus q.focus = collector.focus
if q.focus != nil { if q.focus != nil {
events.Add(q.focus, key.FocusEvent{Focus: true}) events.Add(q.focus, key.FocusEvent{Focus: true})
} else { } else {
state = TextInputClose q.state = TextInputClose
} }
} }
q.state = state
} }
func (q *keyQueue) Push(e event.Event, events *handlerEvents) { func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
@@ -101,32 +107,27 @@ func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
} }
} }
func (q *keyQueue) resolveFocus(events *handlerEvents) (focus event.Tag, changed bool, state TextInputState) { func (k *keyCollector) focusOp(tag event.Tag) {
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() { k.focus = tag
switch ops.OpType(encOp.Data[0]) { k.changed = true
case ops.TypeKeyFocus: }
op := decodeFocusOp(encOp.Data, encOp.Refs)
changed = true func (k *keyCollector) softKeyboard(show bool) {
focus = op.Tag if show {
case ops.TypeKeySoftKeyboard: k.q.state = TextInputOpen
op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs) } else {
if op.Show { k.q.state = TextInputClose
state = TextInputOpen
} else {
state = TextInputClose
}
case ops.TypeKeyInput:
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
h, ok := q.handlers[op.Tag]
if !ok {
h = &keyHandler{new: true}
q.handlers[op.Tag] = h
}
h.visible = true
h.hint = op.Hint
}
} }
return }
func (k *keyCollector) inputOp(op key.InputOp) {
h, ok := k.q.handlers[op.Tag]
if !ok {
h = &keyHandler{new: true}
k.q.handlers[op.Tag] = h
}
h.visible = true
h.hint = op.Hint
} }
func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp { func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp {
@@ -156,3 +157,16 @@ func decodeFocusOp(d []byte, refs []interface{}) key.FocusOp {
Tag: refs[0], Tag: refs[0],
} }
} }
func (t TextInputState) String() string {
switch t {
case TextInputKeep:
return "Keep"
case TextInputClose:
return "Close"
case TextInputOpen:
return "Open"
default:
panic("unexpected value")
}
}
+4 -4
View File
@@ -268,14 +268,14 @@ func assertKeyEventUnexpected(t *testing.T, events []event.Event) {
func assertFocus(t *testing.T, router *Router, expected event.Tag) { func assertFocus(t *testing.T, router *Router, expected event.Tag) {
t.Helper() t.Helper()
if router.kqueue.focus != expected { if got := router.key.queue.focus; got != expected {
t.Errorf("expected %v to be focused, got %v", expected, router.kqueue.focus) t.Errorf("expected %v to be focused, got %v", expected, got)
} }
} }
func assertKeyboard(t *testing.T, router *Router, expected TextInputState) { func assertKeyboard(t *testing.T, router *Router, expected TextInputState) {
t.Helper() t.Helper()
if router.kqueue.state != expected { if got := router.key.queue.state; got != expected {
t.Errorf("expected %v keyboard, got %v", expected, router.kqueue.state) t.Errorf("expected %v keyboard, got %v", expected, got)
} }
} }
+24 -5
View File
@@ -31,7 +31,10 @@ type Router struct {
queue pointerQueue queue pointerQueue
collector pointerCollector collector pointerCollector
} }
kqueue keyQueue key struct {
queue keyQueue
collector keyCollector
}
cqueue clipboardQueue cqueue clipboardQueue
handlers handlerEvents handlers handlerEvents
@@ -75,7 +78,7 @@ func (q *Router) Frame(ops *op.Ops) {
q.collect() q.collect()
q.pointer.queue.Frame(&q.handlers) q.pointer.queue.Frame(&q.handlers)
q.kqueue.Frame(ops, &q.handlers) q.key.queue.Frame(&q.handlers, q.key.collector)
if q.handlers.HadEvents() { if q.handlers.HadEvents() {
q.wakeup = true q.wakeup = true
q.wakeupTime = time.Time{} q.wakeupTime = time.Time{}
@@ -91,7 +94,7 @@ func (q *Router) Queue(events ...event.Event) bool {
case pointer.Event: case pointer.Event:
q.pointer.queue.Push(e, &q.handlers) q.pointer.queue.Push(e, &q.handlers)
case key.EditEvent, key.Event, key.FocusEvent: case key.EditEvent, key.Event, key.FocusEvent:
q.kqueue.Push(e, &q.handlers) q.key.queue.Push(e, &q.handlers)
case clipboard.Event: case clipboard.Event:
q.cqueue.Push(e, &q.handlers) q.cqueue.Push(e, &q.handlers)
} }
@@ -102,12 +105,12 @@ func (q *Router) Queue(events ...event.Event) bool {
// TextInputState returns the input state from the most recent // TextInputState returns the input state from the most recent
// call to Frame. // call to Frame.
func (q *Router) TextInputState() TextInputState { func (q *Router) TextInputState() TextInputState {
return q.kqueue.InputState() return q.key.queue.InputState()
} }
// TextInputHint returns the input mode from the most recent key.InputOp. // TextInputHint returns the input mode from the most recent key.InputOp.
func (q *Router) TextInputHint() (key.InputHint, bool) { func (q *Router) TextInputHint() (key.InputHint, bool) {
return q.kqueue.InputHint() return q.key.queue.InputHint()
} }
// WriteClipboard returns the most recent text to be copied // WriteClipboard returns the most recent text to be copied
@@ -130,6 +133,9 @@ func (q *Router) Cursor() pointer.CursorName {
func (q *Router) collect() { func (q *Router) collect() {
pc := &q.pointer.collector pc := &q.pointer.collector
pc.reset(&q.pointer.queue) pc.reset(&q.pointer.queue)
kc := &q.key.collector
*kc = keyCollector{q: &q.key.queue}
q.key.queue.Reset()
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() { for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
switch ops.OpType(encOp.Data[0]) { switch ops.OpType(encOp.Data[0]) {
case ops.TypeInvalidate: case ops.TypeInvalidate:
@@ -154,6 +160,8 @@ func (q *Router) collect() {
case ops.TypeLoad: case ops.TypeLoad:
id := ops.DecodeLoad(encOp.Data) id := ops.DecodeLoad(encOp.Data)
pc.load(id) pc.load(id)
// Pointer ops.
case ops.TypeArea: case ops.TypeArea:
var op areaOp var op areaOp
op.Decode(encOp.Data) op.Decode(encOp.Data)
@@ -190,6 +198,17 @@ func (q *Router) collect() {
case ops.TypeCursor: case ops.TypeCursor:
name := encOp.Refs[0].(pointer.CursorName) name := encOp.Refs[0].(pointer.CursorName)
pc.cursor(name) pc.cursor(name)
// Key ops.
case ops.TypeKeyFocus:
op := decodeFocusOp(encOp.Data, encOp.Refs)
kc.focusOp(op.Tag)
case ops.TypeKeySoftKeyboard:
op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs)
kc.softKeyboard(op.Show)
case ops.TypeKeyInput:
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
kc.inputOp(op)
} }
} }
} }