use crate::{
builtin_widgets::{FullTheme, InheritTheme, Theme},
clipboard::{Clipboard, MockClipboard},
};
use pin_project_lite::pin_project;
use std::{
cell::RefCell,
ptr::NonNull,
rc::Rc,
sync::Arc,
task::{Context, RawWaker, RawWakerVTable, Waker},
};
use crate::prelude::FuturesLocalScheduler;
pub use futures::task::SpawnError;
use futures::{
executor::{block_on, LocalPool},
task::LocalSpawnExt,
Future,
};
use ribir_text::shaper::TextShaper;
use ribir_text::{font_db::FontDB, TextReorder, TypographyStore};
pub type WakeRuntimeFn = Box<dyn Fn() + Send + Sync>;
#[derive(Clone)]
pub struct AppContext {
app_theme: NonNull<Theme>,
pub font_db: Rc<RefCell<FontDB>>,
pub shaper: TextShaper,
pub reorder: TextReorder,
pub typography_store: TypographyStore,
pub clipboard: Rc<RefCell<dyn Clipboard>>,
pub runtime_waker: Arc<WakeRuntimeFn>,
executor: Executor,
}
#[derive(Clone)]
pub struct Executor {
local: Rc<RefCell<LocalPool>>,
}
impl Default for Executor {
fn default() -> Self {
Self {
local: Rc::new(RefCell::new(LocalPool::default())),
}
}
}
impl AppContext {
pub fn new(theme: FullTheme) -> Self {
let theme = Box::new(Theme::Full(theme));
let app_theme = Box::leak(theme).into();
let mut font_db = FontDB::default();
font_db.load_system_fonts();
let font_db = Rc::new(RefCell::new(font_db));
let shaper = TextShaper::new(font_db.clone());
let reorder = TextReorder::default();
let typography_store = TypographyStore::new(reorder.clone(), font_db.clone(), shaper.clone());
let ctx = AppContext {
font_db,
app_theme,
shaper,
reorder,
typography_store,
clipboard: Rc::new(RefCell::new(MockClipboard {})),
executor: <_>::default(),
runtime_waker: Arc::new(Box::new(move || unimplemented!())),
};
ctx.load_font_from_theme(ctx.app_theme());
ctx
}
pub fn app_theme(&self) -> &Theme { unsafe { self.app_theme.as_ref() } }
#[allow(clippy::mut_from_ref)]
pub fn app_theme_mut(&self) -> &mut Theme {
let mut ptr = self.app_theme;
unsafe { &mut *ptr.as_mut() }
}
pub fn scheduler(&self) -> FuturesLocalScheduler { self.executor.local.borrow().spawner() }
pub(crate) fn end_frame(&mut self) {
self.shaper.end_frame();
self.reorder.end_frame();
self.typography_store.end_frame();
}
pub fn load_font_from_theme(&self, theme: &Theme) {
let mut font_db = self.font_db.borrow_mut();
match theme {
Theme::Full(FullTheme { font_bytes, font_files, .. })
| Theme::Inherit(InheritTheme { font_bytes, font_files, .. }) => {
if let Some(font_bytes) = font_bytes {
font_bytes
.iter()
.for_each(|data| font_db.load_from_bytes(data.clone()));
}
if let Some(font_files) = font_files {
font_files.iter().for_each(|path| {
let _ = font_db.load_font_file(path);
});
}
}
}
}
pub fn run_until_stalled(&self) { self.executor.local.borrow_mut().run_until_stalled() }
}
impl Default for AppContext {
fn default() -> Self { AppContext::new(<_>::default()) }
}
impl AppContext {
pub fn wait_future<F: Future>(f: F) -> F::Output { block_on(f) }
#[inline]
pub fn spawn_local<Fut>(&self, future: Fut) -> Result<(), SpawnError>
where
Fut: Future<Output = ()> + 'static,
{
(self.runtime_waker)();
self
.executor
.local
.borrow()
.spawner()
.spawn_local(LocalFuture {
fut: future,
waker: self.runtime_waker.clone(),
})
}
}
pin_project! {
struct LocalFuture<F> {
#[pin]
fut: F,
waker: Arc<WakeRuntimeFn>,
}
}
impl<F> LocalFuture<F>
where
F: Future,
{
fn local_waker(&self, cx: &mut std::task::Context<'_>) -> Waker {
type RawLocalWaker = (std::task::Waker, Arc<WakeRuntimeFn>);
fn clone(this: *const ()) -> RawWaker {
let waker = this as *const RawLocalWaker;
let (w, cb) = unsafe { &*waker };
let data = Box::new((w.clone(), cb.clone()));
let raw = Box::leak(data) as *const RawLocalWaker;
RawWaker::new(raw as *const (), &VTABLE)
}
unsafe fn wake(this: *const ()) {
let waker = this as *mut RawLocalWaker;
let (w, cb) = unsafe { &*waker };
w.wake_by_ref();
(cb)();
drop(this);
}
unsafe fn wake_by_ref(this: *const ()) {
let waker = this as *mut RawLocalWaker;
let (w, cb) = unsafe { &*waker };
w.wake_by_ref();
(cb)();
}
unsafe fn drop(this: *const ()) {
let waker = this as *mut RawLocalWaker;
let _ = Box::from_raw(waker);
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
let old_waker = cx.waker().clone();
let data = Box::new((old_waker, self.waker.clone()));
let raw = RawWaker::new(
Box::leak(data) as *const RawLocalWaker as *const (),
&VTABLE,
);
unsafe { Waker::from_raw(raw) }
}
}
impl<F> Future for LocalFuture<F>
where
F: Future,
{
type Output = F::Output;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let waker = self.local_waker(cx);
let mut cx = Context::from_waker(&waker);
let this = self.project();
this.fut.poll(&mut cx)
}
}
#[cfg(test)]
mod tests {
use std::{
cell::RefCell,
rc::Rc,
sync::Arc,
sync::Mutex,
task::{Poll, Waker},
};
use super::AppContext;
use futures::Future;
#[derive(Default)]
struct Trigger {
ready: bool,
waker: Option<Waker>,
}
impl Trigger {
fn trigger(&mut self) {
if self.ready {
return;
}
self.ready = true;
if let Some(waker) = self.waker.take() {
waker.wake();
}
}
fn pedding(&mut self, waker: &Waker) { self.waker = Some(waker.clone()) }
}
struct ManualFuture {
trigger: Rc<RefCell<Trigger>>,
cnt: usize,
}
impl Future for ManualFuture {
type Output = usize;
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
if self.trigger.borrow().ready {
Poll::Ready(self.cnt)
} else {
self.trigger.borrow_mut().pedding(cx.waker());
Poll::Pending
}
}
}
#[test]
fn local_future_smoke() {
let mut ctx = AppContext::default();
let ctx_wake_cnt = Arc::new(Mutex::new(0));
let wake_cnt = ctx_wake_cnt.clone();
ctx.runtime_waker = Arc::new(Box::new(move || {
*wake_cnt.lock().unwrap() += 1;
}));
let triggers = (0..3)
.map(|_| Rc::new(RefCell::new(Trigger::default())))
.collect::<Vec<_>>();
let futs = triggers
.clone()
.into_iter()
.map(|trigger| ManualFuture { trigger, cnt: 1 });
let acc = Rc::new(RefCell::new(0));
let sum = acc.clone();
let _ = ctx.spawn_local(async move {
for fut in futs {
let v = fut.await;
*acc.borrow_mut() += v;
}
});
ctx.run_until_stalled();
let mut waker_cnt = *ctx_wake_cnt.lock().unwrap();
ctx.run_until_stalled();
assert_eq!(*sum.borrow(), 0);
assert_eq!(*ctx_wake_cnt.lock().unwrap(), waker_cnt);
for (idx, trigger) in triggers.into_iter().enumerate() {
trigger.borrow_mut().trigger();
waker_cnt += 1;
assert_eq!(*ctx_wake_cnt.lock().unwrap(), waker_cnt);
ctx.run_until_stalled();
assert_eq!(*sum.borrow(), idx + 1);
}
}
}