Skip to content

Commit

Permalink
Adding Runtime support to callbacks
Browse files Browse the repository at this point in the history
One of the oversights of background execution of callbacks was ensuring
the cushy runtime was present. This forced me to ensure that only one
Cushy instance was installed at any given time, as the thread that
executes callbacks needs to enter the runtime. If this is set up before
the PendingApp is created, we need to ensure that the Cushy instance
PendingApp has and the one the callbacks have are the same one. By
ensuring we can only have one global instance, we meet this guarantee
and allow the casual user using the default runtime can create callbacks
before creating their PendingApp and everything works.
  • Loading branch information
ecton committed Jan 23, 2025
1 parent f0823cc commit 4501558
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 50 deletions.
102 changes: 76 additions & 26 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,13 @@ impl PendingApp {
/// [`with_tracing()`](Self::with_tracing)/[`initialize_tracing()`](Self::initialize_tracing)
/// to enable Cushy's built-in trace handling.
pub fn new<Runtime: AppRuntime>(runtime: Runtime) -> Self {
Self::from_cushy(Cushy::registered(BoxedRuntime(Box::new(runtime))))
}

fn from_cushy(cushy: Cushy) -> Self {
let mut app = kludgine::app::PendingApp::default();
app.on_unrecoverable_error(Self::unrecoverable_error);
Self {
app,
cushy: Cushy::new(BoxedRuntime(Box::new(runtime))),
}
Self { app, cushy }
}

/// Sets the error handler that is invoked when Cushy encounters an error
Expand Down Expand Up @@ -163,7 +164,7 @@ impl Run for PendingApp {

impl Default for PendingApp {
fn default() -> Self {
Self::new(DefaultRuntime::default()).with_tracing()
Self::from_cushy(Cushy::current()).with_tracing()
}
}

Expand Down Expand Up @@ -330,6 +331,12 @@ impl Clone for BoxedRuntime {
}
}

impl Default for BoxedRuntime {
fn default() -> Self {
Self(Box::new(DefaultRuntime::default()))
}
}

trait BoxableRuntime: Send {
fn enter_runtime(&self) -> RuntimeGuard<'_>;
fn cloned(&self) -> BoxedRuntime;
Expand Down Expand Up @@ -359,64 +366,99 @@ struct AppSettings {
multi_click_threshold: Duration,
}

static RUNNING_CUSHY: Mutex<Option<Cushy>> = const { Mutex::new(None) };

/// Shared resources for a GUI application.
#[derive(Clone)]
pub struct Cushy {
pub(crate) clipboard: Option<Arc<Mutex<Clipboard>>>,
pub(crate) fonts: FontCollection,
settings: Arc<Mutex<AppSettings>>,
pub(crate) data: Arc<CushyData>,
runtime: BoxedRuntime,
#[cfg(feature = "localization")]
pub(crate) localizations: Localizations,
}

impl Cushy {
fn new(runtime: BoxedRuntime) -> Self {
fn registered(runtime: BoxedRuntime) -> Self {
let cushy = Self::unregistered(runtime);

assert!(
RUNNING_CUSHY.lock().replace(cushy.clone()).is_none(),
"A Cushy instance was already initialized."
);

cushy
}

fn unregistered(runtime: BoxedRuntime) -> Self {
Self {
clipboard: Clipboard::new()
.ok()
.map(|clipboard| Arc::new(Mutex::new(clipboard))),
fonts: FontCollection::default(),
settings: Arc::new(Mutex::new(AppSettings {
multi_click_threshold: Duration::from_millis(500),
})),
data: Arc::new(CushyData {
clipboard: Clipboard::new()
.ok()
.map(|clipboard| Arc::new(Mutex::new(clipboard))),
fonts: FontCollection::default(),
settings: Mutex::new(AppSettings {
multi_click_threshold: Duration::from_millis(500),
}),
#[cfg(feature = "localization")]
localizations: Localizations::default(),
}),
runtime,
#[cfg(feature = "localization")]
localizations: Localizations::default(),
}
}

/// Returns the current Cushy instance, if initialized.
///
/// There can only be one Cushy instance per process.
#[must_use]
pub fn try_current() -> Option<Self> {
RUNNING_CUSHY.lock().clone()
}

/// Returns the current Cushy instance, initializing using the
/// [`DefaultRuntime`] if needed.
///
/// There can only be one Cushy instance per process.
#[must_use]
pub fn current() -> Self {
let mut current = RUNNING_CUSHY.lock();
if let Some(cushy) = &*current {
cushy.clone()
} else {
let default = Cushy::unregistered(BoxedRuntime::default());
*current = Some(default.clone());
default
}
}

/// Returns the duration between two mouse clicks that should be allowed to
/// elapse for the clicks to be considered separate actions.
#[must_use]
pub fn multi_click_threshold(&self) -> Duration {
self.settings.lock().multi_click_threshold
self.data.settings.lock().multi_click_threshold
}

/// Sets the maximum time between sequential clicks that should be
/// considered the same action.
pub fn set_multi_click_threshold(&self, threshold: Duration) {
self.settings.lock().multi_click_threshold = threshold;
self.data.settings.lock().multi_click_threshold = threshold;
}

/// Returns a locked mutex guard to the OS's clipboard, if one was able to be
/// initialized when the window opened.
#[must_use]
pub fn clipboard_guard(&self) -> Option<MutexGuard<'_, Clipboard>> {
self.clipboard.as_ref().map(|mutex| mutex.lock())
self.data.clipboard.as_ref().map(|mutex| mutex.lock())
}

/// Returns the font collection that will be loaded in all Cushy windows.
#[must_use]
pub fn fonts(&self) -> &FontCollection {
&self.fonts
&self.data.fonts
}

/// Returns the localizations that are applied throughout the application.
#[must_use]
#[cfg(feature = "localization")]
pub fn localizations(&self) -> &Localizations {
&self.localizations
&self.data.localizations
}

/// Enters the application's runtime context.
Expand All @@ -433,10 +475,18 @@ impl Cushy {

impl Default for Cushy {
fn default() -> Self {
Self::new(BoxedRuntime(Box::<DefaultRuntime>::default()))
Self::registered(BoxedRuntime::default())
}
}

pub(crate) struct CushyData {
pub(crate) clipboard: Option<Arc<Mutex<Clipboard>>>,
pub(crate) fonts: FontCollection,
settings: Mutex<AppSettings>,
#[cfg(feature = "localization")]
pub(crate) localizations: Localizations,
}

/// A type that is a Cushy application.
pub trait Application: AsApplication<AppEvent<WindowCommand>> {
/// Returns the shared resources for the application.
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ impl MaybeLocalized {
match self {
MaybeLocalized::Text(text) => text.clone(),
#[cfg(feature = "localization")]
MaybeLocalized::Localized(localized) => {
localized.localize(&localization::WindowTranslationContext(&app.localizations))
}
MaybeLocalized::Localized(localized) => localized.localize(
&localization::WindowTranslationContext(&app.data.localizations),
),
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/reactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use tracing::warn;

use self::channel::{AnyChannel, ChannelCallbackFuture};
use self::value::{CallbackDisconnected, DeadlockError, DynamicLockData};
use crate::Lazy;
use crate::{Cushy, Lazy};

pub mod channel;
pub mod value;
Expand Down Expand Up @@ -280,6 +280,8 @@ impl CallbackExecutor {

fn run(mut self) {
IS_EXECUTOR_THREAD.set(true);
let cushy = Cushy::current();
let _runtime = cushy.enter_runtime();

// Because this is stored in a static, this likely will never return an
// error, but if it does, it's during program shutdown, and we can exit safely.
Expand Down
3 changes: 2 additions & 1 deletion src/reactive/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ use builder::Builder;
use parking_lot::{Condvar, Mutex, MutexGuard};
use sealed::{AnyChannelCallback, AsyncCallbackFuture, CallbackKind, ChannelCallbackError};

use crate::reactive::{enqueue_task, BackgroundTask, ChannelTask};
use super::enqueue_task;
use crate::reactive::{BackgroundTask, ChannelTask};
use crate::value::CallbackDisconnected;

pub mod builder;
Expand Down
2 changes: 1 addition & 1 deletion src/reactive/channel/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Builder types for Cushy [`channel`](super)qs.
//! Builder types for Cushy [`channel`](super)s.
use std::future::Future;
use std::marker::PhantomData;

Expand Down
36 changes: 18 additions & 18 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1427,7 +1427,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
)
Expand All @@ -1442,7 +1442,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
)
Expand All @@ -1463,7 +1463,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
)
Expand Down Expand Up @@ -1498,7 +1498,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
graphics,
);
Expand Down Expand Up @@ -1651,7 +1651,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
);
Expand Down Expand Up @@ -1686,7 +1686,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
);
Expand Down Expand Up @@ -1753,7 +1753,7 @@ where
let app = settings.app.clone();
let fonts = Self::load_fonts(
&mut settings,
app.cushy().fonts.clone(),
app.cushy().data.fonts.clone(),
graphics.font_system().db_mut(),
);

Expand Down Expand Up @@ -1902,7 +1902,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
gfx: Exclusive::Owned(Graphics::new(graphics)),
};
Expand Down Expand Up @@ -2190,7 +2190,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
);
Expand Down Expand Up @@ -2253,7 +2253,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
);
Expand Down Expand Up @@ -2298,7 +2298,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
);
Expand Down Expand Up @@ -2345,7 +2345,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
)
Expand All @@ -2366,7 +2366,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
);
Expand Down Expand Up @@ -2408,7 +2408,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
);
Expand Down Expand Up @@ -2453,7 +2453,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
),
Expand Down Expand Up @@ -2481,7 +2481,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
)
Expand Down Expand Up @@ -2532,7 +2532,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
);
Expand Down Expand Up @@ -2983,7 +2983,7 @@ where
self.theme_mode.get(),
&mut self.cursor,
#[cfg(feature = "localization")]
&self.app.cushy().localizations,
&self.app.cushy().data.localizations,
),
kludgine,
);
Expand Down

0 comments on commit 4501558

Please sign in to comment.