@rbxts/planck 0.1.3-rc.5 → 0.2.0

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,10 +1,11 @@
1
- local RunService = game:GetService("RunService")
1
+ --!nonstrict
2
+ local DependencyGraph = require("./DependencyGraph")
3
+ local Pipeline = require("./Pipeline")
4
+ local Phase = require("./Phase")
2
5
 
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'))
6
+ local utils = require("./utils")
7
+ local hooks = require("./hooks")
8
+ local conditions = require("./conditions")
8
9
 
9
10
  local getSystem = utils.getSystem
10
11
  local getSystemName = utils.getSystemName
@@ -49,6 +50,7 @@ Scheduler.Hooks = hooks.Hooks
49
50
  --- Initializes a plugin with the scheduler, see the [Plugin Docs](/docs/plugins) for more information.
50
51
  function Scheduler:addPlugin(plugin)
51
52
  plugin:build(self)
53
+ table.insert(self._plugins, plugin)
52
54
  return self
53
55
  end
54
56
 
@@ -105,8 +107,7 @@ function Scheduler:_handleLogs(systemInfo)
105
107
  end
106
108
 
107
109
  function Scheduler:runSystem(system)
108
- local runIf = self._runIfConditions[system]
109
- if runIf and runIf(table.unpack(self._vargs)) == false then
110
+ if self:_canRun(system) == false then
110
111
  return
111
112
  end
112
113
 
@@ -133,11 +134,14 @@ function Scheduler:runSystem(system)
133
134
 
134
135
  local function systemCall()
135
136
  local function noYield()
136
- local success, err = coroutine.resume(self._thread, function()
137
- system(table.unpack(self._vargs))
137
+ local success, err
138
+ coroutine.resume(self._thread, function()
139
+ success, err = xpcall(system, function(e)
140
+ return debug.traceback(e)
141
+ end, table.unpack(self._vargs))
138
142
  end)
139
143
 
140
- if not success then
144
+ if success == false then
141
145
  didYield = true
142
146
  table.insert(systemInfo.logs, err)
143
147
  hooks.systemError(self, systemInfo, err)
@@ -146,12 +150,16 @@ function Scheduler:runSystem(system)
146
150
 
147
151
  if self._yielded then
148
152
  didYield = true
149
- local trace, line = debug.info(systemInfo.system, "sl")
150
- table.insert(systemInfo.logs, `{trace}:{line}: System yielded`)
153
+ local source, line = debug.info(self._thread, 1, "sl")
154
+ local errMessage = `{source}:{line}: System yielded`
155
+ table.insert(
156
+ systemInfo.logs,
157
+ debug.traceback(self._thread, errMessage, 2)
158
+ )
151
159
  hooks.systemError(
152
160
  self,
153
161
  systemInfo,
154
- `{trace}:{line}: System yielded`
162
+ debug.traceback(self._thread, errMessage, 2)
155
163
  )
156
164
  end
157
165
  end
@@ -205,29 +213,51 @@ function Scheduler:runSystem(system)
205
213
  end
206
214
 
207
215
  function Scheduler:runPhase(phase)
208
- local runIf = self._runIfConditions[phase]
209
- if runIf and runIf(table.unpack(self._vargs)) == false then
216
+ if self:_canRun(phase) == false then
210
217
  return
211
218
  end
212
219
 
213
220
  hooks.phaseBegan(self, phase)
214
221
 
222
+ if not self._phaseToSystems[phase] then
223
+ self._phaseToSystems[phase] = {}
224
+ end
225
+
215
226
  for _, system in self._phaseToSystems[phase] do
216
227
  self:runSystem(system)
217
228
  end
218
229
  end
219
230
 
220
231
  function Scheduler:runPipeline(pipeline)
221
- local runIf = self._runIfConditions[pipeline]
222
- if runIf and runIf(table.unpack(self._vargs)) == false then
232
+ if self:_canRun(pipeline) == false then
223
233
  return
224
234
  end
225
235
 
226
- for _, phase in pipeline._phases do
236
+ local orderedList = pipeline.dependencyGraph:getOrderedList()
237
+ assert(
238
+ orderedList,
239
+ `Pipeline {pipeline} contains a circular dependency, check it's Phases`
240
+ )
241
+
242
+ for _, phase in orderedList do
227
243
  self:runPhase(phase)
228
244
  end
229
245
  end
230
246
 
247
+ function Scheduler:_canRun(dependent)
248
+ local conditions = self._runIfConditions[dependent]
249
+
250
+ if conditions then
251
+ for _, runIf in conditions do
252
+ if runIf(table.unpack(self._vargs)) == false then
253
+ return false
254
+ end
255
+ end
256
+ end
257
+
258
+ return true
259
+ end
260
+
231
261
  --- @method run
232
262
  --- @within Scheduler
233
263
  --- @param phase Phase
@@ -274,37 +304,42 @@ end
274
304
  --- @return Scheduler
275
305
  ---
276
306
  --- Runs all Systems within order.
307
+ ---
308
+ --- :::note
309
+ --- When you add a Pipeline or Phase with an event, it will be grouped
310
+ --- with other Pipelines/Phases on that event. Otherwise, it will be
311
+ --- added to the default group.
312
+ ---
313
+ --- When not running systems on Events, such as with the `runAll` method,
314
+ --- the Default group will be ran first, and then each Event Group in the
315
+ --- order created.
316
+ ---
317
+ --- Pipelines/Phases in these groups are still ordered by their dependencies
318
+ --- and by the order of insertion.
319
+ --- :::
277
320
  function Scheduler:runAll()
278
- for _, phase in self._orderedPhases do
279
- self:run(phase)
280
- end
281
- end
282
-
283
- function Scheduler:_insertPhaseAt(phase, index, instance, event)
321
+ local orderedDefaults = self._defaultDependencyGraph:getOrderedList()
284
322
  assert(
285
- not table.find(self._orderedPhases, phase),
286
- "Phase already initialized"
323
+ orderedDefaults,
324
+ "Default Group contains a circular dependency, check your Pipelines/Phases"
287
325
  )
288
326
 
289
- table.insert(self._orderedPhases, index, phase)
290
- self._phaseToSystems[phase] = {}
291
-
292
- hooks.phaseAdd(self, phase)
293
-
294
- if isValidEvent(instance, event) then
295
- self:_schedulePhase(phase, instance, event)
327
+ for _, dependency in orderedDefaults do
328
+ self:run(dependency)
296
329
  end
297
- end
298
330
 
299
- function Scheduler:insertPhase(phase, instance, event)
300
- local index = table.find(self._orderedPhases, Phase.Update)
301
- self:_insertPhaseAt(phase, index, instance, event)
302
- end
303
-
304
- function Scheduler:insertPipeline(pipeline, instance, event)
305
- for _, phase in pipeline._phases do
306
- self:insertPhase(phase, instance, event)
331
+ for identifier, dependencyGraph in self._eventDependencyGraphs do
332
+ local orderedList = dependencyGraph:getOrderedList()
333
+ assert(
334
+ orderedDefaults,
335
+ `Event Group '{identifier}' contains a circular dependency, check your Pipelines/Phases`
336
+ )
337
+ for _, dependency in orderedList do
338
+ self:run(dependency)
339
+ end
307
340
  end
341
+
342
+ return self
308
343
  end
309
344
 
310
345
  --- @method insert
@@ -312,15 +347,17 @@ end
312
347
  --- @param phase Phase
313
348
  --- @return Scheduler
314
349
  ---
315
- --- Initializes the Phase within the Scheduler, ordering it implicitly.
350
+ --- Initializes the Phase within the Scheduler, ordering it implicitly by
351
+ --- setting it as a dependent of the previous Phase/Pipeline.
316
352
 
317
353
  --- @method insert
318
354
  --- @within Scheduler
319
355
  --- @param pipeline Pipeline
320
356
  --- @return Scheduler
321
357
  ---
322
- --- Initializes all Phases within the Pipeline within the Scheduler,
323
- --- ordering the Pipeline implicitly.
358
+ --- Initializes the Pipeline and it's Phases within the Scheduler,
359
+ --- ordering the Pipeline implicitly by setting it as a dependent
360
+ --- of the previous Phase/Pipeline.
324
361
 
325
362
  --- @method insert
326
363
  --- @within Scheduler
@@ -330,7 +367,8 @@ end
330
367
  --- @return Scheduler
331
368
  ---
332
369
  --- Initializes the Phase within the Scheduler, ordering it implicitly
333
- --- and scheduling it to be ran on the specified event.
370
+ --- by setting it as a dependent of the previous Phase/Pipeline, and
371
+ --- scheduling it to be ran on the specified event.
334
372
  ---
335
373
  --- ```lua
336
374
  --- local myScheduler = Scheduler.new()
@@ -344,22 +382,38 @@ end
344
382
  --- @param event string | EventLike
345
383
  --- @return Scheduler
346
384
  ---
347
- --- Initializes all Phases within the Pipeline within the Scheduler,
348
- --- ordering the Pipeline implicitly and scheduling it to be ran on
349
- --- the specified event.
385
+ --- Initializes the Pipeline and it's Phases within the Scheduler,
386
+ --- ordering the Pipeline implicitly by setting it as a dependent of
387
+ --- the previous Phase/Pipeline, and scheduling it to be ran on the
388
+ --- specified event.
350
389
  ---
351
390
  --- ```lua
352
391
  --- local myScheduler = Scheduler.new()
353
392
  --- :insert(myPipeline, RunService, "Heartbeat")
354
393
  --- ```
355
394
 
356
- function Scheduler:insert(dependent, instance, event)
357
- if isPhase(dependent) then
358
- self:insertPhase(dependent, instance, event)
359
- elseif isPipeline(dependent) then
360
- self:insertPipeline(dependent, instance, event)
395
+ function Scheduler:insert(dependency, instance, event)
396
+ assert(
397
+ isPhase(dependency) or isPipeline(dependency),
398
+ "Unknown dependency passed to Scheduler:insert(unknown, _, _)"
399
+ )
400
+
401
+ if not instance then
402
+ local dependencyGraph = self._defaultDependencyGraph
403
+ dependencyGraph:insertBefore(dependency, self._defaultPhase)
361
404
  else
362
- error("Unknown dependent passed to Scheduler:insert(unknown, _, _)")
405
+ assert(
406
+ isValidEvent(instance, event),
407
+ "Unknown instance/event passed to Scheduler:insert(_, instance, event)"
408
+ )
409
+
410
+ local dependencyGraph = self:_getEventDependencyGraph(instance, event)
411
+ dependencyGraph:insert(dependency)
412
+ end
413
+
414
+ if isPhase(dependency) then
415
+ self._phaseToSystems[dependency] = {}
416
+ hooks.phaseAdd(self, dependency)
363
417
  end
364
418
 
365
419
  return self
@@ -372,8 +426,7 @@ end
372
426
  --- @return Scheduler
373
427
  ---
374
428
  --- Initializes the Phase within the Scheduler, ordering it
375
- --- explicitly after the Phase, or adding to the end of the
376
- --- Pipeline provided.
429
+ --- explicitly by setting the after Phase/Pipeline as a dependent.
377
430
 
378
431
  --- @method insertAfter
379
432
  --- @within Scheduler
@@ -381,40 +434,66 @@ end
381
434
  --- @param after Phase | Pipeline
382
435
  --- @return Scheduler
383
436
  ---
384
- --- Initializes all Phases within the Pipeline within the Scheduler,
385
- --- ordering the Pipeline explicitly after the Phase, or adding
386
- --- to the end of the Pipeline provided.
437
+ --- Initializes the Pipeline and it's Phases within the Scheduler,
438
+ --- ordering the Pipeline explicitly by setting the after Phase/Pipeline
439
+ --- as a dependent.
387
440
 
388
441
  function Scheduler:insertAfter(dependent, after)
389
- if isPhase(after) then
390
- local index = table.find(self._orderedPhases, after)
391
- assert(
392
- index,
393
- "Phase never initialized in Scheduler:insertAfter(_, phase)"
394
- )
442
+ assert(
443
+ isPhase(after) or isPipeline(after),
444
+ "Unknown dependency passed in Scheduler:insertAfter(_, unknown)"
445
+ )
446
+ assert(
447
+ isPhase(dependent) or isPipeline(dependent),
448
+ "Unknown dependent passed in Scheduler:insertAfter(unknown, _)"
449
+ )
395
450
 
396
- if isPhase(dependent) then
397
- self:_insertPhaseAt(dependent, index + 1)
398
- elseif isPipeline(dependent) then
399
- for _, phase in dependent._phases do
400
- index += 1
401
- self:_insertPhaseAt(phase, index)
402
- end
403
- else
404
- error(
405
- "Unknown dependent passed in Scheduler:insertAfter(unknown, _)"
406
- )
407
- end
408
- elseif isPipeline(after) then
409
- local before = after._phases[#after._phases]
451
+ local dependencyGraph = self:_getGraphOfDependency(after)
452
+ dependencyGraph:insertAfter(dependent, after)
410
453
 
411
- if isPhase(dependent) then
412
- after:insert(dependent)
413
- end
454
+ if isPhase(dependent) then
455
+ self._phaseToSystems[dependent] = {}
456
+ hooks.phaseAdd(self, dependent)
457
+ end
414
458
 
415
- self:insertAfter(dependent, before)
416
- else
417
- error("Unknown dependency passed in Scheduler:insertAfter(_, unknown)")
459
+ return self
460
+ end
461
+
462
+ --- @method insertBefore
463
+ --- @within Scheduler
464
+ --- @param phase Phase
465
+ --- @param before Phase | Pipeline
466
+ --- @return Scheduler
467
+ ---
468
+ --- Initializes the Phase within the Scheduler, ordering it
469
+ --- explicitly by setting the before Phase/Pipeline as a dependency.
470
+
471
+ --- @method insertBefore
472
+ --- @within Scheduler
473
+ --- @param pipeline Pipeline
474
+ --- @param before Phase | Pipeline
475
+ --- @return Scheduler
476
+ ---
477
+ --- Initializes the Pipeline and it's Phases within the Scheduler,
478
+ --- ordering the Pipeline explicitly by setting the before Phase/Pipeline
479
+ --- as a dependency.
480
+
481
+ function Scheduler:insertBefore(dependent, before)
482
+ assert(
483
+ isPhase(before) or isPipeline(before),
484
+ "Unknown dependency passed in Scheduler:insertBefore(_, unknown)"
485
+ )
486
+ assert(
487
+ isPhase(dependent) or isPipeline(dependent),
488
+ "Unknown dependent passed in Scheduler:insertBefore(unknown, _)"
489
+ )
490
+
491
+ local dependencyGraph = self:_getGraphOfDependency(before)
492
+ dependencyGraph:insertBefore(dependent, before)
493
+
494
+ if isPhase(dependent) then
495
+ self._phaseToSystems[dependent] = {}
496
+ hooks.phaseAdd(self, dependent)
418
497
  end
419
498
 
420
499
  return self
@@ -434,10 +513,15 @@ function Scheduler:addSystem(system, phase)
434
513
  error("Unknown system passed to Scheduler:addSystem(unknown, phase?)")
435
514
  end
436
515
 
516
+ local name = getSystemName(systemFn)
517
+ if type(system) == "table" and system.name then
518
+ name = system.name
519
+ end
520
+
437
521
  local systemInfo = {
438
522
  system = systemFn,
439
523
  phase = phase,
440
- name = getSystemName(systemFn),
524
+ name = name,
441
525
  logs = {},
442
526
  }
443
527
 
@@ -445,15 +529,26 @@ function Scheduler:addSystem(system, phase)
445
529
  if type(system) == "table" and system.phase then
446
530
  systemInfo.phase = system.phase
447
531
  else
448
- systemInfo.phase = Phase.Update
532
+ systemInfo.phase = self._defaultPhase
449
533
  end
450
534
  end
451
535
 
452
536
  self._systemInfo[systemFn] = systemInfo
537
+
538
+ if not self._phaseToSystems[systemInfo.phase] then
539
+ self._phaseToSystems[systemInfo.phase] = {}
540
+ end
541
+
453
542
  table.insert(self._phaseToSystems[systemInfo.phase], systemFn)
454
543
 
455
544
  hooks.systemAdd(self, systemInfo)
456
545
 
546
+ if type(system) == "table" and system.runConditions then
547
+ for _, condition in system.runConditions do
548
+ self:addRunCondition(systemFn, condition)
549
+ end
550
+ end
551
+
457
552
  return self
458
553
  end
459
554
 
@@ -470,19 +565,27 @@ function Scheduler:addSystems(systems, phase)
470
565
  end
471
566
 
472
567
  local foundSystem = false
568
+ local n = 0
473
569
 
474
570
  for _, system in systems do
571
+ n += 1
475
572
  if getSystem(system) then
476
573
  foundSystem = true
477
574
  self:addSystem(system, phase)
478
575
  end
479
576
  end
480
577
 
578
+ if n == 0 then
579
+ error("Empty table passed to Scheduler:addSystems({ }, phase?)")
580
+ end
581
+
481
582
  if not foundSystem then
482
583
  error(
483
584
  "Unknown table passed to Scheduler:addSystems({ unknown }, phase?)"
484
585
  )
485
586
  end
587
+
588
+ return self
486
589
  end
487
590
 
488
591
  --- @method editSystem
@@ -510,12 +613,28 @@ function Scheduler:editSystem(system, newPhase)
510
613
  assert(index, "Unable to find system within phase")
511
614
 
512
615
  table.remove(systems, index)
616
+
617
+ if not self._phaseToSystems[newPhase] then
618
+ self._phaseToSystems[newPhase] = {}
619
+ end
513
620
  table.insert(self._phaseToSystems[newPhase], systemFn)
514
621
 
515
622
  systemInfo.phase = newPhase
516
623
  return self
517
624
  end
518
625
 
626
+ function Scheduler:_removeCondition(dependent, condition)
627
+ self._runIfConditions[dependent] = nil
628
+
629
+ for _, _conditions in self._runIfConditions do
630
+ if table.find(_conditions, condition) then
631
+ return
632
+ end
633
+ end
634
+
635
+ conditions.cleanupCondition(condition)
636
+ end
637
+
519
638
  --- @method removeSystem
520
639
  --- @within Scheduler
521
640
  --- @param system System
@@ -537,6 +656,14 @@ function Scheduler:removeSystem(system)
537
656
  table.remove(systems, index)
538
657
  self._systemInfo[systemFn] = nil
539
658
 
659
+ if self._runIfConditions[system] then
660
+ for _, condition in self._runIfConditions[system] do
661
+ self:_removeCondition(system, condition)
662
+ end
663
+
664
+ self._runIfConditions[system] = nil
665
+ end
666
+
540
667
  hooks.systemRemove(self, systemInfo)
541
668
 
542
669
  return self
@@ -583,7 +710,7 @@ function Scheduler:replaceSystem(old, new)
583
710
  return self
584
711
  end
585
712
 
586
- --- @method setRunCondition
713
+ --- @method addRunCondition
587
714
  --- @within Scheduler
588
715
  --- @param system System
589
716
  --- @param fn (U...) -> boolean
@@ -591,25 +718,23 @@ end
591
718
  --- Adds a Run Condition which the Scheduler will check before
592
719
  --- this System is ran.
593
720
 
594
- --- @method setRunCondition
721
+ --- @method addRunCondition
595
722
  --- @within Scheduler
596
723
  --- @param phase Phase
597
724
  --- @param fn (U...) -> boolean
598
725
  ---
599
726
  --- Adds a Run Condition which the Scheduler will check before
600
- --- any Systems tagged with this Phase are ran.
727
+ --- any Systems within this Phase are ran.
601
728
 
602
- --- @method setRunCondition
729
+ --- @method addRunCondition
603
730
  --- @within Scheduler
604
731
  --- @param pipeline Pipeline
605
732
  --- @param fn (U...) -> boolean
606
733
  ---
607
734
  --- Adds a Run Condition which the Scheduler will check before
608
735
  --- any Systems within any Phases apart of this Pipeline are ran.\
609
- --- \
610
- --- This Run Condition will be applied to the Phases themselves.
611
736
 
612
- function Scheduler:setRunCondition(dependent, fn)
737
+ function Scheduler:addRunCondition(dependent, fn)
613
738
  local system = getSystem(dependent)
614
739
  if system then
615
740
  dependent = system
@@ -617,111 +742,128 @@ function Scheduler:setRunCondition(dependent, fn)
617
742
 
618
743
  assert(
619
744
  system or isPhase(dependent) or isPipeline(dependent),
620
- "Attempt to pass unknown dependent into Scheduler:setRunCondition(unknown, _)"
745
+ "Attempt to pass unknown dependent into Scheduler:addRunCondition(unknown, _)"
621
746
  )
622
747
 
623
- self._runIfConditions[dependent] = fn
748
+ if not self._runIfConditions[dependent] then
749
+ self._runIfConditions[dependent] = {}
750
+ end
751
+
752
+ table.insert(self._runIfConditions[dependent], fn)
753
+
624
754
  return self
625
755
  end
626
756
 
627
757
  function Scheduler:_addBuiltins()
628
- local i = 0
758
+ self._defaultPhase = Phase.new("Default")
759
+ self._defaultDependencyGraph = DependencyGraph.new()
629
760
 
630
- for _, phase in Pipeline.Startup._phases do
631
- i += 1
632
- self:_insertPhaseAt(phase, i)
633
- end
761
+ self._defaultDependencyGraph:insert(Pipeline.Startup)
762
+ self._defaultDependencyGraph:insert(self._defaultPhase)
634
763
 
635
- for _, phase in Pipeline.Main._phases do
636
- i += 1
637
- self:_insertPhaseAt(phase, i, RunService, "Heartbeat")
764
+ self:addRunCondition(Pipeline.Startup, conditions.runOnce())
765
+ for _, phase in Pipeline.Startup.dependencyGraph.nodes do
766
+ self:addRunCondition(phase, conditions.runOnce())
638
767
  end
768
+ end
639
769
 
640
- local runServiceEvents = {
641
- "PreRender",
642
- "PreAnimation",
643
- "PreSimulation",
644
- "PostSimulation",
645
- }
646
-
647
- for _, event in runServiceEvents do
648
- i += 1
649
- self:_insertPhaseAt(Phase[event], i, RunService, event)
650
- end
770
+ function Scheduler:_scheduleEvent(instance, event)
771
+ local connect = utils.getConnectFunction(instance, event)
772
+ assert(
773
+ connect,
774
+ "Couldn't connect to event as no valid connect methods were found! Ensure the passed event has a 'Connect' or an 'on' method!"
775
+ )
651
776
 
652
- local startupHasRan = {}
777
+ local identifier = getEventIdentifier(instance, event)
653
778
 
654
- for _, phase in Pipeline.Startup._phases do
655
- self:setRunCondition(phase, function()
656
- local hasRan = startupHasRan[phase]
779
+ local dependencyGraph = DependencyGraph.new()
657
780
 
658
- if not hasRan then
659
- startupHasRan[phase] = true
781
+ local callback = function()
782
+ local orderedList = dependencyGraph:getOrderedList()
783
+
784
+ if orderedList == nil then
785
+ local err =
786
+ `Event Group '{identifier}' contains a circular dependency, check your Pipelines/Phases`
787
+ if not recentLogs[err] then
788
+ task.spawn(error, err, 0)
789
+ warn(
790
+ `Planck: Error occurred while running event, this error will be ignored for 10 seconds`
791
+ )
792
+ recentLogs[err] = true
660
793
  end
794
+ end
661
795
 
662
- return not hasRan
663
- end)
796
+ for _, dependency in orderedList do
797
+ self:run(dependency)
798
+ end
664
799
  end
665
- end
666
800
 
667
- local EVENT_CONNECT_METHODS = { "Connect", "On", "on", "connect" }
801
+ self._connectedEvents[identifier] = connect(callback)
802
+ self._eventDependencyGraphs[identifier] = dependencyGraph
803
+ end
668
804
 
669
- -- This is a modified function from Matter by evaera (https://github.com/evaera)
670
- -- License: Copyright (c) 2021 Eryn L. K., MIT License
671
- -- Source: https://github.com/matter-ecs/matter/blob/main/lib/hooks/useEvent.luau
672
- function Scheduler:_scheduleEvent(instance, event)
805
+ function Scheduler:_getEventDependencyGraph(instance, event)
673
806
  local identifier = getEventIdentifier(instance, event)
674
807
 
675
- local callback = function()
676
- for _, phase in self._eventToPhases[identifier] do
677
- self:run(phase)
678
- end
679
- end
680
-
681
- local eventInstance = instance
682
-
683
- if typeof(event) == "RBXScriptSignal" or type(event) == "table" then
684
- eventInstance = event
685
- elseif type(event) == "string" then
686
- eventInstance = instance[event]
808
+ if not self._connectedEvents[identifier] then
809
+ self:_scheduleEvent(instance, event)
687
810
  end
688
811
 
689
- if type(eventInstance) == "function" then
690
- self._connectedEvents[identifier] = eventInstance
691
-
692
- return eventInstance(eventInstance, callback)
693
- elseif typeof(eventInstance) == "RBXScriptSignal" then
694
- self._connectedEvents[identifier] = eventInstance
812
+ return self._eventDependencyGraphs[identifier]
813
+ end
695
814
 
696
- return eventInstance:Connect(callback)
815
+ function Scheduler:_getGraphOfDependency(dependency)
816
+ if table.find(self._defaultDependencyGraph.nodes, dependency) then
817
+ return self._defaultDependencyGraph
697
818
  end
698
819
 
699
- if type(eventInstance) == "table" then
700
- for _, method in EVENT_CONNECT_METHODS do
701
- if type(eventInstance[method]) ~= "function" then
702
- continue
703
- end
704
-
705
- self._connectedEvents[identifier] = eventInstance
706
- return eventInstance[method](eventInstance, callback)
820
+ for _, dependencyGraph in self._eventDependencyGraphs do
821
+ if table.find(dependencyGraph.nodes, dependency) then
822
+ return dependencyGraph
707
823
  end
708
824
  end
709
825
 
710
- error(
711
- "Couldn't connect to event as no valid connect methods were found! Ensure the passed event has a 'Connect' or an 'on' method!"
712
- )
826
+ error("Dependency does not belong to a DependencyGraph")
713
827
  end
714
828
 
715
- function Scheduler:_schedulePhase(phase, instance, event)
716
- local identifier = getEventIdentifier(instance, event)
829
+ --- @within Scheduler
830
+ ---
831
+ --- Disconnects all events, closes all threads, and performs
832
+ --- other cleanup work.
833
+ ---
834
+ --- :::danger
835
+ --- Only use this if you intend to not use the associated
836
+ --- Scheduler anymore. It will not work as intended.
837
+ ---
838
+ --- You should dereference the scheduler object so that
839
+ --- it may be garbage collected.
840
+ --- :::
841
+ ---
842
+ --- :::warning
843
+ --- If you're creating a "throwaway" scheduler, you should
844
+ --- not add plugins like Jabby or the Matter Debugger to it.
845
+ --- These plugins are unable to properly be cleaned up, use
846
+ --- them with caution.
847
+ --- :::
848
+ function Scheduler:cleanup()
849
+ for _, connection in self._connectedEvents do
850
+ utils.disconnectEvent(connection)
851
+ end
852
+
853
+ for _, plugin in self._plugins do
854
+ if plugin.cleanup then
855
+ plugin:cleanup()
856
+ end
857
+ end
717
858
 
718
- if not self._eventToPhases[identifier] then
719
- self._eventToPhases[identifier] = {}
859
+ if self._thread then
860
+ coroutine.close(self._thread)
720
861
  end
721
862
 
722
- table.insert(self._eventToPhases[identifier], phase)
723
- if not self._connectedEvents[identifier] then
724
- self:_scheduleEvent(instance, event)
863
+ for _, _conditions in self._runIfConditions do
864
+ for _, condition in _conditions do
865
+ conditions.cleanupCondition(condition)
866
+ end
725
867
  end
726
868
  end
727
869
 
@@ -738,10 +880,7 @@ function Scheduler.new(...)
738
880
 
739
881
  self._vargs = { ... }
740
882
 
741
- self._orderedPhases = {}
742
- self._orderedPipelines = {}
743
-
744
- self._eventToPhases = {}
883
+ self._eventDependencyGraphs = {}
745
884
  self._connectedEvents = {}
746
885
 
747
886
  self._phaseToSystems = {}
@@ -749,6 +888,8 @@ function Scheduler.new(...)
749
888
 
750
889
  self._runIfConditions = {}
751
890
 
891
+ self._plugins = {}
892
+
752
893
  setmetatable(self, Scheduler)
753
894
 
754
895
  for _, hookName in hooks.Hooks do