+-- ------------------------------------------------------------------------------ --
+-- https://tradeskillmaster.com --
+-- All Rights Reserved* - Detailed license information included with addon. --
+-- ------------------------------------------------------------------------------ --
+local private = { classInfo = {}, instInfo = {} }
+-- Set the keys as weak so that instances of classes can be GC'd (classes are never GC'd)
+setmetatable(private.instInfo, { __mode = "k" })
+local SPECIAL_PROPERTIES = {
+local DEFAULT_INST_FIELDS = {
+ __init = function(self)
+ __tostring = function(self)
+ return private.instInfo[self].str
+-- ============================================================================
+-- ============================================================================
+function TSMClassLibary_DefineClass(name, superclass, ...)
+ assert(type(name) == "string", "Invalid class name: "..tostring(name), 1)
+ for i = 1, select('#', ...) do
+ local modifier = select(i, ...)
+ if modifier == "ABSTRACT" then
+ error("Invalid modifier: "..tostring(modifier))
+ local class = setmetatable({}, private.CLASS_MT)
+ private.classInfo[class] = {
+ superclass = superclass,
+ for key, value in pairs(private.classInfo[superclass].static) do
+ if not private.classInfo[class].superStatic[key] then
+ private.classInfo[class].superStatic[key] = { class = superclass, value = value }
+ superclass = superclass.__super
+-- ============================================================================
+-- ============================================================================
+ __newindex = function(self, key, value)
+ assert(key ~= "__super" and key ~= "__isa" and key ~= "__class", "Can't set reserved key: "..tostring(key))
+ if private.classInfo[self.__class].static[key] ~= nil then
+ private.classInfo[self.__class].static[key] = value
+ elseif not private.instInfo[self].hasSuperclass then
+ -- we just set this directly on the instance table for better performance
+ rawset(self, key, value)
+ private.instInfo[self].fields[key] = value
+ __index = function(self, key)
+ -- This method is super optimized since it's used for every class instance access, meaning function calls and
+ -- table lookup is kept to an absolute minimum, at the expense of readability and code reuse.
+ local instInfo = private.instInfo[self]
+ -- check if this key is an instance field first, since this is the most common case
+ local res = instInfo.fields[key]
+ instInfo.currentClass = nil
+ -- check if it's the special __super field
+ if key == "__super" then
+ -- The class of the current class method we are in, or nil if we're not in a class method.
+ local methodClass = instInfo.methodClass
+ -- We can only access the superclass within a class method and will use the class which defined that method
+ -- as the base class to jump to the superclass of, regardless of what class the instance actually is.
+ if not methodClass then
+ error("The superclass can only be referenced within a class method.")
+ instInfo.currentClass = private.classInfo[instInfo.currentClass or methodClass].superclass
+ if not instInfo.currentClass then
+ error("No super class found.")
+ -- reset the current class since we're not continuing the __super chain
+ local class = instInfo.currentClass or instInfo.class
+ instInfo.currentClass = nil
+ -- check if this is a static key
+ local classInfo = private.classInfo[class]
+ res = classInfo.static[key]
+ -- check if it's a static field in the superclass
+ local superStaticRes = classInfo.superStatic[key]
+ local superclass = superStaticRes.class
+ local superclassInfo = private.classInfo[superclass]
+ res = superStaticRes.value
+ -- check if this field has a default value
+ res = DEFAULT_INST_FIELDS[key]
+ __tostring = function(self)
+ return self:__tostring()
+-- ============================================================================
+-- ============================================================================
+ __newindex = function(self, key, value)
+ assert(not private.classInfo[self].static[key], "Can't modify or override static members")
+ assert(key ~= "__super" and key ~= "__isa" and key ~= "__class", "Reserved word: "..key)
+ if type(value) == "function" then
+ -- We wrap class methods so that within them, the instance appears to be of the defining class
+ private.classInfo[self].static[key] = function(inst, ...)
+ local instInfo = private.instInfo[inst]
+ if not instInfo.isClassLookup[self] then
+ error(format("Attempt to call class method on non-object (%s)!", tostring(inst)))
+ if not instInfo.hasSuperclass then
+ -- don't need to worry about methodClass so just call the function directly
+ return value(inst, ...)
+ local prevMethodClass = instInfo.methodClass
+ instInfo.methodClass = self
+ return private.InstMethodReturnHelper(prevMethodClass, instInfo, value(inst, ...))
+ private.classInfo[self].static[key] = value
+ __index = function(self, key)
+ -- check if it's the special __isa method which all classes implicitly have
+ return private.ClassIsA
+ elseif key == "__name" then
+ return private.classInfo[self].name
+ elseif key == "__super" then
+ return private.classInfo[self].superclass
+ error("Class type is write-only")
+ __tostring = function(self)
+ return "class:"..private.classInfo[self].name
+ __call = function(self, ...)
+ assert(not private.classInfo[self].abstract, "Attempting to instantiate an abstract class!")
+ -- Create a new instance of this class
+ local instStr = strmatch(tostring(inst), "table:[^0-9a-fA-F]*([0-9a-fA-F]+)")
+ setmetatable(inst, private.INST_MT)
+ local hasSuperclass = private.classInfo[self].superclass and true or false
+ private.instInfo[inst] = {
+ __isa = private.InstIsA,
+ str = private.classInfo[self].name..":"..instStr,
+ hasSuperclass = hasSuperclass,
+ if not hasSuperclass then
+ -- set the static members directly on this object for better performance
+ for key, value in pairs(private.classInfo[self].static) do
+ if not SPECIAL_PROPERTIES[key] then
+ rawset(inst, key, value)
+ private.instInfo[inst].isClassLookup[c] = true
+ c = private.classInfo[c].superclass
+ assert(select("#", inst:__init(...)) == 0, "__init must not return any values")
+-- ============================================================================
+-- ============================================================================
+function private.InstMethodReturnHelper(class, instInfo, ...)
+ -- reset methodClass now that the function returned
+ instInfo.methodClass = class
+function private.InstIsA(inst, targetClass)
+ return private.instInfo[inst].isClassLookup[targetClass]
+function private.ClassIsA(class, targetClass)
+ if class == targetClass then return true end