Skip to content

Commit

Permalink
Implement Resource Inheritance
Browse files Browse the repository at this point in the history
* Ability to inherit a resource and do local changes to it.
* Ability to customize in each resource how inheritance happens.
  • Loading branch information
reduz committed Jan 14, 2024
1 parent fbaab3c commit 64f0c42
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 6 deletions.
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))) {
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);

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)) {
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>
</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

0 comments on commit 64f0c42

Please sign in to comment.