MineClone2/mods/CORE/class/init.lua

160 lines
5.4 KiB
Lua

local Object = {}
-- Define a getter that caches the result for the next time it is called
-- This is a static method (self = the class); in this class system static methods start with __ by convention
function Object:__cache_getter(name, func)
-- cache key: prevent overriding the getter function itself
local key = "_" .. name
-- add a function to the class
self[name] = function(self)
-- check if the value is present in the cache
local value = self[key]
-- `== nil` instead of `not value` to allow caching boolean values
if value == nil then
-- call the getter function
value = func(self)
end
-- store result in cache
self[key] = value
-- return result
return value
end
end
-- Define a getter / setter
-- If no argument is specified, it will act as a getter, else as a setter
-- The specified function MUST return the new value, if it returns nil, nil will be used as new value
-- Optionally works in combination with a previously defined cache getter and only really makes sense in that context
function Object:__setter(name, func)
-- since the function is overridden, we need to store the old one in case a cache getter is defined
local cache_getter = self[name]
-- use same key as cache getter to modify getter cache if present
local key = "_" .. name
self[name] = function(self, new)
-- check whether an argument was specified
if new == nil then
if cache_getter then
-- call the cache getter if present
return cache_getter(self)
else
-- return the value else
return self[key]
end
end
-- call the setter and set the new value to the result
self[key] = func(self, new)
end
end
-- Define a comparator function
-- Acts like a setter, except that it does not set the new value but rather compares the present and specified values and returns whether they are equal or not
-- Incompatible with setter
-- The function is optional. The == operator is used else.
function Object:__comparator(name, func)
local cache_getter = self[name]
local key = "_" .. name
self[name] = function(self, expected)
-- the current value is needed everytime, no matter whether there is an argument or not
local actual
if cache_getter then
-- call the cache getter if present
actual = cache_getter(self)
else
-- use the value else
actual = self[key]
end
-- act as a getter if there is no argument
if expected == nil then
return actual
end
if func then
-- if a function as specified, call it
return func(actual, expected)
else
-- else, use the == operator to compare the expected value to the actual
return actual == expected
end
end
end
-- Override an already existing function in a way that the old function is called
-- If nil is returned, the old function is called. Else the return value is returned. (Only the first return value is taken into concern here, multiple are supported tho)
-- This works even if it is applied to the instance of a class when the function is defined by the class
-- It also works with overriding functions that are located in superclasses
function Object:__override(name, func)
-- store the old function
local old_func = self[name]
-- redefine the function with variable arguments
self[name] = function(...)
-- call the new function and store the return values in a table
local rvals = {func(...)}
-- if nil was returned, fall back to the old function
if rvals[1] == nil then
-- if present, call the return function with the values the new function returned (converted back to a tuple)
return old_func(...)
else
-- return the values from the new function else
return unpack(rvals)
end
end
end
-- Works like override except that the new function does not modify the output of the old function but rather the input
-- The new function can decide with what arguments by returing them, including the `self` reference
-- If the "self" arg is not returned the old function is not called
-- Note that this way the new function cannot change the return value of the old function
function Object:__pipe(name, func)
local old_func = self[name]
self[name] = function(self, ...)
local rvals = {func(self, ...)}
-- check if self was returned properly
if rvals[1] then
-- if present, call the return function with the values the new function returned (converted back to a tuple)
return old_func(unpack(rvals))
end
end
end
-- Make class available as table to distribute the Object table
class = setmetatable({Object = Object}, {
-- Create a new class by calling class() with an optional superclass argument
__call = function(super)
return setmetatable({}, {
-- Create a new instance of the class when the class is called
__call = function(_class, ...)
-- Check whether the first argument is an instance of the class
-- If that is the case, just return it - this is to allow "making sure something is the instance of a class" by calling the constructor
local argtbl = {...}
local first_arg = args[1]
if first_arg and type(first_arg) == "table" and inst.CLASS = _class then
return inst
end
-- set the metatable and remember which class the object belongs to
local instance = setmetatable({CLASS = _class}, {
__index = _class,
})
-- call the constructor if present
if instance.constructor then
instance:constructor(...)
end
-- return the created instance
return instance
end,
-- Object as superclass of all classes that dont have a different one
__index = super or Object,
})
end
}