commit 910fbe3e3fe5dd74008c63df9183c79997769f97 Author: Benjamin Moir Date: Sun Aug 14 21:25:33 2016 +1000 Initial diff --git a/deepcopy.lua b/deepcopy.lua new file mode 100644 index 0000000..d642295 --- /dev/null +++ b/deepcopy.lua @@ -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 diff --git a/example/assets/bullet.png b/example/assets/bullet.png new file mode 100644 index 0000000..5b55c61 Binary files /dev/null and b/example/assets/bullet.png differ diff --git a/example/assets/enemy.png b/example/assets/enemy.png new file mode 100644 index 0000000..8afbeb6 Binary files /dev/null and b/example/assets/enemy.png differ diff --git a/example/assets/player.png b/example/assets/player.png new file mode 100644 index 0000000..ce6fd33 Binary files /dev/null and b/example/assets/player.png differ diff --git a/example/conf.lua b/example/conf.lua new file mode 100644 index 0000000..2c0354d --- /dev/null +++ b/example/conf.lua @@ -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 diff --git a/example/main.lua b/example/main.lua new file mode 100644 index 0000000..076b3f7 --- /dev/null +++ b/example/main.lua @@ -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 diff --git a/example/objects/bullet.lua b/example/objects/bullet.lua new file mode 100644 index 0000000..7c3f156 --- /dev/null +++ b/example/objects/bullet.lua @@ -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; +} diff --git a/example/objects/enemy.lua b/example/objects/enemy.lua new file mode 100644 index 0000000..7cbb098 --- /dev/null +++ b/example/objects/enemy.lua @@ -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; +} diff --git a/example/objects/player.lua b/example/objects/player.lua new file mode 100644 index 0000000..80cc2b0 --- /dev/null +++ b/example/objects/player.lua @@ -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; +} diff --git a/example/objects/spawner.lua b/example/objects/spawner.lua new file mode 100644 index 0000000..2fd08be --- /dev/null +++ b/example/objects/spawner.lua @@ -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; +} diff --git a/example/objects/timer.lua b/example/objects/timer.lua new file mode 100644 index 0000000..56d55b8 --- /dev/null +++ b/example/objects/timer.lua @@ -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; +} diff --git a/example/objects/world.lua b/example/objects/world.lua new file mode 100644 index 0000000..fe34289 --- /dev/null +++ b/example/objects/world.lua @@ -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; +} diff --git a/example/readme.md b/example/readme.md new file mode 100644 index 0000000..5cc2bcb --- /dev/null +++ b/example/readme.md @@ -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). \ No newline at end of file diff --git a/object.lua b/object.lua new file mode 100644 index 0000000..6246074 --- /dev/null +++ b/object.lua @@ -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: + { + member = default_value; + + construct = function(...) + end; + + destruct = function(...) + end; + + + } + + local instance = new: (...) + 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. 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, +}) diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..495c926 --- /dev/null +++ b/readme.md @@ -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: + { + member = default_value; + + construct = function(...) + end; + + destruct = function(...) + end; + + + } + + local instance = new: (...) + 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. 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. \ No newline at end of file