Skip to content

Commit

Permalink
Created a lite variant - use case: plugin dev
Browse files Browse the repository at this point in the history
  • Loading branch information
Mullets-Gavin committed Dec 23, 2020
1 parent 74eb7c8 commit b0e9ed8
Show file tree
Hide file tree
Showing 2 changed files with 288 additions and 0 deletions.
236 changes: 236 additions & 0 deletions lite/Lighter.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
--[=[
@Author: Gavin "Mullets" Rosenthal
@Desc: Lighter, a lite variant of Loader, a Roblox Luau Library Loader by Mullet Mafia Dev
@Notes: Lighter has the bare basics for requiring modules with a lazy-load string method
]=]

--[=[
[DOCUMENTATION]:
https://github.com/Mullets-Gavin/Loader
Listed below is a quick glance on the API, visit the link above for proper documentation.
Lighter(module)
Lighter.require(module)
Lighter.VERSION()
[OUTLINE]:
Lighter
├─ .__require(module,requirer)
│ ├─ if instance, requires the module & caches it
│ └─ if string, searches the following:
│ └─ deep search the parent container
├─ Lighter(module) | .require(module)
│ └─ Redirects & returns __require()
├─ .__version() and .VERSION Returns the current version
└─ :__call redirects to .require
[LICENSE]:
MIT License
Copyright (c) 2020 Mullet Mafia Dev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]=]

local RunService = game:GetService('RunService')

local IsStudio = RunService:IsStudio() and 'Studio'
local IsServer = RunService:IsServer() and 'Server'
local IsClient = RunService:IsClient() and 'Client'

local Lighter = {}
Lighter.__index = Lighter
Lighter._ModuleCache = {}
Lighter._Initialized = false
Lighter._Name = string.upper(script.Name)
Lighter._Container = script.Parent
Lighter._Version = {
['MAJOR'] = 1;
['MINOR'] = 0;
['PATCH'] = 0;
}

--[=[
Lighters settings
Defaults:
Lighter.MaxRetryTime = 5
Lighter.Timeout = 5
Lighter.Filter = false
]=]
Lighter.MaxRetryTime = 5
Lighter.Timeout = 5
Lighter.Filter = false

--[=[
Safely require a module like the Roblox require function works
@param module Instance -- required module
@param requirer Instance -- the source script requiring this
@return table?
@private
]=]
local function SafeRequire(module: ModuleScript, requirer: Script): table?
local time = os.clock()
local event; event = RunService.Stepped:Connect(function()
if os.clock() >= time + Lighter.Timeout then
warn(string.format('%s -> %s is taking too long',tostring(requirer),tostring(module)))
if event then
event:Disconnect()
event = nil
end
end
end)

local loaded
local success,response = pcall(function()
loaded = require(module)
end)

if not success then
if type(loaded) == 'nil' and string.find(response,'exactly one value') then
error("Module did not return exactly one value: " .. module:GetFullName(), 3)
else
error("Module " .. module:GetFullName() .. " experienced an error while loading: " .. response, 3)
end
end

if event then
event:Disconnect()
event = nil
end

return loaded
end

--[=[
Deep search a list for a specific name
@param name string -- the name of the module
@param list table -- the list of instances to filter
@return ModuleScript?
@private
]=]
local function DeepSearch(name: string, list: table): ModuleScript?
for count,asset in ipairs(list) do
if not asset:IsA('ModuleScript') then continue end
if Lighter.Filter and asset.Parent:IsA('ModuleScript') then continue end

if string.lower(asset.Name) == name then
return asset
end
end

return nil
end

--[=[
The internal require function which searches the built-in library & all containers per-environment
@param module string | number | ModuleScript -- the module to require
@param requirer Script -- the script that required this function to simulate require()
@return table?
@private
]=]
function Lighter.__require(module: ModuleScript, requirer: Script): table?
local clock = os.clock()
local name = string.lower(typeof(module) == 'Instance' and module.Name or module)

if Lighter._ModuleCache[name] then
return Lighter._ModuleCache[name]
end

if typeof(module) == 'number' or typeof(module) == 'Instance' then
Lighter._ModuleCache[name] = require(module)
return Lighter._ModuleCache[name]
end

while not Lighter._ModuleCache[name] and os.clock() - clock < Lighter.MaxRetryTime do
local asset = DeepSearch(name,Lighter._Container:GetDescendants())
if asset then
Lighter._ModuleCache[name] = SafeRequire(asset,requirer)
return Lighter._ModuleCache[name]
end

RunService.Heartbeat:Wait()
end

assert(Lighter._ModuleCache[name],"attempted to require a non-existant module: '"..name.."'")
return Lighter._ModuleCache[name]
end

--[=[
Require a module instance or search containers for a module with a string
@param module string | number | Instance -- the module type to require
@return RequiredModule?
]=]
function Lighter.require(module: ModuleScript | string | number): table?
local requirer = getfenv(2).script
return Lighter.__require(module,requirer)
end

--[=[
Replace the require function by Roblox
@param module string | number | Instance -- the module type to require
@return table?
]=]
function Lighter:__call(module: ModuleScript | string | number): table?
local requirer = getfenv(2).script
return Lighter.__require(module,requirer)
end

--[=[
Provides the Lighter version when called tostring()
@return FormattedVersion
]=]
function Lighter:__tostring(): string
return 'Lighter '..Lighter.__version()
end

--[=[
Returns the current version of Lighter
@return FormattedVersion
]=]
function Lighter.__version(): string
return string.format('v%d.%d.%d',
Lighter._Version.MAJOR,
Lighter._Version.MINOR,
Lighter._Version.PATCH
)
end
Lighter.VERSION = Lighter.__version()

do
if not Lighter._Initialized and (not IsStudio or (IsStudio and IsServer)) then
Lighter._Initialized = true
print('Lighter by Mullet Mafia Dev initialized','|',Lighter.VERSION)
end

if IsClient and not IsStudio then
while not game:IsLoaded() do
game.Loaded:Wait()
end
end
end

return setmetatable(Lighter,Lighter)
52 changes: 52 additions & 0 deletions lite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<div align="center">
<h1>Lighter</h1>

[![version](https://img.shields.io/badge/version-v1.0.0-red)](https://github.com/Mullets-Gavin/Loader/releases)

Lighter, a lite variant of Loader, a Roblox Luau Library Loader
</div>

## What's Different?
Lighter is a lite variant of Loader, which includes no libraries & you only deep search the parented container. This means you can now have a blazing fast lazy-loader for localized modules without having to worry about any dependencies or conflicting names. This is an excellent choice for plugin development, seen in an example below.

## Hierarchy
```
ReplicatedStorage
└─ Folder
├─ Lighter
└─ Modules
```

## Example
```lua
----------------
-- Initialize --
----------------

-- recommended
local require = require(Folder:WaitForChild('Lighter'))

-- optional
local Lighter = require(Folder:WaitForChild('Lighter'))

---------------------
-- Require Example --
---------------------

local SomeModule = require('SomeModule') -- Lighter('SomeModule')
local SomeModule = require.require('SomeModule') -- Lighter.require('SomeModule')

--------------------
-- Plugin Example --
--------------------

local plugin = script:FindFirstAncestorWhichIsA('Plugin')
local require = require(plugin:FindFirstChild('Lighter',true))
local SomeModule = require('SomeModule')

-- Hierarchy
plugin
├─ Lighter
└─ Modules
└─ SomeModule
```

0 comments on commit b0e9ed8

Please sign in to comment.