use std::{borrow::Borrow, f64, marker::PhantomData, mem};
use stdweb::{web::TypedArray, Array, Object, Reference, UnsafeTypedArray, Value};
use crate::{local::Position, objects::HasPosition, traits::TryInto, RoomName};
#[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
}
}
impl<'a> CostMatrixSet for LocalCostMatrix {
fn set_multi<D, B, P, V>(&mut self, data: D)
where
D: IntoIterator<Item = B>,
B: Borrow<(P, V)>,
P: HasLocalPosition,
V: Borrow<u8>,
{
let iter = data.into_iter();
for entry in iter {
let (pos, cost) = entry.borrow();
self.set(pos.x(), pos.y(), *cost.borrow());
}
}
}
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,
}
}
}
impl<'a> Into<MultiRoomCostResult<'a>> for CostMatrix<'a> {
fn into(self) -> MultiRoomCostResult<'a> {
MultiRoomCostResult::CostMatrix(self)
}
}
impl<'a> Into<SingleRoomCostResult<'a>> for CostMatrix<'a> {
fn into(self) -> SingleRoomCostResult<'a> {
SingleRoomCostResult::CostMatrix(self)
}
}
pub trait HasLocalPosition {
fn x(&self) -> u8;
fn y(&self) -> u8;
}
pub trait CostMatrixSet {
fn set<P, V>(&mut self, position: P, cost: V)
where
P: HasLocalPosition,
V: Borrow<u8>,
{
self.set_multi(&[(position, cost)])
}
fn set_multi<D, B, P, V>(&mut self, data: D)
where
D: IntoIterator<Item = B>,
B: Borrow<(P, V)>,
P: HasLocalPosition,
V: Borrow<u8>;
}
impl<'a> CostMatrixSet for CostMatrix<'a> {
fn set_multi<D, B, P, V>(&mut self, data: D)
where
D: IntoIterator<Item = B>,
B: Borrow<(P, V)>,
P: HasLocalPosition,
V: Borrow<u8>,
{
let iter = data.into_iter();
let (minimum_size, _maximum_size) = iter.size_hint();
let mut storage: Vec<u8> = Vec::with_capacity(minimum_size * 3);
for entry in iter {
let (pos, cost) = entry.borrow();
storage.push(pos.x());
storage.push(pos.y());
storage.push(*cost.borrow());
}
let bits: TypedArray<u8> = storage.as_slice().into();
js!(
let matrix = @{&self.inner};
let raw_data = @{bits};
const element_count = raw_data.length / 3;
for (let index = 0; index < element_count; ++index) {
const offset = index * 3;
matrix.set(raw_data[offset + 0], raw_data[offset + 1], raw_data[offset + 2]);
}
);
}
}
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 trait RoomCostResult: Into<Value> {}
pub enum MultiRoomCostResult<'a> {
CostMatrix(CostMatrix<'a>),
Impassable,
Default,
}
impl<'a> RoomCostResult for MultiRoomCostResult<'a> {}
impl<'a> Default for MultiRoomCostResult<'a> {
fn default() -> Self {
MultiRoomCostResult::Default
}
}
impl<'a> Into<Value> for MultiRoomCostResult<'a> {
fn into(self) -> Value {
match self {
MultiRoomCostResult::CostMatrix(m) => m.inner.into(),
MultiRoomCostResult::Impassable => Value::Bool(false),
MultiRoomCostResult::Default => Value::Undefined,
}
}
}
pub enum SingleRoomCostResult<'a> {
CostMatrix(CostMatrix<'a>),
Default,
}
impl<'a> RoomCostResult for SingleRoomCostResult<'a> {}
impl<'a> Default for SingleRoomCostResult<'a> {
fn default() -> Self {
SingleRoomCostResult::Default
}
}
impl<'a> Into<Value> for SingleRoomCostResult<'a> {
fn into(self) -> Value {
match self {
SingleRoomCostResult::CostMatrix(m) => m.inner.into(),
SingleRoomCostResult::Default => Value::Undefined,
}
}
}
pub struct SearchOptions<'a, F>
where
F: FnMut(RoomName) -> MultiRoomCostResult<'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(RoomName) -> MultiRoomCostResult<'static>> {
fn default() -> Self {
fn cost_matrix(_: RoomName) -> MultiRoomCostResult<'static> {
MultiRoomCostResult::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(RoomName) -> MultiRoomCostResult<'static>> {
#[inline]
pub fn new() -> Self {
Self::default()
}
}
impl<'a, F> SearchOptions<'a, F>
where
F: FnMut(RoomName) -> MultiRoomCostResult<'a>,
{
pub fn room_callback<'b, F2>(self, room_callback: F2) -> SearchOptions<'b, F2>
where
F2: FnMut(RoomName) -> MultiRoomCostResult<'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: FnMut(RoomName) -> MultiRoomCostResult<'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: FnMut(RoomName) -> MultiRoomCostResult<'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)
}
fn search_real<'a, F>(
origin: Position,
goal: &Reference,
opts: SearchOptions<'a, F>,
) -> SearchResults
where
F: FnMut(RoomName) -> MultiRoomCostResult<'a> + 'a,
{
let SearchOptions {
plain_cost,
swamp_cost,
flee,
max_ops,
max_rooms,
heuristic_weight,
max_cost,
..
} = opts;
let mut raw_callback = opts.room_callback;
let mut callback_boxed = move |room_name: RoomName| -> Value { raw_callback(room_name).into() };
let callback_type_erased: &mut (dyn FnMut(RoomName) -> Value + 'a) = &mut callback_boxed;
let callback_lifetime_erased: &'static mut dyn FnMut(RoomName) -> Value =
unsafe { mem::transmute(callback_type_erased) };
let res: ::stdweb::Reference = js!(
let cb = @{callback_lifetime_erased};
let res = PathFinder.search(pos_from_packed(@{origin.packed_repr()}), @{goal}, {
roomCallback: cb,
plainCost: @{plain_cost},
swampCost: @{swamp_cost},
flee: @{flee},
maxOps: @{max_ops},
maxRooms: @{max_rooms},
maxCost: @{max_cost},
heuristicWeight: @{heuristic_weight}
});
cb.drop();
return res;
)
.try_into()
.expect("expected reference from search");
SearchResults {
path: js_unwrap!(@{&res}.path),
ops: js_unwrap!(@{&res}.ops),
cost: js_unwrap!(@{&res}.cost),
incomplete: js_unwrap!(@{&res}.incomplete),
}
}