- Added a new system to dynamically require source from multiple locations (including web)

- Added the Collection Element and moved parts of the List logic to collection
- Added a State Management System
- Added a better system to change the position/size of elements
- Removed the state plugin
This commit is contained in:
Robert Jelic
2025-10-27 08:25:58 +01:00
parent 565209d63e
commit b96875a3e9
19 changed files with 1699 additions and 521 deletions

View File

@@ -17,6 +17,16 @@ local ElementManager = {}
ElementManager._elements = {}
ElementManager._plugins = {}
ElementManager._APIs = {}
ElementManager._config = {
autoLoadMissing = false,
allowRemoteLoading = false,
allowDiskLoading = true,
remoteSources = {},
diskMounts = {},
useGlobalCache = false,
globalCacheName = "_BASALT_ELEMENT_CACHE"
}
local elementsDirectory = fs.combine(dir, "elements")
local pluginsDirectory = fs.combine(dir, "plugins")
@@ -29,7 +39,9 @@ if fs.exists(elementsDirectory) then
ElementManager._elements[name] = {
class = nil,
plugins = {},
loaded = false
loaded = false,
source = "local",
path = nil
}
end
end
@@ -66,7 +78,9 @@ if(minified)then
ElementManager._elements[name:gsub(".lua", "")] = {
class = nil,
plugins = {},
loaded = false
loaded = false,
source = "local",
path = nil
}
end
if(minified_pluginDirectory==nil)then
@@ -90,20 +104,225 @@ if(minified)then
end
end
local function saveToGlobalCache(name, element)
if not ElementManager._config.useGlobalCache then return end
if not _G[ElementManager._config.globalCacheName] then
_G[ElementManager._config.globalCacheName] = {}
end
_G[ElementManager._config.globalCacheName][name] = element
log.debug("Cached element in _G: "..name)
end
local function loadFromGlobalCache(name)
if not ElementManager._config.useGlobalCache then return nil end
if _G[ElementManager._config.globalCacheName] and
_G[ElementManager._config.globalCacheName][name] then
log.debug("Loaded element from _G cache: "..name)
return _G[ElementManager._config.globalCacheName][name]
end
return nil
end
--- Configures the ElementManager
--- @param config table Configuration options
function ElementManager.configure(config)
for k, v in pairs(config) do
if ElementManager._config[k] ~= nil then
ElementManager._config[k] = v
end
end
end
--- Registers a disk mount point for loading elements
--- @param mountPath string The path to the disk mount
function ElementManager.registerDiskMount(mountPath)
if not fs.exists(mountPath) then
error("Disk mount path does not exist: "..mountPath)
end
table.insert(ElementManager._config.diskMounts, mountPath)
log.info("Registered disk mount: "..mountPath)
local elementsPath = fs.combine(mountPath, "elements")
if fs.exists(elementsPath) then
for _, file in ipairs(fs.list(elementsPath)) do
local name = file:match("(.+).lua")
if name then
if not ElementManager._elements[name] then
log.debug("Found element on disk: "..name)
ElementManager._elements[name] = {
class = nil,
plugins = {},
loaded = false,
source = "disk",
path = fs.combine(elementsPath, file)
}
end
end
end
end
end
--- Registers a remote source for an element
--- @param elementName string The name of the element
--- @param url string The URL to load the element from
function ElementManager.registerRemoteSource(elementName, url)
if not ElementManager._config.allowRemoteLoading then
error("Remote loading is disabled. Enable with ElementManager.configure({allowRemoteLoading = true})")
end
ElementManager._config.remoteSources[elementName] = url
if not ElementManager._elements[elementName] then
ElementManager._elements[elementName] = {
class = nil,
plugins = {},
loaded = false,
source = "remote",
path = url
}
else
ElementManager._elements[elementName].source = "remote"
ElementManager._elements[elementName].path = url
end
log.info("Registered remote source for "..elementName..": "..url)
end
local function loadFromRemote(url)
if not http then
error("HTTP API is not available. Enable it in your CC:Tweaked config.")
end
log.info("Loading element from remote: "..url)
local response = http.get(url)
if not response then
error("Failed to download from: "..url)
end
local content = response.readAll()
response.close()
if not content or content == "" then
error("Empty response from: "..url)
end
local func, err = load(content, url, "t", _ENV)
if not func then
error("Failed to load element from "..url..": "..tostring(err))
end
local element = func()
return element
end
local function loadFromDisk(path)
if not fs.exists(path) then
error("Element file does not exist: "..path)
end
log.info("Loading element from disk: "..path)
local func, err = loadfile(path)
if not func then
error("Failed to load element from "..path..": "..tostring(err))
end
local element = func()
return element
end
--- Tries to load an element from any available source
--- @param name string The element name
--- @return boolean success Whether the element was loaded
function ElementManager.tryAutoLoad(name)
-- Try disk mounts first
if ElementManager._config.allowDiskLoading then
for _, mountPath in ipairs(ElementManager._config.diskMounts) do
local elementsPath = fs.combine(mountPath, "elements")
local filePath = fs.combine(elementsPath, name..".lua")
if fs.exists(filePath) then
ElementManager._elements[name] = {
class = nil,
plugins = {},
loaded = false,
source = "disk",
path = filePath
}
ElementManager.loadElement(name)
return true
end
end
end
if ElementManager._config.allowRemoteLoading and ElementManager._config.remoteSources[name] then
ElementManager.loadElement(name)
return true
end
return false
end
--- Loads an element by name. This will load the element and apply any plugins to it.
--- @param name string The name of the element to load
--- @usage ElementManager.loadElement("Button")
function ElementManager.loadElement(name)
if not ElementManager._elements[name] then
-- Try to auto-load if enabled
if ElementManager._config.autoLoadMissing then
local success = ElementManager.tryAutoLoad(name)
if not success then
error("Element '"..name.."' not found and could not be auto-loaded")
end
else
error("Element '"..name.."' not found")
end
end
if not ElementManager._elements[name].loaded then
package.path = main.."rom/?"
local element = require(fs.combine("elements", name))
package.path = defaultPath
local source = ElementManager._elements[name].source or "local"
local element
local loadedFromCache = false
element = loadFromGlobalCache(name)
if element then
loadedFromCache = true
log.info("Loaded element from _G cache: "..name)
elseif source == "local" then
package.path = main.."rom/?"
element = require(fs.combine("elements", name))
package.path = defaultPath
elseif source == "disk" then
if not ElementManager._config.allowDiskLoading then
error("Disk loading is disabled for element: "..name)
end
element = loadFromDisk(ElementManager._elements[name].path)
saveToGlobalCache(name, element)
elseif source == "remote" then
if not ElementManager._config.allowRemoteLoading then
error("Remote loading is disabled for element: "..name)
end
element = loadFromRemote(ElementManager._elements[name].path)
saveToGlobalCache(name, element)
else
error("Unknown source type: "..source)
end
ElementManager._elements[name] = {
class = element,
plugins = element.plugins,
loaded = true
loaded = true,
source = loadedFromCache and "cache" or source,
path = ElementManager._elements[name].path
}
log.debug("Loaded element: "..name)
if not loadedFromCache then
log.debug("Loaded element: "..name.." from "..source)
end
if(ElementManager._plugins[name]~=nil)then
for _, plugin in pairs(ElementManager._plugins[name]) do
@@ -148,6 +367,17 @@ end
--- @param name string The name of the element to get
--- @return table Element The element class
function ElementManager.getElement(name)
if not ElementManager._elements[name] then
if ElementManager._config.autoLoadMissing then
local success = ElementManager.tryAutoLoad(name)
if not success then
error("Element '"..name.."' not found")
end
else
error("Element '"..name.."' not found")
end
end
if not ElementManager._elements[name].loaded then
ElementManager.loadElement(name)
end
@@ -167,4 +397,55 @@ function ElementManager.getAPI(name)
return ElementManager._APIs[name]
end
--- Checks if an element exists (is registered)
--- @param name string The element name
--- @return boolean exists Whether the element exists
function ElementManager.hasElement(name)
return ElementManager._elements[name] ~= nil
end
--- Checks if an element is loaded
--- @param name string The element name
--- @return boolean loaded Whether the element is loaded
function ElementManager.isElementLoaded(name)
return ElementManager._elements[name] and ElementManager._elements[name].loaded or false
end
--- Clears the global cache (_G)
--- @usage ElementManager.clearGlobalCache()
function ElementManager.clearGlobalCache()
if _G[ElementManager._config.globalCacheName] then
_G[ElementManager._config.globalCacheName] = nil
log.info("Cleared global element cache")
end
end
--- Gets cache statistics
--- @return table stats Cache statistics with size and element names
function ElementManager.getCacheStats()
if not _G[ElementManager._config.globalCacheName] then
return {size = 0, elements = {}}
end
local elements = {}
for name, _ in pairs(_G[ElementManager._config.globalCacheName]) do
table.insert(elements, name)
end
return {
size = #elements,
elements = elements
}
end
--- Preloads elements into the global cache
--- @param elementNames table List of element names to preload
function ElementManager.preloadElements(elementNames)
for _, name in ipairs(elementNames) do
if ElementManager._elements[name] and not ElementManager._elements[name].loaded then
ElementManager.loadElement(name)
end
end
end
return ElementManager