@@ -1,12 +1,16 @@
mod async_context;
mod entity_map;
mod model_context;
+#[cfg(any(test, feature = "test-support"))]
+mod test_context;
pub use async_context::*;
pub use entity_map::*;
pub use model_context::*;
use refineable::Refineable;
use smallvec::SmallVec;
+#[cfg(any(test, feature = "test-support"))]
+pub use test_context::*;
use crate::{
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource,
@@ -0,0 +1,137 @@
+use crate::{
+ AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, Handle, MainThread,
+ ModelContext, Result, Task, WindowContext,
+};
+use parking_lot::Mutex;
+use std::{any::Any, future::Future, sync::Arc};
+
+#[derive(Clone)]
+pub struct TestAppContext {
+ pub(crate) app: Arc<Mutex<AppContext>>,
+ pub(crate) executor: Executor,
+}
+
+impl Context for TestAppContext {
+ type EntityContext<'a, 'w, T> = ModelContext<'a, T>;
+ type Result<T> = T;
+
+ fn entity<T: 'static>(
+ &mut self,
+ build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
+ ) -> Self::Result<Handle<T>>
+ where
+ T: Any + Send + Sync,
+ {
+ let mut lock = self.app.lock();
+ lock.entity(build_entity)
+ }
+
+ fn update_entity<T: 'static, R>(
+ &mut self,
+ handle: &Handle<T>,
+ update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
+ ) -> Self::Result<R> {
+ let mut lock = self.app.lock();
+ lock.update_entity(handle, update)
+ }
+}
+
+impl TestAppContext {
+ pub fn refresh(&mut self) -> Result<()> {
+ let mut lock = self.app.lock();
+ lock.refresh();
+ Ok(())
+ }
+
+ pub fn executor(&self) -> &Executor {
+ &self.executor
+ }
+
+ pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {
+ let mut lock = self.app.lock();
+ Ok(f(&mut *lock))
+ }
+
+ pub fn read_window<R>(
+ &self,
+ handle: AnyWindowHandle,
+ update: impl FnOnce(&WindowContext) -> R,
+ ) -> Result<R> {
+ let mut app_context = self.app.lock();
+ app_context.read_window(handle.id, update)
+ }
+
+ pub fn update_window<R>(
+ &self,
+ handle: AnyWindowHandle,
+ update: impl FnOnce(&mut WindowContext) -> R,
+ ) -> Result<R> {
+ let mut app = self.app.lock();
+ app.update_window(handle.id, update)
+ }
+
+ pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
+ where
+ Fut: Future<Output = R> + Send + 'static,
+ R: Send + 'static,
+ {
+ let cx = self.to_async();
+ self.executor.spawn(async move { f(cx).await })
+ }
+
+ pub fn spawn_on_main<Fut, R>(
+ &self,
+ f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static,
+ ) -> Task<R>
+ where
+ Fut: Future<Output = R> + 'static,
+ R: Send + 'static,
+ {
+ let cx = self.to_async();
+ self.executor.spawn_on_main(|| f(cx))
+ }
+
+ pub fn run_on_main<R>(
+ &self,
+ f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
+ ) -> Result<Task<R>>
+ where
+ R: Send + 'static,
+ {
+ let mut app_context = self.app.lock();
+ Ok(app_context.run_on_main(f))
+ }
+
+ pub fn has_global<G: 'static>(&self) -> Result<bool> {
+ let lock = self.app.lock();
+ Ok(lock.has_global::<G>())
+ }
+
+ pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result<R> {
+ let lock = self.app.lock();
+ Ok(read(lock.global(), &lock))
+ }
+
+ pub fn try_read_global<G: 'static, R>(
+ &self,
+ read: impl FnOnce(&G, &AppContext) -> R,
+ ) -> Option<R> {
+ let lock = self.app.lock();
+ Some(read(lock.try_global()?, &lock))
+ }
+
+ pub fn update_global<G: 'static, R>(
+ &mut self,
+ update: impl FnOnce(&mut G, &mut AppContext) -> R,
+ ) -> Result<R> {
+ let mut lock = self.app.lock();
+ Ok(lock.update_global(update))
+ }
+
+ fn to_async(&self) -> AsyncAppContext {
+ AsyncAppContext {
+ app: Arc::downgrade(&self.app),
+ executor: self.executor.clone(),
+ }
+ }
+}
@@ -1,57 +1,87 @@
use crate::PlatformDispatcher;
use async_task::Runnable;
-use collections::{BTreeMap, VecDeque};
+use collections::{BTreeMap, HashMap, VecDeque};
use parking_lot::Mutex;
use rand::prelude::*;
-use std::time::{Duration, Instant};
-
-pub struct TestDispatcher(Mutex<TestDispatcherState>);
+use std::{
+ sync::Arc,
+ time::{Duration, Instant},
+};
+use util::post_inc;
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+struct TestDispatcherId(usize);
+
+pub struct TestDispatcher {
+ id: TestDispatcherId,
+ state: Arc<Mutex<TestDispatcherState>>,
+}
struct TestDispatcherState {
random: StdRng,
- foreground: VecDeque<Runnable>,
+ foreground: HashMap<TestDispatcherId, VecDeque<Runnable>>,
background: Vec<Runnable>,
delayed: BTreeMap<Instant, Runnable>,
time: Instant,
is_main_thread: bool,
+ next_id: TestDispatcherId,
}
impl TestDispatcher {
pub fn new(random: StdRng) -> Self {
let state = TestDispatcherState {
random,
- foreground: VecDeque::new(),
+ foreground: HashMap::default(),
background: Vec::new(),
delayed: BTreeMap::new(),
time: Instant::now(),
is_main_thread: true,
+ next_id: TestDispatcherId(1),
};
- TestDispatcher(Mutex::new(state))
+ TestDispatcher {
+ id: TestDispatcherId(0),
+ state: Arc::new(Mutex::new(state)),
+ }
+ }
+}
+
+impl Clone for TestDispatcher {
+ fn clone(&self) -> Self {
+ let id = post_inc(&mut self.state.lock().next_id.0);
+ Self {
+ id: TestDispatcherId(id),
+ state: self.state.clone(),
+ }
}
}
impl PlatformDispatcher for TestDispatcher {
fn is_main_thread(&self) -> bool {
- self.0.lock().is_main_thread
+ self.state.lock().is_main_thread
}
fn dispatch(&self, runnable: Runnable) {
- self.0.lock().background.push(runnable);
+ self.state.lock().background.push(runnable);
}
fn dispatch_on_main_thread(&self, runnable: Runnable) {
- self.0.lock().foreground.push_back(runnable);
+ self.state
+ .lock()
+ .foreground
+ .entry(self.id)
+ .or_default()
+ .push_back(runnable);
}
fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) {
- let mut state = self.0.lock();
+ let mut state = self.state.lock();
let next_time = state.time + duration;
state.delayed.insert(next_time, runnable);
}
fn poll(&self) -> bool {
- let mut state = self.0.lock();
+ let mut state = self.state.lock();
while let Some((deadline, _)) = state.delayed.first_key_value() {
if *deadline > state.time {
@@ -61,36 +91,48 @@ impl PlatformDispatcher for TestDispatcher {
state.background.push(runnable);
}
- if state.foreground.is_empty() && state.background.is_empty() {
+ let foreground_len: usize = state
+ .foreground
+ .values()
+ .map(|runnables| runnables.len())
+ .sum();
+ let background_len = state.background.len();
+
+ if foreground_len == 0 && background_len == 0 {
return false;
}
- let foreground_len = state.foreground.len();
- let background_len = state.background.len();
- let main_thread = background_len == 0
- || state
- .random
- .gen_ratio(foreground_len as u32, background_len as u32);
+ let main_thread = state.random.gen_ratio(
+ foreground_len as u32,
+ (foreground_len + background_len) as u32,
+ );
let was_main_thread = state.is_main_thread;
state.is_main_thread = main_thread;
let runnable = if main_thread {
- state.foreground.pop_front().unwrap()
+ let state = &mut *state;
+ let runnables = state
+ .foreground
+ .values_mut()
+ .filter(|runnables| !runnables.is_empty())
+ .choose(&mut state.random)
+ .unwrap();
+ runnables.pop_front().unwrap()
} else {
let ix = state.random.gen_range(0..background_len);
- state.background.remove(ix)
+ state.background.swap_remove(ix)
};
drop(state);
runnable.run();
- self.0.lock().is_main_thread = was_main_thread;
+ self.state.lock().is_main_thread = was_main_thread;
true
}
fn advance_clock(&self, by: Duration) {
- self.0.lock().time += by;
+ self.state.lock().time += by;
}
}