gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/ops.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package ui
     4  
     5  import (
     6  	"encoding/binary"
     7  
     8  	"gioui.org/ui/internal/opconst"
     9  )
    10  
    11  // Ops holds a list of operations. Operations are stored in
    12  // serialized form to avoid garbage during construction of
    13  // the ops list.
    14  type Ops struct {
    15  	// version is incremented at each Reset.
    16  	version int
    17  	// data contains the serialized operations.
    18  	data []byte
    19  	// External references for operations.
    20  	refs []interface{}
    21  
    22  	stackDepth int
    23  	macroDepth int
    24  
    25  	inAux  bool
    26  	auxOff int
    27  	auxLen int
    28  }
    29  
    30  // StackOp can save and restore the operation state
    31  // in a stack-like manner.
    32  type StackOp struct {
    33  	stackDepth int
    34  	macroDepth int
    35  	active     bool
    36  	ops        *Ops
    37  }
    38  
    39  // MacroOp can record a list of operations for later
    40  // use.
    41  type MacroOp struct {
    42  	recording bool
    43  	ops       *Ops
    44  	version   int
    45  	pc        pc
    46  }
    47  
    48  type pc struct {
    49  	data int
    50  	refs int
    51  }
    52  
    53  // Push (save) the current operations state.
    54  func (s *StackOp) Push(o *Ops) {
    55  	if s.active {
    56  		panic("unbalanced push")
    57  	}
    58  	s.active = true
    59  	s.ops = o
    60  	o.stackDepth++
    61  	s.stackDepth = o.stackDepth
    62  	s.macroDepth = o.macroDepth
    63  	o.Write([]byte{byte(opconst.TypePush)})
    64  }
    65  
    66  // Pop (restore) a previously Pushed operations state.
    67  func (s *StackOp) Pop() {
    68  	if !s.active {
    69  		panic("unbalanced pop")
    70  	}
    71  	if s.ops.stackDepth != s.stackDepth {
    72  		panic("unbalanced pop")
    73  	}
    74  	if s.ops.macroDepth != s.macroDepth {
    75  		panic("pop in a different macro than push")
    76  	}
    77  	s.active = false
    78  	s.ops.stackDepth--
    79  	s.ops.Write([]byte{byte(opconst.TypePop)})
    80  }
    81  
    82  // Reset the Ops, preparing it for re-use.
    83  func (o *Ops) Reset() {
    84  	o.inAux = false
    85  	o.stackDepth = 0
    86  	// Leave references to the GC.
    87  	for i := range o.refs {
    88  		o.refs[i] = nil
    89  	}
    90  	o.data = o.data[:0]
    91  	o.refs = o.refs[:0]
    92  	o.version++
    93  }
    94  
    95  // Internal use only.
    96  func (o *Ops) Data() []byte {
    97  	return o.data
    98  }
    99  
   100  // Internal use only.
   101  func (o *Ops) Refs() []interface{} {
   102  	return o.refs
   103  }
   104  
   105  // Internal use only.
   106  func (o *Ops) Version() int {
   107  	return o.version
   108  }
   109  
   110  // Internal use only.
   111  func (o *Ops) Aux() []byte {
   112  	if !o.inAux {
   113  		return nil
   114  	}
   115  	aux := o.data[o.auxOff+opconst.TypeAuxLen:]
   116  	return aux[:o.auxLen]
   117  }
   118  
   119  func (d *Ops) write(op []byte, refs ...interface{}) {
   120  	d.data = append(d.data, op...)
   121  	d.refs = append(d.refs, refs...)
   122  }
   123  
   124  // Internal use only.
   125  func (o *Ops) Write(op []byte, refs ...interface{}) {
   126  	t := opconst.OpType(op[0])
   127  	if len(refs) != t.NumRefs() {
   128  		panic("invalid ref count")
   129  	}
   130  	switch t {
   131  	case opconst.TypeAux:
   132  		// Write only the data.
   133  		op = op[1:]
   134  		if !o.inAux {
   135  			o.inAux = true
   136  			o.auxOff = o.pc().data
   137  			o.auxLen = 0
   138  			header := make([]byte, opconst.TypeAuxLen)
   139  			header[0] = byte(opconst.TypeAux)
   140  			o.write(header)
   141  		}
   142  		o.auxLen += len(op)
   143  	default:
   144  		if o.inAux {
   145  			o.inAux = false
   146  			bo := binary.LittleEndian
   147  			bo.PutUint32(o.data[o.auxOff+1:], uint32(o.auxLen))
   148  		}
   149  	}
   150  	o.write(op, refs...)
   151  }
   152  
   153  func (d *Ops) pc() pc {
   154  	return pc{data: len(d.data), refs: len(d.refs)}
   155  }
   156  
   157  // Record a macro of operations.
   158  func (m *MacroOp) Record(o *Ops) {
   159  	if m.recording {
   160  		panic("already recording")
   161  	}
   162  	m.recording = true
   163  	m.ops = o
   164  	m.ops.macroDepth++
   165  	m.pc = o.pc()
   166  	// Reserve room for a macro definition. Updated in Stop.
   167  	m.ops.Write(make([]byte, opconst.TypeMacroDefLen))
   168  	m.fill()
   169  }
   170  
   171  // Stop ends a previously started recording.
   172  func (m *MacroOp) Stop() {
   173  	if !m.recording {
   174  		panic("not recording")
   175  	}
   176  	m.ops.macroDepth--
   177  	m.recording = false
   178  	m.fill()
   179  }
   180  
   181  func (m *MacroOp) fill() {
   182  	pc := m.ops.pc()
   183  	// Fill out the macro definition reserved in Record.
   184  	data := m.ops.data[m.pc.data:]
   185  	data = data[:opconst.TypeMacroDefLen]
   186  	data[0] = byte(opconst.TypeMacroDef)
   187  	bo := binary.LittleEndian
   188  	bo.PutUint32(data[1:], uint32(pc.data))
   189  	bo.PutUint32(data[5:], uint32(pc.refs))
   190  	m.version = m.ops.version
   191  }
   192  
   193  // Add the recorded list of operations. The Ops
   194  // argument may be different than the Ops argument
   195  // passed to Record.
   196  func (m MacroOp) Add(o *Ops) {
   197  	if m.recording {
   198  		panic("a recording is in progress")
   199  	}
   200  	if m.ops == nil {
   201  		return
   202  	}
   203  	data := make([]byte, opconst.TypeMacroLen)
   204  	data[0] = byte(opconst.TypeMacro)
   205  	bo := binary.LittleEndian
   206  	bo.PutUint32(data[1:], uint32(m.pc.data))
   207  	bo.PutUint32(data[5:], uint32(m.pc.refs))
   208  	bo.PutUint32(data[9:], uint32(m.version))
   209  	o.Write(data, m.ops)
   210  }