Dynamic variables/ru: Difference between revisions

From Resonite Wiki
Created page with "Вы можете использовать содержимое динамической переменной для управления поля или ссылкой, используя компоненты DynamicValueVariableDriver<T> и DynamicReferenceVariableDriver<T>."
Updating to match new version of source page
 
(29 intermediate revisions by 3 users not shown)
Line 1: Line 1:
<languages/>
<languages />


== Обзор ==
<div lang="en" dir="ltr" class="mw-content-ltr">
'''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.
</div>


'''Динамические переменные''' позволяют вам читать и писать данные по имени в контексте иерархии [[Slot|слотов]]. Это упрощает управление данными в больших системах; каждый бит данных четко помечен, и вы можете разбивать данные на несколько '''пространств''', чтобы разделять ваши системы.
<div lang="en" dir="ltr" class="mw-content-ltr">
For a quick-start guide on using dynamic variables, the [[How To Use Dynamic Variables]] page serves to be a how-to.
</div>


== Ограничения именования ==
== <span lang="en" dir="ltr" class="mw-content-ltr">Overview</span> ==
При использовании динамических переменных существуют некоторые ограничения на именование пространств и переменных внутри этих пространств.


Пространства и имена переменных '''не могут содержать''':
<div lang="en" dir="ltr" class="mw-content-ltr">
* Любые символы
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.
** Как определено в C# [https://docs.microsoft.com/en-us/dotnet/api/system.char.issymbol?view=net-5.0 Char.IsSymbol]
</div>
* Любую пунктуацию / Пустые строки
** Как определено в C# [https://docs.microsoft.com/en-us/dotnet/api/system.char.ispunctuation?view=net-5.0 Char.IsPunctuation] и C# [https://docs.microsoft.com/en-us/dotnet/api/system.char.iswhitespace?view=net-5.0 Char.IsWhiteSpace]
* '''За исключением''' точки (.), нижнего подчеркивания (_) и пробела ( ).


{{Note|Какую концепцию или свойства предоставляют ваши пространства и переменные? Не торопитесь и назовите их как следует!<br/> Эти имена будут использоваться в разных местах или даже другими пользователями. Изменения этих имен потом может оказаться довольно сложной задачей. |suggestion}}
=== <span lang="en" dir="ltr" class="mw-content-ltr">Variable spaces</span> ===


== Использование ==
<div lang="en" dir="ltr" class="mw-content-ltr">
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]].
</div>


При желании к имени может быть добавлен префикс в виде '''пробела''' заканчивающийся символом '''/''', чтобы выбрать конкретное пространство для использования переменной. Это полезно для того, что различать несвязанные системы, в которых используют динамические переменные.
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>


Некоторые допустимые имена:
=== <span lang="en" dir="ltr" class="mw-content-ltr">Dynamic variables</span> ===


* Health -- нет определенного пространства, имя Health
<div lang="en" dir="ltr" class="mw-content-ltr">
* World/Color -- имя Color, в пространстве World
To create a dynamic variable, attach any one of the following components to a slot that is part of a variable space:
* MyCoolSystem/Score -- имя Score, в пространстве MyCoolSystem
</div>


=== Пространства ===
* <span lang="en" dir="ltr" class="mw-content-ltr">[[Component:DynamicValueVariable|DynamicValueVariable]], for working with [[Value Types|value types]] ([[Type:int|int]], [[Type:string|string]], [[Type:float3|float3]], etc.).</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">[[Component:DynamicReferenceVariable|DynamicReferenceVariable]], for working with [[Reference Types|reference types]] ([[Type:Slot|Slot]], [[Type:User|User]], etc.).</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">[[Component:DynamicTypeVariable|DynamicTypeVariable]], for working with the [[Type:Type|Type]] type.</span>


Динамические переменные могут находиться в слоте содержащий компонент [[DynamicVariableSpace (Component)|DynamicVariableSpace]] или в его дочерних слотах.
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>


Таким образом, пространство для переменных находящиеся в корневом слоте мира можно использовать из любого места, но пространство для переменных на вашем аватаре можно использовать из всех слотов, что хранятся в вашем аватаре.
=== <span lang="en" dir="ltr" class="mw-content-ltr">Dynamic fields</span> ===


=== Переменные ===
<div lang="en" dir="ltr" class="mw-content-ltr">
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:
</div>


Самый простой способ использовать динамические переменные - это использовать компоненты [[Component:DynamicValueVariable|DynamicValueVariable<T>]] и [[Component:DynamicReferenceVariable|DynamicReferenceVariable<T>]]. Они предназначены для значимых (int, float, String, etc.) и ссылочных (Slot, User, etc.) типов, соответственно. Компонент [[Component:DynamicTypeVariable|DynamicTypeVariable]] существует для хранения [[Type:Type|типов]].
* <span lang="en" dir="ltr" class="mw-content-ltr">[[Component:DynamicField|DynamicField]], for working with [[Value Types|value types]].</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">[[Component:DynamicReference|DynamicReference]], for working with [[Reference Types|reference types]].</span>
Эти компоненты хранят значение или непосредственно ссылку на него. Если два компонента содержат одинаковые имена, то они будут иметь одинаковое содержимое.
* <span lang="en" dir="ltr" class="mw-content-ltr">[[Component:DynamicTypeField|DynamicTypeField]], for working with the [[Type:Type|Type]] type.</span>


=== Поля ===
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>


Если вы хотите использовать существующее поле или ссылку на него в качестве содержимого динамической переменной, вы можете использовать компоненты [[Component:DynamicField|DynamicField<T>]] или [[Component:DynamicReference|DynamicReference<T>]]. Вместо того чтобы сохранять что-либо напрямую, они указывают на поле, содержащее значение или ссылочный тип, соответственно.
=== <span lang="en" dir="ltr" class="mw-content-ltr">Naming restrictions</span> ===


Как и в случае с переменными, существует вариант для полей содержащие <code>Тип</code> : [[Component:DynamicTypeField|DynamicTypeField]]
<div lang="en" dir="ltr" class="mw-content-ltr">
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>), space (<code> </code>), and hyphen (<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 aforementioned exceptions.
</div>


<span lang="en" dir="ltr" class="mw-content-ltr">(TODO: clarify value vs. reference types; I think this isn't fully correct)</span>
== <span lang="en" dir="ltr" class="mw-content-ltr">Binding</span> ==


=== Drivers ===
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>


Вы можете использовать содержимое динамической переменной для управления поля или ссылкой, используя компоненты [[Component:DynamicValueVariableDriver|DynamicValueVariableDriver<T>]] и [[Component:DynamicReferenceVariableDriver|DynamicReferenceVariableDriver<T>]].
=== <span lang="en" dir="ltr" class="mw-content-ltr">Direct vs. indirect binding</span> ===


=== <span lang="en" dir="ltr" class="mw-content-ltr">Unlisted types</span> ===
<div lang="en" dir="ltr" class="mw-content-ltr">
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''.
</div>


<span lang="en" dir="ltr" class="mw-content-ltr">When creating a dynamic variable component, you will be given a list of "common types". If the type you seek is not in that list, you will have to enter it by hand. See [[Complex Types in Components]].</span>
<div lang="en" dir="ltr" class="mw-content-ltr">
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>.
</div>


=== <span lang="en" dir="ltr" class="mw-content-ltr">Binding</span> ===
=== <span lang="en" dir="ltr" class="mw-content-ltr">Binding examples</span> ===


<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
Creating, duplicating, or moving a dynamic variable requires '''binding''' that variable to a space.
In the following setup:
This is a process that happens automatically but isn't perfect yet. (see [[#Warning|Warning]])
</div>
</div>
└─ Foo - Variable Space "test"
    └─ Bar - Variable Space "test2"
      └─ Baz - Dynamic Variable "test/var"


<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
In essence it starts searching for a matching dynamic variable space at the slot of the dynamic variable.
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>.
If there is no match, it tries the parent slot, its parent etc. until a matching space is found.
Variables with explicitly given space names only match spaces with the same name.
Variables without explicit space name match all spaces that are not <code>OnlyDirectBinding</code>
</div>
</div>
=== <span lang="en" dir="ltr" class="mw-content-ltr">Binding delay warning</span> ===


<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
The following image will demonstrate the differences in binding:
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:
</div>
</div>
* <span lang="en" dir="ltr" class="mw-content-ltr">[https://github.com/Yellow-Dog-Man/Resonite-Issues/issues/1394 Creating new dynamic reference variables].</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">[https://github.com/Yellow-Dog-Man/Resonite-Issues/issues/2717 Deleting dynamic variables].</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">Changing the name of a dynamic variable or dynamic variable space.</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">Any instance where there is a dynamic variable space change without a component update, such as:</span>
** <span lang="en" dir="ltr" class="mw-content-ltr">[https://github.com/Yellow-Dog-Man/Resonite-Issues/issues/1690 duplicating slots containing a dynamic variable space].</span>
** <span lang="en" dir="ltr" class="mw-content-ltr">Reparenting a slot containing a dynamic variable under a new dynamic variable space.</span>


<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
[[File:Dynamic_Variables.svg|border|an image demonstrating that dynamic variable spaces with enabled OnlyDirectBinding are ignored except by variables which have explicitly declared the same space name]]
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.
* I: <code>A</code> is connected to <code>Inner</code> because <code>Inner</code> is <u>not</u> <code>OnlyDirectBinding</code>.
* II/III: Both variables are bound explicitly.
* IV: <code>A</code> ignores <code>Inner</code> because <code>Inner</code> is <code>OnlyDirectBinding</code>.
* V/VI: Both variables are bound explicitly.
* VII/IX: There is no matching dynamic variable space. Both variables are not bound.
* VIII: The variable is explicitly bound to <code>World</code>.
* I and II share the same value.
* III, IV and VI share the same value.
</div>
</div>


== <span lang="en" dir="ltr" class="mw-content-ltr">Interfacing</span> ==


{{#mermaid:flowchart BT
<div lang="en" dir="ltr" class="mw-content-ltr">
    subgraph outside
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.
       
</div>
        InvalidSpace["Invalid, not linked"]
        RootSlot
    end
    subgraph RootSlot [RootSlot]
        World["Space #34;World#34;"]
        color["World/Color1"] --> World
       
       
        UserRootSlot
       
    end
    subgraph UserRootSlot
        UserSpace["Space #34;User#34;"]
        AvatarRootSlot
        V["User/Voice"] --> UserSpace
        V2["User/Color"] --> UserSpace
    end
   
    subgraph AvatarRootSlot
        avatar["User/Avatar"]
        avatarworld["World/Avatar.U-myid"]
        hips["HipsSlot"] --> AvatarSpace
        invalid["None/float"]
       
        AvatarSpace["Space #34;Avatar#34;"]
       
        Flux2_node0(["DynamicVariableInput#60;ColorX#62;#12;#34;World/Color1#34;"])
        Flux2_node0 -. points to .-> color
    end
   


   
=== <span lang="en" dir="ltr" class="mw-content-ltr">Reading dynvars</span> ===
    avatarworld --> World
    avatar --> UserSpace
    subgraph Flux1
        Flux1_node0(["User Root Slot"]) -. automatically points to .-> UserRootSlot
        Flux1_node1(["Read #34;User/Avatar#34;"]) --> Flux1_node0
        Flux1_node1 -. points to .-> AvatarRootSlot
        Flux1_node2(["Read Variable #34;Avatar/Hips#34;"]) --> Flux1_node1
        Flux1_node2 -. points to .-> hips
    end
    invalid --> InvalidSpace
   
   
   
   
}}


=== <span lang="en" dir="ltr" class="mw-content-ltr">Warning</span> ===
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>


<span lang="en" dir="ltr" class="mw-content-ltr">In a few use-cases binding may take a small amount of time, before which the dynamic variable can appear to be present, but not be readable or writable. Therefore, if you create a dynamic variable using the [[ProtoFlux:Create Dynamic Variable|Create Dynamic Variable]] or [[ProtoFlux:Write Or Create Dynamic Variable|Write Or Create Dynamic Variable]] ProtoFlux Node, or cause it to be duplicated using the [[ProtoFlux:Duplicate Slot |Duplicate Slot]] ProtoFlux Node, or cause it to be moved using the [[ProtoFlux:Set Parent|Set Parent]] ProtoFlux Node, you may find it necessary to add an [[ProtoFlux:Updates Delay|Updates Delay]] or [[ProtoFlux:Updates Delay With Value|Updates Delay With Value]] ProtoFlux Node afterwards in order to ensure the dynamic variables have been bound by the time you use them. A delay of 1 to 3 updates usually suffices.</span>
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>


TODO: more precise list of problematic uses cases (e.g. <code>Duplicate Slot</code> of a whole space works totally fine.)
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>


== <span lang="en" dir="ltr" class="mw-content-ltr">Example Applications</span> ==
=== <span lang="en" dir="ltr" class="mw-content-ltr">Driving from dynvars</span> ===


The following examples will demonstrate a few use-cases of dynamic variables.
<div lang="en" dir="ltr" class="mw-content-ltr">
There is a focus on how to use them to create separate objects
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.
</div>


=== <span lang="en" dir="ltr" class="mw-content-ltr">Modules within an object</span> ===
=== <span lang="en" dir="ltr" class="mw-content-ltr">Writing to dynvars</span> ===


<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
Dynamic variables within an object allow modularization.
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.
Different modules would be connected via dynamic variables within the space located at the root of the object.
If all hard-coded references going in or out of a module are eliminated you can replace it with a different variant/version without additional setup.
Module-local variables can be created with a dynamic variable space located at the root of the module. Use appropriately named spaces to differentiate between the two.
To make available variables more obvious to other people - including you in 6 months - it is recommended to place all dynamic variables within a dedicated slot hierarchy.
</div>
</div>
=== <span lang="en" dir="ltr" class="mw-content-ltr">Driving dynvars</span> ===


<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
Commonly used names for such hierarchies are:
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]].
* DV
* DynVar
* Vars
* etc.
</div>
</div>
=== <span lang="en" dir="ltr" class="mw-content-ltr">Writing/driving delay warning</span> ===
<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
A modular object could look like this:
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.
* Project (Space: <code>MyProject</code>)
** Dynamic Variables
*** projectVar: String (Dynamic variable <code>projectVar</code> of type [[Type:String|String]])
** Module: UI (i.e. [[Component:MeshRenderer|MeshRenderer]] or [[Component:Canvas|Canvas]] plus [[Collider|Colliders]]), may contain:
*** Dynamic Variable Drivers → Visuals
*** [[:Category:Components:Common_UI:Button_Interactions|Button Interactions]] → Dynamic Variables
*** [[:Category:Components:Common_UI:Editors|Editors]] ↔ Dynamic Variables
** Module: Logic (Space: <code>Logic</code>)
*** Dynamic Variables
**** logicVar: int (Dynamic variable <code>logicVar</code> of type [[Type:Int|int]])
**** MyProject/Logic: Slot (Dynamic variable <code>Logic</code> of type [[Slot]] for space <code>MyProject</code>, driven with reference to Slot of module to make it accessible to other modules)
*** [[ProtoFlux]] (may read, write or even drive dynamic variables of space <code>MyProject</code>)
</div>
</div>


===  <span lang="en" dir="ltr" class="mw-content-ltr">Configurable objects</span>  ===
<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
Dynamic variables make it possible to access other object's properties.
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.
</div>
</div>
== <span lang="en" dir="ltr" class="mw-content-ltr">Best practices</span> ==


<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
Assuming objects with a dynamic variable space, a collider and a string variable named <code>Description</code> you could then create a separate tool that reads and displays the <code>Description</code> of the object it is pointed at. The same tool could be extended to edit descriptions.
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:
</div>
</div>
* <span lang="en" dir="ltr" class="mw-content-ltr">It is highly recommended to have only one instance of a dynamic variable (dynamic variable component with the same name and bound to the same space) at any given time.</span>
** <span lang="en" dir="ltr" class="mw-content-ltr">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.</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">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.</span>
** <span lang="en" dir="ltr" class="mw-content-ltr">Using <code>OnlyDirectBinding</code> on a DynamicVariableSpace strictly enforces this behavior, which can prevent misbindings and catch errors earlier.</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">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.</span>
** <span lang="en" dir="ltr" class="mw-content-ltr">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.</span>
== <span lang="en" dir="ltr" class="mw-content-ltr">Default spaces</span> ==


<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
The same concept can also be applied to template slots used within a project.
As of the time of writing, there are three dynamic spaces that exist "by default":
Their instances can be interacted with using dynamic variables.
</div>
</div>
=== <span lang="en" dir="ltr" class="mw-content-ltr">World/User variables</span> ===
 
* <span lang="en" dir="ltr" class="mw-content-ltr"><code>World</code>, which exists under the [[Root]] of any created world. This space is marked as <code>OnlyDirectBinding</code>.</span>
** <span lang="en" dir="ltr" class="mw-content-ltr">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.</span>
* <span lang="en" dir="ltr" class="mw-content-ltr"><code>User</code>, which exists under every [[User]]'s [[ProtoFlux:User Root Slot|User Root Slot]]. This space is <code>OnlyDirectBinding</code>.</span>
** <span lang="en" dir="ltr" class="mw-content-ltr">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.</span>
* <span lang="en" dir="ltr" class="mw-content-ltr"><code>Dash</code>, which exists under the slot containing the [[Component:UserspaceRadiantDash|UserspaceRadiantDash]] in [[userspace]].</span>
 
== <span lang="en" dir="ltr" class="mw-content-ltr">Example usage</span> ==
 
=== <span lang="en" dir="ltr" class="mw-content-ltr">One contained system</span> ===
 
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>
 
<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
There are already pre-made dynamic variable spaces:
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.
* <code>World</code> on [[Root]] (<code>OnlyDirectBinding</code>)
* <code>User</code> on each [[Component:UserRoot|User Root]] Slot (<code>OnlyDirectBinding</code>)
* <code>Dash</code> on Slot of [[Component:UserspaceRadiantDash|UserSpaceRadiantDash]]
They can be used for states that are shared by many objects (i.e. day/night toggle, performance) or to broadcast information into the world. ([[BeatLink]], library objects like the [[Redprint]] manager)
</div>
</div>
[[File:Dynamic Variable Example Internal System.webp|800px]]
=== <span lang="en" dir="ltr" class="mw-content-ltr">Controlling another system</span> ===


<div lang="en" dir="ltr" class="mw-content-ltr">
<div lang="en" dir="ltr" class="mw-content-ltr">
See [[Dynamic Variable Naming Standard]] for a more detailed listing.
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>.
</div>
</div>
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>
== <span lang="en" dir="ltr" class="mw-content-ltr">See also</span> ==
* [[Dynamic Impulses]] <span lang="en" dir="ltr" class="mw-content-ltr">for a similar concept with [[ProtoFlux]] [[Impulses]]</span>
* [[Cloud Variables]] <span lang="en" dir="ltr" class="mw-content-ltr">for a type of variable stored on the cloud and accessible everywhere.</span>

Latest revision as of 23:25, 25 April 2025

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 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.

For a quick-start guide on using dynamic variables, the How To Use Dynamic Variables page serves to be a how-to.

Overview

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.

Variable spaces

By adding a 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 not nested with respect to parent spaces in any form, meaning it is advised to make unique names for variable spaces.

Dynamic variables

To create a dynamic variable, attach any one of the following components to a slot that is part of a variable space:

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.

Dynamic fields

It is possible to "transform" an existing IField on a component into a dynamic variable. This is done by any one of the following components:

Upon attaching the component and dragging the field to convert into TargetField, the pointed field can then be interfaced with like any other dynamic variable.

Naming restrictions

The names of dynamic variables and dynamic variable spaces must not contain symbols, punctuation, or whitespace, except for period (.), underscore (_), space ( ), and hyphen (-). To check if a character is unable to be used in a dynamic variable name, one can use the Is Symbol, Is Punctuation, and Is White Space ProtoFlux nodes, taking care of the aforementioned exceptions.

Binding

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 ValueField. A dynamic variable will go through this binding process every time any part of the component changes.

Direct vs. indirect binding

When making a dynamic variable, the VariableName of a dynamic variable component can be one of the following two forms: VariableName or VariableSpaceName/VariableName. 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 OnlyDirectBinding set to True. If a dynamic variable is directly binding, it will bind to the first dynamic variable space that matches VariableSpaceName.

Binding examples

In the following setup:

└─ Foo - Variable Space "test"
   └─ Bar - Variable Space "test2"
      └─ Baz - Dynamic Variable "test/var"

The dynamic variable test/var will bind to the variable space test. If the variable was instead named var, it will bind to the variable space test2. If the variable was named var and test2 has OnlyDirectBinding enabled, it would bind to test.

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 Delay Updates of 2 or more updates. These include:

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 Read Dynamic Variable node and Dynamic Variable Input nodes exist to read dynamic variables from a slot.

The Read Dynamic Variable node takes in a Source Slot and a Path variable name, and is marked as ContinuouslyChanging. The Dynamic Variable Input node uses a Global for Path 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 Value or Reference 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 DynamicValueVariableDriver or 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 Write Dynamic Variable or 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 crucial that all 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 and bound to the same space) 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 OnlyDirectBinding 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 (.) 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 User/Avatar.Systems.Grabbable.Enabled, User/Avatar.Blendshapes.Blep.MaxClamp, User/Avatar.Systems.Flight.Drag, etc.


Default spaces

As of the time of writing, there are three dynamic spaces that exist "by default":

  • World, which exists under the Root of any created world. This space is marked as OnlyDirectBinding.
    • 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.
  • User, which exists under every User's User Root Slot. This space is OnlyDirectBinding.
    • 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.
  • Dash, which exists under the slot containing the 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 Dynamic Variable Input nodes for performance sake.

Controlling another system

Say that you want to make an external controller for a system on an avatar. This system uses the default User dynamic variable space and namespaces itself with User/CoolAvatarSystem..

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 User Root Slot node. Reading with this system should be done with the 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. User/CoolAvatarSystem.Enabled being a bool). Plugging the previously obtained slot into the Source 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