-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathroutine.go
executable file
·407 lines (331 loc) · 9.25 KB
/
routine.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
// routine is a package for creating sequences of events, primarily for game development in Golang.
package routine
// Properties represents a kind of "local memory" for an Execution object.
type Properties map[any]any
// Init will initialize a property by the given name with the given value
// if it doesn't already exist.
func (p *Properties) Init(propName any, toValue any) {
if p.Has(propName) {
return
}
p.Set(propName, toValue)
}
// Get returns the value associated with the given property identifier.
func (p Properties) Get(propName any) any {
return p[propName]
}
// Has returns if the Properties object has a property associated with
// the given identifier.
func (p Properties) Has(propName any) bool {
_, exists := p[propName]
return exists
}
// Set sets the Properties object with the given property name to the
// value passed.
func (p *Properties) Set(propName any, value any) {
(*p)[propName] = value
}
// Clear clears the properties map.
func (p *Properties) Clear() {
for k := range *p {
delete(*p, k)
}
}
// Delete deletes a key out of the properties map.
func (p *Properties) Delete(keyName string) {
delete(*p, keyName)
}
// Flow is simply a uint8, and represents what a Routine should do following a Action's action.
type Flow uint8
const (
// FlowIdle means that the Routine should cycle and do the same Action again the following Update() cycle.
FlowIdle Flow = iota
// FlowNext means that the Routine should move on to the next Action in the Block.
// If this is returned from the last Action in a Block, the Block will loop.
FlowNext
// FlowFinish indicates the Block should finish its execution, deactivating afterwards.
FlowFinish
)
// Action is an interface that represents an object that can Action and direct the flow of a Routine.
type Action interface {
Init(block *Block) // The Init function is called when a Action is switched to.
Poll(block *Block) Flow // The Poll function is called every frame and can return a Flow, indicating what the Routine should do next.
}
// ActionCollectionable identifies an interface for an Action that allows it to return a slice of Actions to be added to Blocks, Gates, or Collections in definition.
type ActionCollectionable interface {
Actions() []Action
}
// ActionIdentifiable identifies an interface for an action that allows that Action to be used for jumping (as though it were a label).
type ActionIdentifiable interface {
ID() any
}
// Block represents a block of Actions. Blocks execute Actions in sequence, and have an ID that allows them to be
// activated or deactivated at will by their owning Routine.
type Block struct {
currentlyActive bool
active bool
currentFrame int // The current frame of the Block for the currently running Action.
ID any
Actions []Action
index int
indexChanged bool
routine *Routine
}
// SetIndex sets the index of the Action sequence of the Block to the value given.
// This effectively "sets the playhead" of the Block to point to the Action in the given
// slot.
func (b *Block) SetIndex(index int) {
if index < 0 {
index = 0
}
if index > len(b.Actions)-1 {
index = len(b.Actions) - 1
}
if b.index != index {
b.index = index
b.Actions[b.index].Init(b)
b.currentFrame = 0
if b.currentlyActive {
b.indexChanged = true
}
}
}
// JumpTo sets the Block's execution index to the index of a ActionLabel, using the label
// provided.
// If it finds the Label, then it will jump to and return that index. Otherwise, it will return -1.
func (b *Block) JumpTo(labelID any) int {
for i, c := range b.Actions {
if label, ok := c.(ActionIdentifiable); ok {
if label.ID() == labelID {
b.SetIndex(i)
return i
}
}
}
return -1
}
// Index returns the index of the currently active Action in the Block.
func (b *Block) Index() int {
return b.index
}
func (b *Block) update() {
if !b.currentlyActive {
return
}
b.indexChanged = false
p := b.Actions[b.index].Poll(b)
b.currentFrame++
switch p {
case FlowNext:
if !b.indexChanged {
b.index++
}
if b.index > len(b.Actions)-1 {
b.index = 0
b.active = false
b.currentlyActive = false
}
b.Actions[b.index].Init(b)
b.currentFrame = 0
if b.active {
b.update() // We call update again because it should move on unless it's idling, specifically
}
case FlowFinish:
b.index = 0
b.active = false // Restart if we're going to the next Action and we're at the end of the block
b.currentlyActive = false
b.Actions[b.index].Init(b)
b.currentFrame = 0
case FlowIdle:
if b.indexChanged {
b.Actions[b.index].Init(b)
b.currentFrame = 0
}
}
}
// Run runs the specified block.
func (b *Block) Run() {
b.active = true
}
// Running returns if the Block is active.
func (b *Block) Running() bool {
return b.active
}
// Pause pauses the specified block, so that it isn't active when the Routine is run. When it is run again, it resumes execution at its current action.
func (b *Block) Pause() {
b.active = false
}
// Restart restarts the block.
func (b *Block) Restart() {
b.index = -1
b.SetIndex(0)
}
// Stop stops the Block, so that it restarts when it is run again.
func (b *Block) Stop() {
b.Pause()
b.Restart()
}
// Routine returns the currently running routine.
func (b *Block) Routine() *Routine {
return b.routine
}
// CurrentFrame returns the current frame of the Block's execution of the currently executed Action.
// This increases by 1 every Routine.Update() call until the Block executes another Action.
func (b *Block) CurrentFrame() int {
return b.currentFrame
}
// Routine represents a container to run Blocks of code.
type Routine struct {
Blocks []*Block
properties *Properties
}
// New creates a new Routine.
func New() *Routine {
r := &Routine{
Blocks: []*Block{},
properties: &Properties{},
}
return r
}
// Define defines a Block using the ID given and the list of Actions provided and adds it to the Routine.
// The ID can be of any comparable type.
// Define returns the new Block as well.
// If a block with the given blockID already exists, Define will remove the previous one.
func (r *Routine) Define(id any, Actions ...Action) *Block {
newActions := []Action{}
for _, c := range Actions {
if collection, ok := c.(ActionCollectionable); ok {
newActions = append(newActions, collection.Actions()...)
} else {
newActions = append(newActions, c)
}
}
newBlock := &Block{
ID: id,
routine: r,
Actions: newActions,
}
for i, b := range r.Blocks {
if b.ID == id {
r.Blocks[i] = nil
r.Blocks = append(r.Blocks[:i], r.Blocks[i+1:]...)
}
}
r.Blocks = append(r.Blocks, newBlock)
return newBlock
}
// Properties returns the Properties object for the Routine.
func (r *Routine) Properties() *Properties {
return r.properties
}
// Update updates the Routine - this should be called once per frame.
func (r *Routine) Update() {
for _, block := range r.Blocks {
block.currentlyActive = block.active
}
for _, block := range r.Blocks {
block.update()
}
}
// Run runs Blocks with the given IDs.
// If no block IDs are given, then all blocks contained in the Routine are run.
func (r *Routine) Run(blockIDs ...any) {
if len(blockIDs) == 0 {
for _, block := range r.Blocks {
block.Run()
}
} else {
for _, label := range blockIDs {
for _, block := range r.Blocks {
if block.ID == label {
block.Run()
break
}
}
}
}
}
// Pause pauses Blocks with the given IDs.
// If no block IDs are given, then all blocks contained in the Routine are paused.
func (r *Routine) Pause(blockIDs ...any) {
if len(blockIDs) == 0 {
for _, block := range r.Blocks {
block.Pause()
}
} else {
for _, label := range blockIDs {
for _, block := range r.Blocks {
if block.ID == label {
block.Pause()
break
}
}
}
}
}
// Stop stops Blocks with the given IDs.
// If no block IDs are given, then all blocks contained in the Routine are stopped.
func (r *Routine) Stop(blockIDs ...any) {
if len(blockIDs) == 0 {
for _, block := range r.Blocks {
block.Stop()
}
} else {
for _, label := range blockIDs {
for _, block := range r.Blocks {
if block.ID == label {
block.Stop()
break
}
}
}
}
}
// Restart restarts Blocks with the given IDs.
// If no block IDs are given, then all blocks contained in the Routine are restarted.
func (r *Routine) Restart(blockIDs ...any) {
if len(blockIDs) == 0 {
for _, block := range r.Blocks {
block.Restart()
}
} else {
for _, label := range blockIDs {
for _, block := range r.Blocks {
if block.ID == label {
block.Restart()
break
}
}
}
}
}
// Running returns true if at least one Block is running with at least one of the given IDs in the Routine.
// If no IDs are given, then any running Blocks will return.
func (r *Routine) Running(ids ...any) bool {
if len(ids) == 0 {
for _, b := range r.Blocks {
if b.Running() {
return true
}
}
} else {
for _, id := range ids {
for _, b := range r.Blocks {
if b.ID == id && b.Running() {
return true
}
}
}
}
return false
}
// BlockByID returns any Block found with the given ID.
// If no Block with the given id is found, nil is returned.
func (r *Routine) BlockByID(id any) *Block {
for _, block := range r.Blocks {
if block.ID == id {
return block
}
}
return nil
}