use enum_iterator::Sequence;
use js_sys::{Array, JsString, Object};
use num_traits::*;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use crate::{
constants::{Direction, ErrorCode, ExitDirection},
local::RoomName,
objects::RoomTerrain,
prelude::*,
};
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = "map")]
type Map;
#[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = describeExits)]
fn describe_exits(room_name: &JsString) -> Object;
#[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = findExit)]
fn find_exit(from_room: &JsString, to_room: &JsString, options: &JsValue) -> i8;
#[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = findRoute)]
fn find_route(from_room: &JsString, to_room: &JsString, options: &JsValue) -> JsValue;
#[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = getRoomLinearDistance)]
fn get_room_linear_distance(room_1: &JsString, room_2: &JsString, continuous: bool) -> u32;
#[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = getRoomTerrain, catch)]
fn get_room_terrain(room_name: &JsString) -> Result<RoomTerrain, JsValue>;
#[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = getWorldSize)]
fn get_world_size() -> u32;
#[wasm_bindgen(js_namespace = ["Game"], js_class = "map", static_method_of = Map, js_name = getRoomStatus, catch)]
fn get_room_status(room_name: &JsString) -> Result<JsRoomStatusResult, JsValue>;
}
pub fn describe_exits(room_name: RoomName) -> JsHashMap<Direction, RoomName> {
let room_name = room_name.into();
Map::describe_exits(&room_name).into()
}
pub fn get_room_linear_distance(from_room: RoomName, to_room: RoomName, continuous: bool) -> u32 {
let from_room = from_room.into();
let to_room = to_room.into();
Map::get_room_linear_distance(&from_room, &to_room, continuous)
}
pub fn get_room_terrain(room_name: RoomName) -> Option<RoomTerrain> {
let name = room_name.into();
Map::get_room_terrain(&name).ok()
}
pub fn get_world_size() -> u32 {
Map::get_world_size()
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen]
pub type JsRoomStatusResult;
#[wasm_bindgen(method, getter = status)]
pub fn status(this: &JsRoomStatusResult) -> RoomStatus;
#[wasm_bindgen(method, getter = timestamp)]
pub fn timestamp(this: &JsRoomStatusResult) -> Option<f64>;
}
#[derive(Clone, Debug)]
pub struct RoomStatusResult {
status: RoomStatus,
timestamp: Option<f64>,
}
impl RoomStatusResult {
pub fn status(&self) -> RoomStatus {
self.status
}
pub fn timestamp(&self) -> Option<f64> {
self.timestamp
}
}
impl From<JsRoomStatusResult> for RoomStatusResult {
fn from(val: JsRoomStatusResult) -> Self {
RoomStatusResult {
status: val.status(),
timestamp: val.timestamp(),
}
}
}
#[wasm_bindgen]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Sequence, Deserialize, Serialize)]
pub enum RoomStatus {
Normal = "normal",
Closed = "closed",
Novice = "novice",
Respawn = "respawn",
}
pub fn get_room_status(room_name: RoomName) -> Option<RoomStatusResult> {
let name = room_name.into();
Map::get_room_status(&name).ok().map(RoomStatusResult::from)
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen]
pub type JsFindRouteOptions;
#[wasm_bindgen(method, setter = routeCallback)]
pub fn route_callback(
this: &JsFindRouteOptions,
callback: &Closure<dyn FnMut(JsString, JsString) -> f64>,
);
}
impl JsFindRouteOptions {
pub fn new() -> JsFindRouteOptions {
Object::new().unchecked_into()
}
}
impl Default for JsFindRouteOptions {
fn default() -> Self {
Self::new()
}
}
pub struct FindRouteOptions<F>
where
F: FnMut(RoomName, RoomName) -> f64,
{
route_callback: F,
}
impl<F> FindRouteOptions<F>
where
F: FnMut(RoomName, RoomName) -> f64,
{
pub(crate) fn into_js_options<R>(self, callback: impl Fn(&JsFindRouteOptions) -> R) -> R {
let mut raw_callback = self.route_callback;
let mut owned_callback = move |to_room: RoomName, from_room: RoomName| -> f64 {
raw_callback(to_room, from_room)
};
let callback_type_erased: &mut (dyn FnMut(RoomName, RoomName) -> f64) = &mut owned_callback;
let callback_lifetime_erased: &'static mut (dyn FnMut(RoomName, RoomName) -> f64) =
unsafe { std::mem::transmute(callback_type_erased) };
let boxed_callback = Box::new(move |to_room: JsString, from_room: JsString| -> f64 {
let to_room = to_room
.try_into()
.expect("expected 'to' room name in route callback");
let from_room = from_room
.try_into()
.expect("expected 'rom' room name in route callback");
callback_lifetime_erased(to_room, from_room)
}) as Box<dyn FnMut(JsString, JsString) -> f64>;
let closure = Closure::wrap(boxed_callback);
let js_options = JsFindRouteOptions::new();
js_options.route_callback(&closure);
callback(&js_options)
}
}
impl Default for FindRouteOptions<fn(RoomName, RoomName) -> f64> {
fn default() -> Self {
const fn room_cost(_to_room: RoomName, _from_room: RoomName) -> f64 {
1.0
}
FindRouteOptions {
route_callback: room_cost,
}
}
}
impl FindRouteOptions<fn(RoomName, RoomName) -> f64> {
#[inline]
pub fn new() -> Self {
Self::default()
}
}
impl<F> FindRouteOptions<F>
where
F: FnMut(RoomName, RoomName) -> f64,
{
pub fn room_callback<F2>(self, route_callback: F2) -> FindRouteOptions<F2>
where
F2: FnMut(RoomName, RoomName) -> f64,
{
let FindRouteOptions { route_callback: _ } = self;
FindRouteOptions { route_callback }
}
}
#[derive(Debug, Deserialize)]
pub struct RouteStep {
pub exit: ExitDirection,
pub room: RoomName,
}
pub fn find_route<F>(
from: RoomName,
to: RoomName,
options: Option<FindRouteOptions<F>>,
) -> Result<Vec<RouteStep>, ErrorCode>
where
F: FnMut(RoomName, RoomName) -> f64,
{
let from: JsString = from.into();
let to: JsString = to.into();
let result = if let Some(options) = options {
options.into_js_options(|js_options| Map::find_route(&from, &to, js_options))
} else {
Map::find_route(&from, &to, &JsValue::UNDEFINED)
};
if result.is_object() {
let result: &Array = result.unchecked_ref();
let steps: Vec<RouteStep> = result
.iter()
.map(|step| serde_wasm_bindgen::from_value(step).expect("expected route step"))
.collect();
Ok(steps)
} else {
Err(unsafe {
ErrorCode::try_result_from_jsvalue(&result)
.expect("expected return code for pathing failure")
.unwrap_err_unchecked()
})
}
}
pub fn find_exit<F>(
from: RoomName,
to: RoomName,
options: Option<FindRouteOptions<F>>,
) -> Result<ExitDirection, ErrorCode>
where
F: FnMut(RoomName, RoomName) -> f64,
{
let from: JsString = from.into();
let to: JsString = to.into();
let result = if let Some(options) = options {
options.into_js_options(|js_options| Map::find_exit(&from, &to, js_options))
} else {
Map::find_exit(&from, &to, &JsValue::UNDEFINED)
};
if result >= 0 {
Ok(ExitDirection::from_i8(result).expect("expected exit direction for pathing"))
} else {
Err(unsafe { ErrorCode::result_from_i8(result).unwrap_err_unchecked() })
}
}