@flamework-experimental/core 2.0.0-alpha.1 → 2.0.0-alpha.3
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 +35 -27
- package/flamework.build +3 -3
- package/out/dependency.d.ts +4 -0
- package/out/dependency.luau +4 -0
- package/out/flamework.luau +8 -0
- package/out/index.d.ts +4 -2
- package/out/init.luau +10 -2
- package/out/lifecycle/lifecyclePlugin.d.ts +175 -2
- package/out/lifecycle/lifecyclePlugin.luau +636 -102
- package/out/module/module.luau +337 -41
- package/out/module/moduleBuilder.d.ts +12 -3
- package/out/module/moduleBuilder.luau +22 -0
- package/out/module/moduleDefinition.d.ts +6 -0
- package/out/module/providerRegistration.d.ts +8 -0
- package/out/module/providerRegistration.luau +29 -0
- package/out/plugin/pluginDefinition.d.ts +14 -4
- package/out/provider.d.ts +19 -0
- package/out/provider.luau +8 -0
- package/out/reflect.luau +17 -0
- package/out/utility/explainUnresolved.d.ts +9 -0
- package/out/utility/explainUnresolved.luau +43 -0
- package/out/utility/getClassImplements.d.ts +9 -2
- package/out/utility/getClassImplements.luau +89 -6
- package/out/utility/getClassesInPath.d.ts +10 -2
- package/out/utility/getClassesInPath.luau +70 -31
- package/out/utility/globs.d.ts +2 -2
- package/out/utility/globs.luau +3 -3
- package/out/utility/implementsCache.d.ts +16 -0
- package/out/utility/implementsCache.luau +35 -0
- package/out/utility/leftOut.d.ts +35 -0
- package/out/utility/leftOut.luau +171 -0
- package/out/utility/moduleClasses.d.ts +9 -0
- package/out/utility/moduleClasses.luau +64 -0
- package/out/utility/recycleThread.d.ts +12 -0
- package/out/utility/recycleThread.luau +29 -10
- package/out/utility/threadWaits.d.ts +36 -0
- package/out/utility/threadWaits.luau +81 -0
- package/package.json +1 -1
|
@@ -20,3 +20,11 @@ export declare function normalizeProviderConfig(config: ProviderConfig): Provide
|
|
|
20
20
|
* parent's scope.
|
|
21
21
|
*/
|
|
22
22
|
export declare function getProviderClassScope(config: ProviderConfig): ScopeCondition;
|
|
23
|
+
/** v1's default, which a provider that does not set `loadOrder` has. */
|
|
24
|
+
export declare const DEFAULT_LOAD_ORDER = 1;
|
|
25
|
+
/**
|
|
26
|
+
* Where a registration sits in its module's startup: its class's `loadOrder`, or the default. A
|
|
27
|
+
* lazy one has none (`undefined`): it starts when it is first resolved, whatever its decorator says.
|
|
28
|
+
* Own metadata, as everywhere else.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getProviderLoadOrder(config: ProviderConfig): number | undefined;
|
|
@@ -72,9 +72,38 @@ local function getProviderClassScope(config)
|
|
|
72
72
|
inactiveIn = decoratorConfig.inactiveIn,
|
|
73
73
|
}
|
|
74
74
|
end
|
|
75
|
+
--* v1's default, which a provider that does not set `loadOrder` has.
|
|
76
|
+
local DEFAULT_LOAD_ORDER = 1
|
|
77
|
+
--[[
|
|
78
|
+
*
|
|
79
|
+
* Where a registration sits in its module's startup: its class's `loadOrder`, or the default. A
|
|
80
|
+
* lazy one has none (`undefined`): it starts when it is first resolved, whatever its decorator says.
|
|
81
|
+
* Own metadata, as everywhere else.
|
|
82
|
+
|
|
83
|
+
]]
|
|
84
|
+
local function getProviderLoadOrder(config)
|
|
85
|
+
if config.type ~= "class" then
|
|
86
|
+
return DEFAULT_LOAD_ORDER
|
|
87
|
+
end
|
|
88
|
+
if config.lazy == true then
|
|
89
|
+
return nil
|
|
90
|
+
end
|
|
91
|
+
local decoratorConfig = Reflect.getOwnMetadata(config.value, "flamework:providerConfig")
|
|
92
|
+
local _result = decoratorConfig
|
|
93
|
+
if _result ~= nil then
|
|
94
|
+
_result = _result.loadOrder
|
|
95
|
+
end
|
|
96
|
+
local _condition = _result
|
|
97
|
+
if _condition == nil then
|
|
98
|
+
_condition = DEFAULT_LOAD_ORDER
|
|
99
|
+
end
|
|
100
|
+
return _condition
|
|
101
|
+
end
|
|
75
102
|
return {
|
|
76
103
|
assertIsProviderClass = assertIsProviderClass,
|
|
77
104
|
getProviderClassId = getProviderClassId,
|
|
78
105
|
normalizeProviderConfig = normalizeProviderConfig,
|
|
79
106
|
getProviderClassScope = getProviderClassScope,
|
|
107
|
+
getProviderLoadOrder = getProviderLoadOrder,
|
|
108
|
+
DEFAULT_LOAD_ORDER = DEFAULT_LOAD_ORDER,
|
|
80
109
|
}
|
|
@@ -52,15 +52,18 @@ export interface PluginTarget {
|
|
|
52
52
|
*/
|
|
53
53
|
registerClassProvider: (provider: Constructor, options?: ProviderRegistrationOptions) => void;
|
|
54
54
|
/**
|
|
55
|
-
* Registers every
|
|
56
|
-
* `registerProviders` does. This is how a plugin ships a folder of
|
|
57
|
-
* to every class found
|
|
55
|
+
* Registers every `@Provider()` class the modules under a source folder define, exported or not,
|
|
56
|
+
* as the module builder's `registerProviders` does. This is how a plugin ships a folder of
|
|
57
|
+
* providers. The options apply to every class found; when their scope condition does not hold,
|
|
58
|
+
* the folder is not looked up and nothing under it is required.
|
|
58
59
|
*
|
|
59
60
|
* @metadata macro
|
|
60
61
|
*/
|
|
61
62
|
registerProviders: <T extends string>(path: T, options?: ProviderRegistrationOptions, resolved?: Modding.Intrinsic<"path", [T], string[]>) => void;
|
|
62
63
|
/**
|
|
63
|
-
* Registers every
|
|
64
|
+
* Registers every `@Provider()` class the modules under every folder a compile-time glob matches
|
|
65
|
+
* define, exported or not. As with `registerProviders`, a scope condition that does not hold
|
|
66
|
+
* leaves the folders untouched.
|
|
64
67
|
*
|
|
65
68
|
* @metadata macro
|
|
66
69
|
*/
|
|
@@ -94,6 +97,13 @@ export interface PluginTarget {
|
|
|
94
97
|
onPreIgnite: (callback: (module: Module) => void, options?: HookOptions) => void;
|
|
95
98
|
/** Runs after every provider has been constructed. */
|
|
96
99
|
onPostIgnite: (callback: (module: Module) => void, options?: HookOptions) => void;
|
|
100
|
+
/**
|
|
101
|
+
* Runs once ignition has completed: the module is ignited, and its imports count it among
|
|
102
|
+
* their importers. The lifecycle plugin starts the providers here. Nothing can fail the
|
|
103
|
+
* ignition any more, so a hook that raises is warned about and the ones after it still run;
|
|
104
|
+
* once the module has been extinguished -- by an `onStart`, say -- the rest do not run.
|
|
105
|
+
*/
|
|
106
|
+
onIgnited: (callback: (module: Module) => void, options?: HookOptions) => void;
|
|
97
107
|
/** Runs when the module extinguishes, before its providers are released. */
|
|
98
108
|
onExtinguished: (callback: (module: Module) => void, options?: HookOptions) => void;
|
|
99
109
|
/**
|
package/out/provider.d.ts
CHANGED
|
@@ -11,6 +11,25 @@ export interface ProviderDecoratorConfig extends ScopeCondition {
|
|
|
11
11
|
* Defaults to `false`.
|
|
12
12
|
*/
|
|
13
13
|
lazy?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Orders this provider's `onInit` and `onStart` against the other providers the same ignition
|
|
16
|
+
* constructs, as v1's `loadOrder` did: lower goes first. Defaults to `1`; any finite number,
|
|
17
|
+
* negative and fractional ones included. Providers with the same `loadOrder` keep the order
|
|
18
|
+
* they would have without one.
|
|
19
|
+
*
|
|
20
|
+
* Dependency order still wins. The module constructs its providers in ascending `loadOrder`,
|
|
21
|
+
* each after what its constructor takes, so a provider's dependencies are constructed and
|
|
22
|
+
* initialised before it even when theirs is higher: a low `loadOrder` pulls what the provider
|
|
23
|
+
* needs forward with it. `onInit` runs in that construction order. `onStart` runs in ascending
|
|
24
|
+
* `loadOrder` alone, each on its own thread as always, so a lower one runs up to its first yield
|
|
25
|
+
* before the next is started.
|
|
26
|
+
*
|
|
27
|
+
* Only within one module's ignition: imported modules ignite, and start, before it. Per-frame
|
|
28
|
+
* events (`onTick`, `onPhysics`, `onRender`) stay unordered. A lazy provider is not part of
|
|
29
|
+
* the order: it is initialised and started when it is first resolved, and its `loadOrder` is
|
|
30
|
+
* ignored.
|
|
31
|
+
*/
|
|
32
|
+
loadOrder?: number;
|
|
14
33
|
}
|
|
15
34
|
/**
|
|
16
35
|
* Register a class as a provider.
|
package/out/provider.luau
CHANGED
|
@@ -17,6 +17,14 @@ local Reflect = TS.import(script, script.Parent, "reflect").Reflect
|
|
|
17
17
|
]]
|
|
18
18
|
local function Provider(config)
|
|
19
19
|
return function(constructor)
|
|
20
|
+
local _loadOrder = config
|
|
21
|
+
if _loadOrder ~= nil then
|
|
22
|
+
_loadOrder = _loadOrder.loadOrder
|
|
23
|
+
end
|
|
24
|
+
local loadOrder = _loadOrder
|
|
25
|
+
if loadOrder ~= nil and (not (type(loadOrder) == "number") or not (math.abs(loadOrder) < math.huge)) then
|
|
26
|
+
error(`@Provider() on '{constructor}': loadOrder must be a finite number, got {tostring(loadOrder)} ({typeof(loadOrder)})`)
|
|
27
|
+
end
|
|
20
28
|
Reflect.defineMetadata(constructor, "flamework:provider", true)
|
|
21
29
|
Reflect.defineMetadata(constructor, "flamework:providerConfig", config or {})
|
|
22
30
|
end
|
package/out/reflect.luau
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
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
|
|
4
|
+
local recordModuleClass = TS.import(script, script.Parent, "utility", "moduleClasses").recordModuleClass
|
|
2
5
|
--[[
|
|
3
6
|
*
|
|
4
7
|
* Reflection/metadata API
|
|
@@ -60,6 +63,15 @@ do
|
|
|
60
63
|
local _key = key
|
|
61
64
|
local _value = value
|
|
62
65
|
metadata[_key] = _value
|
|
66
|
+
-- What `getClassImplements` keeps per class is built from this key.
|
|
67
|
+
if key == "flamework:implements" then
|
|
68
|
+
forgetImplements(obj)
|
|
69
|
+
end
|
|
70
|
+
-- The transformer records the module a class was defined in under this key, which is how
|
|
71
|
+
-- path registration finds a class its module does not export.
|
|
72
|
+
if key == "flamework:module" and property == nil then
|
|
73
|
+
recordModuleClass(value, obj)
|
|
74
|
+
end
|
|
63
75
|
end
|
|
64
76
|
_container.defineMetadata = defineMetadata
|
|
65
77
|
--[[
|
|
@@ -72,6 +84,7 @@ do
|
|
|
72
84
|
for key, value in pairs(list) do
|
|
73
85
|
metadata[key] = value
|
|
74
86
|
end
|
|
87
|
+
forgetImplements(obj)
|
|
75
88
|
end
|
|
76
89
|
_container.defineMetadataBatch = defineMetadataBatch
|
|
77
90
|
--[[
|
|
@@ -86,6 +99,9 @@ do
|
|
|
86
99
|
local _key = key
|
|
87
100
|
_result[_key] = nil
|
|
88
101
|
end
|
|
102
|
+
if key == "flamework:implements" then
|
|
103
|
+
forgetImplements(obj)
|
|
104
|
+
end
|
|
89
105
|
end
|
|
90
106
|
_container.deleteMetadata = deleteMetadata
|
|
91
107
|
--[[
|
|
@@ -302,6 +318,7 @@ do
|
|
|
302
318
|
local function resetObject(object)
|
|
303
319
|
local _object = object
|
|
304
320
|
metadata[_object] = nil
|
|
321
|
+
forgetImplements(object)
|
|
305
322
|
end
|
|
306
323
|
_container.resetObject = resetObject
|
|
307
324
|
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a dependency no module resolved cannot be resolved, when its id names a Flamework class that
|
|
3
|
+
* has been loaded -- one a module defined at its top level, which the module record knows -- with
|
|
4
|
+
* what to do about it. Nothing for any other id: an interface, a type registered nowhere, a class
|
|
5
|
+
* defined inside a function or never required, whose failure keeps the plain message.
|
|
6
|
+
*
|
|
7
|
+
* `origin` is the class whose constructor asked, when one did.
|
|
8
|
+
*/
|
|
9
|
+
export declare function explainUnresolvedClass(id: string, origin?: object): string | undefined;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local Reflect = TS.import(script, script.Parent.Parent, "reflect").Reflect
|
|
4
|
+
local findModuleClass = TS.import(script, script.Parent, "moduleClasses").findModuleClass
|
|
5
|
+
local function describeModule(moduleScript)
|
|
6
|
+
local _moduleScript = moduleScript
|
|
7
|
+
return if typeof(_moduleScript) == "Instance" then moduleScript:GetFullName() else tostring(moduleScript)
|
|
8
|
+
end
|
|
9
|
+
--[[
|
|
10
|
+
*
|
|
11
|
+
* Why a dependency no module resolved cannot be resolved, when its id names a Flamework class that
|
|
12
|
+
* has been loaded -- one a module defined at its top level, which the module record knows -- with
|
|
13
|
+
* what to do about it. Nothing for any other id: an interface, a type registered nowhere, a class
|
|
14
|
+
* defined inside a function or never required, whose failure keeps the plain message.
|
|
15
|
+
*
|
|
16
|
+
* `origin` is the class whose constructor asked, when one did.
|
|
17
|
+
|
|
18
|
+
]]
|
|
19
|
+
local function explainUnresolvedClass(id, origin)
|
|
20
|
+
local found = findModuleClass(function(value)
|
|
21
|
+
return Reflect.getOwnMetadata(value, "identifier") == id
|
|
22
|
+
end)
|
|
23
|
+
if found == nil then
|
|
24
|
+
return nil
|
|
25
|
+
end
|
|
26
|
+
local _binding = found
|
|
27
|
+
local value = _binding[1]
|
|
28
|
+
local moduleScript = _binding[2]
|
|
29
|
+
local where = describeModule(moduleScript)
|
|
30
|
+
if Reflect.hasOwnMetadata(value, "flamework:provider") then
|
|
31
|
+
return `'{value}' ({where}) is a @Provider() that nothing in this module registers or provides: ` .. "add its folder to registerProviders, register it with registerClassProvider, include the plugin that provides it, " .. "or import a module that has it"
|
|
32
|
+
end
|
|
33
|
+
if Reflect.hasOwnMetadata(value, "flamework:component") then
|
|
34
|
+
if origin ~= nil and Reflect.hasOwnMetadata(origin, "flamework:component") then
|
|
35
|
+
return `'{value}' ({where}) is a component that no ComponentPlugin of this module registers, ` .. `so the component '{origin}' cannot take it: register it in one`
|
|
36
|
+
end
|
|
37
|
+
return `'{value}' ({where}) is a component (@Component), not a provider: a module never constructs a component, ` .. "so Dependency<T>() and constructor injection cannot reach one. Make it a @Provider(), or get it from " .. "Components on the instance it is attached to (components.getComponent<T>(instance))"
|
|
38
|
+
end
|
|
39
|
+
return `'{value}' ({where}) is not a provider: it carries no @Provider() decorator. ` .. "Decorate it with @Provider(), or build it with module.createClassInstance"
|
|
40
|
+
end
|
|
41
|
+
return {
|
|
42
|
+
explainUnresolvedClass = explainUnresolvedClass,
|
|
43
|
+
}
|
|
@@ -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
|
}
|
|
@@ -14,8 +14,16 @@ export declare function importModule(moduleScript: ModuleScript): defined | unde
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function requireModulesInPath(rbxPath: readonly string[]): Array<defined>;
|
|
16
16
|
/**
|
|
17
|
-
* Requires every ModuleScript at and under the specified Rojo path and returns every
|
|
18
|
-
*
|
|
17
|
+
* Requires every ModuleScript at and under the specified Rojo path and returns every Flamework
|
|
18
|
+
* class they hold, each once: every class carrying its own identifier that a module defined at its
|
|
19
|
+
* top level -- exported or not, as v1 registered every decorated class it required -- then every
|
|
20
|
+
* exported value carrying its own identifier that the module did not define there, such as a
|
|
21
|
+
* re-export of a class from elsewhere.
|
|
22
|
+
*
|
|
23
|
+
* A class declared inside a function is not found unless its module exports it: it is created by
|
|
24
|
+
* every call, so the transformer does not record it against its module. Neither is a class
|
|
25
|
+
* compiled by a transformer older than the one that records classes, other than through its
|
|
26
|
+
* module's exports.
|
|
19
27
|
*
|
|
20
28
|
* A module that fails to load raises, as it did in v1: a class that silently fails to register
|
|
21
29
|
* would otherwise only show up later as an unresolvable dependency, far from the cause.
|
|
@@ -3,6 +3,7 @@ local TS = _G[script]
|
|
|
3
3
|
local tsImport = TS.import(script, script.Parent, "tsImport").tsImport
|
|
4
4
|
local Reflect = TS.import(script, script.Parent.Parent, "reflect").Reflect
|
|
5
5
|
local resolveRbxPath = TS.import(script, script.Parent, "pathRoot").resolveRbxPath
|
|
6
|
+
local getModuleClasses = TS.import(script, script.Parent, "moduleClasses").getModuleClasses
|
|
6
7
|
--[[
|
|
7
8
|
*
|
|
8
9
|
* Requires one ModuleScript through the roblox-ts runtime, as an `import` would, and returns what
|
|
@@ -20,6 +21,25 @@ local function importModule(moduleScript)
|
|
|
20
21
|
end
|
|
21
22
|
return value
|
|
22
23
|
end
|
|
24
|
+
--[[
|
|
25
|
+
*
|
|
26
|
+
* Requires every ModuleScript at and under a Rojo path, in tree order, handing each to `visit` with
|
|
27
|
+
* what it exported.
|
|
28
|
+
|
|
29
|
+
]]
|
|
30
|
+
local function loadModulesInPath(rbxPath, visit)
|
|
31
|
+
local _rbxPath = rbxPath
|
|
32
|
+
assert(_rbxPath)
|
|
33
|
+
local preloadPath = resolveRbxPath(rbxPath)
|
|
34
|
+
if preloadPath:IsA("ModuleScript") then
|
|
35
|
+
visit(preloadPath, importModule(preloadPath))
|
|
36
|
+
end
|
|
37
|
+
for _, instance in preloadPath:GetDescendants() do
|
|
38
|
+
if instance:IsA("ModuleScript") then
|
|
39
|
+
visit(instance, importModule(instance))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
23
43
|
--[[
|
|
24
44
|
*
|
|
25
45
|
* Requires every ModuleScript at and under the specified Rojo path, in tree order, and returns
|
|
@@ -32,30 +52,27 @@ end
|
|
|
32
52
|
|
|
33
53
|
]]
|
|
34
54
|
local function requireModulesInPath(rbxPath)
|
|
35
|
-
local _rbxPath = rbxPath
|
|
36
|
-
assert(_rbxPath)
|
|
37
|
-
local preloadPath = resolveRbxPath(rbxPath)
|
|
38
55
|
local loaded = {}
|
|
39
|
-
|
|
40
|
-
local value = importModule(preloadPath)
|
|
56
|
+
loadModulesInPath(rbxPath, function(_, value)
|
|
41
57
|
if value ~= nil then
|
|
42
|
-
|
|
58
|
+
local _value = value
|
|
59
|
+
table.insert(loaded, _value)
|
|
43
60
|
end
|
|
44
|
-
end
|
|
45
|
-
for _, instance in preloadPath:GetDescendants() do
|
|
46
|
-
if instance:IsA("ModuleScript") then
|
|
47
|
-
local value = importModule(instance)
|
|
48
|
-
if value ~= nil then
|
|
49
|
-
table.insert(loaded, value)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
61
|
+
end)
|
|
53
62
|
return loaded
|
|
54
63
|
end
|
|
55
64
|
--[[
|
|
56
65
|
*
|
|
57
|
-
* Requires every ModuleScript at and under the specified Rojo path and returns every
|
|
58
|
-
*
|
|
66
|
+
* Requires every ModuleScript at and under the specified Rojo path and returns every Flamework
|
|
67
|
+
* class they hold, each once: every class carrying its own identifier that a module defined at its
|
|
68
|
+
* top level -- exported or not, as v1 registered every decorated class it required -- then every
|
|
69
|
+
* exported value carrying its own identifier that the module did not define there, such as a
|
|
70
|
+
* re-export of a class from elsewhere.
|
|
71
|
+
*
|
|
72
|
+
* A class declared inside a function is not found unless its module exports it: it is created by
|
|
73
|
+
* every call, so the transformer does not record it against its module. Neither is a class
|
|
74
|
+
* compiled by a transformer older than the one that records classes, other than through its
|
|
75
|
+
* module's exports.
|
|
59
76
|
*
|
|
60
77
|
* A module that fails to load raises, as it did in v1: a class that silently fails to register
|
|
61
78
|
* would otherwise only show up later as an unresolvable dependency, far from the cause.
|
|
@@ -63,25 +80,47 @@ end
|
|
|
63
80
|
]]
|
|
64
81
|
local function getClassesInPath(rbxPath)
|
|
65
82
|
local foundClasses = {}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
local found = {}
|
|
84
|
+
-- Own metadata only: an undecorated subclass inherits its parent's identifier, and must not be
|
|
85
|
+
-- mistaken for a registered class of its own.
|
|
86
|
+
local add = function(value)
|
|
87
|
+
local _value = value
|
|
88
|
+
local _condition = type(_value) == "table"
|
|
89
|
+
if _condition then
|
|
90
|
+
local _value_1 = value
|
|
91
|
+
_condition = not (found[_value_1] ~= nil)
|
|
92
|
+
if _condition then
|
|
93
|
+
_condition = Reflect.hasOwnMetadata(value, "identifier")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
if _condition then
|
|
97
|
+
local _value_1 = value
|
|
98
|
+
found[_value_1] = true
|
|
99
|
+
local _value_2 = value
|
|
100
|
+
table.insert(foundClasses, _value_2)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
loadModulesInPath(rbxPath, function(moduleScript, value)
|
|
104
|
+
local defined = getModuleClasses(moduleScript)
|
|
105
|
+
if defined ~= nil then
|
|
106
|
+
for _, value in defined do
|
|
107
|
+
add(value)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
local _value = value
|
|
111
|
+
if not (type(_value) == "table") then
|
|
112
|
+
return nil
|
|
69
113
|
end
|
|
70
114
|
-- This is an `export =` on a Flamework class.
|
|
71
115
|
if Reflect.hasOwnMetadata(value, "identifier") then
|
|
72
|
-
|
|
73
|
-
|
|
116
|
+
add(value)
|
|
117
|
+
return nil
|
|
74
118
|
end
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-- Own metadata only: an undecorated subclass inherits its parent's identifier, and
|
|
79
|
-
-- must not be mistaken for a registered class of its own.
|
|
80
|
-
if type(member) == "table" and Reflect.hasOwnMetadata(member, "identifier") then
|
|
81
|
-
table.insert(foundClasses, member)
|
|
82
|
-
end
|
|
119
|
+
-- This is an `export` on a Flamework class.
|
|
120
|
+
for _, member in pairs(value) do
|
|
121
|
+
add(member)
|
|
83
122
|
end
|
|
84
|
-
end
|
|
123
|
+
end)
|
|
85
124
|
return foundClasses
|
|
86
125
|
end
|
|
87
126
|
return {
|
package/out/utility/globs.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare function getGlobPaths(glob: string): string[][];
|
|
5
5
|
/**
|
|
6
|
-
* Requires every ModuleScript under every path the glob matched and returns the
|
|
7
|
-
* each at most once.
|
|
6
|
+
* Requires every ModuleScript under every path the glob matched and returns the Flamework classes
|
|
7
|
+
* they hold, exported or not, each at most once (see {@link getClassesInPath}).
|
|
8
8
|
*/
|
|
9
9
|
export declare function getClassesInGlob(glob: string): Array<object>;
|
package/out/utility/globs.luau
CHANGED
|
@@ -37,14 +37,14 @@ local function getGlobPaths(glob)
|
|
|
37
37
|
end
|
|
38
38
|
local paths = _result
|
|
39
39
|
if paths == nil then
|
|
40
|
-
error(`Flamework has no paths for the glob '{glob}'. ` .. "
|
|
40
|
+
error(`Flamework has no paths for the glob '{glob}'. ` .. "A game's globs are resolved at compile time into include/flamework/globs.json, and this one is not there: " .. "the include folder is not in your Rojo project, the glob is used inside a package (a package's globs " .. "are not resolved), the string was not produced by a glob macro, or globs.json is from another build. " .. "A glob that matched no files does not raise: it resolves to no paths, and the build warns about it.", 0)
|
|
41
41
|
end
|
|
42
42
|
return paths
|
|
43
43
|
end
|
|
44
44
|
--[[
|
|
45
45
|
*
|
|
46
|
-
* Requires every ModuleScript under every path the glob matched and returns the
|
|
47
|
-
* each at most once.
|
|
46
|
+
* Requires every ModuleScript under every path the glob matched and returns the Flamework classes
|
|
47
|
+
* they hold, exported or not, each at most once (see {@link getClassesInPath}).
|
|
48
48
|
|
|
49
49
|
]]
|
|
50
50
|
local function getClassesInGlob(glob)
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ScopeCondition } from "../module/scopes";
|
|
2
|
+
/**
|
|
3
|
+
* A path or glob registration that its own scope condition left out. Its folder was never looked up,
|
|
4
|
+
* so nothing under it was loaded, and nothing from it was registered or recorded as inactive: this
|
|
5
|
+
* is what a miss can still say about the classes there.
|
|
6
|
+
*/
|
|
7
|
+
export interface LeftOutRegistration {
|
|
8
|
+
/** The call as written, for messages: `registerProviders("src/server/debug")`. */
|
|
9
|
+
readonly call: string;
|
|
10
|
+
/** The registration's own condition, which did not hold. */
|
|
11
|
+
readonly condition: ScopeCondition;
|
|
12
|
+
/** The folder's tree path, for a path registration. */
|
|
13
|
+
readonly path?: readonly string[];
|
|
14
|
+
/** The glob as the runtime keys it, for a glob registration. */
|
|
15
|
+
readonly glob?: string;
|
|
16
|
+
/** What `path` was relative to when the registration was made. */
|
|
17
|
+
readonly root: Instance;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Records a registration left out by its own condition. Reads nothing but the path root, which is
|
|
21
|
+
* found from Flamework's own metadata, never from the folder.
|
|
22
|
+
*/
|
|
23
|
+
export declare function leftOutRegistration(call: string, condition: ScopeCondition, folder: {
|
|
24
|
+
path?: readonly string[];
|
|
25
|
+
glob?: string;
|
|
26
|
+
}): LeftOutRegistration;
|
|
27
|
+
/**
|
|
28
|
+
* What a miss on `id` can say about the registrations its module or plugin left out by their own
|
|
29
|
+
* condition, without looking at their folders:
|
|
30
|
+
*
|
|
31
|
+
* - the class has loaded some other way, and its module is under one of them: that one, and why;
|
|
32
|
+
* - the class has not loaded: every one of them, since the class may be under any;
|
|
33
|
+
* - nothing when there are none, or when the loaded class is under none of them.
|
|
34
|
+
*/
|
|
35
|
+
export declare function explainLeftOut(id: string, leftOut: ReadonlyArray<LeftOutRegistration>): string | undefined;
|