@flamework-experimental/core 2.0.0-alpha.3 → 2.0.0-alpha.4

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,9 +1,34 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
+ local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
3
4
  local tsImport = TS.import(script, script.Parent, "tsImport").tsImport
4
5
  local Reflect = TS.import(script, script.Parent.Parent, "reflect").Reflect
5
- local resolveRbxPath = TS.import(script, script.Parent, "pathRoot").resolveRbxPath
6
+ local _pathRoot = TS.import(script, script.Parent, "pathRoot")
7
+ local findRbxPath = _pathRoot.findRbxPath
8
+ local getPathRoot = _pathRoot.getPathRoot
9
+ local resolveRbxPath = _pathRoot.resolveRbxPath
6
10
  local getModuleClasses = TS.import(script, script.Parent, "moduleClasses").getModuleClasses
11
+ --* How long {@link requireModules} waits for a folder that is not in the place, in seconds.
12
+ local MISSING_FOLDER_TIMEOUT = 5
13
+ --[[
14
+ *
15
+ * Why this realm cannot require a folder under `game` that belongs to the other realm, or nothing
16
+ * when it can: a client sees nothing of the server's containers, and the server has no
17
+ * `PlayerScripts` to take a client folder from.
18
+
19
+ ]]
20
+ local function otherRealmReason(rbxPath)
21
+ if getPathRoot() ~= game then
22
+ return nil
23
+ end
24
+ local service = rbxPath[1]
25
+ if RunService:IsClient() and (service == "ServerScriptService" or service == "ServerStorage") then
26
+ return `the folder is in {service}, which does not replicate to clients. Call requireModules for it on the server`
27
+ end
28
+ if not RunService:IsClient() and service == "StarterPlayer" then
29
+ return "the folder is in StarterPlayer/StarterPlayerScripts, which only a client requires from, through its PlayerScripts. Call requireModules for it on the client"
30
+ end
31
+ end
7
32
  --[[
8
33
  *
9
34
  * Requires one ModuleScript through the roblox-ts runtime, as an `import` would, and returns what
@@ -23,23 +48,42 @@ local function importModule(moduleScript)
23
48
  end
24
49
  --[[
25
50
  *
26
- * Requires every ModuleScript at and under a Rojo path, in tree order, handing each to `visit` with
51
+ * Requires every ModuleScript at and under an instance, in tree order, handing each to `visit` with
27
52
  * what it exported.
28
53
 
29
54
  ]]
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))
55
+ local function loadModulesIn(root, visit)
56
+ if root:IsA("ModuleScript") then
57
+ visit(root, importModule(root))
36
58
  end
37
- for _, instance in preloadPath:GetDescendants() do
59
+ for _, instance in root:GetDescendants() do
38
60
  if instance:IsA("ModuleScript") then
39
61
  visit(instance, importModule(instance))
40
62
  end
41
63
  end
42
64
  end
65
+ --[[
66
+ *
67
+ * Requires every ModuleScript at and under a Rojo path, in tree order, handing each to `visit` with
68
+ * what it exported. `caller` names the call that gave the path, for the warning a slow wait gets.
69
+
70
+ ]]
71
+ local function loadModulesInPath(rbxPath, visit, caller)
72
+ local _rbxPath = rbxPath
73
+ assert(_rbxPath)
74
+ loadModulesIn(resolveRbxPath(rbxPath, caller), visit)
75
+ end
76
+ --* What every ModuleScript at and under an instance exported, leaving out the ones that exported nothing.
77
+ local function requireModulesIn(root)
78
+ local loaded = {}
79
+ loadModulesIn(root, function(_, value)
80
+ if value ~= nil then
81
+ local _value = value
82
+ table.insert(loaded, _value)
83
+ end
84
+ end)
85
+ return loaded
86
+ end
43
87
  --[[
44
88
  *
45
89
  * Requires every ModuleScript at and under the specified Rojo path, in tree order, and returns
@@ -52,14 +96,48 @@ end
52
96
 
53
97
  ]]
54
98
  local function requireModulesInPath(rbxPath)
55
- local loaded = {}
56
- loadModulesInPath(rbxPath, function(_, value)
57
- if value ~= nil then
58
- local _value = value
59
- table.insert(loaded, _value)
60
- end
61
- end)
62
- return loaded
99
+ local _rbxPath = rbxPath
100
+ assert(_rbxPath)
101
+ return requireModulesIn(resolveRbxPath(rbxPath))
102
+ end
103
+ --[[
104
+ *
105
+ * Requires every ModuleScript at and under a source folder, in tree order, for what the modules do
106
+ * as they load, and returns what each exported, leaving out the ones that exported nothing. This is
107
+ * v1's `Flamework.addPaths` for a folder that holds no providers: modules that register themselves
108
+ * with a library, such as commands.
109
+ *
110
+ * The path is a source path, as a string literal: `requireModules("src/server/commands")`. The build
111
+ * turns it into the Rojo path the folder ends up at, as it does for `registerProviders`, so the
112
+ * folder has to be in your Rojo project.
113
+ *
114
+ * Each module is required through the roblox-ts module cache, so it runs once, however many times
115
+ * it is required. A folder inside a folder that `registerProviders` registers needs no call:
116
+ * registration already requires every ModuleScript under it.
117
+ *
118
+ * Raises when a module fails to load, and when the folder is not in the place. A missing folder is
119
+ * waited for five seconds, once the place has loaded, and the error names the part that is missing.
120
+ * A folder of the other realm's -- a server folder on a client, a client folder on the server --
121
+ * raises at once, saying so.
122
+ *
123
+ * @metadata macro
124
+
125
+ ]]
126
+ local function requireModules(path, rbxPath)
127
+ if rbxPath == nil then
128
+ error(`requireModules("{path}") was called without the folder's Rojo path, which the build fills in. ` .. "Call it directly, with a string literal, in code that the Flamework transformer compiles.", 2)
129
+ end
130
+ local otherRealm = otherRealmReason(rbxPath)
131
+ if otherRealm ~= nil then
132
+ error(`requireModules("{path}"): {otherRealm}.`, 2)
133
+ end
134
+ local _binding = findRbxPath(rbxPath, MISSING_FOLDER_TIMEOUT)
135
+ local found = _binding.found
136
+ local missing = _binding.missing
137
+ if missing ~= nil then
138
+ error(`requireModules("{path}"): the folder is not in the place. The build put it at {table.concat(rbxPath, "/")}, ` .. `and {found:GetFullName()} has no child named '{missing}' after {MISSING_FOLDER_TIMEOUT} seconds. ` .. "The path is misspelled or differs in case from the folder, or the folder is empty and missing from this " .. "clone, since git keeps no empty folder (the build warns about these where the path is used); or it was " .. "moved or renamed after the build; or the Rojo project the place was built from leaves it out.", 2)
139
+ end
140
+ return requireModulesIn(found)
63
141
  end
64
142
  --[[
65
143
  *
@@ -76,9 +154,12 @@ end
76
154
  *
77
155
  * A module that fails to load raises, as it did in v1: a class that silently fails to register
78
156
  * would otherwise only show up later as an unresolvable dependency, far from the cause.
157
+ *
158
+ * The folder is waited for as {@link resolveRbxPath} waits: `caller`, the registration that gave the
159
+ * path (`registerProviders("src/server/services")`), is named in the warning a slow wait gets.
79
160
 
80
161
  ]]
81
- local function getClassesInPath(rbxPath)
162
+ local function getClassesInPath(rbxPath, caller)
82
163
  local foundClasses = {}
83
164
  local found = {}
84
165
  -- Own metadata only: an undecorated subclass inherits its parent's identifier, and must not be
@@ -120,11 +201,12 @@ local function getClassesInPath(rbxPath)
120
201
  for _, member in pairs(value) do
121
202
  add(member)
122
203
  end
123
- end)
204
+ end, caller)
124
205
  return foundClasses
125
206
  end
126
207
  return {
127
208
  importModule = importModule,
128
209
  requireModulesInPath = requireModulesInPath,
210
+ requireModules = requireModules,
129
211
  getClassesInPath = getClassesInPath,
130
212
  }
@@ -13,5 +13,24 @@ export declare function getPathRoot(): Instance;
13
13
  *
14
14
  * Under `game` the first segment names a service, and `StarterPlayer/StarterPlayerScripts` is
15
15
  * answered from the local player's `PlayerScripts`, which is where that content actually runs.
16
+ *
17
+ * A child that is not there within five seconds (on a client, once the place has loaded) is warned
18
+ * about, naming `caller` -- the call that gave the path, such as `registerProviders("src/shared/components")`
19
+ * -- and the child missing, and then waited for without a limit: content that arrives late still
20
+ * resolves, as it always has. The warning comes once per path: a child further down that is late
21
+ * too is the same wait, and is waited for without a second one.
22
+ */
23
+ export declare function resolveRbxPath(rbxPath: readonly string[], caller?: string): Instance;
24
+ /**
25
+ * Walks a compile-time path as {@link resolveRbxPath} does, but gives up on a child that is not
26
+ * there instead of waiting for it forever. A client still loading the place waits for it to load
27
+ * first, since the place's content arrives as it loads; after that, a missing child is given
28
+ * `timeout` seconds to appear.
29
+ *
30
+ * Returns the instance the path names, or the deepest instance it found and the name missing below
31
+ * it.
16
32
  */
17
- export declare function resolveRbxPath(rbxPath: readonly string[]): Instance;
33
+ export declare function findRbxPath(rbxPath: readonly string[], timeout: number): {
34
+ found: Instance;
35
+ missing?: string;
36
+ };
@@ -56,13 +56,14 @@ local function getPathRoot()
56
56
  end
57
57
  --[[
58
58
  *
59
- * Walks a compile-time path from {@link getPathRoot}, waiting for each child in turn.
59
+ * Walks a compile-time path from {@link getPathRoot}, asking `child` for each segment in turn, and
60
+ * stops at the first segment it has no instance for.
60
61
  *
61
62
  * Under `game` the first segment names a service, and `StarterPlayer/StarterPlayerScripts` is
62
63
  * answered from the local player's `PlayerScripts`, which is where that content actually runs.
63
64
 
64
65
  ]]
65
- local function resolveRbxPath(rbxPath)
66
+ local function walkRbxPath(rbxPath, child)
66
67
  -- Copied so that a generated path literal is not consumed by this call.
67
68
  local _array = {}
68
69
  local _length = #_array
@@ -83,9 +84,83 @@ local function resolveRbxPath(rbxPath)
83
84
  end
84
85
  end
85
86
  for _, segment in path do
86
- node = node:WaitForChild(segment)
87
+ local found = child(node, segment)
88
+ if found == nil then
89
+ return {
90
+ found = node,
91
+ missing = segment,
92
+ }
93
+ end
94
+ node = found
95
+ end
96
+ return {
97
+ found = node,
98
+ }
99
+ end
100
+ --[[
101
+ *
102
+ * How long a child of a path is waited for before the wait is warned about, in seconds: when the
103
+ * engine would warn of an infinite yield, which names neither the call nor the source path.
104
+
105
+ ]]
106
+ local MISSING_CHILD_WARNING = 5
107
+ --* A child that is there now, or once a client has loaded the place and `timeout` seconds more have passed.
108
+ local function waitForChild(parent, name, timeout)
109
+ local child = parent:FindFirstChild(name)
110
+ if child ~= nil then
111
+ return child
112
+ end
113
+ -- A client receives the place's content as it loads, so its time only starts once it has loaded.
114
+ if RunService:IsClient() and not game:IsLoaded() then
115
+ game.Loaded:Wait()
87
116
  end
88
- return node
117
+ return parent:WaitForChild(name, timeout)
118
+ end
119
+ --[[
120
+ *
121
+ * Walks a compile-time path from {@link getPathRoot}, waiting for each child in turn.
122
+ *
123
+ * Under `game` the first segment names a service, and `StarterPlayer/StarterPlayerScripts` is
124
+ * answered from the local player's `PlayerScripts`, which is where that content actually runs.
125
+ *
126
+ * A child that is not there within five seconds (on a client, once the place has loaded) is warned
127
+ * about, naming `caller` -- the call that gave the path, such as `registerProviders("src/shared/components")`
128
+ * -- and the child missing, and then waited for without a limit: content that arrives late still
129
+ * resolves, as it always has. The warning comes once per path: a child further down that is late
130
+ * too is the same wait, and is waited for without a second one.
131
+
132
+ ]]
133
+ local function resolveRbxPath(rbxPath, caller)
134
+ local warned = false
135
+ return walkRbxPath(rbxPath, function(parent, name)
136
+ if warned then
137
+ return parent:WaitForChild(name)
138
+ end
139
+ local child = waitForChild(parent, name, MISSING_CHILD_WARNING)
140
+ if child ~= nil then
141
+ return child
142
+ end
143
+ warned = true
144
+ local waiting = if caller ~= nil then `{caller} is still waiting for its folder` else "Flamework is still waiting for a folder"
145
+ warn(`{waiting}: the build put it at {table.concat(rbxPath, "/")}, ` .. `and {parent:GetFullName()} has no child named '{name}' after {MISSING_CHILD_WARNING} seconds. ` .. "The path may be misspelled or differ in case from the folder, or the folder may be empty and missing from " .. "this clone, since git keeps no empty folder (the build warns about these where the path is used); or " .. "the folder was moved or renamed after the build, or the Rojo project the place was built from leaves it " .. "out. It keeps waiting.")
146
+ return parent:WaitForChild(name)
147
+ end).found
148
+ end
149
+ --[[
150
+ *
151
+ * Walks a compile-time path as {@link resolveRbxPath} does, but gives up on a child that is not
152
+ * there instead of waiting for it forever. A client still loading the place waits for it to load
153
+ * first, since the place's content arrives as it loads; after that, a missing child is given
154
+ * `timeout` seconds to appear.
155
+ *
156
+ * Returns the instance the path names, or the deepest instance it found and the name missing below
157
+ * it.
158
+
159
+ ]]
160
+ local function findRbxPath(rbxPath, timeout)
161
+ return walkRbxPath(rbxPath, function(parent, name)
162
+ return waitForChild(parent, name, timeout)
163
+ end)
89
164
  end
90
165
  --[[
91
166
  *
@@ -101,5 +176,6 @@ end
101
176
  return {
102
177
  getPathRoot = getPathRoot,
103
178
  resolveRbxPath = resolveRbxPath,
179
+ findRbxPath = findRbxPath,
104
180
  __setPathRoot = __setPathRoot,
105
181
  }
package/package.json CHANGED
@@ -1,19 +1,26 @@
1
1
  {
2
2
  "name": "@flamework-experimental/core",
3
- "version": "2.0.0-alpha.3",
3
+ "version": "2.0.0-alpha.4",
4
4
  "main": "out/init.luau",
5
5
  "types": "out/index.d.ts",
6
- "scripts": {
7
- "build": "rbxtsc",
8
- "watch": "rbxtsc -w"
9
- },
10
6
  "repository": {
11
7
  "type": "git",
12
- "url": "git+https://github.com/rbxts-flamework/core.git"
8
+ "url": "git+https://github.com/Velover/ExperimentalFlameworkV2.git",
9
+ "directory": "packages/core"
10
+ },
11
+ "homepage": "https://github.com/Velover/ExperimentalFlameworkV2#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/Velover/ExperimentalFlameworkV2/issues"
14
+ },
15
+ "scripts": {
16
+ "build": "rbxtsc",
17
+ "watch": "rbxtsc -w",
18
+ "prepack": "node ../../scripts/copy-readme.mjs && node ../../scripts/copy-docs.mjs"
13
19
  },
14
20
  "files": [
15
21
  "out",
16
- "flamework.build"
22
+ "flamework.build",
23
+ "docs"
17
24
  ],
18
25
  "publishConfig": {
19
26
  "access": "public"