diff --git a/.gitignore b/.gitignore index 8cc8bf7..dadad45 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ scripts/Libraries/* !scripts/.gitkeep !scripts/ExampleModule.lua !scripts/Libraries/ExampleLibrary.lua +!scripts/Libraries/ExampleDependency.lua +!scripts/Libraries/ExampleDependencyError.lua ## ignore vscode settings .vscode/* diff --git a/scripts/ExampleModule.lua b/scripts/ExampleModule.lua index e659bb7..d6879e8 100644 --- a/scripts/ExampleModule.lua +++ b/scripts/ExampleModule.lua @@ -4,7 +4,7 @@ local Player = Bastion.UnitManager:Get('player') local FlashHeal = Bastion.SpellBook:GetSpell(2061) -local AdvancedMath = Bastion:GetLibrary('AdvancedMath') +local AdvancedMath = Bastion:Import('AdvancedMath') print(AdvancedMath:Add(1, 2)) diff --git a/scripts/Libraries/ExampleDependency.lua b/scripts/Libraries/ExampleDependency.lua new file mode 100644 index 0000000..87c1449 --- /dev/null +++ b/scripts/Libraries/ExampleDependency.lua @@ -0,0 +1,21 @@ +local Tinkr, Bastion = ... + +local Player = Bastion.UnitManager:Get('player') + +Bastion:RegisterLibrary(Bastion.Library:New({ + name = 'Dependable', + exports = { + default = function() + local Dependable = {} + + Dependable.__index = Dependable + + function Dependable:Test(a) + print(a) + end + + return Dependable + end, + Test = 5 + } +})) diff --git a/scripts/Libraries/ExampleDependencyError.lua b/scripts/Libraries/ExampleDependencyError.lua new file mode 100644 index 0000000..5fc4485 --- /dev/null +++ b/scripts/Libraries/ExampleDependencyError.lua @@ -0,0 +1,15 @@ +local Tinkr, Bastion = ... + +Bastion:RegisterLibrary(Bastion.Library:New({ + name = 'Circular', + exports = { + default = function(self) + -- Return default first, and then the remaining exports + local Math, OtherExports = self:Import('AdvancedMath') + + print(Math:Add(1, 2)) + + return 'Circular' + end + } +})) diff --git a/scripts/Libraries/ExampleLibrary.lua b/scripts/Libraries/ExampleLibrary.lua index 15a4a36..ddb7afa 100644 --- a/scripts/Libraries/ExampleLibrary.lua +++ b/scripts/Libraries/ExampleLibrary.lua @@ -1,14 +1,25 @@ local Tinkr, Bastion = ... -local ExampleModule = Bastion.Module:New('ExampleModule') -local Player = Bastion.UnitManager:Get('player') +Bastion:RegisterLibrary(Bastion.Library:New({ + name = 'AdvancedMath', + exports = { + default = function(self) -- Function exports are called when the library is loaded + -- Return default first, and then the remaining exports + local Dependable, OtherExports = self:Import('Dependable') -local AdvancedMath = {} + local CircularDependency = self:Import('Circular') -- Causes a circular dependency error -AdvancedMath.__index = AdvancedMath + Dependable:Test(OtherExports.Test) -function AdvancedMath:Add(a, b) - return a + b -end + local AdvancedMath = {} -Bastion:RegisterLibrary('AdvancedMath', AdvancedMath) + AdvancedMath.__index = AdvancedMath + + function AdvancedMath:Add(a, b) + return a + b + end + + return AdvancedMath + end + } +})) diff --git a/src/Library/Library.lua b/src/Library/Library.lua new file mode 100644 index 0000000..3d0b2b8 --- /dev/null +++ b/src/Library/Library.lua @@ -0,0 +1,115 @@ +local Tinkr, Bastion = ... + +---@class Library +---@field name string +---@field dependencies table +---@field exports table +---@field resolved table +local Library = { + name = nil, + dependencies = {}, + exports = { + default = function() + return nil + end + }, + resolved = nil +} + +Library.__index = Library + +---@param name string +---@param library table +---@return Library +function Library:New(library) + local self = { + name = library.name or nil, + dependencies = {}, + exports = library.exports or { + default = function() + return nil + end + }, + resolved = nil + } + + self = setmetatable(self, Library) + + return self +end + +function Library:ResolveExport(export) + if type(export) == 'function' then + return export(self) + end + + return export +end + +function Library:Resolve() + if not self.exports then + error("Library " .. self.name .. " has no exports") + end + + if self.resolved then + if self.exports.default then + return self.resolved[1], self.resolved[2] + end + + return unpack(self.resolved) + end + + if self.exports.default then + -- return default first if it exists + local default = self.exports.default + local remaining = {} + for k, v in pairs(self.exports) do + if k ~= 'default' then + remaining[k] = self:ResolveExport(v) + end + end + + self.resolved = {self:ResolveExport(default), remaining} + + return self.resolved[1], self.resolved[2] + end + + self.resolved = {} + + for k, v in pairs(self.exports) do + self.resolved[k] = self:ResolveExport(v) + end + + return unpack(self.resolved) +end + +function Library:DependsOn(other) + for _, dependency in pairs(self.dependencies) do + if dependency == other then + return true + end + end + + return false +end + +---@param library string +function Library:Import(library) + local lib = Bastion:GetLibrary(library) + + if not lib then + error("Library " .. library .. " does not exist") + end + + if not table.contains(self.dependencies, library) then + table.insert(self.dependencies, library) + end + + if lib:DependsOn(self.name) then + error("Circular dependency detected between " .. self.name .. " and " .. library) + end + + return lib:Resolve() +end + +return Library diff --git a/src/_bastion.lua b/src/_bastion.lua index 4c28181..1185846 100644 --- a/src/_bastion.lua +++ b/src/_bastion.lua @@ -14,6 +14,8 @@ end Bastion.ClassMagic = Bastion.require("ClassMagic") ---@type List Bastion.List = Bastion.require("List") +---@type Library +Bastion.Library = Bastion.require("Library") ---@type NotificationsList, Notification Bastion.NotificationsList, Bastion.Notification = Bastion.require("NotificationsList") ---@type Vector3 @@ -134,15 +136,6 @@ Bastion.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() end end) -function Bastion:RegisterLibrary(name, payload) - LIBRARIES[name] = payload - -- Bastion:Print("Registered Library", name) -end - -function Bastion:GetLibrary(name) - return LIBRARIES[name] -end - Bastion.Ticker = C_Timer.NewTicker(0.1, function() if not Bastion.CombatTimer:IsRunning() and UnitAffectingCombat("player") then Bastion.CombatTimer:Start() @@ -288,6 +281,81 @@ local function Load(dir) end end +---@param library Library +function Bastion:RegisterLibrary(library) + LIBRARIES[library.name] = library + print("Registered library", library.name) +end + +function Bastion:CheckLibraryDependencies() + for k, v in pairs(LIBRARIES) do + if v.dependencies then + for i = 1, #v.dependencies do + local dep = v.dependencies[i] + if LIBRARIES[dep] then + if LIBRARIES[dep].dependencies then + for j = 1, #LIBRARIES[dep].dependencies do + if LIBRARIES[dep].dependencies[j] == v.name then + Bastion:Print("Circular dependency detected between " .. v.name .. " and " .. dep) + return false + end + end + end + else + Bastion:Print("Library " .. v.name .. " depends on " .. dep .. " but it's not registered") + return false + end + end + end + end + + return true +end + +function Bastion:Import(library) + local lib = self:GetLibrary(library) + + if not lib then + error("Library " .. library .. " not found") + end + + return lib:Resolve() +end + +function Bastion:GetLibrary(name) + if not LIBRARIES[name] then + error("Library " .. name .. " not found") + end + + local library = LIBRARIES[name] + + -- if library.dependencies then + -- for i = 1, #library.dependencies do + -- local dep = library.dependencies[i] + -- if LIBRARIES[dep] then + -- if LIBRARIES[dep].dependencies then + -- for j = 1, #LIBRARIES[dep].dependencies do + -- if LIBRARIES[dep].dependencies[j] == library.name then + -- Bastion:Print("Circular dependency detected between " .. library.name .. " and " .. dep) + -- return false + -- end + -- end + -- end + -- else + -- Bastion:Print("Library " .. v.name .. " depends on " .. dep .. " but it's not registered") + -- return false + -- end + -- end + -- end + + return library +end + Load("scripts/bastion/scripts/Libraries/") + +-- if not Bastion:CheckLibraryDependencies() then +-- return +-- end + Load("scripts/bastion/scripts/Modules/") Load("scripts/bastion/scripts/")