• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

asciimoth / pasta / 29304825563

14 Jul 2026 03:59AM UTC coverage: 77.912% (-0.09%) from 78.004%
29304825563

push

github

asciimoth
feat(std): add loops

631 of 826 new or added lines in 10 files covered. (76.39%)

9446 of 12124 relevant lines covered (77.91%)

718.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.95
/pasta/std/node_loop.go
1
package std
2

3
import (
4
        "github.com/asciimoth/configer/configer"
5
        "github.com/asciimoth/pasta/pasta"
6
)
7

8
const (
9
        // NodeTypeForLoop is the class name for ForLoopClass.
10
        NodeTypeForLoop = "pasta/ForLoop"
11
        // NodeTypeWhileLoop is the class name for WhileLoopClass.
12
        NodeTypeWhileLoop = "pasta/WhileLoop"
13
)
14

15
const (
16
        loopLabelWaiting = "waiting"
17
        loopLabelLooping = "looping"
18
)
19

20
// ForLoopClass creates counted trigger loops.
21
//
22
// A ForLoop captures Start index, End index, and Step when Trigger receives a
23
// pasta/trigger event. It then emits LoopStartIteration, the current Index, and
24
// Body for each iteration. The matching Iter node must send LoopEndIteration
25
// back over the pasta/loop link before the next iteration can start.
26
type ForLoopClass struct{}
27

28
func (ForLoopClass) ClassName() string        { return NodeTypeForLoop }
650✔
29
func (ForLoopClass) ShortDescription() string { return "Run counted loop" }
590✔
NEW
30
func (ForLoopClass) LongDescription() string {
×
NEW
31
        return "Runs a counted loop from Start index toward End index by Step. Each iteration emits Body and waits for the associated Iter node to send break or continue over the pasta/loop link."
×
NEW
32
}
×
33
func (ForLoopClass) DefaultNodeParams() pasta.NodeClassParams {
682✔
34
        return pasta.NodeClassParams{PrimaryType: TypeLoop, InitialPorts: []pasta.Port{
682✔
35
                {Direction: "left", Name: "Trigger", Types: []string{TypeTrigger}},
682✔
36
                {Direction: "left", Name: "Start index", Types: []string{TypeInt}},
682✔
37
                {Direction: "left", Name: "End index", Types: []string{TypeInt}},
682✔
38
                {Direction: "left", Name: "Step", Types: []string{TypeInt}},
682✔
39
                {Direction: "right", Name: "Loop", Types: []string{TypeLoop}},
682✔
40
                {Direction: "right", Name: "Body", Types: []string{TypeTrigger}},
682✔
41
                {Direction: "right", Name: "Index", Types: []string{TypeInt}},
682✔
42
                {Direction: "right", Name: "Completed", Types: []string{TypeTrigger}},
682✔
43
        }}
682✔
44
}
682✔
45
func (ForLoopClass) NewNode(_ configer.Config, previous ...*pasta.NodeClassState) (pasta.Node, error) {
11✔
46
        if state := firstState(previous); state != nil {
13✔
47
                state.PrimaryType = TypeLoop
2✔
48
                state.Label = loopLabelWaiting
2✔
49
        }
2✔
50
        return newForLoopNode(), nil
11✔
51
}
52

53
// WhileLoopClass creates trigger loops that run until Iter sends break.
54
//
55
// A WhileLoop starts when Trigger receives a pasta/trigger event. Each
56
// iteration emits Body and waits for the associated Iter node. Continue starts
57
// the next iteration; break emits Completed and ends the loop.
58
type WhileLoopClass struct{}
59

60
func (WhileLoopClass) ClassName() string        { return NodeTypeWhileLoop }
650✔
61
func (WhileLoopClass) ShortDescription() string { return "Run loop until break" }
590✔
NEW
62
func (WhileLoopClass) LongDescription() string {
×
NEW
63
        return "Runs Body repeatedly until the associated Iter node sends break over the pasta/loop link. Continue starts the next iteration."
×
NEW
64
}
×
65
func (WhileLoopClass) DefaultNodeParams() pasta.NodeClassParams {
656✔
66
        return pasta.NodeClassParams{PrimaryType: TypeLoop, InitialPorts: []pasta.Port{
656✔
67
                {Direction: "left", Name: "Trigger", Types: []string{TypeTrigger}},
656✔
68
                {Direction: "right", Name: "Loop", Types: []string{TypeLoop}},
656✔
69
                {Direction: "right", Name: "Body", Types: []string{TypeTrigger}},
656✔
70
                {Direction: "right", Name: "Completed", Types: []string{TypeTrigger}},
656✔
71
        }}
656✔
72
}
656✔
73
func (WhileLoopClass) NewNode(_ configer.Config, previous ...*pasta.NodeClassState) (pasta.Node, error) {
2✔
74
        if state := firstState(previous); state != nil {
2✔
NEW
75
                state.PrimaryType = TypeLoop
×
NEW
76
                state.Label = loopLabelWaiting
×
NEW
77
        }
×
78
        return &whileLoopNode{}, nil
2✔
79
}
80

81
type forLoopConfig struct {
82
        start int
83
        end   int
84
        step  int
85
}
86

87
type forLoopRun struct {
88
        config    forLoopConfig
89
        index     int
90
        iteration uint32
91
        active    bool
92
}
93

94
type forLoopNode struct {
95
        pasta.BasicNode
96

97
        w  *pasta.Workspace
98
        id uint64
99

100
        triggerIn    uint64
101
        startIn      uint64
102
        endIn        uint64
103
        stepIn       uint64
104
        loopOut      uint64
105
        bodyOut      uint64
106
        indexOut     uint64
107
        completedOut uint64
108

109
        latest    forLoopConfig
110
        run       forLoopRun
111
        queued    []forLoopConfig
112
        loopLink  uint64
113
        iteration uint32
114
}
115

116
func newForLoopNode() *forLoopNode {
11✔
117
        return &forLoopNode{latest: forLoopConfig{step: 1}}
11✔
118
}
11✔
119

120
func (n *forLoopNode) OnInit(w *pasta.Workspace, _ pasta.Logger, id uint64, _ string, restored *pasta.NodeInitData, _, _, _, _ bool) error {
11✔
121
        n.w = w
11✔
122
        n.id = id
11✔
123
        if n.latest.step == 0 {
11✔
NEW
124
                n.latest.step = 1
×
NEW
125
        }
×
126
        n.findPorts(restored)
11✔
127
        if err := n.w.SetNodePrimaryLocked(n.id, TypeLoop); err != nil {
11✔
NEW
128
                return err
×
NEW
129
        }
×
130
        return n.setWaiting()
11✔
131
}
132

133
func (n *forLoopNode) OnReady() error {
11✔
134
        n.requestInputs()
11✔
135
        return nil
11✔
136
}
11✔
137

138
func (n *forLoopNode) PreLinkAdd(port uint64, linkType, portDirection string) error {
53✔
139
        switch port {
53✔
140
        case n.triggerIn:
1✔
141
                if portDirection == "left" && linkType == TypeTrigger {
2✔
142
                        return nil
1✔
143
                }
1✔
144
        case n.startIn, n.endIn, n.stepIn:
24✔
145
                if portDirection != "left" || linkType != TypeInt {
24✔
NEW
146
                        break
×
147
                }
148
                snapshot, ok := n.w.PortSnapshotLocked(port)
24✔
149
                if ok && len(snapshot.Links) > 0 {
24✔
NEW
150
                        return pasta.ErrLinkDup
×
NEW
151
                }
×
152
                return nil
24✔
153
        case n.loopOut:
8✔
154
                if portDirection != "right" || linkType != TypeLoop {
8✔
NEW
155
                        break
×
156
                }
157
                snapshot, ok := n.w.PortSnapshotLocked(port)
8✔
158
                if ok && len(snapshot.Links) > 0 {
8✔
NEW
159
                        return pasta.ErrLinkDup
×
NEW
160
                }
×
161
                return nil
8✔
162
        case n.bodyOut, n.completedOut:
13✔
163
                if portDirection == "right" && linkType == TypeTrigger {
26✔
164
                        return nil
13✔
165
                }
13✔
166
        case n.indexOut:
7✔
167
                if portDirection == "right" && linkType == TypeInt {
14✔
168
                        return nil
7✔
169
                }
7✔
170
        }
NEW
171
        return pasta.LinkTypeErr(linkType)
×
172
}
173

174
func (n *forLoopNode) OnLinkAdd(link, port uint64, linkType, portDirection string) error {
53✔
175
        if port == n.loopOut && linkType == TypeLoop {
61✔
176
                n.loopLink = link
8✔
177
        }
8✔
178
        n.clearLoopState()
53✔
179
        if portDirection == "left" && linkType == TypeInt {
77✔
180
                n.requestLink(link, port)
24✔
181
        }
24✔
182
        return nil
53✔
183
}
184

185
func (n *forLoopNode) OnLinkRemoved(link, port uint64, linkType, _ string) error {
1✔
186
        n.clearLoopState()
1✔
187
        if port == n.loopOut && linkType == TypeLoop && link == n.loopLink {
1✔
NEW
188
                n.loopLink = 0
×
NEW
189
        }
×
190
        switch port {
1✔
NEW
191
        case n.startIn, n.endIn:
×
NEW
192
                n.setInput(port, 0)
×
NEW
193
        case n.stepIn:
×
NEW
194
                n.setInput(port, 1)
×
195
        }
196
        return nil
1✔
197
}
198

199
func (n *forLoopNode) OnEvent(event pasta.Event, linkType string, _ []string, receiverPortDirection string) error {
72✔
200
        switch event.ReceiverPort {
72✔
201
        case n.triggerIn:
2✔
202
                if receiverPortDirection == "left" && linkType == TypeTrigger && !IsRequest(event.Payload) {
4✔
203
                        n.triggerLoop()
2✔
204
                }
2✔
205
        case n.startIn, n.endIn, n.stepIn:
54✔
206
                if receiverPortDirection == "left" && linkType == TypeInt {
108✔
207
                        if value, ok := parseIntAny(event.Payload); ok {
108✔
208
                                n.setInput(event.ReceiverPort, value)
54✔
209
                        }
54✔
210
                }
211
        case n.loopOut:
16✔
212
                if receiverPortDirection == "right" && linkType == TypeLoop {
32✔
213
                        n.handleLoopEnd(event)
16✔
214
                }
16✔
NEW
215
        case n.indexOut:
×
NEW
216
                if receiverPortDirection == "right" && linkType == TypeInt && IsRequest(event.Payload) && n.run.active {
×
NEW
217
                        n.w.SendEventLocked(pasta.Event{
×
NEW
218
                                SenderNode:   n.id,
×
NEW
219
                                SenderPort:   n.indexOut,
×
NEW
220
                                ReceiverNode: event.SenderNode,
×
NEW
221
                                ReceiverPort: event.SenderPort,
×
NEW
222
                                Payload:      Int(n.run.index),
×
NEW
223
                        })
×
NEW
224
                }
×
225
        }
226
        return nil
72✔
227
}
228

229
func (n *forLoopNode) OnTrigger() error {
8✔
230
        n.triggerLoop()
8✔
231
        return nil
8✔
232
}
8✔
233

234
func (n *forLoopNode) findPorts(restored *pasta.NodeInitData) {
11✔
235
        if restored == nil {
11✔
NEW
236
                return
×
NEW
237
        }
×
238
        for _, port := range restored.LeftPorts {
55✔
239
                snapshot, ok := n.w.PortSnapshotLocked(port)
44✔
240
                if !ok {
44✔
NEW
241
                        continue
×
242
                }
243
                switch snapshot.Name {
44✔
244
                case "Trigger":
11✔
245
                        n.triggerIn = port
11✔
246
                case "Start index":
11✔
247
                        n.startIn = port
11✔
248
                case "End index":
11✔
249
                        n.endIn = port
11✔
250
                case "Step":
11✔
251
                        n.stepIn = port
11✔
252
                }
253
        }
254
        for _, port := range restored.RightPorts {
55✔
255
                snapshot, ok := n.w.PortSnapshotLocked(port)
44✔
256
                if !ok {
44✔
NEW
257
                        continue
×
258
                }
259
                switch snapshot.Name {
44✔
260
                case "Loop":
11✔
261
                        n.loopOut = port
11✔
262
                        n.loopLink = firstLoopLink(n.w, port)
11✔
263
                case "Body":
11✔
264
                        n.bodyOut = port
11✔
265
                case "Index":
11✔
266
                        n.indexOut = port
11✔
267
                case "Completed":
11✔
268
                        n.completedOut = port
11✔
269
                }
270
        }
271
}
272

273
func (n *forLoopNode) requestInputs() {
11✔
274
        for _, port := range []uint64{n.startIn, n.endIn, n.stepIn} {
44✔
275
                snapshot, ok := n.w.PortSnapshotLocked(port)
33✔
276
                if !ok {
33✔
NEW
277
                        continue
×
278
                }
279
                for _, link := range snapshot.Links {
36✔
280
                        n.requestLink(link, port)
3✔
281
                }
3✔
282
        }
283
}
284

285
func (n *forLoopNode) requestLink(link, port uint64) {
27✔
286
        linkSnapshot, ok := n.w.LinkSnapshotLocked(link)
27✔
287
        if !ok || linkSnapshot.Type != TypeInt {
27✔
NEW
288
                return
×
NEW
289
        }
×
290
        receiverNode, receiverPort := otherEndpoint(linkSnapshot, port)
27✔
291
        n.w.SendEventLocked(pasta.Event{SenderNode: n.id, SenderPort: port, ReceiverNode: receiverNode, ReceiverPort: receiverPort, Payload: RequestValue{}})
27✔
292
}
293

294
func (n *forLoopNode) setInput(port uint64, value int) {
54✔
295
        switch port {
54✔
296
        case n.startIn:
18✔
297
                n.latest.start = value
18✔
298
        case n.endIn:
18✔
299
                n.latest.end = value
18✔
300
        case n.stepIn:
18✔
301
                n.latest.step = value
18✔
302
        }
303
}
304

305
func (n *forLoopNode) triggerLoop() {
10✔
306
        if !n.hasLoopLink() {
11✔
307
                return
1✔
308
        }
1✔
309
        config := n.latest
9✔
310
        if n.run.active {
10✔
311
                n.queued = append(n.queued, config)
1✔
312
                return
1✔
313
        }
1✔
314
        n.startConfig(config)
8✔
315
}
316

317
func (n *forLoopNode) startConfig(config forLoopConfig) {
9✔
318
        if !n.hasLoopLink() {
9✔
NEW
319
                n.clearLoopState()
×
NEW
320
                return
×
NEW
321
        }
×
322
        if !forLoopHasIteration(config.start, config.end, config.step) {
9✔
NEW
323
                n.emitCompleted()
×
NEW
324
                n.startQueued()
×
NEW
325
                return
×
NEW
326
        }
×
327
        n.run = forLoopRun{config: config, index: config.start, active: true}
9✔
328
        n.startIteration()
9✔
329
}
330

331
func (n *forLoopNode) startIteration() {
18✔
332
        if !n.run.active {
18✔
NEW
333
                return
×
NEW
334
        }
×
335
        link := n.validLoopLink()
18✔
336
        if link == 0 {
18✔
NEW
337
                n.clearLoopState()
×
NEW
338
                return
×
NEW
339
        }
×
340
        n.iteration++
18✔
341
        n.run.iteration = n.iteration
18✔
342
        _ = n.setLooping()
18✔
343
        sendToLink(n.w, n.id, n.loopOut, link, LoopStartIteration{Iteration: n.run.iteration})
18✔
344
        sendToPort(n.w, n.id, n.indexOut, Int(n.run.index))
18✔
345
        sendToPort(n.w, n.id, n.bodyOut, Trigger{})
18✔
346
}
347

348
func (n *forLoopNode) handleLoopEnd(event pasta.Event) {
16✔
349
        if !n.run.active || event.Link != n.loopLink {
16✔
NEW
350
                return
×
NEW
351
        }
×
352
        msg, ok := event.Payload.(LoopEndIteration)
16✔
353
        if !ok || msg.Iteration != n.run.iteration {
16✔
NEW
354
                return
×
NEW
355
        }
×
356
        if msg.Break {
17✔
357
                n.finishLoop()
1✔
358
                return
1✔
359
        }
1✔
360
        n.run.index += n.run.config.step
15✔
361
        if !forLoopHasIteration(n.run.index, n.run.config.end, n.run.config.step) {
21✔
362
                n.finishLoop()
6✔
363
                return
6✔
364
        }
6✔
365
        n.startIteration()
9✔
366
}
367

368
func (n *forLoopNode) finishLoop() {
7✔
369
        n.run = forLoopRun{}
7✔
370
        _ = n.setWaiting()
7✔
371
        n.emitCompleted()
7✔
372
        n.startQueued()
7✔
373
}
7✔
374

375
func (n *forLoopNode) startQueued() {
7✔
376
        for len(n.queued) > 0 && !n.run.active {
8✔
377
                config := n.queued[0]
1✔
378
                copy(n.queued, n.queued[1:])
1✔
379
                n.queued[len(n.queued)-1] = forLoopConfig{}
1✔
380
                n.queued = n.queued[:len(n.queued)-1]
1✔
381
                n.startConfig(config)
1✔
382
        }
1✔
383
}
384

385
func (n *forLoopNode) clearLoopState() {
54✔
386
        n.run = forLoopRun{}
54✔
387
        n.queued = nil
54✔
388
        _ = n.setWaiting()
54✔
389
}
54✔
390

391
func (n *forLoopNode) emitCompleted() {
7✔
392
        sendToPort(n.w, n.id, n.completedOut, Trigger{})
7✔
393
}
7✔
394

395
func (n *forLoopNode) hasLoopLink() bool {
19✔
396
        return n.validLoopLink() != 0
19✔
397
}
19✔
398

399
func (n *forLoopNode) validLoopLink() uint64 {
37✔
400
        if n.loopLink != 0 {
73✔
401
                if link, ok := n.w.LinkSnapshotLocked(n.loopLink); ok && link.Type == TypeLoop {
72✔
402
                        return n.loopLink
36✔
403
                }
36✔
NEW
404
                n.loopLink = 0
×
405
        }
406
        n.loopLink = firstLoopLink(n.w, n.loopOut)
1✔
407
        return n.loopLink
1✔
408
}
409

410
func (n *forLoopNode) setLooping() error {
18✔
411
        return n.w.SetNodeLabelLocked(n.id, loopLabelLooping)
18✔
412
}
18✔
413

414
func (n *forLoopNode) setWaiting() error {
72✔
415
        return n.w.SetNodeLabelLocked(n.id, loopLabelWaiting)
72✔
416
}
72✔
417

418
func forLoopHasIteration(index, end, step int) bool {
24✔
419
        switch {
24✔
420
        case step > 0:
24✔
421
                return index < end
24✔
NEW
422
        case step < 0:
×
NEW
423
                return index > end
×
NEW
424
        default:
×
NEW
425
                return false
×
426
        }
427
}
428

429
type whileLoopNode struct {
430
        pasta.BasicNode
431

432
        w  *pasta.Workspace
433
        id uint64
434

435
        triggerIn    uint64
436
        loopOut      uint64
437
        bodyOut      uint64
438
        completedOut uint64
439

440
        loopLink         uint64
441
        active           bool
442
        queued           int
443
        iteration        uint32
444
        currentIteration uint32
445
}
446

447
func (n *whileLoopNode) OnInit(w *pasta.Workspace, _ pasta.Logger, id uint64, _ string, restored *pasta.NodeInitData, _, _, _, _ bool) error {
2✔
448
        n.w = w
2✔
449
        n.id = id
2✔
450
        n.findPorts(restored)
2✔
451
        if err := n.w.SetNodePrimaryLocked(n.id, TypeLoop); err != nil {
2✔
NEW
452
                return err
×
NEW
453
        }
×
454
        return n.setWaiting()
2✔
455
}
456

457
func (n *whileLoopNode) PreLinkAdd(port uint64, linkType, portDirection string) error {
3✔
458
        switch port {
3✔
NEW
459
        case n.triggerIn:
×
NEW
460
                if portDirection == "left" && linkType == TypeTrigger {
×
NEW
461
                        return nil
×
NEW
462
                }
×
463
        case n.loopOut:
1✔
464
                if portDirection != "right" || linkType != TypeLoop {
1✔
NEW
465
                        break
×
466
                }
467
                snapshot, ok := n.w.PortSnapshotLocked(port)
1✔
468
                if ok && len(snapshot.Links) > 0 {
1✔
NEW
469
                        return pasta.ErrLinkDup
×
NEW
470
                }
×
471
                return nil
1✔
472
        case n.bodyOut, n.completedOut:
2✔
473
                if portDirection == "right" && linkType == TypeTrigger {
4✔
474
                        return nil
2✔
475
                }
2✔
476
        }
NEW
477
        return pasta.LinkTypeErr(linkType)
×
478
}
479

480
func (n *whileLoopNode) OnLinkAdd(link, port uint64, linkType, _ string) error {
3✔
481
        if port == n.loopOut && linkType == TypeLoop {
4✔
482
                n.loopLink = link
1✔
483
        }
1✔
484
        n.clearLoopState()
3✔
485
        return nil
3✔
486
}
487

NEW
488
func (n *whileLoopNode) OnLinkRemoved(link, port uint64, linkType, _ string) error {
×
NEW
489
        n.clearLoopState()
×
NEW
490
        if port == n.loopOut && linkType == TypeLoop && link == n.loopLink {
×
NEW
491
                n.loopLink = 0
×
NEW
492
        }
×
NEW
493
        return nil
×
494
}
495

496
func (n *whileLoopNode) OnEvent(event pasta.Event, linkType string, _ []string, receiverPortDirection string) error {
3✔
497
        switch event.ReceiverPort {
3✔
NEW
498
        case n.triggerIn:
×
NEW
499
                if receiverPortDirection == "left" && linkType == TypeTrigger && !IsRequest(event.Payload) {
×
NEW
500
                        n.triggerLoop()
×
NEW
501
                }
×
502
        case n.loopOut:
3✔
503
                if receiverPortDirection == "right" && linkType == TypeLoop {
6✔
504
                        n.handleLoopEnd(event)
3✔
505
                }
3✔
506
        }
507
        return nil
3✔
508
}
509

510
func (n *whileLoopNode) OnTrigger() error {
1✔
511
        n.triggerLoop()
1✔
512
        return nil
1✔
513
}
1✔
514

515
func (n *whileLoopNode) findPorts(restored *pasta.NodeInitData) {
2✔
516
        if restored == nil {
2✔
NEW
517
                return
×
NEW
518
        }
×
519
        for _, port := range restored.LeftPorts {
4✔
520
                snapshot, ok := n.w.PortSnapshotLocked(port)
2✔
521
                if ok && snapshot.Name == "Trigger" {
4✔
522
                        n.triggerIn = port
2✔
523
                }
2✔
524
        }
525
        for _, port := range restored.RightPorts {
8✔
526
                snapshot, ok := n.w.PortSnapshotLocked(port)
6✔
527
                if !ok {
6✔
NEW
528
                        continue
×
529
                }
530
                switch snapshot.Name {
6✔
531
                case "Loop":
2✔
532
                        n.loopOut = port
2✔
533
                        n.loopLink = firstLoopLink(n.w, port)
2✔
534
                case "Body":
2✔
535
                        n.bodyOut = port
2✔
536
                case "Completed":
2✔
537
                        n.completedOut = port
2✔
538
                }
539
        }
540
}
541

542
func (n *whileLoopNode) triggerLoop() {
1✔
543
        if !n.hasLoopLink() {
1✔
NEW
544
                return
×
NEW
545
        }
×
546
        if n.active {
1✔
NEW
547
                n.queued++
×
NEW
548
                return
×
NEW
549
        }
×
550
        n.startIteration()
1✔
551
}
552

553
func (n *whileLoopNode) startIteration() {
3✔
554
        link := n.validLoopLink()
3✔
555
        if link == 0 {
3✔
NEW
556
                n.clearLoopState()
×
NEW
557
                return
×
NEW
558
        }
×
559
        n.active = true
3✔
560
        n.iteration++
3✔
561
        n.currentIteration = n.iteration
3✔
562
        _ = n.setLooping()
3✔
563
        sendToLink(n.w, n.id, n.loopOut, link, LoopStartIteration{Iteration: n.currentIteration})
3✔
564
        sendToPort(n.w, n.id, n.bodyOut, Trigger{})
3✔
565
}
566

567
func (n *whileLoopNode) handleLoopEnd(event pasta.Event) {
3✔
568
        if !n.active || event.Link != n.loopLink {
3✔
NEW
569
                return
×
NEW
570
        }
×
571
        msg, ok := event.Payload.(LoopEndIteration)
3✔
572
        if !ok || msg.Iteration != n.currentIteration {
3✔
NEW
573
                return
×
NEW
574
        }
×
575
        if msg.Break {
4✔
576
                n.finishLoop()
1✔
577
                return
1✔
578
        }
1✔
579
        n.startIteration()
2✔
580
}
581

582
func (n *whileLoopNode) finishLoop() {
1✔
583
        n.active = false
1✔
584
        _ = n.setWaiting()
1✔
585
        sendToPort(n.w, n.id, n.completedOut, Trigger{})
1✔
586
        n.startQueued()
1✔
587
}
1✔
588

589
func (n *whileLoopNode) startQueued() {
1✔
590
        if n.queued < 1 || n.active {
2✔
591
                return
1✔
592
        }
1✔
NEW
593
        if !n.hasLoopLink() {
×
NEW
594
                n.clearLoopState()
×
NEW
595
                return
×
NEW
596
        }
×
NEW
597
        n.queued--
×
NEW
598
        n.startIteration()
×
599
}
600

601
func (n *whileLoopNode) clearLoopState() {
3✔
602
        n.active = false
3✔
603
        n.queued = 0
3✔
604
        _ = n.setWaiting()
3✔
605
}
3✔
606

607
func (n *whileLoopNode) hasLoopLink() bool {
1✔
608
        return n.validLoopLink() != 0
1✔
609
}
1✔
610

611
func (n *whileLoopNode) validLoopLink() uint64 {
4✔
612
        if n.loopLink != 0 {
8✔
613
                if link, ok := n.w.LinkSnapshotLocked(n.loopLink); ok && link.Type == TypeLoop {
8✔
614
                        return n.loopLink
4✔
615
                }
4✔
NEW
616
                n.loopLink = 0
×
617
        }
NEW
618
        n.loopLink = firstLoopLink(n.w, n.loopOut)
×
NEW
619
        return n.loopLink
×
620
}
621

622
func (n *whileLoopNode) setLooping() error {
3✔
623
        return n.w.SetNodeLabelLocked(n.id, loopLabelLooping)
3✔
624
}
3✔
625

626
func (n *whileLoopNode) setWaiting() error {
6✔
627
        return n.w.SetNodeLabelLocked(n.id, loopLabelWaiting)
6✔
628
}
6✔
629

630
func firstLoopLink(w *pasta.Workspace, port uint64) uint64 {
24✔
631
        if port == 0 {
24✔
NEW
632
                return 0
×
NEW
633
        }
×
634
        snapshot, ok := w.PortSnapshotLocked(port)
24✔
635
        if !ok {
24✔
NEW
636
                return 0
×
NEW
637
        }
×
638
        for _, linkID := range snapshot.Links {
24✔
NEW
639
                link, ok := w.LinkSnapshotLocked(linkID)
×
NEW
640
                if ok && link.Type == TypeLoop {
×
NEW
641
                        return linkID
×
NEW
642
                }
×
643
        }
644
        return 0
24✔
645
}
646

647
func sendToPort(w *pasta.Workspace, node, port uint64, payload any) {
64✔
648
        snapshot, ok := w.PortSnapshotLocked(port)
64✔
649
        if !ok {
64✔
NEW
650
                return
×
NEW
651
        }
×
652
        for _, link := range snapshot.Links {
128✔
653
                sendToLink(w, node, port, link, payload)
64✔
654
        }
64✔
655
}
656

657
func sendToLink(w *pasta.Workspace, node, port, link uint64, payload any) {
85✔
658
        snapshot, ok := w.LinkSnapshotLocked(link)
85✔
659
        if !ok {
85✔
NEW
660
                return
×
NEW
661
        }
×
662
        receiverNode, receiverPort := otherEndpoint(snapshot, port)
85✔
663
        w.SendEventLocked(pasta.Event{
85✔
664
                SenderNode:   node,
85✔
665
                SenderPort:   port,
85✔
666
                ReceiverNode: receiverNode,
85✔
667
                ReceiverPort: receiverPort,
85✔
668
                Payload:      payload,
85✔
669
        })
85✔
670
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc