Skip to content

Commit

Permalink
fix(render): support array return values from function components
Browse files Browse the repository at this point in the history
  • Loading branch information
maxpowa committed Aug 27, 2024
1 parent 8844491 commit 66cdf42
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 40 deletions.
10 changes: 3 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,16 @@ end)
- Plain text components (generated via `label`)
- Simple event API
- No need to register a separate event listener, just add a prop
-

### Coming Soon™

- Component shorthand similar to JSX (most likely going to adapt https://github.com/hishamhm/f-strings)
- Save/load event handler restoration
- Context API support (`useContext`)
- Context API support (`createContext`/`useContext`)

### Examples


### Credits






The core render loop/virtual dom is heavily inspired by [O!](https://github.com/zserge/o), with other hook implementations referenced from [Preact](https://github.com/preactjs/preact).
74 changes: 45 additions & 29 deletions lib/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ end

-- Explicit imports here prevent scope pollution
local createScopedHandler = require "__react__.lib.events".createScopedHandler
local enableHookContext = require "__react__.lib.hooks".enableHookContext
local disableHookContext = require "__react__.lib.hooks".disableHookContext
local enableHooks = require "__react__.lib.hooks".enableHooks
local disableHooks = require "__react__.lib.hooks".disableHooks

-- Helper functions since I'm a filthy typescript user and I can't live without certain niceties
local function merge(tbl1, tbl2)
Expand Down Expand Up @@ -53,6 +53,36 @@ local function compareNodeProp(node, k, v)
return node[k] ~= v
end

-- immediate mode rendering
local function renderImmediate(vnode, index, parent, storage, recurse)
local node = parent.children[index]
local createdNewNode = false
if (not node) or ((node and node.type) ~= vnode.type) then
node = addElementToParent(vnode, parent, index)
createdNewNode = true
if not node then
error("Failed to add element to GUI: " .. serpent.line(vnode))
end
end

for k, v in pairs(vnode.props) do
if (k:find("on_gui_") == 1 and type(v) == "function") then
-- add event handler cleanup functions to the storage table
createScopedHandler(defines.events[k], node, v, node.index)
elseif (k == "ref") then
v.current = node
elseif ((not createdNewNode) and compareNodeProp(node, k, v)) then
-- If we created a new node, we don't need to update it
node[k] = v
end
end

-- setup children storage and render (needed even if there are no children, since we can't put arbitrary data on the element itself)
storage.children = storage.children or {}
storage.children[node.index] = storage.children[node.index] or {}
recurse(vnode.children, node, storage.children[node.index])
end

--- Renders a virtual element tree to a parent element
---
--- @param vlist table list of virtual elements, created by React.createElement
Expand All @@ -74,7 +104,9 @@ local function render(vlist, parent, storage)
-- clear hook storage global
storage.hooks = {}

-- render the virtual element list
local ids = {}
local extraNodeCount = 0
for i, vnode in ipairs(vlist) do
local forceUpdate = function() return render(vlist, parent, storage) end

Expand All @@ -86,37 +118,20 @@ local function render(vlist, parent, storage)
end

local index = 1
enableHookContext(hs[k] or {}, index, forceUpdate)
enableHooks(hs[k] or {}, index, forceUpdate)
vnode = vnode.type(vnode.props, vnode.children, forceUpdate)
storage.hooks[k] = disableHookContext()
end

local node = parent.children[i]
local createdNewNode = false
if (not node) or ((node and node.type) ~= vnode.type) then
node = addElementToParent(vnode, parent, i)
createdNewNode = true
if not node then
error("Failed to add element to GUI: " .. serpent.line(vnode))
end
storage.hooks[k] = disableHooks()
end

for k, v in pairs(vnode.props) do
if (k:find("on_gui_") == 1 and type(v) == "function") then
-- add event handler cleanup functions to the storage table
createScopedHandler(defines.events[k], node, v, node.index)
elseif (k == "ref") then
v.current = node
elseif ((not createdNewNode) and compareNodeProp(node, k, v)) then
-- If we created a new node, we don't need to update it
node[k] = v
if is_array(vnode) then
for _, v in ipairs(vnode) do
-- we need to keep the index consistent, so we add extra nodes to the count
extraNodeCount = extraNodeCount + 1
renderImmediate(v, i+extraNodeCount, parent, storage, render)
end
else
renderImmediate(vnode, i+extraNodeCount, parent, storage, render)
end

-- setup children storage and render (needed even if there are no children, since we can't put arbitrary data on the element itself)
storage.children = storage.children or {}
storage.children[node.index] = storage.children[node.index] or {}
render(vnode.children, node, storage.children[node.index])
end

-- Reconciliation
Expand Down Expand Up @@ -148,7 +163,8 @@ local function render(vlist, parent, storage)

-- remove extra elements
while true do
child = parent.children[#vlist + 1]
-- only remove elements that are not part of the virtual element list (including extra nodes added for fragments)
child = parent.children[#vlist + extraNodeCount + 1]
if child then
child.destroy()
render({}, parent, storage)
Expand Down
8 changes: 4 additions & 4 deletions lib/hooks.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ end
local hooks
local index = nil
local forceUpdate
local function enableHookContext(h, i, fu)
local function enableHooks(h, i, fu)
hooks = h
index = i
forceUpdate = fu
end
local function disableHookContext()
local function disableHooks()
index = nil
return hooks
end
Expand Down Expand Up @@ -130,8 +130,8 @@ end

return {
-- Core util for hooks
enableHookContext = enableHookContext,
disableHookContext = disableHookContext,
enableHooks = enableHooks,
disableHooks = disableHooks,

-- Hooks
useReducer = useReducer,
Expand Down

0 comments on commit 66cdf42

Please sign in to comment.