You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Describe the problem or limitation you are having in your project
In the above situations, I've encountered problems where I'm designing a certain kind of node hierarchy interaction that varies across many use cases but has a common pattern of boilerplate code/expectations with just enough similarities that it feels like it should just be a core feature.
In these situations, there is some context node with some cascading property (named whatever). Then have some target descendant node that automatically has access to whatever cascading property they specifically are looking to receive from their above context (usually by having a local parameter populated with the value).
The ideal, loosely-coupled relationship is where some descendant says, "I want x", and some ancestor says, "I'm gonna pass along x to whoever wants it.", and then all of the "work" of connecting the two is handled automatically.
Whether the cascading property on the targetrequires initialization varies between use cases.
Whether the cascading property is statically typed varies between use cases.
Whether the cascading property's availability can be statically evaluated at editor-time varies between use cases (but usually, it can).
If so, and it is required, then the target node should have a configuration warning if the value is inaccessible, sometimes even across scene boundaries.
If so, and it is statically typed, then the target node should have a configuration warning if the provided value would not match the type correctly.
Usually, you want the ancestor to automatically populate the cascading property immediately after the target node's instantiation, i.e. before the target's descendants have been created. Sometimes though, the context itself may need to delay initializing the cascading property until after it is ready (leads to targets using the ready signal or maybe the context doing a propagate_call to set the value, etc.).
Whether the cascading property should be assigned only after the subtree has been attached to the SceneTree varies between use cases. That is, a scene may be instantiated manually (do it now?) and then later attached to the SceneTree (or now?).
The cascading property automatically updates if the target node is reparented and the new hierarchy does not fall under the same context.
If reparented to a hierarchy without a context that can provide the value, and if the cascading property is required, then the application should log an error.
The most common solutions to this problem are...
Let the context be responsible for adding/removing target nodes and updating cascading properties.
Have a singleton track contexts, instantiate targets, and mediate updates for cascading properties.
Have a group of which context nodes are a member. Upon entering the tree, have target nodes scan ancestors until one in the group is found and cache the reference. Then get whatever data is needed from that node manually.
There are a lot of different approaches to it not even mentioned above (thanks to Godot's flexibility), but there are consistent issues with all of them.
Often, the logic has to be tied to very specific data types (custom logic for (re)parenting) that should be shared between many classes (annoying workaround: delegate to external pure functions and reuse across many small class declarations).
Tooling is complicated.
Configuration warnings for different target node types result in problem 1.
Most strategies above involve customizing/wrapping the instantiation of the node, but that would preclude adding nodes the traditional way (not desirable).
You could alternatively use an EditorPlugin that constantly monitors the state of all open scene tabs and reacts to changes by re-validating all the relevant nodes, but Godot doesn't really have a scriptable property metadata/annotation system, so you'd have to hack things with set_meta/get_meta and it would be pretty finicky/difficult to maintain.
Describe the feature / enhancement and how it helps to overcome the problem or limitation
I envision a built-in cascading properties feature that would automatically perform the above operations on behalf of users to bypass any need for re-implementation across many types and simplify common boilerplate use cases. This could be handled via annotations (now that GDScript and C# have them). A context-key could be associated with the property metadata to link up which values should be passed along. This would prevent mixed contextual hierarchies from attempting to populate values related to other types of contextual relationships.
Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
Have an @GlobalScope flags enum to define the settings:
enum {
CASCADE_REQUIRED=1, # must be passed alongCASCADE_STRICT=2, # must be the same data typeCASCADE_TARGET_INIT=4, # assign after child instantiationCASCADE_SOURCE_READY=8, # assign after ancestor is readyCASCADE_DEFAULT=CASCADE_REQUIRED|CASCADE_TARGET_INIT
}
Have the following two annotations:
# target: property# - Declares that the property can be cascaded to descendant cascade parameters# - If `p_key` is empty, then `p_key` is inferred from the property name.
@cascade(p_key: string="")
# target: property# - Declares that the property can be initialized by the first ancestor with a matching `p_key`.# - The annotation's bool params merely configure the cascade flag enum options.# - If `p_key` is empty, then `p_key` is inferred from the property name.# - If `p_is_required` is true, then if the property is empty by the `_ready` callback, log a config warning / runtime error.# - If `p_is_strict` is true, then if an identified cascade property is not the same type, log a config warning / runtime error.# - If `p_is_strict` is true and the parameter property is statically typed as `bool`, then ignore (no Nullable<bool>, so no meaning).
@cascade_parameter(p_key: string="", p_is_required: bool=false, p_is_strict: bool=true)
# Alt format where `p_key` is omitted and inferred
@cascade_parameter(p_is_required: bool=false, p_is_strict: bool=true)
# If you use @onready with it, it automatically# disables `CASCADE_TARGET_INIT` and enables `CASCADE_SOURCE_READY`@onready
@cascade_parameter
Example syntax where key is implied by property name, it is optional, and data type doesn't matter:
Manually defined contextual keys, value supplied from scene export.
Might have many different types of "display" containers that provide a category value, etc.
Might also have other kinds of values passed around hierarchically that you wouldn't want category-related stuff to conflict with.
# category.gdclass_nameCategoryDisplayextendsPanelContainer@export
@cascade("category_name")
varname:=""# descendant.gdextendsNode
@cascade_param("category_name")
@cascade_param("category_name", true) # optional second bool param for an `is_required` flagvarcontainer_category: Stringfunc_init():
pass# container_category is still nullfunc_ready():
pass# container_category is populated
It would need to be a system that would be capable of working at runtime without the Editor (just to pass along the value). The naive approach (but probably the best one?) would be to just make nodes with a cascade_param annotation search up through their ancestors to find one that declares a cascade annotation with the same key and then get the value to assign it to its own property. And then do the same thing any time it is reparented.
Once in the Editor though, the EditorData, to perform static evaluations, would likely need to cache which nodes in which scenes declare/request cascading properties and provide helper functions to perform validation checks. The SceneDock can then generically raise configuration warnings on any relevant node as needed.
Considering the ramifications of cascading properties on node relationships, I think it would also be important to add a separate list of cascading properties in the documentation, in between properties and signals, for each class so that they are clearly distinguished from other properties.
If this enhancement will not be used often, can it be worked around with a few lines of script?
As has been described, there are several different steps and details to the pattern, along with complex editor logic, and the logic is often something that occurs across many different types of hierarchical relationships in node trees, so the "few lines of script" that do get written are often duplicated across many different classes with slight alterations.
Is there a reason why this should be core and not an add-on in the asset library?
I could imagine some parts of this could be emulated in an addon/plugin combination if an annotation API were added to the core Object and/or Script interface(s) and if a few extra signals were added to the SceneDock to make tracking changes to it easier. But this is also a pattern that is central to node trees in the first place since every node has the potential to become a context for other nodes below it. It just converts what would otherwise have been a lot of boilerplate, tooling, and/or explicit searching/assigning into an implicit, configuration-based relationship that reduces boilerplate.
The text was updated successfully, but these errors were encountered:
Calinou
changed the title
Add cascading Node properties to auto-populate values and statically check ancestral dependencies.
Add cascading Node properties to auto-populate values and statically check ancestral dependencies
Aug 16, 2021
Describe the project you are working on
Writing code that involves interactive node hierarchies (my game, plugins, potential core features, answering questions online, etc.).
Describe the problem or limitation you are having in your project
In the above situations, I've encountered problems where I'm designing a certain kind of node hierarchy interaction that varies across many use cases but has a common pattern of boilerplate code/expectations with just enough similarities that it feels like it should just be a core feature.
In these situations, there is some
context
node with somecascading
property (named whatever). Then have sometarget
descendant node that automatically has access to whatevercascading
property they specifically are looking to receive from their abovecontext
(usually by having a local parameter populated with the value).The ideal, loosely-coupled relationship is where some descendant says, "I want
x
", and some ancestor says, "I'm gonna pass alongx
to whoever wants it.", and then all of the "work" of connecting the two is handled automatically.cascading
property on thetarget
requires initialization varies between use cases.cascading
property is statically typed varies between use cases.cascading
property's availability can be statically evaluated at editor-time varies between use cases (but usually, it can).target
node should have a configuration warning if the value is inaccessible, sometimes even across scene boundaries.target
node should have a configuration warning if the provided value would not match the type correctly.cascading
property immediately after the target node's instantiation, i.e. before the target's descendants have been created. Sometimes though, thecontext
itself may need to delay initializing thecascading
property until after it is ready (leads totargets
using theready
signal or maybe thecontext
doing apropagate_call
to set the value, etc.).cascading
property should be assigned only after the subtree has been attached to the SceneTree varies between use cases. That is, a scene may be instantiated manually (do it now?) and then later attached to the SceneTree (or now?).cascading
property automatically updates if the target node is reparented and the new hierarchy does not fall under the samecontext
.context
that can provide the value, and if thecascading
property is required, then the application should log an error.The most common solutions to this problem are...
context
be responsible for adding/removingtarget
nodes and updatingcascading
properties.contexts
, instantiatetargets
, and mediate updates forcascading
properties.context
nodes are a member. Upon entering the tree, havetarget
nodes scan ancestors until one in the group is found and cache the reference. Then get whatever data is needed from that node manually.There are a lot of different approaches to it not even mentioned above (thanks to Godot's flexibility), but there are consistent issues with all of them.
target
node types result in problem 1.set_meta
/get_meta
and it would be pretty finicky/difficult to maintain.Describe the feature / enhancement and how it helps to overcome the problem or limitation
I envision a built-in
cascading
properties feature that would automatically perform the above operations on behalf of users to bypass any need for re-implementation across many types and simplify common boilerplate use cases. This could be handled via annotations (now that GDScript and C# have them). Acontext-key
could be associated with the property metadata to link up which values should be passed along. This would prevent mixed contextual hierarchies from attempting to populate values related to other types of contextual relationships.Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
Have an
@GlobalScope
flags enum to define the settings:Have the following two annotations:
Example syntax where key is implied by property name, it is optional, and data type doesn't matter:
Manually defined contextual keys, value supplied from scene export.
Might have many different types of "display" containers that provide a category value, etc.
Might also have other kinds of values passed around hierarchically that you wouldn't want category-related stuff to conflict with.
It would need to be a system that would be capable of working at runtime without the Editor (just to pass along the value). The naive approach (but probably the best one?) would be to just make nodes with a
cascade_param
annotation search up through their ancestors to find one that declares acascade
annotation with the same key and then get the value to assign it to its own property. And then do the same thing any time it is reparented.Once in the Editor though, the EditorData, to perform static evaluations, would likely need to cache which nodes in which scenes declare/request cascading properties and provide helper functions to perform validation checks. The SceneDock can then generically raise configuration warnings on any relevant node as needed.
Considering the ramifications of cascading properties on node relationships, I think it would also be important to add a separate list of cascading properties in the documentation, in between properties and signals, for each class so that they are clearly distinguished from other properties.
If this enhancement will not be used often, can it be worked around with a few lines of script?
As has been described, there are several different steps and details to the pattern, along with complex editor logic, and the logic is often something that occurs across many different types of hierarchical relationships in node trees, so the "few lines of script" that do get written are often duplicated across many different classes with slight alterations.
Is there a reason why this should be core and not an add-on in the asset library?
I could imagine some parts of this could be emulated in an addon/plugin combination if an
annotation
API were added to the coreObject
and/orScript
interface(s) and if a few extra signals were added to the SceneDock to make tracking changes to it easier. But this is also a pattern that is central to node trees in the first place since every node has the potential to become a context for other nodes below it. It just converts what would otherwise have been a lot of boilerplate, tooling, and/or explicit searching/assigning into an implicit, configuration-based relationship that reduces boilerplate.The text was updated successfully, but these errors were encountered: