#[cfg(feature = "native")]
use std::sync::Arc;
#[cfg(feature = "native")]
use anyhow::{anyhow, Context, Result};
#[cfg(feature = "native")]
use arora_bridge::Bridge;
#[cfg(feature = "native")]
use arora_hal::Hal;
#[cfg(feature = "native")]
use arora_simple_data_store::SimpleDataStore;
#[cfg(feature = "native")]
use log::info;
#[cfg(feature = "native")]
use crate::operator::{serve_access_requests, Frontend};
#[cfg(feature = "native")]
use crate::runtime::Runtime;
#[cfg(feature = "native")]
use crate::Arora;
#[cfg(feature = "native")]
pub fn run() -> Result<()> {
run_with_hal(Arc::new(arora_hal::FakeHal::new()))
}
#[cfg(all(feature = "native", not(feature = "studio-bridge")))]
pub fn run_with_hal(hal: Arc<dyn Hal>) -> Result<()> {
run_with_bridge_builder(hal, SimpleDataStore::new(), || async {
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;
tokio::spawn(async move {
if let Err(e) = server.run(arora_bridge_ws::CancellationToken::new()).await {
log::error!("local bridge server stopped: {e:?}");
}
});
info!("serving the local bridge on ws://127.0.0.1:9000");
let bridge: Arc<dyn Bridge> = Arc::new(bridge);
Ok(bridge)
})
}
#[cfg(feature = "studio-bridge")]
pub fn run_with_hal(hal: Arc<dyn Hal>) -> Result<()> {
crate::studio::run_with_hal(hal)
}
#[cfg(feature = "native")]
pub fn run_with(hal: Arc<dyn Hal>, bridge: Arc<dyn Bridge>, store: SimpleDataStore) -> Result<()> {
run_with_bridge_builder(hal, store, move || async move { Ok(bridge) })
}
#[cfg(feature = "native")]
pub fn run_with_bridge_builder<F, Fut>(
hal: Arc<dyn Hal>,
store: SimpleDataStore,
make_bridge: F,
) -> Result<()>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<Arc<dyn Bridge>>>,
{
run_with_frontend(hal, store, select_frontend(), make_bridge)
}
#[cfg(feature = "native")]
pub fn run_with_frontend<F, Fut>(
hal: Arc<dyn Hal>,
store: SimpleDataStore,
frontend: Frontend,
make_bridge: F,
) -> Result<()>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<Arc<dyn Bridge>>>,
{
let Frontend { operator, on_ready } = frontend;
let tokio = tokio::runtime::Runtime::new().context("failed to start Tokio runtime")?;
let (mut runtime, bridge) = tokio.block_on(async {
let bridge = make_bridge().await.context("failed to build the bridge")?;
let arora = Arora::start().await.context("failed to start Arora")?;
let store: Arc<dyn arora_types::data::DataStore> = Arc::new(store);
let runtime = Runtime::with_io_in(arora, hal, bridge.clone(), store);
Ok::<_, anyhow::Error>((runtime, bridge))
})?;
let (info, device_id) = tokio.block_on(async {
let info = bridge.get_device_info().await.ok().flatten();
let device_id = bridge.device_id().await;
(info, device_id)
});
on_ready(runtime.telemetry(), info, device_id);
info!("engine started; native behavior-tree control nodes ready");
if let Some(path) = std::env::args().nth(1) {
let xml = std::fs::read_to_string(&path)
.with_context(|| format!("could not read Groot file {path}"))?;
runtime
.queue_groot_xml(&xml)
.map_err(|e| anyhow!("failed to queue behavior tree from {path}: {e}"))?;
info!("queued behavior tree from {path}");
}
tokio.spawn({
let bridge = bridge.clone();
async move {
let requests = bridge.access_requests().await;
serve_access_requests(requests, operator).await;
}
});
info!("running — Ctrl-C to stop");
runtime.run().map_err(|e| anyhow!("runtime error: {e}"))
}
#[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()
}