Skip to content
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

Get any Component from Entity #362

Closed
callym opened this issue Mar 29, 2018 · 7 comments

Comments

@callym
Copy link

commented Mar 29, 2018

Hi, is there any way to get an arbitrary component from an entity? The general idea I had was to have some systems that use callbacks/scripts for their behavior (e.g. AI/Collision), but then the callback might want to use other data from the entity. I know I could just add more components to the system, but ideally I could do something like
SystemData = (Entity, ReadAnyComponent) then in the callback, something like
let position = components.get::<Position>(&entity)
or something similar.
I'm not bothered about mutable access or anything similar, just a way of getting arbitrary components from entities without having to define it in the system data, even if there's a performance loss or having to keep the data behind a lock or an Arc or something.
Is this at all possible to do in any way?

@torkleyy

This comment has been minimized.

Copy link
Member

commented Mar 29, 2018

Hello!

First of all, scripting is a complex topic. There's no obvious & good solution for it, but I'm working on that issue.

There's no such way to read an arbitrary component from a system, but it's technically possible from outside. But still, what you describe above would be complicated, because you obviously can't call a generic function from a script. So you need quite a lot functionality:

  • somehow refer to the Rust types from Lua (probably Strings), mapped to some type metadata
  • somehow fetch an unknown type from the world (technically possible, but meh)
  • pass it to Lua and somehow make it usable there

In the latest (released) version, you did such a dynamic fetch with Resources::fetch_id_*. For that, you need to get the TypeId of MaskedStorage<MyComponent>. Then you need a way to extract an entity out of it. That is only possible using it as a trait object (where the trait is a custom, object-safe trait that allows getting an Entity). However, you already have a trait object (Box<Resource>). This trait object doesn't contain any other implementations obviously, so you need a way to somehow get the vtable of your trait object, which is again quite tricky. I made a helper here (not released).

TL;DR: It would be much easier for you to just pass all the components or write the code in Rust.

@callym

This comment has been minimized.

Copy link
Author

commented Mar 29, 2018

Thanks for your reply!

I'm using Gluon as my scripting language which I think can already handle Generics and Rust <-> Gluon type mappings. I already have some code like:

opal.world_mut().create_entity()
    .with(GluonUiComponent {
        name: String::from("Testing Ui"),
        expr: String::from("
let { (|>) } = import! std.function
let { bordered_rectangle, rgba } = import! conrod

let rect =
    bordered_rectangle.new 0.5 0.5 \"rect\" |>
        bordered_rectangle.color (rgba 1.0 0.6 0.4 1.0) |>
        bordered_rectangle.x_y 0.5 0.5 |>
        bordered_rectangle.border 0.55 |>
        bordered_rectangle.border_color (rgba 0.0 0.0 0.0 1.0) |>
        bordered_rectangle.build in rect"),
});

Which works by wrapping Conrod types, so I'm already dealing with the whole type marshaling problem. (Obviously the script here isn't loaded from a file yet, but the proof of concept works)

I guess one way would be some sort of AnyMap of components that you'd have to fill in in an earlier system? Or maybe using a message queue or futures to request components that I could then get from outside the system loop and pass in, but that'd introduce latency between the request and the response.

The problem I'm seeing with the passing all the components in way is that it means that I can't write core things as a library and extend them inside a separate project as easily because any components added in the separate project wouldn't be inside the SystemData of the core systems.

Writing it all in Rust would solve the type marshaling problems, but it would still leave the problem of getting arbitrary components for entities, right? I couldn't have a Movement AI system that works with entities having closures or scripts, I'd have to have a different one for each combination of components/each behavior needed?

@torkleyy

This comment has been minimized.

Copy link
Member

commented Mar 29, 2018

I generally don't recommend to store behavior in components. Not only is it against the ECS design, it also makes the whole fetching more complicated.

@torkleyy

This comment has been minimized.

Copy link
Member

commented Mar 29, 2018

I just noticed, there's one issue that makes modeling a system hard: slide-rs/shred#79

I will need to implement that sooner or later. Would that be useful to you?

@callym

This comment has been minimized.

Copy link
Author

commented Mar 30, 2018

I think the dynamic system data stuff would probably be useful, it'd let components defined outside the core library to be registered into the scripting system(s) without the slightly hacky AnyMap solution I've been toying with.

I'm attempting to use some components just for behavior, the general idea being that they communicate with systems through a messaging system to actually request state changes. Because there's no mutable access to the world in these behavior components, the idea seems to be working okay so far in my little toy demos.

Here's a short gif of a rough AnyMap-based solution here: https://twitter.com/callymcallym/status/979681749855801345

The Gluon script in the example is below. The scripts are compiled into a function which is given a copy of the data map for the entity (which should probably be an Option) and the map structure (which could probably also be stored in the data map).
In this example there's a data_reference in the map which contains an entity ID and that entity's map (if it has one) - from this entity ID, you can look the position of it up in the map.
I'm sure there are way better ways to actually code this in gluon, I'm struggling quite a lot with the functional elements of it, but as a proof of concept, it works. The main UI system doesn't need to know about positions, or other elements, or in theory the map.

let (data, map) = data

let { (|>) } = import! std.function
let data_reference = import! data_reference
let initial_position = import! initial_position
let { location } = import! map
let { vec3 } = import! cgmath
let { bordered_rectangle, rgba } = import! conrod

let position =
    match data_reference.get data with
    | Some ref ->
        match data_reference.entity ref with
            | Some entity ->
                match location map entity with
                    | Some location -> location
                    | None -> vec3.new 0.0 0.0 0.0
            | None -> vec3.new 0.0 0.0 0.0
    | None -> vec3.new 0.0 0.0 0.0

let color = rgba (vec3.x position / 10.0) (vec3.y position / 10.0) (vec3.z position / 10.0) 1.0

let rect =
    bordered_rectangle.new 0.5 0.5 \"rect\" |>
        bordered_rectangle.color color |>
        bordered_rectangle.x_y 0.5 0.5 |>
        bordered_rectangle.border 0.55 |>
        bordered_rectangle.border_color (rgba 0.0 0.0 0.0 1.0) |>
        bordered_rectangle.build in rect
@torkleyy

This comment has been minimized.

Copy link
Member

commented May 13, 2018

Hey @callym, you might be interested in this new example. It might actually be too dynamic for your particular case, but I thought I'd let you know because it's kind of my proof of concept for scripting.

@torkleyy

This comment has been minimized.

Copy link
Member

commented May 15, 2018

Closing this for now, I don't think it adds anything valuable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.