mirror of
https://github.com/1bardesign/batteries.git
synced 2024-11-22 14:14:36 +00:00
BREAKING class interface refactor - all classes will need a minor update, see class.lua
tl;dr is that new no longer needs to call init, calling :new() directly in user code is not allowed, properties are copied, metamethods work, and a config table is needed rather than a class to extend from, so use {extends = superclass} if you want a minimal fix
This commit is contained in:
parent
e1bb76d419
commit
3cc177a0c0
10
async.lua
10
async.lua
@ -18,13 +18,13 @@
|
||||
local path = (...):gsub("async", "")
|
||||
local class = require(path .. "class")
|
||||
|
||||
local async = class()
|
||||
local async = class({
|
||||
name = "async",
|
||||
})
|
||||
|
||||
function async:new()
|
||||
return self:init({
|
||||
tasks = {},
|
||||
tasks_stalled = {},
|
||||
})
|
||||
self.tasks = {}
|
||||
self.tasks_stalled = {}
|
||||
end
|
||||
|
||||
--add a task to the kernel
|
||||
|
170
class.lua
170
class.lua
@ -1,83 +1,131 @@
|
||||
--[[
|
||||
barebones oop basics
|
||||
supports basic inheritance and means you don't have to build/set your own metatable each time
|
||||
|
||||
todo: collect some stats on classes/optional global class registry
|
||||
call the class object to construct a new instance
|
||||
|
||||
classes are used as metatables directly so that
|
||||
metamethods "just work" - except for index, which is
|
||||
used to hook up instance methods
|
||||
|
||||
classes do use a prototype chain for inheritance, but
|
||||
also copy their interfaces (including superclass)
|
||||
|
||||
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
|
||||
|
||||
]]
|
||||
|
||||
local function class(inherits)
|
||||
--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
|
||||
local name = config.name or ("unnamed class %d)"):format(class_id)
|
||||
|
||||
local c = {}
|
||||
--class metatable
|
||||
|
||||
--unique generated id per-class
|
||||
c.__id = class_id
|
||||
|
||||
--the class name
|
||||
c.__name = name
|
||||
|
||||
--prototype
|
||||
c.__index = c
|
||||
|
||||
--return the name of the class
|
||||
function c:type()
|
||||
return name
|
||||
end
|
||||
|
||||
if config.default_tostring then
|
||||
function c:__tostring()
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
--class metatable to set up constructor call
|
||||
setmetatable(c, {
|
||||
--wire up call as ctor
|
||||
__call = function(self, ...)
|
||||
return self:new(...)
|
||||
local instance = setmetatable({}, self)
|
||||
instance:new(...)
|
||||
return instance
|
||||
end,
|
||||
--handle single inheritence chain
|
||||
__index = inherits,
|
||||
__index = extends,
|
||||
})
|
||||
--instance metatable
|
||||
c.__mt = {
|
||||
__index = c,
|
||||
}
|
||||
--common class functions
|
||||
|
||||
--internal initialisation
|
||||
--sets up an initialised object with a default value table
|
||||
--performing a super construction if necessary, and (re-)assigning the right metatable
|
||||
function c:init(t, ...)
|
||||
if inherits and inherits.new then
|
||||
--construct superclass instance, then overlay args table
|
||||
local ct = inherits:new(...)
|
||||
for k,v in pairs(t) do
|
||||
ct[k] = v
|
||||
end
|
||||
t = ct
|
||||
end
|
||||
--upgrade to this class and return
|
||||
return setmetatable(t, self.__mt)
|
||||
end
|
||||
|
||||
--constructor
|
||||
--generally to be overridden
|
||||
function c:new()
|
||||
return self:init({})
|
||||
--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
|
||||
end
|
||||
|
||||
--get the inherited class for super calls if/as needed
|
||||
--allows overrides that still refer to superclass behaviour
|
||||
function c:super()
|
||||
return inherits
|
||||
c.__super = extends
|
||||
--nop by default
|
||||
function c:super() end
|
||||
|
||||
if c.__super then
|
||||
--perform a super construction for an instance
|
||||
function c:super(...)
|
||||
c.__super.new(self, ...)
|
||||
end
|
||||
--implement superclass interface
|
||||
implement(c, c.__super)
|
||||
end
|
||||
|
||||
--delegate a call to the superclass, by name
|
||||
--still a bit clumsy but much cleaner than the inline equivalent,
|
||||
--plus handles heirarchical complications, and detects various mistakes
|
||||
function c:super_call(func_name, ...)
|
||||
--
|
||||
if type(func_name) ~= "string" then
|
||||
error("super_call requires a string function name to look up, got "..tostring(func_name))
|
||||
|
||||
--implement all the passed interfaces/mixins
|
||||
--in order provided
|
||||
if implements then
|
||||
for _, interface in ipairs(implements) do
|
||||
implement(c, interface)
|
||||
end
|
||||
--todo: memoize the below :)
|
||||
local previous_impl = c:super()
|
||||
--find the first superclass that actually has the method
|
||||
while previous_impl and not rawget(previous_impl, func_name) do
|
||||
previous_impl = previous_impl:super()
|
||||
end
|
||||
if not previous_impl then
|
||||
error("failed super call - no superclass in the chain has an implementation of "..func_name)
|
||||
end
|
||||
-- get the function
|
||||
local f = previous_impl[func_name]
|
||||
if not f then -- this should never happen because we bail out earlier
|
||||
error("failed super call - missing function "..func_name.." in superclass")
|
||||
end
|
||||
-- check if someone reuses that reference
|
||||
if f == self[func_name] then
|
||||
error("failed super call - function "..func_name.." is same in superclass as in derived; this will be a infinite recursion!")
|
||||
end
|
||||
-- call that function
|
||||
return f(self, ...)
|
||||
|
||||
--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(...)
|
||||
end
|
||||
|
||||
--done
|
||||
|
1
init.lua
1
init.lua
@ -39,6 +39,7 @@ local _batteries = {
|
||||
manual_gc = require_relative("manual_gc"),
|
||||
colour = require_relative("colour"),
|
||||
pretty = require_relative("pretty"),
|
||||
make_pooled = require_relative("make_pooled"),
|
||||
}
|
||||
|
||||
--assign aliases
|
||||
|
43
make_pooled.lua
Normal file
43
make_pooled.lua
Normal file
@ -0,0 +1,43 @@
|
||||
--[[
|
||||
add pooling functionality to a class
|
||||
|
||||
adds a handful of class and instance methods
|
||||
]]
|
||||
|
||||
return function(class, limit)
|
||||
--shared pooled storage
|
||||
local _pool = {}
|
||||
--size limit for tuning memory upper bound
|
||||
local _pool_limit = limit or 128
|
||||
|
||||
--flush the entire pool
|
||||
function class:flush_pool()
|
||||
if #_pool > 0 then
|
||||
_pool = {}
|
||||
end
|
||||
end
|
||||
|
||||
--drain one element from the pool, if it exists
|
||||
function class:drain_pool()
|
||||
if #_pool > 0 then
|
||||
return table.remove(_pool)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--get a pooled object
|
||||
--(re-initialised with new, or freshly constructed if the pool was empty)
|
||||
function class:pooled(...)
|
||||
if #_pool == 0 then
|
||||
return c(...)
|
||||
end
|
||||
return c.drain_pool():new(...)
|
||||
end
|
||||
|
||||
--release a vector to the pool
|
||||
function class:release()
|
||||
if #_pool < _pool_limit then
|
||||
table.insert(_pool, self)
|
||||
end
|
||||
end
|
||||
end
|
@ -4,13 +4,13 @@
|
||||
|
||||
local path = (...):gsub("pubsub", "")
|
||||
local class = require(path .. "class")
|
||||
local pubsub = class()
|
||||
local pubsub = class({
|
||||
name = "pubsub",
|
||||
})
|
||||
|
||||
--create a new pubsub bus
|
||||
function pubsub:new()
|
||||
return self:init({
|
||||
subscriptions = {},
|
||||
})
|
||||
self.subscriptions = {}
|
||||
end
|
||||
|
||||
--(internal; notify a callback set of an event)
|
||||
|
@ -66,7 +66,7 @@ Extensions to existing lua core modules to provide missing features.
|
||||
|
||||
General utility data structures and algorithms to speed you along your way.
|
||||
|
||||
- [`class`](./class.lua) - Single-inheritance oo in a single function.
|
||||
- [`class`](./class.lua) - OOP with inheritance and interfaces in a single function.
|
||||
- [`functional`](./functional.lua) - Functional programming facilities. `map`, `reduce`, `any`, `match`, `minmax`, `mean`...
|
||||
- [`sequence`](./sequence.lua) - An oo wrapper on sequential tables, so you can do `t:insert(i, v)` instead of `table.insert(t, i, v)`. Also supports method chaining for the `functional` interface above, which can save a lot of needless typing!
|
||||
- [`set`](./set.lua) - A set type supporting a full suite of set operations with fast membership testing and `ipairs`-style iteration.
|
||||
|
13
sequence.lua
13
sequence.lua
@ -6,12 +6,19 @@
|
||||
]]
|
||||
|
||||
local path = (...):gsub("sequence", "")
|
||||
local class = require(path .. "class")
|
||||
local table = require(path .. "tablex") --shadow global table module
|
||||
local functional = require(path .. "functional")
|
||||
local stable_sort = require(path .. "sort").stable_sort
|
||||
|
||||
local sequence = class(table) --proxy missing table fns to tablex api
|
||||
--(not a class, because we want to be able to upgrade tables that are passed in without a copy)
|
||||
local sequence = {}
|
||||
sequence.__index = sequence
|
||||
setmetatable(sequence, {
|
||||
__index = table,
|
||||
__call = function(self, ...)
|
||||
return sequence:new(...)
|
||||
end,
|
||||
})
|
||||
|
||||
--iterators as method calls
|
||||
--(no pairs, sequences are ordered)
|
||||
@ -21,7 +28,7 @@ sequence.iterate = ipairs
|
||||
|
||||
--upgrade a table into a sequence, or create a new sequence
|
||||
function sequence:new(t)
|
||||
return self:init(t or {})
|
||||
return setmetatable(t or {}, sequence)
|
||||
end
|
||||
|
||||
--sorting default to stable
|
||||
|
17
set.lua
17
set.lua
@ -6,21 +6,20 @@ local path = (...):gsub("set", "")
|
||||
local class = require(path .. "class")
|
||||
local table = require(path .. "tablex") --shadow global table module
|
||||
|
||||
local set = class()
|
||||
local set = class({
|
||||
name = "set",
|
||||
})
|
||||
|
||||
--construct a new set
|
||||
--elements is an optional ordered table of elements to be added to the set
|
||||
function set:new(elements)
|
||||
self = self:init({
|
||||
_keyed = {},
|
||||
_ordered = {},
|
||||
})
|
||||
self._keyed = {}
|
||||
self._ordered = {}
|
||||
if elements then
|
||||
for _, v in ipairs(elements) do
|
||||
self:add(v)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--check if an element is present in the set
|
||||
@ -120,7 +119,7 @@ end
|
||||
|
||||
--copy a set
|
||||
function set:copy()
|
||||
return set:new():add_set(self)
|
||||
return set():add_set(self)
|
||||
end
|
||||
|
||||
--create a new set containing the complement of the other set contained in this one
|
||||
@ -141,7 +140,7 @@ end
|
||||
--create a new set containing the intersection of this set with another
|
||||
--only the elements present in both sets will remain in the result
|
||||
function set:intersection(other)
|
||||
local r = set:new()
|
||||
local r = set()
|
||||
for i, v in self:ipairs() do
|
||||
if other:has(v) then
|
||||
r:add(v)
|
||||
@ -157,7 +156,7 @@ end
|
||||
--equal to self:union(other):subtract_set(self:intersection(other))
|
||||
-- but with much less wasted effort
|
||||
function set:symmetric_difference(other)
|
||||
local r = set:new()
|
||||
local r = set()
|
||||
for i, v in self:ipairs() do
|
||||
if not other:has(v) then
|
||||
r:add(v)
|
||||
|
@ -26,17 +26,15 @@
|
||||
local path = (...):gsub("state_machine", "")
|
||||
local class = require(path .. "class")
|
||||
|
||||
local state_machine = class()
|
||||
function state_machine:new(states, start_in_state)
|
||||
self = self:init({
|
||||
states = states or {},
|
||||
current_state_name = "",
|
||||
reset_state_name = start_in_state or "",
|
||||
local state_machine = class({
|
||||
name = "state_machine",
|
||||
})
|
||||
|
||||
function state_machine:new(states, start_in_state)
|
||||
self.states = states or {}
|
||||
self.current_state_name = ""
|
||||
self.reset_state_name = start_in_state or ""
|
||||
self:reset()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--get the current state table (or nil if it doesn't exist)
|
||||
|
15
timer.lua
15
timer.lua
@ -9,19 +9,20 @@
|
||||
|
||||
local path = (...):gsub("timer", "")
|
||||
local class = require(path .. "class")
|
||||
local timer = class()
|
||||
local timer = class({
|
||||
name = "timer",
|
||||
})
|
||||
|
||||
--create a timer, with optional callbacks
|
||||
--callbacks recieve as arguments:
|
||||
-- the current progress as a number from 0 to 1, so can be used for lerps
|
||||
-- the timer object, so can be reset if needed
|
||||
function timer:new(time, on_progress, on_finish)
|
||||
return self:init({
|
||||
time = 0, --set in the reset below
|
||||
timer = 0,
|
||||
on_progress = on_progress,
|
||||
on_finish = on_finish,
|
||||
}):reset(time)
|
||||
self.time = 0
|
||||
self.timer = 0
|
||||
self.on_progress = on_progress
|
||||
self.on_finish = on_finish
|
||||
self:reset(time)
|
||||
end
|
||||
|
||||
--update this timer, calling the relevant callback if it exists
|
||||
|
98
vec2.lua
98
vec2.lua
@ -5,115 +5,67 @@
|
||||
local path = (...):gsub("vec2", "")
|
||||
local class = require(path .. "class")
|
||||
local math = require(path .. "mathx") --shadow global math module
|
||||
local make_pooled = require(path .. "make_pooled")
|
||||
|
||||
local vec2 = class()
|
||||
vec2.type = "vec2"
|
||||
local vec2 = class({
|
||||
name = "vec2",
|
||||
})
|
||||
|
||||
--stringification
|
||||
vec2.__mt.__tostring = function(self)
|
||||
function vec2:__tostring()
|
||||
return ("(%.2f, %.2f)"):format(self.x, self.y)
|
||||
end
|
||||
|
||||
--probably-too-flexible ctor
|
||||
function vec2:new(x, y)
|
||||
if type(x) == "number" and type(y) == "number" then
|
||||
return vec2:xy(x,y)
|
||||
elseif x then
|
||||
if type(x) == "number" then
|
||||
return vec2:filled(x)
|
||||
if type(x) == "number" or type(x) == "nil" then
|
||||
self:sset(x or 0, y)
|
||||
elseif type(x) == "table" then
|
||||
if x.type == "vec2" then
|
||||
return x:copy()
|
||||
elseif x[1] and x[2] then
|
||||
return vec2:xy(x[1], x[2])
|
||||
elseif x.x and x.y then
|
||||
return vec2:xy(x.x, x.y)
|
||||
if x.type and x:type() == "vec2" then
|
||||
self:vset(x)
|
||||
elseif x[1] then
|
||||
self:sset(x[1], x[2])
|
||||
else
|
||||
self:sset(x.x, x.y)
|
||||
end
|
||||
end
|
||||
end
|
||||
return vec2:zero()
|
||||
end
|
||||
|
||||
--explicit ctors
|
||||
function vec2:copy()
|
||||
return self:init({
|
||||
x = self.x, y = self.y
|
||||
})
|
||||
return vec2(self.x, self.y)
|
||||
end
|
||||
|
||||
function vec2:xy(x, y)
|
||||
return self:init({
|
||||
x = x, y = y
|
||||
})
|
||||
return vec2(x, y)
|
||||
end
|
||||
|
||||
function vec2:filled(v)
|
||||
return self:init({
|
||||
x = v, y = v
|
||||
})
|
||||
return vec2(v)
|
||||
end
|
||||
|
||||
function vec2:zero()
|
||||
return vec2:filled(0)
|
||||
end
|
||||
|
||||
--shared pooled storage
|
||||
local _vec2_pool = {}
|
||||
--size limit for tuning memory upper bound
|
||||
local _vec2_pool_limit = 128
|
||||
|
||||
function vec2.pool_size()
|
||||
return #_vec2_pool
|
||||
end
|
||||
|
||||
--flush the entire pool
|
||||
function vec2.flush_pool()
|
||||
if vec2.pool_size() > 0 then
|
||||
_vec2_pool = {}
|
||||
end
|
||||
end
|
||||
|
||||
--drain one element from the pool, if it exists
|
||||
function vec2.drain_pool()
|
||||
if #_vec2_pool > 0 then
|
||||
return table.remove(_vec2_pool)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--get a pooled vector (initialise it yourself)
|
||||
function vec2:pooled()
|
||||
return vec2.drain_pool() or vec2:zero()
|
||||
end
|
||||
|
||||
--get a pooled copy of an existing vector
|
||||
function vec2:pooled_copy()
|
||||
return vec2:pooled():vset(self)
|
||||
end
|
||||
|
||||
--release a vector to the pool
|
||||
function vec2:release(...)
|
||||
if vec2.pool_size() < _vec2_pool_limit then
|
||||
table.insert(_vec2_pool, self)
|
||||
end
|
||||
if ... then
|
||||
vec2.release(...)
|
||||
end
|
||||
return vec2(0)
|
||||
end
|
||||
|
||||
--unpack for multi-args
|
||||
|
||||
function vec2:unpack()
|
||||
return self.x, self.y
|
||||
end
|
||||
|
||||
--pack when a sequence is needed
|
||||
--(not particularly useful)
|
||||
|
||||
function vec2:pack()
|
||||
return {self:unpack()}
|
||||
end
|
||||
|
||||
--shared pooled storage
|
||||
make_pooled(vec2, 128)
|
||||
|
||||
--get a pooled copy of an existing vector
|
||||
function vec2:pooled_copy()
|
||||
return vec2:pooled():vset(self)
|
||||
end
|
||||
|
||||
--modify
|
||||
|
||||
function vec2:sset(x, y)
|
||||
|
101
vec3.lua
101
vec3.lua
@ -7,118 +7,73 @@ local path = (...):gsub("vec3", "")
|
||||
local class = require(path .. "class")
|
||||
local vec2 = require(path .. "vec2")
|
||||
local math = require(path .. "mathx") --shadow global math module
|
||||
local make_pooled = require(path .. "make_pooled")
|
||||
|
||||
local vec3 = class()
|
||||
vec3.type = "vec3"
|
||||
local vec3 = class({
|
||||
name = "vec3",
|
||||
})
|
||||
|
||||
--stringification
|
||||
vec3.__mt.__tostring = function(self)
|
||||
function vec3:__tostring()
|
||||
return ("(%.2f, %.2f, %.2f)"):format(self.x, self.y, self.z)
|
||||
end
|
||||
|
||||
--probably-too-flexible ctor
|
||||
function vec3:new(x, y, z)
|
||||
if x and y and z then
|
||||
return vec3:xyz(x, y, z)
|
||||
elseif x then
|
||||
if type(x) == "number" then
|
||||
return vec3:filled(x)
|
||||
if type(x) == "number" or type(x) == "nil" then
|
||||
self:sset(x or 0, y, z)
|
||||
elseif type(x) == "table" then
|
||||
if x.type == "vec3" then
|
||||
return x:copy()
|
||||
elseif x[1] and x[2] and x[3] then
|
||||
return vec3:xyz(x[1], x[2], x[3])
|
||||
if x.type and x:type() == "vec3" then
|
||||
self:vset(x)
|
||||
elseif x[1] then
|
||||
self:sset(x[1], x[2], x[3])
|
||||
else
|
||||
self:sset(x.x, x.y, x.z)
|
||||
end
|
||||
end
|
||||
end
|
||||
return vec3:zero()
|
||||
end
|
||||
|
||||
--explicit ctors
|
||||
function vec3:copy()
|
||||
return self:init({
|
||||
x = self.x, y = self.y, z = self.z
|
||||
})
|
||||
return vec3(self.x, self.y, self.z)
|
||||
end
|
||||
|
||||
function vec3:xyz(x, y, z)
|
||||
return self:init({
|
||||
x = x, y = y, z = z
|
||||
})
|
||||
return vec3(x, y, z)
|
||||
end
|
||||
|
||||
function vec3:filled(v)
|
||||
return self:init({
|
||||
x = v, y = v, z = v
|
||||
})
|
||||
function vec3:filled(x, y, z)
|
||||
return vec3(x, y, z)
|
||||
end
|
||||
|
||||
function vec3:zero()
|
||||
return vec3:filled(0)
|
||||
return vec3(0, 0, 0)
|
||||
end
|
||||
|
||||
--shared pooled storage
|
||||
local _vec3_pool = {}
|
||||
--size limit for tuning memory upper bound
|
||||
local _vec3_pool_limit = 128
|
||||
|
||||
function vec3.pool_size()
|
||||
return #_vec3_pool
|
||||
--unpack for multi-args
|
||||
function vec3:unpack()
|
||||
return self.x, self.y, self.z
|
||||
end
|
||||
|
||||
--flush the entire pool
|
||||
function vec3.flush_pool()
|
||||
if vec3.pool_size() > 0 then
|
||||
_vec3_pool = {}
|
||||
end
|
||||
--pack when a sequence is needed
|
||||
function vec3:pack()
|
||||
return {self:unpack()}
|
||||
end
|
||||
|
||||
--drain one element from the pool, if it exists
|
||||
function vec3.drain_pool()
|
||||
if #_vec3_pool > 0 then
|
||||
return table.remove(_vec3_pool)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--get a pooled vector (initialise it yourself)
|
||||
function vec3:pooled()
|
||||
return vec3.drain_pool() or vec3:zero()
|
||||
end
|
||||
--handle pooling
|
||||
make_pooled(vec3, 128)
|
||||
|
||||
--get a pooled copy of an existing vector
|
||||
function vec3:pooled_copy()
|
||||
return vec3:pooled():vset(self)
|
||||
end
|
||||
|
||||
--release a vector to the pool
|
||||
function vec3:release()
|
||||
if vec3.pool_size() < _vec3_pool_limit then
|
||||
table.insert(_vec3_pool, self)
|
||||
end
|
||||
end
|
||||
|
||||
--unpack for multi-args
|
||||
|
||||
function vec3:unpack()
|
||||
return self.x, self.y, self.z
|
||||
end
|
||||
|
||||
--pack when a sequence is needed
|
||||
--(not particularly useful)
|
||||
|
||||
function vec3:pack()
|
||||
return {self:unpack()}
|
||||
end
|
||||
|
||||
--modify
|
||||
|
||||
function vec3:sset(x, y, z)
|
||||
if not y then y = x end
|
||||
if not z then z = y end
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
self.y = y or x
|
||||
self.z = z or y or z
|
||||
return self
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user