Skip to content

Commit

Permalink
Added optional tokio integration
Browse files Browse the repository at this point in the history
Closes #147
  • Loading branch information
ecton committed May 12, 2024
1 parent 8f5ce94 commit b57188f
Show file tree
Hide file tree
Showing 9 changed files with 346 additions and 20 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `OverlayLayer::dismiss_all()` dismisses all overlays immediately.
- `Menu` is a new widget type that can be shown in an `OverlayLayer` to create
contextual menus or other popup menus.
- `PendingApp::new` is a new function that accepts an `AppRuntime` implementor.
This abstraction is how Cushy provides the optional integration for `tokio`.
- Features `tokio` and `tokio-multi-thread` enable the tokio integration for
this crate and expose a new type `TokioRuntime`. The `DefaultRuntime`
automatically will use the `TokioRuntime` if either feature is enabled.

When the `tokio` integration is enabled, `tokio::spawn` is able to be invoked
from all Cushy code safely.

[plotters]: https://github.com/plotters-rs/plotters

Expand Down
69 changes: 68 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ default = ["tracing-output", "roboto-flex"]
tracing-output = ["dep:tracing-subscriber"]
roboto-flex = []
plotters = ["dep:plotters", "kludgine/plotters"]
tokio = ["dep:tokio"]
tokio-multi-thread = ["tokio", "tokio/rt-multi-thread"]

[dependencies]
# kludgine = { version = "0.7.0", features = ["app"] }
Expand All @@ -30,6 +32,7 @@ interner = "0.2.1"
kempt = "0.2.1"
intentional = "0.1.0"
tracing = "0.1.40"
tokio = { version = "1.37.0", optional = true, features = ["rt"] }

tracing-subscriber = { version = "0.3", optional = true, features = [
"env-filter",
Expand Down Expand Up @@ -67,11 +70,16 @@ opt-level = 2

[dev-dependencies]
rand = "0.8.5"
tokio = { version = "1.37.0", features = ["time"] }

[[example]]
name = "plotters"
required-features = ["plotters"]

[[example]]
name = "tokio"
required-features = ["tokio"]

[profile.release]
# debug = true
# opt-level = "s"
Expand Down
31 changes: 31 additions & 0 deletions examples/tokio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use std::time::Duration;

use cushy::value::{Destination, Dynamic};
use cushy::widget::MakeWidget;
use cushy::widgets::progress::Progressable;
use cushy::{Open, PendingApp, TokioRuntime};
use tokio::time::sleep;

fn main() {
let app = PendingApp::new(TokioRuntime::default());
let progress = Dynamic::new(0_u8);
let progress_bar = progress.clone().progress_bar();
"Press Me"
.into_button()
.on_click(move |_| {
tokio::spawn(do_something(progress.clone()));
})
.and(progress_bar)
.into_rows()
.centered()
.expand()
.run_in(app)
.expect("error starting Cushy");
}

async fn do_something(progress: Dynamic<u8>) {
for i in 0..u8::MAX {
progress.set(i);
sleep(Duration::from_millis(10)).await
}
}
26 changes: 16 additions & 10 deletions src/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,26 @@ use crate::animation::easings::Linear;
use crate::styles::{Component, RequireInvalidation};
use crate::utils::run_in_bg;
use crate::value::{Destination, Dynamic, Source};
use crate::Cushy;

static ANIMATIONS: Mutex<Animating> = Mutex::new(Animating::new());
static NEW_ANIMATIONS: Condvar = Condvar::new();

fn thread_state() -> MutexGuard<'static, Animating> {
pub(crate) fn spawn(app: Cushy) {
let _ignored = thread_state(Some(app));
}

fn thread_state(app: Option<Cushy>) -> MutexGuard<'static, Animating> {
static THREAD: OnceLock<()> = OnceLock::new();
THREAD.get_or_init(|| {
thread::spawn(animation_thread);
THREAD.get_or_init(move || {
thread::spawn(move || animation_thread(app.as_ref()));
});
ANIMATIONS.lock()
}

fn animation_thread() {
let mut state = thread_state();
fn animation_thread(app: Option<&Cushy>) {
let _guard = app.as_ref().map(|app| app.enter_runtime());
let mut state = thread_state(None);
loop {
if state.running.is_empty() {
state.last_updated = None;
Expand Down Expand Up @@ -104,7 +110,7 @@ fn animation_thread() {
.checked_duration_since(Instant::now())
.unwrap_or(Duration::from_millis(16)),
);
state = thread_state();
state = thread_state(None);
}
}
}
Expand Down Expand Up @@ -456,7 +462,7 @@ where

impl Spawn for Box<dyn Animate> {
fn spawn(self) -> AnimationHandle {
thread_state().spawn(self)
thread_state(None).spawn(self)
}
}

Expand Down Expand Up @@ -512,7 +518,7 @@ impl AnimationHandle {
/// This has the same effect as dropping the handle.
pub fn clear(&mut self) {
if let Some(id) = self.0.take() {
thread_state().remove_animation(id);
thread_state(None).remove_animation(id);
}
}

Expand All @@ -524,7 +530,7 @@ impl AnimationHandle {
/// through completion without needing to hold onto the handle.
pub fn detach(mut self) {
if let Some(id) = self.0.take() {
thread_state().run_unattached(id);
thread_state(None).run_unattached(id);
}
}

Expand All @@ -533,7 +539,7 @@ impl AnimationHandle {
pub fn is_running(&self) -> bool {
let Some(id) = self.0 else { return false };

thread_state().running.contains(&id)
thread_state(None).running.contains(&id)
}

/// Returns true if this animation is complete.
Expand Down
Loading

0 comments on commit b57188f

Please sign in to comment.