Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BlockScriptSerialization: Re-generate block definition for object property blocks #316

Merged
merged 5 commits into from
Nov 20, 2024

Conversation

starnight
Copy link
Contributor

The drag & drop object property's blocks disappear after save & re-open the Godot project. And, shows error:

core/variant/variant_utility.cpp:1092 - Cannot construct block from null block definition. res://addons/block_code/ui/block_canvas/block_canvas.gd:348 - Invalid call. Nonexistent function 'set_parameter_values_on_ready' in base 'Nil'.

It is because the object property blocks are not the predefined blocks in the catalog. So, Block Coding plugin cannot find the block definition from the catalog when places the object property blocks into the cavas via _block_to_ast_node() after re-open the project.

Therefore, introduce _get_obj_property_block_definition() generating object property's getter/setter block definition for get_block_definition(). Besides, it also needs the object property's value type. So, pass the AST node's arguments which is a Dictionary and may contain the "value" feild into get_block_definition(), too.

Fixes: 89beea9 ("BlockCanvas: Implement drag & drop the node's property from Inspector") https://phabricator.endlessm.com/T35649

Comment on lines 138 to 139
block_name = &"%s_set_%s" % [object_name, property_name]
block_definition = _context.block_script.get_obj_property_block_definition(block_name, property_value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like how we construct a string and then pass it to a function which immediately splits it back up into the 3 pieces we had here.

Comment on lines 93 to 97
var property_value = arguments.get("value", null)
block_definition = get_obj_property_block_definition(block_name, property_value)
if block_definition != null:
return block_definition
Copy link
Contributor

@dylanmccall dylanmccall Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried building this feature around _get_parameter_block_definition? There's a bit of overlap here. In fact they're solving the same fundamental problem: it's a type of block where we generate a block definition when we need it, based on an identifier such as a property name.

As it exists, that function only generates the one kind of value block, which is an output parameter for another block. (It needs a base block definition so it knows what output parameters are defined). So it needs extra help, but it should be feasible to extend it similarly to what you have in get_obj_property_block_definition, so our parameter blocks would start looking like MyType_get:property.

I think there's a few ways to make that work. I think ideally we'd have _get_base_block_definition generate a plain MyType_get and MyType_set block for whatever type is at hand, and we'd have something in BlockDefinition that _get_parameter_block_definition calls which would be like "give me a parameterized version of this block definition with foo set to bar".

I realize I'm asking for a fair bit of extra machinery here so I understand if we just want to land a bug fix, but I did have this case in mind when I was adding parameter block definitions. Sorry I didn't really document it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me study this more.

Copy link
Contributor Author

@starnight starnight Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I try some scenario as examples: the "speed" and "floor_stop_on_slope" properties

  • "Object_get:speed" will be split for _get_parameter_block_definition()

    • block_name: "Object_get"
    • parameter_name: "speed". Its type is Vector2
  • "Object_get:floor_stop_on_slope" will be split for _get_parameter_block_definition()

    • block_name: "Object_get"
    • parameter_name: "floor_stop_on_slope". Its type is boolean

Because of the different parameter_names, we have to generate different base block definitions having corresponding display_template for parent_out_parameters.has(parameter_name) dynamically, which means the object property block's logic is different from the parameter block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides, it loses the property's variant type after close the project actually. Because, the property's variant type is not saved in the resource of .tscn. The setter block's variant type can be restored by the stored value. However, getter block does not have the value information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the limitation of the parameter_name and the getter's variant type, I think we cannot generate blocks via _get_parameter_block_definition() now. So, I go back to the _get_obj_property_block_definition() in the updated commits.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for coming in late here, but the property doesn't need to be encoded in the scene file because it's part of the object it's being applied to. It's more work, but you can call get_property_list on the target object to determine the type. I think that would be more elegant than encoding the type in the block name. It wouldn't be that hard to get the property list once at the beginning of deserializing and then find the appropriate property as the definitions are loaded.

@starnight starnight force-pushed the T35649-fix-drag-drop-obj-property branch from ea8226b to abc49ae Compare November 11, 2024 10:34
@starnight starnight marked this pull request as draft November 11, 2024 10:57
@starnight starnight force-pushed the T35649-fix-drag-drop-obj-property branch from abc49ae to 0cc6dcd Compare November 12, 2024 06:53
Extract the getter and setter block definition code from
get_variable_block_definitions() as
get_variable_getter_block_definition() and
get_variable_setter_block_definition(). They can be used by more places
latter.
@starnight starnight marked this pull request as ready for review November 12, 2024 08:49
@starnight starnight requested review from wjt and dylanmccall November 12, 2024 08:54
@starnight starnight force-pushed the T35649-fix-drag-drop-obj-property branch 2 times, most recently from 9a840c2 to 01a44db Compare November 13, 2024 08:30
@starnight
Copy link
Contributor Author

I encode the property's variant type into block name finally. So, after reopen the project, block coding can restore the variant type information of the property's getter block. Here is the demo video:
https://github.com/user-attachments/assets/82e9d524-0d5a-4a1d-947d-7af17a51314a

@starnight starnight marked this pull request as draft November 13, 2024 11:07
@manuq
Copy link
Contributor

manuq commented Nov 13, 2024

Trying to get the property name from the block name like foo from get_var_foo is not ideal. The VariableDefinition in addons/block_code/code_generation/variable_definition.gd already has name and type.

Now as discussed, reusing the variables in BlockScriptSerialization is a problem because properties shouldn't be declared as var in the script. And checking ClassDB.class_get_property_list() doesn't work for custom node's properties like the Simple ones. Two alternatives occur to me to separate variables from properties:

  • Enhace VariableDefinition with an is_property boolean field
  • Add @export var properties: Array[VariableDefinition] to the BlockScriptSerialization resource

@dbnicholson
Copy link
Member

Now as discussed, reusing the variables in BlockScriptSerialization is a problem because properties shouldn't be declared as var in the script. And checking ClassDB.class_get_property_list() doesn't work for custom node's properties like the Simple ones. Two alternatives occur to me to separate variables from properties:

  • Enhace VariableDefinition with an is_property boolean field
  • Add @export var properties: Array[VariableDefinition] to the BlockScriptSerialization resource

I don't see how adding an is_property field helps here since you still need to figure out what type of property it is. I'm also not crazy about exporting the property list since it's bound to get out of sync with the target node's properties.

I came up with a 3rd option. What we really want is to look at the target node's instance, but that's not available in the serialization since it's a child of the node. However, we can find the node from the scene tree because we know that the parent BlockCode node's block_script property will be set to this instance. In editor-only code without safety:

var root := EditorInterface.get_edited_scene_root()
for node in root.find_children("*", "BlockCode"):
    if node.block_script == self:
        var target_properties := node.get_parent().get_property_list()

https://github.com/endlessm/godot-block-coding/tree/block-script-target adds some helpers to do it. When deserializing property block definitions, you'd do something like:

var property := _get_target_property("thename")
var variant_type: Variant.Type = property["type"]

@dbnicholson
Copy link
Member

Another simpler possibility is that we have the BlockCode just tell BlockScriptSerialization about itself when it's instantiated:

diff --git a/addons/block_code/block_code_node/block_code.gd b/addons/block_code/block_code_node/block_code.gd
index 659719c..d08e89a 100644
--- a/addons/block_code/block_code_node/block_code.gd
+++ b/addons/block_code/block_code_node/block_code.gd
@@ -5,7 +5,15 @@ extends Node
 
 const TxUtils := preload("res://addons/block_code/translation/utils.gd")
 
-@export var block_script: BlockScriptSerialization = null
+@export var block_script: BlockScriptSerialization = null:
+       set(value):
+               # If the script is being cleared, remove ourself from the existing script.
+               if value == null:
+                       if block_script:
+                               block_script.block_code_node = null
+               else:
+                       value.block_code_node = self
+               block_script = value
 
 
 func _init():
diff --git a/addons/block_code/serialization/block_script_serialization.gd b/addons/block_code/serialization/block_script_serialization.gd
index 5f3051e..e0847f0 100644
--- a/addons/block_code/serialization/block_script_serialization.gd
+++ b/addons/block_code/serialization/block_script_serialization.gd
@@ -28,6 +28,8 @@ const SCENE_PER_TYPE = {
 @export var generated_script: String
 @export var version: int
 
+var block_code_node: BlockCode
+
 var _available_blocks: Array[BlockDefinition]

With the BlockCode node in hand, you can do all the same things like get the target object with get_parent() and then sift through its properties with target.get_property_list().

@starnight
Copy link
Contributor Author

I came up with a 3rd option. What we really want is to look at the target node's instance, but that's not available in the serialization since it's a child of the node. However, we can find the node from the scene tree because we know that the parent BlockCode node's block_script property will be set to this instance. In editor-only code without safety:

var root := EditorInterface.get_edited_scene_root()
for node in root.find_children("*", "BlockCode"):
    if node.block_script == self:
        var target_properties := node.get_parent().get_property_list()

I was stuck at why I cannot get_parent(), which should be the BlockCode node's parent directly in the addons/block_code/serialization/block_script_serialization.gd until I learned block_script_serialization.gd is loaded as a property of the BlockCode node. Thank you @dbnicholson

@dbnicholson
Copy link
Member

I was stuck at why I cannot get_parent(), which should be the BlockCode node's parent directly in the addons/block_code/serialization/block_script_serialization.gd until I learned block_script_serialization.gd is loaded as a property of the BlockCode node. Thank you @dbnicholson

Just to clarify a bit. Only Nodes like BlockCode are part of the scene tree and can navigate to their ancestors or descendents using things like get_parent(). BlockScriptSerialization is a Resource, which is not a Node. Godot doesn't provide any sort of methods like "what other objects hold a reference to this object".

… drop object property

The object property's getter & setter blocks generating code is similar
to get_variable_(get|set)ter_block_definition(). So, reuse them within
get_property_(get|set)ter_block_definition() to generate property's
blocks including the description.

And, the block name will change to: "(get|set)_var_<property name>".

Fixes: 89beea9 ("BlockCanvas: Implement drag & drop the node's property from Inspector")
https://phabricator.endlessm.com/T35649
…ock code node and script

Build the bidirectional link between the block code node and the block
script. So, the block script can access this block node and the parent
node later easily.

https://phabricator.endlessm.com/T35649
@starnight starnight force-pushed the T35649-fix-drag-drop-obj-property branch from 01a44db to d63a843 Compare November 14, 2024 07:20
@starnight starnight requested a review from manuq November 14, 2024 07:22
@starnight starnight marked this pull request as ready for review November 14, 2024 07:25
@starnight
Copy link
Contributor Author

starnight commented Nov 14, 2024

As #316 (comment), I think having the bidirectional link between the BlockCode and BlockScriptSerialization is more readable and convenient in the future. So, fixed in the direction. Ready for review again!

Copy link
Member

@dbnicholson dbnicholson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes sense to me. The only thing I'd want to see addressed is the change to pass in the block arguments when serializing. That doesn't seem necessary if you're already using the property information for the property getter block.

static func get_property_getter_block_definition(variable: VariableDefinition) -> BlockDefinition:
var block_def := get_variable_getter_block_definition(variable)
block_def.description = "The %s property" % variable.var_name
return block_def
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit unfortunate that these property blocks will be in the Variables section, but I can't think of a better place at the moment.

@@ -28,6 +28,8 @@ const SCENE_PER_TYPE = {
@export var generated_script: String
@export var version: int

var block_code_node: BlockCode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A potential future improvement here would be to cache the parent properties when set so that _get_parent_node_property_info didn't have to iterate the entire property list every time. I don't think that's worth it right now, though.

@starnight starnight force-pushed the T35649-fix-drag-drop-obj-property branch from d63a843 to f738fff Compare November 19, 2024 10:41
@starnight starnight marked this pull request as draft November 19, 2024 11:18
@starnight starnight force-pushed the T35649-fix-drag-drop-obj-property branch from f738fff to 8e9119b Compare November 19, 2024 11:21
@starnight starnight marked this pull request as ready for review November 19, 2024 11:21
Copy link
Member

@dbnicholson dbnicholson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks like it should work! 2 final nitpicks, but feel free to merge after that.

…perty blocks

The drag & drop object property's blocks disappear after save & re-open
the Godot project. And, shows error:

core/variant/variant_utility.cpp:1092 - Cannot construct block from null block definition.
res://addons/block_code/ui/block_canvas/block_canvas.gd:348 - Invalid call. Nonexistent function 'set_parameter_values_on_ready' in base 'Nil'.

It is because the object property blocks are not the predefined blocks
in the catalog. So, Block Coding plugin cannot find the block definition
from the catalog when places the object property blocks into the cavas
via _block_to_ast_node() after re-open the project.

Therefore, introduce _get_obj_property_block_definition() generating
object property's getter/setter block definition for
get_block_definition(). Besides, it also needs the object property's
value type. So, visit the block code node's parent node to finding the
property by _get_parent_node_property_info() to have the type.

https://phabricator.endlessm.com/T35649
Godot allows drag any thing. However, if it is a property, block code
script only manipulates the properties of BlockCode node's parent node.
So, when drag a property, only allow drop the parent node's property.

Fixes: 89beea9 ("BlockCanvas: Implement drag & drop the node's property from Inspector")
https://phabricator.endlessm.com/T35649
@starnight starnight force-pushed the T35649-fix-drag-drop-obj-property branch from 8e9119b to 5c3358d Compare November 20, 2024 02:40
@starnight
Copy link
Contributor Author

Thank you guys' help! I merge this!

@starnight starnight merged commit 3cfcf76 into main Nov 20, 2024
3 checks passed
@starnight starnight deleted the T35649-fix-drag-drop-obj-property branch November 20, 2024 02:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants