[Solved] Suggestion needed for a Vec of Trait, but I might as well be completely off


#1

Let me explain the current use case:

I have something as simple as this:

struct Scene {
  renderers: Vec<Rc<Renderer>>
}

Renderer should either be a Trait or something that lets me pass two type or renderers, an EverythingRenderer that is actually used to render anything that is contained in the scene (I’m not getting into the details as that’s probably not needed) and a TagRenderer that renders only items that have a certain tag.

I thought that since there’s no OOP I might define Renderer as a Trait and have EverythingRenderer and TagRenderer just implement that trait but then the trait has no size so I can’t actually store it into that Vec.

Another option could be to pass a generic to Scene along the line of struct Scene<R> where R: Renderer + Sized but since that Scene object it going to contain other stuff with similar needs I might end up having a struct that requires a lot of generics and I’m not sure that’s good, especially when I later need to pass Scene to some functions.

I thought about using an Enum since I know in advance the renderers that I’m implementing, but that would cut me off with the chance of adding a new renderer in some user code outside of my library unless there’s a way to extend an Enum.

Any hint at what would be the correct idiomatic way to implement this kind of pattern in Rust?
Cheers!


#2

Can you elaborate on this? I’m not sure I understand the issue here. What exactly did you try?


#3

My current best try has been something along this:

pub struct Scene<R> where R: Renderer + Sized {
    renderers: Vec<Rc<R>>,
}

And then in EverythingRenderer I have this:

impl Renderer for EverythingRenderer {
    fn render_end <'sb>(scene: &Scene<EverythingRenderer>, renderer: &'sb mut Canvas<Window>, spritebatch: &'sb mut SpriteBatch) {
        scene.render_entities();
        spritebatch.end(renderer);
    }
}

it works but I really do not like this. And I can’t use this to implement a default render_end inside the Renderer trait itself because the trait isn’t sized and the compiler would keep throwing errors at me.


#4

Is there a reason you’re making Scene generic on Renderer and requiring that it be Sized? I’d have thought, based on your previous post, that Scene is not generic and just stores Renderer trait objects (i.e. the Vec<Rc<Renderer>>).


#5

That was my first try:

pub struct Scene {
    renderers: Vec<Rc<Renderer>>,
}
impl Renderer for EverythingRenderer {
    fn render_end <'sb>(scene: &Scene, renderer: &'sb mut Canvas<Window>, spritebatch: &'sb mut SpriteBatch) {
        scene.render_entities();
        spritebatch.end(renderer);
    }
}

And this is how the Renderer trait is defined:

pub trait Renderer {
    fn before_render() {

    }

    fn render_begin <'sb>(renderer: &'sb mut Canvas<Window>, camera: &'sb mut Camera<ViewportAdapter>, spritebatch: &'sb mut SpriteBatch, shader: Shader) {
        // Sets the current camera viewport if the camera has one
        match camera.get_viewport_adapter() {
            &Some(ViewportAdapter) => {
                let r = camera.get_viewport_adapter();
                let rr = r.unwrap().get_viewport();
                let vp = Rect::new(rr.x as i32, rr.y as i32, rr.w as u32, rr.h as u32);
                renderer.set_viewport(vp);
            },
            _ => {}
        }

        // MonoGame resets the Viewport to the RT size without asking so we have to let the Camera know to update itself
        camera.force_matrix_update();

        let m = camera.get_transform_matrix();
        spritebatch.begin(renderer, SpriteSortMode::SpriteSortModeDeferred, Some(shader), Some(m));
    }
    
    fn render_end <'sb>(scene: &Scene, renderer: &'sb mut Canvas<Window>, spritebatch: &'sb mut SpriteBatch);
    
    fn after_render() {

    }
}

But then I get the following compiler error:

error[E0038]: the trait `renderer::Renderer` cannot be made into an object
  --> src/scene.rs:48:5
   |
48 |     renderers: Vec<Rc<Renderer>>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `renderer::Renderer` cannot be made into an object
   |
   = note: method `before_render` has no receiver
   = note: method `render_begin` has no receiver
   = note: method `render_end` has no receiver
   = note: method `after_render` has no receiver

I thought that might work as I read this post that does something similar: http://keepcalmandlearnrust.com/2017/03/polymorphism-in-rust-enum-vs-trait-struct/


#6

D’oh! I should have read the error description :wink: I completely forgot to add &self all over the trait function. Of course the compiler is correct :smiley:


#7

Right :slight_smile:. The rest looks fine from a cursory glance - that’s the approach I’d take (i.e. struct Scene { renderers: Vec<Rc<Renderer>>}).


#8

Yes, it’s definitely working fine this way and it sounds like the correct way to go to me as well. Good! :wink: