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

Implement Resource State Inheritance #86779

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions core/io/resource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,98 @@ String Resource::get_id_for_path(const String &p_path) const {
}
#endif

bool Resource::_property_can_revert(const StringName &p_name) const {
if (!inherits_state.is_valid()) {
return false;
}
bool value_valid;
Variant value = get(p_name, &value_valid);
if (!value_valid) {
return false;
}
Variant inherits_value = inherits_state->get(p_name, &value_valid);
if (!value_valid) {
return false;
}
return bool(Variant::evaluate(Variant::OP_NOT_EQUAL, value, inherits_value));
}

bool Resource::_property_get_revert(const StringName &p_name, Variant &r_property) const {
if (!inherits_state.is_valid()) {
return false;
}

bool value_valid;
Variant inherits_value = inherits_state->get(p_name, &value_valid);
if (value_valid) {
r_property = inherits_value;
}
return value_valid;
}

void Resource::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "resource_inherits") {
p_property.hint_string = get_class();
}
}

bool Resource::setup_inherits_state(const Ref<Resource> &p_resource) {
bool ret;
if (GDVIRTUAL_CALL(_setup_inherits_state, p_resource, ret)) {
return ret;
}

ERR_FAIL_COND_V_MSG(p_resource.is_valid() && !is_class(p_resource->get_class_name()), false, "Resources must be of (or class inherit) the same class when setting up state inheritance");

if (p_resource.is_valid()) {
copy_from(p_resource);
}
return true;
}

void Resource::set_inherits_state(const Ref<Resource> &p_resource) {
if (setup_inherits_state(p_resource)) {
inherits_state = p_resource;
}
}

Ref<Resource> Resource::get_inherits_state() const {
return inherits_state;
}

bool Resource::is_inherited_state_property_value_saved(const StringName &p_name, const Variant &p_value) const {
bool ret;
if (GDVIRTUAL_CALL(_is_inherited_state_property_value_saved, p_name, p_value, ret)) {
return ret;
}

bool default_value_valid;
Variant default_value = inherits_state->get(p_name, &default_value_valid);
if (!default_value_valid) {
return true; // Not a default value, so save.
}

if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, p_value, default_value))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the reason for skipping bool Variant::operator==(const Variant&) here? I don't understand why it's not written as p_value == default_value. Can you elaborate?

return false;
}

return true;
}

bool Resource::is_property_value_saved(const StringName &p_property, const Variant &p_value) const {
if (!inherits_state.is_valid()) {
Variant default_value = ClassDB::class_get_default_property_value(get_class(), p_property);

if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, p_value, default_value))) {
return false;
}

return true;
} else {
return is_inherited_state_property_value_saved(p_property, p_value);
}
}

void Resource::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_path", "path"), &Resource::_set_path);
ClassDB::bind_method(D_METHOD("take_over_path", "path"), &Resource::_take_over_path);
Expand All @@ -448,6 +540,9 @@ void Resource::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_local_to_scene"), &Resource::is_local_to_scene);
ClassDB::bind_method(D_METHOD("get_local_scene"), &Resource::get_local_scene);
ClassDB::bind_method(D_METHOD("setup_local_to_scene"), &Resource::setup_local_to_scene);
ClassDB::bind_method(D_METHOD("set_inherits_state", "base"), &Resource::set_inherits_state);
ClassDB::bind_method(D_METHOD("get_inherits_state"), &Resource::get_inherits_state);
ClassDB::bind_method(D_METHOD("is_property_value_saved", "property", "value"), &Resource::is_property_value_saved);
Copy link
Contributor

Choose a reason for hiding this comment

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

What would be the benefit of exposing is_property_value_saved to the scripting languages, at least with the given name? Would it not be better for users to know whether or not a given property's value is being inherited or not? That is the information users are after.

This may also be easily confused with @export or PROPERTY_EXPORT_STORAGE.


ClassDB::bind_method(D_METHOD("emit_changed"), &Resource::emit_changed);

Expand All @@ -459,12 +554,15 @@ void Resource::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resource_local_to_scene"), "set_local_to_scene", "is_local_to_scene");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_path", "get_path");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_name"), "set_name", "get_name");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "resource_inherits_state", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), "set_inherits_state", "get_inherits_state");

MethodInfo get_rid_bind("_get_rid");
get_rid_bind.return_val.type = Variant::RID;

::ClassDB::add_virtual_method(get_class_static(), get_rid_bind, true, Vector<String>(), true);
GDVIRTUAL_BIND(_setup_local_to_scene);
GDVIRTUAL_BIND(_setup_inherits_state, "resource");
GDVIRTUAL_BIND(_is_inherited_state_property_value_saved, "property", "value");
}

Resource::Resource() :
Expand Down
15 changes: 15 additions & 0 deletions core/io/resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class Resource : public RefCounted {
String path_cache;
String scene_unique_id;

Ref<Resource> inherits_state;

#ifdef TOOLS_ENABLED
uint64_t last_modified_time = 0;
uint64_t import_last_modified_time = 0;
Expand All @@ -83,6 +85,15 @@ class Resource : public RefCounted {

virtual void reset_local_to_scene();
GDVIRTUAL0(_setup_local_to_scene);
GDVIRTUAL1R(bool, _setup_inherits_state, Ref<Resource>);
GDVIRTUAL2RC(bool, _is_inherited_state_property_value_saved, const StringName &, const Variant &);

bool _property_can_revert(const StringName &p_name) const;
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
void _validate_property(PropertyInfo &p_property) const;

virtual bool is_inherited_state_property_value_saved(const StringName &p_name, const Variant &p_value) const;
virtual bool setup_inherits_state(const Ref<Resource> &p_resource);

public:
static Node *(*_get_local_scene_func)(); //used by editor
Expand Down Expand Up @@ -145,6 +156,10 @@ class Resource : public RefCounted {
String get_id_for_path(const String &p_path) const;
#endif

void set_inherits_state(const Ref<Resource> &p_resource);
Ref<Resource> get_inherits_state() const;
bool is_property_value_saved(const StringName &p_property, const Variant &p_value) const;

Resource();
~Resource();
};
Expand Down
4 changes: 1 addition & 3 deletions core/io/resource_format_binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2189,9 +2189,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
}
}

Variant default_value = ClassDB::class_get_default_property_value(E->get_class(), F.name);

if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, p.value, default_value))) {
if (!E->is_property_value_saved(F.name, p.value)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we have this now, shouldn't we also replace the same sort of check done in gdextension here with this?

continue;
}

Expand Down
2 changes: 2 additions & 0 deletions doc/classes/EditorSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,8 @@
<member name="project_manager/sorting_order" type="int" setter="" getter="">
The sorting order to use in the project manager. When changing the sorting order in the project manager, this setting is set permanently in the editor settings.
</member>
<member name="resource_inherits_state" type="Resource" setter="set_inherits_state" getter="get_inherits_state">
</member>
<member name="run/auto_save/save_before_running" type="bool" setter="" getter="">
If [code]true[/code], saves all scenes and scripts automatically before running the project. Setting this to [code]false[/code] prevents the editor from saving if there are no changes which can speed up the project startup slightly, but it makes it possible to run a project that has unsaved changes. (Unsaved changes will not be visible in the running project.)
</member>
Expand Down
24 changes: 24 additions & 0 deletions doc/classes/Resource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@
Override this method to return a custom [RID] when [method get_rid] is called.
</description>
</method>
<method name="_is_inherited_state_property_value_saved" qualifiers="virtual const">
<return type="bool" />
<param index="0" name="property" type="StringName" />
<param index="1" name="value" type="Variant" />
<description>
</description>
</method>
<method name="_setup_inherits_state" qualifiers="virtual">
<return type="bool" />
<param index="0" name="resource" type="Resource" />
<description>
This function is called when resource state inheritance is being set up. The new base resource is notified via [b]resource[/b] while the existing one can still be obtained via the [member resource_inherits_state] property.
</description>
</method>
<method name="_setup_local_to_scene" qualifiers="virtual">
<return type="void" />
<description>
Expand Down Expand Up @@ -71,6 +85,13 @@
Returns the [RID] of this resource (or an empty RID). Many resources (such as [Texture2D], [Mesh], and so on) are high-level abstractions of resources stored in a specialized server ([DisplayServer], [RenderingServer], etc.), so this function will return the original [RID].
</description>
</method>
<method name="is_property_value_saved" qualifiers="const">
<return type="bool" />
<param index="0" name="property" type="StringName" />
<param index="1" name="value" type="Variant" />
<description>
</description>
Comment on lines +92 to +93
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

Suggested change
<description>
</description>
<description>
Returns [code]true[/code] if the given [param property] would be saved inside this resource's file, given the specified [param value].
</description>

But you can notice a bit of struggle when attempting to properly document this method. Mainly, "saved" is a vague term here. Hence my questions in the above reviews.

</method>
<method name="setup_local_to_scene" is_deprecated="true">
<return type="void" />
<description>
Expand All @@ -87,6 +108,9 @@
</method>
</methods>
<members>
<member name="resource_inherits_state" type="Resource" setter="set_inherits_state" getter="get_inherits_state">
Resource that this resource inherits state from. It must be the same type as this one. When resource state inheritance is used, this resource will inherit the state of properties from the base one and only local notifications vs the base ones will be saved.
</member>
<member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" default="false">
If [code]true[/code], the resource is duplicated for each instance of all scenes using it. At run-time, the resource can be modified in one scene without affecting other instances (see [method PackedScene.instantiate]).
[b]Note:[/b] Changing this property at run-time has no effect on already created duplicate resources.
Expand Down
16 changes: 16 additions & 0 deletions editor/editor_resource_picker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ void EditorResourcePicker::_update_menu_items() {
if (is_editable()) {
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Clear")), TTR("Clear"), OBJ_MENU_CLEAR);
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Make Unique"), OBJ_MENU_MAKE_UNIQUE);
if (edited_resource->get_inherits_state().is_null()) {
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Instance")), TTR("Inherit State"), OBJ_MENU_MAKE_INHERITED);
}

// Check whether the resource has subresources.
List<PropertyInfo> property_list;
Expand Down Expand Up @@ -362,7 +365,20 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
emit_signal(SNAME("resource_changed"), edited_resource);
_update_resource();
} break;
case OBJ_MENU_MAKE_INHERITED: {
if (edited_resource.is_null()) {
return;
}

Ref<Resource> r = static_cast<Resource *>(ClassDB::instantiate(edited_resource->get_class()));
ERR_FAIL_COND(r.is_null());

r->set_inherits_state(edited_resource);

edited_resource = r;
emit_signal(SNAME("resource_changed"), edited_resource);
_update_resource();
} break;
case OBJ_MENU_MAKE_UNIQUE_RECURSIVE: {
if (edited_resource.is_null()) {
return;
Expand Down
1 change: 1 addition & 0 deletions editor/editor_resource_picker.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class EditorResourcePicker : public HBoxContainer {
OBJ_MENU_CLEAR,
OBJ_MENU_MAKE_UNIQUE,
OBJ_MENU_MAKE_UNIQUE_RECURSIVE,
OBJ_MENU_MAKE_INHERITED,
OBJ_MENU_SAVE,
OBJ_MENU_SAVE_AS,
OBJ_MENU_COPY,
Expand Down
4 changes: 1 addition & 3 deletions scene/resources/resource_format_text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2183,9 +2183,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
}
}

Variant default_value = ClassDB::class_get_default_property_value(res->get_class(), name);

if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) {
if (!res->is_property_value_saved(name, value)) {
continue;
}

Expand Down
Loading