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,8 +231,6 @@ 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)
@ -235,6 +238,7 @@ function element:classlessRender()
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)
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
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,6 +65,7 @@ end
end end
end end
]] ]]
if helium.conf.AUTO_RUN then
function love.run() function love.run()
if love.math then if love.math then
@ -101,20 +107,19 @@ function love.run()
-- Call update and draw -- Call update and draw
if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
helium.update(dt) helium.update(dt)
helium.debugLoader.update(dt)
if love.graphics and love.graphics.isActive() then if love.graphics and love.graphics.isActive() then
love.graphics.clear(love.graphics.getBackgroundColor()) love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.origin() love.graphics.origin()
if love.draw then love.draw() end if love.draw then love.draw() end
helium.render() helium.render()
love.graphics.present() love.graphics.present()
end end
if love.timer then love.timer.sleep(0.00001) end if love.timer then love.timer.sleep(0.00001) end
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 = {}