@flamework-experimental/core 2.0.0-alpha.0 → 2.0.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/README.md +6 -0
- package/flamework.build +3 -3
- package/out/flamework.luau +8 -0
- package/out/lifecycle/lifecyclePlugin.d.ts +162 -1
- package/out/lifecycle/lifecyclePlugin.luau +569 -95
- package/out/module/module.luau +213 -19
- package/out/plugin/pluginDefinition.d.ts +7 -0
- package/out/reflect.luau +11 -0
- package/out/utility/getClassImplements.d.ts +9 -2
- package/out/utility/getClassImplements.luau +89 -6
- package/out/utility/implementsCache.d.ts +16 -0
- package/out/utility/implementsCache.luau +35 -0
- package/out/utility/recycleThread.d.ts +12 -0
- package/out/utility/recycleThread.luau +29 -10
- package/out/utility/runtimeConfig.d.ts +2 -0
- package/out/utility/threadWaits.d.ts +36 -0
- package/out/utility/threadWaits.luau +81 -0
- package/package.json +1 -1
package/out/module/module.luau
CHANGED
|
@@ -5,6 +5,9 @@ local convertConciseDependencyInfo = TS.import(script, script.Parent.Parent, "ut
|
|
|
5
5
|
local getClassImplements = TS.import(script, script.Parent.Parent, "utility", "getClassImplements").getClassImplements
|
|
6
6
|
local getClassesInPath = TS.import(script, script.Parent.Parent, "utility", "getClassesInPath").getClassesInPath
|
|
7
7
|
local getClassesInGlob = TS.import(script, script.Parent.Parent, "utility", "globs").getClassesInGlob
|
|
8
|
+
local _threadWaits = TS.import(script, script.Parent.Parent, "utility", "threadWaits")
|
|
9
|
+
local extinguishesBegun = _threadWaits.extinguishesBegun
|
|
10
|
+
local threadWaits = _threadWaits.threadWaits
|
|
8
11
|
local clearDefaultModule = TS.import(script, script.Parent, "defaultModule").clearDefaultModule
|
|
9
12
|
local HookPriority = TS.import(script, script.Parent, "moduleHooks").HookPriority
|
|
10
13
|
local _providerRegistration = TS.import(script, script.Parent, "providerRegistration")
|
|
@@ -88,12 +91,24 @@ local function createModuleInstantiation(state, options)
|
|
|
88
91
|
local instantiatedProviders = {}
|
|
89
92
|
--* Objects handed over by `provideInstance`, attached to their interfaces once every plugin is set up.
|
|
90
93
|
local providedInstances = {}
|
|
94
|
+
--[[
|
|
95
|
+
*
|
|
96
|
+
* Provided objects that have not joined their interfaces: until the ignition reaches them, and
|
|
97
|
+
* for good when an observer refuses one. `release` has nothing to take them out of, and a failed
|
|
98
|
+
* ignition told every observer `onRemoved` for objects it had never been told of, and told the
|
|
99
|
+
* observers that had just undone a refused one of it again.
|
|
100
|
+
|
|
101
|
+
]]
|
|
102
|
+
local unjoinedInstances = {}
|
|
91
103
|
local observers = {}
|
|
92
104
|
local hooks = {}
|
|
93
105
|
local includedPlugins = {}
|
|
94
106
|
local filledSlots = {}
|
|
95
107
|
local temporaryInstances = {}
|
|
96
108
|
local moduleInitState = ModuleInitState.Created
|
|
109
|
+
--* The thread `extinguish` runs on, and what wakes each thread waiting in `awaitExtinguished` for it to finish.
|
|
110
|
+
local extinguishingThread
|
|
111
|
+
local extinguishWaiters = {}
|
|
97
112
|
local switchInitState = function(from, to)
|
|
98
113
|
if moduleInitState ~= from then
|
|
99
114
|
error(`module '{state.debugName}' is in invalid state when transitioning to '{ModuleInitState[to]}', got '{ModuleInitState[moduleInitState]}' when '{ModuleInitState[from]}' was expected.`)
|
|
@@ -292,12 +307,18 @@ local function createModuleInstantiation(state, options)
|
|
|
292
307
|
* observer that raises from its `onAdded` has the ones before it told `onRemoved`, and the
|
|
293
308
|
* error comes out, so a refused object is attached nowhere -- rather than left ticking in the
|
|
294
309
|
* lifecycle's sets, with no handle to remove it by.
|
|
310
|
+
*
|
|
311
|
+
* `dependencies`, what a provider's constructor was given, is handed to the observers with it.
|
|
295
312
|
|
|
296
313
|
]]
|
|
297
|
-
local registerClassInterfaces = function(instance, kind)
|
|
298
|
-
local
|
|
314
|
+
local registerClassInterfaces = function(instance, kind, dependencies)
|
|
315
|
+
local interfaces = getClassImplements(instance)
|
|
316
|
+
-- How many observers have been told `onAdded`, in the order they were told: what a refusal
|
|
317
|
+
-- undoes. Counted rather than recorded, since an attachment that goes through -- every one,
|
|
318
|
+
-- in a game -- then builds nothing to throw away.
|
|
319
|
+
local attached = 0
|
|
299
320
|
local success, err = pcall(function()
|
|
300
|
-
for _, interfaceId in
|
|
321
|
+
for _, interfaceId in interfaces do
|
|
301
322
|
local interested = observers[interfaceId]
|
|
302
323
|
if not interested then
|
|
303
324
|
continue
|
|
@@ -308,18 +329,35 @@ local function createModuleInstantiation(state, options)
|
|
|
308
329
|
_result_1(instance, {
|
|
309
330
|
interfaceId = interfaceId,
|
|
310
331
|
kind = kind,
|
|
332
|
+
dependencies = dependencies,
|
|
311
333
|
})
|
|
312
334
|
end
|
|
313
|
-
|
|
314
|
-
table.insert(added, _arg0)
|
|
335
|
+
attached += 1
|
|
315
336
|
end
|
|
316
337
|
end
|
|
317
338
|
end)
|
|
318
339
|
if not success then
|
|
340
|
+
-- The observers told, found again by walking the same way as far as the count goes.
|
|
341
|
+
local told = {}
|
|
342
|
+
for _, interfaceId in interfaces do
|
|
343
|
+
local interested = observers[interfaceId]
|
|
344
|
+
if not interested then
|
|
345
|
+
continue
|
|
346
|
+
end
|
|
347
|
+
for _1, observer in interested do
|
|
348
|
+
if #told == attached then
|
|
349
|
+
break
|
|
350
|
+
end
|
|
351
|
+
local _arg0 = { interfaceId, observer }
|
|
352
|
+
table.insert(told, _arg0)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
319
355
|
-- Undone in reverse, each step guarded, so that one `onRemoved` raising does not leave
|
|
320
|
-
-- the rest attached; the observer's error is what comes out.
|
|
321
|
-
|
|
322
|
-
|
|
356
|
+
-- the rest attached; the observer's error is what comes out. Marked as refused, so that
|
|
357
|
+
-- an observer owing a departing object a last event -- the lifecycle plugin's
|
|
358
|
+
-- `onExtinguished` while the module extinguishes -- does not deliver it.
|
|
359
|
+
for i = #told - 1, 0, -1 do
|
|
360
|
+
local _binding = told[i + 1]
|
|
323
361
|
local interfaceId = _binding[1]
|
|
324
362
|
local observer = _binding[2]
|
|
325
363
|
guarded("undoing an attachment", function()
|
|
@@ -328,6 +366,7 @@ local function createModuleInstantiation(state, options)
|
|
|
328
366
|
_result_1 = _result_1(instance, {
|
|
329
367
|
interfaceId = interfaceId,
|
|
330
368
|
kind = kind,
|
|
369
|
+
refused = true,
|
|
331
370
|
})
|
|
332
371
|
end
|
|
333
372
|
return _result_1
|
|
@@ -353,11 +392,15 @@ local function createModuleInstantiation(state, options)
|
|
|
353
392
|
end
|
|
354
393
|
end
|
|
355
394
|
end
|
|
395
|
+
--* Constructs a class, resolving what its constructor takes into `resolvedParameters`.
|
|
356
396
|
local resolveDependencyWithOrigin
|
|
357
|
-
local instantiateClassWithDependencies = function(constructor, resolve)
|
|
397
|
+
local instantiateClassWithDependencies = function(constructor, resolve, resolvedParameters)
|
|
398
|
+
if resolvedParameters == nil then
|
|
399
|
+
resolvedParameters = {}
|
|
400
|
+
end
|
|
358
401
|
local dependencies = Reflect.getMetadata(constructor, "flamework:dependencies") or {}
|
|
359
|
-
local resolvedParameters = {}
|
|
360
402
|
for _, dependency in dependencies do
|
|
403
|
+
local _resolvedParameters = resolvedParameters
|
|
361
404
|
local _result_1 = resolve
|
|
362
405
|
if _result_1 ~= nil then
|
|
363
406
|
_result_1 = _result_1(dependency)
|
|
@@ -366,7 +409,7 @@ local function createModuleInstantiation(state, options)
|
|
|
366
409
|
if _condition_1 == nil then
|
|
367
410
|
_condition_1 = resolveDependencyWithOrigin(dependency, constructor)
|
|
368
411
|
end
|
|
369
|
-
table.insert(
|
|
412
|
+
table.insert(_resolvedParameters, _condition_1)
|
|
370
413
|
end
|
|
371
414
|
return constructor.new(unpack(resolvedParameters))
|
|
372
415
|
end
|
|
@@ -405,10 +448,21 @@ local function createModuleInstantiation(state, options)
|
|
|
405
448
|
if moduleProvider then
|
|
406
449
|
local config = moduleProvider.config
|
|
407
450
|
if config.type == "class" then
|
|
408
|
-
local
|
|
451
|
+
local dependencies = {}
|
|
452
|
+
local instantiatedProvider = instantiateClassWithDependencies(config.value, nil, dependencies)
|
|
409
453
|
local _id_1 = info.id
|
|
410
454
|
instantiatedProviders[_id_1] = instantiatedProvider
|
|
411
|
-
|
|
455
|
+
-- Held only if it is attached: one an observer refuses is attached nowhere, so it must
|
|
456
|
+
-- not be cached either -- the next resolve handed it out with no lifecycle at all, and
|
|
457
|
+
-- `release` told every observer, the refusing one included, it was removed again.
|
|
458
|
+
local attached, err = pcall(function()
|
|
459
|
+
return registerClassInterfaces(instantiatedProvider, "provider", dependencies)
|
|
460
|
+
end)
|
|
461
|
+
if not attached then
|
|
462
|
+
local _id_2 = info.id
|
|
463
|
+
instantiatedProviders[_id_2] = nil
|
|
464
|
+
error(err, 0)
|
|
465
|
+
end
|
|
412
466
|
return instantiatedProvider
|
|
413
467
|
elseif config.type == "function" then
|
|
414
468
|
-- Function providers are not cached.
|
|
@@ -602,6 +656,7 @@ local function createModuleInstantiation(state, options)
|
|
|
602
656
|
-- every plugin's observers are in place.
|
|
603
657
|
for _, instance in providedInstances do
|
|
604
658
|
registerClassInterfaces(instance, "provider")
|
|
659
|
+
unjoinedInstances[instance] = nil
|
|
605
660
|
end
|
|
606
661
|
for _, provider in providers do
|
|
607
662
|
-- Lazy providers are constructed the first time they are resolved instead.
|
|
@@ -610,6 +665,15 @@ local function createModuleInstantiation(state, options)
|
|
|
610
665
|
end
|
|
611
666
|
end
|
|
612
667
|
runHooks("postIgnite")
|
|
668
|
+
-- Checked again once everything has run: an `onInit` that yields lets other threads run in
|
|
669
|
+
-- the middle of ignition, and one that extinguished an import then did not see this module
|
|
670
|
+
-- among its importers, since it joins them only below. Carrying on left it ignited onto a
|
|
671
|
+
-- released import that would never take it down.
|
|
672
|
+
for _, imported in imports do
|
|
673
|
+
if not imported.isIgnited() then
|
|
674
|
+
error(`module '{state.debugName}': imported module '{imported.debugName}' was extinguished while this module was igniting`)
|
|
675
|
+
end
|
|
676
|
+
end
|
|
613
677
|
end)
|
|
614
678
|
if not success then
|
|
615
679
|
moduleInitState = ModuleInitState.Extinguishing
|
|
@@ -621,12 +685,29 @@ local function createModuleInstantiation(state, options)
|
|
|
621
685
|
for _, imported in imports do
|
|
622
686
|
imported.addImporter(module)
|
|
623
687
|
end
|
|
688
|
+
-- Ignition has completed, so this is where the lifecycle plugin starts the providers: an
|
|
689
|
+
-- `onStart` sees the module ignited, may extinguish it, and may ignite one that imports it.
|
|
690
|
+
-- Nothing here can fail the ignition, so a hook that raises is warned about. One that
|
|
691
|
+
-- extinguishes the module ends the phase: the hooks after it would set up a module gone.
|
|
692
|
+
for _, hook in sortedHooks("ignited") do
|
|
693
|
+
if moduleInitState ~= ModuleInitState.Ignited then
|
|
694
|
+
break
|
|
695
|
+
end
|
|
696
|
+
guarded("an onIgnited hook", function()
|
|
697
|
+
return hook.callback(module)
|
|
698
|
+
end)
|
|
699
|
+
end
|
|
624
700
|
return module
|
|
625
701
|
end
|
|
626
702
|
local extinguish = function()
|
|
627
703
|
switchInitState(ModuleInitState.Ignited, ModuleInitState.Extinguishing)
|
|
704
|
+
extinguishingThread = coroutine.running()
|
|
705
|
+
extinguishesBegun.count += 1
|
|
628
706
|
-- Importers hold this module's instances, so they go first, and each takes its own importers
|
|
629
|
-
-- down before it returns: the deepest goes first. Copied, since each removes itself.
|
|
707
|
+
-- down before it returns: the deepest goes first. Copied, since each removes itself. One
|
|
708
|
+
-- extinguishing already, on another thread suspended in a handler that yields, is still
|
|
709
|
+
-- using this module's providers, so it is waited for: skipped, it had this module released
|
|
710
|
+
-- under it.
|
|
630
711
|
local _array_2 = {}
|
|
631
712
|
local _length_1 = #_array_2
|
|
632
713
|
for _v in importers do
|
|
@@ -636,6 +717,8 @@ local function createModuleInstantiation(state, options)
|
|
|
636
717
|
for _, importer in _array_2 do
|
|
637
718
|
if not importer.isExtinguished() then
|
|
638
719
|
importer.extinguish()
|
|
720
|
+
else
|
|
721
|
+
importer.awaitExtinguished()
|
|
639
722
|
end
|
|
640
723
|
end
|
|
641
724
|
table.clear(importers)
|
|
@@ -674,10 +757,31 @@ local function createModuleInstantiation(state, options)
|
|
|
674
757
|
-- Providers join their interfaces when they are instantiated, so they have to leave them
|
|
675
758
|
-- too. Without this, a plugin such as the lifecycle plugin keeps holding (and ticking)
|
|
676
759
|
-- providers that belong to an extinguished module.
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
760
|
+
--
|
|
761
|
+
-- Over a copy, taken again until nothing new turns up: a removal runs user code -- the
|
|
762
|
+
-- lifecycle plugin tells a provider resolved since its walk `onExtinguished` as it leaves --
|
|
763
|
+
-- which may resolve a lazy provider for the first time, and a table gaining keys while it
|
|
764
|
+
-- is walked skipped providers, or had others unregistered twice. By object, not by id: an
|
|
765
|
+
-- object provided under several ids joined its interfaces once, so it leaves them once.
|
|
766
|
+
local released = {}
|
|
767
|
+
while true do
|
|
768
|
+
local remaining = {}
|
|
769
|
+
for _, provider in instantiatedProviders do
|
|
770
|
+
if not (released[provider] ~= nil) then
|
|
771
|
+
released[provider] = true
|
|
772
|
+
if not (unjoinedInstances[provider] ~= nil) then
|
|
773
|
+
table.insert(remaining, provider)
|
|
774
|
+
end
|
|
775
|
+
end
|
|
776
|
+
end
|
|
777
|
+
if #remaining == 0 then
|
|
778
|
+
break
|
|
779
|
+
end
|
|
780
|
+
for _, provider in remaining do
|
|
781
|
+
guarded("removing a provider", function()
|
|
782
|
+
return unregisterClassInterfaces(provider, "provider")
|
|
783
|
+
end)
|
|
784
|
+
end
|
|
681
785
|
end
|
|
682
786
|
-- A removal that raised leaves its instance behind, and nothing can add one any more.
|
|
683
787
|
table.clear(temporaryInstances)
|
|
@@ -689,6 +793,80 @@ local function createModuleInstantiation(state, options)
|
|
|
689
793
|
-- Released last, once nothing in here can resolve any more, so that the next root ignited
|
|
690
794
|
-- becomes the default rather than `Dependency<T>()` answering from a dead module.
|
|
691
795
|
clearDefaultModule(module)
|
|
796
|
+
for _, wake in extinguishWaiters do
|
|
797
|
+
wake()
|
|
798
|
+
end
|
|
799
|
+
table.clear(extinguishWaiters)
|
|
800
|
+
end
|
|
801
|
+
local awaitExtinguished = function()
|
|
802
|
+
if moduleInitState ~= ModuleInitState.Extinguishing then
|
|
803
|
+
return nil
|
|
804
|
+
end
|
|
805
|
+
-- On the extinguish's own thread -- one of its handlers extinguishing an import -- it cannot
|
|
806
|
+
-- finish until this returns, so waiting would never end. Nor on a thread the extinguish's
|
|
807
|
+
-- thread is itself waiting on here, however many waits away: two extinguishes, each waiting
|
|
808
|
+
-- for a module the other is taking down, held each other -- and every module between them,
|
|
809
|
+
-- still ticking -- for good. The wait that would close the circle is skipped, as the wait on
|
|
810
|
+
-- the extinguish's own thread is. The chain runs through an ignition waiting for an `onInit`
|
|
811
|
+
-- on its own thread too, which the lifecycle plugin records -- and through one waiting for the
|
|
812
|
+
-- Promise an `onInit` returned, whose work runs on a thread nothing can name: an `async`
|
|
813
|
+
-- method's body runs on one of its own. A chain that ends at one is taken to close the circle,
|
|
814
|
+
-- since the thread that settles it may be this one.
|
|
815
|
+
local running = coroutine.running()
|
|
816
|
+
local thread = extinguishingThread
|
|
817
|
+
local closesCircle = function()
|
|
818
|
+
local waitedOn = thread
|
|
819
|
+
while waitedOn ~= nil do
|
|
820
|
+
local _condition_1 = waitedOn == running
|
|
821
|
+
if not _condition_1 then
|
|
822
|
+
local _waitedOn = waitedOn
|
|
823
|
+
_condition_1 = not (type(_waitedOn) == "thread")
|
|
824
|
+
end
|
|
825
|
+
if _condition_1 then
|
|
826
|
+
return true
|
|
827
|
+
end
|
|
828
|
+
local _waitedOn = waitedOn
|
|
829
|
+
waitedOn = threadWaits[_waitedOn]
|
|
830
|
+
end
|
|
831
|
+
return false
|
|
832
|
+
end
|
|
833
|
+
if closesCircle() then
|
|
834
|
+
return nil
|
|
835
|
+
end
|
|
836
|
+
-- Nor on a thread that has died -- cancelled, as the testing runner cancels a body that
|
|
837
|
+
-- overran, or closed: that extinguish never finishes, and waiting for it held this module,
|
|
838
|
+
-- and everything in it, for good. One that dies while this waits is noticed within a frame.
|
|
839
|
+
if coroutine.status(thread) == "dead" then
|
|
840
|
+
return nil
|
|
841
|
+
end
|
|
842
|
+
local woken = false
|
|
843
|
+
local wake = function()
|
|
844
|
+
if woken then
|
|
845
|
+
return nil
|
|
846
|
+
end
|
|
847
|
+
woken = true
|
|
848
|
+
-- Removed here rather than by this thread once it resumes: one cancelled while it waits
|
|
849
|
+
-- never does, and left its entry behind for good.
|
|
850
|
+
threadWaits[running] = nil
|
|
851
|
+
if coroutine.status(running) ~= "dead" then
|
|
852
|
+
task.spawn(running)
|
|
853
|
+
end
|
|
854
|
+
end
|
|
855
|
+
threadWaits[running] = thread
|
|
856
|
+
table.insert(extinguishWaiters, wake)
|
|
857
|
+
-- Checked again every frame, as well as for a dead extinguish: this thread may be cancelled
|
|
858
|
+
-- while it waits, and the chain may come to close the circle after the wait began -- an
|
|
859
|
+
-- `async` `onInit`'s body waits here before the ignition that called it takes the Promise it
|
|
860
|
+
-- returned and waits on that.
|
|
861
|
+
task.spawn(function()
|
|
862
|
+
while not woken do
|
|
863
|
+
task.wait()
|
|
864
|
+
if coroutine.status(thread) == "dead" or coroutine.status(running) == "dead" or closesCircle() then
|
|
865
|
+
wake()
|
|
866
|
+
end
|
|
867
|
+
end
|
|
868
|
+
end)
|
|
869
|
+
coroutine.yield()
|
|
692
870
|
end
|
|
693
871
|
local isExtinguished = function()
|
|
694
872
|
return moduleInitState >= ModuleInitState.Extinguishing
|
|
@@ -721,6 +899,7 @@ local function createModuleInstantiation(state, options)
|
|
|
721
899
|
-- ▲ Set.delete ▲
|
|
722
900
|
return _valueExisted
|
|
723
901
|
end,
|
|
902
|
+
awaitExtinguished = awaitExtinguished,
|
|
724
903
|
}
|
|
725
904
|
--* What a plugin's setup is handed. Everything registers into this instantiation.
|
|
726
905
|
pluginTarget = {
|
|
@@ -760,8 +939,15 @@ local function createModuleInstantiation(state, options)
|
|
|
760
939
|
local _injectionId = injectionId
|
|
761
940
|
local _instance = instance
|
|
762
941
|
instantiatedProviders[_injectionId] = _instance
|
|
942
|
+
-- Once, however many ids it is provided under: it joins its interfaces once per entry,
|
|
943
|
+
-- and the same object twice was initialised, started and told `onAdded` twice.
|
|
763
944
|
local _instance_1 = instance
|
|
764
|
-
table.
|
|
945
|
+
if not (table.find(providedInstances, _instance_1) ~= nil) then
|
|
946
|
+
local _instance_2 = instance
|
|
947
|
+
table.insert(providedInstances, _instance_2)
|
|
948
|
+
local _instance_3 = instance
|
|
949
|
+
unjoinedInstances[_instance_3] = true
|
|
950
|
+
end
|
|
765
951
|
end,
|
|
766
952
|
includePlugin = includePlugin,
|
|
767
953
|
onPreIgnite = function(callback, hookOptions)
|
|
@@ -780,6 +966,14 @@ local function createModuleInstantiation(state, options)
|
|
|
780
966
|
end
|
|
781
967
|
return registerHook("postIgnite", _exp, _result_1)
|
|
782
968
|
end,
|
|
969
|
+
onIgnited = function(callback, hookOptions)
|
|
970
|
+
local _exp = callback
|
|
971
|
+
local _result_1 = hookOptions
|
|
972
|
+
if _result_1 ~= nil then
|
|
973
|
+
_result_1 = _result_1.priority
|
|
974
|
+
end
|
|
975
|
+
return registerHook("ignited", _exp, _result_1)
|
|
976
|
+
end,
|
|
783
977
|
onExtinguished = function(callback, hookOptions)
|
|
784
978
|
local _exp = callback
|
|
785
979
|
local _result_1 = hookOptions
|
|
@@ -94,6 +94,13 @@ export interface PluginTarget {
|
|
|
94
94
|
onPreIgnite: (callback: (module: Module) => void, options?: HookOptions) => void;
|
|
95
95
|
/** Runs after every provider has been constructed. */
|
|
96
96
|
onPostIgnite: (callback: (module: Module) => void, options?: HookOptions) => void;
|
|
97
|
+
/**
|
|
98
|
+
* Runs once ignition has completed: the module is ignited, and its imports count it among
|
|
99
|
+
* their importers. The lifecycle plugin starts the providers here. Nothing can fail the
|
|
100
|
+
* ignition any more, so a hook that raises is warned about and the ones after it still run;
|
|
101
|
+
* once the module has been extinguished -- by an `onStart`, say -- the rest do not run.
|
|
102
|
+
*/
|
|
103
|
+
onIgnited: (callback: (module: Module) => void, options?: HookOptions) => void;
|
|
97
104
|
/** Runs when the module extinguishes, before its providers are released. */
|
|
98
105
|
onExtinguished: (callback: (module: Module) => void, options?: HookOptions) => void;
|
|
99
106
|
/**
|
package/out/reflect.luau
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local forgetImplements = TS.import(script, script.Parent, "utility", "implementsCache").forgetImplements
|
|
2
4
|
--[[
|
|
3
5
|
*
|
|
4
6
|
* Reflection/metadata API
|
|
@@ -60,6 +62,10 @@ do
|
|
|
60
62
|
local _key = key
|
|
61
63
|
local _value = value
|
|
62
64
|
metadata[_key] = _value
|
|
65
|
+
-- What `getClassImplements` keeps per class is built from this key.
|
|
66
|
+
if key == "flamework:implements" then
|
|
67
|
+
forgetImplements(obj)
|
|
68
|
+
end
|
|
63
69
|
end
|
|
64
70
|
_container.defineMetadata = defineMetadata
|
|
65
71
|
--[[
|
|
@@ -72,6 +78,7 @@ do
|
|
|
72
78
|
for key, value in pairs(list) do
|
|
73
79
|
metadata[key] = value
|
|
74
80
|
end
|
|
81
|
+
forgetImplements(obj)
|
|
75
82
|
end
|
|
76
83
|
_container.defineMetadataBatch = defineMetadataBatch
|
|
77
84
|
--[[
|
|
@@ -86,6 +93,9 @@ do
|
|
|
86
93
|
local _key = key
|
|
87
94
|
_result[_key] = nil
|
|
88
95
|
end
|
|
96
|
+
if key == "flamework:implements" then
|
|
97
|
+
forgetImplements(obj)
|
|
98
|
+
end
|
|
89
99
|
end
|
|
90
100
|
_container.deleteMetadata = deleteMetadata
|
|
91
101
|
--[[
|
|
@@ -302,6 +312,7 @@ do
|
|
|
302
312
|
local function resetObject(object)
|
|
303
313
|
local _object = object
|
|
304
314
|
metadata[_object] = nil
|
|
315
|
+
forgetImplements(object)
|
|
305
316
|
end
|
|
306
317
|
_container.resetObject = resetObject
|
|
307
318
|
end
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The interfaces
|
|
2
|
+
* The interfaces an object implements, own and inherited, each once: the transformer writes every
|
|
3
3
|
* class's own heritage clause, so a subclass that re-declares an interface its parent implements
|
|
4
4
|
* carries the id twice up the chain, and attached to it twice -- the lifecycle's ordered lists ran
|
|
5
5
|
* `onInit` and `onStart` twice for it.
|
|
6
|
+
*
|
|
7
|
+
* Walked once per class and kept: this runs on every attach and detach, and behind
|
|
8
|
+
* `Flamework.implements`. An instance, which implements nothing of its own, answers its class's
|
|
9
|
+
* list; so does a class asked directly. An object that implements something of its own -- a
|
|
10
|
+
* `listen` proxy, a plain object given the metadata -- or whose `__index` is not a table is walked
|
|
11
|
+
* every time, as before, rather than kept per object. The list is shared and frozen: callers must
|
|
12
|
+
* not change it.
|
|
6
13
|
*/
|
|
7
|
-
export declare function getClassImplements(
|
|
14
|
+
export declare function getClassImplements(object: object): ReadonlyArray<string>;
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
local TS = _G[script]
|
|
3
3
|
local Reflect = TS.import(script, script.Parent.Parent, "reflect").Reflect
|
|
4
|
+
local implementsCache = TS.import(script, script.Parent, "implementsCache").implementsCache
|
|
5
|
+
local IMPLEMENTS = "flamework:implements"
|
|
6
|
+
local EMPTY = table.freeze({})
|
|
7
|
+
--* Where `Reflect.getMetadatas` goes next from an object: its metatable's `__index`, its class.
|
|
8
|
+
local function getParent(object)
|
|
9
|
+
local metatable = getmetatable(object)
|
|
10
|
+
if metatable ~= nil and type(metatable) == "table" then
|
|
11
|
+
return rawget(metatable, "__index")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
4
14
|
--[[
|
|
5
15
|
*
|
|
6
|
-
* The
|
|
7
|
-
*
|
|
8
|
-
* carries the id twice up the chain, and attached to it twice -- the lifecycle's ordered lists ran
|
|
9
|
-
* `onInit` and `onStart` twice for it.
|
|
16
|
+
* The walk as `Reflect.getMetadatas` makes it, for an object whose list is not cached: every
|
|
17
|
+
* `flamework:implements` list from the object up its chain, each id once, first seen first.
|
|
10
18
|
|
|
11
19
|
]]
|
|
12
|
-
local function
|
|
20
|
+
local function collect(object)
|
|
13
21
|
local classImplements = {}
|
|
14
22
|
local seen = {}
|
|
15
|
-
for _, implementList in Reflect.getMetadatas(
|
|
23
|
+
for _, implementList in Reflect.getMetadatas(object, IMPLEMENTS) do
|
|
16
24
|
for _1, implementId in implementList do
|
|
17
25
|
if not (seen[implementId] ~= nil) then
|
|
18
26
|
seen[implementId] = true
|
|
@@ -22,6 +30,81 @@ local function getClassImplements(constructor)
|
|
|
22
30
|
end
|
|
23
31
|
return classImplements
|
|
24
32
|
end
|
|
33
|
+
--[[
|
|
34
|
+
*
|
|
35
|
+
* A class's list, built once: its own ids, then those of the class above it that it does not
|
|
36
|
+
* re-declare, which is the order and the ids `collect` finds. One that declares nothing of its own
|
|
37
|
+
* shares the list above it.
|
|
38
|
+
|
|
39
|
+
]]
|
|
40
|
+
local function getCachedImplements(object)
|
|
41
|
+
local _byClass = implementsCache.byClass
|
|
42
|
+
local _object = object
|
|
43
|
+
local cached = _byClass[_object]
|
|
44
|
+
if cached ~= nil then
|
|
45
|
+
return cached
|
|
46
|
+
end
|
|
47
|
+
local parent = getParent(object)
|
|
48
|
+
local inherited = if parent ~= nil then getCachedImplements(parent) else EMPTY
|
|
49
|
+
local list = inherited
|
|
50
|
+
local own = Reflect.getOwnMetadata(object, IMPLEMENTS)
|
|
51
|
+
if own ~= nil then
|
|
52
|
+
local merged = {}
|
|
53
|
+
for _, implementId in own do
|
|
54
|
+
if not (table.find(merged, implementId) ~= nil) then
|
|
55
|
+
table.insert(merged, implementId)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
for _, implementId in inherited do
|
|
59
|
+
if not (table.find(merged, implementId) ~= nil) then
|
|
60
|
+
table.insert(merged, implementId)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
list = table.freeze(merged)
|
|
64
|
+
end
|
|
65
|
+
local _byClass_1 = implementsCache.byClass
|
|
66
|
+
local _object_1 = object
|
|
67
|
+
local _list = list
|
|
68
|
+
_byClass_1[_object_1] = _list
|
|
69
|
+
return list
|
|
70
|
+
end
|
|
71
|
+
--[[
|
|
72
|
+
*
|
|
73
|
+
* The interfaces an object implements, own and inherited, each once: the transformer writes every
|
|
74
|
+
* class's own heritage clause, so a subclass that re-declares an interface its parent implements
|
|
75
|
+
* carries the id twice up the chain, and attached to it twice -- the lifecycle's ordered lists ran
|
|
76
|
+
* `onInit` and `onStart` twice for it.
|
|
77
|
+
*
|
|
78
|
+
* Walked once per class and kept: this runs on every attach and detach, and behind
|
|
79
|
+
* `Flamework.implements`. An instance, which implements nothing of its own, answers its class's
|
|
80
|
+
* list; so does a class asked directly. An object that implements something of its own -- a
|
|
81
|
+
* `listen` proxy, a plain object given the metadata -- or whose `__index` is not a table is walked
|
|
82
|
+
* every time, as before, rather than kept per object. The list is shared and frozen: callers must
|
|
83
|
+
* not change it.
|
|
84
|
+
|
|
85
|
+
]]
|
|
86
|
+
local function getClassImplements(object)
|
|
87
|
+
if Reflect.getOwnMetadata(object, IMPLEMENTS) == nil then
|
|
88
|
+
local parent = getParent(object)
|
|
89
|
+
if parent == nil then
|
|
90
|
+
return EMPTY
|
|
91
|
+
end
|
|
92
|
+
if type(parent) == "table" then
|
|
93
|
+
return getCachedImplements(parent)
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
local _object = object
|
|
97
|
+
local _condition = type(_object) == "table"
|
|
98
|
+
if _condition then
|
|
99
|
+
_condition = rawget(object, "__index") == object
|
|
100
|
+
end
|
|
101
|
+
if _condition then
|
|
102
|
+
-- A class: a roblox-ts class is its own instances' `__index`.
|
|
103
|
+
return getCachedImplements(object)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
return collect(object)
|
|
107
|
+
end
|
|
25
108
|
return {
|
|
26
109
|
getClassImplements = getClassImplements,
|
|
27
110
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each class implements, own and inherited, as `getClassImplements` found it, by the class --
|
|
3
|
+
* and by every class above it, which it walked on the way. Its own module, apart from
|
|
4
|
+
* `getClassImplements`, so that `Reflect`, which that one reads through, can reach it too.
|
|
5
|
+
*/
|
|
6
|
+
export declare const implementsCache: {
|
|
7
|
+
byClass: WeakMap<object, readonly string[]>;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Forgets every list once an object whose `flamework:implements` metadata one was built from has
|
|
11
|
+
* it changed: the lists of the classes below it were built from it too. The transformer writes a
|
|
12
|
+
* class's metadata as the class is defined, before anything can ask, so this does not happen in a
|
|
13
|
+
* game; it keeps a later change seen as it was before the lists were cached. What `listen` gives
|
|
14
|
+
* its proxies changes nothing here: an object that implements something of its own is not cached.
|
|
15
|
+
*/
|
|
16
|
+
export declare function forgetImplements(object: object): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
--[[
|
|
3
|
+
*
|
|
4
|
+
* What each class implements, own and inherited, as `getClassImplements` found it, by the class --
|
|
5
|
+
* and by every class above it, which it walked on the way. Its own module, apart from
|
|
6
|
+
* `getClassImplements`, so that `Reflect`, which that one reads through, can reach it too.
|
|
7
|
+
|
|
8
|
+
]]
|
|
9
|
+
local implementsCache = {
|
|
10
|
+
byClass = setmetatable({}, {
|
|
11
|
+
__mode = "k",
|
|
12
|
+
}),
|
|
13
|
+
}
|
|
14
|
+
--[[
|
|
15
|
+
*
|
|
16
|
+
* Forgets every list once an object whose `flamework:implements` metadata one was built from has
|
|
17
|
+
* it changed: the lists of the classes below it were built from it too. The transformer writes a
|
|
18
|
+
* class's metadata as the class is defined, before anything can ask, so this does not happen in a
|
|
19
|
+
* game; it keeps a later change seen as it was before the lists were cached. What `listen` gives
|
|
20
|
+
* its proxies changes nothing here: an object that implements something of its own is not cached.
|
|
21
|
+
|
|
22
|
+
]]
|
|
23
|
+
local function forgetImplements(object)
|
|
24
|
+
local _byClass = implementsCache.byClass
|
|
25
|
+
local _object = object
|
|
26
|
+
if _byClass[_object] ~= nil then
|
|
27
|
+
implementsCache.byClass = setmetatable({}, {
|
|
28
|
+
__mode = "k",
|
|
29
|
+
})
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
return {
|
|
33
|
+
forgetImplements = forgetImplements,
|
|
34
|
+
implementsCache = implementsCache,
|
|
35
|
+
}
|
|
@@ -1 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `func` with the arguments given, on a thread that is kept for the next call once it returns:
|
|
3
|
+
* one that yields keeps its thread, and the next call gets a new one.
|
|
4
|
+
*
|
|
5
|
+
* The arguments are passed through rather than closed over, so that a caller running many callbacks
|
|
6
|
+
* -- the per-frame events, once per listener every frame -- creates nothing per call.
|
|
7
|
+
*/
|
|
1
8
|
export declare function recycleThread(func: () => void): void;
|
|
9
|
+
export declare function recycleThread<A>(func: (a: A) => void, a: A): void;
|
|
10
|
+
export declare function recycleThread<A, B>(func: (a: A, b: B) => void, a: A, b: B): void;
|
|
11
|
+
export declare function recycleThread<A, B, C>(func: (a: A, b: B, c: C) => void, a: A, b: B, c: C): void;
|
|
12
|
+
export declare function recycleThread<A, B, C, D>(func: (a: A, b: B, c: C, d: D) => void, a: A, b: B, c: C, d: D): void;
|
|
13
|
+
export declare function recycleThread<A, B, C, D, E>(func: (a: A, b: B, c: C, d: D, e: E) => void, a: A, b: B, c: C, d: D, e: E): void;
|