mirror of
https://github.com/Zenithsiz/zsw.git
synced 2026-02-04 02:08:37 +00:00
Heavily revised zsw-playlist to not use the services / resources traits, as well as no external locking.
This commit is contained in:
parent
96067bbf14
commit
95a9a647b7
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
||||
|
||||
@ -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);
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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")?;
|
||||
}
|
||||
|
||||
@ -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 ];
|
||||
|
||||
@ -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 ];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user