2020-01-29 03:26:28 +00:00
|
|
|
--[[
|
|
|
|
barebones oop basics
|
|
|
|
|
2021-07-16 09:39:24 +00:00
|
|
|
construction
|
|
|
|
|
|
|
|
call the class object to construct a new instance
|
|
|
|
|
|
|
|
this will construct a new table, assign it as a class
|
|
|
|
instance, and call `new`
|
|
|
|
|
|
|
|
if you are defining a subclass, you will need to call
|
|
|
|
`self:super(...)` as part of `new` to complete superclass
|
|
|
|
construction - if done correctly this will propagate
|
|
|
|
up the chain and you wont have to think about it
|
2021-07-15 06:09:08 +00:00
|
|
|
|
|
|
|
classes are used as metatables directly so that
|
|
|
|
metamethods "just work" - except for index, which is
|
|
|
|
used to hook up instance methods
|
|
|
|
|
2021-07-16 09:39:24 +00:00
|
|
|
classes do use a prototype chain for inheritance, but
|
|
|
|
also copy their interfaces (including superclass)
|
2021-07-15 06:09:08 +00:00
|
|
|
|
|
|
|
we copy interfaces in classes rather than relying on
|
|
|
|
a prototype chain, so that pairs on the class gets
|
|
|
|
all the methods when implemented as an interface
|
|
|
|
|
|
|
|
class properties are not copied and should likely
|
|
|
|
be accessed through the concrete class object so
|
|
|
|
that everything refers to the same object
|
|
|
|
|
|
|
|
arguments (all optional):
|
|
|
|
name (string):
|
|
|
|
the name to use for type()
|
|
|
|
extends (class):
|
|
|
|
superclass for basic inheritance
|
|
|
|
implements (ordered table of classes):
|
|
|
|
mixins/interfaces
|
|
|
|
default_tostring (boolean):
|
|
|
|
whether or not to provide a default tostring function
|
|
|
|
|
2020-01-29 03:26:28 +00:00
|
|
|
]]
|
|
|
|
|
2021-07-15 06:09:08 +00:00
|
|
|
--generate unique increasing class ids
|
|
|
|
local class_id_gen = 0
|
|
|
|
local function next_class_id()
|
|
|
|
class_id_gen = class_id_gen + 1
|
|
|
|
return class_id_gen
|
|
|
|
end
|
|
|
|
|
|
|
|
--implement an interface into c
|
|
|
|
local function implement(c, interface)
|
|
|
|
c.__is[interface] = true
|
|
|
|
for k, v in pairs(interface) do
|
|
|
|
if c[k] == nil and type(v) == "function" then
|
|
|
|
c[k] = v
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
--build a new class
|
|
|
|
local function class(config)
|
|
|
|
local class_id = next_class_id()
|
|
|
|
|
|
|
|
config = config or {}
|
|
|
|
local extends = config.extends
|
|
|
|
local implements = config.implements
|
2024-04-26 04:14:36 +00:00
|
|
|
local src_location = "call location not available"
|
|
|
|
if debug and debug.getinfo then
|
|
|
|
local dinfo = debug.getinfo(2)
|
|
|
|
local src = dinfo.short_src
|
|
|
|
local line = dinfo.currentline
|
|
|
|
src_location = ("%s:%d"):format(src, line)
|
|
|
|
end
|
|
|
|
local name = config.name or ("unnamed class %d (%s)"):format(
|
|
|
|
class_id,
|
|
|
|
src_location
|
|
|
|
)
|
2021-07-15 06:09:08 +00:00
|
|
|
|
2020-01-29 03:26:28 +00:00
|
|
|
local c = {}
|
2021-07-15 06:09:08 +00:00
|
|
|
|
2021-07-21 09:44:31 +00:00
|
|
|
--prototype
|
|
|
|
c.__index = c
|
|
|
|
|
2021-07-15 06:09:08 +00:00
|
|
|
--unique generated id per-class
|
|
|
|
c.__id = class_id
|
|
|
|
|
2021-07-21 09:44:31 +00:00
|
|
|
--the class name for type calls
|
|
|
|
c.__type = name
|
2021-07-15 06:09:08 +00:00
|
|
|
|
|
|
|
--return the name of the class
|
|
|
|
function c:type()
|
2021-07-21 09:44:31 +00:00
|
|
|
return self.__type
|
2021-07-15 06:09:08 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
if config.default_tostring then
|
|
|
|
function c:__tostring()
|
|
|
|
return name
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
--class metatable to set up constructor call
|
2020-05-11 23:26:11 +00:00
|
|
|
setmetatable(c, {
|
|
|
|
__call = function(self, ...)
|
2021-07-15 06:09:08 +00:00
|
|
|
local instance = setmetatable({}, self)
|
|
|
|
instance:new(...)
|
|
|
|
return instance
|
2020-05-11 23:26:11 +00:00
|
|
|
end,
|
2021-07-15 06:09:08 +00:00
|
|
|
__index = extends,
|
2020-05-11 23:26:11 +00:00
|
|
|
})
|
2020-01-29 03:26:28 +00:00
|
|
|
|
2021-07-15 06:09:08 +00:00
|
|
|
--checking class membership for probably-too-dynamic code
|
|
|
|
--returns true for both extended classes and implemented interfaces
|
|
|
|
--(implemented with a hashset for fast lookups)
|
|
|
|
c.__is = {}
|
|
|
|
c.__is[c] = true
|
|
|
|
function c:is(t)
|
|
|
|
return self.__is[t] == true
|
2020-01-29 03:26:28 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
--get the inherited class for super calls if/as needed
|
|
|
|
--allows overrides that still refer to superclass behaviour
|
2021-07-15 06:09:08 +00:00
|
|
|
c.__super = extends
|
2021-07-16 09:33:47 +00:00
|
|
|
|
|
|
|
--perform a (partial) super construction for an instance
|
|
|
|
--for any nested super calls, it'll call the relevant one in the
|
|
|
|
--heirarchy, assuming no super calls have been missed
|
|
|
|
function c:super(...)
|
|
|
|
if not c.__super then return end
|
|
|
|
--hold reference so we can restore
|
|
|
|
local current_super = c.__super
|
|
|
|
--push next super
|
|
|
|
c.__super = c.__super.__super
|
|
|
|
--call
|
|
|
|
current_super.new(self, ...)
|
|
|
|
--restore
|
|
|
|
c.__super = current_super
|
|
|
|
end
|
|
|
|
|
2020-01-29 03:26:28 +00:00
|
|
|
|
2021-07-15 06:09:08 +00:00
|
|
|
if c.__super then
|
|
|
|
--implement superclass interface
|
|
|
|
implement(c, c.__super)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--implement all the passed interfaces/mixins
|
|
|
|
--in order provided
|
|
|
|
if implements then
|
|
|
|
for _, interface in ipairs(implements) do
|
|
|
|
implement(c, interface)
|
2020-05-19 05:17:55 +00:00
|
|
|
end
|
2021-07-15 06:09:08 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
--default constructor, just proxy to the super constructor
|
|
|
|
--override it and use to set up the properties of the instance
|
|
|
|
--but don't forget to call the super constructor!
|
|
|
|
function c:new(...)
|
|
|
|
self:super(...)
|
2020-05-19 05:17:55 +00:00
|
|
|
end
|
2020-05-11 23:26:11 +00:00
|
|
|
|
2020-01-29 03:26:28 +00:00
|
|
|
--done
|
|
|
|
return c
|
|
|
|
end
|
2020-03-15 10:22:22 +00:00
|
|
|
|
|
|
|
return class
|