io/router: introduce pointerCollector to avoid an ops decode

The router package decodes the entire ops list thrice: once for pointer
ops, once for key ops, once for other ops. This change removes one
decode round by merging other ops and pointer ops decoding.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-10-20 11:35:16 +02:00
parent a4626d9304
commit 60846d112b
2 changed files with 156 additions and 114 deletions
+104 -110
View File
@@ -10,7 +10,6 @@ import (
"gioui.org/internal/ops"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/op"
)
type pointerQueue struct {
@@ -20,12 +19,7 @@ type pointerQueue struct {
cursor pointer.CursorName
handlers map[event.Tag]*pointerHandler
pointers []pointerInfo
reader ops.Reader
nodeStack []int
transStack []f32.Affine2D
// states holds the storage for save/restore ops.
states []f32.Affine2D
scratch []event.Tag
}
@@ -77,7 +71,7 @@ type areaNode struct {
type areaKind uint8
// collectState represents the state for collectHandlers
// collectState represents the state for pointerCollector.
type collectState struct {
t f32.Affine2D
// nodePlusOne is the current node index, plus one to
@@ -86,109 +80,115 @@ type collectState struct {
pass int
}
// pointerCollector tracks the state needed to update an pointerQueue
// from pointer ops.
type pointerCollector struct {
q *pointerQueue
state collectState
nodeStack []int
transStack []f32.Affine2D
// states holds the storage for save/restore ops.
states []f32.Affine2D
}
const (
areaRect areaKind = iota
areaEllipse
)
func (q *pointerQueue) save(id int, state f32.Affine2D) {
if extra := id - len(q.states) + 1; extra > 0 {
q.states = append(q.states, make([]f32.Affine2D, extra)...)
func (c *pointerCollector) save(id int) {
if extra := id - len(c.states) + 1; extra > 0 {
c.states = append(c.states, make([]f32.Affine2D, extra)...)
}
q.states[id] = state
c.states[id] = c.state.t
}
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
var state collectState
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
switch ops.OpType(encOp.Data[0]) {
case ops.TypeSave:
id := ops.DecodeSave(encOp.Data)
q.save(id, state.t)
case ops.TypeLoad:
state = collectState{}
id := ops.DecodeLoad(encOp.Data)
state.t = q.states[id]
case ops.TypeArea:
var op areaOp
op.Decode(encOp.Data)
area := -1
if i := state.nodePlusOne - 1; i != -1 {
n := q.hitTree[i]
area = n.area
}
q.areas = append(q.areas, areaNode{trans: state.t, next: area, area: op, pass: state.pass > 0})
q.nodeStack = append(q.nodeStack, state.nodePlusOne-1)
q.hitTree = append(q.hitTree, hitNode{
next: state.nodePlusOne - 1,
area: len(q.areas) - 1,
})
state.nodePlusOne = len(q.hitTree) - 1 + 1
case ops.TypePopArea:
n := len(q.nodeStack)
state.nodePlusOne = q.nodeStack[n-1] + 1
q.nodeStack = q.nodeStack[:n-1]
case ops.TypePass:
state.pass++
case ops.TypePopPass:
state.pass--
case ops.TypeTransform:
dop, push := ops.DecodeTransform(encOp.Data)
if push {
q.transStack = append(q.transStack, state.t)
}
state.t = state.t.Mul(dop)
case ops.TypePopTransform:
n := len(q.transStack)
state.t = q.transStack[n-1]
q.transStack = q.transStack[:n-1]
case ops.TypePointerInput:
bo := binary.LittleEndian
op := pointer.InputOp{
Tag: encOp.Refs[0].(event.Tag),
Grab: encOp.Data[1] != 0,
Types: pointer.Type(bo.Uint16(encOp.Data[2:])),
}
area := -1
if i := state.nodePlusOne - 1; i != -1 {
n := q.hitTree[i]
area = n.area
}
q.hitTree = append(q.hitTree, hitNode{
next: state.nodePlusOne - 1,
area: area,
tag: op.Tag,
})
state.nodePlusOne = len(q.hitTree) - 1 + 1
h, ok := q.handlers[op.Tag]
if !ok {
h = new(pointerHandler)
q.handlers[op.Tag] = h
// Cancel handlers on (each) first appearance, but don't
// trigger redraw.
events.AddNoRedraw(op.Tag, pointer.Event{Type: pointer.Cancel})
}
h.active = true
h.area = area
h.wantsGrab = h.wantsGrab || op.Grab
h.types = h.types | op.Types
h.scrollRange = image.Rectangle{
Min: image.Point{
X: int(int32(bo.Uint32(encOp.Data[4:]))),
Y: int(int32(bo.Uint32(encOp.Data[8:]))),
},
Max: image.Point{
X: int(int32(bo.Uint32(encOp.Data[12:]))),
Y: int(int32(bo.Uint32(encOp.Data[16:]))),
},
}
case ops.TypeCursor:
q.cursors = append(q.cursors, cursorNode{
name: encOp.Refs[0].(pointer.CursorName),
area: len(q.areas) - 1,
})
}
func (c *pointerCollector) load(id int) {
c.state = collectState{t: c.states[id]}
}
func (c *pointerCollector) area(op areaOp) {
area := -1
if i := c.state.nodePlusOne - 1; i != -1 {
n := c.q.hitTree[i]
area = n.area
}
c.q.areas = append(c.q.areas, areaNode{trans: c.state.t, next: area, area: op, pass: c.state.pass > 0})
c.nodeStack = append(c.nodeStack, c.state.nodePlusOne-1)
c.q.hitTree = append(c.q.hitTree, hitNode{
next: c.state.nodePlusOne - 1,
area: len(c.q.areas) - 1,
})
c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1
}
func (c *pointerCollector) popArea() {
n := len(c.nodeStack)
c.state.nodePlusOne = c.nodeStack[n-1] + 1
c.nodeStack = c.nodeStack[:n-1]
}
func (c *pointerCollector) pass() {
c.state.pass++
}
func (c *pointerCollector) popPass() {
c.state.pass--
}
func (c *pointerCollector) transform(t f32.Affine2D, push bool) {
if push {
c.transStack = append(c.transStack, c.state.t)
}
c.state.t = c.state.t.Mul(t)
}
func (c *pointerCollector) popTransform() {
n := len(c.transStack)
c.state.t = c.transStack[n-1]
c.transStack = c.transStack[:n-1]
}
func (c *pointerCollector) inputOp(op pointer.InputOp, events *handlerEvents) {
area := -1
if i := c.state.nodePlusOne - 1; i != -1 {
n := c.q.hitTree[i]
area = n.area
}
c.q.hitTree = append(c.q.hitTree, hitNode{
next: c.state.nodePlusOne - 1,
area: area,
tag: op.Tag,
})
c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1
h, ok := c.q.handlers[op.Tag]
if !ok {
h = new(pointerHandler)
c.q.handlers[op.Tag] = h
// Cancel handlers on (each) first appearance, but don't
// trigger redraw.
events.AddNoRedraw(op.Tag, pointer.Event{Type: pointer.Cancel})
}
h.active = true
h.area = area
h.wantsGrab = h.wantsGrab || op.Grab
h.types = h.types | op.Types
h.scrollRange = op.ScrollBounds
}
func (c *pointerCollector) cursor(name pointer.CursorName) {
c.q.cursors = append(c.q.cursors, cursorNode{
name: name,
area: len(c.q.areas) - 1,
})
}
func (c *pointerCollector) reset(q *pointerQueue) {
q.reset()
c.state = collectState{}
c.nodeStack = c.nodeStack[:0]
c.transStack = c.transStack[:0]
c.q = q
}
func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) {
@@ -241,10 +241,6 @@ func (q *pointerQueue) reset() {
if q.handlers == nil {
q.handlers = make(map[event.Tag]*pointerHandler)
}
}
func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
q.reset()
for _, h := range q.handlers {
// Reset handler.
h.active = false
@@ -253,11 +249,10 @@ func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
}
q.hitTree = q.hitTree[:0]
q.areas = q.areas[:0]
q.nodeStack = q.nodeStack[:0]
q.transStack = q.transStack[:0]
q.cursors = q.cursors[:0]
q.reader.Reset(&root.Internal)
q.collectHandlers(&q.reader, events)
}
func (q *pointerQueue) Frame(events *handlerEvents) {
for k, h := range q.handlers {
if !h.active {
q.dropHandler(nil, k)
@@ -320,7 +315,6 @@ func (q *pointerQueue) pointerOf(e pointer.Event) int {
}
func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
q.reset()
if e.Type == pointer.Cancel {
q.pointers = q.pointers[:0]
for k := range q.handlers {
+52 -4
View File
@@ -12,6 +12,7 @@ package router
import (
"encoding/binary"
"image"
"time"
"gioui.org/internal/ops"
@@ -26,7 +27,10 @@ import (
// Router is a Queue implementation that routes events
// to handlers declared in operation lists.
type Router struct {
pqueue pointerQueue
pointer struct {
queue pointerQueue
collector pointerCollector
}
kqueue keyQueue
cqueue clipboardQueue
@@ -70,7 +74,7 @@ func (q *Router) Frame(ops *op.Ops) {
q.reader.Reset(&ops.Internal)
q.collect()
q.pqueue.Frame(ops, &q.handlers)
q.pointer.queue.Frame(&q.handlers)
q.kqueue.Frame(ops, &q.handlers)
if q.handlers.HadEvents() {
q.wakeup = true
@@ -85,7 +89,7 @@ func (q *Router) Queue(events ...event.Event) bool {
case profile.Event:
q.profile = e
case pointer.Event:
q.pqueue.Push(e, &q.handlers)
q.pointer.queue.Push(e, &q.handlers)
case key.EditEvent, key.Event, key.FocusEvent:
q.kqueue.Push(e, &q.handlers)
case clipboard.Event:
@@ -120,10 +124,12 @@ func (q *Router) ReadClipboard() bool {
// Cursor returns the last cursor set.
func (q *Router) Cursor() pointer.CursorName {
return q.pqueue.cursor
return q.pointer.queue.cursor
}
func (q *Router) collect() {
pc := &q.pointer.collector
pc.reset(&q.pointer.queue)
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
switch ops.OpType(encOp.Data[0]) {
case ops.TypeInvalidate:
@@ -142,6 +148,48 @@ func (q *Router) collect() {
q.cqueue.ProcessReadClipboard(encOp.Refs)
case ops.TypeClipboardWrite:
q.cqueue.ProcessWriteClipboard(encOp.Refs)
case ops.TypeSave:
id := ops.DecodeSave(encOp.Data)
pc.save(id)
case ops.TypeLoad:
id := ops.DecodeLoad(encOp.Data)
pc.load(id)
case ops.TypeArea:
var op areaOp
op.Decode(encOp.Data)
pc.area(op)
case ops.TypePopArea:
pc.popArea()
case ops.TypePass:
pc.pass()
case ops.TypePopPass:
pc.popPass()
case ops.TypeTransform:
t, push := ops.DecodeTransform(encOp.Data)
pc.transform(t, push)
case ops.TypePopTransform:
pc.popTransform()
case ops.TypePointerInput:
bo := binary.LittleEndian
op := pointer.InputOp{
Tag: encOp.Refs[0].(event.Tag),
Grab: encOp.Data[1] != 0,
Types: pointer.Type(bo.Uint16(encOp.Data[2:])),
ScrollBounds: image.Rectangle{
Min: image.Point{
X: int(int32(bo.Uint32(encOp.Data[4:]))),
Y: int(int32(bo.Uint32(encOp.Data[8:]))),
},
Max: image.Point{
X: int(int32(bo.Uint32(encOp.Data[12:]))),
Y: int(int32(bo.Uint32(encOp.Data[16:]))),
},
},
}
pc.inputOp(op, &q.handlers)
case ops.TypeCursor:
name := encOp.Refs[0].(pointer.CursorName)
pc.cursor(name)
}
}
}