local function MUST_IMPLEMENT_SENTINEL() error("Attempted to access unimplemented field!", 2) end function ZO_VerifyClassImplementation(finalClass, classTraceback) -- Detect any instances of ZO_MUST_IMPLEMENT that are not implemented. local function VisitClass(currentClass) for fieldName, fieldValue in pairs(currentClass) do if fieldValue == MUST_IMPLEMENT_SENTINEL and finalClass[fieldName] == MUST_IMPLEMENT_SENTINEL then local NO_STACK_TRACE = 0 error("Class has unimplemented field: " .. fieldName .. "\n" .. classTraceback, NO_STACK_TRACE) end end if currentClass.__parentClasses then for _, parentClass in ipairs(currentClass.__parentClasses) do VisitClass(parentClass) end end end VisitClass(finalClass) end local RegisterConcreteClass, RemoveConcreteClass local SHOULD_VERIFY_CLASSES = GetCVar("EnableLuaClassVerification") == "1" if SHOULD_VERIFY_CLASSES then local g_concreteClassSet = {} function RegisterConcreteClass(concreteClass, stackLevel) g_concreteClassSet[concreteClass] = debug.traceback(nil, stackLevel + 1) end function RemoveConcreteClass(concreteClass) g_concreteClassSet[concreteClass] = nil end function ZO_VerifyConcreteClasses() for concreteClass, classTraceback in pairs(g_concreteClassSet) do ZO_VerifyClassImplementation(concreteClass, classTraceback) g_concreteClassSet[concreteClass] = nil end end GetEventManager():RegisterForEvent("BaseObject", EVENT_ADD_ON_LOADED, function() ZO_VerifyConcreteClasses() end) else -- Do nothing instead of tracking and automatically verifying classes function RegisterConcreteClass() end function RemoveConcreteClass() end function ZO_VerifyConcreteClasses() end end ZO_Object = {} ZO_Object.__index = ZO_Object function ZO_Object:New(template) local class = template or self local newObject = setmetatable({}, class) return newObject end --- -- Call ZO_Object:Subclass() to create a new class that inherits from this one. -- function ZO_Object:Subclass() local newClass = setmetatable({}, self) newClass.__index = newClass newClass.__parentClasses = { self } newClass.__isAbstractClass = false if self.__isAbstractClass then local PARENT_STACK = 2 RegisterConcreteClass(newClass, PARENT_STACK) end return newClass end --- -- Call ZO_Object:MultiSubclass() to create a new class that -- inherits from multiple parent classes. In situations where the same method is -- defined on multiple classes, the leftmost class in the argument list takes -- priority. It is recommended that you override to avoid this! -- function ZO_Object:MultiSubclass(...) local parentClasses = { self, ... } local newClass = setmetatable({}, { __index = function(table, key) for _, parentClassTable in ipairs(parentClasses) do local value = parentClassTable[key] if value ~= nil then return value end end end }) newClass.__index = newClass newClass.__parentClasses = parentClasses newClass.__isAbstractClass = false for _, parentClass in ipairs(parentClasses) do if parentClass.__isAbstractClass then local PARENT_STACK = 2 RegisterConcreteClass(newClass, PARENT_STACK) break end end return newClass end --- -- Use MUST_IMPLEMENT to create a field that must be implemented by subclasses. -- If a concrete class does not implement a field that contains a MUST_IMPLEMENT, -- it will cause an error after the entire addon has been loaded. -- Example A: ZO_MyAbstractClass.MyAbstractFunction = ZO_MyAbstractClass:MUST_IMPLEMENT() -- Example B: ZO_MyAbstractClass:MUST_IMPLEMENT("MyAbstractFunction") function ZO_Object:MUST_IMPLEMENT(fieldName) self:IGNORE_UNIMPLEMENTED() if fieldName then self[fieldName] = MUST_IMPLEMENT_SENTINEL end return MUST_IMPLEMENT_SENTINEL end --- -- Use IGNORE_UNIMPLEMENTED_FUNCTIONS to mark a class as abstract. Typically used to denote an abstract derived class that -- does not have any of its own MUST_IMPLEMENT fields, but inherits from another abstract class that does. -- Example: ZO_MyAbstractDerivedClass:IGNORE_UNIMPLEMENTED() function ZO_Object:IGNORE_UNIMPLEMENTED() self.__isAbstractClass = true RemoveConcreteClass(self) end --- -- Use STUB to create a field that doesn't have to be implemented by subclasses, but will error if called. -- Example A: ZO_MyParentClass.MyStubbedFunction = ZO_MyAbstractClass:STUB() -- Example B: ZO_MyParentClass:STUB("MyStubbedFunction") local function STUB_SENTINEL() assert(false, "Attempted to access unimplemented field!") end function ZO_Object:STUB(fieldName) if fieldName then self[fieldName] = STUB_SENTINEL end return STUB_SENTINEL end do -- Indicates whether the class 'currentClass', or any of its parent classes, -- is the class 'checkClass'. local function IsClassOrSubclass(currentClass, checkClass) if currentClass == checkClass then return true end if currentClass.__parentClasses then for _, parentClass in ipairs(currentClass.__parentClasses) do if IsClassOrSubclass(parentClass, checkClass) then return true end end end return false end -- Indicates whether this object is an instance of the class -- 'checkClass' or any class that derives from 'checkClass'. function ZO_Object:IsInstanceOf(checkClass) return IsClassOrSubclass(self.__index, checkClass) end end --[[ Here is a simple multiple inheritance example: local A = ZO_Object:Subclass() function A:InitializeA() self.name = "A" end local B = ZO_Object:Subclass() function B:InitializeB() self.text = "B" end C = ZO_Object.MultiSubclass(A, B) function C:New() local obj = ZO_Object.New(self) obj:Initialize() return obj end function C:Initialize() self:InitializeA() self:InitializeB() end ]]-- function ZO_GenerateDataSourceMetaTableIndexFunction(template) return function(tbl, key) local value = template[key] if value == nil then local dataSource = rawget(tbl, "dataSource") if dataSource then value = dataSource[key] end end return value end end ZO_DataSourceObject = {} function ZO_DataSourceObject:New(template) template = template or self local newObject = setmetatable({}, template.instanceMetaTable) local mt = getmetatable(newObject) mt.__index = ZO_GenerateDataSourceMetaTableIndexFunction(template) return newObject end function ZO_DataSourceObject:GetDataSource() return self.dataSource end function ZO_DataSourceObject:SetDataSource(dataSource) self.dataSource = dataSource end function ZO_DataSourceObject:Subclass() local newTemplate = setmetatable({}, { __index = self }) newTemplate.instanceMetaTable = { __index = ZO_GenerateDataSourceMetaTableIndexFunction(newTemplate) } newTemplate.__parentClasses = { self } newTemplate.__isAbstractClass = false return newTemplate end --- -- ZO_InitializingObject is a new Object definition that more directly encodes -- the practices most current ZO_Objects are actually using. In most cases, you -- can directly replace a ZO_Object with a ZO_InitializingObject, and delete the -- redundant :New() definition that most ZO_Object classes defined. -- ZO_InitializingObject = {} zo_mixin(ZO_InitializingObject, ZO_Object) ZO_InitializingObject.__index = ZO_InitializingObject --- -- The external constructor for each object. -- Sample usage: -- myObject = MyClass:New([arguments]) -- function ZO_InitializingObject:New(...) local newObject = setmetatable({}, self) newObject:Initialize(...) return newObject end --- -- Override this initialization function to define how your object should be constructed. -- Sample usage: -- function MyClass:Initialize(argument1, argument2) -- self.myField = argument1 -- end -- You can still create an InitializingObject that does not define an Initialize method; -- new instances will simply call this empty, default Initialize method instead. function ZO_InitializingObject:Initialize() -- To be overridden end -- -- ZO_DeferredInitializingObject is a ZO_InitializingObject wrapper that standardizes the -- logic around deferring some of the initialization until later. -- To use this abstract class: -- (1) Pass in a ZO_Scene, ZO_SceneFragment or ZO_SceneGroup object. -- (2) Define a custom OnDeferredInitialize method. -- ZO_DeferredInitializingObject will call your OnDeferredInitialize method the first -- time the ZO_Scene/ZO_SceneFragment/ZO_SceneGroup is shown. -- Note that this is handled automatically and will happen prior to calling the -- OnShowing method. ZO_DeferredInitializingObject = {} zo_mixin(ZO_DeferredInitializingObject, ZO_InitializingObject) ZO_DeferredInitializingObject.__index = ZO_DeferredInitializingObject --- -- If you must override the Initialize method, be sure to create a ZO_Scene, ZO_SceneFragment or -- ZO_SceneGroup object to the pass to the base ZO_DeferredInitializingObject.Initialize method. -- Sample usage: -- function MyClass:Initialize(control, argument1, argument2) -- ZO_DeferredInitializingObject.Initialize(self, ZO_FadeSceneFragment:New(control)) -- self.myField = argument1 -- end function ZO_DeferredInitializingObject:Initialize(sceneStateObject) assert(sceneStateObject) self.sceneStateObject = sceneStateObject self.sceneStateObject:RegisterCallback("StateChange", function(...) self:OnStateChanged(...) end) if self.sceneStateObject:IsInstanceOf(ZO_Scene) then self.scene = sceneStateObject elseif self.sceneStateObject:IsInstanceOf(ZO_SceneFragment) then self.fragment = sceneStateObject elseif self.sceneStateObject:IsInstanceOf(ZO_SceneGroup) then self.sceneGroup = sceneStateObject end end function ZO_DeferredInitializingObject:PerformDeferredInitialize() if not self.initialized then self.initialized = true self:OnDeferredInitialize() end end function ZO_DeferredInitializingObject:OnStateChanged(_, newState) if newState == ZO_STATE.SHOWING then self:PerformDeferredInitialize() self:OnShowing() elseif newState == ZO_STATE.SHOWN then self:OnShown() elseif newState == ZO_STATE.HIDING then self:OnHiding() elseif newState == ZO_STATE.HIDDEN then self:OnHidden() end end function ZO_DeferredInitializingObject:OnShowing() -- To be overridden end function ZO_DeferredInitializingObject:OnShown() -- To be overridden end function ZO_DeferredInitializingObject:OnHiding() -- To be overridden end function ZO_DeferredInitializingObject:OnHidden() -- To be overridden end function ZO_DeferredInitializingObject:GetScene() return self.scene end function ZO_DeferredInitializingObject:GetFragment() return self.fragment end function ZO_DeferredInitializingObject:GetSceneGroup() return self.sceneGroup end function ZO_DeferredInitializingObject:IsShowing() return self.sceneStateObject:IsShowing() end ZO_DeferredInitializingObject:MUST_IMPLEMENT("OnDeferredInitialize")