I am trying to build a entity component system as part of my journey to learn Rust. I had an idea where each component would have a static id, and objects would have a HashMap of the components it contains (with the limit of one component per type).
Here is the object itself:
pub struct Object {
// TODO : components !
components: HashMap<i32, Box<dyn Component>>
}
impl Object {
pub fn add_component<C: Component>(&self) {
self.components.insert(C::id(), Box::new(C::new()));
}
pub fn get_component<C: Component>(&self) -> Option<&C> {
return self.components.get(C::id())
}
}
And here is my Component trait:
pub trait Component {
fn id() -> i32 {
// one must ensure this returns different id for every component
return 0;
}
fn new<C: Component>() -> C;
fn require_rendering(&self) -> bool {
return false;
}
fn some_function_on_component(&mut self) {
// do something on the component
}
}
Unfortunately, I get this error :
"this trait cannot be made into an object...
...because associated function id
has no self
parameter"
Could anyone explain why does this not work, and how to work around it?
Most of those are fairly easily fixable by following the compiler messages.
The biggest hurdle I came across while trying to get your code to compile is to downcast the Box<dyn>
back to its original type.
That's my attempt, I have no idea if it actually does something in the end, but at least it compiles :)
use std::any::Any;
use std::collections::HashMap;
pub struct Object {
// TODO : components !
components: HashMap<i32, Box<dyn Component>>,
}
impl Object {
pub fn new() -> Self {
Self {
components: HashMap::new(),
}
}
pub fn add_component<C: 'static + Component>(&mut self) {
self.components.insert(C::id(), Box::new(C::new()));
}
pub fn get_component<C: 'static + Component>(&self) -> Option<&C> {
self.components
.get(&C::id())
.map(|boxed_component| boxed_component.as_any().downcast_ref::<C>().unwrap())
}
}
pub trait Component {
fn id() -> i32
where
Self: Sized;
fn new() -> Self
where
Self: Sized;
fn require_rendering(&self) -> bool {
return false;
}
fn some_function_on_component(&mut self) {
// do something on the component
}
fn as_any(&self) -> &dyn Any;
}
Things I've done:
Self: Sized
to id()
and new()
. Both of those are trait functions and therefore have to be resolved at runtime. The step of resolving the type requires something called a "vtable", which only exists on types that actually have a size. (that's at least my understanding of the problem)new
with Self
, as this is what you probably actually want.mut
to self
in add_component
id()
to actually force implementing structs to overwrite it&dyn Component
to the actual component type in get_component
, based on this post. Of course there are probably different solutions, I just picked that one because I didn't feel like doing any further research :)I didn't solve the problem that you currently only get non-mutable objects out of get_component
. This will probably be the next thing you will tackle.
All in all, here is a minimal working example of your code with a "Name" and an "Age" component, just to demonstrate that your approach is feasible: