github.com/Seikaijyu/gio@v0.0.1/internal/ops/ops.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package ops 4 5 import ( 6 "encoding/binary" 7 "image" 8 "math" 9 10 "github.com/Seikaijyu/gio/f32" 11 "github.com/Seikaijyu/gio/internal/byteslice" 12 "github.com/Seikaijyu/gio/internal/scene" 13 ) 14 15 type Ops struct { 16 // version is incremented at each Reset. 17 version uint32 18 // data contains the serialized operations. 19 data []byte 20 // refs hold external references for operations. 21 refs []interface{} 22 // stringRefs provides space for string references, pointers to which will 23 // be stored in refs. Storing a string directly in refs would cause a heap 24 // allocation, to store the string header in an interface value. The backing 25 // array of stringRefs, on the other hand, gets reused between calls to 26 // reset, making string references free on average. 27 // 28 // Appending to stringRefs might reallocate the backing array, which will 29 // leave pointers to the old array in refs. This temporarily causes a slight 30 // increase in memory usage, but this, too, amortizes away as the capacity 31 // of stringRefs approaches its stable maximum. 32 stringRefs []string 33 // nextStateID is the id allocated for the next 34 // StateOp. 35 nextStateID uint32 36 // multipOp indicates a multi-op such as clip.Path is being added. 37 multipOp bool 38 39 macroStack stack 40 stacks [_StackKind]stack 41 } 42 43 type OpType byte 44 45 type Shape byte 46 47 // Start at a high number for easier debugging. 48 const firstOpIndex = 200 49 50 const ( 51 TypeMacro OpType = iota + firstOpIndex 52 TypeCall 53 TypeDefer 54 TypeTransform 55 TypePopTransform 56 TypePushOpacity 57 TypePopOpacity 58 TypeInvalidate 59 TypeImage 60 TypePaint 61 TypeColor 62 TypeLinearGradient 63 TypePass 64 TypePopPass 65 TypePointerInput 66 TypeClipboardRead 67 TypeClipboardWrite 68 TypeSource 69 TypeTarget 70 TypeOffer 71 TypeKeyInput 72 TypeKeyFocus 73 TypeKeySoftKeyboard 74 TypeSave 75 TypeLoad 76 TypeAux 77 TypeClip 78 TypePopClip 79 TypeProfile 80 TypeCursor 81 TypePath 82 TypeStroke 83 TypeSemanticLabel 84 TypeSemanticDesc 85 TypeSemanticClass 86 TypeSemanticSelected 87 TypeSemanticEnabled 88 TypeSnippet 89 TypeSelection 90 TypeActionInput 91 ) 92 93 type StackID struct { 94 id uint32 95 prev uint32 96 } 97 98 // StateOp represents a saved operation snapshot to be restored 99 // later. 100 type StateOp struct { 101 id uint32 102 macroID uint32 103 ops *Ops 104 } 105 106 // stack tracks the integer identities of stack operations to ensure correct 107 // pairing of their push and pop methods. 108 type stack struct { 109 currentID uint32 110 nextID uint32 111 } 112 113 type StackKind uint8 114 115 // ClipOp is the shadow of clip.Op. 116 type ClipOp struct { 117 Bounds image.Rectangle 118 Outline bool 119 Shape Shape 120 } 121 122 const ( 123 ClipStack StackKind = iota 124 TransStack 125 PassStack 126 OpacityStack 127 _StackKind 128 ) 129 130 const ( 131 Path Shape = iota 132 Ellipse 133 Rect 134 ) 135 136 const ( 137 TypeMacroLen = 1 + 4 + 4 138 TypeCallLen = 1 + 4 + 4 + 4 + 4 139 TypeDeferLen = 1 140 TypeTransformLen = 1 + 1 + 4*6 141 TypePopTransformLen = 1 142 TypePushOpacityLen = 1 + 4 143 TypePopOpacityLen = 1 144 TypeRedrawLen = 1 + 8 145 TypeImageLen = 1 + 1 146 TypePaintLen = 1 147 TypeColorLen = 1 + 4 148 TypeLinearGradientLen = 1 + 8*2 + 4*2 149 TypePassLen = 1 150 TypePopPassLen = 1 151 TypePointerInputLen = 1 + 1 + 1*2 + 2*4 + 2*4 152 TypeClipboardReadLen = 1 153 TypeClipboardWriteLen = 1 154 TypeSourceLen = 1 155 TypeTargetLen = 1 156 TypeOfferLen = 1 157 TypeKeyInputLen = 1 + 1 158 TypeKeyFocusLen = 1 + 1 159 TypeKeySoftKeyboardLen = 1 + 1 160 TypeSaveLen = 1 + 4 161 TypeLoadLen = 1 + 4 162 TypeAuxLen = 1 163 TypeClipLen = 1 + 4*4 + 1 + 1 164 TypePopClipLen = 1 165 TypeProfileLen = 1 166 TypeCursorLen = 2 167 TypePathLen = 8 + 1 168 TypeStrokeLen = 1 + 4 169 TypeSemanticLabelLen = 1 170 TypeSemanticDescLen = 1 171 TypeSemanticClassLen = 2 172 TypeSemanticSelectedLen = 2 173 TypeSemanticEnabledLen = 2 174 TypeSnippetLen = 1 + 4 + 4 175 TypeSelectionLen = 1 + 2*4 + 2*4 + 4 + 4 176 TypeActionInputLen = 1 + 1 177 ) 178 179 func (op *ClipOp) Decode(data []byte) { 180 if len(data) < TypeClipLen || OpType(data[0]) != TypeClip { 181 panic("invalid op") 182 } 183 data = data[:TypeClipLen] 184 bo := binary.LittleEndian 185 op.Bounds.Min.X = int(int32(bo.Uint32(data[1:]))) 186 op.Bounds.Min.Y = int(int32(bo.Uint32(data[5:]))) 187 op.Bounds.Max.X = int(int32(bo.Uint32(data[9:]))) 188 op.Bounds.Max.Y = int(int32(bo.Uint32(data[13:]))) 189 op.Outline = data[17] == 1 190 op.Shape = Shape(data[18]) 191 } 192 193 func Reset(o *Ops) { 194 o.macroStack = stack{} 195 o.stacks = [_StackKind]stack{} 196 // Leave references to the GC. 197 for i := range o.refs { 198 o.refs[i] = nil 199 } 200 for i := range o.stringRefs { 201 o.stringRefs[i] = "" 202 } 203 o.data = o.data[:0] 204 o.refs = o.refs[:0] 205 o.stringRefs = o.stringRefs[:0] 206 o.nextStateID = 0 207 o.version++ 208 } 209 210 func Write(o *Ops, n int) []byte { 211 if o.multipOp { 212 panic("cannot mix multi ops with single ones") 213 } 214 o.data = append(o.data, make([]byte, n)...) 215 return o.data[len(o.data)-n:] 216 } 217 218 func BeginMulti(o *Ops) { 219 if o.multipOp { 220 panic("cannot interleave multi ops") 221 } 222 o.multipOp = true 223 } 224 225 func EndMulti(o *Ops) { 226 if !o.multipOp { 227 panic("cannot end non multi ops") 228 } 229 o.multipOp = false 230 } 231 232 func WriteMulti(o *Ops, n int) []byte { 233 if !o.multipOp { 234 panic("cannot use multi ops in single ops") 235 } 236 o.data = append(o.data, make([]byte, n)...) 237 return o.data[len(o.data)-n:] 238 } 239 240 func PushMacro(o *Ops) StackID { 241 return o.macroStack.push() 242 } 243 244 func PopMacro(o *Ops, id StackID) { 245 o.macroStack.pop(id) 246 } 247 248 func FillMacro(o *Ops, startPC PC) { 249 pc := PCFor(o) 250 // Fill out the macro definition reserved in Record. 251 data := o.data[startPC.data:] 252 data = data[:TypeMacroLen] 253 data[0] = byte(TypeMacro) 254 bo := binary.LittleEndian 255 bo.PutUint32(data[1:], uint32(pc.data)) 256 bo.PutUint32(data[5:], uint32(pc.refs)) 257 } 258 259 func AddCall(o *Ops, callOps *Ops, pc PC, end PC) { 260 data := Write1(o, TypeCallLen, callOps) 261 data[0] = byte(TypeCall) 262 bo := binary.LittleEndian 263 bo.PutUint32(data[1:], uint32(pc.data)) 264 bo.PutUint32(data[5:], uint32(pc.refs)) 265 bo.PutUint32(data[9:], uint32(end.data)) 266 bo.PutUint32(data[13:], uint32(end.refs)) 267 } 268 269 func PushOp(o *Ops, kind StackKind) (StackID, uint32) { 270 return o.stacks[kind].push(), o.macroStack.currentID 271 } 272 273 func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) { 274 if o.macroStack.currentID != macroID { 275 panic("stack push and pop must not cross macro boundary") 276 } 277 o.stacks[kind].pop(sid) 278 } 279 280 func Write1(o *Ops, n int, ref1 interface{}) []byte { 281 o.data = append(o.data, make([]byte, n)...) 282 o.refs = append(o.refs, ref1) 283 return o.data[len(o.data)-n:] 284 } 285 286 func Write1String(o *Ops, n int, ref1 string) []byte { 287 o.data = append(o.data, make([]byte, n)...) 288 o.stringRefs = append(o.stringRefs, ref1) 289 o.refs = append(o.refs, &o.stringRefs[len(o.stringRefs)-1]) 290 return o.data[len(o.data)-n:] 291 } 292 293 func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte { 294 o.data = append(o.data, make([]byte, n)...) 295 o.refs = append(o.refs, ref1, ref2) 296 return o.data[len(o.data)-n:] 297 } 298 299 func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte { 300 o.data = append(o.data, make([]byte, n)...) 301 o.stringRefs = append(o.stringRefs, ref2) 302 o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1]) 303 return o.data[len(o.data)-n:] 304 } 305 306 func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte { 307 o.data = append(o.data, make([]byte, n)...) 308 o.refs = append(o.refs, ref1, ref2, ref3) 309 return o.data[len(o.data)-n:] 310 } 311 312 func PCFor(o *Ops) PC { 313 return PC{data: uint32(len(o.data)), refs: uint32(len(o.refs))} 314 } 315 316 func (s *stack) push() StackID { 317 s.nextID++ 318 sid := StackID{ 319 id: s.nextID, 320 prev: s.currentID, 321 } 322 s.currentID = s.nextID 323 return sid 324 } 325 326 func (s *stack) check(sid StackID) { 327 if s.currentID != sid.id { 328 panic("unbalanced operation") 329 } 330 } 331 332 func (s *stack) pop(sid StackID) { 333 s.check(sid) 334 s.currentID = sid.prev 335 } 336 337 // Save the effective transformation. 338 func Save(o *Ops) StateOp { 339 o.nextStateID++ 340 s := StateOp{ 341 ops: o, 342 id: o.nextStateID, 343 macroID: o.macroStack.currentID, 344 } 345 bo := binary.LittleEndian 346 data := Write(o, TypeSaveLen) 347 data[0] = byte(TypeSave) 348 bo.PutUint32(data[1:], uint32(s.id)) 349 return s 350 } 351 352 // Load a previously saved operations state given 353 // its ID. 354 func (s StateOp) Load() { 355 bo := binary.LittleEndian 356 data := Write(s.ops, TypeLoadLen) 357 data[0] = byte(TypeLoad) 358 bo.PutUint32(data[1:], uint32(s.id)) 359 } 360 361 func DecodeCommand(d []byte) scene.Command { 362 var cmd scene.Command 363 copy(byteslice.Uint32(cmd[:]), d) 364 return cmd 365 } 366 367 func EncodeCommand(out []byte, cmd scene.Command) { 368 copy(out, byteslice.Uint32(cmd[:])) 369 } 370 371 func DecodeTransform(data []byte) (t f32.Affine2D, push bool) { 372 if OpType(data[0]) != TypeTransform { 373 panic("invalid op") 374 } 375 push = data[1] != 0 376 data = data[2:] 377 data = data[:4*6] 378 379 bo := binary.LittleEndian 380 a := math.Float32frombits(bo.Uint32(data)) 381 b := math.Float32frombits(bo.Uint32(data[4*1:])) 382 c := math.Float32frombits(bo.Uint32(data[4*2:])) 383 d := math.Float32frombits(bo.Uint32(data[4*3:])) 384 e := math.Float32frombits(bo.Uint32(data[4*4:])) 385 f := math.Float32frombits(bo.Uint32(data[4*5:])) 386 return f32.NewAffine2D(a, b, c, d, e, f), push 387 } 388 389 func DecodeOpacity(data []byte) float32 { 390 if OpType(data[0]) != TypePushOpacity { 391 panic("invalid op") 392 } 393 bo := binary.LittleEndian 394 return math.Float32frombits(bo.Uint32(data[1:])) 395 } 396 397 // DecodeSave decodes the state id of a save op. 398 func DecodeSave(data []byte) int { 399 if OpType(data[0]) != TypeSave { 400 panic("invalid op") 401 } 402 bo := binary.LittleEndian 403 return int(bo.Uint32(data[1:])) 404 } 405 406 // DecodeLoad decodes the state id of a load op. 407 func DecodeLoad(data []byte) int { 408 if OpType(data[0]) != TypeLoad { 409 panic("invalid op") 410 } 411 bo := binary.LittleEndian 412 return int(bo.Uint32(data[1:])) 413 } 414 415 type opProp struct { 416 Size byte 417 NumRefs byte 418 } 419 420 var opProps = [0x100]opProp{ 421 TypeMacro: {Size: TypeMacroLen, NumRefs: 0}, 422 TypeCall: {Size: TypeCallLen, NumRefs: 1}, 423 TypeDefer: {Size: TypeDeferLen, NumRefs: 0}, 424 TypeTransform: {Size: TypeTransformLen, NumRefs: 0}, 425 TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0}, 426 TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0}, 427 TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0}, 428 TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0}, 429 TypeImage: {Size: TypeImageLen, NumRefs: 2}, 430 TypePaint: {Size: TypePaintLen, NumRefs: 0}, 431 TypeColor: {Size: TypeColorLen, NumRefs: 0}, 432 TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0}, 433 TypePass: {Size: TypePassLen, NumRefs: 0}, 434 TypePopPass: {Size: TypePopPassLen, NumRefs: 0}, 435 TypePointerInput: {Size: TypePointerInputLen, NumRefs: 1}, 436 TypeClipboardRead: {Size: TypeClipboardReadLen, NumRefs: 1}, 437 TypeClipboardWrite: {Size: TypeClipboardWriteLen, NumRefs: 1}, 438 TypeSource: {Size: TypeSourceLen, NumRefs: 2}, 439 TypeTarget: {Size: TypeTargetLen, NumRefs: 2}, 440 TypeOffer: {Size: TypeOfferLen, NumRefs: 3}, 441 TypeKeyInput: {Size: TypeKeyInputLen, NumRefs: 2}, 442 TypeKeyFocus: {Size: TypeKeyFocusLen, NumRefs: 1}, 443 TypeKeySoftKeyboard: {Size: TypeKeySoftKeyboardLen, NumRefs: 0}, 444 TypeSave: {Size: TypeSaveLen, NumRefs: 0}, 445 TypeLoad: {Size: TypeLoadLen, NumRefs: 0}, 446 TypeAux: {Size: TypeAuxLen, NumRefs: 0}, 447 TypeClip: {Size: TypeClipLen, NumRefs: 0}, 448 TypePopClip: {Size: TypePopClipLen, NumRefs: 0}, 449 TypeProfile: {Size: TypeProfileLen, NumRefs: 1}, 450 TypeCursor: {Size: TypeCursorLen, NumRefs: 0}, 451 TypePath: {Size: TypePathLen, NumRefs: 0}, 452 TypeStroke: {Size: TypeStrokeLen, NumRefs: 0}, 453 TypeSemanticLabel: {Size: TypeSemanticLabelLen, NumRefs: 1}, 454 TypeSemanticDesc: {Size: TypeSemanticDescLen, NumRefs: 1}, 455 TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0}, 456 TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0}, 457 TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0}, 458 TypeSnippet: {Size: TypeSnippetLen, NumRefs: 2}, 459 TypeSelection: {Size: TypeSelectionLen, NumRefs: 1}, 460 TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0}, 461 } 462 463 func (t OpType) props() (size, numRefs uint32) { 464 v := opProps[t] 465 return uint32(v.Size), uint32(v.NumRefs) 466 } 467 468 func (t OpType) Size() uint32 { 469 return uint32(opProps[t].Size) 470 } 471 472 func (t OpType) NumRefs() uint32 { 473 return uint32(opProps[t].NumRefs) 474 } 475 476 func (t OpType) String() string { 477 switch t { 478 case TypeMacro: 479 return "Macro" 480 case TypeCall: 481 return "Call" 482 case TypeDefer: 483 return "Defer" 484 case TypeTransform: 485 return "Transform" 486 case TypePopTransform: 487 return "PopTransform" 488 case TypePushOpacity: 489 return "PushOpacity" 490 case TypePopOpacity: 491 return "PopOpacity" 492 case TypeInvalidate: 493 return "Invalidate" 494 case TypeImage: 495 return "Image" 496 case TypePaint: 497 return "Paint" 498 case TypeColor: 499 return "Color" 500 case TypeLinearGradient: 501 return "LinearGradient" 502 case TypePass: 503 return "Pass" 504 case TypePopPass: 505 return "PopPass" 506 case TypePointerInput: 507 return "PointerInput" 508 case TypeClipboardRead: 509 return "ClipboardRead" 510 case TypeClipboardWrite: 511 return "ClipboardWrite" 512 case TypeSource: 513 return "Source" 514 case TypeTarget: 515 return "Target" 516 case TypeOffer: 517 return "Offer" 518 case TypeKeyInput: 519 return "KeyInput" 520 case TypeKeyFocus: 521 return "KeyFocus" 522 case TypeKeySoftKeyboard: 523 return "KeySoftKeyboard" 524 case TypeSave: 525 return "Save" 526 case TypeLoad: 527 return "Load" 528 case TypeAux: 529 return "Aux" 530 case TypeClip: 531 return "Clip" 532 case TypePopClip: 533 return "PopClip" 534 case TypeProfile: 535 return "Profile" 536 case TypeCursor: 537 return "Cursor" 538 case TypePath: 539 return "Path" 540 case TypeStroke: 541 return "Stroke" 542 case TypeSemanticLabel: 543 return "SemanticDescription" 544 default: 545 panic("unknown OpType") 546 } 547 }