diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f5eaca7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +resolver = "2" +members = [ + "servicepoint2", + "servicepoint2-binding-cs", + "examples/announce", + "examples/game_of_life", + "examples/moving_line", + "examples/wiping_clear", + "examples/random_brightness" +] diff --git a/README.md b/README.md index 506254b..291e636 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Uses C bindings internally to provide a similar API to rust. Things to keep in m - C# specifics are documented in the library. Use the rust documentation for everything else. Naming and semantics are the same apart from CamelCase instead of kebap_case. - You will only get rust backtraces in debug builds of the native code. - F# is not explicitly tested. If there are usability or functionality problems, please open an issue. +- Reading and writing to instances concurrently is not safe. Only reading concurrently is safe. ```csharp using ServicePoint2; @@ -79,6 +80,7 @@ The lowest common denominator. Things to keep in mind: - Instances get consumed in the same way they do when writing rust / C# code. Do not use an instance after an (implicit!) free. - Option or Result turn into nullable return values - check for NULL! - There are no specifics for C++ here yet. You might get a nicer header when generating directly for C++, but it should be usable. +- Reading and writing to instances concurrently is not safe. Only reading concurrently is safe. ```c++ #include diff --git a/examples/Cargo.lock b/examples/Cargo.lock deleted file mode 100644 index 9862a35..0000000 --- a/examples/Cargo.lock +++ /dev/null @@ -1,567 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "announce" -version = "0.1.0" -dependencies = [ - "clap", - "env_logger", - "servicepoint2", -] - -[[package]] -name = "anstream" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" - -[[package]] -name = "anstyle-parse" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" -dependencies = [ - "anstyle", - "windows-sys", -] - -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "cc" -version = "1.0.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" -dependencies = [ - "jobserver", - "libc", - "once_cell", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "4.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" - -[[package]] -name = "colorchoice" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" - -[[package]] -name = "crc32fast" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "env_filter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log", -] - -[[package]] -name = "flate2" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "game_of_life" -version = "0.1.0" -dependencies = [ - "clap", - "env_logger", - "rand", - "servicepoint2", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" - -[[package]] -name = "jobserver" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" -dependencies = [ - "libc", -] - -[[package]] -name = "libc" -version = "0.2.154" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" - -[[package]] -name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "lz4" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" -dependencies = [ - "libc", - "lz4-sys", -] - -[[package]] -name = "lz4-sys" -version = "1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "memchr" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "miniz_oxide" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" -dependencies = [ - "adler", -] - -[[package]] -name = "moving_line" -version = "0.1.0" -dependencies = [ - "clap", - "env_logger", - "servicepoint2", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro2" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "random_brightness" -version = "0.1.0" -dependencies = [ - "clap", - "env_logger", - "rand", - "servicepoint2", -] - -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - -[[package]] -name = "servicepoint2" -version = "0.2.0" -dependencies = [ - "bzip2", - "flate2", - "log", - "lz4", - "zstd", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" - -[[package]] -name = "wiping_clear" -version = "0.1.0" -dependencies = [ - "clap", - "env_logger", - "servicepoint2", -] - -[[package]] -name = "zstd" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index 725a8a5..0000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[workspace] -resolver = "2" -members = [ - "announce", - "game_of_life", - "moving_line", - "wiping_clear", - "random_brightness" -] diff --git a/examples/announce/src/main.rs b/examples/announce/src/main.rs index 9b1efb5..fea8c07 100644 --- a/examples/announce/src/main.rs +++ b/examples/announce/src/main.rs @@ -20,7 +20,7 @@ fn main() { let connection = Connection::open(&cli.destination).unwrap(); if cli.clear { - connection.send(Command::Clear).unwrap(); + connection.send(Command::Clear.into()).unwrap(); } let mut max_width = 0; @@ -43,6 +43,6 @@ fn main() { } connection - .send(Command::Cp437Data(Origin::top_left(), chars)) + .send(Command::Cp437Data(Origin::top_left(), chars).into()) .unwrap(); } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index ad2b56a..7c4a4b8 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -23,7 +23,7 @@ fn main() { loop { connection - .send(Command::BitmapLinearWin(Origin::top_left(), field.clone())) + .send(Command::BitmapLinearWin(Origin::top_left(), field.clone()).into()) .expect("could not send"); thread::sleep(Duration::from_millis(14)); field = iteration(field); diff --git a/examples/lang_c/Makefile b/examples/lang_c/Makefile index cb2b68e..56b7f23 100644 --- a/examples/lang_c/Makefile +++ b/examples/lang_c/Makefile @@ -3,11 +3,11 @@ OUT_DIR := ${ROOT_DIR}/out SP2_DIR := ${ROOT_DIR}/../../servicepoint2 SP2_INCLUDE := ${ROOT_DIR}/../../servicepoint2-binding-c -SP2_TARGET_RELEASE := ${ROOT_DIR}/../../target/release +SP2_TARGET_RELEASE := ${SP2_DIR}/target/release .PHONY: build run clean -build: ${OUT_DIR}/lang_c +all: ${OUT_DIR}/lang_c run: ${OUT_DIR}/lang_c out/lang_c diff --git a/examples/lang_c/main.c b/examples/lang_c/main.c index 2908103..e736c72 100644 --- a/examples/lang_c/main.c +++ b/examples/lang_c/main.c @@ -8,11 +8,11 @@ int main(void) { sp2_PixelGrid *pixels = sp2_pixel_grid_new(sp2_PIXEL_WIDTH, sp2_PIXEL_HEIGHT); sp2_pixel_grid_fill(pixels, true); + sp2_Command *command = sp2_command_bitmap_linear_win(0, 0, pixels); - if (command == NULL) - return 4; - if (!sp2_connection_send(connection, command)) - return 5; + sp2_Packet *packet = sp2_packet_from_command(command); + if (!sp2_connection_send(connection, packet)) + return 1; sp2_connection_dealloc(connection); return 0; diff --git a/examples/lang_cs/Program.cs b/examples/lang_cs/Program.cs index a150eb9..51869ac 100644 --- a/examples/lang_cs/Program.cs +++ b/examples/lang_cs/Program.cs @@ -2,8 +2,8 @@ using var connection = Connection.Open("127.0.0.1:2342"); -connection.Send(Command.Clear()); -connection.Send(Command.Brightness(128)); +connection.Send(Command.Clear().IntoPacket()); +connection.Send(Command.Brightness(128).IntoPacket()); using var pixels = PixelGrid.New(Constants.PixelWidth, Constants.PixelHeight); @@ -14,6 +14,6 @@ for (var offset = 0; offset < int.MaxValue; offset++) for (var y = 0; y < pixels.Height; y++) pixels[(y + offset) % Constants.PixelWidth, y] = true; - connection.Send(Command.BitmapLinearWin(0, 0, pixels.Clone())); + connection.Send(Command.BitmapLinearWin(0, 0, pixels.Clone()).IntoPacket()); Thread.Sleep(14); } diff --git a/examples/moving_line/src/main.rs b/examples/moving_line/src/main.rs index 023f5da..5a7ee42 100644 --- a/examples/moving_line/src/main.rs +++ b/examples/moving_line/src/main.rs @@ -24,7 +24,7 @@ fn main() { pixels.set((y + x_offset) % PIXEL_WIDTH as usize, y, true); } connection - .send(Command::BitmapLinearWin(Origin::top_left(), pixels.clone())) + .send(Command::BitmapLinearWin(Origin::top_left(), pixels.clone()).into()) .unwrap(); thread::sleep(Duration::from_millis(14)); } diff --git a/examples/random_brightness/src/main.rs b/examples/random_brightness/src/main.rs index 1ccefbf..e764036 100644 --- a/examples/random_brightness/src/main.rs +++ b/examples/random_brightness/src/main.rs @@ -30,13 +30,13 @@ fn main() { let mut filled_grid = PixelGrid::max_sized(); filled_grid.fill(true); connection - .send(BitmapLinearWin(Origin::top_left(), filled_grid)) + .send(BitmapLinearWin(Origin::top_left(), filled_grid).into()) .unwrap(); } // set all pixels to the same random brightness let mut rng = rand::thread_rng(); - connection.send(Brightness(rng.gen())).unwrap(); + connection.send(Brightness(rng.gen()).into()).unwrap(); // continuously update random windows to new random brightness loop { @@ -56,7 +56,7 @@ fn main() { } } - connection.send(CharBrightness(origin, luma)).unwrap(); + connection.send(CharBrightness(origin, luma).into()).unwrap(); std::thread::sleep(wait_duration); } } diff --git a/examples/wiping_clear/src/main.rs b/examples/wiping_clear/src/main.rs index 14ed9a4..ed8be0d 100644 --- a/examples/wiping_clear/src/main.rs +++ b/examples/wiping_clear/src/main.rs @@ -36,7 +36,7 @@ fn main() { let bit_vec = BitVec::from(&*pixel_data); connection - .send(Command::BitmapLinearAnd(0, bit_vec, CompressionCode::Gz)) + .send(Command::BitmapLinearAnd(0, bit_vec, CompressionCode::Gz).into()) .unwrap(); thread::sleep(sleep_duration); } diff --git a/servicepoint2-binding-c/servicepoint2.h b/servicepoint2-binding-c/servicepoint2.h index 0b9e9d6..07ca43e 100644 --- a/servicepoint2-binding-c/servicepoint2.h +++ b/servicepoint2-binding-c/servicepoint2.h @@ -68,7 +68,7 @@ typedef uint16_t sp2_CompressionCode; typedef struct sp2_BitVec sp2_BitVec; /** - * A grid of bytes + * A 2D grid of bytes */ typedef struct sp2_ByteGrid sp2_ByteGrid; @@ -82,13 +82,40 @@ typedef struct sp2_Command sp2_Command; */ typedef struct sp2_Connection sp2_Connection; +/** + * The raw packet. Should probably not be used directly. + */ +typedef struct sp2_Packet sp2_Packet; + /** * A grid of pixels stored in packed bytes. */ typedef struct sp2_PixelGrid sp2_PixelGrid; +/** + * Represents a `&mut [u8]` as a struct usable by C code. + * + * Usage of this type is inherently unsafe. + */ +typedef struct sp2_CByteSlice { + /** + * The start address of the memory + */ + uint8_t *start; + /** + * The amount of memory in bytes + */ + size_t length; +} sp2_CByteSlice; + +/** + * Type alias for documenting the meaning of the u16 in enum values + */ typedef uint16_t sp2_Offset; +/** + * Type alias for documenting the meaning of the u16 in enum values + */ typedef uint8_t sp2_Brightness; #ifdef __cplusplus @@ -140,6 +167,19 @@ struct sp2_BitVec *sp2_bit_vec_new(size_t size); */ bool sp2_bit_vec_set(struct sp2_BitVec *this_, size_t index, bool value); +/** + * Gets an unsafe reference to the data of the `BitVec` instance. + * + * ## Safety + * + * The caller has to make sure to never access the returned memory after the `BitVec` + * instance has been consumed or manually deallocated. + * + * Reading and writing concurrently to either the original instance or the returned data will + * result in undefined behavior. + */ +struct sp2_CByteSlice sp2_bit_vec_unsafe_data_ref(struct sp2_BitVec *this_); + /** * Clones a `ByteGrid`. * The returned instance has to be freed with `byte_grid_dealloc`. @@ -166,7 +206,7 @@ uint8_t sp2_byte_grid_get(const struct sp2_ByteGrid *this_, size_t x, size_t y); /** * Gets the height in pixels of the `ByteGrid` instance. */ -size_t sp2_byte_grid_height(const struct sp2_PixelGrid *this_); +size_t sp2_byte_grid_height(const struct sp2_ByteGrid *this_); /** * Loads a `ByteGrid` with the specified dimensions from the provided data. @@ -191,10 +231,23 @@ void sp2_byte_grid_set(struct sp2_ByteGrid *this_, size_t y, uint8_t value); +/** + * Gets an unsafe reference to the data of the `ByteGrid` instance. + * + * ## Safety + * + * The caller has to make sure to never access the returned memory after the `ByteGrid` + * instance has been consumed or manually deallocated. + * + * Reading and writing concurrently to either the original instance or the returned data will + * result in undefined behavior. + */ +struct sp2_CByteSlice sp2_byte_grid_unsafe_data_ref(struct sp2_ByteGrid *this_); + /** * Gets the width in pixels of the `ByteGrid` instance. */ -size_t sp2_byte_grid_width(const struct sp2_PixelGrid *this_); +size_t sp2_byte_grid_width(const struct sp2_ByteGrid *this_); /** * Allocates a new `Command::BitmapLinear` instance. @@ -283,6 +336,13 @@ struct sp2_Command *sp2_command_fade_out(void); */ struct sp2_Command *sp2_command_hard_reset(void); +/** + * Tries to turn a `Packet` into a `Command`. The packet is gets deallocated in the process. + * + * Returns: pointer to command or NULL + */ +struct sp2_Command *sp2_command_try_from_packet(struct sp2_Packet *packet); + /** * Tries to load a `Command` from the passed array with the specified length. * @@ -309,7 +369,12 @@ struct sp2_Connection *sp2_connection_open(const char *host); * Sends the command instance. The instance is consumed / destroyed and cannot be used after this call. */ bool sp2_connection_send(const struct sp2_Connection *connection, - struct sp2_Command *command_ptr); + struct sp2_Packet *command_ptr); + +/** + * Turns a `Command` into a `Packet`. The command gets deallocated in the process. + */ +struct sp2_Packet *sp2_packet_from_command(struct sp2_Command *command); /** * Clones a `PixelGrid`. @@ -362,6 +427,19 @@ void sp2_pixel_grid_set(struct sp2_PixelGrid *this_, size_t y, bool value); +/** + * Gets an unsafe reference to the data of the `PixelGrid` instance. + * + * ## Safety + * + * The caller has to make sure to never access the returned memory after the `PixelGrid` + * instance has been consumed or manually deallocated. + * + * Reading and writing concurrently to either the original instance or the returned data will + * result in undefined behavior. + */ +struct sp2_CByteSlice sp2_pixel_grid_unsafe_data_ref(struct sp2_PixelGrid *this_); + /** * Gets the width in pixels of the `PixelGrid` instance. */ diff --git a/servicepoint2-binding-cs/build.rs b/servicepoint2-binding-cs/build.rs index 43e61ae..333066b 100644 --- a/servicepoint2-binding-cs/build.rs +++ b/servicepoint2-binding-cs/build.rs @@ -8,6 +8,8 @@ fn main() { .input_extern_file("../servicepoint2/src/connection.rs") .input_extern_file("../servicepoint2/src/pixel_grid.rs") .input_extern_file("../servicepoint2/src/lib.rs") + .input_extern_file("../servicepoint2/src/c_slice.rs") + .input_extern_file("../servicepoint2/src/packet.rs") .csharp_dll_name("servicepoint2") .csharp_namespace("ServicePoint2.BindGen") .csharp_use_nint_types(true) diff --git a/servicepoint2-binding-cs/src/BindGen/ServicePoint2.g.cs b/servicepoint2-binding-cs/src/BindGen/ServicePoint2.g.cs index c5a0642..f960506 100644 --- a/servicepoint2-binding-cs/src/BindGen/ServicePoint2.g.cs +++ b/servicepoint2-binding-cs/src/BindGen/ServicePoint2.g.cs @@ -50,6 +50,10 @@ namespace ServicePoint2.BindGen [DllImport(__DllName, EntryPoint = "sp2_bit_vec_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] public static extern nuint sp2_bit_vec_len(BitVec* @this); + /// Gets an unsafe reference to the data of the `BitVec` instance. ## Safety The caller has to make sure to never access the returned memory after the `BitVec` instance has been consumed or manually deallocated. Reading and writing concurrently to either the original instance or the returned data will result in undefined behavior. + [DllImport(__DllName, EntryPoint = "sp2_bit_vec_unsafe_data_ref", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern CByteSlice sp2_bit_vec_unsafe_data_ref(BitVec* @this); + /// Creates a new `ByteGrid` instance. The returned instance has to be freed with `byte_grid_dealloc`. [DllImport(__DllName, EntryPoint = "sp2_byte_grid_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] public static extern ByteGrid* sp2_byte_grid_new(nuint width, nuint height); @@ -86,9 +90,13 @@ namespace ServicePoint2.BindGen [DllImport(__DllName, EntryPoint = "sp2_byte_grid_height", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] public static extern nuint sp2_byte_grid_height(ByteGrid* @this); - /// Tries to load a `Command` from the passed array with the specified length. returns: NULL in case of an error, pointer to the allocated command otherwise - [DllImport(__DllName, EntryPoint = "sp2_command_try_load", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern Command* sp2_command_try_load(byte* data, nuint length); + /// Gets an unsafe reference to the data of the `ByteGrid` instance. ## Safety The caller has to make sure to never access the returned memory after the `ByteGrid` instance has been consumed or manually deallocated. Reading and writing concurrently to either the original instance or the returned data will result in undefined behavior. + [DllImport(__DllName, EntryPoint = "sp2_byte_grid_unsafe_data_ref", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern CByteSlice sp2_byte_grid_unsafe_data_ref(ByteGrid* @this); + + /// Tries to turn a `Packet` into a `Command`. The packet is gets deallocated in the process. Returns: pointer to command or NULL + [DllImport(__DllName, EntryPoint = "sp2_command_try_from_packet", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern Command* sp2_command_try_from_packet(Packet* packet); /// Clones a `Command` instance [DllImport(__DllName, EntryPoint = "sp2_command_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] @@ -149,7 +157,7 @@ namespace ServicePoint2.BindGen /// Sends the command instance. The instance is consumed / destroyed and cannot be used after this call. [DllImport(__DllName, EntryPoint = "sp2_connection_send", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] [return: MarshalAs(UnmanagedType.U1)] - public static extern bool sp2_connection_send(Connection* connection, Command* command_ptr); + public static extern bool sp2_connection_send(Connection* connection, Packet* command_ptr); /// Closes and deallocates a connection instance [DllImport(__DllName, EntryPoint = "sp2_connection_dealloc", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] @@ -192,9 +200,21 @@ namespace ServicePoint2.BindGen [DllImport(__DllName, EntryPoint = "sp2_pixel_grid_height", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] public static extern nuint sp2_pixel_grid_height(PixelGrid* @this); - /// Gets a reference to the data of the `PixelGrid` instance. - [DllImport(__DllName, EntryPoint = "sp2_pixel_grid_data_ref", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern byte* sp2_pixel_grid_data_ref(PixelGrid* @this); + /// Gets an unsafe reference to the data of the `PixelGrid` instance. ## Safety The caller has to make sure to never access the returned memory after the `PixelGrid` instance has been consumed or manually deallocated. Reading and writing concurrently to either the original instance or the returned data will result in undefined behavior. + [DllImport(__DllName, EntryPoint = "sp2_pixel_grid_unsafe_data_ref", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern CByteSlice sp2_pixel_grid_unsafe_data_ref(PixelGrid* @this); + + /// Turns a `Command` into a `Packet`. The command gets deallocated in the process. + [DllImport(__DllName, EntryPoint = "sp2_packet_from_command", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern Packet* sp2_packet_from_command(Command* command); + + /// 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 + [DllImport(__DllName, EntryPoint = "sp2_packet_try_load", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern Packet* sp2_packet_try_load(byte* data, nuint length); + + /// Deallocates a `Packet`. Note: do not call this if the instance has been consumed in another way, e.g. by sending it. + [DllImport(__DllName, EntryPoint = "sp2_packet_dealloc", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void sp2_packet_dealloc(Packet* @this); } @@ -219,6 +239,18 @@ namespace ServicePoint2.BindGen { } + [StructLayout(LayoutKind.Sequential)] + public unsafe partial struct CByteSlice + { + public byte* start; + public nuint length; + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe partial struct Packet + { + } + public enum Command { diff --git a/servicepoint2-binding-cs/src/BitVec.cs b/servicepoint2-binding-cs/src/BitVec.cs index 202a4b5..4d6da0b 100644 --- a/servicepoint2-binding-cs/src/BitVec.cs +++ b/servicepoint2-binding-cs/src/BitVec.cs @@ -68,11 +68,23 @@ public sealed class BitVec : Sp2NativeInstance } } + public Span Data + { + get + { + unsafe + { + var slice = NativeMethods.sp2_bit_vec_unsafe_data_ref(Instance); + return new Span(slice.start, (int)slice.length); + } + } + } + private unsafe BitVec(BindGen.BitVec* instance) : base(instance) { } - protected override unsafe void Dealloc() + private protected override unsafe void Dealloc() { NativeMethods.sp2_bit_vec_dealloc(Instance); } diff --git a/servicepoint2-binding-cs/src/ByteGrid.cs b/servicepoint2-binding-cs/src/ByteGrid.cs index 112c36e..2c6913d 100644 --- a/servicepoint2-binding-cs/src/ByteGrid.cs +++ b/servicepoint2-binding-cs/src/ByteGrid.cs @@ -111,11 +111,23 @@ public sealed class ByteGrid : Sp2NativeInstance } } + public Span Data + { + get + { + unsafe + { + var slice = NativeMethods.sp2_byte_grid_unsafe_data_ref(Instance); + return new Span(slice.start, (int)slice.length); + } + } + } + private unsafe ByteGrid(BindGen.ByteGrid* instance) : base(instance) { } - protected override unsafe void Dealloc() + private protected override unsafe void Dealloc() { NativeMethods.sp2_byte_grid_dealloc(Instance); } diff --git a/servicepoint2-binding-cs/src/Command.cs b/servicepoint2-binding-cs/src/Command.cs index 73d502c..1cb4e99 100644 --- a/servicepoint2-binding-cs/src/Command.cs +++ b/servicepoint2-binding-cs/src/Command.cs @@ -5,6 +5,22 @@ namespace ServicePoint2; public sealed class Command : Sp2NativeInstance { + public static bool TryFromPacket(Packet packet, [MaybeNullWhen(false)] out Command command) + { + unsafe + { + var result = NativeMethods.sp2_command_try_from_packet(packet.Into()); + if (result == null) + { + command = null; + return false; + } + + command = new Command(result); + return true; + } + } + public Command Clone() { unsafe @@ -13,21 +29,6 @@ public sealed class Command : Sp2NativeInstance } } - public static bool TryLoad(Span bytes, [MaybeNullWhen(false)] out Command command) - { - unsafe - { - fixed (byte* bytesPtr = bytes) - { - var instance = NativeMethods.sp2_command_try_load(bytesPtr, (nuint)bytes.Length); - command = instance == null - ? null - : new Command(instance); - return command != null; - } - } - } - public static Command Clear() { unsafe @@ -124,7 +125,7 @@ public sealed class Command : Sp2NativeInstance { } - protected override unsafe void Dealloc() + private protected override unsafe void Dealloc() { NativeMethods.sp2_command_dealloc(Instance); } diff --git a/servicepoint2-binding-cs/src/Connection.cs b/servicepoint2-binding-cs/src/Connection.cs index 58bb532..13220f3 100644 --- a/servicepoint2-binding-cs/src/Connection.cs +++ b/servicepoint2-binding-cs/src/Connection.cs @@ -16,15 +16,15 @@ public sealed class Connection : Sp2NativeInstance } } - public bool Send(Command command) + public bool Send(Packet packet) { unsafe { - return NativeMethods.sp2_connection_send(Instance, command.Into()); + return NativeMethods.sp2_connection_send(Instance, packet.Into()); } } - protected override unsafe void Dealloc() + private protected override unsafe void Dealloc() { NativeMethods.sp2_connection_dealloc(Instance); } diff --git a/servicepoint2-binding-cs/src/Packet.cs b/servicepoint2-binding-cs/src/Packet.cs new file mode 100644 index 0000000..e04a02d --- /dev/null +++ b/servicepoint2-binding-cs/src/Packet.cs @@ -0,0 +1,39 @@ +using System.Diagnostics.CodeAnalysis; +using ServicePoint2.BindGen; + +namespace ServicePoint2; + +public sealed class Packet : Sp2NativeInstance +{ + public static Packet FromCommand(Command command) + { + unsafe + { + return new Packet(NativeMethods.sp2_packet_from_command(command.Into())); + } + } + + public static bool TryFromBytes(Span bytes, [MaybeNullWhen(false)] out Packet packet) + { + unsafe + { + fixed (byte* bytesPtr = bytes) + { + var instance = NativeMethods.sp2_packet_try_load(bytesPtr, (nuint)bytes.Length); + packet = instance == null + ? null + : new Packet(instance); + return packet != null; + } + } + } + + private unsafe Packet(BindGen.Packet* instance) : base(instance) + { + } + + private protected override unsafe void Dealloc() + { + NativeMethods.sp2_packet_dealloc(Instance); + } +} diff --git a/servicepoint2-binding-cs/src/PixelGrid.cs b/servicepoint2-binding-cs/src/PixelGrid.cs index 855a128..0a7394c 100644 --- a/servicepoint2-binding-cs/src/PixelGrid.cs +++ b/servicepoint2-binding-cs/src/PixelGrid.cs @@ -86,8 +86,8 @@ public sealed class PixelGrid : Sp2NativeInstance { unsafe { - var ptr = NativeMethods.sp2_pixel_grid_data_ref(Instance); - return new Span(ptr, Width * Height / 8); + var slice = NativeMethods.sp2_pixel_grid_unsafe_data_ref(Instance); + return new Span(slice.start, (int)slice.length); } } } @@ -96,7 +96,7 @@ public sealed class PixelGrid : Sp2NativeInstance { } - protected override unsafe void Dealloc() + private protected override unsafe void Dealloc() { NativeMethods.sp2_pixel_grid_dealloc(Instance); } diff --git a/servicepoint2-binding-cs/src/ServicePoint2Extensions.cs b/servicepoint2-binding-cs/src/ServicePoint2Extensions.cs new file mode 100644 index 0000000..e92031f --- /dev/null +++ b/servicepoint2-binding-cs/src/ServicePoint2Extensions.cs @@ -0,0 +1,16 @@ +using System.Diagnostics.CodeAnalysis; + +namespace ServicePoint2; + +public static class ServicePoint2Extensions +{ + public static Packet IntoPacket(this Command command) + { + return Packet.FromCommand(command); + } + + public static bool TryIntoCommand(this Packet packet, [MaybeNullWhen(false)] out Command command) + { + return Command.TryFromPacket(packet, out command); + } +} diff --git a/servicepoint2-binding-cs/src/Sp2NativeInstance.cs b/servicepoint2-binding-cs/src/Sp2NativeInstance.cs index 3c44f28..9b4d94a 100644 --- a/servicepoint2-binding-cs/src/Sp2NativeInstance.cs +++ b/servicepoint2-binding-cs/src/Sp2NativeInstance.cs @@ -22,7 +22,7 @@ public abstract class Sp2NativeInstance _instance = instance; } - protected abstract void Dealloc(); + private protected abstract void Dealloc(); internal unsafe T* Into() { diff --git a/servicepoint2/src/bit_vec.rs b/servicepoint2/src/bit_vec.rs index 6e41c1f..98d6421 100644 --- a/servicepoint2/src/bit_vec.rs +++ b/servicepoint2/src/bit_vec.rs @@ -71,14 +71,17 @@ impl BitVec { self.data.fill(byte); } + /// Gets the length in bits pub fn len(&self) -> usize { self.data.len() * 8 } - pub fn data_ref(&self) -> &[u8] { - &*self.data + /// Get the underlying bits in their raw packed bytes form + pub fn mut_data_ref(&mut self) -> &mut [u8] { + self.data.as_mut_slice() } + /// Calculates the byte index and bitmask for a specific bit in the vector fn get_indexes(&self, index: usize) -> (usize, u8) { let byte_index = index / 8; let bit_in_byte_index = 7 - index % 8; @@ -87,13 +90,15 @@ impl BitVec { } } -impl Into> for BitVec { - fn into(self) -> Vec { - self.data +impl From for Vec { + /// Turns the `BitVec` into the underlying `Vec` + fn from(value: BitVec) -> Self { + value.data } } impl From<&[u8]> for BitVec { + /// Interpret the data as a series of bits and load then into a new `BitVec` instance. fn from(value: &[u8]) -> Self { Self { data: Vec::from(value), @@ -102,6 +107,7 @@ impl From<&[u8]> for BitVec { } impl std::fmt::Debug for BitVec { + /// Formats a `BitVec` for debug. The manual implementation includes the length of the instance. fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fmt.debug_struct("BitVec") .field("len", &self.len()) @@ -112,7 +118,7 @@ impl std::fmt::Debug for BitVec { #[cfg(feature = "c-api")] pub mod c_api { - use crate::BitVec; + use crate::{BitVec, CByteSlice}; /// Creates a new `BitVec` instance. /// The returned instance has to be freed with `bit_vec_dealloc`. @@ -167,4 +173,22 @@ pub mod c_api { pub unsafe extern "C" fn sp2_bit_vec_len(this: *const BitVec) -> usize { (*this).len() } + + /// Gets an unsafe reference to the data of the `BitVec` instance. + /// + /// ## Safety + /// + /// The caller has to make sure to never access the returned memory after the `BitVec` + /// instance has been consumed or manually deallocated. + /// + /// Reading and writing concurrently to either the original instance or the returned data will + /// result in undefined behavior. + #[no_mangle] + pub unsafe extern "C" fn sp2_bit_vec_unsafe_data_ref(this: *mut BitVec) -> CByteSlice { + let data = (*this).mut_data_ref(); + CByteSlice { + start: data.as_mut_ptr_range().start, + length: data.len(), + } + } } diff --git a/servicepoint2/src/byte_grid.rs b/servicepoint2/src/byte_grid.rs index e8a986b..2ce175a 100644 --- a/servicepoint2/src/byte_grid.rs +++ b/servicepoint2/src/byte_grid.rs @@ -1,7 +1,9 @@ -/// A grid of bytes +/// A 2D grid of bytes #[derive(Debug, Clone)] pub struct ByteGrid { + /// Size in the x-axis pub width: usize, + /// Size in the y-axis pub height: usize, data: Vec, } @@ -50,18 +52,24 @@ impl ByteGrid { pub fn fill(&mut self, value: u8) { self.data.fill(value) } + + /// Get the underlying byte rows + pub fn mut_data_ref(&mut self) -> &mut [u8] { + self.data.as_mut_slice() + } } -impl Into> for ByteGrid { - fn into(self) -> Vec { - self.data +impl From for Vec { + /// Turn into the underlying `Vec` containing the rows of bytes. + fn from(value: ByteGrid) -> Self { + value.data } } #[cfg(feature = "c-api")] pub mod c_api { - use crate::ByteGrid; + use crate::{ByteGrid, CByteSlice}; /// Creates a new `ByteGrid` instance. /// The returned instance has to be freed with `byte_grid_dealloc`. @@ -122,4 +130,22 @@ pub mod c_api pub unsafe extern "C" fn sp2_byte_grid_height(this: *const ByteGrid) -> usize { (*this).height } + + /// Gets an unsafe reference to the data of the `ByteGrid` instance. + /// + /// ## Safety + /// + /// The caller has to make sure to never access the returned memory after the `ByteGrid` + /// instance has been consumed or manually deallocated. + /// + /// Reading and writing concurrently to either the original instance or the returned data will + /// result in undefined behavior. + #[no_mangle] + pub unsafe extern "C" fn sp2_byte_grid_unsafe_data_ref(this: *mut ByteGrid) -> CByteSlice { + let data = (*this).mut_data_ref(); + CByteSlice { + start: data.as_mut_ptr_range().start, + length: data.len(), + } + } } \ No newline at end of file diff --git a/servicepoint2/src/c_slice.rs b/servicepoint2/src/c_slice.rs new file mode 100644 index 0000000..f292285 --- /dev/null +++ b/servicepoint2/src/c_slice.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "c-api")] +#[repr(C)] +/// Represents a span of memory (`&mut [u8]` ) as a struct usable by C code. +/// +/// Usage of this type is inherently unsafe. +pub struct CByteSlice { + /// The start address of the memory + pub start: *mut u8, + /// The amount of memory in bytes + pub length: usize, +} diff --git a/servicepoint2/src/command.rs b/servicepoint2/src/command.rs index c921b04..6a8e02a 100644 --- a/servicepoint2/src/command.rs +++ b/servicepoint2/src/command.rs @@ -1,7 +1,8 @@ use crate::{ - BitVec, ByteGrid, CommandCode, CompressionCode, Header, Packet, PixelGrid, + BitVec, ByteGrid, CompressionCode, Header, Packet, PixelGrid, TILE_SIZE, }; +use crate::command_code::CommandCode; use crate::compression::{into_compressed, into_decompressed}; /// An origin marks the top left position of a window sent to the display. @@ -18,8 +19,10 @@ impl Origin { #[derive(Debug, Clone, Copy)] pub struct Size(pub u16, pub u16); +/// Type alias for documenting the meaning of the u16 in enum values pub type Offset = u16; +/// Type alias for documenting the meaning of the u16 in enum values pub type Brightness = u8; /// A command to send to the display. @@ -29,12 +32,14 @@ pub enum Command { Clear, /// Kills the udp daemon, usually results in a reboot of the display. HardReset, + /// Slowly decrease brightness until off? Untested. FadeOut, /// Set the brightness of tiles CharBrightness(Origin, ByteGrid), /// Set the brightness of all tiles Brightness(Brightness), #[deprecated] + /// Legacy command code, gets ignored by the real display. BitmapLegacy, /// Set pixel data starting at the offset. /// The contained BitVec is always uncompressed. @@ -54,9 +59,10 @@ pub enum Command { BitmapLinearWin(Origin, PixelGrid), } -impl Into for Command { - fn into(self) -> Packet { - match self { +impl From for Packet { + /// Move the `Command` into a `Packet` instance for sending. + fn from(value: Command) -> Self { + match value { Command::Clear => command_code_only(CommandCode::Clear), Command::FadeOut => command_code_only(CommandCode::FadeOut), Command::HardReset => command_code_only(CommandCode::HardReset), @@ -64,10 +70,9 @@ impl Into for Command { Command::BitmapLegacy => { command_code_only(CommandCode::BitmapLegacy) } - Command::CharBrightness(origin, grid) => origin_size_payload( - CommandCode::CharBrightness, - origin, - Size(grid.width as u16, grid.height as u16), + Command::CharBrightness(Origin(x, y), grid) => Packet( + Header(CommandCode::CharBrightness.into(), + x, y, grid.width as u16, grid.height as u16), grid.into(), ), Command::Brightness(brightness) => Packet( @@ -126,10 +131,8 @@ impl Into for Command { bits.into(), ) } - Command::Cp437Data(origin, grid) => origin_size_payload( - CommandCode::Cp437Data, - origin, - Size(grid.width as u16, grid.height as u16), + Command::Cp437Data(Origin(x, y), grid) => Packet( + Header(CommandCode::Cp437Data.into(), x, y, grid.width as u16, grid.height as u16), grid.into(), ), } @@ -137,6 +140,7 @@ impl Into for Command { } #[derive(Debug)] +/// Err values for `Command::try_from`. pub enum TryFromPacketError { /// the contained command code does not correspond to a known command InvalidCommand(u16), @@ -155,6 +159,7 @@ pub enum TryFromPacketError { impl TryFrom for Command { type Error = TryFromPacketError; + /// Try to interpret the `Packet` as one containing a `Command` fn try_from(value: Packet) -> Result { let Packet(Header(command_u16, a, b, c, d), _) = value; let command_code = match CommandCode::try_from(command_u16) { @@ -178,9 +183,11 @@ impl TryFrom for Command { )); } - match check_empty_header(header) { - Some(err) => Err(err), - None => Ok(Command::Brightness(payload[0])), + let Header(_, a, b, c, d) = header; + if a != 0 || b != 0 || c != 0 || d != 0 { + Err(TryFromPacketError::ExtraneousHeaderValues) + } else { + Ok(Command::Brightness(payload[0])) } } CommandCode::HardReset => match check_command_only(value) { @@ -238,6 +245,7 @@ impl TryFrom for Command { } } +/// Helper method for BitMapLinear*-Commands into Packet fn bitmap_linear_into_packet( command: CommandCode, offset: Offset, @@ -257,30 +265,12 @@ fn bitmap_linear_into_packet( ) } -fn origin_size_payload( - command: CommandCode, - origin: Origin, - size: Size, - payload: Vec, -) -> Packet { - let Origin(x, y) = origin; - let Size(w, h) = size; - Packet(Header(command.into(), x, y, w, h), payload.into()) -} - +/// Helper method for creating empty packets only containing the command code fn command_code_only(code: CommandCode) -> Packet { Packet(Header(code.into(), 0x0000, 0x0000, 0x0000, 0x0000), vec![]) } -fn check_empty_header(header: Header) -> Option { - let Header(_, a, b, c, d) = header; - if a != 0 || b != 0 || c != 0 || d != 0 { - Some(TryFromPacketError::ExtraneousHeaderValues) - } else { - None - } -} - +/// Helper method for checking that a packet is empty and only contains a command code fn check_command_only(packet: Packet) -> Option { let Packet(Header(_, a, b, c, d), payload) = packet; if payload.len() != 0 { @@ -292,6 +282,7 @@ fn check_command_only(packet: Packet) -> Option { } } +/// Helper method for Packets into BitMapLinear*-Commands fn packet_into_linear_bitmap( packet: Packet, ) -> Result<(BitVec, CompressionCode), TryFromPacketError> { @@ -323,21 +314,17 @@ pub mod c_api use crate::{BitVec, Brightness, ByteGrid, Command, CompressionCode, Offset, Origin, Packet, PixelGrid}; - /// Tries to load a `Command` from the passed array with the specified length. + + /// Tries to turn a `Packet` into a `Command`. The packet is gets deallocated in the process. /// - /// returns: NULL in case of an error, pointer to the allocated command otherwise + /// Returns: pointer to command or NULL #[no_mangle] - pub unsafe extern "C" fn sp2_command_try_load(data: *const u8, length: usize) -> *mut Command { - let data = std::slice::from_raw_parts(data, length); - let packet = match Packet::try_from(data) { + pub unsafe extern "C" fn sp2_command_try_from_packet(packet: *mut Packet) -> *mut Command { + let packet = *Box::from_raw(packet); + match Command::try_from(packet) { Err(_) => return null_mut(), - Ok(packet) => packet - }; - let command = match Command::try_from(packet) { - Err(_) => return null_mut(), - Ok(command) => command, - }; - Box::into_raw(Box::new(command)) + Ok(command) => Box::into_raw(Box::new(command)), + } } /// Clones a `Command` instance diff --git a/servicepoint2/src/command_code.rs b/servicepoint2/src/command_code.rs index 4a9bff9..4c77a23 100644 --- a/servicepoint2/src/command_code.rs +++ b/servicepoint2/src/command_code.rs @@ -1,9 +1,7 @@ -use CommandCode::*; - -/// The codes used for the commands. See the documentation on the corresponding commands. +/// The u16 command codes used for the `Commands`. #[repr(u16)] #[derive(Debug, Copy, Clone)] -pub enum CommandCode { +pub(crate) enum CommandCode { Clear = 0x0002, Cp437Data = 0x0003, CharBrightness = 0x0005, @@ -19,16 +17,20 @@ pub enum CommandCode { BitmapLinearXor = 0x0016, } -impl Into for CommandCode { - fn into(self) -> u16 { - self as u16 +impl From for u16 { + /// returns the u16 command code corresponding to the enum value + fn from(value: CommandCode) -> Self { + value as u16 } } impl TryFrom for CommandCode { type Error = (); + /// Returns the enum value for the specified `u16` or `Error` if the code is unknown. fn try_from(value: u16) -> Result { + use CommandCode::*; + match value { value if value == Clear as u16 => Ok(Clear), value if value == Cp437Data as u16 => Ok(Cp437Data), diff --git a/servicepoint2/src/compression_code.rs b/servicepoint2/src/compression_code.rs index 31f121d..b3e96bd 100644 --- a/servicepoint2/src/compression_code.rs +++ b/servicepoint2/src/compression_code.rs @@ -15,9 +15,9 @@ pub enum CompressionCode { Zs = 0x7a73, } -impl Into for CompressionCode { - fn into(self) -> u16 { - self as u16 +impl From for u16 { + fn from(value: CompressionCode) -> Self { + value as u16 } } diff --git a/servicepoint2/src/connection.rs b/servicepoint2/src/connection.rs index 493d21c..d8656ce 100644 --- a/servicepoint2/src/connection.rs +++ b/servicepoint2/src/connection.rs @@ -42,7 +42,7 @@ impl Connection { /// .expect("connection failed"); /// /// // turn off all pixels - /// connection.send(servicepoint2::Command::Clear) + /// connection.send(servicepoint2::Command::Clear.into()) /// .expect("send failed"); /// /// // turn on all pixels @@ -50,12 +50,12 @@ impl Connection { /// pixels.fill(true); /// /// // send pixels to display - /// connection.send(servicepoint2::Command::BitmapLinearWin(servicepoint2::Origin::top_left(), pixels)) + /// connection.send(servicepoint2::Command::BitmapLinearWin(servicepoint2::Origin::top_left(), pixels).into()) /// .expect("send failed"); /// ``` pub fn send( &self, - packet: impl Into + Debug, + packet: Packet, ) -> Result<(), std::io::Error> { debug!("sending {packet:?}"); let packet: Packet = packet.into(); @@ -71,7 +71,7 @@ pub mod c_api use std::ffi::{c_char, CStr}; use std::ptr::null_mut; - use crate::{Command, Connection}; + use crate::{Connection, Packet}; /// Creates a new instance of Connection. /// The returned instance has to be deallocated with `connection_dealloc`. @@ -87,15 +87,14 @@ pub mod c_api Ok(value) => value }; - let boxed = Box::new(connection); - Box::into_raw(boxed) + Box::into_raw(Box::new(connection)) } /// Sends the command instance. The instance is consumed / destroyed and cannot be used after this call. #[no_mangle] - pub unsafe extern "C" fn sp2_connection_send(connection: *const Connection, command_ptr: *mut Command) -> bool{ - let command = Box::from_raw(command_ptr); - (*connection).send(*command).is_ok() + pub unsafe extern "C" fn sp2_connection_send(connection: *const Connection, command_ptr: *mut Packet) -> bool{ + let packet = Box::from_raw(command_ptr); + (*connection).send(*packet).is_ok() } /// Closes and deallocates a connection instance diff --git a/servicepoint2/src/lib.rs b/servicepoint2/src/lib.rs index 0a72250..05106c1 100644 --- a/servicepoint2/src/lib.rs +++ b/servicepoint2/src/lib.rs @@ -1,12 +1,14 @@ pub use crate::bit_vec::BitVec; pub use crate::byte_grid::ByteGrid; pub use crate::command::{Brightness, Command, Offset, Origin, Size}; -pub use crate::command_code::CommandCode; pub use crate::compression_code::CompressionCode; pub use crate::connection::Connection; pub use crate::packet::{Header, Packet, Payload}; pub use crate::pixel_grid::PixelGrid; +#[cfg(feature = "c-api")] +pub use crate::c_slice::CByteSlice; + mod bit_vec; mod byte_grid; mod command; @@ -16,6 +18,7 @@ mod compression_code; mod connection; mod packet; mod pixel_grid; +mod c_slice; /// size of a single tile in one dimension pub const TILE_SIZE: u16 = 8; diff --git a/servicepoint2/src/packet.rs b/servicepoint2/src/packet.rs index 4bba32b..1d1b0ca 100644 --- a/servicepoint2/src/packet.rs +++ b/servicepoint2/src/packet.rs @@ -1,3 +1,5 @@ +use std::mem::size_of; + /// A raw header. Should probably not be used directly. #[derive(Debug)] pub struct Header(pub u16, pub u16, pub u16, pub u16, pub u16); @@ -9,12 +11,13 @@ pub type Payload = Vec; #[derive(Debug)] pub struct Packet(pub Header, pub Payload); -impl Into for Packet { - fn into(self) -> Vec { - let Packet(Header(mode, a, b, c, d), payload) = self; +impl From for Vec { + /// Turn the packet into raw bytes ready to send + fn from(value: Packet) -> Self { + let Packet(Header(mode, a, b, c, d), payload) = value; let mut packet = vec![0u8; 10 + payload.len()]; - packet[0..=1].copy_from_slice(&u16::to_be_bytes(mode)); + packet[0..=1].copy_from_slice(&u16::to_be_bytes(mode.into())); packet[2..=3].copy_from_slice(&u16::to_be_bytes(a)); packet[4..=5].copy_from_slice(&u16::to_be_bytes(b)); packet[6..=7].copy_from_slice(&u16::to_be_bytes(c)); @@ -33,8 +36,17 @@ fn u16_from_be_slice(slice: &[u8]) -> u16 { u16::from_be_bytes(bytes) } -impl From for Packet { - fn from(value: Vec) -> Self { +impl TryFrom<&[u8]> for Packet { + type Error = (); + + /// Tries to interpret the bytes as a `Packet`. + /// + /// returns: `Error` if slice is not long enough to be a `Packet` + fn try_from(value: &[u8]) -> Result { + if value.len() < size_of::
() { + return Err(()); + } + let mode = u16_from_be_slice(&value[0..=1]); let a = u16_from_be_slice(&value[2..=3]); let b = u16_from_be_slice(&value[4..=5]); @@ -42,19 +54,42 @@ impl From for Packet { let d = u16_from_be_slice(&value[8..=9]); let payload = value[10..].to_vec(); - Packet(Header(mode, a, b, c, d), payload) + Ok(Packet(Header(mode, a, b, c, d), payload)) } } -impl From<&[u8]> for Packet { - fn from(value: &[u8]) -> Self { - let mode = u16_from_be_slice(&value[0..=1]); - let a = u16_from_be_slice(&value[2..=3]); - let b = u16_from_be_slice(&value[4..=5]); - let c = u16_from_be_slice(&value[6..=7]); - let d = u16_from_be_slice(&value[8..=9]); - let payload = value[10..].to_vec(); +#[cfg(feature = "c-api")] +mod c_api { + use std::ptr::null_mut; - Packet(Header(mode, a, b, c, d), payload) + use crate::{Command, Packet}; + + /// Turns a `Command` into a `Packet`. The command gets deallocated in the process. + #[no_mangle] + pub unsafe extern "C" fn sp2_packet_from_command(command: *mut Command) -> *mut Packet { + let command = *Box::from_raw(command); + let packet = command.into(); + Box::into_raw(Box::new(packet)) } -} + + + /// 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 + #[no_mangle] + pub unsafe extern "C" fn sp2_packet_try_load(data: *const u8, length: usize) -> *mut Packet { + let data = std::slice::from_raw_parts(data, length); + match Packet::try_from(data) { + Err(_) => null_mut(), + Ok(packet) => Box::into_raw(Box::new(packet)) + } + } + + /// Deallocates a `Packet`. + /// + /// Note: do not call this if the instance has been consumed in another way, e.g. by sending it. + #[no_mangle] + pub unsafe extern "C" fn sp2_packet_dealloc(this: *mut Packet) { + _ = Box::from_raw(this) + } +} \ No newline at end of file diff --git a/servicepoint2/src/pixel_grid.rs b/servicepoint2/src/pixel_grid.rs index d6b8507..9cad805 100644 --- a/servicepoint2/src/pixel_grid.rs +++ b/servicepoint2/src/pixel_grid.rs @@ -77,20 +77,22 @@ impl PixelGrid { self.bit_vec.fill(value); } - pub fn data_ref(&self) -> &[u8] { - self.bit_vec.data_ref() + pub fn mut_data_ref(&mut self) -> &mut [u8] { + self.bit_vec.mut_data_ref() } } -impl Into> for PixelGrid { - fn into(self) -> Vec { - self.bit_vec.into() +impl From for Vec { + /// Turns a `PixelGrid` into the underlying `Vec`. + fn from(value: PixelGrid) -> Self { + value.bit_vec.into() } } #[cfg(feature = "c-api")] pub mod c_api { + use crate::c_slice::CByteSlice; use crate::PixelGrid; /// Creates a new `PixelGrid` instance. @@ -153,10 +155,21 @@ pub mod c_api (*this).height } - /// Gets a reference to the data of the `PixelGrid` instance. + /// Gets an unsafe reference to the data of the `PixelGrid` instance. + /// + /// ## Safety + /// + /// The caller has to make sure to never access the returned memory after the `PixelGrid` + /// instance has been consumed or manually deallocated. + /// + /// Reading and writing concurrently to either the original instance or the returned data will + /// result in undefined behavior. #[no_mangle] - pub unsafe extern "C" fn sp2_pixel_grid_data_ref(this: *const PixelGrid) -> *const u8 { - // TODO: also return length - (*this).data_ref().as_ptr_range().start + pub unsafe extern "C" fn sp2_pixel_grid_unsafe_data_ref(this: *mut PixelGrid) -> CByteSlice { + let data = (*this).mut_data_ref(); + CByteSlice { + start: data.as_mut_ptr_range().start, + length: data.len(), + } } }