diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e9947d5..2d347ba 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -30,15 +30,10 @@ jobs: - name: build run: cargo build - - name: generate bindings - run: ./generate-binding.sh - - name: check that generated files did not change - run: output=$(git status --porcelain) && [ -z "$output" ] - - name: build example -- glibc release - run: cd example && make clean && make TARGET=aarch64-unknown-linux-gnu PROFILE=release + run: cd example && make -r clean-all && make -r LIBC=gnu LINK=dynamic PROFILE=release - name: build example -- glibc debug - run: cd example && make clean && make TARGET=aarch64-unknown-linux-gnu PROFILE=debug + run: cd example && make -r clean-all && make -r LIBC=gnu LINK=dynamic PROFILE=debug build-size-gnu-unstable: runs-on: ubuntu-latest @@ -52,5 +47,6 @@ jobs: - name: install rust targets run: rustup toolchain install nightly -t aarch64-unknown-linux-gnu -c rust-src --no-self-update - - name: build example -- glibc size-optimized - run: cd example && make clean && make TARGET=aarch64-unknown-linux-gnu PROFILE=size-optimized CARGO="rustup run nightly cargo" LTO=1 + - name: build example -- glibc size_optimized + run: cd example && make clean-all -r + && make -r LIBC=gnu LINK=dynamic PROFILE=size_optimized CARGO="rustup run nightly cargo" LTO=1 diff --git a/.gitignore b/.gitignore index f48287d..6f84fcd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ out .direnv .envrc result -mutants.* \ No newline at end of file +mutants.* +_out_* \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 78e2169..63012a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,15 +4,24 @@ version = 4 [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -25,44 +34,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys", ] [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitvec" @@ -95,30 +104,11 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "cbindgen" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" -dependencies = [ - "clap", - "heck", - "indexmap", - "log", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", - "tempfile", - "toml", -] - [[package]] name = "cc" -version = "1.2.21" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -127,42 +117,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "4.5.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "crc32fast" @@ -174,32 +137,33 @@ dependencies = [ ] [[package]] -name = "equivalent" -version = "1.0.2" +name = "env_filter" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ - "libc", - "windows-sys", + "log", + "regex", ] [[package]] -name = "fastrand" -version = "2.3.0" +name = "env_logger" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -213,9 +177,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -224,25 +188,14 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.15.3" +name = "inherent" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "indexmap" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "6c38228f24186d9cc68c729accb4d413be9eaed6ad07ff79e0270d9e56f3de13" dependencies = [ - "equivalent", - "hashbrown", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -252,10 +205,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "itoa" -version = "1.0.15" +name = "jiff" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "jobserver" @@ -269,15 +240,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "log" @@ -287,15 +252,15 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -306,12 +271,39 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -332,9 +324,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -342,6 +334,35 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rust-lzma" version = "0.6.0" @@ -352,25 +373,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "serde" version = "1.0.219" @@ -391,36 +393,16 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - [[package]] name = "servicepoint" -version = "0.14.1" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6bd5cfa49c73aeecb344680ffbf697abf73e0563a441b93b9723ae43867500f" +checksum = "2800caad491cb44f67e5dd5b8c61ece368eecfe588155d03c7d9864acbad6919" dependencies = [ "bitvec", "bzip2", "flate2", + "inherent", "log", "once_cell", "rust-lzma", @@ -430,9 +412,10 @@ dependencies = [ [[package]] name = "servicepoint_binding_c" -version = "0.14.1" +version = "0.15.0" dependencies = [ - "cbindgen", + "env_logger", + "paste", "servicepoint", ] @@ -442,17 +425,11 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[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.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -465,19 +442,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "tempfile" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" -dependencies = [ - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys", -] - [[package]] name = "thiserror" version = "2.0.12" @@ -498,47 +462,6 @@ dependencies = [ "syn", ] -[[package]] -name = "toml" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" - [[package]] name = "unicode-ident" version = "1.0.18" @@ -639,15 +562,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index 0eba069..f6bef14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "servicepoint_binding_c" -version = "0.14.1" +version = "0.15.0" publish = true edition = "2021" license = "GPL-3.0-or-later" @@ -8,23 +8,26 @@ description = "C bindings for the servicepoint crate." homepage = "https://docs.rs/crate/servicepoint_binding_c" repository = "https://git.berlin.ccc.de/servicepoint/servicepoint" readme = "README.md" -links = "servicepoint" keywords = ["cccb", "cccb-servicepoint", "cbindgen"] [lib] crate-type = ["staticlib", "cdylib", "rlib"] -[build-dependencies] -cbindgen = "0.28.0" - [dependencies.servicepoint] -package = "servicepoint" -version = "0.14.1" +version = "0.15.1" default-features = false +[dependencies.env_logger] +version = "0.11.8" +optional = true + +[dependencies] +paste = "1.0.15" + [features] all_compressions = ["servicepoint/all_compressions"] -default = ["all_compressions", "servicepoint/default"] +default = ["all_compressions", "servicepoint/default", "env_logger"] +env_logger = ["dep:env_logger"] [lints.rust] missing-docs = "warn" @@ -36,7 +39,7 @@ missing_safety_doc = "allow" [package.metadata.docs.rs] all-features = true -[profile.size-optimized] +[profile.size_optimized] inherits = "release" opt-level = 'z' # Optimize for size lto = true # Enable link-time optimization diff --git a/README.md b/README.md index 4788c75..fa0a49b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # servicepoint_binding_c +[![Release](https://git.berlin.ccc.de/servicepoint/servicepoint_binding_c/badges/release.svg)](https://git.berlin.ccc.de/servicepoint/servicepoint_binding_c/releases) [![crates.io](https://img.shields.io/crates/v/servicepoint_binding_c.svg)](https://crates.io/crates/servicepoint) [![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint_binding_c)](https://crates.io/crates/servicepoint) [![docs.rs](https://img.shields.io/docsrs/servicepoint_binding_c)](https://docs.rs/servicepoint/latest/servicepoint/) [![GPLv3 licensed](https://img.shields.io/crates/l/servicepoint_binding_c)](./LICENSE) +[![CI](https://git.berlin.ccc.de/servicepoint/servicepoint_binding_c/badges/workflows/rust.yml/badge.svg)](https://git.berlin.ccc.de/servicepoint/servicepoint_binding_c) In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point Display" or "Airport Display". @@ -12,7 +14,7 @@ This crate contains C bindings for the [servicepoint](https://git.berlin.ccc.de/ ## Examples -```c++ +```c #include #include "servicepoint.h" diff --git a/build.rs b/build.rs deleted file mode 100644 index 83e9641..0000000 --- a/build.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Build script generating the header for the `servicepoint` C library. -//! -//! When the environment variable `SERVICEPOINT_HEADER_OUT` is set, the header is copied there from -//! the out directory. This can be used to use the build script as a command line tool from other -//! build tools. - -use std::{env, fs::copy}; - -fn main() { - let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - println!("cargo::rerun-if-changed={crate_dir}"); - - let config = - 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"; - - if let Ok(bindings) = cbindgen::generate_with_config(crate_dir, config) { - bindings.write_to_file(&header_file); - - 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 221e915..6825722 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -16,27 +16,28 @@ line_endings = "LF" ############################# Codegen Options ################################## -style = "type" +style = "both" usize_is_size_t = true # this is needed because otherwise the order in the C bindings is different on different machines sort_by = "Name" +include_guard = "SERVICEPOINT_BINDINGS_C" + [parse] parse_deps = true include = ["servicepoint", "std"] -extra_bindings = ["servicepoint"] +extra_bindings = ["servicepoint", "servicepoint_binding_c"] [parse.expand] -features = ["full"] +crates = ["servicepoint_binding_c", "paste"] +features = [] [export] include = [] -exclude = [] +exclude = ["BitVec"] [export.rename] -"SpBitVec" = "BitVec" -"SpByteSlice" = "ByteSlice" [enum] rename_variants = "QualifiedScreamingSnakeCase" diff --git a/devShells.nix b/devShells.nix new file mode 100644 index 0000000..de4f709 --- /dev/null +++ b/devShells.nix @@ -0,0 +1,71 @@ +{ + pkgs, + fenix, + selfPkgs, + ... +}: +let + common = { + RUST_BACKTRACE = 1; + RUST_LOG = "all"; + packages = with pkgs; [ + gdb + nix-output-monitor + ]; + }; +in rec { + nightly = pkgs.mkShell ( common // { + inputsFrom = [ + selfPkgs.servicepoint-binding-c-nightly-release + ]; + packages = with pkgs; [ + cargo-expand + cargo-tarpaulin + nix-output-monitor + gcc + gnumake + rustfmt + xe + libgcc + libunwind + pkgsStatic.gcc + pkgsStatic.libgcc + pkgsStatic.musl + rust-cbindgen + ]; + }); + stable = pkgs.mkShell (common // { + inputsFrom = [ + selfPkgs.servicepoint-binding-c + selfPkgs.announce + ]; + packages = with pkgs; [ + (pkgs.symlinkJoin { + name = "rust-toolchain"; + paths = with pkgs; [ + rustc + cargo + rustPlatform.rustcSrc + rustPlatform.rustLibSrc + rustfmt + clippy + cargo-expand + cargo-tarpaulin + ]; + }) + cargo-expand + cargo-tarpaulin + gcc + gnumake + xe + libgcc + libunwind + pkgsStatic.gcc + pkgsStatic.libgcc + pkgsStatic.musl + ]; + + RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}"; + }); + default = stable; +} diff --git a/example/Makefile b/example/Makefile index 32e6091..15e53d1 100644 --- a/example/Makefile +++ b/example/Makefile @@ -1,129 +1,121 @@ +# based on https://make.mad-scientist.net/papers/multi-architecture-builds/ + +_libc := $(if $(LIBC),$(LIBC),gnu) +ifeq (,$(filter gnu musl,$(_libc))) + _link_type := $(error "LIBC has to be set to one of: gnu, musl") +endif + +_link_type := $(if $(LINK),$(LINK),dynamic) +ifeq (,$(filter dynamic static,$(_link_type))) + _link_type := $(error "LINK has to be set to one of: dynamic, static") +endif + +_profile := $(if $(PROFILE),$(PROFILE),release) +ifeq (,$(filter release debug size_optimized,$(_profile))) + _profile := $(error "PROFILE has to be set to one of: debug, release, size_optimized") +endif + +ARCH ?= $(shell uname -m) +RUST_TARGET := $(ARCH)-unknown-linux-$(_libc) + +#----- Begin Boilerplate +ifeq (,$(filter _out_%,$(notdir $(CURDIR)))) + include target.mk +else +#----- End Boilerplate + +VPATH = $(SRCDIR) + CARGO ?= cargo STRIP ?= strip +CC ?= gcc -FEATURES := "" +FEATURES := ""#"env_logger" THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) REPO_ROOT := $(realpath $(THIS_DIR)/..) -export SERVICEPOINT_HEADER_OUT := $(REPO_ROOT)/include +CARGO_OBJDIR := cargo/$(RUST_TARGET)/$(_profile) -override CFG_MUSL := $(if $(CFG_MUSL),$(CFG_MUSL),$(if $(MUSL),$(MUSL),0)) -override CFG_PROFILE := $(if $(CFG_PROFILE),$(CFG_PROFILE),$(if $(PROFILE),$(PROFILE),release)) +_rust_cli_profile := $(if $(filter $(_profile),debug),dev,$(_profile)) -CCFLAGS += -Wall -fwhole-program -fPIE -pie - -STRIPFLAGS := -s --strip-unneeded -R .comment -R .gnu.version -R .note -R .note.gnu.build-id -R .note.ABI-tag - -ifeq ($(CFG_MUSL), 1) - TARGET ?= x86_64-unknown-linux-musl - CC ?= musl-gcc - CCFLAGS += -static -lservicepoint_binding_c - RUSTFLAGS += --crate-type=staticlib -Ctarget-feature=-crt-static -else - TARGET ?= x86_64-unknown-linux-gnu - CC ?= gcc - #CCFLAGS += -shared - CCFLAGS += -Wl,-Bstatic -lservicepoint_binding_c -Wl,-Bdynamic -endif - -#ifeq ($(CFG_PROFILE), size-optimized) -# CCFLAGS += -nodefaultlibs -lc -#endif - -RUST_TARGET_DIR := $(REPO_ROOT)/target/$(TARGET)/$(CFG_PROFILE) - -ifeq ($(CFG_PROFILE), size-optimized) - CARGO_PROFILE := size-optimized - CCFLAGS += -Oz \ - -fwrapv -fomit-frame-pointer -fno-stack-protector\ - -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 \ - -fno-exceptions - CARGOFLAGS += -Zbuild-std="core,std,alloc,proc_macro,panic_abort" \ - -Zbuild-std-features="panic_immediate_abort" - RUSTFLAGS += -Zlocation-detail=none \ - -Zfmt-debug=none \ - -C link-arg=-z,norelro \ - -C panic=abort - #-C link-arg=--hash-style=gnu -else ifeq ($(CFG_PROFILE), release) - CARGO_PROFILE := release - CCFLAGS += -O2 -else ifeq ($(CFG_PROFILE), debug) - CCFLAGS += -Og - CARGO_PROFILE := dev -else - CFG_PROFILE := $(error "PROFILE has to be set to one of: debug, release, size-optimized") -endif +STRIPFLAGS += -s --strip-unneeded -R .comment -R .gnu.version -R .note -R .note.gnu.build-id -R .note.ABI-tag CARGOFLAGS += --manifest-path=$(REPO_ROOT)/Cargo.toml \ - --profile=$(CARGO_PROFILE) \ + --profile=$(_rust_cli_profile) \ --no-default-features \ --features=$(FEATURES) \ - --target=$(TARGET) + --target=$(RUST_TARGET) \ + --target-dir=cargo -ifneq ($(CFG_PROFILE), debug) - CCFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections +STATIC_LINK_LIBS += -lservicepoint_binding_c +_servicepoint_cflags := -I $(REPO_ROOT)/include -L $(CARGO_OBJDIR) +CFLAGS += -Wall -Wextra -pedantic -fwhole-program -fPIE -pie +_no_debug_cflags := -ffunction-sections -fdata-sections -Wl,--gc-sections +size_optimized_CFLAGS += -Oz \ + -fwrapv -fomit-frame-pointer -fno-stack-protector \ + -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 \ + -fno-exceptions \ + $(_no_debug_cflags) + +release_CFLAGS += -O2 \ + $(_no_debug_cflags) +debug_CFLAGS += -Og +static_CFLAGS += -static $(STATIC_LINK_LIBS) +dynamic_CFLAGS += -Wl,-Bstatic $(STATIC_LINK_LIBS) -Wl,-Bdynamic + +_servicepoint_cflags := $(shell pkg-config --libs servicepoint --cflags || echo -I $(REPO_ROOT)/include -L $(CARGO_OBJDIR)) +CFLAGS += $($(_libc)_CFLAGS) $($(_profile)_CFLAGS) $($(_link_type)_CFLAGS) $(_servicepoint_cflags) +ifeq ($(LTO), 1) + CFLAGS += -flto +endif +ifeq ($(_libc),gnu) + ifeq ($(_link_type),static) + CFLAGS += $(error "statically linking glibc is known to be broken") + endif +endif + +size_optimized_RUSTFLAGS += -Zlocation-detail=none \ + -Zfmt-debug=none \ + -C link-arg=-z,norelro \ + -C panic=abort \ + -C link-arg=-Wl,--hash-style=gnu +static_RUSTFLAGS += --crate-type=staticlib -Ctarget-feature=+crt-static + +RUSTFLAGS += $($(_libc)_RUSTFLAGS) $($(_profile)_RUSTFLAGS) $($(_link_type)_RUSTFLAGS) +ifneq ($(_profile), debug) RUSTFLAGS += -C link-arg=-s -C link-arg=-Wl,--gc-sections endif -ifeq ($(LTO), 1) - CCFLAGS += -flto -endif - -_c_src := $(wildcard ./src/*.c) +# ADD NEW EXAMPLES HERE +_c_src := src/announce.c src/brightness_tester.c src/header_logger.c \ + src/moving_line.c src/random_stuff.c src/wiping_clear.c src/undefined.c _programs := $(basename $(notdir $(_c_src))) -_bins := $(addprefix out/, $(_programs)) +_bins := $(_programs) _unstripped_bins := $(addsuffix _unstripped, $(_bins)) -_run_programs := $(addprefix run_, $(_programs)) -_rs_src := $(wildcard ../src/**.rs) ../Cargo.lock ../Cargo.toml ../cbindgen.toml -_sp_artifacts := $(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(RUST_TARGET_DIR)/libservicepoint_binding_c.a $(RUST_TARGET_DIR)/libservicepoint_binding_c.so -all: $(_bins) +.SUFFIXES: -clean: clean-c clean-rust +.PHONY: all build-rust -clean-c: - rm -r out || true +all: build-rust $(_bins) -clean-rust: - rm $(SERVICEPOINT_HEADER_OUT)/servicepoint.h || true - cargo clean +$(_unstripped_bins): %_unstripped : src/%.c src/helpers.h build-rust + $(CC) $< $(CFLAGS) -o $@ -.PHONY: all clean sizes $(_run_programs) clean-c clean-rust - -$(_unstripped_bins): out/%_unstripped: src/%.c $(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(_sp_artifacts) - mkdir -p out || true - $(CC) $< \ - -I $(SERVICEPOINT_HEADER_OUT) \ - -L $(RUST_TARGET_DIR) \ - $(CCFLAGS) \ - -o $@ - -$(_bins): out/%: out/%_unstripped +$(_bins): %: %_unstripped $(STRIP) $(STRIPFLAGS) $^ -o $@ -$(_sp_artifacts): $(_rs_src) - mkdir -p $(SERVICEPOINT_HEADER_OUT) || true +build-rust: # generate servicepoint header and library to link against $(CARGO) rustc $(CARGOFLAGS) -- $(RUSTFLAGS) -$(_run_programs): run_%: out/% FORCE - ./$< - -sizes: $(_bins) - ls -lB out - -analyze-size: out/$(BIN)_unstripped - nm --print-size --size-sort --reverse-sort --radix=d --demangle out/$(BIN)_unstripped \ - | awk '{size=$$2+0; print size "\t" $$4}' \ - | less - -FORCE: ; +#----- Begin Boilerplate +endif diff --git a/example/src/announce.c b/example/src/announce.c index d256cff..dda1f67 100644 --- a/example/src/announce.c +++ b/example/src/announce.c @@ -1,16 +1,9 @@ -#include -#include "servicepoint.h" - +#include "helpers.h" int main(void) { - printf("test\n"); + sock_init(); - UdpSocket *connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); - //UdpSocket *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}); + sp_udp_socket_send_header(sock, (Header) {.command_code = COMMAND_CODE_CLEAR}); CharGrid *grid = sp_char_grid_new(5, 2); if (grid == NULL) @@ -27,11 +20,10 @@ int main(void) { 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); + Packet *packet = sp_char_grid_try_into_packet(grid, 0, 0); if (packet == NULL) return 1; - sp_udp_send_packet(connection, packet); + sp_udp_socket_send_packet(sock, packet); - sp_udp_free(connection); return 0; } diff --git a/example/src/brightness_tester.c b/example/src/brightness_tester.c index c3e42ba..cdd2a05 100644 --- a/example/src/brightness_tester.c +++ b/example/src/brightness_tester.c @@ -1,30 +1,35 @@ #include "servicepoint.h" +#include "helpers.h" -int main(void) { - UdpSocket *connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); - //UdpSocket *connection = sp_udp_open_ipv4(127, 0, 0, 1, 2342); - if (connection == NULL) - return -1; - +void enable_all_pixels(void) { 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; + BitmapCommand *bitmapCommand = sp_bitmap_command_from_bitmap(all_on); + Packet *packet = sp_bitmap_command_try_into_packet(bitmapCommand); + if (packet != NULL) + sp_udp_socket_send_packet(sock, packet); +} - sp_udp_send_packet(connection, packet); +void make_brightness_pattern(BrightnessGrid *grid) { + ByteSlice slice = sp_brightness_grid_data_ref_mut(grid); + for (size_t index = 0; index < slice.length; index++) { + slice.start[index] = (uint8_t)(index % ((size_t) Brightness_MAX)); + } +} + +int main(void) { + sock_init(); + + enable_all_pixels(); BrightnessGrid *grid = sp_brightness_grid_new(TILE_WIDTH, TILE_HEIGHT); + make_brightness_pattern(grid); - 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 *packet = sp_brightness_grid_command_try_into_packet(sp_brightness_grid_command_from_grid(grid)); + if (packet == NULL) + return -2; - packet = sp_brightness_grid_into_packet(grid, 0, 0); - sp_udp_send_packet(connection, packet); - - sp_udp_free(connection); + sp_udp_socket_send_packet(sock, packet); return 0; } diff --git a/example/src/header_logger.c b/example/src/header_logger.c new file mode 100644 index 0000000..82b8566 --- /dev/null +++ b/example/src/header_logger.c @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "servicepoint.h" + +#define DEFAULT_LISTEN_IP "127.0.0.1" + +void handle_error(const char *msg) { + perror(msg); + exit(EXIT_FAILURE); +} + +bool log_command(struct GenericCommand *command) { + switch (command->tag) { + case COMMAND_TAG_INVALID: { + printf("-> this is an invalid command\n"); + break; + } + case COMMAND_TAG_HARD_RESET: { + printf("-> HardReset command - exiting\n"); + return true; + } + case COMMAND_TAG_BITMAP: { + BitmapCommand *bitmapCommand = command->data.bitmap; + + CompressionCode compression = sp_bitmap_command_get_compression(bitmapCommand); + + size_t x, y; + sp_bitmap_command_get_origin(bitmapCommand, &x, &y); + + Bitmap *bitmap = sp_bitmap_command_get_bitmap_mut(bitmapCommand); + size_t w = sp_bitmap_width(bitmap); + size_t h = sp_bitmap_height(bitmap); + + printf("-> BitmapCommand with params: x=%zu, y=%zu, w=%zu, h=%zu, compression=%hu\n", + x, y, w, h, compression); + break; + } + case COMMAND_TAG_BRIGHTNESS_GRID: { + BrightnessGridCommand *gridCommand = command->data.brightness_grid; + + size_t x, y; + sp_brightness_grid_command_get_origin(gridCommand, &x, &y); + + BrightnessGrid *grid = sp_brightness_grid_command_get_grid_mut(gridCommand); + size_t w = sp_brightness_grid_width(grid); + size_t h = sp_brightness_grid_height(grid); + + printf("-> BrightnessGridCommand with params: x=%zu, y=%zu, w=%zu, h=%zu\n", + x, y, w, h); + break; + } + case COMMAND_TAG_CHAR_GRID: { + CharGridCommand *gridCommand = command->data.char_grid; + + size_t x, y; + sp_char_grid_command_get_origin(gridCommand, &x, &y); + + CharGrid *grid = sp_char_grid_command_get_grid_mut(gridCommand); + size_t w = sp_char_grid_width(grid); + size_t h = sp_char_grid_height(grid); + + printf("-> CharGridCommand with params: x=%zu, y=%zu, w=%zu, h=%zu\n", + x, y, w, h); + break; + } + case COMMAND_TAG_CP437_GRID: { + Cp437GridCommand *gridCommand = command->data.cp437_grid; + + size_t x, y; + sp_cp437_grid_command_get_origin(gridCommand, &x, &y); + + Cp437Grid *grid = sp_cp437_grid_command_get_grid_mut(gridCommand); + size_t w = sp_cp437_grid_width(grid); + size_t h = sp_cp437_grid_height(grid); + + printf("-> Cp437GridCommand with params: x=%zu, y=%zu, w=%zu, h=%zu\n", + x, y, w, h); + break; + } + case COMMAND_TAG_BIT_VEC: { + BitVecCommand *bitvecCommand = command->data.bit_vec; + + size_t offset = sp_bit_vec_command_get_offset(bitvecCommand); + CompressionCode compression = sp_bit_vec_command_get_compression(bitvecCommand); + + BinaryOperation operation = sp_bit_vec_command_get_operation(bitvecCommand); + char *operationText; + switch (operation) { + case BINARY_OPERATION_AND: + operationText = "and"; + break; + case BINARY_OPERATION_OR: + operationText = "or"; + break; + case BINARY_OPERATION_XOR: + operationText = "xor"; + break; + case BINARY_OPERATION_OVERWRITE: + operationText ="overwrite"; + break; + default: + operationText ="unknown"; + break; + } + + DisplayBitVec *bitvec = sp_bit_vec_command_get_bitvec_mut(bitvecCommand); + size_t len = sp_display_bit_vec_len(bitvec); + + printf("-> BitVecCommand with params: offset=%zu, length=%zu, compression=%hu, operation=%s\n", + offset, len, compression, operationText); + break; + } + case COMMAND_TAG_CLEAR: { + printf("-> ClearCommand\n"); + break; + } + case COMMAND_TAG_BITMAP_LEGACY: { + printf("-> BitmapLinearLegacy\n"); + break; + } + case COMMAND_TAG_FADE_OUT:{ + printf("-> FadeOutCommand\n"); + break; + } + case COMMAND_TAG_GLOBAL_BRIGHTNESS: { + Brightness brightness = sp_global_brightness_command_get_brightness(command->data.global_brightness); + printf("-> GlobalBrightnessCommand with params: brightness=%hu\n", brightness); + break; + } + default: { + printf("-> unknown command tag %d\n", command->tag); + break; + } + } + + return false; +} + +int main(int argc, char **argv) { + //init_env_logger(); + + int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); + if (udp_socket == -1) + handle_error("socket could not be created\n"); + + char *listen_addr_arg; + if (argc > 1) { + listen_addr_arg = argv[1]; + } else { + listen_addr_arg = DEFAULT_LISTEN_IP; + } + + struct in_addr addr; + if (inet_aton(listen_addr_arg, &addr) == 0) + handle_error("listen ip could not be parsed\n"); + + struct sockaddr_in sockaddrIn = { + .sin_addr = addr, + .sin_family = AF_INET, + .sin_port = htons(2342), + }; + memset(sockaddrIn.sin_zero, 0, sizeof(sockaddrIn.sin_zero)); + + if (bind(udp_socket, (struct sockaddr *) &sockaddrIn, sizeof(sockaddrIn)) == -1) + handle_error("could not bind socket\n"); + + printf("socket ready to receive on %s\n", listen_addr_arg); + + uint8_t buffer[10000]; + bool done = false; + while (!done) { + memset(buffer, 0, sizeof(buffer)); + printf("\n"); + + ssize_t num_bytes = recv(udp_socket, (void *) buffer, sizeof(buffer), 0); + if (num_bytes == -1) + handle_error("could not read from client"); + + Packet *packet = sp_packet_try_load((ByteSlice) { + .start = buffer, + .length = num_bytes, + }); + if (packet == NULL) { + printf("received invalid packet\n"); + continue; + } + + struct Header *header = sp_packet_get_header_mut(packet); + + ByteSlice payload = sp_packet_get_payload(packet); + printf("Received packet: cc=%d, a=%d, b=%d, c=%d, d=%d, payload=%p (len %zu)\n", + header->command_code, header->a, header->b, header->c, header->d, + payload.start, payload.length); + + struct GenericCommand *command = sp_generic_command_try_from_packet(packet); + done = log_command(command); + + sp_generic_command_free(command); + } + + close(udp_socket); + exit(EXIT_SUCCESS); +} diff --git a/example/src/helpers.h b/example/src/helpers.h new file mode 100644 index 0000000..96a0b0a --- /dev/null +++ b/example/src/helpers.h @@ -0,0 +1,44 @@ +#pragma once +#ifndef SERVICEPOINT_BINDING_C_MSLEEP_H +#define SERVICEPOINT_BINDING_C_MSLEEP_H + +#include +#include +#include +#include "servicepoint.h" + +static UdpSocket *sock = NULL; + +void sock_free() { + sp_udp_socket_free(sock); +} + +void sock_init() { + sock = sp_udp_socket_open_ipv4(127, 0, 0, 1, 2342); + //sock = sp_udp_open_ipv4(172, 23, 42, 29, 2342); + if (sock == NULL) + exit(-1); + atexit(sock_free); +} + +/// TODO: all of this for sleeping n ms? There should be a better way! +int msleep(long msec) { + if (msec < 0) { + errno = EINVAL; + return -1; + } + + struct timespec ts = { + .tv_sec = msec / 1000, + .tv_nsec = (msec % 1000) * 1000000, + }; + + int res; + do { + res = nanosleep(&ts, &ts); + } while (res && errno == EINTR); + + return res; +} + +#endif //SERVICEPOINT_BINDING_C_MSLEEP_H diff --git a/example/src/moving_line.c b/example/src/moving_line.c new file mode 100644 index 0000000..1aacceb --- /dev/null +++ b/example/src/moving_line.c @@ -0,0 +1,34 @@ +#include "servicepoint.h" +#include "helpers.h" + +int main(void) { + sock_init(); + + int result = 0; + Bitmap *bitmap = sp_bitmap_new_max_sized(); + for (size_t x = 0; x < sp_bitmap_width(bitmap); x++) { + sp_bitmap_fill(bitmap, false); + + for (size_t y = 0; y < sp_bitmap_height(bitmap); y++) { + sp_bitmap_set(bitmap, (y + x) % PIXEL_WIDTH, y, true); + } + + BitmapCommand *command = sp_bitmap_command_from_bitmap(sp_bitmap_clone(bitmap)); + Packet *packet = sp_bitmap_command_try_into_packet(command); + if (packet == NULL) { + result = -2; + goto exit; + } + + if (!sp_udp_socket_send_packet(sock, packet)) { + result = -3; + goto exit; + } + + msleep(SP_FRAME_PACING_MS); + } + + exit: + sp_bitmap_free(bitmap); + return result; +} diff --git a/example/src/random_stuff.c b/example/src/random_stuff.c index 2ebd485..417899a 100644 --- a/example/src/random_stuff.c +++ b/example/src/random_stuff.c @@ -1,10 +1,9 @@ #include #include "servicepoint.h" +#include "helpers.h" int main(void) { - UdpSocket *connection = sp_udp_open_ipv4(127, 0, 0, 1, 2342); - if (connection == NULL) - return 1; + sock_init(); Bitmap *pixels = sp_bitmap_new(PIXEL_WIDTH, PIXEL_HEIGHT); if (pixels == NULL) @@ -12,15 +11,13 @@ int main(void) { sp_bitmap_fill(pixels, true); - Packet *packet = sp_bitmap_into_packet(pixels, 0, 0, COMPRESSION_CODE_UNCOMPRESSED); + Packet *packet = sp_bitmap_try_into_packet(pixels, 0, 0, COMPRESSION_CODE_UNCOMPRESSED); if (packet == NULL) return 1; - Header *header = sp_packet_get_header(packet); + Header *header = sp_packet_get_header_mut(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); + sp_udp_socket_send_packet(sock, packet); return 0; } diff --git a/example/src/undefined.c b/example/src/undefined.c new file mode 100644 index 0000000..dfa44d8 --- /dev/null +++ b/example/src/undefined.c @@ -0,0 +1,22 @@ +#include +#include "servicepoint.h" +#include "helpers.h" + +/// DO NOT DO ANY OF THIS! +int main(void) { + BitmapCommand *bmcmd = sp_bitmap_command_new(sp_bitmap_new_max_sized(), 0, 0, COMPRESSION_CODE_UNCOMPRESSED); + BitVecCommand *bvcmd = (BitVecCommand *) bmcmd; + sp_bit_vec_command_free(bvcmd); + + uint8_t *data = calloc(1024, 1); + struct GenericCommand generic = { + .tag = COMMAND_TAG_BRIGHTNESS_GRID, + .data = {.null = data}, + }; + + sock_init(); + + sp_udp_socket_send_command(sock, &generic); + + return 0; +} diff --git a/example/src/wiping_clear.c b/example/src/wiping_clear.c new file mode 100644 index 0000000..dc76604 --- /dev/null +++ b/example/src/wiping_clear.c @@ -0,0 +1,34 @@ +#include "servicepoint.h" +#include "helpers.h" + +int main() { + sock_init(); + + Bitmap *enabled_pixels = sp_bitmap_new_max_sized(); + sp_bitmap_fill(enabled_pixels, true); + + int result = 0; + for (int x = 0; x < PIXEL_WIDTH; x++) { + for (int y = 0; y < PIXEL_HEIGHT; y++) { + sp_bitmap_set(enabled_pixels, x, y, false); + } + + DisplayBitVec *bitvec = sp_bitmap_into_bitvec(sp_bitmap_clone(enabled_pixels)); + BitVecCommand *command = sp_bit_vec_command_new(bitvec, 0, BINARY_OPERATION_AND, COMPRESSION_CODE_LZMA); + Packet *packet = sp_bit_vec_command_try_into_packet(command); + if (packet == NULL) { + result = -2; + goto exit; + } + if (!sp_udp_socket_send_packet(sock, packet)) { + result = -3; + goto exit; + } + + msleep(SP_FRAME_PACING_MS); + } + + exit: + sp_bitmap_free(enabled_pixels); + return result; +} diff --git a/example/target.mk b/example/target.mk new file mode 100644 index 0000000..b7e743c --- /dev/null +++ b/example/target.mk @@ -0,0 +1,56 @@ +# Based on https://make.mad-scientist.net/papers/multi-architecture-builds/ + +.SUFFIXES: + +OBJDIR := _out_$(RUST_TARGET)-$(_link_type)-$(_profile) + +# Define the rules to build in the target subdirectories. +# +MAKETARGET = $(MAKE) --no-print-directory -C $@ -f $(CURDIR)/Makefile \ + SRCDIR=$(CURDIR) $(MAKECMDGOALS) + +.PHONY: $(OBJDIR) +$(OBJDIR): + +@[ -d "$@" ] || mkdir -p "$@" + +@$(MAKETARGET) + +# These rules keep make from trying to use the match-anything rule below to +# rebuild the makefiles--ouch! Obviously, if you don't follow my convention +# of using a `.mk' suffix on all non-standard makefiles you'll need to change +# the pattern rule. +# +Makefile : ; +%.mk :: ; + +# Anything we don't know how to build will use this rule. The command is a +# do-nothing command, but the prerequisites ensure that the appropriate +# recursive invocations of make will occur. +# +% :: $(OBJDIR) ; + +# The clean rule is best handled from the source directory: since we're +# rigorous about keeping the target directories containing only target files +# and the source directory containing only source files, `clean' is as trivial +# as removing the target directories! +# +.PHONY: clean clean-all help +clean: + rm -rf $(OBJDIR) +clean-all: + rm -rf _out_* + +help: + @echo "You have the choice of the following parameters:" + @echo "" + @echo "Variable | Description | Default | Values" + @echo "---------+----------------------+-----------+---------------------------" + @echo "LIBC | libc to link against | 'gnu' | 'gnu' or 'musl'" + @echo "PROFILE | Optimization profile | 'release' | 'debug' or 'size_optimized'" + @echo "LINK | | 'dynamic' | 'dynamic' or 'static'" + @echo "CARGO | cargo binary to use | 'cargo' | 'rustup run nightly cargo'" + @echo "CC | C compiler to use | 'gcc' | 'musl-gcc'" + @echo "STRIP | strip command to use | 'strip' | -" + @echo "" + @echo "When building this project, each configuration will result in a separate output directory." + @echo "The target clean only removes the output directory of the specified configuration." + @echo "The target clean-all can be used to remove the builds of all configurations." diff --git a/flake.lock b/flake.lock index 9e70e85..4db936e 100644 --- a/flake.lock +++ b/flake.lock @@ -1,25 +1,80 @@ { "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1751006353, + "narHash": "sha256-icKFXb83uv2ezRCfuq5G8QSwCuaoLywLljSL+UGmPPI=", + "owner": "nix-community", + "repo": "fenix", + "rev": "b37f026b49ecb295a448c96bcbb0c174c14fc91b", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "nix-filter": { + "locked": { + "lastModified": 1731533336, + "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=", + "owner": "numtide", + "repo": "nix-filter", + "rev": "f7653272fd234696ae94229839a99b73c9ab7de0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "nix-filter", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1739357830, - "narHash": "sha256-9xim3nJJUFbVbJCz48UP4fGRStVW5nv4VdbimbKxJ3I=", + "lastModified": 1750838302, + "narHash": "sha256-aVkL3/yu50oQzi2YuKo0ceiCypVZpZXYd2P2p1FMJM4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "0ff09db9d034a04acd4e8908820ba0b410d7a33a", + "rev": "7284e2decc982b81a296ab35aa46e804baaa1cfe", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-24.11", + "ref": "nixos-25.05", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { + "fenix": "fenix", + "nix-filter": "nix-filter", "nixpkgs": "nixpkgs" } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1750871759, + "narHash": "sha256-hMNZXMtlhfjQdu1F4Fa/UFiMoXdZag4cider2R9a648=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "317542c1e4a3ec3467d21d1c25f6a43b80d83e7d", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 82983f7..1501caa 100644 --- a/flake.nix +++ b/flake.nix @@ -2,16 +2,22 @@ description = "Flake for the servicepoint library."; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; + nix-filter.url = "github:numtide/nix-filter"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = inputs@{ self, nixpkgs, + fenix, + nix-filter }: let - lib = nixpkgs.lib; supported-systems = [ "x86_64-linux" "aarch64-linux" @@ -20,52 +26,20 @@ ]; forAllSystems = f: - lib.genAttrs supported-systems ( + nixpkgs.lib.genAttrs supported-systems ( system: f rec { + inherit system self; pkgs = nixpkgs.legacyPackages.${system}; - inherit system; + lib = pkgs.lib // nix-filter.lib; + fenix = inputs.fenix.packages.${system}; + selfPkgs = self.packages.${system}; } ); in { - devShells = forAllSystems ( - { pkgs, system }: - { - default = pkgs.mkShell rec { - buildInputs = with pkgs; [ - xe - xz - libgcc - libunwind - pkgsStatic.musl - ]; - - nativeBuildInputs = with pkgs; [ - (pkgs.symlinkJoin { - name = "rust-toolchain"; - paths = with pkgs; [ - rustc - cargo - rustPlatform.rustcSrc - rustfmt - clippy - cargo-expand - cargo-tarpaulin - ]; - }) - gcc - gdb - pkgsStatic.gcc - gnumake - pkg-config - ]; - - RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; - }; - } - ); - + packages = forAllSystems (import ./packages.nix); + devShells = forAllSystems (import ./devShells.nix); formatter = forAllSystems ({ pkgs, ... }: pkgs.nixfmt-rfc-style); }; } diff --git a/generate-binding.sh b/generate-binding.sh index 45301bd..744ad29 100755 --- a/generate-binding.sh +++ b/generate-binding.sh @@ -3,4 +3,4 @@ set -e SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -SERVICEPOINT_HEADER_OUT="$SCRIPT_PATH/include" cargo build --release +cbindgen --config $SCRIPT_PATH/cbindgen.toml --crate servicepoint_binding_c --output $SCRIPT_PATH/include/servicepoint.h diff --git a/include/servicepoint.h b/include/servicepoint.h index 5863ef5..e2beb27 100644 --- a/include/servicepoint.h +++ b/include/servicepoint.h @@ -1,3 +1,6 @@ +#ifndef SERVICEPOINT_BINDINGS_C +#define SERVICEPOINT_BINDINGS_C + /* Generated with cbindgen:0.28.0 */ /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ @@ -96,7 +99,7 @@ typedef uint8_t BinaryOperation; #endif // __cplusplus /** - * The u16 command codes used for the [Command]s. + * The u16 command codes used for the [`crate::Command`]s. */ enum CommandCode #ifdef __cplusplus @@ -125,28 +128,59 @@ enum CommandCode typedef uint16_t CommandCode; #endif // __cplusplus +/** + * Specifies the kind of command struct. + * + * This is _not_ equivalent to the [servicepoint::CommandCode]s. + */ +enum CommandTag +#ifdef __cplusplus + : uint8_t +#endif // __cplusplus + { + COMMAND_TAG_INVALID = 0, + COMMAND_TAG_BITMAP, + COMMAND_TAG_BIT_VEC, + COMMAND_TAG_BRIGHTNESS_GRID, + COMMAND_TAG_CHAR_GRID, + COMMAND_TAG_CP437_GRID, + COMMAND_TAG_GLOBAL_BRIGHTNESS, + COMMAND_TAG_CLEAR, + COMMAND_TAG_HARD_RESET, + COMMAND_TAG_FADE_OUT, + COMMAND_TAG_BITMAP_LEGACY, +}; +#ifndef __cplusplus +typedef uint8_t CommandTag; +#endif // __cplusplus + /** * Specifies the kind of compression to use. Availability depends on features. * * # Examples * + * create command without payload compression * ```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 + * create command with payload compressed with lzma and appropriate header flags + * ```rust + * # use servicepoint::*; * # let pixels = Bitmap::max_sized(); + * # #[cfg(feature = "compression_lzma")] { * _ = BitmapCommand { * origin: Origin::ZERO, * bitmap: pixels, * compression: CompressionCode::Lzma * }; + * # } * ``` */ enum CompressionCode @@ -179,6 +213,22 @@ enum CompressionCode typedef uint16_t CompressionCode; #endif // __cplusplus +/** + * 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 [`DisplayBitVec`] is always uncompressed. + */ +typedef struct BitVecCommand BitVecCommand; + /** * A fixed-size 2D grid of booleans. * @@ -196,6 +246,161 @@ typedef uint16_t CompressionCode; */ typedef struct Bitmap Bitmap; +/** + * Overwrites a rectangular region of pixels. + * + * Origin coordinates must be divisible by 8. + * + * # Examples + * + * ```rust + * # use servicepoint::*; + * # let connection = FakeConnection; + * # + * let mut bitmap = Bitmap::max_sized(); + * // draw something to the pixels here + * # bitmap.set(2, 5, true); + * + * // create command to send pixels + * let command = BitmapCommand { + * bitmap, + * origin: Origin::ZERO, + * compression: CompressionCode::Uncompressed + * }; + * + * connection.send_command(command).expect("send failed"); + * ``` + */ +typedef struct BitmapCommand BitmapCommand; + +/** + * Legacy command code, gets ignored by the real display. + * + * Might be useful as a noop package. + * + * # Examples + * + * ```rust + * # use servicepoint::*; + * # let connection = FakeConnection; + * // this sends a packet that does nothing + * connection.send_command(BitmapLegacyCommand).unwrap(); + * ``` + */ +typedef struct BitmapLegacyCommand BitmapLegacyCommand; + +/** + * Set the brightness of individual tiles in a rectangular area of the display. + */ +typedef struct BrightnessGridCommand BrightnessGridCommand; + +/** + * Show text on the screen. + * + * The text is sent in the form of a 2D grid of UTF-8 encoded characters (the default encoding in rust). + * + * # Examples + * + * ```rust + * # use servicepoint::*; + * # let connection = FakeConnection; + * let grid = CharGrid::from("Hello,\nWorld!"); + * connection.send_command(CharGridCommand { origin: Origin::ZERO, grid }).expect("send failed"); + * ``` + */ +typedef struct CharGridCommand CharGridCommand; + +/** + * Set all pixels to the off state. Does not affect brightness. + * + * # Examples + * + * ```rust + * # use servicepoint::*; + * # let connection = FakeConnection; + * connection.send_command(ClearCommand).unwrap(); + * ``` + */ +typedef struct ClearCommand ClearCommand; + +/** + * Show text on the screen. + * + * The text is sent in the form of a 2D grid of [CP-437] encoded characters. + * + *
You probably want to use [Command::Utf8Data] instead
+ * + * # Examples + * + * ```rust + * # use servicepoint::*; + * # let connection = FakeConnection; + * let grid = CharGrid::from("Hello,\nWorld!"); + * # #[cfg(feature = "cp437")] { + * let grid = Cp437Grid::from(&grid); + * connection.send_command(Cp437GridCommand{ origin: Origin::ZERO, grid }).expect("send failed"); + * # } + * ``` + * + * ```rust + * # use servicepoint::*; + * # let connection = FakeConnection; + * let grid = Cp437Grid::load_ascii("Hello\nWorld", 5, false).unwrap(); + * connection.send_command(Cp437GridCommand{ origin: Origin::new(2, 2), grid }).unwrap(); + * ``` + * [CP-437]: https://en.wikipedia.org/wiki/Code_page_437 + */ +typedef struct Cp437GridCommand Cp437GridCommand; + +/** + * This is a type only used by cbindgen to have a type for pointers. + */ +typedef struct DisplayBitVec DisplayBitVec; + +/** + *
Untested
+ * + * Slowly decrease brightness until off or something like that? + * + * # Examples + * + * ```rust + * # use servicepoint::*; + * # let connection = FakeConnection; + * connection.send_command(FadeOutCommand).unwrap(); + * ``` + */ +typedef struct FadeOutCommand FadeOutCommand; + +/** + * Set the brightness of all tiles to the same value. + * + * # Examples + * + * ```rust + * # use servicepoint::*; + * # let connection = FakeConnection; + * let command = GlobalBrightnessCommand { brightness: Brightness::MAX }; + * connection.send_command(command).unwrap(); + * ``` + */ +typedef struct GlobalBrightnessCommand GlobalBrightnessCommand; + +/** + * Kills the udp daemon on the display, which usually results in a restart. + * + * Please do not send this in your normal program flow. + * + * # Examples + * + * ```rust + * # use servicepoint::*; + * # let connection = FakeConnection; + * connection.send_command(HardResetCommand).unwrap(); + * ``` + */ +typedef struct HardResetCommand HardResetCommand; + /** * The raw packet. * @@ -205,28 +410,10 @@ typedef struct Bitmap Bitmap; */ typedef struct Packet Packet; -/** - * A vector of bits - * - * # Examples - * ```C - * SPBitVec vec = sp_bitvec_new(8); - * sp_bitvec_set(vec, 5, true); - * sp_bitvec_free(vec); - * ``` - */ -typedef struct SPBitVec SPBitVec; - -/** - * 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; - /** * This is a type only used by cbindgen to have a type for pointers. + * + * See [servicepoint::UdpSocketExt]. */ typedef struct UdpSocket UdpSocket; @@ -260,6 +447,11 @@ typedef struct ValueGrid_char ValueGrid_char; */ typedef struct ValueGrid_u8 ValueGrid_u8; +/** + * Type alias for documenting the meaning of the u16 in enum values + */ +typedef size_t Offset; + /** * Represents a span of memory (`&mut [u8]` ) as a struct. * @@ -270,12 +462,19 @@ typedef struct ValueGrid_u8 ValueGrid_u8; * - 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. + * - if `start` is NULL or `length` is 0, do not dereference `start`. + * + * # Examples + * + * ```c + * ByteSlice empty = {.start: NULL, .length = 0}; + * ``` */ -typedef struct { +typedef struct ByteSlice { /** * The start address of the memory. */ - uint8_t */*notnull*/ start; + uint8_t *start; /** * The amount of memory in bytes. */ @@ -300,7 +499,7 @@ typedef struct { * }).unwrap() * ``` */ -typedef ValueGrid_Brightness BrightnessGrid; +typedef struct ValueGrid_Brightness BrightnessGrid; /** * A display brightness value, checked for correct value range @@ -346,14 +545,49 @@ typedef uint8_t Brightness; * connection.send_command(command).unwrap() * ``` */ -typedef ValueGrid_char CharGrid; +typedef struct ValueGrid_char CharGrid; /** * A grid containing codepage 437 characters. * * The encoding is currently not enforced. */ -typedef ValueGrid_u8 Cp437Grid; +typedef struct ValueGrid_u8 Cp437Grid; + +/** + * Pointer to one of the available command structs. + */ +typedef union CommandUnion { + uint8_t *null; + struct BitmapCommand */*notnull*/ bitmap; + struct BitVecCommand */*notnull*/ bit_vec; + struct BrightnessGridCommand */*notnull*/ brightness_grid; + struct CharGridCommand */*notnull*/ char_grid; + struct Cp437GridCommand */*notnull*/ cp437_grid; + struct GlobalBrightnessCommand */*notnull*/ global_brightness; + struct ClearCommand */*notnull*/ clear; + struct BitmapLegacyCommand */*notnull*/ bitmap_legacy; + struct HardResetCommand */*notnull*/ hard_reset; + struct FadeOutCommand */*notnull*/ fade_out; +} CommandUnion; + +/** + * This struct represents a pointer to one of the possible command structs. + * + * Only ever access `data` with the correct data type as specified by `tag`! + * + * Rust equivalent: [TypedCommand]. + */ +typedef struct GenericCommand { + /** + * Specifies which kind of command struct is contained in `data` + */ + CommandTag tag; + /** + * The pointer to the command struct + */ + union CommandUnion data; +} GenericCommand; /** * A raw header. @@ -365,7 +599,7 @@ typedef ValueGrid_u8 Cp437Grid; * * The contained values are in platform endian-ness and may need to be converted before sending. */ -typedef struct { +typedef struct Header { /** * The first two bytes specify which command this packet represents. */ @@ -390,29 +624,271 @@ typedef struct { +/** + * Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets. + */ +#define SP_FRAME_PACING_MS 30 + + + #ifdef __cplusplus extern "C" { #endif // __cplusplus /** - * Clones a [Bitmap]. + * Calls method [`BitVecCommand::clone`]. + * + *Clones a [`BitVecCommand`] instance. + * + * This function is part of the `bit_vec_command` module. */ -Bitmap */*notnull*/ sp_bitmap_clone(Bitmap */*notnull*/ bitmap); +struct BitVecCommand */*notnull*/ sp_bit_vec_command_clone(struct BitVecCommand */*notnull*/ instance); /** - * Sets the state of all pixels in the [Bitmap]. + *Deallocates a [`BitVecCommand`] instance. + * + * This function is part of the `bit_vec_command` module. + */ +void sp_bit_vec_command_free(struct BitVecCommand */*notnull*/ instance); + +/** + * Gets a reference to the field `bitvec` of the [`servicepoint::BitVecCommand`]. + * + * - The returned reference inherits the lifetime of object in which it is contained. + * - The returned pointer may not be used in a function that consumes the instance, e.g. to create a command. + * + * This function is part of the `bit_vec_command` module. + */ +DisplayBitVec */*notnull*/ sp_bit_vec_command_get_bitvec_mut(struct BitVecCommand */*notnull*/ instance); + +/** + * Gets the value of field `compression` of the [`servicepoint::BitVecCommand`]. + * + * This function is part of the `bit_vec_command` module. + */ +CompressionCode sp_bit_vec_command_get_compression(struct BitVecCommand */*notnull*/ instance); + +/** + * Gets the value of field `offset` of the [`servicepoint::BitVecCommand`]. + * + * This function is part of the `bit_vec_command` module. + */ +Offset sp_bit_vec_command_get_offset(struct BitVecCommand */*notnull*/ instance); + +/** + * Gets the value of field `operation` of the [`servicepoint::BitVecCommand`]. + * + * This function is part of the `bit_vec_command` module. + */ +BinaryOperation sp_bit_vec_command_get_operation(struct BitVecCommand */*notnull*/ instance); + +/** + * 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 [`DisplayBitVec`] is always uncompressed. + * + * This function is part of the `bit_vec_command` module. + */ +struct BitVecCommand */*notnull*/ sp_bit_vec_command_new(DisplayBitVec */*notnull*/ bitvec, + size_t offset, + BinaryOperation operation, + CompressionCode compression); + +/** + * Sets the value of field `bitvec` of the [`servicepoint::BitVecCommand`]. + * The provided value is moved into the instance, potentially invalidating previously taken references. + * + * This function is part of the `bit_vec_command` module. + */ +void sp_bit_vec_command_set_bitvec(struct BitVecCommand */*notnull*/ instance, + DisplayBitVec */*notnull*/ value); + +/** + * Sets the value of field `compression` of the [`servicepoint::BitVecCommand`]. + * + * This function is part of the `bit_vec_command` module. + */ +void sp_bit_vec_command_set_compression(struct BitVecCommand */*notnull*/ instance, + CompressionCode value); + +/** + * Sets the value of field `offset` of the [`servicepoint::BitVecCommand`]. + * + * This function is part of the `bit_vec_command` module. + */ +void sp_bit_vec_command_set_offset(struct BitVecCommand */*notnull*/ instance, + Offset value); + +/** + * Sets the value of field `operation` of the [`servicepoint::BitVecCommand`]. + * + * This function is part of the `bit_vec_command` module. + */ +void sp_bit_vec_command_set_operation(struct BitVecCommand */*notnull*/ instance, + BinaryOperation value); + +/** + *Tries to turn a [`BitVecCommand`] into a [Packet]. + * + * Returns: NULL or a [Packet] containing the command. + * + * [Packet]: [`servicepoint::Packet`] + * + * This function is part of the `bit_vec_command` module. + */ +struct Packet *sp_bit_vec_command_try_into_packet(struct BitVecCommand */*notnull*/ instance); + +/** + * Calls method [`Bitmap::clone`]. + * + *Clones a [`Bitmap`] instance. + * + * This function is part of the `bitmap` module. + */ +struct Bitmap */*notnull*/ sp_bitmap_clone(struct Bitmap */*notnull*/ instance); + +/** + * Calls method [`BitmapCommand::clone`]. + * + *Clones a [`BitmapCommand`] instance. + * + * This function is part of the `bitmap_command` module. + */ +struct BitmapCommand */*notnull*/ sp_bitmap_command_clone(struct BitmapCommand */*notnull*/ instance); + +/** + *Deallocates a [`BitmapCommand`] instance. + * + * This function is part of the `bitmap_command` module. + */ +void sp_bitmap_command_free(struct BitmapCommand */*notnull*/ instance); + +/** + * Move the provided [Bitmap] into a new [BitmapCommand], + * leaving other fields as their default values. + * + * Rust equivalent: `BitmapCommand::from(bitmap)` + * + * This function is part of the `bitmap_command` module. + */ +struct BitmapCommand */*notnull*/ sp_bitmap_command_from_bitmap(struct Bitmap */*notnull*/ bitmap); + +/** + * Gets a reference to the field `bitmap` of the [`servicepoint::BitmapCommand`]. + * + * - The returned reference inherits the lifetime of object in which it is contained. + * - The returned pointer may not be used in a function that consumes the instance, e.g. to create a command. + * + * This function is part of the `bitmap_command` module. + */ +struct Bitmap */*notnull*/ sp_bitmap_command_get_bitmap_mut(struct BitmapCommand */*notnull*/ instance); + +/** + * Gets the value of field `compression` of the [`servicepoint::BitmapCommand`]. + * + * This function is part of the `bitmap_command` module. + */ +CompressionCode sp_bitmap_command_get_compression(struct BitmapCommand */*notnull*/ instance); + +/** + * Reads the origin field of the [`BitmapCommand`]. + * + * This function is part of the `bitmap_command` module. + */ +void sp_bitmap_command_get_origin(struct BitmapCommand */*notnull*/ command, + size_t */*notnull*/ origin_x, + size_t */*notnull*/ origin_y); + +/** + * Sets a window of pixels to the specified values. + * + * The passed [Bitmap] gets consumed. + * + * Returns: a new [BitmapCommand] instance. + * + * This function is part of the `bitmap_command` module. + */ +struct BitmapCommand */*notnull*/ sp_bitmap_command_new(struct Bitmap */*notnull*/ bitmap, + size_t origin_x, + size_t origin_y, + CompressionCode compression); + +/** + * Sets the value of field `bitmap` of the [`servicepoint::BitmapCommand`]. + * The provided value is moved into the instance, potentially invalidating previously taken references. + * + * This function is part of the `bitmap_command` module. + */ +void sp_bitmap_command_set_bitmap(struct BitmapCommand */*notnull*/ instance, + struct Bitmap */*notnull*/ value); + +/** + * Sets the value of field `compression` of the [`servicepoint::BitmapCommand`]. + * + * This function is part of the `bitmap_command` module. + */ +void sp_bitmap_command_set_compression(struct BitmapCommand */*notnull*/ instance, + CompressionCode value); + +/** + * Overwrites the origin field of the [`BitmapCommand`]. + * + * This function is part of the `bitmap_command` module. + */ +void sp_bitmap_command_set_origin(struct BitmapCommand */*notnull*/ command, + size_t origin_x, + size_t origin_y); + +/** + *Tries to turn a [`BitmapCommand`] into a [Packet]. + * + * Returns: NULL or a [Packet] containing the command. + * + * [Packet]: [`servicepoint::Packet`] + * + * This function is part of the `bitmap_command` module. + */ +struct Packet *sp_bitmap_command_try_into_packet(struct BitmapCommand */*notnull*/ instance); + +/** + * Calls method [`Bitmap::data_ref_mut`]. + * + * Gets an unsafe reference to the data of the [Bitmap] instance. + * + * The returned memory is valid for the lifetime of the bitmap. + * + * This function is part of the `bitmap` module. + */ +struct ByteSlice sp_bitmap_data_ref_mut(struct Bitmap */*notnull*/ instance); + +/** + * Calls method [`Bitmap::fill`]. + * + * Sets the state of all cells in the grid. * * # Arguments * - * - `bitmap`: instance to write to - * - `value`: the value to set all pixels to + * - `value`: the value to set all cells to + * + * This function is part of the `bitmap` module. */ -void sp_bitmap_fill(Bitmap */*notnull*/ bitmap, bool value); +void sp_bitmap_fill(struct Bitmap */*notnull*/ instance, bool value); /** - * Deallocates a [Bitmap]. + *Deallocates a [`Bitmap`] instance. + * + * This function is part of the `bitmap` module. */ -void sp_bitmap_free(Bitmap */*notnull*/ bitmap); +void sp_bitmap_free(struct Bitmap */*notnull*/ instance); /** * Tries to convert the BitVec to a Bitmap. @@ -420,48 +896,44 @@ void sp_bitmap_free(Bitmap */*notnull*/ bitmap); * The provided BitVec gets consumed. * * Returns NULL in case of error. + * + * This function is part of the `bitmap` module. */ -Bitmap *sp_bitmap_from_bitvec(size_t width, SPBitVec */*notnull*/ bitvec); +struct Bitmap *sp_bitmap_from_bitvec(size_t width, + DisplayBitVec */*notnull*/ bitvec); /** - * Gets the current value at the specified position in the [Bitmap]. + * Calls method [`Bitmap::get`]. + * + * Gets the current value at the specified position. * * # Arguments * - * - `bitmap`: instance to read from * - `x` and `y`: position of the cell to read * * # Panics * * - when accessing `x` or `y` out of bounds + * + * This function is part of the `bitmap` module. */ -bool sp_bitmap_get(Bitmap */*notnull*/ bitmap, size_t x, size_t y); +bool sp_bitmap_get(struct Bitmap */*notnull*/ instance, size_t x, size_t y); /** - * Gets the height in pixels of the [Bitmap] instance. + * Calls method [`Bitmap::height`]. * - * # Arguments + * Gets the height. * - * - `bitmap`: instance to read from + * This function is part of the `bitmap` module. */ -size_t sp_bitmap_height(Bitmap */*notnull*/ bitmap); +size_t sp_bitmap_height(struct Bitmap */*notnull*/ instance); /** - * 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]. + * Consumes the Bitmap and returns the contained BitVec. * - * The provided [Bitmap] gets consumed. - * - * Returns NULL in case of an error. + * This function is part of the `bitmap` module. */ -Packet *sp_bitmap_into_packet(Bitmap */*notnull*/ bitmap, - size_t x, - size_t y, - CompressionCode compression); +DisplayBitVec */*notnull*/ sp_bitmap_into_bitvec(struct Bitmap */*notnull*/ bitmap); /** * Loads a [Bitmap] with the specified dimensions from the provided data. @@ -472,10 +944,12 @@ Packet *sp_bitmap_into_packet(Bitmap */*notnull*/ bitmap, * - `height`: size in pixels in y-direction * * returns: [Bitmap] that contains a copy of the provided data, or NULL in case of an error. + * + * This function is part of the `bitmap` module. */ -Bitmap *sp_bitmap_load(size_t width, - size_t height, - ByteSlice data); +struct Bitmap *sp_bitmap_load(size_t width, + size_t height, + struct ByteSlice data); /** * Creates a new [Bitmap] with the specified dimensions. @@ -501,230 +975,215 @@ Bitmap *sp_bitmap_load(size_t width, * sp_bitmap_set(grid, 0, 0, false); * sp_bitmap_free(grid); * ``` + * + * This function is part of the `bitmap` module. */ -Bitmap *sp_bitmap_new(size_t width, size_t height); +struct Bitmap *sp_bitmap_new(size_t width, size_t height); /** * Creates a new [Bitmap] with a size matching the screen. * * returns: [Bitmap] initialized to all pixels off. + * + * This function is part of the `bitmap` module. */ -Bitmap */*notnull*/ sp_bitmap_new_max_sized(void); +struct Bitmap */*notnull*/ sp_bitmap_new_max_sized(void); /** - * Sets the value of the specified position in the [Bitmap]. + * Calls method [`Bitmap::set`]. + * + * Sets the value of the specified position. * * # Arguments * - * - `bitmap`: instance to write to * - `x` and `y`: position of the cell * - `value`: the value to write to the cell * * # Panics * * - when accessing `x` or `y` out of bounds + * + * This function is part of the `bitmap` module. */ -void sp_bitmap_set(Bitmap */*notnull*/ bitmap, size_t x, size_t y, bool value); +void sp_bitmap_set(struct Bitmap */*notnull*/ instance, + size_t x, + size_t y, + bool value); /** - * Gets an unsafe reference to the data of the [Bitmap] instance. + * Creates a [BitmapCommand] and immediately turns that into a [Packet]. * - * The returned memory is valid for the lifetime of the bitmap. - */ -ByteSlice sp_bitmap_unsafe_data_ref(Bitmap */*notnull*/ bitmap); - -/** - * Gets the width 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 [Bitmap] - */ -size_t sp_bitmap_width(Bitmap */*notnull*/ bitmap); - -/** - * Clones a [SPBitVec]. - */ -SPBitVec */*notnull*/ sp_bitvec_clone(SPBitVec */*notnull*/ bit_vec); - -/** - * Sets the value of all bits in the [SPBitVec]. - * - * # Arguments - * - * - `bit_vec`: instance to write to - * - `value`: the value to set all bits to - */ -void sp_bitvec_fill(SPBitVec */*notnull*/ bit_vec, bool value); - -/** - * Deallocates a [SPBitVec]. - */ -void sp_bitvec_free(SPBitVec */*notnull*/ bit_vec); - -/** - * Gets the value of a bit from the [SPBitVec]. - * - * # Arguments - * - * - `bit_vec`: instance to read from - * - `index`: the bit index to read - * - * returns: value of the bit - * - * # Panics - * - * - when accessing `index` out of bounds - */ -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. + * The provided [Bitmap] gets consumed. * * Returns NULL in case of an error. + * + * This function is part of the `bitmap` module. */ -Packet *sp_bitvec_into_packet(SPBitVec */*notnull*/ bitvec, - size_t offset, - BinaryOperation operation, - CompressionCode compression); +struct Packet *sp_bitmap_try_into_packet(struct Bitmap */*notnull*/ bitmap, + size_t x, + size_t y, + CompressionCode compression); /** - * Returns true if length is 0. + * Calls method [`Bitmap::width`]. + * + * Gets the width. + * + * This function is part of the `bitmap` module. + */ +size_t sp_bitmap_width(struct Bitmap */*notnull*/ instance); + +/** + * Calls method [`BrightnessGrid::clone`]. + * + *Clones a [`BrightnessGrid`] instance. + * + * This function is part of the `brightness_grid` module. + */ +BrightnessGrid */*notnull*/ sp_brightness_grid_clone(BrightnessGrid */*notnull*/ instance); + +/** + * Calls method [`BrightnessGridCommand::clone`]. + * + *Clones a [`BrightnessGridCommand`] instance. + * + * This function is part of the `brightness_grid_command` module. + */ +struct BrightnessGridCommand */*notnull*/ sp_brightness_grid_command_clone(struct BrightnessGridCommand */*notnull*/ instance); + +/** + *Deallocates a [`BrightnessGridCommand`] instance. + * + * This function is part of the `brightness_grid_command` module. + */ +void sp_brightness_grid_command_free(struct BrightnessGridCommand */*notnull*/ instance); + +/** + * Moves the provided [BrightnessGrid] into a new [BrightnessGridCommand], + * leaving other fields as their default values. + * + * This function is part of the `brightness_grid_command` module. + */ +struct BrightnessGridCommand */*notnull*/ sp_brightness_grid_command_from_grid(BrightnessGrid */*notnull*/ grid); + +/** + * Gets a reference to the field `grid` of the [`servicepoint::BrightnessGridCommand`]. + * + * - The returned reference inherits the lifetime of object in which it is contained. + * - The returned pointer may not be used in a function that consumes the instance, e.g. to create a command. + * + * This function is part of the `brightness_grid_command` module. + */ +BrightnessGrid */*notnull*/ sp_brightness_grid_command_get_grid_mut(struct BrightnessGridCommand */*notnull*/ instance); + +/** + * Reads the origin field of the [`BrightnessGridCommand`]. + * + * This function is part of the `brightness_grid_command` module. + */ +void sp_brightness_grid_command_get_origin(struct BrightnessGridCommand */*notnull*/ command, + size_t */*notnull*/ origin_x, + size_t */*notnull*/ origin_y); + +/** + * Set the brightness of individual tiles in a rectangular area of the display. + * + * The passed [BrightnessGrid] gets consumed. + * + * Returns: a new [BrightnessGridCommand] instance. + * + * This function is part of the `brightness_grid_command` module. + */ +struct BrightnessGridCommand */*notnull*/ sp_brightness_grid_command_new(BrightnessGrid */*notnull*/ grid, + size_t origin_x, + size_t origin_y); + +/** + * Sets the value of field `grid` of the [`servicepoint::BrightnessGridCommand`]. + * The provided value is moved into the instance, potentially invalidating previously taken references. + * + * This function is part of the `brightness_grid_command` module. + */ +void sp_brightness_grid_command_set_grid(struct BrightnessGridCommand */*notnull*/ instance, + BrightnessGrid */*notnull*/ value); + +/** + * Overwrites the origin field of the [`BrightnessGridCommand`]. + * + * This function is part of the `brightness_grid_command` module. + */ +void sp_brightness_grid_command_set_origin(struct BrightnessGridCommand */*notnull*/ command, + size_t origin_x, + size_t origin_y); + +/** + *Tries to turn a [`BrightnessGridCommand`] into a [Packet]. + * + * Returns: NULL or a [Packet] containing the command. + * + * [Packet]: [`servicepoint::Packet`] + * + * This function is part of the `brightness_grid_command` module. + */ +struct Packet *sp_brightness_grid_command_try_into_packet(struct BrightnessGridCommand */*notnull*/ instance); + +/** + * Gets an unsafe reference to the data of the instance. + * + * The returned memory is valid for the lifetime of the grid. + * + * This function is part of the `brightness_grid` module. + */ +struct ByteSlice sp_brightness_grid_data_ref_mut(BrightnessGrid */*notnull*/ instance); + +/** + * Calls method [`BrightnessGrid::fill`]. + * + * Sets the state of all cells in the grid. * * # Arguments * - * - `bit_vec`: instance to write to - */ -bool sp_bitvec_is_empty(SPBitVec */*notnull*/ bit_vec); - -/** - * Gets the length of the [SPBitVec] in bits. - * - * # Arguments - * - * - `bit_vec`: instance to write to - */ -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. - */ -SPBitVec */*notnull*/ sp_bitvec_load(ByteSlice data); - -/** - * Creates a new [SPBitVec] instance. - * - * # Arguments - * - * - `size`: size in bits. - * - * returns: [SPBitVec] with all bits set to false. - * - * # Panics - * - * - when `size` is not divisible by 8. - */ -SPBitVec */*notnull*/ sp_bitvec_new(size_t size); - -/** - * Sets the value of a bit in the [SPBitVec]. - * - * # Arguments - * - * - `bit_vec`: instance to write to - * - `index`: the bit index to edit - * - `value`: the value to set the bit to - * - * # Panics - * - * - when accessing `index` out of bounds - */ -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 - */ -ByteSlice sp_bitvec_unsafe_data_ref(SPBitVec */*notnull*/ bit_vec); - -/** - * Clones a [BrightnessGrid]. - */ -BrightnessGrid */*notnull*/ sp_brightness_grid_clone(BrightnessGrid */*notnull*/ brightness_grid); - -/** - * Sets the value of all cells in the [BrightnessGrid]. - * - * # Arguments - * - * - `brightness_grid`: instance to write to * - `value`: the value to set all cells to + * + * This function is part of the `brightness_grid` module. */ -void sp_brightness_grid_fill(BrightnessGrid */*notnull*/ brightness_grid, +void sp_brightness_grid_fill(BrightnessGrid */*notnull*/ instance, Brightness value); /** - * Deallocates a [BrightnessGrid]. + *Deallocates a [`BrightnessGrid`] instance. + * + * This function is part of the `brightness_grid` module. */ -void sp_brightness_grid_free(BrightnessGrid */*notnull*/ brightness_grid); +void sp_brightness_grid_free(BrightnessGrid */*notnull*/ instance); /** + * Calls method [`BrightnessGrid::get`]. + * * Gets the current value at the specified position. * * # Arguments * - * - `brightness_grid`: instance to read from * - `x` and `y`: position of the cell to read * - * returns: value at position - * * # Panics - * - When accessing `x` or `y` out of bounds. + * + * - when accessing `x` or `y` out of bounds + * + * This function is part of the `brightness_grid` module. */ -Brightness sp_brightness_grid_get(BrightnessGrid */*notnull*/ brightness_grid, +Brightness sp_brightness_grid_get(BrightnessGrid */*notnull*/ instance, size_t x, size_t y); /** - * Gets the height of the [BrightnessGrid] instance. + * Calls method [`BrightnessGrid::height`]. * - * # Arguments + * Gets the height. * - * - `brightness_grid`: instance to read from - * - * returns: height + * This function is part of the `brightness_grid` module. */ -size_t sp_brightness_grid_height(BrightnessGrid */*notnull*/ brightness_grid); - -/** - * Creates a [BrightnessGridCommand] and immediately turns that into a [Packet]. - * - * The provided [BrightnessGrid] gets consumed. - * - * Returns NULL in case of an error. - */ -Packet *sp_brightness_grid_into_packet(BrightnessGrid */*notnull*/ grid, - size_t x, - size_t y); +size_t sp_brightness_grid_height(BrightnessGrid */*notnull*/ instance); /** * Loads a [BrightnessGrid] with the specified dimensions from the provided data. @@ -732,10 +1191,12 @@ Packet *sp_brightness_grid_into_packet(BrightnessGrid */*notnull*/ grid, * Any out of range values will be set to [Brightness::MAX] or [Brightness::MIN]. * * returns: new [BrightnessGrid] instance, or NULL in case of an error. + * + * This function is part of the `brightness_grid` module. */ BrightnessGrid *sp_brightness_grid_load(size_t width, size_t height, - ByteSlice data); + struct ByteSlice data); /** * Creates a new [BrightnessGrid] with the specified dimensions. @@ -744,124 +1205,210 @@ BrightnessGrid *sp_brightness_grid_load(size_t width, * * # Examples * ```C - * UdpConnection connection = sp_udp_open("127.0.0.1:2342"); + * UdpSocket *connection = sp_udp_open("127.0.0.1:2342"); * if (connection == NULL) * return 1; * - * BrightnessGrid 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); * - * TypedCommand command = sp_command_char_brightness(grid); + * TypedCommand *command = sp_command_char_brightness(grid); * sp_udp_free(connection); * ``` + * + * This function is part of the `brightness_grid` module. */ BrightnessGrid */*notnull*/ sp_brightness_grid_new(size_t width, size_t height); /** - * Sets the value of the specified position in the [BrightnessGrid]. + * Calls method [`BrightnessGrid::set`]. + * + * Sets the value of the specified position. * * # Arguments * - * - `brightness_grid`: instance to write to * - `x` and `y`: position of the cell * - `value`: the value to write to the cell * - * returns: old value of the cell - * * # Panics * - * - When accessing `x` or `y` out of bounds. + * - when accessing `x` or `y` out of bounds + * + * This function is part of the `brightness_grid` module. */ -void sp_brightness_grid_set(BrightnessGrid */*notnull*/ brightness_grid, +void sp_brightness_grid_set(BrightnessGrid */*notnull*/ instance, size_t x, size_t y, Brightness value); /** - * Gets an unsafe reference to the data of the [BrightnessGrid] instance. + * Creates a [BrightnessGridCommand] and immediately turns that into a [Packet]. * - * The returned memory is valid for the lifetime of the brightness grid. + * The provided [BrightnessGrid] gets consumed. + * + * Returns NULL in case of an error. + * + * This function is part of the `brightness_grid` module. + */ +struct Packet *sp_brightness_grid_try_into_packet(BrightnessGrid */*notnull*/ grid, + size_t x, + size_t y); + +/** + * Calls method [`BrightnessGrid::width`]. + * + * Gets the width. + * + * This function is part of the `brightness_grid` module. + */ +size_t sp_brightness_grid_width(BrightnessGrid */*notnull*/ instance); + +/** + * Calls method [`CharGrid::clone`]. + * + *Clones a [`CharGrid`] instance. + * + * This function is part of the `char_grid` module. + */ +CharGrid */*notnull*/ sp_char_grid_clone(CharGrid */*notnull*/ instance); + +/** + * Calls method [`CharGridCommand::clone`]. + * + *Clones a [`CharGridCommand`] instance. + * + * This function is part of the `char_grid_command` module. + */ +struct CharGridCommand */*notnull*/ sp_char_grid_command_clone(struct CharGridCommand */*notnull*/ instance); + +/** + *Deallocates a [`CharGridCommand`] instance. + * + * This function is part of the `char_grid_command` module. + */ +void sp_char_grid_command_free(struct CharGridCommand */*notnull*/ instance); + +/** + * Moves the provided [CharGrid] into a new [CharGridCommand], + * leaving other fields as their default values. + * + * This function is part of the `char_grid_command` module. + */ +struct CharGridCommand */*notnull*/ sp_char_grid_command_from_grid(CharGrid */*notnull*/ grid); + +/** + * Gets a reference to the field `grid` of the [`servicepoint::CharGridCommand`]. + * + * - The returned reference inherits the lifetime of object in which it is contained. + * - The returned pointer may not be used in a function that consumes the instance, e.g. to create a command. + * + * This function is part of the `char_grid_command` module. + */ +CharGrid */*notnull*/ sp_char_grid_command_get_grid_mut(struct CharGridCommand */*notnull*/ instance); + +/** + * Reads the origin field of the [`CharGridCommand`]. + * + * This function is part of the `char_grid_command` module. + */ +void sp_char_grid_command_get_origin(struct CharGridCommand */*notnull*/ command, + size_t */*notnull*/ origin_x, + size_t */*notnull*/ origin_y); + +/** + * Show UTF-8 encoded text on the screen. + * + * The passed [CharGrid] gets consumed. + * + * Returns: a new [CharGridCommand] instance. + * + * This function is part of the `char_grid_command` module. + */ +struct CharGridCommand */*notnull*/ sp_char_grid_command_new(CharGrid */*notnull*/ grid, + size_t origin_x, + size_t origin_y); + +/** + * Sets the value of field `grid` of the [`servicepoint::CharGridCommand`]. + * The provided value is moved into the instance, potentially invalidating previously taken references. + * + * This function is part of the `char_grid_command` module. + */ +void sp_char_grid_command_set_grid(struct CharGridCommand */*notnull*/ instance, + CharGrid */*notnull*/ value); + +/** + * Overwrites the origin field of the [`CharGridCommand`]. + * + * This function is part of the `char_grid_command` module. + */ +void sp_char_grid_command_set_origin(struct CharGridCommand */*notnull*/ command, + size_t origin_x, + size_t origin_y); + +/** + *Tries to turn a [`CharGridCommand`] into a [Packet]. + * + * Returns: NULL or a [Packet] containing the command. + * + * [Packet]: [`servicepoint::Packet`] + * + * This function is part of the `char_grid_command` module. + */ +struct Packet *sp_char_grid_command_try_into_packet(struct CharGridCommand */*notnull*/ instance); + +/** + * Sets the value of all cells in the grid. * * # Arguments * - * - `brightness_grid`: instance to read from - * - * returns: slice of bytes underlying the `brightness_grid`. - */ -ByteSlice sp_brightness_grid_unsafe_data_ref(BrightnessGrid */*notnull*/ brightness_grid); - -/** - * Gets the width of the [BrightnessGrid] instance. - * - * # Arguments - * - * - `brightness_grid`: instance to read from - * - * returns: width - */ -size_t sp_brightness_grid_width(BrightnessGrid */*notnull*/ brightness_grid); - -/** - * Clones a [CharGrid]. - */ -CharGrid */*notnull*/ sp_char_grid_clone(CharGrid */*notnull*/ char_grid); - -/** - * Sets the value of all cells in the [CharGrid]. - * - * # Arguments - * - * - `char_grid`: instance to write to * - `value`: the value to set all cells to + * - when providing values that cannot be converted to Rust's `char`. + * + * This function is part of the `char_grid` module. */ -void sp_char_grid_fill(CharGrid */*notnull*/ char_grid, uint32_t value); +void sp_char_grid_fill(CharGrid */*notnull*/ instance, uint32_t value); /** - * Deallocates a [CharGrid]. + *Deallocates a [`CharGrid`] instance. + * + * This function is part of the `char_grid` module. */ -void sp_char_grid_free(CharGrid */*notnull*/ char_grid); +void sp_char_grid_free(CharGrid */*notnull*/ instance); /** * Returns the current value at the specified position. * * # Arguments * - * - `char_grid`: instance to read from * - `x` and `y`: position of the cell to read * * # Panics * * - when accessing `x` or `y` out of bounds + * + * This function is part of the `char_grid` module. */ -uint32_t sp_char_grid_get(CharGrid */*notnull*/ char_grid, size_t x, size_t y); +uint32_t sp_char_grid_get(CharGrid */*notnull*/ instance, size_t x, size_t y); /** - * Gets the height of the [CharGrid] instance. + * Calls method [`CharGrid::height`]. * - * # Arguments + * Gets the height. * - * - `char_grid`: instance to read from + * This function is part of the `char_grid` module. */ -size_t sp_char_grid_height(CharGrid */*notnull*/ char_grid); - -/** - * Creates a [CharGridCommand] and immediately turns that into a [Packet]. - * - * The provided [CharGrid] gets consumed. - * - * Returns NULL in case of an error. - */ -Packet *sp_char_grid_into_packet(CharGrid */*notnull*/ grid, - size_t x, - size_t y); +size_t sp_char_grid_height(CharGrid */*notnull*/ instance); /** * Loads a [CharGrid] with the specified dimensions from the provided data. * * returns: new CharGrid or NULL in case of an error + * + * This function is part of the `char_grid` module. */ -CharGrid *sp_char_grid_load(size_t width, size_t height, ByteSlice data); +CharGrid *sp_char_grid_load(size_t width, size_t height, struct ByteSlice data); /** * Creates a new [CharGrid] with the specified dimensions. @@ -876,15 +1423,16 @@ CharGrid *sp_char_grid_load(size_t width, size_t height, ByteSlice data); * sp_char_grid_set(grid, 0, 0, '!'); * sp_char_grid_free(grid); * ``` + * + * This function is part of the `char_grid` module. */ CharGrid */*notnull*/ sp_char_grid_new(size_t width, size_t height); /** - * Sets the value of the specified position in the [CharGrid]. + * Sets the value of the specified position in the grid. * * # Arguments * - * - `char_grid`: instance to write to * - `x` and `y`: position of the cell * - `value`: the value to write to the cell * @@ -893,195 +1441,264 @@ CharGrid */*notnull*/ sp_char_grid_new(size_t width, size_t height); * # Panics * * - when accessing `x` or `y` out of bounds + * - when providing values that cannot be converted to Rust's `char`. + * + * This function is part of the `char_grid` module. */ -void sp_char_grid_set(CharGrid */*notnull*/ char_grid, +void sp_char_grid_set(CharGrid */*notnull*/ instance, size_t x, size_t y, uint32_t value); /** - * Gets the width of the [CharGrid] instance. + * Creates a [CharGridCommand] and immediately turns that into a [Packet]. * - * # Arguments + * The provided [CharGrid] gets consumed. * - * - `char_grid`: instance to read from + * Returns NULL in case of an error. + * + * This function is part of the `char_grid` module. */ -size_t sp_char_grid_width(CharGrid */*notnull*/ char_grid); +struct Packet *sp_char_grid_try_into_packet(CharGrid */*notnull*/ grid, + size_t x, + size_t y); /** - * Sets a window of pixels to the specified values. + * Calls method [`CharGrid::width`]. * - * The passed [Bitmap] gets consumed. + * Gets the width. * - * Returns: a new [servicepoint::BitmapCommand] instance. + * This function is part of the `char_grid` module. */ -TypedCommand *sp_command_bitmap(size_t x, - size_t y, - Bitmap */*notnull*/ bitmap, - CompressionCode compression); +size_t sp_char_grid_width(CharGrid */*notnull*/ instance); /** - * Set pixel data starting at the pixel offset on screen. + * Calls method [`ClearCommand::clone`]. * - * 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. + *Clones a [`ClearCommand`] instance. * - * 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. + * This function is part of the `clear_command` module. */ -TypedCommand *sp_command_bitvec(size_t offset, - SPBitVec */*notnull*/ bit_vec, - CompressionCode compression, - BinaryOperation operation); +struct ClearCommand */*notnull*/ sp_clear_command_clone(struct ClearCommand */*notnull*/ instance); /** - * Set the brightness of individual tiles in a rectangular area of the display. + *Deallocates a [`ClearCommand`] instance. * - * The passed [BrightnessGrid] gets consumed. - * - * Returns: a new [servicepoint::Command::CharBrightness] instance. + * This function is part of the `clear_command` module. */ -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); +void sp_clear_command_free(struct ClearCommand */*notnull*/ instance); /** * Set all pixels to the off state. * * Does not affect brightness. * - * Returns: a new [servicepoint::Command::Clear] instance. + * Returns: a new [`ClearCommand`] instance. * - * # Examples - * - * ```C - * sp_udp_send_command(connection, sp_command_clear()); - * ``` + * This function is part of the `clear_command` module. */ -TypedCommand */*notnull*/ sp_command_clear(void); +struct ClearCommand */*notnull*/ sp_clear_command_new(void); /** - * Clones a [TypedCommand] instance. + *Tries to turn a [`ClearCommand`] into a [Packet]. * - * returns: new [TypedCommand] instance. + * Returns: NULL or a [Packet] containing the command. + * + * [Packet]: [`servicepoint::Packet`] + * + * This function is part of the `clear_command` module. */ -TypedCommand */*notnull*/ sp_command_clone(TypedCommand */*notnull*/ command); +struct Packet *sp_clear_command_try_into_packet(struct ClearCommand */*notnull*/ instance); /** - * Show codepage 437 encoded text on the screen. + * Calls method [`Cp437Grid::clone`]. * - * The passed [Cp437Grid] gets consumed. + *Clones a [`Cp437Grid`] instance. * - * Returns: a new [servicepoint::Cp437GridCommand] instance. + * This function is part of the `cp437_grid` module. */ -TypedCommand */*notnull*/ sp_command_cp437_grid(size_t x, - size_t y, - Cp437Grid */*notnull*/ grid); +Cp437Grid */*notnull*/ sp_cp437_grid_clone(Cp437Grid */*notnull*/ instance); /** - * A yet-to-be-tested command. + * Calls method [`Cp437GridCommand::clone`]. * - * Returns: a new [servicepoint::Command::FadeOut] instance. + *Clones a [`Cp437GridCommand`] instance. + * + * This function is part of the `cp437_grid_command` module. */ -TypedCommand */*notnull*/ sp_command_fade_out(void); +struct Cp437GridCommand */*notnull*/ sp_cp437_grid_command_clone(struct Cp437GridCommand */*notnull*/ instance); /** - * Deallocates a [TypedCommand]. + *Deallocates a [`Cp437GridCommand`] instance. * - * # Examples - * - * ```C - * TypedCommand c = sp_command_clear(); - * sp_command_free(c); - * ``` + * This function is part of the `cp437_grid_command` module. */ -void sp_command_free(TypedCommand */*notnull*/ command); +void sp_cp437_grid_command_free(struct Cp437GridCommand */*notnull*/ instance); /** - * Set the brightness of all tiles to the same value. + * Moves the provided [Cp437Grid] into a new [Cp437GridCommand], + * leaving other fields as their default values. * - * Returns: a new [servicepoint::Command::Brightness] instance. + * This function is part of the `cp437_grid_command` module. */ -TypedCommand */*notnull*/ sp_command_global_brightness(Brightness brightness); +struct Cp437GridCommand */*notnull*/ sp_cp437_grid_command_from_grid(Cp437Grid */*notnull*/ grid); /** - * Kills the udp daemon on the display, which usually results in a restart. + * Gets a reference to the field `grid` of the [`servicepoint::Cp437GridCommand`]. * - * Please do not send this in your normal program flow. + * - The returned reference inherits the lifetime of object in which it is contained. + * - The returned pointer may not be used in a function that consumes the instance, e.g. to create a command. * - * Returns: a new [servicepoint::Command::HardReset] instance. + * This function is part of the `cp437_grid_command` module. */ -TypedCommand */*notnull*/ sp_command_hard_reset(void); +Cp437Grid */*notnull*/ sp_cp437_grid_command_get_grid_mut(struct Cp437GridCommand */*notnull*/ instance); /** - * Tries to turn a [Packet] into a [TypedCommand]. + * Reads the origin field of the [`Cp437GridCommand`]. * - * The packet is deallocated in the process. - * - * Returns: pointer to new [TypedCommand] instance or NULL if parsing failed. + * This function is part of the `cp437_grid_command` module. */ -TypedCommand *sp_command_try_from_packet(Packet */*notnull*/ packet); +void sp_cp437_grid_command_get_origin(struct Cp437GridCommand */*notnull*/ command, + size_t */*notnull*/ origin_x, + size_t */*notnull*/ origin_y); /** - * Clones a [Cp437Grid]. + * Show text on the screen. + * + * The text is sent in the form of a 2D grid of [CP-437] encoded characters. + * + * The origin is relative to the top-left of the display. + * + * This function is part of the `cp437_grid_command` module. */ -Cp437Grid */*notnull*/ sp_cp437_grid_clone(Cp437Grid */*notnull*/ cp437_grid); +struct Cp437GridCommand */*notnull*/ sp_cp437_grid_command_new(Cp437Grid */*notnull*/ grid, + size_t origin_x, + size_t origin_y); /** - * Sets the value of all cells in the [Cp437Grid]. + * Sets the value of field `grid` of the [`servicepoint::Cp437GridCommand`]. + * The provided value is moved into the instance, potentially invalidating previously taken references. + * + * This function is part of the `cp437_grid_command` module. + */ +void sp_cp437_grid_command_set_grid(struct Cp437GridCommand */*notnull*/ instance, + Cp437Grid */*notnull*/ value); + +/** + * Overwrites the origin field of the [`Cp437GridCommand`]. + * + * This function is part of the `cp437_grid_command` module. + */ +void sp_cp437_grid_command_set_origin(struct Cp437GridCommand */*notnull*/ command, + size_t origin_x, + size_t origin_y); + +/** + *Tries to turn a [`Cp437GridCommand`] into a [Packet]. + * + * Returns: NULL or a [Packet] containing the command. + * + * [Packet]: [`servicepoint::Packet`] + * + * This function is part of the `cp437_grid_command` module. + */ +struct Packet *sp_cp437_grid_command_try_into_packet(struct Cp437GridCommand */*notnull*/ instance); + +/** + * Calls method [`Cp437Grid::data_ref_mut`]. + * + * Gets an unsafe reference to the data of the grid. + * + * The returned memory is valid for the lifetime of the instance. + * + * This function is part of the `cp437_grid` module. + */ +struct ByteSlice sp_cp437_grid_data_ref_mut(Cp437Grid */*notnull*/ instance); + +/** + * Calls method [`Cp437Grid::fill`]. + * + * Sets the state of all cells in the grid. * * # Arguments * - * - `cp437_grid`: instance to write to * - `value`: the value to set all cells to + * + * This function is part of the `cp437_grid` module. */ -void sp_cp437_grid_fill(Cp437Grid */*notnull*/ cp437_grid, uint8_t value); +void sp_cp437_grid_fill(Cp437Grid */*notnull*/ instance, uint8_t value); /** - * Deallocates a [Cp437Grid]. + *Deallocates a [`Cp437Grid`] instance. + * + * This function is part of the `cp437_grid` module. */ -void sp_cp437_grid_free(Cp437Grid */*notnull*/ cp437_grid); +void sp_cp437_grid_free(Cp437Grid */*notnull*/ instance); /** + * Calls method [`Cp437Grid::get`]. + * * Gets the current value at the specified position. * * # Arguments * - * - `cp437_grid`: instance to read from * - `x` and `y`: position of the cell to read * * # Panics * * - when accessing `x` or `y` out of bounds + * + * This function is part of the `cp437_grid` module. */ -uint8_t sp_cp437_grid_get(Cp437Grid */*notnull*/ cp437_grid, - size_t x, - size_t y); +uint8_t sp_cp437_grid_get(Cp437Grid */*notnull*/ instance, size_t x, size_t y); /** - * Gets the height of the [Cp437Grid] instance. + * Calls method [`Cp437Grid::height`]. + * + * Gets the height. + * + * This function is part of the `cp437_grid` module. + */ +size_t sp_cp437_grid_height(Cp437Grid */*notnull*/ instance); + +/** + * Loads a [Cp437Grid] with the specified dimensions from the provided data. + * + * This function is part of the `cp437_grid` module. + */ +Cp437Grid *sp_cp437_grid_load(size_t width, + size_t height, + struct ByteSlice data); + +/** + * Creates a new [Cp437Grid] with the specified dimensions. + * + * returns: [Cp437Grid] initialized to 0. + * + * This function is part of the `cp437_grid` module. + */ +Cp437Grid */*notnull*/ sp_cp437_grid_new(size_t width, size_t height); + +/** + * Calls method [`Cp437Grid::set`]. + * + * Sets the value of the specified position. * * # Arguments * - * - `cp437_grid`: instance to read from + * - `x` and `y`: position of the cell + * - `value`: the value to write to the cell + * + * # Panics + * + * - when accessing `x` or `y` out of bounds + * + * This function is part of the `cp437_grid` module. */ -size_t sp_cp437_grid_height(Cp437Grid */*notnull*/ cp437_grid); +void sp_cp437_grid_set(Cp437Grid */*notnull*/ instance, + size_t x, + size_t y, + uint8_t value); /** * Creates a [Cp437GridCommand] and immediately turns that into a [Packet]. @@ -1089,98 +1706,381 @@ size_t sp_cp437_grid_height(Cp437Grid */*notnull*/ cp437_grid); * The provided [Cp437Grid] gets consumed. * * Returns NULL in case of an error. - */ -Packet *sp_cp437_grid_into_packet(Cp437Grid */*notnull*/ grid, - size_t x, - size_t y); - -/** - * Loads a [Cp437Grid] with the specified dimensions from the provided data. - */ -Cp437Grid *sp_cp437_grid_load(size_t width, size_t height, ByteSlice data); - -/** - * Creates a new [Cp437Grid] with the specified dimensions. * - * returns: [Cp437Grid] initialized to 0. + * This function is part of the `cp437_grid` module. */ -Cp437Grid */*notnull*/ sp_cp437_grid_new(size_t width, size_t height); +struct Packet *sp_cp437_grid_try_into_packet(Cp437Grid */*notnull*/ grid, + size_t x, + size_t y); /** - * Sets the value of the specified position in the [Cp437Grid]. + * Calls method [`Cp437Grid::width`]. + * + * Gets the width. + * + * This function is part of the `cp437_grid` module. + */ +size_t sp_cp437_grid_width(Cp437Grid */*notnull*/ instance); + +/** + * Calls method [`DisplayBitVec::as_raw_mut_slice`]. + * + * Gets an unsafe reference to the data of the [DisplayBitVec] instance. + * + * The returned memory is valid for the lifetime of the bitvec. + * + * This function is part of the `display_bit_vec` module. + */ +struct ByteSlice sp_display_bit_vec_as_raw_mut_slice(DisplayBitVec */*notnull*/ instance); + +/** + * Calls method [`DisplayBitVec::clone`]. + * + *Clones a [`DisplayBitVec`] instance. + * + * This function is part of the `display_bit_vec` module. + */ +DisplayBitVec */*notnull*/ sp_display_bit_vec_clone(DisplayBitVec */*notnull*/ instance); + +/** + * Calls method [`DisplayBitVec::fill`]. + * + * Sets the value of all bits. * * # Arguments * - * - `cp437_grid`: instance to write to - * - `x` and `y`: position of the cell - * - `value`: the value to write to the cell + * - `value`: the value to set all bits to * - * returns: old value of the cell + * This function is part of the `display_bit_vec` module. + */ +void sp_display_bit_vec_fill(DisplayBitVec */*notnull*/ instance, bool value); + +/** + *Deallocates a [`DisplayBitVec`] instance. + * + * This function is part of the `display_bit_vec` module. + */ +void sp_display_bit_vec_free(DisplayBitVec */*notnull*/ instance); + +/** + * Gets the value of a bit. + * + * # Arguments + * + * - `bit_vec`: instance to read from + * - `index`: the bit index to read + * + * returns: value of the bit * * # Panics * - * - when accessing `x` or `y` out of bounds - */ -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 [Cp437Grid] instance. + * - when accessing `index` out of bounds * - * The returned memory is valid for the lifetime of the grid. + * This function is part of the `display_bit_vec` module. */ -ByteSlice sp_cp437_grid_unsafe_data_ref(Cp437Grid */*notnull*/ cp437_grid); +bool sp_display_bit_vec_get(DisplayBitVec */*notnull*/ instance, size_t index); /** - * Gets the width of the [Cp437Grid] instance. + * Calls method [`DisplayBitVec::is_empty`]. + * + * Returns true if length is 0. + * + * This function is part of the `display_bit_vec` module. + */ +bool sp_display_bit_vec_is_empty(DisplayBitVec */*notnull*/ instance); + +/** + * Calls method [`DisplayBitVec::len`]. + * + * Gets the length in bits. + * + * This function is part of the `display_bit_vec` module. + */ +size_t sp_display_bit_vec_len(DisplayBitVec */*notnull*/ instance); + +/** + * Interpret the data as a series of bits and load then into a new [DisplayBitVec] instance. + * + * returns: [DisplayBitVec] instance containing data. + * + * This function is part of the `display_bit_vec` module. + */ +DisplayBitVec */*notnull*/ sp_display_bit_vec_load(struct ByteSlice data); + +/** + * Creates a new [DisplayBitVec] instance. * * # Arguments * - * - `cp437_grid`: instance to read from + * - `size`: size in bits. + * + * returns: [DisplayBitVec] with all bits set to false. + * + * # Panics + * + * - when `size` is not divisible by 8. + * + * This function is part of the `display_bit_vec` module. */ -size_t sp_cp437_grid_width(Cp437Grid */*notnull*/ cp437_grid); +DisplayBitVec */*notnull*/ sp_display_bit_vec_new(size_t size); /** - * Clones a [Packet]. + * Calls method [`DisplayBitVec::set`]. + * + * Sets the value of a bit. + * + * # Arguments + * + * - `index`: the bit index to edit + * - `value`: the value to set the bit to + * + * # Panics + * + * - when accessing `index` out of bounds + * + * This function is part of the `display_bit_vec` module. */ -Packet */*notnull*/ sp_packet_clone(Packet */*notnull*/ packet); +void sp_display_bit_vec_set(DisplayBitVec */*notnull*/ instance, + size_t index, + bool value); /** - * Deallocates a [Packet]. - */ -void sp_packet_free(Packet */*notnull*/ packet); - -/** - * Turns a [TypedCommand] into a [Packet]. - * The [TypedCommand] gets consumed. + * Creates a [BitVecCommand] and immediately turns that into a [Packet]. + * + * The provided [DisplayBitVec] gets consumed. * * Returns NULL in case of an error. + * + * This function is part of the `display_bit_vec` module. */ -Packet *sp_packet_from_command(TypedCommand */*notnull*/ command); +struct Packet *sp_display_bit_vec_try_into_packet(DisplayBitVec */*notnull*/ bitvec, + size_t offset, + BinaryOperation operation, + CompressionCode compression); + +/** + * Call this function at the beginning of main to enable rust logging controlled by the + * `RUST_LOG` environment variable. See [env_logger](https://docs.rs/env_logger/latest/env_logger/). + * + * This function is part of the `envlogger` module. + */ +void sp_envlogger_init(void); + +/** + * Calls method [`FadeOutCommand::clone`]. + * + *Clones a [`FadeOutCommand`] instance. + * + * This function is part of the `fade_out_command` module. + */ +struct FadeOutCommand */*notnull*/ sp_fade_out_command_clone(struct FadeOutCommand */*notnull*/ instance); + +/** + *Deallocates a [`FadeOutCommand`] instance. + * + * This function is part of the `fade_out_command` module. + */ +void sp_fade_out_command_free(struct FadeOutCommand */*notnull*/ instance); + +/** + * A yet-to-be-tested command. + * + * Returns: a new [`FadeOutCommand`] instance. + * + * This function is part of the `fade_out_command` module. + */ +struct FadeOutCommand */*notnull*/ sp_fade_out_command_new(void); + +/** + *Tries to turn a [`FadeOutCommand`] into a [Packet]. + * + * Returns: NULL or a [Packet] containing the command. + * + * [Packet]: [`servicepoint::Packet`] + * + * This function is part of the `fade_out_command` module. + */ +struct Packet *sp_fade_out_command_try_into_packet(struct FadeOutCommand */*notnull*/ instance); + +/** + * Calls method [`GenericCommand::clone`]. + * + *Clones a [`GenericCommand`] instance. + * + * This function is part of the `generic_command` module. + */ +struct GenericCommand */*notnull*/ sp_generic_command_clone(struct GenericCommand */*notnull*/ instance); + +/** + *Deallocates a [`GenericCommand`] instance. + * + * This function is part of the `generic_command` module. + */ +void sp_generic_command_free(struct GenericCommand */*notnull*/ instance); + +/** + * Tries to turn a [Packet] into a [GenericCommand]. + * + * The packet is dropped in the process. + * + * Returns: pointer to new [GenericCommand] instance or NULL if parsing failed. + * + * This function is part of the `generic_command` module. + */ +struct GenericCommand */*notnull*/ sp_generic_command_try_from_packet(struct Packet */*notnull*/ packet); + +/** + * Tries to turn a [GenericCommand] into a [Packet]. + * The [GenericCommand] gets consumed. + * + * Returns tag [CommandTag::Invalid] in case of an error. + * + * This function is part of the `generic_command` module. + */ +struct Packet *sp_generic_command_try_into_packet(struct GenericCommand */*notnull*/ command); + +/** + * Calls method [`GlobalBrightnessCommand::clone`]. + * + *Clones a [`GlobalBrightnessCommand`] instance. + * + * This function is part of the `global_brightness_command` module. + */ +struct GlobalBrightnessCommand */*notnull*/ sp_global_brightness_command_clone(struct GlobalBrightnessCommand */*notnull*/ instance); + +/** + *Deallocates a [`GlobalBrightnessCommand`] instance. + * + * This function is part of the `global_brightness_command` module. + */ +void sp_global_brightness_command_free(struct GlobalBrightnessCommand */*notnull*/ instance); + +/** + * Gets the value of field `brightness` of the [`servicepoint::GlobalBrightnessCommand`]. + * + * This function is part of the `global_brightness_command` module. + */ +Brightness sp_global_brightness_command_get_brightness(struct GlobalBrightnessCommand */*notnull*/ instance); + +/** + * Set the brightness of all tiles to the same value. + * + * Returns: a new [GlobalBrightnessCommand] instance. + * + * This function is part of the `global_brightness_command` module. + */ +struct GlobalBrightnessCommand */*notnull*/ sp_global_brightness_command_new(Brightness brightness); + +/** + * Sets the value of field `brightness` of the [`servicepoint::GlobalBrightnessCommand`]. + * + * This function is part of the `global_brightness_command` module. + */ +void sp_global_brightness_command_set_brightness(struct GlobalBrightnessCommand */*notnull*/ instance, + Brightness value); + +/** + *Tries to turn a [`GlobalBrightnessCommand`] into a [Packet]. + * + * Returns: NULL or a [Packet] containing the command. + * + * [Packet]: [`servicepoint::Packet`] + * + * This function is part of the `global_brightness_command` module. + */ +struct Packet *sp_global_brightness_command_try_into_packet(struct GlobalBrightnessCommand */*notnull*/ instance); + +/** + * Calls method [`HardResetCommand::clone`]. + * + *Clones a [`HardResetCommand`] instance. + * + * This function is part of the `hard_reset_command` module. + */ +struct HardResetCommand */*notnull*/ sp_hard_reset_command_clone(struct HardResetCommand */*notnull*/ instance); + +/** + *Deallocates a [`HardResetCommand`] instance. + * + * This function is part of the `hard_reset_command` module. + */ +void sp_hard_reset_command_free(struct HardResetCommand */*notnull*/ instance); + +/** + * 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 [`HardResetCommand`] instance. + * + * This function is part of the `hard_reset_command` module. + */ +struct HardResetCommand */*notnull*/ sp_hard_reset_command_new(void); + +/** + *Tries to turn a [`HardResetCommand`] into a [Packet]. + * + * Returns: NULL or a [Packet] containing the command. + * + * [Packet]: [`servicepoint::Packet`] + * + * This function is part of the `hard_reset_command` module. + */ +struct Packet *sp_hard_reset_command_try_into_packet(struct HardResetCommand */*notnull*/ instance); + +/** + * Calls method [`Packet::clone`]. + * + *Clones a [`Packet`] instance. + * + * This function is part of the `packet` module. + */ +struct Packet */*notnull*/ sp_packet_clone(struct Packet */*notnull*/ instance); + +/** + *Deallocates a [`Packet`] instance. + * + * This function is part of the `packet` module. + */ +void sp_packet_free(struct Packet */*notnull*/ instance); /** * Creates a raw [Packet] from parts. * * returns: new instance. Will never return null. + * + * This function is part of the `packet` module. */ -Packet */*notnull*/ sp_packet_from_parts(Header header, - const ByteSlice *payload); +struct Packet */*notnull*/ sp_packet_from_parts(struct Header header, + struct ByteSlice payload); /** - * Returns a pointer to the header field of the provided packet. + * Gets the value of field `header` of the [`servicepoint::Packet`]. * - * The returned header can be changed and will be valid for the lifetime of the packet. + * This function is part of the `packet` module. */ -Header */*notnull*/ sp_packet_get_header(Packet */*notnull*/ packet); +struct Header sp_packet_get_header(struct Packet */*notnull*/ instance); + +/** + * Gets a reference to the field `header` of the [`servicepoint::Packet`]. + * + * - The returned reference inherits the lifetime of object in which it is contained. + * - The returned pointer may not be used in a function that consumes the instance, e.g. to create a command. + * + * This function is part of the `packet` module. + */ +struct Header */*notnull*/ sp_packet_get_header_mut(struct Packet */*notnull*/ instance); /** * Returns a pointer to the current payload of the provided packet. * + * Returns an [ByteSlice::INVALID] instance in case the packet does not have any payload. + * * The returned memory can be changed and will be valid until a new payload is set. + * + * This function is part of the `packet` module. */ -ByteSlice sp_packet_get_payload(Packet */*notnull*/ packet); +struct ByteSlice sp_packet_get_payload(struct Packet */*notnull*/ packet); /** * Serialize the packet into the provided buffer. @@ -1188,72 +2088,96 @@ ByteSlice sp_packet_get_payload(Packet */*notnull*/ packet); * # Panics * * - if the buffer is not big enough to hold header+payload. + * + * This function is part of the `packet` module. */ -void sp_packet_serialize_to(Packet */*notnull*/ packet, ByteSlice buffer); +size_t sp_packet_serialize_to(struct Packet */*notnull*/ packet, + struct ByteSlice buffer); + +/** + * Sets the value of field `header` of the [`servicepoint::Packet`]. + * + * This function is part of the `packet` module. + */ +void sp_packet_set_header(struct Packet */*notnull*/ instance, + struct Header value); /** * Sets the payload of the provided packet to the provided data. * * This makes previous payload pointers invalid. + * + * This function is part of the `packet` module. */ -void sp_packet_set_payload(Packet */*notnull*/ packet, ByteSlice data); +void sp_packet_set_payload(struct Packet */*notnull*/ packet, + struct 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 + * + * This function is part of the `packet` module. */ -Packet *sp_packet_try_load(ByteSlice data); +struct Packet *sp_packet_try_load(struct ByteSlice data); /** * Converts u16 into [CommandCode]. * * If the provided value is not valid, false is returned and result is not changed. + * + * This function is part of the `sp` module. */ -bool sp_u16_to_command_code(uint16_t code, - CommandCode *result); +bool sp_sp_u16_to_command_code(uint16_t code, + CommandCode */*notnull*/ result); /** - * Closes and deallocates a [UdpConnection]. + *Deallocates a [`UdpSocket`] instance. + * + * This function is part of the `udp_socket` module. */ -void sp_udp_free(UdpSocket */*notnull*/ connection); +void sp_udp_socket_free(struct UdpSocket */*notnull*/ instance); /** - * Creates a new instance of [UdpConnection]. + * Creates a new instance of [UdpSocket]. * * returns: NULL if connection fails, or connected instance * * # Examples * * ```C - * UdpConnection connection = sp_udp_open("172.23.42.29:2342"); + * UdpSocket connection = sp_udp_open("172.23.42.29:2342"); * if (connection != NULL) * sp_udp_send_command(connection, sp_command_clear()); * ``` + * + * This function is part of the `udp_socket` module. */ -UdpSocket *sp_udp_open(char */*notnull*/ host); +struct UdpSocket *sp_udp_socket_open(char */*notnull*/ host); /** - * Creates a new instance of [UdpConnection]. + * Creates a new instance of [UdpSocket]. * * returns: NULL if connection fails, or connected instance * * # Examples * * ```C - * UdpConnection connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); + * UdpSocket connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); * if (connection != NULL) * sp_udp_send_command(connection, sp_command_clear()); * ``` + * + * This function is part of the `udp_socket` module. */ -UdpSocket *sp_udp_open_ipv4(uint8_t ip1, - uint8_t ip2, - uint8_t ip3, - uint8_t ip4, - uint16_t port); +struct UdpSocket *sp_udp_socket_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]. + * Sends a [GenericCommand] to the display using the [UdpSocket]. * * The passed `command` gets consumed. * @@ -1264,12 +2188,14 @@ UdpSocket *sp_udp_open_ipv4(uint8_t ip1, * ```C * sp_udp_send_command(connection, sp_command_brightness(5)); * ``` + * + * This function is part of the `udp_socket` module. */ -bool sp_udp_send_command(UdpSocket */*notnull*/ connection, - TypedCommand */*notnull*/ command); +bool sp_udp_socket_send_command(struct UdpSocket */*notnull*/ connection, + struct GenericCommand */*notnull*/ command); /** - * Sends a [Header] to the display using the [UdpConnection]. + * Sends a [Header] to the display using the [UdpSocket]. * * returns: true in case of success * @@ -1278,19 +2204,26 @@ bool sp_udp_send_command(UdpSocket */*notnull*/ connection, * ```C * sp_udp_send_header(connection, sp_command_brightness(5)); * ``` + * + * This function is part of the `udp_socket` module. */ -bool sp_udp_send_header(UdpSocket */*notnull*/ udp_connection, Header header); +bool sp_udp_socket_send_header(struct UdpSocket */*notnull*/ udp_connection, + struct Header header); /** - * Sends a [Packet] to the display using the [UdpConnection]. + * Sends a [Packet] to the display using the [UdpSocket]. * * The passed `packet` gets consumed. * * returns: true in case of success + * + * This function is part of the `udp_socket` module. */ -bool sp_udp_send_packet(UdpSocket */*notnull*/ connection, - Packet */*notnull*/ packet); +bool sp_udp_socket_send_packet(struct UdpSocket */*notnull*/ connection, + struct Packet */*notnull*/ packet); #ifdef __cplusplus } // extern "C" #endif // __cplusplus + +#endif /* SERVICEPOINT_BINDINGS_C */ diff --git a/nix-build-all.sh b/nix-build-all.sh new file mode 100755 index 0000000..9472cc5 --- /dev/null +++ b/nix-build-all.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -e +set -x + +BUILD="nom build -L" + +$BUILD .#servicepoint-binding-c -o result + +$BUILD .#servicepoint-binding-c-stable-release -o result-stable-release +$BUILD .#servicepoint-binding-c-stable-size -o result-stable-size +$BUILD .#servicepoint-binding-c-nightly-release -o result-nightly-release + +$BUILD .#all-examples -o result-examples +$BUILD .#all-examples-size -o result-examples-size diff --git a/packages.nix b/packages.nix new file mode 100644 index 0000000..e01882e --- /dev/null +++ b/packages.nix @@ -0,0 +1,181 @@ +{ + pkgs, + lib, + fenix, + selfPkgs, + ... +}: +let + version = "0.15.0"; + mkServicepoint = + { + rustPlatform, + pkgs, + buildType ? "release", + buildNoDefaultFeatures ? false, + cargoBuildFlags ? [ ], + nativeBuildInputs ? [], + stdlib ? false, + }: + rustPlatform.buildRustPackage (finalAttrs: { + inherit version buildType cargoBuildFlags stdlib; + + pname = "servicepoint-binding-c"; + src = lib.filter { + root = ./.; + include = [ + ./Cargo.lock + ./Cargo.toml + ./src + ./include + ./LICENSE + ./cbindgen.toml + ]; + }; + cargoLock.lockFile = ./Cargo.lock; + useFetchCargoVendor = true; + meta = { + description = "C bindings for the servicepoint crate."; + homepage = "https://git.berlin.ccc.de/servicepoint/servicepoint-binding-c"; + license = lib.licenses.gpl3Plus; + pkgConfigModules = [ "servicepoint" ]; + }; + nativeBuildInputs = [ pkgs.pkg-config ] ++ nativeBuildInputs; + buildInputs = [ pkgs.xz ]; + postInstall = '' + cp -r $src/include $out + + mkdir -p $out/lib/pkgconfig + sed "s:\$out:$out:g" ${./servicepoint.pc.in} | sed "s:\$version:$version:g" > $out/lib/pkgconfig/servicepoint.pc + ''; + }); + mkExample = + { + name, + servicepointBinding, + pkgs, + EXTRA_CFLAGS ? "", + static ? false, + }: + let + staticPkgConfigParam = if static then "--static" else ""; + in + pkgs.gccStdenv.mkDerivation { + inherit version EXTRA_CFLAGS; + pname = "servicepoint-c-example-${name}"; + nativeBuildInputs = [ pkgs.pkg-config ]; + buildInputs = [ + servicepointBinding + pkgs.xz + ]; + src = ./example/src; + buildPhase = '' + set -e + set -x + mkdir -p $out/bin + $CC ${name}.c ${if static then "-static" else ""} $CFLAGS $EXTRA_CFLAGS \ + $(pkg-config --libs --cflags ${staticPkgConfigParam} servicepoint) \ + $(pkg-config --libs --cflags ${staticPkgConfigParam} liblzma) \ + -o $out/bin/${name} + ''; + }; + examples = [ + "announce" + "brightness_tester" + "header_logger" + "moving_line" + "random_stuff" + "wiping_clear" + ]; + mkAllExamples = + suffix: + pkgs.symlinkJoin { + name = "servicepoint-all-examples"; + paths = builtins.map (e: selfPkgs."${e}${suffix}") examples; + }; + size-cflags = [ + "-Oz" + "-fwrapv" + "-fomit-frame-pointer" + "-fno-stack-protector" + "-fno-unroll-loops" + "-fno-unwind-tables" + "-fno-asynchronous-unwind-tables" + "-fmerge-all-constants" + "-fvisibility=hidden" + "-Bsymbolic" + "-fno-ident" + "-fno-exceptions" + "-ffunction-sections" + "-fdata-sections" + "-Wl,-z,norelro" + "-Wl,--hash-style=gnu" + "-Wl,--gc-sections" + "-Wl,--exclude-libs,ALL" + ]; + rustPlatform-stable = pkgs.rustPlatform; + rustPlatform-nightly = pkgs.makeRustPlatform fenix.complete; + rustPlatform-musl-stable = pkgs.pkgsMusl.rustPlatform; + rustPlatform-musl-nightly = pkgs.pkgsMusl.makeRustPlatform fenix.complete; + stable-release-args = { + inherit pkgs; + rustPlatform = rustPlatform-stable; + }; + nightly-release-args = { + inherit pkgs; + rustPlatform = rustPlatform-nightly; + }; + musl-stable-release-args = { + pkgs = pkgs.pkgsMusl; + rustPlatform = rustPlatform-musl-stable; + }; + musl-nightly-release-args = { + pkgs = pkgs.pkgsMusl; + rustPlatform = rustPlatform-musl-nightly; + }; + stable-size-args = { + buildType = "size_optimized"; + buildNoDefaultFeatures = true; + }; + nightly-size-args = { + cargoBuildFlags = [ + "-Zbuild-std=core,std,alloc,proc_macro,panic_abort" + "-Zbuild-std-features=panic_immediate_abort" + ]; + stdlib = true; + # TODO: remove hard-coded target + nativeBuildInputs = [fenix.targets."x86_64-unknown-linux-gnu".latest.rust-std]; + # TODO: those override the nix flags + # NIX_CFLAGS_COMPILE = builtins.toString ["-Oz" "-fwrapv" "-fomit-frame-pointer" "-fno-stack-protector" "-fno-unroll-loops" "-fno-unwind-tables" "-fno-asynchronous-unwind-tables" "-fmerge-all-constants" "-fvisibility=hidden" "-Bsymbolic" "-fno-ident" "-fno-exceptions" "-ffunction-sections" "-fdata-sections"]; + # NIX_CFLAGS_LINK = builtins.toString ["Wl,-z,norelro" "-Wl,--hash-style=gnu" "-Wl,--gc-sections" "-Wl,--exclude-libs,ALL"]; + }; +in +rec { + servicepoint-binding-c-stable-release = mkServicepoint stable-release-args; + servicepoint-binding-c-nightly-release = mkServicepoint nightly-release-args; + servicepoint-binding-c-stable-size = mkServicepoint (stable-release-args // stable-size-args); + + servicepoint-binding-c = servicepoint-binding-c-stable-release; + + all-examples = mkAllExamples ""; + all-examples-size = mkAllExamples "-size"; +} +# construct one package per example +// (lib.genAttrs examples ( + name: + mkExample { + inherit name pkgs; + servicepointBinding = selfPkgs.servicepoint-binding-c; + } +)) +# construct another pakage per example, but optimized for size with stable rust +// (lib.mapAttrs' (name: value: lib.nameValuePair ("${name}-size") value) ( + lib.genAttrs examples ( + name: + mkExample { + inherit name pkgs; + servicepointBinding = selfPkgs.servicepoint-binding-c-stable-size; + EXTRA_CFLAGS = builtins.toString size-cflags; + } + ) +)) diff --git a/servicepoint.pc.in b/servicepoint.pc.in new file mode 100644 index 0000000..5f474e7 --- /dev/null +++ b/servicepoint.pc.in @@ -0,0 +1,6 @@ +Name: servicepoint +Description: C bindings for the servicepoint library +Version: $version +URL: https://git.berlin.ccc.de/servicepoint/servicepoint-binding-c +Libs: -L$out/lib -lservicepoint_binding_c +Cflags: -I$out/include diff --git a/src/bitmap.rs b/src/bitmap.rs deleted file mode 100644 index 5db4af0..0000000 --- a/src/bitmap.rs +++ /dev/null @@ -1,226 +0,0 @@ -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; - -/// 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 -/// -/// ```C -/// Cp437Grid grid = sp_bitmap_new(8, 3); -/// sp_bitmap_fill(grid, true); -/// sp_bitmap_set(grid, 0, 0, false); -/// sp_bitmap_free(grid); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_new( - width: usize, - height: usize, -) -> *mut Bitmap { - if let Some(bitmap) = Bitmap::new(width, height) { - heap_move(bitmap) - } else { - std::ptr::null_mut() - } -} - -/// Creates a new [Bitmap] with a size matching the screen. -/// -/// returns: [Bitmap] initialized to all pixels off. -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_new_max_sized() -> NonNull { - heap_move_nonnull(Bitmap::max_sized()) -} - -/// 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. -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_load( - width: usize, - height: usize, - 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() - } -} - -/// Tries to convert the BitVec to a Bitmap. -/// -/// The provided BitVec gets consumed. -/// -/// 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: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { bitmap.as_ref().clone() }) -} - -/// Deallocates a [Bitmap]. -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_free(bitmap: NonNull) { - unsafe { heap_drop(bitmap) } -} - -/// 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 -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_get( - bitmap: NonNull, - x: usize, - y: usize, -) -> bool { - unsafe { bitmap.as_ref().get(x, y) } -} - -/// Sets the value of the specified position in the [Bitmap]. -/// -/// # Arguments -/// -/// - `bitmap`: instance to write to -/// - `x` and `y`: position of the cell -/// - `value`: the value to write to the cell -/// -/// # Panics -/// -/// - when accessing `x` or `y` out of bounds -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_set( - bitmap: NonNull, - x: usize, - y: usize, - value: bool, -) { - unsafe { (*bitmap.as_ptr()).set(x, y, value) }; -} - -/// Sets the state of all pixels in the [Bitmap]. -/// -/// # Arguments -/// -/// - `bitmap`: instance to write to -/// - `value`: the value to set all pixels to -#[no_mangle] -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 [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 [Bitmap] -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_width(bitmap: NonNull) -> usize { - unsafe { bitmap.as_ref().width() } -} - -/// Gets the height in pixels of the [Bitmap] instance. -/// -/// # Arguments -/// -/// - `bitmap`: instance to read from -#[no_mangle] -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 [Bitmap] instance. -/// -/// The returned memory is valid for the lifetime of the bitmap. -#[no_mangle] -pub unsafe extern "C" fn sp_bitmap_unsafe_data_ref( - 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 deleted file mode 100644 index 0e7ee5e..0000000 --- a/src/bitvec.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; -use servicepoint::{ - BinaryOperation, BitVecCommand, CompressionCode, DisplayBitVec, Packet, -}; -use std::ptr::NonNull; - -/// A vector of bits -/// -/// # Examples -/// ```C -/// SPBitVec vec = sp_bitvec_new(8); -/// sp_bitvec_set(vec, 5, true); -/// sp_bitvec_free(vec); -/// ``` -pub struct SPBitVec(pub(crate) DisplayBitVec); - -impl Clone for SPBitVec { - fn clone(&self) -> Self { - SPBitVec(self.0.clone()) - } -} - -/// Creates a new [SPBitVec] instance. -/// -/// # Arguments -/// -/// - `size`: size in bits. -/// -/// returns: [SPBitVec] with all bits set to false. -/// -/// # Panics -/// -/// - when `size` is not divisible by 8. -#[no_mangle] -pub unsafe extern "C" fn sp_bitvec_new(size: usize) -> NonNull { - heap_move_nonnull(SPBitVec(DisplayBitVec::repeat(false, size))) -} - -/// Interpret the data as a series of bits and load then into a new [SPBitVec] instance. -/// -/// returns: [SPBitVec] instance containing data. -#[no_mangle] -pub unsafe extern "C" fn sp_bitvec_load(data: ByteSlice) -> NonNull { - let data = unsafe { data.as_slice() }; - heap_move_nonnull(SPBitVec(DisplayBitVec::from_slice(data))) -} - -/// Clones a [SPBitVec]. -#[no_mangle] -pub unsafe extern "C" fn sp_bitvec_clone( - bit_vec: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { bit_vec.as_ref().clone() }) -} - -/// Deallocates a [SPBitVec]. -#[no_mangle] -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]. -/// -/// # Arguments -/// -/// - `bit_vec`: instance to read from -/// - `index`: the bit index to read -/// -/// returns: value of the bit -/// -/// # Panics -/// -/// - when accessing `index` out of bounds -#[no_mangle] -pub unsafe extern "C" fn sp_bitvec_get( - bit_vec: NonNull, - index: usize, -) -> bool { - unsafe { *bit_vec.as_ref().0.get(index).unwrap() } -} - -/// Sets the value of a bit in the [SPBitVec]. -/// -/// # Arguments -/// -/// - `bit_vec`: instance to write to -/// - `index`: the bit index to edit -/// - `value`: the value to set the bit to -/// -/// # Panics -/// -/// - when accessing `index` out of bounds -#[no_mangle] -pub unsafe extern "C" fn sp_bitvec_set( - bit_vec: NonNull, - index: usize, - value: bool, -) { - unsafe { (*bit_vec.as_ptr()).0.set(index, value) } -} - -/// Sets the value of all bits in the [SPBitVec]. -/// -/// # Arguments -/// -/// - `bit_vec`: instance to write to -/// - `value`: the value to set all bits to -#[no_mangle] -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. -/// -/// # Arguments -/// -/// - `bit_vec`: instance to write to -#[no_mangle] -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. -/// -/// # Arguments -/// -/// - `bit_vec`: instance to write to -#[no_mangle] -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 -#[no_mangle] -pub unsafe extern "C" fn sp_bitvec_unsafe_data_ref( - 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 deleted file mode 100644 index 1099be0..0000000 --- a/src/brightness_grid.rs +++ /dev/null @@ -1,197 +0,0 @@ -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; - -/// 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); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn sp_brightness_grid_new( - width: usize, - height: usize, -) -> NonNull { - heap_move_nonnull(BrightnessGrid::new(width, height)) -} - -/// Loads a [BrightnessGrid] with the specified dimensions from the provided data. -/// -/// Any out of range values will be set to [Brightness::MAX] or [Brightness::MIN]. -/// -/// 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: 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 [BrightnessGrid]. -#[no_mangle] -pub unsafe extern "C" fn sp_brightness_grid_clone( - brightness_grid: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { brightness_grid.as_ref().clone() }) -} - -/// Deallocates a [BrightnessGrid]. -#[no_mangle] -pub unsafe extern "C" fn sp_brightness_grid_free( - brightness_grid: NonNull, -) { - unsafe { heap_drop(brightness_grid) } -} - -/// Gets the current value at the specified position. -/// -/// # Arguments -/// -/// - `brightness_grid`: instance to read from -/// - `x` and `y`: position of the cell to read -/// -/// returns: value at position -/// -/// # Panics -/// - When accessing `x` or `y` out of bounds. -#[no_mangle] -pub unsafe extern "C" fn sp_brightness_grid_get( - brightness_grid: NonNull, - x: usize, - y: usize, -) -> Brightness { - unsafe { brightness_grid.as_ref().get(x, y) } -} - -/// Sets the value of the specified position in the [BrightnessGrid]. -/// -/// # Arguments -/// -/// - `brightness_grid`: instance to write to -/// - `x` and `y`: position of the cell -/// - `value`: the value to write to the cell -/// -/// returns: old value of the cell -/// -/// # Panics -/// -/// - When accessing `x` or `y` out of bounds. -#[no_mangle] -pub unsafe extern "C" fn sp_brightness_grid_set( - brightness_grid: NonNull, - x: usize, - y: usize, - value: Brightness, -) { - unsafe { (*brightness_grid.as_ptr()).set(x, y, value) }; -} - -/// Sets the value of all cells in the [BrightnessGrid]. -/// -/// # Arguments -/// -/// - `brightness_grid`: instance to write to -/// - `value`: the value to set all cells to -#[no_mangle] -pub unsafe extern "C" fn sp_brightness_grid_fill( - brightness_grid: NonNull, - value: Brightness, -) { - unsafe { (*brightness_grid.as_ptr()).fill(value) }; -} - -/// Gets the width of the [BrightnessGrid] instance. -/// -/// # Arguments -/// -/// - `brightness_grid`: instance to read from -/// -/// returns: width -#[no_mangle] -pub unsafe extern "C" fn sp_brightness_grid_width( - brightness_grid: NonNull, -) -> usize { - unsafe { brightness_grid.as_ref().width() } -} - -/// Gets the height of the [BrightnessGrid] instance. -/// -/// # Arguments -/// -/// - `brightness_grid`: instance to read from -/// -/// returns: height -#[no_mangle] -pub unsafe extern "C" fn sp_brightness_grid_height( - brightness_grid: NonNull, -) -> usize { - unsafe { brightness_grid.as_ref().height() } -} - -/// 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`. -#[no_mangle] -pub unsafe extern "C" fn sp_brightness_grid_unsafe_data_ref( - 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 deleted file mode 100644 index de7d0df..0000000 --- a/src/byte_slice.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! FFI slice helper - -use std::ptr::NonNull; - -/// 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. -#[repr(C)] -pub struct ByteSlice { - /// The start address of the memory. - pub start: NonNull, - /// 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 deleted file mode 100644 index bfb2585..0000000 --- a/src/char_grid.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; -use servicepoint::{CharGrid, CharGridCommand, Grid, Origin, Packet}; -use std::ptr::NonNull; - -/// 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); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn sp_char_grid_new( - width: usize, - height: usize, -) -> NonNull { - heap_move_nonnull(CharGrid::new(width, height)) -} - -/// Loads a [CharGrid] with the specified dimensions from the provided data. -/// -/// 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: 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 [CharGrid]. -#[no_mangle] -pub unsafe extern "C" fn sp_char_grid_clone( - char_grid: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { char_grid.as_ref().clone() }) -} - -/// Deallocates a [CharGrid]. -#[no_mangle] -pub unsafe extern "C" fn sp_char_grid_free(char_grid: NonNull) { - unsafe { heap_drop(char_grid) } -} - -/// Returns the current value at the specified position. -/// -/// # Arguments -/// -/// - `char_grid`: instance to read from -/// - `x` and `y`: position of the cell to read -/// -/// # Panics -/// -/// - when accessing `x` or `y` out of bounds -#[no_mangle] -pub unsafe extern "C" fn sp_char_grid_get( - char_grid: NonNull, - x: usize, - y: usize, -) -> u32 { - unsafe { char_grid.as_ref().get(x, y) as u32 } -} - -/// Sets the value of the specified position in the [CharGrid]. -/// -/// # Arguments -/// -/// - `char_grid`: instance to write to -/// - `x` and `y`: position of the cell -/// - `value`: the value to write to the cell -/// -/// returns: old value of the cell -/// -/// # Panics -/// -/// - when accessing `x` or `y` out of bounds -#[no_mangle] -pub unsafe extern "C" fn sp_char_grid_set( - char_grid: NonNull, - x: usize, - y: usize, - value: u32, -) { - unsafe { (*char_grid.as_ptr()).set(x, y, char::from_u32(value).unwrap()) }; -} - -/// Sets the value of all cells in the [CharGrid]. -/// -/// # Arguments -/// -/// - `char_grid`: instance to write to -/// - `value`: the value to set all cells to -#[no_mangle] -pub unsafe extern "C" fn sp_char_grid_fill( - char_grid: NonNull, - value: u32, -) { - unsafe { (*char_grid.as_ptr()).fill(char::from_u32(value).unwrap()) }; -} - -/// Gets the width of the [CharGrid] instance. -/// -/// # Arguments -/// -/// - `char_grid`: instance to read from -#[no_mangle] -pub unsafe extern "C" fn sp_char_grid_width( - char_grid: NonNull, -) -> usize { - unsafe { char_grid.as_ref().width() } -} - -/// Gets the height of the [CharGrid] instance. -/// -/// # Arguments -/// -/// - `char_grid`: instance to read from -#[no_mangle] -pub unsafe extern "C" fn sp_char_grid_height( - char_grid: NonNull, -) -> usize { - 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/commands/bitmap_command.rs b/src/commands/bitmap_command.rs new file mode 100644 index 0000000..d325bce --- /dev/null +++ b/src/commands/bitmap_command.rs @@ -0,0 +1,38 @@ +use crate::macros::wrap; +use servicepoint::{Bitmap, BitmapCommand, CompressionCode, Origin}; +use std::ptr::NonNull; + +wrap! { + BitmapCommand { + derives: crate::commands::derive_command[Bitmap], crate::commands::derive_origin_accessors; + properties: + prop bitmap: Bitmap { get mut; set move; }; + prop compression: CompressionCode { get; set; }; + functions: + /// Sets a window of pixels to the specified values. + /// + /// The passed [Bitmap] gets consumed. + /// + /// Returns: a new [BitmapCommand] instance. + fn new( + bitmap: move NonNull, + origin_x: val usize, + origin_y: val usize, + compression: val CompressionCode, + ) -> move NonNull { + BitmapCommand { + bitmap, + origin: Origin::new(origin_x, origin_y), + compression, + } + }; + + /// Move the provided [Bitmap] into a new [BitmapCommand], + /// leaving other fields as their default values. + /// + /// Rust equivalent: `BitmapCommand::from(bitmap)` + fn from_bitmap(bitmap: move NonNull) -> move NonNull { + bitmap.into() + }; + } +} diff --git a/src/commands/bitvec_command.rs b/src/commands/bitvec_command.rs new file mode 100644 index 0000000..82b92fb --- /dev/null +++ b/src/commands/bitvec_command.rs @@ -0,0 +1,42 @@ +use crate::macros::wrap; +use servicepoint::{ + BinaryOperation, BitVecCommand, CompressionCode, DisplayBitVec, Offset, +}; +use std::ptr::NonNull; + +wrap!( + BitVecCommand { + derives: crate::commands::derive_command[BitVec]; + properties: + prop bitvec: DisplayBitVec { get mut; set move; }; + prop offset: Offset { get; set; }; + prop operation: BinaryOperation { get; set; }; + prop compression: CompressionCode { get; set; }; + functions: + /// 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 [`DisplayBitVec`] is always uncompressed. + fn new( + bitvec: move NonNull, + offset: val usize, + operation: val BinaryOperation, + compression: val CompressionCode, + ) -> move NonNull { + BitVecCommand { + bitvec, + offset, + operation, + compression, + } + }; + } +); diff --git a/src/commands/brightness_grid_command.rs b/src/commands/brightness_grid_command.rs new file mode 100644 index 0000000..ef568bd --- /dev/null +++ b/src/commands/brightness_grid_command.rs @@ -0,0 +1,33 @@ +use crate::macros::wrap; +use servicepoint::{BrightnessGrid, BrightnessGridCommand, Origin}; +use std::ptr::NonNull; + +wrap!( + BrightnessGridCommand { + derives: crate::commands::derive_command[BrightnessGrid], crate::commands::derive_origin_accessors; + properties: + prop grid: BrightnessGrid { get mut; set move; }; + functions: + /// Set the brightness of individual tiles in a rectangular area of the display. + /// + /// The passed [BrightnessGrid] gets consumed. + /// + /// Returns: a new [BrightnessGridCommand] instance. + fn new( + grid: move NonNull, + origin_x: val usize, + origin_y: val usize + ) -> move NonNull { + BrightnessGridCommand { + grid, + origin: Origin::new(origin_x, origin_y), + } + }; + + /// Moves the provided [BrightnessGrid] into a new [BrightnessGridCommand], + /// leaving other fields as their default values. + fn from_grid(grid: move NonNull) -> move NonNull { + grid.into() + }; + } +); diff --git a/src/commands/cc_only_commands.rs b/src/commands/cc_only_commands.rs new file mode 100644 index 0000000..22a4a3b --- /dev/null +++ b/src/commands/cc_only_commands.rs @@ -0,0 +1,39 @@ +use servicepoint::{ClearCommand, FadeOutCommand, HardResetCommand}; + +macro_rules! wrap_cc_only { + ($(#[$meta:meta])* $command:ident) => { + ::paste::paste!{ + $crate::macros::wrap!{ + [< $command Command >] { + derives: $crate::commands::derive_command[$command]; + functions: + $(#[$meta])* + /// + #[doc = " Returns: a new [`" [< $command Command >] "`] instance."] + fn new() -> move ::core::ptr::NonNull<[< $command Command >]> { + [< $command Command >] + }; + } + } + } + }; +} + +wrap_cc_only!( + /// Set all pixels to the off state. + /// + /// Does not affect brightness. + Clear +); + +wrap_cc_only!( + /// Kills the udp daemon on the display, which usually results in a restart. + /// + /// Please do not send this in your normal program flow. + HardReset +); + +wrap_cc_only!( + /// A yet-to-be-tested command. + FadeOut +); diff --git a/src/commands/char_grid_command.rs b/src/commands/char_grid_command.rs new file mode 100644 index 0000000..96d8b90 --- /dev/null +++ b/src/commands/char_grid_command.rs @@ -0,0 +1,33 @@ +use crate::macros::wrap; +use servicepoint::{CharGrid, CharGridCommand, Origin}; +use std::ptr::NonNull; + +wrap!( + CharGridCommand { + derives: crate::commands::derive_command[CharGrid], crate::commands::derive_origin_accessors; + properties: + prop grid: CharGrid { get mut; set move; }; + functions: + /// Show UTF-8 encoded text on the screen. + /// + /// The passed [CharGrid] gets consumed. + /// + /// Returns: a new [CharGridCommand] instance. + fn new( + grid: move NonNull, + origin_x: val usize, + origin_y: val usize, + ) -> move NonNull { + CharGridCommand { + grid, + origin: Origin::new(origin_x, origin_y), + } + }; + + /// Moves the provided [CharGrid] into a new [CharGridCommand], + /// leaving other fields as their default values. + fn from_grid(grid: move NonNull) -> move NonNull { + grid.into() + }; + } +); diff --git a/src/commands/cp437_grid_command.rs b/src/commands/cp437_grid_command.rs new file mode 100644 index 0000000..19b35f6 --- /dev/null +++ b/src/commands/cp437_grid_command.rs @@ -0,0 +1,33 @@ +use crate::macros::wrap; +use servicepoint::{Cp437Grid, Cp437GridCommand, Origin}; +use std::ptr::NonNull; + +wrap!( + Cp437GridCommand { + derives: crate::commands::derive_command[Cp437Grid], crate::commands::derive_origin_accessors; + properties: + prop grid: Cp437Grid { get mut; set move; }; + functions: + /// Show text on the screen. + /// + /// The text is sent in the form of a 2D grid of [CP-437] encoded characters. + /// + /// The origin is relative to the top-left of the display. + fn new( + grid: move NonNull, + origin_x: val usize, + origin_y: val usize, + ) -> move NonNull { + Cp437GridCommand { + grid, + origin: Origin::new(origin_x, origin_y), + } + }; + + /// Moves the provided [Cp437Grid] into a new [Cp437GridCommand], + /// leaving other fields as their default values. + fn from_grid(grid: move NonNull) -> move NonNull { + grid.into() + }; + } +); diff --git a/src/commands/generic_command.rs b/src/commands/generic_command.rs new file mode 100644 index 0000000..2991352 --- /dev/null +++ b/src/commands/generic_command.rs @@ -0,0 +1,293 @@ +use crate::{ + macros::{derive_clone, derive_free, wrap}, + mem::{ + heap_clone, heap_drop, heap_move, heap_move_nonnull, heap_move_ok, + heap_remove, + }, +}; +use servicepoint::{ + BitVecCommand, BitmapCommand, BrightnessGridCommand, CharGridCommand, + ClearCommand, Cp437GridCommand, FadeOutCommand, GlobalBrightnessCommand, + HardResetCommand, Packet, TypedCommand, +}; +use std::ptr::{null_mut, NonNull}; + +/// Pointer to one of the available command structs. +#[repr(C)] +#[allow(missing_docs)] +pub union CommandUnion { + pub null: *mut u8, + pub bitmap: NonNull, + pub bit_vec: NonNull, + pub brightness_grid: NonNull, + pub char_grid: NonNull, + pub cp437_grid: NonNull, + pub global_brightness: NonNull, + pub clear: NonNull, + #[allow(deprecated)] + pub bitmap_legacy: NonNull, + pub hard_reset: NonNull, + pub fade_out: NonNull, +} + +/// Specifies the kind of command struct. +/// +/// This is _not_ equivalent to the [servicepoint::CommandCode]s. +#[repr(u8)] +#[allow(missing_docs)] +pub enum CommandTag { + Invalid = 0, + Bitmap, + BitVec, + BrightnessGrid, + CharGrid, + Cp437Grid, + GlobalBrightness, + Clear, + HardReset, + FadeOut, + BitmapLegacy, +} + +/// This struct represents a pointer to one of the possible command structs. +/// +/// Only ever access `data` with the correct data type as specified by `tag`! +/// +/// Rust equivalent: [TypedCommand]. +#[repr(C)] +pub struct GenericCommand { + /// Specifies which kind of command struct is contained in `data` + pub tag: CommandTag, + /// The pointer to the command struct + pub data: CommandUnion, +} + +impl GenericCommand { + pub(crate) const INVALID: GenericCommand = GenericCommand { + tag: CommandTag::Invalid, + data: CommandUnion { null: null_mut() }, + }; +} + +impl Clone for GenericCommand { + fn clone(&self) -> Self { + unsafe { + match self.tag { + CommandTag::Clear => GenericCommand { + tag: CommandTag::Clear, + data: CommandUnion { + clear: heap_clone(self.data.clear), + }, + }, + CommandTag::CharGrid => GenericCommand { + tag: CommandTag::CharGrid, + data: CommandUnion { + char_grid: heap_clone(self.data.char_grid), + }, + }, + CommandTag::Cp437Grid => GenericCommand { + tag: CommandTag::Cp437Grid, + data: CommandUnion { + cp437_grid: heap_clone(self.data.cp437_grid), + }, + }, + CommandTag::Bitmap => GenericCommand { + tag: CommandTag::Bitmap, + data: CommandUnion { + bitmap: heap_clone(self.data.bitmap), + }, + }, + CommandTag::GlobalBrightness => GenericCommand { + tag: CommandTag::GlobalBrightness, + data: CommandUnion { + global_brightness: heap_clone( + self.data.global_brightness, + ), + }, + }, + CommandTag::BrightnessGrid => GenericCommand { + tag: CommandTag::BrightnessGrid, + data: CommandUnion { + brightness_grid: heap_clone(self.data.brightness_grid), + }, + }, + CommandTag::BitVec => GenericCommand { + tag: CommandTag::BitVec, + data: CommandUnion { + bit_vec: heap_clone(self.data.bit_vec), + }, + }, + CommandTag::HardReset => GenericCommand { + tag: CommandTag::HardReset, + data: CommandUnion { + hard_reset: heap_clone(self.data.hard_reset), + }, + }, + CommandTag::FadeOut => GenericCommand { + tag: CommandTag::FadeOut, + data: CommandUnion { + fade_out: heap_clone(self.data.fade_out), + }, + }, + #[allow(deprecated)] + CommandTag::BitmapLegacy => GenericCommand { + tag: CommandTag::BitmapLegacy, + data: CommandUnion { + bitmap_legacy: heap_clone(self.data.bitmap_legacy), + }, + }, + CommandTag::Invalid => GenericCommand::INVALID, + } + } + } +} + +impl Drop for GenericCommand { + fn drop(&mut self) { + unsafe { + match self.tag { + CommandTag::Invalid => (), + CommandTag::Bitmap => heap_drop(self.data.bitmap), + CommandTag::BitVec => heap_drop(self.data.bit_vec), + CommandTag::BrightnessGrid => { + heap_drop(self.data.brightness_grid) + } + CommandTag::CharGrid => heap_drop(self.data.char_grid), + CommandTag::Cp437Grid => heap_drop(self.data.cp437_grid), + CommandTag::GlobalBrightness => { + heap_drop(self.data.global_brightness) + } + CommandTag::Clear => heap_drop(self.data.clear), + CommandTag::HardReset => heap_drop(self.data.hard_reset), + CommandTag::FadeOut => heap_drop(self.data.fade_out), + CommandTag::BitmapLegacy => heap_drop(self.data.bitmap_legacy), + } + } + + *self = Self::INVALID; + } +} + +derive_clone!(GenericCommand); +derive_free!(GenericCommand); + +wrap! { + GenericCommand { + functions: + /// Tries to turn a [Packet] into a [GenericCommand]. + /// + /// The packet is dropped in the process. + /// + /// Returns: pointer to new [GenericCommand] instance or NULL if parsing failed. + fn try_from_packet(packet: move NonNull) -> move NonNull { + servicepoint::TypedCommand::try_from(packet) + .map(|value| match value { + TypedCommand::Clear(clear) => GenericCommand { + tag: CommandTag::Clear, + data: CommandUnion { + clear: heap_move_nonnull(clear), + }, + }, + TypedCommand::CharGrid(char_grid) => GenericCommand { + tag: CommandTag::CharGrid, + data: CommandUnion { + char_grid: heap_move_nonnull(char_grid), + }, + }, + TypedCommand::Cp437Grid(cp437_grid) => GenericCommand { + tag: CommandTag::Cp437Grid, + data: CommandUnion { + cp437_grid: heap_move_nonnull(cp437_grid), + }, + }, + TypedCommand::Bitmap(bitmap) => GenericCommand { + tag: CommandTag::Bitmap, + data: CommandUnion { + bitmap: heap_move_nonnull(bitmap), + }, + }, + TypedCommand::Brightness(global_brightness) => GenericCommand { + tag: CommandTag::GlobalBrightness, + data: CommandUnion { + global_brightness: heap_move_nonnull(global_brightness), + }, + }, + TypedCommand::BrightnessGrid(brightness_grid) => GenericCommand { + tag: CommandTag::BrightnessGrid, + data: CommandUnion { + brightness_grid: heap_move_nonnull(brightness_grid), + }, + }, + TypedCommand::BitVec(bitvec) => GenericCommand { + tag: CommandTag::BitVec, + data: CommandUnion { + bit_vec: heap_move_nonnull(bitvec), + }, + }, + TypedCommand::HardReset(hard_reset) => GenericCommand { + tag: CommandTag::HardReset, + data: CommandUnion { + hard_reset: heap_move_nonnull(hard_reset), + }, + }, + TypedCommand::FadeOut(fade_out) => GenericCommand { + tag: CommandTag::FadeOut, + data: CommandUnion { + fade_out: heap_move_nonnull(fade_out), + }, + }, + #[allow(deprecated)] + TypedCommand::BitmapLegacy(bitmap_legacy) => GenericCommand { + tag: CommandTag::BitmapLegacy, + data: CommandUnion { + bitmap_legacy: heap_move_nonnull(bitmap_legacy), + }, + }, + }) + .unwrap_or_else(move |_| GenericCommand { + tag: CommandTag::Invalid, + data: CommandUnion { null: null_mut() }, + }) + }; + methods: + /// Tries to turn a [GenericCommand] into a [Packet]. + /// The [GenericCommand] gets consumed. + /// + /// Returns tag [CommandTag::Invalid] in case of an error. + fn try_into_packet(move command) -> val *mut Packet { + match command.tag { + CommandTag::Invalid => null_mut(), + CommandTag::Bitmap => { + heap_move_ok(unsafe { heap_remove(command.data.bitmap).try_into() }) + } + CommandTag::BitVec => { + heap_move_ok(unsafe { heap_remove(command.data.bit_vec).try_into() }) + } + CommandTag::BrightnessGrid => heap_move_ok(unsafe { + heap_remove(command.data.brightness_grid).try_into() + }), + CommandTag::CharGrid => heap_move_ok(unsafe { + heap_remove(command.data.char_grid).try_into() + }), + CommandTag::Cp437Grid => heap_move_ok(unsafe { + heap_remove(command.data.cp437_grid).try_into() + }), + CommandTag::GlobalBrightness => heap_move(unsafe { + heap_remove(command.data.global_brightness).into() + }), + CommandTag::Clear => { + heap_move(unsafe { heap_remove(command.data.clear).into() }) + } + CommandTag::HardReset => { + heap_move(unsafe { heap_remove(command.data.hard_reset).into() }) + } + CommandTag::FadeOut => { + heap_move(unsafe { heap_remove(command.data.fade_out).into() }) + } + CommandTag::BitmapLegacy => { + heap_move(unsafe { heap_remove(command.data.bitmap_legacy).into() }) + } + } + }; + } +} diff --git a/src/commands/global_brightness_command.rs b/src/commands/global_brightness_command.rs new file mode 100644 index 0000000..1ab9a1b --- /dev/null +++ b/src/commands/global_brightness_command.rs @@ -0,0 +1,18 @@ +use crate::macros::wrap; +use servicepoint::{Brightness, GlobalBrightnessCommand}; +use std::ptr::NonNull; + +wrap!( + GlobalBrightnessCommand { + derives: crate::commands::derive_command[GlobalBrightness]; + properties: + prop brightness: Brightness { get; set; }; + functions: + /// Set the brightness of all tiles to the same value. + /// + /// Returns: a new [GlobalBrightnessCommand] instance. + fn new(brightness: val Brightness) -> move NonNull { + GlobalBrightnessCommand::from(brightness) + }; + } +); diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..c8e9dfc --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,87 @@ +mod bitmap_command; +mod bitvec_command; +mod brightness_grid_command; +mod cc_only_commands; +mod char_grid_command; +mod cp437_grid_command; +mod generic_command; +mod global_brightness_command; + +pub use bitmap_command::*; +pub use bitvec_command::*; +pub use brightness_grid_command::*; +pub use char_grid_command::*; +pub use cp437_grid_command::*; +pub use generic_command::*; + +macro_rules! derive_origin_accessors { + ($object_type:ident) => { + ::paste::paste! { + $crate::macros::wrap_methods!($object_type; + #[doc = " Reads the origin field of the [`" $object_type "`]."] + fn get_origin( + ref command, + origin_x: mut ::core::ptr::NonNull, + origin_y: mut ::core::ptr::NonNull + ) { + let origin = command.origin; + *origin_x = origin.x; + *origin_y = origin.y; + }; + + #[doc = " Overwrites the origin field of the [`" $object_type "`]."] + fn set_origin(mut command, origin_x: val usize, origin_y: val usize) { + command.origin = ::servicepoint::Origin::new(origin_x, origin_y); + }; + ); + } + }; +} + +macro_rules! derive_command_from { + ($command:ident) => { + ::paste::paste! { + impl From<::servicepoint::[< $command Command >]> for $crate::commands::GenericCommand { + fn from(command: ::servicepoint::[< $command Command >]) -> Self { + Self { + tag: $crate::commands::CommandTag::$command, + data: $crate::commands::CommandUnion { + [< $command:snake >]: $crate::mem::heap_move_nonnull(command) + }, + } + } + } + } + }; +} + +macro_rules! derive_command_into_packet { + ($command_type:ident) => { + ::paste::paste! { + $crate::macros::wrap_method!($command_type; + #[doc = "Tries to turn a [`" $command_type "`] into a [Packet]."] + /// + /// Returns: NULL or a [Packet] containing the command. + /// + /// [Packet]: [`servicepoint::Packet`] + fn try_into_packet(move instance) -> move_ok *mut ::servicepoint::Packet { + instance.try_into() + }; + ); + } + } +} + +macro_rules! derive_command { + ($object_type:ident, $command:ident) => { + $crate::macros::derive_clone!($object_type); + $crate::macros::derive_free!($object_type); + $crate::commands::derive_command_from!($command); + $crate::commands::derive_command_into_packet!($object_type); + }; +} + +pub(crate) use { + derive_command, derive_command_from, derive_command_into_packet, + derive_origin_accessors, +}; diff --git a/src/containers/bitmap.rs b/src/containers/bitmap.rs new file mode 100644 index 0000000..5974016 --- /dev/null +++ b/src/containers/bitmap.rs @@ -0,0 +1,98 @@ +use crate::{containers::ByteSlice, macros::wrap}; +use servicepoint::{ + Bitmap, BitmapCommand, CompressionCode, DataRef, DisplayBitVec, Grid, + Origin, Packet, +}; +use std::ptr::NonNull; + +wrap! { + Bitmap { + derives: crate::containers::derive_container, crate::containers::derive_grid[bool]; + functions: + /// 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 + /// + /// ```C + /// Cp437Grid grid = sp_bitmap_new(8, 3); + /// sp_bitmap_fill(grid, true); + /// sp_bitmap_set(grid, 0, 0, false); + /// sp_bitmap_free(grid); + /// ``` + fn new(width: val usize, height: val usize) -> move_some *mut Bitmap { + Bitmap::new(width, height) + }; + + /// Creates a new [Bitmap] with a size matching the screen. + /// + /// returns: [Bitmap] initialized to all pixels off. + fn new_max_sized() -> move NonNull { + Bitmap::max_sized() + }; + + /// 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. + fn load( + width: val usize, + height: val usize, + data: slice ByteSlice, + ) -> move_ok *mut Bitmap { + Bitmap::load(width, height, data) + }; + + /// Tries to convert the BitVec to a Bitmap. + /// + /// The provided BitVec gets consumed. + /// + /// Returns NULL in case of error. + fn from_bitvec( + width: val usize, + bitvec: move NonNull, + ) -> move_ok *mut Bitmap { + Bitmap::from_bitvec(width, bitvec) + }; + + methods: + /// Consumes the Bitmap and returns the contained BitVec. + fn into_bitvec(move bitmap) -> move NonNull { + bitmap.into() + }; + + /// Creates a [BitmapCommand] and immediately turns that into a [Packet]. + /// + /// The provided [Bitmap] gets consumed. + /// + /// Returns NULL in case of an error. + fn try_into_packet(move bitmap, x: val usize, y: val usize, compression: val CompressionCode) -> move_ok *mut Packet { + Packet::try_from(BitmapCommand { + bitmap, + origin: Origin::new(x, y), + compression, + }) + }; + + /// Gets an unsafe reference to the data of the [Bitmap] instance. + /// + /// The returned memory is valid for the lifetime of the bitmap. + fn data_ref_mut(mut instance) -> slice ByteSlice; + } +} diff --git a/src/containers/bitvec.rs b/src/containers/bitvec.rs new file mode 100644 index 0000000..57e4fd8 --- /dev/null +++ b/src/containers/bitvec.rs @@ -0,0 +1,99 @@ +use crate::{containers::ByteSlice, macros::wrap}; +use servicepoint::{ + BinaryOperation, BitVecCommand, CompressionCode, DisplayBitVec, Packet, +}; +use std::ptr::NonNull; + +wrap! { + DisplayBitVec { + derives: crate::containers::derive_container; + functions: + /// Creates a new [DisplayBitVec] instance. + /// + /// # Arguments + /// + /// - `size`: size in bits. + /// + /// returns: [DisplayBitVec] with all bits set to false. + /// + /// # Panics + /// + /// - when `size` is not divisible by 8. + fn new(size: val usize) -> move NonNull { + DisplayBitVec::repeat(false, size) + }; + + /// Interpret the data as a series of bits and load then into a new [DisplayBitVec] instance. + /// + /// returns: [DisplayBitVec] instance containing data. + fn load(data: slice ByteSlice) -> move NonNull { + DisplayBitVec::from_slice(data) + }; + + methods: + /// Creates a [BitVecCommand] and immediately turns that into a [Packet]. + /// + /// The provided [DisplayBitVec] gets consumed. + /// + /// Returns NULL in case of an error. + fn try_into_packet( + move bitvec, + offset: val usize, + operation: val BinaryOperation, + compression: val CompressionCode + ) -> move_ok *mut Packet { + Packet::try_from(BitVecCommand { + bitvec, + offset, + operation, + compression, + }) + }; + + /// Gets the value of a bit. + /// + /// # Arguments + /// + /// - `bit_vec`: instance to read from + /// - `index`: the bit index to read + /// + /// returns: value of the bit + /// + /// # Panics + /// + /// - when accessing `index` out of bounds + fn get(ref instance, index: val usize) -> val bool { + instance.get(index).map(|x| *x).unwrap_or(false) + }; + + /// Sets the value of a bit. + /// + /// # Arguments + /// + /// - `index`: the bit index to edit + /// - `value`: the value to set the bit to + /// + /// # Panics + /// + /// - when accessing `index` out of bounds + fn set(mut instance, index: val usize, value: val bool); + + /// Sets the value of all bits. + /// + /// # Arguments + /// + /// - `value`: the value to set all bits to + fn fill(mut instance, value: val bool); + + /// Gets the length in bits. + fn len(ref instance) -> val usize; + + /// Returns true if length is 0. + fn is_empty(ref instance) -> val bool; + + /// Gets an unsafe reference to the data of the [DisplayBitVec] instance. + /// + /// The returned memory is valid for the lifetime of the bitvec. + fn as_raw_mut_slice(mut instance) -> slice ByteSlice; + } +} diff --git a/src/containers/brightness_grid.rs b/src/containers/brightness_grid.rs new file mode 100644 index 0000000..3b3fabb --- /dev/null +++ b/src/containers/brightness_grid.rs @@ -0,0 +1,73 @@ +use crate::{containers::ByteSlice, macros::wrap}; +use servicepoint::{ + Brightness, BrightnessGrid, BrightnessGridCommand, ByteGrid, DataRef, Grid, + Origin, Packet, +}; +use std::{mem::transmute, ptr::NonNull}; + +wrap! { + BrightnessGrid { + derives: crate::containers::derive_container, crate::containers::derive_grid[Brightness]; + functions: + /// Creates a new [BrightnessGrid] with the specified dimensions. + /// + /// returns: [BrightnessGrid] initialized to 0. + /// + /// # Examples + /// ```C + /// UdpSocket *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); + /// ``` + fn new(width: val usize, height: val usize) -> move NonNull { + BrightnessGrid::new(width, height) + }; + + /// Loads a [BrightnessGrid] with the specified dimensions from the provided data. + /// + /// Any out of range values will be set to [Brightness::MAX] or [Brightness::MIN]. + /// + /// returns: new [BrightnessGrid] instance, or NULL in case of an error. + fn load( + width: val usize, + height: val usize, + data: slice ByteSlice, + ) -> move_some *mut BrightnessGrid { + ByteGrid::load(width, height, data) + .map(move |grid| grid.map(Brightness::saturating_from)) + }; + + methods: + /// Creates a [BrightnessGridCommand] and immediately turns that into a [Packet]. + /// + /// The provided [BrightnessGrid] gets consumed. + /// + /// Returns NULL in case of an error. + fn try_into_packet(move grid, x: val usize, y: val usize) -> move_ok *mut Packet { + Packet::try_from(BrightnessGridCommand { + grid, + origin: Origin::new(x, y), + }) + }; + + /// Gets an unsafe reference to the data of the instance. + /// + /// The returned memory is valid for the lifetime of the grid. + fn data_ref_mut(mut instance) -> slice ByteSlice { + //noinspection RsAssertEqual + const _: () = assert!(size_of::() == 1); + + let br_slice = instance.data_ref_mut(); + unsafe { + transmute::<&mut [Brightness], &mut [u8]>(br_slice) + } + }; + } +} diff --git a/src/containers/byte_slice.rs b/src/containers/byte_slice.rs new file mode 100644 index 0000000..81380e9 --- /dev/null +++ b/src/containers/byte_slice.rs @@ -0,0 +1,54 @@ +//! FFI slice helper + +/// 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. +/// - if `start` is NULL or `length` is 0, do not dereference `start`. +/// +/// # Examples +/// +/// ```c +/// ByteSlice empty = {.start: NULL, .length = 0}; +/// ``` +#[repr(C)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ByteSlice { + /// The start address of the memory. + pub start: *mut u8, + /// The amount of memory in bytes. + pub length: usize, +} + +impl ByteSlice { + /// Represents an invalid [ByteSlice] instance. + pub const INVALID: ByteSlice = ByteSlice { + start: std::ptr::null_mut(), + length: 0, + }; + + pub(crate) unsafe fn as_slice(&self) -> &[u8] { + assert!(!self.start.is_null()); + unsafe { std::slice::from_raw_parts(self.start, self.length) } + } + + #[allow( + clippy::mut_from_ref, + reason = "This is used to get a pointer from the C side." + )] + pub(crate) unsafe fn as_slice_mut(&self) -> &mut [u8] { + unsafe { std::slice::from_raw_parts_mut(self.start, self.length) } + } + + pub(crate) unsafe fn from_slice(slice: &mut [u8]) -> Self { + Self { + start: slice.as_mut_ptr(), + length: slice.len(), + } + } +} diff --git a/src/containers/char_grid.rs b/src/containers/char_grid.rs new file mode 100644 index 0000000..8946b50 --- /dev/null +++ b/src/containers/char_grid.rs @@ -0,0 +1,84 @@ +use crate::{containers::ByteSlice, macros::wrap}; +use servicepoint::{CharGrid, CharGridCommand, Grid, Origin, Packet}; +use std::ptr::NonNull; + +wrap! { + CharGrid { + derives: crate::containers::derive_container, crate::containers::derive_get_width_height; + functions: + /// 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); + /// ``` + fn new(width: val usize, height: val usize) -> move NonNull { + CharGrid::new(width, height) + }; + + /// Loads a [CharGrid] with the specified dimensions from the provided data. + /// + /// returns: new CharGrid or NULL in case of an error + fn load(width: val usize, height: val usize, data: slice ByteSlice) -> move_ok *mut CharGrid { + CharGrid::load_utf8(width, height, data.to_vec()) + }; + methods: + /// Returns the current value at the specified position. + /// + /// # Arguments + /// + /// - `x` and `y`: position of the cell to read + /// + /// # Panics + /// + /// - when accessing `x` or `y` out of bounds + fn get(ref instance, x: val usize, y: val usize) -> val u32 { + instance.get(x, y) as u32 + }; + + /// Sets the value of the specified position in the grid. + /// + /// # Arguments + /// + /// - `x` and `y`: position of the cell + /// - `value`: the value to write to the cell + /// + /// returns: old value of the cell + /// + /// # Panics + /// + /// - when accessing `x` or `y` out of bounds + /// - when providing values that cannot be converted to Rust's `char`. + fn set(mut instance, x: val usize, y: val usize, value: val u32) { + instance.set(x, y, char::from_u32(value).unwrap()) + }; + + /// Sets the value of all cells in the grid. + /// + /// # Arguments + /// + /// - `value`: the value to set all cells to + /// - when providing values that cannot be converted to Rust's `char`. + fn fill(mut instance, value: val u32) { + instance.fill(char::from_u32(value).unwrap()) + }; + + /// Creates a [CharGridCommand] and immediately turns that into a [Packet]. + /// + /// The provided [CharGrid] gets consumed. + /// + /// Returns NULL in case of an error. + fn try_into_packet(move grid, x: val usize, y: val usize) -> move_ok *mut Packet { + Packet::try_from(CharGridCommand { + grid, + origin: Origin::new(x, y), + }) + }; + } +} diff --git a/src/containers/cp437_grid.rs b/src/containers/cp437_grid.rs new file mode 100644 index 0000000..b67d1ae --- /dev/null +++ b/src/containers/cp437_grid.rs @@ -0,0 +1,41 @@ +use crate::{containers::ByteSlice, macros::wrap}; +use servicepoint::{ + Cp437Grid, Cp437GridCommand, DataRef, Grid, Origin, Packet, +}; +use std::ptr::NonNull; + +wrap! { + Cp437Grid { + derives: crate::containers::derive_container, crate::containers::derive_grid[u8]; + functions: + /// Creates a new [Cp437Grid] with the specified dimensions. + /// + /// returns: [Cp437Grid] initialized to 0. + fn new(width: val usize, height: val usize) -> move NonNull { + Cp437Grid::new(width, height) + }; + + /// Loads a [Cp437Grid] with the specified dimensions from the provided data. + fn load(width: val usize, height: val usize, data: slice ByteSlice) -> move_some *mut Cp437Grid { + Cp437Grid::load(width, height, data) + }; + + methods: + /// Creates a [Cp437GridCommand] and immediately turns that into a [Packet]. + /// + /// The provided [Cp437Grid] gets consumed. + /// + /// Returns NULL in case of an error. + fn try_into_packet(move grid, x: val usize, y: val usize) -> move_ok *mut Packet { + Packet::try_from(Cp437GridCommand { + grid, + origin: Origin::new(x, y), + }) + }; + + /// Gets an unsafe reference to the data of the grid. + /// + /// The returned memory is valid for the lifetime of the instance. + fn data_ref_mut(mut instance) -> slice ByteSlice; + } +} diff --git a/src/containers/mod.rs b/src/containers/mod.rs new file mode 100644 index 0000000..5e0b48d --- /dev/null +++ b/src/containers/mod.rs @@ -0,0 +1,77 @@ +mod bitmap; +mod bitvec; +mod brightness_grid; +mod byte_slice; +mod char_grid; +mod cp437_grid; + +pub use bitmap::*; +pub use bitvec::*; +pub use brightness_grid::*; +pub use byte_slice::*; +pub use char_grid::*; +pub use cp437_grid::*; + +macro_rules! derive_container { + ($object_type:ident) => { + $crate::macros::derive_clone!($object_type); + $crate::macros::derive_free!($object_type); + }; +} + +macro_rules! derive_get_width_height { + ($object_type:ident) => { + $crate::macros::wrap_methods! {$object_type; + /// Gets the width. + fn width(ref instance) -> val usize; + + /// Gets the height. + fn height(ref instance) -> val usize; + } + }; +} + +macro_rules! derive_grid { + ($object_type:ident, $value_type:ident) => { + $crate::containers::derive_get_width_height!($object_type); + $crate::macros::wrap_methods! {$object_type; + /// Gets the current value at the specified position. + /// + /// # Arguments + /// + /// - `x` and `y`: position of the cell to read + /// + /// # Panics + /// + /// - when accessing `x` or `y` out of bounds + fn get(ref instance, x: val usize, y: val usize) -> val $value_type; + + /// Sets the value of the specified position. + /// + /// # Arguments + /// + /// - `x` and `y`: position of the cell + /// - `value`: the value to write to the cell + /// + /// # Panics + /// + /// - when accessing `x` or `y` out of bounds + fn set(mut instance, x: val usize, y: val usize, value: val $value_type); + + /// Sets the state of all cells in the grid. + /// + /// # Arguments + /// + /// - `value`: the value to set all cells to + fn fill(mut instance, value: val $value_type); + } + }; +} + +pub(crate) use {derive_container, derive_get_width_height, derive_grid}; + +mod _hidden { + /// This is a type only used by cbindgen to have a type for pointers. + #[allow(unused)] + pub struct DisplayBitVec; +} diff --git a/src/cp437_grid.rs b/src/cp437_grid.rs deleted file mode 100644 index ff70d2b..0000000 --- a/src/cp437_grid.rs +++ /dev/null @@ -1,157 +0,0 @@ -use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; -use servicepoint::{ - Cp437Grid, Cp437GridCommand, DataRef, Grid, Origin, Packet, -}; -use std::ptr::NonNull; - -/// Creates a new [Cp437Grid] with the specified dimensions. -/// -/// returns: [Cp437Grid] initialized to 0. -#[no_mangle] -pub unsafe extern "C" fn sp_cp437_grid_new( - width: usize, - height: usize, -) -> NonNull { - heap_move_nonnull(Cp437Grid::new(width, height)) -} - -/// 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: 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 [Cp437Grid]. -#[no_mangle] -pub unsafe extern "C" fn sp_cp437_grid_clone( - cp437_grid: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { cp437_grid.as_ref().clone() }) -} - -/// Deallocates a [Cp437Grid]. -#[no_mangle] -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. -/// -/// # Arguments -/// -/// - `cp437_grid`: instance to read from -/// - `x` and `y`: position of the cell to read -/// -/// # Panics -/// -/// - when accessing `x` or `y` out of bounds -#[no_mangle] -pub unsafe extern "C" fn sp_cp437_grid_get( - cp437_grid: NonNull, - x: usize, - y: usize, -) -> u8 { - unsafe { cp437_grid.as_ref().get(x, y) } -} - -/// Sets the value of the specified position in the [Cp437Grid]. -/// -/// # Arguments -/// -/// - `cp437_grid`: instance to write to -/// - `x` and `y`: position of the cell -/// - `value`: the value to write to the cell -/// -/// returns: old value of the cell -/// -/// # Panics -/// -/// - when accessing `x` or `y` out of bounds -#[no_mangle] -pub unsafe extern "C" fn sp_cp437_grid_set( - cp437_grid: NonNull, - x: usize, - y: usize, - value: u8, -) { - unsafe { (*cp437_grid.as_ptr()).set(x, y, value) }; -} - -/// Sets the value of all cells in the [Cp437Grid]. -/// -/// # Arguments -/// -/// - `cp437_grid`: instance to write to -/// - `value`: the value to set all cells to -#[no_mangle] -pub unsafe extern "C" fn sp_cp437_grid_fill( - cp437_grid: NonNull, - value: u8, -) { - unsafe { (*cp437_grid.as_ptr()).fill(value) }; -} - -/// Gets the width of the [Cp437Grid] instance. -/// -/// # Arguments -/// -/// - `cp437_grid`: instance to read from -#[no_mangle] -pub unsafe extern "C" fn sp_cp437_grid_width( - cp437_grid: NonNull, -) -> usize { - unsafe { cp437_grid.as_ref().width() } -} - -/// Gets the height of the [Cp437Grid] instance. -/// -/// # Arguments -/// -/// - `cp437_grid`: instance to read from -#[no_mangle] -pub unsafe extern "C" fn sp_cp437_grid_height( - cp437_grid: NonNull, -) -> usize { - unsafe { cp437_grid.as_ref().height() } -} - -/// Gets an unsafe reference to the data of the [Cp437Grid] instance. -/// -/// 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: 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 60c42ab..f884a83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,14 +2,12 @@ //! //! # Examples //! -//! Make sure to check out [this GitHub repo](https://github.com/arfst23/ServicePoint) as well! -//! //! ```C //! #include //! #include "servicepoint.h" //! //! int main(void) { -//! UdpConnection *connection = sp_udp_open("172.23.42.29:2342"); +//! UdpSocket *connection = sp_udp_open("172.23.42.29:2342"); //! if (connection == NULL) //! return 1; //! @@ -24,49 +22,32 @@ //! return 0; //! } //! ``` +//! +//! There are more examples in the source repository. -pub use crate::bitmap::*; -pub use crate::bitvec::*; -pub use crate::brightness_grid::*; -pub use crate::byte_slice::*; -pub use crate::char_grid::*; -pub use crate::cp437_grid::*; -pub use crate::packet::*; -pub use crate::typed_command::*; -pub use crate::udp::*; -pub use servicepoint::CommandCode; -use std::ptr::NonNull; - -mod bitmap; -mod bitvec; -mod brightness_grid; -mod byte_slice; -mod char_grid; -mod cp437_grid; -mod packet; -mod typed_command; -mod udp; - -use std::time::Duration; +/// Functions related to commands. +pub mod commands; +/// Functions related to [servicepoint::Bitmap], [servicepoint::CharGrid] and friends. +pub mod containers; +pub(crate) mod macros; +pub(crate) mod mem; +/// Functions related to [servicepoint::Packet]. +pub mod packet; +/// Functions related to [servicepoint::UdpSocketExt]. +pub mod udp; /// 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 const SP_FRAME_PACING_MS: u128 = 30; -pub(crate) fn heap_move(x: T) -> *mut T { - Box::into_raw(Box::new(x)) +#[cfg(feature = "env_logger")] +mod feature_env_logger { + use crate::macros::wrap_functions; + + wrap_functions!(envlogger; + /// Call this function at the beginning of main to enable rust logging controlled by the + /// `RUST_LOG` environment variable. See [env_logger](https://docs.rs/env_logger/latest/env_logger/). + fn init() { + env_logger::init(); + }; + ); } - -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()) } -} - -/// This is a type only used by cbindgen to have a type for pointers. -pub struct UdpSocket; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..89d3369 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,326 @@ +macro_rules! derive_free { + ($object_type:ident) => { + $crate::macros::wrap_method!($object_type; + #[doc = concat!("Deallocates a [`", stringify!($object_type), "`] instance.")] + #[allow(dropping_copy_types)] + fn free(move instance) { + ::std::mem::drop(instance) + }; + ); + }; +} + +macro_rules! derive_clone { + ($object_type:ident) => { + $crate::macros::wrap_method!($object_type; + #[doc = concat!("Clones a [`", stringify!($object_type), "`] instance.")] + fn clone(ref instance) -> move ::core::ptr::NonNull<$object_type>; + ); + }; +} + +macro_rules! wrap_method { + ( + $object_type:ident; + $(#[$meta:meta])+ + fn $function:ident($ref_or_mut:ident $instance:ident $(, $($param_name:ident: $param_modifier:ident $param_type:ty),*)?) + $(-> $return_modifier:ident $return_type:ty)?; + ) => { + ::paste::paste!{ + $crate::macros::wrap_method!( + $object_type; + #[doc = " Calls method [`" $object_type "::" $function "`]."] + /// + $(#[$meta])+ + fn $function($ref_or_mut $instance $(, $($param_name: $param_modifier $param_type),*)?) + $(-> $return_modifier $return_type)? { + $instance.$function($($($param_name),*)?) + }; + ); + } + }; + ($object_type:ident; + $(#[$meta:meta])+ + fn $function:ident($ref_or_mut:ident $instance:ident $(, $($param_name:ident: $param_modifier:ident $param_type:ty),*)?) + $(-> $return_modifier:ident $return_type:ty)? + $impl:block; + ) => { + paste::paste! { + $crate::macros::wrap_functions!(associate $object_type; + $(#[$meta])* + fn $function( + $instance: $ref_or_mut ::core::ptr::NonNull<$object_type> + $(,$($param_name: $param_modifier $param_type),*)? + ) $(-> $return_modifier $return_type)? + $impl; + ); + } + }; +} + +macro_rules! wrap_methods { + ( + $object_type:ident; + $( + $(#[$meta:meta])+ + fn $function:ident($ref_or_mut:ident $instance:ident $(, $($param_name:ident: $param_modifier:ident $param_type:ty),*)?) + $(-> $return_modifier:ident $return_type:ty)? + $($impl:block)?; + )+ + ) => { + paste::paste! { + $( + $crate::macros::wrap_method!($object_type; + $(#[$meta])* + fn $function($ref_or_mut $instance $(, $($param_name: $param_modifier $param_type),*)?) + $(-> $return_modifier $return_type)? + $($impl)?; + ); + )+ + } + }; +} + +macro_rules! wrap_fields_accessor { + (get; $object_type:ident :: $prop_name:ident : $prop_type:ty) => { + paste::paste! { + $crate::macros::wrap_method! {$object_type; + #[doc = " Gets the value of field `" $prop_name + "` of the [`servicepoint::" $object_type "`]."] + fn [](ref instance) -> val $prop_type { + instance.$prop_name + }; + } + } + }; + (mut get; $object_type:ident :: $prop_name:ident : $prop_type:ty) => { + paste::paste! { + $crate::macros::wrap_method! {$object_type; + #[doc = " Gets a reference to the field `" $prop_name + "` of the [`servicepoint::" $object_type "`]."] + /// + /// - The returned reference inherits the lifetime of object in which it is contained. + /// - The returned pointer may not be used in a function that consumes the instance, e.g. to create a command. + fn [](mut instance) -> val ::core::ptr::NonNull<$prop_type> { + ::core::ptr::NonNull::from(&mut instance.$prop_name) + }; + } + } + }; + (set; $object_type:ident :: $prop_name:ident : $prop_type:ty) => { + paste::paste! { + $crate::macros::wrap_method! {$object_type; + #[doc = " Sets the value of field `" $prop_name + "` of the [`servicepoint::" $object_type "`]."] + fn [](mut instance, value: val $prop_type) { + instance.$prop_name = value; + }; + } + } + }; + (move set; $object_type:ident :: $prop_name:ident : $prop_type:ty) => { + paste::paste! { + $crate::macros::wrap_method! {$object_type; + #[doc = " Sets the value of field `" $prop_name + "` of the [`servicepoint::" $object_type "`]."] + /// The provided value is moved into the instance, potentially invalidating previously taken references. + fn [](mut instance, value: move ::core::ptr::NonNull<$prop_type>) { + instance.$prop_name = value; + }; + } + } + }; +} + +macro_rules! wrap_fields { + ( + $object_type:ident; + $( + prop $prop_name:ident : $prop_type:ty { $($accessor:ident $($modifier:ident)?;)+ }; + )+ + ) => { + $($( + ::paste::paste!{ + $crate::macros::wrap_fields_accessor! { + $($modifier)? $accessor; + $object_type :: $prop_name: $prop_type + } + } + )+)+ + }; +} + +macro_rules! apply_param_modifier { + (move, $param_name:ident) => { + unsafe { $crate::mem::heap_remove($param_name) } + }; + (val, $param_name:ident) => { + $param_name + }; + (mut, $param_name:ident) => { + unsafe { (&mut *$param_name.as_ptr()) } + }; + (ref, $param_name:ident) => { + unsafe { $param_name.as_ref() } + }; + (slice, $param_name:ident) => { + unsafe { $param_name.as_slice() } + }; +} + +macro_rules! apply_return_modifier { + (val, $result:ident) => { + $result + }; + (move, $result:ident) => { + $crate::mem::heap_move_nonnull($result) + }; + (move_ok, $result:ident) => { + $crate::mem::heap_move_ok($result) + }; + (move_some, $result:ident) => { + $crate::mem::heap_move_some($result) + }; + (slice, $result:ident) => { + unsafe { ByteSlice::from_slice($result) } + }; +} + +macro_rules! wrap_function { + ( + $module:ident; + $(#[$meta:meta])+ + fn $function:ident($($param_name:ident: $param_modifier:ident $param_type:ty),*$(,)?) + $(-> $return_modifier:ident $return_type:ty)? + $block:block; + ) => { + ::paste::paste! { + $(#[$meta])+ + #[doc = ""] + #[doc = " This function is part of the `" $module "` module."] + #[no_mangle] + pub unsafe extern "C" fn [< sp_ $module _ $function >]( + $($param_name: $param_type),* + ) $(-> $return_type)? + { + $( + let $param_name = $crate::macros::apply_param_modifier!($param_modifier, $param_name); + )* + let result = $block; + $( + let result = $crate::macros::apply_return_modifier!($return_modifier, result); + )? + result + } + } + }; +} + +macro_rules! wrap_functions { + ( + $module:ident; + $( + $(#[$meta:meta])+ + fn $function:ident($($param_name:ident: $param_modifier:ident $param_type:ty),*$(,)?) + $(-> $return_modifier:ident $return_type:ty)? + $block:block; + )+ + ) => { + ::paste::paste! { + $( + $crate::macros::wrap_function!($module; + $(#[$meta])+ + fn $function($($param_name: $param_modifier $param_type),*) $(-> $return_modifier $return_type)? + $block; + ); + )+ + } + }; + ( + associate $object_type:ident; + $( + $(#[$meta:meta])+ + fn $function:ident($($param_name:ident: $param_modifier:ident $param_type:ty),*$(,)?) + $(-> $return_modifier:ident $return_type:ty)? + $block:block; + )+ + ) => { + ::paste::paste! { + $crate::macros::wrap_functions!{[< $object_type:snake >]; + $( + $(#[$meta])+ + fn $function($($param_name: $param_modifier $param_type),*) $(-> $return_modifier $return_type)? + $block; + )+ + } + } + }; +} + +macro_rules! wrap { + ( + $object_type:ident { + $( + derives: + $( + $derive:path $( [ $( $derive_arg:tt ),+ ] )? + ),+; + )? + $( + properties: + $( + prop $prop_name:ident : $prop_type:ty { $($accessor:ident $($modifier:ident)?;)+ }; + )* + )? + $( + functions: + $( + $(#[$fn_meta:meta])+ + fn $fn_name:ident($($fn_param_name:ident: $fn_param_modifier:ident $fn_param_type:ty),*$(,)?) + $(-> $fn_return_modifier:ident $fn_return_type:ty)? + $fn_block:block; + )* + )? + $( + methods: + $( + $(#[$method_meta:meta])+ + fn $method_name:ident($method_instance_modifier:ident $method_instance:ident $(, $($method_param_name:ident: $method_param_modifier:ident $method_param_type:ty),*)?) + $(-> $method_return_modifier:ident $method_return_type:ty)? + $($method_impl:block)?; + )* + )? + } + ) => { + $($( + $derive!($object_type $(, $($derive_arg),+)?); + )+)? + $($( + $crate::macros::wrap_fields!($object_type; + prop $prop_name : $prop_type { $($accessor $($modifier)?;)+ }; + ); + )*)? + $($( + $crate::macros::wrap_functions!(associate $object_type; + $(#[$fn_meta])+ + fn $fn_name($($fn_param_name: $fn_param_modifier $fn_param_type),*) + $(-> $fn_return_modifier $fn_return_type)? + $fn_block; + ); + )*)? + $($( + $crate::macros::wrap_method!($object_type; + $(#[$method_meta])+ + fn $method_name($method_instance_modifier $method_instance $(, $($method_param_name: $method_param_modifier $method_param_type),*)?) + $(-> $method_return_modifier $method_return_type)? + $($method_impl)?; + ); + )*)? + }; +} + +pub(crate) use { + apply_param_modifier, apply_return_modifier, derive_clone, derive_free, + wrap, wrap_fields, wrap_fields_accessor, wrap_function, wrap_functions, + wrap_method, wrap_methods, +}; diff --git a/src/mem.rs b/src/mem.rs new file mode 100644 index 0000000..6555999 --- /dev/null +++ b/src/mem.rs @@ -0,0 +1,29 @@ +use std::ptr::NonNull; + +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) fn heap_move_ok(x: Result) -> *mut T { + x.map(|x| heap_move(x)).unwrap_or(std::ptr::null_mut()) +} + +pub(crate) fn heap_move_some(x: Option) -> *mut T { + x.map(|x| heap_move(x)).unwrap_or(std::ptr::null_mut()) +} + +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()) } +} + +pub(crate) unsafe fn heap_clone(source: NonNull) -> NonNull { + heap_move_nonnull(unsafe { source.as_ref().clone() }) +} diff --git a/src/packet.rs b/src/packet.rs index d8b1579..00a34a9 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,128 +1,85 @@ -use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice}; -use servicepoint::{CommandCode, Header, Packet, TypedCommand}; +use crate::{ + containers::ByteSlice, + macros::{derive_clone, derive_free, wrap, wrap_functions}, +}; +use servicepoint::{CommandCode, Header, Packet}; use std::ptr::NonNull; -/// Turns a [TypedCommand] into a [Packet]. -/// The [TypedCommand] gets consumed. -/// -/// Returns NULL in case of an error. -#[no_mangle] -pub unsafe extern "C" fn sp_packet_from_command( - 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() - } -} +derive_clone!(Packet); +derive_free!(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 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), - } -} +wrap! { + Packet { + properties: + prop header: Header { get; get mut; set; }; + functions: + /// 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 + fn try_load(data: slice ByteSlice) -> move_ok *mut Packet { + servicepoint::Packet::try_from(data) + }; -/// Creates a raw [Packet] from parts. -/// -/// returns: new instance. Will never return null. -#[no_mangle] -pub unsafe extern "C" fn sp_packet_from_parts( - header: Header, - payload: *const ByteSlice, -) -> NonNull { - let payload = if payload.is_null() { - vec![] - } else { - let payload = unsafe { (*payload).as_slice() }; - Vec::from(payload) - }; + /// Creates a raw [Packet] from parts. + /// + /// returns: new instance. Will never return null. + fn from_parts(header: val Header, payload: val ByteSlice) -> move NonNull { + let payload = if payload == ByteSlice::INVALID { + None + } else { + Some(Vec::from(unsafe { payload.as_slice() })) + }; - heap_move_nonnull(Packet { header, payload }) -} - -/// Returns a pointer to the header field of the provided packet. -/// -/// 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 -/// -/// - 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: NonNull, -) -> NonNull { - heap_move_nonnull(unsafe { packet.as_ref().clone() }) -} - -/// Deallocates a [Packet]. -#[no_mangle] -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; + Packet { header, payload } + }; + methods: + /// Returns a pointer to the current payload of the provided packet. + /// + /// Returns an [ByteSlice::INVALID] instance in case the packet does not have any payload. + /// + /// The returned memory can be changed and will be valid until a new payload is set. + fn get_payload(mut packet) -> val ByteSlice { + match &mut packet.payload { + None => ByteSlice::INVALID, + Some(payload) => unsafe { ByteSlice::from_slice(payload) }, } - true - } - Err(_) => false, + }; + + /// Sets the payload of the provided packet to the provided data. + /// + /// This makes previous payload pointers invalid. + fn set_payload(mut packet, data: val ByteSlice) { + packet.payload = if data == ByteSlice::INVALID { + None + } else { + Some(unsafe { data.as_slice().to_vec() }) + } + }; + + /// Serialize the packet into the provided buffer. + /// + /// # Panics + /// + /// - if the buffer is not big enough to hold header+payload. + fn serialize_to(mut packet, buffer: val ByteSlice) -> val usize { + unsafe { + packet.serialize_to(buffer.as_slice_mut()).unwrap_or(0) + } + }; } } + +wrap_functions!(sp; + /// Converts u16 into [CommandCode]. + /// + /// If the provided value is not valid, false is returned and result is not changed. + fn u16_to_command_code(code: val u16, result: mut NonNull) -> val bool { + match CommandCode::try_from(code) { + Ok(code) => { + *result = code; + true + } + Err(_) => false, + } + }; +); diff --git a/src/typed_command.rs b/src/typed_command.rs deleted file mode 100644 index 9e27540..0000000 --- a/src/typed_command.rs +++ /dev/null @@ -1,201 +0,0 @@ -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/udp.rs b/src/udp.rs index f68aa16..22a7f7c 100644 --- a/src/udp.rs +++ b/src/udp.rs @@ -1,119 +1,117 @@ -use crate::{heap_drop, heap_move, heap_remove}; -use servicepoint::{Header, Packet, TypedCommand, UdpSocketExt}; -use std::ffi::{c_char, CStr}; -use std::net::{Ipv4Addr, SocketAddrV4, UdpSocket}; -use std::ptr::NonNull; +use crate::{ + commands::{CommandTag, GenericCommand}, + macros::{derive_free, wrap}, + mem::heap_remove, +}; +use servicepoint::{Header, Packet, UdpSocketExt}; +use std::{ + ffi::{c_char, CStr}, + net::{Ipv4Addr, SocketAddrV4, UdpSocket}, + ptr::NonNull, +}; -/// 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()); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn sp_udp_open(host: NonNull) -> *mut UdpSocket { - let host = unsafe { CStr::from_ptr(host.as_ptr()) } - .to_str() - .expect("Bad encoding"); - let connection = match UdpSocket::bind_connect(host) { - Err(_) => return std::ptr::null_mut(), - Ok(value) => value, - }; +derive_free!(UdpSocket); - heap_move(connection) +wrap! { + UdpSocket { + functions: + /// Creates a new instance of [UdpSocket]. + /// + /// returns: NULL if connection fails, or connected instance + /// + /// # Examples + /// + /// ```C + /// UdpSocket connection = sp_udp_open("172.23.42.29:2342"); + /// if (connection != NULL) + /// sp_udp_send_command(connection, sp_command_clear()); + /// ``` + fn open(host: val NonNull) -> move_ok *mut UdpSocket { + let host = unsafe { CStr::from_ptr(host.as_ptr()) } + .to_str() + .expect("Bad encoding"); + UdpSocket::bind_connect(host) + }; + + /// Creates a new instance of [UdpSocket]. + /// + /// returns: NULL if connection fails, or connected instance + /// + /// # Examples + /// + /// ```C + /// UdpSocket connection = sp_udp_open_ipv4(172, 23, 42, 29, 2342); + /// if (connection != NULL) + /// sp_udp_send_command(connection, sp_command_clear()); + /// ``` + fn open_ipv4(ip1: val u8, ip2: val u8, ip3: val u8, ip4: val u8, port: val u16) -> move_ok *mut UdpSocket { + let addr = SocketAddrV4::new(Ipv4Addr::from([ip1, ip2, ip3, ip4]), port); + UdpSocket::bind_connect(addr) + }; + methods: + /// Sends a [Packet] to the display using the [UdpSocket]. + /// + /// The passed `packet` gets consumed. + /// + /// returns: true in case of success + fn send_packet(ref connection, packet: move NonNull) -> val bool { + connection.send(&Vec::from(packet)).is_ok() + }; + + /// Sends a [GenericCommand] to the display using the [UdpSocket]. + /// + /// The passed `command` gets consumed. + /// + /// returns: true in case of success + /// + /// # Examples + /// + /// ```C + /// sp_udp_send_command(connection, sp_command_brightness(5)); + /// ``` + fn send_command(ref connection, command: mut NonNull) -> val bool { + unsafe { + let result = match command.tag { + CommandTag::Invalid => return false, + CommandTag::Bitmap => connection.send_command(heap_remove(command.data.bitmap)), + CommandTag::BitVec => connection.send_command(heap_remove(command.data.bit_vec)), + CommandTag::BrightnessGrid => connection.send_command(heap_remove(command.data.brightness_grid)), + CommandTag::CharGrid => connection.send_command(heap_remove(command.data.char_grid)), + CommandTag::Cp437Grid => connection.send_command(heap_remove(command.data.cp437_grid)), + CommandTag::GlobalBrightness => connection.send_command(heap_remove(command.data.global_brightness)), + CommandTag::Clear => connection.send_command(heap_remove(command.data.clear)), + CommandTag::HardReset => connection.send_command(heap_remove(command.data.hard_reset)), + CommandTag::FadeOut => connection.send_command(heap_remove(command.data.fade_out)), + CommandTag::BitmapLegacy => connection.send_command(heap_remove(command.data.bitmap_legacy)), + }.is_some(); + *command = GenericCommand::INVALID; + result + } + }; + + /// Sends a [Header] to the display using the [UdpSocket]. + /// + /// returns: true in case of success + /// + /// # Examples + /// + /// ```C + /// sp_udp_send_header(connection, sp_command_brightness(5)); + /// ``` + fn send_header(ref udp_connection, header: val Header) -> val bool { + let packet = Packet { + header, + payload: None, + }; + udp_connection.send(&Vec::from(packet)).is_ok() + }; + } } -/// 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()); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn sp_udp_open_ipv4( - ip1: u8, - ip2: u8, - ip3: u8, - ip4: u8, - port: u16, -) -> *mut UdpSocket { - let addr = SocketAddrV4::new(Ipv4Addr::from([ip1, ip2, ip3, ip4]), port); - let connection = match UdpSocket::bind_connect(addr) { - Err(_) => return std::ptr::null_mut(), - Ok(value) => value, - }; - heap_move(connection) -} - -/// Sends a [Packet] to the display using the [UdpConnection]. -/// -/// The passed `packet` gets consumed. -/// -/// returns: true in case of success -#[no_mangle] -pub unsafe extern "C" fn sp_udp_send_packet( - connection: NonNull, - packet: NonNull, -) -> bool { - let packet = unsafe { heap_remove(packet) }; - unsafe { connection.as_ref().send(&Vec::from(packet)) }.is_ok() -} - -/// 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)); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn sp_udp_send_command( - connection: NonNull, - command: NonNull, -) -> bool { - let command = unsafe { heap_remove(command) }; - unsafe { connection.as_ref().send_command(command) }.is_some() -} - -/// 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)); -/// ``` -#[no_mangle] -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(&Vec::from(packet)) - .is_ok() -} - -/// Closes and deallocates a [UdpConnection]. -#[no_mangle] -pub unsafe extern "C" fn sp_udp_free(connection: NonNull) { - unsafe { heap_drop(connection) } +mod _hidden { + /// This is a type only used by cbindgen to have a type for pointers. + /// + /// See [servicepoint::UdpSocketExt]. + #[allow(unused)] + pub struct UdpSocket; }