-
Notifications
You must be signed in to change notification settings - Fork 21
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
Handle SimpleSpawner compatibility #290
Conversation
func _set_name(value): | ||
var new_name = _renamed_blocks.get(value) | ||
if new_name: | ||
print("Migrating block %s to new name %s" % [value, new_name]) | ||
name = new_name | ||
if Engine.is_editor_hint(): | ||
EditorInterface.mark_scene_as_unsaved() | ||
else: | ||
name = value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy with just this as a place to start. Two thoughts for the distant future:
- There's a danger that the old name is now unusable in this case, so we might want to couple it with a version number or something.
- This requires that we maintain a list of renamed blocks in the core plugin code. That's an issue for blocks added outside of the core plugin. Those aren't a thing at the moment, but a different (albeit fussier) approach could be a
renamed_from
field in block_definition.gd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, this only handles pure renames. If you changed the arguments or something like that, this wouldn't work. We do have a version number in the higher level BlockScriptSerialization, but it's not available here right now.
@wjt poked at adding aliases for blocks in #284. I didn't look closely at his approach there, but it was my initial suggestion 😆
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I actually did change the argument name in the "set frequency period" block...
func _get_property_list() -> Array[Dictionary]: | ||
return [ | ||
{ | ||
# spawn_frequency was renamed to spawn_period | ||
"name": "spawn_frequency", | ||
"class_name": &"", | ||
"type": TYPE_FLOAT, | ||
"hint": PROPERTY_HINT_NONE, | ||
"hint_string": "", | ||
"usage": PROPERTY_USAGE_NONE, | ||
}, | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh, interesting!
"spawn_frequency": | ||
return spawn_period | ||
_: | ||
return null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm somewhat disturbed that returning null means "handle the property normally" (ergo a property can't just be null), but, indeed. This is fun!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, null
as sentinel and value is always fun.
"simplespawner_set_spawn_period", | ||
] | ||
|
||
var scene: PackedScene = load("res://tests/data/simple_spawner_compat.tscn") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh, it's marvellous to have an example of a test with a scene file! Thank you for writing tests :)
@@ -16,7 +16,9 @@ func _init(p_name: StringName = &"", p_children: Array[BlockSerialization] = [], | |||
|
|||
|
|||
# Block name backwards compatibility handling. | |||
const _renamed_blocks: Dictionary = {} | |||
const _renamed_blocks: Dictionary = { | |||
&"simplespawner_set_spawn_frequency": &"simplespawner_set_spawn_period", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess one could extend this so that the values are not just a string but a tuple of:
- new block name
- map from old parameter name to new parameter name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, something like that. An issue here is that we don't control the order that Godot sets the property in. So, name might not be migrated in this case. Here's an untested thing I came up with:
diff --git a/addons/block_code/serialization/block_serialization.gd b/addons/block_code/serialization/block_serialization.gd
index 4332b73..46ffd4b 100644
--- a/addons/block_code/serialization/block_serialization.gd
+++ b/addons/block_code/serialization/block_serialization.gd
@@ -6,7 +6,8 @@ const BlockSerialization = preload("res://addons/block_code/serialization/block_
@export var name: StringName:
set = _set_name
@export var children: Array[BlockSerialization]
-@export var arguments: Dictionary # String, ValueBlockSerialization
+@export var arguments: Dictionary: # String, ValueBlockSerialization
+ set = _set_arguments
func _init(p_name: StringName = &"", p_children: Array[BlockSerialization] = [], p_arguments: Dictionary = {}):
@@ -30,3 +31,35 @@ func _set_name(value):
EditorInterface.mark_scene_as_unsaved()
else:
name = value
+
+
+const _renamed_arguments: Dictionary = {
+ &"simplespawner_set_spawn_period": {
+ "new_frequency": "new_period",
+ },
+}
+
+
+func _set_arguments(value):
+ if not value is Dictionary:
+ return
+
+ var renamed_args = _renamed_arguments.get(name)
+ if not renamed_args:
+ # Try with the new block name if it hasn't been migrated yet.
+ var new_block_name = _renamed_blocks.get(name)
+ if new_block_name:
+ renamed_args = _renamed_arguments.get(new_block_name)
+
+ if renamed_args:
+ value = value.duplicate()
+ for old_arg in renamed_args.keys():
+ if not old_arg in value:
+ continue
+
+ var new_arg = renamed_args[old_arg]
+ print("Migrating block %s argument %s to new name %s" % [name, old_arg, new_arg])
+ value[new_arg] = value[old_arg]
+ value.erase(old_arg)
+
+ arguments = value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice!
If a block or its arguments has been renamed, loading a scene with the old block name will fail and the user will be required to replace the block. Handle renamed blocks and arguments by migrating the names to the new values in the serialization resources. In order for the setters to run in the editor, the classes need to be tool scripts. https://phabricator.endlessm.com/T35547
Renaming the spawn_frequency properties and blocks will break any existing scene since those are serialized. Migrate the spawn_frequency property by providing it as an additional unstored property that simply wraps spawn_period. Migrate the block names and arguments using the serialization renaming support from the previous commit. A test using a scene created before the renaming is provided to verify the migration. While this does not handle the worst compatibility breakage, it can be used as a model for other migrations. Note that the migration stub in MainPanel is likely too late for any legitimate fixes. Any migration likely needs to happen when the scene is loaded and instantiated, which is before MainPanel gets a hold of the objects. https://phabricator.endlessm.com/T35547
4a57cf8
to
6191c88
Compare
I added block argument rename handling as described above. The required |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is fantastic to see this migration happening, with unit test even!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the comment is the major concern. However, it is good to land this PR as try run.
This adds handling for the SimpleSpawner compatibility breakage in #272. While I don't expect it to significantly affect our users, I took it as a chance to investigate how these issues could be handled. It shows how to handle a renamed property and a renamed block. I added everyone as reviewers to see what I came up with.
The added test validates the migration, but you can also see it for yourself by loading the
res://tests/data/simple_spawner_compat.tscn
scene in the editor. It should show that the scene is unsaved. After saving you'll see the names in the scene file have been updated appropriately.https://phabricator.endlessm.com/T35547