Rust - Get component in Vec<Box dyn ComponentTrait>> (ECS) - rust

I'am a junior developer with Rust language.
I come from JavaScript and a lot of features and specificities are still unclear to me.
Currently, I'm looking to build my own ECS (entity component system) system in Rust.
I'am stay stuck when i want to get an component from an entity.
Actualy i store component in entity with an dyn boxed vector, it's that a good way?
My code:
enum ComponentEnum {
Position,
Size
}
trait Component {}
// Position Component
#[derive(PartialEq, PartialOrd, Debug)]
struct Position {
x: i32,
y: i32
}
// Size Component
#[derive(PartialEq, PartialOrd, Debug)]
struct Size {
height: i32,
width: i32
}
impl Component for Position {}
impl Component for Size {}
struct Entity {
id: usize,
components: Vec<Box<dyn Component>>
}
impl Entity {
fn new(index: usize) -> Self {
Entity { id: index, components: vec![] }
}
// Add a component in Entity
fn add_component<T: 'static + Component>(&mut self, component: T) {
self.components.push(Box::new(component));
}
}
struct EntityStore {
entities: Vec<Entity>,
current_index: usize,
}
impl EntityStore {
fn new() -> EntityStore {
EntityStore { entities: vec![], current_index: 0 }
}
fn generate_index(&self) -> usize {
unimplemented!();
}
// Stop creation system and update EntityStore current_index
fn end(&mut self) -> &mut Entity {
let entity = self.entities.get_mut(self.current_index).unwrap();
self.current_index = self.current_index + 1;
entity
}
fn create_entity(&mut self) -> &mut Self {
let mut entity = Entity::new(self.current_index);
self.entities.push(entity);
self
}
// Add component to entity
fn with_component<T: 'static + Component>(&mut self, component: T) -> &mut Self {
let mut entity = self.entities.get_mut(self.current_index).unwrap();
entity.add_component(component);
self
}
}
fn main() {
let mut es = EntityStore::new();
// Make entity
let mut entity1 = es
.create_entity()
.with_component(Position { x: 0, y: 0 })
.with_component(Size { height: 10, width: 10 })
.end();
// Get entity position component
// let component_position_entity1 = entity1.get_component(ComponentEnum::Position);
}
How can I get my Position component back from my entity?
EDIT:
Here, a test function to get a component (in Entity implementation) :
fn get_component(&mut self, component_enum: ComponentEnum) { //want return Position or Size component
let mut entity_components = &self.components;
// Search component by Name ?
// Currently, i try to compare Component trait with Component Enum element...
let component = entity_components
.iter_mut()
.find(|component| component == component_enum)
.unwrap();
// Here, the component type is "&mut Box<dyn Component>" but i want type like "&mut Position" or "&mut Size"
component // Here i need to return a Position or Size struct component, but i have Component Trait so i can't use Position/Size functions
}
Thanks.

I would use enums to differentiate between components types (bear in mind I have very little experience with ECS systems in general). Then you have various ways of getting one type, but I have made a method get_component, that takes a closure to use when finding the right components. You can then pass it a closure that checks for a position component specifically.
This is my implementation, based on your example:
// Position Component
#[derive(PartialEq, PartialOrd, Debug)]
struct Position {
x: i32,
y: i32
}
// Size Component
#[derive(PartialEq, PartialOrd, Debug)]
struct Size {
height: i32,
width: i32
}
#[derive(PartialEq, PartialOrd, Debug)]
enum Component {
Position(Position),
Size(Size)
}
struct Entity {
id: usize,
components: Vec<Component>
}
impl Entity {
fn new(index: usize) -> Self {
Entity { id: index, components: vec![] }
}
// Add a component in Entity
fn add_component(&mut self, component: Component) {
self.components.push(component);
}
fn get_component(&self, pred: impl Fn(&&Component) -> bool) -> Option<&Component>{
self.components.iter().find(pred)
}
}
struct EntityStore {
entities: Vec<Entity>,
current_index: usize,
}
impl EntityStore {
fn new() -> EntityStore {
EntityStore { entities: vec![], current_index: 0 }
}
fn generate_index(&self) -> usize {
unimplemented!();
}
// Stop creation system and update EntityStore current_index
fn end(&mut self) -> &mut Entity {
let entity = self.entities.get_mut(self.current_index).unwrap();
self.current_index = self.current_index + 1;
entity
}
fn create_entity(&mut self) -> &mut Self {
let mut entity = Entity::new(self.current_index);
self.entities.push(entity);
self
}
// Add component to entity
fn with_component(&mut self, component: Component) -> &mut Self {
let mut entity = self.entities.get_mut(self.current_index).unwrap();
entity.add_component(component);
self
}
}
fn main() {
let mut es = EntityStore::new();
// Make entity
let mut entity1 = es
.create_entity()
.with_component(Component::Position(Position { x: 0, y: 0 }))
.with_component(Component::Size(Size { height: 10, width: 10 }))
.end();
// Get entity position component
let component_position_entity1 = entity1.get_component(|c| if let Component::Position(_) = c { true} else {false});
println!("{:?}", component_position_entity1);
}
Note that there many alternatives to my get_component, but my main point is to use enums to differentiate component types and to not use Box<dyn Component>.

#user4815162342 posted this in the comments to index into entity.components directly:
like this:
fn main() {
let mut es = EntityStore::new();
// Make entity
let mut entity1 = es
.create_entity()
.with_component(Position { x: 0, y: 0 })
.with_component(Size { height: 10, width: 10 })
.end();
// Get entity position component
let v0 = &entity1.components[0];
let v1 = &entity1.components[1];
v0.display();
v1.display();
}
But since the index depends on the order that the entities were added, then you'd be much better off storing the entities components in a hash map, or with an enum tag to make it clearer what each component is.

Related

Rc::downgrade does not seem to drop ownership in a Rust program

I'm trying to create a simple minesweeper in Rust. For this I want to create a Grid object, holding all Case objects. When a Case is clicked, it notifies the Grid.
I want all cases to have a Weak reference to the grid. Why does the borrow checker prevent me from doing so?
Quoting the doc:
Weak is a version of Rc that holds a non-owning reference to the managed allocation.
link marked by the comment should not be owning the grid, yet the compiler tells me the value has been moved.
use std::rc::{Rc, Weak};
pub struct Grid {
dimensions: (usize, usize),
n_mines: usize,
content: Vec<Vec<Case>>,
}
impl Grid {
pub fn new(dimensions: (usize, usize), n_mines: usize) -> Self {
let (n, m) = dimensions;
let mines: Vec<Vec<Case>> = Vec::with_capacity(m);
let mut grid = Self {
dimensions: dimensions,
n_mines: n_mines,
content: mines,
};
let link = Rc::new(grid);
let link = Rc::downgrade(&link); // here link loses the ownership of grid, right ?
println!("{}\n", grid.n_mines);
for i in 0..m {
let mut line: Vec<Case> = Vec::with_capacity(n);
for j in 0..n {
let case = Case::new_with_parent((i, j), Weak::clone(&link));
line.push(case)
}
grid.content.push(line);
}
grid
}
pub fn click_neighbours(&mut self, coordinates: (usize, usize)) {
let surrouding = vec![(0, 0), (0, 1)]; // not a real implementation
for coord in surrouding {
self.content[coord.0][coord.1].click();
}
}
}
pub struct Case {
pub coordinates: (usize, usize),
parent: Weak<Grid>,
}
impl Case {
pub fn new_with_parent(coordinates: (usize, usize), parent: Weak<Grid>) -> Case {
Self {
coordinates: coordinates,
parent: parent,
}
}
pub fn click(&mut self) {
match self.parent.upgrade() {
Some(x) => x.click_neighbours(self.coordinates),
None => println!("whatever, not the issue here"),
}
}
}
fn main() {
let grid = Grid::new((10, 10), 5);
}
I tried multiple variation around enforcement of borrowing rules at runtime by putting the grid content into a RefCell. I'm also currently trying to adapt the Observer design pattern implementation in Rust to my problem, but without any success so far.
There is a misconception here, by moving the grid here:
let link = Rc::new(grid);
you irrevocably destroy grid, nothing you do to link brings back your old grid.
The only thing that could do so would be to use Rc::try_unwrap to unwrap link and assign it back to grid,
but that's not what you want to do here.
Instead you can additionally wrap the grid in a RefCell like so:
let grid = Rc::new(RefCell::new(grid));
Which in then lets you edit the grid inside with borrow_mut:
grid.borrow_mut().content.push(line);
You can see in PitaJ's answer how the full listing of your code should look like.
grid is moved into Rc::new, so you can't use it any longer. You'll have to return that Rc from Grid::new
You can't modify the value within the Rc while you hold Weak references to it. So you'll have to wrap the inner value in a RefCell as well. Otherwise the loop where you create the lines is impossible
Here's a valid version of your code:
use std::cell::RefCell;
use std::rc::{Rc, Weak};
pub struct Grid {
dimensions: (usize, usize),
n_mines: usize,
content: Vec<Vec<Case>>,
}
impl Grid {
pub fn new(dimensions: (usize, usize), n_mines: usize) -> Rc<RefCell<Self>> {
let (n, m) = dimensions;
let mines: Vec<Vec<Case>> = Vec::with_capacity(m);
let grid = Rc::new(RefCell::new(Self {
dimensions: dimensions,
n_mines: n_mines,
content: mines,
}));
let link_weak = Rc::downgrade(&grid);
println!("{}\n", grid.borrow().n_mines);
{
// avoid repeatedly calling `borrow_mut` in the loop
let grid_mut = &mut *grid.borrow_mut();
for i in 0..m {
let mut line: Vec<Case> = Vec::with_capacity(n);
for j in 0..n {
let case = Case::new_with_parent((i, j), Weak::clone(&link_weak));
line.push(case)
}
grid_mut.content.push(line);
}
}
grid
}
pub fn click_neighbours(&mut self, coordinates: (usize, usize)) {
let surrouding = vec![(0, 0), (0, 1)]; // not a real implementation
for coord in surrouding {
self.content[coord.0][coord.1].click();
}
}
}
pub struct Case {
pub coordinates: (usize, usize),
parent: Weak<RefCell<Grid>>,
}
impl Case {
pub fn new_with_parent(coordinates: (usize, usize), parent: Weak<RefCell<Grid>>) -> Case {
Self {
coordinates: coordinates,
parent: parent,
}
}
pub fn click(&mut self) {
match self.parent.upgrade() {
Some(grid) => grid.borrow_mut().click_neighbours(self.coordinates),
None => println!("whatever, not the issue here"),
}
}
}
fn main() {
let grid = Grid::new((10, 10), 5);
}
playground

Collection with Traits that return Self

I'm trying to have a collection of objects that implement a particular trait.
If I use a trait that returns a value, this works
use std::collections::BTreeMap;
struct World {
entities: Vec<usize>,
database: BTreeMap<usize, Box<ReadValue>>,
//database : BTreeMap<usize,Box<ReadEcs>>, // Doesn't work
}
struct SourceInputGateway {
entity_id: usize,
}
trait ReadValue {
fn read(&self) -> f32;
}
impl ReadValue for SourceInputGateway {
fn read(&self) -> f32 {
0.0
}
}
But if I want to return Self as a value then this doesn't work, either as a method template param or associated type
trait ReadEcs {
type T;
fn read(&self) -> &Self::T;
}
impl ReadEcs for SourceInputGateway {
type T = SourceInputGateway;
fn read(&self) -> &Self::T {
self
}
}
What I would like to do is have a map of types that implement ReadEcs, the concrete type of which is not important.
Further clarification edit
If I expand the example by adding
// Different sized type
struct ComputeCalculator {
entity_id : usize,
name : String,
}
impl ReadValue for ComputeCalculator {
fn read(&self) -> f32 {
1230.0
}
}
then I can do this
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_write() {
let mut world = World::new();
world.database.insert(0,Box::new(SourceInputGateway{ entity_id : 1}));
world.database.insert(2,Box::new(ComputeCalculator{ entity_id : 2 , name : "foo".into() }));
for (k,ref v) in world.database {
let item : &Box<ReadValue> = v;
item.read();
}
}
}
but if I change or add a trait method that returns Self, I can't do this.
I'd like to understand a way to get around it without unsafe pointers.
I thought about this a little more and I think it's possible to solve this while retaining all the advantages of type safety and contiguous storage.
Defining a entity manager with pointers to storage
struct Entities {
entities: Vec<usize>,
containers: Vec<Box<Storage>>,
}
The components themselves that store data related to behaviour
struct Position {
entity_id: usize,
position: f32,
}
struct Velocity {
entity_id: usize,
velocity: f32,
}
We will have many instances of components, so we need a contiguous memory store, accessed by an index.
struct PositionStore {
storage: Vec<Position>,
}
struct VelocityStore {
storage: Vec<Velocity>,
}
trait Storage {
// Create and delete instances of the component
fn allocate(&mut self, entity_id: usize) -> usize;
// Interface methods that would correspond to a base class in C++
fn do_this(&self);
// fn do_that(&self);
}
The trait for the store implements an arena style storage plus the methods it would pass to to the components. This would likely be in the "System" part of an ECS but is left as an exercise for later.
I would like a hint as to how to pass a Fn() that constructs a custom object to the allocate() method. I've not figured that out yet.
impl Storage for PositionStore {
fn allocate(&mut self, entity_id: usize) -> usize {
self.storage.push(Position {
entity_id,
position: 0.0,
});
self.storage.len() - 1
}
fn run(&self) {
self.storage.iter().for_each(|item| { println!("{}",item.position); });
}
}
impl Storage for VelocityStore {
fn allocate(&mut self, entity_id: usize) -> usize {
self.storage.push(Velocity {
entity_id,
velocity: 0.0,
});
self.storage.len() - 1
}
fn do_this(&self) {
self.storage.iter().for_each(|item| { println!("{}",item.velocity); });
}
}
Some boiler plate.
impl Default for PositionStore {
fn default() -> PositionStore {
PositionStore {
storage: Vec::new(),
}
}
}
impl Default for VelocityStore {
fn default() -> VelocityStore {
VelocityStore {
storage: Vec::new(),
}
}
}
I think this could be thought about a little more. It stores the relationship between the storage of the components and their position
Instead of T::default(), you may want to pass a lambda function that has a specific initialisation for each of your components
impl Entities {
fn register<T>(&mut self) -> usize
where
T: Storage + Default + 'static,
{
self.containers.push(Box::new(T::default()));
self.containers.len() - 1
}
fn create<T>(&mut self, entity_id: usize, id: usize) -> usize {
self.containers[id].allocate(entity_id)
}
fn run_loop(&self) {
self.containers.iter().for_each(|x| x.do_this());
}
}
A test case to see if this works
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arbitary() {
let mut entities = Entities {
entities: Vec::new(),
containers: Vec::new(),
};
let velocity_store_id = entities.register::<VelocityStore>();
let position_store_id = entities.register::<PositionStore>();
let _ = entities.create::<Velocity>(123, velocity_store_id);
let _ = entities.create::<Velocity>(234, velocity_store_id);
let _ = entities.create::<Position>(234, position_store_id);
let _ = entities.create::<Position>(567, position_store_id);
entities.run_loop();
}
}

Nested struct with reference

I have some nested structs and cannot create a back reference to the parent struct. An example:
struct Foo<'a> {
parent: &'a Bar<'a>,
}
impl<'a> Foo<'a> {
fn new(parent: &'a Bar) -> Self {
Foo { parent: parent }
}
fn hello_world(&self) -> String {
self.parent.hello().to_owned() + " world"
}
}
struct Bar<'b> {
child: Option<Foo<'b>>,
data: &'static str,
}
impl<'b> Bar<'b> {
fn new() -> Self {
Bar {
child: None,
data: "hello",
}
}
fn hello(&self) -> &str {
self.data
}
fn get_foo(&self) -> Option<&Foo> {
self.child.as_ref()
}
}
fn main() {
let bar = Bar::new();
assert_eq!("hello", bar.hello());
match bar.get_foo() {
Some(foo) => assert_eq!("hello world", foo.hello_world()),
None => (),
}
}
How can I replace None with Some<Foo> with a reference to Bar? So far I'm not sure that it is possible.
It's not exactly a drop-in solution for your example, but I believe you can create "circular references" as you specify using Arc and RwLock. The API is not exactly the same (e.g., parent is an optional field), I renamed some objects, and it is definitely more verbose, but your tests pass!
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone)]
struct Child {
parent: Option<Arc<RwLock<Parent>>>
}
impl Child {
fn new() -> Self {
Child {
parent: None
}
}
fn hello_world(&self) -> String {
let x = self.parent.as_ref().unwrap().clone();
let y = x.read().unwrap();
y.hello().to_owned() + " world"
}
}
#[derive(Debug, Clone)]
struct Parent {
child: Option<Arc<RwLock<Child>>>,
data: &'static str
}
impl Parent {
fn new() -> Self {
Parent {
child: None,
data: "hello"
}
}
fn hello(&self) -> &str {
self.data
}
fn get_child(&self) -> Option<Arc<RwLock<Child>>> {
self.child.as_ref().map(|x| x.clone() )
}
}
fn main() {
let parent = Arc::new(RwLock::new(Parent::new()));
let child = Arc::new(RwLock::new(Child::new()));
parent.write().unwrap().child = Some(child.clone());
child.write().unwrap().parent = Some(parent.clone());
assert_eq!("hello", parent.read().unwrap().hello());
{
let x = parent.read().unwrap();
match x.get_child() {
Some(child) => { assert_eq!("hello world", child.read().unwrap().hello_world()); }
None => {},
}
}
}
I have a similar problem, and am not entirely satisfied with the proposed solutions.
If your structure is really nested (i.e. you have a notion of "parent" and "child", with a unique parent for each child), then it seems natural that the parent should own the child(ren). So using Rc/Arc (which are designed to allow for multiple owners) does not look like the right solution -- all the less so because, as #Shepmaster points out, it "encourages" (or at least allows) the creation of cyclic references.
My idea was to have each child hold a raw pointer to its parent:
pub struct Node {
parent: *mut Node,
// ...
}
Since a node is owned by its parent, it can only be borrowed (resp. mutably borrowed) while its parent is being borrowed (resp. mutably borrowed). So in that context, it should be safe to cast self.parent to a &Node (resp. &mut Node, if self is mutable).
impl Node {
pub fn get_parent(&self) -> Option<&Node> {
unsafe { self.parent.as_ref() }
}
pub fn get_mut_parent(&mut self) -> Option<&mut Node> {
unsafe { self.parent.as_mut() }
}
}
However, this requires that the address of the parent node never changes (i.e. the parent is never moved). This can be achieved by only ever handling boxed nodes.
pub struct Node {
parent: *mut Node,
children: Vec<Box<Node>>,
// ..
}
impl Node {
pub fn new(data: &str) -> Box<Node> {
Box::new(Node {
parent: std::ptr::null_mut(),
children: vec![],
// ..
})
}
pub fn append_child(&mut self, mut child: Box<Node>) -> usize {
child.parent = self;
self.children.push(child);
self.children.len() - 1
}
}
I implemented a full-fledged example in the playground.

Cannot move out of borrowed content and Builder pattern

I am just learning Rust. I am trying to create a builder struct for my Game struct. Here is the code:
struct Input {
keys_pressed: HashMap<VirtualKeyCode, bool>,
}
pub struct GameBuilder {
settings: GameSettings,
input: Input,
}
impl GameBuilder {
pub fn new() -> GameBuilder {
GameBuilder {
settings: GameSettings {
window_dimensions: (800, 600),
title: "".to_string(),
},
input: Input {
keys_pressed: HashMap::new(),
}
}
}
pub fn with_dimensions(&mut self, width: u32, height: u32) -> &mut GameBuilder {
self.settings.window_dimensions = (width, height);
self
}
pub fn with_title(&mut self, title: &str) -> &mut GameBuilder {
self.settings.title = title.to_string();
self
}
pub fn game_keys(&mut self, keys: Vec<VirtualKeyCode>) -> &mut GameBuilder {
for key in keys {
self.input.keys_pressed.insert(key, false);
}
self
}
pub fn build(&self) -> Game {
let (width, height) = self.settings.window_dimensions;
Game {
display: glutin::WindowBuilder::new()
.with_dimensions(width, height)
.with_title(self.settings.title.to_string())
.build_glium()
.ok()
.expect("Error in WindowBuilder"),
state: GameState::Running,
input: self.input,
}
}
}
But this code complains in the last line input: self.input with this:
error: cannot move out of borrowed content
I think I understand why. Since the argument passed in the function is &self, I cannot take ownership of it, and that what the last line is doing.
I thought that maybe changing &self to self would work, but then the compile argues that I cannot mutate self.
There is also the Copy trait from what I know, and that maybe should solve the problem. But Input is basically a HashMap, which means that a copy could be expensive if the hash itself is too big.
How would be a nice way of solving this problem?
Edit:
I tried doing this:
#[derive(Debug, Copy, Clone)]
struct Input {
keys_pressed: HashMap<VirtualKeyCode, bool>,
}
But the compiler complains:
error: the trait `Copy` may not be implemented for this type; field `keys_pressed` does not implement `Copy`
Given how your method signatures are formulated, you appear to be aiming for chaining:
let game = GameBuilder::new().with_dimensions(...)
.with_title(...)
.build();
In Rust, this requires that GameBuilder be passed by value:
pub fn with_dimensions(self, ...) -> GameBuilder {
// ...
}
And in order to be able to mutate self within the method, you need to make it mut:
pub fn with_dimensions(mut self, ...) -> GameBuilder {
}
If you change the signature of with_dimensions, with_title, game_keys and build to take self by value (mut self if mutation is intended), then chaining should work.
Try the builder pattern with Option and take()
Example:
#[derive(PartialEq, Debug)]
struct Game {
window: Window,
}
#[derive(PartialEq, Debug)]
struct Window {
title: String,
dimensions: (u32, u32),
}
struct GameBuilder {
window_title: Option<String>,
window_dimensions: Option<(u32, u32)>,
}
impl GameBuilder {
fn new() -> Self {
Self {
window_title: None,
window_dimensions: None,
}
}
fn window_title(&mut self, window_title: &str) -> &mut Self {
self.window_title = Some(window_title.to_owned());
self
}
fn window_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
self.window_dimensions = Some((width, height));
self
}
fn build(&mut self) -> Result<Game, Box<dyn std::error::Error>> {
Ok(Game {
window: Window {
// `ok_or(&str)?` works, because From<&str> is implemented for Box<dyn Error>
title: self.window_title.take().ok_or("window_title is unset")?,
dimensions: self
.window_dimensions
.take()
.ok_or("window_dimensions are unset")?,
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let mut builder = GameBuilder::new();
builder.window_title("Awesome Builder");
builder.window_dimensions(800, 600);
let game = builder.build();
assert_eq!(
game.expect("build success"),
Game {
window: Window {
title: "Awesome Builder".into(),
dimensions: (800, 600)
}
}
);
}
#[test]
fn test_1() {
let game2 = GameBuilder::new()
.window_title("Busy Builder")
.window_dimensions(1234, 123)
.build();
assert_eq!(
game2.expect("build success"),
Game {
window: Window {
title: "Busy Builder".into(),
dimensions: (1234, 123),
}
}
)
}
}

Is there an idiomatic way to implement the component pattern?

Basically a object (struct) is constructed by composing different components. Each concrete component being easily swapped by another component matching the interface (I guess trait).
I'm currently trying to implement with traits which got me into some errors and made me start thinking if this is a common thing in Rust.
// usage example
fn main() {
let obj = MainObject::new(Component1::new(), Component2::new(), Component3());
// Where each component is a type(struct) with some well predefined methods.
}
The main idea behind this is to implement the Component pattern commonly used in games. Basically the game would contain a lot of different objects, with slight variations in behavior and contained data. Instead of having a big class hierarchy, the objects are composed of standard components, more complete example would be.
pub struct Container
{
input: InputHandlerComponent, // Probably a trait
physics: PhysicsComponent, // Probably a trait
renderer: RendererCompoent // Probably a trait
}
impl Container {
fn new(p: PhysicsComponent, i: InputComponent, r: RenderComponent) -> Container {
Container {input: i, physics: p, renderer: r}
}
}
struct ConcretePhysicsComponent;
impl PhysicsComponent for ConcretePhysicsComponent
{
// ...
}
struct ConcreteInputComponent;
impl InputComponent for ConcreteInputComponent
{
// ...
}
struct ConcreteRendererComponent;
impl RendererComponent for ConcreteRendererComponent
{
// ...
}
struct AnotherConcreteRendererComponent;
impl RendererComponent for AnotherConcreteRendererComponent
{
// ...
}
// usage example
fn main() {
let obj = Container::new(ConcreteInputComponent::new(), ConcretePhysicsComponent::new(), ConcreteRendererComponent::new());
// Where each component is a type(struct) with some well predefined methods.
// This is a slightly modified version of this object, with changed rendering behaviour
let obj2 = Container::new(ConcreteInputComponent::new(), ConcretePhysicsComponent::new(), AnotherConcreteRendererComponent::new()); }
It sounds like you are just asking about traits, multiple concrete implementations of that trait, and a wrapper object that restricts itself to types that implement that trait. Optionally, the container can implement the trait by delegating it to the inner object.
trait Health {
fn life(&self) -> u8;
fn hit_for(&mut self, lost_life: u8);
}
#[derive(Debug, Copy, Clone)]
struct WimpyHealth(u8);
impl Health for WimpyHealth {
fn life(&self) -> u8 { self.0 }
fn hit_for(&mut self, lost_life: u8) { self.0 -= lost_life * 2; }
}
#[derive(Debug, Copy, Clone)]
struct BuffHealth(u8);
impl Health for BuffHealth {
fn life(&self) -> u8 { self.0 }
fn hit_for(&mut self, lost_life: u8) { self.0 -= lost_life / 2; }
}
#[derive(Debug, Copy, Clone)]
struct Player<H> {
health: H,
}
impl<H> Health for Player<H>
where H: Health
{
fn life(&self) -> u8 { self.health.life() }
fn hit_for(&mut self, lost_life: u8) { self.health.hit_for(lost_life) }
}
fn main() {
let mut player_one = Player { health: WimpyHealth(128) };
let mut player_two = Player { health: BuffHealth(128) };
player_one.hit_for(12);
player_two.hit_for(12);
println!("{:?}", player_one);
println!("{:?}", player_two);
}
it is not possible to have an array of such Players without using Boxed values
That's correct. An array or vector (or any generic type, really) needs to all be of the same type. This is especially important for arrays/vectors because their memory layout is contiguous and each item needs to be at a fixed interval.
If you were allowed to have different types, then you could have one player that had a health that took 1 byte and another player with health that took 2 bytes. Then all the offsets would be incorrect.
You can implement the Health trait for a Box<Health>, and then the Player objects can be stored sequentially, but they would each have a pointer to the appropriate concrete implementation of Health via the box.
impl<H: ?Sized> Health for Box<H>
where H: Health
{
fn life(&self) -> u8 { (**self).life() }
fn hit_for(&mut self, lost_life: u8) { (**self).hit_for(lost_life) }
}
fn main() {
let mut players = vec![
Player { health: Box::new(WimpyHealth(128)) as Box<Health> },
Player { health: Box::new(BuffHealth(128)) as Box<Health> }
];
for player in players.iter_mut() {
player.hit_for(42);
}
println!("{:?}", players[0].life());
println!("{:?}", players[1].life());
}

Resources