Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 72 additions & 51 deletions src/Overture.luau
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
--!nonstrict
--// Initialization

local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")
const LogService = game:GetService("LogService")
const RunService = game:GetService("RunService")
const CollectionService = game:GetService("CollectionService")

--[=[
@class Overture
Expand All @@ -17,17 +18,30 @@ local CollectionService = game:GetService("CollectionService")
Remember, all library names in Overture must be unique!
:::
]=]
local Overture = {}
local LibraryThreadCache = {}
local Libraries: {[string]: ModuleScript} = {}
const Overture = {}

const LibrarySignalCache: {[string]: {BindableEvent}} = {}
const Libraries: {[string]: ModuleScript} = {}

const IsClient = RunService:IsClient()
local IsDebug = (script:GetAttribute("Debug") == true)

--// Variables

const YIELD_WARN_DELAY = 5

--// Functions

--[=[
@within Overture
@ignore
]=]
local function Retrieve(InstanceName: string, InstanceClass: string, InstanceParent: Instance, ForceWait: boolean?): Instance
const function Retrieve(
InstanceName: string,
InstanceClass: string,
InstanceParent: Instance,
ForceWait: boolean?
): Instance
if ForceWait and RunService:IsRunning() then
return InstanceParent:WaitForChild(InstanceName)
end
Expand All @@ -50,7 +64,7 @@ end
@param Tag -- The CollectionService tag
@param Function -- The function to call
]=]
local function BindToTag(Tag: string, Function: (Instance) -> ()): RBXScriptConnection
const function BindToTag(Tag: string, Function: (Instance) -> ()): RBXScriptConnection
for _, Value in CollectionService:GetTagged(Tag) do
task.spawn(Function, Value)
end
Expand All @@ -64,12 +78,11 @@ end

@param Module -- The ModuleScript to require
@param NamedImports -- When provided, returns the named variables instead of the entire ModuleScript
@return any?
]=]
local function RequireModule(Module: ModuleScript, NamedImports: {string}?)
const function RequireModule(Module: ModuleScript, NamedImports: { string }?): any?
if NamedImports then
local Exports = require(Module)
local Imports = {}
const Exports = require(Module)
const Imports = {}

for ImportIndex, ImportName in ipairs(NamedImports) do
Imports[ImportIndex] = Exports[ImportName]
Expand Down Expand Up @@ -114,18 +127,41 @@ end
@yields
@param Index -- The name of the ModuleScript
@param NamedImports -- When provided, returns the named variables instead of the entire ModuleScript
@return any?

@error The library `Index` does not exist! -- Thrown on the server, if no ModuleScript exists with the given name.
]=]
function Overture:LoadLibrary(Index: string, NamedImports: {string}?)
function Overture:LoadLibrary(Index: string, NamedImports: {string}?): any?
if Libraries[Index] then
return RequireModule(Libraries[Index], NamedImports)
else
assert(not RunService:IsServer(), "The library \"" .. Index .. "\" does not exist!")
assert(IsClient, `The library "{Index}" does not exist!`)

LibrarySignalCache[Index] = (LibrarySignalCache[Index] or {})
const Signal = Instance.new("BindableEvent")
table.insert(LibrarySignalCache[Index], Signal)

const CallStack = `{debug.traceback("Stack Begin")}Stack End`
local DebugThread: thread? do
if IsDebug then
DebugThread = task.delay(YIELD_WARN_DELAY, function()
LogService:Warn([[Infinite yield possible on 'Overture:LoadLibrary("{LibraryName}")']], {
LibraryName = Index
})

for _, Line in string.split(CallStack, "\n") do
LogService:Info(Line)
end
end)
end
end

local Object = Signal.Event:Wait()

table.insert(LibraryThreadCache, {Thread = coroutine.running(), RequestedIndex = Index, RequestedAt = time()})
return RequireModule(coroutine.yield(), NamedImports)
if DebugThread then
pcall(task.cancel, DebugThread)
end

return RequireModule(Object, NamedImports)
end
end

Expand All @@ -145,12 +181,9 @@ end
@yields
@client
@param ... any
@return any?
]=]
function Overture:LoadLibraryOnClient(...)
if RunService:IsClient() then
return self:LoadLibrary(...)
end
function Overture:LoadLibraryOnClient(...): any?
return if IsClient then self:LoadLibrary(...) else nil
end

--[=[
Expand All @@ -168,12 +201,9 @@ end

@server
@param ... any
@return any?
]=]
function Overture:LoadLibraryOnServer(...)
if RunService:IsServer() then
return self:LoadLibrary(...)
end
function Overture:LoadLibraryOnServer(...): any?
return if IsClient then nil else self:LoadLibrary(...)
end

--[=[
Expand All @@ -190,12 +220,12 @@ end
@param Parent -- An optional override parent Instance. Useful for retrieving dependencies.
]=]
function Overture:Get(InstanceClass: string, InstanceName: string, Parent: Instance?): Instance
local SetFolder = (Parent or Retrieve(InstanceClass, "Folder", script, RunService:IsClient()))
local Item = SetFolder:FindFirstChild(InstanceName)
const SetFolder = (Parent or Retrieve(InstanceClass, "Folder", script, IsClient))
const Item = SetFolder:FindFirstChild(InstanceName)

if Item then
return Item
elseif RunService:IsServer() or not RunService:IsRunning() then
elseif not IsClient or not RunService:IsRunning() then
return Retrieve(InstanceName, InstanceClass, SetFolder)
else
return SetFolder:WaitForChild(InstanceName)
Expand Down Expand Up @@ -234,37 +264,28 @@ end
@param Parent -- An optional override parent Instance. Useful for retrieving dependencies.
]=]
function Overture:WaitFor(InstanceClass: string, InstanceName: string, Parent: Instance?): Instance
local IsClient = RunService:IsClient()
return (Parent or Retrieve(InstanceClass, "Folder", script, IsClient)):WaitForChild(InstanceName, if IsClient then math.huge else nil)
return (Parent or Retrieve(InstanceClass, "Folder", script, IsClient) :: any) --// silence linter
:WaitForChild(InstanceName, if IsClient then math.huge else nil) :: Instance
end

task.spawn(BindToTag, "oLibrary", function(Object)
Libraries[Object.Name] = Object

for _, Cached in LibraryThreadCache do
if Object.Name == Cached.RequestedIndex then
task.defer(Cached.Thread, Object)
task.delay(1, function()
table.remove(LibraryThreadCache, table.find(LibraryThreadCache, Cached))
end)
end
if not LibrarySignalCache[Object.Name] then
return
end
end)

task.spawn(function()
while script:GetAttribute("Debug") do
task.wait(1)

for _, Cached in LibraryThreadCache do
if Cached.WarningEmitted then continue end
if (time() - Cached.RequestedAt) > 5 then
warn(string.format([[Infinite yield possible on Overture:LoadLibrary("%s").]], Cached.RequestedIndex))
Cached.WarningEmitted = true
end
end

for _, Signal in LibrarySignalCache[Object.Name] do
Signal:Fire(Object)
end

LibrarySignalCache[Object.Name] = nil
end)

--// Triggers

script:GetAttributeChangedSignal("Debug"):Connect(function()
IsDebug = (script:GetAttribute("Debug") == true)
end)

return Overture