screeps/
js_collections.rs1use std::{fmt, marker::PhantomData, str::FromStr};
3
4use js_sys::{Array, JsString, Object};
5use wasm_bindgen::prelude::*;
6
7use crate::{game, local::RawObjectIdParseError, prelude::*};
8
9#[wasm_bindgen]
10extern "C" {
11 #[derive(Clone)]
12 #[wasm_bindgen(extends = Object)]
13 pub(crate) type ObjectExt;
14
15 #[wasm_bindgen(method, structural, indexing_setter)]
16 pub(crate) fn set(this: &ObjectExt, prop: &str, val: &JsValue);
17
18 #[wasm_bindgen(method, structural, indexing_setter)]
19 pub(crate) fn set_value(this: &ObjectExt, prop: &JsValue, val: &JsValue);
20
21 #[wasm_bindgen(method, structural, indexing_getter)]
22 pub(crate) fn get_value(this: &ObjectExt, prop: &JsValue) -> JsValue;
23}
24
25pub trait JsCollectionIntoValue {
26 fn into_value(self) -> JsValue;
27}
28
29pub trait JsCollectionFromValue {
30 fn from_value(val: JsValue) -> Self;
31}
32
33pub struct JsHashMap<K, V> {
36 map: Object,
37 _phantom: PhantomData<(K, V)>,
38}
39
40impl<K, V> JsHashMap<K, V>
41where
42 K: JsCollectionFromValue,
43{
44 pub fn keys(&self) -> impl Iterator<Item = K> {
45 let array = Object::keys(self.map.unchecked_ref());
46
47 OwnedArrayIter::new(array)
48 }
49}
50
51impl<K, V> JsHashMap<K, V>
52where
53 V: JsCollectionFromValue,
54{
55 pub fn values(&self) -> impl Iterator<Item = V> {
56 let array = Object::values(self.map.unchecked_ref());
57
58 OwnedArrayIter::new(array)
59 }
60}
61
62impl<K, V> JsHashMap<K, V>
63where
64 K: JsCollectionFromValue,
65 V: JsCollectionFromValue,
66{
67 pub fn entries(&self) -> impl Iterator<Item = (K, V)> {
68 let array = Object::entries(self.map.unchecked_ref());
69
70 OwnedArrayIter::new(array)
71 }
72}
73
74impl<K, V> JsHashMap<K, V>
75where
76 K: JsCollectionIntoValue,
77 V: JsCollectionFromValue,
78{
79 pub fn get(&self, key: K) -> Option<V> {
80 let key = key.into_value();
81 let val = JsCast::unchecked_ref::<ObjectExt>(&self.map).get_value(&key);
82 if val.is_null() || val.is_undefined() {
83 return None;
84 }
85 let val = V::from_value(val);
86
87 Some(val)
88 }
89}
90
91impl<K, V> JsHashMap<K, V>
92where
93 K: JsCollectionIntoValue,
94 V: JsCollectionIntoValue,
95{
96 pub fn set(&self, key: K, value: V) {
97 let key = key.into_value();
98 let value = value.into_value();
99 JsCast::unchecked_ref::<ObjectExt>(&self.map).set_value(&key, &value);
100 }
101}
102
103impl<K, V> From<Object> for JsHashMap<K, V> {
104 fn from(map: Object) -> Self {
105 Self {
106 map,
107 _phantom: Default::default(),
108 }
109 }
110}
111
112impl<K, V> From<JsValue> for JsHashMap<K, V> {
113 fn from(val: JsValue) -> Self {
114 Self {
115 map: val.into(),
116 _phantom: Default::default(),
117 }
118 }
119}
120
121#[derive(Debug, Clone)]
122pub struct OwnedArrayIter<T> {
123 range: std::ops::Range<u32>,
124 array: Array,
125 _phantom: PhantomData<T>,
126}
127
128impl<T> OwnedArrayIter<T> {
129 pub fn new(array: Array) -> Self {
130 OwnedArrayIter {
131 range: 0..array.length(),
132 array,
133 _phantom: Default::default(),
134 }
135 }
136}
137
138impl<T> std::iter::Iterator for OwnedArrayIter<T>
139where
140 T: JsCollectionFromValue,
141{
142 type Item = T;
143
144 fn next(&mut self) -> Option<Self::Item> {
145 let index = self.range.next()?;
146 let val = self.array.get(index);
147 let val = T::from_value(val);
148 Some(val)
149 }
150
151 #[inline]
152 fn size_hint(&self) -> (usize, Option<usize>) {
153 self.range.size_hint()
154 }
155}
156
157impl<T> std::iter::DoubleEndedIterator for OwnedArrayIter<T>
158where
159 T: JsCollectionFromValue,
160{
161 fn next_back(&mut self) -> Option<Self::Item> {
162 let index = self.range.next_back()?;
163 let val = self.array.get(index);
164 let val = T::from_value(val);
165 Some(val)
166 }
167}
168
169impl<T> std::iter::FusedIterator for OwnedArrayIter<T> where T: JsCollectionFromValue {}
170
171impl<T> std::iter::ExactSizeIterator for OwnedArrayIter<T> where T: JsCollectionFromValue {}
172
173pub struct JsObjectId<T> {
189 pub raw: JsString,
190 phantom: PhantomData<T>,
191}
192
193impl<T> Clone for JsObjectId<T> {
195 fn clone(&self) -> JsObjectId<T> {
196 JsObjectId {
197 raw: self.raw.clone(),
198 phantom: PhantomData,
199 }
200 }
201}
202impl<T> fmt::Debug for JsObjectId<T> {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 self.raw.fmt(f)
205 }
206}
207impl<T> PartialEq for JsObjectId<T> {
208 fn eq(&self, o: &JsObjectId<T>) -> bool {
209 self.raw.eq(&o.raw)
210 }
211}
212impl<T> Eq for JsObjectId<T> {}
213
214impl<T> FromStr for JsObjectId<T> {
215 type Err = RawObjectIdParseError;
216
217 fn from_str(s: &str) -> Result<Self, Self::Err> {
218 let js_string: JsString = s.into();
219 Ok(js_string.into())
220 }
221}
222
223impl<T> fmt::Display for JsObjectId<T> {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 String::from(&self.raw).fmt(f)
226 }
227}
228
229impl<T> JsObjectId<T> {
230 pub fn into_type<U>(self) -> JsObjectId<U> {
236 JsObjectId {
237 raw: self.raw,
238 phantom: PhantomData,
239 }
240 }
241
242 pub fn resolve(&self) -> Option<T>
250 where
251 T: MaybeHasId + JsCast,
252 {
253 game::get_object_by_js_id_typed(self)
254 }
255}
256
257impl<T> From<JsString> for JsObjectId<T> {
258 fn from(raw: JsString) -> Self {
259 JsObjectId {
260 raw,
261 phantom: PhantomData,
262 }
263 }
264}
265
266impl<T> From<JsObjectId<T>> for JsString {
267 fn from(id: JsObjectId<T>) -> Self {
268 id.raw
269 }
270}
271
272impl<T> From<JsObjectId<T>> for String {
273 fn from(id: JsObjectId<T>) -> Self {
274 id.to_string()
275 }
276}
277
278impl JsCollectionIntoValue for JsString {
282 fn into_value(self) -> JsValue {
283 self.unchecked_into()
284 }
285}
286
287impl JsCollectionFromValue for JsString {
288 fn from_value(val: JsValue) -> JsString {
289 val.unchecked_into()
290 }
291}
292
293impl JsCollectionIntoValue for String {
294 fn into_value(self) -> JsValue {
295 self.into()
296 }
297}
298
299impl JsCollectionFromValue for String {
300 fn from_value(val: JsValue) -> String {
301 let val: JsString = val.unchecked_into();
302
303 val.into()
304 }
305}
306
307impl JsCollectionIntoValue for u8 {
308 fn into_value(self) -> JsValue {
309 JsValue::from_f64(self as f64)
310 }
311}
312
313impl JsCollectionFromValue for u8 {
314 fn from_value(val: JsValue) -> u8 {
315 if let Some(val) = val.as_string() {
316 val.parse::<u8>().expect("expected parseable u8 string")
317 } else {
318 val.as_f64().expect("expected number value") as u8
319 }
320 }
321}
322
323impl JsCollectionIntoValue for u32 {
324 fn into_value(self) -> JsValue {
325 JsValue::from_f64(self as f64)
326 }
327}
328
329impl JsCollectionFromValue for u32 {
330 fn from_value(val: JsValue) -> u32 {
331 if let Some(val) = val.as_string() {
332 val.parse::<u32>().expect("expected parseable u32 string")
333 } else {
334 val.as_f64().expect("expected number value") as u32
335 }
336 }
337}
338
339impl<K, V> JsCollectionFromValue for (K, V)
340where
341 K: JsCollectionFromValue,
342 V: JsCollectionFromValue,
343{
344 fn from_value(val: JsValue) -> Self {
345 let val: &Array = val.dyn_ref().expect("expected tuple of length 2");
346 let k = K::from_value(val.get(0));
347 let v = V::from_value(val.get(1));
348 (k, v)
349 }
350}