@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.
@@ -1,11 +1,11 @@
1
1
  --!nonstrict
2
- local DependencyGraph = require("./DependencyGraph")
3
- local Pipeline = require("./Pipeline")
4
- local Phase = require("./Phase")
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("./utils")
7
- local hooks = require("./hooks")
8
- local conditions = require("./conditions")
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...) -> any)
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 = debug.info(systemInfo.system, "n")
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
- systemInfo.deltaTime = now - (systemInfo.lastTime or now)
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, err
163
+ local success, errOrSys, cleanup
138
164
  coroutine.resume(self._thread, function()
139
- success, err = xpcall(system, function(e)
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, table.unpack(self._vargs))
169
+ end)
142
170
  end)
143
171
 
144
172
  if success == false then
145
173
  didYield = true
146
- table.insert(systemInfo.logs, err)
147
- hooks.systemError(self, systemInfo, err)
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 addSystems
580
+ --- @method addSystem
503
581
  --- @within Scheduler
504
- --- @param systems System
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(systemFn)
517
- if type(system) == "table" and system.name then
518
- name = system.name
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
- phase = phase,
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 remove a non-exist system in Scheduler:removeSystem(_)"
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-exist system in Scheduler:removeSystem(_)"
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.name = getSystemName(newSystemFn)
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