op.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  /*
   4  
   5  Package op implements operations for updating a user interface.
   6  
   7  Gio programs use operations, or ops, for describing their user
   8  interfaces. There are operations for drawing, defining input
   9  handlers, changing window properties as well as operations for
  10  controlling the execution of other operations.
  11  
  12  Ops represents a list of operations. The most important use
  13  for an Ops list is to describe a complete user interface update
  14  to a ui/app.Window's Update method.
  15  
  16  Drawing a colored square:
  17  
  18  	import "github.com/p9c/p9/pkg/gel/gio/unit"
  19  	import "github.com/p9c/p9/pkg/gel/gio/app"
  20  	import "github.com/p9c/p9/pkg/gel/gio/op/paint"
  21  
  22  	var w app.Window
  23  	var e system.FrameEvent
  24  	ops := new(op.Ops)
  25  	...
  26  	ops.Reset()
  27  	paint.ColorOp{Color: ...}.Add(ops)
  28  	paint.PaintOp{Rect: ...}.Add(ops)
  29  	e.Frame(ops)
  30  
  31  State
  32  
  33  An Ops list can be viewed as a very simple virtual machine: it has an implicit
  34  mutable state stack and execution flow can be controlled with macros.
  35  
  36  The Save function saves the current state for later restoring:
  37  
  38  	ops := new(op.Ops)
  39  	// Save the current state, in particular the transform.
  40  	state := op.Save(ops)
  41  	// Apply a transform to subsequent operations.
  42  	op.Offset(...).Add(ops)
  43  	...
  44  	// Restore the previous transform.
  45  	state.Load()
  46  
  47  You can also use this one-line to save the current state and restore it at the
  48  end of a function :
  49  
  50    defer op.Save(ops).Load()
  51  
  52  The MacroOp records a list of operations to be executed later:
  53  
  54  	ops := new(op.Ops)
  55  	macro := op.Record(ops)
  56  	// Record operations by adding them.
  57  	op.InvalidateOp{}.Add(ops)
  58  	...
  59  	// End recording.
  60  	call := macro.Stop()
  61  
  62  	// replay the recorded operations:
  63  	call.Add(ops)
  64  
  65  */
  66  package op
  67  
  68  import (
  69  	"encoding/binary"
  70  	"math"
  71  	"time"
  72  
  73  	"github.com/p9c/p9/pkg/gel/gio/f32"
  74  	"github.com/p9c/p9/pkg/gel/gio/internal/opconst"
  75  )
  76  
  77  // Ops holds a list of operations. Operations are stored in
  78  // serialized form to avoid garbage during construction of
  79  // the ops list.
  80  type Ops struct {
  81  	// version is incremented at each Reset.
  82  	version int
  83  	// data contains the serialized operations.
  84  	data []byte
  85  	// refs hold external references for operations.
  86  	refs []interface{}
  87  	// nextStateID is the id allocated for the next
  88  	// StateOp.
  89  	nextStateID int
  90  
  91  	macroStack stack
  92  }
  93  
  94  // StateOp represents a saved operation snapshop to be restored
  95  // later.
  96  type StateOp struct {
  97  	id      int
  98  	macroID int
  99  	ops     *Ops
 100  }
 101  
 102  // MacroOp records a list of operations for later use.
 103  type MacroOp struct {
 104  	ops *Ops
 105  	id  stackID
 106  	pc  pc
 107  }
 108  
 109  // CallOp invokes the operations recorded by Record.
 110  type CallOp struct {
 111  	// Ops is the list of operations to invoke.
 112  	ops *Ops
 113  	pc  pc
 114  }
 115  
 116  // InvalidateOp requests a redraw at the given time. Use
 117  // the zero value to request an immediate redraw.
 118  type InvalidateOp struct {
 119  	At time.Time
 120  }
 121  
 122  // TransformOp applies a transform to the current transform. The zero value
 123  // for TransformOp represents the identity transform.
 124  type TransformOp struct {
 125  	t f32.Affine2D
 126  }
 127  
 128  // stack tracks the integer identities of MacroOp
 129  // operations to ensure correct pairing of Record/End.
 130  type stack struct {
 131  	currentID int
 132  	nextID    int
 133  }
 134  
 135  type stackID struct {
 136  	id   int
 137  	prev int
 138  }
 139  
 140  type pc struct {
 141  	data int
 142  	refs int
 143  }
 144  
 145  // Defer executes c after all other operations have completed,
 146  // including previously deferred operations.
 147  // Defer saves the current transformation and restores it prior
 148  // to execution. All other operation state is reset.
 149  //
 150  // Note that deferred operations are executed in first-in-first-out
 151  // order, unlike the Go facility of the same name.
 152  func Defer(o *Ops, c CallOp) {
 153  	if c.ops == nil {
 154  		return
 155  	}
 156  	state := Save(o)
 157  	// Wrap c in a macro that loads the saved state before execution.
 158  	m := Record(o)
 159  	load(o, opconst.InitialStateID, opconst.AllState)
 160  	load(o, state.id, opconst.TransformState)
 161  	c.Add(o)
 162  	c = m.Stop()
 163  	// A Defer is recorded as a TypeDefer followed by the
 164  	// wrapped macro.
 165  	data := o.Write(opconst.TypeDeferLen)
 166  	data[0] = byte(opconst.TypeDefer)
 167  	c.Add(o)
 168  }
 169  
 170  // Save the current operations state.
 171  func Save(o *Ops) StateOp {
 172  	o.nextStateID++
 173  	s := StateOp{
 174  		ops:     o,
 175  		id:      o.nextStateID,
 176  		macroID: o.macroStack.currentID,
 177  	}
 178  	save(o, s.id)
 179  	return s
 180  }
 181  
 182  // save records a save of the operations state to
 183  // id.
 184  func save(o *Ops, id int) {
 185  	bo := binary.LittleEndian
 186  	data := o.Write(opconst.TypeSaveLen)
 187  	data[0] = byte(opconst.TypeSave)
 188  	bo.PutUint32(data[1:], uint32(id))
 189  }
 190  
 191  // Load a previously saved operations state.
 192  func (s StateOp) Load() {
 193  	if s.ops.macroStack.currentID != s.macroID {
 194  		panic("load in a different macro than save")
 195  	}
 196  	if s.id == 0 {
 197  		panic("zero-value op")
 198  	}
 199  	load(s.ops, s.id, opconst.AllState)
 200  }
 201  
 202  // load a previously saved operations state given
 203  // its ID. Only state included in mask is affected.
 204  func load(o *Ops, id int, m opconst.StateMask) {
 205  	bo := binary.LittleEndian
 206  	data := o.Write(opconst.TypeLoadLen)
 207  	data[0] = byte(opconst.TypeLoad)
 208  	data[1] = byte(m)
 209  	bo.PutUint32(data[2:], uint32(id))
 210  }
 211  
 212  // Reset the Ops, preparing it for re-use. Reset invalidates
 213  // any recorded macros.
 214  func (o *Ops) Reset() {
 215  	o.macroStack = stack{}
 216  	// Leave references to the GC.
 217  	for i := range o.refs {
 218  		o.refs[i] = nil
 219  	}
 220  	o.data = o.data[:0]
 221  	o.refs = o.refs[:0]
 222  	o.nextStateID = 0
 223  	o.version++
 224  }
 225  
 226  // Data is for internal use only.
 227  func (o *Ops) Data() []byte {
 228  	return o.data
 229  }
 230  
 231  // Refs is for internal use only.
 232  func (o *Ops) Refs() []interface{} {
 233  	return o.refs
 234  }
 235  
 236  // Version is for internal use only.
 237  func (o *Ops) Version() int {
 238  	return o.version
 239  }
 240  
 241  // Write is for internal use only.
 242  func (o *Ops) Write(n int) []byte {
 243  	o.data = append(o.data, make([]byte, n)...)
 244  	return o.data[len(o.data)-n:]
 245  }
 246  
 247  // Write1 is for internal use only.
 248  func (o *Ops) Write1(n int, ref1 interface{}) []byte {
 249  	o.data = append(o.data, make([]byte, n)...)
 250  	o.refs = append(o.refs, ref1)
 251  	return o.data[len(o.data)-n:]
 252  }
 253  
 254  // Write2 is for internal use only.
 255  func (o *Ops) Write2(n int, ref1, ref2 interface{}) []byte {
 256  	o.data = append(o.data, make([]byte, n)...)
 257  	o.refs = append(o.refs, ref1, ref2)
 258  	return o.data[len(o.data)-n:]
 259  }
 260  
 261  func (o *Ops) pc() pc {
 262  	return pc{data: len(o.data), refs: len(o.refs)}
 263  }
 264  
 265  // Record a macro of operations.
 266  func Record(o *Ops) MacroOp {
 267  	m := MacroOp{
 268  		ops: o,
 269  		id:  o.macroStack.push(),
 270  		pc:  o.pc(),
 271  	}
 272  	// Reserve room for a macro definition. Updated in Stop.
 273  	m.ops.Write(opconst.TypeMacroLen)
 274  	m.fill()
 275  	return m
 276  }
 277  
 278  // Stop ends a previously started recording and returns an
 279  // operation for replaying it.
 280  func (m MacroOp) Stop() CallOp {
 281  	m.ops.macroStack.pop(m.id)
 282  	m.fill()
 283  	return CallOp{
 284  		ops: m.ops,
 285  		pc:  m.pc,
 286  	}
 287  }
 288  
 289  func (m MacroOp) fill() {
 290  	pc := m.ops.pc()
 291  	// Fill out the macro definition reserved in Record.
 292  	data := m.ops.data[m.pc.data:]
 293  	data = data[:opconst.TypeMacroLen]
 294  	data[0] = byte(opconst.TypeMacro)
 295  	bo := binary.LittleEndian
 296  	bo.PutUint32(data[1:], uint32(pc.data))
 297  	bo.PutUint32(data[5:], uint32(pc.refs))
 298  }
 299  
 300  // Add the recorded list of operations. Add
 301  // panics if the Ops containing the recording
 302  // has been reset.
 303  func (c CallOp) Add(o *Ops) {
 304  	if c.ops == nil {
 305  		return
 306  	}
 307  	data := o.Write1(opconst.TypeCallLen, c.ops)
 308  	data[0] = byte(opconst.TypeCall)
 309  	bo := binary.LittleEndian
 310  	bo.PutUint32(data[1:], uint32(c.pc.data))
 311  	bo.PutUint32(data[5:], uint32(c.pc.refs))
 312  }
 313  
 314  func (r InvalidateOp) Add(o *Ops) {
 315  	data := o.Write(opconst.TypeRedrawLen)
 316  	data[0] = byte(opconst.TypeInvalidate)
 317  	bo := binary.LittleEndian
 318  	// UnixNano cannot represent the zero time.
 319  	if t := r.At; !t.IsZero() {
 320  		nanos := t.UnixNano()
 321  		if nanos > 0 {
 322  			bo.PutUint64(data[1:], uint64(nanos))
 323  		}
 324  	}
 325  }
 326  
 327  // Offset creates a TransformOp with the offset o.
 328  func Offset(o f32.Point) TransformOp {
 329  	return TransformOp{t: f32.Affine2D{}.Offset(o)}
 330  }
 331  
 332  // Affine creates a TransformOp representing the transformation a.
 333  func Affine(a f32.Affine2D) TransformOp {
 334  	return TransformOp{t: a}
 335  }
 336  
 337  func (t TransformOp) Add(o *Ops) {
 338  	data := o.Write(opconst.TypeTransformLen)
 339  	data[0] = byte(opconst.TypeTransform)
 340  	bo := binary.LittleEndian
 341  	a, b, c, d, e, f := t.t.Elems()
 342  	bo.PutUint32(data[1:], math.Float32bits(a))
 343  	bo.PutUint32(data[1+4*1:], math.Float32bits(b))
 344  	bo.PutUint32(data[1+4*2:], math.Float32bits(c))
 345  	bo.PutUint32(data[1+4*3:], math.Float32bits(d))
 346  	bo.PutUint32(data[1+4*4:], math.Float32bits(e))
 347  	bo.PutUint32(data[1+4*5:], math.Float32bits(f))
 348  }
 349  
 350  func (s *stack) push() stackID {
 351  	s.nextID++
 352  	sid := stackID{
 353  		id:   s.nextID,
 354  		prev: s.currentID,
 355  	}
 356  	s.currentID = s.nextID
 357  	return sid
 358  }
 359  
 360  func (s *stack) check(sid stackID) {
 361  	if s.currentID != sid.id {
 362  		panic("unbalanced operation")
 363  	}
 364  }
 365  
 366  func (s *stack) pop(sid stackID) {
 367  	s.check(sid)
 368  	s.currentID = sid.prev
 369  }
 370