op: change Defer to only restore transformation state

It turns out restoring all operation state from the moment Defer
is executed is too much; for example, a right-click pop-up needs
the transformation, but not the current clip.

Change Defer to only restore the transformation, and reset all
other state.

Other combinations may be needed in future; we'll deal with them then,
possibly by exposing the load state mask.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-01-19 20:04:30 +01:00
parent eea1dbc176
commit d6886737a5
8 changed files with 84 additions and 34 deletions
+17 -6
View File
@@ -869,6 +869,13 @@ func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) {
return return
} }
func (d *drawOps) save(id int, state drawState) {
if extra := id - len(d.states) + 1; extra > 0 {
d.states = append(d.states, make([]drawState, extra)...)
}
d.states[id] = state
}
func (d *drawOps) collectOps(r *ops.Reader, state drawState) { func (d *drawOps) collectOps(r *ops.Reader, state drawState) {
var ( var (
quads quadsOp quads quadsOp
@@ -876,6 +883,7 @@ func (d *drawOps) collectOps(r *ops.Reader, state drawState) {
dashes dashOp dashes dashOp
z int z int
) )
d.save(opconst.InitialStateID, state)
loop: loop:
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]) {
@@ -1029,13 +1037,16 @@ loop:
} }
case opconst.TypeSave: case opconst.TypeSave:
id := ops.DecodeSave(encOp.Data) id := ops.DecodeSave(encOp.Data)
if extra := id - len(d.states) + 1; extra > 0 { d.save(id, state)
d.states = append(d.states, make([]drawState, extra)...)
}
d.states[id] = state
case opconst.TypeLoad: case opconst.TypeLoad:
id := ops.DecodeLoad(encOp.Data) id, mask := ops.DecodeLoad(encOp.Data)
state = d.states[id] s := d.states[id]
if mask&opconst.TransformState != 0 {
state.t = s.t
}
if mask&^opconst.TransformState != 0 {
state = s
}
} }
} }
} }
+15 -1
View File
@@ -57,7 +57,7 @@ const (
TypeKeyFocusLen = 1 + 1 TypeKeyFocusLen = 1 + 1
TypeKeySoftKeyboardLen = 1 + 1 TypeKeySoftKeyboardLen = 1 + 1
TypeSaveLen = 1 + 4 TypeSaveLen = 1 + 4
TypeLoadLen = 1 + 4 TypeLoadLen = 1 + 1 + 4
TypeAuxLen = 1 TypeAuxLen = 1
TypeClipLen = 1 + 4*4 + 1 TypeClipLen = 1 + 4*4 + 1
TypeProfileLen = 1 TypeProfileLen = 1
@@ -67,6 +67,20 @@ const (
TypeDashLen = 1 + 4 + 1 TypeDashLen = 1 + 4 + 1
) )
// StateMask is a bitmask of state types a load operation
// should restore.
type StateMask uint8
const (
TransformState StateMask = 1 << iota
AllState = ^StateMask(0)
)
// InitialStateID is the ID for saving and loading
// the initial operation state.
const InitialStateID = 0
func (t OpType) Size() int { func (t OpType) Size() int {
return [...]int{ return [...]int{
TypeMacroLen, TypeMacroLen,
+3 -3
View File
@@ -72,11 +72,11 @@ func DecodeSave(data []byte) int {
return int(bo.Uint32(data[1:])) return int(bo.Uint32(data[1:]))
} }
// DecodeLoad decodes the state id of a restore op. // DecodeLoad decodes the state id and mask of a load op.
func DecodeLoad(data []byte) int { func DecodeLoad(data []byte) (int, opconst.StateMask) {
if opconst.OpType(data[0]) != opconst.TypeLoad { if opconst.OpType(data[0]) != opconst.TypeLoad {
panic("invalid op") panic("invalid op")
} }
bo := binary.LittleEndian bo := binary.LittleEndian
return int(bo.Uint32(data[1:])) return int(bo.Uint32(data[2:])), opconst.StateMask(data[1])
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 420 B

+7 -3
View File
@@ -107,13 +107,17 @@ func TestDeferredPaint(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
state := op.Save(o) state := op.Save(o)
clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o) clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o)
paint.ColorOp{Color: color.NRGBA{A: 0xff, R: 0xff}}.Add(o) paint.ColorOp{Color: color.NRGBA{A: 0xff, G: 0xff}}.Add(o)
paint.PaintOp{}.Add(o)
op.Affine(f32.Affine2D{}.Offset(f32.Pt(20, 20))).Add(o)
m := op.Record(o) m := op.Record(o)
clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o)
paint.ColorOp{Color: color.NRGBA{A: 0xff, R: 0xff, G: 0xff}}.Add(o)
paint.PaintOp{}.Add(o) paint.PaintOp{}.Add(o)
paintMacro := m.Stop() paintMacro := m.Stop()
op.Defer(o, paintMacro) op.Defer(o, paintMacro)
paint.ColorOp{Color: color.NRGBA{A: 0xff, G: 0xff}}.Add(o)
paint.PaintOp{}.Add(o)
state.Load() state.Load()
op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Add(o) op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Add(o)
clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o) clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o)
+4 -2
View File
@@ -140,7 +140,7 @@ func (q *keyQueue) resolveFocus(events *handlerEvents) resolveState {
q.states[id] = state q.states[id] = state
state = resolveState{} state = resolveState{}
case opconst.TypeLoad: case opconst.TypeLoad:
id := ops.DecodeLoad(encOp.Data) id, mask := ops.DecodeLoad(encOp.Data)
restored := q.states[id] restored := q.states[id]
if state.keyboard > restored.keyboard { if state.keyboard > restored.keyboard {
restored.keyboard = state.keyboard restored.keyboard = state.keyboard
@@ -148,7 +148,9 @@ func (q *keyQueue) resolveFocus(events *handlerEvents) resolveState {
if state.pri.replaces(restored.pri) { if state.pri.replaces(restored.pri) {
restored.tag, restored.pri = state.tag, state.pri restored.tag, restored.pri = state.tag, state.pri
} }
state = restored if mask != 0 {
state = restored
}
} }
} }
return state return state
+17 -6
View File
@@ -85,22 +85,33 @@ const (
areaEllipse areaEllipse
) )
func (q *pointerQueue) save(id int, state collectState) {
if extra := id - len(q.states) + 1; extra > 0 {
q.states = append(q.states, make([]collectState, extra)...)
}
q.states[id] = state
}
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) { func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
state := collectState{ state := collectState{
area: -1, area: -1,
node: -1, node: -1,
} }
q.save(opconst.InitialStateID, state)
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.TypeSave: case opconst.TypeSave:
id := ops.DecodeSave(encOp.Data) id := ops.DecodeSave(encOp.Data)
if extra := id - len(q.states) + 1; extra > 0 { q.save(id, state)
q.states = append(q.states, make([]collectState, extra)...)
}
q.states[id] = state
case opconst.TypeLoad: case opconst.TypeLoad:
id := ops.DecodeLoad(encOp.Data) id, mask := ops.DecodeLoad(encOp.Data)
state = q.states[id] s := q.states[id]
if mask&opconst.TransformState != 0 {
state.t = s.t
}
if mask&^opconst.TransformState != 0 {
state = s
}
case opconst.TypePass: case opconst.TypePass:
state.pass = encOp.Data[1] != 0 state.pass = encOp.Data[1] != 0
case opconst.TypeArea: case opconst.TypeArea:
+21 -13
View File
@@ -144,8 +144,8 @@ type pc struct {
// Defer executes c after all other operations have completed, // Defer executes c after all other operations have completed,
// including previously deferred operations. // including previously deferred operations.
// Defer saves the operation state, which is then loaded prior // Defer saves the current transformation and restores it prior
// to execution. // to execution. All other operation state is reset.
// //
// Note that deferred operations are executed in first-in-first-out // Note that deferred operations are executed in first-in-first-out
// order, unlike the Go facility of the same name. // order, unlike the Go facility of the same name.
@@ -156,8 +156,8 @@ func Defer(o *Ops, c CallOp) {
state := Save(o) state := Save(o)
// Wrap c in a macro that loads the saved state before execution. // Wrap c in a macro that loads the saved state before execution.
m := Record(o) m := Record(o)
// load(o, opconst.InitialStateID, opconst.AllState)
state.load() load(o, state.id, opconst.TransformState)
c.Add(o) c.Add(o)
c = m.Stop() c = m.Stop()
// A Defer is recorded as a TypeDefer followed by the // A Defer is recorded as a TypeDefer followed by the
@@ -175,11 +175,17 @@ func Save(o *Ops) StateOp {
id: o.nextStateID, id: o.nextStateID,
macroID: o.macroStack.currentID, macroID: o.macroStack.currentID,
} }
save(o, s.id)
return s
}
// save records a save of the operations state to
// id.
func save(o *Ops, id int) {
bo := binary.LittleEndian bo := binary.LittleEndian
data := o.Write(opconst.TypeSaveLen) data := o.Write(opconst.TypeSaveLen)
data[0] = byte(opconst.TypeSave) data[0] = byte(opconst.TypeSave)
bo.PutUint32(data[1:], uint32(s.id)) bo.PutUint32(data[1:], uint32(id))
return s
} }
// Load a previously saved operations state. // Load a previously saved operations state.
@@ -187,18 +193,20 @@ func (s StateOp) Load() {
if s.ops.macroStack.currentID != s.macroID { if s.ops.macroStack.currentID != s.macroID {
panic("load in a different macro than save") panic("load in a different macro than save")
} }
s.load()
}
// load is like Load without the same-macro check.
func (s StateOp) load() {
if s.id == 0 { if s.id == 0 {
panic("zero-value op") panic("zero-value op")
} }
load(s.ops, s.id, opconst.AllState)
}
// load a previously saved operations state given
// its ID. Only state included in mask is affected.
func load(o *Ops, id int, m opconst.StateMask) {
bo := binary.LittleEndian bo := binary.LittleEndian
data := s.ops.Write(opconst.TypeLoadLen) data := o.Write(opconst.TypeLoadLen)
data[0] = byte(opconst.TypeLoad) data[0] = byte(opconst.TypeLoad)
bo.PutUint32(data[1:], uint32(s.id)) data[1] = byte(m)
bo.PutUint32(data[2:], uint32(id))
} }
// Reset the Ops, preparing it for re-use. Reset invalidates // Reset the Ops, preparing it for re-use. Reset invalidates