Heavily revised zsw-playlist to not use the services / resources traits, as well as no external locking.

This commit is contained in:
Filipe Rodrigues 2022-11-20 09:43:39 +00:00
parent 96067bbf14
commit 95a9a647b7
9 changed files with 336 additions and 239 deletions

7
Cargo.lock generated
View File

@ -560,9 +560,9 @@ dependencies = [
[[package]]
name = "crossbeam"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845"
checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-channel",
@ -3180,8 +3180,11 @@ name = "zsw-playlist"
version = "0.1.0"
dependencies = [
"async-channel",
"crossbeam",
"futures",
"parking_lot 0.12.0",
"rand",
"tracing",
"zsw-util",
]

View File

@ -9,8 +9,7 @@ mod load;
// Imports
use {
super::Image,
zsw_playlist::{PlaylistImage, PlaylistResource, PlaylistService},
zsw_util::{Resources, Services},
zsw_playlist::{PlaylistImage, PlaylistReceiver},
};
/// Image loader service
@ -37,19 +36,12 @@ impl ImageLoaderService {
/// Runs this image loader
///
/// Multiple image loaders may run at the same time
///
/// # Blocking
/// Locks [`zsw_playlist::PlaylistLock`] on `playlist`
pub async fn run<S, R>(&self, services: &S, resources: &R) -> !
where
S: Services<PlaylistService>,
R: Resources<PlaylistResource>,
{
let playlist = services.service::<PlaylistService>();
pub async fn run(&self, playlist_receiver: PlaylistReceiver) {
loop {
// DEADLOCK: Caller ensures we can lock it
let image = playlist.next().await;
// Get the next image, or quit if no more
let Some(image) = playlist_receiver.next() else {
break;
};
match &*image {
PlaylistImage::File(path) => match load::load_image(path) {
@ -68,10 +60,7 @@ impl ImageLoaderService {
// If we couldn't load, log, remove the path and retry
Err(err) => {
tracing::info!(?path, ?err, "Unable to load file");
// DEADLOCK: Caller ensures we can lock it
let mut playlist_resource = resources.resource::<PlaylistResource>().await;
playlist.remove_image(&mut playlist_resource, &image).await;
playlist_receiver.remove_image(image);
},
},
}

View File

@ -8,11 +8,18 @@ version = "0.1.0"
[dependencies]
# Zsw
zsw-util = {path = "../zsw-util"}
zsw-util = { path = "../zsw-util" }
# Async
async-channel = "1.6.1"
futures = "0.3.21"
# Util
parking_lot = "0.12.0"
crossbeam = "0.8.2"
# Logging
tracing = "0.1.32"
# Random
rand = "0.8.4"

View File

@ -7,140 +7,12 @@
// Imports
use {
crossbeam::channel,
parking_lot::RwLock,
rand::prelude::SliceRandom,
std::{collections::HashSet, path::PathBuf, sync::Arc},
zsw_util::Resources,
std::{collections::HashSet, mem, path::PathBuf, sync::Arc},
};
/// Image playlist
#[derive(Debug)]
pub struct PlaylistService {
/// Image sender
img_tx: async_channel::Sender<Arc<PlaylistImage>>,
/// Image receiver
img_rx: async_channel::Receiver<Arc<PlaylistImage>>,
}
impl PlaylistService {
/// Creates a new, empty, playlist, alongside all resources
#[must_use]
pub fn new() -> (Self, PlaylistResource) {
// Note: Making the close channel unbounded is what allows us to not block
// in `Self::stop`.
let (img_tx, img_rx) = async_channel::bounded(1);
// Create the service
let service = Self { img_tx, img_rx };
// Create the resource
let resource = PlaylistResource {
root_path: None,
images: HashSet::new(),
cur_images: vec![],
};
(service, resource)
}
/// Runs the playlist
///
/// # Blocking
/// Locks [`PlaylistLock`] on `self`.
pub async fn run<R>(&self, resources: &R) -> !
where
R: Resources<PlaylistResource>,
{
loop {
// Get the next image to send
// DEADLOCK: Caller ensures we can lock it
// Note: It's important to not have this in the match expression, as it would
// keep the lock through the whole match.
let next = resources.resource::<PlaylistResource>().await.cur_images.pop();
// Then check if we got it
match next {
// If we got it, send it
// DEADLOCK: We don't hold any locks while sending
// Note: This can't return an `Err` because `self` owns a receiver
Some(image) => self.img_tx.send(image).await.expect("Image receiver was closed"),
// Else get the next batch and shuffle them
// DEADLOCK: Caller ensures we can lock it.
None => {
let mut resource = resources.resource::<PlaylistResource>().await;
let resource = &mut *resource;
resource.cur_images.extend(resource.images.iter().cloned());
resource.cur_images.shuffle(&mut rand::thread_rng());
},
}
}
}
/// Removes an image
pub async fn remove_image<'a>(&'a self, resource: &mut PlaylistResource, image: &PlaylistImage) {
// Note: We don't care if the image actually existed or not
let _ = resource.images.remove(image);
}
/// Sets the root path
pub async fn set_root_path<'a>(&'a self, resource: &mut PlaylistResource, root_path: PathBuf) {
// Remove all existing paths and add new ones
resource.images.clear();
zsw_util::visit_dir(&root_path, &mut |path| {
let _ = resource.images.insert(Arc::new(PlaylistImage::File(path)));
});
// Remove all current paths too
resource.cur_images.clear();
// Save the root path
resource.root_path = Some(root_path);
}
/// Returns the root path
pub async fn root_path<'a>(&'a self, resource: &'a PlaylistResource) -> Option<PathBuf> {
resource.root_path.clone()
}
/// Retrieves the next image
///
/// # Blocking
/// Locks [`PlaylistLock`] on `??`
// TODO: Replace `Locks` with a barrier on the channel
// Note: Doesn't literally lock it, but the other side of the channel
// needs to lock it in order to progress, so it's equivalent
pub async fn next(&self) -> Arc<PlaylistImage> {
// Note: This can't return an `Err` because `self` owns a sender
// DEADLOCK: Caller ensures it won't hold an `PlaylistLock`,
// and we ensure the other side of the channel
// can progress.
self.img_rx.recv().await.expect("Image sender was closed")
}
/// Peeks the next images
pub async fn peek_next(&self, resource: &PlaylistResource, mut f: impl FnMut(&PlaylistImage) + Send) {
for image in resource.cur_images.iter().rev() {
f(image);
}
}
}
/// Playlist resource
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct PlaylistResource {
/// Root path
// TODO: Use this properly
root_path: Option<PathBuf>,
/// All images
images: HashSet<Arc<PlaylistImage>>,
/// Current images
cur_images: Vec<Arc<PlaylistImage>>,
}
/// A playlist image
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
pub enum PlaylistImage {
@ -148,3 +20,221 @@ pub enum PlaylistImage {
File(PathBuf),
// TODO: URL
}
/// Playlist event
enum Event {
/// Remove Image
RemoveImg(Arc<PlaylistImage>),
/// Change root path
ChangeRoot(PathBuf),
}
/// Playlist inner
#[derive(Debug)]
pub struct PlaylistInner {
/// Root path
root_path: Option<PathBuf>,
/// All images
all_images: HashSet<Arc<PlaylistImage>>,
/// Current images
cur_images: Vec<Arc<PlaylistImage>>,
}
/// Playlist receiver
///
/// Receives images from the playlist
#[derive(Clone, Debug)]
pub struct PlaylistReceiver {
/// Image receiver
img_rx: channel::Receiver<Arc<PlaylistImage>>,
/// Event sender
event_tx: channel::Sender<Event>,
}
impl PlaylistReceiver {
/// Retrieves the next image.
///
/// Returns `None` if the playlist manager was closed
#[must_use]
pub fn next(&self) -> Option<Arc<PlaylistImage>> {
self.img_rx.recv().ok()
}
/// Removes an image
pub fn remove_image(&self, image: Arc<PlaylistImage>) {
// TODO: Care about this?
let _res = self.event_tx.send(Event::RemoveImg(image));
}
}
/// Playlist manager
#[derive(Clone, Debug)]
pub struct PlaylistManager {
/// Inner
inner: Arc<RwLock<PlaylistInner>>,
/// Event sender
event_tx: channel::Sender<Event>,
}
impl PlaylistManager {
/// Removes an image
pub fn remove_image(&self, image: Arc<PlaylistImage>) {
// TODO: Care about this?
let _res = self.event_tx.send(Event::RemoveImg(image));
}
/// Sets the root path
pub fn set_root_path(&self, root_path: impl Into<PathBuf>) {
// TODO: Care about this?
let _res = self.event_tx.send(Event::ChangeRoot(root_path.into()));
}
/// Returns the root path
#[must_use]
pub fn root_path(&self) -> Option<PathBuf> {
self.inner.read().root_path.clone()
}
/// Returns the remaining images in the current shuffle
#[must_use]
pub fn peek_next(&self) -> Vec<Arc<PlaylistImage>> {
self.inner.read().cur_images.clone()
}
}
/// Playlist runner
///
/// Responsible for driving the
/// playlist receiver
#[derive(Debug)]
pub struct PlaylistRunner {
/// Inner
inner: Arc<RwLock<PlaylistInner>>,
/// Image sender
img_tx: channel::Sender<Arc<PlaylistImage>>,
/// Event receiver
event_rx: channel::Receiver<Event>,
}
impl PlaylistRunner {
/// Runs the playlist runner.
///
/// Returns once the playlist receiver is dropped
pub fn run(self) {
'run: loop {
// Lock the inner data for writing
let mut inner = self.inner.write();
match inner.all_images.is_empty() {
// If we have no images, block on the next event
true => {
// Note: Important we drop this before blocking
mem::drop(inner);
let event = match self.event_rx.recv() {
Ok(event) => event,
Err(channel::RecvError) => break 'run,
};
let mut inner = self.inner.write();
Self::handle_event(&mut inner, event);
continue;
},
// Else just consume all events we have
false => loop {
let event = match self.event_rx.try_recv() {
Ok(event) => event,
Err(channel::TryRecvError::Empty) => break,
Err(channel::TryRecvError::Disconnected) => break 'run,
};
Self::handle_event(&mut inner, event);
},
}
// Check if we have a next image to send
match inner.cur_images.pop() {
// If we got it, send it.
// Note: It's important to drop the lock while awaiting
// Note: If the sender got closed in the meantime, quit
Some(image) => {
mem::drop(inner);
if self.img_tx.send(image).is_err() {
break;
}
},
// Else get the next batch and shuffle them
// Note: If we got here, `all_images` has at least 1 image,
// so we won't be "spin locking" here.
None => {
let inner = &mut *inner;
tracing::debug!("Reshuffling playlist");
inner.cur_images.extend(inner.all_images.iter().cloned());
inner.cur_images.shuffle(&mut rand::thread_rng());
},
}
}
}
/// Handles event `event`
fn handle_event(inner: &mut PlaylistInner, event: Event) {
match event {
// Note: We don't care if the image actually existed or not
Event::RemoveImg(image) => {
let _ = inner.all_images.remove(&image);
},
Event::ChangeRoot(root_path) => {
// Remove all existing paths and add new ones
inner.all_images.clear();
zsw_util::visit_dir(&root_path, &mut |path| {
let _ = inner.all_images.insert(Arc::new(PlaylistImage::File(path)));
});
// Remove all current paths too
inner.cur_images.clear();
// Save the root path
inner.root_path = Some(root_path);
},
};
}
}
/// Creates the playlist service
#[must_use]
pub fn create() -> (PlaylistRunner, PlaylistReceiver, PlaylistManager) {
// Create the channels
// Note: Since processing a single playlist item is cheap, we
// use a small buffer size
let (img_tx, img_rx) = channel::bounded(1);
let (event_tx, event_rx) = channel::unbounded();
let inner = PlaylistInner {
root_path: None,
all_images: HashSet::new(),
cur_images: vec![],
};
let inner = Arc::new(RwLock::new(inner));
let playlist_runner = PlaylistRunner {
inner: Arc::clone(&inner),
img_tx,
event_rx,
};
let playlist_receiver = PlaylistReceiver {
img_rx,
event_tx: event_tx.clone(),
};
let playlist_manager = PlaylistManager { inner, event_tx };
(playlist_runner, playlist_receiver, playlist_manager)
}

View File

@ -16,7 +16,7 @@ use {
path::{Path, PathBuf},
},
zsw_panels::{Panel, Panels, PanelsResource},
zsw_playlist::{PlaylistResource, PlaylistService},
zsw_playlist::PlaylistManager,
zsw_util::{Resources, Services},
};
@ -40,14 +40,17 @@ impl Profiles {
///
/// # Lock
/// [`ProfilesLock`]
/// - [`zsw_playlist::PlaylistLock`]
/// - [`zsw_panels::PanelsLock`]
pub async fn run_loader_applier<S, R>(&self, path: &Path, services: &S, resources: &R)
where
S: Services<PlaylistService> + Services<Panels>,
R: Resources<PanelsResource> + Resources<PlaylistResource> + Resources<ProfilesResource>,
/// - [`zsw_panels::PanelsLock`]
pub async fn run_loader_applier<S, R>(
&self,
path: &Path,
services: &S,
resources: &R,
playlist_manager: PlaylistManager,
) where
S: Services<Panels>,
R: Resources<PanelsResource> + Resources<ProfilesResource>,
{
let playlist = services.service::<PlaylistService>();
let panels = services.service::<Panels>();
// DEADLOCK: Caller ensures we can lock it
@ -61,13 +64,10 @@ impl Profiles {
// Lock
// DEADLOCK: Caller ensures we can lock them in this order after profiles lock
let mut playlist_resource = resources.resource::<PlaylistResource>().await;
let mut panels_resource = resources.resource::<PanelsResource>().await;
// Then apply
profile
.apply(playlist, panels, &mut playlist_resource, &mut panels_resource)
.await;
profile.apply(&playlist_manager, panels, &mut panels_resource).await;
},
Err(err) => tracing::warn!(?err, "Unable to load profile"),
@ -118,14 +118,14 @@ pub struct Profile {
impl Profile {
/// Applies a profile
pub async fn apply<'playlist, 'panels>(
pub async fn apply<'panels>(
&self,
playlist: &'playlist PlaylistService,
playlist_manager: &PlaylistManager,
panels: &'panels Panels,
playlist_resource: &mut PlaylistResource,
panels_resource: &mut PanelsResource,
) {
playlist.set_root_path(playlist_resource, self.root_path.clone()).await;
tracing::debug!("Applying profile");
playlist_manager.set_root_path(self.root_path.clone());
panels.replace_panels(panels_resource, self.panels.iter().copied());
}
}

View File

@ -21,7 +21,7 @@ use {
},
zsw_egui::{Egui, EguiPainterResource, EguiPlatformResource},
zsw_panels::{Panel, PanelState, PanelStateImage, PanelStateImages, Panels, PanelsResource},
zsw_playlist::{PlaylistImage, PlaylistResource, PlaylistService},
zsw_playlist::{PlaylistImage, PlaylistManager},
zsw_profiles::{Profile, Profiles, ProfilesResource},
zsw_util::{Rect, Resources, Services},
zsw_wgpu::{Wgpu, WgpuSurfaceResource},
@ -82,16 +82,16 @@ impl SettingsWindow {
/// - [`zsw_playlist::PlaylistLock`] on `playlist`
/// - [`zsw_panels::PanelsLock`] on `panels`
/// Blocks until [`Self::update_paint_jobs`] on `egui` is called.
pub async fn run<S, R>(&self, services: &S, resources: &R, egui_painter_resource: &mut EguiPainterResource) -> !
pub async fn run<S, R>(
&self,
services: &S,
resources: &R,
egui_painter_resource: &mut EguiPainterResource,
playlist_manager: PlaylistManager,
) -> !
where
S: Services<Wgpu>
+ Services<Egui>
+ Services<Window>
+ Services<Panels>
+ Services<PlaylistService>
+ Services<Profiles>,
S: Services<Wgpu> + Services<Egui> + Services<Window> + Services<Panels> + Services<Profiles>,
R: Resources<PanelsResource>
+ Resources<PlaylistResource>
+ Resources<ProfilesResource>
+ Resources<WgpuSurfaceResource>
+ Resources<EguiPlatformResource>,
@ -99,7 +99,6 @@ impl SettingsWindow {
let wgpu = services.service::<Wgpu>();
let egui = services.service::<Egui>();
let profiles = services.service::<Profiles>();
let playlist = services.service::<PlaylistService>();
let window = services.service::<Window>();
let panels = services.service::<Panels>();
@ -120,9 +119,6 @@ impl SettingsWindow {
// DEADLOCK: Caller ensures we can lock it after the platform lock
let mut profiles_resource = resources.resource::<ProfilesResource>().await;
// DEADLOCK: Caller ensures we can lock it after the profiles lock
let mut playlist_resource = resources.resource::<PlaylistResource>().await;
// DEADLOCK: Caller ensures we can lock it after the panels lock
let mut panels_resource = resources.resource::<PanelsResource>().await;
@ -137,9 +133,8 @@ impl SettingsWindow {
surface_size,
window,
panels,
playlist,
&playlist_manager,
profiles,
&mut playlist_resource,
&mut panels_resource,
&mut profiles_resource,
)
@ -155,16 +150,15 @@ impl SettingsWindow {
}
/// Draws the settings window
fn draw<'playlist, 'panels, 'profiles>(
fn draw<'panels, 'profiles>(
inner: &mut Inner,
ctx: &egui::CtxRef,
_frame: &epi::Frame,
surface_size: PhysicalSize<u32>,
window: &Window,
panels: &'panels Panels,
playlist: &'playlist PlaylistService,
playlist_manager: &PlaylistManager,
profiles: &'profiles Profiles,
playlist_resource: &mut PlaylistResource,
panels_resource: &mut PanelsResource,
profiles_resource: &mut ProfilesResource,
) -> Result<(), anyhow::Error> {
@ -189,9 +183,8 @@ impl SettingsWindow {
&mut inner.new_panel_state,
surface_size,
panels,
playlist,
playlist_manager,
profiles,
playlist_resource,
panels_resource,
profiles_resource,
);
@ -213,14 +206,13 @@ impl SettingsWindow {
}
/// Draws the settings window
fn draw_settings_window<'playlist, 'panels, 'profiles>(
fn draw_settings_window<'panels, 'profiles>(
ui: &mut egui::Ui,
new_panel_state: &mut NewPanelState,
surface_size: PhysicalSize<u32>,
panels: &'panels Panels,
playlist: &'playlist PlaylistService,
playlist_manager: &PlaylistManager,
profiles: &'profiles Profiles,
playlist_resource: &mut PlaylistResource,
panels_resource: &mut PanelsResource,
profiles_resource: &mut ProfilesResource,
) {
@ -229,15 +221,14 @@ fn draw_settings_window<'playlist, 'panels, 'profiles>(
self::draw_panels(ui, new_panel_state, surface_size, panels, panels_resource);
});
ui.collapsing("Playlist", |ui| {
self::draw_playlist(ui, playlist, playlist_resource);
self::draw_playlist(ui, playlist_manager);
});
ui.collapsing("Profile", |ui| {
self::draw_profile(
ui,
panels,
playlist,
playlist_manager,
profiles,
playlist_resource,
panels_resource,
profiles_resource,
);
@ -245,12 +236,11 @@ fn draw_settings_window<'playlist, 'panels, 'profiles>(
}
/// Draws the profile settings
fn draw_profile<'playlist, 'panels, 'profiles>(
fn draw_profile<'panels, 'profiles>(
ui: &mut egui::Ui,
panels: &'panels Panels,
playlist: &'playlist PlaylistService,
playlist_manager: &PlaylistManager,
profiles: &'profiles Profiles,
playlist_resource: &mut PlaylistResource,
panels_resource: &mut PanelsResource,
profiles_resource: &mut ProfilesResource,
) {
@ -259,9 +249,7 @@ fn draw_profile<'playlist, 'panels, 'profiles>(
ui.horizontal(|ui| {
ui.label(path.display().to_string());
if ui.button("Apply").clicked() {
profile
.apply(playlist, panels, playlist_resource, panels_resource)
.block_on();
profile.apply(playlist_manager, panels, panels_resource).block_on();
}
});
}
@ -294,7 +282,7 @@ fn draw_profile<'playlist, 'panels, 'profiles>(
if let Some(path) = file_dialog {
let profile = {
Profile {
root_path: match playlist.root_path(playlist_resource).block_on() {
root_path: match playlist_manager.root_path() {
Some(path) => path,
None => {
tracing::warn!("No root path was set");
@ -317,18 +305,14 @@ fn draw_profile<'playlist, 'panels, 'profiles>(
}
/// Draws the playlist settings
fn draw_playlist<'playlist>(
ui: &mut egui::Ui,
playlist: &'playlist PlaylistService,
playlist_resource: &mut PlaylistResource,
) {
fn draw_playlist(ui: &mut egui::Ui, playlist_manager: &PlaylistManager) {
// Draw the root path
ui.horizontal(|ui| {
// Show the current root path
ui.label("Root path");
ui.add_space(10.0);
{
match playlist.root_path(playlist_resource).block_on() {
match playlist_manager.root_path() {
Some(root_path) => ui.label(root_path.display().to_string()),
None => ui.label("<None>"),
};
@ -341,7 +325,7 @@ fn draw_playlist<'playlist>(
match file_dialog {
Ok(file_dialog) => {
if let Some(path) = file_dialog {
playlist.set_root_path(playlist_resource, path).block_on();
playlist_manager.set_root_path(path);
// TODO: Maybe reset both panels and loaders?
}
@ -354,13 +338,13 @@ fn draw_playlist<'playlist>(
// Draw all paths in the pipeline
ui.collapsing("Upcoming", |ui| {
egui::ScrollArea::new([true, true]).max_height(500.0).show(ui, |ui| {
playlist
.peek_next(playlist_resource, |image| match image {
for image in playlist_manager.peek_next() {
match &*image {
PlaylistImage::File(path) => {
ui.label(path.display().to_string());
},
})
.block_on();
}
}
});
});
}

View File

@ -36,7 +36,7 @@ use {
zsw_img::ImageLoaderService,
zsw_input::Input,
zsw_panels::Panels,
zsw_playlist::PlaylistService,
zsw_playlist::{PlaylistManager, PlaylistReceiver, PlaylistRunner},
zsw_profiles::Profiles,
zsw_renderer::Renderer,
zsw_settings_window::SettingsWindow,
@ -51,7 +51,9 @@ pub async fn run(args: &Args) -> Result<(), anyhow::Error> {
let window = Arc::new(window);
// Create all services and resources
let (services, resources, resources_mut) = self::create_services_resources(Arc::clone(&window)).await?;
// TODO: Create and spawn all services in the same function
let (services, resources, resources_mut, playlist_runner, playlist_receiver, playlist_manager) =
self::create_services_resources(Arc::clone(&window)).await?;
let services = Arc::new(services);
let resources = Arc::new(resources);
tracing::debug!(?services, ?resources, "Created services and resources");
@ -60,7 +62,15 @@ pub async fn run(args: &Args) -> Result<(), anyhow::Error> {
let mut event_handler = EventHandler::new();
// Spawn all futures
let join_handle = self::spawn_services(&services, &resources, resources_mut, args);
let join_handle = self::spawn_services(
&services,
&resources,
resources_mut,
playlist_runner,
playlist_receiver,
playlist_manager,
args,
);
// Run the event loop until exit
event_loop.run_return(|event, _, control_flow| {
@ -78,7 +88,17 @@ pub async fn run(args: &Args) -> Result<(), anyhow::Error> {
/// Creates all services and resources
pub async fn create_services_resources(
window: Arc<Window>,
) -> Result<(Services, Resources, ResourcesMut), anyhow::Error> {
) -> Result<
(
Services,
Resources,
ResourcesMut,
PlaylistRunner,
PlaylistReceiver,
PlaylistManager,
),
anyhow::Error,
> {
// Create the wgpu service
// TODO: Execute future in background and continue initializing
let (wgpu, wgpu_surface_resource) = Wgpu::new(Arc::clone(&window))
@ -86,7 +106,7 @@ pub async fn create_services_resources(
.context("Unable to create renderer")?;
// Create the playlist
let (playlist, playlist_resource) = PlaylistService::new();
let (playlist_runner, playlist_receiver, playlist_manager) = zsw_playlist::create();
// Create the image loader
let image_loader = ImageLoaderService::new();
@ -115,7 +135,6 @@ pub async fn create_services_resources(
let services = Services {
window,
wgpu,
playlist,
image_loader,
panels,
egui,
@ -128,7 +147,6 @@ pub async fn create_services_resources(
// Bundle the resources
let resources = Resources {
panels: Mutex::new(panels_resource),
playlist: Mutex::new(playlist_resource),
profiles: Mutex::new(profiles_resource),
wgpu_surface: Mutex::new(wgpu_surface_resource),
egui_platform: Mutex::new(egui_platform_resource),
@ -139,44 +157,58 @@ pub async fn create_services_resources(
egui_painter: egui_painter_resource,
};
Ok((services, resources, resources_mut))
Ok((
services,
resources,
resources_mut,
playlist_runner,
playlist_receiver,
playlist_manager,
))
}
/// Spawns all services and returns a future to join them all
// TODO: Hide future behind a `JoinHandle` type.
#[allow(clippy::needless_pass_by_value)] // Ergonomics
pub fn spawn_services(
services: &Arc<Services>,
resources: &Arc<Resources>,
mut resources_mut: ResourcesMut,
playlist_runner: PlaylistRunner,
playlist_receiver: PlaylistReceiver,
playlist_manager: PlaylistManager,
args: &Args,
) -> impl Future<Output = Result<(), anyhow::Error>> {
/// Macro to help spawn a service runner
macro spawn_service_runner([$($clones:ident),* $(,)?] $name:expr => $runner:expr) {{
$(
let $clones = Arc::clone(&$clones);
let $clones = $clones.clone();
)*
task::Builder::new().name($name).spawn(async move { $runner.await })
}}
// Spawn all
let profiles_loader_task = args.profile.clone().map(move |path| {
let profiles_loader_task = args.profile.clone().map(|path| {
spawn_service_runner!(
[services, resources] "Profiles loader" =>
services.profiles.run_loader_applier(&path, &*services, &*resources)
[services, resources, playlist_manager] "Profiles loader" =>
services.profiles.run_loader_applier(&path, &*services, &*resources, playlist_manager)
)
});
let playlist_task =
spawn_service_runner!([services, resources] "Playlist runner" => services.playlist.run(&*resources));
let playlist_runner_task = task::Builder::new()
.name("Playlist runner")
.spawn_blocking(move || playlist_runner.run());
let image_loader_tasks = (0..self::image_loader_tasks())
.map(|idx| {
spawn_service_runner!(
[services, resources] &format!("Image loader #{idx}") => services.image_loader.run(&*services, &*resources)
[services, playlist_receiver] &format!("Image loader #{idx}") => services.image_loader.run(playlist_receiver)
)
})
.collect::<Vec<_>>();
let settings_window_task = spawn_service_runner!(
[services, resources] "Settings window runner" => services.settings_window.run(&*services, &*resources, &mut resources_mut.egui_painter)
[services, resources] "Settings window runner" => services.settings_window.run(&*services, &*resources, &mut resources_mut.egui_painter, playlist_manager)
);
let renderer_task =
spawn_service_runner!([services, resources] "Renderer" => services.renderer.run(&*services, &*resources));
@ -186,7 +218,9 @@ pub fn spawn_services(
if let Some(task) = profiles_loader_task {
task.await.context("Unable to await for profiles loader runner")?;
}
playlist_task.await.context("Unable to await for playlist runner")?;
playlist_runner_task
.await
.context("Unable to await for playlist runner")?;
for task in image_loader_tasks {
task.await.context("Unable to wait for image loader runner")?;
}

View File

@ -5,7 +5,6 @@ use {
futures::lock::{Mutex, MutexLockFuture},
zsw_egui::{EguiPainterResource, EguiPlatformResource, EguiRenderPassResource},
zsw_panels::PanelsResource,
zsw_playlist::PlaylistResource,
zsw_profiles::ProfilesResource,
zsw_util::ResourcesBundle,
zsw_wgpu::WgpuSurfaceResource,
@ -17,9 +16,6 @@ pub struct Resources {
/// Panels
pub panels: Mutex<PanelsResource>,
/// Playlist
pub playlist: Mutex<PlaylistResource>,
/// Profiles
pub profiles: Mutex<ProfilesResource>,
@ -44,7 +40,6 @@ impl ResourcesBundle for Resources {}
#[duplicate::duplicate_item(
ty field;
[ PanelsResource ] [ panels ];
[ PlaylistResource ] [ playlist ];
[ ProfilesResource ] [ profiles ];
[ WgpuSurfaceResource ] [ wgpu_surface ];
[ EguiPlatformResource ] [ egui_platform ];

View File

@ -8,7 +8,6 @@ use {
zsw_img::ImageLoaderService,
zsw_input::Input,
zsw_panels::Panels,
zsw_playlist::PlaylistService,
zsw_profiles::Profiles,
zsw_renderer::Renderer,
zsw_settings_window::SettingsWindow,
@ -28,9 +27,6 @@ pub struct Services {
/// Wgpu
pub wgpu: Wgpu,
/// Playlist
pub playlist: PlaylistService,
/// Image loader
pub image_loader: ImageLoaderService,
@ -59,7 +55,6 @@ impl ServicesBundle for Services {}
ty field;
[ Window ] [ window ];
[ Wgpu ] [ wgpu ];
[ PlaylistService ] [ playlist ];
[ ImageLoaderService ] [ image_loader ];
[ Panels ] [ panels ];
[ Egui ] [ egui ];