Hotswapping+new index

This commit is contained in:
qfx 2020-01-29 22:19:21 +02:00
parent 004e6cb34a
commit 07dbbb06e9
5 changed files with 211 additions and 139 deletions

5
conf.lua Normal file
View File

@ -0,0 +1,5 @@
return {
HOTSWAP = true,
AUTO_RUN = true,
DEBUG = true,
}

View File

@ -1,4 +1,5 @@
--[[ Element superclass ]]
--[[ Love is currently a hard dependency, although not in many places ]]
local path = string.sub(..., 1, string.len(...) - string.len(".core.element"))
local helium = require(path .. ".dummy")
@ -58,11 +59,8 @@ setmetatable(element,{
local func, loader = ...
if type(func)=='function' then
self = setmetatable({}, element)
self.renderer = ...
self.classless = true
elseif type(cls)=='table' then
self = setmetatable({}, cls)
self.classless = false
self.parentFunc = func
self.classless = true
end
if loader then
@ -105,32 +103,79 @@ function element:new()
inserted = false
}
self.view = {
self.baseState = {}
self.baseView = {
x = 0,
y = 0,
w = 10,
h = 10,
}
self.view = setmetatable({},{
__index = function(t, index)
return self.baseView[index]
end,
__newindex = function(t, index, val)
if self.baseView[index] ~= val then
self.baseView[index] = val
self.context:bubbleUpdate()
self:updateInputCtx()
end
end
})
--Context makes sure element internals don't have to worry about absolute coordinates
self.inputContext = helium.input.newContext(self)
self.context = context.new(self)
if self.classless then
self.classlessState = {}
self.classlessData = {}
self.classlessFuncs = {}
--Allows manipulation of the arbitrary state
self.classlessFuncs.getState = function ()
return self.state
end
--Allows manipulation of rendering width height, relative X and relative Y
self.classlessFuncs.getView = function ()
self.settings.restrictView = true
return self.view
end
end
end
function element:updateInputCtx()
self.inputContext:update()
if self.settings.canvasW then
if self.settings.canvasW < self.view.w or self.settings.canvasH < self.view.h then
self.settings.canvasW = self.view.w*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)
end
self.quad = love.graphics.newQuad(0, 0, self.view.w, self.view.h, self.settings.canvasW, self.settings.canvasH)
end
end
--Hotswapping code
function element:reLoader(newFunc)
self.renderer = newFunc
self.inputContext:set()
self.inputContext:destroy()
self.parentFunc = newFunc
self.renderer = self.parentFunc(self.parameters,self.state,self.view)
self.context:bubbleUpdate()
self.inputContext:unset()
end
--Called once dimensions are validated
function element:setup()
self.state =
setmetatable(
{},
{
self.state = setmetatable({},{
__index = function(t, index)
return self.baseState[index]
end,
@ -140,15 +185,11 @@ function element:setup()
self.context:bubbleUpdate()
end
end
}
)
})
self.parameters =
setmetatable(
{},
{
self.parameters = setmetatable({},{
__index = function(t, index)
return self.baseParams[index] or nil
return self.baseParams[index]
end,
__newindex = function(t, index, val)
if self.baseParams[index] ~= val then
@ -156,68 +197,32 @@ function element:setup()
self.context:bubbleUpdate()
end
end
}
)
})
self.canvas = love.graphics.newCanvas(self.view.w, self.view.h)
self.quad = love.graphics.newQuad(0, 0, self.view.w, self.view.h, self.view.w, self.view.h)
self.settings.canvasW = self.view.w*1.25
self.settings.canvasH = self.view.h*1.25
--Context makes sure element internals don't have to worry about absolute coordinates
self.inputContext = helium.input.newContext(self)
self.context = context.new(self)
self.canvas = love.graphics.newCanvas(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.renderer = self.parentFunc(self.parameters,self.state,self.view)
self.inputContext:unset()
end
self.settings.isSetup = true
--Classless rendering
if self.classless then
self.classlessData.loadEffect = function (func)
if self.classlessData.loaded and self.classlessData.loadCaptured then
return self.classlessData.loadCaptured
elseif not self.classlessData.loaded then
self.classlessData.loadCaptured = func()
self.classlessData.loaded = true
if self.classlessData.loadCaptured then
return self.classlessData.loadCaptured
end
end
end
---@param initial any
---@return any
---@return function
self.classlessData.useState = function (initial)
self.settings.indice = self.settings.indice + 1
local indice = self.settings.indice
if self.classlessState[indice] and self.classlessState[indice].state then
return self.classlessState[indice].state, self.classlessState[indice].setState
else
self.classlessState[indice] = {}
self.classlessState[indice].state = initial
self.classlessState[indice].setState = function(set)
self.classlessState[indice].state = set
self.context:bubbleUpdate()
end
self.classlessState[indice].getState = function()
return self.classlessState[indice].state
end
return self.classlessState[indice].state, self.classlessState[indice].setState, self.classlessState[indice].getState
end
end
end
end
function element:classlessRender()
self.inputContext:set()
self.settings.indice = 0
if type(self.renderer)=='function' then
local denv = getfenv(self.renderer)
denv['useState'] = self.classlessData.useState
denv['loadEffect'] = self.classlessData.loadEffect
local status,err = pcall(self.renderer,self.parameters, self.view.w, self.view.h)
self.inputContext:set()
if type(self.renderer)=='function' then
local status, err = pcall(self.renderer)
if not status then
love.graphics.setColor(1,0,0)
@ -226,15 +231,14 @@ function element:classlessRender()
love.graphics.printf("Error: "..err,0,0,self.view.w)
end
denv['useState'] = nil
denv['loadEffect'] = nil
elseif type(self.renderer)=='string' then
love.graphics.setColor(1,0,0)
love.graphics.rectangle('line',0,0,self.view.w,self.view.h)
love.graphics.setColor(1,1,1)
love.graphics.printf("Error: "..self.renderer,0,0,self.view.w)
love.graphics.setColor(1,0,0)
love.graphics.rectangle('line',0,0,self.view.w,self.view.h)
love.graphics.setColor(1,1,1)
love.graphics.printf("Error: "..self.renderer,0,0,self.view.w)
end
self.inputContext:unset()
end
function element:renderWrapper()
@ -286,10 +290,12 @@ end
---@param w number
---@param h number
function element:draw(params, x, y, w, h)
self.view.x = x or self.view.x
self.view.y = y or self.view.y
self.view.w = w or self.view.w
self.view.h = h or self.view.h
if not self.view.lock then
self.view.x = x or self.view.x
self.view.y = y or self.view.y
self.view.w = w or self.view.w
self.view.h = h or self.view.h
end
if params then
if type(params)=='table' and self.baseParams then

View File

@ -36,7 +36,7 @@ local activeContext
]]
function input.newContext(element)
local ctx = setmetatable({view = element.view, subs = {}}, context)
local ctx = setmetatable({elem = element, subs = {}}, context)
return ctx
end
@ -44,16 +44,22 @@ end
function context:set()
if activeContext then
self.parentCtx = activeContext
self.absX = self.parentCtx.absX + self.view.x
self.absY = self.parentCtx.absY + self.view.y
self.absX = self.parentCtx.absX + self.elem.view.x
self.absY = self.parentCtx.absY + self.elem.view.y
activeContext = self
else
self.absX = self.view.x
self.absY = self.view.y
self.absX = self.elem.view.x
self.absY = self.elem.view.y
activeContext = self
end
end
function context:update()
for i, sub in ipairs(self.subs) do
sub:contextUpdate(self.absX,self.absY)
end
end
function context:unset()
if self.parentCtx then
activeContext = self.parentCtx
@ -64,7 +70,7 @@ end
function context:destroy()
for i, e in ipairs(self.subs) do
self.subs:destroy()
e:destroy()
end
end
@ -84,10 +90,14 @@ function subscription.create(x, y, w, h, eventType, callback, doff)
y = activeContext.absY + y,
w = w,
h = h,
ix = x,
iy = y,
eventType = eventType,
active = doff or true,
callback = callback
},subscription)
activeContext.subs[#activeContext.subs+1] = sub
else
sub = setmetatable({
x = x,
@ -118,10 +128,15 @@ function subscription:on()
end
function subscription:destroy()
self.destroy = true
self.destroyStat = true
self.active = false
end
function subscription:contextUpdate(absX, absY)
self.x = absX + self.ix
self.y = absY + self.iy
end
function subscription:update(x, y, w, h)
self.x = x or self.x
self.y = y or self.y
@ -189,12 +204,25 @@ function input.eventHandlers.mousereleased(x, y, btn)
end
end
end
end
if input.subscriptions.dragged then
for index, sub in ipairs(input.subscriptions.dragged) do
if sub.currentEvent then
sub.currentEvent = false
captured = true
if sub.cleanUp then
sub.cleanUp(x, y)
end
end
end
end
return captured
end
function input.eventHandlers.mousepressed(x, y, btn)
local captured = false
if input.subscriptions.mousepressed then
@ -231,6 +259,18 @@ function input.eventHandlers.mousepressed(x, y, btn)
end
end
if input.subscriptions.dragged then
for index, sub in ipairs(input.subscriptions.dragged) do
local succ = sub:checkInside(x, y)
if succ and sub.active then
sub.currentEvent = true
captured = true
end
end
end
return captured
end
@ -264,7 +304,7 @@ function input.eventHandlers.keyreleased(btn)
return captured
end
function input.eventHandlers.mousemoved(x, y)
function input.eventHandlers.mousemoved(x, y, dx, dy)
local captured = false
if input.subscriptions.hover then
@ -285,6 +325,22 @@ function input.eventHandlers.mousemoved(x, y)
end
end
if input.subscriptions.dragged then
for index, sub in ipairs(input.subscriptions.dragged) do
if sub.active and sub.currentEvent then
if not sub.cleanUp then
sub.cleanUp = sub:emit(x, y, dx, dy)
else
sub:emit(x, y, dx, dy)
end
sub.currentEvent = true
captured = true
end
end
end
return captured
end

View File

@ -5,10 +5,11 @@
----------------------------------------------------]]
local path = ...
local helium = require(path..".dummy")
helium.conf = require(path..".conf")
helium.utils = require(path..".utils")
helium.element = require(path..".core.element")
helium.input = require(path..".core.input")
helium.debugLoader = require(path..".debugLoader")
helium.loader = require(path..".loader")
helium.elementBuffer = {}
function helium.render()
@ -17,7 +18,11 @@ function helium.render()
end
end
function helium.update()
function helium.update(dt)
if helium.conf.HOTSWAP then
helium.loader.update(dt)
end
local remove = false
for i, e in ipairs(helium.elementBuffer) do
@ -60,61 +65,61 @@ end
end
end
]]
function love.run()
if helium.conf.AUTO_RUN then
function love.run()
if love.math then
love.math.setRandomSeed(os.time())
end
if love.math then
love.math.setRandomSeed(os.time())
end
if love.load then love.load(arg) end
if love.load then love.load(arg) end
-- We don't want the first frame's dt to include time taken by love.load.
if love.timer then love.timer.step() end
-- We don't want the first frame's dt to include time taken by love.load.
if love.timer then love.timer.step() end
local dt = 0
local dt = 0
-- Main loop time.
while true do
-- Process events.
if love.event then
love.event.pump()
for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then
if not love.quit or not love.quit() then
return a
-- Main loop time.
while true do
-- Process events.
if love.event then
love.event.pump()
for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then
if not love.quit or not love.quit() then
return a
end
end
if not(helium.input.eventHandlers[name]) or not(helium.input.eventHandlers[name](a, b, c, d, e, f)) then
love.handlers[name](a, b, c, d, e, f)
end
end
if not(helium.input.eventHandlers[name]) or not(helium.input.eventHandlers[name](a, b, c, d, e, f)) then
love.handlers[name](a, b, c, d, e, f)
end
end
-- Update dt, as we'll be passing it to update
if love.timer then
love.timer.step()
dt = love.timer.getDelta()
end
-- Call update and draw
if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
helium.update(dt)
if love.graphics and love.graphics.isActive() then
love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.origin()
if love.draw then love.draw() end
helium.render()
love.graphics.present()
end
if love.timer then love.timer.sleep(0.00001) end
end
-- Update dt, as we'll be passing it to update
if love.timer then
love.timer.step()
dt = love.timer.getDelta()
end
-- Call update and draw
if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
helium.update(dt)
helium.debugLoader.update(dt)
if love.graphics and love.graphics.isActive() then
love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.origin()
if love.draw then love.draw() end
helium.render()
love.graphics.present()
end
if love.timer then love.timer.sleep(0.00001) end
end
end
return helium

View File

@ -1,5 +1,5 @@
local path = string.sub(..., 1, string.len(...) - string.len(".debugLoader"))
local path = string.sub(..., 1, string.len(...) - string.len(".loader"))
local helium = require(path..'.dummy')
local elements = {}
local debugLoader = {}