Specialized components
Visual scripting
ValueType
23 min
overview valuetype is a fundamental concept in the ir engine visual scripting system that defines the nature and behavior of data that flows between nodes each valuetype represents a specific kind of data (such as numbers, strings, or vectors) and provides a complete specification for how that data should be created, manipulated, compared, and stored by formalizing these data types, the system ensures consistent handling of values throughout the visual script, enables type checking for connections, and supports serialization for saving and loading scripts this chapter explores the concept, structure, and implementation of valuetypes within the ir engine core concepts type definition purpose valuetypes serve several essential purposes in the visual scripting system data identity they provide a unique identifier for each kind of data default values they define how to create default instances of each type serialization they specify how to convert between in memory values and json compatible formats comparison they define how to determine if two values of the same type are equal cloning they provide methods for creating independent copies of values interpolation they enable blending between values of the same type by centralizing these aspects in a formal type definition, the system can handle data consistently across all components of the visual scripting framework type compatibility valuetypes are fundamental to determining whether connections between sockets are valid sockets with the same valuetype are always compatible some valuetypes may be implicitly convertible to others (e g , integer to float) incompatible types require explicit conversion nodes this type system helps prevent logical errors by ensuring that data flows correctly between nodes implementation valuetype interface the core interface for value types is valuetype\<tvalue, tjson> // simplified from src/engine/values/valuetype ts export interface valuetype\<tvalue = any, tjson = any> { name string; // unique identifier (e g , "float", "string") creator () => tvalue; // creates a default value deserialize (jsonvalue tjson) => tvalue; // converts from json to in memory format serialize (value tvalue) => tjson; // converts from in memory format to json lerp (start tvalue, end tvalue, t number) => tvalue; // interpolates between values equals (a tvalue, b tvalue) => boolean; // compares values for equality clone (value tvalue) => tvalue; // creates an independent copy } this generic interface uses two type parameters tvalue the actual in memory data type (e g , javascript number, string, or custom object) tjson the json compatible representation of the data (e g , number, string, or array) common value types the ir engine includes several built in value types for common data floatvalue (for numbers) // simplified from src/profiles/core/values/floatvalue ts export const floatvalue valuetype\<number, number | string> = { name 'float', creator () => 0, // default is 0 deserialize (value) => (typeof value === 'string' ? parsesafefloat(value, 0) value), serialize (value number) => value, // numbers serialize as themselves lerp (start number, end number, t number) => start (1 t) + end t, // standard linear interpolation equals (a number, b number) => a === b, // simple equality check clone (value number) => value // numbers are primitive, so just return }; stringvalue (for text) // simplified from src/profiles/core/values/stringvalue ts export const stringvalue valuetype\<string, string> = { name 'string', creator () => '', // default is empty string deserialize (value string) => value, // strings deserialize as themselves serialize (value string) => value, // strings serialize as themselves lerp (start string, end string, t number) => (t < 0 5 ? start end), // simple threshold based interpolation equals (a string, b string) => a === b, // simple equality check clone (value string) => value // strings are primitive like }; booleanvalue (for true/false) // simplified from src/profiles/core/values/booleanvalue ts export const booleanvalue valuetype\<boolean, boolean> = { name 'boolean', creator () => false, // default is false deserialize (value boolean) => value, // booleans deserialize as themselves serialize (value boolean) => value, // booleans serialize as themselves lerp (start boolean, end boolean, t number) => (t < 0 5 ? start end), // simple threshold based interpolation equals (a boolean, b boolean) => a === b, // simple equality check clone (value boolean) => value // booleans are primitive }; complex value types for more complex data structures, the valuetype implementation becomes more sophisticated vec3value (for 3d vectors) // simplified concept for src/profiles/scene/values/vec3value ts export const vec3value valuetype\<vec3, \[number, number, number]> = { name 'vec3', creator () => new vec3(0, 0, 0), // default is origin (0,0,0) deserialize (json \[number, number, number]) => new vec3(json\[0], json\[1], json\[2]), // create from array serialize (value vec3) => \[value x, value y, value z] as \[number, number, number], // convert to array lerp (start vec3, end vec3, t number) => new vec3( start x (1 t) + end x t, start y (1 t) + end y t, start z (1 t) + end z t ), // component wise interpolation equals (a vec3, b vec3) => a x === b x && a y === b y && a z === b z, // component wise equality clone (value vec3) => new vec3(value x, value y, value z) // create new instance with same values }; listvalue (for arrays/collections) // simplified concept for src/profiles/struct/values/listvalue ts export const listvalue valuetype\<any\[], any\[]> = { name 'list', creator () => \[], // default is empty array deserialize (json any\[]) => json map(item => deserializeitem(item)), // recursively deserialize items serialize (value any\[]) => value map(item => serializeitem(item)), // recursively serialize items lerp (start any\[], end any\[], t number) => { // if arrays have different lengths, use threshold approach if (start length !== end length) { return t < 0 5 ? clone(start) clone(end); } // otherwise, interpolate each element return start map((item, index) => lerpitem(item, end\[index], t) ); }, equals (a any\[], b any\[]) => { if (a length !== b length) return false; return a every((item, index) => areitemsequal(item, b\[index]) ); }, clone (value any\[]) => value map(item => cloneitem(item)) // deep clone the array }; special value types some value types serve special purposes in the system flowvalue (for execution flow) // simplified concept for src/profiles/core/values/flowvalue ts export const flowvalue valuetype\<void, null> = { name 'flow', creator () => undefined, // flow has no actual value deserialize () => undefined, // nothing to deserialize serialize () => null, // serialize as null lerp () => undefined, // cannot interpolate flow equals () => true, // all flow values are equal clone () => undefined // nothing to clone }; this special type is used for execution sockets that control the flow of execution rather than carrying data values valuetypemap all value types are collected in a central registry called the valuetypemap // from src/engine/values/valuetypemap ts export type valuetypemap = { readonly \[key string] valuetype }; this map allows the system to look up value types by name // example usage function getvaluetype(typename string) valuetype | undefined { return valuetypemap\[typename]; } function createdefaultvalue(typename string) any { const valuetype = getvaluetype(typename); if (!valuetype) { throw new error(`unknown value type ${typename}`); } return valuetype creator(); } the valuetypemap is typically populated during system initialization, with value types from various profiles being registered value type operations serialization and deserialization when saving a visual script to a file, the system uses value types to convert in memory data to json // simplified concept function serializesocketvalue(socket socket) any { const valuetype = getvaluetype(socket valuetypename); if (!valuetype) { throw new error(`unknown value type ${socket valuetypename}`); } return valuetype serialize(socket value); } function deserializesocketvalue(socket socket, jsonvalue any) void { const valuetype = getvaluetype(socket valuetypename); if (!valuetype) { throw new error(`unknown value type ${socket valuetypename}`); } socket value = valuetype deserialize(jsonvalue); } this process ensures that complex data structures can be saved to and loaded from json files type checking and conversion when connecting sockets, the system uses value types to determine compatibility // simplified concept function canconnect( sourcesocket socket, targetsocket socket ) boolean { // direct type match if (sourcesocket valuetypename === targetsocket valuetypename) { return true; } // check for implicit conversion return canconvert(sourcesocket valuetypename, targetsocket valuetypename); } function canconvert( sourcetypename string, targettypename string ) boolean { // conversion rules (could be more sophisticated) const conversionmap record\<string, string\[]> = { 'integer' \['float', 'string'], 'float' \['string'], 'boolean' \['string'] // additional conversion rules }; return conversionmap\[sourcetypename]? includes(targettypename) || false; } for cases where direct connections aren't possible, explicit conversion nodes can be used graph lr addnode\["add (outputs float)"] > convertnode\["float to string"] convertnode > printnode\["print message (expects string)"] value interpolation the lerp function enables smooth transitions between values, which is particularly useful for animations // simplified concept function animatevalue\<t>( startvalue t, endvalue t, valuetypename string, duration number, onupdate (value t) => void ) void { const valuetype = getvaluetype(valuetypename); if (!valuetype) { throw new error(`unknown value type ${valuetypename}`); } const starttime = date now(); function update() { const elapsed = date now() starttime; const t = math min(elapsed / duration, 1); const currentvalue = valuetype lerp(startvalue, endvalue, t); onupdate(currentvalue); if (t < 1) { requestanimationframe(update); } } update(); } example use case let's examine how value types are used in a simple visual script that adds two numbers and displays the result graph lr numbera\["number a 5"] > addnode\["add"] numberb\["number b 3"] > addnode addnode > convertnode\["float to string"] convertnode > concatnode\["concatenate"] textprefix\["text 'result '"] > concatnode concatnode > printnode\["print message"] in this example the numbera and numberb nodes provide constant values of type float the addnode adds these values, producing a float result (8) the convertnode converts the float to a string using the appropriate conversion logic the concatnode combines the prefix text with the converted number, producing a string ("result 8") the printnode displays the final string each step in this process relies on value types to ensure that data is handled correctly floatvalue defines how the numbers are stored and added conversion logic (possibly using stringvalue ) defines how to convert the number to text stringvalue defines how the strings are concatenated and displayed next steps with an understanding of how value types define the data that flows through visual scripts, the next chapter explores how the execution engine processes these scripts at runtime next execution engine (visualscriptengine & fiber) docid\ pdzsrkltjmeslv7drzk6v