Хранение вектора структур, содержащих замыкания в Rust - PullRequest
0 голосов
/ 06 апреля 2020

В моем приложении Rust я хотел бы сохранить вектор структур, содержащих замыкания, которые вызываются позже.

Пока что у меня есть что-то вроде этого (для игры в тетрис):

pub struct TimestepTimer {
    pub frameForExecution: i32,
    pub func: Box<dyn Fn() -> ()>
}

pub struct Game {
    pub frame: i32,
    pub timers: Vec<TimestepTimer>
}

impl Game {
    fn add_timer (&mut self, framesFromNow: i32, func: Box<dyn Fn() -> ()>) {
        self.timers.push(TimestepTimer {
            frameForExecution: self.frame + framesFromNow,
            func
        });
    }

    /* Example of a function that would use add_timer */
    pub fn begin_gravity(&mut self) {
        self.add_timer(30, Box::new(|| {
            self.move_down();
            // Gravity happens repeatedly
            self.begin_gravity();
        }));
    }

    // Later on would be a function that checks the frame no.
    // and calls the timers' closures which are due
}

На данный момент это не работает из-за E0495, говоря о ссылке, созданной Box :: new не может пережить вызов функции begin_gravity, но также 'должен быть действителен для stati c продолжительность жизни *

Я совершенно новичок в Rust, так что это совсем не может быть идиоматическим c решением.

Кажется, что каждое другое решение, которое я пробую, встречается с другой ошибкой компилятора (хотя я знаю, что это моя ошибка, а не ошибка компилятора)

1 Ответ

2 голосов
/ 06 апреля 2020

Это в основном дубликат этого вопроса : ваше закрытие сохраняет ссылку на self (так что он может вызывать self.move_down() и self.begin_gravity()), и вы пытаетесь сохранить его внутри self → это невозможно.

Вы можете получить аналогичный эффект, если измените свое закрытие на аргумент &mut Game и оперируете этим:

pub struct TimestepTimer {
    pub frameForExecution: i32,
    pub func: Box<dyn Fn(&mut Game) -> ()>
}

pub struct Game {
    pub frame: i32,
    pub timers: Vec<TimestepTimer>
}

impl Game {
    fn add_timer (&mut self, framesFromNow: i32, func: Box<dyn Fn(&mut Game) -> ()>) {
        self.timers.push(TimestepTimer {
            frameForExecution: self.frame + framesFromNow,
            func
        });
    }

    /* Example of a function that would use add_timer */
    pub fn begin_gravity(&mut self) {
        self.add_timer(30, Box::new(|s: &mut Game| {
            // self.move_down();
            // Gravity happens repeatedly
            s.begin_gravity();
        }));
    }

    // Later on would be a function that checks the frame no.
    // and calls the timers' closures which are due
}

Детская площадка

Но теперь у вас возникнут проблемы при вызове таймеров, потому что вам понадобится ссылка на self.timers для его итерации, и в то же время вам нужно будет передать изменяемую ссылку на self в замыкания, но компилятор не позволит вам иметь неизменную ссылку на self.timers и изменяемую ссылку на self одновременно. Если вы действительно хотите сохранить свои таймеры в Vec, самый простой способ решить эту проблему - заменить timers пустым вектором и заполнить его, как вы go:

pub fn step (&mut self) {
    self.frame += 1;
    let mut timers = vec![];
    std::mem::swap (&mut self.timers, &mut timers);
    for t in timers {
        if self.frame > t.frameForExecution {
            (t.func)(self);
        } else {
            self.timers.push (t);
        }
    }
}

Детская площадка

Однако, вероятно, было бы лучше хранить таймеры в BinaryHeap, который позволяет гораздо более чистое и эффективное выполнение l oop:

pub fn step (&mut self) {
    self.frame += 1;
    while let Some (t) = self.timers.peek() {
        if t.frameForExecution >= self.frame { break; }
        // `unwrap` is ok here because we know from the `peek` that
        // there is at least one timer in `timers`
        let t = self.timers.pop().unwrap();
        (t.func)(self);
    }
}

Детская площадка

Это потребует реализации Ord вместе с несколькими другими чертами на TimestepTimer:

impl Ord for TimestepTimer {
    fn cmp (&self, other: &Self) -> Ordering {
        // Note the swapped order for `self` and `other` so that the `BinaryHeap`
        // will return the earlier timers first.
        other.frameForExecution.cmp (&self.frameForExecution)
    }
}

impl PartialOrd for TimestepTimer {
    fn partial_cmp (&self, other: &Self) -> Option<Ordering> {
        Some (self.cmp (other))
    }
}

impl PartialEq for TimestepTimer {
    fn eq (&self, other: &Self) -> bool {
        self.frameForExecution == other.frameForExecution
    }
}

impl Eq for TimestepTimer {}
...