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 ]] --[[ 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 path = string.sub(..., 1, string.len(...) - string.len(".core.element"))
local helium = require(path .. ".dummy") local helium = require(path .. ".dummy")
@ -58,11 +59,8 @@ setmetatable(element,{
local func, loader = ... local func, loader = ...
if type(func)=='function' then if type(func)=='function' then
self = setmetatable({}, element) self = setmetatable({}, element)
self.renderer = ... self.parentFunc = func
self.classless = true self.classless = true
elseif type(cls)=='table' then
self = setmetatable({}, cls)
self.classless = false
end end
if loader then if loader then
@ -105,32 +103,79 @@ function element:new()
inserted = false inserted = false
} }
self.view = { self.baseState = {}
self.baseView = {
x = 0, x = 0,
y = 0, y = 0,
w = 10, w = 10,
h = 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 if self.classless then
self.classlessState = {} self.classlessFuncs = {}
self.classlessData = {}
--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
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 --Hotswapping code
function element:reLoader(newFunc) 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.context:bubbleUpdate()
self.inputContext:unset()
end end
--Called once dimensions are validated --Called once dimensions are validated
function element:setup() function element:setup()
self.state = self.state = setmetatable({},{
setmetatable(
{},
{
__index = function(t, index) __index = function(t, index)
return self.baseState[index] return self.baseState[index]
end, end,
@ -140,15 +185,11 @@ function element:setup()
self.context:bubbleUpdate() self.context:bubbleUpdate()
end end
end end
} })
)
self.parameters = self.parameters = setmetatable({},{
setmetatable(
{},
{
__index = function(t, index) __index = function(t, index)
return self.baseParams[index] or nil return self.baseParams[index]
end, end,
__newindex = function(t, index, val) __newindex = function(t, index, val)
if self.baseParams[index] ~= val then if self.baseParams[index] ~= val then
@ -156,68 +197,32 @@ function element:setup()
self.context:bubbleUpdate() self.context:bubbleUpdate()
end end
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.canvas = love.graphics.newCanvas(self.view.w*1.25, self.view.h*1.25)
self.inputContext = helium.input.newContext(self)
self.context = context.new(self) 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 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 end
function element:classlessRender() 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 if not status then
love.graphics.setColor(1,0,0) love.graphics.setColor(1,0,0)
@ -226,15 +231,14 @@ function element:classlessRender()
love.graphics.printf("Error: "..err,0,0,self.view.w) love.graphics.printf("Error: "..err,0,0,self.view.w)
end end
denv['useState'] = nil
denv['loadEffect'] = nil
elseif type(self.renderer)=='string' then elseif type(self.renderer)=='string' then
love.graphics.setColor(1,0,0) love.graphics.setColor(1,0,0)
love.graphics.rectangle('line',0,0,self.view.w,self.view.h) love.graphics.rectangle('line',0,0,self.view.w,self.view.h)
love.graphics.setColor(1,1,1) love.graphics.setColor(1,1,1)
love.graphics.printf("Error: "..self.renderer,0,0,self.view.w) love.graphics.printf("Error: "..self.renderer,0,0,self.view.w)
end end
self.inputContext:unset() self.inputContext:unset()
end end
function element:renderWrapper() function element:renderWrapper()
@ -286,10 +290,12 @@ end
---@param w number ---@param w number
---@param h number ---@param h number
function element:draw(params, x, y, w, h) function element:draw(params, x, y, w, h)
self.view.x = x or self.view.x if not self.view.lock then
self.view.y = y or self.view.y self.view.x = x or self.view.x
self.view.w = w or self.view.w self.view.y = y or self.view.y
self.view.h = h or self.view.h self.view.w = w or self.view.w
self.view.h = h or self.view.h
end
if params then if params then
if type(params)=='table' and self.baseParams then if type(params)=='table' and self.baseParams then

View File

@ -36,7 +36,7 @@ local activeContext
]] ]]
function input.newContext(element) function input.newContext(element)
local ctx = setmetatable({view = element.view, subs = {}}, context) local ctx = setmetatable({elem = element, subs = {}}, context)
return ctx return ctx
end end
@ -44,16 +44,22 @@ end
function context:set() function context:set()
if activeContext then if activeContext then
self.parentCtx = activeContext self.parentCtx = activeContext
self.absX = self.parentCtx.absX + self.view.x self.absX = self.parentCtx.absX + self.elem.view.x
self.absY = self.parentCtx.absY + self.view.y self.absY = self.parentCtx.absY + self.elem.view.y
activeContext = self activeContext = self
else else
self.absX = self.view.x self.absX = self.elem.view.x
self.absY = self.view.y self.absY = self.elem.view.y
activeContext = self activeContext = self
end end
end end
function context:update()
for i, sub in ipairs(self.subs) do
sub:contextUpdate(self.absX,self.absY)
end
end
function context:unset() function context:unset()
if self.parentCtx then if self.parentCtx then
activeContext = self.parentCtx activeContext = self.parentCtx
@ -64,7 +70,7 @@ end
function context:destroy() function context:destroy()
for i, e in ipairs(self.subs) do for i, e in ipairs(self.subs) do
self.subs:destroy() e:destroy()
end end
end end
@ -84,10 +90,14 @@ function subscription.create(x, y, w, h, eventType, callback, doff)
y = activeContext.absY + y, y = activeContext.absY + y,
w = w, w = w,
h = h, h = h,
ix = x,
iy = y,
eventType = eventType, eventType = eventType,
active = doff or true, active = doff or true,
callback = callback callback = callback
},subscription) },subscription)
activeContext.subs[#activeContext.subs+1] = sub
else else
sub = setmetatable({ sub = setmetatable({
x = x, x = x,
@ -118,10 +128,15 @@ function subscription:on()
end end
function subscription:destroy() function subscription:destroy()
self.destroy = true self.destroyStat = true
self.active = false self.active = false
end end
function subscription:contextUpdate(absX, absY)
self.x = absX + self.ix
self.y = absY + self.iy
end
function subscription:update(x, y, w, h) function subscription:update(x, y, w, h)
self.x = x or self.x self.x = x or self.x
self.y = y or self.y self.y = y or self.y
@ -191,10 +206,23 @@ 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 return captured
end end
function input.eventHandlers.mousepressed(x, y, btn) function input.eventHandlers.mousepressed(x, y, btn)
local captured = false local captured = false
if input.subscriptions.mousepressed then if input.subscriptions.mousepressed then
@ -232,6 +260,18 @@ function input.eventHandlers.mousepressed(x, y, btn)
end end
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 return captured
end end
@ -264,7 +304,7 @@ function input.eventHandlers.keyreleased(btn)
return captured return captured
end end
function input.eventHandlers.mousemoved(x, y) function input.eventHandlers.mousemoved(x, y, dx, dy)
local captured = false local captured = false
if input.subscriptions.hover then if input.subscriptions.hover then
@ -285,6 +325,22 @@ function input.eventHandlers.mousemoved(x, y)
end end
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 return captured
end end

View File

@ -5,10 +5,11 @@
----------------------------------------------------]] ----------------------------------------------------]]
local path = ... local path = ...
local helium = require(path..".dummy") local helium = require(path..".dummy")
helium.conf = require(path..".conf")
helium.utils = require(path..".utils") helium.utils = require(path..".utils")
helium.element = require(path..".core.element") helium.element = require(path..".core.element")
helium.input = require(path..".core.input") helium.input = require(path..".core.input")
helium.debugLoader = require(path..".debugLoader") helium.loader = require(path..".loader")
helium.elementBuffer = {} helium.elementBuffer = {}
function helium.render() function helium.render()
@ -17,7 +18,11 @@ function helium.render()
end end
end end
function helium.update() function helium.update(dt)
if helium.conf.HOTSWAP then
helium.loader.update(dt)
end
local remove = false local remove = false
for i, e in ipairs(helium.elementBuffer) do for i, e in ipairs(helium.elementBuffer) do
@ -60,61 +65,61 @@ end
end end
end end
]] ]]
function love.run() if helium.conf.AUTO_RUN then
function love.run()
if love.math then if love.math then
love.math.setRandomSeed(os.time()) love.math.setRandomSeed(os.time())
end 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. -- We don't want the first frame's dt to include time taken by love.load.
if love.timer then love.timer.step() end if love.timer then love.timer.step() end
local dt = 0 local dt = 0
-- Main loop time. -- Main loop time.
while true do while true do
-- Process events. -- Process events.
if love.event then if love.event then
love.event.pump() love.event.pump()
for name, a,b,c,d,e,f in love.event.poll() do for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then if name == "quit" then
if not love.quit or not love.quit() then if not love.quit or not love.quit() then
return a 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
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 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 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
end end
return helium 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 helium = require(path..'.dummy')
local elements = {} local elements = {}
local debugLoader = {} local debugLoader = {}