This is the Lua module for generating ProtoFlux UI elements on pages such as To String (ProtoFlux)
Please note, due to caching, updates made to this module will not immediately take effect on pages using it. If you need to see changes right away, use the editor view of a given page, which will update immediately.
Parameters
Name
Name of this ProtoFlux node. This should be the same as the name you see at the top of a given node in Resonite.
Category
Category for this ProtoFlux node. This is not the full category path, but only the direct parent - it should be the same as what appears at the bottom of a given node in Resonite.
Inline
Controls whether this node is displayed inline, or floats to the right. If this parameter is set, the node will draw inline, regardless of the value passed to it.
If you need content to flow below the element, use <div style="clear:right;"></div>
, add the class .floatnone
to an element, or use other elements with a similar CSS style tag.
Inputs
A string containing a JSON-encoded array of Name/Type information for each input on this node. Example input might be similar to the following, which adds two inputs of type String
, one named Input1
and one named Input2
[{"Name":"Input1", "Type":"String"}, {"Name":"Input2", "Type":"String"}]
Outputs
A string containing a JSON-encoded array of Name/Type information for each output on this node. Example input might be similar to the following, which adds two outputs of type bool
, one named Output1
and one named Output2
[{"Name":"Output1", "Type":"bool"}, {"Name":"Output2", "Type":"bool"}]
Globals
A string containing a JSON-encoded array of Name/Type information for each global on this node. Example input might be similar to the following, which adds two inputs of type User
, one named Global1
and one named Global2
[{"Name":"Global1", "Type":"User"}, {"Name":"Global2", "Type":"User"}]
Custom
Either true
or false
(not case sensitive, defaults to false
), if true
the page is not added to ProtoFlux:All. This is mainly used for custom nodes.
Custom=false
Example Usage
If we combine all of the above examples together, we end up with this template invocation:
{{#Invoke:ProtoFlux|GenerateUI |Name=Example Name |Category=Example Category |Inputs= [ {"Name": "Input1", "Type": "Call"}, {"Name": "Input2", "Type": "String"} ] |Outputs= [ {"Name": "Output1", "Type": "bool"}, {"Name": "Output2", "Type": "bool"} ] |Globals= [ {"Name": "Global1", "Type": "User"}, {"Name": "Global2", "Type": "User"} ] |}}
which results in the following:
Lua error at line 292: attempt to call global 'module' (a nil value).
-- Package definition to return to Scribunto - this allows us to define methods that can be called
-- when this module is targeted.
local p = {}
-- Type colors - RGB values to be included in the HTML output when a color is needed. This will be replaced with CSS at some point.
local typeColor =
{
User = '255, 128, 255',
Impulse = '179, 255, 255',
bool = '115, 115, 115',
AsyncImpulse = '204, 179, 255',
String = '245, 31, 31',
Dummy = '255, 0, 255',
IFormatProvider = '168, 143, 214',
float = '0, 255, 255',
ColorProfile = '255, 196, 54',
colorX = '255, 89, 0',
Component = '112, 76, 85',
float3 = '0, 255, 255',
float2 = '0, 255, 255',
ulong = '0, 255, 127',
int = '0, 255, 0',
Continuation = '255, 255, 178',
Slot = '191, 255, 127',
WebsocketClient = '191, 242, 176',
IField = '171, 243, 133',
ICollider = '126, 84, 37',
IAssetProvider = '154, 66, 186',
Uri = '123, 49, 186',
IComponent = '93, 134, 70',
Variable = '175, 193, 56',
Animation = '188,255,254',
int2 = '0, 255, 0',
floatQ = '0, 127, 255'
}
local pack2 = {
"int2",
"float2",
"uint2",
"double2",
"long2",
"ulong2",
"bool2"
}
local pack3 = {
"int3",
"float3",
"uint3",
"double3",
"long3",
"ulong3",
"bool3"
}
local pack4 = {
"int4",
"float4",
"uint4",
"double4",
"long4",
"ulong4",
"bool4"
}
--- Method that generates the HTML output
-- @param frame A Scribunto frame instance. frame.args contains the parameters passed into the module call
function p.GenerateUI( frame )
-- Parse the JSON input, returning an empty array if no argument was passed
local inputs = mw.text.jsonDecode(frame.args.Inputs or '[]')
local outputs = mw.text.jsonDecode(frame.args.Outputs or '[]')
local globals = mw.text.jsonDecode(frame.args.Globals or '[]')
-- Create an HTML div element to contain our node UI
local protofluxContainer = mw.html.create( 'div' )
protofluxContainer
:attr('class', frame.args.Inline and '' or 'floatright')
:cssText('color: rgb(224, 224, 224); background-color: rgb(18, 20, 28); width: 256px; display: flex; flex-direction: column; align-items: stretch;')
:tag('div') -- HTML div to contain the node title
:cssText('padding: 10px 0px 10px 0px; text-align:center; font-weight: bold; background-color: rgb(26, 41, 54); font-size: 18pt; flex-grow: 1;')
:wikitext(frame.args.Name)
:done() -- Close node title div
local processedInputs = {}
local processedOutputs = {}
for i=1,#inputs do
for j=1,(inputs and inputs[i].Multi or 1) do
inputs[i].MultiIndex = j
table.insert(processedInputs, { Name=inputs[i].Name, Type=inputs[i].Type, Multi=inputs[i].Multi, MultiIndex=j })
end
end
for i=1,#outputs do
for j=1,(outputs and outputs[i].Multi or 1) do
table.insert(processedOutputs, { Name=outputs[i].Name, Type=outputs[i].Type, Multi=outputs[i].Multi, MultiIndex=j })
end
end
-- Calculate the larger number of rows required (either for inputs or outputs, if the node is asymmetric)
local maxRows = math.max(#processedInputs, #processedOutputs)
-- Iterate over each row, and populate it with the inputs/outputs. If the node is asymmetric,
-- the value passed for either input or output might be nil.
for i=1,maxRows do
CreateConnectorRow(protofluxContainer, processedInputs[i], processedOutputs[i])
end
-- Iterate over each global value in the node, and create a row. These elements take up the entire width
-- of the node, and so don't need to be balanced in any way.
for i=1,#globals do
CreateGlobalsRow(protofluxContainer, globals[i])
end
protofluxContainer
:tag('div') -- HTML div to contain the node category footer
:cssText('text-align: center; padding: 10px; font-size: 18pt; color: rgb(64,64,64); font-weight: bold;')
:wikitext(frame.args.Category)
:done() -- Close category footer div
-- Return the HTML generated above to the wiki page this script is invoked from.
return tostring(protofluxContainer) .. '[[Category:ProtoFlux:All]]'
end
--- Creates a new ProtoFlux connector row in the output
-- @param Container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param Input Table containing a Name and Type for the input on this row. Can be nil if no input is to be placed on this row.
-- @param Output Table containing a Name and Type for the output on this row. Can be nil if no output is to be placed on this row.
function CreateConnectorRow(Container, Input, Output)
local connectorRow = Container
:tag('div') -- HTML div to contain the connector row. We specify a min-height of 70px for consistency.
:cssText('display: flex; min-height: 70px;')
-- Create the input (left) attachement point
CreateConnectorAttachmentPoint(connectorRow, Input, true)
local c = connectorRow
:tag('div') -- HTML div to contain the input and output labels in the connector row
:cssText( 'flex-grow: 2; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between;')
if Input and Input.Multi and Input.MultiIndex == 1 then
c
:tag('div') -- HTML div to contain the input label
:attr('title', Input and (Input.Name .. ' <' .. Input.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: left; overflow: hidden; text-overflow: ellipsis; padding-left: 4px; border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;')
:wikitext(Input and Input.Name or '' )
:done() -- Close input label div
elseif Input and Input.Multi and Input.MultiIndex > 1 then
local c2 = c
:tag('div') -- HTML div to contain the input label
:attr('title', Input and (Input.Name .. ' <' .. Input.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: left; padding-left: 4px; border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;')
if Input.Multi == Input.MultiIndex then
c2
:tag('div')
:cssText('position:relative; top:2rem; display:flex; gap: 4px;')
:tag('div')
:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
:wikitext('+')
:done()
:tag('div')
:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
:wikitext('-')
:done()
end
else
c
:tag('div') -- HTML div to contain the input label
:attr('title', Input and (Input.Name .. ' <' .. Input.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: left; overflow: hidden; text-overflow: ellipsis; padding-left: 4px; border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color:' .. GetTypeColor(Input, 0.6) .. ';')
:wikitext(Input and Input.Name or '' )
:done() -- Close input label div
end
if Output and Output.Multi and Output.MultiIndex == 1 then
c
:tag('div') -- HTML div to contain the output label
:attr('title', Output and (Output.Name .. ' <' .. Output.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: right; overflow: hidden; text-overflow: ellipsis; padding-right: 4px; border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;')
:wikitext(Output and Output.Name or '')
:done() -- Close output label div
elseif Output and Output.Multi and Output.MultiIndex > 1 then
local c2 = c
:tag('div') -- HTML div to contain the output label
:attr('title', Output and (Output.Name .. ' <' .. Output.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: right; padding-right: 4px; border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;;')
if Output.Multi == Output.MultiIndex then
c2
:tag('div')
:cssText('display:flex; justify-content:flex-end; gap: 4px;')
:tag('div')
:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
:wikitext('+')
:done()
:tag('div')
:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
:wikitext('-')
:done()
end
else
c
:tag('div') -- HTML div to contain the output label
:attr('title', Output and (Output.Name .. ' <' .. Output.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: right; overflow: hidden; text-overflow: ellipsis; padding-right: 4px; border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: ' .. GetTypeColor(Output, 0.6) .. ';')
:wikitext(Output and Output.Name or '')
:done() -- Close output label div
end
-- Create the output (right) attachment point
CreateConnectorAttachmentPoint(connectorRow, Output, false);
end
--- Creates a new ProtoFlux global input field row in the output
-- @param Container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param Global Table containing a Name and Type for the global field on this row.
function CreateGlobalsRow(Container, Global)
Container
:tag( 'div' )
:attr('title', Global.Name .. ' <' .. Global.Type .. '>') -- Add a basic mouseover description
:cssText('display: flex; min-height: 70px; flex-direction: column; border-left: 10px solid ' .. GetTypeColor(Global, 1.0) .. ';')
:tag( 'div' )
:cssText('display: flex; flex-direction: row; align-items: center; flex-grow:1; overflow: hidden;')
:tag( 'span' )
:cssText('text-align: center; overflow: hidden; text-overflow: ellipsis; font-size: 14pt; font-weight: bold; flex-grow:1;')
:wikitext(Global.Name)
:done()
:done()
:tag( 'div' )
:attr('style', 'display:flex; gap: 10px; flex-grow: 1;')
:tag('div')
:cssText('border-radius: 16px; background-color: #777; font-style: italic; text-align:center; flex-grow: 3; display: flex; flex-direction: column; justify-content: center;')
:tag( 'span' )
:wikitext('null')
:done()
:done()
:tag( 'div')
:cssText('border-radius: 16px; background-color: #333; text-align:center; flex-grow: 1; display: flex; flex-direction: column; justify-content: center;')
:tag ( 'span' )
:wikitext('∅')
end
--- Creates a new input or output attachement point for a connector row.
-- @param Container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param Connector Table containing a Name (Optional) and Type (Required) for the input/output attachment point.
-- @param isInput True if this is an input side attachment point, false if it is an output side attachment point.
function CreateConnectorAttachmentPoint(Container, Connector, isInput)
if Connector and (Connector.Type == "Impulse" or Connector.Type == "AsyncImpulse" or Connector.Type == "Continuation") then
Container
:tag( 'div' )
:attr('title', Connector and (Connector.Name .. ' <' .. Connector.Type .. '>') or '') -- Add a basic mouseover description
:cssText('width:30px; background-color: black; fill: ' .. GetTypeColor(Connector, 0.3) .. '; stroke: ' .. GetTypeColor(Connector, 1.0))
:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type="InputArrow"}}))
elseif Connector then
Container
:tag( 'div' )
:attr('title', Connector and (Connector.Name .. ' <' .. Connector.Type .. '>') or '') -- Add a basic mouseover description
:cssText('width:30px; fill: ' .. GetTypeColor(Connector, 0.3) .. '; stroke: ' .. GetTypeColor(Connector, 1.0))
:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type=(isInput and "InputBox" or "OutputBox")}}))
end
end
--- Returns an RGBA value representing the type color within Resonite
-- @param Connector Table containing a Name (Optional) and Type (Required) for the input/output attachment point.
-- @param Alpha The alpha to use in the RGBA value.
function GetTypeColor(Connector, Alpha)
return (Connector and 'rgba(' .. (typeColor[Connector.Type] or '0, 0, 0') .. ',' .. Alpha .. ')' or 'rgba(0, 0, 0, 0)')
end
--Copyright (c) 2007-2008 Neil Richardson (nrich@iinet.net.au)
--
--Permission is hereby granted, free of charge, to any person obtaining a copy
--of this software and associated documentation files (the "Software"), to deal
--in the Software without restriction, including without limitation the rights
--to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
--copies of the Software, and to permit persons to whom the Software is
--furnished to do so, subject to the following conditions:
--
--The above copyright notice and this permission notice shall be included in all
--copies or substantial portions of the Software.
--
--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
--IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
--FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
--AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
--LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
--OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
--IN THE SOFTWARE.
module('CRC32', package.seeall)
local max = 2^32 -1
local CRC32 = {
0,79764919,159529838,222504665,319059676,
398814059,445009330,507990021,638119352,
583659535,797628118,726387553,890018660,
835552979,1015980042,944750013,1276238704,
1221641927,1167319070,1095957929,1595256236,
1540665371,1452775106,1381403509,1780037320,
1859660671,1671105958,1733955601,2031960084,
2111593891,1889500026,1952343757,2552477408,
2632100695,2443283854,2506133561,2334638140,
2414271883,2191915858,2254759653,3190512472,
3135915759,3081330742,3009969537,2905550212,
2850959411,2762807018,2691435357,3560074640,
3505614887,3719321342,3648080713,3342211916,
3287746299,3467911202,3396681109,4063920168,
4143685023,4223187782,4286162673,3779000052,
3858754371,3904687514,3967668269,881225847,
809987520,1023691545,969234094,662832811,
591600412,771767749,717299826,311336399,
374308984,453813921,533576470,25881363,
88864420,134795389,214552010,2023205639,
2086057648,1897238633,1976864222,1804852699,
1867694188,1645340341,1724971778,1587496639,
1516133128,1461550545,1406951526,1302016099,
1230646740,1142491917,1087903418,2896545431,
2825181984,2770861561,2716262478,3215044683,
3143675388,3055782693,3001194130,2326604591,
2389456536,2200899649,2280525302,2578013683,
2640855108,2418763421,2498394922,3769900519,
3832873040,3912640137,3992402750,4088425275,
4151408268,4197601365,4277358050,3334271071,
3263032808,3476998961,3422541446,3585640067,
3514407732,3694837229,3640369242,1762451694,
1842216281,1619975040,1682949687,2047383090,
2127137669,1938468188,2001449195,1325665622,
1271206113,1183200824,1111960463,1543535498,
1489069629,1434599652,1363369299,622672798,
568075817,748617968,677256519,907627842,
853037301,1067152940,995781531,51762726,
131386257,177728840,240578815,269590778,
349224269,429104020,491947555,4046411278,
4126034873,4172115296,4234965207,3794477266,
3874110821,3953728444,4016571915,3609705398,
3555108353,3735388376,3664026991,3290680682,
3236090077,3449943556,3378572211,3174993278,
3120533705,3032266256,2961025959,2923101090,
2868635157,2813903052,2742672763,2604032198,
2683796849,2461293480,2524268063,2284983834,
2364738477,2175806836,2238787779,1569362073,
1498123566,1409854455,1355396672,1317987909,
1246755826,1192025387,1137557660,2072149281,
2135122070,1912620623,1992383480,1753615357,
1816598090,1627664531,1707420964,295390185,
358241886,404320391,483945776,43990325,
106832002,186451547,266083308,932423249,
861060070,1041341759,986742920,613929101,
542559546,756411363,701822548,3316196985,
3244833742,3425377559,3370778784,3601682597,
3530312978,3744426955,3689838204,3819031489,
3881883254,3928223919,4007849240,4037393693,
4100235434,4180117107,4259748804,2310601993,
2373574846,2151335527,2231098320,2596047829,
2659030626,2470359227,2550115596,2947551409,
2876312838,2788305887,2733848168,3165939309,
3094707162,3040238851,2985771188,
}
local function xor(a, b)
local calc = 0
for i = 32, 0, -1 do
local val = 2 ^ i
local aa = false
local bb = false
if a == 0 then
calc = calc + b
break
end
if b == 0 then
calc = calc + a
break
end
if a >= val then
aa = true
a = a - val
end
if b >= val then
bb = true
b = b - val
end
if not (aa and bb) and (aa or bb) then
calc = calc + val
end
end
return calc
end
local function lshift(num, left)
local res = num * (2 ^ left)
return res % (2 ^ 32)
end
local function rshift(num, right)
local res = num / (2 ^ right)
return math.floor(res)
end
function Hash(str)
local count = string.len(tostring(str))
local crc = max
local i = 1
while count > 0 do
local byte = string.byte(str, i)
crc = xor(lshift(crc, 8), CRC32[xor(rshift(crc, 24), byte) + 1])
i = i + 1
count = count - 1
end
return crc
end
return p