use compo::prelude::*;
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{
DispatchMessageW, MSG, PM_REMOVE, PeekMessageW, TranslateMessage, WM_QUIT,
};
#[cfg(target_os = "ios")]
use {
block2::RcBlock,
objc2::{ClassType, msg_send},
objc2_foundation::{NSRunLoop, NSString, NSTimer},
};
#[cfg(target_os = "android")]
use {
jni::{
AttachGuard, JNIEnv, JavaVM, NativeMethod,
errors::{Error, JniError, Result as JniResult},
},
std::{
any::Any,
cell::{Cell, RefCell},
ptr::null_mut,
},
tracing::error,
};
#[cfg(target_os = "macos")]
use {
block2::RcBlock,
objc2::{ClassType, msg_send},
objc2_foundation::{NSRunLoop, NSString, NSTimer},
};
#[cfg(target_os = "android")]
thread_local! {
static RT: Rc<Runtime<'static, ()>> = Rc::new(Runtime::new());
static COMPONENT: Cell<Rc<dyn Any>> = Cell::new(Rc::new(()));
static JAVA_VM: RefCell<JniResult<JavaVM>> = RefCell::new(unsafe { JavaVM::from_raw(null_mut()) });
}
#[cfg(windows)]
fn handle_windows_message(r#loop: &Loop) {
unsafe {
let mut msg = MSG::default();
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
if msg.message == WM_QUIT {
r#loop.quit();
break;
}
let _ = TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
}
#[cfg(not(target_os = "android"))]
pub fn run<'a, C, F>(entry: F)
where
C: Component<'a> + 'a,
F: AsyncFn(Weak<C>) + 'a,
{
#[cfg(windows)]
Loop::new()
.register_poll_handler(handle_windows_message)
.run(entry);
#[cfg(target_os = "ios")]
{
let rt = Rc::new(Runtime::new());
let rt_weak = Rc::downgrade(&rt);
let c = Rc::new(C::new(rt_weak.clone()));
let c_weak = Rc::downgrade(&c);
rt.spawn(async move { entry(c_weak).await });
let run_loop = NSRunLoop::mainRunLoop();
let poll_block = RcBlock::new(move || {
if let Some(rt) = rt_weak.upgrade() {
rt.poll_all();
}
});
let timer: *mut NSTimer = unsafe {
msg_send![NSTimer::class(),
scheduledTimerWithTimeInterval: 0.01,
repeats: true,
block: &*poll_block
]
};
let mode = NSString::from_str("NSDefaultRunLoopMode");
let _: () = unsafe { msg_send![&run_loop, addTimer: timer, forMode: &*mode] };
#[cfg(not(feature = "application"))]
run_loop.run();
#[cfg(feature = "application")]
if let Some(mtm) = objc2::MainThreadMarker::new() {
objc2_ui_kit::UIApplication::main(None, None, mtm)
} else {
tracing::error!("Can't run on non-main-thread.")
}
}
#[cfg(target_os = "macos")]
{
let rt = Rc::new(Runtime::new());
let rt_weak = Rc::downgrade(&rt);
let c = Rc::new(C::new(rt_weak.clone()));
let c_weak = Rc::downgrade(&c);
rt.spawn(async move { entry(c_weak).await });
let run_loop = NSRunLoop::mainRunLoop();
let poll_block = RcBlock::new(move || {
if let Some(rt) = rt_weak.upgrade() {
rt.poll_all();
}
});
let timer: *mut NSTimer = unsafe {
msg_send![NSTimer::class(),
scheduledTimerWithTimeInterval: 0.01,
repeats: true,
block: &*poll_block
]
};
let mode = NSString::from_str("NSDefaultRunLoopMode");
let _: () = unsafe { msg_send![&run_loop, addTimer: timer, forMode: &*mode] };
#[cfg(not(feature = "application"))]
run_loop.run();
#[cfg(feature = "application")]
if let Some(mtm) = objc2::MainThreadMarker::new() {
let app = objc2_app_kit::NSApplication::sharedApplication(mtm);
app.activate();
app.run();
} else {
tracing::error!("Can't run on non-main-thread.")
}
}
}
#[cfg(target_os = "android")]
pub fn run<C, F>(vm: JavaVM, entry: F)
where
C: Component<'static> + 'static,
F: AsyncFn(Weak<C>) + 'static,
{
JAVA_VM.set(Ok(vm));
RT.with(|rt| {
let rt_weak = Rc::downgrade(rt);
let c = Rc::new(C::new(rt_weak.clone()));
let c_weak = Rc::downgrade(&c);
rt.spawn(async move { entry(c_weak).await });
COMPONENT.set(c);
});
if let Err(e) = vm_exec(|mut env| {
const CLASS: &str = "rust/compo/MainLoop";
env.call_static_method(CLASS, "run", "()V", &[])?;
let method = NativeMethod {
name: "poll_all".into(),
sig: "()V".into(),
fn_ptr: poll_all as *mut _,
};
env.register_native_methods(CLASS, &[method])
}) {
error!(?e, "Run failed.");
}
}
#[cfg(target_os = "android")]
unsafe extern "C" fn poll_all(_env: JNIEnv) {
RT.with(|rt| rt.poll_all());
}
#[cfg(target_os = "android")]
pub fn vm_exec<F, R>(f: F) -> JniResult<R>
where
F: for<'a> FnOnce(AttachGuard<'a>) -> JniResult<R>,
{
JAVA_VM.with_borrow_mut(move |vm| match vm {
Ok(vm) => match vm.attach_current_thread() {
Ok(env) => f(env),
Err(e) => Err(e),
},
Err(e) => Err(match e {
Error::WrongJValueType(e1, e2) => Error::WrongJValueType(e1, e2),
Error::InvalidCtorReturn => Error::InvalidCtorReturn,
Error::InvalidArgList(e) => Error::InvalidArgList(e.to_owned()),
Error::MethodNotFound { name, sig } => Error::MethodNotFound {
name: name.to_owned(),
sig: sig.to_owned(),
},
Error::FieldNotFound { name, sig } => Error::FieldNotFound {
name: name.to_owned(),
sig: sig.to_owned(),
},
Error::JavaException => Error::JavaException,
Error::JNIEnvMethodNotFound(e) => Error::JNIEnvMethodNotFound(e),
Error::NullPtr(e) => Error::NullPtr(e),
Error::NullDeref(e) => Error::NullDeref(e),
Error::TryLock => Error::TryLock,
Error::JavaVMMethodNotFound(e) => Error::JavaVMMethodNotFound(e),
Error::FieldAlreadySet(e) => Error::FieldAlreadySet(e.to_owned()),
Error::ThrowFailed(e) => Error::ThrowFailed(e.to_owned()),
Error::ParseFailed(e1, e2) => Error::ParseFailed(e1.to_owned(), e2.to_owned()),
Error::JniCall(e) => Error::JniCall(match e {
JniError::Unknown => JniError::Unknown,
JniError::ThreadDetached => JniError::ThreadDetached,
JniError::WrongVersion => JniError::WrongVersion,
JniError::NoMemory => JniError::NoMemory,
JniError::AlreadyCreated => JniError::AlreadyCreated,
JniError::InvalidArguments => JniError::InvalidArguments,
JniError::Other(e) => JniError::Other(*e),
}),
}),
})
}