Specialized components
Visual scripting
Node & value registry
20 min
overview the node and value registry serves as the central repository for all building blocks available in the ir engine visual scripting system it maintains a comprehensive catalog of node definitions and value types, providing a single source of truth that all components of the system can reference by centralizing these definitions, the registry ensures consistency across the editor, runtime, and serialization systems, while also enabling extensibility through the addition of new node types and data types this chapter explores the concept, structure, and implementation of the node and value registry within the ir engine core concepts registry purpose the registry serves several essential purposes in the visual scripting system centralized catalog it provides a single location where all available node types and value types are defined component lookup it enables the system to look up definitions by name when needed consistency enforcement it ensures that all parts of the system share the same understanding of nodes and values extensibility support it allows new node types and value types to be added to the system dependency management it can provide access to shared services that nodes might require by fulfilling these roles, the registry forms the foundation upon which the entire visual scripting system is built registry components the registry consists of two primary collections node definitions map a collection of all available node types, where each entry is identified by a unique type name (e g , "math/add", "events/onstart") each entry contains a complete node definition with inputs, outputs, and behavior each definition includes a factory function for creating instances of that node type value types map a collection of all available data types, where each entry is identified by a unique type name (e g , "float", "string", "boolean") each entry contains a complete value type definition with serialization, comparison, and other operations each definition specifies how data of that type should be handled throughout the system these collections provide the blueprints that the system uses to create and manipulate nodes and values implementation registry interface the core interface for the registry is iregistry // simplified from src/engine/registry ts export interface iregistry { readonly values valuetypemap; // collection of value type definitions readonly nodes nodedefinitionsmap; // collection of node definitions readonly dependencies record\<string, unknown>; // shared services and resources } this interface defines the structure that all registry implementations must follow, ensuring they provide access to node definitions, value types, and dependencies value type map the valuetypemap is a collection of value type definitions // from src/engine/values/valuetypemap ts import { valuetype } from ' /valuetype'; export type valuetypemap = { readonly \[key string] valuetype }; this simple map structure allows value types to be looked up by name keys are value type names (e g , "float", "string", "boolean") values are the corresponding valuetype objects that define behavior node definitions map the nodedefinitionsmap is a collection of node definitions // simplified from src/engine/nodes/registry/nodedefinitionsmap ts import { inodedefinition, ihasnodefactory } from ' /nodedefinitions'; // a node definition that includes a factory function export type nodedefinition = ihasnodefactory & pick\<inodedefinition, 'typename' | 'othertypenames'>; // map of node definitions by type name export type nodedefinitionsmap = { readonly \[type string] nodedefinition; }; this map structure allows node definitions to be looked up by type name keys are node type names (e g , "math/add", "events/onstart") values are the corresponding nodedefinition objects that include factories registry creation the registry is typically created during system initialization // simplified from src/functions/createregistry ts import { iregistry } from ' /engine/registry'; import { registercoreprofile } from ' /profiles/core/registercoreprofile'; import { registersceneprofile } from ' /profiles/scene/registersceneprofile'; import { registerstructprofile } from ' /profiles/struct/registerstructprofile'; export function createbaseregistry() iregistry { // start with an empty registry const emptyregistry iregistry = { values {}, nodes {}, dependencies {} }; // register profiles to populate the registry let registry = registercoreprofile(emptyregistry); registry = registersceneprofile(registry); registry = registerstructprofile(registry); // validate the registry to ensure consistency const errors = validateregistry(registry); if (errors length > 0) { console error('registry validation errors ', errors); } return registry; } this function creates an empty registry structure calls registration functions from various profiles to populate it validates the registry to ensure all references are valid returns the fully populated registry registry validation to ensure the registry is in a consistent state, a validation function checks for common issues // simplified from src/engine/validateregistry ts export function validateregistry(registry iregistry) string\[] { const errors string\[] = \[]; // check that all value types referenced by node definitions exist for (const \[nodename, nodedef] of object entries(registry nodes)) { // check input sockets for (const \[socketname, socketdef] of object entries(nodedef in)) { const valuetypename = typeof socketdef === 'string' ? socketdef socketdef valuetype; if (!registry values\[valuetypename]) { errors push( `node "${nodename}" input socket "${socketname}" references ` + `unknown value type "${valuetypename}"` ); } } // check output sockets (similar to inputs) // } // check for duplicate node type names const nodetypenames = new set\<string>(); for (const nodedef of object values(registry nodes)) { if (nodetypenames has(nodedef typename)) { errors push(`duplicate node type name "${nodedef typename}"`); } nodetypenames add(nodedef typename); } // additional validation checks return errors; } this validation helps catch issues early, such as references to non existent value types duplicate node type names missing required properties inconsistent definitions registry usage the registry is used by various components of the visual scripting system editor usage the visual script editor uses the registry to populate its node palette // simplified concept function buildnodepalette(registry iregistry) nodepaletteitem\[] { const palette nodepaletteitem\[] = \[]; // group nodes by category const nodesbycategory record\<string, nodedefinition\[]> = {}; // collect all node definitions for (const nodedef of object values(registry nodes)) { const category = nodedef category || 'uncategorized'; if (!nodesbycategory\[category]) { nodesbycategory\[category] = \[]; } nodesbycategory\[category] push(nodedef); } // create palette items for each category and node for (const \[category, nodes] of object entries(nodesbycategory)) { palette push({ type 'category', name category }); for (const nodedef of nodes) { palette push({ type 'node', name nodedef label || nodedef typename, typename nodedef typename, category }); } } return palette; } this function collects all node definitions from the registry groups them by category creates a structured palette that the editor can display graph loading when loading a visual script from json, the system uses the registry to instantiate nodes // simplified from src/engine/graphs/io/readgraphfromjson ts function createnodefromjson( nodejson nodejson, registry iregistry, graph igraph ) inode { // look up the node definition by type name const nodedefinition = registry nodes\[nodejson type]; if (!nodedefinition) { throw new error(`unknown node type ${nodejson type}`); } // create a node instance using the factory const node = nodedefinition nodefactory(graph, nodejson configuration || {}); // set up input values from json for (const \[inputname, inputvalue] of object entries(nodejson inputs || {})) { const inputsocket = node inputs find(s => s name === inputname); if (inputsocket) { // if the input is a literal value (not a connection) if (!islinkreference(inputvalue)) { // get the value type definition const valuetype = registry values\[inputsocket valuetypename]; if (!valuetype) { throw new error( `unknown value type ${inputsocket valuetypename}` ); } // deserialize the value using the value type inputsocket value = valuetype deserialize(inputvalue); } } } return node; } this function looks up the node definition in the registry using the type name from json uses the node factory to create a new instance sets up input values, using value types from the registry to deserialize data execution engine the execution engine indirectly uses the registry through node instances // simplified concept function resolvesocketvalue( engine visualscriptengine, inputsocket socket ) void { // if the socket has an incoming connection if (inputsocket links length > 0) { const sourcelink = inputsocket links\[0]; const sourcenode = engine nodes\[sourcelink nodeid]; const sourcesocket = sourcenode outputs find( s => s name === sourcelink socketname ); // if the source is a function node, execute it if (isfunctionnode(sourcenode)) { // the function node's exec method uses logic from its node definition // which was originally obtained from the registry sourcenode exec(); // copy the value inputsocket value = sourcesocket value; } } } while the execution engine doesn't directly access the registry during runtime, it relies on node instances created using factories from the registry node behavior defined in node definitions from the registry value operations defined in value types from the registry registry workflow the registry is central to the workflow of the visual scripting system sequencediagram participant editor participant graphloader participant execengine as execution engine participant registry participant nodedefs as node definitions participant valuetypes as value types registry >>nodedefs contains registry >>valuetypes contains editor >>registry get all node definitions registry >>nodedefs query nodedefs >>registry list of definitions registry >>editor node definitions for palette graphloader >>registry get node definition "math/add" registry >>nodedefs look up "math/add" nodedefs >>registry "math/add" definition registry >>graphloader node definition graphloader >>registry get value type "float" registry >>valuetypes look up "float" valuetypes >>registry "float" value type registry >>graphloader value type definition execengine >>registry (indirect) use node behavior execengine >>registry (indirect) use value operations this diagram illustrates how the registry contains collections of node definitions and value types the editor queries the registry to build its node palette the graph loader uses the registry to instantiate nodes and deserialize values the execution engine indirectly uses definitions from the registry next steps with an understanding of how the node and value registry provides a central repository for all building blocks, the next chapter explores how these building blocks are organized and added to the registry through profiles next profiles docid\ yvbfxwoodvgscpww0inam