This commit is contained in:
Benjamin Moir 2016-08-14 21:25:33 +10:00
commit 910fbe3e3f
15 changed files with 890 additions and 0 deletions

429
deepcopy.lua Normal file
View File

@ -0,0 +1,429 @@
--[[ deepcopy.lua
Changes in this fork
====================
- Added support for table.unpack (Lua 5.2+)
Deep-copy function for Lua - v0.2
==============================
- Does not overflow the stack.
- Maintains cyclic-references
- Copies metatables
- Maintains common upvalues between copied functions (for Lua 5.2 only)
TODO
----
- Document usage (properly) and provide examples
- Implement handling of LuaJIT FFI ctypes
- Provide option to only set metatables, not copy (as if they were
immutable)
- Find a way to replicate `debug.upvalueid` and `debug.upvaluejoin` in
Lua 5.1
- Copy function environments in Lua 5.1 and LuaJIT
(Lua 5.2's _ENV is actually a good idea!)
- Handle C functions
Usage
-----
copy = table.deecopy(orig)
copy = table.deecopy(orig, params, customcopyfunc_list)
`params` is a table of parameters to inform the copy functions how to
copy the data. The default ones available are:
- `value_ignore` (`table`/`nil`): any keys in this table will not be
copied (value should be `true`). (default: `nil`)
- `value_translate` (`table`/`nil`): any keys in this table will result
in the associated value, rather than a copy. (default: `nil`)
(Note: this can be useful for global tables: {[math] = math, ..})
- `metatable_immutable` (`boolean`): assume metatables are immutable and
do not copy them (only set). (default: `false`)
- `function_immutable` (`boolean`): do not copy function values; instead
use the original value. (default: `false`)
- `function_env` (`table`/`nil`): Set the enviroment of functions to
this value (via fourth arg of `loadstring`). (default: `nil`)
this value. (default: `nil`)
- `function_upvalue_isolate` (`boolean`): do not join common upvalues of
copied functions (only applicable for Lua 5.2 and LuaJIT). (default:
`false`)
- `function_upvalue_dontcopy` (`boolean`): do not copy upvalue values
(does not stop joining). (default: `false`)
`customcopyfunc_list` is a table of typenames to copy functions.
For example, a simple solution for userdata:
{ ["userdata"] = function(stack, orig, copy, state, arg1, arg2)
if state == nil then
copy = orig
local orig_uservalue = debug.getuservalue(orig)
if orig_uservalue ~= nil then
stack:recurse(orig_uservalue)
return copy, 'uservalue'
end
return copy, true
elseif state == 'uservalue' then
local copy_uservalue = arg2
if copy_uservalue ~= nil then
debug.setuservalue(copy, copy_uservalue)
end
return copy, true
end
end }
Any parameters passed to the `params` are available in `stack`.
You can use custom paramter names, but keep in mind that numeric keys and
string keys prefixed with a single underscore are reserved.
License
-------
Copyright (C) 2012 Declan White
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
]]
do
local type = rawtype or type
local rawget = rawget
local rawset = rawset
local next = rawnext or next
local getmetatable = debug and debug.getmetatable or getmetatable
local setmetatable = debug and debug.setmetatable or setmetatable
local debug_getupvalue = debug and debug.getupvalue or nil
local debug_setupvalue = debug and debug.setupvalue or nil
local debug_upvalueid = debug and debug.upvalueid or nil
local debug_upvaluejoin = debug and debug.upvaluejoin or nil
local table = table
local unpack = unpack or table.unpack
table.deepcopy_copyfunc_list = {
--["type"] = function(stack, orig, copy, state, temp1, temp2, temp..., tempN)
--
-- -- When complete:
-- state = true
--
-- -- Store temporary variables between iterations using these:
-- -- (Note: you MUST NOT call these AFTER recurse)
-- stack:_push(tempN+1, tempN+2, tempN+..., tempN+M)
-- stack:_pop(K)
-- -- K is the number to pop.
-- -- If you wanted to pop two from the last state and push four new ones:
-- stack:_pop(2)
-- stack:_push('t', 'e', 's', 't')
--
-- -- To copy a child value:
-- -- (Note: any calls to push or pop MUST be BEFORE a call to this)
-- state:recurse(childvalue_orig)
-- -- This will leave two temp variables on the stack for the next iteration
-- -- .., childvalue_orig, childvalue_copy
-- -- which are available via the varargs (temp...)
-- -- (Note: the copy may be nil if it was not copied (because caller
-- -- specified it not to be)).
-- -- You can only call this once per iteration.
--
-- -- Return like this:
-- -- (Temp variables are not part of the return list due to optimisation.)
-- return copy, state
--
--end,
_plainolddata = function(stack, orig, copy, state)
return orig, true
end,
["table"] = function(stack, orig, copy, state, arg1, arg2, arg3, arg4)
local orig_prevkey, grabkey = nil, false
if state == nil then -- 'init'
-- Initial state, check for metatable, or get first key
-- orig, copy:nil, state
copy = stack[orig]
if copy ~= nil then -- Check if already copied
return copy, true
else
copy = {} -- Would be nice if you could preallocate sizes!
stack[orig] = copy
local orig_meta = getmetatable(orig)
if orig_meta ~= nil then -- This table has a metatable, copy it
if not stack.metatable_immutable then
stack:_recurse(orig_meta)
return copy, 'metatable'
else
setmetatable(copy, orig_meta)
end
end
end
-- No metatable, go straight to copying key-value pairs
orig_prevkey = nil -- grab first key
grabkey = true --goto grabkey
elseif state == 'metatable' then
-- Metatable has been copied, set it and get first key
-- orig, copy:{}, state, metaorig, metacopy
local copy_meta = arg2--select(2, ...)
stack:_pop(2)
if copy_meta ~= nil then
setmetatable(copy, copy_meta)
end
-- Now start copying key-value pairs
orig_prevkey = nil -- grab first key
grabkey = true --goto grabkey
elseif state == 'key' then
-- Key has been copied, now copy value
-- orig, copy:{}, state, keyorig, keycopy
local orig_key = arg1--select(1, ...)
local copy_key = arg2--select(2, ...)
if copy_key ~= nil then
-- leave keyorig and keycopy on the stack
local orig_value = rawget(orig, orig_key)
stack:_recurse(orig_value)
return copy, 'value'
else -- key not copied? move onto next
stack:_pop(2) -- pop keyorig, keycopy
orig_prevkey = orig_key
grabkey = true--goto grabkey
end
elseif state == 'value' then
-- Value has been copied, set it and get next key
-- orig, copy:{}, state, keyorig, keycopy, valueorig, valuecopy
local orig_key = arg1--select(1, ...)
local copy_key = arg2--select(2, ...)
--local orig_value = arg3--select(3, ...)
local copy_value = arg4--select(4, ...)
stack:_pop(4)
if copy_value ~= nil then
rawset(copy, copy_key, copy_value)
end
-- Grab next key to copy
orig_prevkey = orig_key
grabkey = true --goto grabkey
end
--return
--::grabkey::
if grabkey then
local orig_key, orig_value = next(orig, orig_prevkey)
if orig_key ~= nil then
stack:_recurse(orig_key) -- Copy key
return copy, 'key'
else
return copy, true -- Key is nil, copying of table is complete
end
end
return
end,
["function"] = function(stack, orig, copy, state, arg1, arg2, arg3)
local grabupvalue, grabupvalue_idx = false, nil
if state == nil then
-- .., orig, copy, state
copy = stack[orig]
if copy ~= nil then
return copy, true
elseif stack.function_immutable then
copy = orig
return copy, true
else
copy = loadstring(string.dump(orig), nil, nil, stack.function_env)
stack[orig] = copy
if debug_getupvalue ~= nil and debug_setupvalue ~= nil then
grabupvalue = true
grabupvalue_idx = 1
else
-- No way to get/set upvalues!
return copy, true
end
end
elseif this_state == 'upvalue' then
-- .., orig, copy, state, uvidx, uvvalueorig, uvvaluecopy
local orig_upvalue_idx = arg1
--local orig_upvalue_value = arg2
local copy_upvalue_value = arg3
stack:_pop(3)
debug_setupvalue(copy, orig_upvalue_idx, copy_upvalue_value)
grabupvalue_idx = orig_upvalue_idx+1
stack:_push(grabupvalue_idx)
grabupvalue = true
end
if grabupvalue then
-- .., orig, copy, retto, state, uvidx
local upvalue_idx_curr = grabupvalue_idx
for upvalue_idx = upvalue_idx_curr, math.huge do
local upvalue_name, upvalue_value_orig = debug_getupvalue(orig, upvalue_idx)
if upvalue_name ~= nil then
local upvalue_handled = false
if not stack.function_upvalue_isolate and debug_upvalueid ~= nil and debug_upvaluejoin ~= nil then
local upvalue_uid = debug.upvalueid(orig, upvalue_idx)
-- Attempting to store an upvalueid of a function as a child of root is UB!
local other_orig = stack[upvalue_uid]
if other_orig ~= nil then
for other_upvalue_idx = 1, math.huge do
if upvalue_uid == debug_upvalueid(other_orig, other_upvalue_idx) then
local other_copy = stack[other_orig]
debug_upvaluejoin(
copy, upvalue_idx,
other_copy, other_upvalue_idx
)
break
end
end
upvalue_handled = true
else
stack[upvalue_uid] = orig
end
end
if not stack.function_upvalue_dontcopy and not upvalue_handled and upvalue_value_orig ~= nil then
stack:_recurse(upvalue_value_orig)
return copy, 'upvalue'
end
else
stack:_pop(1) -- pop uvidx
return copy, true
end
end
end
end,
["userdata"] = nil,
["lightuserdata"] = nil,
["thread"] = nil,
}
table.deepcopy_copyfunc_list["number" ] = table.deepcopy_copyfunc_list._plainolddata
table.deepcopy_copyfunc_list["string" ] = table.deepcopy_copyfunc_list._plainolddata
table.deepcopy_copyfunc_list["boolean"] = table.deepcopy_copyfunc_list._plainolddata
-- `nil` should never be encounted... but just in case:
table.deepcopy_copyfunc_list["nil" ] = table.deepcopy_copyfunc_list._plainolddata
do
local ORIG, COPY, RETTO, STATE, SIZE = 0, 1, 2, 3, 4
function table.deepcopy_push(...)
local arg_list_len = select('#', ...)
local stack_offset = stack._top+1
for arg_i = 1, arg_list_len do
stack[stack_offset+arg_i] = select(arg_i, ...)
end
stack._top = stack_top+arg_list_len
end
function table.deepcopy_pop(stack, count)
stack._top = stack._top-count
end
function table.deepcopy_recurse(stack, orig)
local retto = stack._ptr
local stack_top = stack._top
local stack_ptr = stack_top+1
stack._top = stack_top+SIZE
stack._ptr = stack_ptr
stack[stack_ptr+ORIG ] = orig
stack[stack_ptr+COPY ] = nil
stack[stack_ptr+RETTO] = retto
stack[stack_ptr+STATE] = nil
end
function table.deepcopy(root, params, customcopyfunc_list)
local stack = params or {}
--orig,copy,retto,state,[temp...,] partorig,partcopy,partretoo,partstate
stack[1+ORIG ] = root stack[1+COPY ] = nil
stack[1+RETTO] = nil stack[1+STATE] = nil
stack._ptr = 1 stack._top = 4
stack._push = table.deepcopy_push stack._pop = table.deepcopy_pop
stack._recurse = table.deepcopy_recurse
--[[local stack_dbg do -- debug
stack_dbg = stack
stack = setmetatable({}, {
__index = stack_dbg,
__newindex = function(t, k, v)
stack_dbg[k] = v
if tonumber(k) then
local stack = stack_dbg
local line_stack, line_label, line_stptr = "", "", ""
for stack_i = 1, math.max(stack._top, stack._ptr) do
local s_stack = (
(type(stack[stack_i]) == 'table' or type(stack[stack_i]) == 'function')
and string.gsub(tostring(stack[stack_i]), "^.-(%x%x%x%x%x%x%x%x)$", "<%1>")
or tostring(stack[stack_i])
), type(stack[stack_i])
local s_label = ""--dbg_label_dict[stack_i] or "?!?"
local s_stptr = (stack_i == stack._ptr and "*" or "")..(stack_i == k and "^" or "")
local maxlen = math.max(#s_stack, #s_label, #s_stptr)+1
line_stack = line_stack..s_stack..string.rep(" ", maxlen-#s_stack)
--line_label = line_label..s_label..string.rep(" ", maxlen-#s_label)
line_stptr = line_stptr..s_stptr..string.rep(" ", maxlen-#s_stptr)
end
io.stdout:write(
line_stack
--.. "\n"..line_label
.. "\n"..line_stptr
.. ""
)
io.read()
elseif false then
io.stdout:write(("stack.%s = %s"):format(
k,
(
(type(v) == 'table' or type(v) == 'function')
and string.gsub(tostring(v), "^.-(%x%x%x%x%x%x%x%x)$", "<%1>")
or tostring(v)
)
))
io.read()
end
end,
})
end]]
local copyfunc_list = table.deepcopy_copyfunc_list
repeat
local stack_ptr = stack._ptr
local this_orig = stack[stack_ptr+ORIG]
local this_copy, this_state
stack[0] = stack[0]
if stack.value_ignore and stack.value_ignore[this_orig] then
this_copy = nil
this_state = true --goto valuefound
else
if stack.value_translate then
this_copy = stack.value_translate[this_orig]
if this_copy ~= nil then
this_state = true --goto valuefound
end
end
if not this_state then
local this_orig_type = type(this_orig)
local copyfunc = (
customcopyfunc_list and customcopyfunc_list[this_orig_type]
or copyfunc_list[this_orig_type]
or error(("cannot copy type %q"):format(this_orig_type), 2)
)
this_copy, this_state = copyfunc(
stack,
this_orig,
stack[stack_ptr+COPY],
unpack(stack--[[_dbg]], stack_ptr+STATE, stack._top)
)
end
end
stack[stack_ptr+COPY] = this_copy
--::valuefound::
if this_state == true then
local retto = stack[stack_ptr+RETTO]
stack._top = stack_ptr+1 -- pop retto, state, temp...
-- Leave orig and copy on stack for parent object
stack_ptr = retto -- return to parent's stack frame
stack._ptr = stack_ptr
else
stack[stack_ptr+STATE] = this_state
end
until stack_ptr == nil
return stack[1+COPY]
end
end
end

BIN
example/assets/bullet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
example/assets/enemy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
example/assets/player.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

8
example/conf.lua Normal file
View File

@ -0,0 +1,8 @@
function love.conf(t)
t.title = "Scrolling Shooter Demo"
t.window.width = 480
t.window.height = 800
-- for Windows debugging
t.console = true
end

38
example/main.lua Normal file
View File

@ -0,0 +1,38 @@
require "object"
require "objects.world"
require "objects.enemy"
require "objects.player"
require "objects.spawner"
local world, player, spawner
function love.load(arg)
-- create world
world = new: world()
-- create player
player = new: player(world)
-- create enemy spawner
spawner = new: spawner("enemy", 0.4, function()
return world, math.random(10, love.graphics.getWidth() - 10), -10
end)
end
function love.update(dt)
-- you can retrieve return values from events.
-- you will receive a table indexed by instances
local r = event: update(dt)
-- this line would delete any object spawned by the spawner.
-- pointless, but a good example
--delete(r[spawner])
if love.keyboard.isDown("escape") then
love.event.quit()
end
end
function love.draw()
event: draw()
end

View File

@ -0,0 +1,40 @@
require "object"
object: bullet
{
x = 0;
y = 0;
image = love.graphics.newImage "assets/bullet.png";
construct = function(world, owner, x, y)
self.world = world
self.owner = owner
self.x = x
self.y = y
local w, h = image:getWidth(), image:getHeight()
world.add(self, x, y, w, h)
end;
update = function(dt)
y = y - 250 * dt
if y < 0 then delete(self) return end
-- update physics
world.move(self, x, y)
end;
draw = function()
love.graphics.draw(image, x, y)
end;
collision = function(other)
if typeof(other) == "enemy" then
owner.score = owner.score + 100
delete(self)
end
end;
destruct = function()
world.remove(self)
end;
}

38
example/objects/enemy.lua Normal file
View File

@ -0,0 +1,38 @@
require "object"
object: enemy
{
x = 0;
y = 0;
image = love.graphics.newImage "assets/enemy.png";
construct = function(world, x, y)
self.world = world
self.x = x
self.y = y
local w, h = image:getWidth(), image:getHeight()
world.add(self, x, y, w, h)
end;
update = function(dt)
y = y + 200 * dt
if y > 850 then delete(self) return end
-- update physics
world.move(self, x, y)
end;
collision = function(other)
if typeof(other) == "bullet" then
delete(self)
end
end;
draw = function()
love.graphics.draw(image, x, y)
end;
destruct = function()
world.remove(self)
end;
}

View File

@ -0,0 +1,68 @@
require "object"
require "objects.bullet"
require "objects.timer"
object: player
{
x = 200;
y = 710;
image = love.graphics.newImage "assets/player.png";
speed = 150;
score = 0;
isAlive = true;
canShoot = true;
shootInterval = 0.2;
construct = function(world)
self.world = world
local w, h = image:getWidth(), image:getHeight()
world.add(self, x, y, w, h)
end;
collision = function(other)
if typeof(other) == "enemy" then
delete(other)
isAlive = false
end
end;
update = function(dt)
if isAlive then
local right_x_boundary = love.graphics.getWidth() - image:getWidth()
if love.keyboard.isDown("a", "left") and x > 0 then
x = x - speed * dt
elseif love.keyboard.isDown("d", "right") and x < right_x_boundary then
x = x + speed * dt
end
if love.keyboard.isDown("space", "lctrl", "rctrl") and canShoot then
-- shoot bullet
canShoot = false
new: bullet(world, self, x + image:getWidth() / 2, y)
new: timer(shootInterval, function() canShoot = true end)
end
-- update physics
world.move(self, x, y)
elseif love.keyboard.isDown("r") then
isAlive = true
score = 0
for k, v in pairs(world.objects) do
if k ~= self then delete(k) end
end
end
end;
draw = function()
if isAlive then
love.graphics.draw(image, x, y)
else
love.graphics.print("Press 'R' to restart", love.graphics.getWidth() / 2 - 50, love.graphics.getHeight() / 2 - 10)
end
end;
destruct = function()
world.remove(self)
end;
}

View File

@ -0,0 +1,24 @@
require "object"
require "objects.timer"
object: spawner
{
timer = nil;
spawn_type = nil;
spawn_argf = nil;
spawn_time = nil;
construct = function(obj, t, f)
spawn_type = obj
spawn_argf = f
spawn_time = t
end;
update = function(dt)
if not timer or timer.dead then
timer = new: timer(spawn_time, function()
new(spawn_type, spawn_argf())
end)
end
end;
}

24
example/objects/timer.lua Normal file
View File

@ -0,0 +1,24 @@
require "object"
object: timer
{
time_left = 0;
func = nil;
args = nil;
dead = false;
construct = function(t, f, ...)
time_left = t
func = f
args = {...}
end;
update = function(dt)
time_left = time_left - dt
if time_left < 0 then
func((unpack or table.unpack)(args))
dead = true
delete(self)
end
end;
}

41
example/objects/world.lua Normal file
View File

@ -0,0 +1,41 @@
require "object"
object: world
{
objects = {};
checkCollision = function(x1, y1, w1, h1, x2, y2, w2, h2)
return x1 < x2 + w2 and
x2 < x1 + w1 and
y1 < y2 + h2 and
y2 < y1 + h1
end;
add = function(obj, x, y, w, h)
objects[obj] = { x = x, y = y, w = w, h = h }
end;
remove = function(obj)
objects[obj] = nil
end;
move = function(obj, x, y, w, h)
if objects[obj] then
objects[obj].x = x
objects[obj].y = y
objects[obj].w = w or objects[obj].w
objects[obj].h = h or objects[obj].h
end
end;
update = function(dt)
for o1, v1 in pairs(objects) do
for o2, v2 in pairs(objects) do
if checkCollision(v1.x, v1.y, v1.w, v1.h, v2.x, v2.y, v2.w, v2.h) then
if o1.collision then o1.collision(o2) end
if o2.collision then o2.collision(o1) end
end
end
end
end;
}

4
example/readme.md Normal file
View File

@ -0,0 +1,4 @@
# Scrolling Shooter Demo
This is an adaption of the Love2D tutorial available [here](http://www.osmstudios.com/tutorials/your-first-love2d-game-in-200-lines-part-1-of-3).
All sprites used in the demo can be downloaded from [here](http://opengameart.org/content/aircrafts).

134
object.lua Normal file
View File

@ -0,0 +1,134 @@
--[[ object.lua
Object system for Lua
=====================
Designed for use with Love2D
written by TheZombieKiller
Dependencies
============
deepcopy.lua - https://gist.github.com/DaZombieKiller/1b5690842b8138a4024c55760e2d253f#file-deepcopy-lua
Usage
=====
object: <object name>
{
member = default_value;
construct = function(...)
end;
destruct = function(...)
end;
<et cetera>
}
local instance = new: <object name>(...)
print(typeof(instance))
instance.method(...)
event: update() -- calls update method on all instances that have it
delete(instance)
An object cannot contain a member named "self".
Usage of self.<member name> inside a member function is not necessary,
as you can access the member directly, without using "self". However, you can
optionally use it anyway, because self is automatically assigned to the instance.
License
=======
Copyright (c) 2016 Benjamin Moir
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
require "deepcopy"
local __instances = {}
local __objects = {}
local function call_index(t, func, ...)
local args = {...}
if #args > 0 then
return getmetatable(t).__index(nil, func)(nil, ...)
else
return function(...)
return getmetatable(t).__index(nil, func)(nil, ...)
end
end
end
object = setmetatable({}, {
__call = call_index,
__newindex = function() end,
__index = function(_, k)
return function(_, body)
if body.self ~= nil then error "an object cannot contain 'self'" end
__objects[k] = body
end
end,
})
new = setmetatable({}, {
__call = call_index,
__newindex = function() end,
__index = function(_, n)
return function(_, ...)
if not __objects[n] then error("unknown object '" .. n .. "'") end
local instance = {}
local data = table.deepcopy(__objects[n], { function_env = instance }, {
["userdata"] = function(stack, orig, copy, state, arg1, arg2)
return orig, true
end,
})
__instances[instance] = setmetatable(instance, {
__index = function(t, k)
if k == "self" then
return instance
else
return data[k] or (_ENV or _G)[k]
end
end,
__newindex = function(t, k, v)
data[k] = v
end,
__object = n,
})
if instance.construct then instance.construct(...) end
return instance
end
end,
})
delete = function(instance, ...)
if not instance then return end
__instances[instance] = nil
if instance.destruct then return instance.destruct(...) end
end
typeof = function(instance)
if not instance then return nil end
return getmetatable(instance).__object
end
event = setmetatable({}, {
__call = call_index,
__newindex = function() end,
__index = function(_, method)
return function(_, ...)
local r = {}
for instance in pairs(__instances) do
if instance[method] then
r[instance] = instance[method](...)
end
end
return r
end
end,
})

42
readme.md Normal file
View File

@ -0,0 +1,42 @@
# object.lua
## Object system for Lua
Designed for use with Love2D
written by TheZombieKiller
## Dependencies
[deepcopy.lua](https://gist.github.com/DaZombieKiller/1b5690842b8138a4024c55760e2d253f#file-deepcopy-lua) (included)
## Usage
object: <object name>
{
member = default_value;
construct = function(...)
end;
destruct = function(...)
end;
<et cetera>
}
local instance = new: <object name>(...)
print(typeof(instance))
instance.method(...)
event: update() -- calls update method on all instances that have it
delete(instance)
An object cannot contain a member named "self".
Usage of self.<member name> inside a member function is not necessary, as you can access the member directly, without using "self".
However, you can optionally use it anyway, because self is automatically assigned to the instance.
## License
Copyright (c) 2016 Benjamin Moir
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.