Skip to content

Conversation

@dsnopek
Copy link
Contributor

@dsnopek dsnopek commented Jun 18, 2025

This PR aims to allow editor plugins to control the arguments that are used when launching a project from the editor

The ultimate goal is to allow the godot_openxr_vendor extension to simulate running a hybrid immersive/flat VR app on desktop, so you don't need to deploy to the headset for testing.

See GodotVR/godot_openxr_vendors#312 for the rest of the implementation of that (also depends on #108852)

This is a DRAFT for now, because I need to do more testing (especially with the new embedded game view on multiple platforms)

@dsnopek dsnopek added this to the 4.x milestone Jun 18, 2025
@dsnopek dsnopek requested review from a team as code owners June 18, 2025 16:08
@dsnopek dsnopek marked this pull request as draft June 18, 2025 16:09
@dsnopek dsnopek force-pushed the editor-run-control branch 4 times, most recently from cc21f5f to 65d28c4 Compare June 23, 2025 20:29
@dsnopek dsnopek changed the title [DRAFT] Allow editor plugins to modify run arguments and re-run from within project Allow editor plugins to modify run arguments and re-run from within project Jun 23, 2025
@dsnopek
Copy link
Contributor Author

dsnopek commented Jun 23, 2025

This could still use more testing, although, it's working well for me on Linux (both with the embedded game view and not) so I think I'll take it out of draft

NOTE: this is squarely targeting Godot 4.6 at this point

@dsnopek dsnopek marked this pull request as ready for review June 23, 2025 20:51
@dsnopek dsnopek requested a review from a team as a code owner June 23, 2025 20:51
@KoBeWi
Copy link
Member

KoBeWi commented Jun 28, 2025

I think it would be more efficient if you just passed the Vector as argument and don't expect it to be returned. Since it's passed as reference, the plugin can modify it all the same, without making an unnecessary copy. Although the copy is only made when a plugin implements the method, so it's probably fine either way.

I also noticed that there is discrepancy between arguments passed to the new method and the ones returned by OS.get_cmdline_args() in running project. Specifically, the _run_scene() is called with empty array, while at runtime the array includes --scene and scene path. Not a big deal though.

@KoBeWi
Copy link
Member

KoBeWi commented Jun 28, 2025

What are the debugger changes for?

@dsnopek
Copy link
Contributor Author

dsnopek commented Jun 28, 2025

@KoBeWi Thanks for the review!

I think it would be more efficient if you just passed the Vector as argument and don't expect it to be returned. Since it's passed as reference, the plugin can modify it all the same, without making an unnecessary copy.

While passing it as a reference and modifying it in place would work in the engine itself, I don't think it would work from GDScript or GDExtension. In that case, I think a copy would be made as soon as any elements were removed or added to the Vector, and the whole point of this virtual method is to add or remove stuff.

Since this only happens when the user clicks the "Run project" or "Run scene" button, I don't think we have to worry about being very efficient here.

I also noticed that there is discrepancy between arguments passed to the new method and the ones returned by OS.get_cmdline_args() in running project. Specifically, the _run_scene() is called with empty array, while at runtime the array includes --scene and scene path.

This is by design. I don't think we want plugins to mess with all the arguments that the editor adds automatically, just the additional ones. Otherwise a plugin could sneakily change which scene is being run, or the port the debugger is going to connect to, etc.

What are the debugger changes for?

To allow the application to trigger itself to relaunch with different arguments, while still allowing the new instance to run in the embedded game view (if enabled) and connect to the debugger.

These changes are to enable PR GodotVR/godot_openxr_vendors#312 for the godot_openxr_vendors GDExtension.

That PR aims to allow basically the same as PR #103972 (which only works when running the editor on Android), but instead simulating it on the desktop, so you can test hybrid apps (XR apps that can switch between immersive and 2D panel modes) without having run on a real headset.

@KoBeWi
Copy link
Member

KoBeWi commented Jun 28, 2025

To allow the application to trigger itself to relaunch with different arguments, while still allowing the new instance to run in the embedded game view (if enabled) and connect to the debugger.

How can this be tested?

@dsnopek
Copy link
Contributor Author

dsnopek commented Jul 1, 2025

@KoBeWi:

How can this be tested?

Well, you can test it via GodotVR/godot_openxr_vendors#312 and the Meta XR Simulator, but that's a lot of extra stuff to setup :-)

So, here's a minimal demo app to test it:

editor-plugin-run-test.zip

@KoBeWi
Copy link
Member

KoBeWi commented Jul 4, 2025

Here's a scene that does (almost) the same in vanilla Godot:
main2.tscn.txt
The only difference is that the project re-launches without debugger (though that can be fixed probably), and set_restart_on_exit() sometimes fails.

I guess adding request_run_scene is fine, but it's not really related to this PR and can be done separately.

@dsnopek
Copy link
Contributor Author

dsnopek commented Jul 21, 2025

Here's a scene that does (almost) the same in vanilla Godot:
main2.tscn.txt
The only difference is that the project re-launches without debugger (though that can be fixed probably), and set_restart_on_exit() sometimes fails.

I tried a whole bunch of times, but could not get this to work for me on Linux (X11). I also suspect that it would cause problems with the embedded game view even on platforms where it's working, but I haven't tested that

I guess adding request_run_scene is fine, but it's not really related to this PR and can be done separately.

Sure, I can move it to its own PR!

However, I really need both in order to implement the feature that I'm aiming to implement (which is a way to test hybrid apps - similar to PR #103972 - but on desktop, rather than on Android)

UPDATE: Actually, I don't need both, only this one! See this comment for the details

@dsnopek
Copy link
Contributor Author

dsnopek commented Jul 21, 2025

I've moved the debugger part into its own PR: #108852

UPDATE: I've managed to find a way to implement what #108852 did using only this PR and an EditorDebuggerPlugin, so that PR is no longer needed, only this one here

@dsnopek dsnopek changed the title Allow editor plugins to modify run arguments and re-run from within project Allow editor plugins to modify run arguments Jul 22, 2025
@KoBeWi KoBeWi modified the milestones: 4.x, 4.6 Jul 23, 2025
@KoBeWi
Copy link
Member

KoBeWi commented Jul 23, 2025

While passing it as a reference and modifying it in place would work in the engine itself, I don't think it would work from GDScript or GDExtension.

I tested this and indeed GDScript makes a copy.
However you can change call_run_scene() and EditorPlugin.run_scene() to modify the argument. This way only the virtual method will replace the vector.
You can also avoid replacing the vector unnecessarily by using GDVIRTUAL_IS_OVERRIDDEN.

Since this only happens when the user clicks the "Run project" or "Run scene" button, I don't think we have to worry about being very efficient here.

Being inefficient may affect project starting time. It's likely not a problem here, but optimizing it won't make the code much more complex. Though it only involves internal changes, so it can be done in a follow-up PR if you don't want to do it now.

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

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

Tested locally, it works as expected. Code looks good to me.

Testing project: test_pr_107671.zip
This project contains an editor plugin that implements "play from position" functionality.

play_from_position.mp4

Note that one limitation with this approach is that arguments are always appended at the end, with no way to control the order between plugins. This can have an importance if you're passing user command line arguments (i.e. --engine-arg -- --user-arg), as anything after -- will be ignored by the engine itself and treated as an user argument.

Having support for specifying user command line arguments in a way that isn't reliant on order (e.g. +user-arg) could help resolve this in the long run.

@dsnopek dsnopek force-pushed the editor-run-control branch from da1256d to ffb315c Compare July 24, 2025 16:30
@dsnopek
Copy link
Contributor Author

dsnopek commented Jul 24, 2025

@KoBeWi: Thanks for all the review!

Being inefficient may affect project starting time. It's likely not a problem here, but optimizing it won't make the code much more complex.

I still really don't think this will be inefficient: Vector does copy-on-write, so if there have been no changes, we're really just re-assigning the reference.

Anyway, I've gone ahead and modified the code as you described here:

However you can change call_run_scene() and EditorPlugin.run_scene() to modify the argument. This way only the virtual method will replace the vector.
You can also avoid replacing the vector unnecessarily by using GDVIRTUAL_IS_OVERRIDDEN.

We actually don't need GDVIRTUAL_IS_OVERRIDDEN() because GDVIRTUAL_CALL() will only return true if it's overridden. But, otherwise, this is included in my latest push.

@Calinou:

Tested locally, it works as expected. Code looks good to me.

Thanks for the testing and review! That's a really cool use for this feature :-)

@dsnopek dsnopek force-pushed the editor-run-control branch 2 times, most recently from c356373 to c4b7943 Compare July 24, 2025 17:09
@KoBeWi
Copy link
Member

KoBeWi commented Jul 25, 2025

Note that one limitation with this approach is that arguments are always appended at the end, with no way to control the order between plugins.

Not really. You get access to the whole argument array, so you can check if another plugin has already added -- and insert your arguments accordingly.

@dsnopek dsnopek force-pushed the editor-run-control branch from d548586 to 15dace3 Compare August 8, 2025 18:53
@dsnopek dsnopek force-pushed the editor-run-control branch from 15dace3 to 3d46656 Compare October 1, 2025 10:43
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.

I kind of get why, and maybe it has been discussed in the past, but why does this function have to return a new array derived from the original, instead of modifying the array in-place (which would probably require an Array[String], or potentially having the returned value appended onto the original array internally?
I just personally find the approach of "take the array as a parameter, then return almost the same array, if you want" to be awkward API.

@dsnopek
Copy link
Contributor Author

dsnopek commented Oct 1, 2025

I kind of get why, and maybe it has been discussed in the past, but why does this function have to return a new array derived from the original, instead of modifying the array in-place (which would probably require an Array[String], or potentially having the returned value appended onto the original array internally?

While modifying the array in place works in C++ within the engine itself, in either GDScript or GDExtension, modifying the array in-place won't update the original because of copy-on-write.

I'm not sure what you mean by "having the returned value appended onto the original array internally"?

@dsnopek dsnopek force-pushed the editor-run-control branch from 3d46656 to fe27a72 Compare October 1, 2025 12:37
@Mickeon
Copy link
Member

Mickeon commented Oct 1, 2025

I'm not sure what you mean by "having the returned value appended onto the original array internally"?

Essentially the same behavior as Object's _get_property_list and Node's _get_configuration_warnings, which would limit this function to only being able to add extra arguments, but is potentially more expected.

@dsnopek
Copy link
Contributor Author

dsnopek commented Oct 1, 2025

Essentially the same behavior as Object's _get_property_list and Node's _get_configuration_warnings, which would limit this function to only being able to add extra arguments, but is potentially more expected.

Unfortunately, if this is limited to only adding arguments, then it won't work for the use case I created this PR for - I need to be able to remove and modify arguments as well.

@Repiteo Repiteo merged commit d6c5c4e into godotengine:master Oct 20, 2025
51 of 60 checks passed
@Repiteo
Copy link
Contributor

Repiteo commented Oct 20, 2025

Thanks!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants