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

Virtual methods take Option<Gd<T>> (unless whitelisted) #883

Merged
merged 5 commits into from
Sep 3, 2024

Conversation

Bromeon
Copy link
Member

@Bromeon Bromeon commented Sep 2, 2024

Fixes #494. There is a list with exceptions.

Tip

Please help me correct and complete this.

If you encounter parameters that are provably non-null (through docs or implementation), please don't just .unwrap() them but maybe mention them in a comment here (even after PR is closed), or open a PR directly.


My mid-term hope is still that we can query this information from Godot itself. There are efforts like godotengine/godot#86079 underway.

Sometimes inconvenient, but the only way if no nullability information
is provided. Exceptions can be worked out over time.
@Bromeon Bromeon added bug c: register Register classes, functions and other symbols to GDScript labels Sep 2, 2024
@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-883

@andreymal
Copy link
Contributor

andreymal commented Sep 3, 2024

yay it works

EditorPlugin._handles(object) seems to be non-null btw

@Bromeon Bromeon force-pushed the bugfix/virtual-methods-nullable branch from 9af6a86 to 18391df Compare September 3, 2024 15:53
@Bromeon Bromeon enabled auto-merge September 3, 2024 15:56
@Bromeon Bromeon added this pull request to the merge queue Sep 3, 2024
@Bromeon
Copy link
Member Author

Bromeon commented Sep 3, 2024

Merging now. I'd appreciate input about more parameters that can be non-null in this thread.

On Discord, it was stated that we should use non-null by default and special-case the parameters that are null, because those are typically documented. However, a single mistake here would mean the API is unusable and needs a breaking SemVer change (Gd<T> -> Option<Gd<T>>), whereas a mistake this way just comes at a slight inconvenience (extra .unwrap()).

Merged via the queue into master with commit 0c91b8a Sep 3, 2024
15 checks passed
@Bromeon Bromeon deleted the bugfix/virtual-methods-nullable branch September 3, 2024 16:04
@lmsonic
Copy link

lmsonic commented Sep 5, 2024

Hi, I scoured the API for documentation for when virtual methods mention null in their description. This is not exhaustive because there are a bunch of <CLASS_NAME>Extension classes not documented that have virtual methods that correspond to methods in the default godot classes that might pass null values (See MultiplayerAPI/MultiplayerAPIExtension). Also a lot of these that I documented are Variant type null, which is okay in the gdext API, but I still wrote them down.

Control

  • Object _make_custom_tooltip(for_text: String) virtual const

Virtual method to be implemented by the user. Returns a Control node that should be used as a tooltip instead of the default one. The for_text includes the contents of the tooltip_text property.

The returned node must be of type Control or Control-derived. It can have child nodes of any type. It is freed when the tooltip disappears, so make sure you always provide a new instance (if you want to use a pre-existing node from your scene tree, you can duplicate it and pass the duplicated instance). When null or a non-Control node is returned, the default tooltip will be used instead.

The returned node will be added as child to a PopupPanel, so you should only provide the contents of that panel. That PopupPanel can be themed using Theme.set_stylebox for the type "TooltipPanel" (see tooltip_text for an example).

Note: The tooltip is shrunk to minimal size. If you want to ensure it's fully visible, you might want to set its custom_minimum_size to some non-zero value.

Note: The node (and any relevant children) should be CanvasItem.visible when returned, otherwise, the viewport that instantiates it will not be able to calculate its minimum size reliably.

Example of usage with a custom-constructed node:

func _make_custom_tooltip(for_text):
	var label = Label.new()
	label.text = for_text
	return label

Example of usage with a custom scene instance:

func _make_custom_tooltip(for_text):
	var tooltip = preload("res://some_tooltip_scene.tscn").instantiate()
	tooltip.get_node("Label").text = for_text
	return tooltip

Godot calls this method to get data that can be dragged and dropped onto controls that expect drop data. Returns null if there is no data to drag. Controls that want to receive drop data should implement can_drop_data and drop_data. at_position is local to this control. Drag may be forced with force_drag.

A preview that will follow the mouse that should represent the data can be set with set_drag_preview. A good time to set the preview is in this method.

func _get_drag_data(position):
	var mydata = make_data() # This is your custom method generating the drag data.
	set_drag_preview(make_preview(mydata)) # This is your custom method generating the preview of the drag data.
	return mydata

EditorPlugin

  • void _edit(object: Object) virtual

This function is used for plugins that edit specific object types (nodes or resources). It requests the editor to edit the given object.

object can be null if the plugin was editing an object, but there is no longer any selected object handled by this plugin. It can be used to cleanup editing state.

EditorScenePostImport

Post-processes scenes after import.

Description

Imported scenes can be automatically modified right after import by setting their Custom Script Import property to a tool script that inherits from this class.

The post_import callback receives the imported scene's root node and returns the modified version of the scene. Usage example:

@tool # Needed so it runs in editor.
extends EditorScenePostImport

# This sample changes all node names.
# Called right after the scene is imported and gets the root node.
func _post_import(scene):
	# Change all node names to "modified_[oldnodename]"
	iterate(scene)
	return scene # Remember to return the imported scene

func iterate(node):
	if node != null:
		node.name = "modified_" + node.name
		for child in node.get_children():
			iterate(child)

Object _post_import(scene: Node) virtual

Called after the scene was imported. This method must return the modified version of the scene.

EditorScenePostImportPlugin

Variant _get_option_visibility(path: String, for_animation: bool, option: String) virtual const

Return true or false whether a given option should be visible. Return null to ignore.

Variant _get_option_visibility(path: String, for_animation: bool, option: String) virtual const

Return true or false whether a given option should be visible. Return null to ignore.

GLTFDocumentExtension

Error _export_node(state: GLTFState, gltf_node: GLTFNode, json: Dictionary, node: Node) virtual

Part of the export process. This method is run after _get_saveable_image_formats and before _export_post. If this GLTFDocumentExtension is used for exporting images, this runs after _serialize_texture_json.

This method can be used to modify the final JSON of each node. Data should be primarily stored in gltf_node prior to serializing the JSON, but the original Godot node is also provided if available. The node may be null if not available, such as when exporting GLTF data not generated from a Godot scene.

Node3D _generate_scene_node(state: GLTFState, gltf_node: GLTFNode, scene_parent: Node) virtual

Part of the import process. This method is run after _import_post_parse and before _import_node.

Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node.

Note: The scene_parent parameter may be null if this is the single root node.

MultiplayerAPI

Error object_configuration_add(object: Object, configuration: Variant)

Notifies the MultiplayerAPI of a new configuration for the given object. This method is used internally by SceneTree to configure the root path for this MultiplayerAPI (passing null and a valid NodePath as configuration). This method can be further used by MultiplayerAPI implementations to provide additional features, refer to specific implementation (e.g. SceneMultiplayer) for details on how they use it.

Note: This method is mostly relevant when extending or overriding the MultiplayerAPI behavior via MultiplayerAPIExtension.

Error object_configuration_remove(object: Object, configuration: Variant)

Notifies the MultiplayerAPI to remove a configuration for the given object. This method is used internally by SceneTree to configure the root path for this MultiplayerAPI (passing null and an empty NodePath as configuration). This method can be further used by MultiplayerAPI implementations to provide additional features, refer to specific implementation (e.g. SceneMultiplayer) for details on how they use it.

Note: This method is mostly relevant when extending or overriding the MultiplayerAPI behavior via MultiplayerAPIExtension.

MultiplayerAPIExtension

Error _object_configuration_add(object: Object, configuration: Variant) virtual

Callback for MultiplayerAPI.object_configuration_add.

Error _object_configuration_remove(object: Object, configuration: Variant) virtual

Callback for MultiplayerAPI.object_configuration_remove.

Object

Variant _get(property: StringName) virtual

Override this method to customize the behavior of get. Should return the given property's value, or null if the property should be handled normally.

Combined with _set and _get_property_list, this method allows defining custom properties, which is particularly useful for editor plugins. Note that a property must be present in get_property_list, otherwise this method will not be called.

func _get(property):
	if property == "fake_property":
		print("Getting my property!")
		return 4

func _get_property_list():
	return [
		{ "name": "fake_property", "type": TYPE_INT }
	]

OpenXRExtensionWrapperExtension

Dictionary _get_requested_extensions() virtual

Returns a Dictionary of OpenXR extensions related to this extension. The Dictionary should contain the name of the extension, mapped to a bool * cast to an integer:

  • If the bool * is a nullptr this extension is mandatory.

  • If the bool * points to a boolean, the boolean will be updated to true if the extension is enabled.

ScriptExtension

Variant _get_script_method_argument_count(method: StringName) virtual const 🔗

Return the expected argument count for the given method, or null if it can't be determined (which will then fall back to the default behavior).

@lmsonic
Copy link

lmsonic commented Sep 5, 2024

When most of the time the Godot API mentions null, it's either a case of default values to null, which are covered by the extension methods + AsObjectArg, or you can pass null to a non default argument (usually to "reset" things) which is covered by AsObjectArg, or some methods return null, which is represented in gdext by Option<Gd<CLASS_NAME>>. As mentioned at the top of my comment, sometimes it is also mentioned when some Variant in input or output can be null.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug c: register Register classes, functions and other symbols to GDScript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Virtual methods must take Option<Gd<T>> instead of Gd<T>
4 participants