Skip to content

Conversation

sauk2
Copy link
Contributor

@sauk2 sauk2 commented Mar 5, 2025

🎉 New feature

Related to gazebosim/gz-tools#7 and #2737

Summary

This PR introduces a standalone executable for gz sim by following a similar approach to gz model. The new executable is integrated into cmdsim.rb.in, replacing the existing functionality. It is also a step in the direction of decoupling the server from GUI components, allowing us to build gz-sim library without GUI support.

Two executables are created - gz-sim-main and gz-sim-gui. The gz-sim-main executable contains the code to launch the server in the same process and invoke gz-sim-gui in a separate process. For, Unix-based systems, std::system() is used, and for Windows, CreateProcess() from windows.h is used.

The majority of the behavior remains consistent, including:

  • Launching the Server using -s
  • Launching the GUI using -g
  • Launching the Quickstart menu when no argument is provided

Changes to the launch behaviour

  • Launching with -s runs the server in the main thread.
  • Launching with -g runs the GUI as a separate process.
  • Running without -s or -g launches both the Server and GUI in separate threads. The Server instance is intentionally created non-blocking so its lifecycle can be controlled. A SIGINT closes both the server and GUI but in case the GUI is closed from the screen (through the window x), the server is shutdown manually.

Enabling/disabling GUI support

An option ENABLE_GUI is provided to enable/disable building with GUI. It is ON by default, and turning it OFF builds libgz-sim<version>.so without any link to gz-gui by not building libgz-sim<version>-gui.so. The executable gz-sim-main is also built without any link to libgz-sim<version>-gui.so, and gz-sim-gui executable is not built at all.

Additionally, a new job has been added CI to build gz-sim without GUI enabled.

Test it

To test the executables, check for UNIT_gz_TEST and INTEGRATION_log_system.

To build gz-sim without GUI support, the library must be built with -DENABLE_GUI=OFF as a CMake argument. Check linkages using ldd for libgz-sim<version>.so and gz-sim-main to find no linkages to any GUI related libraries.

Checklist

  • Signed all commits for DCO
  • Added tests
  • Added example and/or tutorial
  • Updated documentation (as needed)
  • Updated migration guide (as needed)
  • Consider updating Python bindings (if the library has them)
  • codecheck passed (See contributing)
  • All tests passed (See test coverage)
  • While waiting for a review on your PR, please help review another open pull request to support the maintainers

Note to maintainers: Remember to use Squash-Merge and edit the commit message to match the pull request summary while retaining Signed-off-by messages.

@sauk2 sauk2 force-pushed the sim-standalone-exe branch from 2eda54f to 87f72ba Compare March 9, 2025 16:57
@sauk2 sauk2 changed the title [WIP] Standalone gz sim executable Standalone gz sim executable Mar 11, 2025
@sauk2 sauk2 marked this pull request as ready for review March 11, 2025 15:13
@sauk2 sauk2 requested a review from mjcarroll as a code owner March 11, 2025 15:13
@j-rivero
Copy link
Contributor

Before looking into the code: gz-sim-sim seems a weird name to my eyes :) although it can serve as a transitioning step to other naming or model.

Given the client / server architecture I would expect to find something like: gz-sim-client and gz-sim-server separate standalone applications that can be run @sauk2 what do you think? We would need some separate that does not like to Qt libraries for packaging the server-only and get that into the official docker hub.

@traversaro
Copy link
Contributor

traversaro commented Mar 11, 2025

Thanks a lot @sauk2 !

I put together an hack (or let's call it a "proof of concept", to be more fancy) to understand if this new standalone executable could be used to be able to get a working gz sim on Windows and macOS (i.e. fixing #168, #44 and #2393), and it seems that indeed it is working fine, at least on Windows, while macOS is untested. For anyone curious the hack/poc is at https://github.com/traversaro/ign-gazebo/tree/sim-standalone-exe-win .

Once a consensus is reached on this PR, I can try to cleanup the code in https://github.com/traversaro/ign-gazebo/tree/sim-standalone-exe-win to have it in PR shape.

@traversaro
Copy link
Contributor

For anyone curious the hack/poc is at https://github.com/traversaro/ign-gazebo/tree/sim-standalone-exe-win .

For clarity, there I used tinyprocesslib because it is a quick copy&paste from https://github.com/gazebosim/gazebo-classic/blob/b22c6e15e52299865b31093b8feebc9ca19e26e8/gazebo/gazebo_main.cc#L259 . However, probably it make more sense to use gz::utils::Subprocess, see https://gazebosim.org/api/utils/2/classgz_1_1utils_1_1Subprocess.html and gazebosim/gz-utils#127 . By the way, I wonder if it could make sense to use two different process for server and client also on Linux, so that we avoid having different code paths for Linux vs macOS/Windows, or there is some advantage of using a single process for gz sim on Linux?

@j-rivero
Copy link
Contributor

By the way, I wonder if it could make sense to use two different process for server and client also on Linux, so that we avoid having different code paths for Linux vs macOS/Windows, or there is some advantage of using a single process for gz sim on Linux?

That is a good point. Ideally we should go with one code path for all unless we find a good reason or a problem in a particular arch. Maybe wrong but I have always assumed that the ruby code currently spawns two different processes via Process.fork for the GUI and the server so we maintain the two separate processes although we change the way that they are coordinated and orchestratred.

@sauk2 sauk2 marked this pull request as draft March 17, 2025 12:52
@sauk2
Copy link
Contributor Author

sauk2 commented Mar 17, 2025

@j-rivero and @traversaro Thanks a lot for all your feedback! Sorry for the delay in making changes and responding. I've moved this PR back to draft since I believe further discussion is needed before it's ready for merge.

Based on your comments, it made sense to handle the server and GUI as separate processes rather than separate threads. To that end, I've split them into two distinct CLI executables - gz-sim-server and gz-sim-gui, each responsible for its own flags. These are now invoked by a main CLI executable, gz-sim-main, which forwards the appropriate flags to the Server and GUI executables while launching them as separate subprocesses using the gz::utils::Subprocess class. This executable is now available to gz-tools and the cmdsim.rb script. By maintaining a single outward-facing executable, process management is handled at the executable level rather than at the Ruby level, as it has been until now.

Handling the -s and -g flags to launch the server and GUI separately is straightforward, but the real challenge lies in orchestrating the full Server+GUI launch. This scenario can terminate in multiple ways - a SIGINT will cleanly shut down both processes but issues arise when the user manually closes the GUI as this does not signal the Server to shut down. Currently, I’m using the Alive() function to monitor both processes and if the GUI exits due to user input, the server is explicitly terminated as well.

Another issue is that logs are not streamed to the terminal in the current implementation. The Stdout() function in gz::utils::Subprocess only provides output after the process completes, which is not ideal. One potential solution is exposing subprocess_read_stdout within gz::utils::Subprocess to enable real-time output streaming. I’ve put together a mock implementation for testing and I’d appreciate any thoughts on how best to integrate this into the utility class.

Additionally, the server and GUI flags are currently defined within their respective executables. I’m exploring ways to extract these flags so they can be included in the main help message - one idea is to invoke each executable with --help and capture their stdout.

I’d appreciate any suggestions or feedback on these points. Thanks again!

@traversaro
Copy link
Contributor

Thanks a lot for the great work!

Currently, I’m using the Alive() function to monitor both processes and if the GUI exits due to user input, the server is explicitly terminated as well.

We used a similar solution in Gazebo Classic on Windows, and it worked fine.

Another issue is that logs are not streamed to the terminal in the current implementation. The Stdout() function in gz::utils::Subprocess only provides output after the process completes, which is not ideal. One potential solution is exposing subprocess_read_stdout within gz::utils::Subprocess to enable real-time output streaming. I’ve put together a mock implementation for testing and I’d appreciate any thoughts on how best to integrate this into the utility class.

Interesting. I do not have experience with subprocess.h, but with reproc and tiny-process-lib the subprocess output is automatically printed in the terminal that launched the main process. Perhaps there is some way to achieve this, without the need to manually process the stdout and stderr from the child processes?

Additionally, the server and GUI flags are currently defined within their respective executables. I’m exploring ways to extract these flags so they can be included in the main help message - one idea is to invoke each executable with --help and capture their stdout.

Good point. Could it make sense to move the logic of those flags to a simple header-only library, and call it from both the main executable and server/client ones?

@j-rivero
Copy link
Contributor

Another issue is that logs are not streamed to the terminal in the current implementation. The Stdout() function in gz::utils::Subprocess only provides output after the process completes, which is not ideal. One potential solution is exposing subprocess_read_stdout within gz::utils::Subprocess to enable real-time output streaming. I’ve put together a mock implementation for testing and I’d appreciate any thoughts on how best to integrate this into the utility class.

Interesting. I do not have experience with subprocess.h, but with reproc and tiny-process-lib the subprocess output is automatically printed in the terminal that launched the main process. Perhaps there is some way to achieve this, without the need to manually process the stdout and stderr from the child processes?

Sounds like a bug/defect in subprocess that would need to be fixed. I think that we can go with the workaround by now if it is producing the expect result and ticket the bug in gz-utils in order not to go too far with this PR.

Additionally, the server and GUI flags are currently defined within their respective executables. I’m exploring ways to extract these flags so they can be included in the main help message - one idea is to invoke each executable with --help and capture their stdout.

Good point. Could it make sense to move the logic of those flags to a simple header-only library, and call it from both the main executable and server/client ones?

+1.

@sauk2
Copy link
Contributor Author

sauk2 commented Mar 26, 2025

@traversaro and @j-rivero, Sorry for the delay in getting back to you, and thanks for the feedback!

I initially tried using gz::utils::Subprocess, but it was challenging to retrieve logs and manage termination properly. Instead, I switched to using the system() function which was much simpler to work with and appears to be cross-platform.

I've made a few changes that seem to work well. Instead of maintaining separate executables for gz-sim-main and gz-sim-server, I’ve merged them into a single executable while gz-sim-gui remains separate. The separation between the server and GUI is still maintained.

Now, the server process runs directly from the main executable and the GUI is launched as a separate process using system(). One of the challenges with having separate server and GUI executables was passing the correct flags to each. The allow_extras() function of CLI11 allows additional arguments, but in many cases, it causes the positional argument to be misrecognized. To resolve this, I now launch the server directly from the main executable while constructing and forwarding the correct arguments to the GUI.

I still need to figure out how to make this work on Windows, so it would be great to get some advice on this!

I’d be happy to get your feedback and do let me know if this approach works for you!

@traversaro
Copy link
Contributor

I still need to figure out how to make this work on Windows, so it would be great to get some advice on this!

Sorry, I arrived late and I see that you already worked on this. To be honest I always used tiny-process-lib and reproc, so even without using them directly you can check for their implementations to understand more.

Copy link
Contributor

@j-rivero j-rivero left a comment

Choose a reason for hiding this comment

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

Going slowly through the changes:

Blackbox testing a little detected some differences with current gz behaviour:

  • Running launching a simulation and running gz-gui-main after it displays the screen to select default worlds. If I run current gz sim -g this does not happen and the gui appears with the world that it is being run by the server.
  • If I use gz-gui-main from the command line, select a world, and close the GUI window (using the window manager X) , the command line is still running, not finished.

We can address this problem later no need to do it in this PR but: the PR is an step in the right direction but the current approach does not isolate the gui/server in a way that we can package them separately.

app.add_flag("--force-version", "Use a particular library version.");
app.add_flag("--versions", "Show the available versions.");

app.add_flag("-g", opt->launchGui, "Run and manage Gazebo GUI");
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
app.add_flag("-g", opt->launchGui, "Run and manage Gazebo GUI");
app.add_flag("-g", opt->launchGui, "Run and manage only the Gazebo GUI");

@j-rivero
Copy link
Contributor

j-rivero commented Apr 3, 2025

Gave it a try on Windows, it builds nicely.

There is a problem with the initial screen probably unrelated to this PR.
image

gz-sim-exe runs fine for launch both and only the server. Same problem than in Linux, it does not tear down the server if I close the gui. gz-sim-gui works fine.

@sauk2
Copy link
Contributor Author

sauk2 commented Apr 4, 2025

Sorry, I arrived late and I see that you already worked on this. To be honest I always used tiny-process-lib and reproc, so even without using them directly you can check for their implementations to understand more.

@traversaro thank you for pointing me in the right direction! I have moved to using the CreateProcess() function from windows.h to launch the GUI as a separate process. Let me know what you think of this approach!

We can address this problem later no need to do it in this PR but: the PR is an step in the right direction but the current approach does not isolate the gui/server in a way that we can package them separately.

@j-rivero Thanks for your feedback and for trying out the functionality!

If I use gz-gui-main from the command line, select a world, and close the GUI window (using the window manager X) , the command line is still running, not finished.

I made some changes to try to fix this issue by running a non-blocking server and manually controlling its lifecycle. Now, when the GUI is closed, the server is shut down manually without having to worry about handling signals. Do give it a look and let me know if you are still getting that problem.

We can address this problem later no need to do it in this PR but: the PR is an step in the right direction but the current approach does not isolate the gui/server in a way that we can package them separately.

I have started working on this now by wrapping the GUI components within conditional statements. Hoping to push some commits on this soon!

@traversaro
Copy link
Contributor

@traversaro thank you for pointing me in the right direction! I have moved to using the CreateProcess() function from windows.h to launch the GUI as a separate process. Let me know what you think of this approach!

That is great, thanks!

@j-rivero
Copy link
Contributor

j-rivero commented Apr 7, 2025

We can address this problem later no need to do it in this PR but: the PR is an step in the right direction but the current approach does not isolate the gui/server in a way that we can package them separately.

I have started working on this now by wrapping the GUI components within conditional statements. Hoping to push some commits on this soon!

Thanks ! Let me clarify one detail: we do want to have the Gazebo server independent of using (and/or linking) any GUI component. This can be achieve by building Gazebo without the GUI (like the option you are adding) but we also should be able to get it done without disabling the GUI and providing independent binaries that we can package in different packages (i.e: gz-sim-server.deb / gz-sim-gui.deb) from a single build. Installing the gz-sim-server should bring a server only experience without all the GUI dependencies.

I made some changes to try to fix this issue by running a non-blocking server and manually controlling its lifecycle. Now, when the GUI is closed, the server is shut down manually without having to worry about handling signals. Do give it a look and let me know if you are still getting that problem.

Yes ! testing now works as expected on Linux. The only caveat is that invoking the GUI display the initial world selection window that I think make no sense to have it there.

@j-rivero
Copy link
Contributor

j-rivero commented Apr 9, 2025

After speaking with the PMC yesterday we agree that the change in this PR is a bit risky to deploy it directly on a released distribution like Ionic, so we probably want it to go into a development version so it gets well testing during the release cycle before we ship it to the users. @sauk2sorry for the late decision, I can take care of migrating it the Jetty branch.

The other point to move this forward is to list what is still missing? From my personal testing:

  • Manual testing in Mac [blocker]
  • Initial world selection screen in Windows do not work properly [non-merge-blocker]
  • gz-sim-gui displays the world initial selection screen [non-merge-blocker]
  • gz-sim-gui input parameters referenced SDF files [non-merge-blocker]
  • Parameters are duplicated between gz-sim-gui / gz-sim-main [non-merge-blocker]

@sauk2
Copy link
Contributor Author

sauk2 commented Apr 10, 2025

Thank you for the feedback! I agree that it would be best to include this in the next version. I appreciate your offering to change the target branch to main; please feel free to proceed with that. I'll address the review comments once that's done.

@j-rivero
Copy link
Contributor

j-rivero commented May 7, 2025

Follow up in #2849

@j-rivero j-rivero closed this May 7, 2025
@github-project-automation github-project-automation bot moved this from Inbox to Done in Core development May 7, 2025
@sauk2 sauk2 mentioned this pull request Jun 30, 2025
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🏛️ ionic Gazebo Ionic
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants