#[cfg(feature = "native")]
use std::sync::Arc;
#[cfg(feature = "native")]
use anyhow::{anyhow, Context, Result};
#[cfg(feature = "native")]
use arora_bridge::{AccessRequestStream, Bridge, BridgeResult, DeviceInfo, InboundStream};
#[cfg(feature = "native")]
use arora_hal::Hal;
#[cfg(feature = "native")]
use arora_types::data::DataStore;
#[cfg(feature = "native")]
use arora_types::data::StateChange;
#[cfg(feature = "native")]
use futures::FutureExt;
#[cfg(feature = "native")]
use log::info;
#[cfg(feature = "native")]
use crate::operator::{serve_access_requests, Frontend};
#[cfg(feature = "native")]
use crate::Arora;
#[cfg(feature = "native")]
#[derive(Debug, Default, clap::Parser)]
pub struct DeviceCli {
pub groot: Option<std::path::PathBuf>,
}
#[cfg(feature = "native")]
pub async fn run() -> Result<()> {
Arora::builder().run().await
}
#[cfg(feature = "native")]
pub async fn run_with_hal(hal: Box<dyn Hal>) -> Result<()> {
Arora::builder().with_hal(hal).run().await
}
#[cfg(feature = "native")]
pub(crate) async fn local_ws_bridge() -> Result<Box<dyn Bridge>> {
let server = Arc::new(arora_bridge_ws::AroraWSServer::new(
arora_bridge_ws::ServerConfig::default(),
));
let bridge = arora_bridge_ws::bridge::WsBridge::new(server.clone()).await;
let listener = server
.bind()
.await
.map_err(|e| anyhow!("local bridge: {e}"))?;
let cancel = arora_bridge_ws::CancellationToken::new();
tokio::spawn({
let cancel = cancel.clone();
async move {
if let Err(e) = server.run_on(listener, cancel).await {
log::error!("local bridge server stopped: {e:?}");
}
}
});
info!("serving the local bridge on ws://127.0.0.1:9000");
Ok(Box::new(LocalBridge {
inner: bridge,
server: cancel,
}))
}
#[cfg(feature = "native")]
struct LocalBridge {
inner: arora_bridge_ws::bridge::WsBridge,
server: arora_bridge_ws::CancellationToken,
}
#[cfg(feature = "native")]
impl Drop for LocalBridge {
fn drop(&mut self) {
self.server.cancel();
}
}
#[cfg(feature = "native")]
#[async_trait::async_trait]
impl Bridge for LocalBridge {
fn take_inbound(&mut self) -> InboundStream {
self.inner.take_inbound()
}
fn try_send(&mut self, change: &StateChange) {
self.inner.try_send(change)
}
async fn get_device_info(&self) -> BridgeResult<Option<DeviceInfo>> {
self.inner.get_device_info().await
}
async fn update_device_info(
&self,
info: Option<DeviceInfo>,
) -> BridgeResult<Option<DeviceInfo>> {
self.inner.update_device_info(info).await
}
async fn device_id(&self) -> Option<String> {
self.inner.device_id().await
}
async fn access_requests(&self) -> AccessRequestStream {
self.inner.access_requests().await
}
}
#[cfg(feature = "native")]
pub async fn run_with(
hal: Box<dyn Hal>,
bridge: Box<dyn Bridge>,
store: Box<dyn DataStore>,
) -> Result<()> {
Arora::builder()
.with_hal(hal)
.with_bridge(bridge)
.with_data_store(store)
.run()
.await
}
#[cfg(feature = "native")]
pub async fn run_with_frontend(
hal: Box<dyn Hal>,
bridge: Box<dyn Bridge>,
store: Box<dyn DataStore>,
frontend: Frontend,
) -> Result<()> {
run_builder_with_frontend(
Arora::builder()
.with_hal(hal)
.with_bridge(bridge)
.with_data_store(store),
frontend,
)
.await
}
#[cfg(feature = "native")]
pub(crate) async fn run_builder_with_frontend(
mut builder: crate::AroraBuilder,
frontend: Frontend,
) -> Result<()> {
let Frontend {
operator, on_ready, ..
} = frontend;
let bridge = builder
.bridges
.first_mut()
.ok_or_else(|| anyhow!("no bridge injected"))?;
let info = bridge.get_device_info().await.ok().flatten();
let device_id = bridge.device_id().await;
let access_requests = bridge.access_requests().await;
let mut arora = builder.build().context("failed to build Arora")?;
on_ready(arora.store().subscribe(), info, device_id);
info!("engine started; native behavior-tree control nodes ready");
let serving = serve_access_requests(access_requests, operator).fuse();
futures::pin_mut!(serving);
info!("running — Ctrl-C to stop");
let run = arora.run(Arora::DEFAULT_STEP_PERIOD).fuse();
futures::pin_mut!(run);
loop {
futures::select_biased! {
result = run => return result.map_err(|e| anyhow!("runtime error: {e}")),
_ = serving => {}
}
}
}
#[cfg(feature = "native")]
pub(crate) fn select_frontend() -> Frontend {
#[cfg(feature = "tui")]
{
use std::io::IsTerminal;
if std::io::stdout().is_terminal() {
match crate::tui::tui_frontend() {
Ok(frontend) => return frontend,
Err(e) => eprintln!("arora: terminal UI unavailable ({e}); running headless"),
}
}
}
crate::operator::default_frontend()
}