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' })
1515export type LoadedModule = { [string ]: any }
1616export type LoadedModules = { LoadedModule }
1717export type ModulePath = string
18- export type ModulePaths = { ModulePath }
19- export type ModuleScripts = { ModuleScript }
2018export type Errors = { [string ]: string }
2119export type PredicateFn = (module : ModuleScript ) -> boolean
2220export type Signal =
@@ -25,7 +23,6 @@ export type Signal =
2523 | RBXScriptSignal
2624export type Event = Signal | ((callback : (...any ) -> ()) -> Task )
2725export type Cleanup = () -> ()
28-
2926export type Task =
3027 Instance
3128 | RBXScriptConnection
@@ -130,33 +127,78 @@ local function loadModule(value: any, errors: Errors?): (LoadedModule?, string?)
130127 return result , identity
131128end
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+
133176local 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
182224end
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)
217261end
218262
219263local 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(
300345end
301346
302347local 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 )
399444end
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]=]
412457function 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' )
431476end
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' )
448489end
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]=]
459500function 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]=]
495536function 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 )
544585end
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]=]
557598function 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]=]
576617function 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]=]
592633function 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 )
612650end
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 )
626664end
0 commit comments