Specialized components
Visual scripting
Visual script node
19 min
overview visual script nodes are the fundamental building blocks of the ir engine visual scripting system each node represents a specific operation, function, or action that can be performed within a visual script nodes are placed on the visual script graph and connected to create logical flows and data transformations by combining different types of nodes, users can create complex behaviors without writing traditional code this chapter explores the concept, structure, and implementation of visual script nodes within the ir engine core concepts node purpose and function a visual script node serves as a discrete unit of functionality within a visual script each node performs a specific, well defined task nodes can process data, control execution flow, or trigger events nodes communicate with each other through input and output sockets multiple nodes work together to create complete logical operations nodes abstract complex operations into visual components that can be easily understood and manipulated, making programming more accessible to non programmers node structure every node consists of several key components visual representation the node's appearance in the editor, typically a box with a title and connection points input sockets connection points that receive data or execution signals from other nodes output sockets connection points that send data or execution signals to other nodes internal logic the code that defines what the node does when executed configuration node specific settings that can be adjusted by the user this structure allows nodes to be both self contained units and interconnected parts of a larger system node types the ir engine categorizes nodes into several types based on their role in the execution flow // from src/engine/nodes/nodeinstance ts enum nodetype { event, // nodes that initiate execution flows flow, // nodes that process execution flows function, // nodes that process data without execution flow async // nodes that handle time dependent operations } each type has specific characteristics event nodes serve as entry points for execution, triggered by external events examples "on game start", "on mouse click", "on player enters area" have output execution sockets but no input execution sockets begin the flow of execution through the graph flow nodes perform actions as part of an execution sequence examples "play sound", "move object", "branch (if/else)" have both input and output execution sockets process data and determine the next steps in the execution flow function nodes transform data without directly participating in execution flow examples "add numbers", "get player name", "convert text to uppercase" typically have only data sockets, not execution sockets provide data to other nodes when requested async nodes handle operations that take time to complete examples "wait for seconds", "load file", "download data" manage asynchronous operations without blocking the execution flow often have special handling in the execution engine implementation node interface the core interface for all nodes is inode , which defines the essential properties and methods // simplified from src/engine/nodes/nodeinstance ts interface inode { description inodedescription; // metadata about the node type inputs socket\[]; // input connection points outputs socket\[]; // output connection points graph igraph; // reference to the containing graph configuration nodeconfiguration; // node specific settings nodetype nodetype; // event, flow, function, or async label? string; // optional user defined name metadata? any; // additional information // methods for reading input values and writing output values readinput\<t>(inputname string) t; writeoutput\<t>(outputname string, value t) void; } this interface ensures that all node types provide a consistent set of properties and behaviors, regardless of their specific functionality base node class the node abstract class implements the inode interface and provides common functionality for all node types // simplified from src/engine/nodes/node ts export abstract class node\<tnodetype extends nodetype> implements inode { public readonly description inodedescription; public readonly inputs socket\[]; public readonly outputs socket\[]; public readonly graph igraph; public readonly configuration nodeconfiguration; public readonly nodetype tnodetype; public label? string; public metadata? any; constructor( description inodedescription, graph igraph, configuration nodeconfiguration = {}, nodetype tnodetype ) { this description = description; this graph = graph; this configuration = configuration; this nodetype = nodetype; this inputs = createinputsockets(description inputs, this); this outputs = createoutputsockets(description outputs, this); } // read a value from an input socket readinput = \<t>(inputname string) t => { const socket = this inputs find(s => s name === inputname); if (!socket) throw new error(`input socket '${inputname}' not found`); return readinputfromsocket(socket) as t; }; // write a value to an output socket writeoutput = \<t>(outputname string, value t) void => { const socket = this outputs find(s => s name === outputname); if (!socket) throw new error(`output socket '${outputname}' not found`); writeoutputtosocket(socket, value); }; } this base class initializes the node with its description, graph reference, and configuration creates input and output sockets based on the node description provides methods for reading input values and writing output values handles common error checking and validation specialized node classes specific node types extend the base node class to implement their unique behaviors event node // simplified from src/engine/nodes/eventnode ts export class eventnode extends node\<nodetype event> { constructor(description inodedescription, graph igraph, configuration nodeconfiguration = {}) { super(description, graph, configuration, nodetype event); // validate that event nodes have no input execution sockets assert mustbetrue(!this inputs some(socket => socket valuetypename === 'flow')); // validate that event nodes have at least one output execution socket assert mustbetrue(this outputs some(socket => socket valuetypename === 'flow')); } // method to trigger the event trigger = (fiber fiber) void => { // find the output execution socket const outputsocket = this outputs find(s => s valuetypename === 'flow'); if (outputsocket) { // continue execution through this socket fiber commit(this, outputsocket name); } }; } flow node // simplified from src/engine/nodes/flownode ts export class flownode extends node\<nodetype flow> { constructor(description inodedescription, graph igraph, configuration nodeconfiguration = {}) { super(description, graph, configuration, nodetype flow); // validate that flow nodes have at least one input execution socket assert mustbetrue(this inputs some(socket => socket valuetypename === 'flow')); } // method called when the node is triggered by execution flow triggered = (fiber fiber, inputsocketname string) void => { // read input values const inputvalues = this gatherinputvalues(); // execute node specific logic const result = this executenodelogic(inputvalues); // write output values this applyoutputvalues(result); // continue execution through the appropriate output socket const outputsocketname = this determinenextoutputsocket(result); fiber commit(this, outputsocketname); }; // these methods would be implemented by specific flow node types protected gatherinputvalues() any { / / } protected executenodelogic(inputs any) any { / / } protected applyoutputvalues(result any) void { / / } protected determinenextoutputsocket(result any) string { / / } } function node // simplified from src/engine/nodes/functionnode ts export class functionnode extends node\<nodetype function> { constructor(description inodedescription, graph igraph, configuration nodeconfiguration = {}) { super(description, graph, configuration, nodetype function); } // method called when another node requests data from this node exec = () void => { // read input values const inputvalues = this gatherinputvalues(); // execute function logic const result = this calculateresult(inputvalues); // write result to output sockets this applyoutputvalues(result); }; // these methods would be implemented by specific function node types protected gatherinputvalues() any { / / } protected calculateresult(inputs any) any { / / } protected applyoutputvalues(result any) void { / / } } node execution the execution of nodes is managed by the execution engine, which follows these general steps flow node execution sequencediagram participant ee as execution engine participant prevnode as previous node participant currentnode as current flow node participant nextnode as next node prevnode >>ee activate output execution socket ee >>currentnode call triggered() method currentnode >>currentnode read input values currentnode >>currentnode execute node logic currentnode >>currentnode write output values currentnode >>ee commit to next output socket ee >>nextnode call triggered() method the execution engine calls the node's triggered method the node reads values from its input sockets the node performs its specific logic the node writes values to its output sockets the node tells the execution engine which output execution socket to follow next function node execution sequencediagram participant requestingnode as requesting node participant funcnode as function node requestingnode >>funcnode request output value funcnode >>funcnode call exec() method funcnode >>funcnode read input values funcnode >>funcnode calculate result funcnode >>funcnode write to output sockets funcnode >>requestingnode return requested value another node requests a value from the function node the function node's exec method is called the function node reads values from its input sockets the function node calculates its result the function node writes the result to its output sockets the requesting node receives the value example use case let's examine an enhanced alarm system implemented with visual script nodes graph td a\[event ontriggeractivated] > b{branch is alarm on?} c\[function get variable 'isalarmenabled'] >|data| b b true > d\[flow play alarm sound] b false > e\[flow do nothing/end] in this example event node ( ontriggeractivated ) initiates the execution flow when a trigger is activated function node ( get variable 'isalarmenabled' ) retrieves the current value of the "isalarmenabled" variable flow node ( branch is alarm on? ) checks the condition and directs execution to one of two paths flow node ( play alarm sound ) plays the alarm sound if the condition is true flow node ( do nothing/end ) terminates the execution flow if the condition is false the corresponding node instances might be structured like // conceptual representation const triggernode eventnode = { nodetype nodetype event, description { name "ontriggeractivated", / / }, inputs \[], outputs \[ { name "triggered", valuetypename "flow", / / } ], / / }; const getvariablenode functionnode = { nodetype nodetype function, description { name "getvariable", / / }, inputs \[], outputs \[ { name "value", valuetypename "boolean", / / } ], configuration { variablename "isalarmenabled" }, / / }; const branchnode flownode = { nodetype nodetype flow, description { name "branch", / / }, inputs \[ { name "exec", valuetypename "flow", connectedto { nodeid triggernode id, socketname "triggered" } }, { name "condition", valuetypename "boolean", connectedto { nodeid getvariablenode id, socketname "value" } } ], outputs \[ { name "true", valuetypename "flow", / / }, { name "false", valuetypename "flow", / / } ], / / }; const playsoundnode flownode = { nodetype nodetype flow, description { name "playsound", / / }, inputs \[ { name "exec", valuetypename "flow", connectedto { nodeid branchnode id, socketname "true" } }, { name "soundfile", valuetypename "string", value "alarm wav" } ], outputs \[ { name "completed", valuetypename "flow", / / } ], / / }; next steps with an understanding of visual script nodes as the building blocks of visual scripts, the next chapter explores the connection mechanism that allows nodes to communicate with each other sockets and links next socket & link docid 4mirdezjys2rte9fotsqk