@@ -17,6 +17,16 @@ local ElementManager = {}
1717ElementManager ._elements = {}
1818ElementManager ._plugins = {}
1919ElementManager ._APIs = {}
20+ ElementManager ._config = {
21+ autoLoadMissing = false ,
22+ allowRemoteLoading = false ,
23+ allowDiskLoading = true ,
24+ remoteSources = {},
25+ diskMounts = {},
26+ useGlobalCache = false ,
27+ globalCacheName = " _BASALT_ELEMENT_CACHE"
28+ }
29+
2030local elementsDirectory = fs .combine (dir , " elements" )
2131local pluginsDirectory = fs .combine (dir , " plugins" )
2232
@@ -29,7 +39,9 @@ if fs.exists(elementsDirectory) then
2939 ElementManager ._elements [name ] = {
3040 class = nil ,
3141 plugins = {},
32- loaded = false
42+ loaded = false ,
43+ source = " local" ,
44+ path = nil
3345 }
3446 end
3547 end
@@ -66,7 +78,9 @@ if(minified)then
6678 ElementManager ._elements [name :gsub (" .lua" , " " )] = {
6779 class = nil ,
6880 plugins = {},
69- loaded = false
81+ loaded = false ,
82+ source = " local" ,
83+ path = nil
7084 }
7185 end
7286 if (minified_pluginDirectory == nil )then
@@ -90,20 +104,225 @@ if(minified)then
90104 end
91105end
92106
107+ local function saveToGlobalCache (name , element )
108+ if not ElementManager ._config .useGlobalCache then return end
109+
110+ if not _G [ElementManager ._config .globalCacheName ] then
111+ _G [ElementManager ._config .globalCacheName ] = {}
112+ end
113+
114+ _G [ElementManager ._config .globalCacheName ][name ] = element
115+ log .debug (" Cached element in _G: " .. name )
116+ end
117+
118+ local function loadFromGlobalCache (name )
119+ if not ElementManager ._config .useGlobalCache then return nil end
120+
121+ if _G [ElementManager ._config .globalCacheName ] and
122+ _G [ElementManager ._config .globalCacheName ][name ] then
123+ log .debug (" Loaded element from _G cache: " .. name )
124+ return _G [ElementManager ._config .globalCacheName ][name ]
125+ end
126+
127+ return nil
128+ end
129+
130+ --- Configures the ElementManager
131+ --- @param config table Configuration options
132+ function ElementManager .configure (config )
133+ for k , v in pairs (config ) do
134+ if ElementManager ._config [k ] ~= nil then
135+ ElementManager ._config [k ] = v
136+ end
137+ end
138+ end
139+
140+ --- Registers a disk mount point for loading elements
141+ --- @param mountPath string The path to the disk mount
142+ function ElementManager .registerDiskMount (mountPath )
143+ if not fs .exists (mountPath ) then
144+ error (" Disk mount path does not exist: " .. mountPath )
145+ end
146+ table.insert (ElementManager ._config .diskMounts , mountPath )
147+ log .info (" Registered disk mount: " .. mountPath )
148+
149+ local elementsPath = fs .combine (mountPath , " elements" )
150+ if fs .exists (elementsPath ) then
151+ for _ , file in ipairs (fs .list (elementsPath )) do
152+ local name = file :match (" (.+).lua" )
153+ if name then
154+ if not ElementManager ._elements [name ] then
155+ log .debug (" Found element on disk: " .. name )
156+ ElementManager ._elements [name ] = {
157+ class = nil ,
158+ plugins = {},
159+ loaded = false ,
160+ source = " disk" ,
161+ path = fs .combine (elementsPath , file )
162+ }
163+ end
164+ end
165+ end
166+ end
167+ end
168+
169+ --- Registers a remote source for an element
170+ --- @param elementName string The name of the element
171+ --- @param url string The URL to load the element from
172+ function ElementManager .registerRemoteSource (elementName , url )
173+ if not ElementManager ._config .allowRemoteLoading then
174+ error (" Remote loading is disabled. Enable with ElementManager.configure({allowRemoteLoading = true})" )
175+ end
176+ ElementManager ._config .remoteSources [elementName ] = url
177+
178+ if not ElementManager ._elements [elementName ] then
179+ ElementManager ._elements [elementName ] = {
180+ class = nil ,
181+ plugins = {},
182+ loaded = false ,
183+ source = " remote" ,
184+ path = url
185+ }
186+ else
187+ ElementManager ._elements [elementName ].source = " remote"
188+ ElementManager ._elements [elementName ].path = url
189+ end
190+
191+ log .info (" Registered remote source for " .. elementName .. " : " .. url )
192+ end
193+
194+ local function loadFromRemote (url )
195+ if not http then
196+ error (" HTTP API is not available. Enable it in your CC:Tweaked config." )
197+ end
198+
199+ log .info (" Loading element from remote: " .. url )
200+
201+ local response = http .get (url )
202+ if not response then
203+ error (" Failed to download from: " .. url )
204+ end
205+
206+ local content = response .readAll ()
207+ response .close ()
208+
209+ if not content or content == " " then
210+ error (" Empty response from: " .. url )
211+ end
212+
213+ local func , err = load (content , url , " t" , _ENV )
214+ if not func then
215+ error (" Failed to load element from " .. url .. " : " .. tostring (err ))
216+ end
217+
218+ local element = func ()
219+ return element
220+ end
221+
222+ local function loadFromDisk (path )
223+ if not fs .exists (path ) then
224+ error (" Element file does not exist: " .. path )
225+ end
226+
227+ log .info (" Loading element from disk: " .. path )
228+
229+ local func , err = loadfile (path )
230+ if not func then
231+ error (" Failed to load element from " .. path .. " : " .. tostring (err ))
232+ end
233+
234+ local element = func ()
235+ return element
236+ end
237+
238+ --- Tries to load an element from any available source
239+ --- @param name string The element name
240+ --- @return boolean success Whether the element was loaded
241+ function ElementManager .tryAutoLoad (name )
242+ -- Try disk mounts first
243+ if ElementManager ._config .allowDiskLoading then
244+ for _ , mountPath in ipairs (ElementManager ._config .diskMounts ) do
245+ local elementsPath = fs .combine (mountPath , " elements" )
246+ local filePath = fs .combine (elementsPath , name .. " .lua" )
247+
248+ if fs .exists (filePath ) then
249+ ElementManager ._elements [name ] = {
250+ class = nil ,
251+ plugins = {},
252+ loaded = false ,
253+ source = " disk" ,
254+ path = filePath
255+ }
256+ ElementManager .loadElement (name )
257+ return true
258+ end
259+ end
260+ end
261+
262+ if ElementManager ._config .allowRemoteLoading and ElementManager ._config .remoteSources [name ] then
263+ ElementManager .loadElement (name )
264+ return true
265+ end
266+
267+ return false
268+ end
269+
93270--- Loads an element by name. This will load the element and apply any plugins to it.
94271--- @param name string The name of the element to load
95272--- @usage ElementManager.loadElement("Button")
96273function ElementManager .loadElement (name )
274+ if not ElementManager ._elements [name ] then
275+ -- Try to auto-load if enabled
276+ if ElementManager ._config .autoLoadMissing then
277+ local success = ElementManager .tryAutoLoad (name )
278+ if not success then
279+ error (" Element '" .. name .. " ' not found and could not be auto-loaded" )
280+ end
281+ else
282+ error (" Element '" .. name .. " ' not found" )
283+ end
284+ end
285+
97286 if not ElementManager ._elements [name ].loaded then
98- package.path = main .. " rom/?"
99- local element = require (fs .combine (" elements" , name ))
100- package.path = defaultPath
287+ local source = ElementManager ._elements [name ].source or " local"
288+ local element
289+ local loadedFromCache = false
290+
291+ element = loadFromGlobalCache (name )
292+ if element then
293+ loadedFromCache = true
294+ log .info (" Loaded element from _G cache: " .. name )
295+ elseif source == " local" then
296+ package.path = main .. " rom/?"
297+ element = require (fs .combine (" elements" , name ))
298+ package.path = defaultPath
299+ elseif source == " disk" then
300+ if not ElementManager ._config .allowDiskLoading then
301+ error (" Disk loading is disabled for element: " .. name )
302+ end
303+ element = loadFromDisk (ElementManager ._elements [name ].path )
304+ saveToGlobalCache (name , element )
305+ elseif source == " remote" then
306+ if not ElementManager ._config .allowRemoteLoading then
307+ error (" Remote loading is disabled for element: " .. name )
308+ end
309+ element = loadFromRemote (ElementManager ._elements [name ].path )
310+ saveToGlobalCache (name , element )
311+ else
312+ error (" Unknown source type: " .. source )
313+ end
314+
101315 ElementManager ._elements [name ] = {
102316 class = element ,
103317 plugins = element .plugins ,
104- loaded = true
318+ loaded = true ,
319+ source = loadedFromCache and " cache" or source ,
320+ path = ElementManager ._elements [name ].path
105321 }
106- log .debug (" Loaded element: " .. name )
322+
323+ if not loadedFromCache then
324+ log .debug (" Loaded element: " .. name .. " from " .. source )
325+ end
107326
108327 if (ElementManager ._plugins [name ]~= nil )then
109328 for _ , plugin in pairs (ElementManager ._plugins [name ]) do
148367--- @param name string The name of the element to get
149368--- @return table Element The element class
150369function ElementManager .getElement (name )
370+ if not ElementManager ._elements [name ] then
371+ if ElementManager ._config .autoLoadMissing then
372+ local success = ElementManager .tryAutoLoad (name )
373+ if not success then
374+ error (" Element '" .. name .. " ' not found" )
375+ end
376+ else
377+ error (" Element '" .. name .. " ' not found" )
378+ end
379+ end
380+
151381 if not ElementManager ._elements [name ].loaded then
152382 ElementManager .loadElement (name )
153383 end
@@ -167,4 +397,55 @@ function ElementManager.getAPI(name)
167397 return ElementManager ._APIs [name ]
168398end
169399
400+ --- Checks if an element exists (is registered)
401+ --- @param name string The element name
402+ --- @return boolean exists Whether the element exists
403+ function ElementManager .hasElement (name )
404+ return ElementManager ._elements [name ] ~= nil
405+ end
406+
407+ --- Checks if an element is loaded
408+ --- @param name string The element name
409+ --- @return boolean loaded Whether the element is loaded
410+ function ElementManager .isElementLoaded (name )
411+ return ElementManager ._elements [name ] and ElementManager ._elements [name ].loaded or false
412+ end
413+
414+ --- Clears the global cache (_G)
415+ --- @usage ElementManager.clearGlobalCache()
416+ function ElementManager .clearGlobalCache ()
417+ if _G [ElementManager ._config .globalCacheName ] then
418+ _G [ElementManager ._config .globalCacheName ] = nil
419+ log .info (" Cleared global element cache" )
420+ end
421+ end
422+
423+ --- Gets cache statistics
424+ --- @return table stats Cache statistics with size and element names
425+ function ElementManager .getCacheStats ()
426+ if not _G [ElementManager ._config .globalCacheName ] then
427+ return {size = 0 , elements = {}}
428+ end
429+
430+ local elements = {}
431+ for name , _ in pairs (_G [ElementManager ._config .globalCacheName ]) do
432+ table.insert (elements , name )
433+ end
434+
435+ return {
436+ size = # elements ,
437+ elements = elements
438+ }
439+ end
440+
441+ --- Preloads elements into the global cache
442+ --- @param elementNames table List of element names to preload
443+ function ElementManager .preloadElements (elementNames )
444+ for _ , name in ipairs (elementNames ) do
445+ if ElementManager ._elements [name ] and not ElementManager ._elements [name ].loaded then
446+ ElementManager .loadElement (name )
447+ end
448+ end
449+ end
450+
170451return ElementManager
0 commit comments