@rbxts/planck 0.3.0-alpha.2 → 0.3.0-rc.1

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.
@@ -1,48 +1,86 @@
1
- --!nonstrict
2
1
  local DependencyGraph = require(script.Parent.DependencyGraph)
3
2
  local Pipeline = require(script.Parent.Pipeline)
4
3
  local Phase = require(script.Parent.Phase)
5
4
 
5
+ type DependencyGraph<T> = DependencyGraph.DependencyGraph<T>
6
+ type Pipeline = Pipeline.Pipeline
7
+ type Phase = Phase.Phase
8
+
6
9
  local utils = require(script.Parent.utils)
7
- local hooks = require(script.Parent.hooks)
8
10
  local conditions = require(script.Parent.conditions)
9
11
 
10
12
  local getSystem = utils.getSystem
11
13
  local getSystemName = utils.getSystemName
14
+ local getEventIdentifier = utils.getEventIdentifier
12
15
 
16
+ local isSystem = utils.isSystem
13
17
  local isPhase = utils.isPhase
14
18
  local isPipeline = utils.isPipeline
15
-
16
19
  local isValidEvent = utils.isValidEvent
17
- local getEventIdentifier = utils.getEventIdentifier
20
+
21
+ type GenericTable = utils.GenericTable
22
+ type SignalLike<U...> = utils.SignalLike<U...>
23
+ type ConnectionLike = utils.ConnectionLike
24
+ type ConnectFn<T, U...> = utils.ConnectFn<T, U...>
25
+ type Callback<U...> = utils.Callback<U...>
26
+
27
+ export type SystemFn<U...> = utils.SystemFn<U...>
28
+ export type InitializerSystemFn<U...> = utils.InitializerSystemFn<U...>
29
+ export type InitializerResult<U...> = utils.InitializerResult<U...>
30
+
31
+ type InternalSystem<U...> = utils.InternalSystem<U...>
32
+ export type System<U...> = utils.System<U...>
33
+ export type SystemTable<U...> = utils.SystemTable<U...>
34
+
35
+ type Condition<U...> = conditions.Condition<U...>
36
+
37
+ type Vec<T> = { T }
38
+ type Map<K, V> = { [K]: V }
39
+
40
+ type BasePlugin<T> = {
41
+ build: (self: T, scheduler: Scheduler<...unknown>) -> (),
42
+ new: (...any) -> T,
43
+ }
44
+
45
+ type PluginImpl = {
46
+ __index: PluginImpl,
47
+ build: (self: Plugin, scheduler: Scheduler<...unknown>) -> (),
48
+ cleanup: ((self: Plugin) -> ())?,
49
+ new: (...any) -> Plugin,
50
+ }
51
+
52
+ type Plugin = setmetatable<{}, PluginImpl>
53
+
54
+ type SystemLog<E> = {
55
+ -- An Error object or message
56
+ e: E,
57
+ -- The string that will be printed
58
+ log: string,
59
+ -- The traceback as reported by the Scheduler
60
+ trace: string,
61
+ }
62
+
63
+ export type SystemInfo<U...> = {
64
+ system: InternalSystem<U...>,
65
+ run: InternalSystem<U...>,
66
+ cleanup: SystemFn<U...>?,
67
+ initialized: boolean,
68
+ name: string,
69
+ deltaTime: number?,
70
+ lastTime: number?,
71
+ timeLastLogged: number?,
72
+ recentLogs: { [string]: boolean }?,
73
+ logs: { SystemLog<unknown> },
74
+ phase: Phase,
75
+ }
76
+
77
+ type Dependent<U...> = Pipeline | Phase | System<U...>
78
+ type Dependency = Pipeline | Phase
18
79
 
19
80
  -- Recent errors in Planks itself
20
81
  local recentLogs = {}
21
82
  local timeLastLogged = os.clock()
22
83
 
23
- --- @type SystemFn ((U...) -> ())
24
- --- @within Scheduler
25
- --- Standard system function that runs every time it's scheduled
26
-
27
- --- @type InitializerSystemFn ((U...) -> (SystemFn<U...> | (SystemFn<U...>, CleanupFn)))
28
- --- @within Scheduler
29
- --- Initializer system that returns the runtime function, optionally with cleanup
30
-
31
- --- @type CleanupFn (() -> ())
32
- --- @within Scheduler
33
- --- Cleanup function called when system is removed
34
-
35
- --- @interface SystemTable
36
- --- @within Scheduler
37
- --- .system SystemFn<U...> | InitializerSystemFn<U...>
38
- --- .phase Phase?
39
- --- .name string?
40
- --- .runConditions {RunCondition}?
41
- --- .[any] any
42
-
43
- --- @type System SystemFn<U...> | SystemTable<U...>
44
- --- @within Scheduler
45
-
46
84
  --- @class Scheduler
47
85
  ---
48
86
  --- An Object which handles scheduling Systems to run within different
@@ -52,22 +90,270 @@ local timeLastLogged = os.clock()
52
90
  local Scheduler = {}
53
91
  Scheduler.__index = Scheduler
54
92
 
55
- Scheduler.Hooks = hooks.Hooks
56
-
57
93
  --- @method addPlugin
58
94
  --- @within Scheduler
59
95
  --- @param plugin PlanckPlugin
60
96
  ---
61
97
  --- Initializes a plugin with the scheduler, see the [Plugin Docs](/docs/plugins) for more information.
62
- function Scheduler:addPlugin(plugin)
63
- plugin:build(self)
64
- table.insert(self._plugins, plugin)
98
+ function Scheduler.addPlugin<T, U...>(
99
+ self: Scheduler<U...>,
100
+ plugin: setmetatable<{}, BasePlugin<T>>
101
+ )
102
+ local _plugin = plugin :: Plugin
103
+ _plugin:build(self)
104
+
105
+ table.insert(self._plugins, _plugin)
65
106
  return self
66
107
  end
67
108
 
68
- function Scheduler:_addHook(hook, fn)
69
- assert(self._hooks[hook], `Unknown Hook: {hook}`)
70
- table.insert(self._hooks[hook], fn)
109
+ type PartialHookContext = {
110
+ scheduler: Scheduler<...unknown>,
111
+ }
112
+
113
+ type Hook<T> = {
114
+ __T: T,
115
+ }
116
+
117
+ type Hooks = {
118
+ SystemAdd: Hook<(context: SystemHookContext) -> ()>,
119
+ SystemRemove: Hook<(context: SystemHookContext) -> ()>,
120
+ SystemReplace: Hook<(context: SystemReplaceContext) -> ()>,
121
+ SystemEdited: Hook<(context: SystemEditedContext) -> ()>,
122
+ SystemCleanup: Hook<(context: SystemErrorContext) -> ()>,
123
+ SystemError: Hook<(context: SystemErrorContext) -> ()>,
124
+ SystemTriedRun: Hook<(context: SystemHookContext) -> ()>,
125
+
126
+ OuterSystemCall: Hook<(context: SystemCallContext) -> SystemCallHookFn>,
127
+ InnerSystemCall: Hook<(context: SystemCallContext) -> SystemCallHookFn>,
128
+ SystemCall: Hook<(context: SystemCallContext) -> SystemCallHookFn>,
129
+
130
+ PhaseAdd: Hook<(context: PhaseContext) -> ()>,
131
+ PhaseBegan: Hook<(context: PhaseContext) -> ()>,
132
+ }
133
+
134
+ --- @prop Hooks { [string]: string }
135
+ --- @within Scheduler
136
+ --- @since 0.3.0
137
+ ---
138
+ --- See [creating plugins](/docs/plugins/creating).
139
+ Scheduler.Hooks = (
140
+ {
141
+ SystemAdd = "SystemAdd",
142
+ SystemRemove = "SystemRemove",
143
+ SystemReplace = "SystemReplace",
144
+ SystemEdited = "SystemEdited",
145
+ SystemCleanup = "SystemCleanup",
146
+ SystemError = "SystemError",
147
+ SystemTriedRun = "SystemTriedRun",
148
+
149
+ OuterSystemCall = "OuterSystemCall",
150
+ InnerSystemCall = "InnerSystemCall",
151
+ SystemCall = "SystemCall",
152
+
153
+ PhaseAdd = "PhaseAdd",
154
+ PhaseBegan = "PhaseBegan",
155
+ } :: any
156
+ ) :: Hooks
157
+
158
+ type HookId = keyof<typeof(Scheduler.Hooks)>
159
+
160
+ --- @within Scheduler
161
+ --- @since 0.3.0
162
+ ---
163
+ --- Allows for registering hooks, see [creating plugins](/docs/plugins/creating).
164
+ Scheduler.addHook = function<T, U...>(self: Scheduler<U...>, hook: Hook<T>, fn: T)
165
+ local hookId: HookId = hook :: any
166
+
167
+ assert(self._hooks[hookId], `Unknown Hook: {hook}`)
168
+ table.insert(self._hooks[hookId], fn)
169
+ end
170
+
171
+ --- @method _addHook
172
+ --- @within Scheduler
173
+ --- @deprecated 0.3.0 -- Use `Scheduler:addHook()` instead
174
+ --- @private
175
+ ---
176
+ --- Internal method for adding hooks maintained for backwards compatibility.
177
+ --- Use [Scheduler:addHook] instead.
178
+ Scheduler._addHook = Scheduler.addHook
179
+
180
+ local function callHooks<U...>(
181
+ hooks: Vec<(PartialHookContext) -> unknown>,
182
+ info: PartialHookContext
183
+ )
184
+ for _, hook in hooks do
185
+ local success, err = pcall(hook, info)
186
+ if not success then
187
+ warn("Unexpected error in hook:", err)
188
+ end
189
+ end
190
+ end
191
+
192
+ export type SystemHookContext = PartialHookContext & {
193
+ system: SystemInfo<...any>,
194
+ }
195
+
196
+ function Scheduler._systemAdd<U...>(
197
+ self: Scheduler<U...>,
198
+ systemInfo: SystemInfo<U...>
199
+ )
200
+ local context = {
201
+ scheduler = self,
202
+ system = systemInfo,
203
+ }
204
+
205
+ callHooks(self._hooks["SystemAdd"], context)
206
+ end
207
+
208
+ function Scheduler._systemRemove<U...>(
209
+ self: Scheduler<U...>,
210
+ systemInfo: SystemInfo<U...>
211
+ )
212
+ local context: SystemHookContext = {
213
+ scheduler = self,
214
+ system = systemInfo :: any,
215
+ }
216
+
217
+ callHooks(self._hooks["SystemRemove"], context)
218
+ end
219
+
220
+ export type SystemReplaceContext = PartialHookContext & {
221
+ new: SystemInfo<...any>,
222
+ old: SystemInfo<...any>,
223
+ }
224
+
225
+ function Scheduler._systemReplace<U...>(
226
+ self: Scheduler<U...>,
227
+ oldSystemInfo: SystemInfo<U...>,
228
+ newSystemInfo: SystemInfo<U...>
229
+ )
230
+ local context: SystemReplaceContext = {
231
+ scheduler = self,
232
+ new = newSystemInfo :: any,
233
+ old = oldSystemInfo :: any,
234
+ }
235
+
236
+ callHooks(self._hooks["SystemReplace"], context)
237
+ end
238
+
239
+ export type SystemEditedContext = SystemHookContext & {
240
+ old: Phase,
241
+ new: Phase,
242
+ }
243
+
244
+ function Scheduler._systemEdited<U...>(
245
+ self: Scheduler<U...>,
246
+ systemInfo: SystemInfo<U...>,
247
+ old: Phase,
248
+ new: Phase
249
+ )
250
+ local context: SystemEditedContext = {
251
+ scheduler = self,
252
+ system = systemInfo :: any,
253
+ new = new,
254
+ old = old,
255
+ }
256
+
257
+ callHooks(self._hooks["SystemEdited"], context)
258
+ end
259
+
260
+ export type SystemErrorContext = PartialHookContext & {
261
+ system: SystemInfo<...any>,
262
+ error: SystemLog<unknown>?,
263
+ }
264
+
265
+ function Scheduler._systemCleanup<E, U...>(
266
+ self: Scheduler<U...>,
267
+ systemInfo,
268
+ cleanupError: SystemLog<E>?
269
+ )
270
+ local context: SystemErrorContext = {
271
+ scheduler = self,
272
+ system = systemInfo,
273
+ error = cleanupError,
274
+ }
275
+
276
+ callHooks(self._hooks["SystemCleanup"], context)
277
+ end
278
+
279
+ function Scheduler._systemError<E, U...>(
280
+ self: Scheduler<U...>,
281
+ systemInfo,
282
+ err: SystemLog<E>
283
+ )
284
+ local context: SystemErrorContext = {
285
+ scheduler = self,
286
+ system = systemInfo,
287
+ error = err,
288
+ }
289
+
290
+ callHooks(self._hooks["SystemError"], context)
291
+ end
292
+
293
+ function Scheduler._systemTriedRun<T, U...>(self: Scheduler<U...>, systemInfo)
294
+ local context: SystemHookContext = {
295
+ scheduler = self,
296
+ system = systemInfo,
297
+ }
298
+
299
+ callHooks(self._hooks["SystemTriedRun"], context)
300
+ end
301
+
302
+ type SystemHookId = "OuterSystemCall" | "InnerSystemCall" | "SystemCall"
303
+
304
+ type SystemCallHookFn = () -> ()
305
+ type SystemCallCallback = (SystemCallContext) -> SystemCallHookFn
306
+ export type SystemCallContext = PartialHookContext & {
307
+ system: SystemInfo<...any>,
308
+ nextFn: SystemCallHookFn,
309
+ }
310
+
311
+ function Scheduler._systemCall<T, U...>(
312
+ self: Scheduler<U...>,
313
+ hookName: SystemHookId,
314
+ systemInfo: SystemInfo<U...>,
315
+ nextFn: SystemCallHookFn
316
+ )
317
+ for _, hook in self._hooks[hookName] :: Vec<SystemCallCallback> do
318
+ local context: SystemCallContext = {
319
+ scheduler = self,
320
+ system = systemInfo :: any,
321
+ nextFn = nextFn,
322
+ }
323
+
324
+ nextFn = hook(context)
325
+
326
+ if not nextFn then
327
+ local source, line = debug.info(hook, "sl")
328
+ warn(
329
+ `{source}:{line}: Expected 'SystemCall' hook to return a function`
330
+ )
331
+ end
332
+ end
333
+
334
+ nextFn()
335
+ end
336
+
337
+ export type PhaseContext = PartialHookContext & {
338
+ phase: Phase,
339
+ }
340
+
341
+ function Scheduler._phaseAdd<U...>(self: Scheduler<U...>, phase: Phase)
342
+ local context: PhaseContext = {
343
+ scheduler = self,
344
+ phase = phase,
345
+ }
346
+
347
+ callHooks(self._hooks["PhaseAdd"], context)
348
+ end
349
+
350
+ function Scheduler._phaseBegan<U...>(self: Scheduler<U...>, phase: Phase)
351
+ local context: PhaseContext = {
352
+ scheduler = self,
353
+ phase = phase,
354
+ }
355
+
356
+ callHooks(self._hooks["PhaseBegan"], context)
71
357
  end
72
358
 
73
359
  --- @method getDeltaTime
@@ -76,7 +362,7 @@ end
76
362
  ---
77
363
  --- Returns the time since the system was ran last.
78
364
  --- This must be used within a registered system.
79
- function Scheduler:getDeltaTime()
365
+ function Scheduler.getDeltaTime<U...>(self: Scheduler<U...>)
80
366
  if self._currentSystem then
81
367
  return self._currentSystem.deltaTime or 0
82
368
  end
@@ -91,8 +377,35 @@ function Scheduler:getDeltaTime()
91
377
  return self._systemInfo[systemFn].deltaTime or 0
92
378
  end
93
379
 
380
+ function Scheduler._reportLog<E, U...>(
381
+ _: Scheduler<U...>,
382
+ systemInfo: SystemInfo<U...>,
383
+ log: SystemLog<E>,
384
+ -- A warning after the error providing context
385
+ context: string
386
+ )
387
+ local recentLogs = systemInfo.recentLogs
388
+ local logMessage = log.log
389
+
390
+ assert(recentLogs)
391
+
392
+ if not recentLogs[logMessage] then
393
+ -- LUAU FUTURE: Inference bug
394
+ task.spawn<<(unknown, number)>>(error, logMessage, 0)
395
+ warn(
396
+ `Planck: {context},`
397
+ .. " "
398
+ .. `this error will be ignored for 10 seconds`
399
+ )
400
+ recentLogs[logMessage] = true
401
+ end
402
+ end
403
+
94
404
  -- Inspiration from https://github.com/matter-ecs/matter <3
95
- function Scheduler:_handleLogs(systemInfo)
405
+ function Scheduler._handleLogs<U...>(
406
+ self: Scheduler<U...>,
407
+ systemInfo: SystemInfo<U...>
408
+ )
96
409
  if not systemInfo.timeLastLogged then
97
410
  systemInfo.timeLastLogged = os.clock()
98
411
  end
@@ -101,6 +414,12 @@ function Scheduler:_handleLogs(systemInfo)
101
414
  systemInfo.recentLogs = {}
102
415
  end
103
416
 
417
+ -- selene: allow(incorrect_standard_library_use)
418
+ do
419
+ assert(systemInfo.timeLastLogged)
420
+ assert(systemInfo.recentLogs)
421
+ end
422
+
104
423
  if os.clock() - systemInfo.timeLastLogged > 10 then
105
424
  systemInfo.timeLastLogged = os.clock()
106
425
  systemInfo.recentLogs = {}
@@ -108,20 +427,22 @@ function Scheduler:_handleLogs(systemInfo)
108
427
 
109
428
  local name = systemInfo.name
110
429
 
111
- for _, logMessage in systemInfo.logs do
112
- if not systemInfo.recentLogs[logMessage] then
113
- task.spawn(error, logMessage, 0)
114
- warn(
115
- `Planck: Error occurred in system{string.len(name) > 0 and ` '{name}'` or ""}, this error will be ignored for 10 seconds`
116
- )
117
- systemInfo.recentLogs[logMessage] = true
118
- end
430
+ for _, log in systemInfo.logs do
431
+ self:_reportLog(
432
+ systemInfo,
433
+ log,
434
+ `Error occurred in system{string.len(name) > 0 and ` '{name}'` or ""}`
435
+ )
119
436
  end
120
437
 
121
438
  table.clear(systemInfo.logs)
122
439
  end
123
440
 
124
- function Scheduler:runSystem(system, justInitialized)
441
+ function Scheduler.runSystem<U...>(
442
+ self: Scheduler<U...>,
443
+ system: InternalSystem<U...>,
444
+ justInitialized: boolean
445
+ )
125
446
  local systemInfo = self._systemInfo[system]
126
447
  local now = os.clock()
127
448
 
@@ -133,6 +454,7 @@ function Scheduler:runSystem(system, justInitialized)
133
454
 
134
455
  if justInitialized ~= true then
135
456
  if self:_canRun(system) == false then
457
+ self:_systemTriedRun(systemInfo)
136
458
  return
137
459
  end
138
460
 
@@ -155,40 +477,54 @@ function Scheduler:runSystem(system, justInitialized)
155
477
  coroutine.resume(self._thread)
156
478
  end
157
479
 
480
+ -- selene: allow(incorrect_standard_library_use)
481
+ assert(self._thread)
482
+
158
483
  local didYield = false
159
484
  local hasSystem = false
160
485
 
161
486
  local function systemCall()
162
487
  local function noYield()
163
488
  local success, errOrSys, cleanup
489
+
164
490
  coroutine.resume(self._thread, function()
165
491
  success, errOrSys, cleanup = xpcall(function()
166
492
  return systemInfo.run(table.unpack(self._vargs))
167
493
  end, function(e)
168
- return debug.traceback(e)
494
+ return {
495
+ e = e,
496
+ log = debug.traceback(tostring(e)),
497
+ trace = debug.traceback(),
498
+ }
169
499
  end)
170
500
  end)
171
501
 
172
502
  if success == false then
173
503
  didYield = true
504
+
505
+ -- selene: allow(incorrect_standard_library_use)
506
+ assert(errOrSys)
174
507
  table.insert(systemInfo.logs, errOrSys)
175
- hooks.systemError(self, systemInfo, errOrSys)
508
+ self:_systemError(systemInfo, errOrSys)
509
+
176
510
  return
177
511
  end
178
512
 
179
513
  if self._yielded then
180
514
  didYield = true
515
+
181
516
  local source, line = debug.info(self._thread, 1, "sl")
182
517
  local errMessage = `{source}:{line}: System yielded`
183
- table.insert(
184
- systemInfo.logs,
185
- debug.traceback(self._thread, errMessage, 2)
186
- )
187
- hooks.systemError(
188
- self,
189
- systemInfo,
190
- debug.traceback(self._thread, errMessage, 2)
191
- )
518
+ local err = debug.traceback(self._thread, errMessage, 2)
519
+
520
+ local log = {
521
+ e = err,
522
+ log = err,
523
+ trace = debug.traceback(self._thread, nil, 2),
524
+ }
525
+
526
+ table.insert(systemInfo.logs, log :: SystemLog<unknown>)
527
+ self:_systemError(systemInfo, log)
192
528
  return
193
529
  end
194
530
 
@@ -227,29 +563,32 @@ function Scheduler:runSystem(system, justInitialized)
227
563
  end
228
564
  end
229
565
 
230
- local err = string.format(
231
- "System '%s' initializer returned invalid type. "
232
- .. "Expected: function, {system?, cleanup?}, or (function, function). "
233
- .. "Got: %s, %s",
234
- systemInfo.name,
235
- type(errOrSys),
236
- type(cleanup)
237
- )
238
- table.insert(systemInfo.logs, err)
239
- hooks.systemError(self, systemInfo, err)
566
+ local errMessage = `System '{systemInfo.name}' initializer returned invalid type. `
567
+ .. "Expected: function, {system?, cleanup?}, or (function, function). "
568
+ .. `Got: {typeof(errOrSys)}, {typeof(cleanup)}`
569
+
570
+ local err = debug.traceback(errMessage)
571
+ local log = {
572
+ e = err,
573
+ log = err,
574
+ trace = debug.traceback(),
575
+ }
576
+
577
+ table.insert(systemInfo.logs, log :: SystemLog<unknown>)
578
+ self:_systemError(systemInfo, log)
240
579
  systemInfo.initialized = true
241
580
  end
242
581
  end
243
582
 
244
- hooks.systemCall(self, "SystemCall", systemInfo, noYield)
583
+ self:_systemCall("SystemCall", systemInfo, noYield)
245
584
  end
246
585
 
247
586
  local function inner()
248
- hooks.systemCall(self, "InnerSystemCall", systemInfo, systemCall)
587
+ self:_systemCall("InnerSystemCall", systemInfo, systemCall)
249
588
  end
250
589
 
251
590
  local function outer()
252
- hooks.systemCall(self, "OuterSystemCall", systemInfo, inner)
591
+ self:_systemCall("OuterSystemCall", systemInfo, inner)
253
592
  end
254
593
 
255
594
  if os.clock() - timeLastLogged > 10 then
@@ -257,18 +596,18 @@ function Scheduler:runSystem(system, justInitialized)
257
596
  recentLogs = {}
258
597
  end
259
598
 
260
- local success, err: string? = pcall(outer)
261
- if not success and not recentLogs[err] then
262
- task.spawn(error, err, 0)
263
- warn(
264
- `Planck: Error occurred while running hooks, this error will be ignored for 10 seconds`
265
- )
266
- hooks.systemError(
267
- self,
268
- systemInfo,
269
- `Error occurred while running hooks: {err}`
270
- )
271
- recentLogs[err] = true
599
+ -- LUAU FUTURE: Better types for pcalls
600
+ local success, err = xpcall(outer :: any, function(e)
601
+ return {
602
+ e = e,
603
+ log = debug.traceback(`Error occurred while running hooks: {e}`),
604
+ trace = debug.traceback(),
605
+ }
606
+ end)
607
+
608
+ if not success then
609
+ self:_systemError(systemInfo, err)
610
+ self:_reportLog(systemInfo, err, `Error occurred while running hooks`)
272
611
  end
273
612
 
274
613
  if didYield then
@@ -294,23 +633,23 @@ function Scheduler:runSystem(system, justInitialized)
294
633
  end
295
634
  end
296
635
 
297
- function Scheduler:runPhase(phase)
636
+ function Scheduler.runPhase<U...>(self: Scheduler<U...>, phase: Phase)
298
637
  if self:_canRun(phase) == false then
299
638
  return
300
639
  end
301
640
 
302
- hooks.phaseBegan(self, phase)
641
+ self:_phaseBegan(phase)
303
642
 
304
643
  if not self._phaseToSystems[phase] then
305
644
  self._phaseToSystems[phase] = {}
306
645
  end
307
646
 
308
647
  for _, system in self._phaseToSystems[phase] do
309
- self:runSystem(system)
648
+ self:runSystem(system, false)
310
649
  end
311
650
  end
312
651
 
313
- function Scheduler:runPipeline(pipeline)
652
+ function Scheduler.runPipeline<U...>(self: Scheduler<U...>, pipeline: Pipeline)
314
653
  if self:_canRun(pipeline) == false then
315
654
  return
316
655
  end
@@ -326,12 +665,12 @@ function Scheduler:runPipeline(pipeline)
326
665
  end
327
666
  end
328
667
 
329
- function Scheduler:_canRun(dependent)
330
- local conditions = self._runIfConditions[dependent]
668
+ function Scheduler._canRun<U...>(self: Scheduler<U...>, dependent: Dependent<U...>)
669
+ local runConditions = self._runIfConditions[dependent :: any]
331
670
 
332
- if conditions then
333
- for _, runIf in conditions do
334
- if runIf(table.unpack(self._vargs)) == false then
671
+ if runConditions then
672
+ for _, runIf in runConditions do
673
+ if runIf and not runIf(table.unpack(self._vargs)) then
335
674
  return false
336
675
  end
337
676
  end
@@ -360,19 +699,20 @@ end
360
699
  --- @return Scheduler
361
700
  ---
362
701
  --- Runs the System, passing in the arguments of the Scheduler, `U...`.
363
- function Scheduler:run(dependent)
364
- if not dependent then
365
- error("No dependent specified in Scheduler:run(_)")
366
- end
702
+ function Scheduler.run<U...>(self: Scheduler<U...>, dependent: Dependent<U...>)
703
+ assert(
704
+ dependent ~= nil,
705
+ "No dependent specified in Scheduler:run(dependent?)"
706
+ )
367
707
 
368
708
  self:runPipeline(Pipeline.Startup)
369
709
 
370
- if getSystem(dependent) then
371
- self:runSystem(dependent)
710
+ if isSystem(dependent) then
711
+ self:runSystem(dependent :: any, false)
372
712
  elseif isPhase(dependent) then
373
- self:runPhase(dependent)
713
+ self:runPhase(dependent :: any)
374
714
  elseif isPipeline(dependent) then
375
- self:runPipeline(dependent)
715
+ self:runPipeline(dependent :: any)
376
716
  else
377
717
  error("Unknown dependent passed into Scheduler:run(unknown)")
378
718
  end
@@ -398,7 +738,7 @@ end
398
738
  --- Pipelines/Phases in these groups are still ordered by their dependencies
399
739
  --- and by the order of insertion.
400
740
  --- :::
401
- function Scheduler:runAll()
741
+ function Scheduler.runAll<U...>(self: Scheduler<U...>)
402
742
  local orderedDefaults = self._defaultDependencyGraph:getOrderedList()
403
743
  assert(
404
744
  orderedDefaults,
@@ -412,9 +752,10 @@ function Scheduler:runAll()
412
752
  for identifier, dependencyGraph in self._eventDependencyGraphs do
413
753
  local orderedList = dependencyGraph:getOrderedList()
414
754
  assert(
415
- orderedDefaults,
755
+ orderedList,
416
756
  `Event Group '{identifier}' contains a circular dependency, check your Pipelines/Phases`
417
757
  )
758
+
418
759
  for _, dependency in orderedList do
419
760
  self:run(dependency)
420
761
  end
@@ -423,6 +764,52 @@ function Scheduler:runAll()
423
764
  return self
424
765
  end
425
766
 
767
+ type InsertFn =
768
+ & (<U...>(
769
+ self: Scheduler<U...>,
770
+ dependency: Phase | Pipeline
771
+ ) -> Scheduler<U...>)
772
+ -- RBXScriptSignal & nil
773
+ & (<U...>(
774
+ self: Scheduler<U...>,
775
+ dependency: Phase | Pipeline,
776
+ signal: RBXScriptSignal<U...>
777
+ ) -> Scheduler<U...>)
778
+ -- Instance & RBXScriptSignal
779
+ -- Instance & string
780
+ & (<U...>(
781
+ self: Scheduler<U...>,
782
+ dependency: Phase | Pipeline,
783
+ instance: Instance,
784
+ event: RBXScriptSignal<U...> | string
785
+ ) -> Scheduler<U...>)
786
+ -- SignalLike & nil
787
+ & (<U...>(
788
+ self: Scheduler<U...>,
789
+ dependency: Phase | Pipeline,
790
+ signal: SignalLike<U...>
791
+ ) -> Scheduler<U...>)
792
+ -- table & string
793
+ & (<U...>(
794
+ self: Scheduler<U...>,
795
+ dependency: Phase | Pipeline,
796
+ table: GenericTable,
797
+ event: string
798
+ ) -> Scheduler<U...>)
799
+ -- table & connectable method
800
+ & (<T, U...>(
801
+ self: Scheduler<U...>,
802
+ dependency: Phase | Pipeline,
803
+ instance: GenericTable,
804
+ connectMethod: (GenericTable, Callback<U...>, ...any) -> T
805
+ ) -> Scheduler<U...>)
806
+ -- connectable function
807
+ & (<T, U...>(
808
+ self: Scheduler<U...>,
809
+ dependency: Phase | Pipeline,
810
+ connectFn: (Callback<U...>, ...any) -> T
811
+ ) -> Scheduler<U...>)
812
+
426
813
  --- @method insert
427
814
  --- @within Scheduler
428
815
  --- @param phase Phase
@@ -472,7 +859,12 @@ end
472
859
  --- local myScheduler = Scheduler.new()
473
860
  --- :insert(myPipeline, RunService, "Heartbeat")
474
861
  --- ```
475
- function Scheduler:insert(dependency, instance, event)
862
+ local insertFn: InsertFn = function<U...>(
863
+ self: Scheduler<U...>,
864
+ dependency: Phase | Pipeline,
865
+ instance: any,
866
+ event: any
867
+ ): Scheduler<U...>
476
868
  assert(
477
869
  isPhase(dependency) or isPipeline(dependency),
478
870
  "Unknown dependency passed to Scheduler:insert(unknown, _, _)"
@@ -492,13 +884,15 @@ function Scheduler:insert(dependency, instance, event)
492
884
  end
493
885
 
494
886
  if isPhase(dependency) then
495
- self._phaseToSystems[dependency] = {}
496
- hooks.phaseAdd(self, dependency)
887
+ self._phaseToSystems[dependency :: Phase] = {}
888
+ self:_phaseAdd(dependency :: Phase)
497
889
  end
498
890
 
499
891
  return self
500
892
  end
501
893
 
894
+ Scheduler.insert = insertFn
895
+
502
896
  --- @method insertAfter
503
897
  --- @within Scheduler
504
898
  --- @param phase Phase
@@ -517,7 +911,11 @@ end
517
911
  --- Initializes the Pipeline and it's Phases within the Scheduler,
518
912
  --- ordering the Pipeline explicitly by setting the after Phase/Pipeline
519
913
  --- as a dependent.
520
- function Scheduler:insertAfter(dependent, after)
914
+ function Scheduler.insertAfter<U...>(
915
+ self: Scheduler<U...>,
916
+ dependent: Dependency,
917
+ after: Dependency
918
+ )
521
919
  assert(
522
920
  isPhase(after) or isPipeline(after),
523
921
  "Unknown dependency passed in Scheduler:insertAfter(_, unknown)"
@@ -531,8 +929,8 @@ function Scheduler:insertAfter(dependent, after)
531
929
  dependencyGraph:insertAfter(dependent, after)
532
930
 
533
931
  if isPhase(dependent) then
534
- self._phaseToSystems[dependent] = {}
535
- hooks.phaseAdd(self, dependent)
932
+ self._phaseToSystems[dependent :: Phase] = {}
933
+ self:_phaseAdd(dependent :: Phase)
536
934
  end
537
935
 
538
936
  return self
@@ -556,7 +954,11 @@ end
556
954
  --- Initializes the Pipeline and it's Phases within the Scheduler,
557
955
  --- ordering the Pipeline explicitly by setting the before Phase/Pipeline
558
956
  --- as a dependency.
559
- function Scheduler:insertBefore(dependent, before)
957
+ function Scheduler.insertBefore<U...>(
958
+ self: Scheduler<U...>,
959
+ dependent: Dependency,
960
+ before: Dependency
961
+ )
560
962
  assert(
561
963
  isPhase(before) or isPipeline(before),
562
964
  "Unknown dependency passed in Scheduler:insertBefore(_, unknown)"
@@ -570,8 +972,8 @@ function Scheduler:insertBefore(dependent, before)
570
972
  dependencyGraph:insertBefore(dependent, before)
571
973
 
572
974
  if isPhase(dependent) then
573
- self._phaseToSystems[dependent] = {}
574
- hooks.phaseAdd(self, dependent)
975
+ self._phaseToSystems[dependent :: Phase] = {}
976
+ self:_phaseAdd(dependent :: Phase)
575
977
  end
576
978
 
577
979
  return self
@@ -605,12 +1007,13 @@ end
605
1007
  --- end
606
1008
  --- end
607
1009
  --- ```
608
- function Scheduler:addSystem(system, phase)
1010
+ function Scheduler.addSystem<U...>(
1011
+ self: Scheduler<U...>,
1012
+ system: System<U...>,
1013
+ phase: Phase?
1014
+ )
609
1015
  local systemFn = getSystem(system)
610
-
611
- if not systemFn then
612
- error("Unknown system passed to Scheduler:addSystem(unknown, phase?)")
613
- end
1016
+ assert(systemFn, "Unknown system passed to Scheduler:addSystem(unknown, _)")
614
1017
 
615
1018
  local name = getSystemName(system)
616
1019
 
@@ -623,7 +1026,7 @@ function Scheduler:addSystem(system, phase)
623
1026
  scheduledPhase = self._defaultPhase
624
1027
  end
625
1028
 
626
- local systemInfo = {
1029
+ local systemInfo: SystemInfo<U...> = {
627
1030
  system = systemFn,
628
1031
  run = systemFn,
629
1032
  cleanup = nil,
@@ -633,15 +1036,18 @@ function Scheduler:addSystem(system, phase)
633
1036
  initialized = false,
634
1037
  }
635
1038
 
636
- self._systemInfo[systemFn] = systemInfo
1039
+ self._systemInfo[systemFn] = systemInfo :: any
637
1040
 
638
1041
  if not self._phaseToSystems[systemInfo.phase] then
639
1042
  self._phaseToSystems[systemInfo.phase] = {}
640
1043
  end
641
1044
 
642
- table.insert(self._phaseToSystems[systemInfo.phase], systemFn)
1045
+ table.insert(
1046
+ self._phaseToSystems[systemInfo.phase],
1047
+ systemFn :: InternalSystem<...any>
1048
+ )
643
1049
 
644
- hooks.systemAdd(self, systemInfo)
1050
+ self:_systemAdd(systemInfo)
645
1051
 
646
1052
  if type(system) == "table" and system.runConditions then
647
1053
  for _, condition in system.runConditions do
@@ -662,10 +1068,15 @@ end
662
1068
  ---
663
1069
  --- Adds the Systems to the Scheduler, scheduling them to be ran
664
1070
  --- implicitly within the provided Phase or on the default Main phase.
665
- function Scheduler:addSystems(systems, phase)
666
- if type(systems) ~= "table" then
667
- error("Unknown systems passed to Scheduler:addSystems(unknown, phase?)")
668
- end
1071
+ function Scheduler.addSystems<U...>(
1072
+ self: Scheduler<U...>,
1073
+ systems: { System<U...> },
1074
+ phase: Phase?
1075
+ )
1076
+ assert(
1077
+ type(systems) == "table",
1078
+ "Unknown value passed to Scheduler:addSystems(unknown, _). This value should be an array."
1079
+ )
669
1080
 
670
1081
  local foundSystem = false
671
1082
  local n = 0
@@ -678,15 +1089,12 @@ function Scheduler:addSystems(systems, phase)
678
1089
  end
679
1090
  end
680
1091
 
681
- if n == 0 then
682
- error("Empty table passed to Scheduler:addSystems({ }, phase?)")
683
- end
1092
+ assert(n ~= 0, "Empty table passed to Scheduler:addSystems({ }, _)")
684
1093
 
685
- if not foundSystem then
686
- error(
687
- "Unknown table passed to Scheduler:addSystems({ unknown }, phase?)"
688
- )
689
- end
1094
+ assert(
1095
+ foundSystem,
1096
+ "Table containing unknown values passed to Scheduler:addSystems({ unknown }, _)"
1097
+ )
690
1098
 
691
1099
  return self
692
1100
  end
@@ -697,22 +1105,32 @@ end
697
1105
  --- @param newPhase Phase
698
1106
  ---
699
1107
  --- Changes the Phase that this system is scheduled on.
700
- function Scheduler:editSystem(system, newPhase)
1108
+ function Scheduler.editSystem<U...>(
1109
+ self: Scheduler<U...>,
1110
+ system: System<U...>,
1111
+ newPhase: Phase
1112
+ )
701
1113
  local systemFn = getSystem(system)
1114
+ assert(
1115
+ systemFn,
1116
+ "Unknown system passed to Scheduler:editSystem(unknown, _)"
1117
+ )
1118
+
702
1119
  local systemInfo = self._systemInfo[systemFn]
703
1120
  assert(
704
1121
  systemInfo,
705
- "Attempt to edit a non-existent system in Scheduler:editSystem(_)"
1122
+ "Attempt to edit a non-existent system in Scheduler:editSystem(system, _)"
706
1123
  )
707
1124
 
708
1125
  assert(
709
1126
  newPhase and self._phaseToSystems[newPhase] ~= nil or true,
710
- "Phase never initialized before using Scheduler:editSystem(_, Phase)"
1127
+ "Phase never initialized before using Scheduler:editSystem(_, phase)"
711
1128
  )
712
1129
 
713
- local systems = self._phaseToSystems[systemInfo.phase]
1130
+ local oldPhase = systemInfo.phase
1131
+ local systems = self._phaseToSystems[oldPhase]
714
1132
 
715
- local index = table.find(systems, systemFn)
1133
+ local index = table.find(systems, systemFn :: InternalSystem<...any>)
716
1134
  assert(index, "Unable to find system within phase")
717
1135
 
718
1136
  table.remove(systems, index)
@@ -720,17 +1138,26 @@ function Scheduler:editSystem(system, newPhase)
720
1138
  if not self._phaseToSystems[newPhase] then
721
1139
  self._phaseToSystems[newPhase] = {}
722
1140
  end
723
- table.insert(self._phaseToSystems[newPhase], systemFn)
1141
+ table.insert(
1142
+ self._phaseToSystems[newPhase],
1143
+ systemFn :: InternalSystem<...any>
1144
+ )
724
1145
 
725
1146
  systemInfo.phase = newPhase
1147
+ self:_systemEdited(systemInfo :: SystemInfo<U...>, oldPhase, newPhase)
1148
+
726
1149
  return self
727
1150
  end
728
1151
 
729
- function Scheduler:_removeCondition(dependent, condition)
730
- self._runIfConditions[dependent] = nil
1152
+ function Scheduler._removeCondition<U...>(
1153
+ self: Scheduler<U...>,
1154
+ dependent: Dependent<U...>,
1155
+ condition: Condition<U...>
1156
+ )
1157
+ self._runIfConditions[dependent :: any] = nil
731
1158
 
732
1159
  for _, _conditions in self._runIfConditions do
733
- if table.find(_conditions, condition) then
1160
+ if table.find(_conditions :: any, condition) then
734
1161
  return
735
1162
  end
736
1163
  end
@@ -767,47 +1194,55 @@ end
767
1194
  --- -- Later...
768
1195
  --- scheduler:removeSystem(networkSystem) -- Cleanup executes here
769
1196
  --- ```
770
- function Scheduler:removeSystem(system)
1197
+ function Scheduler.removeSystem<U...>(self: Scheduler<U...>, system: System<U...>)
771
1198
  local systemFn = getSystem(system)
1199
+ assert(systemFn, "Invalid system passed to Scheduler:removeSystem(system)")
1200
+
772
1201
  local systemInfo = self._systemInfo[systemFn]
773
1202
  assert(
774
1203
  systemInfo,
775
- "Attempt to remove a non-existent system in Scheduler:removeSystem(_)"
1204
+ "Attempt to remove a non-existent system in Scheduler:removeSystem(system)"
776
1205
  )
777
1206
 
778
1207
  if systemInfo.cleanup then
779
- local success, err =
780
- pcall(systemInfo.cleanup, table.unpack(self._vargs))
1208
+ local success: boolean, err: SystemLog<unknown> = xpcall<<unknown, (nil), (
1209
+ SystemLog<unknown>
1210
+ )>>(systemInfo.cleanup, function(e)
1211
+ local errMessage =
1212
+ `Cleanup failed for system '{systemInfo.name}': {e}`
1213
+
1214
+ return {
1215
+ e = e,
1216
+ log = debug.traceback(errMessage),
1217
+ trace = debug.traceback(),
1218
+ }
1219
+ end, table.unpack(self._vargs))
1220
+
781
1221
  if success then
782
- hooks.systemCleanup(self, systemInfo, nil)
1222
+ self:_systemCleanup(systemInfo, nil)
783
1223
  else
784
- local errMsg = string.format(
785
- "Cleanup failed for system '%s': %s",
786
- systemInfo.name,
787
- tostring(err)
788
- )
789
- hooks.systemError(self, systemInfo, errMsg)
790
- hooks.systemCleanup(self, systemInfo, errMsg)
1224
+ self:_systemError(systemInfo, err)
1225
+ self:_systemCleanup(systemInfo, err)
791
1226
  end
792
1227
  end
793
1228
 
794
1229
  local systems = self._phaseToSystems[systemInfo.phase]
795
1230
 
796
- local index = table.find(systems, systemFn)
1231
+ local index = table.find(systems :: any, systemFn)
797
1232
  assert(index, "Unable to find system within phase")
798
1233
 
799
1234
  table.remove(systems, index)
800
1235
  self._systemInfo[systemFn] = nil
801
1236
 
802
- if self._runIfConditions[system] then
803
- for _, condition in self._runIfConditions[system] do
1237
+ if self._runIfConditions[system :: any] then
1238
+ for _, condition in self._runIfConditions[system :: any] do
804
1239
  self:_removeCondition(system, condition)
805
1240
  end
806
1241
 
807
- self._runIfConditions[system] = nil
1242
+ self._runIfConditions[system :: any] = nil
808
1243
  end
809
1244
 
810
- hooks.systemRemove(self, systemInfo)
1245
+ self:_systemRemove(systemInfo :: SystemInfo<U...>)
811
1246
 
812
1247
  return self
813
1248
  end
@@ -818,33 +1253,49 @@ end
818
1253
  --- @param new System
819
1254
  ---
820
1255
  --- Replaces the System with a new System.
821
- function Scheduler:replaceSystem(old, new)
822
- local oldSystemFn = getSystem(old)
1256
+ function Scheduler.replaceSystem<U...>(
1257
+ self: Scheduler<U...>,
1258
+ old: System<U...>,
1259
+ new: System<U...>
1260
+ )
1261
+ local oldSystemFn = getSystem(old) :: InternalSystem<...any>
1262
+ assert(
1263
+ oldSystemFn,
1264
+ "Attempt to pass non-system in Scheduler:replaceSystem(unknown, _)"
1265
+ )
1266
+
823
1267
  local oldSystemInfo = self._systemInfo[oldSystemFn]
824
1268
  assert(
825
1269
  oldSystemInfo,
826
- "Attempt to replace a non-existent system in Scheduler:replaceSystem(unknown, _)"
1270
+ "Attempt to replace a non-existent system in Scheduler:replaceSystem(system, _)"
827
1271
  )
828
1272
 
829
- local newSystemFn = getSystem(new)
1273
+ local newSystemFn = getSystem(new) :: InternalSystem<...any>
830
1274
  assert(
831
1275
  newSystemFn,
832
1276
  "Attempt to pass non-system in Scheduler:replaceSystem(_, unknown)"
833
1277
  )
834
1278
 
835
1279
  if oldSystemInfo.cleanup then
836
- local success, err =
837
- pcall(oldSystemInfo.cleanup, table.unpack(self._vargs))
1280
+ -- LUAU FUTURE: Better types for xpcalls
1281
+ local success: boolean, err: SystemLog<unknown> = xpcall<<unknown, (nil), (
1282
+ SystemLog<unknown>
1283
+ )>>(oldSystemInfo.cleanup, function(e)
1284
+ local errMessage =
1285
+ `Cleanup failed for system '{oldSystemInfo.name}': {e}`
1286
+
1287
+ return {
1288
+ e = e,
1289
+ log = debug.traceback(errMessage),
1290
+ trace = debug.traceback(),
1291
+ }
1292
+ end, table.unpack(self._vargs))
1293
+
838
1294
  if success then
839
- hooks.systemCleanup(self, oldSystemInfo, nil)
1295
+ self:_systemCleanup(oldSystemInfo, nil)
840
1296
  else
841
- local errMsg = string.format(
842
- "Cleanup failed for system '%s': %s",
843
- oldSystemInfo.name,
844
- tostring(err)
845
- )
846
- hooks.systemError(self, oldSystemInfo, errMsg)
847
- hooks.systemCleanup(self, oldSystemInfo, errMsg)
1297
+ self:_systemError(oldSystemInfo, err)
1298
+ self:_systemCleanup(oldSystemInfo, err)
848
1299
  end
849
1300
  end
850
1301
 
@@ -864,7 +1315,10 @@ function Scheduler:replaceSystem(old, new)
864
1315
  oldSystemInfo.initialized = false
865
1316
  oldSystemInfo.name = getSystemName(new)
866
1317
 
867
- hooks.systemReplace(self, copy, oldSystemInfo)
1318
+ self:_systemReplace(
1319
+ copy :: SystemInfo<U...>,
1320
+ oldSystemInfo :: SystemInfo<U...>
1321
+ )
868
1322
 
869
1323
  self._systemInfo[newSystemFn] = self._systemInfo[oldSystemFn]
870
1324
  self._systemInfo[oldSystemFn] = nil
@@ -875,7 +1329,7 @@ end
875
1329
  --- @method addRunCondition
876
1330
  --- @within Scheduler
877
1331
  --- @param system System
878
- --- @param fn (U...) -> boolean
1332
+ --- @param fn (U...) -> any
879
1333
  ---
880
1334
  --- Adds a Run Condition which the Scheduler will check before
881
1335
  --- this System is ran.
@@ -883,7 +1337,7 @@ end
883
1337
  --- @method addRunCondition
884
1338
  --- @within Scheduler
885
1339
  --- @param phase Phase
886
- --- @param fn (U...) -> boolean
1340
+ --- @param fn (U...) -> any
887
1341
  ---
888
1342
  --- Adds a Run Condition which the Scheduler will check before
889
1343
  --- any Systems within this Phase are ran.
@@ -891,14 +1345,18 @@ end
891
1345
  --- @method addRunCondition
892
1346
  --- @within Scheduler
893
1347
  --- @param pipeline Pipeline
894
- --- @param fn (U...) -> boolean
1348
+ --- @param fn (U...) -> any
895
1349
  ---
896
1350
  --- Adds a Run Condition which the Scheduler will check before
897
1351
  --- any Systems within any Phases apart of this Pipeline are ran.
898
- function Scheduler:addRunCondition(dependent, fn)
1352
+ function Scheduler.addRunCondition<U...>(
1353
+ self: Scheduler<U...>,
1354
+ dependent: Dependent<U...>,
1355
+ fn: Condition<U...>
1356
+ )
899
1357
  fn = if typeof(fn) == "table" then fn[1] else fn
900
1358
 
901
- local system = getSystem(dependent)
1359
+ local system: SystemFn<...any>? = getSystem(dependent :: any) :: any
902
1360
  if system then
903
1361
  dependent = system
904
1362
  end
@@ -908,30 +1366,17 @@ function Scheduler:addRunCondition(dependent, fn)
908
1366
  "Attempt to pass unknown dependent into Scheduler:addRunCondition(unknown, _)"
909
1367
  )
910
1368
 
911
- if not self._runIfConditions[dependent] then
912
- self._runIfConditions[dependent] = {}
1369
+ if not self._runIfConditions[dependent :: any] then
1370
+ self._runIfConditions[dependent :: any] = {}
913
1371
  end
914
1372
 
915
- table.insert(self._runIfConditions[dependent], fn)
1373
+ table.insert(self._runIfConditions[dependent :: any], fn)
916
1374
 
917
1375
  return self
918
1376
  end
919
1377
 
920
- function Scheduler:_addBuiltins()
921
- self._defaultPhase = Phase.new("Default")
922
- self._defaultDependencyGraph = DependencyGraph.new()
923
-
924
- self._defaultDependencyGraph:insert(Pipeline.Startup)
925
- self._defaultDependencyGraph:insert(self._defaultPhase)
926
-
927
- self:addRunCondition(Pipeline.Startup, conditions.runOnce())
928
- for _, phase in Pipeline.Startup.dependencyGraph.nodes do
929
- self:addRunCondition(phase, conditions.runOnce())
930
- end
931
- end
932
-
933
- function Scheduler:_scheduleEvent(instance, event)
934
- local connect = utils.getConnectFunction(instance, event)
1378
+ function Scheduler._scheduleEvent<U...>(self: Scheduler<U...>, instance, event)
1379
+ local connect = utils.getConnectFn(instance, event)
935
1380
  assert(
936
1381
  connect,
937
1382
  "Couldn't connect to event as no valid connect methods were found! Ensure the passed event has a 'Connect' or an 'on' method!"
@@ -948,24 +1393,32 @@ function Scheduler:_scheduleEvent(instance, event)
948
1393
  local err =
949
1394
  `Event Group '{identifier}' contains a circular dependency, check your Pipelines/Phases`
950
1395
  if not recentLogs[err] then
951
- task.spawn(error, err, 0)
1396
+ -- LUAU FUTURE: Inference bug
1397
+ task.spawn<<(string, number?)>>(error, err, 0)
952
1398
  warn(
953
1399
  `Planck: Error occurred while running event, this error will be ignored for 10 seconds`
954
1400
  )
955
1401
  recentLogs[err] = true
956
1402
  end
1403
+
1404
+ return
957
1405
  end
958
1406
 
959
1407
  for _, dependency in orderedList do
960
- self:run(dependency)
1408
+ self:run(dependency :: any)
961
1409
  end
962
1410
  end
963
1411
 
964
1412
  self._connectedEvents[identifier] = connect(callback)
965
- self._eventDependencyGraphs[identifier] = dependencyGraph
1413
+ self._eventDependencyGraphs[identifier] =
1414
+ dependencyGraph :: DependencyGraph<Dependency>
966
1415
  end
967
1416
 
968
- function Scheduler:_getEventDependencyGraph(instance, event)
1417
+ function Scheduler._getEventDependencyGraph<U...>(
1418
+ self: Scheduler<U...>,
1419
+ instance,
1420
+ event
1421
+ )
969
1422
  local identifier = getEventIdentifier(instance, event)
970
1423
 
971
1424
  if not self._connectedEvents[identifier] then
@@ -975,7 +1428,10 @@ function Scheduler:_getEventDependencyGraph(instance, event)
975
1428
  return self._eventDependencyGraphs[identifier]
976
1429
  end
977
1430
 
978
- function Scheduler:_getGraphOfDependency(dependency)
1431
+ function Scheduler._getGraphOfDependency<U...>(
1432
+ self: Scheduler<U...>,
1433
+ dependency: Dependency
1434
+ )
979
1435
  if table.find(self._defaultDependencyGraph.nodes, dependency) then
980
1436
  return self._defaultDependencyGraph
981
1437
  end
@@ -1008,14 +1464,16 @@ end
1008
1464
  --- These plugins are unable to properly be cleaned up, use
1009
1465
  --- them with caution.
1010
1466
  --- :::
1011
- function Scheduler:cleanup()
1467
+ function Scheduler.cleanup<U...>(self: Scheduler<U...>)
1012
1468
  for _, connection in self._connectedEvents do
1013
1469
  utils.disconnectEvent(connection)
1014
1470
  end
1015
1471
 
1016
1472
  for _, plugin in self._plugins do
1017
- if plugin.cleanup then
1018
- plugin:cleanup()
1473
+ -- LUAU FUTURE: Type solver doesn't play nice here
1474
+ local cleanup: ((self: Plugin) -> ())? = plugin.cleanup
1475
+ if cleanup then
1476
+ cleanup(plugin)
1019
1477
  end
1020
1478
  end
1021
1479
 
@@ -1036,34 +1494,64 @@ end
1036
1494
  ---
1037
1495
  --- Creates a new Scheduler, the args passed will be passed to
1038
1496
  --- any System anytime it is ran by the Scheduler.
1039
- function Scheduler.new(...)
1040
- local self = {}
1041
-
1042
- self._hooks = {}
1043
-
1044
- self._vargs = { ... }
1045
-
1046
- self._eventDependencyGraphs = {}
1047
- self._connectedEvents = {}
1048
-
1049
- self._phaseToSystems = {}
1050
- self._systemInfo = {}
1051
-
1052
- self._runIfConditions = {}
1053
-
1054
- self._plugins = {}
1055
-
1056
- setmetatable(self, Scheduler)
1057
-
1058
- for _, hookName in hooks.Hooks do
1497
+ function Scheduler.new<U...>(...: U...): Scheduler<U...>
1498
+ local defaultPhase = Phase.new("Default")
1499
+ local defaultDependencyGraph: DependencyGraph<Dependency> =
1500
+ DependencyGraph.new() :: any
1501
+
1502
+ defaultDependencyGraph:insert(Pipeline.Startup)
1503
+ defaultDependencyGraph:insert(defaultPhase)
1504
+
1505
+ local self: Scheduler<U...> = setmetatable({
1506
+ _defaultPhase = defaultPhase,
1507
+ _defaultDependencyGraph = defaultDependencyGraph,
1508
+ _eventDependencyGraphs = {},
1509
+ _connectedEvents = {},
1510
+ _plugins = {},
1511
+ _phaseToSystems = {},
1512
+ _hooks = {},
1513
+ _runIfConditions = {},
1514
+ _systemInfo = {},
1515
+ _thread = nil :: any,
1516
+ _currentSystem = nil :: any,
1517
+ _yielded = false,
1518
+ _vargs = { ... },
1519
+ }, Scheduler)
1520
+
1521
+ for _, hookName in self.Hooks :: any do
1059
1522
  if not self._hooks[hookName] then
1060
1523
  self._hooks[hookName] = {}
1061
1524
  end
1062
1525
  end
1063
1526
 
1064
- self:_addBuiltins()
1527
+ self:addRunCondition(Pipeline.Startup, conditions.runOnce())
1528
+ for _, phase in Pipeline.Startup.dependencyGraph.nodes do
1529
+ self:addRunCondition(phase, conditions.runOnce())
1530
+ end
1065
1531
 
1066
- return self
1532
+ return self :: Scheduler<U...>
1067
1533
  end
1068
1534
 
1535
+ export type Scheduler<U...> = setmetatable<
1536
+ {
1537
+ _defaultPhase: Phase,
1538
+ _defaultDependencyGraph: DependencyGraph<Dependency>,
1539
+ _eventDependencyGraphs: { [string]: DependencyGraph<Dependency> },
1540
+ _connectedEvents: { [string]: utils.ConnectionLike },
1541
+ _plugins: Vec<Plugin>,
1542
+ -- LUAU FIXME: Should be System<U...>
1543
+ _phaseToSystems: Map<Phase, Vec<InternalSystem<...any>>>,
1544
+ _hooks: Map<HookId, Vec<(PartialHookContext) -> unknown>>,
1545
+ -- LUAU FIXME: Should be <Dependent<U...>, Condition<U...>>
1546
+ _runIfConditions: Map<Dependent<...any>, Vec<Condition<...any>>>,
1547
+ -- LUAU FIXME: Should be SystemInfo<U...>
1548
+ _systemInfo: Map<InternalSystem<U...>, SystemInfo<...any>>,
1549
+ _currentSystem: SystemInfo<...any>,
1550
+ _thread: thread?,
1551
+ _yielded: boolean,
1552
+ _vargs: { any },
1553
+ },
1554
+ typeof(Scheduler)
1555
+ >
1556
+
1069
1557
  return Scheduler