use crate::common::{Dimension, Point, Rgb, Vector2};
use crate::error::Error;
use crate::helpers::Ignore;
use core::fmt::{Debug, Formatter};
use minicbor::Encode;
use oc_wasm_futures::invoke::{component_method, Buffer};
use oc_wasm_helpers::{error::NullAndStringOr, Lockable};
use oc_wasm_safe::{
component::{Invoker, MethodCallError},
extref, Address,
};
pub const TYPE: &str = "gpu";
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Gpu(Address);
impl Gpu {
#[must_use = "This function is only useful for its return value"]
pub fn new(address: Address) -> Self {
Self(address)
}
#[must_use = "This function is only useful for its return value"]
pub fn address(&self) -> &Address {
&self.0
}
}
impl<'a, B: 'a + Buffer> Lockable<'a, 'a, B> for Gpu {
type Locked = Locked<'a, B>;
fn lock(&self, invoker: &'a mut Invoker, buffer: &'a mut B) -> Self::Locked {
Locked {
address: self.0,
invoker,
buffer,
}
}
}
pub struct Locked<'a, B: Buffer> {
address: Address,
invoker: &'a mut Invoker,
buffer: &'a mut B,
}
impl<'a, B: Buffer> Locked<'a, B> {
pub async fn bind(&mut self, screen: Address, reset: bool) -> Result<(), Error> {
let ret: NullAndStringOr<'_, Ignore> = component_method(
self.invoker,
self.buffer,
&self.address,
"bind",
Some(&(screen, reset)),
)
.await?;
match ret {
NullAndStringOr::Ok(_) => Ok(()),
NullAndStringOr::Err(_) => Err(Error::BadScreen),
}
}
pub async fn get_screen(&mut self) -> Result<Option<Address>, Error> {
let ret: NullAndStringOr<'_, (Option<Address>,)> = component_method::<(), _, _>(
self.invoker,
self.buffer,
&self.address,
"getScreen",
None,
)
.await?;
Ok(match ret {
NullAndStringOr::Ok(ret) => ret.0,
NullAndStringOr::Err(_) => None,
})
}
pub async fn get_background(&mut self) -> Result<Colour, Error> {
self.get_colour("getBackground").await
}
pub async fn set_background(
&mut self,
colour: Colour,
) -> Result<(Rgb, Option<PaletteIndex>), Error> {
self.set_colour(colour, "setBackground").await
}
pub async fn get_foreground(&mut self) -> Result<Colour, Error> {
self.get_colour("getForeground").await
}
pub async fn set_foreground(
&mut self,
colour: Colour,
) -> Result<(Rgb, Option<PaletteIndex>), Error> {
self.set_colour(colour, "setForeground").await
}
pub async fn get_palette_colour(&mut self, index: PaletteIndex) -> Result<Rgb, Error> {
let ret: Result<NullAndStringOr<'_, (u32,)>, _> = component_method(
self.invoker,
self.buffer,
&self.address,
"getPaletteColor",
Some(&(index.0,)),
)
.await;
let ret = Self::map_bad_parameters(ret, Error::BadPaletteIndex)?;
let ret = Self::map_no_screen(ret)?;
Ok(Rgb(ret.0))
}
pub async fn set_palette_colour(
&mut self,
index: PaletteIndex,
colour: Rgb,
) -> Result<Rgb, Error> {
let ret: Result<NullAndStringOr<'_, (u32,)>, _> = component_method(
self.invoker,
self.buffer,
&self.address,
"setPaletteColor",
Some(&(index.0, colour.0)),
)
.await;
let ret = Self::map_bad_parameters(ret, Error::BadPaletteIndex)?;
let ret = Self::map_no_screen(ret)?;
Ok(Rgb(ret.0))
}
pub async fn max_depth(&mut self) -> Result<u8, Error> {
let ret: NullAndStringOr<'_, (u8,)> = component_method::<(), _, _>(
self.invoker,
self.buffer,
&self.address,
"maxDepth",
None,
)
.await?;
let ret = Self::map_no_screen(ret)?;
Ok(ret.0)
}
pub async fn get_depth(&mut self) -> Result<u8, Error> {
let ret: NullAndStringOr<'_, (u8,)> = component_method::<(), _, _>(
self.invoker,
self.buffer,
&self.address,
"getDepth",
None,
)
.await?;
let ret = Self::map_no_screen(ret)?;
Ok(ret.0)
}
pub async fn set_depth(&mut self, depth: u8) -> Result<(), Error> {
let ret: Result<NullAndStringOr<'_, Ignore>, _> = component_method(
self.invoker,
self.buffer,
&self.address,
"setDepth",
Some(&(depth,)),
)
.await;
let ret = Self::map_bad_parameters(ret, Error::BadDepth)?;
Self::map_no_screen(ret)?;
Ok(())
}
pub async fn max_resolution(&mut self) -> Result<Dimension, Error> {
self.get_dimension("maxResolution").await
}
pub async fn get_resolution(&mut self) -> Result<Dimension, Error> {
self.get_dimension("getResolution").await
}
pub async fn set_resolution(&mut self, resolution: Dimension) -> Result<bool, Error> {
self.set_dimension("setResolution", resolution).await
}
pub async fn get_viewport(&mut self) -> Result<Dimension, Error> {
self.get_dimension("getViewport").await
}
pub async fn set_viewport(&mut self, resolution: Dimension) -> Result<bool, Error> {
self.set_dimension("setViewport", resolution).await
}
pub async fn get(&mut self, point: Point) -> Result<CharacterCellContents, Error> {
type Return<'character> = (&'character str, u32, u32, Option<u32>, Option<u32>);
let ret: Result<NullAndStringOr<'_, Return<'_>>, _> = component_method(
self.invoker,
self.buffer,
&self.address,
"get",
Some(&(point.x, point.y)),
)
.await;
let ret = Self::map_bad_parameters(ret, Error::BadCoordinate)?;
let ret = Self::map_no_screen(ret)?;
if let Some(character) = ret.0.chars().next() {
Ok(CharacterCellContents {
character,
foreground: (Rgb(ret.1), ret.3.map(PaletteIndex)),
background: (Rgb(ret.2), ret.4.map(PaletteIndex)),
})
} else {
Err(Error::BadComponent(oc_wasm_safe::error::Error::CborDecode))
}
}
pub async fn set(
&mut self,
position: Point,
text: &str,
direction: TextDirection,
) -> Result<(), Error> {
#[derive(Encode)]
#[cbor(array)]
struct Params<'a> {
#[n(0)]
x: u32,
#[n(1)]
y: u32,
#[n(2)]
value: extref::String<'a>,
#[n(3)]
direction: TextDirection,
}
let text = unsafe { extref::String::new(text) };
let ret: NullAndStringOr<'_, Ignore> = component_method(
self.invoker,
self.buffer,
&self.address,
"set",
Some(&Params {
x: position.x,
y: position.y,
value: text,
direction,
}),
)
.await?;
match ret {
NullAndStringOr::Ok(_) => Ok(()),
NullAndStringOr::Err("no screen") => Err(Error::BadScreen),
NullAndStringOr::Err("not enough energy") => Err(Error::NotEnoughEnergy),
NullAndStringOr::Err(_) => {
Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
}
}
}
pub async fn copy(
&mut self,
source: Point,
dimension: Dimension,
translation: Vector2,
) -> Result<(), Error> {
#[derive(Encode)]
#[cbor(array)]
struct Params {
#[n(0)]
x: u32,
#[n(1)]
y: u32,
#[n(2)]
width: u32,
#[n(3)]
height: u32,
#[n(4)]
tx: i32,
#[n(5)]
ty: i32,
}
let ret: NullAndStringOr<'_, Ignore> = component_method(
self.invoker,
self.buffer,
&self.address,
"copy",
Some(&Params {
x: source.x,
y: source.y,
width: dimension.width,
height: dimension.height,
tx: translation.x,
ty: translation.y,
}),
)
.await?;
match ret {
NullAndStringOr::Ok(_) => Ok(()),
NullAndStringOr::Err("no screen") => Err(Error::BadScreen),
NullAndStringOr::Err("not enough energy") => Err(Error::NotEnoughEnergy),
NullAndStringOr::Err(_) => {
Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
}
}
}
pub async fn fill(
&mut self,
target: Point,
dimension: Dimension,
character: char,
) -> Result<(), Error> {
#[derive(Encode)]
#[cbor(array)]
struct Params<'a> {
#[n(0)]
x: u32,
#[n(1)]
y: u32,
#[n(2)]
width: u32,
#[n(3)]
height: u32,
#[n(4)]
value: &'a str,
}
let mut character_buffer = [0_u8; 4];
let character = character.encode_utf8(&mut character_buffer);
let ret: Result<NullAndStringOr<'_, Ignore>, _> = component_method(
self.invoker,
self.buffer,
&self.address,
"fill",
Some(&Params {
x: target.x,
y: target.y,
width: dimension.width,
height: dimension.height,
value: character,
}),
)
.await;
match ret {
Ok(NullAndStringOr::Ok(_)) => Ok(()),
Ok(NullAndStringOr::Err("no screen")) => Err(Error::BadScreen),
Ok(NullAndStringOr::Err("not enough energy")) => Err(Error::NotEnoughEnergy),
Ok(NullAndStringOr::Err(_)) => {
Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
}
Err(
e @ (MethodCallError::Other(exception) | MethodCallError::BadParameters(exception)),
) => {
const INVALID_FILL_VALUE: &str = "invalid fill value";
const ERROR_MESSAGE_BUFFER_SIZE: usize = INVALID_FILL_VALUE.len();
let mut message_buffer = [0_u8; ERROR_MESSAGE_BUFFER_SIZE];
match exception.message(&mut message_buffer) {
Ok(INVALID_FILL_VALUE) => Err(Error::BadFillCharacter),
_ => Err(Error::BadComponent(e.into())),
}
}
Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
Err(e) => {
Err(Error::BadComponent(e.into()))
}
}
}
#[allow(clippy::missing_panics_doc)] async fn get_colour(&mut self, method: &str) -> Result<Colour, Error> {
let ret: NullAndStringOr<'_, (u32, bool)> =
component_method::<(), _, _>(self.invoker, self.buffer, &self.address, method, None)
.await?;
let ret = Self::map_no_screen(ret)?;
Ok(if ret.1 {
Colour::PaletteIndex(PaletteIndex(ret.0))
} else {
Colour::Rgb(Rgb(ret.0))
})
}
#[allow(clippy::missing_panics_doc)] async fn set_colour(
&mut self,
colour: Colour,
method: &str,
) -> Result<(Rgb, Option<PaletteIndex>), Error> {
let params: (u32, bool) = match colour {
Colour::Rgb(rgb) => (rgb.0, false),
Colour::PaletteIndex(pi) => (pi.0, true),
};
let ret: Result<NullAndStringOr<'_, (u32, Option<u32>)>, _> = component_method(
self.invoker,
self.buffer,
&self.address,
method,
Some(¶ms),
)
.await;
let ret = Self::map_bad_parameters(ret, Error::BadPaletteIndex)?;
let ret = Self::map_no_screen(ret)?;
Ok((Rgb(ret.0), ret.1.map(PaletteIndex)))
}
async fn get_dimension(&mut self, method: &str) -> Result<Dimension, Error> {
let ret: NullAndStringOr<'_, Dimension> =
component_method::<(), _, _>(self.invoker, self.buffer, &self.address, method, None)
.await?;
let ret = Self::map_no_screen(ret)?;
Ok(ret)
}
async fn set_dimension(&mut self, method: &str, parameter: Dimension) -> Result<bool, Error> {
let ret: Result<NullAndStringOr<'_, (bool,)>, _> = component_method(
self.invoker,
self.buffer,
&self.address,
method,
Some(¶meter),
)
.await;
let ret = Self::map_bad_parameters(ret, Error::BadCoordinate)?;
let ret = Self::map_no_screen(ret)?;
Ok(ret.0)
}
fn map_bad_parameters<T>(
x: Result<T, MethodCallError<'_>>,
bad_parameters: Error,
) -> Result<T, Error> {
x.map_err(|e| match e {
MethodCallError::BadParameters(_) => bad_parameters,
MethodCallError::TooManyDescriptors => Error::TooManyDescriptors,
e => Error::BadComponent(e.into()),
})
}
fn map_no_screen<T>(x: NullAndStringOr<'_, T>) -> Result<T, Error> {
match x {
NullAndStringOr::Ok(x) => Ok(x),
NullAndStringOr::Err("no screen") => Err(Error::BadScreen),
NullAndStringOr::Err(_) => {
Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
}
}
}
}
impl<B: Buffer> Debug for Locked<'_, B> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
Gpu::new(self.address).fmt(f)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PaletteIndex(pub u32);
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Colour {
Rgb(Rgb),
PaletteIndex(PaletteIndex),
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CharacterCellContents {
pub character: char,
pub foreground: (Rgb, Option<PaletteIndex>),
pub background: (Rgb, Option<PaletteIndex>),
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TextDirection {
Horizontal,
Vertical,
}
impl<Context> Encode<Context> for TextDirection {
fn encode<W: minicbor::encode::Write>(
&self,
e: &mut minicbor::Encoder<W>,
_: &mut Context,
) -> Result<(), minicbor::encode::Error<W::Error>> {
e.bool(*self == Self::Vertical)?;
Ok(())
}
}