Skip to content

Conversation

@reduz
Copy link
Member

@reduz reduz commented Jan 4, 2024

  • Ability to inherit a resource and do local changes to it.
  • Ability to customize in each resource how inheritance happens.

An inherited resource has a reference to the original one and shows the values of the original one. It can override these values and only those overrides are saved, otherwise the values in the original resource are used. This allows to have a base resource with a lot of parameters customized, and then inherited ones that may tweak one thing or two.

Ideally, this feature will also allow to tweak animations and animationlibraries from imported scenes (without having to do the hack of saving them to disk), although this needs other code changes that will happen in separate PRs.

How it looks:

image

This PR is intended to play together with this one and this proposal, to allow much better editing and usage of imported assets.

@reduz reduz requested a review from a team as a code owner January 4, 2024 12:34
@Mickeon
Copy link
Member

Mickeon commented Jan 4, 2024

Is a inherited Resource still its own thing at run-time?

That is, at run-time, modifying any property of a Resource does not affect any inherited Resource, is that correct?

By the way this would allow closing godotengine/godot-proposals#8094

@Calinou Calinou added this to the 4.x milestone Jan 4, 2024
@reduz
Copy link
Member Author

reduz commented Jan 4, 2024

@Mickeon It is its own thing. The only thing I did not implement in this PR is, if while during the editor, changing the base resource should change the inherited ones (basically something editor-only to check if the property edited in the base one is the same value as in the inherited ones, then change the inherited ones). This should not be hard to do.

During run-time, however, the inherited ones are its own thing and only fetch the non-modified properties from the base one.

@Mickeon
Copy link
Member

Mickeon commented Jan 4, 2024

That is excellent, and simple to describe, too. Users that want properties to propagate at run-time can listen for the changed signal of the inherited Resource, and/or override _setup_inherited. As an exposed method, it's marginally more useful than _setup_local_to_scene 💦.

Not sure about the current names, but this overall looks like a surprisingly simple yet powerful feature.

@Ansraer
Copy link
Contributor

Ansraer commented Jan 4, 2024

It would be nice to have some kind of logo in the corner of inherited resources to let the user know that it depends on something else. And maybe we could highlight the parent resource if the "sub-resource-icon" is pressed?
Otherwise this could get messy quickly, especially if you are collaborating with other people and have no clue that some resources are sub resources. Trying to figure out why some of your resource's values change seemingly spontanously sounds like a nightmare to me.

Oh, it would also be nice if the inspector would some show (e.g. with a different background color) which fields the sub resource overwrites. And a way to un_overwrite a field would probably also be necessary.

@reduz
Copy link
Member Author

reduz commented Jan 5, 2024

@Ansraer yeah I could put the instance logo in the inspector. Regarding the editor, anything that has the revert button is what it has been overriden.

@TokisanGames
Copy link
Contributor

Does this store only changes from the original or is it a unique instance that is easily revertable to the original?

The context I'm thinking about is a vertex painting tool, which is not practical to use in Godot due to the current design.

As we've discussed at length, the only safe option is to keep meshes tied to GLBs so they can be reimported on demand. But the meshes are locked, so we can't paint vertices. In order to do so, we need unique resources, but then we have disassociated ArrayMeshes and when the format inevitably changes, we have a problem. We can reimport the GLBs, but Godot will complain about the old format on the disassociated ArrayMeshes in all mesh instances we have painted.

Does this PR help solve this dilemma for vertex painting?

@Calinou
Copy link
Member

Calinou commented Jan 5, 2024

Does this PR help solve this dilemma for vertex painting?

A different solution is needed, similar to the existing UV2 caching system used for lightmap unwraps: godotengine/godot-proposals#6486

@reduz
Copy link
Member Author

reduz commented Jan 10, 2024

Vertex painting is far more involved and I have the feeling it should need a separate implementation (not even using vertex arrays). I will open a proposal soon about this.

@reduz reduz force-pushed the resource-inheritance branch from 552e162 to 859e28b Compare January 10, 2024 12:45
@reduz reduz requested a review from a team as a code owner January 10, 2024 12:45
@reduz
Copy link
Member Author

reduz commented Jan 10, 2024

This is ready for review.

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?

@YuriSizov
Copy link
Contributor

So there is a limitation in the current implementation that only the same type can be inherited by a resource. But I think we should allow narrowing types. I can imagine a situation where I have a base StyleBox with some content margins configured, and then have a StyleBoxFlat and a StyleBoxEmpty inheriting it, but adding their own unique properties as well. In fact, this is definitely a use case we want to be able to handle in various multistate controls, like buttons.

@reduz
Copy link
Member Author

reduz commented Jan 11, 2024

@YuriSizov The limitation is because by default it may lead to mistakes, but it definitely could be more lean (as in, if the resource class also inherits the base class its should be fine).

I can modify it for this as you suggest.

@YuriSizov
Copy link
Contributor

Yes, please do, I think it would be useful and expected.

* Ability to inherit a resource and do local changes to it.
* Ability to customize in each resource how inheritance happens.
@reduz reduz force-pushed the resource-inheritance branch from 859e28b to 64f0c42 Compare January 14, 2024 18:38
@reduz reduz changed the title Implement Resource Inheritance Implement Resource State Inheritance Jan 14, 2024
@reduz
Copy link
Member Author

reduz commented Jan 14, 2024

I applied the suggested changes. Also renamed the feature to "state inheritance" which makes more sense and is less confusing vs regular inheritance.

@fire
Copy link
Member

fire commented Feb 26, 2024

@reduz Were we expecting to queue this for merging in 4.3? You mentioned letting this bake in the last Pipeline and Animations meeting.

Copy link
Contributor

@theraot theraot left a comment

Choose a reason for hiding this comment

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

  1. This works as expected for materials.

  2. The usability of this PR is poor. It would be better to have a way to create an inherited resource from the filesystem, similar to creating an inherited scene.

  3. The option to set resource_inherits_state is available for PacketScene (opening the PacketScene in the inspector, not editing the scene), and it does not work. I believe it should not be visible to the inspector.

  4. I tested this in the context this proposal of mine: godotengine/godot-proposals#9802 and found it lacking for animations, but I believe that could be addressed separately.

@DaloLorn
Copy link

DaloLorn commented Jun 5, 2024

Point 2 doesn't seem like a dealbreaker - better a bad UX than no UX for this sort of thing - but on the other hand, now that it's missed the deadline for inclusion in 4.3, it does sound like something that should be addressed in time for 4.4.

@theraot
Copy link
Contributor

theraot commented Jun 5, 2024

None of what I said is a deal breaker. I didn't say it was.

Addendum: I'm glad people are looking at this, because I need it.

Copy link
Member

@Mickeon Mickeon left a comment

Choose a reason for hiding this comment

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

It is my realization ONLY NOW that the below comments have always been pending and Github never reminded me of this.

Copy link
Member

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.

Comment on lines 741 to 742
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it's better not to expose new features, without a bit of information at least. This is to not leave users that do not religiously follow the blogposts wandering in confusion.

Suggested change
<member name="resource_inherits" type="Resource" setter="set_inherits" getter="get_inherits">
</member>
<member name="resource_inherits" type="Resource" setter="set_inherits" getter="get_inherits">
The resource inherited by this resource, or [code]null[/code] if it does not exist.
In the editor, a new inherited resource can be created by clicking on "Inherit" in the Inspector's popup menu.
</member>

Copy link
Member

Choose a reason for hiding this comment

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

Wait a minute, EditorSettings? Why is this property here? What is it for?

Copy link
Member

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.

@CsloudX

This comment was marked as off-topic.

@Calinou
Copy link
Member

Calinou commented Jun 19, 2024

@CsloudX Please don't bump issues without contributing significant new information. Use the 👍 reaction button on the first post instead.

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?

@fstxz
Copy link
Contributor

fstxz commented Jan 24, 2025

Reverting property value doesn't work properly. Let's say you have a script item.gd:

class_name Item
extends Resource

@export var cost: int

Then you create a resource apple.tres, set cost to 10.
Then you create a new resource that inherits apple.tres, keep the cost at 10 and save it as new_apple.tres

Now if you look at the new_apple.tres's properties, the cost can be reverted, even though it's unchanged from the base apple.tres. Clicking revert button reverts the property's value to the script's default value, in this case to 0.

The cost is not saved in the file itself if the value is 10, so this part works correctly, it's just the UI that is acting weirdly, the revert button is always visible and can be clicked multiple times.

If you go back to apple.tres and change the cost to 20 and save, then go back to new_apple.tres and revert the cost, it will change from 10 to 20, which is good, however the revert button is still visible, clicking it again will change the cost from 20 to 0, clicking again changes the cost from 0 to 20.

@OhiraKyou
Copy link
Contributor

OhiraKyou commented Feb 13, 2025

Would this implementation also work for meshes extracted from GLTF files on reimport?

Use case

GPU particle draw passes use the materials assigned to the mesh data. This is a problem, because that data is shared. So, you can't use the same mesh for multiple GPU passes. Note that, when more than one draw pass is used, the geometry instance material override is not a valid solution, because it doesn't support assigning a separate override per draw pass.

Furthermore, any materials assigned to a draw pass are lost on reimport due to the data being stored in the mesh asset that gets overwritten on extraction. And, even if one is willing to accept that material data is shared between all passes that use the same mesh, it's not even possible to assign that one set of persistent materials when exporting placeholder materials from Blender (see PR 64903).

Resource inheritance has the potential to offer workarounds for these problems by enabling mesh resources to be inherited with material overrides that, ideally, should survive model reimports and consequent mesh data overwrites.

Ideal alternative solution

Although resource inheritance would solve draw pass problems, the ideal solution would be giving draw passes individual material override options. Really, draw passes should have been implemented as a new resource type. And, if a new resource type were added for them, they would need to be automatically converted in old projects.

Valla-Chan added a commit to Valla-Chan/Godot-3.5.1-Ghodost that referenced this pull request Feb 26, 2025
@akien-mga akien-mga modified the milestones: 4.x, 4.5 May 26, 2025
@fire fire self-requested a review June 24, 2025 18:21
@Zephilinox
Copy link
Contributor

what's needed to get this feature merged? I have a custom import workflow for 3D assets to get around some issues with the default one, but there's a particular pain point with importing the updated materials while keeping any changes made on the godot side, which this PR would solve

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Work in progress

Development

Successfully merging this pull request may close these issues.

Allow defining inheriting Styleboxes using a parent Stylebox with overrides Add support for Resource inheritance (prototypal inheritance)