158 lines
		
	
	
	
		
			4 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			158 lines
		
	
	
	
		
			4 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| --[[
 | |
|     https://github.com/overextended/ox_lib
 | |
| 
 | |
|     This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
 | |
| 
 | |
|     Copyright © 2025 Linden <https://github.com/thelindat>
 | |
| ]]
 | |
| 
 | |
| ---@diagnostic disable: invisible
 | |
| local getinfo = debug.getinfo
 | |
| 
 | |
| ---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
 | |
|         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)
 | |
|     end
 | |
| 
 | |
|     return true
 | |
| end
 | |
| 
 | |
| ---@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
 | |
| 
 | |
| ---Creates a new instance of the given class.
 | |
| ---@protected
 | |
| ---@generic T
 | |
| ---@param class T | OxClass
 | |
| ---@return T
 | |
| function mixins.new(class, ...)
 | |
|     local constructor = getConstructor(class)
 | |
|     local private = {}
 | |
|     local obj = setmetatable({ private = 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, ...)
 | |
|     end
 | |
| 
 | |
|     rawset(obj, 'super', nil)
 | |
| 
 | |
|     if private ~= obj.private or next(obj.private) then
 | |
|         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
 | |
| 
 | |
|     return obj
 | |
| end
 | |
| 
 | |
| ---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
 | |
| 
 | |
| ---Creates a new class.
 | |
| ---@generic S : OxClass
 | |
| ---@generic T : string
 | |
| ---@param name `T`
 | |
| ---@param super? S
 | |
| ---@return `T`
 | |
| function lib.class(name, super)
 | |
|     assertType(1, name, 'string')
 | |
| 
 | |
|     local class = table.clone(mixins)
 | |
| 
 | |
|     class.__name = name
 | |
|     class.__index = class
 | |
| 
 | |
|     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
 | |
| end
 | |
| 
 | |
| return lib.class
 | 
