Extract notification receiving into an observer

This commit is contained in:
Savanni D'Gerinel 2024-02-27 23:21:54 -05:00 committed by savanni
parent f6c82cbcb0
commit 3a5cb17e09
3 changed files with 54 additions and 43 deletions

View File

@ -3,9 +3,10 @@ pub mod ui;
mod view_models; mod view_models;
mod views; mod views;
use kifu_core::{Core, CoreRequest, CoreResponse}; use async_std::task::yield_now;
use std::sync::Arc; use kifu_core::{Core, CoreRequest, CoreResponse, Observable};
use tokio::{runtime::Runtime, spawn}; use std::{rc::Rc, sync::Arc};
use tokio::runtime::Runtime;
#[derive(Clone)] #[derive(Clone)]
pub struct CoreApi { pub struct CoreApi {
@ -37,3 +38,39 @@ where
println!("[Trace: {}] {:?}", trace_name, end - start); println!("[Trace: {}] {:?}", trace_name, end - start);
result result
} }
struct LocalObserver<T> {
join_handle: glib::JoinHandle<()>,
handler: Rc<dyn Fn(T)>,
}
impl<T: 'static> LocalObserver<T> {
fn new(observable: &dyn Observable<T>, handler: impl Fn(T) + 'static) -> Self {
let listener = observable.subscribe();
let handler = Rc::new(handler);
let join_handle = glib::spawn_future_local({
let handler = handler.clone();
async move {
loop {
match listener.recv().await {
Ok(msg) => handler(msg),
Err(err) => {
unimplemented!("Should display an error message in the UI: {}", err)
}
}
yield_now().await;
}
}
});
Self {
join_handle,
handler,
}
}
}
impl<T> Drop for LocalObserver<T> {
fn drop(&mut self) {
self.join_handle.abort();
}
}

View File

@ -14,30 +14,25 @@ General Public License for more details.
You should have received a copy of the GNU General Public License along with Kifu. If not, see <https://www.gnu.org/licenses/>. You should have received a copy of the GNU General Public License along with Kifu. If not, see <https://www.gnu.org/licenses/>.
*/ */
use async_channel::Receiver; use crate::LocalObserver;
use async_std::task::{spawn, spawn_blocking, yield_now, JoinHandle};
use kifu_core::{Core, CoreNotification}; use kifu_core::{Core, CoreNotification};
/// DatabaseViewModel controls the view that the user sees when starting the application if the application has been configured and if there are no games in progress. It provides a window into the database, showing a list of recently recorded games (whether from this app or from a main database). It also provides the UI for starting a new game. This will render an empty database view if the user hasn't configured a database yet. /// DatabaseViewModel controls the view that the user sees when starting the application if the application has been configured and if there are no games in progress. It provides a window into the database, showing a list of recently recorded games (whether from this app or from a main database). It also provides the UI for starting a new game. This will render an empty database view if the user hasn't configured a database yet.
pub struct DatabaseViewModel { pub struct DatabaseViewModel {
core: Core, core: Core,
notification_handler: JoinHandle<()>, notification_observer: LocalObserver<CoreNotification>,
widget: gtk::Box, widget: gtk::Box,
} }
impl DatabaseViewModel { impl DatabaseViewModel {
fn new(core: Core, notifications: Receiver<CoreNotification>) -> Self { fn new(core: Core) -> Self {
let handler = spawn(async move { let notification_observer = LocalObserver::new(&core, |msg| {
loop { println!("DatabaseViewModelHandler called with message: {:?}", msg)
let message = notifications.recv().await;
println!("Message received in DatabaseViewModel: {:?}", message);
yield_now().await;
}
}); });
Self { Self {
core, core,
notification_handler: handler, notification_observer,
widget: gtk::Box::new(gtk::Orientation::Horizontal, 0), widget: gtk::Box::new(gtk::Orientation::Horizontal, 0),
} }
} }

View File

@ -18,6 +18,8 @@ use async_std::{channel::Receiver, task::yield_now};
use kifu_core::{Color, Core, CoreNotification, Goban, Observable, Player}; use kifu_core::{Color, Core, CoreNotification, Goban, Observable, Player};
use std::{cell::RefCell, rc::Rc, time::Duration}; use std::{cell::RefCell, rc::Rc, time::Duration};
use crate::LocalObserver;
pub struct GameState { pub struct GameState {
goban: Goban, goban: Goban,
white_clock: Duration, white_clock: Duration,
@ -37,13 +39,13 @@ struct GameViewModelPrivate {
/// The Game View Model manages the current state of the game. It shows the two player cards, the board, the current capture count, the current player, and it maintains the UI for the clock (bearing in mind that the real clock is managed in the core). This view model should only be created once the details of the game, whether a game in progress or a new game (this view model won't know the difference) is known. /// The Game View Model manages the current state of the game. It shows the two player cards, the board, the current capture count, the current player, and it maintains the UI for the clock (bearing in mind that the real clock is managed in the core). This view model should only be created once the details of the game, whether a game in progress or a new game (this view model won't know the difference) is known.
pub struct GameViewModel { pub struct GameViewModel {
core: Core, core: Core,
notification_handler: glib::JoinHandle<()>, notification_observer: LocalObserver<CoreNotification>,
widget: gtk::Box, widget: gtk::Box,
data: Rc<RefCell<GameViewModelPrivate>>, data: Rc<RefCell<GameViewModelPrivate>>,
} }
impl GameViewModelPrivate { impl GameViewModelPrivate {
fn handle(&mut self, message: CoreNotification) {} fn handle(&mut self, _message: CoreNotification) {}
} }
impl GameViewModel { impl GameViewModel {
@ -54,38 +56,15 @@ impl GameViewModel {
state: game, state: game,
})); }));
let notification_handler = { let notification_observer = LocalObserver::new(&core, |msg| {
let notifications = core.subscribe(); println!("GameViewModelHandler called with message: {:?}", msg)
let data: Rc<RefCell<GameViewModelPrivate>> = data.clone(); });
glib::spawn_future_local(Self::listen(notifications, data))
};
Self { Self {
core, core,
notification_handler, notification_observer,
widget: gtk::Box::new(gtk::Orientation::Horizontal, 0), widget: gtk::Box::new(gtk::Orientation::Horizontal, 0),
data, data,
} }
} }
async fn listen(
notifications: Receiver<CoreNotification>,
data: Rc<RefCell<GameViewModelPrivate>>,
) {
loop {
match notifications.recv().await {
Ok(msg) => data.borrow_mut().handle(msg),
Err(err) => {
unimplemented!("Should display an error message in the UI: {}", err)
}
}
yield_now().await;
}
}
}
impl Drop for GameViewModel {
fn drop(&mut self) {
self.notification_handler.abort();
}
} }