amethyst/legion

How to get component/components from a particular entity?

DrOptix opened this issue · 2 comments

I browsed the docs, but I could not figure out for myself.

I know we have queries that will let us go trough all the components of type C from a world, but is there a way to get a component of type C from a particular entity?

Actually I'm interested in getting references &T and &mut T to the component attached to a particular entity.

I tried to do something like:

pub fn with_component<T: Component>(&self) -> Option<&T> {
        if let Ok(entry) = self.world.borrow_mut().entry_ref(self.entity) {
            if let Ok(component) = entry.get_component::<T>() {
                return Some(component);
            }
        }

        None
    }

but then it complained that the reference I return does not live long enough.

I want something like this because I just want &T or &mut T to the component, why do we need to deal with that entry step? :-(

Thank you!

More info. My current solution is to pass a callback that gets called with the component reference,

I need all this for a little game engine I work on:
https://github.com/DrOptix/ghost-engine

Relevant files are entity.rs from ghost-engine project and sandbox_layer.rs from ghost-sandbox

pub fn with_component<T: Component, F: FnOnce(&T)>(&self, f: F) {
        if let Ok(entry) = self.world.borrow_mut().entry_ref(self.entity) {
            if let Ok(component) = entry.get_component::<T>() {
                f(component);
            }
        }
    }

    pub fn with_component_mut<T: Component, F: FnOnce(&mut T)>(&mut self, f: F) {
        if let Ok(mut entry) = self.world.borrow_mut().entry_mut(self.entity) {
            if let Ok(component) = entry.get_component_mut::<T>() {
                f(component);
            }
        }
    }

And I use it like this when I want to draw some kind of egui UI for the component:

self.square_entity
                    .with_component_mut::<SpriteRendererComponent, _>(|comp| {
                        ui.horizontal(|ui| {
                            ui.label("Color:");

                            let mut new_color = [
                                (comp.color.x * 255.0) as u8,
                                (comp.color.y * 255.0) as u8,
                                (comp.color.z * 255.0) as u8,
                                (comp.color.w * 255.0) as u8,
                            ];

                            ui.color_edit_button_srgba_unmultiplied(&mut new_color);

                            comp.color = glam::vec4(
                                new_color[0] as f32 / 255.0,
                                new_color[1] as f32 / 255.0,
                                new_color[2] as f32 / 255.0,
                                new_color[3] as f32 / 255.0,
                            );
                        });

                        ui.separator();
                    });

I can live with this, but I would try to avoid the need to design for my self an API where I need to provide callbacks.

Hi @DrOptix,
Unfortunately, I don't see another solution in your case since your Entity is self-contained and stores the world wrapped into RefCell.

However, have you considered the option of making your engine less object-oriented and more ECS-driven, meaning that most of the logic is written in systems?
As an example, I can show you some code I wrote when considering such OOP-to-ECS migration. This system is added to the main application Schedule and contains all the logic needed to create and modify entities in my sandbox (I can't say it's super efficient, but I like how it works):

#[legion::system]
fn manual_creator(#[state] current_editable_entity: &mut Option<Entity>,
                  #[resource] mouse: &Mouse,
                  #[resource] factory: &EntityFactory,
                  #[resource] mouse_button_pressed: &Events<MouseButtonPressed>,
                  #[resource] mouse_button_released: &Events<MouseButtonReleased>,
                  commands: &mut CommandBuffer,
                  world: &mut SubWorld,
                  _dummy: &mut Query<(&Position, &mut Mass)>)
{
    // pressing left mouse button initiates new entity creation
    {
        let should_create_new_entity = mouse_button_pressed
            .iter()
            .find(|MouseButtonPressed { button, .. }| *button == MouseButton::Left)
            .is_some();

        if should_create_new_entity {
            if let Some(CursorState::InsideWindow { x, y }) = mouse.get_cursor_state() {
                *current_editable_entity = Some(factory.create_blank(x, y, commands));
            }
            return;
        }
    }

    // releasing left mouse button finalizes entity creation
    {
        let should_finalize_creation = mouse_button_released
            .iter()
            .find(|MouseButtonReleased { button, .. }| *button == MouseButton::Left)
            .is_some();

        if should_finalize_creation {
            factory.finalize_creation(current_editable_entity.take().unwrap(), commands, world);
            return;
        }
    }

    // moving the mouse cursor updates the mass for the current editable entity (if any)
    if current_editable_entity.is_some() {
        if let Some(CursorState::InsideWindow { x, y}) = mouse.get_cursor_state() {
            let mut entry = world.entry_mut(*current_editable_entity.as_ref().unwrap()).unwrap();
            let position = entry.get_component::<Position>().unwrap().clone();
            let mass = entry.get_component_mut::<Mass>().unwrap();

            let distance_from_center = (position.0 - Vec2::new(x, y)).magnitude();
            *mass = radius_to_mass(distance_from_center);
        }
    }
}