oc_wasm_opencomputers/
gpu.rs

1//! Provides high-level access to the GPU APIs.
2
3use crate::common::{Dimension, Point, Rgb, Vector2};
4use crate::error::Error;
5use crate::helpers::Ignore;
6use core::fmt::{Debug, Formatter};
7use minicbor::Encode;
8use oc_wasm_futures::invoke::{component_method, Buffer};
9use oc_wasm_helpers::{error::NullAndStringOr, Lockable};
10use oc_wasm_safe::{
11	component::{Invoker, MethodCallError},
12	extref, Address,
13};
14
15/// The type name for GPU components.
16pub const TYPE: &str = "gpu";
17
18/// A GPU component.
19#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
20pub struct Gpu(Address);
21
22impl Gpu {
23	/// Creates a wrapper around a GPU.
24	///
25	/// The `address` parameter is the address of the GPU. It is not checked for correctness at
26	/// this time because network topology could change after this function returns; as such, each
27	/// usage of the value may fail instead.
28	#[must_use = "This function is only useful for its return value"]
29	pub fn new(address: Address) -> Self {
30		Self(address)
31	}
32
33	/// Returns the address of the GPU.
34	#[must_use = "This function is only useful for its return value"]
35	pub fn address(&self) -> &Address {
36		&self.0
37	}
38}
39
40impl<'a, B: 'a + Buffer> Lockable<'a, 'a, B> for Gpu {
41	type Locked = Locked<'a, B>;
42
43	fn lock(&self, invoker: &'a mut Invoker, buffer: &'a mut B) -> Self::Locked {
44		Locked {
45			address: self.0,
46			invoker,
47			buffer,
48		}
49	}
50}
51
52/// A GPU component on which methods can be invoked.
53///
54/// This type combines a GPU address, an [`Invoker`] that can be used to make method calls, and a
55/// scratch buffer used to perform CBOR encoding and decoding. A value of this type can be created
56/// by calling [`Gpu::lock`], and it can be dropped to return the borrow of the invoker and buffer
57/// to the caller so they can be reused for other purposes.
58///
59/// The `'a` lifetime is the lifetime of the invoker and the buffer. The `B` type is the type of
60/// scratch buffer to use.
61pub struct Locked<'a, B: Buffer> {
62	/// The component address.
63	address: Address,
64
65	/// The invoker.
66	invoker: &'a mut Invoker,
67
68	/// The buffer.
69	buffer: &'a mut B,
70}
71
72impl<'a, B: Buffer> Locked<'a, B> {
73	/// Binds the GPU to a screen.
74	///
75	/// If `reset` is `true`, then the screen is reset to its maximum resolution and colour depth,
76	/// and its colours are set to white on black. The text content, however, is not cleared.
77	///
78	/// # Errors
79	/// * [`BadComponent`](Error::BadComponent)
80	/// * [`BadScreen`](Error::BadScreen)
81	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
82	pub async fn bind(&mut self, screen: Address, reset: bool) -> Result<(), Error> {
83		let ret: NullAndStringOr<'_, Ignore> = component_method(
84			self.invoker,
85			self.buffer,
86			&self.address,
87			"bind",
88			Some(&(screen, reset)),
89		)
90		.await?;
91		match ret {
92			NullAndStringOr::Ok(_) => Ok(()),
93			NullAndStringOr::Err(_) => Err(Error::BadScreen),
94		}
95	}
96
97	/// Returns the address of the screen the GPU is bound to, or `None` if it is unbound.
98	///
99	/// # Errors
100	/// * [`BadComponent`](Error::BadComponent)
101	/// * [`BadScreen`](Error::BadScreen)
102	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
103	pub async fn get_screen(&mut self) -> Result<Option<Address>, Error> {
104		let ret: NullAndStringOr<'_, (Option<Address>,)> = component_method::<(), _, _>(
105			self.invoker,
106			self.buffer,
107			&self.address,
108			"getScreen",
109			None,
110		)
111		.await?;
112		Ok(match ret {
113			NullAndStringOr::Ok(ret) => ret.0,
114			NullAndStringOr::Err(_) => None,
115		})
116	}
117
118	/// Returns the background colour.
119	///
120	/// # Errors
121	/// * [`BadComponent`](Error::BadComponent)
122	/// * [`BadScreen`](Error::BadScreen)
123	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
124	pub async fn get_background(&mut self) -> Result<Colour, Error> {
125		self.get_colour("getBackground").await
126	}
127
128	/// Sets the background colour, returning the old colour.
129	///
130	/// # Errors
131	/// * [`BadComponent`](Error::BadComponent)
132	/// * [`BadPaletteIndex`](Error::BadPaletteIndex)
133	/// * [`BadScreen`](Error::BadScreen)
134	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
135	pub async fn set_background(
136		&mut self,
137		colour: Colour,
138	) -> Result<(Rgb, Option<PaletteIndex>), Error> {
139		self.set_colour(colour, "setBackground").await
140	}
141
142	/// Returns the foreground colour.
143	///
144	/// # Errors
145	/// * [`BadComponent`](Error::BadComponent)
146	/// * [`BadScreen`](Error::BadScreen)
147	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
148	pub async fn get_foreground(&mut self) -> Result<Colour, Error> {
149		self.get_colour("getForeground").await
150	}
151
152	/// Sets the foreground colour, returning the old colour.
153	///
154	/// # Errors
155	/// * [`BadComponent`](Error::BadComponent)
156	/// * [`BadPaletteIndex`](Error::BadPaletteIndex)
157	/// * [`BadScreen`](Error::BadScreen)
158	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
159	pub async fn set_foreground(
160		&mut self,
161		colour: Colour,
162	) -> Result<(Rgb, Option<PaletteIndex>), Error> {
163		self.set_colour(colour, "setForeground").await
164	}
165
166	/// Returns the colour at a palette index.
167	///
168	/// # Errors
169	/// * [`BadComponent`](Error::BadComponent)
170	/// * [`BadPaletteIndex`](Error::BadPaletteIndex)
171	/// * [`BadScreen`](Error::BadScreen)
172	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
173	pub async fn get_palette_colour(&mut self, index: PaletteIndex) -> Result<Rgb, Error> {
174		let ret: Result<NullAndStringOr<'_, (u32,)>, _> = component_method(
175			self.invoker,
176			self.buffer,
177			&self.address,
178			"getPaletteColor",
179			Some(&(index.0,)),
180		)
181		.await;
182		let ret = Self::map_bad_parameters(ret, Error::BadPaletteIndex)?;
183		let ret = Self::map_no_screen(ret)?;
184		Ok(Rgb(ret.0))
185	}
186
187	/// Sets the colour at a palette index, returning the old colour.
188	///
189	/// # Errors
190	/// * [`BadComponent`](Error::BadComponent)
191	/// * [`BadPaletteIndex`](Error::BadPaletteIndex)
192	/// * [`BadScreen`](Error::BadScreen)
193	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
194	pub async fn set_palette_colour(
195		&mut self,
196		index: PaletteIndex,
197		colour: Rgb,
198	) -> Result<Rgb, Error> {
199		let ret: Result<NullAndStringOr<'_, (u32,)>, _> = component_method(
200			self.invoker,
201			self.buffer,
202			&self.address,
203			"setPaletteColor",
204			Some(&(index.0, colour.0)),
205		)
206		.await;
207		let ret = Self::map_bad_parameters(ret, Error::BadPaletteIndex)?;
208		let ret = Self::map_no_screen(ret)?;
209		Ok(Rgb(ret.0))
210	}
211
212	/// Returns the maximum supported colour depth based on the tiers of the GPU and the bound
213	/// screen.
214	///
215	/// # Errors
216	/// * [`BadComponent`](Error::BadComponent)
217	/// * [`BadScreen`](Error::BadScreen)
218	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
219	pub async fn max_depth(&mut self) -> Result<u8, Error> {
220		let ret: NullAndStringOr<'_, (u8,)> = component_method::<(), _, _>(
221			self.invoker,
222			self.buffer,
223			&self.address,
224			"maxDepth",
225			None,
226		)
227		.await?;
228		let ret = Self::map_no_screen(ret)?;
229		Ok(ret.0)
230	}
231
232	/// Returns the colour depth currently in use.
233	///
234	/// # Errors
235	/// * [`BadComponent`](Error::BadComponent)
236	/// * [`BadScreen`](Error::BadScreen)
237	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
238	pub async fn get_depth(&mut self) -> Result<u8, Error> {
239		let ret: NullAndStringOr<'_, (u8,)> = component_method::<(), _, _>(
240			self.invoker,
241			self.buffer,
242			&self.address,
243			"getDepth",
244			None,
245		)
246		.await?;
247		let ret = Self::map_no_screen(ret)?;
248		Ok(ret.0)
249	}
250
251	/// Sets the colour depth.
252	///
253	/// # Errors
254	/// * [`BadComponent`](Error::BadComponent)
255	/// * [`BadDepth`](Error::BadDepth)
256	/// * [`BadScreen`](Error::BadScreen)
257	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
258	pub async fn set_depth(&mut self, depth: u8) -> Result<(), Error> {
259		let ret: Result<NullAndStringOr<'_, Ignore>, _> = component_method(
260			self.invoker,
261			self.buffer,
262			&self.address,
263			"setDepth",
264			Some(&(depth,)),
265		)
266		.await;
267		let ret = Self::map_bad_parameters(ret, Error::BadDepth)?;
268		Self::map_no_screen(ret)?;
269		Ok(())
270	}
271
272	/// Returns the maximum supported resolution based on the tiers of the GPU and the bound
273	/// screen.
274	///
275	/// # Errors
276	/// * [`BadComponent`](Error::BadComponent)
277	/// * [`BadScreen`](Error::BadScreen)
278	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
279	pub async fn max_resolution(&mut self) -> Result<Dimension, Error> {
280		self.get_dimension("maxResolution").await
281	}
282
283	/// Returns the resolution currently in use.
284	///
285	/// # Errors
286	/// * [`BadComponent`](Error::BadComponent)
287	/// * [`BadScreen`](Error::BadScreen)
288	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
289	pub async fn get_resolution(&mut self) -> Result<Dimension, Error> {
290		self.get_dimension("getResolution").await
291	}
292
293	/// Sets the screen resolution, returning whether or not it was changed from its previous
294	/// value.
295	///
296	/// # Errors
297	/// * [`BadComponent`](Error::BadComponent)
298	/// * [`BadCoordinate`](Error::BadCoordinate)
299	/// * [`BadScreen`](Error::BadScreen)
300	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
301	pub async fn set_resolution(&mut self, resolution: Dimension) -> Result<bool, Error> {
302		self.set_dimension("setResolution", resolution).await
303	}
304
305	/// Returns the viewport currently in use.
306	///
307	/// # Errors
308	/// * [`BadComponent`](Error::BadComponent)
309	/// * [`BadScreen`](Error::BadScreen)
310	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
311	pub async fn get_viewport(&mut self) -> Result<Dimension, Error> {
312		self.get_dimension("getViewport").await
313	}
314
315	/// Sets the viewport, returning whether or not it was changed from its previous value.
316	///
317	/// # Errors
318	/// * [`BadComponent`](Error::BadComponent)
319	/// * [`BadCoordinate`](Error::BadCoordinate)
320	/// * [`BadScreen`](Error::BadScreen)
321	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
322	pub async fn set_viewport(&mut self, resolution: Dimension) -> Result<bool, Error> {
323		self.set_dimension("setViewport", resolution).await
324	}
325
326	/// Returns the character and colours at a specific character cell.
327	///
328	/// It is possible for a Java string to contain an unpaired UTF-16 surrogate half. It is
329	/// possible (in Lua, at least) to place such a byte sequence into a character cell in a
330	/// screen. Should this method be called on such a character cell, a replacement character is
331	/// silently returned instead.
332	///
333	/// # Errors
334	/// * [`BadComponent`](Error::BadComponent)
335	/// * [`BadCoordinate`](Error::BadCoordinate)
336	/// * [`BadScreen`](Error::BadScreen)
337	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
338	pub async fn get(&mut self, point: Point) -> Result<CharacterCellContents, Error> {
339		type Return<'character> = (&'character str, u32, u32, Option<u32>, Option<u32>);
340		let ret: Result<NullAndStringOr<'_, Return<'_>>, _> = component_method(
341			self.invoker,
342			self.buffer,
343			&self.address,
344			"get",
345			Some(&(point.x, point.y)),
346		)
347		.await;
348		let ret = Self::map_bad_parameters(ret, Error::BadCoordinate)?;
349		let ret = Self::map_no_screen(ret)?;
350		if let Some(character) = ret.0.chars().next() {
351			Ok(CharacterCellContents {
352				character,
353				foreground: (Rgb(ret.1), ret.3.map(PaletteIndex)),
354				background: (Rgb(ret.2), ret.4.map(PaletteIndex)),
355			})
356		} else {
357			// A GPU’s get method never returns an empty string. Therefore, if we see one,
358			// we must not have been talking to a GPU.
359			Err(Error::BadComponent(oc_wasm_safe::error::Error::CborDecode))
360		}
361	}
362
363	/// Writes text to the screen.
364	///
365	/// # Errors
366	/// * [`BadComponent`](Error::BadComponent)
367	/// * [`BadScreen`](Error::BadScreen)
368	/// * [`NotEnoughEnergy`](Error::NotEnoughEnergy)
369	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
370	pub async fn set(
371		&mut self,
372		position: Point,
373		text: &str,
374		direction: TextDirection,
375	) -> Result<(), Error> {
376		#[derive(Encode)]
377		#[cbor(array)]
378		struct Params<'a> {
379			#[n(0)]
380			x: u32,
381			#[n(1)]
382			y: u32,
383			#[n(2)]
384			value: extref::String<'a>,
385			#[n(3)]
386			direction: TextDirection,
387		}
388		// SAFETY: component_method() both encodes and submits the CBOR in one go.
389		let text = unsafe { extref::String::new(text) };
390		let ret: NullAndStringOr<'_, Ignore> = component_method(
391			self.invoker,
392			self.buffer,
393			&self.address,
394			"set",
395			Some(&Params {
396				x: position.x,
397				y: position.y,
398				value: text,
399				direction,
400			}),
401		)
402		.await?;
403		match ret {
404			NullAndStringOr::Ok(_) => Ok(()),
405			NullAndStringOr::Err("no screen") => Err(Error::BadScreen),
406			NullAndStringOr::Err("not enough energy") => Err(Error::NotEnoughEnergy),
407			NullAndStringOr::Err(_) => {
408				Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
409			}
410		}
411	}
412
413	/// Copies data from one rectangle to another.
414	///
415	/// # Errors
416	/// * [`BadComponent`](Error::BadComponent)
417	/// * [`BadScreen`](Error::BadScreen)
418	/// * [`NotEnoughEnergy`](Error::NotEnoughEnergy)
419	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
420	pub async fn copy(
421		&mut self,
422		source: Point,
423		dimension: Dimension,
424		translation: Vector2,
425	) -> Result<(), Error> {
426		#[derive(Encode)]
427		#[cbor(array)]
428		struct Params {
429			#[n(0)]
430			x: u32,
431			#[n(1)]
432			y: u32,
433			#[n(2)]
434			width: u32,
435			#[n(3)]
436			height: u32,
437			#[n(4)]
438			tx: i32,
439			#[n(5)]
440			ty: i32,
441		}
442		let ret: NullAndStringOr<'_, Ignore> = component_method(
443			self.invoker,
444			self.buffer,
445			&self.address,
446			"copy",
447			Some(&Params {
448				x: source.x,
449				y: source.y,
450				width: dimension.width,
451				height: dimension.height,
452				tx: translation.x,
453				ty: translation.y,
454			}),
455		)
456		.await?;
457		match ret {
458			NullAndStringOr::Ok(_) => Ok(()),
459			NullAndStringOr::Err("no screen") => Err(Error::BadScreen),
460			NullAndStringOr::Err("not enough energy") => Err(Error::NotEnoughEnergy),
461			NullAndStringOr::Err(_) => {
462				Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
463			}
464		}
465	}
466
467	/// Fills a rectangle with a character.
468	///
469	/// # Errors
470	/// * [`BadComponent`](Error::BadComponent)
471	/// * [`BadScreen`](Error::BadScreen)
472	/// * [`NotEnoughEnergy`](Error::NotEnoughEnergy)
473	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
474	pub async fn fill(
475		&mut self,
476		target: Point,
477		dimension: Dimension,
478		character: char,
479	) -> Result<(), Error> {
480		#[derive(Encode)]
481		#[cbor(array)]
482		struct Params<'a> {
483			#[n(0)]
484			x: u32,
485			#[n(1)]
486			y: u32,
487			#[n(2)]
488			width: u32,
489			#[n(3)]
490			height: u32,
491			#[n(4)]
492			value: &'a str,
493		}
494		let mut character_buffer = [0_u8; 4];
495		let character = character.encode_utf8(&mut character_buffer);
496		let ret: Result<NullAndStringOr<'_, Ignore>, _> = component_method(
497			self.invoker,
498			self.buffer,
499			&self.address,
500			"fill",
501			Some(&Params {
502				x: target.x,
503				y: target.y,
504				width: dimension.width,
505				height: dimension.height,
506				value: character,
507			}),
508		)
509		.await;
510		match ret {
511			Ok(NullAndStringOr::Ok(_)) => Ok(()),
512			Ok(NullAndStringOr::Err("no screen")) => Err(Error::BadScreen),
513			Ok(NullAndStringOr::Err("not enough energy")) => Err(Error::NotEnoughEnergy),
514			Ok(NullAndStringOr::Err(_)) => {
515				Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
516			}
517			Err(
518				e @ (MethodCallError::Other(exception) | MethodCallError::BadParameters(exception)),
519			) => {
520				// BadParameters would be the sensible error if the character involves more than
521				// one UTF-16 code unit. However, OpenComputers throws unqualified Exception, not
522				// IllegalArgumentException, in this case, which maps to Other. Just in case they
523				// make it more sane later, check both! And for more certainty, check the message
524				// too.
525				const INVALID_FILL_VALUE: &str = "invalid fill value";
526				const ERROR_MESSAGE_BUFFER_SIZE: usize = INVALID_FILL_VALUE.len();
527				let mut message_buffer = [0_u8; ERROR_MESSAGE_BUFFER_SIZE];
528				match exception.message(&mut message_buffer) {
529					Ok(INVALID_FILL_VALUE) => Err(Error::BadFillCharacter),
530					_ => Err(Error::BadComponent(e.into())),
531				}
532			}
533			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
534			Err(e) => {
535				// Any other errors convert to BadComponent as usual.
536				Err(Error::BadComponent(e.into()))
537			}
538		}
539	}
540
541	/// Returns the foreground or background colour.
542	///
543	/// # Errors
544	/// * [`BadComponent`](Error::BadComponent)
545	/// * [`BadScreen`](Error::BadScreen)
546	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
547	#[allow(clippy::missing_panics_doc)] // Only encode() calls to a buffer which cannot fail.
548	async fn get_colour(&mut self, method: &str) -> Result<Colour, Error> {
549		let ret: NullAndStringOr<'_, (u32, bool)> =
550			component_method::<(), _, _>(self.invoker, self.buffer, &self.address, method, None)
551				.await?;
552		let ret = Self::map_no_screen(ret)?;
553		Ok(if ret.1 {
554			Colour::PaletteIndex(PaletteIndex(ret.0))
555		} else {
556			Colour::Rgb(Rgb(ret.0))
557		})
558	}
559
560	/// Sets the foreground or background colour, returning the old colour.
561	///
562	/// # Errors
563	/// * [`BadComponent`](Error::BadComponent)
564	/// * [`BadPaletteIndex`](Error::BadPaletteIndex)
565	/// * [`BadScreen`](Error::BadScreen)
566	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
567	#[allow(clippy::missing_panics_doc)] // Only encode() calls to a buffer which cannot fail.
568	async fn set_colour(
569		&mut self,
570		colour: Colour,
571		method: &str,
572	) -> Result<(Rgb, Option<PaletteIndex>), Error> {
573		let params: (u32, bool) = match colour {
574			Colour::Rgb(rgb) => (rgb.0, false),
575			Colour::PaletteIndex(pi) => (pi.0, true),
576		};
577		let ret: Result<NullAndStringOr<'_, (u32, Option<u32>)>, _> = component_method(
578			self.invoker,
579			self.buffer,
580			&self.address,
581			method,
582			Some(&params),
583		)
584		.await;
585		let ret = Self::map_bad_parameters(ret, Error::BadPaletteIndex)?;
586		let ret = Self::map_no_screen(ret)?;
587		Ok((Rgb(ret.0), ret.1.map(PaletteIndex)))
588	}
589
590	/// Returns the current or maximum resolution or the current viewport size.
591	///
592	/// # Errors
593	/// * [`BadComponent`](Error::BadComponent)
594	/// * [`BadScreen`](Error::BadScreen)
595	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
596	async fn get_dimension(&mut self, method: &str) -> Result<Dimension, Error> {
597		let ret: NullAndStringOr<'_, Dimension> =
598			component_method::<(), _, _>(self.invoker, self.buffer, &self.address, method, None)
599				.await?;
600		let ret = Self::map_no_screen(ret)?;
601		Ok(ret)
602	}
603
604	/// Sets the resolution or viewport size, returning whether or not it changed.
605	///
606	/// # Errors
607	/// * [`BadComponent`](Error::BadComponent)
608	/// * [`BadCoordinate`](Error::BadCoordinate)
609	/// * [`BadScreen`](Error::BadScreen)
610	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
611	async fn set_dimension(&mut self, method: &str, parameter: Dimension) -> Result<bool, Error> {
612		let ret: Result<NullAndStringOr<'_, (bool,)>, _> = component_method(
613			self.invoker,
614			self.buffer,
615			&self.address,
616			method,
617			Some(&parameter),
618		)
619		.await;
620		let ret = Self::map_bad_parameters(ret, Error::BadCoordinate)?;
621		let ret = Self::map_no_screen(ret)?;
622		Ok(ret.0)
623	}
624
625	/// Given a “raw” `Result` whose [`MethodCallError::BadParameters`] needs to map to one
626	/// specific [`Error`] value, with all others except [`MethodCallError::TooManyDescriptors`]
627	/// mapping to [`Error::BadComponent`], returns the “cooked” `Result` with the errors mapped
628	/// accordingly.
629	fn map_bad_parameters<T>(
630		x: Result<T, MethodCallError<'_>>,
631		bad_parameters: Error,
632	) -> Result<T, Error> {
633		x.map_err(|e| match e {
634			MethodCallError::BadParameters(_) => bad_parameters,
635			MethodCallError::TooManyDescriptors => Error::TooManyDescriptors,
636			e => Error::BadComponent(e.into()),
637		})
638	}
639
640	/// Given a `NullAndStringOr` from a function whose only expected null-and-string return is “no
641	/// screen”, maps that error to [`Error::BadScreen`], all other null-and-string errors to
642	/// [`Error::BadComponent`], and returns any success object unmodified.
643	fn map_no_screen<T>(x: NullAndStringOr<'_, T>) -> Result<T, Error> {
644		match x {
645			NullAndStringOr::Ok(x) => Ok(x),
646			NullAndStringOr::Err("no screen") => Err(Error::BadScreen),
647			NullAndStringOr::Err(_) => {
648				Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
649			}
650		}
651	}
652}
653
654impl<B: Buffer> Debug for Locked<'_, B> {
655	fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
656		Gpu::new(self.address).fmt(f)
657	}
658}
659
660/// A palette index.
661#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
662pub struct PaletteIndex(pub u32);
663
664/// The types of colours available on a GPU.
665#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
666pub enum Colour {
667	/// A 24-bit RGB value.
668	Rgb(Rgb),
669
670	/// A palette index.
671	PaletteIndex(PaletteIndex),
672}
673
674/// The full contents of a character cell.
675#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
676pub struct CharacterCellContents {
677	/// The character displayed at this position.
678	pub character: char,
679	/// The foreground colour.
680	pub foreground: (Rgb, Option<PaletteIndex>),
681	/// The background colour.
682	pub background: (Rgb, Option<PaletteIndex>),
683}
684
685/// The possible directions in which text can be written.
686#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
687pub enum TextDirection {
688	/// The text is written from left to write.
689	Horizontal,
690	/// The text is written from top to bottom.
691	Vertical,
692}
693
694impl<Context> Encode<Context> for TextDirection {
695	fn encode<W: minicbor::encode::Write>(
696		&self,
697		e: &mut minicbor::Encoder<W>,
698		_: &mut Context,
699	) -> Result<(), minicbor::encode::Error<W::Error>> {
700		e.bool(*self == Self::Vertical)?;
701		Ok(())
702	}
703}