Refactor for better configuration and node management

master
informatic 2017-01-12 22:16:52 +01:00
parent 805a4273aa
commit 30247ffb87
18 changed files with 340 additions and 257 deletions

View File

@ -1,12 +1,13 @@
return {
transitionTime = 1,
cycleTime = 5,
screens = {
require 'screens.at',
require 'screens.cube',
require 'screens.weather',
require 'screens.time',
require 'screens.misery',
require 'screens.screen1',
displayTime = 2,
transitionTime = 0.5,
nodes = {
{'nodes.at'},
{'nodes.cube'},
{'nodes.time'},
{'nodes.weather'},
{'nodes.misery', displayTime = 7},
{'nodes.screen1'},
},
environment = os.getenv('ENV') or 'prod',
}

106
core/node-manager.lua Normal file
View File

@ -0,0 +1,106 @@
local NodeManager = class('NodeManager', {
state = 'running',
stateTime = 0,
currentNode = nil,
})
function NodeManager:init(config)
self.config = config
self.nodes = {}
self.nodeConfs = {}
self.currentNode = nil
print('Initializing NodeManager')
end
function NodeManager:load()
self:configChanged()
end
function NodeManager:resize(w, h)
self.secondaryCanvas = love.graphics.newCanvas(w, h)
end
function NodeManager:configChanged()
local cnt = {}
local newNodes = {}
local newNodeConfs = {}
for _, c_ in ipairs(self.config.nodes) do
local nodeConfig = lume.clone(c_)
local hash = inspect(nodeConfig)
if cnt[hash] == nil then cnt[hash] = 0 end
cnt[hash] = cnt[hash] + 1
hash = hash .. '-' .. tostring(cnt[hash])
local nodeName = nodeConfig[1]
table.remove(nodeConfig, 1)
if self.nodeConfs[hash] then
print('Using existing node:', self.nodeConfs[hash], hash)
newNodes[#newNodes + 1] = self.nodeConfs[hash]
else
print('Creating new node.', nodeName, inspect(nodeConfig))
local status, err = pcall(function()
newNodes[#newNodes + 1] = require(nodeName)(nodeConfig)
end)
if err then
print("Error occured while loading", nodeName, err)
return
end
end
newNodeConfs[hash] = newNodes[#newNodes]
end
self.nodes = newNodes
self.nodeConfs = newNodeConfs
end
function NodeManager:render()
if not self.currentNode then self.currentNode = self.nodes[1] end
if not self.currentNode then return end
love.graphics.print('state: ' .. self.state .. '; counter: ' .. tostring(self.stateTime), 50, 50)
self.currentNode:render()
if self.state == 'transitioning' and self.currentNode ~= self.nextNode and self.nextNode then
self.secondaryCanvas:renderTo(function()
self.nextNode:render()
end)
love.graphics.setColor(255, 255, 255, 255 * (self.stateTime / self.config.transitionTime))
love.graphics.draw(self.secondaryCanvas, 0, 0)
end
end
function NodeManager:update(dt)
if not self.currentNode then self.currentNode = self.nodes[1] end
if not self.currentNode then return end
self.stateTime = self.stateTime + dt
if self.state == 'transitioning' and self.stateTime >= self.config.transitionTime then
self.stateTime = 0
self.state = 'running'
self.currentNode:afterExit()
-- self.currentNode, self.nextNode = self.nextNode, self.nodes[(lume.find(self.nodes, self.nextNode) or 1) % #self.nodes + 1]
self.currentNode = self.nextNode
self.currentNode:afterEnter()
elseif self.state == 'running' and self.stateTime >= (self.currentNode.displayTime or self.config.displayTime) then
self.stateTime = 0
self.state = 'transitioning'
self.currentNode:beforeExit()
self.nextNode = self.nodes[(lume.find(self.nodes, self.currentNode) or 1) % #self.nodes + 1]
self.nextNode:beforeEnter()
end
self.currentNode:update(dt)
if self.state == 'transitioning' and self.currentNode ~= self.nextNode and self.nextNode then
self.nextNode:update(dt)
end
end
return NodeManager

16
core/node.lua Normal file
View File

@ -0,0 +1,16 @@
local Node = class('Node', {})
function Node:init(opts)
for i, n in pairs(opts) do
self[i] = n
end
end
function Node:update(dt) end
function Node:beforeEnter() end
function Node:afterEnter() end
function Node:beforeExit() end
function Node:afterExit() end
return Node

33
core/thread-node.lua Normal file
View File

@ -0,0 +1,33 @@
local ThreadNode = Node:extend('ThreadNode', {
threadFile = nil,
threadChannel = nil,
updateInterval = 5,
lastUpdate = 0,
state = nil,
})
function ThreadNode:update(dt)
if not self.state then self:checkUpdate() end
end
function ThreadNode:checkUpdate()
if self.threadFile and self.threadChannel then
if self.lastUpdate < love.timer.getTime() - self.updateInterval or
(not self.state and self.lastUpdate < love.timer.getTime() - 5) then
self.lastUpdate = love.timer.getTime()
print(self.threadChannel, "Updating...")
local updateThread = love.thread.newThread(self.threadFile)
updateThread:start()
end
self.state = love.thread.getChannel(self.threadChannel):pop() or self.state
end
end
function ThreadNode:afterExit()
print('exit')
self:checkUpdate()
end
return ThreadNode

View File

@ -1,17 +1,24 @@
local debugGraph = require 'vendor.debugGraph'
local inspect = require 'vendor.inspect'
local push = require 'vendor.push'
local lurker = require 'vendor.lurker'
class = require('vendor.30log')
inspect = require('vendor.inspect')
lume = require('vendor.lume')
Node = require('core.node')
ThreadNode = require('core.thread-node')
NodeManager = require('core.node-manager')
local config = require('config')
environment = os.getenv('ENV')
local push = require('vendor.push')
local lurker = require('vendor.lurker')
local debugGraph = require('vendor.debugGraph')
local fpsGraph, memGraph;
local gameWidth, gameHeight = 1280, 720
local windowWidth, windowHeight = love.window.getDesktopDimensions()
windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 --make the window a bit smaller than the screen itself
if environment == 'DEV' then
if config.environment == 'dev' then
push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, {fullscreen = false, resizable = true})
else
push:setupScreen(gameWidth, gameHeight, gameWidth, gameHeight, {fullscreen = true})
@ -19,37 +26,22 @@ end
function love.resize(w, h)
push:resize(w, h)
secondaryCanvas = love.graphics.newCanvas(push:getWidth(), push:getHeight())
manager:resize(w, h)
end
function love.load()
state = {
currentScreen = 1,
transitioning = false,
stateCounter = 0,
}
manager = NodeManager(config)
manager:load()
manager:resize(push:getWidth(), push:getHeight())
love.mouse.setVisible( false )
secondaryCanvas = love.graphics.newCanvas(push:getWidth(), push:getHeight())
fpsGraph = debugGraph:new('fps', 0, 0)
memGraph = debugGraph:new('mem', 0, 30)
for key, node in ipairs(config.screens) do
node.load()
end
lurker.quiet = true
end
function lurker.preswap(f)
if f:match('_thread.lua') then
print("Preventing _thread update!")
return false
end
end
function getw() return push:getWidth() end
function geth() return push:getHeight() end
@ -60,14 +52,7 @@ function love.draw()
oldw, oldh = love.graphics.getWidth, love.graphics.getHeight
love.graphics.getWidth, love.graphics.getHeight = getw, geth
config.screens[state.currentScreen].render()
if state.transitioning then
-- Render next screen into canvas and fade accordingly
secondaryCanvas:renderTo(config.screens[state.currentScreen % #config.screens + 1].render)
love.graphics.setColor(255, 255, 255, 255 * (state.stateCounter / config.transitionTime)) -- red, green, blue, opacity (this would be white with 20% opacity)
love.graphics.draw(secondaryCanvas, 0, 0)
end
manager:render()
-- Draw graphs
love.graphics.setColor(255, 255, 255, 128)
@ -83,30 +68,21 @@ function love.draw()
end
function love.update(dt)
config.screens[state.currentScreen].update(dt)
if state.transitioning then
config.screens[state.currentScreen % #config.screens + 1].update(dt)
end
state.stateCounter = state.stateCounter + dt
if state.transitioning then
if state.stateCounter >= config.transitionTime then
state.stateCounter = 0
state.transitioning = false
state.currentScreen = (state.currentScreen % #config.screens) + 1
end
else
if state.stateCounter >= config.cycleTime then
state.stateCounter = 0
state.transitioning = true
end
end
manager:update(dt)
-- Update the graphs
fpsGraph:update(dt)
memGraph:update(dt)
lurker.update()
end
function lurker.preswap(f)
if f == 'config.lua' then
print('config reloaded, notifying nodemanager')
package.loaded['config'] = nil
manager.config = require('config');
manager:configChanged();
elseif f:match('_thread.lua') then
return false
end
end

46
nodes/at.lua Normal file
View File

@ -0,0 +1,46 @@
local node = ThreadNode:extend('nodes.at', {
threadFile = 'nodes/at_thread.lua',
threadChannel = 'at',
api = 'http://at.hackerspace.pl/api',
})
local bigFont = love.graphics.newFont('fonts/Lato-Light.ttf', 80)
local textFont = love.graphics.newFont('fonts/Lato-Light.ttf', 50)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state then
love.graphics.setColor( 255, 255, 255, 100 )
love.graphics.setFont(bigFont)
love.graphics.printf('Currently at hackerspace:', 50, 100, love.graphics.getWidth() - 100, 'center')
usersList = (table.concat(lume.map(self.state.users, function(v) return v.login end), ', ') or 'nobody...') .. '\n'
if self.state.unknown > 0 then
usersList = usersList .. '\n...and ' .. tostring(self.state.unknown) .. ' unknown creatures'
end
if self.state.kektops > 0 then
usersList = usersList .. '\n...and ' .. tostring(self.state.kektops) .. ' kektops'
end
if self.state.esps > 0 then
usersList = usersList .. '\n...and ' .. tostring(self.state.esps) .. ' ESPs'
end
love.graphics.setColor( 255, 255, 255 )
love.graphics.setFont(textFont)
love.graphics.printf(usersList, 50, 220, love.graphics.getWidth() - 100, 'center')
else
love.graphics.setColor( 255, 255, 255, 80 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading at...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
return node

View File

@ -1,4 +1,5 @@
local node = {}
local node = Node:extend('nodes.at', {
})
local papa = love.graphics.newImage("papa.png")
local h = 25.0
local v = {
@ -31,9 +32,7 @@ local E = 100 * math.tan(2*math.pi/3)
function to2d(p) return p[1] * E / (p[3]+E), p[2] * E / (p[3]+E) end
function node.load() end
function node.render()
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
love.graphics.setColor( 255, 255, 255 )
@ -47,7 +46,7 @@ function node.render()
end
end
function node.update(dt)
function node:update(dt)
for _, vec in ipairs(v) do
angle = -dt/3
nv0 = math.cos(angle) * vec[1] + math.sin(angle) * vec[3]

View File

@ -1,29 +1,27 @@
local node = {}
local node = ThreadNode:extend('node.misery', {
threadFile = 'nodes/misery_thread.lua',
threadChannel = 'misery',
updateInterval = 2 * 60,
})
local inspect = require('vendor.inspect')
local textFont = love.graphics.newFont('fonts/Lato-Light.ttf', 50)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
local updateInterval = 2 * 60
local lastUpdate = 0
local state = nil
function node.load() end
function node.unload() end
function node.render()
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if state then
if self.state then
love.graphics.setColor( 255, 255, 255 )
love.graphics.setFont(textFont)
love.graphics.printf(state.entry, 50, 180, love.graphics.getWidth() - 100, 'center')
love.graphics.printf(self.state.entry, 50, 180, love.graphics.getWidth() - 100, 'center')
love.graphics.setColor( 255, 255, 255, 200 )
love.graphics.setFont(smallFont)
local description = 'added by ' .. state.author .. ' on ' .. os.date('%Y/%m/%d %X', state.added)
local description = 'added by ' .. self.state.author .. ' on ' .. os.date('%Y/%m/%d %X', self.state.added)
love.graphics.printf(description, 200, love.graphics.getHeight() - 100, love.graphics.getWidth() - 400, 'center')
else
love.graphics.setColor( 255, 255, 255, 80 )
@ -33,17 +31,4 @@ function node.render()
end
end
function node.update(dt)
if lastUpdate < love.timer.getTime() - updateInterval or
(not state and lastUpdate < love.timer.getTime() - 5) then
lastUpdate = love.timer.getTime()
print("Updating...")
local updateThread = love.thread.newThread('screens/misery_thread.lua')
updateThread:start()
end
state = love.thread.getChannel('misery'):pop() or state
end
return node

View File

@ -1,9 +1,6 @@
local node = {}
local node = Node:extend('nodes.screen1', {})
function node.load()
end
function node.render()
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
@ -16,7 +13,4 @@ function node.render()
end
end
function node.update(dt)
end
return node

View File

@ -1,11 +1,9 @@
local node = {}
local node = Node:extend('nodes.time', {})
local textFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 400)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 60)
function node.load() end
function node.render()
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
@ -18,7 +16,4 @@ function node.render()
love.graphics.printf(os.date('%Y/%m/%d'), 0, 575, love.graphics.getWidth(), 'center');
end
function node.update(dt)
end
return node

50
nodes/weather.lua Normal file
View File

@ -0,0 +1,50 @@
local node = ThreadNode:extend('nodes.weather', {
threadFile = 'nodes/weather_thread.lua',
threadChannel = 'weather',
updateInterval = 5 * 60,
})
local weatherFont = love.graphics.newFont('fonts/weathericons-regular-webfont.ttf', 400)
local textFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 300)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
local weatherGlyphs = {
snow = "",
mist = "",
clear = "",
clouds = "",
}
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state then
love.graphics.setColor( 255, 255, 255 )
if weatherGlyphs[self.state.weather] then
love.graphics.setFont(weatherFont)
love.graphics.print(weatherGlyphs[self.state.weather], 120, 35)
else
love.graphics.setFont(smallFont)
love.graphics.print(self.state.weather, 150, love.graphics.getHeight()/2 - 60)
end
love.graphics.setFont(textFont)
love.graphics.printf(tostring(self.state.temperature) .. "°", 600, 150, 650, 'center')
love.graphics.setFont(smallFont)
love.graphics.printf(os.date("Last update: %Y/%m/%d %H:%M", self.state.lastUpdate), 0, love.graphics.getHeight() - 60, love.graphics.getWidth(), 'center')
if self.state.insideTemperature then
love.graphics.printf("Room: " .. tostring(self.state.insideTemperature) .. "° / " .. tostring(self.state.insideHumidity) .. "%RH", 0, love.graphics.getHeight() - 100, love.graphics.getWidth(), 'center')
end
else
love.graphics.setColor( 255, 255, 255, 80 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading weather...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
return node

View File

@ -1,63 +0,0 @@
local node = {}
local lume = require('vendor.lume');
local bigFont = love.graphics.newFont('fonts/Lato-Light.ttf', 80)
local textFont = love.graphics.newFont('fonts/Lato-Light.ttf', 50)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
local updateInterval = 2 * 60
local lastUpdate = 0
local state = nil
function node.load() end
function node.unload() end
function node.render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if state then
love.graphics.setColor( 255, 255, 255, 100 )
love.graphics.setFont(bigFont)
love.graphics.printf('Currently at hackerspace:', 50, 100, love.graphics.getWidth() - 100, 'center')
usersList = (table.concat(lume.map(state.users, function(v) return v.login end), ', ') or 'nobody...') .. '\n'
if state.unknown > 0 then
usersList = usersList .. '\n...and ' .. tostring(state.unknown) .. ' unknown creatures'
end
if state.kektops > 0 then
usersList = usersList .. '\n...and ' .. tostring(state.kektops) .. ' kektops'
end
if state.esps > 0 then
usersList = usersList .. '\n...and ' .. tostring(state.esps) .. ' ESPs'
end
love.graphics.setColor( 255, 255, 255 )
love.graphics.setFont(textFont)
love.graphics.printf(usersList, 50, 220, love.graphics.getWidth() - 100, 'center')
else
love.graphics.setColor( 255, 255, 255, 80 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading at...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
function node.update(dt)
if lastUpdate < love.timer.getTime() - updateInterval or
(not state and lastUpdate < love.timer.getTime() - 5) then
lastUpdate = love.timer.getTime()
print("Updating...")
local updateThread = love.thread.newThread('screens/at_thread.lua')
updateThread:start()
end
state = love.thread.getChannel('at'):pop() or state
end
return node

View File

@ -1,19 +0,0 @@
local node = {}
function node.render()
love.graphics.setColor( 80, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
love.graphics.setColor(255, 255, 255, 255)
love.graphics.setNewFont(90)
love.graphics.printf("Screen 2!", 0, love.graphics.getHeight()/2 - 45, love.graphics.getWidth(), 'center')
love.graphics.setColor(255, 50, 0)
love.graphics.circle("fill", 60, 300, 50)
end
function node.update(dt)
end
return node

View File

@ -1,67 +0,0 @@
local node = {}
local inspect = require('vendor.inspect')
local weatherFont = love.graphics.newFont('fonts/weathericons-regular-webfont.ttf', 400)
local textFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 300)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
local weatherGlyphs = {
snow = "",
mist = "",
clear = "",
clouds = "",
}
local updateInterval = 5 * 60
local lastUpdate = 0
local state = nil
function node.load() end
function node.unload() end
function node.render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if state then
love.graphics.setColor( 255, 255, 255 )
if weatherGlyphs[state.weather] then
love.graphics.setFont(weatherFont)
love.graphics.print(weatherGlyphs[state.weather], 120, 35)
else
love.graphics.setFont(smallFont)
love.graphics.print(state.weather, 150, love.graphics.getHeight()/2 - 60)
end
love.graphics.setFont(textFont)
love.graphics.printf(tostring(state.temperature) .. "°", 600, 150, 650, 'center')
love.graphics.setFont(smallFont)
love.graphics.printf(os.date("Last update: %Y/%m/%d %H:%M", state.lastUpdate), 0, love.graphics.getHeight() - 60, love.graphics.getWidth(), 'center')
if state.insideTemperature then
love.graphics.printf("Room: " .. tostring(state.insideTemperature) .. "° / " .. tostring(state.insideHumidity) .. "%RH", 0, love.graphics.getHeight() - 100, love.graphics.getWidth(), 'center')
end
else
love.graphics.setColor( 255, 255, 255, 80 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading weather...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
function node.update(dt)
if lastUpdate < love.timer.getTime() - updateInterval or
(not state and lastUpdate < love.timer.getTime() - 5) then
lastUpdate = love.timer.getTime()
print("Updating...")
local updateThread = love.thread.newThread('screens/weather_thread.lua')
updateThread:start()
end
state = love.thread.getChannel('weather'):pop() or state
end
return node

31
vendor/30log.lua vendored Normal file
View File

@ -0,0 +1,31 @@
local next, assert, pairs, type, tostring, setmetatable, baseMt, _instances, _classes, _class = next, assert, pairs, type, tostring, setmetatable, {}, setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'})
local function assert_call_from_class(class, method) assert(_classes[class], ('Wrong method call. Expected class:%s.'):format(method)) end; local function assert_call_from_instance(instance, method) assert(_instances[instance], ('Wrong method call. Expected instance:%s.'):format(method)) end
local function bind(f, v) return function(...) return f(v, ...) end end
local default_filter = function() return true end
local function deep_copy(t, dest, aType) t = t or {}; local r = dest or {}; for k,v in pairs(t) do if aType ~= nil and type(v) == aType then r[k] = (type(v) == 'table') and ((_classes[v] or _instances[v]) and v or deep_copy(v)) or v elseif aType == nil then r[k] = (type(v) == 'table') and k~= '__index' and ((_classes[v] or _instances[v]) and v or deep_copy(v)) or v end; end return r end
local function instantiate(call_init,self,...) assert_call_from_class(self, 'new(...) or class(...)'); local instance = {class = self}; _instances[instance] = tostring(instance); deep_copy(self, instance, 'table')
instance.__index, instance.__subclasses, instance.__instances, instance.mixins = nil, nil, nil, nil; setmetatable(instance,self); if call_init and self.init then if type(self.init) == 'table' then deep_copy(self.init, instance) else self.init(instance, ...) end end; return instance
end
local function extend(self, name, extra_params)
assert_call_from_class(self, 'extend(...)'); local heir = {}; _classes[heir] = tostring(heir); self.__subclasses[heir] = true; deep_copy(extra_params, deep_copy(self, heir))
heir.name, heir.__index, heir.super, heir.mixins = extra_params and extra_params.name or name, heir, self, {}; return setmetatable(heir,self)
end
baseMt = { __call = function (self,...) return self:new(...) end, __tostring = function(self,...)
if _instances[self] then return ("instance of '%s' (%s)"):format(rawget(self.class,'name') or '?', _instances[self]) end; return _classes[self] and ("class '%s' (%s)"):format(rawget(self,'name') or '?', _classes[self]) or self end
}; _classes[baseMt] = tostring(baseMt); setmetatable(baseMt, {__tostring = baseMt.__tostring})
local class = {isClass = function(t) return not not _classes[t] end, isInstance = function(t) return not not _instances[t] end}
_class = function(name, attr) local c = deep_copy(attr); _classes[c] = tostring(c)
c.name, c.__tostring, c.__call, c.new, c.create, c.extend, c.__index, c.mixins, c.__instances, c.__subclasses = name or c.name, baseMt.__tostring, baseMt.__call, bind(instantiate, true), bind(instantiate, false), extend, c, setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'})
c.subclasses = function(self, filter, ...) assert_call_from_class(self, 'subclasses(class)'); filter = filter or default_filter; local subclasses = {}; for class in pairs(_classes) do if class ~= baseMt and class:subclassOf(self) and filter(class,...) then subclasses[#subclasses + 1] = class end end; return subclasses end
c.instances = function(self, filter, ...) assert_call_from_class(self, 'instances(class)'); filter = filter or default_filter; local instances = {}; for instance in pairs(_instances) do if instance:instanceOf(self) and filter(instance, ...) then instances[#instances + 1] = instance end end; return instances end
c.subclassOf = function(self, superclass) assert_call_from_class(self, 'subclassOf(superclass)'); assert(class.isClass(superclass), 'Wrong argument given to method "subclassOf()". Expected a class.'); local super = self.super; while super do if super == superclass then return true end; super = super.super end; return false end
c.classOf = function(self, subclass) assert_call_from_class(self, 'classOf(subclass)'); assert(class.isClass(subclass), 'Wrong argument given to method "classOf()". Expected a class.'); return subclass:subclassOf(self) end
c.instanceOf = function(self, fromclass) assert_call_from_instance(self, 'instanceOf(class)'); assert(class.isClass(fromclass), 'Wrong argument given to method "instanceOf()". Expected a class.'); return ((self.class == fromclass) or (self.class:subclassOf(fromclass))) end
c.cast = function(self, toclass) assert_call_from_instance(self, 'instanceOf(class)'); assert(class.isClass(toclass), 'Wrong argument given to method "cast()". Expected a class.'); setmetatable(self, toclass); self.class = toclass; return self end
c.with = function(self,...) assert_call_from_class(self, 'with(mixin)'); for _, mixin in ipairs({...}) do assert(self.mixins[mixin] ~= true, ('Attempted to include a mixin which was already included in %s'):format(tostring(self))); self.mixins[mixin] = true; deep_copy(mixin, self, 'function') end return self end
c.includes = function(self, mixin) assert_call_from_class(self,'includes(mixin)'); return not not (self.mixins[mixin] or (self.super and self.super:includes(mixin))) end
c.without = function(self, ...) assert_call_from_class(self, 'without(mixin)'); for _, mixin in ipairs({...}) do
assert(self.mixins[mixin] == true, ('Attempted to remove a mixin which is not included in %s'):format(tostring(self))); local classes = self:subclasses(); classes[#classes + 1] = self
for _, class in ipairs(classes) do for method_name, method in pairs(mixin) do if type(method) == 'function' then class[method_name] = nil end end end; self.mixins[mixin] = nil end; return self end; return setmetatable(c, baseMt) end
class._DESCRIPTION = '30 lines library for object orientation in Lua'; class._VERSION = '30log v1.2.0'; class._URL = 'http://github.com/Yonaba/30log'; class._LICENSE = 'MIT LICENSE <http://www.opensource.org/licenses/mit-license.php>'
return setmetatable(class,{__call = function(_,...) return _class(...) end })