2
votes

I'm trying to write a simple game with SFML and Rust, but the borrow-checker is proving to be my greatest enemy on this journey.

There are a bunch of cases where SFML needs a reference to another object. In the code below, I need a reference to a Font or else Text doesn't show anything to the user.

Problem is, I've tried a bunch of things and the reference itself never lives long enough. It obviously works if I create the Text object on the draw method, but I would like to avoid creating things inside the main loop of the application.

Is this a case where I should take a look onto the unsafe operations? Is there a combination of Rc, RefCell, Box, etc. that meets my needs?

Please try to explain me what I should be doing and what is wrong in my current mindset, if possible.

extern crate sfml;

use sfml::system::{ Clock, Vector2f };
use sfml::graphics::{ Color, Font, RenderTarget, RenderWindow, Text, Transformable };

pub struct FpsMeter<'a> {
    position: Vector2f,
    clock:    Clock,
    value:    f32,

    text:     Text<'a>
}

impl<'a> FpsMeter<'a> {
    pub fn new() -> Self {
        let font = match Font::new_from_file("assets/sansation.ttf") {
            Some(fnt) => fnt,
            None      => panic!("Cannot open resource: sansation.ttf"),
        };

        let mut text = Text::new_init(
            &format!("FPS: {}", 0),
            &font,
            20  
        ).expect("Could not create text");

        FpsMeter {
            position: Vector2f::new(0., 0.),
            clock:    Clock::new(),
            value:    0.,

            text: text,
        }
    }

    pub fn set_position2f(&mut self, x: f32, y: f32) {
        self.position.x = x;
        self.position.y = y;
    }

    pub fn restart(&mut self) {
        self.value = 1. / self.clock.restart().as_seconds();
    }

    pub fn draw(&mut self, window: &mut RenderWindow) {
        self.text.set_position(&self.position);
        self.text.set_color(&Color::white());

        window.draw(&self.text);
    }
}
1

1 Answers

2
votes

I'm not familiar with rust-sfml, so I might be misreading your issue, but it should look like this. You have a Font and Text (that you don't control, they're constructed for you by the library) where Text holds a reference to Font. Simplified:

struct Font;
struct Text<'a> { font: &'a Font }

Then you have a FpsMeter (that you do control) that has a Text field. Again, simplified:

struct FpsMeter<'a> {
    text: Text<'a>
}

now, if that's the case, I don't think you can create Text (or at least Font) in the same method where you create your FpsMeter, as the reference to Font can't escape the stack frame of the constructor function. You will need to pass a pre-built Text to your constructor. For instance:

impl<'a> FpsMeter<'a> {
    fn new(txt: Text<'a>) -> FpsMeter<'a> {
        FpsMeter { text: txt }
    } 
}

or possibly:

impl<'a> FpsMeter<'a> {
    fn new(fnt: &'a Font) -> FpsMeter<'a> {
        FpsMeter { text: Text { font: fnt } }
    } 
}

toy example on the playground