op: implement StackOp in terms of general save/load of state

Push/Pop only allows saving and restoring operation state in a
stack-like manner. We're going to need restoring arbitrary state
for implementing deferred operations.

Generalize state save/restore and implement Push and Pop on top of
that.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-01-11 19:59:51 +01:00
parent f114acdb02
commit f7902f299b
6 changed files with 139 additions and 71 deletions
+16 -11
View File
@@ -61,6 +61,7 @@ type renderer struct {
type drawOps struct { type drawOps struct {
profile bool profile bool
reader ops.Reader reader ops.Reader
states []drawState
cache *resourceCache cache *resourceCache
vertCache []byte vertCache []byte
viewport image.Point viewport image.Point
@@ -87,7 +88,6 @@ type drawState struct {
t f32.Affine2D t f32.Affine2D
cpath *pathOp cpath *pathOp
rect bool rect bool
z int
matType materialType matType materialType
// Current paint.ImageOp // Current paint.ImageOp
@@ -869,11 +869,12 @@ func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) {
return return
} }
func (d *drawOps) collectOps(r *ops.Reader, state drawState) int { func (d *drawOps) collectOps(r *ops.Reader, state drawState) {
var ( var (
quads quadsOp quads quadsOp
stroke clip.StrokeStyle stroke clip.StrokeStyle
dashes dashOp dashes dashOp
z int
) )
loop: loop:
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
@@ -995,19 +996,19 @@ loop:
d.allImageOps = d.allImageOps[:0] d.allImageOps = d.allImageOps[:0]
d.zimageOps = d.zimageOps[:0] d.zimageOps = d.zimageOps[:0]
d.imageOps = d.imageOps[:0] d.imageOps = d.imageOps[:0]
state.z = 0 z = 0
d.clearColor = mat.color.Opaque() d.clearColor = mat.color.Opaque()
continue continue
} }
state.z++ z++
if state.z != int(uint16(state.z)) { if z != int(uint16(z)) {
// TODO(eliasnaur) gioui.org/issue/127. // TODO(eliasnaur) gioui.org/issue/127.
panic("more than 65k paint objects not supported") panic("more than 65k paint objects not supported")
} }
// Assume 16-bit depth buffer. // Assume 16-bit depth buffer.
const zdepth = 1 << 16 const zdepth = 1 << 16
// Convert z to window-space, assuming depth range [0;1]. // Convert z to window-space, assuming depth range [0;1].
zf := float32(state.z)*2/zdepth - 1.0 zf := float32(z)*2/zdepth - 1.0
img := imageOp{ img := imageOp{
z: zf, z: zf,
path: state.cpath, path: state.cpath,
@@ -1026,13 +1027,17 @@ loop:
state.cpath = state.cpath.parent state.cpath = state.cpath.parent
state.rect = wasrect state.rect = wasrect
} }
case opconst.TypePush: case opconst.TypeSave:
state.z = d.collectOps(r, state) id := ops.DecodeSave(encOp.Data)
case opconst.TypePop: if extra := id - len(d.states) + 1; extra > 0 {
break loop d.states = append(d.states, make([]drawState, extra)...)
}
d.states[id] = state
case opconst.TypeLoad:
id := ops.DecodeLoad(encOp.Data)
state = d.states[id]
} }
} }
return state.z
} }
func expandPathOp(p *pathOp, clip image.Rectangle) { func expandPathOp(p *pathOp, clip image.Rectangle) {
+6 -6
View File
@@ -25,8 +25,8 @@ const (
TypeKeyInput TypeKeyInput
TypeKeyFocus TypeKeyFocus
TypeKeySoftKeyboard TypeKeySoftKeyboard
TypePush TypeSave
TypePop TypeLoad
TypeAux TypeAux
TypeClip TypeClip
TypeProfile TypeProfile
@@ -54,8 +54,8 @@ const (
TypeKeyInputLen = 1 TypeKeyInputLen = 1
TypeKeyFocusLen = 1 + 1 TypeKeyFocusLen = 1 + 1
TypeKeySoftKeyboardLen = 1 + 1 TypeKeySoftKeyboardLen = 1 + 1
TypePushLen = 1 TypeSaveLen = 1 + 4
TypePopLen = 1 TypeLoadLen = 1 + 4
TypeAuxLen = 1 TypeAuxLen = 1
TypeClipLen = 1 + 4*4 + 1 TypeClipLen = 1 + 4*4 + 1
TypeProfileLen = 1 TypeProfileLen = 1
@@ -84,8 +84,8 @@ func (t OpType) Size() int {
TypeKeyInputLen, TypeKeyInputLen,
TypeKeyFocusLen, TypeKeyFocusLen,
TypeKeySoftKeyboardLen, TypeKeySoftKeyboardLen,
TypePushLen, TypeSaveLen,
TypePopLen, TypeLoadLen,
TypeAuxLen, TypeAuxLen,
TypeClipLen, TypeClipLen,
TypeProfileLen, TypeProfileLen,
+18
View File
@@ -62,3 +62,21 @@ func DecodeTransform(data []byte) (t f32.Affine2D) {
f := math.Float32frombits(bo.Uint32(data[4*5:])) f := math.Float32frombits(bo.Uint32(data[4*5:]))
return f32.NewAffine2D(a, b, c, d, e, f) return f32.NewAffine2D(a, b, c, d, e, f)
} }
// DecodeSave decodes the state id of a save op.
func DecodeSave(data []byte) int {
if opconst.OpType(data[0]) != opconst.TypeSave {
panic("invalid op")
}
bo := binary.LittleEndian
return int(bo.Uint32(data[1:]))
}
// DecodeLoad decodes the state id of a restore op.
func DecodeLoad(data []byte) int {
if opconst.OpType(data[0]) != opconst.TypeLoad {
panic("invalid op")
}
bo := binary.LittleEndian
return int(bo.Uint32(data[1:]))
}
+42 -27
View File
@@ -17,6 +17,14 @@ type keyQueue struct {
handlers map[event.Tag]*keyHandler handlers map[event.Tag]*keyHandler
reader ops.Reader reader ops.Reader
state TextInputState state TextInputState
// states store states during resolveFocus
states []resolveState
}
type resolveState struct {
tag event.Tag
pri listenerPriority
keyboard TextInputState
} }
type keyHandler struct { type keyHandler struct {
@@ -56,9 +64,9 @@ func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
} }
q.reader.Reset(root) q.reader.Reset(root)
focus, pri, keyboard := q.resolveFocus(events) state := q.resolveFocus(events)
if pri == priNone { if state.pri == priNone {
focus = nil state.tag = nil
} }
for k, h := range q.handlers { for k, h := range q.handlers {
if !h.visible { if !h.visible {
@@ -66,26 +74,26 @@ func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
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
keyboard = TextInputClose state.keyboard = TextInputClose
} }
} }
if h.new && k != focus { if h.new && k != state.tag {
// Reset the handler on (each) first appearance. // Reset the handler on (each) first appearance.
events.Add(k, key.FocusEvent{Focus: false}) events.Add(k, key.FocusEvent{Focus: false})
} }
} }
if focus != q.focus { if state.tag != 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 = state.tag
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 {
keyboard = TextInputClose state.keyboard = TextInputClose
} }
} }
q.state = keyboard q.state = state.keyboard
} }
func (q *keyQueue) Push(e event.Event, events *handlerEvents) { func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
@@ -94,28 +102,28 @@ func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
} }
} }
func (q *keyQueue) resolveFocus(events *handlerEvents) (tag event.Tag, pri listenerPriority, keyboard TextInputState) { func (q *keyQueue) resolveFocus(events *handlerEvents) resolveState {
loop: var state resolveState
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 opconst.OpType(encOp.Data[0]) { switch opconst.OpType(encOp.Data[0]) {
case opconst.TypeKeyFocus: case opconst.TypeKeyFocus:
op := decodeFocusOp(encOp.Data, encOp.Refs) op := decodeFocusOp(encOp.Data, encOp.Refs)
if op.Focus { if op.Focus {
pri = priNewFocus state.pri = priNewFocus
} else { } else {
pri, keyboard = priNone, TextInputClose state.pri, state.keyboard = priNone, TextInputClose
} }
case opconst.TypeKeySoftKeyboard: case opconst.TypeKeySoftKeyboard:
op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs) op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs)
if op.Show { if op.Show {
keyboard = TextInputOpen state.keyboard = TextInputOpen
} else { } else {
keyboard = TextInputClose state.keyboard = TextInputClose
} }
case opconst.TypeKeyInput: case opconst.TypeKeyInput:
op := decodeKeyInputOp(encOp.Data, encOp.Refs) op := decodeKeyInputOp(encOp.Data, encOp.Refs)
if op.Tag == q.focus && pri < priCurrentFocus { if op.Tag == q.focus && state.pri < priCurrentFocus {
pri = priCurrentFocus state.pri = priCurrentFocus
} }
h, ok := q.handlers[op.Tag] h, ok := q.handlers[op.Tag]
if !ok { if !ok {
@@ -123,20 +131,27 @@ loop:
q.handlers[op.Tag] = h q.handlers[op.Tag] = h
} }
h.visible = true h.visible = true
tag = op.Tag state.tag = op.Tag
case opconst.TypePush: case opconst.TypeSave:
newK, newPri, newKeyboard := q.resolveFocus(events) id := ops.DecodeSave(encOp.Data)
if newKeyboard > keyboard { if extra := id - len(q.states) + 1; extra > 0 {
keyboard = newKeyboard q.states = append(q.states, make([]resolveState, extra)...)
} }
if newPri.replaces(pri) { q.states[id] = state
tag, pri = newK, newPri state = resolveState{}
case opconst.TypeLoad:
id := ops.DecodeLoad(encOp.Data)
restored := q.states[id]
if state.keyboard > restored.keyboard {
restored.keyboard = state.keyboard
} }
case opconst.TypePop: if state.pri.replaces(restored.pri) {
break loop restored.tag, restored.pri = state.tag, state.pri
}
state = restored
} }
} }
return tag, pri, keyboard return state
} }
func (p listenerPriority) replaces(p2 listenerPriority) bool { func (p listenerPriority) replaces(p2 listenerPriority) bool {
+38 -19
View File
@@ -23,6 +23,8 @@ type pointerQueue struct {
pointers []pointerInfo pointers []pointerInfo
reader ops.Reader reader ops.Reader
// states holds the storage for save/restore ops.
states []collectState
scratch []event.Tag scratch []event.Tag
} }
@@ -70,34 +72,51 @@ type areaNode struct {
type areaKind uint8 type areaKind uint8
// collectState represents the state for collectHandlers
type collectState struct {
t f32.Affine2D
area int
node int
pass bool
}
const ( const (
areaRect areaKind = iota areaRect areaKind = iota
areaEllipse areaEllipse
) )
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t f32.Affine2D, area, node int, pass bool) { func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
state := collectState{
area: -1,
node: -1,
}
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
switch opconst.OpType(encOp.Data[0]) { switch opconst.OpType(encOp.Data[0]) {
case opconst.TypePush: case opconst.TypeSave:
q.collectHandlers(r, events, t, area, node, pass) id := ops.DecodeSave(encOp.Data)
case opconst.TypePop: if extra := id - len(q.states) + 1; extra > 0 {
return q.states = append(q.states, make([]collectState, extra)...)
}
q.states[id] = state
case opconst.TypeLoad:
id := ops.DecodeLoad(encOp.Data)
state = q.states[id]
case opconst.TypePass: case opconst.TypePass:
pass = encOp.Data[1] != 0 state.pass = encOp.Data[1] != 0
case opconst.TypeArea: case opconst.TypeArea:
var op areaOp var op areaOp
op.Decode(encOp.Data) op.Decode(encOp.Data)
q.areas = append(q.areas, areaNode{trans: t, next: area, area: op}) q.areas = append(q.areas, areaNode{trans: state.t, next: state.area, area: op})
area = len(q.areas) - 1 state.area = len(q.areas) - 1
q.hitTree = append(q.hitTree, hitNode{ q.hitTree = append(q.hitTree, hitNode{
next: node, next: state.node,
area: area, area: state.area,
pass: pass, pass: state.pass,
}) })
node = len(q.hitTree) - 1 state.node = len(q.hitTree) - 1
case opconst.TypeTransform: case opconst.TypeTransform:
dop := ops.DecodeTransform(encOp.Data) dop := ops.DecodeTransform(encOp.Data)
t = t.Mul(dop) state.t = state.t.Mul(dop)
case opconst.TypePointerInput: case opconst.TypePointerInput:
op := pointer.InputOp{ op := pointer.InputOp{
Tag: encOp.Refs[0].(event.Tag), Tag: encOp.Refs[0].(event.Tag),
@@ -105,12 +124,12 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t f
Types: pointer.Type(encOp.Data[2]), Types: pointer.Type(encOp.Data[2]),
} }
q.hitTree = append(q.hitTree, hitNode{ q.hitTree = append(q.hitTree, hitNode{
next: node, next: state.node,
area: area, area: state.area,
pass: pass, pass: state.pass,
tag: op.Tag, tag: op.Tag,
}) })
node = len(q.hitTree) - 1 state.node = len(q.hitTree) - 1
h, ok := q.handlers[op.Tag] h, ok := q.handlers[op.Tag]
if !ok { if !ok {
h = new(pointerHandler) h = new(pointerHandler)
@@ -118,7 +137,7 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t f
events.Add(op.Tag, pointer.Event{Type: pointer.Cancel}) events.Add(op.Tag, pointer.Event{Type: pointer.Cancel})
} }
h.active = true h.active = true
h.area = area h.area = state.area
h.wantsGrab = h.wantsGrab || op.Grab h.wantsGrab = h.wantsGrab || op.Grab
h.types = h.types | op.Types h.types = h.types | op.Types
case opconst.TypeCursor: case opconst.TypeCursor:
@@ -192,7 +211,7 @@ func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
q.areas = q.areas[:0] q.areas = q.areas[:0]
q.cursors = q.cursors[:0] q.cursors = q.cursors[:0]
q.reader.Reset(root) q.reader.Reset(root)
q.collectHandlers(&q.reader, events, f32.Affine2D{}, -1, -1, false) q.collectHandlers(&q.reader, events)
for k, h := range q.handlers { for k, h := range q.handlers {
if !h.active { if !h.active {
q.dropHandlers(events, k) q.dropHandlers(events, k)
+19 -8
View File
@@ -82,8 +82,11 @@ type Ops struct {
version int version int
// data contains the serialized operations. // data contains the serialized operations.
data []byte data []byte
// External references for operations. // refs hold external references for operations.
refs []interface{} refs []interface{}
// nextStateID is the id allocated for the next
// StackOp.
nextStateID int
stackStack stack stackStack stack
macroStack stack macroStack stack
@@ -92,7 +95,8 @@ type Ops struct {
// StackOp saves and restores the operation state // StackOp saves and restores the operation state
// in a stack-like manner. // in a stack-like manner.
type StackOp struct { type StackOp struct {
id stackID id int
stackID stackID
macroID int macroID int
ops *Ops ops *Ops
} }
@@ -142,13 +146,17 @@ type pc struct {
// Push (save) the current operations state. // Push (save) the current operations state.
func Push(o *Ops) StackOp { func Push(o *Ops) StackOp {
o.nextStateID++
s := StackOp{ s := StackOp{
ops: o, ops: o,
id: o.stackStack.push(), id: o.nextStateID,
stackID: o.stackStack.push(),
macroID: o.macroStack.currentID, macroID: o.macroStack.currentID,
} }
data := o.Write(opconst.TypePushLen) bo := binary.LittleEndian
data[0] = byte(opconst.TypePush) data := o.Write(opconst.TypeSaveLen)
data[0] = byte(opconst.TypeSave)
bo.PutUint32(data[1:], uint32(s.id))
return s return s
} }
@@ -157,9 +165,11 @@ func (s StackOp) Pop() {
if s.ops.macroStack.currentID != s.macroID { if s.ops.macroStack.currentID != s.macroID {
panic("pop in a different macro than push") panic("pop in a different macro than push")
} }
s.ops.stackStack.pop(s.id) s.ops.stackStack.pop(s.stackID)
data := s.ops.Write(opconst.TypePopLen) bo := binary.LittleEndian
data[0] = byte(opconst.TypePop) data := s.ops.Write(opconst.TypeLoadLen)
data[0] = byte(opconst.TypeLoad)
bo.PutUint32(data[1:], uint32(s.id))
} }
// Reset the Ops, preparing it for re-use. Reset invalidates // Reset the Ops, preparing it for re-use. Reset invalidates
@@ -173,6 +183,7 @@ func (o *Ops) Reset() {
} }
o.data = o.data[:0] o.data = o.data[:0]
o.refs = o.refs[:0] o.refs = o.refs[:0]
o.nextStateID = 0
o.version++ o.version++
} }