2024-12-30 10:15:34 +00:00
|
|
|
---@diagnostic disable: invisible
|
|
|
|
local getinfo = debug.getinfo
|
2024-12-29 20:02:43 +00:00
|
|
|
|
|
|
|
---Ensure the given argument or property has a valid type, otherwise throwing an error.
|
|
|
|
---@param id number | string
|
|
|
|
---@param var any
|
|
|
|
---@param expected type
|
|
|
|
local function assertType(id, var, expected)
|
|
|
|
local received = type(var)
|
|
|
|
|
|
|
|
if received ~= expected then
|
2024-12-30 10:15:34 +00:00
|
|
|
error(("expected %s %s to have type '%s' (received %s)")
|
|
|
|
:format(type(id) == 'string' and 'field' or 'argument', id, expected, received), 3)
|
|
|
|
end
|
|
|
|
|
|
|
|
if expected == 'table' and table.type(var) ~= 'hash' then
|
|
|
|
error(("expected argument %s to have table.type 'hash' (received %s)")
|
|
|
|
:format(id, table.type(var)), 3)
|
2024-12-29 20:02:43 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2024-12-30 10:15:34 +00:00
|
|
|
---@alias OxClassConstructor<T> fun(self: T, ...: unknown): nil
|
|
|
|
|
|
|
|
---@class OxClass
|
|
|
|
---@field private __index table
|
|
|
|
---@field protected __name string
|
|
|
|
---@field protected private? { [string]: unknown }
|
|
|
|
---@field protected super? OxClassConstructor
|
|
|
|
---@field protected constructor? OxClassConstructor
|
|
|
|
local mixins = {}
|
|
|
|
local constructors = {}
|
|
|
|
|
|
|
|
---Somewhat hacky way to remove the constructor from the class.__index.
|
|
|
|
---Maybe add static fields in the future?
|
|
|
|
---@param class OxClass
|
|
|
|
local function getConstructor(class)
|
|
|
|
local constructor = constructors[class] or class.constructor
|
|
|
|
|
|
|
|
if class.constructor then
|
|
|
|
constructors[class] = class.constructor
|
|
|
|
class.constructor = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
return constructor
|
|
|
|
end
|
|
|
|
|
|
|
|
local function void() return '' end
|
|
|
|
|
2024-12-29 20:02:43 +00:00
|
|
|
---Creates a new instance of the given class.
|
2024-12-30 10:15:34 +00:00
|
|
|
---@protected
|
|
|
|
---@generic T
|
|
|
|
---@param class T | OxClass
|
|
|
|
---@return T
|
|
|
|
function mixins.new(class, ...)
|
|
|
|
local constructor = getConstructor(class)
|
|
|
|
|
|
|
|
local obj = setmetatable({
|
|
|
|
private = {}
|
|
|
|
}, class)
|
|
|
|
|
|
|
|
if constructor then
|
|
|
|
local parent = class
|
|
|
|
|
|
|
|
rawset(obj, 'super', function(self, ...)
|
|
|
|
parent = getmetatable(parent)
|
|
|
|
constructor = getConstructor(parent)
|
|
|
|
|
|
|
|
if constructor then return constructor(self, ...) end
|
|
|
|
end)
|
|
|
|
|
|
|
|
constructor(obj, ...)
|
2024-12-29 20:02:43 +00:00
|
|
|
end
|
|
|
|
|
2024-12-30 10:15:34 +00:00
|
|
|
rawset(obj, 'super', nil)
|
2024-12-29 20:02:43 +00:00
|
|
|
|
2024-12-30 10:15:34 +00:00
|
|
|
if next(obj.private) then
|
|
|
|
local private = table.clone(obj.private)
|
|
|
|
|
|
|
|
table.wipe(obj.private)
|
|
|
|
setmetatable(obj.private, {
|
|
|
|
__metatable = 'private',
|
|
|
|
__tostring = void,
|
|
|
|
__index = function(self, index)
|
|
|
|
local di = getinfo(2, 'n')
|
|
|
|
|
|
|
|
if di.namewhat ~= 'method' and di.namewhat ~= '' then return end
|
|
|
|
|
|
|
|
return private[index]
|
|
|
|
end,
|
|
|
|
__newindex = function(self, index, value)
|
|
|
|
local di = getinfo(2, 'n')
|
|
|
|
|
|
|
|
if di.namewhat ~= 'method' and di.namewhat ~= '' then
|
|
|
|
error(("cannot set value of private field '%s'"):format(index), 2)
|
|
|
|
end
|
|
|
|
|
|
|
|
private[index] = value
|
|
|
|
end
|
|
|
|
})
|
|
|
|
else
|
|
|
|
obj.private = nil
|
|
|
|
end
|
2024-12-29 20:02:43 +00:00
|
|
|
|
|
|
|
return obj
|
|
|
|
end
|
|
|
|
|
2024-12-30 10:15:34 +00:00
|
|
|
---Checks if an object is an instance of the given class.
|
|
|
|
---@param class OxClass
|
|
|
|
function mixins:isClass(class)
|
|
|
|
return getmetatable(self) == class
|
|
|
|
end
|
|
|
|
|
|
|
|
---Checks if an object is an instance or derivative of the given class.
|
|
|
|
---@param class OxClass
|
|
|
|
function mixins:instanceOf(class)
|
|
|
|
local mt = getmetatable(self)
|
|
|
|
|
|
|
|
while mt do
|
|
|
|
if mt == class then return true end
|
|
|
|
|
|
|
|
mt = getmetatable(mt)
|
|
|
|
end
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2024-12-29 20:02:43 +00:00
|
|
|
---Creates a new class.
|
2024-12-30 10:15:34 +00:00
|
|
|
---@generic S : OxClass
|
|
|
|
---@generic T : string
|
|
|
|
---@param name `T`
|
|
|
|
---@param super? S
|
|
|
|
---@return `T`
|
2024-12-29 20:02:43 +00:00
|
|
|
function lib.class(name, super)
|
|
|
|
assertType(1, name, 'string')
|
|
|
|
|
2024-12-30 10:15:34 +00:00
|
|
|
local class = table.clone(mixins)
|
2024-12-29 20:02:43 +00:00
|
|
|
|
2024-12-30 10:15:34 +00:00
|
|
|
class.__name = name
|
2024-12-29 20:02:43 +00:00
|
|
|
class.__index = class
|
|
|
|
|
2024-12-30 10:15:34 +00:00
|
|
|
if super then
|
|
|
|
assertType('super', super, 'table')
|
|
|
|
setmetatable(class, super)
|
|
|
|
end
|
|
|
|
|
|
|
|
---@todo See if there's a way we can auto-create a class using the name and super
|
|
|
|
return class
|
2024-12-29 20:02:43 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return lib.class
|