godotengine / godot-proposals

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

Allow providing source code editing capabilities for GDExtension classes #9806

Open raulsntos opened 6 months ago

raulsntos commented 6 months ago

Describe the project you are working on

Godot :slightly_smiling_face:

Describe the problem or limitation you are having in your project

GDExtension classes are treated as opaque binary blobs that can't be manipulated, so they can't participate in the same features available to scripts. The Godot editor can't create GDExtension classes, it can't open their source code, and it can't add signal callbacks.

Even though GDExtensions are built libraries, sometimes their source code is available. For example, when the user is using language bindings like godot-cpp or godot-rust to develop their game.

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

GDExtensions / addons will be able to implement a new editor plugin type EditorExtensionSourceCodePlugin that allows providing the necessary callbacks for the editor to let certain extension classes participate in editor features.

For some of these features, the editor will need new UI to indicate that the extension class can participate in these features, or where the dialogs made for scripting are not sufficient and need to display different data.

The editor features that this proposal focuses on are the following:

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

Add a new plugin type EditorExtensionSourceCodePlugin that can be implemented by a GDExtension or an addon (e.g.: GDScript) to provide editor features:

class EditorExtensionSourceCodePlugin {
    // Opening classes source code.
    virtual bool overrides_external_editor();
    virtual Error get_source_path(const String &p_class_name);
    virtual Error open_in_external_editor(const String &p_source_path, int p_line, int p_col);

    // Creating projects or files.
    virtual String get_language_name();
    virtual PackedStringArray get_available_templates(const String &p_base_class_name);
    virtual TypedArray<Dictionary> get_template_options(const String &p_template_name);
    virtual void create_class_source(const String &p_class_name, const String &p_base_class_name, const String &p_directory, const String &p_template_name, const Dictionary &p_template_options);

    // Adding signal callbacks.
    virtual void add_method_func(const String &p_class_name, const String &p_method_name, const PackedStringArray &p_args);
}

Opening classes source code

This API is similar to the ScriptLanguage::overrides_external_editor and ScriptLanguage::open_in_external_editor APIs.

The editor can show a button in the scene tree like it does for scripts today. The mockup below uses the PluginScript for illustrative purposes:

The editor will call the open_in_external_editor API to open the source code of an extension class. The editor delegates the opening implementation to the plugin which can choose how to integrate best with the preferred external editor.

The plugin can return false in overrides_external_editor to indicate that it doesn't support this feature.

To get the path to the file that defines an extension class, the editor will call get_source_path. Allowing the plugin to indicate the absolute path to the source code file. The file path can be outside of the project directory.

Creating projects or files

The Create New popup menu will allow creating native classes and the user can use templates, if any is available, for the initial class source code. Similarly to how the Create Script dialog works. The mockup below uses the PluginScript icon for illustrative purposes:

The dialog will allow users to choose the language of the GDExtension class based on the registered plugins. The name of the language is provided by the get_language_name API. The user will be able to name the class and choose a base class, as well as select a template and configure its options.

The editor will retrieve the list of available templates from the plugin by calling get_available_templates with the name of the base class selected. The plugin is encouraged to only provide templates that apply to the selected base class (for example, only provide templates for 3D character controllers when the class derives from CharacterBody3D).

When a template is selected, the editor will call get_template_options to populate the creation dialog with additional options provided by the selected template. Similar to how the ExportPlugin::_get_export_options API allows providing additional export options.

When the user confirms, the editor will call create_class_source to let the plugin create the file. The plugin will receive all the information provided by the user in the dialog. This allows the plugin to fully control the creation of the file, including how templating works (for example this could allow .NET to use the existing templating support with dotnet new). If the language requires more than one file, the plugin is able to create as many files as needed, or modify existing ones (like adding the newly created files to the build system so they are included in the compilation).

Some of the templating described may overlap with this other proposal:

Adding signal callbacks

The Connect Signal dialog will allow selecting native classes to connect a signal to existing methods or, if the native class supports it, to a new method that will be created subsequently.

Signals can already be connected to methods of native classes (both built-in classes and GDExtension classes). But it doesn't allow creating new methods while connecting signals.

In order to support creating a new method, the native class will need to provide a callback that the editor can use to create and add the method to the native class source code. Scripts support this by implementing the ScriptLanguage::make_function API but it relies on the editor adding the returned string to the source code which won't work properly with some languages (see https://github.com/godotengine/godot/issues/12908).

With the new plugin, the editor will call the add_method_func API which delegates all of the necessary file manipulation to the plugin. The p_args parameter contains a string array with the types of the signal parameters, the format would be the same as the one used by the ScriptLanguage::make_function API: {PARAMETER_NAME}:{PARAMETER_TYPE}.

Open questions

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

There is currently no way for GDExtension classes to participate in some editor tooling features.

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

This is about exposing editor functionality to GDExtension classes.

dsnopek commented 6 months ago

Thanks! This looks really great. :-)

When this gets to the implementation stage, I'm excited to try to add support for godot-cpp - I think the folks who build their game logic in C++ will find this really useful.

When the user confirms, the editor will call create_class_source to let the plugin create the file. The plugin will receive all the information provided by the user in the dialog. This allows the plugin to fully control the creation of the file, including how templating works

There's a number of tricky technical details, but for godot-cpp, it'd be really great if this could add the GDREGISTER_CLASS() to the register_types.cpp. And if so, it'd be really great to give an option to make a runtime class in the creation dialog (resulting in GDREGISTER_RUNTIME_CLASS() instead), which I think will be the most popular option for folks doing game logic.

AThousandShips commented 6 months ago

Feature that would be really useful for the godot-cpp side is to fetch required includes, to ensure the class has the necessary stuff, see for example: