use std::fmt::Display;
use std::future::Future;
use napi::Error as NapiError;
use napi_derive::napi;
use serde_json::Value;
use tokio::runtime::Builder as RuntimeBuilder;
use crate::client::Client;
use crate::params::{MakeParams, ScorecardParams, StepParams};
fn block_on<F, T, E>(future: F) -> napi::Result<T>
where
F: Future<Output = Result<T, E>>,
E: Display,
{
RuntimeBuilder::new_current_thread()
.enable_all()
.build()
.map_err(|e| NapiError::from_reason(e.to_string()))?
.block_on(future)
.map_err(|e| NapiError::from_reason(e.to_string()))
}
#[napi(object)]
pub struct EnvironmentInfo {
pub game_id: String,
pub title: Option<String>,
pub default_fps: Option<u32>,
pub tags: Option<Vec<String>>,
}
#[napi(object)]
pub struct FrameData {
pub game_id: String,
pub guid: Option<String>,
pub state: String,
pub levels_completed: u32,
pub win_levels: u32,
pub available_actions: Vec<u32>,
pub full_reset: bool,
}
#[napi(object)]
pub struct EnvironmentScorecard {
pub card_id: String,
pub score: f64,
pub competition_mode: Option<bool>,
pub total_environments_completed: Option<u32>,
pub total_environments: Option<u32>,
pub total_levels_completed: Option<u32>,
pub total_levels: Option<u32>,
pub total_actions: Option<u32>,
}
#[napi(js_name = "ArcAgiClient")]
pub struct NapiArcAgiClient {
inner: Client,
}
#[napi]
impl NapiArcAgiClient {
#[napi(constructor)]
pub fn new(
api_key: Option<String>,
base_url: Option<String>,
cookie_store: Option<bool>,
proxy: Option<String>,
) -> napi::Result<Self> {
let mut builder = Client::builder();
if let Some(key) = api_key {
builder = builder.api_key(key);
}
if let Some(url) = base_url {
builder = builder.base_url(url);
}
if cookie_store.unwrap_or(false) {
builder = builder.cookie_store(true);
}
if let Some(proxy_url) = proxy {
builder = builder.proxy(proxy_url);
}
let inner = builder
.build()
.map_err(|e| NapiError::from_reason(e.to_string()))?;
Ok(Self { inner })
}
#[napi]
pub fn get_anonymous_key(&self) -> napi::Result<String> {
block_on(self.inner.get_anonymous_key())
}
#[napi]
pub fn list_environments(&self) -> napi::Result<Vec<EnvironmentInfo>> {
let envs = block_on(self.inner.list_environments())?;
Ok(envs
.into_iter()
.map(|e| EnvironmentInfo {
game_id: e.game_id,
title: e.title,
default_fps: e.default_fps,
tags: e.tags,
})
.collect())
}
#[napi]
pub fn get_environment(&self, game_id: String) -> napi::Result<EnvironmentInfo> {
let info = block_on(self.inner.get_environment(&game_id))?;
Ok(EnvironmentInfo {
game_id: info.game_id,
title: info.title,
default_fps: info.default_fps,
tags: info.tags,
})
}
#[napi]
pub fn open_scorecard(
&self,
source_url: Option<String>,
tags: Option<Vec<String>>,
competition_mode: Option<bool>,
) -> napi::Result<String> {
let mut params = ScorecardParams::new();
if let Some(url) = source_url {
params = params.source_url(url);
}
if let Some(t) = tags {
params = params.tags(t);
}
if let Some(cm) = competition_mode {
params = params.competition_mode(cm);
}
block_on(self.inner.open_scorecard(Some(params)))
}
#[napi]
pub fn get_scorecard(&self, card_id: String) -> napi::Result<EnvironmentScorecard> {
let card = block_on(self.inner.get_scorecard(&card_id))?;
Ok(EnvironmentScorecard {
card_id: card.card_id,
score: card.score,
competition_mode: card.competition_mode,
total_environments_completed: card.total_environments_completed,
total_environments: card.total_environments,
total_levels_completed: card.total_levels_completed,
total_levels: card.total_levels,
total_actions: card.total_actions,
})
}
#[napi]
pub fn close_scorecard(&self, card_id: String) -> napi::Result<EnvironmentScorecard> {
let card = block_on(self.inner.close_scorecard(&card_id))?;
Ok(EnvironmentScorecard {
card_id: card.card_id,
score: card.score,
competition_mode: card.competition_mode,
total_environments_completed: card.total_environments_completed,
total_environments: card.total_environments,
total_levels_completed: card.total_levels_completed,
total_levels: card.total_levels,
total_actions: card.total_actions,
})
}
#[napi]
pub fn reset(
&self,
game_id: String,
scorecard_id: String,
guid: Option<String>,
seed: Option<u32>,
) -> napi::Result<FrameData> {
let mut params = MakeParams::new(&game_id, &scorecard_id).seed(seed.unwrap_or(0));
if let Some(g) = guid {
params = params.guid(g);
}
let frame = block_on(self.inner.reset(params))?;
Ok(FrameData {
game_id: frame.game_id,
guid: frame.guid,
state: frame.state.as_str().to_string(),
levels_completed: frame.levels_completed,
win_levels: frame.win_levels,
available_actions: frame.available_actions,
full_reset: frame.full_reset,
})
}
#[napi]
pub fn step(
&self,
game_id: String,
scorecard_id: String,
guid: String,
action_id: u32,
data: Option<Value>,
reasoning: Option<Value>,
) -> napi::Result<FrameData> {
let mut params = StepParams::new(&game_id, &scorecard_id, &guid, action_id);
if let Some(d) = data {
params = params.data(d);
}
if let Some(r) = reasoning {
params = params.reasoning(r);
}
let frame = block_on(self.inner.step(params))?;
Ok(FrameData {
game_id: frame.game_id,
guid: frame.guid,
state: frame.state.as_str().to_string(),
levels_completed: frame.levels_completed,
win_levels: frame.win_levels,
available_actions: frame.available_actions,
full_reset: frame.full_reset,
})
}
}