use std::future::Future;
use compio_log::{error, warn};
use futures_util::StreamExt;
use winio_elm::{Component, Root, RunEvent};
use crate::{sys, sys::App as SysApp};
#[cfg(feature = "compio-compat")]
type WinioRuntimeCompat = compio::compat::RuntimeCompat<sys::CompioAdapter>;
#[cfg(feature = "compio-compat")]
use compio::runtime::Runtime;
#[cfg(not(feature = "compio-compat"))]
mod compat_stub {
use std::io;
pub struct Runtime(());
impl Runtime {
#[inline]
pub fn new() -> io::Result<Self> {
Ok(Self(()))
}
}
pub struct WinioRuntimeCompat(());
impl WinioRuntimeCompat {
#[inline]
pub fn new(_runtime: Runtime) -> io::Result<Self> {
Ok(Self(()))
}
#[inline]
pub fn execute<F: Future>(&self, f: F) -> F {
f
}
}
}
#[cfg(not(feature = "compio-compat"))]
use compat_stub::*;
pub struct App {
app: SysApp,
name: String,
}
impl App {
pub fn builder() -> AppBuilder {
AppBuilder::default()
}
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Default)]
pub struct AppBuilder {
name: String,
#[cfg(target_os = "android")]
app: Option<android_activity::AndroidApp>,
}
impl AppBuilder {
pub fn name(mut self, name: impl AsRef<str>) -> Self {
self.name = name.as_ref().to_string();
self
}
#[cfg(target_os = "android")]
pub fn android_app(mut self, app: android_activity::AndroidApp) -> Self {
self.app = Some(app);
self
}
pub fn build(self) -> sys::Result<App> {
#[allow(unused_mut)]
let mut app = SysApp::new(
#[cfg(target_os = "android")]
self.app.ok_or(sys::Error::NoApp)?,
)?;
#[cfg(not(any(windows, target_vendor = "apple", target_os = "android")))]
app.set_app_id(&self.name)?;
Ok(App {
app,
name: self.name,
})
}
}
#[cfg(not(target_os = "android"))]
impl App {
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
self.block_on_with_runtime(Runtime::new().unwrap(), future)
}
pub fn block_on_with_runtime<F: Future>(&self, runtime: Runtime, future: F) -> F::Output {
let compat = WinioRuntimeCompat::new(runtime).unwrap();
let future = compat.execute(future);
if std::mem::size_of_val(&future) >= 2048 {
self.app.block_on(Box::pin(future))
} else {
self.app.block_on(future)
}
}
}
#[cfg(target_os = "android")]
impl App {
pub fn spawn<F: Future<Output = ()>>(
&self,
future: impl (FnOnce() -> F) + Sync + Send + 'static,
) {
self.spawn_with_runtime(|| Runtime::new().unwrap(), future);
}
pub fn spawn_with_runtime<F: Future<Output = ()>>(
&self,
runtime: impl (FnOnce() -> Runtime) + Sync + Send + 'static,
future: impl (FnOnce() -> F) + Sync + Send + 'static,
) {
self.app.block_on(move || {
let runtime = runtime();
let compat = WinioRuntimeCompat::new(runtime).unwrap();
async move { compat.execute(future()).await }
})
}
}
#[allow(async_fn_in_trait)]
pub trait ComponentExt: Component {
async fn run<'a>(init: impl Into<Self::Init<'a>>) -> Result<Self::Event, Self::Error>;
async fn run_until_event<'a>(
init: impl Into<Self::Init<'a>>,
) -> Result<Self::Event, Self::Error>;
}
impl<T: Component> ComponentExt for T {
async fn run<'a>(init: impl Into<Self::Init<'a>>) -> Result<Self::Event, Self::Error> {
let mut component = Root::<Self>::init(init).await?;
let stream = component.run();
let mut stream = std::pin::pin!(stream);
stream
.next()
.await
.expect("component exits unexpectedly")
.flatten()
}
async fn run_until_event<'a>(
init: impl Into<Self::Init<'a>>,
) -> Result<Self::Event, Self::Error> {
let mut component = Root::<Self>::init(init).await?;
let stream = component.run();
let mut stream = std::pin::pin!(stream);
while let Some(event) = stream.next().await {
match event {
RunEvent::Event(e) => return Ok(e),
RunEvent::UpdateErr(_e) => {
error!("Component update error: {_e:?}");
}
RunEvent::RenderErr(_e) => {
error!("Component render error: {_e:?}");
}
_ => {
warn!("Unrecognized event.");
}
}
}
unreachable!("component exits unexpectedly")
}
}