use std::{f64, marker::PhantomData, mem};
use scoped_tls::scoped_thread_local;
use stdweb::{web::TypedArray, Array, Object, Reference, UnsafeTypedArray};
use crate::{local::Position, objects::HasPosition, traits::TryInto};
#[derive(Clone, Debug)]
pub struct LocalCostMatrix {
bits: Vec<u8>,
}
#[inline]
fn pos_as_idx(x: u8, y: u8) -> usize {
(x as usize) * 50 + (y as usize)
}
impl Default for LocalCostMatrix {
fn default() -> Self {
Self::new()
}
}
impl LocalCostMatrix {
#[inline]
pub fn new() -> Self {
LocalCostMatrix {
bits: vec![0; 2500],
}
}
#[inline]
pub fn set(&mut self, x: u8, y: u8, val: u8) {
self.bits[pos_as_idx(x, y)] = val;
}
#[inline]
pub fn get(&self, x: u8, y: u8) -> u8 {
self.bits[pos_as_idx(x, y)]
}
pub fn upload(&self) -> CostMatrix<'static> {
let bits: TypedArray<u8> = self.bits[..].into();
CostMatrix {
inner: (js! {
var matrix = Object.create(PathFinder.CostMatrix.prototype);
matrix._bits = @{bits};
return matrix;
})
.try_into()
.expect("expected function returning CostMatrix to return a Reference"),
lifetime: PhantomData,
}
}
pub unsafe fn as_uploaded<'a>(&'a self) -> CostMatrix<'a> {
let bits: UnsafeTypedArray<'_, u8> = UnsafeTypedArray::new(&self.bits);
CostMatrix {
inner: (js! {
var bits = @{bits};
var matrix = Object.create(PathFinder.CostMatrix.prototype);
matrix._bits = bits;
return matrix;
})
.try_into()
.expect("expected function returning CostMatrix to return a Reference"),
lifetime: PhantomData,
}
}
}
impl Into<Vec<u8>> for LocalCostMatrix {
#[inline]
fn into(self) -> Vec<u8> {
self.bits
}
}
pub struct CostMatrix<'a> {
pub(crate) inner: Reference,
pub(crate) lifetime: PhantomData<&'a ()>,
}
impl Default for CostMatrix<'static> {
fn default() -> Self {
CostMatrix {
inner: js_unwrap!(new PathFinder.CostMatrix()),
lifetime: PhantomData,
}
}
}
mod serde_impls {
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
use super::LocalCostMatrix;
impl Serialize for LocalCostMatrix {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.bits.serialize(s)
}
}
impl<'de> Deserialize<'de> for LocalCostMatrix {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bits: Vec<u8> = Vec::deserialize(deserializer)?;
if bits.len() != 2500 {
return Err(D::Error::invalid_length(
bits.len(),
&"a vec of length 2500",
));
}
Ok(LocalCostMatrix { bits })
}
}
}
pub struct SearchOptions<'a, F>
where
F: Fn(String) -> CostMatrix<'a>,
{
room_callback: F,
plain_cost: u8,
swamp_cost: u8,
flee: bool,
max_ops: u32,
max_rooms: u32,
max_cost: f64,
heuristic_weight: f64,
}
impl Default for SearchOptions<'static, fn(String) -> CostMatrix<'static>> {
fn default() -> Self {
fn cost_matrix(_: String) -> CostMatrix<'static> {
CostMatrix::default()
}
SearchOptions {
room_callback: cost_matrix,
plain_cost: 1,
swamp_cost: 5,
flee: false,
max_ops: 2000,
max_rooms: 16,
max_cost: f64::INFINITY,
heuristic_weight: 1.2,
}
}
}
impl SearchOptions<'static, fn(String) -> CostMatrix<'static>> {
#[inline]
pub fn new() -> Self {
Self::default()
}
}
impl<'a, F> SearchOptions<'a, F>
where
F: Fn(String) -> CostMatrix<'a>,
{
pub fn room_callback<'b, F2>(self, room_callback: F2) -> SearchOptions<'b, F2>
where
F2: Fn(String) -> CostMatrix<'b>,
{
let SearchOptions {
room_callback: _,
plain_cost,
swamp_cost,
flee,
max_ops,
max_rooms,
max_cost,
heuristic_weight,
} = self;
SearchOptions {
room_callback,
plain_cost,
swamp_cost,
flee,
max_ops,
max_rooms,
max_cost,
heuristic_weight,
}
}
#[inline]
pub fn plain_cost(mut self, cost: u8) -> Self {
self.plain_cost = cost;
self
}
#[inline]
pub fn swamp_cost(mut self, cost: u8) -> Self {
self.swamp_cost = cost;
self
}
#[inline]
pub fn flee(mut self, flee: bool) -> Self {
self.flee = flee;
self
}
#[inline]
pub fn max_ops(mut self, ops: u32) -> Self {
self.max_ops = ops;
self
}
#[inline]
pub fn max_rooms(mut self, rooms: u32) -> Self {
self.max_rooms = rooms;
self
}
#[inline]
pub fn max_cost(mut self, cost: f64) -> Self {
self.max_cost = cost;
self
}
#[inline]
pub fn heuristic_weight(mut self, weight: f64) -> Self {
self.heuristic_weight = weight;
self
}
}
pub struct SearchResults {
path: Array,
pub ops: u32,
pub cost: u32,
pub incomplete: bool,
}
impl SearchResults {
#[inline]
pub fn opaque_path(&self) -> &Array {
&self.path
}
pub fn load_local_path(&self) -> Vec<Position> {
self.path
.clone()
.try_into()
.expect("expected PathFinder.search path result to be an array of RoomPositions")
}
}
pub fn search<'a, O, G, F>(
origin: &O,
goal: &G,
range: u32,
opts: SearchOptions<'a, F>,
) -> SearchResults
where
O: ?Sized + HasPosition,
G: ?Sized + HasPosition,
F: Fn(String) -> CostMatrix<'a> + 'a,
{
let pos = goal.pos();
search_real(
origin.pos(),
&js_unwrap!({pos: pos_from_packed(@{pos.packed_repr()}), range: @{range}}),
opts,
)
}
pub fn search_many<'a, O, G, I, F>(origin: &O, goal: G, opts: SearchOptions<'a, F>) -> SearchResults
where
O: HasPosition,
G: IntoIterator<Item = (I, u32)>,
I: HasPosition,
F: Fn(String) -> CostMatrix<'a> + 'a,
{
let goals: Vec<Object> = goal
.into_iter()
.map(|(target, range)| {
let pos = target.pos();
js_unwrap!({pos: pos_from_packed(@{pos.packed_repr()}), range: @{range}})
})
.collect();
if goals.is_empty() {
return SearchResults {
cost: 0,
incomplete: true,
ops: 0,
path: js_unwrap!([]),
};
}
let goals_js: Reference = js_unwrap!(@{goals});
search_real(origin.pos(), &goals_js, opts)
}
scoped_thread_local!(static PF_CALLBACK: &'static dyn Fn(String) -> Reference);
fn search_real<'a, F>(
origin: Position,
goal: &Reference,
opts: SearchOptions<'a, F>,
) -> SearchResults
where
F: Fn(String) -> CostMatrix<'a> + 'a,
{
fn callback(input: String) -> Reference {
PF_CALLBACK.with(|callback| callback(input))
}
let raw_callback = opts.room_callback;
let callback_unboxed = move |input| raw_callback(input).inner;
let callback_type_erased: &(dyn Fn(String) -> Reference + 'a) = &callback_unboxed;
let callback_lifetime_erased: &'static dyn Fn(String) -> Reference =
unsafe { mem::transmute(callback_type_erased) };
let SearchOptions {
plain_cost,
swamp_cost,
flee,
max_ops,
max_rooms,
heuristic_weight,
max_cost,
..
} = opts;
PF_CALLBACK.set(&callback_lifetime_erased, || {
let res: ::stdweb::Reference = js_unwrap! {
PathFinder.search(pos_from_packed(@{origin.packed_repr()}), @{goal}, {
roomCallback: @{callback},
plainCost: @{plain_cost},
swampCost: @{swamp_cost},
flee: @{flee},
maxOps: @{max_ops},
maxRooms: @{max_rooms},
maxCost: @{max_cost},
heuristicWeight: @{heuristic_weight}
})
};
SearchResults {
path: js_unwrap!(@{&res}.path),
ops: js_unwrap!(@{&res}.ops),
cost: js_unwrap!(@{&res}.cost),
incomplete: js_unwrap!(@{&res}.incomplete),
}
})
}