godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.17k stars 98 forks source link

Support reactive logic to Resource operations (save/load/reimport) in both editor and game contexts #3232

Open willnationsdev opened 3 years ago

willnationsdev commented 3 years ago

Describe the project you are working on

Plugins for RPG/simulation games/mod tools.

Describe the problem or limitation you are having in your project

  1. I want an addon that registers peripheral operations whenever a Resource is saved or loaded.
  2. I want this addon to work seamlessly between Editor contexts and game/app contexts.
  3. The user of the addon should not be required to use any third-party classes to take advantage of the added save/load operations, i.e. they should automatically trigger while using built-in ResourceSaver and ResourceLoader methods (so as to have out-of-the-box integration with other addons/plugins and pre-existing game-specific APIs).

Existing, ineffective solutions...

  1. In the Editor context, I can detect changes to files with EditorFileSystem signal resources_reload and resources_reimported, but...
    • Is not a universal editor/game/app contextual solution.
  2. For both contexts, I can find an already-loaded resource and connect to its changed signal, but...
    • changed does not imply it is saved or loaded, so this can't detect when a file is saved or loaded anyway.
  3. I could have my addon manually scan the filesystem for changes, but...
    • This results in script-powered filesystem scans and polling which will not be performant.
    • The editor already does this work on its own, so I'd effectively be duplicating this work for in-game/app use.
  4. I could add a ResourceFormatLoader and ResourceFormatSaver to evaluate any given resource being saved or loaded, but...
    • These only apply effectively to custom resources since these savers/loaders are executed in a loop and will exit that loop after the first matching loader/saver that successfully returns without an error. That is, it is possible for the correct saver/loader to evaluate the Resource prior to the custom saver/loader and terminate the process, thus preventing the custom one from ever having a chance to evaluate the Resource in the first place.
    • It is also a conceptual "hack" since that is not the intended purpose of the savers/loaders.
  5. I can write my own custom save/load singletons for the addon and perform peripheral operations there, but...
    • Other addons and game code must then adapt to using those singletons in place of the built-in ResourceSaver and ResourceLoader singletons. No out-of-the-box integration.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Now that Godot has first-class functions in the scripting API via Callable, we can add filter callbacks that can be registered to the ResourceSaver and ResourceLoader respectively. These would be callbacks that evaluate the currently saved/loaded resource, but do not impact the actual saver/loader logic.

These callbacks can be implicitly auto-registered based on the existence of global script classes extending a ResourceFilter type.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Define a script-accessible type as below:

public class ResourceFilter : public RefCounted {
    GDCLASS(ResourceFilter, RefCounted);
protected:
    void _bind_methods();
    void _on_save(const String &p_path);
    void _on_load(const String &p_path);
    void _on_reimport(const String &p_path);
public:
    ResourceFilter() {}
};

Have the ResourceLoader or ResourceSaver (one of them) create instances of all registered ResourceFilter types on startup and keep those cached for the duration of the app/editor. They can then sort them into pre-defined lists of filters based on whether they implement the corresponding above functions (so that they don't bother iterating over them if the method has no implementation).

Whenever the corresponding operations occur in the respective singletons, iterate through the filters and call their methods. This may warrant a change to the default ResourceSavedCallback function ResourceSaver::save_callback in both the app and editor contexts, but I'm not sure.

If this enhancement will not be used often, can it be worked around with a few lines of script?

As mentioned, while there are alternative solutions that are powered by script code, they do not fully satisfy the needs of the feature.

Is there a reason why this should be core and not an add-on in the asset library?

See above.

kleonc commented 3 years ago

Wouldn't it be simpler to add resource_saved/resource_loaded signals to ResourceSaver/ResourceLoader (and resource_reimported signal probably to ResourceLoader too, I'm not sure if it belongs in there)?

willnationsdev commented 3 years ago

Ah, yeah, that would be another approach, and probably better overall too since it is more flexible and could accommodate both my own OOP approach or any other custom solution. I just initially thought of mimicking the way the ResourceFormatLoader/Saver stuff worked, but straight-up signals might be more efficient/simpler.