Connection is now a trait

This commit is contained in:
Vinzenz Schroeter 2025-03-06 23:50:08 +01:00
parent 111f35b242
commit b691ef33f8
20 changed files with 296 additions and 231 deletions

View file

@ -24,7 +24,7 @@ use servicepoint::*;
fn main() {
// establish connection
let connection = Connection::open("172.23.42.29:2342")
let connection = connection::Udp::open("172.23.42.29:2342")
.expect("connection failed");
// clear screen content

View file

@ -31,7 +31,7 @@ fn main() {
cli.text.push("Hello, CCCB!".to_string());
}
let connection = Connection::open(&cli.destination)
let connection = connection::Udp::open(&cli.destination)
.expect("could not connect to display");
if cli.clear {

View file

@ -11,7 +11,7 @@ struct Cli {
fn main() {
let cli = Cli::parse();
let connection = Connection::open(cli.destination)
let connection = connection::Udp::open(cli.destination)
.expect("could not connect to display");
let mut pixels = Bitmap::max_sized();

View file

@ -3,6 +3,7 @@
use clap::Parser;
use rand::{distributions, Rng};
use servicepoint::*;
use std::net::UdpSocket;
use std::thread;
#[derive(Parser, Debug)]
@ -16,7 +17,7 @@ struct Cli {
fn main() {
let cli = Cli::parse();
let connection = Connection::open(&cli.destination)
let connection = connection::Udp::open(&cli.destination)
.expect("could not connect to display");
let mut field = make_random_field(cli.probability);

View file

@ -11,7 +11,7 @@ struct Cli {
}
fn main() {
let connection = Connection::open(Cli::parse().destination)
let connection = connection::Udp::open(Cli::parse().destination)
.expect("could not connect to display");
let mut pixels = Bitmap::max_sized();

View file

@ -4,6 +4,7 @@
use clap::Parser;
use rand::Rng;
use servicepoint::*;
use std::net::UdpSocket;
use std::time::Duration;
#[derive(Parser, Debug)]
@ -19,7 +20,7 @@ struct Cli {
fn main() {
let cli = Cli::parse();
let connection = Connection::open(cli.destination)
let connection = connection::Udp::open(cli.destination)
.expect("could not connect to display");
let wait_duration = Duration::from_millis(cli.wait_ms);

View file

@ -1,13 +1,13 @@
//! Example for how to use the WebSocket connection
use servicepoint::connection::Websocket;
use servicepoint::{
Bitmap, Command, CompressionCode, Connection, Grid, Origin,
};
fn main() {
let connection =
Connection::open_websocket("ws://localhost:8080".parse().unwrap())
.unwrap();
Websocket::open("ws://localhost:8080".parse().unwrap()).unwrap();
connection.send(Command::Clear).unwrap();

View file

@ -1,4 +1,5 @@
//! An example on how to modify the image on screen without knowing the current content.
use clap::Parser;
use servicepoint::*;
use std::thread;
@ -20,7 +21,7 @@ fn main() {
Duration::from_millis(cli.time / PIXEL_WIDTH as u64),
);
let connection = Connection::open(cli.destination)
let connection = connection::Udp::open(cli.destination)
.expect("could not connect to display");
let mut enabled_pixels = Bitmap::max_sized();

View file

@ -9,12 +9,12 @@ use rand::{
/// # Examples
///
/// ```
/// # use servicepoint::{Brightness, Command, Connection};
/// # use servicepoint::{Brightness, Command, Connection, connection};
/// let b = Brightness::MAX;
/// let val: u8 = b.into();
///
/// let b = Brightness::try_from(7).unwrap();
/// # let connection = Connection::Fake;
/// # let connection = connection::Fake;
/// let result = connection.send(Command::Brightness(b));
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]

View file

@ -8,12 +8,12 @@ use crate::ByteGrid;
/// # Examples
///
/// ```rust
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Connection, Grid, Origin};
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Connection, Grid, Origin, connection};
/// let mut grid = BrightnessGrid::new(2,2);
/// grid.set(0, 0, Brightness::MIN);
/// grid.set(1, 1, Brightness::MIN);
///
/// # let connection = Connection::Fake;
/// # let connection = connection::Fake;
/// connection.send(Command::CharBrightness(Origin::new(3, 7), grid)).unwrap()
/// ```
pub type BrightnessGrid = ValueGrid<Brightness>;

View file

@ -10,11 +10,11 @@ use std::string::FromUtf8Error;
/// # Examples
///
/// ```rust
/// # use servicepoint::{CharGrid, Command, Connection, Origin};
/// # use servicepoint::{connection, CharGrid, Command, Connection, Origin};
/// let grid = CharGrid::from("You can\nload multiline\nstrings directly");
/// assert_eq!(grid.get_row_str(1), Some("load multiline\0\0".to_string()));
///
/// # let connection = Connection::Fake;
/// # let connection = connection::Fake;
/// let command = Command::Utf8Data(Origin::ZERO, grid);
/// ```
pub type CharGrid = ValueGrid<char>;

View file

@ -37,7 +37,7 @@ pub type Offset = usize;
/// # Examples
///
/// ```rust
/// use servicepoint::{Brightness, Command, Connection, Packet};
/// use servicepoint::{connection, Brightness, Command, Connection, Packet};
/// #
/// // create command
/// let command = Command::Brightness(Brightness::MAX);
@ -52,7 +52,7 @@ pub type Offset = usize;
/// assert_eq!(command, round_tripped);
///
/// // send command
/// # let connection = Connection::Fake;
/// # let connection = connection::Fake;
/// connection.send(command).unwrap();
/// ```
#[derive(Debug, Clone, PartialEq)]
@ -62,8 +62,8 @@ pub enum Command {
/// # Examples
///
/// ```rust
/// # use servicepoint::{Command, Connection};
/// # let connection = Connection::Fake;
/// # use servicepoint::{connection, Command, Connection};
/// # let connection = connection::Fake;
/// connection.send(Command::Clear).unwrap();
/// ```
Clear,
@ -75,8 +75,8 @@ pub enum Command {
/// # Examples
///
/// ```rust
/// # use servicepoint::{Command, Connection, Origin, CharGrid};
/// # let connection = Connection::Fake;
/// # use servicepoint::*;
/// # let connection = connection::Fake;
/// let grid = CharGrid::from("Hello,\nWorld!");
/// connection.send(Command::Utf8Data(Origin::ZERO, grid)).expect("send failed");
/// ```
@ -91,16 +91,16 @@ pub enum Command {
/// # Examples
///
/// ```rust
/// # use servicepoint::{Command, Connection, Origin, CharGrid, Cp437Grid};
/// # let connection = Connection::Fake;
/// # use servicepoint::{Command, Connection, Origin, CharGrid, Cp437Grid, connection};
/// # let connection = connection::Fake;
/// let grid = CharGrid::from("Hello,\nWorld!");
/// let grid = Cp437Grid::from(&grid);
/// connection.send(Command::Cp437Data(Origin::ZERO, grid)).expect("send failed");
/// ```
///
/// ```rust
/// # use servicepoint::{Command, Connection, Cp437Grid, Origin};
/// # let connection = Connection::Fake;
/// # use servicepoint::{connection, Command, Connection, Cp437Grid, Origin};
/// # let connection = connection::Fake;
/// let grid = Cp437Grid::load_ascii("Hello\nWorld", 5, false).unwrap();
/// connection.send(Command::Cp437Data(Origin::new(2, 2), grid)).unwrap();
/// ```
@ -114,8 +114,8 @@ pub enum Command {
/// # Examples
///
/// ```rust
/// # use servicepoint::{Command, CompressionCode, Grid, Bitmap};
/// # let connection = servicepoint::Connection::Fake;
/// # use servicepoint::{Command, CompressionCode, Grid, Bitmap, Connection};
/// # let connection = servicepoint::connection::Fake;
/// #
/// let mut pixels = Bitmap::max_sized();
/// // draw something to the pixels here
@ -137,8 +137,8 @@ pub enum Command {
/// # Examples
///
/// ```rust
/// # use servicepoint::{Brightness, Command, Connection};
/// # let connection = Connection::Fake;
/// # use servicepoint::{connection, Brightness, Command, Connection};
/// # let connection = connection::Fake;
/// let command = Command::Brightness(Brightness::MAX);
/// connection.send(command).unwrap();
/// ```
@ -186,8 +186,8 @@ pub enum Command {
/// # Examples
///
/// ```rust
/// # use servicepoint::{Command, Connection};
/// # let connection = Connection::Fake;
/// # use servicepoint::{connection, Command, Connection};
/// # let connection = connection::Fake;
/// connection.send(Command::HardReset).unwrap();
/// ```
HardReset,
@ -199,8 +199,8 @@ pub enum Command {
/// # Examples
///
/// ```rust
/// # use servicepoint::{Command, Connection};
/// # let connection = Connection::Fake;
/// # use servicepoint::{connection, Command, Connection};
/// # let connection = connection::Fake;
/// connection.send(Command::FadeOut).unwrap();
/// ```
FadeOut,
@ -212,8 +212,8 @@ pub enum Command {
/// # Examples
///
/// ```rust
/// # use servicepoint::{Command, Connection};
/// # let connection = Connection::Fake;
/// # use servicepoint::{connection, Command, Connection};
/// # let connection = connection::Fake;
/// // this sends a packet that does nothing
/// # #[allow(deprecated)]
/// connection.send(Command::BitmapLegacy).unwrap();

View file

@ -1,175 +0,0 @@
use crate::packet::Packet;
use std::fmt::Debug;
/// A connection to the display.
///
/// Used to send [Packets][Packet] or [Commands][crate::Command].
///
/// # Examples
/// ```rust
/// let connection = servicepoint::Connection::open("127.0.0.1:2342")
/// .expect("connection failed");
/// connection.send(servicepoint::Command::Clear)
/// .expect("send failed");
/// ```
#[derive(Debug)]
pub enum Connection {
/// A connection using the UDP protocol.
///
/// Use this when sending commands directly to the display.
///
/// Requires the feature "protocol_udp" which is enabled by default.
#[cfg(feature = "protocol_udp")]
Udp(std::net::UdpSocket),
/// A connection using the WebSocket protocol.
///
/// Note that you will need to forward the WebSocket messages via UDP to the display.
/// You can use [servicepoint-websocket-relay] for this.
///
/// To create a new WebSocket automatically, use [Connection::open_websocket].
///
/// Requires the feature "protocol_websocket" which is disabled by default.
///
/// [servicepoint-websocket-relay]: https://github.com/kaesaecracker/servicepoint-websocket-relay
#[cfg(feature = "protocol_websocket")]
WebSocket(
std::sync::Mutex<
tungstenite::WebSocket<
tungstenite::stream::MaybeTlsStream<std::net::TcpStream>,
>,
>,
),
/// A fake connection for testing that does not actually send anything.
Fake,
}
#[derive(Debug, thiserror::Error)]
pub enum SendError {
#[error("IO error occurred while sending")]
IoError(#[from] std::io::Error),
#[cfg(feature = "protocol_websocket")]
#[error("WebSocket error occurred while sending")]
WebsocketError(#[from] tungstenite::Error),
}
impl Connection {
/// Open a new UDP socket and connect to the provided host.
///
/// Note that this is UDP, which means that the open call can succeed even if the display is unreachable.
///
/// The address of the display in CCCB is `172.23.42.29:2342`.
///
/// # Errors
///
/// Any errors resulting from binding the udp socket.
///
/// # Examples
/// ```rust
/// let connection = servicepoint::Connection::open("127.0.0.1:2342")
/// .expect("connection failed");
/// ```
#[cfg(feature = "protocol_udp")]
pub fn open(
addr: impl std::net::ToSocketAddrs + Debug,
) -> std::io::Result<Self> {
log::info!("connecting to {addr:?}");
let socket = std::net::UdpSocket::bind("0.0.0.0:0")?;
socket.connect(addr)?;
Ok(Self::Udp(socket))
}
/// Open a new WebSocket and connect to the provided host.
///
/// Requires the feature "protocol_websocket" which is disabled by default.
///
/// # Examples
///
/// ```no_run
/// use tungstenite::http::Uri;
/// use servicepoint::{Command, Connection};
/// let uri = "ws://localhost:8080".parse().unwrap();
/// let mut connection = Connection::open_websocket(uri)
/// .expect("could not connect");
/// connection.send(Command::Clear)
/// .expect("send failed");
/// ```
#[cfg(feature = "protocol_websocket")]
pub fn open_websocket(
uri: tungstenite::http::Uri,
) -> tungstenite::Result<Self> {
use tungstenite::{
client::IntoClientRequest, connect, ClientRequestBuilder,
};
log::info!("connecting to {uri:?}");
let request = ClientRequestBuilder::new(uri).into_client_request()?;
let (sock, _) = connect(request)?;
Ok(Self::WebSocket(std::sync::Mutex::new(sock)))
}
/// Send something packet-like to the display. Usually this is in the form of a Command.
///
/// # Arguments
///
/// - `packet`: the packet-like to send
///
/// returns: true if packet was sent, otherwise false
///
/// # Examples
///
/// ```rust
/// let connection = servicepoint::Connection::Fake;
/// // turn off all pixels on display
/// connection.send(servicepoint::Command::Clear)
/// .expect("send failed");
/// ```
pub fn send(&self, packet: impl Into<Packet>) -> Result<(), SendError> {
let packet = packet.into();
log::debug!("sending {packet:?}");
let data: Vec<u8> = packet.into();
match self {
#[cfg(feature = "protocol_udp")]
Connection::Udp(socket) => {
socket
.send(&data)
.map_err(SendError::IoError)
.map(move |_| ()) // ignore Ok value
}
#[cfg(feature = "protocol_websocket")]
Connection::WebSocket(socket) => {
let mut socket = socket.lock().unwrap();
socket
.send(tungstenite::Message::Binary(data.into()))
.map_err(SendError::WebsocketError)
}
Connection::Fake => {
let _ = data;
Ok(())
}
}
}
}
impl Drop for Connection {
fn drop(&mut self) {
#[cfg(feature = "protocol_websocket")]
if let Connection::WebSocket(sock) = self {
_ = sock.try_lock().map(move |mut sock| sock.close(None));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn send_fake() {
let data: &[u8] = &[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let packet = Packet::try_from(data).unwrap();
Connection::Fake.send(packet).unwrap()
}
}

30
src/connection/fake.rs Normal file
View file

@ -0,0 +1,30 @@
use crate::{Connection, Packet};
use log::debug;
#[derive(Debug)]
/// A fake connection for testing that does not actually send anything.
pub struct Fake;
impl Connection for Fake {
// TODO: () does not implement Error+Debug, some placeholder is needed
type Error = std::io::Error;
fn send(&self, packet: impl Into<Packet>) -> Result<(), Self::Error> {
let data: Vec<u8> = packet.into().into();
debug!("Sending fake packet: {data:?}");
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Packet;
#[test]
fn send_fake() {
let data: &[u8] = &[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let packet = Packet::try_from(data).unwrap();
Fake.send(packet).unwrap()
}
}

53
src/connection/mod.rs Normal file
View file

@ -0,0 +1,53 @@
//! This module contains the [Connection] trait and all implementations provided in this library.
use crate::Packet;
use std::error::Error;
use std::fmt::Debug;
mod fake;
#[cfg(feature = "protocol_udp")]
mod udp;
#[cfg(feature = "protocol_websocket")]
mod websocket;
pub use fake::*;
#[cfg(feature = "protocol_udp")]
pub use udp::*;
#[cfg(feature = "protocol_websocket")]
pub use websocket::*;
/// A connection to the display.
///
/// Used to send [Packets][Packet] or [Commands][crate::Command].
///
/// # Examples
/// ```rust
/// # use servicepoint::Connection;
/// let connection = servicepoint::connection::Udp::open("127.0.0.1:2342")
/// .expect("connection failed");
/// connection.send(servicepoint::Command::Clear)
/// .expect("send failed");
/// ```
pub trait Connection: Debug {
/// The error that can occur when sending a packet
type Error: Error + Debug;
/// Send something packet-like to the display. Usually this is in the form of a Command.
///
/// # Arguments
///
/// - `packet`: the packet-like to send
///
/// returns: true if packet was sent, otherwise false
///
/// # Examples
///
/// ```rust
/// # use servicepoint::connection::Connection;
/// let connection = servicepoint::connection::Fake;
/// // turn off all pixels on display
/// connection.send(servicepoint::Command::Clear)
/// .expect("send failed");
/// ```
fn send(&self, packet: impl Into<Packet>) -> Result<(), Self::Error>;
}

54
src/connection/udp.rs Normal file
View file

@ -0,0 +1,54 @@
use crate::{Connection, Packet};
use std::fmt::Debug;
use std::net::UdpSocket;
/// A connection using the UDP protocol.
///
/// Use this when sending commands directly to the display.
///
/// Requires the feature "protocol_udp" which is enabled by default.
#[derive(Debug)]
pub struct Udp {
socket: UdpSocket,
}
impl Udp {
/// Open a new UDP socket and connect to the provided host.
///
/// Note that this is UDP, which means that the open call can succeed even if the display is unreachable.
///
/// The address of the display in CCCB is `172.23.42.29:2342`.
///
/// # Errors
///
/// Any errors resulting from binding the udp socket.
///
/// # Examples
/// ```rust
/// let connection = servicepoint::connection::Udp::open("127.0.0.1:2342")
/// .expect("connection failed");
/// ```
pub fn open(
addr: impl std::net::ToSocketAddrs + Debug,
) -> std::io::Result<Self> {
log::info!("connecting to {addr:?}");
let socket = UdpSocket::bind("0.0.0.0:0")?;
socket.connect(addr)?;
Ok(Self { socket })
}
}
impl Connection for Udp {
type Error = std::io::Error;
fn send(&self, packet: impl Into<Packet>) -> Result<(), Self::Error> {
let data: Vec<u8> = packet.into().into();
self.socket.send(&data).map(move |_| ()) // ignore Ok value
}
}
impl From<UdpSocket> for Udp {
fn from(socket: UdpSocket) -> Self {
Self { socket }
}
}

View file

@ -0,0 +1,68 @@
use crate::{Connection, Packet};
/// A connection using the WebSocket protocol.
///
/// Note that you will need to forward the WebSocket messages via UDP to the display.
/// You can use [servicepoint-websocket-relay] for this.
///
/// To create a new WebSocket automatically, use [Connection::open_websocket].
///
/// Requires the feature "protocol_websocket" which is disabled by default.
///
/// [servicepoint-websocket-relay]: https://github.com/kaesaecracker/servicepoint-websocket-relay
#[derive(Debug)]
pub struct Websocket(
std::sync::Mutex<
tungstenite::WebSocket<
tungstenite::stream::MaybeTlsStream<std::net::TcpStream>,
>,
>,
);
impl Connection for Websocket {
type Error = tungstenite::Error;
fn send(&self, packet: impl Into<Packet>) -> Result<(), Self::Error> {
let data: Vec<u8> = packet.into().into();
let mut socket = self.0.lock().unwrap();
socket.send(tungstenite::Message::Binary(data.into()))
}
}
impl Websocket {
/// Open a new WebSocket and connect to the provided host.
///
/// Requires the feature "protocol_websocket" which is disabled by default.
///
/// # Examples
///
/// ```no_run
/// use tungstenite::http::Uri;
/// use servicepoint::{
/// Command,
/// connection::{Websocket as WebsocketConnection, Connection}
/// };
/// let uri = "ws://localhost:8080".parse().unwrap();
/// let mut connection = WebsocketConnection::open(uri)
/// .expect("could not connect");
/// connection.send(Command::Clear)
/// .expect("send failed");
/// ```
pub fn open(uri: tungstenite::http::Uri) -> tungstenite::Result<Self> {
use tungstenite::{
client::IntoClientRequest, connect, ClientRequestBuilder,
};
log::info!("connecting to {uri:?}");
let request = ClientRequestBuilder::new(uri).into_client_request()?;
let (sock, _) = connect(request)?;
Ok(Self(std::sync::Mutex::new(sock)))
}
}
impl Drop for Websocket {
fn drop(&mut self) {
_ = self.0.try_lock().map(move |mut sock| sock.close(None));
}
}

View file

@ -52,8 +52,8 @@ pub const PIXEL_COUNT: usize = PIXEL_WIDTH * PIXEL_HEIGHT;
///
/// ```rust
/// # use std::time::Instant;
/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, Bitmap};
/// # let connection = servicepoint::Connection::Fake;
/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, Bitmap, Connection};
/// # let connection = servicepoint::connection::Fake;
/// # let pixels = Bitmap::max_sized();
/// loop {
/// let start = Instant::now();

View file

@ -9,10 +9,10 @@
//! ### Clear display
//!
//! ```rust
//! use servicepoint::{Connection, Command};
//! use servicepoint::{Connection, Command, connection};
//!
//! // establish a connection
//! let connection = Connection::open("127.0.0.1:2342")
//! let connection = connection::Udp::open("127.0.0.1:2342")
//! .expect("connection failed");
//!
//! // turn off all pixels on display
@ -23,8 +23,8 @@
//! ### Set all pixels to on
//!
//! ```rust
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap};
//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed");
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap, Connection};
//! # let connection = servicepoint::connection::Udp::open("127.0.0.1:2342").expect("connection failed");
//! // turn on all pixels in a grid
//! let mut pixels = Bitmap::max_sized();
//! pixels.fill(true);
@ -43,14 +43,14 @@
//! ### Send text
//!
//! ```rust
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap, CharGrid};
//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed");
//! # use servicepoint::*;
//! # let connection = connection::Udp::open("127.0.0.1:2342").expect("connection failed");
//! // create a text grid
//! let mut grid = CharGrid::from("Hello\nCCCB?");
//! // modify the grid
//! grid.set(grid.width() - 1, 1, '!');
//! // create the command to send the data
//! let command = Command::Utf8Data(servicepoint::Origin::ZERO, grid);
//! let command = Command::Utf8Data(Origin::ZERO, grid);
//! // send command to display
//! connection.send(command).expect("send failed");
//! ```
@ -84,7 +84,7 @@ mod command;
mod command_code;
mod compression;
mod compression_code;
mod connection;
pub mod connection;
mod constants;
mod cp437_grid;
mod data_ref;
@ -95,7 +95,6 @@ mod value_grid;
#[cfg(feature = "cp437")]
mod cp437;
#[cfg(feature = "cp437")]
pub use crate::cp437::Cp437Converter;

View file

@ -154,16 +154,13 @@ impl<T: Value> ValueGrid<T> {
/// }
/// }
/// ```
pub fn iter(&self) -> Iter<T> {
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.data.iter()
}
/// Iterate over all rows in [ValueGrid] top to bottom.
pub fn iter_rows(&self) -> IterGridRows<T> {
IterGridRows {
byte_grid: self,
row: 0,
}
IterGridRows { grid: self, row: 0 }
}
/// Returns an iterator that allows modifying each value.
@ -318,6 +315,17 @@ impl<T: Value> ValueGrid<T> {
chunk.copy_from_slice(row);
Ok(())
}
/// Enumerates all values in the grid.
pub fn enumerate(
&self,
) -> impl Iterator<Item = (usize, usize, T)> + use<'_, T> {
EnumerateGrid {
grid: self,
column: 0,
row: 0,
}
}
}
/// Errors that can occur when loading a grid
@ -392,7 +400,7 @@ impl<T: Value> From<ValueGrid<T>> for Vec<T> {
/// An iterator iver the rows in a [ValueGrid]
pub struct IterGridRows<'t, T: Value> {
byte_grid: &'t ValueGrid<T>,
grid: &'t ValueGrid<T>,
row: usize,
}
@ -400,18 +408,43 @@ impl<'t, T: Value> Iterator for IterGridRows<'t, T> {
type Item = Iter<'t, T>;
fn next(&mut self) -> Option<Self::Item> {
if self.row >= self.byte_grid.height {
if self.row >= self.grid.height {
return None;
}
let start = self.row * self.byte_grid.width;
let end = start + self.byte_grid.width;
let result = self.byte_grid.data[start..end].iter();
let start = self.row * self.grid.width;
let end = start + self.grid.width;
let result = self.grid.data[start..end].iter();
self.row += 1;
Some(result)
}
}
pub struct EnumerateGrid<'t, T: Value> {
grid: &'t ValueGrid<T>,
row: usize,
column: usize,
}
impl<'t, T: Value> Iterator for EnumerateGrid<'t, T> {
type Item = (usize, usize, T);
fn next(&mut self) -> Option<Self::Item> {
if self.row >= self.grid.height {
return None;
}
let result =
Some((self.row, self.column, self.grid.get(self.row, self.column)));
self.column += 1;
if self.column == self.grid.width {
self.column = 0;
self.row += 1;
}
result
}
}
#[cfg(test)]
mod tests {
use crate::{