How to best implement something generic instead of a variable field

I would like to get rid of this drawmode field and 'monomorphize' this draw_rect_to function.
Thus avoiding the 'match' statement.

I could write a Bmp<D> struct
I could also write a draw_rect_to<D>() function separating drawmode entirely.
But both would "uglify" calling code.
Any ideas for nice implementations? (maybe PhantomData? transmute?)

struct Bmp
{
	width: i32,
	height: i32,
	drawmode: DrawMode,
	pixels: Vec<Rgba>,
}

impl Bmp
{
    pub fn draw_rect_to(&self, dst: &mut Self, src_rect: Rect, dst_x: i32, dst_y: i32)
    {
        if let Some(clip) = Self::perform_clip_rect(&self, dst, src_rect, dst_x, dst_y)
        {
            match self.drawmode
            {
                DrawMode::Opaque => self.transfer_rect_opaque(dst, &clip.0, clip.1, clip.2),
                DrawMode::Transparent => self.transfer_rect_transparent(dst, &clip.0, clip.1, clip.2),
                DrawMode::Blend => self.transfer_rect_blend(dst, &clip.0, clip.1, clip.2),
                DrawMode::Custom { combine } => self.transfer_rect_custom(dst, &clip.0, clip.1, clip.2, combine),
            }
        }
    }
}

It looks like you are trying to write generic code without writing generic code. Why is that? What's "ugly" about generics?

1 Like

I would like creation of Bmp to be without generics. But that is doable by a default alias I think, which is then "opaque".
I do not like the 'look' of these :: for calling generic functions.

Currently I have to make all my bitmaps in-game mutable when drawing because I have to change to drawmode. Looking for a good way too avoid that.

You'll get used to it. You don't need the turbofish most of the time anyway, due to type inference.

If you want generic code, then just write generic code. The syntax of any particular language feature shouldn't be the primary reason for how you structure your code. You should organize your code around domain concepts, not surface syntax.

8 Likes

true.

I was wondering (I know it is dangerzone and don't know how it works) if transmute could be used to 'cast' between drawmodes.

transmute is extremely dangerous, since it tells the compiler to treat the thing you're transmuting as a bit pattern, and interpret it as-if it was something else. Unless you know for sure that this is OK, you should avoid it.

Virtually everything that you can do with transmute can be done without unsafe using the standard library's functionality. The few things that you can't do this way depend crucially on details of how a given system works, and tend to be wildly unportable.

1 Like

No. If you need to ask whether you should use transmute, then you are not ready for using transmute.

6 Likes

:slight_smile: also true i believe

1 Like

if you want an alternative to type parameters, you can check out the impl Trait syntax, as well as type objects and the Any trait. trait objects have a runtime overhead, but they do have their uses, such as reducing code size and handling cases where you don't know the type until runtime. i wouldn't mess with transmute unless you already have experience in an unsafe language like C, since unsafe rust can actually be a fair bit trickier than C, due to factors like unspecified memory layouts.

also, unless i'm mistaken, i think you might've mixed up monomorphic and polymorphic?

Do you mean as an input to the draw_rect_to instead of draw_rect_to<D>? Aka argument position impl Trait (APIT).

Because they are the same thing, only APIT is strictly less functional. The literal only benefit is the look (assuming you prefer the APIT look).

2 Likes

(Emphasis added)

It shouldn't be as ugly as you seem to think (playground). Did you have some specific calling code that you wanted to avoid?

Ok I will not mention transmute anymore. It is not needed anyway.
At least I deleted the field from Bmp and added a parameter to the draw functions, like:

pub fn draw_to(&self, dst: &mut Self, dst_x: i32, dst_y: i32, mode: DrawMode)
{
    // match statement on <mode> and do your thing.
}

And I meant monomorph, not polymorph.
@drmason13: yes, thanks. True, but this only works if drawmode is constant, isnt it?

I think if the drawmode is variable, monomorphization is impossible anyway?
But I do not know enough of the compiling process.
In my chessprogram I managed to heavily monomorphize stuff with trait constants (with very good speed result), but here the drawmode is variable and not constant.

let src = Bmp::new(100, 100);
let mut dst = Bmp::new(200, 200);
let mode = some_sprite.drawmode; // variable
bmp.draw_to(&mut dst, 10, 10, mode);

I see what you mean, and you're right, at some point somewhere you'll need to have a compile-time known DrawMode to avoid handling each possible DrawMode at runtime.

It could be that the sprites are Sprite<DrawMode> to always have a fixed DrawMode. And... I guess sprites are fed in as assets - grouped by DrawMode - and there are separate functions that load a Sprite with a certain DrawMode. And now all the code handling a sprite has to be generic... That doesn't sound fun! :nauseated_face:

I think the enum is what you actually want. If you don't know what a sprite's DrawMode will be until runtime, but you know at compile time that it has to be one of a fixed set of possible values. Well, that's what enums are for! :partying_face:

1 Like

That would be to complicated indeed. And the drawmode of sprites change while playing the game. So I guess I will have to do with a plain parameter.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.