oc_wasm_opencomputers/redstone.rs
1//! Provides high-level access to the redstone APIs.
2
3use crate::common::{Colour, COLOURS};
4use crate::error::Error;
5use crate::helpers::Ignore;
6use minicbor::{Decode, Encode};
7use oc_wasm_futures::invoke::{component_method, Buffer};
8use oc_wasm_helpers::{
9 sides::{Side, BLOCK_SIDES},
10 Lockable,
11};
12use oc_wasm_safe::{component::Invoker, Address};
13
14/// The type name for redstone components.
15pub const TYPE: &str = "redstone";
16
17/// A redstone component.
18#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
19pub struct Redstone(Address);
20
21impl Redstone {
22 /// Creates a wrapper around a redstone block or card.
23 ///
24 /// The `address` parameter is the address of the component. It is not checked for correctness
25 /// at this time because network topology could change after this function returns; as such,
26 /// each usage of the value may fail instead.
27 #[must_use = "This function is only useful for its return value"]
28 pub fn new(address: Address) -> Self {
29 Self(address)
30 }
31
32 /// Returns the address of the component.
33 #[must_use = "This function is only useful for its return value"]
34 pub fn address(&self) -> &Address {
35 &self.0
36 }
37}
38
39impl<'invoker, 'buffer, B: 'buffer + Buffer> Lockable<'invoker, 'buffer, B> for Redstone {
40 type Locked = Locked<'invoker, 'buffer, B>;
41
42 fn lock(&self, invoker: &'invoker mut Invoker, buffer: &'buffer mut B) -> Self::Locked {
43 Locked {
44 address: self.0,
45 invoker,
46 buffer,
47 }
48 }
49}
50
51/// A redstone component on which methods can be invoked.
52///
53/// This type combines a redstone block or card address, an [`Invoker`] that can be used to make
54/// method calls, and a scratch buffer used to perform CBOR encoding and decoding. A value of this
55/// type can be created by calling [`Redstone::lock`], and it can be dropped to return the borrow
56/// of the invoker and buffer to the caller so they can be reused for other purposes.
57///
58/// The `'invoker` lifetime is the lifetime of the invoker. The `'buffer` lifetime is the lifetime
59/// of the buffer. The `B` type is the type of scratch buffer to use.
60pub struct Locked<'invoker, 'buffer, B: Buffer> {
61 /// The component address.
62 address: Address,
63
64 /// The invoker.
65 invoker: &'invoker mut Invoker,
66
67 /// The buffer.
68 buffer: &'buffer mut B,
69}
70
71impl<'invoker, 'buffer, B: Buffer> Locked<'invoker, 'buffer, B> {
72 /// Returns the signal strengths received on all six sides.
73 ///
74 /// The returned array is indexed by side. For a redstone block, the indices should be
75 /// [absolute sides](crate::common::AbsoluteSide). For a redstone card in a computer, the
76 /// indices should be [relative sides](crate::common::RelativeSide).
77 ///
78 /// # Errors
79 /// * [`BadComponent`](Error::BadComponent)
80 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
81 pub async fn get_input(&mut self) -> Result<[u8; BLOCK_SIDES], Error> {
82 self.get_vanilla("getInput").await
83 }
84
85 /// Returns the signal strengths received on a side.
86 ///
87 /// For a redstone block, the `side` parameter must be an [absolute
88 /// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
89 /// must be a [relative side](crate::common::RelativeSide).
90 ///
91 /// # Errors
92 /// * [`BadComponent`](Error::BadComponent)
93 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
94 pub async fn get_side_input(&mut self, side: impl Side) -> Result<u8, Error> {
95 self.get_side_vanilla("getInput", side.into()).await
96 }
97
98 /// Returns the bundled signal strengths received on all six sides.
99 ///
100 /// The returned array is indexed by side. For a redstone block, the indices should be
101 /// [absolute sides](crate::common::AbsoluteSide). For a redstone card in a computer, the
102 /// indices should be [relative sides](crate::common::RelativeSide). Each element of the outer
103 /// array is itself an array indexed by colour.
104 ///
105 /// # Errors
106 /// * [`BadComponent`](Error::BadComponent)
107 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
108 pub async fn get_bundled_input(&mut self) -> Result<[[u8; COLOURS]; BLOCK_SIDES], Error> {
109 self.get_bundled("getBundledInput").await
110 }
111
112 /// Returns the bundled signal strengths received on a side.
113 ///
114 /// For a redstone block, the `side` parameter must be an [absolute
115 /// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
116 /// must be a [relative side](crate::common::RelativeSide).
117 ///
118 /// The returned array is indexed by colour.
119 ///
120 /// # Errors
121 /// * [`BadComponent`](Error::BadComponent)
122 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
123 pub async fn get_side_bundled_input(
124 &mut self,
125 side: impl Side,
126 ) -> Result<[u8; COLOURS], Error> {
127 self.get_side_bundled("getBundledInput", side.into()).await
128 }
129
130 /// Returns the bundled signal strength received on a side on a single colour of wire.
131 ///
132 /// For a redstone block, the `side` parameter must be an [absolute
133 /// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
134 /// must be a [relative side](crate::common::RelativeSide).
135 ///
136 /// # Errors
137 /// * [`BadComponent`](Error::BadComponent)
138 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
139 pub async fn get_side_colour_bundled_input(
140 &mut self,
141 side: impl Side,
142 colour: Colour,
143 ) -> Result<u8, Error> {
144 self.get_side_colour_bundled("getBundledInput", side.into(), colour.into())
145 .await
146 }
147
148 /// Returns the signal strengths emitted on all six sides.
149 ///
150 /// The returned array is indexed by side. For a redstone block, the indices should be
151 /// [absolute sides](crate::common::AbsoluteSide). For a redstone card in a computer, the
152 /// indices should be [relative sides](crate::common::RelativeSide).
153 ///
154 /// # Errors
155 /// * [`BadComponent`](Error::BadComponent)
156 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
157 pub async fn get_output(&mut self) -> Result<[u8; BLOCK_SIDES], Error> {
158 self.get_vanilla("getOutput").await
159 }
160
161 /// Returns the signal strength emitted on a side.
162 ///
163 /// For a redstone block, the `side` parameter must be an [absolute
164 /// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
165 /// must be a [relative side](crate::common::RelativeSide).
166 ///
167 /// # Errors
168 /// * [`BadComponent`](Error::BadComponent)
169 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
170 pub async fn get_side_output(&mut self, side: impl Side) -> Result<u8, Error> {
171 self.get_side_vanilla("getOutput", side.into()).await
172 }
173
174 /// Returns the bundled signal strengths emitted on all six sides.
175 ///
176 /// The returned array is indexed by side. For a redstone block, the indices should be
177 /// [absolute sides](crate::common::AbsoluteSide). For a redstone card in a computer, the
178 /// indices should be [relative sides](crate::common::RelativeSide). Each element of the outer
179 /// array is itself an array indexed by colour.
180 ///
181 /// # Errors
182 /// * [`BadComponent`](Error::BadComponent)
183 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
184 pub async fn get_bundled_output(&mut self) -> Result<[[u8; COLOURS]; BLOCK_SIDES], Error> {
185 self.get_bundled("getBundledOutput").await
186 }
187
188 /// Returns the bundled signal strengths emitted on a side.
189 ///
190 /// For a redstone block, the `side` parameter must be an [absolute
191 /// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
192 /// must be a [relative side](crate::common::RelativeSide).
193 ///
194 /// The returned array is indexed by colour.
195 ///
196 /// # Errors
197 /// * [`BadComponent`](Error::BadComponent)
198 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
199 pub async fn get_side_bundled_output(
200 &mut self,
201 side: impl Side,
202 ) -> Result<[u8; COLOURS], Error> {
203 self.get_side_bundled("getBundledOutput", side.into()).await
204 }
205
206 /// Returns the bundled signal strength emitted on a side on a single colour of wire.
207 ///
208 /// For a redstone block, the `side` parameter must be an [absolute
209 /// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
210 /// must be a [relative side](crate::common::RelativeSide).
211 ///
212 /// # Errors
213 /// * [`BadComponent`](Error::BadComponent)
214 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
215 pub async fn get_side_colour_bundled_output(
216 &mut self,
217 side: impl Side,
218 colour: Colour,
219 ) -> Result<u8, Error> {
220 self.get_side_colour_bundled("getBundledOutput", side.into(), colour.into())
221 .await
222 }
223
224 /// Sets the signal strengths to emit on any subset of the sides.
225 ///
226 /// The `levels` parameter contains an element for each side. For a redstone block, the indices
227 /// into this array are [absolute sides](crate::common::AbsoluteSide). For a redstone card in a
228 /// computer, the indices are [relative sides](crate::common::RelativeSide). Each element of
229 /// the array can be `Some` with the new signal strength to emit, or `None` to leave that side
230 /// unmodified.
231 ///
232 /// The old signal levels, prior to modification, are returned.
233 ///
234 /// # Errors
235 /// * [`BadComponent`](Error::BadComponent)
236 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
237 pub async fn set_output(
238 &mut self,
239 levels: &[Option<u8>; BLOCK_SIDES],
240 ) -> Result<[u8; BLOCK_SIDES], Error> {
241 let ret: (ArrayAsMap<u8, BLOCK_SIDES>,) = component_method(
242 self.invoker,
243 self.buffer,
244 &self.address,
245 "setOutput",
246 Some(&(ArrayAsMap(*levels),)),
247 )
248 .await?;
249 Ok(ret.0 .0)
250 }
251
252 /// Sets the signal strength to emit on a single side.
253 ///
254 /// The `side` parameter selects the side to modify. For a redstone block, the value must be an
255 /// [absolute side](crate::common::AbsoluteSide). For a redstone card in a computer, the value
256 /// must be a [relative side](crate::common::RelativeSide).
257 ///
258 /// The old signal level, prior to modification, is returned.
259 ///
260 /// # Errors
261 /// * [`BadComponent`](Error::BadComponent)
262 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
263 pub async fn set_side_output(&mut self, side: impl Side, level: u8) -> Result<u8, Error> {
264 let side: u8 = side.into();
265 let ret: (u8,) = component_method(
266 self.invoker,
267 self.buffer,
268 &self.address,
269 "setOutput",
270 Some(&(side, level)),
271 )
272 .await?;
273 Ok(ret.0)
274 }
275
276 /// Sets the bundled signal strengths to emit on any subset of wires on any subset of sides.
277 ///
278 /// The `levels` parameter contains an element for each side. For a redstone block, the indices
279 /// into this array are [absolute sides](crate::common::AbsoluteSide). For a redstone card in a
280 /// computer, the indices are [relative sides](crate::common::RelativeSide). Each element of
281 /// the array is itself another array. The inner arrays are indexed by colour. Each element of
282 /// the inner array can be `Some` with the new signal strength to emit, or `None` to leave that
283 /// colour unmodified.
284 ///
285 /// # Errors
286 /// * [`BadComponent`](Error::BadComponent)
287 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
288 pub async fn set_bundled_output(
289 &mut self,
290 levels: &[[Option<u8>; COLOURS]; BLOCK_SIDES],
291 ) -> Result<(), Error> {
292 let param: (ArrayAsMap<ArrayAsMap<Option<u8>, COLOURS>, BLOCK_SIDES>,) = (ArrayAsMap([
293 ArrayAsMap(levels[0]),
294 ArrayAsMap(levels[1]),
295 ArrayAsMap(levels[2]),
296 ArrayAsMap(levels[3]),
297 ArrayAsMap(levels[4]),
298 ArrayAsMap(levels[5]),
299 ]),);
300 component_method::<_, Ignore, _>(
301 self.invoker,
302 self.buffer,
303 &self.address,
304 "setBundledOutput",
305 Some(¶m),
306 )
307 .await?;
308 Ok(())
309 }
310
311 /// Sets the bundled signal strengths to emit on any subset of wires on a single side.
312 ///
313 /// The `side` parameter selects the side to modify. For a redstone block, the value must be an
314 /// [absolute side](crate::common::AbsoluteSide). For a redstone card in a computer, the value
315 /// must be a [relative side](crate::common::RelativeSide).
316 ///
317 /// The `levels` parameter contains an element for each colour. Each element of `levels` can be
318 /// `Some` with the new signal strength to emit, or `None` to leave that colour unmodified.
319 ///
320 /// # Errors
321 /// * [`BadComponent`](Error::BadComponent)
322 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
323 pub async fn set_side_bundled_output(
324 &mut self,
325 side: impl Side,
326 levels: &[Option<u8>; COLOURS],
327 ) -> Result<(), Error> {
328 let side: u8 = side.into();
329 component_method::<_, Ignore, _>(
330 self.invoker,
331 self.buffer,
332 &self.address,
333 "setBundledOutput",
334 Some(&(side, ArrayAsMap(*levels))),
335 )
336 .await?;
337 Ok(())
338 }
339
340 /// Sets the bundled signal strength to emit on a single wire on a single side.
341 ///
342 /// The `side` parameter selects the side to modify. For a redstone block, the value must be an
343 /// [absolute side](crate::common::AbsoluteSide). For a redstone card in a computer, the value
344 /// must be a [relative side](crate::common::RelativeSide).
345 ///
346 /// The old signal level, prior to modification, is returned.
347 ///
348 /// # Errors
349 /// * [`BadComponent`](Error::BadComponent)
350 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
351 pub async fn set_side_colour_bundled_output(
352 &mut self,
353 side: impl Side,
354 colour: Colour,
355 level: u8,
356 ) -> Result<u8, Error> {
357 let side: u8 = side.into();
358 let colour: u8 = colour.into();
359 let ret: (u8,) = component_method(
360 self.invoker,
361 self.buffer,
362 &self.address,
363 "setBundledOutput",
364 Some(&(side, colour, level)),
365 )
366 .await?;
367 Ok(ret.0)
368 }
369
370 /// Returns the comparator value on a given side.
371 ///
372 /// For a redstone block, the `side` parameter must be an [absolute
373 /// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
374 /// must be a [relative side](crate::common::RelativeSide).
375 ///
376 /// The returned value is the signal level that would be emitted by a comparator sensing the
377 /// adjacent block. If the target block is not readable by a comparator, zero is returned.
378 ///
379 /// # Errors
380 /// * [`BadComponent`](Error::BadComponent)
381 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
382 pub async fn get_comparator_input(&mut self, side: impl Side) -> Result<u8, Error> {
383 let side: u8 = side.into();
384 let ret: (u8,) = component_method(
385 self.invoker,
386 self.buffer,
387 &self.address,
388 "getComparatorInput",
389 Some(&(side,)),
390 )
391 .await?;
392 Ok(ret.0)
393 }
394
395 /// Returns the wake threshold.
396 ///
397 /// When any redstone input changes from being strictly less than the threshold to greater than
398 /// or equal to the threshold, the containing computer (in the case of a redstone card) or all
399 /// connected computers (in the case of a redstone block) are powered on if they are off.
400 ///
401 /// # Errors
402 /// * [`BadComponent`](Error::BadComponent)
403 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
404 pub async fn get_wake_threshold(&mut self) -> Result<u32, Error> {
405 let ret: (u32,) = component_method::<(), _, _>(
406 self.invoker,
407 self.buffer,
408 &self.address,
409 "getWakeThreshold",
410 None,
411 )
412 .await?;
413 Ok(ret.0)
414 }
415
416 /// Sets the wake threshold.
417 ///
418 /// When any redstone input changes from being strictly less than the threshold to greater than
419 /// or equal to the threshold, the containing computer (in the case of a redstone card) or all
420 /// connected computers (in the case of a redstone block) are powered on if they are off.
421 ///
422 /// The old wake threshold, prior to modification, is returned.
423 ///
424 /// # Errors
425 /// * [`BadComponent`](Error::BadComponent)
426 /// * [`TooManyDescriptors`](Error::TooManyDescriptors)
427 pub async fn set_wake_threshold(&mut self, threshold: u32) -> Result<u32, Error> {
428 let ret: (u32,) = component_method(
429 self.invoker,
430 self.buffer,
431 &self.address,
432 "setWakeThreshold",
433 Some(&(threshold,)),
434 )
435 .await?;
436 Ok(ret.0)
437 }
438
439 async fn get_vanilla(&mut self, method: &str) -> Result<[u8; BLOCK_SIDES], Error> {
440 let ret: (ArrayAsMap<u8, BLOCK_SIDES>,) =
441 component_method::<(), _, _>(self.invoker, self.buffer, &self.address, method, None)
442 .await?;
443 Ok(ret.0 .0)
444 }
445
446 async fn get_side_vanilla(&mut self, method: &str, side: u8) -> Result<u8, Error> {
447 let ret: (u8,) = component_method(
448 self.invoker,
449 self.buffer,
450 &self.address,
451 method,
452 Some(&(side,)),
453 )
454 .await?;
455 Ok(ret.0)
456 }
457
458 async fn get_bundled(&mut self, method: &str) -> Result<[[u8; COLOURS]; BLOCK_SIDES], Error> {
459 let ret: (ArrayAsMap<ArrayAsMap<u8, COLOURS>, BLOCK_SIDES>,) =
460 component_method::<(), _, _>(self.invoker, self.buffer, &self.address, method, None)
461 .await?;
462 Ok([
463 ret.0 .0[0].0,
464 ret.0 .0[1].0,
465 ret.0 .0[2].0,
466 ret.0 .0[3].0,
467 ret.0 .0[4].0,
468 ret.0 .0[5].0,
469 ])
470 }
471
472 async fn get_side_bundled(&mut self, method: &str, side: u8) -> Result<[u8; COLOURS], Error> {
473 let ret: (ArrayAsMap<u8, COLOURS>,) = component_method(
474 self.invoker,
475 self.buffer,
476 &self.address,
477 method,
478 Some(&(side,)),
479 )
480 .await?;
481 Ok(ret.0 .0)
482 }
483
484 async fn get_side_colour_bundled(
485 &mut self,
486 method: &str,
487 side: u8,
488 colour: u8,
489 ) -> Result<u8, Error> {
490 let ret: (u8,) = component_method(
491 self.invoker,
492 self.buffer,
493 &self.address,
494 method,
495 Some(&(side, colour)),
496 )
497 .await?;
498 Ok(ret.0)
499 }
500}
501
502/// An array that is CBOR-encoded as a map keyed by array index, rather than a CBOR array.
503///
504/// To be decoded, the element type must implement [`Copy`], [`Decode`](minicbor::Decode) and
505/// [`Default`], and any elements not included in the CBOR-encoded map will be returned at their
506/// default values.
507///
508/// To be encoded, the element type must be an [`Option`] and the type contained therein must
509/// implement [`Encode`](minicbor::Encode). `None` values are omitted from the map entirely, while
510/// `Some` values are encoded as their contents, keyed by array position.
511#[derive(Clone, Copy)]
512struct ArrayAsMap<T, const LENGTH: usize>(pub [T; LENGTH]);
513
514impl<'buffer, Context, T: Copy + Decode<'buffer, Context> + Default, const LENGTH: usize>
515 Decode<'buffer, Context> for ArrayAsMap<T, LENGTH>
516{
517 fn decode(
518 d: &mut minicbor::Decoder<'buffer>,
519 context: &mut Context,
520 ) -> Result<Self, minicbor::decode::Error> {
521 let mut ret = [T::default(); LENGTH];
522 // The CBOR fits in memory, so it must be <2³² elements.
523 #[allow(clippy::cast_possible_truncation)]
524 let length = d
525 .map()?
526 .ok_or_else(|| minicbor::decode::Error::message(""))? as usize;
527 for _ in 0..length {
528 let key = d.u32()?;
529 ret[key as usize] = d.decode_with(context)?;
530 }
531 Ok(Self(ret))
532 }
533}
534
535impl<Context, T: Encode<Context>, const LENGTH: usize> Encode<Context>
536 for ArrayAsMap<Option<T>, LENGTH>
537{
538 fn encode<W: minicbor::encode::Write>(
539 &self,
540 e: &mut minicbor::Encoder<W>,
541 context: &mut Context,
542 ) -> Result<(), minicbor::encode::Error<W::Error>> {
543 let count = self.0.iter().filter(|i| i.is_some()).count();
544 e.map(count as u64)?;
545 for i in 0..LENGTH {
546 if let Some(elt) = &self.0[i] {
547 e.u64(i as u64)?;
548 e.encode_with(elt, context)?;
549 }
550 }
551 Ok(())
552 }
553}
554
555impl<Context, T: Encode<Context>, const INNER_LENGTH: usize, const OUTER_LENGTH: usize>
556 Encode<Context> for ArrayAsMap<ArrayAsMap<Option<T>, INNER_LENGTH>, OUTER_LENGTH>
557{
558 fn encode<W: minicbor::encode::Write>(
559 &self,
560 e: &mut minicbor::Encoder<W>,
561 context: &mut Context,
562 ) -> Result<(), minicbor::encode::Error<W::Error>> {
563 e.map(OUTER_LENGTH as u64)?;
564 for i in 0..OUTER_LENGTH {
565 e.u64(i as u64)?;
566 e.encode_with(&self.0[i], context)?;
567 }
568 Ok(())
569 }
570}
571
572impl<T, const LENGTH: usize> Default for ArrayAsMap<T, LENGTH>
573where
574 [T; LENGTH]: Default,
575{
576 fn default() -> Self {
577 Self(Default::default())
578 }
579}