-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor callbacks + tuple lock handling
This change fundamentally changes how change callbacks work on Dynamics. Prior to this change, callbacks executed on the thread that was performing the change. This could lead to situations where multiple threads were executing callback chains which leads to unpredictable locking patterns on the dynamics. The basic deadlock detection was not enough. This change defers callbacks to a single callback thread. This thread ensures that no dynamic can have callbacks enqueued more than once. By limiting execution to one set of callbacks at any given time, this greatly reduces the surface for locks to contend with each other. The next issue was how tuple-related functions like for_each/map_each were acquiring their locks. By calling a.read() then b.read(), this was causing a to be held in a locked state while b was being aquired. If users are careful to always acquire their locks in this order, everything is fine. But with Cushy there can be unexpected situations where these locks are being held. This change also refactors lock acquisition for tuples to try to acquire all the locks in a non-blocking way. If any lock woould block, the initial locks are dropped while the lock that would block is waited on. After this is acquired the process starts over again to gain all the locks. This isn't perfect, but it doesn't require unsafe. With unsafe, we could in theory create a ring of callbacks that handles acquiring all of the locks into MaybeUninits. Upon successfully calling all callbacks, the values can be assumed init. But writing all of this in macro_rules isn't fun, and the current solution alleviates the main problem
- Loading branch information
Showing
9 changed files
with
732 additions
and
212 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
use cushy::value::Dynamic; | ||
use cushy::widget::MakeWidget; | ||
use cushy::widgets::label::Displayable; | ||
use cushy::Run; | ||
use figures::units::Lp; | ||
|
||
fn main() -> cushy::Result { | ||
let count = Dynamic::new(0_usize); | ||
|
||
count | ||
.to_label() | ||
.expand() | ||
.and( | ||
"Count" | ||
.into_button() | ||
.on_click(move |_| { | ||
*count.lock() += 1; | ||
}) | ||
.expand(), | ||
) | ||
.into_columns() | ||
.pad() | ||
.width(Lp::inches(3)) | ||
.into_window() | ||
.titled("Counter") | ||
.resize_to_fit(true) | ||
.run() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
use cushy::value::Dynamic; | ||
use cushy::widget::MakeWidget; | ||
use cushy::widgets::input::InputValue; | ||
use cushy::Run; | ||
use figures::units::Lp; | ||
|
||
fn main() -> cushy::Result { | ||
let celsius = Dynamic::new(100f32); | ||
let farenheit = celsius.linked( | ||
|celsius| *celsius * 9. / 5. + 32., | ||
|farenheit| (*farenheit - 32.) * 5. / 9., | ||
); | ||
|
||
let celsius_string = celsius.linked_string(); | ||
let farenheight_string = farenheit.linked_string(); | ||
|
||
celsius_string | ||
.into_input() | ||
.expand() | ||
.and("Celsius =") | ||
.and(farenheight_string.into_input().expand()) | ||
.and("Farenheit") | ||
.into_columns() | ||
.pad() | ||
.width(Lp::inches(4)) | ||
.into_window() | ||
.titled("Temperature Converter") | ||
.resize_to_fit(true) | ||
.run() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
use std::time::{Duration, Instant}; | ||
|
||
use cushy::value::{Destination, Dynamic, DynamicReader, Source}; | ||
use cushy::widget::MakeWidget; | ||
use cushy::widgets::progress::Progressable; | ||
use cushy::widgets::slider::Slidable; | ||
use cushy::{Open, PendingApp}; | ||
use figures::units::Lp; | ||
|
||
#[derive(PartialEq, Debug, Clone)] | ||
struct Timer { | ||
started_at: Instant, | ||
duration: Duration, | ||
} | ||
|
||
impl Default for Timer { | ||
fn default() -> Self { | ||
Self { | ||
started_at: Instant::now() - Duration::from_secs(1), | ||
duration: Duration::from_secs(1), | ||
} | ||
} | ||
} | ||
|
||
fn main() -> cushy::Result { | ||
let pending = PendingApp::default(); | ||
let cushy = pending.cushy().clone(); | ||
let _runtime = cushy.enter_runtime(); | ||
|
||
let timer = Dynamic::<Timer>::default(); | ||
let duration = timer.linked_accessor(|timer| &timer.duration, |timer| &mut timer.duration); | ||
|
||
let elapsed = spawn_timer(&timer); | ||
let duration_label = duration.map_each(|duration| format!("{}s", duration.as_secs_f32())); | ||
|
||
elapsed | ||
.progress_bar_between( | ||
duration | ||
.weak_clone() | ||
.map_each_cloned(|duration| Duration::ZERO..=duration), | ||
) | ||
.fit_horizontally() | ||
.and(duration_label) | ||
.and( | ||
"Duration" | ||
.and( | ||
duration | ||
.slider_between(Duration::ZERO, Duration::from_secs(30)) | ||
.expand_horizontally(), | ||
) | ||
.into_columns(), | ||
) | ||
.and("Reset".into_button().on_click(move |_| { | ||
timer.lock().started_at = Instant::now(); | ||
})) | ||
.into_rows() | ||
.pad() | ||
.width(Lp::inches(4)) | ||
.into_window() | ||
.titled("Timer") | ||
.resize_to_fit(true) | ||
.run_in(pending) | ||
} | ||
|
||
fn spawn_timer(timer: &Dynamic<Timer>) -> DynamicReader<Duration> { | ||
let timer = timer.create_reader(); | ||
let elapsed = Dynamic::new(timer.map_ref(|timer| timer.duration)); | ||
let elapsed_reader = elapsed.weak_clone().into_reader(); | ||
std::thread::spawn(move || loop { | ||
let settings = timer.get(); | ||
|
||
// Update the elapsed time, clamping to the duration of the timer. | ||
let duration_since_started = settings.started_at.elapsed().min(settings.duration); | ||
elapsed.set(duration_since_started); | ||
|
||
if duration_since_started < settings.duration { | ||
// The timer is still running, "tick" the timer by sleeping and | ||
// allow the loop to continue. | ||
std::thread::sleep(Duration::from_millis(16)); | ||
} else { | ||
// Block the thread until the timer settings have been changed. | ||
timer.block_until_updated(); | ||
} | ||
}); | ||
elapsed_reader | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.