упр.09 задача 1

Краен срок:
10.12.2025 23:59
Точки:
5

Имплементирайте event dispatcher.

Класът трябва да поддържа следните методи:

  • on(event, callback) - регистрира подадената callback функция свързана със събититето event. Връща Id, което уникално идентифицира този callback
  • off(id) - премахва и връща предварително регистриран callback по неговото Id
  • emit(event, state) - изпълнява всички callback функции регистрирани за даденото събитие. Подава състоянието state като аргумент на всяка callback функция

Класът трябва да поддържа използване в многонишков контекст.
В случая искаме типа EventDispatcher да е лек handle към реалната имплементация (виж отдолу защо).
За имплементацията може да използвате private структура (например Inner).
EventDispatcher държи указател (Arc) към стойност от тип Inner. Или Arc<Mutex<Inner>>, ако искаме да модифицираме състоянието Inner.

EventDispatcher трябва да може да се клонира. Клонирането създава нов handle към същата вътрешна стойност.

EventDispatcher трябва да може да се използва от няколко нишки едновременно.
Идеята е да клонираме и подадем по един handle на всяка нишка, през този handle нишките трябва да могат да извикват on, off и emit паралелно една с друга.

Добавете необходимите ограничения (trait bounds), така че кода да позволява използването от множество нишки.
Съвет: направете си пример (или ползвайте базовия тест) и вижте къде ще ви се скара компилатора.

use std::sync::{Arc, Mutex};

pub struct Id {
    /* каквито данни ви трябват */
}

pub struct EventDispatcher<S> {
    inner: Arc<Mutex</* ...data... */>>,
}

impl<S> EventDispatcher<S> {
    pub fn new() -> Self {
        todo!()
    }

    pub fn on(
        &self,
        event: &str,
        callback: Box<dyn FnMut(&mut S) /* + ... */>,
    ) -> Id {
        todo!()
    }

    pub fn off(
        &self,
        id: Id,
    ) -> Option<Box<dyn FnMut(&mut S) /* + ... */>> {
        todo!()
    }

    pub fn emit(&self, event: &str, state: &mut S) {
        todo!()
    }
}

impl<S> Clone for EventDispatcher<S> {
    fn clone(&self) -> Self {
        EventDispatcher { inner: Arc::clone(&self.inner) }
    }
}

Бонус (1т): Какво ще стане, ако от callback функция извикаме някой от on или off методите?
Погрижете се да може от callback за събитие event="foo" да може да се извикват on и off за друго събитие event="bar", без това да причинява deadlock.

Подсказка: за целта ще трябва да направите две неща:

  • да имате отделен мутекс (Mutex/RwLock) за всеки отделен event, в допълнение към глобалния мутекс върху цялата структура
  • при извикване на callback функциите за едно събитие, да държите заключен само мутекса на това събитие и да не държите заключен мутекса върху цялата структура.

Кога да използваме или да не използваме типове, които се държат като handle-и:

TODO


Задължително прочетете (или си припомнете): Указания за предаване на домашни

Погрижете се решението ви да се компилира с базовия тест:

// Include the solution source in the same file, so we
// don't have to worry about item visibility.
// Please don't use `include!` in real code, this is a hack
// around the checking system.
include!{ "../src/lib.rs" }
fn test_basic() {
#[derive(Default)]
struct State {
total_foos: usize,
total_bars: usize,
}
let evt_dispatcher = EventDispatcher::<State>::new();
let foo_id1 = evt_dispatcher.on("foo", Box::new(|state| { state.total_foos += 1; }));
let foo_id2 = evt_dispatcher.on("foo", Box::new(|state| { state.total_foos += 10; }));
let _foo_id3 = evt_dispatcher.on("foo", Box::new(|state| { state.total_foos += 100; }));
let _bar_id1 = evt_dispatcher.on("bar", Box::new(|state| { state.total_bars += 1; }));
let mut state = State::default();
evt_dispatcher.emit("foo", &mut state);
assert_eq!(state.total_foos, 111);
assert_eq!(state.total_bars, 0);
let mut state = State::default();
evt_dispatcher.emit("bar", &mut state);
assert_eq!(state.total_foos, 0);
assert_eq!(state.total_bars, 1);
assert!(evt_dispatcher.off(foo_id1).is_some());
assert!(evt_dispatcher.off(foo_id2).is_some());
let mut state = State::default();
evt_dispatcher.emit("foo", &mut state);
evt_dispatcher.emit("bar", &mut state);
assert_eq!(state.total_foos, 100);
assert_eq!(state.total_bars, 1);
}
fn test_multithreaded() {
#[derive(Default)]
struct State {
total_foos: usize,
}
let evt_dispatcher = EventDispatcher::<State>::new();
let ed = evt_dispatcher.clone();
let t1 = std::thread::spawn(move || {
ed.on("foo", Box::new(|state| { state.total_foos += 1; }));
});
let ed = evt_dispatcher.clone();
let t2 = std::thread::spawn(move || {
ed.on("foo", Box::new(|state| { state.total_foos += 10; }));
});
t1.join().unwrap();
t2.join().unwrap();
let mut state = State::default();
evt_dispatcher.emit("foo", &mut state);
assert_eq!(state.total_foos, 11);
}
fn test_reentrant() {
#[derive(Default)]
struct State {
total_foos: usize,
total_bars: usize,
is_counting_bars: Option<Id>,
}
let evt_dispatcher = EventDispatcher::<State>::new();
evt_dispatcher.on("foo", Box::new(|state| { state.total_foos += 1; }));
let toggle_bars_callback: Box<dyn FnMut(&mut State) + Send> = {
let ed = evt_dispatcher.clone();
Box::new(move |state| {
match state.is_counting_bars.take() {
Some(id) => {
ed.off(id);
}
None => {
let id = ed.on("bar", Box::new(|state| { state.total_bars += 1; }));
state.is_counting_bars = Some(id);
}
}
})
};
evt_dispatcher.on("foo", toggle_bars_callback);
let mut state = State::default();
evt_dispatcher.emit("bar", &mut state);
assert_eq!(state.total_foos, 0);
assert_eq!(state.total_bars, 0);
evt_dispatcher.emit("foo", &mut state);
assert_eq!(state.total_foos, 1);
assert_eq!(state.total_bars, 0);
evt_dispatcher.emit("bar", &mut state);
evt_dispatcher.emit("bar", &mut state);
assert_eq!(state.total_foos, 1);
assert_eq!(state.total_bars, 2);
evt_dispatcher.emit("foo", &mut state);
evt_dispatcher.emit("bar", &mut state);
assert_eq!(state.total_foos, 2);
assert_eq!(state.total_bars, 2);
}
fn with_timeout(timeout: std::time::Duration, f: fn()) {
let (sender, receiver) = std::sync::mpsc::sync_channel(1);
let _ = std::thread::spawn(move || {
let catch_result = std::panic::catch_unwind(f);
sender.send(catch_result).unwrap();
});
match receiver.recv_timeout(timeout) {
Ok(Ok(())) => {},
Ok(Err(panic_payload)) => std::panic::resume_unwind(panic_payload),
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => panic!("test timeout"),
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => unreachable!(),
};
}
#[test]
fn test_basic_run1() {
with_timeout(std::time::Duration::from_secs(3), test_basic);
}
#[test]
fn test_basic_run2() {
// дупликат, за да се получат правилния брой точки за оценяването (2 × 1т)
with_timeout(std::time::Duration::from_secs(3), test_basic);
}
#[test]
fn test_multithreaded_run1() {
with_timeout(std::time::Duration::from_secs(3), test_multithreaded);
}
#[test]
fn test_multithreaded_run2() {
// дупликат, за да се получат правилния брой точки за оценяването (2 × 1т)
with_timeout(std::time::Duration::from_secs(3), test_multithreaded);
}
#[test]
fn test_reentrant_run() {
with_timeout(std::time::Duration::from_secs(3), test_reentrant);
}