Skip to content

multi-robot support with parallel spawn#294

Draft
anishk85 wants to merge 1 commit into
JdeRobot:humble-develfrom
anishk85:multi-robot-parallel-spawn
Draft

multi-robot support with parallel spawn#294
anishk85 wants to merge 1 commit into
JdeRobot:humble-develfrom
anishk85:multi-robot-parallel-spawn

Conversation

@anishk85

@anishk85 anishk85 commented Jul 9, 2026

Copy link
Copy Markdown

multi-robot support with parallel spawn

the manager now handles n robots instead of just one, and they spawn in parallel.

what changed

  • the manager keeps a list of robot launchers/configs instead of a single one.
    launch, reset, pause and teardown just loop over it — the state machine is
    untouched and doesn't know there's more than one robot.
  • robot in the launch config is now an array (single-robot exercises are
    just a list of one). an old single-object robot still works.
  • each robot has an entity name, passed to its launch file as entity:= and
    used as the ros/gazebo namespace, so n robots get their own cmd_vel, odom,
    camera and never collide.

parallel spawn

before, each robot's run() started the launch and then blocked polling gazebo
until it appeared — so robots came up one after another. reset respawns the whole
group, so every reset paid the same cost.

we tried threading it earlier and it hung: the gz spawn-poll is a blocking call
that holds the gil, so the threads just starved each other.

so instead of threads, it's split in two:

  • run() only starts the launch and returns
  • wait_spawned() does the spawn-poll, on the main thread
  • _run_all_robots() starts all the launches, then waits for all of them

the launches run as separate processes, so the group comes up in about one spawn
time instead of n — and reset gets the same speed-up for free.

reset

reset kills every robot launcher, removes each entity from gazebo, resets the
world, then relaunches the group. gazebo's world reset wipes runtime-spawned
entities (and resetting a live one directly segfaults), so respawning is the
right way.

@anishk85 anishk85 marked this pull request as draft July 9, 2026 21:25

@javizqh javizqh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review every single one of the changes. Right now it is not mergeable

Comment thread robotics_application_manager/manager/launcher/launcher_gzsim.py
@@ -1,3 +1,4 @@
import re

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remove

from gz.transport13 import Node


def _find_drone_namespaces():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remove all of this

# reset the AS2 state machine for every drone that's up (not just drone0).
# _find_drone_namespaces returns [] for non-drone exercises, so this is
# a no-op there and only the world reset below runs.
for ns in _find_drone_namespaces():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

remove the drone section

10000,
)

# reset each drone's controller too (not just drone0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remove

except Exception as e:
LogManager.logger.exception(f"Error refreshing GTK applications: {e}")

def _kill_all_applications(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remove this function and add it inline where it should go. Functions for 1 line are not needed

to_lint = app_cfg["linter"]

# Unzip the app
# the code comes as a base64 data-uri from the browser, strip the header part

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why change the comments. Must be reverted

try:
stop_process_and_children(self.application_process)
self.application_process = None
# Pause sim first — freezes Gazebo physics immediately so every

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Irrelevant comment

Comment thread robotics_application_manager/manager/manager.py
)
except Exception as e:
LogManager.logger.exception("Exception terminating scene launcher")
# relaunch all robots (in parallel) so they respawn at their start pose

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Missing check

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Missing

@anishk85

Copy link
Copy Markdown
Author

hi @javizqh requested chnages were done please review once

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Extra config does not need to be stored

extra_config = ""

# pass the entity name to the launch. entity is the gazebo model name
# (used to spawn/remove the model); the launch file decides how to use it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please remove the last part

self.world_type = None
self.robot_launcher = None
self.robot_config = None
# one launcher per robot (a list, so N robots are supported the same way

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remove irrelevant comment. It is clear that a list of robot launchers is a list of robots launchers

self.robot_launcher.run(
robot_cfg["entity"], robot_cfg["start_pose"], robot_cfg["extra_config"]
)
self._run_all_robots()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Missing

with open("/workspace/code/app.zip", "wb") as result:
result.write(base64.b64decode(code))

# just extract evrything to /workspace/code — if theres a processB/ folder

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Incorrect. Entrypoints is a list and it decides where are the executables

)
except Exception as e:
LogManager.logger.exception("Exception terminating scene launcher")
# relaunch all robots (in parallel) so they respawn at their start pose

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Missing

@anishk85

Copy link
Copy Markdown
Author

hi @javizqh this is regarding the previous comment "Incorrect. Not all cfgs have a launcher"
i think this one might be a misread

originally i had two parallel lists (robot_launchers + robot_configs) and zipped
them, and you flagged that as "not all cfgs have a launcher".

to fix that i dropped robot_configs entirely — but then the launcher had to keep
its own entity / start_pose / extra_config so it could be re-run on reset,
which is why extra_config ended up stored here. that's what you're seeing now.

so the two comments pull in opposite directions: if i don't store the config on the
launcher i need the second list back (and the zip returns), and if i keep the second
list i'm back to the first comment.

i think the shape that closes both is a single list of pairs, so nothing is stored on
the launcher and there are no parallel lists that could drift:

# each entry: (launcher, config) — appended together, so they cannot diverge
self.robots = []
...
self.robots.append((LauncherRobot(**cfg.model_dump()), robot_cfg))
...
for launcher, robot_cfg in self.robots:
    launcher.run(
        robot_cfg["entity"], robot_cfg["start_pose"], robot_cfg["extra_config"]
    )

that drops extra_config from the launcher and removes the zip at the same time.
does that work for you, or would you rather i keep the config out of the manager
entirely and solve it a different way?

@javizqh

javizqh commented Jul 12, 2026

Copy link
Copy Markdown
Member

There can be cases where there is no robot assign to a world, so the robot_cfg has a dummy value instead of a real one. In that case it does not have a launcher

@anishk85

Copy link
Copy Markdown
Author

you're right, thanks — i was only thinking about the case where every config is a
real robot. a world with no robot still sends a config, just a dummy one with no
type, so it gets skipped and never has a launcher.

i've restructured it so the two can't drift: the launcher and the config that
produced it are stored together as a pair, and a dummy config is skipped before the
pair is ever created.

self.robots = []
...
launcher = LauncherRobot(**cfg.model_dump())
self.robots.append((launcher, robot_cfg))
...
for launcher, robot_cfg in self.robots:
    launcher.run(
        robot_cfg["entity"], robot_cfg["start_pose"], robot_cfg["extra_config"]
    )

no zip, no parallel lists, and extra_config isn't stored on the launcher any more
either — it stays in the config and is read at run time.
How this seems to you ?

@javizqh

javizqh commented Jul 12, 2026

Copy link
Copy Markdown
Member

This and the comment above are the same. The problem is that you are missing a simple if statement

@anishk85

Copy link
Copy Markdown
Author

ah, got it — i was overcomplicating this. you're right, it's just an if.

@anishk85 anishk85 force-pushed the multi-robot-parallel-spawn branch from 8583162 to c8465a2 Compare July 12, 2026 18:11
@anishk85

Copy link
Copy Markdown
Author

hi @javizqh requesting a review please pardon for any repeated ones as my local and upstream are litlle messed up.
i will fix that after confirming with these changes.

@javizqh

javizqh commented Jul 12, 2026

Copy link
Copy Markdown
Member

@anishk85 are you using an LLM to write the comments here, the use of -- and reply patterns are strange? If you are doing so, please refrain from doing it any longer

@anishk85

anishk85 commented Jul 12, 2026

Copy link
Copy Markdown
Author

@anishk85 are you using an LLM to write the comments here, the use of -- and reply patterns are strange? If you are doing so, please refrain from doing it any longer

Hey, just wanted to clarify something. I don't use any LLM to write my comments.

The difference you might notice is mostly because I switch between my phone and my laptop When I'm replying from my phone (like I did this time), I have the Grammarly keyboard turned on It automatically fixes grammar, punctuation, and sometimes even rephrases things a little, so the message can end up looking more polished than how I'd normally type.

The only reason I use Grammarly on my phone is because typing on a small keyboard is annoying and it's easy to make mistakes. It saves me from constantly fixing typos, especially when I'm having work-related or formal conversations on the go.

When I'm on my laptop, I usually just type everything myself, so my writing is a bit different. You'll probably notice the same kind of variation in my Slack messages too—it really just depends on which device I'm using at the time.

@anishk85

Copy link
Copy Markdown
Author

Okay, noted. Since I can't change it now from my phone at the moment due to some constraints, I'll reply to the GitHub thread from my laptop.

@javizqh

javizqh commented Jul 12, 2026

Copy link
Copy Markdown
Member

Okay, no problem with that. Just, check the comment before sending it, I do not have the time to read a lot of comments and if they are nonsensical I lose even more time

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants