Refactor + prevent memory leaks

This commit is contained in:
qfx 2020-02-11 20:07:18 +02:00
parent 31317ad926
commit 5d707925c6
4 changed files with 123 additions and 102 deletions

View File

@ -11,7 +11,7 @@ local activeContext
---@param elem element ---@param elem element
function context.new(elem) function context.new(elem)
local ctx = setmetatable({view = elem.view, element = elem}, context) local ctx = setmetatable({view = elem.view, element = elem, childrenContexts={}}, context)
return ctx return ctx
end end
@ -27,7 +27,10 @@ end
function context:set() function context:set()
if activeContext then if activeContext then
if not self.parentCtx then
self.parentCtx = activeContext self.parentCtx = activeContext
activeContext.childrenContexts[#activeContext.childrenContexts] = self
end
self.absX = self.parentCtx.absX + self.view.x self.absX = self.parentCtx.absX + self.view.x
self.absY = self.parentCtx.absY + self.view.y self.absY = self.parentCtx.absY + self.view.y
@ -49,10 +52,18 @@ function context:unset()
end end
end end
function context:destroy()
self.elem:undraw()
for i=1,#self.childrenContexts do
self.childrenContexts[i]:destroy()
end
end
---@class element ---@class element
local element = {} local element = {}
element.__index = element element.__index = element
local type,pcall = type,pcall
setmetatable(element,{ setmetatable(element,{
__call = function(cls, ...) __call = function(cls, ...)
local self local self
@ -60,7 +71,6 @@ setmetatable(element,{
if type(func)=='function' then if type(func)=='function' then
self = setmetatable({}, element) self = setmetatable({}, element)
self.parentFunc = func self.parentFunc = func
self.classless = true
end end
if loader then if loader then
@ -79,11 +89,6 @@ setmetatable(element,{
--Dummy functions --Dummy functions
function element:renderer() print('no renderer') end function element:renderer() print('no renderer') end
function element:updater() end
function element:constructor() end
--Control functions --Control functions
--The new function that should be used for element creation --The new function that should be used for element creation
function element:new() function element:new()
@ -92,7 +97,7 @@ function element:new()
self.parameters = {} self.parameters = {}
--The element canvas --The element canvas
self.canvas = nil --self.canvas = nil
--Internal settings --Internal settings
self.settings = { self.settings = {
@ -129,7 +134,6 @@ function element:new()
self.inputContext = helium.input.newContext(self) self.inputContext = helium.input.newContext(self)
self.context = context.new(self) self.context = context.new(self)
if self.classless then
self.classlessFuncs = {} self.classlessFuncs = {}
--Allows manipulation of the arbitrary state --Allows manipulation of the arbitrary state
@ -143,7 +147,6 @@ function element:new()
return self.view return self.view
end end
end end
end
function element:updateInputCtx() function element:updateInputCtx()
self.inputContext:update() self.inputContext:update()
@ -173,6 +176,7 @@ function element:reLoader(newFunc)
self.inputContext:unset() self.inputContext:unset()
end end
local newCanvas,newQuad = love.graphics.newCanvas,love.graphics.newQuad
--Called once dimensions are validated --Called once dimensions are validated
function element:setup() function element:setup()
self.state = setmetatable({},{ self.state = setmetatable({},{
@ -203,21 +207,17 @@ function element:setup()
self.settings.canvasW = self.view.w*1.25 self.settings.canvasW = self.view.w*1.25
self.settings.canvasH = self.view.h*1.25 self.settings.canvasH = self.view.h*1.25
self.canvas = love.graphics.newCanvas(self.view.w*1.25, self.view.h*1.25) self.canvas = newCanvas(self.view.w*1.25, self.view.h*1.25)
self.quad = newQuad(0, 0, self.view.w, self.view.h, self.view.w*1.25, self.view.h*1.25)
self.quad = love.graphics.newQuad(0, 0, self.view.w, self.view.h, self.view.w*1.25, self.view.h*1.25)
if self.classless then
self.inputContext:set() self.inputContext:set()
self.renderer = self.parentFunc(self.parameters,self.state,self.view) self.renderer = self.parentFunc(self.parameters,self.state,self.view)
self.inputContext:unset() self.inputContext:unset()
end
self.settings.isSetup = true self.settings.isSetup = true
end end
local setColor,rectangle,setFont,printf = love.graphics.setColor,love.graphics.rectangle,love.graphics.setFont,love.graphics.printf
function element:classlessRender() function element:classlessRender()
self.inputContext:set() self.inputContext:set()
@ -225,38 +225,38 @@ function element:classlessRender()
local status, err = pcall(self.renderer) local status, err = pcall(self.renderer)
if not status then if not status then
love.graphics.setColor(1,0,0) setColor(1,0,0)
love.graphics.rectangle('line',0,0,self.view.w,self.view.h) rectangle('line',0,0,self.view.w,self.view.h)
love.graphics.setColor(1,1,1) setColor(1,1,1)
love.graphics.printf("Error: "..err,0,0,self.view.w) printf("Error: "..err,0,0,self.view.w)
end end
elseif type(self.renderer)=='string' then elseif type(self.renderer)=='string' then
love.graphics.setColor(1,0,0) setColor(1,0,0)
love.graphics.rectangle('line',0,0,self.view.w,self.view.h) rectangle('line',0,0,self.view.w,self.view.h)
love.graphics.setColor(1,1,1) setColor(1,1,1)
love.graphics.printf("Error: "..self.renderer,0,0,self.view.w) printf("Error: "..self.renderer,0,0,self.view.w)
end end
self.inputContext:unset() self.inputContext:unset()
end end
local getCanvas,setCanvas,clear = love.graphics.getCanvas,love.graphics.setCanvas,love.graphics.clear
function element:renderWrapper() function element:renderWrapper()
local cnvs = love.graphics.getCanvas() local cnvs = getCanvas()
love.graphics.setCanvas({self.canvas, stencil = true}) setCanvas({self.canvas, stencil = true})
love.graphics.clear() clear()
if self.classless and self.parameters then if self.parameters then
self:classlessRender() self:classlessRender()
self.settings.pendingUpdate = false self.settings.pendingUpdate = false
else
self:renderer()
end end
love.graphics.setCanvas(cnvs) setCanvas(cnvs)
end end
local draw = love.graphics.draw
function element:externalRender() function element:externalRender()
self.context:set() self.context:set()
@ -265,8 +265,8 @@ function element:externalRender()
self.settings.needsRendering = false self.settings.needsRendering = false
end end
love.graphics.setColor(1,1,1) setColor(1,1,1)
love.graphics.draw(self.canvas, self.quad, self.view.x, self.view.y) draw(self.canvas, self.quad, self.view.x, self.view.y)
self.context:unset() self.context:unset()
end end
@ -282,6 +282,7 @@ function element:externalUpdate()
end end
end end
local insert = table.insert
--External functions --External functions
--Acts as the entrypoint for beginning rendering --Acts as the entrypoint for beginning rendering
---@param params any ---@param params any
@ -291,10 +292,14 @@ end
---@param h number ---@param h number
function element:draw(params, x, y, w, h) function element:draw(params, x, y, w, h)
if not self.view.lock then if not self.view.lock then
self.view.x = x or self.view.x --self.view.x = x or self.view.x
self.view.y = y or self.view.y --self.view.y = y or self.view.y
self.view.w = w or self.view.w --self.view.w = w or self.view.w
self.view.h = h or self.view.h --self.view.h = h or self.view.h
if x then self.view.x = x end
if y then self.view.y = y end
if w then self.view.w = w end
if h then self.view.h = h end
end end
if params then if params then
@ -316,13 +321,15 @@ function element:draw(params, x, y, w, h)
self:externalRender() self:externalRender()
elseif not self.settings.inserted then elseif not self.settings.inserted then
self.settings.inserted = true self.settings.inserted = true
table.insert(helium.elementBuffer, self) insert(helium.elementBuffer, self)
end end
end end
function element:undraw() function element:undraw()
self.settings.remove = true self.settings.remove = true
self.settings.isSetup = false self.settings.isSetup = false
self.inputContext:set()
self.inputContext:destroy()
end end
return element return element

View File

@ -36,14 +36,17 @@ local activeContext
]] ]]
function input.newContext(element) function input.newContext(element)
local ctx = setmetatable({elem = element, subs = {}}, context) local ctx = setmetatable({elem = element, subs = {}, childContexts={}}, context)
return ctx return ctx
end end
function context:set() function context:set()
if activeContext then if activeContext then
if not self.parentCtx then
activeContext.childContexts[#activeContext.childContexts+1] = self
self.parentCtx = activeContext self.parentCtx = activeContext
end
self.absX = self.parentCtx.absX + self.elem.view.x self.absX = self.parentCtx.absX + self.elem.view.x
self.absY = self.parentCtx.absY + self.elem.view.y self.absY = self.parentCtx.absY + self.elem.view.y
activeContext = self activeContext = self
@ -72,6 +75,9 @@ function context:destroy()
for i, e in ipairs(self.subs) do for i, e in ipairs(self.subs) do
e:destroy() e:destroy()
end end
for i, e in ipairs(self.childContexts) do
e:destroy()
end
end end
---@param x number ---@param x number
@ -149,33 +155,23 @@ function subscription:emit(...)
end end
function subscription:checkInside(x, y) function subscription:checkInside(x, y)
if x>self.x and x<self.x+self.w and y>self.y and y<self.y+self.h then return x>self.x and x<self.x+self.w and y>self.y and y<self.y+self.h
return true
else
return false
end
end end
function subscription:checkOutside(x, y) function subscription:checkOutside(x, y)
if x>self.x and x<self.x+self.w and y>self.y and y<self.y+self.h then return not (x>self.x and x<self.x+self.w and y>self.y and y<self.y+self.h)
return false
else
return true
end
end end
input.subscribe = function(...) input.subscribe = subscription.create
return subscription.create(...)
end
function input.eventHandlers.mousereleased(x, y, btn) function input.eventHandlers.mousereleased(x, y, btn)
local captured = false local captured = false
if input.subscriptions.mousereleased then if input.subscriptions.mousereleased then
for index, sub in ipairs(input.subscriptions.mousereleased) do for index, sub in ipairs(input.subscriptions.mousereleased) do
local succ = sub:checkInside(x, y) --local succ = sub:checkInside(x, y)
if succ and sub.active then if sub.active and sub:checkInside(x, y) then -- succ and sub:check
sub:emit(x, y, btn) sub:emit(x, y, btn)
captured = true captured = true
end end
@ -184,9 +180,9 @@ function input.eventHandlers.mousereleased(x, y, btn)
end end
if input.subscriptions.mousereleased_outside then if input.subscriptions.mousereleased_outside then
for index, sub in ipairs(input.subscriptions.mousereleased_outside) do for index, sub in ipairs(input.subscriptions.mousereleased_outside) do
local succ = sub:checkOutside(x, y) --local succ = sub:checkOutside(x, y)
if succ and sub.active then if sub.active and sub:checkOutside(x, y) then -- succ and sub.active then
sub:emit(x, y, btn) sub:emit(x, y, btn)
captured = true captured = true
end end
@ -227,9 +223,9 @@ function input.eventHandlers.mousepressed(x, y, btn)
local captured = false local captured = false
if input.subscriptions.mousepressed then if input.subscriptions.mousepressed then
for index, sub in ipairs(input.subscriptions.mousepressed) do for index, sub in ipairs(input.subscriptions.mousepressed) do
local succ = sub:checkInside(x, y) --local succ = sub:checkInside(x, y)
if succ and sub.active then if sub.active and sub:checkInside(x, y) then -- succ and sub:check
sub:emit(x, y, btn) sub:emit(x, y, btn)
captured = true captured = true
end end
@ -238,9 +234,9 @@ function input.eventHandlers.mousepressed(x, y, btn)
end end
if input.subscriptions.mousepressed_outside then if input.subscriptions.mousepressed_outside then
for index, sub in ipairs(input.subscriptions.mousepressed_outside) do for index, sub in ipairs(input.subscriptions.mousepressed_outside) do
local succ = sub:checkOutside(x, y) --local succ = sub:checkOutside(x, y)
if succ and sub.active then if sub.active and sub:checkOutside(x, y) then -- succ and sub.active then
sub:emit(x, y, btn) sub:emit(x, y, btn)
captured = true captured = true
end end
@ -262,9 +258,9 @@ function input.eventHandlers.mousepressed(x, y, btn)
if input.subscriptions.dragged then if input.subscriptions.dragged then
for index, sub in ipairs(input.subscriptions.dragged) do for index, sub in ipairs(input.subscriptions.dragged) do
local succ = sub:checkInside(x, y) --local succ = sub:checkInside(x, y)
if succ and sub.active then if sub.active and sub:checkInside(x, y) then -- succ and sub:check
sub.currentEvent = true sub.currentEvent = true
captured = true captured = true
end end
@ -279,7 +275,7 @@ function input.eventHandlers.keypressed(btn)
local captured = false local captured = false
if input.subscriptions.keypressed then if input.subscriptions.keypressed then
for index, sub in ipairs(input.subscriptions.keypressed) do for index, sub in ipairs(input.subscriptions.keypressed) do
if sub.active ==true then if sub.active then -- ==true then
sub:emit( btn) sub:emit( btn)
captured = true captured = true
end end
@ -311,11 +307,11 @@ function input.eventHandlers.mousemoved(x, y, dx, dy)
for index, sub in ipairs(input.subscriptions.hover) do for index, sub in ipairs(input.subscriptions.hover) do
local succ = sub:checkInside(x, y) local succ = sub:checkInside(x, y)
if succ and sub.active and not sub.currentEvent then if sub.active and not sub.currentEvent and succ then
sub.cleanUp = sub:emit(x, y) sub.cleanUp = sub:emit(x, y)
sub.currentEvent = true sub.currentEvent = true
captured = true captured = true
elseif sub.currentEvent and not sub:checkInside(x, y) then elseif sub.currentEvent and not succ then
sub.currentEvent = false sub.currentEvent = false
captured = true captured = true
if sub.cleanUp then if sub.cleanUp then
@ -334,7 +330,7 @@ function input.eventHandlers.mousemoved(x, y, dx, dy)
else else
sub:emit(x, y, dx, dy) sub:emit(x, y, dx, dy)
end end
sub.currentEvent = true --sub.currentEvent = true -- checked in the condition so must be true
captured = true captured = true
end end

View File

@ -5,23 +5,40 @@ local elements = {}
local debugLoader = {} local debugLoader = {}
--Return level: 1--string; 2--chunk; 3--return value; default: element factory --Return level: 1--string; 2--chunk; 3--return value; default: element factory
local function loader(path) local function loader(path)
local succ = true
--File string
local fileContents, err = love.filesystem.read(path) local fileContents, err = love.filesystem.read(path)
local lastLoaded, status, func, succ, ret
if fileContents then if fileContents==nil then
lastLoaded = love.filesystem.getInfo(path).modtime print('Error loading ',path,':',tostring(err),', will continue watching!')
status, func = pcall(loadstring,fileContents) succ = false
if not status then
print('Error compiling ',path,':',tostring(err),', will continue watching!')
else
succ, ret = pcall(func,path)
end end
local t, lastLoaded
if succ then
t = love.filesystem.getInfo(path)
lastLoaded = t['modtime']
end
--Chunk
local status, err
if succ then
status, err = pcall(loadstring,fileContents)
end
if status==false or status==nil then
print('Error compiling ',path,':',tostring(err),', will continue watching!')
succ = false
end
--Return values
local ret
if succ then
succ, ret = pcall(err,path)
if not succ then if not succ then
print('Error calling ',path,':',tostring(ret)) print('Error calling ',path,':',tostring(ret))
end end
else
print('Error loading ',path,':',tostring(err),', will continue watching!')
end end
return fileContents, err, ret, lastLoaded return fileContents, err, ret, lastLoaded
@ -60,6 +77,7 @@ function debugLoader.update(dt)
--If last save time differs then start reload sequence --If last save time differs then start reload sequence
local _, _, ret, lastLoaded = loader(elem[4]) local _, _, ret, lastLoaded = loader(elem[4])
local setfuncs = {} local setfuncs = {}
local reloader = function(setFunc) local reloader = function(setFunc)

View File

@ -4,25 +4,25 @@ function utils.ArrayRemove(t, fnKeep)
local j, n = 1, #t; local j, n = 1, #t;
for i=1,n do for i=1,n do
if (fnKeep(t, i, j)) then if fnKeep(t, i, j) then
-- Move i's kept value to j's position, if it's not already there. -- Move i's kept value to j's position, if it's not already there.
if (i ~= j) then if i ~= j then
t[j] = t[i]; t[j] = t[i];
t[i] = nil; --t[i] = nil;
end end
j = j + 1; -- Increment position of where we'll place the next kept value. j = j + 1; -- Increment position of where we'll place the next kept value.
else end --else
t[i] = nil; t[i] = nil; -- in both if cases you nil it sooooo
end --end
end end
return t; return t;
end end
function utils.tableMerge(t, bt) function utils.tableMerge(t, bt)
for i, e in pairs(t) do for k, v in pairs(t) do
if e ~= bt[i] then if v ~= bt[k] then
bt[i] = e bt[k] = v
end end
end end
end end