ros2_rust icon indicating copy to clipboard operation
ros2_rust copied to clipboard

Support for Bevy

Open TanJunKiat opened this issue 1 year ago • 7 comments

  • Tried to add a subscriber node as a system in Bevy but Bevy stopped running (black screen) after the node started spinning

  • Tried multi-threading. It works (bevy and ros node both running), but ros node is unable to update the resource database in Bevy with the new messages.

Any help or suggestion will be appreciated and we can try to get it working and improve the Docs or an example for the community.

Setup:

  • Ubuntu 22.04
  • ROS2 humble built from binary
  • Bevy 0.12

TanJunKiat avatar Dec 27 '23 11:12 TanJunKiat

Hi there!

I've been using ros2_rust along with bevy (although v0.11) without much problems.

Here's how I integrate ROS 2 interfaces within my bevy application:

  1. I have a dedicated startup plugin that initializes a bevy Resource which is a struct that bundles an rclrs::Context and an Arc<rclrs::Node>. A Default trait for this resource is defined which instantiates the context and node. The same plugin also adds a system which retrieves the above resource, checks if the context is okay and if so, calls rclrs::spin_once() with a clone of the node.
  2. In plugins where I need to define, pub/sub/client/services, I define a bevy Component struct with the corresponding rclrs entity. Then, there are systems that query for the Resource above, initialize the component using the node from the resource, and finally insert the component into the entities.

Hope that helps!

Yadunund avatar Dec 27 '23 14:12 Yadunund

use bevy::prelude::*;
use std::sync::{Arc, Mutex};
use std_msgs::msg::String as StringMsg;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(NodePlugin)
        .add_systems(Startup, setup)
        .add_systems(Update,print_string)
        .run();
}

fn setup() {
    eprintln!("Bevy initialized.");
}

fn print_string(
    string_node: ResMut<NodeSetting>,
) {
    let s = string_node.data.clone().lock().unwrap().clone().unwrap();
    eprintln!("{}",&s.data);
    eprintln!("Received string.");
}

pub struct NodePlugin;

#[derive(Resource)]
struct NodeSetting {
    node: Arc<rclrs::Node>,
    _subscription: Arc<rclrs::Subscription<StringMsg>>,
    data: Arc<Mutex<Option<StringMsg>>>,
}

impl Plugin for NodePlugin {
    fn build (&self, app: &mut App){
        let context = rclrs::Context::new(std::env::args()).unwrap();
        let node = rclrs::Node::new(&context, "republisher").unwrap();
        let data = Arc::new(Mutex::new(None));
        let data_cb = Arc::clone(&data);
        let string_node = NodeSetting {
            node: node.clone(),
            data: data.clone(),
            _subscription: node.create_subscription(
                "in_topic",
                rclrs::QOS_PROFILE_DEFAULT,
                move |msg: StringMsg| {
                    *data_cb.lock().unwrap() = Some(msg);  // (4)
                },
            ).unwrap(),
        };
        
        app.insert_resource(string_node)
        .add_systems(Update, spin_node)
        .insert_resource(Time::<Fixed>::from_hz(1.0));
    }
}

fn spin_node(string_node: ResMut<NodeSetting>){
    eprintln!("spinning node ");
    rclrs::spin_once(string_node.node.clone(),None).unwrap();
    eprintln!("exit node ");
}

TanJunKiat avatar Dec 28 '23 01:12 TanJunKiat

@Yadunund thanks for the support.

I tried to create a simple test code to try out what you suggested.

Not sure did I understood the recommendation correctly but my Bevy still gets stuck after the node was spun once.

TanJunKiat avatar Dec 28 '23 01:12 TanJunKiat

Is it stuck inside the spin_once or elsewhere? I'd recommend putting a sensible timeout in the spin_once.

mxgrey avatar Dec 28 '23 02:12 mxgrey

Instead of transferring the subscription data using an Arc<Mutex<Option<T>>> I would recommend creating a mpsc channel. Give the sender to your subscription callback and store the receiver in your resource.

Your subscription callback would simply pass the messages into the sender. Your print_string system would drain the receiver using try_recv.

mxgrey avatar Dec 28 '23 02:12 mxgrey

use bevy::prelude::*;
use std::{sync::{Arc, Mutex}, time::Duration};
use std_msgs::msg::String as StringMsg;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(NodePlugin)
        .add_systems(Startup, setup)
        .add_systems(Update,print_string)
        .run();
}

fn setup() {
    eprintln!("Bevy initialized.");
}

fn print_string(
    string_node: ResMut<NodeSetting>,
) {
    let guard = string_node.data.lock().unwrap();
    if guard.as_ref() != None{   
        let info = guard.as_ref().unwrap();
        eprintln!("Bevy got message: {}",&info.data);
    }
}

pub struct NodePlugin;

#[derive(Resource)]
struct NodeSetting {
    node: Arc<rclrs::Node>,
    _subscription: Arc<rclrs::Subscription<StringMsg>>,
    data: Arc<Mutex<Option<StringMsg>>>,
}

impl Plugin for NodePlugin {
    fn build (&self, app: &mut App){
        let context = rclrs::Context::new(std::env::args()).unwrap();
        let node = rclrs::Node::new(&context, "republisher").unwrap();
        let data = Arc::new(Mutex::new(None));
        let data_cb = Arc::clone(&data);
        let string_node = NodeSetting {
            node: node.clone(),
            data: data.clone(),
            _subscription: node.create_subscription(
                "in_topic",
                rclrs::QOS_PROFILE_DEFAULT,
                move |msg: StringMsg| {
                    eprintln!("Node received message: {}",msg.data);
                    *data_cb.lock().unwrap() = Some(msg);  // (4)
                },
            ).unwrap(),
        };
        
        app.insert_resource(string_node)
        .add_systems(Update, spin_node)
        .insert_resource(Time::<Fixed>::from_hz(1.0));
    }
}

fn spin_node(string_node: ResMut<NodeSetting>){
    eprintln!("Entering node ");
    let _action = rclrs::spin_once(string_node.node.clone(),Some(Duration::new(1, 0)));
    eprintln!("Exiting node ");
}

TanJunKiat avatar Dec 28 '23 02:12 TanJunKiat

@mxgrey @Yadunund Awesome guys.

Thank you so much for the help.

I managed to get the message on the bevy resource struct and bevy can print the string as it receives it.

TanJunKiat avatar Dec 28 '23 02:12 TanJunKiat