@rbxts/planck 0.1.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/Phase.d.ts +3 -0
- package/out/Phase.luau +89 -0
- package/out/Pipeline.d.ts +4 -0
- package/out/Pipeline.luau +74 -0
- package/out/Scheduler.d.ts +3 -0
- package/out/Scheduler.luau +755 -0
- package/out/hooks.d.ts +4 -0
- package/out/hooks.luau +145 -0
- package/out/index.d.ts +12 -0
- package/out/init.luau +149 -0
- package/out/types.d.ts +150 -0
- package/out/utils.d.ts +4 -0
- package/out/utils.luau +83 -0
- package/package.json +51 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
local RunService = game:GetService("RunService")
|
|
2
|
+
|
|
3
|
+
local Pipeline = require(script.Parent:WaitForChild('Pipeline'))
|
|
4
|
+
local Phase = require(script.Parent:WaitForChild('Phase'))
|
|
5
|
+
|
|
6
|
+
local utils = require(script.Parent:WaitForChild('utils'))
|
|
7
|
+
local hooks = require(script.Parent:WaitForChild('hooks'))
|
|
8
|
+
|
|
9
|
+
local getSystem = utils.getSystem
|
|
10
|
+
local getSystemName = utils.getSystemName
|
|
11
|
+
|
|
12
|
+
local isPhase = utils.isPhase
|
|
13
|
+
local isPipeline = utils.isPipeline
|
|
14
|
+
|
|
15
|
+
local isValidEvent = utils.isValidEvent
|
|
16
|
+
local getEventIdentifier = utils.getEventIdentifier
|
|
17
|
+
|
|
18
|
+
-- Recent errors in Planks itself
|
|
19
|
+
local recentLogs = {}
|
|
20
|
+
local timeLastLogged = os.clock()
|
|
21
|
+
|
|
22
|
+
--- @type SystemFn ((U...) -> any)
|
|
23
|
+
--- @within Scheduler
|
|
24
|
+
|
|
25
|
+
--- @interface SystemTable
|
|
26
|
+
--- @within Scheduler
|
|
27
|
+
--- .system SystemFn<U...>
|
|
28
|
+
--- .phase Phase?
|
|
29
|
+
--- .[any] any
|
|
30
|
+
|
|
31
|
+
--- @type System SystemFn<U...> | SystemTable<U...>
|
|
32
|
+
--- @within Scheduler
|
|
33
|
+
|
|
34
|
+
--- @class Scheduler
|
|
35
|
+
---
|
|
36
|
+
--- An Object which handles scheduling Systems to run within different
|
|
37
|
+
--- Phases. The order of which Systems run will be defined either
|
|
38
|
+
--- implicitly by when it was added, or explicitly by tagging the system
|
|
39
|
+
--- with a Phase.
|
|
40
|
+
local Scheduler = {}
|
|
41
|
+
Scheduler.__index = Scheduler
|
|
42
|
+
|
|
43
|
+
Scheduler.Hooks = hooks.Hooks
|
|
44
|
+
|
|
45
|
+
--- @method addPlugin
|
|
46
|
+
--- @within Scheduler
|
|
47
|
+
--- @param plugin PlanckPlugin
|
|
48
|
+
---
|
|
49
|
+
--- Initializes a plugin with the scheduler, see the [Plugin Docs](/docs/plugins) for more information.
|
|
50
|
+
function Scheduler:addPlugin(plugin)
|
|
51
|
+
plugin:build(self)
|
|
52
|
+
return self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
function Scheduler:_addHook(hook, fn)
|
|
56
|
+
assert(self._hooks[hook], `Unknown Hook: {hook}`)
|
|
57
|
+
table.insert(self._hooks[hook], fn)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
-- Inspiration from https://github.com/matter-ecs/matter <3
|
|
61
|
+
function Scheduler:_handleLogs(systemInfo)
|
|
62
|
+
if not systemInfo.timeLastLogged then
|
|
63
|
+
systemInfo.timeLastLogged = os.clock()
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if not systemInfo.recentLogs then
|
|
67
|
+
systemInfo.recentLogs = {}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if os.clock() - systemInfo.timeLastLogged > 10 then
|
|
71
|
+
systemInfo.timeLastLogged = os.clock()
|
|
72
|
+
systemInfo.recentLogs = {}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
local name = debug.info(systemInfo.system, "n")
|
|
76
|
+
|
|
77
|
+
for _, logMessage in systemInfo.logs do
|
|
78
|
+
if not systemInfo.recentLogs[logMessage] then
|
|
79
|
+
task.spawn(error, logMessage, 0)
|
|
80
|
+
warn(
|
|
81
|
+
`Planck: Error occurred in system{string.len(name) > 0 and ` '{name}'` or ""}, this error will be ignored for 10 seconds`
|
|
82
|
+
)
|
|
83
|
+
systemInfo.recentLogs[logMessage] = true
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
table.clear(systemInfo.logs)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
function Scheduler:runSystem(system)
|
|
91
|
+
local runIf = self._runIfConditions[system]
|
|
92
|
+
if runIf and runIf(table.unpack(self._vargs)) == false then
|
|
93
|
+
return
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
local systemInfo = self._systemInfo[system]
|
|
97
|
+
|
|
98
|
+
if not self._thread then
|
|
99
|
+
self._thread = coroutine.create(function()
|
|
100
|
+
while true do
|
|
101
|
+
local fn = coroutine.yield()
|
|
102
|
+
self._yielded = true
|
|
103
|
+
fn()
|
|
104
|
+
self._yielded = false
|
|
105
|
+
end
|
|
106
|
+
end)
|
|
107
|
+
|
|
108
|
+
coroutine.resume(self._thread)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
local didYield = false
|
|
112
|
+
|
|
113
|
+
local function systemCall()
|
|
114
|
+
local function noYield()
|
|
115
|
+
local success, err = coroutine.resume(self._thread, function()
|
|
116
|
+
system(table.unpack(self._vargs))
|
|
117
|
+
end)
|
|
118
|
+
|
|
119
|
+
if not success then
|
|
120
|
+
didYield = true
|
|
121
|
+
table.insert(systemInfo.logs, err)
|
|
122
|
+
hooks.systemError(self, systemInfo, err)
|
|
123
|
+
return
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if self._yielded then
|
|
127
|
+
didYield = true
|
|
128
|
+
local trace, line = debug.info(systemInfo.system, "sl")
|
|
129
|
+
table.insert(systemInfo.logs, `{trace}:{line}: System yielded`)
|
|
130
|
+
hooks.systemError(
|
|
131
|
+
self,
|
|
132
|
+
systemInfo,
|
|
133
|
+
`{trace}:{line}: System yielded`
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
hooks.systemCall(self, "SystemCall", systemInfo, noYield)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
local function inner()
|
|
142
|
+
hooks.systemCall(self, "InnerSystemCall", systemInfo, systemCall)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
local function outer()
|
|
146
|
+
hooks.systemCall(self, "OuterSystemCall", systemInfo, inner)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
if os.clock() - timeLastLogged > 10 then
|
|
150
|
+
timeLastLogged = os.clock()
|
|
151
|
+
recentLogs = {}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
local success, err: string? = pcall(outer)
|
|
155
|
+
if not success and not recentLogs[err] then
|
|
156
|
+
task.spawn(error, err, 0)
|
|
157
|
+
warn(
|
|
158
|
+
`Planck: Error occurred while running hooks, this error will be ignored for 10 seconds`
|
|
159
|
+
)
|
|
160
|
+
hooks.systemError(
|
|
161
|
+
self,
|
|
162
|
+
systemInfo,
|
|
163
|
+
`Error occurred while running hooks: {err}`
|
|
164
|
+
)
|
|
165
|
+
recentLogs[err] = true
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
if didYield then
|
|
169
|
+
coroutine.close(self._thread)
|
|
170
|
+
|
|
171
|
+
self._thread = coroutine.create(function()
|
|
172
|
+
while true do
|
|
173
|
+
local fn = coroutine.yield()
|
|
174
|
+
self._yielded = true
|
|
175
|
+
fn()
|
|
176
|
+
self._yielded = false
|
|
177
|
+
end
|
|
178
|
+
end)
|
|
179
|
+
|
|
180
|
+
coroutine.resume(self._thread)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
self:_handleLogs(systemInfo)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
function Scheduler:runPhase(phase)
|
|
187
|
+
local runIf = self._runIfConditions[phase]
|
|
188
|
+
if runIf and runIf(table.unpack(self._vargs)) == false then
|
|
189
|
+
return
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
hooks.phaseBegan(self, phase)
|
|
193
|
+
|
|
194
|
+
for _, system in self._phaseToSystems[phase] do
|
|
195
|
+
self:runSystem(system)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
function Scheduler:runPipeline(pipeline)
|
|
200
|
+
local runIf = self._runIfConditions[pipeline]
|
|
201
|
+
if runIf and runIf(table.unpack(self._vargs)) == false then
|
|
202
|
+
return
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
for _, phase in pipeline._phases do
|
|
206
|
+
self:runPhase(phase)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
--- @method run
|
|
211
|
+
--- @within Scheduler
|
|
212
|
+
--- @param phase Phase
|
|
213
|
+
--- @return Scheduler
|
|
214
|
+
---
|
|
215
|
+
--- Runs all Systems tagged with the Phase in order.
|
|
216
|
+
|
|
217
|
+
--- @method run
|
|
218
|
+
--- @within Scheduler
|
|
219
|
+
--- @param pipeline Pipeline
|
|
220
|
+
--- @return Scheduler
|
|
221
|
+
---
|
|
222
|
+
--- Runs all Systems tagged with any Phase within the Pipeline in order.
|
|
223
|
+
|
|
224
|
+
--- @method run
|
|
225
|
+
--- @within Scheduler
|
|
226
|
+
--- @param system System
|
|
227
|
+
--- @return Scheduler
|
|
228
|
+
---
|
|
229
|
+
--- Runs the System, passing in the arguments of the Scheduler, `U...`.
|
|
230
|
+
|
|
231
|
+
function Scheduler:run(dependent)
|
|
232
|
+
if not dependent then
|
|
233
|
+
error("No dependent specified in Scheduler:run(_)")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
self:runPipeline(Pipeline.Startup)
|
|
237
|
+
|
|
238
|
+
if getSystem(dependent) then
|
|
239
|
+
self:runSystem(dependent)
|
|
240
|
+
elseif isPhase(dependent) then
|
|
241
|
+
self:runPhase(dependent)
|
|
242
|
+
elseif isPipeline(dependent) then
|
|
243
|
+
self:runPipeline(dependent)
|
|
244
|
+
else
|
|
245
|
+
error("Unknown dependent passed into Scheduler:run(unknown)")
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
return self
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
--- @method runAll
|
|
252
|
+
--- @within Scheduler
|
|
253
|
+
--- @return Scheduler
|
|
254
|
+
---
|
|
255
|
+
--- Runs all Systems within order.
|
|
256
|
+
function Scheduler:runAll()
|
|
257
|
+
for _, phase in self._orderedPhases do
|
|
258
|
+
self:run(phase)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
function Scheduler:_insertPhaseAt(phase, index, instance, event)
|
|
263
|
+
assert(
|
|
264
|
+
not table.find(self._orderedPhases, phase),
|
|
265
|
+
"Phase already initialized"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
table.insert(self._orderedPhases, index, phase)
|
|
269
|
+
self._phaseToSystems[phase] = {}
|
|
270
|
+
|
|
271
|
+
hooks.phaseAdd(self, phase)
|
|
272
|
+
|
|
273
|
+
if isValidEvent(instance, event) then
|
|
274
|
+
self:_schedulePhase(phase, instance, event)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
function Scheduler:insertPhase(phase, instance, event)
|
|
279
|
+
local index = table.find(self._orderedPhases, Phase.Update)
|
|
280
|
+
self:_insertPhaseAt(phase, index, instance, event)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
function Scheduler:insertPipeline(pipeline, instance, event)
|
|
284
|
+
for _, phase in pipeline._phases do
|
|
285
|
+
self:insertPhase(phase, instance, event)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
--- @method insert
|
|
290
|
+
--- @within Scheduler
|
|
291
|
+
--- @param phase Phase
|
|
292
|
+
--- @return Scheduler
|
|
293
|
+
---
|
|
294
|
+
--- Initializes the Phase within the Scheduler, ordering it implicitly.
|
|
295
|
+
|
|
296
|
+
--- @method insert
|
|
297
|
+
--- @within Scheduler
|
|
298
|
+
--- @param pipeline Pipeline
|
|
299
|
+
--- @return Scheduler
|
|
300
|
+
---
|
|
301
|
+
--- Initializes all Phases within the Pipeline within the Scheduler,
|
|
302
|
+
--- ordering the Pipeline implicitly.
|
|
303
|
+
|
|
304
|
+
--- @method insert
|
|
305
|
+
--- @within Scheduler
|
|
306
|
+
--- @param phase Phase
|
|
307
|
+
--- @param instance Instance | EventLike
|
|
308
|
+
--- @param event string | EventLike
|
|
309
|
+
--- @return Scheduler
|
|
310
|
+
---
|
|
311
|
+
--- Initializes the Phase within the Scheduler, ordering it implicitly
|
|
312
|
+
--- and scheduling it to be ran on the specified event.
|
|
313
|
+
---
|
|
314
|
+
--- ```lua
|
|
315
|
+
--- local myScheduler = Scheduler.new()
|
|
316
|
+
--- :insert(myPhase, RunService, "Heartbeat")
|
|
317
|
+
--- ```
|
|
318
|
+
|
|
319
|
+
--- @method insert
|
|
320
|
+
--- @within Scheduler
|
|
321
|
+
--- @param pipeline Pipeline
|
|
322
|
+
--- @param instance Instance | EventLike
|
|
323
|
+
--- @param event string | EventLike
|
|
324
|
+
--- @return Scheduler
|
|
325
|
+
---
|
|
326
|
+
--- Initializes all Phases within the Pipeline within the Scheduler,
|
|
327
|
+
--- ordering the Pipeline implicitly and scheduling it to be ran on
|
|
328
|
+
--- the specified event.
|
|
329
|
+
---
|
|
330
|
+
--- ```lua
|
|
331
|
+
--- local myScheduler = Scheduler.new()
|
|
332
|
+
--- :insert(myPipeline, RunService, "Heartbeat")
|
|
333
|
+
--- ```
|
|
334
|
+
|
|
335
|
+
function Scheduler:insert(dependent, instance, event)
|
|
336
|
+
if isPhase(dependent) then
|
|
337
|
+
self:insertPhase(dependent, instance, event)
|
|
338
|
+
elseif isPipeline(dependent) then
|
|
339
|
+
self:insertPipeline(dependent, instance, event)
|
|
340
|
+
else
|
|
341
|
+
error("Unknown dependent passed to Scheduler:insert(unknown, _, _)")
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
return self
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
--- @method insertAfter
|
|
348
|
+
--- @within Scheduler
|
|
349
|
+
--- @param phase Phase
|
|
350
|
+
--- @param after Phase | Pipeline
|
|
351
|
+
--- @return Scheduler
|
|
352
|
+
---
|
|
353
|
+
--- Initializes the Phase within the Scheduler, ordering it
|
|
354
|
+
--- explicitly after the Phase, or adding to the end of the
|
|
355
|
+
--- Pipeline provided.
|
|
356
|
+
|
|
357
|
+
--- @method insertAfter
|
|
358
|
+
--- @within Scheduler
|
|
359
|
+
--- @param pipeline Pipeline
|
|
360
|
+
--- @param after Phase | Pipeline
|
|
361
|
+
--- @return Scheduler
|
|
362
|
+
---
|
|
363
|
+
--- Initializes all Phases within the Pipeline within the Scheduler,
|
|
364
|
+
--- ordering the Pipeline explicitly after the Phase, or adding
|
|
365
|
+
--- to the end of the Pipeline provided.
|
|
366
|
+
|
|
367
|
+
function Scheduler:insertAfter(dependent, after)
|
|
368
|
+
if isPhase(after) then
|
|
369
|
+
local index = table.find(self._orderedPhases, after)
|
|
370
|
+
assert(
|
|
371
|
+
index,
|
|
372
|
+
"Phase never initialized in Scheduler:insertAfter(_, phase)"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
if isPhase(dependent) then
|
|
376
|
+
self:_insertPhaseAt(dependent, index + 1)
|
|
377
|
+
elseif isPipeline(dependent) then
|
|
378
|
+
for _, phase in dependent._phases do
|
|
379
|
+
index += 1
|
|
380
|
+
self:_insertPhaseAt(phase, index)
|
|
381
|
+
end
|
|
382
|
+
else
|
|
383
|
+
error(
|
|
384
|
+
"Unknown dependent passed in Scheduler:insertAfter(unknown, _)"
|
|
385
|
+
)
|
|
386
|
+
end
|
|
387
|
+
elseif isPipeline(after) then
|
|
388
|
+
local before = after._phases[#after._phases]
|
|
389
|
+
|
|
390
|
+
if isPhase(dependent) then
|
|
391
|
+
after:insert(dependent)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
self:insertAfter(dependent, before)
|
|
395
|
+
else
|
|
396
|
+
error("Unknown dependency passed in Scheduler:insertAfter(_, unknown)")
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
return self
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
--- @method addSystems
|
|
403
|
+
--- @within Scheduler
|
|
404
|
+
--- @param systems System
|
|
405
|
+
--- @param phase Phase?
|
|
406
|
+
---
|
|
407
|
+
--- Adds the System to the Scheduler, scheduling it to be ran
|
|
408
|
+
--- implicitly within the provided Phase or on the default Main phase.
|
|
409
|
+
function Scheduler:addSystem(system, phase)
|
|
410
|
+
local systemFn = getSystem(system)
|
|
411
|
+
|
|
412
|
+
if not systemFn then
|
|
413
|
+
error("Unknown system passed to Scheduler:addSystem(unknown, phase?)")
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
local systemInfo = {
|
|
417
|
+
system = systemFn,
|
|
418
|
+
phase = phase,
|
|
419
|
+
name = getSystemName(systemFn),
|
|
420
|
+
logs = {},
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if not phase then
|
|
424
|
+
if type(system) == "table" and system.phase then
|
|
425
|
+
systemInfo.phase = system.phase
|
|
426
|
+
else
|
|
427
|
+
systemInfo.phase = Phase.Update
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
self._systemInfo[systemFn] = systemInfo
|
|
432
|
+
table.insert(self._phaseToSystems[systemInfo.phase], systemFn)
|
|
433
|
+
|
|
434
|
+
hooks.systemAdd(self, systemInfo)
|
|
435
|
+
|
|
436
|
+
return self
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
--- @method addSystems
|
|
440
|
+
--- @within Scheduler
|
|
441
|
+
--- @param systems { System }
|
|
442
|
+
--- @param phase Phase?
|
|
443
|
+
---
|
|
444
|
+
--- Adds the Systems to the Scheduler, scheduling them to be ran
|
|
445
|
+
--- implicitly within the provided Phase or on the default Main phase.
|
|
446
|
+
function Scheduler:addSystems(systems, phase)
|
|
447
|
+
if type(systems) ~= "table" then
|
|
448
|
+
error("Unknown systems passed to Scheduler:addSystems(unknown, phase?)")
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
local foundSystem = false
|
|
452
|
+
|
|
453
|
+
for _, system in systems do
|
|
454
|
+
if getSystem(system) then
|
|
455
|
+
foundSystem = true
|
|
456
|
+
self:addSystem(system, phase)
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
if not foundSystem then
|
|
461
|
+
error(
|
|
462
|
+
"Unknown table passed to Scheduler:addSystems({ unknown }, phase?)"
|
|
463
|
+
)
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
--- @method editSystem
|
|
468
|
+
--- @within Scheduler
|
|
469
|
+
--- @param system System
|
|
470
|
+
--- @param newPhase Phase
|
|
471
|
+
---
|
|
472
|
+
--- Changes the Phase that this system is scheduled on.
|
|
473
|
+
function Scheduler:editSystem(system, newPhase)
|
|
474
|
+
local systemFn = getSystem(system)
|
|
475
|
+
local systemInfo = self._systemInfo[systemFn]
|
|
476
|
+
assert(
|
|
477
|
+
systemInfo,
|
|
478
|
+
"Attempt to edit a non-exist system in Scheduler:editSystem(_)"
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
assert(
|
|
482
|
+
newPhase and self._phaseToSystems[newPhase] ~= nil or true,
|
|
483
|
+
"Phase never initialized before using Scheduler:editSystem(_, Phase)"
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
local systems = self._phaseToSystems[systemInfo.phase]
|
|
487
|
+
|
|
488
|
+
local index = table.find(systems, systemFn)
|
|
489
|
+
assert(index, "Unable to find system within phase")
|
|
490
|
+
|
|
491
|
+
table.remove(systems, index)
|
|
492
|
+
table.insert(self._phaseToSystems[newPhase], systemFn)
|
|
493
|
+
|
|
494
|
+
systemInfo.phase = newPhase
|
|
495
|
+
return self
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
--- @method removeSystem
|
|
499
|
+
--- @within Scheduler
|
|
500
|
+
--- @param system System
|
|
501
|
+
---
|
|
502
|
+
--- Removes the System from the Scheduler.
|
|
503
|
+
function Scheduler:removeSystem(system)
|
|
504
|
+
local systemFn = getSystem(system)
|
|
505
|
+
local systemInfo = self._systemInfo[systemFn]
|
|
506
|
+
assert(
|
|
507
|
+
systemInfo,
|
|
508
|
+
"Attempt to remove a non-exist system in Scheduler:removeSystem(_)"
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
local systems = self._phaseToSystems[systemInfo.phase]
|
|
512
|
+
|
|
513
|
+
local index = table.find(systems, systemFn)
|
|
514
|
+
assert(index, "Unable to find system within phase")
|
|
515
|
+
|
|
516
|
+
table.remove(systems, index)
|
|
517
|
+
self._systemInfo[systemFn] = nil
|
|
518
|
+
|
|
519
|
+
hooks.systemRemove(self, systemInfo)
|
|
520
|
+
|
|
521
|
+
return self
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
--- @method replaceSystem
|
|
525
|
+
--- @within Scheduler
|
|
526
|
+
--- @param old System
|
|
527
|
+
--- @param new System
|
|
528
|
+
---
|
|
529
|
+
--- Replaces the System with a new System.
|
|
530
|
+
function Scheduler:replaceSystem(old, new)
|
|
531
|
+
local oldSystemFn = getSystem(old)
|
|
532
|
+
local oldSystemInfo = self._systemInfo[oldSystemFn]
|
|
533
|
+
assert(
|
|
534
|
+
oldSystemInfo,
|
|
535
|
+
"Attempt to replace a non-existent system in Scheduler:replaceSystem(unknown, _)"
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
local newSystemFn = getSystem(new)
|
|
539
|
+
assert(
|
|
540
|
+
newSystemFn,
|
|
541
|
+
"Attempt to pass non-system in Scheduler:replaceSystem(_, unknown)"
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
local systems = self._phaseToSystems[oldSystemInfo.phase]
|
|
545
|
+
|
|
546
|
+
local index = table.find(systems, oldSystemFn)
|
|
547
|
+
assert(index, "Unable to find system within phase")
|
|
548
|
+
|
|
549
|
+
table.remove(systems, index)
|
|
550
|
+
table.insert(systems, index, newSystemFn)
|
|
551
|
+
|
|
552
|
+
local copy = table.clone(oldSystemInfo)
|
|
553
|
+
|
|
554
|
+
oldSystemInfo.system = newSystemFn
|
|
555
|
+
oldSystemInfo.name = getSystemName(newSystemFn)
|
|
556
|
+
|
|
557
|
+
hooks.systemReplace(self, copy, oldSystemInfo)
|
|
558
|
+
|
|
559
|
+
self._systemInfo[newSystemFn] = self._systemInfo[oldSystemFn]
|
|
560
|
+
self._systemInfo[oldSystemFn] = nil
|
|
561
|
+
|
|
562
|
+
return self
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
--- @method setRunCondition
|
|
566
|
+
--- @within Scheduler
|
|
567
|
+
--- @param system System
|
|
568
|
+
--- @param fn (U...) -> boolean
|
|
569
|
+
---
|
|
570
|
+
--- Adds a Run Condition which the Scheduler will check before
|
|
571
|
+
--- this System is ran.
|
|
572
|
+
|
|
573
|
+
--- @method setRunCondition
|
|
574
|
+
--- @within Scheduler
|
|
575
|
+
--- @param phase Phase
|
|
576
|
+
--- @param fn (U...) -> boolean
|
|
577
|
+
---
|
|
578
|
+
--- Adds a Run Condition which the Scheduler will check before
|
|
579
|
+
--- any Systems tagged with this Phase are ran.
|
|
580
|
+
|
|
581
|
+
--- @method setRunCondition
|
|
582
|
+
--- @within Scheduler
|
|
583
|
+
--- @param pipeline Pipeline
|
|
584
|
+
--- @param fn (U...) -> boolean
|
|
585
|
+
---
|
|
586
|
+
--- Adds a Run Condition which the Scheduler will check before
|
|
587
|
+
--- any Systems within any Phases apart of this Pipeline are ran.\
|
|
588
|
+
--- \
|
|
589
|
+
--- This Run Condition will be applied to the Phases themselves.
|
|
590
|
+
|
|
591
|
+
function Scheduler:setRunCondition(dependent, fn)
|
|
592
|
+
local system = getSystem(dependent)
|
|
593
|
+
if system then
|
|
594
|
+
dependent = system
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
assert(
|
|
598
|
+
system or isPhase(dependent) or isPipeline(dependent),
|
|
599
|
+
"Attempt to pass unknown dependent into Scheduler:setRunCondition(unknown, _)"
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
self._runIfConditions[dependent] = fn
|
|
603
|
+
return self
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
function Scheduler:_addBuiltins()
|
|
607
|
+
local i = 0
|
|
608
|
+
|
|
609
|
+
for _, phase in Pipeline.Startup._phases do
|
|
610
|
+
i += 1
|
|
611
|
+
self:_insertPhaseAt(phase, i)
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
for _, phase in Pipeline.Main._phases do
|
|
615
|
+
i += 1
|
|
616
|
+
self:_insertPhaseAt(phase, i, RunService, "Heartbeat")
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
local runServiceEvents = {
|
|
620
|
+
"PreRender",
|
|
621
|
+
"PreAnimation",
|
|
622
|
+
"PreSimulation",
|
|
623
|
+
"PostSimulation",
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
for _, event in runServiceEvents do
|
|
627
|
+
i += 1
|
|
628
|
+
self:_insertPhaseAt(Phase[event], i, RunService, event)
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
local startupHasRan = {}
|
|
632
|
+
|
|
633
|
+
self:setRunCondition(Pipeline.Startup, function()
|
|
634
|
+
local phases = Pipeline.Startup._phases
|
|
635
|
+
local hasRan = startupHasRan[phases[#phases] ]
|
|
636
|
+
|
|
637
|
+
if not hasRan then
|
|
638
|
+
startupHasRan[phases[#phases] ] = true
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
return not hasRan
|
|
642
|
+
end)
|
|
643
|
+
|
|
644
|
+
for _, phase in Pipeline.Startup._phases do
|
|
645
|
+
self:setRunCondition(phase, function()
|
|
646
|
+
local hasRan = startupHasRan[phase]
|
|
647
|
+
|
|
648
|
+
if not hasRan then
|
|
649
|
+
startupHasRan[phase] = true
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
return not hasRan
|
|
653
|
+
end)
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
local EVENT_CONNECT_METHODS = { "Connect", "On", "on", "connect" }
|
|
658
|
+
|
|
659
|
+
-- This is a modified function from Matter by evaera (https://github.com/evaera)
|
|
660
|
+
-- License: Copyright (c) 2021 Eryn L. K., MIT License
|
|
661
|
+
-- Source: https://github.com/matter-ecs/matter/blob/main/lib/hooks/useEvent.luau
|
|
662
|
+
function Scheduler:_scheduleEvent(instance, event)
|
|
663
|
+
local identifier = getEventIdentifier(instance, event)
|
|
664
|
+
|
|
665
|
+
local callback = function()
|
|
666
|
+
for _, phase in self._eventToPhases[identifier] do
|
|
667
|
+
self:runPhase(phase)
|
|
668
|
+
end
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
local eventInstance = instance
|
|
672
|
+
|
|
673
|
+
if typeof(event) == "RBXScriptSignal" or type(event) == "table" then
|
|
674
|
+
eventInstance = event
|
|
675
|
+
elseif type(event) == "string" then
|
|
676
|
+
eventInstance = instance[event]
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
if type(eventInstance) == "function" then
|
|
680
|
+
self._connectedEvents[identifier] = eventInstance
|
|
681
|
+
|
|
682
|
+
return eventInstance(eventInstance, callback)
|
|
683
|
+
elseif typeof(eventInstance) == "RBXScriptSignal" then
|
|
684
|
+
self._connectedEvents[identifier] = eventInstance
|
|
685
|
+
|
|
686
|
+
return eventInstance:Connect(callback)
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
if type(eventInstance) == "table" then
|
|
690
|
+
for _, method in EVENT_CONNECT_METHODS do
|
|
691
|
+
if type(eventInstance[method]) ~= "function" then
|
|
692
|
+
continue
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
self._connectedEvents[identifier] = eventInstance
|
|
696
|
+
return eventInstance[method](eventInstance, callback)
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
error(
|
|
701
|
+
"Couldn't connect to event as no valid connect methods were found! Ensure the passed event has a 'Connect' or an 'on' method!"
|
|
702
|
+
)
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
function Scheduler:_schedulePhase(phase, instance, event)
|
|
706
|
+
local identifier = getEventIdentifier(instance, event)
|
|
707
|
+
|
|
708
|
+
if not self._eventToPhases[identifier] then
|
|
709
|
+
self._eventToPhases[identifier] = {}
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
table.insert(self._eventToPhases[identifier], phase)
|
|
713
|
+
if not self._connectedEvents[identifier] then
|
|
714
|
+
self:_scheduleEvent(instance, event)
|
|
715
|
+
end
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
--- @function new
|
|
719
|
+
--- @within Scheduler
|
|
720
|
+
--- @param args U...
|
|
721
|
+
---
|
|
722
|
+
--- Creates a new Scheduler, the args passed will be passed to
|
|
723
|
+
--- any System anytime it is ran by the Scheduler.
|
|
724
|
+
function Scheduler.new(...)
|
|
725
|
+
local self = {}
|
|
726
|
+
|
|
727
|
+
self._hooks = {}
|
|
728
|
+
|
|
729
|
+
self._vargs = { ... }
|
|
730
|
+
|
|
731
|
+
self._orderedPhases = {}
|
|
732
|
+
self._orderedPipelines = {}
|
|
733
|
+
|
|
734
|
+
self._eventToPhases = {}
|
|
735
|
+
self._connectedEvents = {}
|
|
736
|
+
|
|
737
|
+
self._phaseToSystems = {}
|
|
738
|
+
self._systemInfo = {}
|
|
739
|
+
|
|
740
|
+
self._runIfConditions = {}
|
|
741
|
+
|
|
742
|
+
setmetatable(self, Scheduler)
|
|
743
|
+
|
|
744
|
+
for _, hookName in hooks.Hooks do
|
|
745
|
+
if not self._hooks[hookName] then
|
|
746
|
+
self._hooks[hookName] = {}
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
self:_addBuiltins()
|
|
751
|
+
|
|
752
|
+
return self
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
return Scheduler
|