comparison lib/AceModuleCore-2.0/AceModuleCore-2.0.lua @ 23:dba04d85c799

Added missing files in 1.0 dev tree
author Flick <flickerstreak@gmail.com>
date Fri, 07 Mar 2008 22:17:51 +0000
parents
children
comparison
equal deleted inserted replaced
22:1b9323256a1b 23:dba04d85c799
1 --[[
2 Name: AceModuleCore-2.0
3 Revision: $Rev: 43318 $
4 Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
5 Inspired By: Ace 1.x by Turan (turan@gryphon.com)
6 Website: http://www.wowace.com/
7 Documentation: http://www.wowace.com/index.php/AceModuleCore-2.0
8 SVN: http://svn.wowace.com/root/trunk/Ace2/AceModuleCore-2.0
9 Description: Mixin to provide a module system so that modules or plugins can
10 use an addon as its core.
11 Dependencies: AceLibrary, AceOO-2.0, AceAddon-2.0, AceEvent-2.0 (optional)
12 License: LGPL v2.1
13 ]]
14
15 local MAJOR_VERSION = "AceModuleCore-2.0"
16 local MINOR_VERSION = "$Revision: 43318 $"
17
18 if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end
19 if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
20
21 if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end
22
23 local function safecall(func, ...)
24 local success, err = pcall(func, ...)
25 if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("\n(.-: )in.-\n") or "") .. err) end
26 end
27
28 local AceEvent
29 local AceOO = AceLibrary:GetInstance("AceOO-2.0")
30 local AceModuleCore = AceOO.Mixin {
31 "NewModule",
32 "HasModule",
33 "GetModule",
34 "IsModule",
35 "IterateModules",
36 "IterateModulesWithMethod",
37 "CallMethodOnAllModules",
38 "SetModuleMixins",
39 "SetModuleClass",
40 "IsModuleActive",
41 "ToggleModuleActive",
42 "SetModuleDefaultState",
43 }
44 local AceAddon
45
46 local function getlibrary(lib)
47 if type(lib) == "string" then
48 return AceLibrary(lib)
49 else
50 return lib
51 end
52 end
53
54 local new, del
55 do
56 local list = setmetatable({}, {__mode='k'})
57 function new()
58 local t = next(list)
59 if t then
60 list[t] = nil
61 return t
62 else
63 return {}
64 end
65 end
66 function del(t)
67 for k in pairs(t) do
68 t[k] = nil
69 end
70 list[t] = true
71 return nil
72 end
73 end
74
75 local iterList = setmetatable({}, {__mode='v'})
76 local modulesWithMethod = setmetatable({}, {__mode='kv'})
77 do
78 local function func(t)
79 local i = t.i + 1
80 local l = t.l
81 local k = l[i]
82 if k then
83 t.i = i
84 return k, l.m[k]
85 else
86 t = del(t)
87 end
88 end
89 function AceModuleCore:IterateModules()
90 local list = iterList[self]
91 if not list then
92 list = new()
93 for k in pairs(self.modules) do
94 list[#list+1] = k
95 end
96 table.sort(list)
97 list.m = self.modules
98 iterList[self] = list
99 end
100 local t = new()
101 t.i = 0
102 t.l = list
103 return func, t, nil
104 end
105
106 function AceModuleCore:IterateModulesWithMethod(method)
107 local masterList = modulesWithMethod[self]
108 if not masterList then
109 masterList = new()
110 modulesWithMethod[self] = masterList
111 end
112 local list = masterList[method]
113 if not list then
114 list = new()
115 for k, v in pairs(self.modules) do
116 if self:IsModuleActive(k) and type(v[method]) == "function" then
117 list[#list+1] = k
118 end
119 end
120 table.sort(list)
121 list.m = self.modules
122 masterList[method] = list
123 end
124 local t = new()
125 t.i = 0
126 t.l = list
127 return func, t, nil
128 end
129
130 --[[----------------------------------------------------------------------------------
131 Notes:
132 Safely calls the given method on all active modules if it exists on said modules. This will automatically subvert any errors that occur in the modules.
133 Arguments:
134 string - the name of the method.
135 tuple - the list of arguments to call the method with.
136 Example:
137 core:CallMethodOnAllModules("OnSomething")
138 core:CallMethodOnAllModules("OnSomethingElse", 1, 2, 3, 4)
139 ------------------------------------------------------------------------------------]]
140 function AceModuleCore:CallMethodOnAllModules(method, ...)
141 for name, module in self:IterateModulesWithMethod(method) do
142 local success, ret = pcall(module[method], module, ...)
143 if not success then
144 geterrorhandler()(ret)
145 end
146 end
147 end
148 end
149
150 --[[----------------------------------------------------------------------------
151 Notes:
152 Create a new module, parented to self.
153 The module created does, in fact, inherit from AceAddon-2.0.
154 Arguments:
155 string - name/title of the Module.
156 list of mixins the module is to inherit from.
157 Example:
158 MyModule = core:NewModule('MyModule', "AceEvent-2.0", "AceHook-2.1")
159 ------------------------------------------------------------------------------]]
160 local tmp = {}
161 function AceModuleCore:NewModule(name, ...)
162 if not self.modules then
163 AceModuleCore:error("CreatePrototype() must be called before attempting to create a new module.", 2)
164 end
165 AceModuleCore:argCheck(name, 2, "string")
166 if name:len() == 0 then
167 AceModuleCore:error("Bad argument #2 to `NewModule`, string must not be empty")
168 end
169 if self.modules[name] then
170 AceModuleCore:error("The module %q has already been registered", name)
171 end
172
173 if iterList[self] then
174 iterList[self] = del(iterList[self])
175 end
176
177 for i = 1, select('#', ...) do
178 tmp[i] = getlibrary((select(i, ...)))
179 end
180
181 if self.moduleMixins then
182 for _,mixin in ipairs(self.moduleMixins) do
183 local exists = false
184 for _,v in ipairs(tmp) do
185 if mixin == v then
186 exists = true
187 break
188 end
189 end
190 if not exists then
191 tmp[#tmp+1] = mixin
192 end
193 end
194 end
195
196 local module = AceOO.Classpool(self.moduleClass, unpack(tmp)):new(name)
197 self.modules[name] = module
198 module.name = name
199 module.title = name
200
201 AceModuleCore.totalModules[module] = self
202
203 if modulesWithMethod[self] then
204 for k,v in pairs(modulesWithMethod[self]) do
205 modulesWithMethod[self] = del(v)
206 end
207 end
208
209 if type(self.OnModuleCreated) == "function" then
210 safecall(self.OnModuleCreated, self, name, module)
211 end
212 if AceEvent then
213 AceEvent:TriggerEvent("Ace2_ModuleCreated", module)
214 end
215
216 local num = #tmp
217 for i = 1, num do
218 tmp[i] = nil
219 end
220 return module
221 end
222 --[[----------------------------------------------------------------------------------
223 Notes:
224 Return whether the module names given are all available in the core.
225 Arguments:
226 list of strings that are the names of the modules. (typically you'd only check for one)
227 Returns:
228 * boolean - Whether all the modules are available in the core.
229 Example:
230 if core:HasModule('Bank') then
231 -- do banking
232 end
233 ------------------------------------------------------------------------------------]]
234 function AceModuleCore:HasModule(...)
235 for i = 1, select('#', ...) do
236 if not self.modules[select(i, ...)] then
237 return false
238 end
239 end
240
241 return true
242 end
243
244 --[[------------------------------------------------------------------------------------
245 Notes:
246 Return the module "name" if it exists.
247 If the module doesnot exist, an error is thrown.
248 Arguments:
249 string - the name of the module.
250 Returns:
251 The module requested, if it exists.
252 Example:
253 local bank = core:GetModule('Bank')
254 ------------------------------------------------------------------------------------]]
255 function AceModuleCore:GetModule(name)
256 if not self.modules then
257 AceModuleCore:error("Error initializing class. Please report error.")
258 end
259 if not self.modules[name] then
260 AceModuleCore:error("Cannot find module %q.", name)
261 end
262 return self.modules[name]
263 end
264
265 --[[----------------------------------------------------------------------------------
266 Notes:
267 Return whether the given module is actually a module.
268 Arguments:
269 reference to the module
270 Returns:
271 * boolean - whether the given module is actually a module.
272 Example:
273 if core:IsModule(module) then
274 -- do something
275 end
276 -- alternatively
277 if AceModuleCore:IsModule(module) then
278 -- checks all modules, no matter the parent
279 end
280 ------------------------------------------------------------------------------------]]
281 function AceModuleCore:IsModule(module)
282 if self == AceModuleCore then
283 return AceModuleCore.totalModules[module]
284 elseif type(module) == "table" then
285 if module.name and self.modules[module.name] and self.modules[module.name].name == module.name then
286 return true
287 end
288 for k,v in pairs(self.modules) do
289 if v == module then
290 return true
291 end
292 end
293 return false
294 end
295 end
296
297 --[[----------------------------------------------------------------------------------
298 Notes:
299 * Sets the default mixins for a given module.
300 * This cannot be called after :NewModule() has been called.
301 * This should really only be called if you use the mixins in your prototype.
302 Arguments:
303 list of mixins (up to 20)
304 Example:
305 core:SetModuleMixins("AceEvent-2.0", "AceHook-2.0")
306 ------------------------------------------------------------------------------------]]
307 function AceModuleCore:SetModuleMixins(...)
308 if self.moduleMixins then
309 AceModuleCore:error('Cannot call "SetModuleMixins" twice')
310 elseif not self.modules then
311 AceModuleCore:error("Error initializing class. Please report error.")
312 elseif next(self.modules) then
313 AceModuleCore:error('Cannot call "SetModuleMixins" after "NewModule" has been called.')
314 end
315
316 self.moduleMixins = { ... }
317 for i,v in ipairs(self.moduleMixins) do
318 self.moduleMixins[i] = getlibrary(v)
319 end
320 end
321
322 -- #NODOC
323 function AceModuleCore:SetModuleClass(class)
324 class = getlibrary(class)
325 if not AceOO.inherits(class, AceOO.Class) then
326 AceModuleCore:error("Bad argument #2 to `SetModuleClass' (Class expected)")
327 end
328 if not self.modules then
329 AceModuleCore:error("Error initializing class. Please report error.")
330 end
331 if self.customModuleClass then
332 AceModuleCore:error("Cannot call `SetModuleClass' twice.")
333 end
334 self.customModuleClass = true
335 self.moduleClass = class
336 self.modulePrototype = class.prototype
337 end
338
339 local mt = {__index=function(self, key)
340 self[key] = false
341 return false
342 end}
343 local defaultState = setmetatable({}, {__index=function(self, key)
344 local t = setmetatable({}, mt)
345 self[key] = t
346 return t
347 end})
348
349 local function isDisabled(core, module)
350 local moduleName
351 if type(module) == "table" then
352 moduleName = module.name
353 else
354 moduleName = module
355 end
356 local disabled
357 if type(module) == "table" and type(module.IsActive) == "function" then
358 return not module:IsActive()
359 elseif AceOO.inherits(core, "AceDB-2.0") then
360 local _,profile = core:GetProfile()
361 disabled = core.db and core.db.raw and core.db.raw.disabledModules and core.db.raw.disabledModules[profile] and core.db.raw.disabledModules[profile][moduleName]
362 else
363 disabled = core.disabledModules and core.disabledModules[moduleName]
364 end
365 if disabled == nil then
366 return defaultState[core][moduleName]
367 else
368 return disabled
369 end
370 end
371
372 --[[----------------------------------------------------------------------------------
373 Notes:
374 Sets the default active state of a module. This should be called before the ADDON_LOADED of the module.
375 Arguments:
376 string - name of the module.
377 table - reference to the module.
378 boolean - new state. false means disabled by default, true means enabled by default (true is the default).
379 Example:
380 self:SetModuleDefaultState('bank', false)
381 ------------------------------------------------------------------------------------]]
382 function AceModuleCore:SetModuleDefaultState(module, state)
383 AceModuleCore:argCheck(module, 2, "table", "string")
384 AceModuleCore:argCheck(state, 3, "boolean")
385
386 if type(module) == "table" then
387 if not self:IsModule(module) then
388 AceModuleCore:error("%q is not a module", module)
389 end
390 module = module.name
391 end
392
393 defaultState[self][module] = not state
394 end
395
396 --[[----------------------------------------------------------------------------------
397 Notes:
398 Toggles the active state of a module.
399
400 This calls module:ToggleActive([state]) if available.
401
402 If suspending, This will call :OnDisable() on the module if it is available. Also, it will iterate through the addon's mixins and call :OnEmbedDisable(module) if available. - this in turn will, through AceEvent and others, unregister events/hooks/etc. depending on the mixin. Also, it will call :OnModuleDisable(module) on the core if it is available.
403
404 If resuming, This will call :OnEnable(first) on the module if it is available. Also, it will iterate through the addon's mixins and call :OnEmbedEnable(module) if available. - this in turn will, through AceEvent and others, unregister events/hooks/etc. depending on the mixin. Also, it will call :OnModuleEnable(module) on the core if it is available.
405
406 If you call :ToggleModuleActive("name or module, true) and it is already active, it silently returns, same if you pass false and it is inactive.
407
408 Arguments:
409 string/table - name of the module or a reference to the module
410 [optional] boolean - new state. (default not :IsModuleActive("name" or module))
411 Returns:
412 * boolean - Whether the module is now in an active (enabled) state.
413 Example:
414 self:ToggleModuleActive('bank')
415 ------------------------------------------------------------------------------------]]
416 function AceModuleCore:ToggleModuleActive(module, state)
417 AceModuleCore:argCheck(module, 2, "table", "string")
418 AceModuleCore:argCheck(state, 3, "nil", "boolean")
419
420 if type(module) == "string" then
421 if not self:HasModule(module) then
422 AceModuleCore:error("Cannot find module %q", module)
423 end
424 module = self:GetModule(module)
425 elseif not self:IsModule(module) then
426 AceModuleCore:error("%q is not a module", module)
427 end
428
429 local disable
430 if state == nil then
431 disable = self:IsModuleActive(module)
432 else
433 disable = not state
434 if disable ~= self:IsModuleActive(module) then
435 return
436 end
437 end
438
439 if type(module.ToggleActive) == "function" then
440 return module:ToggleActive(not disable)
441 elseif AceOO.inherits(self, "AceDB-2.0") then
442 if not self.db or not self.db.raw then
443 AceModuleCore:error("Cannot toggle a module until `RegisterDB' has been called and `ADDON_LOADED' has been fired.")
444 end
445 if type(self.db.raw.disabledModules) ~= "table" then
446 self.db.raw.disabledModules = {}
447 end
448 local _,profile = self:GetProfile()
449 if type(self.db.raw.disabledModules[profile]) ~= "table" then
450 self.db.raw.disabledModules[profile] = {}
451 end
452 if type(self.db.raw.disabledModules[profile][module.name]) ~= "table" then
453 local value = nil
454 if disable ~= defaultState[self][module.name] then
455 value = disable
456 end
457 self.db.raw.disabledModules[profile][module.name] = value
458 end
459 if not disable then
460 if not next(self.db.raw.disabledModules[profile]) then
461 self.db.raw.disabledModules[profile] = nil
462 end
463 if not next(self.db.raw.disabledModules) then
464 self.db.raw.disabledModules = nil
465 end
466 end
467 else
468 if type(self.disabledModules) ~= "table" then
469 self.disabledModules = {}
470 end
471 local value = nil
472 if disable ~= defaultState[self][module.name] then
473 value = disable
474 end
475 self.disabledModules[module.name] = value
476 end
477 if AceOO.inherits(module, "AceAddon-2.0") then
478 if not AceAddon.addonsStarted[module] then
479 return
480 end
481 end
482 if not disable then
483 local first = nil
484 if AceOO.inherits(module, "AceAddon-2.0") then
485 if AceAddon.addonsEnabled and not AceAddon.addonsEnabled[module] then
486 AceAddon.addonsEnabled[module] = true
487 first = true
488 end
489 end
490 local current = module.class
491 while true do
492 if current == AceOO.Class then
493 break
494 end
495 if current.mixins then
496 for mixin in pairs(current.mixins) do
497 if type(mixin.OnEmbedEnable) == "function" then
498 safecall(mixin.OnEmbedEnable, mixin, module, first)
499 end
500 end
501 end
502 current = current.super
503 end
504 if type(module.OnEnable) == "function" then
505 safecall(module.OnEnable, module, first)
506 end
507 if AceEvent then
508 AceEvent:TriggerEvent("Ace2_AddonEnabled", module, first)
509 end
510 else
511 local current = module.class
512 while true do
513 if current == AceOO.Class then
514 break
515 end
516 if current.mixins then
517 for mixin in pairs(current.mixins) do
518 if type(mixin.OnEmbedDisable) == "function" then
519 safecall(mixin.OnEmbedDisable, mixin, module)
520 end
521 end
522 end
523 current = current.super
524 end
525 if type(module.OnDisable) == "function" then
526 safecall(module.OnDisable, module)
527 end
528 if AceEvent then
529 AceEvent:TriggerEvent("Ace2_AddonDisabled", module)
530 end
531 end
532 return not disable
533 end
534
535 --[[-----------------------------------------------------------------------
536 Notes:
537 Returns whether the module is in an active (enabled) state. This calls module:IsActive() if available. if notLoaded is set, then "name" must be a string.
538 Arguments:
539 string/table - name of the module or a reference to the module
540 [optional] - boolean - if set, this will check modules that are not loaded as well. (default: false)
541 Returns:
542 * boolean - Whether the module is in an active (enabled) state.
543 Example:
544 assert(self:IsModuleActive('bank'))
545 ------------------------------------------------------------------------]]
546 function AceModuleCore:IsModuleActive(module, notLoaded)
547 AceModuleCore:argCheck(module, 2, "table", "string")
548 AceModuleCore:argCheck(notLoaded, 3, "nil", "boolean")
549 if notLoaded then
550 AceModuleCore:argCheck(module, 2, "string")
551 end
552
553 if AceModuleCore == self then
554 self:argCheck(module, 2, "table")
555
556 local core = AceModuleCore.totalModules[module]
557 if not core then
558 self:error("Bad argument #2 to `IsModuleActive'. Not a module")
559 end
560 return core:IsModuleActive(module)
561 end
562
563 if type(module) == "string" then
564 if not notLoaded and not self:HasModule(module) then
565 AceModuleCore:error("Cannot find module %q", module)
566 end
567 if not notLoaded then
568 module = self:GetModule(module)
569 else
570 module = self:HasModule(module) and self:GetModule(module) or module
571 end
572 else
573 if not self:IsModule(module) then
574 AceModuleCore:error("%q is not a module", module)
575 end
576 end
577
578 return not isDisabled(self, module)
579 end
580
581 -- #NODOC
582 function AceModuleCore:OnInstanceInit(target)
583 if target.modules then
584 do return end
585 AceModuleCore:error("OnInstanceInit cannot be called twice")
586 end
587
588 if not AceAddon then
589 if AceLibrary:HasInstance("AceAddon-2.0") then
590 AceAddon = AceLibrary("AceAddon-2.0")
591 else
592 self:error(MAJOR_VERSION .. " requires AceAddon-2.0")
593 end
594 end
595 target.modules = {}
596
597 target.moduleClass = AceOO.Class("AceAddon-2.0")
598 target.modulePrototype = target.moduleClass.prototype
599 end
600
601 AceModuleCore.OnManualEmbed = AceModuleCore.OnInstanceInit
602
603 function AceModuleCore.OnEmbedProfileDisable(AceModuleCore, self, newProfile)
604 if not AceOO.inherits(self, "AceDB-2.0") then
605 return
606 end
607 local _,currentProfile = self:GetProfile()
608 for k, module in pairs(self.modules) do
609 if type(module.IsActive) == "function" or type(module.ToggleActive) == "function" then
610 -- continue
611 else
612 local currentActive = not self.db or not self.db.raw or not self.db.raw.disabledModules or not self.db.raw.disabledModules[currentProfile] or not self.db.raw.disabledModules[currentProfile][module.name]
613 local newActive = not self.db or not self.db.raw or not self.db.raw.disabledModules or not self.db.raw.disabledModules[newProfile] or not self.db.raw.disabledModules[newProfile][module.name]
614 if currentActive ~= newActive then
615 self:ToggleModuleActive(module)
616 if not self.db.raw.disabledModules then
617 self.db.raw.disabledModules = {}
618 end
619 if not self.db.raw.disabledModules[currentProfile] then
620 self.db.raw.disabledModules[currentProfile] = {}
621 end
622 self.db.raw.disabledModules[currentProfile][module.name] = not currentActive or nil
623 end
624 end
625 end
626 end
627
628 -- #NODOC
629 function AceModuleCore:Ace2_AddonEnabled(module, first)
630 local addon = self.totalModules[module]
631 if not addon then
632 return
633 end
634
635 if modulesWithMethod[addon] then
636 for k,v in pairs(modulesWithMethod[addon]) do
637 modulesWithMethod[addon] = del(v)
638 end
639 end
640 if type(addon.OnModuleEnable) == "function" then
641 safecall(addon.OnModuleEnable, addon, module, first)
642 end
643 end
644
645 -- #NODOC
646 function AceModuleCore:Ace2_AddonDisabled(module)
647 local addon = self.totalModules[module]
648 if not addon then
649 return
650 end
651
652 if modulesWithMethod[addon] then
653 for k,v in pairs(modulesWithMethod[addon]) do
654 modulesWithMethod[addon] = del(v)
655 end
656 end
657 if type(addon.OnModuleDisable) == "function" then
658 safecall(addon.OnModuleDisable, addon, module)
659 end
660 end
661
662 local function activate(self, oldLib, oldDeactivate)
663 AceModuleCore = self
664
665 self.totalModules = oldLib and oldLib.totalModules or {}
666
667 self:activate(oldLib, oldDeactivate)
668
669 if oldDeactivate then
670 oldDeactivate(oldLib)
671 end
672 end
673
674 local function external(self, major, instance)
675 if major == "AceEvent-2.0" then
676 AceEvent = instance
677 AceEvent:embed(self)
678
679 self:UnregisterAllEvents()
680 self:RegisterEvent("Ace2_AddonEnabled")
681 self:RegisterEvent("Ace2_AddonDisabled")
682 elseif major == "AceAddon-2.0" then
683 AceAddon = instance
684 end
685 end
686
687 AceLibrary:Register(AceModuleCore, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
688 AceModuleCore = AceLibrary(MAJOR_VERSION)