Compare commits

...

22 commits

Author SHA1 Message Date
Vinzenz Schroeter 70cf6116cb build all variations in CI to keep track of what works
Some checks failed
Rust / build-gnu-apt (pull_request) Successful in 4m15s
Rust / build-musl-beta (pull_request) Failing after 51s
Rust / build-size-gnu-unstable (pull_request) Successful in 1m13s
Rust / build-size-musl-unstable (pull_request) Failing after 48s
2025-05-04 11:32:18 +02:00
Vinzenz Schroeter 388f3289b3 a bunch of options for the Makefile 2025-05-04 11:32:18 +02:00
Vinzenz Schroeter a1d40e3049 update to released servicepoint version 2025-05-04 11:32:18 +02:00
Vinzenz Schroeter 7859a61443 features, cargo fmt 2025-05-04 11:32:18 +02:00
Vinzenz Schroeter 1b007e59fb add functions to convert containers directly into packet 2025-05-04 11:32:18 +02:00
Vinzenz Schroeter 3f0075493d heap_remove, sp_brightness_grid_into_packet 2025-05-04 11:32:17 +02:00
Vinzenz Schroeter 709468c835 add command code constants, send header 2025-05-04 11:32:17 +02:00
Vinzenz Schroeter 372cb450f2 translate announce and brightness tester examples 2025-05-04 11:32:17 +02:00
Vinzenz Schroeter a418e4f0c3 sp_brightness_grid_load ignore out of range
Some checks failed
Rust / build (pull_request) Failing after 30s
2025-04-12 18:49:12 +02:00
Vinzenz Schroeter 2e468fb47a add sp_bitmap_from_bitvec 2025-04-12 18:41:43 +02:00
Vinzenz Schroeter c797bb6894 add sp_bitmap_into_bitvec 2025-04-12 18:35:48 +02:00
Vinzenz Schroeter f483b5a2d5 unify heap allocation handling
Some checks failed
Rust / build (pull_request) Failing after 1m25s
2025-04-12 18:05:01 +02:00
Vinzenz Schroeter d6331ea9a7 rename/merge functions to match rust side more 2025-04-12 17:38:19 +02:00
Vinzenz Schroeter 2d3828fb2b add fn to pass ip:port as values 2025-04-12 16:49:55 +02:00
Vinzenz Schroeter 613f21310e fix rebase
Some checks failed
Rust / build (pull_request) Failing after 1m23s
2025-04-12 16:33:39 +02:00
Vinzenz Schroeter 7c8ede61b0 move generic safety information into README 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 0c3bc0b004 wip fix documentation 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter cf2c72de7c use brightness type from base crate 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 30bec50f7c use NotNull for parameters 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter b443adc3ab add /*notnull*/ comments in header 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 24ff341f9d re-export constants from base lib 2025-04-12 16:25:20 +02:00
Vinzenz Schroeter 35cb42df48 wip remove newtypes 2025-04-12 16:25:20 +02:00
27 changed files with 1798 additions and 3245 deletions

View file

@ -13,32 +13,77 @@ env:
RUSTFLAGS: "-Dwarnings"
jobs:
build:
build-gnu-apt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update repos
run: sudo apt-get update -qq
- name: Install rust toolchain
run: sudo apt-get install -qy cargo rust-clippy
- name: install lzma
run: sudo apt-get install -qy liblzma-dev
- name: install gcc
run: sudo apt-get install -qy gcc make
- name: Install toolchain
run: sudo apt-get install -qy cargo rust-clippy liblzma-dev gcc make pkgconf
- name: Run Clippy
run: cargo clippy
- 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
run: cargo build
- name: build example -- glibc release
run: cd example && make clean && make TARGET=aarch64-unknown-linux-gnu PROFILE=release
- name: build example -- glibc debug
run: cd example && make clean && make TARGET=aarch64-unknown-linux-gnu PROFILE=debug
- name: build example
run: cd example && make
# this _should_ have been -stable, but there is a bug when running in the container ("Invalid cross-device link")
build-musl-beta:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update repos
run: sudo apt-get update -qq
- name: Install toolchain
run: sudo apt-get install -qy liblzma-dev gcc make pkgconf musl-dev musl-tools rustup
- name: install rust target
run: rustup default beta && rustup target add aarch64-unknown-linux-musl && rustup component add rust-src && rustup update
- name: build example -- musl release
run: cd example && make clean && make TARGET=aarch64-unknown-linux-musl PROFILE=release MUSL=1 CARGO="rustup run beta cargo"
- name: build example -- musl debug
run: cd example && make clean && make TARGET=aarch64-unknown-linux-musl PROFILE=debug MUSL=1 CARGO="rustup run beta cargo"
build-size-gnu-unstable:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update repos
run: sudo apt-get update -qq
- name: Install toolchain
run: sudo apt-get install -qy liblzma-dev gcc make pkgconf rustup
- 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"
build-size-musl-unstable:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update repos
run: sudo apt-get update -qq
- name: Install toolchain
run: sudo apt-get install -qy liblzma-dev gcc make pkgconf musl-dev musl-tools rustup
- name: install rust targets
run: rustup toolchain install nightly -t aarch64-unknown-linux-musl -c rust-src --no-self-update
- name: build example -- musl size-optimized
run: cd example && make clean && make TARGET=aarch64-unknown-linux-musl PROFILE=size-optimized MUSL=1 LTO=1 CARGO="rustup run nightly cargo"

202
Cargo.lock generated
View file

@ -49,19 +49,20 @@ dependencies = [
[[package]]
name = "anstyle-wincon"
version = "3.0.6"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys",
]
[[package]]
name = "bitflags"
version = "2.7.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "bitvec"
@ -77,22 +78,20 @@ dependencies = [
[[package]]
name = "bzip2"
version = "0.5.0"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bafdbf26611df8c14810e268ddceda071c297570a5fb360ceddf617fe417ef58"
checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
version = "0.1.13+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
dependencies = [
"cc",
"libc",
"pkg-config",
]
@ -117,9 +116,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.9"
version = "1.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b"
checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0"
dependencies = [
"jobserver",
"libc",
@ -134,18 +133,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.26"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.26"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
dependencies = [
"anstream",
"anstyle",
@ -176,15 +175,15 @@ dependencies = [
[[package]]
name = "equivalent"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.10"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys",
@ -198,9 +197,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "flate2"
version = "1.0.35"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -214,20 +213,21 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "getrandom"
version = "0.2.15"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
[[package]]
name = "heck"
@ -237,9 +237,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "indexmap"
version = "2.7.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown",
@ -253,36 +253,37 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.14"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jobserver"
version = "0.1.32"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
dependencies = [
"getrandom",
"libc",
]
[[package]]
name = "libc"
version = "0.2.169"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "log"
version = "0.4.22"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
@ -292,43 +293,49 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.2"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
]
[[package]]
name = "once_cell"
version = "1.20.2"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "pkg-config"
version = "0.3.31"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro2"
version = "1.0.93"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "radium"
version = "0.7.0"
@ -347,9 +354,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.43"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags",
"errno",
@ -360,24 +367,24 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.18"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.217"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
@ -386,9 +393,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.135"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
@ -407,8 +414,9 @@ dependencies = [
[[package]]
name = "servicepoint"
version = "0.13.2"
source = "git+https://git.berlin.ccc.de/servicepoint/servicepoint/?branch=next#971bee5065139f220022e8108cfaa9c263b8a8a0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce70bae3641ccafdeb9832ac367efd51243e0708ef35151ad8c2c4ee578aa4a"
dependencies = [
"bitvec",
"bzip2",
@ -442,9 +450,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.96"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
@ -459,11 +467,10 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.15.0"
version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
dependencies = [
"cfg-if",
"fastrand",
"getrandom",
"once_cell",
@ -473,18 +480,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.11"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.11"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
@ -493,9 +500,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.19"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
dependencies = [
"serde",
"serde_spanned",
@ -505,31 +512,38 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.8"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.22"
version = "0.22.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
name = "toml_write"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
@ -545,9 +559,12 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "windows-sys"
@ -624,13 +641,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.24"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
[[package]]
name = "wyz"
version = "0.5.1"
@ -642,27 +668,27 @@ dependencies = [
[[package]]
name = "zstd"
version = "0.13.2"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.1"
version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.13+zstd.1.5.6"
version = "2.0.15+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
dependencies = [
"cc",
"pkg-config",

View file

@ -19,20 +19,20 @@ cbindgen = "0.28.0"
[dependencies.servicepoint]
package = "servicepoint"
version = "0.13.2"
version = "0.14.0"
default-features = false
features = ["protocol_udp"]
git = "https://git.berlin.ccc.de/servicepoint/servicepoint/"
branch = "next"
[features]
full = ["servicepoint/all_compressions", "servicepoint/default"]
default = ["full"]
all_compressions = ["servicepoint/all_compressions"]
default = ["all_compressions", "servicepoint/default"]
[lints.rust]
missing-docs = "warn"
unsafe_op_in_unsafe_fn = "warn"
[lints.clippy]
missing_safety_doc = "allow"
[package.metadata.docs.rs]
all-features = true

View file

@ -82,6 +82,21 @@ You have the choice of linking statically (recommended) or dynamically.
- documentation is included in the header and
available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/)
## Safety
Functions expect that C code honors NonNull annotations.
Any created instances have to be freed in some way.
Pointers to those instances cannot be used anymore after that.
Instances cannot be shared between threads and need to be locked in the using code.
Enum values have to be used as-is. Do not pass values that are not part of the enum.
UTF-8 or UTF-32 encoding has to be used properly.
Brightness values provided as u8 parameters must be in range.
## Everything else
Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for

View file

@ -10,7 +10,9 @@ 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 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";

View file

@ -24,7 +24,8 @@ sort_by = "Name"
[parse]
parse_deps = true
include = ["servicepoint"]
include = ["servicepoint", "std"]
extra_bindings = ["servicepoint"]
[parse.expand]
features = ["full"]
@ -34,7 +35,11 @@ include = []
exclude = []
[export.rename]
"TypedCommand" = "Command"
"SpBitVec" = "BitVec"
"SpByteSlice" = "ByteSlice"
[enum]
rename_variants = "QualifiedScreamingSnakeCase"
[ptr]
non_null_attribute = "/*notnull*/"

View file

@ -1,30 +1,130 @@
CC := gcc
CARGO ?= cargo
STRIP ?= strip
FEATURES := ""
THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
REPO_ROOT := $(THIS_DIR)/..
REPO_ROOT := $(realpath $(THIS_DIR)/..)
export SERVICEPOINT_HEADER_OUT := $(REPO_ROOT)/include
build: out/example
override CFG_MUSL := $(if $(CFG_MUSL),$(CFG_MUSL),$(if $(MUSL),$(MUSL),0))
override CFG_PROFILE := $(if $(CFG_PROFILE),$(CFG_PROFILE),$(if $(PROFILE),$(PROFILE),release))
clean:
rm -r out || true
rm include/servicepoint.h || true
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)
OUT_DIR := $(realpath $(THIS_DIR)/out/)
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
CARGOFLAGS += --manifest-path=$(REPO_ROOT)/Cargo.toml \
--profile=$(CARGO_PROFILE) \
--no-default-features \
--features=$(FEATURES) \
--target=$(TARGET)
ifneq ($(CFG_PROFILE), debug)
CCFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections
RUSTFLAGS += -C link-arg=-s -C link-arg=-Wl,--gc-sections
endif
ifeq ($(LTO), 1)
CCFLAGS += -flto
endif
_c_src := $(wildcard ./src/*.c)
_programs := $(basename $(notdir $(_c_src)))
_bins := $(addprefix out/, $(_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)
clean: clean-c clean-rust
clean-c:
rm -r $(OUT_DIR) || true
clean-rust:
rm $(SERVICEPOINT_HEADER_OUT)/servicepoint.h || true
cargo clean
run: out/example
out/example
.PHONY: all clean sizes $(_run_programs) clean-c clean-rust
PHONY: build clean dependencies run
out/example: dependencies main.c
$(_unstripped_bins): out/%_unstripped: src/%.c $(SERVICEPOINT_HEADER_OUT)/servicepoint.h $(_sp_artifacts)
mkdir -p out || true
${CC} main.c \
-I $(REPO_ROOT)/include \
-L $(REPO_ROOT)/target/release \
-Wl,-Bstatic -lservicepoint_binding_c \
-Wl,-Bdynamic -llzma \
-o out/example
${CC} $< \
-I $(SERVICEPOINT_HEADER_OUT) \
-L $(RUST_TARGET_DIR) \
$(CCFLAGS) \
-o $@
dependencies: FORCE
cargo build --manifest-path=$(REPO_ROOT)/Cargo.toml --release
$(_bins): out/%: out/%_unstripped
$(STRIP) $(STRIPFLAGS) $^ -o $@
$(_sp_artifacts): $(_rs_src)
mkdir -p $(SERVICEPOINT_HEADER_OUT) || true
# 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_DIR)/$(BIN)_unstripped \
| awk '{size=$$2+0; print size "\t" $$4}' \
| less
FORCE: ;

View file

@ -1,28 +0,0 @@
#include <stdio.h>
#include "servicepoint.h"
int main(void) {
UdpConnection *connection = sp_connection_open("localhost:2342");
if (connection == NULL)
return 1;
Bitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
if (pixels == NULL)
return 1;
sp_bitmap_fill(pixels, true);
Command *command = sp_command_bitmap_linear_win(0, 0, pixels, SP_COMPRESSION_CODE_UNCOMPRESSED);
if (command == NULL)
return 1;
Packet *packet = sp_packet_from_command(command);
Header *header = sp_packet_get_header(packet);
printf("[%d, %d, %d, %d, %d]\n", header->command_code, header->a, header->b, header->c, header->d);
sp_connection_send_packet(connection, packet);
sp_connection_free(connection);
return 0;
}

37
example/src/announce.c Normal file
View file

@ -0,0 +1,37 @@
#include <stdio.h>
#include "servicepoint.h"
int main(void) {
printf("test\n");
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});
CharGrid *grid = sp_char_grid_new(5, 2);
if (grid == NULL)
return 1;
sp_char_grid_set(grid, 0, 0, 'H');
sp_char_grid_set(grid, 1, 0, 'e');
sp_char_grid_set(grid, 2, 0, 'l');
sp_char_grid_set(grid, 3, 0, 'l');
sp_char_grid_set(grid, 4, 0, 'o');
sp_char_grid_set(grid, 0, 1, 'W');
sp_char_grid_set(grid, 1, 1, 'o');
sp_char_grid_set(grid, 2, 1, 'r');
sp_char_grid_set(grid, 3, 1, 'l');
sp_char_grid_set(grid, 4, 1, 'd');
Packet *packet = sp_char_grid_into_packet(grid, 0, 0);
if (packet == NULL)
return 1;
sp_udp_send_packet(connection, packet);
sp_udp_free(connection);
return 0;
}

View file

@ -0,0 +1,30 @@
#include "servicepoint.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;
Bitmap *all_on = sp_bitmap_new_max_sized();
sp_bitmap_fill(all_on, true);
Packet *packet = sp_bitmap_into_packet(all_on, 0, 0, COMPRESSION_CODE_UNCOMPRESSED);
if (packet == NULL)
return -1;
sp_udp_send_packet(connection, packet);
BrightnessGrid *grid = sp_brightness_grid_new(TILE_WIDTH, TILE_HEIGHT);
ByteSlice slice = sp_brightness_grid_unsafe_data_ref(grid);
for (size_t index = 0; index < slice.length; index++) {
slice.start[index] = (uint8_t) (index % ((size_t) Brightness_MAX));
}
packet = sp_brightness_grid_into_packet(grid, 0, 0);
sp_udp_send_packet(connection, packet);
sp_udp_free(connection);
return 0;
}

View file

@ -0,0 +1,25 @@
#include "servicepoint.h"
int main(void) {
UdpSocket *connection = sp_udp_open_ipv4(127, 0, 0, 1, 2342);
if (connection == NULL)
return 1;
Bitmap *pixels = sp_bitmap_new(PIXEL_WIDTH, PIXEL_HEIGHT);
if (pixels == NULL)
return 1;
sp_bitmap_fill(pixels, true);
Packet *packet = sp_bitmap_into_packet(pixels, 0, 0, COMPRESSION_CODE_UNCOMPRESSED);
if (packet == NULL)
return 1;
Header *header = sp_packet_get_header(packet);
// printf("[%d, %d, %d, %d, %d]\n", header->command_code, header->a, header->b, header->c, header->d);
sp_udp_send_packet(connection, packet);
sp_udp_free(connection);
return 0;
}

View file

@ -1,73 +0,0 @@
CC := gcc
CARGO := rustup run nightly cargo
THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
REPO_ROOT := $(THIS_DIR)/../../
RUST_TARGET_DIR := $(REPO_ROOT)/target/x86_64-unknown-linux-musl/size-optimized
RUSTFLAGS := -Zlocation-detail=none \
-Zfmt-debug=none \
-C linker=musl-gcc \
-C link-arg=-s \
-C link-arg=--gc-sections \
-C link-arg=-z,norelro \
-C link-arg=--hash-style=gnu \
--crate-type=staticlib \
-C panic=abort
CARGOFLAGS := --manifest-path=$(REPO_ROOT)/Cargo.toml \
--profile=size-optimized \
--no-default-features \
--target=x86_64-unknown-linux-musl \
-Zbuild-std="core,std,alloc,proc_macro,panic_abort" \
-Zbuild-std-features="panic_immediate_abort" \
CCFLAGS := -static -Os \
-ffunction-sections -fdata-sections \
-fwrapv -fomit-frame-pointer -fno-stack-protector\
-fwhole-program \
-nodefaultlibs -lservicepoint_binding_c -lc \
-Wl,--gc-sections \
-fno-unroll-loops \
-fno-unwind-tables -fno-asynchronous-unwind-tables \
-fmerge-all-constants \
-Wl,-z,norelro \
-Wl,--hash-style=gnu \
-fvisibility=hidden \
-Bsymbolic \
-Wl,--exclude-libs,ALL \
-fno-ident
#-fuse-ld=gold \
-fno-exceptions
#-Wl,--icf=all \
export SERVICEPOINT_HEADER_OUT := $(THIS_DIR)/include
build: out/lang_c
clean:
rm -r out || true
rm include/servicepoint.h || true
cargo clean
run: out/lang_c
out/lang_c
PHONY: build clean dependencies run
out/lang_c_unstripped: dependencies src/main.c
mkdir -p out || true
${CC} src/main.c \
-I $(SERVICEPOINT_HEADER_OUT) \
-L $(RUST_TARGET_DIR)\
$(CCFLAGS) \
-o out/lang_c_unstripped
out/lang_c: out/lang_c_unstripped
strip -s -R .comment -R .gnu.version --strip-unneeded out/lang_c_unstripped -o out/lang_c
#strip -S --strip-unneeded --remove-section=.note.gnu.gold-version --remove-section=.comment --remove-section=.note --remove-section=.note.gnu.build-id --remove-section=.note.ABI-tag
dependencies: FORCE
mkdir -p include || true
# generate servicepoint header and binary to link against
${CARGO} rustc $(CARGOFLAGS) -- $(RUSTFLAGS)
FORCE: ;

View file

@ -33,16 +33,17 @@
{ pkgs, system }:
{
default = pkgs.mkShell rec {
buildInputs = with pkgs;[
buildInputs = with pkgs; [
xe
xz
libgcc
#glibc.static
musl
libunwind
];
#libgcc
#glibc
pkgsStatic.musl
# libunwind
nativeBuildInputs = with pkgs;[
];
nativeBuildInputs = with pkgs; [
(pkgs.symlinkJoin {
name = "rust-toolchain";
paths = with pkgs; [
@ -57,12 +58,14 @@
];
})
gcc
gdb
pkgsStatic.gcc
gnumake
pkg-config
];
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
#RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
CARGO = "rustup run nightly cargo";
};
}
);

File diff suppressed because it is too large Load diff

View file

@ -1,31 +1,18 @@
//! C functions for interacting with [SPBitmap]s
//!
//! prefix `sp_bitmap_`
//!
//! A grid of pixels.
//!
//! # 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);
//! ```
use servicepoint::{DataRef, Grid};
use crate::byte_slice::ByteSlice;
use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, SPBitVec};
use servicepoint::{
Bitmap, BitmapCommand, CompressionCode, DataRef, Grid, Origin, Packet,
};
use std::ptr::NonNull;
use crate::byte_slice::SPByteSlice;
/// Creates a new [SPBitmap] with the specified dimensions.
/// Creates a new [Bitmap] with the specified dimensions.
///
/// # Arguments
///
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: [SPBitmap] initialized to all pixels off, or NULL in case of an error.
/// returns: [Bitmap] initialized to all pixels off, or NULL in case of an error.
///
/// # Errors
///
@ -33,131 +20,89 @@ use crate::byte_slice::SPByteSlice;
///
/// - when the width is not dividable by 8
///
/// # Safety
/// # Examples
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
/// ```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 servicepoint::Bitmap {
if let Some(bitmap) = servicepoint::Bitmap::new(width, height) {
Box::leak(Box::new(bitmap))
) -> *mut Bitmap {
if let Some(bitmap) = Bitmap::new(width, height) {
heap_move(bitmap)
} else {
std::ptr::null_mut()
}
}
/// Creates a new [SPBitmap] with a size matching the screen.
/// Creates a new [Bitmap] with a size matching the screen.
///
/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling [sp_bitmap_free].
/// returns: [Bitmap] initialized to all pixels off.
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_new_screen_sized(
) -> NonNull<servicepoint::Bitmap> {
let result = Box::new(servicepoint::Bitmap::max_sized());
NonNull::from(Box::leak(result))
pub unsafe extern "C" fn sp_bitmap_new_max_sized() -> NonNull<Bitmap> {
heap_move_nonnull(Bitmap::max_sized())
}
/// Loads a [SPBitmap] with the specified dimensions from the provided data.
/// Loads a [Bitmap] with the specified dimensions from the provided data.
///
/// # Arguments
///
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: [SPBitmap] that contains a copy of the provided data, or NULL in case of an error.
///
/// # Errors
///
/// In the following cases, this function will return NULL:
///
/// - when the dimensions and data size do not match exactly.
/// - when the width is not dividable by 8
///
/// # Panics
///
/// - when `data` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length` bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
/// returns: [Bitmap] that contains a copy of the provided data, or NULL in case of an error.
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
) -> *mut servicepoint::Bitmap {
assert!(!data.is_null());
let data = unsafe { std::slice::from_raw_parts(data, data_length) };
if let Ok(bitmap) = servicepoint::Bitmap::load(width, height, data) {
Box::leak(Box::new(bitmap))
data: ByteSlice,
) -> *mut Bitmap {
let data = unsafe { data.as_slice() };
if let Ok(bitmap) = Bitmap::load(width, height, data) {
heap_move(bitmap)
} else {
std::ptr::null_mut()
}
}
/// Clones a [SPBitmap].
/// Tries to convert the BitVec to a Bitmap.
///
/// Will never return NULL.
/// The provided BitVec gets consumed.
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
/// Returns NULL in case of error.
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_from_bitvec(
width: usize,
bitvec: NonNull<SPBitVec>,
) -> *mut Bitmap {
let bitvec = unsafe { heap_remove(bitvec) };
if let Ok(bitmap) = Bitmap::from_bitvec(width, bitvec.0) {
heap_move(bitmap)
} else {
std::ptr::null_mut()
}
}
/// Clones a [Bitmap].
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_clone(
bitmap: *const servicepoint::Bitmap,
) -> NonNull<servicepoint::Bitmap> {
assert!(!bitmap.is_null());
let result = Box::new(unsafe { (*bitmap).clone() });
NonNull::from(Box::leak(result))
bitmap: NonNull<Bitmap>,
) -> NonNull<Bitmap> {
heap_move_nonnull(unsafe { bitmap.as_ref().clone() })
}
/// Deallocates a [SPBitmap].
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not used concurrently or after bitmap call
/// - `bitmap` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
/// Deallocates a [Bitmap].
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_free(bitmap: *mut servicepoint::Bitmap) {
assert!(!bitmap.is_null());
_ = unsafe { Box::from_raw(bitmap) };
pub unsafe extern "C" fn sp_bitmap_free(bitmap: NonNull<Bitmap>) {
unsafe { heap_drop(bitmap) }
}
/// Gets the current value at the specified position in the [SPBitmap].
/// Gets the current value at the specified position in the [Bitmap].
///
/// # Arguments
///
@ -166,26 +111,17 @@ pub unsafe extern "C" fn sp_bitmap_free(bitmap: *mut servicepoint::Bitmap) {
///
/// # Panics
///
/// - when `bitmap` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_get(
bitmap: *const servicepoint::Bitmap,
bitmap: NonNull<Bitmap>,
x: usize,
y: usize,
) -> bool {
assert!(!bitmap.is_null());
unsafe { (*bitmap).get(x, y) }
unsafe { bitmap.as_ref().get(x, y) }
}
/// Sets the value of the specified position in the [SPBitmap].
/// Sets the value of the specified position in the [Bitmap].
///
/// # Arguments
///
@ -193,57 +129,31 @@ pub unsafe extern "C" fn sp_bitmap_get(
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
/// # Panics
///
/// - when `bitmap` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_set(
bitmap: *mut servicepoint::Bitmap,
bitmap: NonNull<Bitmap>,
x: usize,
y: usize,
value: bool,
) {
assert!(!bitmap.is_null());
unsafe { (*bitmap).set(x, y, value) };
unsafe { (*bitmap.as_ptr()).set(x, y, value) };
}
/// Sets the state of all pixels in the [SPBitmap].
/// Sets the state of all pixels in the [Bitmap].
///
/// # Arguments
///
/// - `bitmap`: instance to write to
/// - `value`: the value to set all pixels to
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_fill(
bitmap: *mut servicepoint::Bitmap,
value: bool,
) {
assert!(!bitmap.is_null());
unsafe { (*bitmap).fill(value) };
pub unsafe extern "C" fn sp_bitmap_fill(bitmap: NonNull<Bitmap>, value: bool) {
unsafe { (*bitmap.as_ptr()).fill(value) };
}
/// Gets the width in pixels of the [SPBitmap] instance.
/// Gets the width in pixels of the [Bitmap] instance.
///
/// # Arguments
///
@ -257,55 +167,60 @@ pub unsafe extern "C" fn sp_bitmap_fill(
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` points to a valid [Bitmap]
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_width(
bitmap: *const servicepoint::Bitmap,
) -> usize {
assert!(!bitmap.is_null());
unsafe { (*bitmap).width() }
pub unsafe extern "C" fn sp_bitmap_width(bitmap: NonNull<Bitmap>) -> usize {
unsafe { bitmap.as_ref().width() }
}
/// Gets the height in pixels of the [SPBitmap] instance.
/// Gets the height in pixels of the [Bitmap] instance.
///
/// # Arguments
///
/// - `bitmap`: instance to read from
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_height(
bitmap: *const servicepoint::Bitmap,
) -> usize {
assert!(!bitmap.is_null());
unsafe { (*bitmap).height() }
pub unsafe extern "C" fn sp_bitmap_height(bitmap: NonNull<Bitmap>) -> usize {
unsafe { bitmap.as_ref().height() }
}
/// Gets an unsafe reference to the data of the [SPBitmap] instance.
/// Gets an unsafe reference to the data of the [Bitmap] instance.
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - the returned memory range is never accessed after the passed [SPBitmap] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBitmap] or directly
/// The returned memory is valid for the lifetime of the bitmap.
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_unsafe_data_ref(
bitmap: *mut servicepoint::Bitmap,
) -> SPByteSlice {
assert!(!bitmap.is_null());
unsafe { SPByteSlice::from_slice((*bitmap).data_ref_mut()) }
mut bitmap: NonNull<Bitmap>,
) -> 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<Bitmap>,
) -> NonNull<SPBitVec> {
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<Bitmap>,
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(),
}
}

View file

@ -1,8 +1,5 @@
//! C functions for interacting with [SPBitVec]s
//!
//! prefix `sp_bitvec_`
use crate::SPByteSlice;
use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice};
use servicepoint::{BinaryOperation, BitVecCommand, CompressionCode, DisplayBitVec, Packet};
use std::ptr::NonNull;
/// A vector of bits
@ -13,19 +10,7 @@ use std::ptr::NonNull;
/// sp_bitvec_set(vec, 5, true);
/// sp_bitvec_free(vec);
/// ```
pub struct SPBitVec(servicepoint::BitVecU8Msb0);
impl From<servicepoint::BitVecU8Msb0> for SPBitVec {
fn from(actual: servicepoint::BitVecU8Msb0) -> Self {
Self(actual)
}
}
impl From<SPBitVec> for servicepoint::BitVecU8Msb0 {
fn from(value: SPBitVec) -> Self {
value.0
}
}
pub struct SPBitVec(pub(crate) DisplayBitVec);
impl Clone for SPBitVec {
fn clone(&self) -> Self {
@ -39,95 +24,37 @@ impl Clone for SPBitVec {
///
/// - `size`: size in bits.
///
/// returns: [SPBitVec] with all bits set to false. Will never return NULL.
/// returns: [SPBitVec] with all bits set to false.
///
/// # Panics
///
/// - when `size` is not divisible by 8.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_new(size: usize) -> NonNull<SPBitVec> {
let result = Box::new(SPBitVec(servicepoint::BitVecU8Msb0::repeat(false, size)));
NonNull::from(Box::leak(result))
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. Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
/// returns: [SPBitVec] instance containing data.
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_load(
data: *const u8,
data_length: usize,
) -> NonNull<SPBitVec> {
assert!(!data.is_null());
let data = unsafe { std::slice::from_raw_parts(data, data_length) };
let result = Box::new(SPBitVec(servicepoint::BitVecU8Msb0::from_slice(data)));
NonNull::from(Box::leak(result))
pub unsafe extern "C" fn sp_bitvec_load(data: ByteSlice) -> NonNull<SPBitVec> {
let data = unsafe { data.as_slice() };
heap_move_nonnull(SPBitVec(DisplayBitVec::from_slice(data)))
}
/// Clones a [SPBitVec].
///
/// returns: new [SPBitVec] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_clone(
bit_vec: *const SPBitVec,
bit_vec: NonNull<SPBitVec>,
) -> NonNull<SPBitVec> {
assert!(!bit_vec.is_null());
let result = Box::new(unsafe { (*bit_vec).clone() });
NonNull::from(Box::leak(result))
heap_move_nonnull(unsafe { bit_vec.as_ref().clone() })
}
/// Deallocates a [SPBitVec].
///
/// # Panics
///
/// - when `but_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `bit_vec` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_free(bit_vec: *mut SPBitVec) {
assert!(!bit_vec.is_null());
_ = unsafe { Box::from_raw(bit_vec) };
pub unsafe extern "C" fn sp_bitvec_free(bit_vec: NonNull<SPBitVec>) {
unsafe { heap_drop(bit_vec) }
}
/// Gets the value of a bit from the [SPBitVec].
@ -141,22 +68,13 @@ pub unsafe extern "C" fn sp_bitvec_free(bit_vec: *mut SPBitVec) {
///
/// # Panics
///
/// - when `bit_vec` is NULL
/// - when accessing `index` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_get(
bit_vec: *const SPBitVec,
bit_vec: NonNull<SPBitVec>,
index: usize,
) -> bool {
assert!(!bit_vec.is_null());
unsafe { *(*bit_vec).0.get(index).unwrap() }
unsafe { *bit_vec.as_ref().0.get(index).unwrap() }
}
/// Sets the value of a bit in the [SPBitVec].
@ -169,23 +87,14 @@ pub unsafe extern "C" fn sp_bitvec_get(
///
/// # Panics
///
/// - when `bit_vec` is NULL
/// - when accessing `index` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_set(
bit_vec: *mut SPBitVec,
bit_vec: NonNull<SPBitVec>,
index: usize,
value: bool,
) {
assert!(!bit_vec.is_null());
unsafe { (*bit_vec).0.set(index, value) }
unsafe { (*bit_vec.as_ptr()).0.set(index, value) }
}
/// Sets the value of all bits in the [SPBitVec].
@ -194,21 +103,12 @@ pub unsafe extern "C" fn sp_bitvec_set(
///
/// - `bit_vec`: instance to write to
/// - `value`: the value to set all bits to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_fill(bit_vec: *mut SPBitVec, value: bool) {
assert!(!bit_vec.is_null());
unsafe { (*bit_vec).0.fill(value) }
pub unsafe extern "C" fn sp_bitvec_fill(
bit_vec: NonNull<SPBitVec>,
value: bool,
) {
unsafe { (*bit_vec.as_ptr()).0.fill(value) }
}
/// Gets the length of the [SPBitVec] in bits.
@ -216,20 +116,9 @@ pub unsafe extern "C" fn sp_bitvec_fill(bit_vec: *mut SPBitVec, value: bool) {
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_len(bit_vec: *const SPBitVec) -> usize {
assert!(!bit_vec.is_null());
unsafe { (*bit_vec).0.len() }
pub unsafe extern "C" fn sp_bitvec_len(bit_vec: NonNull<SPBitVec>) -> usize {
unsafe { bit_vec.as_ref().0.len() }
}
/// Returns true if length is 0.
@ -237,43 +126,47 @@ pub unsafe extern "C" fn sp_bitvec_len(bit_vec: *const SPBitVec) -> usize {
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_is_empty(bit_vec: *const SPBitVec) -> bool {
assert!(!bit_vec.is_null());
unsafe { (*bit_vec).0.is_empty() }
pub unsafe extern "C" fn sp_bitvec_is_empty(
bit_vec: NonNull<SPBitVec>,
) -> bool {
unsafe { bit_vec.as_ref().0.is_empty() }
}
/// Gets an unsafe reference to the data of the [SPBitVec] instance.
///
/// The returned memory is valid for the lifetime of the bitvec.
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - the returned memory range is never accessed after the passed [SPBitVec] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBitVec] or directly
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_unsafe_data_ref(
bit_vec: *mut SPBitVec,
) -> SPByteSlice {
assert!(!bit_vec.is_null());
unsafe { SPByteSlice::from_slice((*bit_vec).0.as_raw_mut_slice() ) }
bit_vec: NonNull<SPBitVec>,
) -> 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<SPBitVec>,
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(),
}
}

View file

@ -1,150 +1,71 @@
//! C functions for interacting with [SPBrightnessGrid]s
//!
//! prefix `sp_brightness_grid_`
//!
//!
//! A grid containing brightness values.
//!
//! # Examples
//! ```C
//! SPConnection connection = sp_connection_open("127.0.0.1:2342");
//! if (connection == NULL)
//! return 1;
//!
//! SPBrightnessGrid grid = sp_brightness_grid_new(2, 2);
//! sp_brightness_grid_set(grid, 0, 0, 0);
//! sp_brightness_grid_set(grid, 1, 1, 10);
//!
//! SPCommand command = sp_command_char_brightness(grid);
//! sp_connection_free(connection);
//! ```
use crate::SPByteSlice;
use servicepoint::{BrightnessGrid, DataRef, Grid};
use std::convert::Into;
use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice};
use servicepoint::{
Brightness, BrightnessGrid, BrightnessGridCommand, ByteGrid, DataRef, Grid,
Origin, Packet,
};
use std::mem::transmute;
use std::ptr::NonNull;
/// see [servicepoint::Brightness::MIN]
pub const SP_BRIGHTNESS_MIN: u8 = 0;
/// see [servicepoint::Brightness::MAX]
pub const SP_BRIGHTNESS_MAX: u8 = 11;
/// Count of possible brightness values
pub const SP_BRIGHTNESS_LEVELS: u8 = 12;
/// Creates a new [SPBrightnessGrid] with the specified dimensions.
/// Creates a new [BrightnessGrid] with the specified dimensions.
///
/// returns: [SPBrightnessGrid] initialized to 0. Will never return NULL.
/// returns: [BrightnessGrid] initialized to 0.
///
/// # Safety
/// # Examples
/// ```C
/// UdpConnection connection = sp_udp_open("127.0.0.1:2342");
/// if (connection == NULL)
/// return 1;
///
/// The caller has to make sure that:
/// BrightnessGrid grid = sp_brightness_grid_new(2, 2);
/// sp_brightness_grid_set(grid, 0, 0, 0);
/// sp_brightness_grid_set(grid, 1, 1, 10);
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
/// 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<BrightnessGrid> {
let result = Box::new(servicepoint::BrightnessGrid::new(
width, height,
));
NonNull::from(Box::leak(result))
heap_move_nonnull(BrightnessGrid::new(width, height))
}
/// Loads a [SPBrightnessGrid] with the specified dimensions from the provided data.
/// Loads a [BrightnessGrid] with the specified dimensions from the provided data.
///
/// returns: new [SPBrightnessGrid] instance. Will never return NULL.
/// Any out of range values will be set to [Brightness::MAX] or [Brightness::MIN].
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
/// returns: new [BrightnessGrid] instance, or NULL in case of an error.
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
data: ByteSlice,
) -> *mut BrightnessGrid {
assert!(!data.is_null());
let data = unsafe { std::slice::from_raw_parts(data, data_length) };
let grid = match servicepoint::ByteGrid::load(width, height, data) {
None => return std::ptr::null_mut(),
Some(grid) => grid,
};
if let Ok(grid) = servicepoint::BrightnessGrid::try_from(grid) {
Box::leak(Box::new(grid))
} else {
std::ptr::null_mut()
let data = unsafe { data.as_slice() };
match ByteGrid::load(width, height, data)
.map(move |grid| grid.map(Brightness::saturating_from))
{
None => std::ptr::null_mut(),
Some(grid) => heap_move(grid),
}
}
/// Clones a [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: new [SPBrightnessGrid] instance. Will never return NULL.
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
/// Clones a [BrightnessGrid].
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_clone(
brightness_grid: *const BrightnessGrid,
brightness_grid: NonNull<BrightnessGrid>,
) -> NonNull<BrightnessGrid> {
assert!(!brightness_grid.is_null());
let result = Box::new(unsafe { (*brightness_grid).clone() });
NonNull::from(Box::leak(result))
heap_move_nonnull(unsafe { brightness_grid.as_ref().clone() })
}
/// Deallocates a [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not used concurrently or after this call
/// - `brightness_grid` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
/// Deallocates a [BrightnessGrid].
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_free(
brightness_grid: *mut BrightnessGrid,
brightness_grid: NonNull<BrightnessGrid>,
) {
assert!(!brightness_grid.is_null());
_ = unsafe { Box::from_raw(brightness_grid) };
unsafe { heap_drop(brightness_grid) }
}
/// Gets the current value at the specified position.
@ -157,27 +78,17 @@ pub unsafe extern "C" fn sp_brightness_grid_free(
/// returns: value at position
///
/// # Panics
///
/// - when `brightness_grid` is NULL
/// - When accessing `x` or `y` out of bounds.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_get(
brightness_grid: *const BrightnessGrid,
brightness_grid: NonNull<BrightnessGrid>,
x: usize,
y: usize,
) -> u8 {
assert!(!brightness_grid.is_null());
unsafe { (*brightness_grid).get(x, y) }.into()
) -> Brightness {
unsafe { brightness_grid.as_ref().get(x, y) }
}
/// Sets the value of the specified position in the [SPBrightnessGrid].
/// Sets the value of the specified position in the [BrightnessGrid].
///
/// # Arguments
///
@ -189,135 +100,98 @@ pub unsafe extern "C" fn sp_brightness_grid_get(
///
/// # Panics
///
/// - when `brightness_grid` is NULL
/// - When accessing `x` or `y` out of bounds.
/// - When providing an invalid brightness value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_set(
brightness_grid: *mut BrightnessGrid,
brightness_grid: NonNull<BrightnessGrid>,
x: usize,
y: usize,
value: u8,
value: Brightness,
) {
assert!(!brightness_grid.is_null());
let brightness = servicepoint::Brightness::try_from(value)
.expect("invalid brightness value");
unsafe { (*brightness_grid).set(x, y, brightness) };
unsafe { (*brightness_grid.as_ptr()).set(x, y, value) };
}
/// Sets the value of all cells in the [SPBrightnessGrid].
/// Sets the value of all cells in the [BrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to write to
/// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `brightness_grid` is NULL
/// - When providing an invalid brightness value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_fill(
brightness_grid: *mut BrightnessGrid,
value: u8,
brightness_grid: NonNull<BrightnessGrid>,
value: Brightness,
) {
assert!(!brightness_grid.is_null());
let brightness = servicepoint::Brightness::try_from(value)
.expect("invalid brightness value");
unsafe { (*brightness_grid).fill(brightness) };
unsafe { (*brightness_grid.as_ptr()).fill(value) };
}
/// Gets the width of the [SPBrightnessGrid] instance.
/// Gets the width of the [BrightnessGrid] instance.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: width
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_width(
brightness_grid: *const BrightnessGrid,
brightness_grid: NonNull<BrightnessGrid>,
) -> usize {
assert!(!brightness_grid.is_null());
unsafe { (*brightness_grid).width() }
unsafe { brightness_grid.as_ref().width() }
}
/// Gets the height of the [SPBrightnessGrid] instance.
/// Gets the height of the [BrightnessGrid] instance.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: height
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_height(
brightness_grid: *const BrightnessGrid,
brightness_grid: NonNull<BrightnessGrid>,
) -> usize {
assert!(!brightness_grid.is_null());
unsafe { (*brightness_grid).height() }
unsafe { brightness_grid.as_ref().height() }
}
/// Gets an unsafe reference to the data of the [SPBrightnessGrid] instance.
/// Gets an unsafe reference to the data of the [BrightnessGrid] instance.
///
/// The returned memory is valid for the lifetime of the brightness grid.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: slice of bytes underlying the `brightness_grid`.
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - the returned memory range is never accessed after the passed [SPBrightnessGrid] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBrightnessGrid] or directly
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_unsafe_data_ref(
brightness_grid: *mut BrightnessGrid,
) -> SPByteSlice {
assert!(!brightness_grid.is_null());
assert_eq!(core::mem::size_of::<servicepoint::Brightness>(), 1);
let data = unsafe { (*brightness_grid).data_ref_mut() };
// this assumes more about the memory layout than rust guarantees. yikes!
unsafe { SPByteSlice::from_slice(transmute(data)) }
brightness_grid: NonNull<BrightnessGrid>,
) -> ByteSlice {
//noinspection RsAssertEqual
const _: () = assert!(size_of::<Brightness>() == 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<BrightnessGrid>,
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(),
}
}

View file

@ -2,10 +2,7 @@
use std::ptr::NonNull;
#[repr(C)]
/// Represents a span of memory (`&mut [u8]` ) as a struct usable by C code.
///
/// You should not create an instance of this type in your C code.
/// Represents a span of memory (`&mut [u8]` ) as a struct.
///
/// # Safety
///
@ -14,16 +11,15 @@ use std::ptr::NonNull;
/// - accesses to the memory pointed to with `start` is never accessed outside `length`
/// - the lifetime of the `CByteSlice` does not outlive the memory it points to, as described in
/// the function returning this type.
/// - an instance of this created from C is never passed to a consuming function, as the rust code
/// will try to free the memory of a potentially separate allocator.
pub struct SPByteSlice {
/// The start address of the memory
#[repr(C)]
pub struct ByteSlice {
/// The start address of the memory.
pub start: NonNull<u8>,
/// The amount of memory in bytes
/// The amount of memory in bytes.
pub length: usize,
}
impl SPByteSlice {
impl ByteSlice {
pub(crate) unsafe fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.start.as_ptr(), self.length) }
}

View file

@ -1,127 +1,59 @@
//! C functions for interacting with [SPCharGrid]s
//!
//! prefix `sp_char_grid_`
//!
//! A C-wrapper for grid containing UTF-8 characters.
//!
//! As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers.
//!
//! The encoding is enforced in most cases by the rust standard library
//! and will panic when provided with illegal characters.
//!
//! # Examples
//!
//! ```C
//! CharGrid grid = sp_char_grid_new(4, 3);
//! sp_char_grid_fill(grid, '?');
//! sp_char_grid_set(grid, 0, 0, '!');
//! sp_char_grid_free(grid);
//! ```
use servicepoint::{CharGrid, Grid};
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 [SPCharGrid] with the specified dimensions.
/// Creates a new [CharGrid] with the specified dimensions.
///
/// returns: [SPCharGrid] initialized to 0. Will never return NULL.
/// returns: [CharGrid] initialized to 0.
///
/// # Safety
/// # Examples
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_char_grid_free`.
/// ```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<CharGrid> {
let result = Box::new(CharGrid::new(width, height));
NonNull::from(Box::leak(result))
heap_move_nonnull(CharGrid::new(width, height))
}
/// Loads a [SPCharGrid] with the specified dimensions from the provided data.
/// Loads a [CharGrid] with the specified dimensions from the provided data.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
/// - when `data` is not valid UTF-8
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_char_grid_free`.
/// returns: new CharGrid or NULL in case of an error
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
) -> NonNull<CharGrid> {
assert!(data.is_null());
let data = unsafe { std::slice::from_raw_parts(data, data_length) };
// TODO remove unwrap
let result = Box::new(
CharGrid::load_utf8(width, height, data.to_vec())
.unwrap(),
);
NonNull::from(Box::leak(result))
data: ByteSlice,
) -> *mut CharGrid {
let data = unsafe { data.as_slice() };
if let Ok(grid) = CharGrid::load_utf8(width, height, data.to_vec()) {
heap_move(grid)
} else {
std::ptr::null_mut()
}
}
/// Clones a [SPCharGrid].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_char_grid_free`.
/// Clones a [CharGrid].
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_clone(
char_grid: *const CharGrid,
char_grid: NonNull<CharGrid>,
) -> NonNull<CharGrid> {
assert!(!char_grid.is_null());
let result = Box::new(unsafe { (*char_grid).clone() });
NonNull::from(Box::leak(result))
heap_move_nonnull(unsafe { char_grid.as_ref().clone() })
}
/// Deallocates a [SPCharGrid].
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not used concurrently or after char_grid call
/// - `char_grid` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
/// Deallocates a [CharGrid].
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_free(char_grid: *mut CharGrid) {
assert!(!char_grid.is_null());
_ = unsafe { Box::from_raw(char_grid) };
pub unsafe extern "C" fn sp_char_grid_free(char_grid: NonNull<CharGrid>) {
unsafe { heap_drop(char_grid) }
}
/// Gets the current value at the specified position.
/// Returns the current value at the specified position.
///
/// # Arguments
///
@ -130,26 +62,17 @@ pub unsafe extern "C" fn sp_char_grid_free(char_grid: *mut CharGrid) {
///
/// # Panics
///
/// - when `char_grid` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_get(
char_grid: *const CharGrid,
char_grid: NonNull<CharGrid>,
x: usize,
y: usize,
) -> u32 {
assert!(!char_grid.is_null());
unsafe { (*char_grid).get(x, y) as u32 }
unsafe { char_grid.as_ref().get(x, y) as u32 }
}
/// Sets the value of the specified position in the [SPCharGrid].
/// Sets the value of the specified position in the [CharGrid].
///
/// # Arguments
///
@ -161,96 +84,72 @@ pub unsafe extern "C" fn sp_char_grid_get(
///
/// # Panics
///
/// - when `char_grid` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPBitVec]
/// - `char_grid` is not written to or read from concurrently
///
/// [SPBitVec]: [crate::SPBitVec]
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_set(
char_grid: *mut CharGrid,
char_grid: NonNull<CharGrid>,
x: usize,
y: usize,
value: u32,
) {
assert!(!char_grid.is_null());
unsafe { (*char_grid).set(x, y, char::from_u32(value).unwrap()) };
unsafe { (*char_grid.as_ptr()).set(x, y, char::from_u32(value).unwrap()) };
}
/// Sets the value of all cells in the [SPCharGrid].
/// Sets the value of all cells in the [CharGrid].
///
/// # Arguments
///
/// - `char_grid`: instance to write to
/// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_fill(
char_grid: *mut CharGrid,
char_grid: NonNull<CharGrid>,
value: u32,
) {
assert!(!char_grid.is_null());
unsafe { (*char_grid).fill(char::from_u32(value).unwrap()) };
unsafe { (*char_grid.as_ptr()).fill(char::from_u32(value).unwrap()) };
}
/// Gets the width of the [SPCharGrid] instance.
/// Gets the width of the [CharGrid] instance.
///
/// # Arguments
///
/// - `char_grid`: instance to read from
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_width(
char_grid: *const CharGrid,
char_grid: NonNull<CharGrid>,
) -> usize {
assert!(!char_grid.is_null());
unsafe { (*char_grid).width() }
unsafe { char_grid.as_ref().width() }
}
/// Gets the height of the [SPCharGrid] instance.
/// Gets the height of the [CharGrid] instance.
///
/// # Arguments
///
/// - `char_grid`: instance to read from
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_height(
char_grid: *const CharGrid,
char_grid: NonNull<CharGrid>,
) -> usize {
assert!(!char_grid.is_null());
unsafe { (*char_grid).height() }
unsafe { char_grid.as_ref().height() }
}
/// Creates a [CharGridCommand] and immediately turns that into a [Packet].
///
/// The provided [CharGrid] gets consumed.
///
/// Returns NULL in case of an error.
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_into_packet(
grid: NonNull<CharGrid>,
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(),
}
}

View file

@ -1,524 +0,0 @@
//! C functions for interacting with [SPCommand]s
//!
//! prefix `sp_command_`
use crate::{SPBitVec, SPCompressionCode, SPCp437Grid};
use servicepoint::{BinaryOperation, BrightnessGrid, CharGrid, GlobalBrightnessCommand, Packet, TypedCommand};
use std::ptr::NonNull;
/// A low-level display command.
///
/// This struct and associated functions implement the UDP protocol for the display.
///
/// To send a [SPCommand], use a [SPConnection].
///
/// # Examples
///
/// ```C
/// sp_connection_send_command(connection, sp_command_clear());
/// sp_connection_send_command(connection, sp_command_brightness(5));
/// ```
///
/// [SPConnection]: [crate::SPConnection]
/// Tries to turn a [SPPacket] into a [SPCommand].
///
/// The packet is deallocated in the process.
///
/// Returns: pointer to new [SPCommand] instance or NULL if parsing failed.
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - [SPPacket] points to a valid instance of [SPPacket]
/// - [SPPacket] is not used concurrently or after this call
/// - the result is checked for NULL
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_try_from_packet(
packet: *mut Packet,
) -> *mut TypedCommand {
let packet = *unsafe { Box::from_raw(packet) };
match servicepoint::TypedCommand::try_from(packet) {
Err(_) => std::ptr::null_mut(),
Ok(command) => Box::into_raw(Box::new(command)),
}
}
/// Clones a [SPCommand] instance.
///
/// returns: new [SPCommand] instance. Will never return NULL.
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `command` points to a valid instance of [SPCommand]
/// - `command` is not written to concurrently
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_clone(
command: *const TypedCommand,
) -> NonNull<TypedCommand> {
assert!(!command.is_null());
let result = Box::new(unsafe { (*command).clone() });
NonNull::from(Box::leak(result))
}
/// Set all pixels to the off state.
///
/// Does not affect brightness.
///
/// Returns: a new [servicepoint::Command::Clear] instance. Will never return NULL.
///
/// # Examples
///
/// ```C
/// sp_connection_send_command(connection, sp_command_clear());
/// ```
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_clear() -> NonNull<TypedCommand> {
let result = Box::new(servicepoint::ClearCommand.into());
NonNull::from(Box::leak(result))
}
/// Kills the udp daemon on the display, which usually results in a restart.
///
/// Please do not send this in your normal program flow.
///
/// Returns: a new [servicepoint::Command::HardReset] instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_hard_reset() -> NonNull<TypedCommand> {
let result = Box::new(servicepoint::HardResetCommand.into());
NonNull::from(Box::leak(result))
}
/// A yet-to-be-tested command.
///
/// Returns: a new [servicepoint::Command::FadeOut] instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_fade_out() -> NonNull<TypedCommand> {
let result = Box::new(servicepoint::FadeOutCommand.into());
NonNull::from(Box::leak(result))
}
/// Set the brightness of all tiles to the same value.
///
/// Returns: a new [servicepoint::Command::Brightness] instance. Will never return NULL.
///
/// # Panics
///
/// - When the provided brightness value is out of range (0-11).
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_brightness(
brightness: u8,
) -> NonNull<TypedCommand> {
let brightness = servicepoint::Brightness::try_from(brightness)
.expect("invalid brightness");
let result =
Box::new(GlobalBrightnessCommand::from(brightness).into());
NonNull::from(Box::leak(result))
}
/// Set the brightness of individual tiles in a rectangular area of the display.
///
/// The passed [SPBrightnessGrid] gets consumed.
///
/// Returns: a new [servicepoint::Command::CharBrightness] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPBrightnessGrid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_char_brightness(
x: usize,
y: usize,
grid: *mut BrightnessGrid,
) -> NonNull<TypedCommand> {
assert!(!grid.is_null());
let grid = unsafe { *Box::from_raw(grid) };
let result = Box::new(
servicepoint::BrightnessGridCommand {
origin: servicepoint::Origin::new(x, y),
grid,
}
.into(),
);
NonNull::from(Box::leak(result))
}
/// Set pixel data starting at the pixel offset on screen.
///
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
/// once the starting row is full, overwriting will continue on column 0.
///
/// The contained [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinear] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> *mut TypedCommand {
unsafe {
sp_command_bitmap_linear_internal(
offset,
bit_vec,
compression,
BinaryOperation::Overwrite,
)
}
}
/// Set pixel data according to an and-mask starting at the offset.
///
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
/// once the starting row is full, overwriting will continue on column 0.
///
/// The contained [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearAnd] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_and(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> *mut TypedCommand {
unsafe {
sp_command_bitmap_linear_internal(
offset,
bit_vec,
compression,
BinaryOperation::Xor,
)
}
}
/// Set pixel data according to an or-mask starting at the offset.
///
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
/// once the starting row is full, overwriting will continue on column 0.
///
/// The contained [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearOr] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_or(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> *mut TypedCommand {
unsafe {
sp_command_bitmap_linear_internal(
offset,
bit_vec,
compression,
BinaryOperation::Or,
)
}
}
/// Set pixel data according to a xor-mask starting at the offset.
///
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
/// once the starting row is full, overwriting will continue on column 0.
///
/// The contained [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearXor] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_xor(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> *mut TypedCommand {
unsafe {
sp_command_bitmap_linear_internal(
offset,
bit_vec,
compression,
BinaryOperation::Xor,
)
}
}
#[inline]
unsafe fn sp_command_bitmap_linear_internal(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
operation: BinaryOperation,
) -> *mut TypedCommand {
assert!(!bit_vec.is_null());
let bit_vec = unsafe { *Box::from_raw(bit_vec) };
let compression = match compression.try_into() {
Ok(compression) => compression,
Err(_) => return std::ptr::null_mut(),
};
let command = servicepoint::BitVecCommand {
offset,
operation,
bitvec: bit_vec.into(),
compression,
}
.into();
Box::leak(Box::new(command))
}
/// Show codepage 437 encoded text on the screen.
///
/// The passed [SPCp437Grid] gets consumed.
///
/// Returns: a new [servicepoint::Command::Cp437Data] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is null
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPCp437Grid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_cp437_data(
x: usize,
y: usize,
grid: *mut SPCp437Grid,
) -> NonNull<TypedCommand> {
assert!(!grid.is_null());
let grid = *unsafe { Box::from_raw(grid) };
let result = Box::new(
servicepoint::Cp437GridCommand {
origin: servicepoint::Origin::new(x, y),
grid: grid.0,
}
.into(),
);
NonNull::from(Box::leak(result))
}
/// Show UTF-8 encoded text on the screen.
///
/// The passed [SPCharGrid] gets consumed.
///
/// Returns: a new [servicepoint::Command::Utf8Data] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is null
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPCharGrid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_utf8_data(
x: usize,
y: usize,
grid: *mut CharGrid,
) -> NonNull<TypedCommand> {
assert!(!grid.is_null());
let grid = unsafe { *Box::from_raw(grid) };
let result = Box::new(
servicepoint::CharGridCommand {
origin: servicepoint::Origin::new(x, y),
grid,
}
.into(),
);
NonNull::from(Box::leak(result))
}
/// Sets a window of pixels to the specified values.
///
/// The passed [SPBitmap] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearWin] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bitmap` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid instance of [SPBitmap]
/// - `bitmap` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_win(
x: usize,
y: usize,
bitmap: *mut servicepoint::Bitmap,
compression: SPCompressionCode,
) -> *mut TypedCommand {
assert!(!bitmap.is_null());
let bitmap = unsafe { *Box::from_raw(bitmap) };
let compression = match compression.try_into() {
Ok(compression) => compression,
Err(_) => return std::ptr::null_mut(),
};
let command = servicepoint::BitmapCommand {
origin: servicepoint::Origin::new(x, y),
bitmap,
compression,
}
.into();
Box::leak(Box::new(command))
}
/// Deallocates a [SPCommand].
///
/// # Examples
///
/// ```C
/// SPCommand c = sp_command_clear();
/// sp_command_free(c);
/// ```
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `command` points to a valid [SPCommand]
/// - `command` is not used concurrently or after this call
/// - `command` was not passed to another consuming function, e.g. to create a [SPPacket]
#[no_mangle]
pub unsafe extern "C" fn sp_command_free(command: *mut TypedCommand) {
assert!(!command.is_null());
_ = unsafe { Box::from_raw(command) };
}

View file

@ -1,150 +0,0 @@
//! C functions for interacting with [SPConnection]s
//!
//! prefix `sp_connection_`
//!
//! A connection to the display.
//!
//! # Examples
//!
//! ```C
//! CConnection connection = sp_connection_open("172.23.42.29:2342");
//! if (connection != NULL)
//! sp_connection_send_command(connection, sp_command_clear());
//! ```
use servicepoint::{Connection, Packet, TypedCommand, UdpConnection};
use std::ffi::{c_char, CStr};
/// Creates a new instance of [SPConnection].
///
/// returns: NULL if connection fails, or connected instance
///
/// # Panics
///
/// - when `host` is null or an invalid host
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_connection_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_connection_open(
host: *const c_char,
) -> *mut UdpConnection {
assert!(!host.is_null());
let host = unsafe { CStr::from_ptr(host) }
.to_str()
.expect("Bad encoding");
let connection = match UdpConnection::open(host) {
Err(_) => return std::ptr::null_mut(),
Ok(value) => value,
};
Box::into_raw(Box::new(connection))
}
//#[no_mangle]
//pub unsafe extern "C" fn sp_connection_open_ipv4(
// host: SocketAddrV4,
//) -> *mut SPConnection {
// let connection = match servicepoint::UdpConnection::open(host) {
// Err(_) => return std::ptr::null_mut(),
// Ok(value) => value,
// };
//
// Box::into_raw(Box::new(SPConnection(connection)))
//}
// /// Creates a new instance of [SPUdpConnection] for testing that does not actually send anything.
// ///
// /// returns: a new instance. Will never return NULL.
// ///
// /// # Safety
// ///
// /// The caller has to make sure that:
// ///
// /// - the returned instance is freed in some way, either by using a consuming function or
// /// by explicitly calling `sp_connection_free`.
// #[no_mangle]
// pub unsafe extern "C" fn sp_connection_fake() -> NonNull<SPUdpConnection> {
// let result = Box::new(SPUdpConnection(servicepoint::Connection::Fake));
// NonNull::from(Box::leak(result))
// }
/// Sends a [SPPacket] to the display using the [SPConnection].
///
/// The passed `packet` gets consumed.
///
/// returns: true in case of success
///
/// # Panics
///
/// - when `connection` is NULL
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid instance of [SPConnection]
/// - `packet` points to a valid instance of [SPPacket]
/// - `packet` is not used concurrently or after this call
#[no_mangle]
pub unsafe extern "C" fn sp_connection_send_packet(
connection: *const UdpConnection,
packet: *mut Packet,
) -> bool {
assert!(!connection.is_null());
assert!(!packet.is_null());
let packet = unsafe { Box::from_raw(packet) };
unsafe { (*connection).send(*packet) }.is_ok()
}
/// Sends a [SPCommand] to the display using the [SPConnection].
///
/// The passed `command` gets consumed.
///
/// returns: true in case of success
///
/// # Panics
///
/// - when `connection` is NULL
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid instance of [SPConnection]
/// - `command` points to a valid instance of [SPPacket]
/// - `command` is not used concurrently or after this call
#[no_mangle]
pub unsafe extern "C" fn sp_connection_send_command(
connection: *const UdpConnection,
command: *mut TypedCommand,
) -> bool {
assert!(!connection.is_null());
assert!(!command.is_null());
let command = *unsafe { Box::from_raw(command) };
unsafe { (*connection).send(command) }.is_ok()
}
/// Closes and deallocates a [SPConnection].
///
/// # Panics
///
/// - when `connection` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid [SPConnection]
/// - `connection` is not used concurrently or after this call
#[no_mangle]
pub unsafe extern "C" fn sp_connection_free(connection: *mut UdpConnection) {
assert!(!connection.is_null());
_ = unsafe { Box::from_raw(connection) };
}

View file

@ -1,48 +0,0 @@
//! re-exported constants for use in C
use servicepoint::CompressionCode;
use std::time::Duration;
/// size of a single tile in one dimension
pub const SP_TILE_SIZE: usize = 8;
/// Display tile count in the x-direction
pub const SP_TILE_WIDTH: usize = 56;
/// Display tile count in the y-direction
pub const SP_TILE_HEIGHT: usize = 20;
/// Display width in pixels
pub const SP_PIXEL_WIDTH: usize = SP_TILE_WIDTH * SP_TILE_SIZE;
/// Display height in pixels
pub const SP_PIXEL_HEIGHT: usize = SP_TILE_HEIGHT * SP_TILE_SIZE;
/// pixel count on whole screen
pub const SP_PIXEL_COUNT: usize = SP_PIXEL_WIDTH * SP_PIXEL_HEIGHT;
/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets.
pub const SP_FRAME_PACING_MS: u128 = Duration::from_millis(30).as_millis();
/// Specifies the kind of compression to use.
#[repr(u16)]
pub enum SPCompressionCode {
/// no compression
Uncompressed = 0x0,
/// compress using flate2 with zlib header
Zlib = 0x677a,
/// compress using bzip2
Bzip2 = 0x627a,
/// compress using lzma
Lzma = 0x6c7a,
/// compress using Zstandard
Zstd = 0x7a73,
}
impl TryFrom<SPCompressionCode> for CompressionCode {
type Error = ();
fn try_from(value: SPCompressionCode) -> Result<Self, Self::Error> {
CompressionCode::try_from(value as u16).map_err(|_| ())
}
}

View file

@ -1,130 +1,48 @@
//! C functions for interacting with [SPCp437Grid]s
//!
//! prefix `sp_cp437_grid_`
use crate::SPByteSlice;
use servicepoint::{DataRef, Grid};
use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice};
use servicepoint::{
Cp437Grid, Cp437GridCommand, DataRef, Grid, Origin, Packet,
};
use std::ptr::NonNull;
/// A C-wrapper for grid containing codepage 437 characters.
/// Creates a new [Cp437Grid] with the specified dimensions.
///
/// The encoding is currently not enforced.
///
/// # Examples
///
/// ```C
/// Cp437Grid grid = sp_cp437_grid_new(4, 3);
/// sp_cp437_grid_fill(grid, '?');
/// sp_cp437_grid_set(grid, 0, 0, '!');
/// sp_cp437_grid_free(grid);
/// ```
#[repr(transparent)]
pub struct SPCp437Grid(pub(crate) servicepoint::Cp437Grid);
impl Clone for SPCp437Grid {
fn clone(&self) -> Self {
SPCp437Grid(self.0.clone())
}
}
/// Creates a new [SPCp437Grid] with the specified dimensions.
///
/// returns: [SPCp437Grid] initialized to 0. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
/// returns: [Cp437Grid] initialized to 0.
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_new(
width: usize,
height: usize,
) -> NonNull<SPCp437Grid> {
let result =
Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height)));
NonNull::from(Box::leak(result))
) -> NonNull<Cp437Grid> {
heap_move_nonnull(Cp437Grid::new(width, height))
}
/// Loads a [SPCp437Grid] with the specified dimensions from the provided data.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
/// Loads a [Cp437Grid] with the specified dimensions from the provided data.
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
) -> *mut SPCp437Grid {
assert!(data.is_null());
let data = unsafe { std::slice::from_raw_parts(data, data_length) };
let grid = servicepoint::Cp437Grid::load(width, height, data);
data: ByteSlice,
) -> *mut Cp437Grid {
let data = unsafe { data.as_slice() };
let grid = Cp437Grid::load(width, height, data);
if let Some(grid) = grid {
Box::leak(Box::new(SPCp437Grid(grid)))
heap_move(grid)
} else {
std::ptr::null_mut()
}
}
/// Clones a [SPCp437Grid].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
/// Clones a [Cp437Grid].
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_clone(
cp437_grid: *const SPCp437Grid,
) -> NonNull<SPCp437Grid> {
assert!(!cp437_grid.is_null());
let result = Box::new(unsafe { (*cp437_grid).clone() });
NonNull::from(Box::leak(result))
cp437_grid: NonNull<Cp437Grid>,
) -> NonNull<Cp437Grid> {
heap_move_nonnull(unsafe { cp437_grid.as_ref().clone() })
}
/// Deallocates a [SPCp437Grid].
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not used concurrently or after cp437_grid call
/// - `cp437_grid` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
/// Deallocates a [Cp437Grid].
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: *mut SPCp437Grid) {
assert!(!cp437_grid.is_null());
_ = unsafe { Box::from_raw(cp437_grid) };
pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: NonNull<Cp437Grid>) {
unsafe { heap_drop(cp437_grid) }
}
/// Gets the current value at the specified position.
@ -136,26 +54,17 @@ pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: *mut SPCp437Grid) {
///
/// # Panics
///
/// - when `cp437_grid` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_get(
cp437_grid: *const SPCp437Grid,
cp437_grid: NonNull<Cp437Grid>,
x: usize,
y: usize,
) -> u8 {
assert!(!cp437_grid.is_null());
unsafe { (*cp437_grid).0.get(x, y) }
unsafe { cp437_grid.as_ref().get(x, y) }
}
/// Sets the value of the specified position in the [SPCp437Grid].
/// Sets the value of the specified position in the [Cp437Grid].
///
/// # Arguments
///
@ -167,118 +76,82 @@ pub unsafe extern "C" fn sp_cp437_grid_get(
///
/// # Panics
///
/// - when `cp437_grid` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPBitVec]
/// - `cp437_grid` is not written to or read from concurrently
///
/// [SPBitVec]: [crate::SPBitVec]
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_set(
cp437_grid: *mut SPCp437Grid,
cp437_grid: NonNull<Cp437Grid>,
x: usize,
y: usize,
value: u8,
) {
assert!(!cp437_grid.is_null());
unsafe { (*cp437_grid).0.set(x, y, value) };
unsafe { (*cp437_grid.as_ptr()).set(x, y, value) };
}
/// Sets the value of all cells in the [SPCp437Grid].
/// Sets the value of all cells in the [Cp437Grid].
///
/// # Arguments
///
/// - `cp437_grid`: instance to write to
/// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_fill(
cp437_grid: *mut SPCp437Grid,
cp437_grid: NonNull<Cp437Grid>,
value: u8,
) {
assert!(!cp437_grid.is_null());
unsafe { (*cp437_grid).0.fill(value) };
unsafe { (*cp437_grid.as_ptr()).fill(value) };
}
/// Gets the width of the [SPCp437Grid] instance.
/// Gets the width of the [Cp437Grid] instance.
///
/// # Arguments
///
/// - `cp437_grid`: instance to read from
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_width(
cp437_grid: *const SPCp437Grid,
cp437_grid: NonNull<Cp437Grid>,
) -> usize {
assert!(!cp437_grid.is_null());
unsafe { (*cp437_grid).0.width() }
unsafe { cp437_grid.as_ref().width() }
}
/// Gets the height of the [SPCp437Grid] instance.
/// Gets the height of the [Cp437Grid] instance.
///
/// # Arguments
///
/// - `cp437_grid`: instance to read from
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_height(
cp437_grid: *const SPCp437Grid,
cp437_grid: NonNull<Cp437Grid>,
) -> usize {
assert!(!cp437_grid.is_null());
unsafe { (*cp437_grid).0.height() }
unsafe { cp437_grid.as_ref().height() }
}
/// Gets an unsafe reference to the data of the [SPCp437Grid] instance.
/// Gets an unsafe reference to the data of the [Cp437Grid] instance.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - the returned memory range is never accessed after the passed [SPCp437Grid] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPCp437Grid] or directly
/// The returned memory is valid for the lifetime of the grid.
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_unsafe_data_ref(
cp437_grid: *mut SPCp437Grid,
) -> SPByteSlice {
unsafe {SPByteSlice::from_slice((*cp437_grid).0.data_ref_mut()) }
cp437_grid: NonNull<Cp437Grid>,
) -> 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<Cp437Grid>,
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(),
}
}

View file

@ -9,18 +9,18 @@
//! #include "servicepoint.h"
//!
//! int main(void) {
//! SPConnection *connection = sp_connection_open("172.23.42.29:2342");
//! UdpConnection *connection = sp_udp_open("172.23.42.29:2342");
//! if (connection == NULL)
//! return 1;
//!
//! SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
//! Bitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
//! sp_bitmap_fill(pixels, true);
//!
//! SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
//! while (sp_connection_send_command(connection, sp_command_clone(command)));
//! TypedCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
//! while (sp_udp_send_command(connection, sp_command_clone(command)));
//!
//! sp_command_free(command);
//! sp_connection_free(connection);
//! sp_udp_free(connection);
//! return 0;
//! }
//! ```
@ -30,19 +30,43 @@ pub use crate::bitvec::*;
pub use crate::brightness_grid::*;
pub use crate::byte_slice::*;
pub use crate::char_grid::*;
pub use crate::command::*;
pub use crate::connection::*;
pub use crate::constants::*;
pub use crate::cp437_grid::*;
pub use crate::packet::*;
pub use servicepoint::CommandCode;
use std::ptr::NonNull;
pub use crate::typed_command::*;
pub use crate::udp::*;
mod bitmap;
mod bitvec;
mod brightness_grid;
mod byte_slice;
mod char_grid;
mod command;
mod connection;
mod constants;
mod cp437_grid;
mod packet;
mod typed_command;
mod udp;
use std::time::Duration;
/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets.
pub const SP_FRAME_PACING_MS: u128 = Duration::from_millis(30).as_millis();
pub(crate) fn heap_move<T>(x: T) -> *mut T {
Box::into_raw(Box::new(x))
}
pub(crate) fn heap_move_nonnull<T>(x: T) -> NonNull<T> {
NonNull::from(Box::leak(Box::new(x)))
}
pub(crate) unsafe fn heap_drop<T>(x: NonNull<T>) {
drop(unsafe { heap_remove(x) });
}
pub(crate) unsafe fn heap_remove<T>(x: NonNull<T>) -> 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;

View file

@ -1,188 +1,128 @@
//! C functions for interacting with [SPPacket]s
//!
//! prefix `sp_packet_`
//!
//!
//! The raw packet
use crate::SPByteSlice;
use servicepoint::{Header, Packet, TypedCommand};
use crate::{heap_drop, heap_move, heap_move_nonnull, heap_remove, ByteSlice};
use servicepoint::{CommandCode, Header, Packet, TypedCommand};
use std::ptr::NonNull;
/// Turns a [SPCommand] into a [SPPacket].
/// The [SPCommand] gets consumed.
/// Turns a [TypedCommand] into a [Packet].
/// The [TypedCommand] gets consumed.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - [SPCommand] points to a valid instance of [SPCommand]
/// - [SPCommand] is not used concurrently or after this call
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
/// Returns NULL in case of an error.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_from_command(
command: *mut TypedCommand,
command: NonNull<TypedCommand>,
) -> *mut Packet {
assert!(!command.is_null());
let command = unsafe { *Box::from_raw(command) };
let command = unsafe { heap_remove(command) };
if let Ok(packet) = command.try_into() {
Box::leak(Box::new(packet))
heap_move(packet)
} else {
std::ptr::null_mut()
}
}
/// Tries to load a [SPPacket] from the passed array with the specified length.
/// 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
///
/// # Panics
///
/// - when `data` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory region of at least `length` bytes
/// - `data` is not written to concurrently
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_try_load(
data: *const u8,
length: usize,
) -> *mut Packet {
assert!(!data.is_null());
let data = unsafe { std::slice::from_raw_parts(data, length) };
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) => Box::into_raw(Box::new(packet)),
Ok(packet) => heap_move(packet),
}
}
/// Creates a raw [SPPacket] from parts.
///
/// # Arguments
///
/// - `command_code` specifies which command this packet contains
/// - `a`, `b`, `c` and `d` are command-specific header values
/// - `payload` is the optional data that is part of the command
/// - `payload_len` is the size of the payload
/// Creates a raw [Packet] from parts.
///
/// returns: new instance. Will never return null.
///
/// # Panics
///
/// - when `payload` is null, but `payload_len` is not zero
/// - when `payload_len` is zero, but `payload` is nonnull
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `payload` points to a valid memory region of at least `payload_len` bytes
/// - `payload` is not written to concurrently
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling [sp_packet_free].
#[no_mangle]
pub unsafe extern "C" fn sp_packet_from_parts(
header: Header,
payload: *const u8,
payload_len: usize,
payload: *const ByteSlice,
) -> NonNull<Packet> {
assert_eq!(payload.is_null(), payload_len == 0);
let payload = if payload.is_null() {
vec![]
} else {
let payload =
unsafe { std::slice::from_raw_parts(payload, payload_len) };
let payload = unsafe { (*payload).as_slice() };
Vec::from(payload)
};
let packet = Box::new(Packet { header, payload });
NonNull::from(Box::leak(packet))
heap_move_nonnull(Packet { header, payload })
}
#[no_mangle]
pub unsafe extern "C" fn sp_packet_get_header(packet: *mut Packet) -> *mut Header {
assert!(!packet.is_null());
&mut unsafe { (*packet).header }
}
#[no_mangle]
pub unsafe extern "C" fn sp_packet_get_payload(packet: *mut Packet) -> SPByteSlice {
assert!(!packet.is_null());
unsafe { SPByteSlice::from_slice(&mut *(*packet).payload) }
}
#[no_mangle]
pub unsafe extern "C" fn sp_packet_set_payload(packet: *mut Packet, data: SPByteSlice) {
assert!(!packet.is_null());
unsafe {
(*packet).payload = data.as_slice().to_vec()
}
}
#[no_mangle]
pub unsafe extern "C" fn sp_packet_write_to(
packet: *const Packet,
mut buffer: SPByteSlice,
) {
assert!(!packet.is_null());
unsafe {
(*packet).serialize_to(buffer.as_slice_mut());
}
}
/// Clones a [SPPacket].
/// Returns a pointer to the header field of the provided packet.
///
/// Will never return NULL.
/// The returned header can be changed and will be valid for the lifetime of the packet.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_get_header(
packet: NonNull<Packet>,
) -> NonNull<Header> {
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<Packet>,
) -> 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<Packet>,
data: ByteSlice,
) {
unsafe { (*packet.as_ptr()).payload = data.as_slice().to_vec() }
}
/// Serialize the packet into the provided buffer.
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `packet` points to a valid [SPPacket]
/// - `packet` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
/// - if the buffer is not big enough to hold header+payload.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_serialize_to(
packet: NonNull<Packet>,
mut buffer: ByteSlice,
) {
unsafe {
packet.as_ref().serialize_to(buffer.as_slice_mut());
}
}
/// Clones a [Packet].
#[no_mangle]
pub unsafe extern "C" fn sp_packet_clone(
packet: *const Packet,
packet: NonNull<Packet>,
) -> NonNull<Packet> {
assert!(!packet.is_null());
let result = Box::new(unsafe { (*packet).clone() });
NonNull::from(Box::leak(result))
heap_move_nonnull(unsafe { packet.as_ref().clone() })
}
/// Deallocates a [SPPacket].
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `packet` points to a valid [SPPacket]
/// - `packet` is not used concurrently or after this call
/// Deallocates a [Packet].
#[no_mangle]
pub unsafe extern "C" fn sp_packet_free(packet: *mut Packet) {
assert!(!packet.is_null());
_ = unsafe { Box::from_raw(packet) }
pub unsafe extern "C" fn sp_packet_free(packet: NonNull<Packet>) {
unsafe { heap_drop(packet) }
}
/// Converts u16 into [CommandCode].
///
/// If the provided value is not valid, false is returned and result is not changed.
#[no_mangle]
pub unsafe extern "C" fn sp_u16_to_command_code(
code: u16,
result: *mut CommandCode,
) -> bool {
match CommandCode::try_from(code) {
Ok(code) => {
unsafe {
*result = code;
}
true
}
Err(_) => false,
}
}

201
src/typed_command.rs Normal file
View file

@ -0,0 +1,201 @@
use crate::{heap_drop, heap_move, heap_move_nonnull, SPBitVec};
use servicepoint::{
BinaryOperation, Bitmap, Brightness, BrightnessGrid, CharGrid,
CompressionCode, Cp437Grid, GlobalBrightnessCommand, Packet, TypedCommand,
};
use std::ptr::NonNull;
/// Tries to turn a [Packet] into a [TypedCommand].
///
/// The packet is deallocated in the process.
///
/// Returns: pointer to new [TypedCommand] instance or NULL if parsing failed.
#[no_mangle]
pub unsafe extern "C" fn sp_command_try_from_packet(
packet: NonNull<Packet>,
) -> *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<TypedCommand>,
) -> NonNull<TypedCommand> {
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<TypedCommand> {
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<TypedCommand> {
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<TypedCommand> {
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<TypedCommand> {
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<BrightnessGrid>,
) -> NonNull<TypedCommand> {
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<SPBitVec>,
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<Cp437Grid>,
) -> NonNull<TypedCommand> {
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<CharGrid>,
) -> NonNull<TypedCommand> {
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<Bitmap>,
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<TypedCommand>) {
unsafe { heap_drop(command) }
}

119
src/udp.rs Normal file
View file

@ -0,0 +1,119 @@
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;
/// 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<c_char>,
) -> *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,
};
heap_move(connection)
}
/// 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<UdpSocket>,
packet: NonNull<Packet>,
) -> 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<UdpSocket>,
command: NonNull<TypedCommand>,
) -> 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<UdpSocket>,
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<UdpSocket>) {
unsafe { heap_drop(connection) }
}