diff --git a/Cargo.lock b/Cargo.lock index 9fcfa8f..968bfeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler2" @@ -98,9 +98,9 @@ dependencies = [ [[package]] name = "cbindgen" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" dependencies = [ "clap", "heck", @@ -407,9 +407,8 @@ dependencies = [ [[package]] name = "servicepoint" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93b52049be55a15fe37c13249d7f96aa8a5de56e1a41838e74a822ee8316a0c4" +version = "0.13.2" +source = "git+https://git.berlin.ccc.de/servicepoint/servicepoint/?branch=next#114385868af03f8cba7c87a630b501bb0106d140" dependencies = [ "bitvec", "bzip2", diff --git a/Cargo.toml b/Cargo.toml index 7750abc..e92ceb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,14 +15,34 @@ keywords = ["cccb", "cccb-servicepoint", "cbindgen"] crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -cbindgen = "0.27.0" +cbindgen = "0.28.0" [dependencies.servicepoint] -version = "0.13.1" -features = ["all_compressions"] +package = "servicepoint" +version = "0.13.2" +default-features = false +features = ["protocol_udp"] +git = "https://git.berlin.ccc.de/servicepoint/servicepoint/" +branch = "next" + +[features] +full = ["servicepoint/all_compressions", "servicepoint/default"] +default = ["full"] [lints.rust] missing-docs = "warn" +unsafe_op_in_unsafe_fn = "warn" + +[lints.clippy] +missing_safety_doc = "allow" [package.metadata.docs.rs] all-features = true + +[profile.size-optimized] +inherits = "release" +opt-level = 'z' # Optimize for size +lto = true # Enable link-time optimization +codegen-units = 1 # Reduce number of codegen units to increase optimizations +panic = 'abort' # Abort on panic +strip = true # Strip symbols from binary diff --git a/README.md b/README.md index e085b1b..2f089d3 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,21 @@ You have the choice of linking statically (recommended) or dynamically. - documentation is included in the header and available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/) +## Safety + +Functions expect that C code honors NonNull annotations. + +Any created instances have to be freed in some way. +Pointers to those instances cannot be used anymore after that. + +Instances cannot be shared between threads and need to be locked in the using code. + +Enum values have to be used as-is. Do not pass values that are not part of the enum. + +UTF-8 or UTF-32 encoding has to be used properly. + +Brightness values provided as u8 parameters must be in range. + ## Everything else Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for diff --git a/build.rs b/build.rs index 93bf703..83e9641 100644 --- a/build.rs +++ b/build.rs @@ -6,28 +6,30 @@ use std::{env, fs::copy}; -use cbindgen::{generate_with_config, Config}; - fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); println!("cargo::rerun-if-changed={crate_dir}"); let config = - Config::from_file(crate_dir.clone() + "/cbindgen.toml").unwrap(); + cbindgen::Config::from_file(crate_dir.clone() + "/cbindgen.toml") + .unwrap(); let output_dir = env::var("OUT_DIR").unwrap(); let header_file = output_dir.clone() + "/servicepoint.h"; - generate_with_config(crate_dir, config) - .unwrap() - .write_to_file(&header_file); - println!("cargo:include={output_dir}"); + if let Ok(bindings) = cbindgen::generate_with_config(crate_dir, config) { + bindings.write_to_file(&header_file); - println!("cargo::rerun-if-env-changed=SERVICEPOINT_HEADER_OUT"); - if let Ok(header_out) = env::var("SERVICEPOINT_HEADER_OUT") { - let header_copy = header_out + "/servicepoint.h"; - println!("cargo:warning=Copying header to {header_copy}"); - copy(header_file, &header_copy).unwrap(); - println!("cargo::rerun-if-changed={header_copy}"); + println!("cargo:include={output_dir}"); + + println!("cargo::rerun-if-env-changed=SERVICEPOINT_HEADER_OUT"); + if let Ok(header_out) = env::var("SERVICEPOINT_HEADER_OUT") { + let header_copy = header_out + "/servicepoint.h"; + println!("cargo:warning=Copying header to {header_copy}"); + copy(header_file, &header_copy).unwrap(); + println!("cargo::rerun-if-changed={header_copy}"); + } + } else { + eprintln!("cargo:warning=Servicepoint header could not be generated"); } } diff --git a/cbindgen.toml b/cbindgen.toml index 7fc0fdf..221e915 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -19,18 +19,27 @@ line_endings = "LF" style = "type" usize_is_size_t = true -# this is needed because otherwise the order in the C# bindings is different on different machines +# this is needed because otherwise the order in the C bindings is different on different machines sort_by = "Name" [parse] -parse_deps = false +parse_deps = true +include = ["servicepoint", "std"] +extra_bindings = ["servicepoint"] [parse.expand] -all_features = true +features = ["full"] [export] include = [] exclude = [] +[export.rename] +"SpBitVec" = "BitVec" +"SpByteSlice" = "ByteSlice" + [enum] rename_variants = "QualifiedScreamingSnakeCase" + +[ptr] +non_null_attribute = "/*notnull*/" diff --git a/example/Makefile b/example/Makefile index abdd69f..f69f385 100644 --- a/example/Makefile +++ b/example/Makefile @@ -1,30 +1,94 @@ CC := gcc +CARGO := rustup run nightly cargo + +TARGET := x86_64-unknown-linux-musl +PROFILE := size-optimized THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) REPO_ROOT := $(THIS_DIR)/.. +RUST_TARGET_DIR := $(REPO_ROOT)/target/$(TARGET)/$(PROFILE) +export SERVICEPOINT_HEADER_OUT := $(REPO_ROOT)/include -build: out/example +RUSTFLAGS := -Zlocation-detail=none \ + -Zfmt-debug=none \ + -C linker=musl-gcc \ + -C link-arg=-s \ + -C link-arg=--gc-sections \ + -C link-arg=-z,norelro \ + -C link-arg=--hash-style=gnu \ + --crate-type=staticlib \ + -C panic=abort + +CARGOFLAGS := --manifest-path=$(REPO_ROOT)/Cargo.toml \ + --profile=$(PROFILE) \ + --no-default-features \ + --target=$(TARGET) \ + -Zbuild-std="core,std,alloc,proc_macro,panic_abort" \ + -Zbuild-std-features="panic_immediate_abort" \ + +CCFLAGS := -static -Os \ + -ffunction-sections -fdata-sections \ + -fwrapv -fomit-frame-pointer -fno-stack-protector\ + -fwhole-program \ + -nodefaultlibs -lservicepoint_binding_c -lc \ + -Wl,--gc-sections \ + -fno-unroll-loops \ + -fno-unwind-tables -fno-asynchronous-unwind-tables \ + -fmerge-all-constants \ + -Wl,-z,norelro \ + -Wl,--hash-style=gnu \ + -fvisibility=hidden \ + -Bsymbolic \ + -Wl,--exclude-libs,ALL \ + -fno-ident \ + -Wall + #-fuse-ld=gold \ + -fno-exceptions + #-Wl,--icf=all \ + +STRIPFLAGS := -s --strip-unneeded -R .comment -R .gnu.version -R .comment -R .note -R .note.gnu.build-id -R .note.ABI-tag + +_c_src := $(wildcard *.c) +_programs := $(basename $(_c_src)) +_bins := $(addprefix out/, $(_programs)) +_unstripped_bins := $(addsuffix _unstripped, $(_bins)) +_run_programs := $(addprefix run_, $(_programs)) +_rs_src := $(wildcard ../src/**.rs) ../Cargo.lock + +all: $(_bins) clean: rm -r out || true rm include/servicepoint.h || true cargo clean -run: out/example - out/example +PHONY: all clean sizes $(_run_programs) -PHONY: build clean dependencies run - -out/example: dependencies main.c +$(_unstripped_bins): out/%_unstripped: %.c $(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(RUST_TARGET_DIR)/libservicepoint_binding_c.a mkdir -p out || true - ${CC} main.c \ - -I $(REPO_ROOT)/include \ - -L $(REPO_ROOT)/target/release \ - -Wl,-Bstatic -lservicepoint_binding_c \ - -Wl,-Bdynamic -llzma \ - -o out/example + ${CC} $^ \ + -I $(SERVICEPOINT_HEADER_OUT) \ + -L $(RUST_TARGET_DIR)\ + $(CCFLAGS) \ + -o $@ -dependencies: FORCE - cargo build --manifest-path=$(REPO_ROOT)/Cargo.toml --release +$(_bins): out/%: out/%_unstripped + strip $(STRIPFLAGS) $^ -o $@ + +$(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(RUST_TARGET_DIR)/libservicepoint_binding_c.a: $(_rs_src) + mkdir -p include || true + # generate servicepoint header and binary to link against + ${CARGO} rustc $(CARGOFLAGS) -- $(RUSTFLAGS) + +$(_run_programs): run_%: out/% FORCE + ./$< + +sizes: $(_bins) + ls -lB out + +#analyze-size: out/example_unstripped +# nm --print-size --size-sort --reverse-sort --radix=d --demangle out/example_unstripped \ +# | awk '{size=$$2+0; print size "\t" $$4}' \ +# | less FORCE: ; diff --git a/example/announce.c b/example/announce.c new file mode 100644 index 0000000..737eff5 --- /dev/null +++ b/example/announce.c @@ -0,0 +1,33 @@ +#include "servicepoint.h" + +int main(void) { + //UdpConnection *connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); + UdpConnection *connection = sp_udp_open_ipv4(127, 0, 0, 1, 2342); + if (connection == NULL) + return 1; + + sp_udp_send_header(connection, (Header) {.command_code = COMMAND_CODE_CLEAR}); + + CharGrid *grid = sp_char_grid_new(5, 2); + if (grid == NULL) + return 1; + + sp_char_grid_set(grid, 0, 0, 'H'); + sp_char_grid_set(grid, 1, 0, 'e'); + sp_char_grid_set(grid, 2, 0, 'l'); + sp_char_grid_set(grid, 3, 0, 'l'); + sp_char_grid_set(grid, 4, 0, 'o'); + sp_char_grid_set(grid, 0, 1, 'W'); + sp_char_grid_set(grid, 1, 1, 'o'); + sp_char_grid_set(grid, 2, 1, 'r'); + sp_char_grid_set(grid, 3, 1, 'l'); + sp_char_grid_set(grid, 4, 1, 'd'); + + Packet *packet = sp_char_grid_into_packet(grid, 0, 0); + if (packet == NULL) + return 1; + sp_udp_send_packet(connection, packet); + + sp_udp_free(connection); + return 0; +} diff --git a/example/brightness_tester.c b/example/brightness_tester.c new file mode 100644 index 0000000..d06d30d --- /dev/null +++ b/example/brightness_tester.c @@ -0,0 +1,30 @@ +#include "servicepoint.h" + +int main(void) { + // UdpConnection *connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); + UdpConnection *connection = sp_udp_open_ipv4(127, 0, 0, 1, 2342); + if (connection == NULL) + return -1; + + Bitmap *all_on = sp_bitmap_new_max_sized(); + sp_bitmap_fill(all_on, true); + + Packet *packet = sp_bitmap_into_packet(all_on, 0, 0, COMPRESSION_CODE_UNCOMPRESSED); + if (packet == NULL) + return -1; + + sp_udp_send_packet(connection, packet); + + BrightnessGrid *grid = sp_brightness_grid_new(TILE_WIDTH, TILE_HEIGHT); + + ByteSlice slice = sp_brightness_grid_unsafe_data_ref(grid); + for (size_t index = 0; index < slice.length; index++) { + slice.start[index] = (uint8_t) (index % ((size_t) Brightness_MAX)); + } + + packet = sp_brightness_grid_into_packet(grid, 0, 0); + sp_udp_send_packet(connection, packet); + + sp_udp_free(connection); + return 0; +} diff --git a/example/main.c b/example/main.c deleted file mode 100644 index 1454804..0000000 --- a/example/main.c +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include "servicepoint.h" - -int main(void) { - SPConnection *connection = sp_connection_open("localhost:2342"); - if (connection == NULL) - return 1; - - SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT); - sp_bitmap_fill(pixels, true); - - SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, SP_COMPRESSION_CODE_UNCOMPRESSED); - sp_connection_send_command(connection, command); - - sp_connection_free(connection); - return 0; -} diff --git a/example/random_stuff.c b/example/random_stuff.c new file mode 100644 index 0000000..ca5a14d --- /dev/null +++ b/example/random_stuff.c @@ -0,0 +1,26 @@ +#include +#include "servicepoint.h" + +int main(void) { + UdpConnection *connection = sp_udp_open_ipv4(127, 0, 0, 1, 2342); + if (connection == NULL) + return 1; + + Bitmap *pixels = sp_bitmap_new(PIXEL_WIDTH, PIXEL_HEIGHT); + if (pixels == NULL) + return 1; + + sp_bitmap_fill(pixels, true); + + Packet *packet = sp_bitmap_into_packet(pixels, 0, 0, COMPRESSION_CODE_UNCOMPRESSED); + if (packet == NULL) + return 1; + + Header *header = sp_packet_get_header(packet); + printf("[%d, %d, %d, %d, %d]\n", header->command_code, header->a, header->b, header->c, header->d); + + sp_udp_send_packet(connection, packet); + + sp_udp_free(connection); + return 0; +} diff --git a/flake.nix b/flake.nix index 9318237..b4f874f 100644 --- a/flake.nix +++ b/flake.nix @@ -33,25 +33,34 @@ { pkgs, system }: { default = pkgs.mkShell rec { - packages = with pkgs; [ + buildInputs = with pkgs;[ + xe + xz + libgcc + #glibc.static + musl + libunwind + ]; + + nativeBuildInputs = with pkgs;[ (pkgs.symlinkJoin { name = "rust-toolchain"; paths = with pkgs; [ - rustc - cargo - rustPlatform.rustcSrc - rustfmt - clippy + #rustc + #cargo + #rustPlatform.rustcSrc + #rustfmt + #clippy cargo-expand cargo-tarpaulin + rustup ]; }) gcc gnumake - xe - xz pkg-config ]; + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; }; } diff --git a/include/servicepoint.h b/include/servicepoint.h index d9cbe57..77d2e36 100644 --- a/include/servicepoint.h +++ b/include/servicepoint.h @@ -1,4 +1,4 @@ -/* Generated with cbindgen:0.27.0 */ +/* Generated with cbindgen:0.28.0 */ /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ @@ -8,55 +8,148 @@ #include #include -/** - * Count of possible brightness values - */ -#define SP_BRIGHTNESS_LEVELS 12 - -/** - * see [servicepoint::Brightness::MAX] - */ -#define SP_BRIGHTNESS_MAX 11 - -/** - * see [servicepoint::Brightness::MIN] - */ -#define SP_BRIGHTNESS_MIN 0 - /** * pixel count on whole screen */ -#define SP_PIXEL_COUNT (SP_PIXEL_WIDTH * SP_PIXEL_HEIGHT) +#define PIXEL_COUNT (PIXEL_WIDTH * PIXEL_HEIGHT) /** * Display height in pixels + * + * # Examples + * + * ```rust + * # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap}; + * let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); + * ``` */ -#define SP_PIXEL_HEIGHT (SP_TILE_HEIGHT * SP_TILE_SIZE) +#define PIXEL_HEIGHT (TILE_HEIGHT * TILE_SIZE) /** * Display width in pixels + * + * # Examples + * + * ```rust + * # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap}; + * let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); + * ``` */ -#define SP_PIXEL_WIDTH (SP_TILE_WIDTH * SP_TILE_SIZE) +#define PIXEL_WIDTH (TILE_WIDTH * TILE_SIZE) /** * Display tile count in the y-direction + * + * # Examples + * + * ```rust + * # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH}; + * let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT); + * ``` */ -#define SP_TILE_HEIGHT 20 +#define TILE_HEIGHT 20 /** * size of a single tile in one dimension */ -#define SP_TILE_SIZE 8 +#define TILE_SIZE 8 /** * Display tile count in the x-direction + * + * # Examples + * + * ```rust + * # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH}; + * let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT); + * ``` */ -#define SP_TILE_WIDTH 56 +#define TILE_WIDTH 56 /** - * Specifies the kind of compression to use. + * Binary operations for use with the [`BitVecCommand`] command. */ -enum SPCompressionCode +enum BinaryOperation +#ifdef __cplusplus + : uint8_t +#endif // __cplusplus + { + /** + * r := a + */ + BINARY_OPERATION_OVERWRITE, + /** + * r := a && b + */ + BINARY_OPERATION_AND, + /** + * r := a || b + */ + BINARY_OPERATION_OR, + /** + * r := (a || b) && (a != b) + */ + BINARY_OPERATION_XOR, +}; +#ifndef __cplusplus +typedef uint8_t BinaryOperation; +#endif // __cplusplus + +/** + * The u16 command codes used for the [Command]s. + */ +enum CommandCode +#ifdef __cplusplus + : uint16_t +#endif // __cplusplus + { + COMMAND_CODE_CLEAR = 2, + COMMAND_CODE_CP437_DATA = 3, + COMMAND_CODE_CHAR_BRIGHTNESS = 5, + COMMAND_CODE_BRIGHTNESS = 7, + COMMAND_CODE_HARD_RESET = 11, + COMMAND_CODE_FADE_OUT = 13, + COMMAND_CODE_BITMAP_LEGACY = 16, + COMMAND_CODE_BITMAP_LINEAR = 18, + COMMAND_CODE_BITMAP_LINEAR_WIN_UNCOMPRESSED = 19, + COMMAND_CODE_BITMAP_LINEAR_AND = 20, + COMMAND_CODE_BITMAP_LINEAR_OR = 21, + COMMAND_CODE_BITMAP_LINEAR_XOR = 22, + COMMAND_CODE_BITMAP_LINEAR_WIN_ZLIB = 23, + COMMAND_CODE_BITMAP_LINEAR_WIN_BZIP2 = 24, + COMMAND_CODE_BITMAP_LINEAR_WIN_LZMA = 25, + COMMAND_CODE_UTF8_DATA = 32, + COMMAND_CODE_BITMAP_LINEAR_WIN_ZSTD = 26, +}; +#ifndef __cplusplus +typedef uint16_t CommandCode; +#endif // __cplusplus + +/** + * Specifies the kind of compression to use. Availability depends on features. + * + * # Examples + * + * ```rust + * # use servicepoint::*; + * // create command without payload compression + * # let pixels = Bitmap::max_sized(); + * _ = BitmapCommand { + * origin: Origin::ZERO, + * bitmap: pixels, + * compression: CompressionCode::Uncompressed + * }; + * + * // create command with payload compressed with lzma and appropriate header flags + * # let pixels = Bitmap::max_sized(); + * _ = BitmapCommand { + * origin: Origin::ZERO, + * bitmap: pixels, + * compression: CompressionCode::Lzma + * }; + * ``` + */ +enum CompressionCode #ifdef __cplusplus : uint16_t #endif // __cplusplus @@ -64,28 +157,54 @@ enum SPCompressionCode /** * no compression */ - SP_COMPRESSION_CODE_UNCOMPRESSED = 0, + COMPRESSION_CODE_UNCOMPRESSED = 0, /** * compress using flate2 with zlib header */ - SP_COMPRESSION_CODE_ZLIB = 26490, + COMPRESSION_CODE_ZLIB = 26490, /** * compress using bzip2 */ - SP_COMPRESSION_CODE_BZIP2 = 25210, + COMPRESSION_CODE_BZIP2 = 25210, /** * compress using lzma */ - SP_COMPRESSION_CODE_LZMA = 27770, + COMPRESSION_CODE_LZMA = 27770, /** * compress using Zstandard */ - SP_COMPRESSION_CODE_ZSTD = 31347, + COMPRESSION_CODE_ZSTD = 31347, }; #ifndef __cplusplus -typedef uint16_t SPCompressionCode; +typedef uint16_t CompressionCode; #endif // __cplusplus +/** + * A fixed-size 2D grid of booleans. + * + * The values are stored in packed bytes (8 values per byte) in the same order as used by the display for storing pixels. + * This means that no conversion is necessary for sending the data to the display. + * The downside is that the width has to be a multiple of 8. + * + * # Examples + * + * ```rust + * use servicepoint::Bitmap; + * let mut bitmap = Bitmap::new(8, 2); + * + * ``` + */ +typedef struct Bitmap Bitmap; + +/** + * The raw packet. + * + * Contents should probably only be used directly to use features not exposed by the library. + * + * You may want to use [`crate::Command`] or [`crate::TypedCommand`] instead. + */ +typedef struct Packet Packet; + /** * A vector of bits * @@ -99,7 +218,284 @@ typedef uint16_t SPCompressionCode; typedef struct SPBitVec SPBitVec; /** - * A grid of pixels. + * This enum contains all commands provided by the library. + * This is useful in case you want one data type for all kinds of commands without using `dyn`. + * + * Please look at the contained structs for documentation per command. + */ +typedef struct TypedCommand TypedCommand; + +/** + * 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. + */ +typedef struct UdpConnection UdpConnection; + +/** + * A 2D grid of values. + * + * The memory layout is the one the display expects in [`crate::Command`]s. + * + * This structure can be used with any type that implements the [Value] trait. + * You can also use the concrete type aliases provided in this crate, e.g. [`crate::CharGrid`] and [`crate::ByteGrid`]. + */ +typedef struct ValueGrid_Brightness ValueGrid_Brightness; + +/** + * A 2D grid of values. + * + * The memory layout is the one the display expects in [`crate::Command`]s. + * + * This structure can be used with any type that implements the [Value] trait. + * You can also use the concrete type aliases provided in this crate, e.g. [`crate::CharGrid`] and [`crate::ByteGrid`]. + */ +typedef struct ValueGrid_char ValueGrid_char; + +/** + * A 2D grid of values. + * + * The memory layout is the one the display expects in [`crate::Command`]s. + * + * This structure can be used with any type that implements the [Value] trait. + * You can also use the concrete type aliases provided in this crate, e.g. [`crate::CharGrid`] and [`crate::ByteGrid`]. + */ +typedef struct ValueGrid_u8 ValueGrid_u8; + +/** + * Represents a span of memory (`&mut [u8]` ) as a struct. + * + * # Safety + * + * The caller has to make sure that: + * + * - accesses to the memory pointed to with `start` is never accessed outside `length` + * - the lifetime of the `CByteSlice` does not outlive the memory it points to, as described in + * the function returning this type. + */ +typedef struct { + /** + * The start address of the memory. + */ + uint8_t */*notnull*/ start; + /** + * The amount of memory in bytes. + */ + size_t length; +} ByteSlice; + +/** + * A grid containing brightness values. + * + * # Examples + * + * ```rust + * # use servicepoint::*; + * let mut grid = BrightnessGrid::new(2,2); + * grid.set(0, 0, Brightness::MIN); + * grid.set(1, 1, Brightness::MIN); + * + * # let connection = FakeConnection; + * connection.send(BrightnessGridCommand { + * origin: Origin::new(3, 7), + * grid + * }).unwrap() + * ``` + */ +typedef ValueGrid_Brightness BrightnessGrid; + +/** + * A display brightness value, checked for correct value range + * + * # Examples + * + * ``` + * # use servicepoint::*; + * let b = Brightness::MAX; + * let val: u8 = b.into(); + * + * let b = Brightness::try_from(7).unwrap(); + * # let connection = FakeConnection; + * let result = connection.send(GlobalBrightnessCommand::from(b)); + * ``` + */ +typedef uint8_t Brightness; +/** + * highest possible brightness value, 11 + */ +#define Brightness_MAX 11 +/** + * lowest possible brightness value, 0 + */ +#define Brightness_MIN 0 + +/** + * A grid containing UTF-8 characters. + * + * To send a `CharGrid` to the display, use a [`crate::CharGridCommand`]. + * + * Also see [`ValueGrid`] for the non-specialized operations and examples. + * + * # Examples + * + * ```rust + * # use servicepoint::*; + * 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 = FakeConnection; + * let command = CharGridCommand { origin: Origin::ZERO, grid }; + * connection.send(command).unwrap() + * ``` + */ +typedef ValueGrid_char CharGrid; + +/** + * A grid containing codepage 437 characters. + * + * The encoding is currently not enforced. + */ +typedef ValueGrid_u8 Cp437Grid; + +/** + * A raw header. + * + * The header specifies the kind of command, the size of the payload and where to display the + * payload, where applicable. + * + * Because the meaning of most fields depend on the command, there are no speaking names for them. + * + * The contained values are in platform endian-ness and may need to be converted before sending. + */ +typedef struct { + /** + * The first two bytes specify which command this packet represents. + */ + uint16_t command_code; + /** + * First command-specific value + */ + uint16_t a; + /** + * Second command-specific value + */ + uint16_t b; + /** + * Third command-specific value + */ + uint16_t c; + /** + * Fourth command-specific value + */ + uint16_t d; +} Header; + + + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Clones a [Bitmap]. + */ +Bitmap */*notnull*/ sp_bitmap_clone(Bitmap */*notnull*/ bitmap); + +/** + * Sets the state of all pixels in the [Bitmap]. + * + * # Arguments + * + * - `bitmap`: instance to write to + * - `value`: the value to set all pixels to + */ +void sp_bitmap_fill(Bitmap */*notnull*/ bitmap, bool value); + +/** + * Deallocates a [Bitmap]. + */ +void sp_bitmap_free(Bitmap */*notnull*/ bitmap); + +/** + * Tries to convert the BitVec to a Bitmap. + * + * The provided BitVec gets consumed. + * + * Returns NULL in case of error. + */ +Bitmap *sp_bitmap_from_bitvec(size_t width, SPBitVec */*notnull*/ bitvec); + +/** + * Gets the current value at the specified position in the [Bitmap]. + * + * # Arguments + * + * - `bitmap`: instance to read from + * - `x` and `y`: position of the cell to read + * + * # Panics + * + * - when accessing `x` or `y` out of bounds + */ +bool sp_bitmap_get(Bitmap */*notnull*/ bitmap, size_t x, size_t y); + +/** + * Gets the height in pixels of the [Bitmap] instance. + * + * # Arguments + * + * - `bitmap`: instance to read from + */ +size_t sp_bitmap_height(Bitmap */*notnull*/ bitmap); + +/** + * Consumes the Bitmap and returns the contained BitVec + */ +SPBitVec */*notnull*/ sp_bitmap_into_bitvec(Bitmap */*notnull*/ bitmap); + +/** + * Creates a [BitmapCommand] and immediately turns that into a [Packet]. + * + * The provided [Bitmap] gets consumed. + * + * Returns NULL in case of an error. + */ +Packet *sp_bitmap_into_packet(Bitmap */*notnull*/ bitmap, + size_t x, + size_t y, + CompressionCode compression); + +/** + * Loads a [Bitmap] with the specified dimensions from the provided data. + * + * # Arguments + * + * - `width`: size in pixels in x-direction + * - `height`: size in pixels in y-direction + * + * returns: [Bitmap] that contains a copy of the provided data, or NULL in case of an error. + */ +Bitmap *sp_bitmap_load(size_t width, + size_t height, + ByteSlice data); + +/** + * Creates a new [Bitmap] with the specified dimensions. + * + * # Arguments + * + * - `width`: size in pixels in x-direction + * - `height`: size in pixels in y-direction + * + * returns: [Bitmap] initialized to all pixels off, or NULL in case of an error. + * + * # Errors + * + * In the following cases, this function will return NULL: + * + * - when the width is not dividable by 8 * * # Examples * @@ -110,298 +506,17 @@ typedef struct SPBitVec SPBitVec; * sp_bitmap_free(grid); * ``` */ -typedef struct SPBitmap SPBitmap; +Bitmap *sp_bitmap_new(size_t width, size_t height); /** - * A grid containing brightness values. + * Creates a new [Bitmap] with a size matching the screen. * - * # Examples - * ```C - * SPConnection connection = sp_connection_open("127.0.0.1:2342"); - * if (connection == NULL) - * return 1; - * - * SPBrightnessGrid grid = sp_brightness_grid_new(2, 2); - * sp_brightness_grid_set(grid, 0, 0, 0); - * sp_brightness_grid_set(grid, 1, 1, 10); - * - * SPCommand command = sp_command_char_brightness(grid); - * sp_connection_free(connection); - * ``` + * returns: [Bitmap] initialized to all pixels off. */ -typedef struct SPBrightnessGrid SPBrightnessGrid; +Bitmap */*notnull*/ sp_bitmap_new_max_sized(void); /** - * A C-wrapper for grid containing UTF-8 characters. - * - * As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers. - * - * The encoding is enforced in most cases by the rust standard library - * and will panic when provided with illegal characters. - * - * # Examples - * - * ```C - * CharGrid grid = sp_char_grid_new(4, 3); - * sp_char_grid_fill(grid, '?'); - * sp_char_grid_set(grid, 0, 0, '!'); - * sp_char_grid_free(grid); - * ``` - */ -typedef struct SPCharGrid SPCharGrid; - -/** - * A low-level display command. - * - * This struct and associated functions implement the UDP protocol for the display. - * - * To send a [SPCommand], use a [SPConnection]. - * - * # Examples - * - * ```C - * sp_connection_send_command(connection, sp_command_clear()); - * sp_connection_send_command(connection, sp_command_brightness(5)); - * ``` - * - * [SPConnection]: [crate::SPConnection] - */ -typedef struct SPCommand SPCommand; - -/** - * A connection to the display. - * - * # Examples - * - * ```C - * CConnection connection = sp_connection_open("172.23.42.29:2342"); - * if (connection != NULL) - * sp_connection_send_command(connection, sp_command_clear()); - * ``` - */ -typedef struct SPConnection SPConnection; - -/** - * A C-wrapper for grid containing codepage 437 characters. - * - * The encoding is currently not enforced. - * - * # Examples - * - * ```C - * Cp437Grid grid = sp_cp437_grid_new(4, 3); - * sp_cp437_grid_fill(grid, '?'); - * sp_cp437_grid_set(grid, 0, 0, '!'); - * sp_cp437_grid_free(grid); - * ``` - */ -typedef struct SPCp437Grid SPCp437Grid; - -/** - * The raw packet - */ -typedef struct SPPacket SPPacket; - -/** - * Represents a span of memory (`&mut [u8]` ) as a struct usable by C code. - * - * You should not create an instance of this type in your C code. - * - * # Safety - * - * The caller has to make sure that: - * - * - accesses to the memory pointed to with `start` is never accessed outside `length` - * - the lifetime of the `CByteSlice` does not outlive the memory it points to, as described in - * the function returning this type. - * - an instance of this created from C is never passed to a consuming function, as the rust code - * will try to free the memory of a potentially separate allocator. - */ -typedef struct { - /** - * The start address of the memory - */ - uint8_t *start; - /** - * The amount of memory in bytes - */ - size_t length; -} SPByteSlice; - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * Clones a [SPBitmap]. - * - * Will never return NULL. - * - * # Panics - * - * - when `bitmap` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `bitmap` points to a valid [SPBitmap] - * - `bitmap` is not written to concurrently - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_bitmap_free`. - */ -SPBitmap *sp_bitmap_clone(const SPBitmap *bitmap); - -/** - * Sets the state of all pixels in the [SPBitmap]. - * - * # Arguments - * - * - `bitmap`: instance to write to - * - `value`: the value to set all pixels to - * - * # Panics - * - * - when `bitmap` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `bitmap` points to a valid [SPBitmap] - * - `bitmap` is not written to or read from concurrently - */ -void sp_bitmap_fill(SPBitmap *bitmap, bool value); - -/** - * Deallocates a [SPBitmap]. - * - * # Panics - * - * - when `bitmap` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `bitmap` points to a valid [SPBitmap] - * - `bitmap` is not used concurrently or after bitmap call - * - `bitmap` was not passed to another consuming function, e.g. to create a [SPCommand] - * - * [SPCommand]: [crate::SPCommand] - */ -void sp_bitmap_free(SPBitmap *bitmap); - -/** - * Gets the current value at the specified position in the [SPBitmap]. - * - * # Arguments - * - * - `bitmap`: instance to read from - * - `x` and `y`: position of the cell to read - * - * # Panics - * - * - when `bitmap` is NULL - * - when accessing `x` or `y` out of bounds - * - * # Safety - * - * The caller has to make sure that: - * - * - `bitmap` points to a valid [SPBitmap] - * - `bitmap` is not written to concurrently - */ -bool sp_bitmap_get(const SPBitmap *bitmap, size_t x, size_t y); - -/** - * Gets the height in pixels of the [SPBitmap] instance. - * - * # Arguments - * - * - `bitmap`: instance to read from - * - * # Panics - * - * - when `bitmap` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `bitmap` points to a valid [SPBitmap] - */ -size_t sp_bitmap_height(const SPBitmap *bitmap); - -/** - * Loads a [SPBitmap] with the specified dimensions from the provided data. - * - * # Arguments - * - * - `width`: size in pixels in x-direction - * - `height`: size in pixels in y-direction - * - * returns: [SPBitmap] that contains a copy of the provided data. Will never return NULL. - * - * # Panics - * - * - when `data` is NULL - * - when the dimensions and data size do not match exactly. - * - when the width is not dividable by 8 - * - * # Safety - * - * The caller has to make sure that: - * - * - `data` points to a valid memory location of at least `data_length` bytes in size. - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_bitmap_free`. - */ -SPBitmap *sp_bitmap_load(size_t width, - size_t height, - const uint8_t *data, - size_t data_length); - -/** - * Creates a new [SPBitmap] with the specified dimensions. - * - * # Arguments - * - * - `width`: size in pixels in x-direction - * - `height`: size in pixels in y-direction - * - * returns: [SPBitmap] initialized to all pixels off. Will never return NULL. - * - * # Panics - * - * - when the width is not dividable by 8 - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_bitmap_free`. - */ -SPBitmap *sp_bitmap_new(size_t width, - size_t height); - -/** - * Creates a new [SPBitmap] with a size matching the screen. - * - * returns: [SPBitmap] initialized to all pixels off. Will never return NULL. - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling [sp_bitmap_free]. - */ -SPBitmap *sp_bitmap_new_screen_sized(void); - -/** - * Sets the value of the specified position in the [SPBitmap]. + * Sets the value of the specified position in the [Bitmap]. * * # Arguments * @@ -409,41 +524,21 @@ SPBitmap *sp_bitmap_new_screen_sized(void); * - `x` and `y`: position of the cell * - `value`: the value to write to the cell * - * returns: old value of the cell - * * # Panics * - * - when `bitmap` is NULL * - when accessing `x` or `y` out of bounds - * - * # Safety - * - * The caller has to make sure that: - * - * - `bitmap` points to a valid [SPBitmap] - * - `bitmap` is not written to or read from concurrently */ -void sp_bitmap_set(SPBitmap *bitmap, size_t x, size_t y, bool value); +void sp_bitmap_set(Bitmap */*notnull*/ bitmap, size_t x, size_t y, bool value); /** - * Gets an unsafe reference to the data of the [SPBitmap] instance. + * Gets an unsafe reference to the data of the [Bitmap] instance. * - * # Panics - * - * - when `bitmap` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `bitmap` points to a valid [SPBitmap] - * - the returned memory range is never accessed after the passed [SPBitmap] has been freed - * - the returned memory range is never accessed concurrently, either via the [SPBitmap] or directly + * The returned memory is valid for the lifetime of the bitmap. */ -SPByteSlice sp_bitmap_unsafe_data_ref(SPBitmap *bitmap); +ByteSlice sp_bitmap_unsafe_data_ref(Bitmap */*notnull*/ bitmap); /** - * Gets the width in pixels of the [SPBitmap] instance. + * Gets the width in pixels of the [Bitmap] instance. * * # Arguments * @@ -457,29 +552,14 @@ SPByteSlice sp_bitmap_unsafe_data_ref(SPBitmap *bitmap); * * The caller has to make sure that: * - * - `bitmap` points to a valid [SPBitmap] + * - `bitmap` points to a valid [Bitmap] */ -size_t sp_bitmap_width(const SPBitmap *bitmap); +size_t sp_bitmap_width(Bitmap */*notnull*/ bitmap); /** * Clones a [SPBitVec]. - * - * returns: new [SPBitVec] instance. Will never return NULL. - * - * # Panics - * - * - when `bit_vec` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid [SPBitVec] - * - `bit_vec` is not written to concurrently - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_bitvec_free`. */ -SPBitVec *sp_bitvec_clone(const SPBitVec *bit_vec); +SPBitVec */*notnull*/ sp_bitvec_clone(SPBitVec */*notnull*/ bit_vec); /** * Sets the value of all bits in the [SPBitVec]. @@ -488,38 +568,13 @@ SPBitVec *sp_bitvec_clone(const SPBitVec *bit_vec); * * - `bit_vec`: instance to write to * - `value`: the value to set all bits to - * - * # Panics - * - * - when `bit_vec` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid [SPBitVec] - * - `bit_vec` is not written to or read from concurrently */ -void sp_bitvec_fill(SPBitVec *bit_vec, bool value); +void sp_bitvec_fill(SPBitVec */*notnull*/ bit_vec, bool value); /** * Deallocates a [SPBitVec]. - * - * # Panics - * - * - when `but_vec` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid [SPBitVec] - * - `bit_vec` is not used concurrently or after this call - * - `bit_vec` was not passed to another consuming function, e.g. to create a [SPCommand] - * - * [SPCommand]: [crate::SPCommand] */ -void sp_bitvec_free(SPBitVec *bit_vec); +void sp_bitvec_free(SPBitVec */*notnull*/ bit_vec); /** * Gets the value of a bit from the [SPBitVec]. @@ -533,17 +588,21 @@ void sp_bitvec_free(SPBitVec *bit_vec); * * # Panics * - * - when `bit_vec` is NULL * - when accessing `index` out of bounds - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid [SPBitVec] - * - `bit_vec` is not written to concurrently */ -bool sp_bitvec_get(const SPBitVec *bit_vec, size_t index); +bool sp_bitvec_get(SPBitVec */*notnull*/ bit_vec, size_t index); + +/** + * Creates a [BitVecCommand] and immediately turns that into a [Packet]. + * + * The provided [SPBitVec] gets consumed. + * + * Returns NULL in case of an error. + */ +Packet *sp_bitvec_into_packet(SPBitVec */*notnull*/ bitvec, + size_t offset, + BinaryOperation operation, + CompressionCode compression); /** * Returns true if length is 0. @@ -551,18 +610,8 @@ bool sp_bitvec_get(const SPBitVec *bit_vec, size_t index); * # Arguments * * - `bit_vec`: instance to write to - * - * # Panics - * - * - when `bit_vec` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid [SPBitVec] */ -bool sp_bitvec_is_empty(const SPBitVec *bit_vec); +bool sp_bitvec_is_empty(SPBitVec */*notnull*/ bit_vec); /** * Gets the length of the [SPBitVec] in bits. @@ -570,39 +619,15 @@ bool sp_bitvec_is_empty(const SPBitVec *bit_vec); * # Arguments * * - `bit_vec`: instance to write to - * - * # Panics - * - * - when `bit_vec` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid [SPBitVec] */ -size_t sp_bitvec_len(const SPBitVec *bit_vec); +size_t sp_bitvec_len(SPBitVec */*notnull*/ bit_vec); /** * Interpret the data as a series of bits and load then into a new [SPBitVec] instance. * - * returns: [SPBitVec] instance containing data. Will never return NULL. - * - * # Panics - * - * - when `data` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `data` points to a valid memory location of at least `data_length` - * bytes in size. - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_bitvec_free`. + * returns: [SPBitVec] instance containing data. */ -SPBitVec *sp_bitvec_load(const uint8_t *data, - size_t data_length); +SPBitVec */*notnull*/ sp_bitvec_load(ByteSlice data); /** * Creates a new [SPBitVec] instance. @@ -611,20 +636,13 @@ SPBitVec *sp_bitvec_load(const uint8_t *data, * * - `size`: size in bits. * - * returns: [SPBitVec] with all bits set to false. Will never return NULL. + * returns: [SPBitVec] with all bits set to false. * * # Panics * * - when `size` is not divisible by 8. - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_bitvec_free`. */ -SPBitVec *sp_bitvec_new(size_t size); +SPBitVec */*notnull*/ sp_bitvec_new(size_t size); /** * Sets the value of a bit in the [SPBitVec]. @@ -637,107 +655,41 @@ SPBitVec *sp_bitvec_new(size_t size); * * # Panics * - * - when `bit_vec` is NULL * - when accessing `index` out of bounds - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid [SPBitVec] - * - `bit_vec` is not written to or read from concurrently */ -void sp_bitvec_set(SPBitVec *bit_vec, size_t index, bool value); +void sp_bitvec_set(SPBitVec */*notnull*/ bit_vec, size_t index, bool value); /** * Gets an unsafe reference to the data of the [SPBitVec] instance. * + * The returned memory is valid for the lifetime of the bitvec. + * * # Arguments * * - `bit_vec`: instance to write to - * - * # Panics - * - * - when `bit_vec` is NULL - * - * ## Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid [SPBitVec] - * - the returned memory range is never accessed after the passed [SPBitVec] has been freed - * - the returned memory range is never accessed concurrently, either via the [SPBitVec] or directly */ -SPByteSlice sp_bitvec_unsafe_data_ref(SPBitVec *bit_vec); +ByteSlice sp_bitvec_unsafe_data_ref(SPBitVec */*notnull*/ bit_vec); /** - * Clones a [SPBrightnessGrid]. - * - * # Arguments - * - * - `brightness_grid`: instance to read from - * - * returns: new [SPBrightnessGrid] instance. Will never return NULL. - * - * # Panics - * - * - when `brightness_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `brightness_grid` points to a valid [SPBrightnessGrid] - * - `brightness_grid` is not written to concurrently - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_brightness_grid_free`. + * Clones a [BrightnessGrid]. */ -SPBrightnessGrid *sp_brightness_grid_clone(const SPBrightnessGrid *brightness_grid); +BrightnessGrid */*notnull*/ sp_brightness_grid_clone(BrightnessGrid */*notnull*/ brightness_grid); /** - * Sets the value of all cells in the [SPBrightnessGrid]. + * Sets the value of all cells in the [BrightnessGrid]. * * # Arguments * * - `brightness_grid`: instance to write to * - `value`: the value to set all cells to - * - * # Panics - * - * - when `brightness_grid` is NULL - * - When providing an invalid brightness value - * - * # Safety - * - * The caller has to make sure that: - * - * - `brightness_grid` points to a valid [SPBrightnessGrid] - * - `brightness_grid` is not written to or read from concurrently */ -void sp_brightness_grid_fill(SPBrightnessGrid *brightness_grid, uint8_t value); +void sp_brightness_grid_fill(BrightnessGrid */*notnull*/ brightness_grid, + Brightness value); /** - * Deallocates a [SPBrightnessGrid]. - * - * # Arguments - * - * - `brightness_grid`: instance to read from - * - * # Panics - * - * - when `brightness_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `brightness_grid` points to a valid [SPBrightnessGrid] - * - `brightness_grid` is not used concurrently or after this call - * - `brightness_grid` was not passed to another consuming function, e.g. to create a [SPCommand] - * - * [SPCommand]: [crate::SPCommand] + * Deallocates a [BrightnessGrid]. */ -void sp_brightness_grid_free(SPBrightnessGrid *brightness_grid); +void sp_brightness_grid_free(BrightnessGrid */*notnull*/ brightness_grid); /** * Gets the current value at the specified position. @@ -750,83 +702,68 @@ void sp_brightness_grid_free(SPBrightnessGrid *brightness_grid); * returns: value at position * * # Panics - * - * - when `brightness_grid` is NULL * - When accessing `x` or `y` out of bounds. - * - * # Safety - * - * The caller has to make sure that: - * - * - `brightness_grid` points to a valid [SPBrightnessGrid] - * - `brightness_grid` is not written to concurrently */ -uint8_t sp_brightness_grid_get(const SPBrightnessGrid *brightness_grid, - size_t x, - size_t y); +Brightness sp_brightness_grid_get(BrightnessGrid */*notnull*/ brightness_grid, + size_t x, + size_t y); /** - * Gets the height of the [SPBrightnessGrid] instance. + * Gets the height of the [BrightnessGrid] instance. * * # Arguments * * - `brightness_grid`: instance to read from * * returns: height - * - * # Panics - * - * - when `brightness_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `brightness_grid` points to a valid [SPBrightnessGrid] */ -size_t sp_brightness_grid_height(const SPBrightnessGrid *brightness_grid); +size_t sp_brightness_grid_height(BrightnessGrid */*notnull*/ brightness_grid); /** - * Loads a [SPBrightnessGrid] with the specified dimensions from the provided data. + * Creates a [BrightnessGridCommand] and immediately turns that into a [Packet]. * - * returns: new [SPBrightnessGrid] instance. Will never return NULL. + * The provided [BrightnessGrid] gets consumed. * - * # Panics - * - * - when `data` is NULL - * - when the provided `data_length` does not match `height` and `width` - * - * # Safety - * - * The caller has to make sure that: - * - * - `data` points to a valid memory location of at least `data_length` - * bytes in size. - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_brightness_grid_free`. + * Returns NULL in case of an error. */ -SPBrightnessGrid *sp_brightness_grid_load(size_t width, - size_t height, - const uint8_t *data, - size_t data_length); +Packet *sp_brightness_grid_into_packet(BrightnessGrid */*notnull*/ grid, + size_t x, + size_t y); /** - * Creates a new [SPBrightnessGrid] with the specified dimensions. + * Loads a [BrightnessGrid] with the specified dimensions from the provided data. * - * returns: [SPBrightnessGrid] initialized to 0. Will never return NULL. + * Any out of range values will be set to [Brightness::MAX] or [Brightness::MIN]. * - * # Safety - * - * The caller has to make sure that: - * - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_brightness_grid_free`. + * returns: new [BrightnessGrid] instance, or NULL in case of an error. */ -SPBrightnessGrid *sp_brightness_grid_new(size_t width, - size_t height); +BrightnessGrid *sp_brightness_grid_load(size_t width, + size_t height, + ByteSlice data); /** - * Sets the value of the specified position in the [SPBrightnessGrid]. + * Creates a new [BrightnessGrid] with the specified dimensions. + * + * returns: [BrightnessGrid] initialized to 0. + * + * # Examples + * ```C + * UdpConnection connection = sp_udp_open("127.0.0.1:2342"); + * if (connection == NULL) + * return 1; + * + * BrightnessGrid grid = sp_brightness_grid_new(2, 2); + * sp_brightness_grid_set(grid, 0, 0, 0); + * sp_brightness_grid_set(grid, 1, 1, 10); + * + * TypedCommand command = sp_command_char_brightness(grid); + * sp_udp_free(connection); + * ``` + */ +BrightnessGrid */*notnull*/ sp_brightness_grid_new(size_t width, size_t height); + +/** + * Sets the value of the specified position in the [BrightnessGrid]. * * # Arguments * @@ -838,128 +775,59 @@ SPBrightnessGrid *sp_brightness_grid_new(size_t width, * * # Panics * - * - when `brightness_grid` is NULL * - When accessing `x` or `y` out of bounds. - * - When providing an invalid brightness value - * - * # Safety - * - * The caller has to make sure that: - * - * - `brightness_grid` points to a valid [SPBrightnessGrid] - * - `brightness_grid` is not written to or read from concurrently */ -void sp_brightness_grid_set(SPBrightnessGrid *brightness_grid, +void sp_brightness_grid_set(BrightnessGrid */*notnull*/ brightness_grid, size_t x, size_t y, - uint8_t value); + Brightness value); /** - * Gets an unsafe reference to the data of the [SPBrightnessGrid] instance. + * Gets an unsafe reference to the data of the [BrightnessGrid] instance. + * + * The returned memory is valid for the lifetime of the brightness grid. * * # Arguments * * - `brightness_grid`: instance to read from * * returns: slice of bytes underlying the `brightness_grid`. - * - * # Panics - * - * - when `brightness_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `brightness_grid` points to a valid [SPBrightnessGrid] - * - the returned memory range is never accessed after the passed [SPBrightnessGrid] has been freed - * - the returned memory range is never accessed concurrently, either via the [SPBrightnessGrid] or directly */ -SPByteSlice sp_brightness_grid_unsafe_data_ref(SPBrightnessGrid *brightness_grid); +ByteSlice sp_brightness_grid_unsafe_data_ref(BrightnessGrid */*notnull*/ brightness_grid); /** - * Gets the width of the [SPBrightnessGrid] instance. + * Gets the width of the [BrightnessGrid] instance. * * # Arguments * * - `brightness_grid`: instance to read from * * returns: width - * - * # Panics - * - * - when `brightness_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `brightness_grid` points to a valid [SPBrightnessGrid] */ -size_t sp_brightness_grid_width(const SPBrightnessGrid *brightness_grid); +size_t sp_brightness_grid_width(BrightnessGrid */*notnull*/ brightness_grid); /** - * Clones a [SPCharGrid]. - * - * Will never return NULL. - * - * # Panics - * - * - when `char_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `char_grid` points to a valid [SPCharGrid] - * - `char_grid` is not written to concurrently - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_char_grid_free`. + * Clones a [CharGrid]. */ -SPCharGrid *sp_char_grid_clone(const SPCharGrid *char_grid); +CharGrid */*notnull*/ sp_char_grid_clone(CharGrid */*notnull*/ char_grid); /** - * Sets the value of all cells in the [SPCharGrid]. + * Sets the value of all cells in the [CharGrid]. * * # Arguments * * - `char_grid`: instance to write to * - `value`: the value to set all cells to - * - * # Panics - * - * - when `char_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `char_grid` points to a valid [SPCharGrid] - * - `char_grid` is not written to or read from concurrently */ -void sp_char_grid_fill(SPCharGrid *char_grid, uint32_t value); +void sp_char_grid_fill(CharGrid */*notnull*/ char_grid, uint32_t value); /** - * Deallocates a [SPCharGrid]. - * - * # Panics - * - * - when `char_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `char_grid` points to a valid [SPCharGrid] - * - `char_grid` is not used concurrently or after char_grid call - * - `char_grid` was not passed to another consuming function, e.g. to create a [SPCommand] - * - * [SPCommand]: [crate::SPCommand] + * Deallocates a [CharGrid]. */ -void sp_char_grid_free(SPCharGrid *char_grid); +void sp_char_grid_free(CharGrid */*notnull*/ char_grid); /** - * Gets the current value at the specified position. + * Returns the current value at the specified position. * * # Arguments * @@ -968,79 +836,55 @@ void sp_char_grid_free(SPCharGrid *char_grid); * * # Panics * - * - when `char_grid` is NULL * - when accessing `x` or `y` out of bounds - * - * # Safety - * - * The caller has to make sure that: - * - * - `char_grid` points to a valid [SPCharGrid] - * - `char_grid` is not written to concurrently */ -uint32_t sp_char_grid_get(const SPCharGrid *char_grid, size_t x, size_t y); +uint32_t sp_char_grid_get(CharGrid */*notnull*/ char_grid, size_t x, size_t y); /** - * Gets the height of the [SPCharGrid] instance. + * Gets the height of the [CharGrid] instance. * * # Arguments * * - `char_grid`: instance to read from - * - * # Panics - * - * - when `char_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `char_grid` points to a valid [SPCharGrid] */ -size_t sp_char_grid_height(const SPCharGrid *char_grid); +size_t sp_char_grid_height(CharGrid */*notnull*/ char_grid); /** - * Loads a [SPCharGrid] with the specified dimensions from the provided data. + * Creates a [CharGridCommand] and immediately turns that into a [Packet]. * - * Will never return NULL. + * The provided [CharGrid] gets consumed. * - * # Panics - * - * - when `data` is NULL - * - when the provided `data_length` does not match `height` and `width` - * - when `data` is not valid UTF-8 - * - * # Safety - * - * The caller has to make sure that: - * - * - `data` points to a valid memory location of at least `data_length` - * bytes in size. - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_char_grid_free`. + * Returns NULL in case of an error. */ -SPCharGrid *sp_char_grid_load(size_t width, - size_t height, - const uint8_t *data, - size_t data_length); +Packet *sp_char_grid_into_packet(CharGrid */*notnull*/ grid, + size_t x, + size_t y); /** - * Creates a new [SPCharGrid] with the specified dimensions. + * Loads a [CharGrid] with the specified dimensions from the provided data. * - * returns: [SPCharGrid] initialized to 0. Will never return NULL. - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_char_grid_free`. + * returns: new CharGrid or NULL in case of an error */ -SPCharGrid *sp_char_grid_new(size_t width, - size_t height); +CharGrid *sp_char_grid_load(size_t width, size_t height, ByteSlice data); /** - * Sets the value of the specified position in the [SPCharGrid]. + * Creates a new [CharGrid] with the specified dimensions. + * + * returns: [CharGrid] initialized to 0. + * + * # Examples + * + * ```C + * CharGrid grid = sp_char_grid_new(4, 3); + * sp_char_grid_fill(grid, '?'); + * sp_char_grid_set(grid, 0, 0, '!'); + * sp_char_grid_free(grid); + * ``` + */ +CharGrid */*notnull*/ sp_char_grid_new(size_t width, size_t height); + +/** + * Sets the value of the specified position in the [CharGrid]. * * # Arguments * @@ -1052,41 +896,33 @@ SPCharGrid *sp_char_grid_new(size_t width, * * # Panics * - * - when `char_grid` is NULL * - when accessing `x` or `y` out of bounds - * - * # Safety - * - * The caller has to make sure that: - * - * - `char_grid` points to a valid [SPBitVec] - * - `char_grid` is not written to or read from concurrently - * - * [SPBitVec]: [crate::SPBitVec] */ -void sp_char_grid_set(SPCharGrid *char_grid, +void sp_char_grid_set(CharGrid */*notnull*/ char_grid, size_t x, size_t y, uint32_t value); /** - * Gets the width of the [SPCharGrid] instance. + * Gets the width of the [CharGrid] instance. * * # Arguments * * - `char_grid`: instance to read from - * - * # Panics - * - * - when `char_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `char_grid` points to a valid [SPCharGrid] */ -size_t sp_char_grid_width(const SPCharGrid *char_grid); +size_t sp_char_grid_width(CharGrid */*notnull*/ char_grid); + +/** + * Sets a window of pixels to the specified values. + * + * The passed [Bitmap] gets consumed. + * + * Returns: a new [servicepoint::BitmapCommand] instance. + */ +TypedCommand *sp_command_bitmap(size_t x, + size_t y, + Bitmap */*notnull*/ bitmap, + CompressionCode compression); /** * Set pixel data starting at the pixel offset on screen. @@ -1094,513 +930,137 @@ size_t sp_char_grid_width(const SPCharGrid *char_grid); * The screen will continuously overwrite more pixel data without regarding the offset, meaning * once the starting row is full, overwriting will continue on column 0. * - * The contained [SPBitVec] is always uncompressed. + * The [`BinaryOperation`] will be applied on the display comparing old and sent bit. * - * The passed [SPBitVec] gets consumed. + * `new_bit = old_bit op sent_bit` * - * Returns: a new [servicepoint::Command::BitmapLinear] instance. Will never return NULL. + * For example, [`BinaryOperation::Or`] can be used to turn on some pixels without affecting other pixels. * - * # Panics - * - * - when `bit_vec` is null - * - when `compression_code` is not a valid value - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid instance of [SPBitVec] - * - `bit_vec` is not used concurrently or after this call - * - `compression` matches one of the allowed enum values - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. + * The contained [`BitVecU8Msb0`] is always uncompressed. */ -SPCommand *sp_command_bitmap_linear(size_t offset, - SPBitVec *bit_vec, - SPCompressionCode compression); - -/** - * Set pixel data according to an and-mask starting at the offset. - * - * The screen will continuously overwrite more pixel data without regarding the offset, meaning - * once the starting row is full, overwriting will continue on column 0. - * - * The contained [SPBitVec] is always uncompressed. - * - * The passed [SPBitVec] gets consumed. - * - * Returns: a new [servicepoint::Command::BitmapLinearAnd] instance. Will never return NULL. - * - * # Panics - * - * - when `bit_vec` is null - * - when `compression_code` is not a valid value - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid instance of [SPBitVec] - * - `bit_vec` is not used concurrently or after this call - * - `compression` matches one of the allowed enum values - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. - */ -SPCommand *sp_command_bitmap_linear_and(size_t offset, - SPBitVec *bit_vec, - SPCompressionCode compression); - -/** - * Set pixel data according to an or-mask starting at the offset. - * - * The screen will continuously overwrite more pixel data without regarding the offset, meaning - * once the starting row is full, overwriting will continue on column 0. - * - * The contained [SPBitVec] is always uncompressed. - * - * The passed [SPBitVec] gets consumed. - * - * Returns: a new [servicepoint::Command::BitmapLinearOr] instance. Will never return NULL. - * - * # Panics - * - * - when `bit_vec` is null - * - when `compression_code` is not a valid value - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid instance of [SPBitVec] - * - `bit_vec` is not used concurrently or after this call - * - `compression` matches one of the allowed enum values - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. - */ -SPCommand *sp_command_bitmap_linear_or(size_t offset, - SPBitVec *bit_vec, - SPCompressionCode compression); - -/** - * Sets a window of pixels to the specified values. - * - * The passed [SPBitmap] gets consumed. - * - * Returns: a new [servicepoint::Command::BitmapLinearWin] instance. Will never return NULL. - * - * # Panics - * - * - when `bitmap` is null - * - when `compression_code` is not a valid value - * - * # Safety - * - * The caller has to make sure that: - * - * - `bitmap` points to a valid instance of [SPBitmap] - * - `bitmap` is not used concurrently or after this call - * - `compression` matches one of the allowed enum values - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. - */ -SPCommand *sp_command_bitmap_linear_win(size_t x, - size_t y, - SPBitmap *bitmap, - SPCompressionCode compression_code); - -/** - * Set pixel data according to a xor-mask starting at the offset. - * - * The screen will continuously overwrite more pixel data without regarding the offset, meaning - * once the starting row is full, overwriting will continue on column 0. - * - * The contained [SPBitVec] is always uncompressed. - * - * The passed [SPBitVec] gets consumed. - * - * Returns: a new [servicepoint::Command::BitmapLinearXor] instance. Will never return NULL. - * - * # Panics - * - * - when `bit_vec` is null - * - when `compression_code` is not a valid value - * - * # Safety - * - * The caller has to make sure that: - * - * - `bit_vec` points to a valid instance of [SPBitVec] - * - `bit_vec` is not used concurrently or after this call - * - `compression` matches one of the allowed enum values - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. - */ -SPCommand *sp_command_bitmap_linear_xor(size_t offset, - SPBitVec *bit_vec, - SPCompressionCode compression); - -/** - * Set the brightness of all tiles to the same value. - * - * Returns: a new [servicepoint::Command::Brightness] instance. Will never return NULL. - * - * # Panics - * - * - When the provided brightness value is out of range (0-11). - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. - */ -SPCommand *sp_command_brightness(uint8_t brightness); +TypedCommand *sp_command_bitvec(size_t offset, + SPBitVec */*notnull*/ bit_vec, + CompressionCode compression, + BinaryOperation operation); /** * Set the brightness of individual tiles in a rectangular area of the display. * - * The passed [SPBrightnessGrid] gets consumed. + * The passed [BrightnessGrid] gets consumed. * - * Returns: a new [servicepoint::Command::CharBrightness] instance. Will never return NULL. - * - * # Panics - * - * - when `grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `grid` points to a valid instance of [SPBrightnessGrid] - * - `grid` is not used concurrently or after this call - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. + * Returns: a new [servicepoint::Command::CharBrightness] instance. */ -SPCommand *sp_command_char_brightness(size_t x, - size_t y, - SPBrightnessGrid *grid); +TypedCommand */*notnull*/ sp_command_brightness_grid(size_t x, + size_t y, + BrightnessGrid */*notnull*/ grid); + +/** + * Show UTF-8 encoded text on the screen. + * + * The passed [CharGrid] gets consumed. + * + * Returns: a new [servicepoint::CharGridCommand] instance. + */ +TypedCommand */*notnull*/ sp_command_char_grid(size_t x, + size_t y, + CharGrid */*notnull*/ grid); /** * Set all pixels to the off state. * * Does not affect brightness. * - * Returns: a new [servicepoint::Command::Clear] instance. Will never return NULL. + * Returns: a new [servicepoint::Command::Clear] instance. * * # Examples * * ```C - * sp_connection_send_command(connection, sp_command_clear()); + * sp_udp_send_command(connection, sp_command_clear()); * ``` - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. */ -SPCommand *sp_command_clear(void); +TypedCommand */*notnull*/ sp_command_clear(void); /** - * Clones a [SPCommand] instance. + * Clones a [TypedCommand] instance. * - * returns: new [SPCommand] instance. Will never return NULL. - * - * # Panics - * - * - when `command` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `command` points to a valid instance of [SPCommand] - * - `command` is not written to concurrently - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. + * returns: new [TypedCommand] instance. */ -SPCommand *sp_command_clone(const SPCommand *command); +TypedCommand */*notnull*/ sp_command_clone(TypedCommand */*notnull*/ command); /** * Show codepage 437 encoded text on the screen. * - * The passed [SPCp437Grid] gets consumed. + * The passed [Cp437Grid] gets consumed. * - * Returns: a new [servicepoint::Command::Cp437Data] instance. Will never return NULL. - * - * # Panics - * - * - when `grid` is null - * - * # Safety - * - * The caller has to make sure that: - * - * - `grid` points to a valid instance of [SPCp437Grid] - * - `grid` is not used concurrently or after this call - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. + * Returns: a new [servicepoint::Cp437GridCommand] instance. */ -SPCommand *sp_command_cp437_data(size_t x, - size_t y, - SPCp437Grid *grid); +TypedCommand */*notnull*/ sp_command_cp437_grid(size_t x, + size_t y, + Cp437Grid */*notnull*/ grid); /** * A yet-to-be-tested command. * - * Returns: a new [servicepoint::Command::FadeOut] instance. Will never return NULL. - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. + * Returns: a new [servicepoint::Command::FadeOut] instance. */ -SPCommand *sp_command_fade_out(void); +TypedCommand */*notnull*/ sp_command_fade_out(void); /** - * Deallocates a [SPCommand]. + * Deallocates a [TypedCommand]. * * # Examples * * ```C - * SPCommand c = sp_command_clear(); + * TypedCommand c = sp_command_clear(); * sp_command_free(c); * ``` - * - * # Panics - * - * - when `command` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `command` points to a valid [SPCommand] - * - `command` is not used concurrently or after this call - * - `command` was not passed to another consuming function, e.g. to create a [SPPacket] */ -void sp_command_free(SPCommand *command); +void sp_command_free(TypedCommand */*notnull*/ command); + +/** + * Set the brightness of all tiles to the same value. + * + * Returns: a new [servicepoint::Command::Brightness] instance. + */ +TypedCommand */*notnull*/ sp_command_global_brightness(Brightness brightness); /** * Kills the udp daemon on the display, which usually results in a restart. * * Please do not send this in your normal program flow. * - * Returns: a new [servicepoint::Command::HardReset] instance. Will never return NULL. - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. + * Returns: a new [servicepoint::Command::HardReset] instance. */ -SPCommand *sp_command_hard_reset(void); +TypedCommand */*notnull*/ sp_command_hard_reset(void); /** - * Tries to turn a [SPPacket] into a [SPCommand]. + * Tries to turn a [Packet] into a [TypedCommand]. * * The packet is deallocated in the process. * - * Returns: pointer to new [SPCommand] instance or NULL if parsing failed. - * - * # Panics - * - * - when `packet` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - [SPPacket] points to a valid instance of [SPPacket] - * - [SPPacket] is not used concurrently or after this call - * - the result is checked for NULL - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. + * Returns: pointer to new [TypedCommand] instance or NULL if parsing failed. */ -SPCommand *sp_command_try_from_packet(SPPacket *packet); +TypedCommand *sp_command_try_from_packet(Packet */*notnull*/ packet); /** - * Show UTF-8 encoded text on the screen. - * - * The passed [SPCharGrid] gets consumed. - * - * Returns: a new [servicepoint::Command::Utf8Data] instance. Will never return NULL. - * - * # Panics - * - * - when `grid` is null - * - * # Safety - * - * The caller has to make sure that: - * - * - `grid` points to a valid instance of [SPCharGrid] - * - `grid` is not used concurrently or after this call - * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_command_free`. + * Clones a [Cp437Grid]. */ -SPCommand *sp_command_utf8_data(size_t x, - size_t y, - SPCharGrid *grid); +Cp437Grid */*notnull*/ sp_cp437_grid_clone(Cp437Grid */*notnull*/ cp437_grid); /** - * Creates a new instance of [SPConnection] for testing that does not actually send anything. - * - * returns: a new instance. Will never return NULL. - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_connection_free`. - */ -SPConnection *sp_connection_fake(void); - -/** - * Closes and deallocates a [SPConnection]. - * - * # Panics - * - * - when `connection` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `connection` points to a valid [SPConnection] - * - `connection` is not used concurrently or after this call - */ -void sp_connection_free(SPConnection *connection); - -/** - * Creates a new instance of [SPConnection]. - * - * returns: NULL if connection fails, or connected instance - * - * # Panics - * - * - when `host` is null or an invalid host - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_connection_free`. - */ -SPConnection *sp_connection_open(const char *host); - -/** - * Sends a [SPCommand] to the display using the [SPConnection]. - * - * The passed `command` gets consumed. - * - * returns: true in case of success - * - * # Panics - * - * - when `connection` is NULL - * - when `command` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `connection` points to a valid instance of [SPConnection] - * - `command` points to a valid instance of [SPPacket] - * - `command` is not used concurrently or after this call - */ -bool sp_connection_send_command(const SPConnection *connection, - SPCommand *command); - -/** - * Sends a [SPPacket] to the display using the [SPConnection]. - * - * The passed `packet` gets consumed. - * - * returns: true in case of success - * - * # Panics - * - * - when `connection` is NULL - * - when `packet` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `connection` points to a valid instance of [SPConnection] - * - `packet` points to a valid instance of [SPPacket] - * - `packet` is not used concurrently or after this call - */ -bool sp_connection_send_packet(const SPConnection *connection, - SPPacket *packet); - -/** - * Clones a [SPCp437Grid]. - * - * Will never return NULL. - * - * # Panics - * - * - when `cp437_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `cp437_grid` points to a valid [SPCp437Grid] - * - `cp437_grid` is not written to concurrently - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_cp437_grid_free`. - */ -SPCp437Grid *sp_cp437_grid_clone(const SPCp437Grid *cp437_grid); - -/** - * Sets the value of all cells in the [SPCp437Grid]. + * Sets the value of all cells in the [Cp437Grid]. * * # Arguments * * - `cp437_grid`: instance to write to * - `value`: the value to set all cells to - * - * # Panics - * - * - when `cp437_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `cp437_grid` points to a valid [SPCp437Grid] - * - `cp437_grid` is not written to or read from concurrently */ -void sp_cp437_grid_fill(SPCp437Grid *cp437_grid, uint8_t value); +void sp_cp437_grid_fill(Cp437Grid */*notnull*/ cp437_grid, uint8_t value); /** - * Deallocates a [SPCp437Grid]. - * - * # Panics - * - * - when `cp437_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `cp437_grid` points to a valid [SPCp437Grid] - * - `cp437_grid` is not used concurrently or after cp437_grid call - * - `cp437_grid` was not passed to another consuming function, e.g. to create a [SPCommand] - * - * [SPCommand]: [crate::SPCommand] + * Deallocates a [Cp437Grid]. */ -void sp_cp437_grid_free(SPCp437Grid *cp437_grid); +void sp_cp437_grid_free(Cp437Grid */*notnull*/ cp437_grid); /** * Gets the current value at the specified position. @@ -1612,78 +1072,46 @@ void sp_cp437_grid_free(SPCp437Grid *cp437_grid); * * # Panics * - * - when `cp437_grid` is NULL * - when accessing `x` or `y` out of bounds - * - * # Safety - * - * The caller has to make sure that: - * - * - `cp437_grid` points to a valid [SPCp437Grid] - * - `cp437_grid` is not written to concurrently */ -uint8_t sp_cp437_grid_get(const SPCp437Grid *cp437_grid, size_t x, size_t y); +uint8_t sp_cp437_grid_get(Cp437Grid */*notnull*/ cp437_grid, + size_t x, + size_t y); /** - * Gets the height of the [SPCp437Grid] instance. + * Gets the height of the [Cp437Grid] instance. * * # Arguments * * - `cp437_grid`: instance to read from - * - * # Panics - * - * - when `cp437_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `cp437_grid` points to a valid [SPCp437Grid] */ -size_t sp_cp437_grid_height(const SPCp437Grid *cp437_grid); +size_t sp_cp437_grid_height(Cp437Grid */*notnull*/ cp437_grid); /** - * Loads a [SPCp437Grid] with the specified dimensions from the provided data. + * Creates a [Cp437GridCommand] and immediately turns that into a [Packet]. * - * Will never return NULL. + * The provided [Cp437Grid] gets consumed. * - * # Panics - * - * - when `data` is NULL - * - when the provided `data_length` does not match `height` and `width` - * - * # Safety - * - * The caller has to make sure that: - * - * - `data` points to a valid memory location of at least `data_length` - * bytes in size. - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_cp437_grid_free`. + * Returns NULL in case of an error. */ -SPCp437Grid *sp_cp437_grid_load(size_t width, - size_t height, - const uint8_t *data, - size_t data_length); +Packet *sp_cp437_grid_into_packet(Cp437Grid */*notnull*/ grid, + size_t x, + size_t y); /** - * Creates a new [SPCp437Grid] with the specified dimensions. - * - * returns: [SPCp437Grid] initialized to 0. Will never return NULL. - * - * # Safety - * - * The caller has to make sure that: - * - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_cp437_grid_free`. + * Loads a [Cp437Grid] with the specified dimensions from the provided data. */ -SPCp437Grid *sp_cp437_grid_new(size_t width, - size_t height); +Cp437Grid *sp_cp437_grid_load(size_t width, size_t height, ByteSlice data); /** - * Sets the value of the specified position in the [SPCp437Grid]. + * Creates a new [Cp437Grid] with the specified dimensions. + * + * returns: [Cp437Grid] initialized to 0. + */ +Cp437Grid */*notnull*/ sp_cp437_grid_new(size_t width, size_t height); + +/** + * Sets the value of the specified position in the [Cp437Grid]. * * # Arguments * @@ -1695,172 +1123,178 @@ SPCp437Grid *sp_cp437_grid_new(size_t width, * * # Panics * - * - when `cp437_grid` is NULL * - when accessing `x` or `y` out of bounds - * - * # Safety - * - * The caller has to make sure that: - * - * - `cp437_grid` points to a valid [SPBitVec] - * - `cp437_grid` is not written to or read from concurrently - * - * [SPBitVec]: [crate::SPBitVec] */ -void sp_cp437_grid_set(SPCp437Grid *cp437_grid, +void sp_cp437_grid_set(Cp437Grid */*notnull*/ cp437_grid, size_t x, size_t y, uint8_t value); /** - * Gets an unsafe reference to the data of the [SPCp437Grid] instance. + * Gets an unsafe reference to the data of the [Cp437Grid] instance. * - * Will never return NULL. - * - * # Panics - * - * - when `cp437_grid` is NULL - * - * ## Safety - * - * The caller has to make sure that: - * - * - `cp437_grid` points to a valid [SPCp437Grid] - * - the returned memory range is never accessed after the passed [SPCp437Grid] has been freed - * - the returned memory range is never accessed concurrently, either via the [SPCp437Grid] or directly + * The returned memory is valid for the lifetime of the grid. */ -SPByteSlice sp_cp437_grid_unsafe_data_ref(SPCp437Grid *cp437_grid); +ByteSlice sp_cp437_grid_unsafe_data_ref(Cp437Grid */*notnull*/ cp437_grid); /** - * Gets the width of the [SPCp437Grid] instance. + * Gets the width of the [Cp437Grid] instance. * * # Arguments * * - `cp437_grid`: instance to read from - * - * # Panics - * - * - when `cp437_grid` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `cp437_grid` points to a valid [SPCp437Grid] */ -size_t sp_cp437_grid_width(const SPCp437Grid *cp437_grid); +size_t sp_cp437_grid_width(Cp437Grid */*notnull*/ cp437_grid); /** - * Clones a [SPPacket]. - * - * Will never return NULL. - * - * # Panics - * - * - when `packet` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `packet` points to a valid [SPPacket] - * - `packet` is not written to concurrently - * - the returned instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_packet_free`. + * Clones a [Packet]. */ -SPPacket *sp_packet_clone(const SPPacket *packet); +Packet */*notnull*/ sp_packet_clone(Packet */*notnull*/ packet); /** - * Deallocates a [SPPacket]. - * - * # Panics - * - * - when `packet` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `packet` points to a valid [SPPacket] - * - `packet` is not used concurrently or after this call + * Deallocates a [Packet]. */ -void sp_packet_free(SPPacket *packet); +void sp_packet_free(Packet */*notnull*/ packet); /** - * Turns a [SPCommand] into a [SPPacket]. - * The [SPCommand] gets consumed. + * Turns a [TypedCommand] into a [Packet]. + * The [TypedCommand] gets consumed. * - * Will never return NULL. - * - * # Panics - * - * - when `command` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - [SPCommand] points to a valid instance of [SPCommand] - * - [SPCommand] is not used concurrently or after this call - * - the returned [SPPacket] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_packet_free`. + * Returns NULL in case of an error. */ -SPPacket *sp_packet_from_command(SPCommand *command); +Packet *sp_packet_from_command(TypedCommand */*notnull*/ command); /** - * Creates a raw [SPPacket] from parts. - * - * # Arguments - * - * - `command_code` specifies which command this packet contains - * - `a`, `b`, `c` and `d` are command-specific header values - * - `payload` is the optional data that is part of the command - * - `payload_len` is the size of the payload + * Creates a raw [Packet] from parts. * * returns: new instance. Will never return null. - * - * # Panics - * - * - when `payload` is null, but `payload_len` is not zero - * - when `payload_len` is zero, but `payload` is nonnull - * - * # Safety - * - * The caller has to make sure that: - * - * - `payload` points to a valid memory region of at least `payload_len` bytes - * - `payload` is not written to concurrently - * - the returned [SPPacket] instance is freed in some way, either by using a consuming function or - * by explicitly calling [sp_packet_free]. */ -SPPacket *sp_packet_from_parts(uint16_t command_code, - uint16_t a, - uint16_t b, - uint16_t c, - uint16_t d, - const uint8_t *payload, - size_t payload_len); +Packet */*notnull*/ sp_packet_from_parts(Header header, + const ByteSlice *payload); /** - * Tries to load a [SPPacket] from the passed array with the specified length. + * Returns a pointer to the header field of the provided packet. * - * returns: NULL in case of an error, pointer to the allocated packet otherwise + * The returned header can be changed and will be valid for the lifetime of the packet. + */ +Header */*notnull*/ sp_packet_get_header(Packet */*notnull*/ packet); + +/** + * Returns a pointer to the current payload of the provided packet. + * + * The returned memory can be changed and will be valid until a new payload is set. + */ +ByteSlice sp_packet_get_payload(Packet */*notnull*/ packet); + +/** + * Serialize the packet into the provided buffer. * * # Panics * - * - when `data` is NULL - * - * # Safety - * - * The caller has to make sure that: - * - * - `data` points to a valid memory region of at least `length` bytes - * - `data` is not written to concurrently - * - the returned [SPPacket] instance is freed in some way, either by using a consuming function or - * by explicitly calling `sp_packet_free`. + * - if the buffer is not big enough to hold header+payload. */ -SPPacket *sp_packet_try_load(const uint8_t *data, - size_t length); +void sp_packet_serialize_to(Packet */*notnull*/ packet, ByteSlice buffer); + +/** + * Sets the payload of the provided packet to the provided data. + * + * This makes previous payload pointers invalid. + */ +void sp_packet_set_payload(Packet */*notnull*/ packet, ByteSlice data); + +/** + * Tries to load a [Packet] from the passed array with the specified length. + * + * returns: NULL in case of an error, pointer to the allocated packet otherwise + */ +Packet *sp_packet_try_load(ByteSlice data); + +/** + * Converts u16 into [CommandCode]. + * + * If the provided value is not valid, false is returned and result is not changed. + */ +bool sp_u16_to_command_code(uint16_t code, + CommandCode *result); + +/** + * Closes and deallocates a [UdpConnection]. + */ +void sp_udp_free(UdpConnection */*notnull*/ connection); + +/** + * Creates a new instance of [UdpConnection]. + * + * returns: NULL if connection fails, or connected instance + * + * # Examples + * + * ```C + * UdpConnection connection = sp_udp_open("172.23.42.29:2342"); + * if (connection != NULL) + * sp_udp_send_command(connection, sp_command_clear()); + * ``` + */ +UdpConnection *sp_udp_open(char */*notnull*/ host); + +/** + * Creates a new instance of [UdpConnection]. + * + * returns: NULL if connection fails, or connected instance + * + * # Examples + * + * ```C + * UdpConnection connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); + * if (connection != NULL) + * sp_udp_send_command(connection, sp_command_clear()); + * ``` + */ +UdpConnection *sp_udp_open_ipv4(uint8_t ip1, + uint8_t ip2, + uint8_t ip3, + uint8_t ip4, + uint16_t port); + +/** + * Sends a [TypedCommand] to the display using the [UdpConnection]. + * + * The passed `command` gets consumed. + * + * returns: true in case of success + * + * # Examples + * + * ```C + * sp_udp_send_command(connection, sp_command_brightness(5)); + * ``` + */ +bool sp_udp_send_command(UdpConnection */*notnull*/ connection, + TypedCommand */*notnull*/ command); + +/** + * Sends a [Header] to the display using the [UdpConnection]. + * + * returns: true in case of success + * + * # Examples + * + * ```C + * sp_udp_send_header(connection, sp_command_brightness(5)); + * ``` + */ +bool sp_udp_send_header(UdpConnection */*notnull*/ udp_connection, + Header header); + +/** + * Sends a [Packet] to the display using the [UdpConnection]. + * + * The passed `packet` gets consumed. + * + * returns: true in case of success + */ +bool sp_udp_send_packet(UdpConnection */*notnull*/ connection, + Packet */*notnull*/ packet); #ifdef __cplusplus } // extern "C" diff --git a/src/bitmap.rs b/src/bitmap.rs index 3313385..358be48 100644 --- a/src/bitmap.rs +++ b/src/bitmap.rs @@ -1,13 +1,22 @@ -//! C functions for interacting with [SPBitmap]s -//! -//! prefix `sp_bitmap_` - -use servicepoint::{DataRef, Grid}; +use crate::byte_slice::ByteSlice; +use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, SPBitVec}; +use servicepoint::{Bitmap, BitmapCommand, CompressionCode, DataRef, Grid, Origin, Packet}; use std::ptr::NonNull; -use crate::byte_slice::SPByteSlice; - -/// A grid of pixels. +/// Creates a new [Bitmap] with the specified dimensions. +/// +/// # Arguments +/// +/// - `width`: size in pixels in x-direction +/// - `height`: size in pixels in y-direction +/// +/// returns: [Bitmap] initialized to all pixels off, or NULL in case of an error. +/// +/// # Errors +/// +/// In the following cases, this function will return NULL: +/// +/// - when the width is not dividable by 8 /// /// # Examples /// @@ -17,135 +26,81 @@ use crate::byte_slice::SPByteSlice; /// sp_bitmap_set(grid, 0, 0, false); /// sp_bitmap_free(grid); /// ``` -pub struct SPBitmap(pub(crate) servicepoint::Bitmap); - -/// Creates a new [SPBitmap] with the specified dimensions. -/// -/// # Arguments -/// -/// - `width`: size in pixels in x-direction -/// - `height`: size in pixels in y-direction -/// -/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL. -/// -/// # Panics -/// -/// - when the width is not dividable by 8 -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_bitmap_free`. #[no_mangle] pub unsafe extern "C" fn sp_bitmap_new( width: usize, height: usize, -) -> NonNull { - let result = Box::new(SPBitmap(servicepoint::Bitmap::new(width, height))); - NonNull::from(Box::leak(result)) +) -> *mut Bitmap { + if let Some(bitmap) = Bitmap::new(width, height) { + heap_move(bitmap) + } else { + std::ptr::null_mut() + } } -/// Creates a new [SPBitmap] with a size matching the screen. +/// Creates a new [Bitmap] with a size matching the screen. /// -/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL. -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling [sp_bitmap_free]. +/// returns: [Bitmap] initialized to all pixels off. #[no_mangle] -pub unsafe extern "C" fn sp_bitmap_new_screen_sized() -> NonNull { - let result = Box::new(SPBitmap(servicepoint::Bitmap::max_sized())); - NonNull::from(Box::leak(result)) +pub unsafe extern "C" fn sp_bitmap_new_max_sized() -> NonNull { + heap_move_nonnull(Bitmap::max_sized()) } -/// Loads a [SPBitmap] with the specified dimensions from the provided data. +/// Loads a [Bitmap] with the specified dimensions from the provided data. /// /// # Arguments /// /// - `width`: size in pixels in x-direction /// - `height`: size in pixels in y-direction /// -/// returns: [SPBitmap] that contains a copy of the provided data. Will never return NULL. -/// -/// # Panics -/// -/// - when `data` is NULL -/// - when the dimensions and data size do not match exactly. -/// - when the width is not dividable by 8 -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `data` points to a valid memory location of at least `data_length` bytes in size. -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_bitmap_free`. +/// returns: [Bitmap] that contains a copy of the provided data, or NULL in case of an error. #[no_mangle] pub unsafe extern "C" fn sp_bitmap_load( width: usize, height: usize, - data: *const u8, - data_length: usize, -) -> NonNull { - assert!(!data.is_null()); - let data = std::slice::from_raw_parts(data, data_length); - let result = - Box::new(SPBitmap(servicepoint::Bitmap::load(width, height, data))); - NonNull::from(Box::leak(result)) + data: ByteSlice, +) -> *mut Bitmap { + let data = unsafe { data.as_slice() }; + if let Ok(bitmap) = Bitmap::load(width, height, data) { + heap_move(bitmap) + } else { + std::ptr::null_mut() + } } -/// Clones a [SPBitmap]. +/// Tries to convert the BitVec to a Bitmap. /// -/// Will never return NULL. +/// The provided BitVec gets consumed. /// -/// # Panics -/// -/// - when `bitmap` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bitmap` points to a valid [SPBitmap] -/// - `bitmap` is not written to concurrently -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_bitmap_free`. +/// Returns NULL in case of error. +#[no_mangle] +pub unsafe extern "C" fn sp_bitmap_from_bitvec( + width: usize, + bitvec: NonNull, +) -> *mut Bitmap { + let bitvec = unsafe { heap_remove(bitvec) }; + if let Ok(bitmap) = Bitmap::from_bitvec(width, bitvec.0) { + heap_move(bitmap) + } else { + std::ptr::null_mut() + } +} + +/// Clones a [Bitmap]. #[no_mangle] pub unsafe extern "C" fn sp_bitmap_clone( - bitmap: *const SPBitmap, -) -> NonNull { - assert!(!bitmap.is_null()); - let result = Box::new(SPBitmap((*bitmap).0.clone())); - NonNull::from(Box::leak(result)) + bitmap: NonNull, +) -> NonNull { + heap_move_nonnull(unsafe { bitmap.as_ref().clone() }) } -/// Deallocates a [SPBitmap]. -/// -/// # Panics -/// -/// - when `bitmap` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bitmap` points to a valid [SPBitmap] -/// - `bitmap` is not used concurrently or after bitmap call -/// - `bitmap` was not passed to another consuming function, e.g. to create a [SPCommand] -/// -/// [SPCommand]: [crate::SPCommand] +/// Deallocates a [Bitmap]. #[no_mangle] -pub unsafe extern "C" fn sp_bitmap_free(bitmap: *mut SPBitmap) { - assert!(!bitmap.is_null()); - _ = Box::from_raw(bitmap); +pub unsafe extern "C" fn sp_bitmap_free(bitmap: NonNull) { + unsafe { heap_drop(bitmap) } } -/// Gets the current value at the specified position in the [SPBitmap]. +/// Gets the current value at the specified position in the [Bitmap]. /// /// # Arguments /// @@ -154,26 +109,17 @@ pub unsafe extern "C" fn sp_bitmap_free(bitmap: *mut SPBitmap) { /// /// # Panics /// -/// - when `bitmap` is NULL /// - when accessing `x` or `y` out of bounds -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bitmap` points to a valid [SPBitmap] -/// - `bitmap` is not written to concurrently #[no_mangle] pub unsafe extern "C" fn sp_bitmap_get( - bitmap: *const SPBitmap, + bitmap: NonNull, x: usize, y: usize, ) -> bool { - assert!(!bitmap.is_null()); - (*bitmap).0.get(x, y) + unsafe { bitmap.as_ref().get(x, y) } } -/// Sets the value of the specified position in the [SPBitmap]. +/// Sets the value of the specified position in the [Bitmap]. /// /// # Arguments /// @@ -181,54 +127,31 @@ pub unsafe extern "C" fn sp_bitmap_get( /// - `x` and `y`: position of the cell /// - `value`: the value to write to the cell /// -/// returns: old value of the cell -/// /// # Panics /// -/// - when `bitmap` is NULL /// - when accessing `x` or `y` out of bounds -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bitmap` points to a valid [SPBitmap] -/// - `bitmap` is not written to or read from concurrently #[no_mangle] pub unsafe extern "C" fn sp_bitmap_set( - bitmap: *mut SPBitmap, + bitmap: NonNull, x: usize, y: usize, value: bool, ) { - assert!(!bitmap.is_null()); - (*bitmap).0.set(x, y, value); + unsafe { (*bitmap.as_ptr()).set(x, y, value) }; } -/// Sets the state of all pixels in the [SPBitmap]. +/// Sets the state of all pixels in the [Bitmap]. /// /// # Arguments /// /// - `bitmap`: instance to write to /// - `value`: the value to set all pixels to -/// -/// # Panics -/// -/// - when `bitmap` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bitmap` points to a valid [SPBitmap] -/// - `bitmap` is not written to or read from concurrently #[no_mangle] -pub unsafe extern "C" fn sp_bitmap_fill(bitmap: *mut SPBitmap, value: bool) { - assert!(!bitmap.is_null()); - (*bitmap).0.fill(value); +pub unsafe extern "C" fn sp_bitmap_fill(bitmap: NonNull, value: bool) { + unsafe { (*bitmap.as_ptr()).fill(value) }; } -/// Gets the width in pixels of the [SPBitmap] instance. +/// Gets the width in pixels of the [Bitmap] instance. /// /// # Arguments /// @@ -242,55 +165,60 @@ pub unsafe extern "C" fn sp_bitmap_fill(bitmap: *mut SPBitmap, value: bool) { /// /// The caller has to make sure that: /// -/// - `bitmap` points to a valid [SPBitmap] +/// - `bitmap` points to a valid [Bitmap] #[no_mangle] -pub unsafe extern "C" fn sp_bitmap_width(bitmap: *const SPBitmap) -> usize { - assert!(!bitmap.is_null()); - (*bitmap).0.width() +pub unsafe extern "C" fn sp_bitmap_width(bitmap: NonNull) -> usize { + unsafe { bitmap.as_ref().width() } } -/// Gets the height in pixels of the [SPBitmap] instance. +/// Gets the height in pixels of the [Bitmap] instance. /// /// # Arguments /// /// - `bitmap`: instance to read from -/// -/// # Panics -/// -/// - when `bitmap` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bitmap` points to a valid [SPBitmap] #[no_mangle] -pub unsafe extern "C" fn sp_bitmap_height(bitmap: *const SPBitmap) -> usize { - assert!(!bitmap.is_null()); - (*bitmap).0.height() +pub unsafe extern "C" fn sp_bitmap_height(bitmap: NonNull) -> usize { + unsafe { bitmap.as_ref().height() } } -/// Gets an unsafe reference to the data of the [SPBitmap] instance. +/// Gets an unsafe reference to the data of the [Bitmap] instance. /// -/// # Panics -/// -/// - when `bitmap` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bitmap` points to a valid [SPBitmap] -/// - the returned memory range is never accessed after the passed [SPBitmap] has been freed -/// - the returned memory range is never accessed concurrently, either via the [SPBitmap] or directly +/// The returned memory is valid for the lifetime of the bitmap. #[no_mangle] pub unsafe extern "C" fn sp_bitmap_unsafe_data_ref( - bitmap: *mut SPBitmap, -) -> SPByteSlice { - assert!(!bitmap.is_null()); - let data = (*bitmap).0.data_ref_mut(); - SPByteSlice { - start: NonNull::new(data.as_mut_ptr_range().start).unwrap(), - length: data.len(), + mut bitmap: NonNull, +) -> ByteSlice { + unsafe { ByteSlice::from_slice(bitmap.as_mut().data_ref_mut()) } +} + +/// Consumes the Bitmap and returns the contained BitVec +#[no_mangle] +pub unsafe extern "C" fn sp_bitmap_into_bitvec( + bitmap: NonNull, +) -> NonNull { + let bitmap = unsafe { heap_remove(bitmap) }; + heap_move_nonnull(SPBitVec(bitmap.into())) +} + +/// Creates a [BitmapCommand] and immediately turns that into a [Packet]. +/// +/// The provided [Bitmap] gets consumed. +/// +/// Returns NULL in case of an error. +#[no_mangle] +pub unsafe extern "C" fn sp_bitmap_into_packet( + bitmap: NonNull, + x: usize, + y: usize, + compression: CompressionCode +) -> *mut Packet { + let bitmap = unsafe { heap_remove(bitmap) }; + match Packet::try_from(BitmapCommand { + bitmap, + origin: Origin::new(x, y), + compression, + }) { + Ok(packet) => heap_move(packet), + Err(_) => std::ptr::null_mut(), } } diff --git a/src/bitvec.rs b/src/bitvec.rs index 484e849..22f7545 100644 --- a/src/bitvec.rs +++ b/src/bitvec.rs @@ -1,8 +1,5 @@ -//! C functions for interacting with [SPBitVec]s -//! -//! prefix `sp_bitvec_` - -use crate::SPByteSlice; +use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; +use servicepoint::{BinaryOperation, BitVecCommand, BitVecU8Msb0, CompressionCode, Packet}; use std::ptr::NonNull; /// A vector of bits @@ -13,19 +10,7 @@ use std::ptr::NonNull; /// sp_bitvec_set(vec, 5, true); /// sp_bitvec_free(vec); /// ``` -pub struct SPBitVec(servicepoint::BitVec); - -impl From for SPBitVec { - fn from(actual: servicepoint::BitVec) -> Self { - Self(actual) - } -} - -impl From for servicepoint::BitVec { - fn from(value: SPBitVec) -> Self { - value.0 - } -} +pub struct SPBitVec(pub(crate) BitVecU8Msb0); impl Clone for SPBitVec { fn clone(&self) -> Self { @@ -39,95 +24,37 @@ impl Clone for SPBitVec { /// /// - `size`: size in bits. /// -/// returns: [SPBitVec] with all bits set to false. Will never return NULL. +/// returns: [SPBitVec] with all bits set to false. /// /// # Panics /// /// - when `size` is not divisible by 8. -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_bitvec_free`. #[no_mangle] pub unsafe extern "C" fn sp_bitvec_new(size: usize) -> NonNull { - let result = Box::new(SPBitVec(servicepoint::BitVec::repeat(false, size))); - NonNull::from(Box::leak(result)) + heap_move_nonnull(SPBitVec(BitVecU8Msb0::repeat(false, size))) } /// Interpret the data as a series of bits and load then into a new [SPBitVec] instance. /// -/// returns: [SPBitVec] instance containing data. Will never return NULL. -/// -/// # Panics -/// -/// - when `data` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `data` points to a valid memory location of at least `data_length` -/// bytes in size. -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_bitvec_free`. +/// returns: [SPBitVec] instance containing data. #[no_mangle] -pub unsafe extern "C" fn sp_bitvec_load( - data: *const u8, - data_length: usize, -) -> NonNull { - assert!(!data.is_null()); - let data = std::slice::from_raw_parts(data, data_length); - let result = Box::new(SPBitVec(servicepoint::BitVec::from_slice(data))); - NonNull::from(Box::leak(result)) +pub unsafe extern "C" fn sp_bitvec_load(data: ByteSlice) -> NonNull { + let data = unsafe { data.as_slice() }; + heap_move_nonnull(SPBitVec(BitVecU8Msb0::from_slice(data))) } /// Clones a [SPBitVec]. -/// -/// returns: new [SPBitVec] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `bit_vec` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid [SPBitVec] -/// - `bit_vec` is not written to concurrently -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_bitvec_free`. #[no_mangle] pub unsafe extern "C" fn sp_bitvec_clone( - bit_vec: *const SPBitVec, + bit_vec: NonNull, ) -> NonNull { - assert!(!bit_vec.is_null()); - let result = Box::new((*bit_vec).clone()); - NonNull::from(Box::leak(result)) + heap_move_nonnull(unsafe { bit_vec.as_ref().clone() }) } /// Deallocates a [SPBitVec]. -/// -/// # Panics -/// -/// - when `but_vec` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid [SPBitVec] -/// - `bit_vec` is not used concurrently or after this call -/// - `bit_vec` was not passed to another consuming function, e.g. to create a [SPCommand] -/// -/// [SPCommand]: [crate::SPCommand] #[no_mangle] -pub unsafe extern "C" fn sp_bitvec_free(bit_vec: *mut SPBitVec) { - assert!(!bit_vec.is_null()); - _ = Box::from_raw(bit_vec); +pub unsafe extern "C" fn sp_bitvec_free(bit_vec: NonNull) { + unsafe { heap_drop(bit_vec) } } /// Gets the value of a bit from the [SPBitVec]. @@ -141,22 +68,13 @@ pub unsafe extern "C" fn sp_bitvec_free(bit_vec: *mut SPBitVec) { /// /// # Panics /// -/// - when `bit_vec` is NULL /// - when accessing `index` out of bounds -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid [SPBitVec] -/// - `bit_vec` is not written to concurrently #[no_mangle] pub unsafe extern "C" fn sp_bitvec_get( - bit_vec: *const SPBitVec, + bit_vec: NonNull, index: usize, ) -> bool { - assert!(!bit_vec.is_null()); - *(*bit_vec).0.get(index).unwrap() + unsafe { *bit_vec.as_ref().0.get(index).unwrap() } } /// Sets the value of a bit in the [SPBitVec]. @@ -169,23 +87,14 @@ pub unsafe extern "C" fn sp_bitvec_get( /// /// # Panics /// -/// - when `bit_vec` is NULL /// - when accessing `index` out of bounds -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid [SPBitVec] -/// - `bit_vec` is not written to or read from concurrently #[no_mangle] pub unsafe extern "C" fn sp_bitvec_set( - bit_vec: *mut SPBitVec, + bit_vec: NonNull, index: usize, value: bool, ) { - assert!(!bit_vec.is_null()); - (*bit_vec).0.set(index, value) + unsafe { (*bit_vec.as_ptr()).0.set(index, value) } } /// Sets the value of all bits in the [SPBitVec]. @@ -194,21 +103,12 @@ pub unsafe extern "C" fn sp_bitvec_set( /// /// - `bit_vec`: instance to write to /// - `value`: the value to set all bits to -/// -/// # Panics -/// -/// - when `bit_vec` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid [SPBitVec] -/// - `bit_vec` is not written to or read from concurrently #[no_mangle] -pub unsafe extern "C" fn sp_bitvec_fill(bit_vec: *mut SPBitVec, value: bool) { - assert!(!bit_vec.is_null()); - (*bit_vec).0.fill(value) +pub unsafe extern "C" fn sp_bitvec_fill( + bit_vec: NonNull, + value: bool, +) { + unsafe { (*bit_vec.as_ptr()).0.fill(value) } } /// Gets the length of the [SPBitVec] in bits. @@ -216,20 +116,9 @@ pub unsafe extern "C" fn sp_bitvec_fill(bit_vec: *mut SPBitVec, value: bool) { /// # Arguments /// /// - `bit_vec`: instance to write to -/// -/// # Panics -/// -/// - when `bit_vec` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid [SPBitVec] #[no_mangle] -pub unsafe extern "C" fn sp_bitvec_len(bit_vec: *const SPBitVec) -> usize { - assert!(!bit_vec.is_null()); - (*bit_vec).0.len() +pub unsafe extern "C" fn sp_bitvec_len(bit_vec: NonNull) -> usize { + unsafe { bit_vec.as_ref().0.len() } } /// Returns true if length is 0. @@ -237,47 +126,47 @@ pub unsafe extern "C" fn sp_bitvec_len(bit_vec: *const SPBitVec) -> usize { /// # Arguments /// /// - `bit_vec`: instance to write to -/// -/// # Panics -/// -/// - when `bit_vec` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid [SPBitVec] #[no_mangle] -pub unsafe extern "C" fn sp_bitvec_is_empty(bit_vec: *const SPBitVec) -> bool { - assert!(!bit_vec.is_null()); - (*bit_vec).0.is_empty() +pub unsafe extern "C" fn sp_bitvec_is_empty( + bit_vec: NonNull, +) -> bool { + unsafe { bit_vec.as_ref().0.is_empty() } } /// Gets an unsafe reference to the data of the [SPBitVec] instance. /// +/// The returned memory is valid for the lifetime of the bitvec. +/// /// # Arguments /// /// - `bit_vec`: instance to write to -/// -/// # Panics -/// -/// - when `bit_vec` is NULL -/// -/// ## Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid [SPBitVec] -/// - the returned memory range is never accessed after the passed [SPBitVec] has been freed -/// - the returned memory range is never accessed concurrently, either via the [SPBitVec] or directly #[no_mangle] pub unsafe extern "C" fn sp_bitvec_unsafe_data_ref( - bit_vec: *mut SPBitVec, -) -> SPByteSlice { - assert!(!bit_vec.is_null()); - let data = (*bit_vec).0.as_raw_mut_slice(); - SPByteSlice { - start: NonNull::new(data.as_mut_ptr_range().start).unwrap(), - length: data.len(), + bit_vec: NonNull, +) -> ByteSlice { + unsafe { ByteSlice::from_slice((*bit_vec.as_ptr()).0.as_raw_mut_slice()) } +} + +/// Creates a [BitVecCommand] and immediately turns that into a [Packet]. +/// +/// The provided [SPBitVec] gets consumed. +/// +/// Returns NULL in case of an error. +#[no_mangle] +pub unsafe extern "C" fn sp_bitvec_into_packet( + bitvec: NonNull, + offset: usize, + operation: BinaryOperation, + compression: CompressionCode +) -> *mut Packet { + let bitvec = unsafe { heap_remove(bitvec) }.0; + match Packet::try_from(BitVecCommand { + bitvec, + offset, + operation, + compression, + }) { + Ok(packet) => heap_move(packet), + Err(_) => std::ptr::null_mut(), } } diff --git a/src/brightness_grid.rs b/src/brightness_grid.rs index 83af008..1099be0 100644 --- a/src/brightness_grid.rs +++ b/src/brightness_grid.rs @@ -1,146 +1,71 @@ -//! C functions for interacting with [SPBrightnessGrid]s -//! -//! prefix `sp_brightness_grid_` - -use crate::SPByteSlice; -use servicepoint::{DataRef, Grid}; -use std::convert::Into; -use std::intrinsics::transmute; +use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; +use servicepoint::{ + Brightness, BrightnessGrid, BrightnessGridCommand, ByteGrid, DataRef, Grid, + Origin, Packet, +}; +use std::mem::transmute; use std::ptr::NonNull; -/// see [servicepoint::Brightness::MIN] -pub const SP_BRIGHTNESS_MIN: u8 = 0; -/// see [servicepoint::Brightness::MAX] -pub const SP_BRIGHTNESS_MAX: u8 = 11; -/// Count of possible brightness values -pub const SP_BRIGHTNESS_LEVELS: u8 = 12; - -/// A grid containing brightness values. +/// Creates a new [BrightnessGrid] with the specified dimensions. +/// +/// returns: [BrightnessGrid] initialized to 0. /// /// # Examples /// ```C -/// SPConnection connection = sp_connection_open("127.0.0.1:2342"); +/// UdpConnection connection = sp_udp_open("127.0.0.1:2342"); /// if (connection == NULL) /// return 1; /// -/// SPBrightnessGrid grid = sp_brightness_grid_new(2, 2); +/// BrightnessGrid grid = sp_brightness_grid_new(2, 2); /// sp_brightness_grid_set(grid, 0, 0, 0); /// sp_brightness_grid_set(grid, 1, 1, 10); /// -/// SPCommand command = sp_command_char_brightness(grid); -/// sp_connection_free(connection); +/// TypedCommand command = sp_command_char_brightness(grid); +/// sp_udp_free(connection); /// ``` -#[derive(Clone)] -pub struct SPBrightnessGrid(pub(crate) servicepoint::BrightnessGrid); - -/// Creates a new [SPBrightnessGrid] with the specified dimensions. -/// -/// returns: [SPBrightnessGrid] initialized to 0. Will never return NULL. -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_brightness_grid_free`. #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_new( width: usize, height: usize, -) -> NonNull { - let result = Box::new(SPBrightnessGrid(servicepoint::BrightnessGrid::new( - width, height, - ))); - NonNull::from(Box::leak(result)) +) -> NonNull { + heap_move_nonnull(BrightnessGrid::new(width, height)) } -/// Loads a [SPBrightnessGrid] with the specified dimensions from the provided data. +/// Loads a [BrightnessGrid] with the specified dimensions from the provided data. /// -/// returns: new [SPBrightnessGrid] instance. Will never return NULL. +/// Any out of range values will be set to [Brightness::MAX] or [Brightness::MIN]. /// -/// # Panics -/// -/// - when `data` is NULL -/// - when the provided `data_length` does not match `height` and `width` -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `data` points to a valid memory location of at least `data_length` -/// bytes in size. -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_brightness_grid_free`. +/// returns: new [BrightnessGrid] instance, or NULL in case of an error. #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_load( width: usize, height: usize, - data: *const u8, - data_length: usize, -) -> NonNull { - assert!(!data.is_null()); - let data = std::slice::from_raw_parts(data, data_length); - let grid = servicepoint::ByteGrid::load(width, height, data); - let grid = servicepoint::BrightnessGrid::try_from(grid) - .expect("invalid brightness value"); - let result = Box::new(SPBrightnessGrid(grid)); - NonNull::from(Box::leak(result)) + data: ByteSlice, +) -> *mut BrightnessGrid { + let data = unsafe { data.as_slice() }; + + match ByteGrid::load(width, height, data) + .map(move |grid| grid.map(Brightness::saturating_from)) + { + None => std::ptr::null_mut(), + Some(grid) => heap_move(grid), + } } -/// Clones a [SPBrightnessGrid]. -/// -/// # Arguments -/// -/// - `brightness_grid`: instance to read from -/// -/// returns: new [SPBrightnessGrid] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `brightness_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `brightness_grid` points to a valid [SPBrightnessGrid] -/// - `brightness_grid` is not written to concurrently -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_brightness_grid_free`. +/// Clones a [BrightnessGrid]. #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_clone( - brightness_grid: *const SPBrightnessGrid, -) -> NonNull { - assert!(!brightness_grid.is_null()); - let result = Box::new((*brightness_grid).clone()); - NonNull::from(Box::leak(result)) + brightness_grid: NonNull, +) -> NonNull { + heap_move_nonnull(unsafe { brightness_grid.as_ref().clone() }) } -/// Deallocates a [SPBrightnessGrid]. -/// -/// # Arguments -/// -/// - `brightness_grid`: instance to read from -/// -/// # Panics -/// -/// - when `brightness_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `brightness_grid` points to a valid [SPBrightnessGrid] -/// - `brightness_grid` is not used concurrently or after this call -/// - `brightness_grid` was not passed to another consuming function, e.g. to create a [SPCommand] -/// -/// [SPCommand]: [crate::SPCommand] +/// Deallocates a [BrightnessGrid]. #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_free( - brightness_grid: *mut SPBrightnessGrid, + brightness_grid: NonNull, ) { - assert!(!brightness_grid.is_null()); - _ = Box::from_raw(brightness_grid); + unsafe { heap_drop(brightness_grid) } } /// Gets the current value at the specified position. @@ -153,27 +78,17 @@ pub unsafe extern "C" fn sp_brightness_grid_free( /// returns: value at position /// /// # Panics -/// -/// - when `brightness_grid` is NULL /// - When accessing `x` or `y` out of bounds. -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `brightness_grid` points to a valid [SPBrightnessGrid] -/// - `brightness_grid` is not written to concurrently #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_get( - brightness_grid: *const SPBrightnessGrid, + brightness_grid: NonNull, x: usize, y: usize, -) -> u8 { - assert!(!brightness_grid.is_null()); - (*brightness_grid).0.get(x, y).into() +) -> Brightness { + unsafe { brightness_grid.as_ref().get(x, y) } } -/// Sets the value of the specified position in the [SPBrightnessGrid]. +/// Sets the value of the specified position in the [BrightnessGrid]. /// /// # Arguments /// @@ -185,138 +100,98 @@ pub unsafe extern "C" fn sp_brightness_grid_get( /// /// # Panics /// -/// - when `brightness_grid` is NULL /// - When accessing `x` or `y` out of bounds. -/// - When providing an invalid brightness value -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `brightness_grid` points to a valid [SPBrightnessGrid] -/// - `brightness_grid` is not written to or read from concurrently #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_set( - brightness_grid: *mut SPBrightnessGrid, + brightness_grid: NonNull, x: usize, y: usize, - value: u8, + value: Brightness, ) { - assert!(!brightness_grid.is_null()); - let brightness = servicepoint::Brightness::try_from(value) - .expect("invalid brightness value"); - (*brightness_grid).0.set(x, y, brightness); + unsafe { (*brightness_grid.as_ptr()).set(x, y, value) }; } -/// Sets the value of all cells in the [SPBrightnessGrid]. +/// Sets the value of all cells in the [BrightnessGrid]. /// /// # Arguments /// /// - `brightness_grid`: instance to write to /// - `value`: the value to set all cells to -/// -/// # Panics -/// -/// - when `brightness_grid` is NULL -/// - When providing an invalid brightness value -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `brightness_grid` points to a valid [SPBrightnessGrid] -/// - `brightness_grid` is not written to or read from concurrently #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_fill( - brightness_grid: *mut SPBrightnessGrid, - value: u8, + brightness_grid: NonNull, + value: Brightness, ) { - assert!(!brightness_grid.is_null()); - let brightness = servicepoint::Brightness::try_from(value) - .expect("invalid brightness value"); - (*brightness_grid).0.fill(brightness); + unsafe { (*brightness_grid.as_ptr()).fill(value) }; } -/// Gets the width of the [SPBrightnessGrid] instance. +/// Gets the width of the [BrightnessGrid] instance. /// /// # Arguments /// /// - `brightness_grid`: instance to read from /// /// returns: width -/// -/// # Panics -/// -/// - when `brightness_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `brightness_grid` points to a valid [SPBrightnessGrid] #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_width( - brightness_grid: *const SPBrightnessGrid, + brightness_grid: NonNull, ) -> usize { - assert!(!brightness_grid.is_null()); - (*brightness_grid).0.width() + unsafe { brightness_grid.as_ref().width() } } -/// Gets the height of the [SPBrightnessGrid] instance. +/// Gets the height of the [BrightnessGrid] instance. /// /// # Arguments /// /// - `brightness_grid`: instance to read from /// /// returns: height -/// -/// # Panics -/// -/// - when `brightness_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `brightness_grid` points to a valid [SPBrightnessGrid] #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_height( - brightness_grid: *const SPBrightnessGrid, + brightness_grid: NonNull, ) -> usize { - assert!(!brightness_grid.is_null()); - (*brightness_grid).0.height() + unsafe { brightness_grid.as_ref().height() } } -/// Gets an unsafe reference to the data of the [SPBrightnessGrid] instance. +/// Gets an unsafe reference to the data of the [BrightnessGrid] instance. +/// +/// The returned memory is valid for the lifetime of the brightness grid. /// /// # Arguments /// /// - `brightness_grid`: instance to read from /// /// returns: slice of bytes underlying the `brightness_grid`. -/// -/// # Panics -/// -/// - when `brightness_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `brightness_grid` points to a valid [SPBrightnessGrid] -/// - the returned memory range is never accessed after the passed [SPBrightnessGrid] has been freed -/// - the returned memory range is never accessed concurrently, either via the [SPBrightnessGrid] or directly #[no_mangle] pub unsafe extern "C" fn sp_brightness_grid_unsafe_data_ref( - brightness_grid: *mut SPBrightnessGrid, -) -> SPByteSlice { - assert!(!brightness_grid.is_null()); - assert_eq!(core::mem::size_of::(), 1); - let data = (*brightness_grid).0.data_ref_mut(); - // this assumes more about the memory layout than rust guarantees. yikes! - let data: &mut [u8] = transmute(data); - SPByteSlice { - start: NonNull::new(data.as_mut_ptr_range().start).unwrap(), - length: data.len(), + brightness_grid: NonNull, +) -> ByteSlice { + //noinspection RsAssertEqual + const _: () = assert!(size_of::() == 1); + + let data = unsafe { (*brightness_grid.as_ptr()).data_ref_mut() }; + unsafe { + ByteSlice::from_slice(transmute::<&mut [Brightness], &mut [u8]>(data)) + } +} + +/// Creates a [BrightnessGridCommand] and immediately turns that into a [Packet]. +/// +/// The provided [BrightnessGrid] gets consumed. +/// +/// Returns NULL in case of an error. +#[no_mangle] +pub unsafe extern "C" fn sp_brightness_grid_into_packet( + grid: NonNull, + x: usize, + y: usize, +) -> *mut Packet { + let grid = unsafe { heap_remove(grid) }; + match Packet::try_from(BrightnessGridCommand { + grid, + origin: Origin::new(x, y), + }) { + Ok(packet) => heap_move(packet), + Err(_) => std::ptr::null_mut(), } } diff --git a/src/byte_slice.rs b/src/byte_slice.rs index 678a5d7..de7d0df 100644 --- a/src/byte_slice.rs +++ b/src/byte_slice.rs @@ -2,10 +2,7 @@ use std::ptr::NonNull; -#[repr(C)] -/// Represents a span of memory (`&mut [u8]` ) as a struct usable by C code. -/// -/// You should not create an instance of this type in your C code. +/// Represents a span of memory (`&mut [u8]` ) as a struct. /// /// # Safety /// @@ -14,11 +11,29 @@ use std::ptr::NonNull; /// - accesses to the memory pointed to with `start` is never accessed outside `length` /// - the lifetime of the `CByteSlice` does not outlive the memory it points to, as described in /// the function returning this type. -/// - an instance of this created from C is never passed to a consuming function, as the rust code -/// will try to free the memory of a potentially separate allocator. -pub struct SPByteSlice { - /// The start address of the memory +#[repr(C)] +pub struct ByteSlice { + /// The start address of the memory. pub start: NonNull, - /// The amount of memory in bytes + /// The amount of memory in bytes. pub length: usize, } + +impl ByteSlice { + pub(crate) unsafe fn as_slice(&self) -> &[u8] { + unsafe { std::slice::from_raw_parts(self.start.as_ptr(), self.length) } + } + + pub(crate) unsafe fn as_slice_mut(&mut self) -> &mut [u8] { + unsafe { + std::slice::from_raw_parts_mut(self.start.as_ptr(), self.length) + } + } + + pub(crate) unsafe fn from_slice(slice: &mut [u8]) -> Self { + Self { + start: NonNull::new(slice.as_mut_ptr()).unwrap(), + length: slice.len(), + } + } +} diff --git a/src/char_grid.rs b/src/char_grid.rs index dfaf225..bfb2585 100644 --- a/src/char_grid.rs +++ b/src/char_grid.rs @@ -1,16 +1,10 @@ -//! C functions for interacting with [SPCharGrid]s -//! -//! prefix `sp_char_grid_` - -use servicepoint::Grid; +use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; +use servicepoint::{CharGrid, CharGridCommand, Grid, Origin, Packet}; use std::ptr::NonNull; -/// A C-wrapper for grid containing UTF-8 characters. +/// Creates a new [CharGrid] with the specified dimensions. /// -/// As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers. -/// -/// The encoding is enforced in most cases by the rust standard library -/// and will panic when provided with illegal characters. +/// returns: [CharGrid] initialized to 0. /// /// # Examples /// @@ -20,115 +14,46 @@ use std::ptr::NonNull; /// sp_char_grid_set(grid, 0, 0, '!'); /// sp_char_grid_free(grid); /// ``` -pub struct SPCharGrid(pub(crate) servicepoint::CharGrid); - -impl Clone for SPCharGrid { - fn clone(&self) -> Self { - SPCharGrid(self.0.clone()) - } -} - -/// Creates a new [SPCharGrid] with the specified dimensions. -/// -/// returns: [SPCharGrid] initialized to 0. Will never return NULL. -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_char_grid_free`. #[no_mangle] pub unsafe extern "C" fn sp_char_grid_new( width: usize, height: usize, -) -> NonNull { - let result = - Box::new(SPCharGrid(servicepoint::CharGrid::new(width, height))); - NonNull::from(Box::leak(result)) +) -> NonNull { + heap_move_nonnull(CharGrid::new(width, height)) } -/// Loads a [SPCharGrid] with the specified dimensions from the provided data. +/// Loads a [CharGrid] with the specified dimensions from the provided data. /// -/// Will never return NULL. -/// -/// # Panics -/// -/// - when `data` is NULL -/// - when the provided `data_length` does not match `height` and `width` -/// - when `data` is not valid UTF-8 -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `data` points to a valid memory location of at least `data_length` -/// bytes in size. -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_char_grid_free`. +/// returns: new CharGrid or NULL in case of an error #[no_mangle] pub unsafe extern "C" fn sp_char_grid_load( width: usize, height: usize, - data: *const u8, - data_length: usize, -) -> NonNull { - assert!(data.is_null()); - let data = std::slice::from_raw_parts(data, data_length); - let result = Box::new(SPCharGrid( - servicepoint::CharGrid::load_utf8(width, height, data.to_vec()) - .unwrap(), - )); - NonNull::from(Box::leak(result)) + data: ByteSlice, +) -> *mut CharGrid { + let data = unsafe { data.as_slice() }; + if let Ok(grid) = CharGrid::load_utf8(width, height, data.to_vec()) { + heap_move(grid) + } else { + std::ptr::null_mut() + } } -/// Clones a [SPCharGrid]. -/// -/// Will never return NULL. -/// -/// # Panics -/// -/// - when `char_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `char_grid` points to a valid [SPCharGrid] -/// - `char_grid` is not written to concurrently -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_char_grid_free`. +/// Clones a [CharGrid]. #[no_mangle] pub unsafe extern "C" fn sp_char_grid_clone( - char_grid: *const SPCharGrid, -) -> NonNull { - assert!(!char_grid.is_null()); - let result = Box::new((*char_grid).clone()); - NonNull::from(Box::leak(result)) + char_grid: NonNull, +) -> NonNull { + heap_move_nonnull(unsafe { char_grid.as_ref().clone() }) } -/// Deallocates a [SPCharGrid]. -/// -/// # Panics -/// -/// - when `char_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `char_grid` points to a valid [SPCharGrid] -/// - `char_grid` is not used concurrently or after char_grid call -/// - `char_grid` was not passed to another consuming function, e.g. to create a [SPCommand] -/// -/// [SPCommand]: [crate::SPCommand] +/// Deallocates a [CharGrid]. #[no_mangle] -pub unsafe extern "C" fn sp_char_grid_free(char_grid: *mut SPCharGrid) { - assert!(!char_grid.is_null()); - _ = Box::from_raw(char_grid); +pub unsafe extern "C" fn sp_char_grid_free(char_grid: NonNull) { + unsafe { heap_drop(char_grid) } } -/// Gets the current value at the specified position. +/// Returns the current value at the specified position. /// /// # Arguments /// @@ -137,26 +62,17 @@ pub unsafe extern "C" fn sp_char_grid_free(char_grid: *mut SPCharGrid) { /// /// # Panics /// -/// - when `char_grid` is NULL /// - when accessing `x` or `y` out of bounds -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `char_grid` points to a valid [SPCharGrid] -/// - `char_grid` is not written to concurrently #[no_mangle] pub unsafe extern "C" fn sp_char_grid_get( - char_grid: *const SPCharGrid, + char_grid: NonNull, x: usize, y: usize, ) -> u32 { - assert!(!char_grid.is_null()); - (*char_grid).0.get(x, y) as u32 + unsafe { char_grid.as_ref().get(x, y) as u32 } } -/// Sets the value of the specified position in the [SPCharGrid]. +/// Sets the value of the specified position in the [CharGrid]. /// /// # Arguments /// @@ -168,96 +84,72 @@ pub unsafe extern "C" fn sp_char_grid_get( /// /// # Panics /// -/// - when `char_grid` is NULL /// - when accessing `x` or `y` out of bounds -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `char_grid` points to a valid [SPBitVec] -/// - `char_grid` is not written to or read from concurrently -/// -/// [SPBitVec]: [crate::SPBitVec] #[no_mangle] pub unsafe extern "C" fn sp_char_grid_set( - char_grid: *mut SPCharGrid, + char_grid: NonNull, x: usize, y: usize, value: u32, ) { - assert!(!char_grid.is_null()); - (*char_grid).0.set(x, y, char::from_u32(value).unwrap()); + unsafe { (*char_grid.as_ptr()).set(x, y, char::from_u32(value).unwrap()) }; } -/// Sets the value of all cells in the [SPCharGrid]. +/// Sets the value of all cells in the [CharGrid]. /// /// # Arguments /// /// - `char_grid`: instance to write to /// - `value`: the value to set all cells to -/// -/// # Panics -/// -/// - when `char_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `char_grid` points to a valid [SPCharGrid] -/// - `char_grid` is not written to or read from concurrently #[no_mangle] pub unsafe extern "C" fn sp_char_grid_fill( - char_grid: *mut SPCharGrid, + char_grid: NonNull, value: u32, ) { - assert!(!char_grid.is_null()); - (*char_grid).0.fill(char::from_u32(value).unwrap()); + unsafe { (*char_grid.as_ptr()).fill(char::from_u32(value).unwrap()) }; } -/// Gets the width of the [SPCharGrid] instance. +/// Gets the width of the [CharGrid] instance. /// /// # Arguments /// /// - `char_grid`: instance to read from -/// -/// # Panics -/// -/// - when `char_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `char_grid` points to a valid [SPCharGrid] #[no_mangle] pub unsafe extern "C" fn sp_char_grid_width( - char_grid: *const SPCharGrid, + char_grid: NonNull, ) -> usize { - assert!(!char_grid.is_null()); - (*char_grid).0.width() + unsafe { char_grid.as_ref().width() } } -/// Gets the height of the [SPCharGrid] instance. +/// Gets the height of the [CharGrid] instance. /// /// # Arguments /// /// - `char_grid`: instance to read from -/// -/// # Panics -/// -/// - when `char_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `char_grid` points to a valid [SPCharGrid] #[no_mangle] pub unsafe extern "C" fn sp_char_grid_height( - char_grid: *const SPCharGrid, + char_grid: NonNull, ) -> usize { - assert!(!char_grid.is_null()); - (*char_grid).0.height() + unsafe { char_grid.as_ref().height() } +} + +/// Creates a [CharGridCommand] and immediately turns that into a [Packet]. +/// +/// The provided [CharGrid] gets consumed. +/// +/// Returns NULL in case of an error. +#[no_mangle] +pub unsafe extern "C" fn sp_char_grid_into_packet( + grid: NonNull, + x: usize, + y: usize, +) -> *mut Packet { + let grid = unsafe { heap_remove(grid) }; + match Packet::try_from(CharGridCommand { + grid, + origin: Origin::new(x, y), + }) { + Ok(packet) => heap_move(packet), + Err(_) => std::ptr::null_mut(), + } } diff --git a/src/command.rs b/src/command.rs deleted file mode 100644 index f7e50ea..0000000 --- a/src/command.rs +++ /dev/null @@ -1,498 +0,0 @@ -//! C functions for interacting with [SPCommand]s -//! -//! prefix `sp_command_` - -use std::ptr::{null_mut, NonNull}; - -use crate::{ - SPBitVec, SPBitmap, SPBrightnessGrid, SPCharGrid, SPCompressionCode, - SPCp437Grid, SPPacket, -}; - -/// A low-level display command. -/// -/// This struct and associated functions implement the UDP protocol for the display. -/// -/// To send a [SPCommand], use a [SPConnection]. -/// -/// # Examples -/// -/// ```C -/// sp_connection_send_command(connection, sp_command_clear()); -/// sp_connection_send_command(connection, sp_command_brightness(5)); -/// ``` -/// -/// [SPConnection]: [crate::SPConnection] -pub struct SPCommand(pub(crate) servicepoint::Command); - -impl Clone for SPCommand { - fn clone(&self) -> Self { - SPCommand(self.0.clone()) - } -} - -/// Tries to turn a [SPPacket] into a [SPCommand]. -/// -/// The packet is deallocated in the process. -/// -/// Returns: pointer to new [SPCommand] instance or NULL if parsing failed. -/// -/// # Panics -/// -/// - when `packet` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - [SPPacket] points to a valid instance of [SPPacket] -/// - [SPPacket] is not used concurrently or after this call -/// - the result is checked for NULL -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_try_from_packet( - packet: *mut SPPacket, -) -> *mut SPCommand { - let packet = *Box::from_raw(packet); - match servicepoint::Command::try_from(packet.0) { - Err(_) => null_mut(), - Ok(command) => Box::into_raw(Box::new(SPCommand(command))), - } -} - -/// Clones a [SPCommand] instance. -/// -/// returns: new [SPCommand] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `command` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `command` points to a valid instance of [SPCommand] -/// - `command` is not written to concurrently -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_clone( - command: *const SPCommand, -) -> NonNull { - assert!(!command.is_null()); - let result = Box::new((*command).clone()); - NonNull::from(Box::leak(result)) -} - -/// Set all pixels to the off state. -/// -/// Does not affect brightness. -/// -/// Returns: a new [servicepoint::Command::Clear] instance. Will never return NULL. -/// -/// # Examples -/// -/// ```C -/// sp_connection_send_command(connection, sp_command_clear()); -/// ``` -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_clear() -> NonNull { - let result = Box::new(SPCommand(servicepoint::Command::Clear)); - NonNull::from(Box::leak(result)) -} - -/// Kills the udp daemon on the display, which usually results in a restart. -/// -/// Please do not send this in your normal program flow. -/// -/// Returns: a new [servicepoint::Command::HardReset] instance. Will never return NULL. -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_hard_reset() -> NonNull { - let result = Box::new(SPCommand(servicepoint::Command::HardReset)); - NonNull::from(Box::leak(result)) -} - -/// A yet-to-be-tested command. -/// -/// Returns: a new [servicepoint::Command::FadeOut] instance. Will never return NULL. -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_fade_out() -> NonNull { - let result = Box::new(SPCommand(servicepoint::Command::FadeOut)); - NonNull::from(Box::leak(result)) -} - -/// Set the brightness of all tiles to the same value. -/// -/// Returns: a new [servicepoint::Command::Brightness] instance. Will never return NULL. -/// -/// # Panics -/// -/// - When the provided brightness value is out of range (0-11). -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_brightness( - brightness: u8, -) -> NonNull { - let brightness = servicepoint::Brightness::try_from(brightness) - .expect("invalid brightness"); - let result = - Box::new(SPCommand(servicepoint::Command::Brightness(brightness))); - NonNull::from(Box::leak(result)) -} - -/// Set the brightness of individual tiles in a rectangular area of the display. -/// -/// The passed [SPBrightnessGrid] gets consumed. -/// -/// Returns: a new [servicepoint::Command::CharBrightness] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `grid` points to a valid instance of [SPBrightnessGrid] -/// - `grid` is not used concurrently or after this call -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_char_brightness( - x: usize, - y: usize, - grid: *mut SPBrightnessGrid, -) -> NonNull { - assert!(!grid.is_null()); - let byte_grid = *Box::from_raw(grid); - let result = Box::new(SPCommand(servicepoint::Command::CharBrightness( - servicepoint::Origin::new(x, y), - byte_grid.0, - ))); - NonNull::from(Box::leak(result)) -} - -/// Set pixel data starting at the pixel offset on screen. -/// -/// The screen will continuously overwrite more pixel data without regarding the offset, meaning -/// once the starting row is full, overwriting will continue on column 0. -/// -/// The contained [SPBitVec] is always uncompressed. -/// -/// The passed [SPBitVec] gets consumed. -/// -/// Returns: a new [servicepoint::Command::BitmapLinear] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `bit_vec` is null -/// - when `compression_code` is not a valid value -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid instance of [SPBitVec] -/// - `bit_vec` is not used concurrently or after this call -/// - `compression` matches one of the allowed enum values -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_bitmap_linear( - offset: usize, - bit_vec: *mut SPBitVec, - compression: SPCompressionCode, -) -> NonNull { - assert!(!bit_vec.is_null()); - let bit_vec = *Box::from_raw(bit_vec); - let result = Box::new(SPCommand(servicepoint::Command::BitmapLinear( - offset, - bit_vec.into(), - compression.try_into().expect("invalid compression code"), - ))); - NonNull::from(Box::leak(result)) -} - -/// Set pixel data according to an and-mask starting at the offset. -/// -/// The screen will continuously overwrite more pixel data without regarding the offset, meaning -/// once the starting row is full, overwriting will continue on column 0. -/// -/// The contained [SPBitVec] is always uncompressed. -/// -/// The passed [SPBitVec] gets consumed. -/// -/// Returns: a new [servicepoint::Command::BitmapLinearAnd] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `bit_vec` is null -/// - when `compression_code` is not a valid value -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid instance of [SPBitVec] -/// - `bit_vec` is not used concurrently or after this call -/// - `compression` matches one of the allowed enum values -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_bitmap_linear_and( - offset: usize, - bit_vec: *mut SPBitVec, - compression: SPCompressionCode, -) -> NonNull { - assert!(!bit_vec.is_null()); - let bit_vec = *Box::from_raw(bit_vec); - let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearAnd( - offset, - bit_vec.into(), - compression.try_into().expect("invalid compression code"), - ))); - NonNull::from(Box::leak(result)) -} - -/// Set pixel data according to an or-mask starting at the offset. -/// -/// The screen will continuously overwrite more pixel data without regarding the offset, meaning -/// once the starting row is full, overwriting will continue on column 0. -/// -/// The contained [SPBitVec] is always uncompressed. -/// -/// The passed [SPBitVec] gets consumed. -/// -/// Returns: a new [servicepoint::Command::BitmapLinearOr] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `bit_vec` is null -/// - when `compression_code` is not a valid value -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid instance of [SPBitVec] -/// - `bit_vec` is not used concurrently or after this call -/// - `compression` matches one of the allowed enum values -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_bitmap_linear_or( - offset: usize, - bit_vec: *mut SPBitVec, - compression: SPCompressionCode, -) -> NonNull { - assert!(!bit_vec.is_null()); - let bit_vec = *Box::from_raw(bit_vec); - let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearOr( - offset, - bit_vec.into(), - compression.try_into().expect("invalid compression code"), - ))); - NonNull::from(Box::leak(result)) -} - -/// Set pixel data according to a xor-mask starting at the offset. -/// -/// The screen will continuously overwrite more pixel data without regarding the offset, meaning -/// once the starting row is full, overwriting will continue on column 0. -/// -/// The contained [SPBitVec] is always uncompressed. -/// -/// The passed [SPBitVec] gets consumed. -/// -/// Returns: a new [servicepoint::Command::BitmapLinearXor] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `bit_vec` is null -/// - when `compression_code` is not a valid value -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bit_vec` points to a valid instance of [SPBitVec] -/// - `bit_vec` is not used concurrently or after this call -/// - `compression` matches one of the allowed enum values -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_bitmap_linear_xor( - offset: usize, - bit_vec: *mut SPBitVec, - compression: SPCompressionCode, -) -> NonNull { - assert!(!bit_vec.is_null()); - let bit_vec = *Box::from_raw(bit_vec); - let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearXor( - offset, - bit_vec.into(), - compression.try_into().expect("invalid compression code"), - ))); - NonNull::from(Box::leak(result)) -} - -/// Show codepage 437 encoded text on the screen. -/// -/// The passed [SPCp437Grid] gets consumed. -/// -/// Returns: a new [servicepoint::Command::Cp437Data] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `grid` is null -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `grid` points to a valid instance of [SPCp437Grid] -/// - `grid` is not used concurrently or after this call -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_cp437_data( - x: usize, - y: usize, - grid: *mut SPCp437Grid, -) -> NonNull { - assert!(!grid.is_null()); - let grid = *Box::from_raw(grid); - let result = Box::new(SPCommand(servicepoint::Command::Cp437Data( - servicepoint::Origin::new(x, y), - grid.0, - ))); - NonNull::from(Box::leak(result)) -} - -/// Show UTF-8 encoded text on the screen. -/// -/// The passed [SPCharGrid] gets consumed. -/// -/// Returns: a new [servicepoint::Command::Utf8Data] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `grid` is null -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `grid` points to a valid instance of [SPCharGrid] -/// - `grid` is not used concurrently or after this call -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_utf8_data( - x: usize, - y: usize, - grid: *mut SPCharGrid, -) -> NonNull { - assert!(!grid.is_null()); - let grid = *Box::from_raw(grid); - let result = Box::new(SPCommand(servicepoint::Command::Utf8Data( - servicepoint::Origin::new(x, y), - grid.0, - ))); - NonNull::from(Box::leak(result)) -} - -/// Sets a window of pixels to the specified values. -/// -/// The passed [SPBitmap] gets consumed. -/// -/// Returns: a new [servicepoint::Command::BitmapLinearWin] instance. Will never return NULL. -/// -/// # Panics -/// -/// - when `bitmap` is null -/// - when `compression_code` is not a valid value -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `bitmap` points to a valid instance of [SPBitmap] -/// - `bitmap` is not used concurrently or after this call -/// - `compression` matches one of the allowed enum values -/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_command_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_command_bitmap_linear_win( - x: usize, - y: usize, - bitmap: *mut SPBitmap, - compression_code: SPCompressionCode, -) -> NonNull { - assert!(!bitmap.is_null()); - let byte_grid = (*Box::from_raw(bitmap)).0; - let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearWin( - servicepoint::Origin::new(x, y), - byte_grid, - compression_code - .try_into() - .expect("invalid compression code"), - ))); - NonNull::from(Box::leak(result)) -} - -/// Deallocates a [SPCommand]. -/// -/// # Examples -/// -/// ```C -/// SPCommand c = sp_command_clear(); -/// sp_command_free(c); -/// ``` -/// -/// # Panics -/// -/// - when `command` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `command` points to a valid [SPCommand] -/// - `command` is not used concurrently or after this call -/// - `command` was not passed to another consuming function, e.g. to create a [SPPacket] -#[no_mangle] -pub unsafe extern "C" fn sp_command_free(command: *mut SPCommand) { - assert!(!command.is_null()); - _ = Box::from_raw(command); -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..1ea03d5 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,3 @@ +mod typed; + +pub use typed::*; diff --git a/src/commands/typed.rs b/src/commands/typed.rs new file mode 100644 index 0000000..9e27540 --- /dev/null +++ b/src/commands/typed.rs @@ -0,0 +1,201 @@ +use crate::{heap_drop, heap_move, heap_move_nonnull, SPBitVec}; +use servicepoint::{ + BinaryOperation, Bitmap, Brightness, BrightnessGrid, CharGrid, + CompressionCode, Cp437Grid, GlobalBrightnessCommand, Packet, TypedCommand, +}; +use std::ptr::NonNull; + +/// Tries to turn a [Packet] into a [TypedCommand]. +/// +/// The packet is deallocated in the process. +/// +/// Returns: pointer to new [TypedCommand] instance or NULL if parsing failed. +#[no_mangle] +pub unsafe extern "C" fn sp_command_try_from_packet( + packet: NonNull, +) -> *mut TypedCommand { + let packet = *unsafe { Box::from_raw(packet.as_ptr()) }; + match servicepoint::TypedCommand::try_from(packet) { + Err(_) => std::ptr::null_mut(), + Ok(command) => heap_move(command), + } +} + +/// Clones a [TypedCommand] instance. +/// +/// returns: new [TypedCommand] instance. +#[no_mangle] +pub unsafe extern "C" fn sp_command_clone( + command: NonNull, +) -> NonNull { + heap_move_nonnull(unsafe { command.as_ref().clone() }) +} + +/// Set all pixels to the off state. +/// +/// Does not affect brightness. +/// +/// Returns: a new [servicepoint::Command::Clear] instance. +/// +/// # Examples +/// +/// ```C +/// sp_udp_send_command(connection, sp_command_clear()); +/// ``` +#[no_mangle] +pub unsafe extern "C" fn sp_command_clear() -> NonNull { + heap_move_nonnull(servicepoint::ClearCommand.into()) +} + +/// Kills the udp daemon on the display, which usually results in a restart. +/// +/// Please do not send this in your normal program flow. +/// +/// Returns: a new [servicepoint::Command::HardReset] instance. +#[no_mangle] +pub unsafe extern "C" fn sp_command_hard_reset() -> NonNull { + heap_move_nonnull(servicepoint::HardResetCommand.into()) +} + +/// A yet-to-be-tested command. +/// +/// Returns: a new [servicepoint::Command::FadeOut] instance. +#[no_mangle] +pub unsafe extern "C" fn sp_command_fade_out() -> NonNull { + heap_move_nonnull(servicepoint::FadeOutCommand.into()) +} + +/// Set the brightness of all tiles to the same value. +/// +/// Returns: a new [servicepoint::Command::Brightness] instance. +#[no_mangle] +pub unsafe extern "C" fn sp_command_global_brightness( + brightness: Brightness, +) -> NonNull { + heap_move_nonnull(GlobalBrightnessCommand::from(brightness).into()) +} + +/// Set the brightness of individual tiles in a rectangular area of the display. +/// +/// The passed [BrightnessGrid] gets consumed. +/// +/// Returns: a new [servicepoint::Command::CharBrightness] instance. +#[no_mangle] +pub unsafe extern "C" fn sp_command_brightness_grid( + x: usize, + y: usize, + grid: NonNull, +) -> NonNull { + let grid = unsafe { *Box::from_raw(grid.as_ptr()) }; + let result = servicepoint::BrightnessGridCommand { + origin: servicepoint::Origin::new(x, y), + grid, + } + .into(); + heap_move_nonnull(result) +} + +/// Set pixel data starting at the pixel offset on screen. +/// +/// The screen will continuously overwrite more pixel data without regarding the offset, meaning +/// once the starting row is full, overwriting will continue on column 0. +/// +/// The [`BinaryOperation`] will be applied on the display comparing old and sent bit. +/// +/// `new_bit = old_bit op sent_bit` +/// +/// For example, [`BinaryOperation::Or`] can be used to turn on some pixels without affecting other pixels. +/// +/// The contained [`BitVecU8Msb0`] is always uncompressed. +#[no_mangle] +pub unsafe extern "C" fn sp_command_bitvec( + offset: usize, + bit_vec: NonNull, + compression: CompressionCode, + operation: BinaryOperation, +) -> *mut TypedCommand { + let bit_vec = unsafe { *Box::from_raw(bit_vec.as_ptr()) }; + let command = servicepoint::BitVecCommand { + offset, + operation, + bitvec: bit_vec.0, + compression, + } + .into(); + heap_move(command) +} + +/// Show codepage 437 encoded text on the screen. +/// +/// The passed [Cp437Grid] gets consumed. +/// +/// Returns: a new [servicepoint::Cp437GridCommand] instance. +#[no_mangle] +pub unsafe extern "C" fn sp_command_cp437_grid( + x: usize, + y: usize, + grid: NonNull, +) -> NonNull { + let grid = *unsafe { Box::from_raw(grid.as_ptr()) }; + let result = servicepoint::Cp437GridCommand { + origin: servicepoint::Origin::new(x, y), + grid, + } + .into(); + heap_move_nonnull(result) +} + +/// Show UTF-8 encoded text on the screen. +/// +/// The passed [CharGrid] gets consumed. +/// +/// Returns: a new [servicepoint::CharGridCommand] instance. +#[no_mangle] +pub unsafe extern "C" fn sp_command_char_grid( + x: usize, + y: usize, + grid: NonNull, +) -> NonNull { + let grid = unsafe { *Box::from_raw(grid.as_ptr()) }; + let result = servicepoint::CharGridCommand { + origin: servicepoint::Origin::new(x, y), + grid, + } + .into(); + heap_move_nonnull(result) +} + +/// Sets a window of pixels to the specified values. +/// +/// The passed [Bitmap] gets consumed. +/// +/// Returns: a new [servicepoint::BitmapCommand] instance. +#[no_mangle] +pub unsafe extern "C" fn sp_command_bitmap( + x: usize, + y: usize, + bitmap: NonNull, + compression: CompressionCode, +) -> *mut TypedCommand { + let bitmap = unsafe { *Box::from_raw(bitmap.as_ptr()) }; + let command = servicepoint::BitmapCommand { + origin: servicepoint::Origin::new(x, y), + bitmap, + compression, + } + .into(); + heap_move(command) +} + +/// Deallocates a [TypedCommand]. +/// +/// # Examples +/// +/// ```C +/// TypedCommand c = sp_command_clear(); +/// sp_command_free(c); +/// ``` +#[no_mangle] +pub unsafe extern "C" fn sp_command_free(command: NonNull) { + unsafe { heap_drop(command) } +} diff --git a/src/connection.rs b/src/connection.rs index 8b31243..93e0a21 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -1,139 +1,119 @@ -//! C functions for interacting with [SPConnection]s -//! -//! prefix `sp_connection_` - +use crate::{heap_drop, heap_move, heap_remove}; +use servicepoint::{Connection, Header, Packet, TypedCommand, UdpConnection}; use std::ffi::{c_char, CStr}; -use std::ptr::{null_mut, NonNull}; +use std::net::{Ipv4Addr, SocketAddrV4}; +use std::ptr::NonNull; -use crate::{SPCommand, SPPacket}; - -/// A connection to the display. +/// Creates a new instance of [UdpConnection]. +/// +/// returns: NULL if connection fails, or connected instance /// /// # Examples /// /// ```C -/// CConnection connection = sp_connection_open("172.23.42.29:2342"); +/// UdpConnection connection = sp_udp_open("172.23.42.29:2342"); /// if (connection != NULL) -/// sp_connection_send_command(connection, sp_command_clear()); +/// sp_udp_send_command(connection, sp_command_clear()); /// ``` -pub struct SPConnection(pub(crate) servicepoint::Connection); - -/// Creates a new instance of [SPConnection]. -/// -/// returns: NULL if connection fails, or connected instance -/// -/// # Panics -/// -/// - when `host` is null or an invalid host -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_connection_free`. #[no_mangle] -pub unsafe extern "C" fn sp_connection_open( - host: *const c_char, -) -> *mut SPConnection { - assert!(!host.is_null()); - let host = CStr::from_ptr(host).to_str().expect("Bad encoding"); - let connection = match servicepoint::Connection::open(host) { - Err(_) => return null_mut(), +pub unsafe extern "C" fn sp_udp_open( + host: NonNull, +) -> *mut UdpConnection { + let host = unsafe { CStr::from_ptr(host.as_ptr()) } + .to_str() + .expect("Bad encoding"); + let connection = match UdpConnection::open(host) { + Err(_) => return std::ptr::null_mut(), Ok(value) => value, }; - Box::into_raw(Box::new(SPConnection(connection))) + heap_move(connection) } -/// Creates a new instance of [SPConnection] for testing that does not actually send anything. +/// Creates a new instance of [UdpConnection]. /// -/// returns: a new instance. Will never return NULL. +/// returns: NULL if connection fails, or connected instance /// -/// # Safety +/// # Examples /// -/// The caller has to make sure that: -/// -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_connection_free`. +/// ```C +/// UdpConnection connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); +/// if (connection != NULL) +/// sp_udp_send_command(connection, sp_command_clear()); +/// ``` #[no_mangle] -pub unsafe extern "C" fn sp_connection_fake() -> NonNull { - let result = Box::new(SPConnection(servicepoint::Connection::Fake)); - NonNull::from(Box::leak(result)) +pub unsafe extern "C" fn sp_udp_open_ipv4( + ip1: u8, + ip2: u8, + ip3: u8, + ip4: u8, + port: u16, +) -> *mut UdpConnection { + let addr = SocketAddrV4::new(Ipv4Addr::from([ip1, ip2, ip3, ip4]), port); + let connection = match UdpConnection::open(addr) { + Err(_) => return std::ptr::null_mut(), + Ok(value) => value, + }; + heap_move(connection) } -/// Sends a [SPPacket] to the display using the [SPConnection]. +/// Sends a [Packet] to the display using the [UdpConnection]. /// /// The passed `packet` gets consumed. /// /// returns: true in case of success -/// -/// # Panics -/// -/// - when `connection` is NULL -/// - when `packet` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `connection` points to a valid instance of [SPConnection] -/// - `packet` points to a valid instance of [SPPacket] -/// - `packet` is not used concurrently or after this call #[no_mangle] -pub unsafe extern "C" fn sp_connection_send_packet( - connection: *const SPConnection, - packet: *mut SPPacket, +pub unsafe extern "C" fn sp_udp_send_packet( + connection: NonNull, + packet: NonNull, ) -> bool { - assert!(!connection.is_null()); - assert!(!packet.is_null()); - let packet = Box::from_raw(packet); - (*connection).0.send((*packet).0).is_ok() + let packet = unsafe { heap_remove(packet) }; + unsafe { connection.as_ref().send(packet) }.is_ok() } -/// Sends a [SPCommand] to the display using the [SPConnection]. +/// Sends a [TypedCommand] to the display using the [UdpConnection]. /// /// The passed `command` gets consumed. /// /// returns: true in case of success /// -/// # Panics +/// # Examples /// -/// - when `connection` is NULL -/// - when `command` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `connection` points to a valid instance of [SPConnection] -/// - `command` points to a valid instance of [SPPacket] -/// - `command` is not used concurrently or after this call +/// ```C +/// sp_udp_send_command(connection, sp_command_brightness(5)); +/// ``` #[no_mangle] -pub unsafe extern "C" fn sp_connection_send_command( - connection: *const SPConnection, - command: *mut SPCommand, +pub unsafe extern "C" fn sp_udp_send_command( + connection: NonNull, + command: NonNull, ) -> bool { - assert!(!connection.is_null()); - assert!(!command.is_null()); - let command = (*Box::from_raw(command)).0; - (*connection).0.send(command).is_ok() + let command = unsafe { heap_remove(command) }; + unsafe { connection.as_ref().send(command) }.is_ok() } -/// Closes and deallocates a [SPConnection]. +/// Sends a [Header] to the display using the [UdpConnection]. /// -/// # Panics +/// returns: true in case of success /// -/// - when `connection` is NULL +/// # Examples /// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `connection` points to a valid [SPConnection] -/// - `connection` is not used concurrently or after this call +/// ```C +/// sp_udp_send_header(connection, sp_command_brightness(5)); +/// ``` #[no_mangle] -pub unsafe extern "C" fn sp_connection_free(connection: *mut SPConnection) { - assert!(!connection.is_null()); - _ = Box::from_raw(connection); +pub unsafe extern "C" fn sp_udp_send_header( + udp_connection: NonNull, + header: Header, +) -> bool { + let packet = Packet { + header, + payload: vec![], + }; + unsafe { udp_connection.as_ref() }.send(packet).is_ok() +} + +/// Closes and deallocates a [UdpConnection]. +#[no_mangle] +pub unsafe extern "C" fn sp_udp_free(connection: NonNull) { + unsafe { heap_drop(connection) } } diff --git a/src/constants.rs b/src/constants.rs deleted file mode 100644 index 1a268f4..0000000 --- a/src/constants.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! re-exported constants for use in C - -use servicepoint::CompressionCode; -use std::time::Duration; - -/// size of a single tile in one dimension -pub const SP_TILE_SIZE: usize = 8; - -/// Display tile count in the x-direction -pub const SP_TILE_WIDTH: usize = 56; - -/// Display tile count in the y-direction -pub const SP_TILE_HEIGHT: usize = 20; - -/// Display width in pixels -pub const SP_PIXEL_WIDTH: usize = SP_TILE_WIDTH * SP_TILE_SIZE; - -/// Display height in pixels -pub const SP_PIXEL_HEIGHT: usize = SP_TILE_HEIGHT * SP_TILE_SIZE; - -/// pixel count on whole screen -pub const SP_PIXEL_COUNT: usize = SP_PIXEL_WIDTH * SP_PIXEL_HEIGHT; - -/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets. -pub const SP_FRAME_PACING_MS: u128 = Duration::from_millis(30).as_millis(); - -/// Specifies the kind of compression to use. -#[repr(u16)] -pub enum SPCompressionCode { - /// no compression - Uncompressed = 0x0, - /// compress using flate2 with zlib header - Zlib = 0x677a, - /// compress using bzip2 - Bzip2 = 0x627a, - /// compress using lzma - Lzma = 0x6c7a, - /// compress using Zstandard - Zstd = 0x7a73, -} - -impl TryFrom for CompressionCode { - type Error = (); - - fn try_from(value: SPCompressionCode) -> Result { - CompressionCode::try_from(value as u16) - } -} diff --git a/src/cp437_grid.rs b/src/cp437_grid.rs index 9b366c8..01aeb1e 100644 --- a/src/cp437_grid.rs +++ b/src/cp437_grid.rs @@ -1,127 +1,46 @@ -//! C functions for interacting with [SPCp437Grid]s -//! -//! prefix `sp_cp437_grid_` - -use crate::SPByteSlice; -use servicepoint::{DataRef, Grid}; +use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; +use servicepoint::{Cp437Grid, Cp437GridCommand, DataRef, Grid, Origin, Packet}; use std::ptr::NonNull; -/// A C-wrapper for grid containing codepage 437 characters. +/// Creates a new [Cp437Grid] with the specified dimensions. /// -/// The encoding is currently not enforced. -/// -/// # Examples -/// -/// ```C -/// Cp437Grid grid = sp_cp437_grid_new(4, 3); -/// sp_cp437_grid_fill(grid, '?'); -/// sp_cp437_grid_set(grid, 0, 0, '!'); -/// sp_cp437_grid_free(grid); -/// ``` -pub struct SPCp437Grid(pub(crate) servicepoint::Cp437Grid); - -impl Clone for SPCp437Grid { - fn clone(&self) -> Self { - SPCp437Grid(self.0.clone()) - } -} - -/// Creates a new [SPCp437Grid] with the specified dimensions. -/// -/// returns: [SPCp437Grid] initialized to 0. Will never return NULL. -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_cp437_grid_free`. +/// returns: [Cp437Grid] initialized to 0. #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_new( width: usize, height: usize, -) -> NonNull { - let result = - Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height))); - NonNull::from(Box::leak(result)) +) -> NonNull { + heap_move_nonnull(Cp437Grid::new(width, height)) } -/// Loads a [SPCp437Grid] with the specified dimensions from the provided data. -/// -/// Will never return NULL. -/// -/// # Panics -/// -/// - when `data` is NULL -/// - when the provided `data_length` does not match `height` and `width` -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `data` points to a valid memory location of at least `data_length` -/// bytes in size. -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_cp437_grid_free`. +/// Loads a [Cp437Grid] with the specified dimensions from the provided data. #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_load( width: usize, height: usize, - data: *const u8, - data_length: usize, -) -> NonNull { - assert!(data.is_null()); - let data = std::slice::from_raw_parts(data, data_length); - let result = Box::new(SPCp437Grid(servicepoint::Cp437Grid::load( - width, height, data, - ))); - NonNull::from(Box::leak(result)) + data: ByteSlice, +) -> *mut Cp437Grid { + let data = unsafe { data.as_slice() }; + let grid = Cp437Grid::load(width, height, data); + if let Some(grid) = grid { + heap_move(grid) + } else { + std::ptr::null_mut() + } } -/// Clones a [SPCp437Grid]. -/// -/// Will never return NULL. -/// -/// # Panics -/// -/// - when `cp437_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `cp437_grid` points to a valid [SPCp437Grid] -/// - `cp437_grid` is not written to concurrently -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_cp437_grid_free`. +/// Clones a [Cp437Grid]. #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_clone( - cp437_grid: *const SPCp437Grid, -) -> NonNull { - assert!(!cp437_grid.is_null()); - let result = Box::new((*cp437_grid).clone()); - NonNull::from(Box::leak(result)) + cp437_grid: NonNull, +) -> NonNull { + heap_move_nonnull(unsafe { cp437_grid.as_ref().clone() }) } -/// Deallocates a [SPCp437Grid]. -/// -/// # Panics -/// -/// - when `cp437_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `cp437_grid` points to a valid [SPCp437Grid] -/// - `cp437_grid` is not used concurrently or after cp437_grid call -/// - `cp437_grid` was not passed to another consuming function, e.g. to create a [SPCommand] -/// -/// [SPCommand]: [crate::SPCommand] +/// Deallocates a [Cp437Grid]. #[no_mangle] -pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: *mut SPCp437Grid) { - assert!(!cp437_grid.is_null()); - _ = Box::from_raw(cp437_grid); +pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: NonNull) { + unsafe { heap_drop(cp437_grid) } } /// Gets the current value at the specified position. @@ -133,26 +52,17 @@ pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: *mut SPCp437Grid) { /// /// # Panics /// -/// - when `cp437_grid` is NULL /// - when accessing `x` or `y` out of bounds -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `cp437_grid` points to a valid [SPCp437Grid] -/// - `cp437_grid` is not written to concurrently #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_get( - cp437_grid: *const SPCp437Grid, + cp437_grid: NonNull, x: usize, y: usize, ) -> u8 { - assert!(!cp437_grid.is_null()); - (*cp437_grid).0.get(x, y) + unsafe { cp437_grid.as_ref().get(x, y) } } -/// Sets the value of the specified position in the [SPCp437Grid]. +/// Sets the value of the specified position in the [Cp437Grid]. /// /// # Arguments /// @@ -164,122 +74,82 @@ pub unsafe extern "C" fn sp_cp437_grid_get( /// /// # Panics /// -/// - when `cp437_grid` is NULL /// - when accessing `x` or `y` out of bounds -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `cp437_grid` points to a valid [SPBitVec] -/// - `cp437_grid` is not written to or read from concurrently -/// -/// [SPBitVec]: [crate::SPBitVec] #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_set( - cp437_grid: *mut SPCp437Grid, + cp437_grid: NonNull, x: usize, y: usize, value: u8, ) { - assert!(!cp437_grid.is_null()); - (*cp437_grid).0.set(x, y, value); + unsafe { (*cp437_grid.as_ptr()).set(x, y, value) }; } -/// Sets the value of all cells in the [SPCp437Grid]. +/// Sets the value of all cells in the [Cp437Grid]. /// /// # Arguments /// /// - `cp437_grid`: instance to write to /// - `value`: the value to set all cells to -/// -/// # Panics -/// -/// - when `cp437_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `cp437_grid` points to a valid [SPCp437Grid] -/// - `cp437_grid` is not written to or read from concurrently #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_fill( - cp437_grid: *mut SPCp437Grid, + cp437_grid: NonNull, value: u8, ) { - assert!(!cp437_grid.is_null()); - (*cp437_grid).0.fill(value); + unsafe { (*cp437_grid.as_ptr()).fill(value) }; } -/// Gets the width of the [SPCp437Grid] instance. +/// Gets the width of the [Cp437Grid] instance. /// /// # Arguments /// /// - `cp437_grid`: instance to read from -/// -/// # Panics -/// -/// - when `cp437_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `cp437_grid` points to a valid [SPCp437Grid] #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_width( - cp437_grid: *const SPCp437Grid, + cp437_grid: NonNull, ) -> usize { - assert!(!cp437_grid.is_null()); - (*cp437_grid).0.width() + unsafe { cp437_grid.as_ref().width() } } -/// Gets the height of the [SPCp437Grid] instance. +/// Gets the height of the [Cp437Grid] instance. /// /// # Arguments /// /// - `cp437_grid`: instance to read from -/// -/// # Panics -/// -/// - when `cp437_grid` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `cp437_grid` points to a valid [SPCp437Grid] #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_height( - cp437_grid: *const SPCp437Grid, + cp437_grid: NonNull, ) -> usize { - assert!(!cp437_grid.is_null()); - (*cp437_grid).0.height() + unsafe { cp437_grid.as_ref().height() } } -/// Gets an unsafe reference to the data of the [SPCp437Grid] instance. +/// Gets an unsafe reference to the data of the [Cp437Grid] instance. /// -/// Will never return NULL. -/// -/// # Panics -/// -/// - when `cp437_grid` is NULL -/// -/// ## Safety -/// -/// The caller has to make sure that: -/// -/// - `cp437_grid` points to a valid [SPCp437Grid] -/// - the returned memory range is never accessed after the passed [SPCp437Grid] has been freed -/// - the returned memory range is never accessed concurrently, either via the [SPCp437Grid] or directly +/// The returned memory is valid for the lifetime of the grid. #[no_mangle] pub unsafe extern "C" fn sp_cp437_grid_unsafe_data_ref( - cp437_grid: *mut SPCp437Grid, -) -> SPByteSlice { - let data = (*cp437_grid).0.data_ref_mut(); - SPByteSlice { - start: NonNull::new(data.as_mut_ptr_range().start).unwrap(), - length: data.len(), + cp437_grid: NonNull, +) -> ByteSlice { + unsafe { ByteSlice::from_slice((*cp437_grid.as_ptr()).data_ref_mut()) } +} + +/// Creates a [Cp437GridCommand] and immediately turns that into a [Packet]. +/// +/// The provided [Cp437Grid] gets consumed. +/// +/// Returns NULL in case of an error. +#[no_mangle] +pub unsafe extern "C" fn sp_cp437_grid_into_packet( + grid: NonNull, + x: usize, + y: usize, +) -> *mut Packet { + let grid = unsafe { heap_remove(grid) }; + match Packet::try_from(Cp437GridCommand { + grid, + origin: Origin::new(x, y), + }) { + Ok(packet) => heap_move(packet), + Err(_) => std::ptr::null_mut(), } } diff --git a/src/lib.rs b/src/lib.rs index 887fb40..05fb009 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,18 +9,18 @@ //! #include "servicepoint.h" //! //! int main(void) { -//! SPConnection *connection = sp_connection_open("172.23.42.29:2342"); +//! UdpConnection *connection = sp_udp_open("172.23.42.29:2342"); //! if (connection == NULL) //! return 1; //! -//! SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT); +//! Bitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT); //! sp_bitmap_fill(pixels, true); //! -//! SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed); -//! while (sp_connection_send_command(connection, sp_command_clone(command))); +//! TypedCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed); +//! while (sp_udp_send_command(connection, sp_command_clone(command))); //! //! sp_command_free(command); -//! sp_connection_free(connection); +//! sp_udp_free(connection); //! return 0; //! } //! ``` @@ -30,19 +30,40 @@ pub use crate::bitvec::*; pub use crate::brightness_grid::*; pub use crate::byte_slice::*; pub use crate::char_grid::*; -pub use crate::command::*; +pub use crate::commands::*; pub use crate::connection::*; -pub use crate::constants::*; pub use crate::cp437_grid::*; pub use crate::packet::*; +pub use servicepoint::CommandCode; +use std::ptr::NonNull; mod bitmap; mod bitvec; mod brightness_grid; mod byte_slice; mod char_grid; -mod command; +mod commands; mod connection; -mod constants; mod cp437_grid; mod packet; + +use std::time::Duration; + +/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets. +pub const SP_FRAME_PACING_MS: u128 = Duration::from_millis(30).as_millis(); + +pub(crate) fn heap_move(x: T) -> *mut T { + Box::into_raw(Box::new(x)) +} + +pub(crate) fn heap_move_nonnull(x: T) -> NonNull { + NonNull::from(Box::leak(Box::new(x))) +} + +pub(crate) unsafe fn heap_drop(x: NonNull) { + drop(unsafe { heap_remove(x) }); +} + +pub(crate) unsafe fn heap_remove(x: NonNull) -> T { + unsafe { *Box::from_raw(x.as_ptr()) } +} diff --git a/src/packet.rs b/src/packet.rs index 9293a8a..d8b1579 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,166 +1,128 @@ -//! C functions for interacting with [SPPacket]s -//! -//! prefix `sp_packet_` +use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; +use servicepoint::{CommandCode, Header, Packet, TypedCommand}; +use std::ptr::NonNull; -use std::ptr::{null_mut, NonNull}; - -use crate::SPCommand; - -/// The raw packet -pub struct SPPacket(pub(crate) servicepoint::Packet); - -/// Turns a [SPCommand] into a [SPPacket]. -/// The [SPCommand] gets consumed. +/// Turns a [TypedCommand] into a [Packet]. +/// The [TypedCommand] gets consumed. /// -/// Will never return NULL. -/// -/// # Panics -/// -/// - when `command` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - [SPCommand] points to a valid instance of [SPCommand] -/// - [SPCommand] is not used concurrently or after this call -/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_packet_free`. +/// Returns NULL in case of an error. #[no_mangle] pub unsafe extern "C" fn sp_packet_from_command( - command: *mut SPCommand, -) -> NonNull { - assert!(!command.is_null()); - let command = *Box::from_raw(command); - let result = Box::new(SPPacket(command.0.into())); - NonNull::from(Box::leak(result)) -} - -/// Tries to load a [SPPacket] from the passed array with the specified length. -/// -/// returns: NULL in case of an error, pointer to the allocated packet otherwise -/// -/// # Panics -/// -/// - when `data` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `data` points to a valid memory region of at least `length` bytes -/// - `data` is not written to concurrently -/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_packet_free`. -#[no_mangle] -pub unsafe extern "C" fn sp_packet_try_load( - data: *const u8, - length: usize, -) -> *mut SPPacket { - assert!(!data.is_null()); - let data = std::slice::from_raw_parts(data, length); - match servicepoint::Packet::try_from(data) { - Err(_) => null_mut(), - Ok(packet) => Box::into_raw(Box::new(SPPacket(packet))), + command: NonNull, +) -> *mut Packet { + let command = unsafe { heap_remove(command) }; + if let Ok(packet) = command.try_into() { + heap_move(packet) + } else { + std::ptr::null_mut() } } -/// Creates a raw [SPPacket] from parts. +/// Tries to load a [Packet] from the passed array with the specified length. /// -/// # Arguments -/// -/// - `command_code` specifies which command this packet contains -/// - `a`, `b`, `c` and `d` are command-specific header values -/// - `payload` is the optional data that is part of the command -/// - `payload_len` is the size of the payload +/// returns: NULL in case of an error, pointer to the allocated packet otherwise +#[no_mangle] +pub unsafe extern "C" fn sp_packet_try_load(data: ByteSlice) -> *mut Packet { + let data = unsafe { data.as_slice() }; + match servicepoint::Packet::try_from(data) { + Err(_) => std::ptr::null_mut(), + Ok(packet) => heap_move(packet), + } +} + +/// Creates a raw [Packet] from parts. /// /// returns: new instance. Will never return null. -/// -/// # Panics -/// -/// - when `payload` is null, but `payload_len` is not zero -/// - when `payload_len` is zero, but `payload` is nonnull -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `payload` points to a valid memory region of at least `payload_len` bytes -/// - `payload` is not written to concurrently -/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or -/// by explicitly calling [sp_packet_free]. #[no_mangle] pub unsafe extern "C" fn sp_packet_from_parts( - command_code: u16, - a: u16, - b: u16, - c: u16, - d: u16, - payload: *const u8, - payload_len: usize, -) -> NonNull { - assert_eq!(payload.is_null(), payload_len == 0); - + header: Header, + payload: *const ByteSlice, +) -> NonNull { let payload = if payload.is_null() { vec![] } else { - let payload = std::slice::from_raw_parts(payload, payload_len); + let payload = unsafe { (*payload).as_slice() }; Vec::from(payload) }; - let packet = servicepoint::Packet { - header: servicepoint::Header { - command_code, - a, - b, - c, - d, - }, - payload, - }; - let result = Box::new(SPPacket(packet)); - NonNull::from(Box::leak(result)) + heap_move_nonnull(Packet { header, payload }) } -/// Clones a [SPPacket]. +/// Returns a pointer to the header field of the provided packet. /// -/// Will never return NULL. +/// The returned header can be changed and will be valid for the lifetime of the packet. +#[no_mangle] +pub unsafe extern "C" fn sp_packet_get_header( + packet: NonNull, +) -> NonNull
{ + NonNull::from(&mut unsafe { (*packet.as_ptr()).header }) +} + +/// Returns a pointer to the current payload of the provided packet. +/// +/// The returned memory can be changed and will be valid until a new payload is set. +#[no_mangle] +pub unsafe extern "C" fn sp_packet_get_payload( + packet: NonNull, +) -> ByteSlice { + unsafe { ByteSlice::from_slice(&mut (*packet.as_ptr()).payload) } +} + +/// Sets the payload of the provided packet to the provided data. +/// +/// This makes previous payload pointers invalid. +#[no_mangle] +pub unsafe extern "C" fn sp_packet_set_payload( + packet: NonNull, + data: ByteSlice, +) { + unsafe { (*packet.as_ptr()).payload = data.as_slice().to_vec() } +} + +/// Serialize the packet into the provided buffer. /// /// # Panics /// -/// - when `packet` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `packet` points to a valid [SPPacket] -/// - `packet` is not written to concurrently -/// - the returned instance is freed in some way, either by using a consuming function or -/// by explicitly calling `sp_packet_free`. +/// - if the buffer is not big enough to hold header+payload. +#[no_mangle] +pub unsafe extern "C" fn sp_packet_serialize_to( + packet: NonNull, + mut buffer: ByteSlice, +) { + unsafe { + packet.as_ref().serialize_to(buffer.as_slice_mut()); + } +} + +/// Clones a [Packet]. #[no_mangle] pub unsafe extern "C" fn sp_packet_clone( - packet: *const SPPacket, -) -> NonNull { - assert!(!packet.is_null()); - let result = Box::new(SPPacket((*packet).0.clone())); - NonNull::from(Box::leak(result)) + packet: NonNull, +) -> NonNull { + heap_move_nonnull(unsafe { packet.as_ref().clone() }) } -/// Deallocates a [SPPacket]. -/// -/// # Panics -/// -/// - when `packet` is NULL -/// -/// # Safety -/// -/// The caller has to make sure that: -/// -/// - `packet` points to a valid [SPPacket] -/// - `packet` is not used concurrently or after this call +/// Deallocates a [Packet]. #[no_mangle] -pub unsafe extern "C" fn sp_packet_free(packet: *mut SPPacket) { - assert!(!packet.is_null()); - _ = Box::from_raw(packet) +pub unsafe extern "C" fn sp_packet_free(packet: NonNull) { + unsafe { heap_drop(packet) } +} + +/// Converts u16 into [CommandCode]. +/// +/// If the provided value is not valid, false is returned and result is not changed. +#[no_mangle] +pub unsafe extern "C" fn sp_u16_to_command_code( + code: u16, + result: *mut CommandCode, +) -> bool { + match CommandCode::try_from(code) { + Ok(code) => { + unsafe { + *result = code; + } + true + } + Err(_) => false, + } }