From 60846d112b1763e766fb75f8e0f211f57f90f528 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Wed, 20 Oct 2021 11:35:16 +0200 Subject: [PATCH] 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 --- io/router/pointer.go | 214 +++++++++++++++++++++---------------------- io/router/router.go | 56 ++++++++++- 2 files changed, 156 insertions(+), 114 deletions(-) diff --git a/io/router/pointer.go b/io/router/pointer.go index 44551b59..0638dfec 100644 --- a/io/router/pointer.go +++ b/io/router/pointer.go @@ -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 { diff --git a/io/router/router.go b/io/router/router.go index 1b344dd6..c83877f1 100644 --- a/io/router/router.go +++ b/io/router/router.go @@ -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) } } }