@rbxts/planck 0.2.5 → 0.3.0-alpha.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/package.json +40 -51
- package/{out → src}/DependencyGraph.luau +220 -180
- package/src/Phase.d.ts +23 -0
- package/{out → src}/Phase.luau +41 -41
- package/src/Pipeline.d.ts +26 -0
- package/{out → src}/Pipeline.luau +86 -86
- package/src/Scheduler.d.ts +385 -0
- package/{out → src}/Scheduler.luau +207 -44
- package/src/__tests__/InitializerSystems.test.luau +660 -0
- package/src/__tests__/Scheduler.test.luau +313 -0
- package/src/__tests__/conditions.test.luau +147 -0
- package/src/__tests__/hooks.test.luau +54 -0
- package/src/__tests__/systems.test.luau +192 -0
- package/src/conditions.d.ts +69 -0
- package/{out → src}/conditions.luau +189 -151
- package/{out → src}/hooks.luau +163 -145
- package/src/index.d.ts +12 -0
- package/src/init.luau +207 -0
- package/src/utils.d.ts +10 -0
- package/{out → src}/utils.luau +197 -161
- package/out/Phase.d.ts +0 -8
- package/out/Pipeline.d.ts +0 -11
- package/out/Scheduler.d.ts +0 -31
- package/out/conditions.d.ts +0 -14
- package/out/hooks.d.ts +0 -4
- package/out/index.d.ts +0 -18
- package/out/init.luau +0 -143
- package/out/types.d.ts +0 -113
- package/out/utils.d.ts +0 -4
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--!nonstrict
|
|
2
|
-
local DependencyGraph = require(
|
|
3
|
-
local Pipeline = require(
|
|
4
|
-
local Phase = require(
|
|
2
|
+
local DependencyGraph = require(script.Parent.DependencyGraph)
|
|
3
|
+
local Pipeline = require(script.Parent.Pipeline)
|
|
4
|
+
local Phase = require(script.Parent.Phase)
|
|
5
5
|
|
|
6
|
-
local utils = require(
|
|
7
|
-
local hooks = require(
|
|
8
|
-
local conditions = require(
|
|
6
|
+
local utils = require(script.Parent.utils)
|
|
7
|
+
local hooks = require(script.Parent.hooks)
|
|
8
|
+
local conditions = require(script.Parent.conditions)
|
|
9
9
|
|
|
10
10
|
local getSystem = utils.getSystem
|
|
11
11
|
local getSystemName = utils.getSystemName
|
|
@@ -20,13 +20,24 @@ local getEventIdentifier = utils.getEventIdentifier
|
|
|
20
20
|
local recentLogs = {}
|
|
21
21
|
local timeLastLogged = os.clock()
|
|
22
22
|
|
|
23
|
-
--- @type SystemFn ((U...) ->
|
|
23
|
+
--- @type SystemFn ((U...) -> ())
|
|
24
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
|
|
25
34
|
|
|
26
35
|
--- @interface SystemTable
|
|
27
36
|
--- @within Scheduler
|
|
28
|
-
--- .system SystemFn<U...>
|
|
37
|
+
--- .system SystemFn<U...> | InitializerSystemFn<U...>
|
|
29
38
|
--- .phase Phase?
|
|
39
|
+
--- .name string?
|
|
40
|
+
--- .runConditions {RunCondition}?
|
|
30
41
|
--- .[any] any
|
|
31
42
|
|
|
32
43
|
--- @type System SystemFn<U...> | SystemTable<U...>
|
|
@@ -66,6 +77,10 @@ end
|
|
|
66
77
|
--- Returns the time since the system was ran last.
|
|
67
78
|
--- This must be used within a registered system.
|
|
68
79
|
function Scheduler:getDeltaTime()
|
|
80
|
+
if self._currentSystem then
|
|
81
|
+
return self._currentSystem.deltaTime or 0
|
|
82
|
+
end
|
|
83
|
+
|
|
69
84
|
local systemFn = debug.info(2, "f")
|
|
70
85
|
if not systemFn or not self._systemInfo[systemFn] then
|
|
71
86
|
error(
|
|
@@ -91,7 +106,7 @@ function Scheduler:_handleLogs(systemInfo)
|
|
|
91
106
|
systemInfo.recentLogs = {}
|
|
92
107
|
end
|
|
93
108
|
|
|
94
|
-
local name =
|
|
109
|
+
local name = systemInfo.name
|
|
95
110
|
|
|
96
111
|
for _, logMessage in systemInfo.logs do
|
|
97
112
|
if not systemInfo.recentLogs[logMessage] then
|
|
@@ -106,16 +121,26 @@ function Scheduler:_handleLogs(systemInfo)
|
|
|
106
121
|
table.clear(systemInfo.logs)
|
|
107
122
|
end
|
|
108
123
|
|
|
109
|
-
function Scheduler:runSystem(system)
|
|
110
|
-
if self:_canRun(system) == false then
|
|
111
|
-
return
|
|
112
|
-
end
|
|
113
|
-
|
|
124
|
+
function Scheduler:runSystem(system, justInitialized)
|
|
114
125
|
local systemInfo = self._systemInfo[system]
|
|
115
126
|
local now = os.clock()
|
|
116
127
|
|
|
117
|
-
|
|
128
|
+
if not systemInfo then
|
|
129
|
+
error(
|
|
130
|
+
"Attempted to run a non-registered system, make sure it is added to the Scheduler"
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if justInitialized ~= true then
|
|
135
|
+
if self:_canRun(system) == false then
|
|
136
|
+
return
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
systemInfo.deltaTime = now - (systemInfo.lastTime or now)
|
|
140
|
+
end
|
|
141
|
+
|
|
118
142
|
systemInfo.lastTime = now
|
|
143
|
+
self._currentSystem = systemInfo
|
|
119
144
|
|
|
120
145
|
if not self._thread then
|
|
121
146
|
self._thread = coroutine.create(function()
|
|
@@ -131,20 +156,23 @@ function Scheduler:runSystem(system)
|
|
|
131
156
|
end
|
|
132
157
|
|
|
133
158
|
local didYield = false
|
|
159
|
+
local hasSystem = false
|
|
134
160
|
|
|
135
161
|
local function systemCall()
|
|
136
162
|
local function noYield()
|
|
137
|
-
local success,
|
|
163
|
+
local success, errOrSys, cleanup
|
|
138
164
|
coroutine.resume(self._thread, function()
|
|
139
|
-
success,
|
|
165
|
+
success, errOrSys, cleanup = xpcall(function()
|
|
166
|
+
return systemInfo.run(table.unpack(self._vargs))
|
|
167
|
+
end, function(e)
|
|
140
168
|
return debug.traceback(e)
|
|
141
|
-
end
|
|
169
|
+
end)
|
|
142
170
|
end)
|
|
143
171
|
|
|
144
172
|
if success == false then
|
|
145
173
|
didYield = true
|
|
146
|
-
table.insert(systemInfo.logs,
|
|
147
|
-
hooks.systemError(self, systemInfo,
|
|
174
|
+
table.insert(systemInfo.logs, errOrSys)
|
|
175
|
+
hooks.systemError(self, systemInfo, errOrSys)
|
|
148
176
|
return
|
|
149
177
|
end
|
|
150
178
|
|
|
@@ -161,6 +189,55 @@ function Scheduler:runSystem(system)
|
|
|
161
189
|
systemInfo,
|
|
162
190
|
debug.traceback(self._thread, errMessage, 2)
|
|
163
191
|
)
|
|
192
|
+
return
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
if not systemInfo.initialized then
|
|
196
|
+
if errOrSys == nil and cleanup == nil then
|
|
197
|
+
systemInfo.initialized = true
|
|
198
|
+
return
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if type(errOrSys) == "function" then
|
|
202
|
+
systemInfo.run = errOrSys
|
|
203
|
+
systemInfo.initialized = true
|
|
204
|
+
if type(cleanup) == "function" then
|
|
205
|
+
systemInfo.cleanup = cleanup
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
hasSystem = true
|
|
209
|
+
return
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
if type(errOrSys) == "table" then
|
|
213
|
+
hasSystem = type(errOrSys.system) == "function"
|
|
214
|
+
local hasCleanup = type(errOrSys.cleanup) == "function"
|
|
215
|
+
|
|
216
|
+
if hasSystem or hasCleanup then
|
|
217
|
+
if hasSystem then
|
|
218
|
+
systemInfo.run = errOrSys.system
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
if hasCleanup then
|
|
222
|
+
systemInfo.cleanup = errOrSys.cleanup
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
systemInfo.initialized = true
|
|
226
|
+
return
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
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)
|
|
240
|
+
systemInfo.initialized = true
|
|
164
241
|
end
|
|
165
242
|
end
|
|
166
243
|
|
|
@@ -210,6 +287,11 @@ function Scheduler:runSystem(system)
|
|
|
210
287
|
end
|
|
211
288
|
|
|
212
289
|
self:_handleLogs(systemInfo)
|
|
290
|
+
self._currentSystem = nil
|
|
291
|
+
|
|
292
|
+
if hasSystem and justInitialized ~= true then
|
|
293
|
+
self:runSystem(system, true)
|
|
294
|
+
end
|
|
213
295
|
end
|
|
214
296
|
|
|
215
297
|
function Scheduler:runPhase(phase)
|
|
@@ -278,7 +360,6 @@ end
|
|
|
278
360
|
--- @return Scheduler
|
|
279
361
|
---
|
|
280
362
|
--- Runs the System, passing in the arguments of the Scheduler, `U...`.
|
|
281
|
-
|
|
282
363
|
function Scheduler:run(dependent)
|
|
283
364
|
if not dependent then
|
|
284
365
|
error("No dependent specified in Scheduler:run(_)")
|
|
@@ -391,7 +472,6 @@ end
|
|
|
391
472
|
--- local myScheduler = Scheduler.new()
|
|
392
473
|
--- :insert(myPipeline, RunService, "Heartbeat")
|
|
393
474
|
--- ```
|
|
394
|
-
|
|
395
475
|
function Scheduler:insert(dependency, instance, event)
|
|
396
476
|
assert(
|
|
397
477
|
isPhase(dependency) or isPipeline(dependency),
|
|
@@ -437,7 +517,6 @@ end
|
|
|
437
517
|
--- Initializes the Pipeline and it's Phases within the Scheduler,
|
|
438
518
|
--- ordering the Pipeline explicitly by setting the after Phase/Pipeline
|
|
439
519
|
--- as a dependent.
|
|
440
|
-
|
|
441
520
|
function Scheduler:insertAfter(dependent, after)
|
|
442
521
|
assert(
|
|
443
522
|
isPhase(after) or isPipeline(after),
|
|
@@ -477,7 +556,6 @@ end
|
|
|
477
556
|
--- Initializes the Pipeline and it's Phases within the Scheduler,
|
|
478
557
|
--- ordering the Pipeline explicitly by setting the before Phase/Pipeline
|
|
479
558
|
--- as a dependency.
|
|
480
|
-
|
|
481
559
|
function Scheduler:insertBefore(dependent, before)
|
|
482
560
|
assert(
|
|
483
561
|
isPhase(before) or isPipeline(before),
|
|
@@ -499,13 +577,34 @@ function Scheduler:insertBefore(dependent, before)
|
|
|
499
577
|
return self
|
|
500
578
|
end
|
|
501
579
|
|
|
502
|
-
--- @method
|
|
580
|
+
--- @method addSystem
|
|
503
581
|
--- @within Scheduler
|
|
504
|
-
--- @param
|
|
582
|
+
--- @param system System
|
|
505
583
|
--- @param phase Phase?
|
|
584
|
+
--- @return Scheduler
|
|
506
585
|
---
|
|
507
586
|
--- Adds the System to the Scheduler, scheduling it to be ran
|
|
508
587
|
--- implicitly within the provided Phase or on the default Main phase.
|
|
588
|
+
---
|
|
589
|
+
--- **Initializer Systems**: Systems can optionally return a function on their
|
|
590
|
+
--- first execution, which becomes the runtime system. This allows one-time
|
|
591
|
+
--- setup logic without creating separate initialization phases.
|
|
592
|
+
---
|
|
593
|
+
--- ```lua
|
|
594
|
+
--- local function renderSystem(world, state)
|
|
595
|
+
--- -- This runs once on first execution
|
|
596
|
+
--- local renderables = world:query(Transform, Model):cached()
|
|
597
|
+
---
|
|
598
|
+
--- -- This runs on each subsequent execution
|
|
599
|
+
--- return function(world, state)
|
|
600
|
+
--- for id, transform, model in renderables do
|
|
601
|
+
--- render(transform, model)
|
|
602
|
+
--- end
|
|
603
|
+
--- end, function()
|
|
604
|
+
--- -- Optional cleanup logic runs on removeSystem
|
|
605
|
+
--- end
|
|
606
|
+
--- end
|
|
607
|
+
--- ```
|
|
509
608
|
function Scheduler:addSystem(system, phase)
|
|
510
609
|
local systemFn = getSystem(system)
|
|
511
610
|
|
|
@@ -513,26 +612,27 @@ function Scheduler:addSystem(system, phase)
|
|
|
513
612
|
error("Unknown system passed to Scheduler:addSystem(unknown, phase?)")
|
|
514
613
|
end
|
|
515
614
|
|
|
516
|
-
local name = getSystemName(
|
|
517
|
-
|
|
518
|
-
|
|
615
|
+
local name = getSystemName(system)
|
|
616
|
+
|
|
617
|
+
local scheduledPhase
|
|
618
|
+
if phase then
|
|
619
|
+
scheduledPhase = phase
|
|
620
|
+
elseif type(system) == "table" and system.phase then
|
|
621
|
+
scheduledPhase = system.phase
|
|
622
|
+
else
|
|
623
|
+
scheduledPhase = self._defaultPhase
|
|
519
624
|
end
|
|
520
625
|
|
|
521
626
|
local systemInfo = {
|
|
522
627
|
system = systemFn,
|
|
523
|
-
|
|
628
|
+
run = systemFn,
|
|
629
|
+
cleanup = nil,
|
|
630
|
+
phase = scheduledPhase,
|
|
524
631
|
name = name,
|
|
525
632
|
logs = {},
|
|
633
|
+
initialized = false,
|
|
526
634
|
}
|
|
527
635
|
|
|
528
|
-
if not phase then
|
|
529
|
-
if type(system) == "table" and system.phase then
|
|
530
|
-
systemInfo.phase = system.phase
|
|
531
|
-
else
|
|
532
|
-
systemInfo.phase = self._defaultPhase
|
|
533
|
-
end
|
|
534
|
-
end
|
|
535
|
-
|
|
536
636
|
self._systemInfo[systemFn] = systemInfo
|
|
537
637
|
|
|
538
638
|
if not self._phaseToSystems[systemInfo.phase] then
|
|
@@ -545,6 +645,9 @@ function Scheduler:addSystem(system, phase)
|
|
|
545
645
|
|
|
546
646
|
if type(system) == "table" and system.runConditions then
|
|
547
647
|
for _, condition in system.runConditions do
|
|
648
|
+
condition = if typeof(condition) == "table"
|
|
649
|
+
then condition[1]
|
|
650
|
+
else condition
|
|
548
651
|
self:addRunCondition(systemFn, condition)
|
|
549
652
|
end
|
|
550
653
|
end
|
|
@@ -599,7 +702,7 @@ function Scheduler:editSystem(system, newPhase)
|
|
|
599
702
|
local systemInfo = self._systemInfo[systemFn]
|
|
600
703
|
assert(
|
|
601
704
|
systemInfo,
|
|
602
|
-
"Attempt to
|
|
705
|
+
"Attempt to edit a non-existent system in Scheduler:editSystem(_)"
|
|
603
706
|
)
|
|
604
707
|
|
|
605
708
|
assert(
|
|
@@ -638,16 +741,56 @@ end
|
|
|
638
741
|
--- @method removeSystem
|
|
639
742
|
--- @within Scheduler
|
|
640
743
|
--- @param system System
|
|
744
|
+
--- @return Scheduler
|
|
641
745
|
---
|
|
642
746
|
--- Removes the System from the Scheduler.
|
|
747
|
+
---
|
|
748
|
+
--- If the system provided a cleanup function during initialization,
|
|
749
|
+
--- that cleanup function will be executed before removal.
|
|
750
|
+
---
|
|
751
|
+
--- ```lua
|
|
752
|
+
--- -- System with cleanup
|
|
753
|
+
--- local function networkSystem(world, state)
|
|
754
|
+
--- local connection = Players.PlayerAdded:Connect(function(player)
|
|
755
|
+
--- -- Player joined logic
|
|
756
|
+
--- end)
|
|
757
|
+
---
|
|
758
|
+
--- return function(world, state)
|
|
759
|
+
--- -- Runtime logic
|
|
760
|
+
--- end, function()
|
|
761
|
+
--- -- Cleanup runs on removeSystem
|
|
762
|
+
--- connection:Disconnect()
|
|
763
|
+
--- end
|
|
764
|
+
--- end
|
|
765
|
+
---
|
|
766
|
+
--- scheduler:addSystem(networkSystem, Phase.Update)
|
|
767
|
+
--- -- Later...
|
|
768
|
+
--- scheduler:removeSystem(networkSystem) -- Cleanup executes here
|
|
769
|
+
--- ```
|
|
643
770
|
function Scheduler:removeSystem(system)
|
|
644
771
|
local systemFn = getSystem(system)
|
|
645
772
|
local systemInfo = self._systemInfo[systemFn]
|
|
646
773
|
assert(
|
|
647
774
|
systemInfo,
|
|
648
|
-
"Attempt to remove a non-
|
|
775
|
+
"Attempt to remove a non-existent system in Scheduler:removeSystem(_)"
|
|
649
776
|
)
|
|
650
777
|
|
|
778
|
+
if systemInfo.cleanup then
|
|
779
|
+
local success, err =
|
|
780
|
+
pcall(systemInfo.cleanup, table.unpack(self._vargs))
|
|
781
|
+
if success then
|
|
782
|
+
hooks.systemCleanup(self, systemInfo, nil)
|
|
783
|
+
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)
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
|
|
651
794
|
local systems = self._phaseToSystems[systemInfo.phase]
|
|
652
795
|
|
|
653
796
|
local index = table.find(systems, systemFn)
|
|
@@ -689,6 +832,22 @@ function Scheduler:replaceSystem(old, new)
|
|
|
689
832
|
"Attempt to pass non-system in Scheduler:replaceSystem(_, unknown)"
|
|
690
833
|
)
|
|
691
834
|
|
|
835
|
+
if oldSystemInfo.cleanup then
|
|
836
|
+
local success, err =
|
|
837
|
+
pcall(oldSystemInfo.cleanup, table.unpack(self._vargs))
|
|
838
|
+
if success then
|
|
839
|
+
hooks.systemCleanup(self, oldSystemInfo, nil)
|
|
840
|
+
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)
|
|
848
|
+
end
|
|
849
|
+
end
|
|
850
|
+
|
|
692
851
|
local systems = self._phaseToSystems[oldSystemInfo.phase]
|
|
693
852
|
|
|
694
853
|
local index = table.find(systems, oldSystemFn)
|
|
@@ -700,7 +859,10 @@ function Scheduler:replaceSystem(old, new)
|
|
|
700
859
|
local copy = table.clone(oldSystemInfo)
|
|
701
860
|
|
|
702
861
|
oldSystemInfo.system = newSystemFn
|
|
703
|
-
oldSystemInfo.
|
|
862
|
+
oldSystemInfo.run = newSystemFn
|
|
863
|
+
oldSystemInfo.cleanup = nil
|
|
864
|
+
oldSystemInfo.initialized = false
|
|
865
|
+
oldSystemInfo.name = getSystemName(new)
|
|
704
866
|
|
|
705
867
|
hooks.systemReplace(self, copy, oldSystemInfo)
|
|
706
868
|
|
|
@@ -732,9 +894,10 @@ end
|
|
|
732
894
|
--- @param fn (U...) -> boolean
|
|
733
895
|
---
|
|
734
896
|
--- Adds a Run Condition which the Scheduler will check before
|
|
735
|
-
--- any Systems within any Phases apart of this Pipeline are ran
|
|
736
|
-
|
|
897
|
+
--- any Systems within any Phases apart of this Pipeline are ran.
|
|
737
898
|
function Scheduler:addRunCondition(dependent, fn)
|
|
899
|
+
fn = if typeof(fn) == "table" then fn[1] else fn
|
|
900
|
+
|
|
738
901
|
local system = getSystem(dependent)
|
|
739
902
|
if system then
|
|
740
903
|
dependent = system
|
|
@@ -903,4 +1066,4 @@ function Scheduler.new(...)
|
|
|
903
1066
|
return self
|
|
904
1067
|
end
|
|
905
1068
|
|
|
906
|
-
return Scheduler
|
|
1069
|
+
return Scheduler
|