Skip to content

Commit ff6700d

Browse files
committed
Add folder loading
1 parent 8138822 commit ff6700d

4 files changed

Lines changed: 105 additions & 67 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Bootstrapper solves this by treating execution flow as a single, managed pipelin
2424
Add this to your wally.toml:
2525

2626
```
27-
ldgerrits/bootstrapper@^1.2.1
27+
ldgerrits/bootstrapper@1.2.2
2828
```
2929

3030
## Quick Start

docs/intro.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Bootstrapper solves this by treating execution flow as a single, managed pipelin
2828
Add this to your wally.toml:
2929

3030
```
31-
ldgerrits/bootstrapper@^1.2.1
31+
ldgerrits/bootstrapper@^1.2.2
3232
```
3333

3434
## Quick Start

src/init.luau

Lines changed: 102 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--!strict
2-
--v1.2.0 • LDGerrits
2+
--v1.2.2 • LDGerrits
33
--[=[
44
@class Bootstrapper
55
A lightweight, agnostic module loader and scheduler for Roblox.
@@ -15,8 +15,6 @@ local moduleRegistry = setmetatable({}, { __mode = 'k' })
1515
export type LoadedModule = { [string]: any }
1616
export type LoadedModules = { LoadedModule }
1717
export type ModulePath = string
18-
export type ModulePaths = { ModulePath }
19-
export type ModuleScripts = { ModuleScript }
2018
export type Errors = { [string]: string }
2119
export type PredicateFn = (module: ModuleScript) -> boolean
2220
export type Signal =
@@ -25,7 +23,6 @@ export type Signal =
2523
| RBXScriptSignal
2624
export type Event = Signal | ((callback: (...any) -> ()) -> Task)
2725
export type Cleanup = () -> ()
28-
2926
export type Task =
3027
Instance
3128
| RBXScriptConnection
@@ -130,33 +127,78 @@ local function loadModule(value: any, errors: Errors?): (LoadedModule?, string?)
130127
return result, identity
131128
end
132129

130+
local function resolveModules(
131+
modules: { Instance | ModulePath | LoadedModule },
132+
predicate: PredicateFn?
133+
): { Instance | ModulePath | LoadedModule }
134+
local queue: { any } = {}
135+
local seen: { [any]: boolean } = {}
136+
137+
for _, value in modules do
138+
if type(value) == 'table' or type(value) == 'string' then
139+
if not seen[value] then
140+
seen[value] = true
141+
table.insert(queue, value)
142+
end
143+
elseif typeof(value) == 'Instance' then
144+
if value:IsA('ModuleScript') then
145+
if not seen[value] and (not predicate or predicate(value :: ModuleScript)) then
146+
seen[value] = true
147+
table.insert(queue, value :: ModuleScript)
148+
end
149+
else
150+
local folderModules: { ModuleScript } = {}
151+
for _, desc in value:GetChildren() do
152+
if
153+
desc:IsA('ModuleScript')
154+
and not seen[desc]
155+
and (not predicate or predicate(desc :: ModuleScript))
156+
then
157+
seen[desc] = true
158+
table.insert(folderModules, desc :: ModuleScript)
159+
end
160+
end
161+
162+
table.sort(folderModules, function(a, b)
163+
return a.Name < b.Name
164+
end)
165+
166+
for _, mod in folderModules do
167+
table.insert(queue, mod)
168+
end
169+
end
170+
end
171+
end
172+
173+
return queue
174+
end
175+
133176
local function loadModules(
134-
modules: { Instance | ModulePath },
177+
modules: { Instance | ModulePath | LoadedModule },
135178
shouldSort: boolean,
136179
predicate: PredicateFn?
137180
): (LoadedModules, Errors?)
138181
local loadQueue = {}
182+
local resolvedQueue = resolveModules(modules, predicate)
139183

140-
for _, item in modules do
141-
local isModuleScript = typeof(item) == 'Instance' and item:IsA('ModuleScript')
142-
local isModulePath = type(item) == 'string'
184+
for _, value in resolvedQueue do
185+
local isModuleScript = typeof(value) == 'Instance'
186+
local isTable = type(value) == 'table'
143187

144-
if not (isModuleScript or isModulePath) then
145-
continue
146-
end
147-
148-
if isModuleScript and predicate and not predicate(item :: ModuleScript) then
149-
continue
150-
end
188+
local identity = if isTable
189+
then moduleRegistry[value] or 'UnknownModule'
190+
elseif isModuleScript then (value :: ModuleScript).Name
191+
else ((value :: string):match('([^/]+)$') or value :: string)
151192

152-
local identity = if isModuleScript
153-
then (item :: ModuleScript).Name
154-
else ((item :: ModulePath):match('([^/]+)$') or item)
193+
local fullName = if isTable
194+
then identity
195+
elseif isModuleScript then (value :: ModuleScript):GetFullName()
196+
else value :: string
155197

156198
table.insert(loadQueue, {
157199
identity = identity,
158-
value = item,
159-
fullName = if isModuleScript then (item :: ModuleScript):GetFullName() else item,
200+
value = value,
201+
fullName = fullName,
160202
})
161203
end
162204

@@ -181,10 +223,12 @@ local function loadModules(
181223
return loadedModules, next(errors) ~= nil and errors or nil
182224
end
183225

184-
local function filterModules(modules: LoadedModules | ModuleScripts | ModulePaths, methodName: string)
226+
local function filterModules(modules: { Instance | ModulePath | LoadedModule }, methodName: string)
185227
local loadedModules, callbacks, identities, count = {}, {}, {}, 0
186-
for _, item in modules :: any do
187-
local module, identity = loadModule(item)
228+
local resolvedQueue = resolveModules(modules)
229+
230+
for _, value in resolvedQueue do
231+
local module, identity = loadModule(value)
188232
if not module then
189233
continue
190234
end
@@ -217,7 +261,7 @@ local function parseMethodName(methodName: string): (string, boolean)
217261
end
218262

219263
local function dispatch(
220-
modules: LoadedModules | ModuleScripts | ModulePaths,
264+
modules: { Instance | ModulePath | LoadedModule },
221265
methodName: string,
222266
args: { [number]: any, n: number },
223267
mode: 'Sync' | 'Async' | 'Concurrent'
@@ -226,9 +270,10 @@ local function dispatch(
226270
local loadedModules: LoadedModules = {}
227271
local errors: Errors = {}
228272
local errorSuffix = if mode == 'Sync' then '' else `({mode})`
273+
local resolvedQueue = resolveModules(modules)
229274

230-
local function execute(item)
231-
local module, identity = loadModule(item, errors)
275+
local function execute(value)
276+
local module, identity = loadModule(value, errors)
232277
if not module then
233278
return
234279
end
@@ -257,18 +302,18 @@ local function dispatch(
257302
end
258303

259304
if mode == 'Sync' then
260-
for _, item in modules :: any do
261-
execute(item)
305+
for _, value in resolvedQueue do
306+
execute(value)
262307
end
263308
elseif mode == 'Async' then
264309
task.spawn(function()
265-
for _, item in modules :: any do
266-
execute(item)
310+
for _, value in resolvedQueue do
311+
execute(value)
267312
end
268313
end)
269314
else
270-
for _, item in modules :: any do
271-
task.spawn(execute, item)
315+
for _, value in resolvedQueue do
316+
task.spawn(execute, value)
272317
end
273318
end
274319

@@ -300,7 +345,7 @@ local function createRunner(
300345
end
301346

302347
local function bindToRunService(
303-
modules: LoadedModules | ModuleScripts | ModulePaths,
348+
modules: { Instance | ModulePath | LoadedModule },
304349
eventName: string,
305350
methodName: string,
306351
override: (number | EnumItem)?
@@ -391,10 +436,10 @@ end
391436
@yields
392437
@function loadSequence
393438
@within Bootstrapper
394-
@param modules ModuleScripts | ModulePaths -- The array of ModuleScripts or ModulePaths.
439+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
395440
@return LoadedModules, Errors?
396441
]=]
397-
function Bootstrapper.loadSequence(modules: ModuleScripts | ModulePaths): (LoadedModules, Errors?)
442+
function Bootstrapper.loadSequence(modules: { Instance | ModulePath | LoadedModule }): (LoadedModules, Errors?)
398443
return loadModules(modules, false)
399444
end
400445

@@ -404,13 +449,13 @@ end
404449
@yields
405450
@function run
406451
@within Bootstrapper
407-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
452+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
408453
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
409454
@param ... any -- Additional arguments to pass to the method.
410455
@return LoadedModules, Errors?
411456
]=]
412457
function Bootstrapper.run(
413-
modules: LoadedModules | ModuleScripts | ModulePaths,
458+
modules: { Instance | ModulePath | LoadedModule },
414459
methodName: string,
415460
...: any
416461
): (LoadedModules, Errors?)
@@ -422,11 +467,11 @@ end
422467
Maintains order but does not yield.
423468
@function runAsync
424469
@within Bootstrapper
425-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
470+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
426471
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
427472
@param ... any -- Additional arguments to pass to the method.
428473
]=]
429-
function Bootstrapper.runAsync(modules: LoadedModules | ModuleScripts | ModulePaths, methodName: string, ...: any): ()
474+
function Bootstrapper.runAsync(modules: { Instance | ModulePath | LoadedModule }, methodName: string, ...: any): ()
430475
dispatch(modules, methodName, table.pack(...), 'Async')
431476
end
432477

@@ -435,29 +480,25 @@ end
435480
Fastest execution, but order of completion is not guaranteed.
436481
@function runConcurrent
437482
@within Bootstrapper
438-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
483+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
439484
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
440485
@param ... any -- Additional arguments to pass to the method.
441486
]=]
442-
function Bootstrapper.runConcurrent(
443-
modules: LoadedModules | ModuleScripts | ModulePaths,
444-
methodName: string,
445-
...: any
446-
): ()
487+
function Bootstrapper.runConcurrent(modules: { Instance | ModulePath | LoadedModule }, methodName: string, ...: any): ()
447488
dispatch(modules, methodName, table.pack(...), 'Concurrent')
448489
end
449490

450491
--[=[
451492
Binds a method across all modules to an RBXScriptSignal, a custom Signal, or a subscription function.
452493
@function bindTo
453494
@within Bootstrapper
454-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
495+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
455496
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
456497
@param event Event -- An RBXScriptSignal, custom Signal or a subscription function.
457498
@return Cleanup -- A cleanup function to disconnect the event.
458499
]=]
459500
function Bootstrapper.bindTo(
460-
modules: LoadedModules | ModuleScripts | ModulePaths,
501+
modules: { Instance | ModulePath | LoadedModule },
461502
methodName: string,
462503
event: Event
463504
): Cleanup
@@ -486,14 +527,14 @@ end
486527
Binds a method to run at a specific time interval (in seconds) while not allowing drift.
487528
@function bindToInterval
488529
@within Bootstrapper
489-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
530+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
490531
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
491532
@param interval number -- Time in seconds between calls.
492533
@param signal RBXScriptSignal? -- Optional signal to hook (defaults to Heartbeat).
493534
@return Cleanup -- A function to stop the interval.
494535
]=]
495536
function Bootstrapper.bindToInterval(
496-
modules: LoadedModules | ModuleScripts | ModulePaths,
537+
modules: { Instance | ModulePath | LoadedModule },
497538
methodName: string,
498539
interval: number,
499540
signal: RBXScriptSignal?
@@ -535,11 +576,11 @@ end
535576
Hooks a specified method in all modules to `RunService.Heartbeat`.
536577
@function bindToHeartbeat
537578
@within Bootstrapper
538-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
579+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
539580
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
540581
@return Cleanup -- A cleanup function to disconnect the event.
541582
]=]
542-
function Bootstrapper.bindToHeartbeat(modules: LoadedModules | ModuleScripts | ModulePaths, methodName: string): Cleanup
583+
function Bootstrapper.bindToHeartbeat(modules: { Instance | ModulePath | LoadedModule }, methodName: string): Cleanup
543584
return bindToRunService(modules, 'Heartbeat', methodName)
544585
end
545586

@@ -549,13 +590,13 @@ end
549590
@client
550591
@function bindToRenderStep
551592
@within Bootstrapper
552-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
593+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
553594
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
554595
@param priority number? -- Optional execution priority. If provided, uses BindToRenderStep instead of RenderStepped.
555596
@return Cleanup -- A cleanup function to disconnect the event.
556597
]=]
557598
function Bootstrapper.bindToRenderStep(
558-
modules: LoadedModules | ModuleScripts | ModulePaths,
599+
modules: { Instance | ModulePath | LoadedModule },
559600
methodName: string,
560601
priority: number?
561602
): Cleanup
@@ -568,13 +609,13 @@ end
568609
or `RunService:BindToSimulation` if a frequency is given.
569610
@function bindToPreSimulation
570611
@within Bootstrapper
571-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
612+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
572613
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
573614
@param frequency Enum.StepFrequency? -- Optional execution frequency. If provided, uses BindToSimulation instead of PreSimulation.
574615
@return Cleanup -- A cleanup function to disconnect the event.
575616
]=]
576617
function Bootstrapper.bindToPreSimulation(
577-
modules: LoadedModules | ModuleScripts | ModulePaths,
618+
modules: { Instance | ModulePath | LoadedModule },
578619
methodName: string,
579620
frequency: Enum.StepFrequency?
580621
): Cleanup
@@ -585,12 +626,12 @@ end
585626
Hooks a specified method in all modules to `RunService.PostSimulation`.
586627
@function bindToPostSimulation
587628
@within Bootstrapper
588-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
629+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
589630
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
590631
@return Cleanup -- A cleanup function to disconnect the event.
591632
]=]
592633
function Bootstrapper.bindToPostSimulation(
593-
modules: LoadedModules | ModuleScripts | ModulePaths,
634+
modules: { Instance | ModulePath | LoadedModule },
594635
methodName: string
595636
): Cleanup
596637
return bindToRunService(modules, 'PostSimulation', methodName)
@@ -600,14 +641,11 @@ end
600641
Hooks a specified method in all modules to `RunService.PreAnimation`.
601642
@function bindToPreAnimation
602643
@within Bootstrapper
603-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
644+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
604645
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
605646
@return Cleanup -- A cleanup function to disconnect the event.
606647
]=]
607-
function Bootstrapper.bindToPreAnimation(
608-
modules: LoadedModules | ModuleScripts | ModulePaths,
609-
methodName: string
610-
): Cleanup
648+
function Bootstrapper.bindToPreAnimation(modules: { Instance | ModulePath | LoadedModule }, methodName: string): Cleanup
611649
return bindToRunService(modules, 'PreAnimation', methodName)
612650
end
613651

@@ -616,11 +654,11 @@ end
616654
@client
617655
@function bindToPreRender
618656
@within Bootstrapper
619-
@param modules LoadedModules | ModuleScripts | ModulePaths -- The array of loaded modules, ModuleScripts, or ModulePaths.
657+
@param modules { Instance | ModulePath | LoadedModule } -- An array of ModuleScripts, Paths, Folders, or loaded modules.
620658
@param methodName string -- The method to call. Prefix with '.' for standard calls; otherwise, the module is implicitly passed as 'self'.
621659
@return Cleanup -- A cleanup function to disconnect the event.
622660
]=]
623-
function Bootstrapper.bindToPreRender(modules: LoadedModules | ModuleScripts | ModulePaths, methodName: string): Cleanup
661+
function Bootstrapper.bindToPreRender(modules: { Instance | ModulePath | LoadedModule }, methodName: string): Cleanup
624662
assert(RunService:IsClient(), '[Bootstrapper] bindToPreRender can only be called on the Client.')
625663
return bindToRunService(modules, 'PreRender', methodName)
626664
end

0 commit comments

Comments
 (0)