User:Yosh/sandbox: Difference between revisions

From Resonite Wiki
m user root slot >:(
translate test
Line 1: Line 1:
'''Dynamic variables''', commonly shorted to '''dyn vars''' or '''dynvars''', is a system of data storage wherein one can store arbitrary, scoped data under slot hierarchies with arbitrary names, akin to that of an [https://en.wikipedia.org/wiki/Associative_array associative array]. Their usage is usually found in large systems with many moving parts, but can nonetheless be useful as easy "global" values that can be changed on an object.
<languages></languages>


For a quick-start guide on using dynamic variables, the [[How To Use Dynamic Variables]] page serves to be a how-to.
{{Infobox Component
|Image=ValueGradientDriver`1Component.png
|Name=ValueGradientDriver
}}


== Overview ==
<translate>
Dynamic variables are managed with two parts: ''dynamic variable spaces'' and ''dynamic variables''. Dynamic variables live under a dynamic variable space, and can be created, modified, and destroyed dynamically.
The '''ValueGradientDriver''' component changes the value of the field defined in <code>Target</code> based on the items in the <code>Points</code> list and their <code>Position</code> in relation to the <code>Progress</code> value.
</translate>


=== Variable Spaces ===
== <translate>Fields</translate> ==
By adding a [[Component:DynamicVariableSpace|DynamicVariableSpace]] to a slot, the slot and all of its children become a part of the named variable space. This does not necessarily mean that every dynamic variable under this slot is "attached" to the space. This is explained further in [[#Binding]].


A slot can be part of multiple spaces at once. Spaces under child slots are <em>not</em> nested with respect to parent spaces in any form, meaning it is advised to make unique names for variable spaces.
{{Table ComponentFields
|Progress|Float|<translate>Controls which items from the <code>Points</code> list will be used to drive the value of <code>Target</code></translate>
|Target|{{RootFieldType|FieldDrive`1|T}}|TypeAdv1=true|<translate>The field that this component will drive the value of</translate>
|Interpolate|Bool|<translate>Controls whether or not this component will interpolate (or blend) between the nearest two <code>Points</code> to the current <code>Progress</code> value.</translate>
|Points|{{RootFieldType|SyncList`1|[[#Point|Point]]&lt;T&gt;}}|TypeAdv3=true|<translate>A list of items indicating their <code>Position</code> (in relation to <code>Progress</code>), and a <code>Value</code></translate>
}}


=== Dynamic Variables ===
== <translate>Usage</translate> ==
To create a dynamic variable, attach any one of the following components to a slot that is part of a variable space:


* [[Component:DynamicValueVariable|DynamicValueVariable]], for working with [[Value Types|value types]] ([[Type:int|int]], [[Type:string|string]], [[Type:float3|float3]], etc.).
<translate>
* [[Component:DynamicReferenceVariable|DynamicReferenceVariable]], for working with [[Reference Types|reference types]] ([[Type:Slot|Slot]], [[Type:User|User]], etc.).
Each point in the <code>Points</code> list has a <code>Position</code> field and <code>Value</code> field. The <code>Position</code> field is used for comparison with the component's <code>Progress</code> field, while the <code>Value</code> field is the value used for driving the field in <code>Target</code>.
* [[Component:DynamicTypeVariable|DynamicTypeVariable]], for working with the [[Type:Type|Type]] type.


Dynamic variables have a name and a value. A variable's name is its unique identifier within the variable space, while its value is the value of the dynamic variable.
When <code>Interpolate</code> is checked, the value stored in <code>Target</code> is linearly interpolated between the <code>Value</code>s of the two points surrounding the current <code>Progress</code>. When unchecked, the output value is simply set to the <code>Value</code> of the closest point before the current <code>Progress</code>. The only exception to this is when no point exists before the current <code>Progress</code>, in which case the first point after the current <code>Progress</code> is used.


=== Dynamic Fields ===
If two points have the same <code>Position</code>, then the point of greatest index takes priority if the value is not interpolated or if the positions are exactly equal to the current <code>Progress</code>. ''During'' interpolation, however, the point of ''least'' index takes priority.
It is possible to "transform" an existing [[Type:IField|IField]] on a component into a dynamic variable. This is done by any one of the following components:
</translate>


* [[Component:DynamicField|DynamicField]], for working with [[Value Types|value types]].
== <translate>Examples</translate> ==
* [[Component:DynamicReference|DynamicReference]], for working with [[Reference Types|reference types]].
* [[Component:DynamicTypeField|DynamicTypeField]], for working with the [[Type:Type|Type]] type.


Upon attaching the component and dragging the field to convert into <code>TargetField</code>, the pointed field can then be interfaced with like any other dynamic variable.
<gallery widths=560px heights=480px>
File:component_example_ValueGradientDriver.webm|alt=<translate>A ValueGradientDriver is set up with five float values. Index 0 has position 0.00 and value 0, index 1 has position 0.77 and value 2, index 2 has position 0.25 and value 4, index 3 has position 0.67 and value 6, and index 4 has position 1.00 and value 8. As progress moves from 0 to 1, the target field is linearly interpolated between the values of indices 0, 2, 3, 1, then 4. Once interpolation is unchecked, the target field is merely set to the previous index value.</translate>|<translate>General usage of the component, showing interpolation and non-interpolation between five float values.</translate>
File:Component_example_ValueGradientDriver_IndexOfMax.webp|alt=<translate>A ValueGradientDriver is set up with five int values. The progress is fixed at 1 and each point's value is set to its index. Interpolation is disabled. The index of the point with the greatest position is output.</translate>|<translate>An unorthodox yet valid usage of the component, as way to find the index of the maximum value in a list of floats (as the <code>Position</code> values) without the need for ProtoFlux. <code>Progress</code> may be set to 0 to find the index of the minimum value as well.</translate>
</gallery>


=== Naming Restrictions ===
== <translate>See Also</translate> ==
The names of dynamic variables and dynamic variable spaces must not contain symbols, punctuation, or whitespace, <em>except</em> for period (<code>.</code>), underscore (<code>_</code>), and space (<code> </code>). To check if a character is unable to be used in a dynamic variable name, one can use the [[ProtoFlux:Is Symbol|Is Symbol]], [[ProtoFlux:Is Punctuation|Is Punctuation]], and [[ProtoFlux:Is White Space|Is White Space]] ProtoFlux nodes, taking care of the three exceptions above.


== Binding ==
[[Category:Components{{#translation:}}|Value Gradient Driver`1]]
The process of a dynamic variable being associated with a given space is called ''binding''. A dynamic variable component will traverse up the slot hierarchy, including its current slot, looking for an applicable variable space to bind to. If a dynamic variable does not find a dynamic variable space that it can bind to, it will not be accessible outside of the component itself, essentially reducing to a glorified [[Component:ValueField|ValueField]]. A dynamic variable will go through this binding process every time any part of the component changes.
[[Category:Generic Components{{#translation:}}|Value Gradient Driver`1]]
 
[[Category:Components With Nested Types{{#translation:}}|Value Gradient Driver`1]]
=== Direct Vs Indirect Binding ===
[[Category:Components:Transform:Drivers{{#translation:}}|Value Gradient Driver`1]]
When making a dynamic variable, the <code>VariableName</code> of a dynamic variable component can be one of the following two forms: <code>VariableName</code> or <code>VariableSpaceName/VariableName</code>. The former represents ''indirect binding'', while the latter represents ''direct binding''.
 
If a dynamic variable is indirectly binding, it will bind to the first dynamic variable space that does not have <code>OnlyDirectBinding</code> set to <code>True</code>. If a dynamic variable is directly binding, it will bind to the first dynamic variable space that matches <code>VariableSpaceName</code>.
 
=== Binding Examples ===
In the following setup:
 
└─ Foo - Variable Space "test"
    └─ Bar - Variable Space "test2"
      └─ Baz - Dynamic Variable "test/var"
 
The dynamic variable <code>test/var</code> will bind to the variable space <code>test</code>. If the variable was instead named <code>var</code>, it will bind to the variable space <code>test2</code>. If the variable was named <code>var</code> and <code>test2</code> has <code>OnlyDirectBinding</code> enabled, it would bind to <code>test</code>.
 
=== Binding Delay Warning ===
As of the time of writing, there exist a few instances where created variables do not instantly bind/rebind to a dynamic variable space, requiring a [[ProtoFlux:Delay Updates|Delay Updates]] of 2 or more updates. These include:
 
* [https://github.com/Yellow-Dog-Man/Resonite-Issues/issues/1394 Creating new dynamic reference variables].
* [https://github.com/Yellow-Dog-Man/Resonite-Issues/issues/2717 Deleting dynamic variables].
* Changing the name of a dynamic variable or dynamic variable space.
* Any instance where there is a dynamic variable space change without a component update, such as:
** [https://github.com/Yellow-Dog-Man/Resonite-Issues/issues/1690 duplicating slots containing a dynamic variable space].
** Reparenting a slot containing a dynamic variable under a new dynamic variable space.
 
If you find dynamic variables to be behaving weirdly, and you are doing any of these operations, try adding a delay of 2 or more updates between such operations.
 
== Interfacing ==
A variable space can be interfaced with any slot that resides within the variable space, even if the slot is not part of the hierarchy containing the exact dynamic variable being interfaced with.
 
=== Reading Dynvars ===
In ProtoFlux, the [[ProtoFlux:Read Dynamic Variable|Read Dynamic Variable]] node and [[ProtoFlux:Dynamic Variable Input|Dynamic Variable Input]] nodes exist to read dynamic variables from a slot.
 
The Read Dynamic Variable node takes in a <code>Source</code> [[Slot]] and a <code>Path</code> variable name, and is marked as [[ContinuouslyChanging]]. The Dynamic Variable Input node uses a [[Global]] for <code>Path</code> and binds to spaces in the slot hierarchy it exists in. Therefore, when reading constant-name dynamic variables within the same dynamic variable space of the slot the node exists in, the Dynamic Variable Input node is preferred. When reading dynamic variables from a space outside the node's slot hierarchy, the Read Dynamic Variable node must be used.
 
It is also possible to read dynamic variables by sourcing the <code>Value</code> or <code>Reference</code> field of the component directly. This is not recommended, as if the dynamic variable ever gets deleted and remade, the source will break, removing half the functionality of a dynamic variable.
 
=== Driving from Dynvars ===
By using a [[Component:DynamicValueVariableDriver|DynamicValueVariableDriver]] or [[Component:DynamicReferenceVariableDriver|DynamicReferenceVariableDriver]] component, fields can be driven using the value of a dynamic variable. This is recommended over traditional methods of driving by virtue of its flexibility and compactness.
 
=== Writing to Dynvars ===
Dynamic variables should be written to via the [[ProtoFlux:Write Dynamic Variable|Write Dynamic Variable]] or [[ProtoFlux:Write Or Create Dynamic Variable|Write Or Create Dynamic Variable]] ProtoFlux nodes. The former will write to existing dynamic variables, while the latter will create the dynamic variable if it doesn't exist already.
 
=== Driving Dynvars ===
Driving dynamic variables must be done with caution. If a dynamic variable is being driven, it is <em>crucial</em> that <em>all</em> instances of the same dynamic variable are driven by the same value. Otherwise, clients will fight over which value is the "true" value of the dynamic variable and cause inconsistent behavior. The other caution relates to how driving is essentially a local write each frame, which is expanded on in [[#Writing/Driving_Delay_Warning]].
 
=== Writing/Driving Delay Warning ===
If dynamic variables are written to via sourcing the component field and writing, it will incur a delay of 1 frame before the value is propagated to any read nodes. As such, it is highly not recommended to directly write to dynamic variable components, and always recommended to use the proper ProtoFlux nodes.
 
When driving the value of a dynamic variable, it is essentially a local write to the component every frame. As such, the 1 frame of delay will apply there too. There is currently no good way to avoid this.
 
== Best Practices ==
Even though dynamic variables allow for a wide array of freedom, there are a few practices generally considered to be favorable when working with dynamic variables:
 
* It is highly recommended to have only one instance of a dynamic variable (dynamic variable component with the same name) at any given time.
** There aren't any huge problems with having multiple dynamic variable instances if none or all of the instances are being driven, but it allows for cleaner organization of variables.
* Using variable names that directly bind allows for a clearer overview of what space the variables should be bound to. Indirectly binding variable names are more suited for variables that are dynamically created and/or destroyed as part of an object's function.
** Using <code>OnlyDirectBinding</code> on a DynamicVariableSpace strictly enforces this behavior, which can prevent misbindings and catch errors earlier.
* Dynamic variable spaces are not nested. If a system is complex enough, or if a DynamicVariableSpace is being shared by multiple objects, using periods (<code>.</code>) to pseudo-isolate objects or systems from one another is encouraged.
** e.g. an avatar and all of its features could contain the dynamic variables <code>User/Avatar.Systems.Grabbable.Enabled</code>, <code>User/Avatar.Blendshapes.Blep.MaxClamp</code>, <code>User/Avatar.Systems.Flight.Drag</code>, etc.
 
== Default Spaces ==
As of the time of writing, there are three dynamic spaces that exist "by default":
 
* <code>World</code>, which exists under the [[Root]] of any created world. This space is marked as <code>OnlyDirectBinding</code>.
** Useful for things that should globally affect the world or broadcast information throughout the world. Example items that use this space include [[BeatLink]] and the [[Redprint]] manager.
* <code>User</code>, which exists under every [[User]]'s [[ProtoFlux:User Root Slot|User Root Slot]]. This space is <code>OnlyDirectBinding</code>.
** Useful for systems that affect avatars, as outside objects can rely on a standardized space being available for each user to read and write variables on.
* <code>Dash</code>, which exists under the slot containing the [[Component:UserspaceRadiantDash|UserspaceRadiantDash]] in userspace.
 
== Example Usage ==
 
=== One Contained System ===
Say you are working on a large and complicated system. In lieu of trying to manage "global" settings by sourcing ValueField components, you can use dynamic variables to help assign names to settings or other useful values. This is usually accomplished by placing a DynamicVariableSpace on the root of your object (or root of your "library") and having a specialized dynamic variable slot for containing the dynamic variables. Depending on how many dynamic variables are used, it may be beneficial to delegate them to multiple slots.
 
A screenshot showing a simple setup like this is shown below. Note the use of [[ProtoFlux:Dynamic Variable Input|Dynamic Variable Input]] nodes for performance sake.
 
[[File:Dynamic Variable Example Internal System.webp|800px]]
 
=== Controlling Another System ===
Say that you want to make an external controller for a system on an avatar. This system uses the default <code>User</code> dynamic variable space and namespaces itself with <code>User/CoolAvatarSystem.</code>.
 
The simplest way to get a slot to interface with this system can be obtained by getting the [[User]] in some fashion and plugging it into the [[ProtoFlux:User Root Slot|User Root Slot]] node. Reading with this system should be done with the [[ProtoFlux:Read Dynamic Variable|Read Dynamic Variable]] node, as the controller is not part of the dynamic variable space on the user.
 
From there, it's simply a matter of knowing the right variable names and types (e.g. <code>User/CoolAvatarSystem.Enabled</code> being a <code>bool</code>). Plugging the previously obtained slot into the <code>Source</code> of a Read Dynamic Variable of the matching type and path will get you the current value, while using a Write Dynamic Variable will allow you to write to it.
 
== See Also ==
* [[Dynamic Impulses]]

Revision as of 23:40, 25 January 2025

Component image 
ValueGradientDriver component as seen in the Scene Inspector


The ValueGradientDriver component changes the value of the field defined in Target based on the items in the Points list and their Position in relation to the Progress value.

Fields

Fields
Name Type Description
persistent Bool Determines whether or not this item will be saved to the server.
UpdateOrder Int Controls the order in which this component is updated.
Enabled Bool Controls whether or not this component is enabled. Some components stop their functionality when this field is disabled, but some don't.
Progress Float Controls which items from the Points list will be used to drive the value of Target
Target field drive of T The field that this component will drive the value of
Interpolate Bool Controls whether or not this component will interpolate (or blend) between the nearest two Points to the current Progress value.
Points list of Point<T> A list of items indicating their Position (in relation to Progress), and a Value

Usage

Each point in the Points list has a Position field and Value field. The Position field is used for comparison with the component's Progress field, while the Value field is the value used for driving the field in Target.

When Interpolate is checked, the value stored in Target is linearly interpolated between the Values of the two points surrounding the current Progress. When unchecked, the output value is simply set to the Value of the closest point before the current Progress. The only exception to this is when no point exists before the current Progress, in which case the first point after the current Progress is used.

If two points have the same Position, then the point of greatest index takes priority if the value is not interpolated or if the positions are exactly equal to the current Progress. During interpolation, however, the point of least index takes priority.

Examples

See Also