//! Provides high-level access to the redstone APIs.
use crate::common::{Colour, COLOURS};
use crate::error::Error;
use crate::helpers::Ignore;
use minicbor::{Decode, Encode};
use oc_wasm_futures::invoke::{component_method, Buffer};
use oc_wasm_helpers::{
sides::{Side, BLOCK_SIDES},
Lockable,
};
use oc_wasm_safe::{component::Invoker, Address};
/// The type name for redstone components.
pub const TYPE: &str = "redstone";
/// A redstone component.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Redstone(Address);
impl Redstone {
/// Creates a wrapper around a redstone block or card.
///
/// The `address` parameter is the address of the component. It is not checked for correctness
/// at this time because network topology could change after this function returns; as such,
/// each usage of the value may fail instead.
#[must_use = "This function is only useful for its return value"]
pub fn new(address: Address) -> Self {
Self(address)
}
/// Returns the address of the component.
#[must_use = "This function is only useful for its return value"]
pub fn address(&self) -> &Address {
&self.0
}
}
impl<'invoker, 'buffer, B: 'buffer + Buffer> Lockable<'invoker, 'buffer, B> for Redstone {
type Locked = Locked<'invoker, 'buffer, B>;
fn lock(&self, invoker: &'invoker mut Invoker, buffer: &'buffer mut B) -> Self::Locked {
Locked {
address: self.0,
invoker,
buffer,
}
}
}
/// A redstone component on which methods can be invoked.
///
/// This type combines a redstone block or card address, an [`Invoker`](Invoker) that can be used
/// to make method calls, and a scratch buffer used to perform CBOR encoding and decoding. A value
/// of this type can be created by calling [`Redstone::lock`](Redstone::lock), and it can be
/// dropped to return the borrow of the invoker and buffer to the caller so they can be reused for
/// other purposes.
///
/// The `'invoker` lifetime is the lifetime of the invoker. The `'buffer` lifetime is the lifetime
/// of the buffer. The `B` type is the type of scratch buffer to use.
pub struct Locked<'invoker, 'buffer, B: Buffer> {
/// The component address.
address: Address,
/// The invoker.
invoker: &'invoker mut Invoker,
/// The buffer.
buffer: &'buffer mut B,
}
impl<'invoker, 'buffer, B: Buffer> Locked<'invoker, 'buffer, B> {
/// Returns the signal strengths received on all six sides.
///
/// The returned array is indexed by side. For a redstone block, the indices should be
/// [absolute sides](crate::common::AbsoluteSide). For a redstone card in a computer, the
/// indices should be [relative sides](crate::common::RelativeSide).
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_input(&mut self) -> Result<[u8; BLOCK_SIDES], Error> {
self.get_vanilla("getInput").await
}
/// Returns the signal strengths received on a side.
///
/// For a redstone block, the `side` parameter must be an [absolute
/// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
/// must be a [relative side](crate::common::RelativeSide).
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_side_input(&mut self, side: impl Side) -> Result<u8, Error> {
self.get_side_vanilla("getInput", side.into()).await
}
/// Returns the bundled signal strengths received on all six sides.
///
/// The returned array is indexed by side. For a redstone block, the indices should be
/// [absolute sides](crate::common::AbsoluteSide). For a redstone card in a computer, the
/// indices should be [relative sides](crate::common::RelativeSide). Each element of the outer
/// array is itself an array indexed by colour.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_bundled_input(&mut self) -> Result<[[u8; COLOURS]; BLOCK_SIDES], Error> {
self.get_bundled("getBundledInput").await
}
/// Returns the bundled signal strengths received on a side.
///
/// For a redstone block, the `side` parameter must be an [absolute
/// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
/// must be a [relative side](crate::common::RelativeSide).
///
/// The returned array is indexed by colour.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_side_bundled_input(
&mut self,
side: impl Side,
) -> Result<[u8; COLOURS], Error> {
self.get_side_bundled("getBundledInput", side.into()).await
}
/// Returns the bundled signal strength received on a side on a single colour of wire.
///
/// For a redstone block, the `side` parameter must be an [absolute
/// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
/// must be a [relative side](crate::common::RelativeSide).
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_side_colour_bundled_input(
&mut self,
side: impl Side,
colour: Colour,
) -> Result<u8, Error> {
self.get_side_colour_bundled("getBundledInput", side.into(), colour.into())
.await
}
/// Returns the signal strengths emitted on all six sides.
///
/// The returned array is indexed by side. For a redstone block, the indices should be
/// [absolute sides](crate::common::AbsoluteSide). For a redstone card in a computer, the
/// indices should be [relative sides](crate::common::RelativeSide).
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_output(&mut self) -> Result<[u8; BLOCK_SIDES], Error> {
self.get_vanilla("getOutput").await
}
/// Returns the signal strength emitted on a side.
///
/// For a redstone block, the `side` parameter must be an [absolute
/// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
/// must be a [relative side](crate::common::RelativeSide).
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_side_output(&mut self, side: impl Side) -> Result<u8, Error> {
self.get_side_vanilla("getOutput", side.into()).await
}
/// Returns the bundled signal strengths emitted on all six sides.
///
/// The returned array is indexed by side. For a redstone block, the indices should be
/// [absolute sides](crate::common::AbsoluteSide). For a redstone card in a computer, the
/// indices should be [relative sides](crate::common::RelativeSide). Each element of the outer
/// array is itself an array indexed by colour.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_bundled_output(&mut self) -> Result<[[u8; COLOURS]; BLOCK_SIDES], Error> {
self.get_bundled("getBundledOutput").await
}
/// Returns the bundled signal strengths emitted on a side.
///
/// For a redstone block, the `side` parameter must be an [absolute
/// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
/// must be a [relative side](crate::common::RelativeSide).
///
/// The returned array is indexed by colour.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_side_bundled_output(
&mut self,
side: impl Side,
) -> Result<[u8; COLOURS], Error> {
self.get_side_bundled("getBundledOutput", side.into()).await
}
/// Returns the bundled signal strength emitted on a side on a single colour of wire.
///
/// For a redstone block, the `side` parameter must be an [absolute
/// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
/// must be a [relative side](crate::common::RelativeSide).
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_side_colour_bundled_output(
&mut self,
side: impl Side,
colour: Colour,
) -> Result<u8, Error> {
self.get_side_colour_bundled("getBundledOutput", side.into(), colour.into())
.await
}
/// Sets the signal strengths to emit on any subset of the sides.
///
/// The `levels` parameter contains an element for each side. For a redstone block, the indices
/// into this array are [absolute sides](crate::common::AbsoluteSide). For a redstone card in a
/// computer, the indices are [relative sides](crate::common::RelativeSide). Each element of
/// the array can be `Some` with the new signal strength to emit, or `None` to leave that side
/// unmodified.
///
/// The old signal levels, prior to modification, are returned.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn set_output(
&mut self,
levels: &[Option<u8>; BLOCK_SIDES],
) -> Result<[u8; BLOCK_SIDES], Error> {
let ret: (ArrayAsMap<u8, BLOCK_SIDES>,) = component_method(
self.invoker,
self.buffer,
&self.address,
"setOutput",
Some(&(ArrayAsMap(*levels),)),
)
.await?;
Ok(ret.0 .0)
}
/// Sets the signal strength to emit on a single side.
///
/// The `side` parameter selects the side to modify. For a redstone block, the value must be an
/// [absolute side](crate::common::AbsoluteSide). For a redstone card in a computer, the value
/// must be a [relative side](crate::common::RelativeSide).
///
/// The old signal level, prior to modification, is returned.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn set_side_output(&mut self, side: impl Side, level: u8) -> Result<u8, Error> {
let side: u8 = side.into();
let ret: (u8,) = component_method(
self.invoker,
self.buffer,
&self.address,
"setOutput",
Some(&(side, level)),
)
.await?;
Ok(ret.0)
}
/// Sets the bundled signal strengths to emit on any subset of wires on any subset of sides.
///
/// The `levels` parameter contains an element for each side. For a redstone block, the indices
/// into this array are [absolute sides](crate::common::AbsoluteSide). For a redstone card in a
/// computer, the indices are [relative sides](crate::common::RelativeSide). Each element of
/// the array is itself another array. The inner arrays are indexed by colour. Each element of
/// the inner array can be `Some` with the new signal strength to emit, or `None` to leave that
/// colour unmodified.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn set_bundled_output(
&mut self,
levels: &[[Option<u8>; COLOURS]; BLOCK_SIDES],
) -> Result<(), Error> {
let param: (ArrayAsMap<ArrayAsMap<Option<u8>, COLOURS>, BLOCK_SIDES>,) = (ArrayAsMap([
ArrayAsMap(levels[0]),
ArrayAsMap(levels[1]),
ArrayAsMap(levels[2]),
ArrayAsMap(levels[3]),
ArrayAsMap(levels[4]),
ArrayAsMap(levels[5]),
]),);
component_method::<_, Ignore, _>(
self.invoker,
self.buffer,
&self.address,
"setBundledOutput",
Some(¶m),
)
.await?;
Ok(())
}
/// Sets the bundled signal strengths to emit on any subset of wires on a single side.
///
/// The `side` parameter selects the side to modify. For a redstone block, the value must be an
/// [absolute side](crate::common::AbsoluteSide). For a redstone card in a computer, the value
/// must be a [relative side](crate::common::RelativeSide).
///
/// The `levels` parameter contains an element for each colour. Each element of `levels` can be
/// `Some` with the new signal strength to emit, or `None` to leave that colour unmodified.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn set_side_bundled_output(
&mut self,
side: impl Side,
levels: &[Option<u8>; COLOURS],
) -> Result<(), Error> {
let side: u8 = side.into();
component_method::<_, Ignore, _>(
self.invoker,
self.buffer,
&self.address,
"setBundledOutput",
Some(&(side, ArrayAsMap(*levels))),
)
.await?;
Ok(())
}
/// Sets the bundled signal strength to emit on a single wire on a single side.
///
/// The `side` parameter selects the side to modify. For a redstone block, the value must be an
/// [absolute side](crate::common::AbsoluteSide). For a redstone card in a computer, the value
/// must be a [relative side](crate::common::RelativeSide).
///
/// The old signal level, prior to modification, is returned.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn set_side_colour_bundled_output(
&mut self,
side: impl Side,
colour: Colour,
level: u8,
) -> Result<u8, Error> {
let side: u8 = side.into();
let colour: u8 = colour.into();
let ret: (u8,) = component_method(
self.invoker,
self.buffer,
&self.address,
"setBundledOutput",
Some(&(side, colour, level)),
)
.await?;
Ok(ret.0)
}
/// Returns the comparator value on a given side.
///
/// For a redstone block, the `side` parameter must be an [absolute
/// side](crate::common::AbsoluteSide). For a redstone card in a computer, the `side` parameter
/// must be a [relative side](crate::common::RelativeSide).
///
/// The returned value is the signal level that would be emitted by a comparator sensing the
/// adjacent block. If the target block is not readable by a comparator, zero is returned.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_comparator_input(&mut self, side: impl Side) -> Result<u8, Error> {
let side: u8 = side.into();
let ret: (u8,) = component_method(
self.invoker,
self.buffer,
&self.address,
"getComparatorInput",
Some(&(side,)),
)
.await?;
Ok(ret.0)
}
/// Returns the wake threshold.
///
/// When any redstone input changes from being strictly less than the threshold to greater than
/// or equal to the threshold, the containing computer (in the case of a redstone card) or all
/// connected computers (in the case of a redstone block) are powered on if they are off.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn get_wake_threshold(&mut self) -> Result<u32, Error> {
let ret: (u32,) = component_method::<(), _, _>(
self.invoker,
self.buffer,
&self.address,
"getWakeThreshold",
None,
)
.await?;
Ok(ret.0)
}
/// Sets the wake threshold.
///
/// When any redstone input changes from being strictly less than the threshold to greater than
/// or equal to the threshold, the containing computer (in the case of a redstone card) or all
/// connected computers (in the case of a redstone block) are powered on if they are off.
///
/// The old wake threshold, prior to modification, is returned.
///
/// # Errors
/// * [`BadComponent`](Error::BadComponent)
/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
pub async fn set_wake_threshold(&mut self, threshold: u32) -> Result<u32, Error> {
let ret: (u32,) = component_method(
self.invoker,
self.buffer,
&self.address,
"setWakeThreshold",
Some(&(threshold,)),
)
.await?;
Ok(ret.0)
}
async fn get_vanilla(&mut self, method: &str) -> Result<[u8; BLOCK_SIDES], Error> {
let ret: (ArrayAsMap<u8, BLOCK_SIDES>,) =
component_method::<(), _, _>(self.invoker, self.buffer, &self.address, method, None)
.await?;
Ok(ret.0 .0)
}
async fn get_side_vanilla(&mut self, method: &str, side: u8) -> Result<u8, Error> {
let ret: (u8,) = component_method(
self.invoker,
self.buffer,
&self.address,
method,
Some(&(side,)),
)
.await?;
Ok(ret.0)
}
async fn get_bundled(&mut self, method: &str) -> Result<[[u8; COLOURS]; BLOCK_SIDES], Error> {
let ret: (ArrayAsMap<ArrayAsMap<u8, COLOURS>, BLOCK_SIDES>,) =
component_method::<(), _, _>(self.invoker, self.buffer, &self.address, method, None)
.await?;
Ok([
ret.0 .0[0].0,
ret.0 .0[1].0,
ret.0 .0[2].0,
ret.0 .0[3].0,
ret.0 .0[4].0,
ret.0 .0[5].0,
])
}
async fn get_side_bundled(&mut self, method: &str, side: u8) -> Result<[u8; COLOURS], Error> {
let ret: (ArrayAsMap<u8, COLOURS>,) = component_method(
self.invoker,
self.buffer,
&self.address,
method,
Some(&(side,)),
)
.await?;
Ok(ret.0 .0)
}
async fn get_side_colour_bundled(
&mut self,
method: &str,
side: u8,
colour: u8,
) -> Result<u8, Error> {
let ret: (u8,) = component_method(
self.invoker,
self.buffer,
&self.address,
method,
Some(&(side, colour)),
)
.await?;
Ok(ret.0)
}
}
/// An array that is CBOR-encoded as a map keyed by array index, rather than a CBOR array.
///
/// To be decoded, the element type must implement [`Copy`](Copy), [`Decode`](minicbor::Decode) and
/// [`Default`](Default), and any elements not included in the CBOR-encoded map will be returned at
/// their default values.
///
/// To be encoded, the element type must be an [`Option`](Option) and the type contained therein
/// must implement [`Encode`](minicbor::Encode). `None` values are omitted from the map entirely,
/// while `Some` values are encoded as their contents, keyed by array position.
#[derive(Clone, Copy)]
struct ArrayAsMap<T, const LENGTH: usize>(pub [T; LENGTH]);
impl<'buffer, Context, T: Copy + Decode<'buffer, Context> + Default, const LENGTH: usize>
Decode<'buffer, Context> for ArrayAsMap<T, LENGTH>
{
fn decode(
d: &mut minicbor::Decoder<'buffer>,
context: &mut Context,
) -> Result<Self, minicbor::decode::Error> {
let mut ret = [T::default(); LENGTH];
// The CBOR fits in memory, so it must be <2³² elements.
#[allow(clippy::cast_possible_truncation)]
let length = d
.map()?
.ok_or_else(|| minicbor::decode::Error::message(""))? as usize;
for _ in 0..length {
let key = d.u32()?;
ret[key as usize] = d.decode_with(context)?;
}
Ok(Self(ret))
}
}
impl<Context, T: Encode<Context>, const LENGTH: usize> Encode<Context>
for ArrayAsMap<Option<T>, LENGTH>
{
fn encode<W: minicbor::encode::Write>(
&self,
e: &mut minicbor::Encoder<W>,
context: &mut Context,
) -> Result<(), minicbor::encode::Error<W::Error>> {
let count = self.0.iter().filter(|i| i.is_some()).count();
e.map(count as u64)?;
for i in 0..LENGTH {
if let Some(elt) = &self.0[i] {
e.u64(i as u64)?;
e.encode_with(elt, context)?;
}
}
Ok(())
}
}
impl<Context, T: Encode<Context>, const INNER_LENGTH: usize, const OUTER_LENGTH: usize>
Encode<Context> for ArrayAsMap<ArrayAsMap<Option<T>, INNER_LENGTH>, OUTER_LENGTH>
{
fn encode<W: minicbor::encode::Write>(
&self,
e: &mut minicbor::Encoder<W>,
context: &mut Context,
) -> Result<(), minicbor::encode::Error<W::Error>> {
e.map(OUTER_LENGTH as u64)?;
for i in 0..OUTER_LENGTH {
e.u64(i as u64)?;
e.encode_with(&self.0[i], context)?;
}
Ok(())
}
}
impl<T, const LENGTH: usize> Default for ArrayAsMap<T, LENGTH>
where
[T; LENGTH]: Default,
{
fn default() -> Self {
Self(Default::default())
}
}