-
Notifications
You must be signed in to change notification settings - Fork 15
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
Refactor optional model properties #263
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Insert RobotMarker if instance has Tasks Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is moving things in a really good direction with the extensibility it gives. But we need to make sure to find the right balance between flexibility and explicitness.
I've left some inline comments to recommend changes that I think will help lean the balance back towards more explicitness.
rmf_site_format/src/task.rs
Outdated
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] | ||
#[cfg_attr(feature = "bevy", derive(Component, Reflect))] | ||
pub struct GoToPlace { | ||
pub location: NameInSite, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can just be a String
. NameInSite
only exists to be a component that wraps a String
.
rmf_site_format/src/model.rs
Outdated
/// Defines a property in a model description, that will be added to all instances | ||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] | ||
#[cfg_attr(feature = "bevy", derive(Component))] | ||
pub struct OptionalModelProperties<T: RefTrait>(pub Vec<OptionalModelProperty<T>>); | ||
pub struct OptionalModelData(pub serde_json::Value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if instead of the RobotMarker
component we replace this OptionalModelData
struct with
#[derive(Serialize, Deserialize, Component, ...)]
pub struct Robot {
/// A dictionary of skills that this robot can perform.
/// The value of each skill is its configuration.
/// Mobility is stored here.
pub skills: HashMap<String, serde_json::Value>,
}
This would simplify OptionalModelData
and RobotMarker
into one struct and makes the intention of the component more clear.
A complication is that Bundles can't contain optional components, so instead of storing Robot
inside of ModelDescriptionBundle
, we should store it adjacent, e.g.:
pub struct Site {
...
pub model_descriptions: HashMap<u32, ModelDescriptionBundle>,
pub robots: HashMap<u32, Robot>,
...
}
This way the Robot
component can be optionally inserted into whichever model descriptions are included in the robots
map. We just need to make sure to process model_descriptions
first to spawn the model descriptions and then insert the robots
components on top of those spawned entities.
rmf_site_format/src/model.rs
Outdated
@@ -163,7 +119,8 @@ pub struct ModelInstance<T: RefTrait> { | |||
pub marker: ModelMarker, | |||
#[serde(skip)] | |||
pub instance_marker: InstanceMarker, | |||
pub optional_properties: OptionalModelProperties<T>, | |||
#[serde(default, skip_serializing_if = "is_default")] | |||
pub optional_data: OptionalModelData, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think shoving Tasks
into this struct makes much sense for the long-term vision here.
- It implies that users are expected to assign specific tasks to specific robots in a specific order, but Open-RMF should have a task dispatching system.
- Tasks would be a good thing to be accounted for in scenarios.
Apologies for complicating the PR, but I would recommend the following:
We change Task
to be
pub enum Task {
Dispatch(DispatchTaskRequest),
Direct(RobotTaskRequest),
}
DispatchTaskRequest
can be based on the dispatch_task_request
schema and RobotTaskRequest
can be based on the robot_task_request
schema.
Each would internally contain a request: TaskRequest
.
In the Site
struct we would have
pub struct Site {
...
pub tasks: BTreeMap<u32, Task>,
}
and then in Scenarios:
pub struct Scenarios<T> {
...
pub added_tasks: Vec<T>,
pub removed_tasks: Vec<T>,
}
Admittedly this will complicate things for the experimental mapf extension since we'll need to introduce a task dispatcher before we can generate the motion plans, but I think this will make the most sense for the site editor in the long-term. We can follow-up this PR with a very rudimentary prototype task dispatcher that just assigns tasks to suitable robot instances in a round-robin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed in your PR description
I didn't add in Task schedule / integration with scenarios as this PR is getting a little bloated already, happy to refine this refactor and bring in task schedules after.
If you want to save this change for a follow-up PR that should be okay.
if let Some(go_to_place) = tasks | ||
.0 | ||
.first() | ||
.and_then(|t| serde_json::from_value::<GoToPlace>(t.config.clone()).ok()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some misgivings about the scalability here. This won't work for other task types that might also benefit from being able to highlight entities in the scene. It also requires looping through all locations, which isn't great.
I'd suggest we just remove this functionality for now and we can think more about how to give task-based visual cues some time down the road in a later PR.
rmf_site_format/src/mobility.rs
Outdated
pub config: serde_json::Value, | ||
pub bidirectional: bool, | ||
pub collision_radius: f32, | ||
pub rotation_center_offset: [f32; 2], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should put all these fields (bidirectional
, collision_radius
, and rotation_center_offset
) into struct DifferentialDrive
since they might not make sense for other forms of mobility, e.g. omni wheel drive.
I'm not certain that collision_radius
belongs inside of Mobility
at all.. collision information probably deserves its own component. But let's keep it in DifferentialDrive
for now until we can think it over more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With reference to the comment above, if we were to simplify RobotMarker
/OptionalModelData
to Robot
, collision_radius
might go well as a field under Robot
alongside skills
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I was thinking about what it should look like as its own thing parallel to Mobility
.
Here's one way I can imagine it working:
- Instead of
Robot::skills
we can call itRobot::properties
- We introduce a
Collision
struct similar toMobility
that has akind
andconfig
- We have a circle collision kind:
pub struct CircleCollision {
pub radius: f32,
pub offset: Vec2,
}
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Michael X. Grey <greyxmike@gmail.com>
Signed-off-by: Michael X. Grey <grey@openrobotics.org>
over Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
…optional_model_properties
Thanks @mxgrey for the comments! After another round of iteration here are the main changes with the suggestions:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implementation is looking a lot more clean, very nice!
I just left a few more small nitpicks that should be easy to get through, and then a few longer term remarks to think about for a future PR.
@@ -72,34 +73,18 @@ pub fn update_model_instance_visual_cues( | |||
if let Some(tasks) = tasks { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this isn't hurting anything, but I don't believe any of this block is needed any longer.
} | ||
} | ||
} | ||
// TODO(@xiyuoh) support task-based visual cues | ||
} | ||
} | ||
|
||
// When instances are removed, prevent any location from supporting them | ||
for removed in removed_components.read() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is needed any longer either.
rmf_site_editor/src/site/load.rs
Outdated
.insert(ModelProperty(robot_data.clone())); | ||
} else { | ||
// TODO(@xiyuoh) consider creating an entity if not found | ||
error!("Robot is pointing to a non-existent model description!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's include the robot_id
and robot_data
values in this message.
.get(&Collision::label()) | ||
.and_then(|c| serde_json::from_value::<Collision>(c.clone()).ok()) | ||
.filter(|c| c.kind() == CircleCollision::label()) | ||
.and_then(|c| serde_json::from_value::<CircleCollision>(c.config().clone()).ok()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since egui is an immediate mode GUI framework, we're going to be parsing these values on every frame update. That's probably not an enormous burden given how lightweight these structs are, but I worry about the scalability in general.
We can move along with this PR as-is, but we should think about how to find the right balance with this extensible structure. For example, maybe CircleCollision
should be a component which we add or remove from the entity as needed based on changes in the properties inside Robot
. Then this widget can query for CircleCollision
instead of doing this parsing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. I'll follow up on this.
let mobility = robot | ||
.properties | ||
.get(&mobility_label) | ||
.and_then(|m| serde_json::from_value::<Mobility>(m.clone()).ok()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar concerns about the high frequency deserialization here
|
||
/// This resource keeps track of all the properties that can be configured for a robot. | ||
#[derive(Resource)] | ||
pub struct RobotPropertyData(pub HashMap<String, HashMap<String, ShowRobotPropertyWidgetFn>>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Maybe rename this to RobotPropertyWidgets
. Data
doesn't seem like the right descriptor to me.
app.world | ||
.resource_mut::<RobotPropertyData>() | ||
.0 | ||
.get_mut(&Collision::label()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use
.entry(Collision::label())
.or_default()
here so that this doesn't fail if the entry isn't already present.
app.world | ||
.resource_mut::<RobotPropertyData>() | ||
.0 | ||
.get_mut(&Mobility::label()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could use .entry
here as well.
Signed-off-by: Xiyu Oh <xiyu@openrobotics.org>
This PR targets #254 to refactor the current way of storing optional model description/instance data to file. The following changes aim to address the issues brought up in the ticket:
Mobility
to represent mobile robots and accommodates mobility types other than the providedDifferentialDrive
.Mobility
is now an optional ModelProperty for model descriptions, and users can opt to selectDifferentialDrive
or add their own plugins to introduce other kinds of mobility to the editor.Mobility
data (kind
andconfig
), along with any other optional ModelProperty we may introduce for model descriptions, are stored to file underOptionalModelData
, which is aserde_json::Value
.Task
is no longer an enum that only supports default tasks (GoToPlace
andWaitFor
) in the site editor. It is similar toMobility
where users can create their own task kinds and add them to the editor using plugins. When creating a newTask
, any registered Task type can be selected from the editor, and their own configuration widget will appear accordingly.Tasks
are also stored to file underOptionalModelData
as aserde_json::Value
. To enable adding tasks to a model instance, users will need to toggle theRobot
property button for its model description.TypeId
to identify each ModelProperty, but it is not compatible withserde_json
. I've since changed it toComponentId
instead.Mobility configuration:
Task configuration:
Some considerations as I was working on the refactor:
Mobility
andTasks
within the renamedOptionalModelData
component (hopefully the naming is more suitable thanOptionalModelProperties
) instead of creating separate components for model descriptions/instances. Making the switching from a HashMap toserde_json::Value
should help with increasing configurability for either case.Mobility
is added for model descriptions andTasks
for model instances, like before.Mobility
orTasks
to the Model Description/Instance bundles respectively. To make sure that we don't have an unnecessary component required for non-mobile and non-robot models.MobileRobotMarker
toRobotMarker
and retained it as a separate ModelProperty fromMobility
. This is for future use cases where we may support non-mobile robots that are also capable of performing tasks. TheRobotMarker
property is used to allow task creation for model instances affiliated to the selected description.