Merge pull request #10 from cccb/improve-binding-docs

Improve binding documentation
This commit is contained in:
Vinzenz Schroeter 2024-09-07 15:20:36 +02:00 committed by GitHub
commit c5cb6475b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 3383 additions and 1659 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ out
bin
obj
.direnv
.envrc

28
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,28 @@
# Contributing
Contributions are accepted in any form (issues, documentation, feature requests, code, review, ...).
All creatures welcome.
## Pull requests
Feel free to create a PR, even if your change is not done yet.
Mark your PR as a draft as long as you do not want it to be merged.
The main branch is supposed to be a working version, including language bindings,
which means sometimes your PR may be merged into a temporary development branch.
Unit tests and documentation are required for the core library.
## Language bindings
Pull requests for your preferred language will be accepted.
If there is no code generator, it should call the C ABI methods provided by `servicepoint_binding_c`.
It should be able to send most of the basic commands in a way the simulator accepts, receiving is
not required for the merge.
It is okay for the feature set of a language binding to lag behind the one of the rust crate.
This also means you do not have to expose a feature to all the language bindings when adding something to the core.
If your change may break other language bindings, please note that in your PR description so someone can check them.

441
Cargo.lock generated
View file

@ -3,10 +3,10 @@
version = 3
[[package]]
name = "adler"
version = "1.0.2"
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
@ -19,9 +19,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.14"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [
"anstyle",
"anstyle-parse",
@ -34,61 +34,38 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anstyle-parse"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.3"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [
"anstyle",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.6.0"
@ -107,6 +84,12 @@ dependencies = [
"wyz",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bzip2"
version = "0.4.4"
@ -130,11 +113,11 @@ dependencies = [
[[package]]
name = "cbindgen"
version = "0.26.0"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49"
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
dependencies = [
"clap 3.2.25",
"clap",
"heck 0.4.1",
"indexmap",
"log",
@ -142,20 +125,20 @@ dependencies = [
"quote",
"serde",
"serde_json",
"syn 1.0.109",
"syn",
"tempfile",
"toml",
]
[[package]]
name = "cc"
version = "1.0.101"
version = "1.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d"
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
dependencies = [
"jobserver",
"libc",
"once_cell",
"shlex",
]
[[package]]
@ -166,24 +149,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.2.25"
version = "4.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"atty",
"bitflags 1.3.2",
"clap_lex 0.2.4",
"indexmap",
"strsim 0.10.0",
"termcolor",
"textwrap",
]
[[package]]
name = "clap"
version = "4.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
dependencies = [
"clap_builder",
"clap_derive",
@ -191,48 +159,39 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.7"
version = "4.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
dependencies = [
"anstream",
"anstyle",
"clap_lex 0.7.1",
"strsim 0.11.1",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.5"
version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.68",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "clap_lex"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]]
name = "colorchoice"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "crc32fast"
@ -245,14 +204,20 @@ dependencies = [
[[package]]
name = "csbindgen"
version = "1.9.1"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf70eb656f35e0e6956cbde31c66431c53d8a546823489719099c71525767a9c"
checksum = "c26b9831049b947d154bba920e4124053def72447be6fb106a96f483874b482a"
dependencies = [
"regex",
"syn 1.0.109",
"syn",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.9"
@ -260,20 +225,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "fastrand"
version = "2.1.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "flate2"
version = "1.0.30"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -298,9 +263,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.12.3"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heck"
@ -314,30 +279,21 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.9.3"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"autocfg",
"equivalent",
"hashbrown",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.0"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
@ -347,9 +303,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jobserver"
version = "0.1.31"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [
"libc",
]
@ -364,9 +320,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.155"
version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "linux-raw-sys"
@ -376,9 +332,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "log"
version = "0.4.21"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
@ -388,11 +344,11 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
dependencies = [
"adler",
"adler2",
]
[[package]]
@ -401,12 +357,6 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "os_str_bytes"
version = "6.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
[[package]]
name = "pkg-config"
version = "0.3.30"
@ -415,9 +365,12 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
@ -430,9 +383,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.36"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
@ -475,9 +428,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.5"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
@ -514,15 +467,15 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.34"
version = "0.38.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36"
dependencies = [
"bitflags 2.6.0",
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -533,42 +486,52 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.203"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.118"
version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
dependencies = [
"serde",
]
[[package]]
name = "servicepoint"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"bitvec",
"bzip2",
"clap 4.5.7",
"clap",
"flate2",
"log",
"rand",
@ -578,7 +541,7 @@ dependencies = [
[[package]]
name = "servicepoint_binding_c"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"cbindgen",
"servicepoint",
@ -586,7 +549,7 @@ dependencies = [
[[package]]
name = "servicepoint_binding_cs"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"csbindgen",
"servicepoint",
@ -594,10 +557,10 @@ dependencies = [
]
[[package]]
name = "strsim"
version = "0.10.0"
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strsim"
@ -607,20 +570,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "1.0.109"
version = "2.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
dependencies = [
"proc-macro2",
"quote",
@ -635,38 +587,49 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.10.1"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
dependencies = [
"cfg-if",
"fastrand",
"once_cell",
"rustix",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
[[package]]
name = "toml"
version = "0.5.11"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
@ -693,37 +656,6 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"windows-sys",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
@ -734,10 +666,19 @@ dependencies = [
]
[[package]]
name = "windows-targets"
version = "0.52.5"
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
@ -751,51 +692,60 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]
[[package]]
name = "wyz"
@ -807,28 +757,49 @@ dependencies = [
]
[[package]]
name = "zstd"
version = "0.13.1"
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zstd"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.1.0"
version = "7.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a"
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.11+zstd.1.5.6"
version = "2.0.13+zstd.1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4"
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
dependencies = [
"cc",
"pkg-config",

View file

@ -6,7 +6,7 @@ members = [
]
[workspace.package]
version = "0.7.0"
version = "0.8.0"
[workspace.lints.rust]
missing-docs = "warn"

View file

@ -8,69 +8,28 @@ programming languages.
Take a look at the contained crates for language specific information:
| Language | Readme |
|----------|---------------------------------------------------------------------|
|-----------|---------------------------------------------------------------------|
| Rust | [servicepoint](crates/servicepoint/README.md) |
| C / C++ | [servicepoint_binding_c](crates/servicepoint_binding_c/README.md) |
| C# / F# | [servicepoint_binding_cs](crates/servicepoint_binding_cs/README.md) |
| .NET (C#) | [servicepoint_binding_cs](crates/servicepoint_binding_cs/README.md) |
## Projects using the library
- screen simulator (rust): [servicepoint-simulator](https://github.com/kaesaecracker/servicepoint-simulator)
- A bunch of projects (C): [arfst23/ServicePoint](https://github.com/arfst23/ServicePoint), including
- a CLI tool to display image files on the display or use the display as a TTY
- a BSD games robots clone
- a split-flap-display simulator
- animations that play on the display
- tanks game (C#): [servicepoint-tanks](https://github.com/kaesaecracker/cccb-tanks-cs)
- cellular automata slideshow (rust): [servicepoint-life](https://github.com/kaesaecracker/servicepoint-life)
To add yourself to the list, open a pull request.
## About the display
## Contributing
- Resolution: 352x160=56,320 pixels
- Pixels are grouped into 44x20=880 tiles (8x8=64 pixels each)
- Smallest addressable unit: row of pixels inside of a tile (8 pixels = 1 byte)
- The brightness can only be set per tile
- Screen content can be changed using a simple UDP protocol
- Between each row of tiles, there is a gap of around 4 pixels size. This gap changes the aspect ratio of the display.
### Binary format
A UDP package sent to the display has a header size of 10 bytes.
Each header value has a size of two bytes (unsigned 16 bit integer).
Depending on the command, there can be a payload following the header.
The commands are implemented in DisplayCommands.
To change screen contents, these commands are the most relevant:
1. Clear screen
- command: `0x0002`
- (rest does not matter)
2. Send CP437 data: render specified text into rectangular region
- command: `0x0003`
- top left tile x
- top left tile y
- width in tiles
- height in tiles
- payload: (width in tiles * height in tiles) bytes
- 1 byte = 1 character
- each character is rendered into one tile (mono-spaced)
- characters are encoded using code page 437
3. Send bitmap window: set pixel states for a rectangular region
- command: `0x0013`
- top left tile x
- top left _pixel_ y
- width in tiles
- height in _pixels_
- payload: (width in tiles * height in pixels) bytes
- network byte order
- 1 bit = 1 pixel
There are other commands implemented as well, e.g. for changing the brightness.
See [CONTRIBUTING.md](CONTRIBUTING.md).
## What happened to servicepoint2?
After `servicepoint2` has been merged into `servicepoint`, `servicepoint2` will not continue to get any updates.
## Contributing
Contributions are accepted in any form (issues, documentation, feature requests, code, review, ...).
All creatures welcome.

41
about_display.md Normal file
View file

@ -0,0 +1,41 @@
# About the display
- Resolution: 352x160=56,320 pixels
- Pixels are grouped into 44x20=880 tiles (8x8=64 pixels each)
- Smallest addressable unit: row of pixels inside of a tile (8 pixels = 1 byte)
- The brightness can only be set per tile
- Screen content can be changed using a simple UDP protocol
- Between each row of tiles, there is a gap of around 4 pixels size. This gap changes the aspect ratio of the display.
### Binary format
A UDP package sent to the display has a header size of 10 bytes.
Each header value has a size of two bytes (unsigned 16 bit integer).
Depending on the command, there can be a payload following the header.
To change screen contents, these commands are the most relevant:
1. Clear screen
- command: `0x0002`
- (rest does not matter)
2. Send CP437 data: render specified text into rectangular region
- command: `0x0003`
- top left tile x
- top left tile y
- width in tiles
- height in tiles
- payload: (width in tiles * height in tiles) bytes
- 1 byte = 1 character
- each character is rendered into one tile (mono-spaced)
- characters are encoded using code page 437
3. Send bitmap window: set pixel states for a rectangular region
- command: `0x0013`
- top left tile x
- top left _pixel_ y
- width in tiles
- height in _pixels_
- payload: (width in tiles * height in pixels) bytes
- network byte order
- 1 bit = 1 pixel
There are other commands implemented as well, e.g. for changing the brightness.

View file

@ -46,7 +46,7 @@ In the likely case you only need one of them, you can include that one specifica
```toml
[dependencies]
servicepoint = { version = "0.7.0", default-features = false, features = ["compression-bz"] }
servicepoint = { version = "0.8.0", default-features = false, features = ["compression-bz"] }
```
## Everything else

View file

@ -190,10 +190,15 @@ impl TryFrom<Packet> for Command {
/// Try to interpret the `Packet` as one containing a `Command`
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
let Packet(Header(command_u16, a, _, _, _), _) = packet;
let command_code = match CommandCode::try_from(command_u16) {
let Packet {
header: Header {
command_code, a, ..
},
..
} = packet;
let command_code = match CommandCode::try_from(command_code) {
Err(()) => {
return Err(TryFromPacketError::InvalidCommand(command_u16));
return Err(TryFromPacketError::InvalidCommand(command_code));
}
Ok(value) => value,
};
@ -266,8 +271,17 @@ impl Command {
packet: Packet,
compression: CompressionCode,
) -> Result<Command, TryFromPacketError> {
let Packet(Header(_, tiles_x, pixels_y, tile_w, pixel_h), payload) =
packet;
let Packet {
header:
Header {
command_code: _,
a: tiles_x,
b: pixels_y,
c: tile_w,
d: pixel_h,
},
payload,
} = packet;
let payload = match into_decompressed(compression, payload) {
None => return Err(TryFromPacketError::DecompressionFailed),
@ -290,7 +304,17 @@ impl Command {
packet: Packet,
command: Command,
) -> Result<Command, TryFromPacketError> {
let Packet(Header(_, a, b, c, d), payload) = packet;
let Packet {
header:
Header {
command_code: _,
a,
b,
c,
d,
},
payload,
} = packet;
if !payload.is_empty() {
Err(TryFromPacketError::UnexpectedPayloadSize(0, payload.len()))
} else if a != 0 || b != 0 || c != 0 || d != 0 {
@ -304,7 +328,16 @@ impl Command {
fn packet_into_linear_bitmap(
packet: Packet,
) -> Result<(SpBitVec, CompressionCode), TryFromPacketError> {
let Packet(Header(_, _, length, sub, reserved), payload) = packet;
let Packet {
header:
Header {
b: length,
c: sub,
d: reserved,
..
},
payload,
} = packet;
if reserved != 0 {
return Err(TryFromPacketError::ExtraneousHeaderValues);
}
@ -330,7 +363,17 @@ impl Command {
fn packet_into_char_brightness(
packet: &Packet,
) -> Result<Command, TryFromPacketError> {
let Packet(Header(_, x, y, width, height), payload) = packet;
let Packet {
header:
Header {
command_code: _,
a: x,
b: y,
c: width,
d: height,
},
payload,
} = packet;
let grid =
PrimitiveGrid::load(*width as usize, *height as usize, payload);
@ -348,7 +391,17 @@ impl Command {
fn packet_into_brightness(
packet: &Packet,
) -> Result<Command, TryFromPacketError> {
let Packet(Header(_, a, b, c, d), payload) = packet;
let Packet {
header:
Header {
command_code: _,
a,
b,
c,
d,
},
payload,
} = packet;
if payload.len() != 1 {
return Err(TryFromPacketError::UnexpectedPayloadSize(
1,
@ -369,7 +422,17 @@ impl Command {
fn packet_into_cp437(
packet: &Packet,
) -> Result<Command, TryFromPacketError> {
let Packet(Header(_, a, b, c, d), payload) = packet;
let Packet {
header:
Header {
command_code: _,
a,
b,
c,
d,
},
payload,
} = packet;
Ok(Command::Cp437Data(
Origin::new(*a as usize, *b as usize),
Cp437Grid::load(*c as usize, *d as usize, payload),
@ -483,7 +546,16 @@ mod tests {
#[test]
fn error_invalid_command() {
let p = Packet(Header(0xFF, 0x00, 0x00, 0x00, 0x00), vec![]);
let p = Packet {
header: Header {
command_code: 0xFF,
a: 0x00,
b: 0x00,
c: 0x00,
d: 0x00,
},
payload: vec![],
};
let result = Command::try_from(p);
assert!(matches!(
result,
@ -493,10 +565,16 @@ mod tests {
#[test]
fn error_extraneous_header_values_clear() {
let p = Packet(
Header(CommandCode::Clear.into(), 0x05, 0x00, 0x00, 0x00),
vec![],
);
let p = Packet {
header: Header {
command_code: CommandCode::Clear.into(),
a: 0x05,
b: 0x00,
c: 0x00,
d: 0x00,
},
payload: vec![],
};
let result = Command::try_from(p);
assert!(matches!(
result,
@ -506,10 +584,16 @@ mod tests {
#[test]
fn error_extraneous_header_values_brightness() {
let p = Packet(
Header(CommandCode::Brightness.into(), 0x00, 0x13, 0x37, 0x00),
vec![5],
);
let p = Packet {
header: Header {
command_code: CommandCode::Brightness.into(),
a: 0x00,
b: 0x13,
c: 0x37,
d: 0x00,
},
payload: vec![5],
};
let result = Command::try_from(p);
assert!(matches!(
result,
@ -519,10 +603,16 @@ mod tests {
#[test]
fn error_extraneous_header_hard_reset() {
let p = Packet(
Header(CommandCode::HardReset.into(), 0x00, 0x00, 0x00, 0x01),
vec![],
);
let p = Packet {
header: Header {
command_code: CommandCode::HardReset.into(),
a: 0x00,
b: 0x00,
c: 0x00,
d: 0x01,
},
payload: vec![],
};
let result = Command::try_from(p);
assert!(matches!(
result,
@ -532,10 +622,16 @@ mod tests {
#[test]
fn error_extraneous_header_fade_out() {
let p = Packet(
Header(CommandCode::FadeOut.into(), 0x10, 0x00, 0x00, 0x01),
vec![],
);
let p = Packet {
header: Header {
command_code: CommandCode::FadeOut.into(),
a: 0x10,
b: 0x00,
c: 0x00,
d: 0x01,
},
payload: vec![],
};
let result = Command::try_from(p);
assert!(matches!(
result,
@ -545,10 +641,16 @@ mod tests {
#[test]
fn error_unexpected_payload() {
let p = Packet(
Header(CommandCode::FadeOut.into(), 0x00, 0x00, 0x00, 0x00),
vec![5, 7],
);
let p = Packet {
header: Header {
command_code: CommandCode::FadeOut.into(),
a: 0x00,
b: 0x00,
c: 0x00,
d: 0x00,
},
payload: vec![5, 7],
};
let result = Command::try_from(p);
assert!(matches!(
result,
@ -565,14 +667,18 @@ mod tests {
compression,
)
.into();
let Packet(header, mut payload) = p;
let Packet {
header,
mut payload,
} = p;
// mangle it
for byte in payload.iter_mut() {
*byte -= *byte / 2;
}
let p = Packet(header, payload);
let p = Packet { header, payload };
let result = Command::try_from(p);
if compression != CompressionCode::Uncompressed {
assert_eq!(result, Err(TryFromPacketError::DecompressionFailed))
@ -591,14 +697,17 @@ mod tests {
compression,
)
.into();
let Packet(header, mut payload) = p;
let Packet {
header,
mut payload,
} = p;
// mangle it
for byte in payload.iter_mut() {
*byte -= *byte / 2;
}
let p = Packet(header, payload);
let p = Packet { header, payload };
let result = Command::try_from(p);
if compression != CompressionCode::Uncompressed {
assert_eq!(result, Err(TryFromPacketError::DecompressionFailed))
@ -612,32 +721,59 @@ mod tests {
#[test]
fn unexpected_payload_size_brightness() {
assert_eq!(
Command::try_from(Packet(
Header(CommandCode::Brightness.into(), 0, 0, 0, 0),
vec!(),
)),
Command::try_from(Packet {
header: Header {
command_code: CommandCode::Brightness.into(),
a: 0,
b: 0,
c: 0,
d: 0,
},
payload: vec!()
}),
Err(TryFromPacketError::UnexpectedPayloadSize(1, 0))
);
assert_eq!(
Command::try_from(Packet(
Header(CommandCode::Brightness.into(), 0, 0, 0, 0),
vec!(0, 0),
)),
Command::try_from(Packet {
header: Header {
command_code: CommandCode::Brightness.into(),
a: 0,
b: 0,
c: 0,
d: 0,
},
payload: vec!(0, 0)
}),
Err(TryFromPacketError::UnexpectedPayloadSize(1, 2))
);
}
#[test]
fn error_reserved_used() {
let Packet(header, payload) = Command::BitmapLinear(
let Packet { header, payload } = Command::BitmapLinear(
0,
BitVec::repeat(false, 8),
CompressionCode::Uncompressed,
)
.into();
let Header(command, offset, length, sub, _reserved) = header;
let p = Packet(Header(command, offset, length, sub, 69), payload);
let Header {
command_code: command,
a: offset,
b: length,
c: sub,
d: _reserved,
} = header;
let p = Packet {
header: Header {
command_code: command,
a: offset,
b: length,
c: sub,
d: 69,
},
payload,
};
assert_eq!(
Command::try_from(p),
Err(TryFromPacketError::ExtraneousHeaderValues)
@ -646,14 +782,29 @@ mod tests {
#[test]
fn error_invalid_compression() {
let Packet(header, payload) = Command::BitmapLinear(
let Packet { header, payload } = Command::BitmapLinear(
0,
BitVec::repeat(false, 8),
CompressionCode::Uncompressed,
)
.into();
let Header(command, offset, length, _sub, reserved) = header;
let p = Packet(Header(command, offset, length, 42, reserved), payload);
let Header {
command_code: command,
a: offset,
b: length,
c: _sub,
d: reserved,
} = header;
let p = Packet {
header: Header {
command_code: command,
a: offset,
b: length,
c: 42,
d: reserved,
},
payload,
};
assert_eq!(
Command::try_from(p),
Err(TryFromPacketError::InvalidCompressionCode(42))
@ -662,17 +813,29 @@ mod tests {
#[test]
fn error_unexpected_size() {
let Packet(header, payload) = Command::BitmapLinear(
let Packet { header, payload } = Command::BitmapLinear(
0,
BitVec::repeat(false, 8),
CompressionCode::Uncompressed,
)
.into();
let Header(command, offset, length, compression, reserved) = header;
let p = Packet(
Header(command, offset, 420, compression, reserved),
let Header {
command_code: command,
a: offset,
b: length,
c: compression,
d: reserved,
} = header;
let p = Packet {
header: Header {
command_code: command,
a: offset,
b: 420,
c: compression,
d: reserved,
},
payload,
);
};
assert_eq!(
Command::try_from(p),
Err(TryFromPacketError::UnexpectedPayloadSize(

View file

@ -9,14 +9,21 @@ use crate::Packet;
///
/// # Examples
/// ```rust
/// # use servicepoint::Command;
/// let connection = servicepoint::Connection::open("172.23.42.29:2342")
/// .expect("connection failed");
/// connection.send(Command::Clear)
/// connection.send(servicepoint::Command::Clear)
/// .expect("send failed");
/// ```
pub struct Connection {
socket: UdpSocket,
pub enum Connection {
/// A real connection using the UDP protocol
Udp(UdpSocket),
/// A fake connection for testing that does not actually send anything
Fake,
}
#[derive(Debug)]
pub enum SendError {
IoError(std::io::Error),
}
impl Connection {
@ -37,39 +44,37 @@ impl Connection {
info!("connecting to {addr:?}");
let socket = UdpSocket::bind("0.0.0.0:0")?;
socket.connect(addr)?;
Ok(Self { socket })
Ok(Self::Udp(socket))
}
/// Send something packet-like to the display. Usually this is in the form of a Command.
///
/// # Arguments
///
/// * `packet`: the packet-like to send
/// - `packet`: the packet-like to send
///
/// returns: Ok if packet was sent, otherwise socket error
///
/// # Errors
///
/// Any errors produced while sending using the underlying socket.
/// returns: true if packet was sent, otherwise false
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{Command, CompressionCode, Grid, PixelGrid};
/// # let connection = servicepoint::Connection::open("172.23.42.29:2342")
/// # .expect("connection failed");
/// # let connection = servicepoint::Connection::Fake;
/// // turn off all pixels on display
/// connection.send(Command::Clear)
/// connection.send(servicepoint::Command::Clear)
/// .expect("send failed");
/// ```
pub fn send(
&self,
packet: impl Into<Packet>,
) -> Result<(), std::io::Error> {
pub fn send(&self, packet: impl Into<Packet>) -> Result<(), SendError> {
let packet = packet.into();
debug!("sending {packet:?}");
let data: Vec<u8> = packet.into();
self.socket.send(&data)?;
Ok(())
match self {
Connection::Udp(socket) => {
socket
.send(&data)
.map_err(SendError::IoError)
.map(move |_| ()) // ignore Ok value
}
Connection::Fake => Ok(()),
}
}
}

View file

@ -4,7 +4,7 @@ pub trait Grid<T> {
///
/// # Arguments
///
/// * `x` and `y`: position of the cell to read
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
@ -15,7 +15,7 @@ pub trait Grid<T> {
///
/// # Arguments
///
/// * `x` and `y`: position of the cell to read
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
@ -26,7 +26,7 @@ pub trait Grid<T> {
///
/// # Arguments
///
/// * `x` and `y`: position of the cell to read
/// - `x` and `y`: position of the cell to read
///
/// returns: Value at position or None
fn get_optional(&self, x: isize, y: isize) -> Option<T> {
@ -41,7 +41,7 @@ pub trait Grid<T> {
///
/// # Arguments
///
/// * `x` and `y`: position of the cell to read
/// - `x` and `y`: position of the cell to read
///
/// returns: the old value or None
fn set_optional(&mut self, x: isize, y: isize, value: T) -> bool {

View file

@ -3,25 +3,84 @@ use std::mem::size_of;
use crate::command_code::CommandCode;
use crate::compression::into_compressed;
use crate::{
Command, CompressionCode, Grid, Offset, Origin, PixelGrid, Pixels,
Command, CompressionCode, Grid, Offset, Origin, PixelGrid, Pixels, Tiles,
TILE_SIZE,
};
/// A raw header. Should probably not be used directly.
#[derive(Debug, PartialEq)]
pub struct Header(pub u16, pub u16, pub u16, pub u16, pub u16);
/// A raw header.
///
/// The header specifies the kind of command, the size of the payload and where to display the
/// payload, where applicable.
///
/// Because the meaning of most fields depend on the command, there are no speaking names for them.
///
/// Should probably only be used directly to use features not exposed by the library.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Header {
/// The first two bytes specify which command this packet represents.
pub command_code: u16,
/// First command-specific value
pub a: u16,
/// Second command-specific value
pub b: u16,
/// Third command-specific value
pub c: u16,
/// Fourth command-specific value
pub d: u16,
}
/// The raw payload. Should probably not be used directly.
/// The raw payload.
///
/// Should probably only be used directly to use features not exposed by the library.
pub type Payload = Vec<u8>;
/// The raw packet. Should probably not be used directly.
#[derive(Debug, PartialEq)]
pub struct Packet(pub Header, pub Payload);
/// The raw packet.
///
/// Contents should probably only be used directly to use features not exposed by the library.
///
/// # Examples
///
/// Converting a packet to a command and back:
///
/// ```rust
/// # use servicepoint::{Command, Packet};
/// # let command = Command::Clear;
/// let packet: Packet = command.into();
/// let command: Command = Command::try_from(packet).expect("could not read packet");
/// ```
///
/// Converting a packet to bytes and back:
///
/// ```rust
/// # use servicepoint::{Command, Packet};
/// # let command = Command::Clear;
/// # let packet: Packet = command.into();
/// let bytes: Vec<u8> = packet.into();
/// let packet = Packet::try_from(bytes).expect("could not read packet from bytes");
/// ```
///
#[derive(Clone, Debug, PartialEq)]
pub struct Packet {
/// Meta-information for the packed command
pub header: Header,
/// The data for the packed command
pub payload: Payload,
}
impl From<Packet> for Vec<u8> {
/// Turn the packet into raw bytes ready to send
fn from(value: Packet) -> Self {
let Packet(Header(mode, a, b, c, d), payload) = value;
let Packet {
header:
Header {
command_code: mode,
a,
b,
c,
d,
},
payload,
} = value;
let mut packet = vec![0u8; 10 + payload.len()];
packet[0..=1].copy_from_slice(&u16::to_be_bytes(mode));
@ -36,13 +95,6 @@ impl From<Packet> for Vec<u8> {
}
}
fn u16_from_be_slice(slice: &[u8]) -> u16 {
let mut bytes = [0u8; 2];
bytes[0] = slice[0];
bytes[1] = slice[1];
u16::from_be_bytes(bytes)
}
impl TryFrom<&[u8]> for Packet {
type Error = ();
@ -54,14 +106,31 @@ impl TryFrom<&[u8]> for Packet {
return Err(());
}
let mode = u16_from_be_slice(&value[0..=1]);
let a = u16_from_be_slice(&value[2..=3]);
let b = u16_from_be_slice(&value[4..=5]);
let c = u16_from_be_slice(&value[6..=7]);
let d = u16_from_be_slice(&value[8..=9]);
let header = {
let command_code = Self::u16_from_be_slice(&value[0..=1]);
let a = Self::u16_from_be_slice(&value[2..=3]);
let b = Self::u16_from_be_slice(&value[4..=5]);
let c = Self::u16_from_be_slice(&value[6..=7]);
let d = Self::u16_from_be_slice(&value[8..=9]);
Header {
command_code,
a,
b,
c,
d,
}
};
let payload = value[10..].to_vec();
Ok(Packet(Header(mode, a, b, c, d), payload))
Ok(Packet { header, payload })
}
}
impl TryFrom<Vec<u8>> for Packet {
type Error = ();
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Self::try_from(value.as_slice())
}
}
@ -79,26 +148,23 @@ impl From<Command> for Packet {
Command::BitmapLegacy => {
Self::command_code_only(CommandCode::BitmapLegacy)
}
Command::CharBrightness(origin, grid) => Packet(
Header(
CommandCode::CharBrightness.into(),
origin.x as u16,
origin.y as u16,
grid.width() as u16,
grid.height() as u16,
),
grid.into(),
),
Command::Brightness(brightness) => Packet(
Header(
CommandCode::Brightness.into(),
0x00000,
0x0000,
0x0000,
0x0000,
),
vec![brightness.into()],
),
Command::CharBrightness(origin, grid) => {
Self::origin_grid_to_packet(
origin,
grid,
CommandCode::CharBrightness,
)
}
Command::Brightness(brightness) => Packet {
header: Header {
command_code: CommandCode::Brightness.into(),
a: 0x00000,
b: 0x0000,
c: 0x0000,
d: 0x0000,
},
payload: vec![brightness.into()],
},
Command::BitmapLinearWin(origin, pixels, compression) => {
Self::bitmap_win_into_packet(origin, pixels, compression)
}
@ -134,15 +200,10 @@ impl From<Command> for Packet {
bits.into(),
)
}
Command::Cp437Data(origin, grid) => Packet(
Header(
CommandCode::Cp437Data.into(),
origin.x as u16,
origin.y as u16,
grid.width() as u16,
grid.height() as u16,
),
grid.into(),
Command::Cp437Data(origin, grid) => Self::origin_grid_to_packet(
origin,
grid,
CommandCode::Cp437Data,
),
}
}
@ -159,16 +220,16 @@ impl Packet {
) -> Packet {
let length = payload.len() as u16;
let payload = into_compressed(compression, payload);
Packet(
Header(
command.into(),
offset as u16,
length,
compression.into(),
0,
),
Packet {
header: Header {
command_code: command.into(),
a: offset as u16,
b: length,
c: compression.into(),
d: 0,
},
payload,
)
}
}
#[allow(clippy::cast_possible_truncation)]
@ -198,15 +259,54 @@ impl Packet {
CompressionCode::Zstd => CommandCode::BitmapLinearWinZstd,
};
Packet(
Header(command.into(), tile_x, origin.y as u16, tile_w, pixel_h),
Packet {
header: Header {
command_code: command.into(),
a: tile_x,
b: origin.y as u16,
c: tile_w,
d: pixel_h,
},
payload,
)
}
}
/// Helper method for creating empty packets only containing the command code
fn command_code_only(code: CommandCode) -> Packet {
Packet(Header(code.into(), 0x0000, 0x0000, 0x0000, 0x0000), vec![])
Packet {
header: Header {
command_code: code.into(),
a: 0x0000,
b: 0x0000,
c: 0x0000,
d: 0x0000,
},
payload: vec![],
}
}
fn u16_from_be_slice(slice: &[u8]) -> u16 {
let mut bytes = [0u8; 2];
bytes[0] = slice[0];
bytes[1] = slice[1];
u16::from_be_bytes(bytes)
}
fn origin_grid_to_packet<T>(
origin: Origin<Tiles>,
grid: impl Grid<T> + Into<Payload>,
command_code: CommandCode,
) -> Packet {
Packet {
header: Header {
command_code: command_code.into(),
a: origin.x as u16,
b: origin.y as u16,
c: grid.width() as u16,
d: grid.height() as u16,
},
payload: grid.into(),
}
}
}
@ -216,10 +316,31 @@ mod tests {
#[test]
fn round_trip() {
let p = Packet(Header(0, 1, 2, 3, 4), vec![42u8; 23]);
let p = Packet {
header: Header {
command_code: 0,
a: 1,
b: 2,
c: 3,
d: 4,
},
payload: vec![42u8; 23],
};
let data: Vec<u8> = p.into();
let p = Packet::try_from(&*data).unwrap();
assert_eq!(p, Packet(Header(0, 1, 2, 3, 4), vec![42u8; 23]));
assert_eq!(
p,
Packet {
header: Header {
command_code: 0,
a: 1,
b: 2,
c: 3,
d: 4
},
payload: vec![42u8; 23]
}
);
}
#[test]

View file

@ -17,8 +17,8 @@ impl PixelGrid {
///
/// # Arguments
///
/// * `width`: size in pixels in x-direction
/// * `height`: size in pixels in y-direction
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: `PixelGrid` initialized to all pixels off
///
@ -44,8 +44,8 @@ impl PixelGrid {
///
/// # Arguments
///
/// * `width`: size in pixels in x-direction
/// * `height`: size in pixels in y-direction
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: `PixelGrid` that contains a copy of the provided data
///
@ -121,8 +121,8 @@ impl Grid<bool> for PixelGrid {
///
/// # Arguments
///
/// * `x` and `y`: position of the cell
/// * `value`: the value to write to the cell
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
@ -143,8 +143,8 @@ impl Grid<bool> for PixelGrid {
///
/// # Arguments
///
/// * `this`: instance to write to
/// * `value`: the value to set all pixels to
/// - `this`: instance to write to
/// - `value`: the value to set all pixels to
fn fill(&mut self, value: bool) {
self.bit_vec.fill(value);
}

View file

@ -82,7 +82,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
///
/// # Arguments
///
/// * `x` and `y`: position of the cell
/// - `x` and `y`: position of the cell
///
/// # Panics
///
@ -96,7 +96,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
///
/// # Arguments
///
/// * `x` and `y`: position of the cell
/// - `x` and `y`: position of the cell
///
/// returns: Reference to cell or None
pub fn get_ref_mut_optional(
@ -117,8 +117,8 @@ impl<T: PrimitiveGridType> Grid<T> for PrimitiveGrid<T> {
///
/// # Arguments
///
/// * `x` and `y`: position of the cell
/// * `value`: the value to write to the cell
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// # Panics
///
@ -132,7 +132,7 @@ impl<T: PrimitiveGridType> Grid<T> for PrimitiveGrid<T> {
///
/// # Arguments
///
/// * `x` and `y`: position of the cell to read
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///

View file

@ -14,10 +14,10 @@ links = "servicepoint"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
cbindgen = "0.26.0"
cbindgen = "0.27.0"
[dependencies.servicepoint]
version = "0.7.0"
version = "0.8.0"
path = "../servicepoint"
features = ["all_compressions"]

View file

@ -17,19 +17,18 @@ This crate contains C bindings for the `servicepoint` library, enabling users to
#include "servicepoint.h"
int main(void) {
sp_Connection *connection = sp_connection_open("localhost:2342");
SPConnection *connection = sp_connection_open("172.23.42.29:2342");
if (connection == NULL)
return 1;
sp_PixelGrid *pixels = sp_pixel_grid_new(sp_PIXEL_WIDTH, sp_PIXEL_HEIGHT);
SPPixelGrid *pixels = sp_pixel_grid_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
sp_pixel_grid_fill(pixels, true);
sp_Command *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
sp_Packet *packet = sp_packet_from_command(command);
if (!sp_connection_send(connection, packet))
return 1;
SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
while (sp_connection_send_command(connection, sp_command_clone(command)));
sp_connection_dealloc(connection);
sp_command_free(command);
sp_connection_free(connection);
return 0;
}
```
@ -53,7 +52,7 @@ You have the choice of linking statically (recommended) or dynamically.
## Notes on differences to rust library
- function names are: `sp_` \<struct_name\> \<rust name\>.
- Instances get consumed in the same way they do when writing rust / C# code. Do not use an instance after an (implicit!) free.
- Instances get consumed in the same way they do when writing rust code. Do not use an instance after an (implicit!) free.
- Option<T> or Result<T, E> turn into nullable return values - check for NULL!
- There are no specifics for C++ here yet. You might get a nicer header when generating directly for C++, but it should be usable.
- Reading and writing to instances concurrently is not safe. Only reading concurrently is safe.

View file

@ -2,6 +2,8 @@ language = "C"
include_version = true
cpp_compat = true
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
############################ Code Style Options ################################
braces = "SameLine"
@ -15,22 +17,17 @@ line_endings = "LF"
############################# Codegen Options ##################################
style = "both"
sort_by = "Name"
usize_is_size_t = true
[defines]
#"feature = compression_zlib" = "SP_FEATURE_compression_zlib"
#"feature = compression_bzip2" = "SP_FEATURE_compression_bzip2"
#"feature = compression_lzma" = "SP_FEATURE_compression_lzma"
#"feature = compression_zstd" = "SP_FEATURE_compression_zstd"
[export]
prefix = "sp_"
# this is needed because otherwise the order in the C# bindings is different on different machines
sort_by = "Name"
[parse]
parse_deps = true
include = ["servicepoint"]
extra_bindings = ["servicepoint"]
parse_deps = false
[parse.expand]
all_features = true
[export]
include = []
exclude = []

View file

@ -6,8 +6,9 @@ REPO_ROOT := $(THIS_DIR)/../../../..
build: out/lang_c
clean:
rm -r out
rm include/servicepoint.h
rm -r out || true
rm include/servicepoint.h || true
cargo clean
run: out/lang_c
out/lang_c

View file

@ -2,18 +2,17 @@
#include "servicepoint.h"
int main(void) {
sp_Connection *connection = sp_connection_open("localhost:2342");
SPConnection *connection = sp_connection_open("172.23.42.29:2342");
if (connection == NULL)
return 1;
sp_PixelGrid *pixels = sp_pixel_grid_new(sp_PIXEL_WIDTH, sp_PIXEL_HEIGHT);
SPPixelGrid *pixels = sp_pixel_grid_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
sp_pixel_grid_fill(pixels, true);
sp_Command *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
sp_Packet *packet = sp_packet_from_command(command);
if (!sp_connection_send(connection, packet))
return 1;
SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
while (sp_connection_send_command(connection, sp_command_clone(command)));
sp_connection_dealloc(connection);
sp_command_free(command);
sp_connection_free(connection);
return 0;
}

View file

@ -1,38 +1,45 @@
//! C functions for interacting with `BitVec`s
//! C functions for interacting with `SPBitVec`s
//!
//! prefix `sp_bit_vec_`
use crate::c_slice::CByteSlice;
use crate::SPByteSlice;
use servicepoint::bitvec::prelude::{BitVec, Msb0};
/// cbindgen:no-export
type SpBitVec = BitVec<u8, Msb0>;
/// A vector of bits
///
/// # Examples
/// ```C
/// SPBitVec vec = sp_bit_vec_new(8);
/// sp_bit_vec_set(vec, 5, true);
/// sp_bit_vec_free(vec);
/// ```
pub struct SPBitVec(BitVec<u8, Msb0>);
/// Opaque struct needed for correct code generation.
#[derive(Clone)]
pub struct CBitVec {
actual: SpBitVec,
}
impl From<SpBitVec> for CBitVec {
fn from(actual: SpBitVec) -> Self {
Self { actual }
impl From<BitVec<u8, Msb0>> for SPBitVec {
fn from(actual: BitVec<u8, Msb0>) -> Self {
Self(actual)
}
}
impl From<CBitVec> for SpBitVec {
fn from(value: CBitVec) -> Self {
value.actual
impl From<SPBitVec> for BitVec<u8, Msb0> {
fn from(value: SPBitVec) -> Self {
value.0
}
}
/// Creates a new `BitVec` instance.
impl Clone for SPBitVec {
fn clone(&self) -> Self {
SPBitVec(self.0.clone())
}
}
/// Creates a new `SPBitVec` instance.
///
/// # Arguments
///
/// * `size`: size in bits.
/// - `size`: size in bits.
///
/// returns: `BitVec` with all bits set to false.
/// returns: `SPBitVec` with all bits set to false. Will never return NULL.
///
/// # Panics
///
@ -43,15 +50,13 @@ impl From<CBitVec> for SpBitVec {
/// 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_bit_vec_dealloc`.
/// by explicitly calling `sp_bit_vec_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bit_vec_new(size: usize) -> *mut CBitVec {
Box::into_raw(Box::new(CBitVec {
actual: SpBitVec::repeat(false, size),
}))
pub unsafe extern "C" fn sp_bit_vec_new(size: usize) -> *mut SPBitVec {
Box::into_raw(Box::new(SPBitVec(BitVec::repeat(false, size))))
}
/// Interpret the data as a series of bits and load then into a new `BitVec` instance.
/// Interpret the data as a series of bits and load then into a new `SPBitVec` instance.
///
/// # Safety
///
@ -60,55 +65,53 @@ pub unsafe extern "C" fn sp_bit_vec_new(size: usize) -> *mut CBitVec {
/// - `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_bit_vec_dealloc`.
/// by explicitly calling `sp_bit_vec_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bit_vec_load(
data: *const u8,
data_length: usize,
) -> *mut CBitVec {
) -> *mut SPBitVec {
let data = std::slice::from_raw_parts(data, data_length);
Box::into_raw(Box::new(CBitVec {
actual: SpBitVec::from_slice(data),
}))
Box::into_raw(Box::new(SPBitVec(BitVec::from_slice(data))))
}
/// Clones a `BitVec`.
/// Clones a `SPBitVec`.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BitVec`
/// - `this` is not written to concurrently
/// - `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_bit_vec_dealloc`.
/// by explicitly calling `sp_bit_vec_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bit_vec_clone(
this: *const CBitVec,
) -> *mut CBitVec {
Box::into_raw(Box::new((*this).clone()))
bit_vec: *const SPBitVec,
) -> *mut SPBitVec {
Box::into_raw(Box::new((*bit_vec).clone()))
}
/// Deallocates a `BitVec`.
/// Deallocates a `SPBitVec`.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BitVec`
/// - `this` is not used concurrently or after this call
/// - `this` was not passed to another consuming function, e.g. to create a `Command`
/// - `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`
#[no_mangle]
pub unsafe extern "C" fn sp_bit_vec_dealloc(this: *mut CBitVec) {
_ = Box::from_raw(this);
pub unsafe extern "C" fn sp_bit_vec_free(bit_vec: *mut SPBitVec) {
_ = Box::from_raw(bit_vec);
}
/// Gets the value of a bit from the `BitVec`.
/// Gets the value of a bit from the `SPBitVec`.
///
/// # Arguments
///
/// * `this`: instance to read from
/// * `index`: the bit index to read
/// - `bit_vec`: instance to read from
/// - `index`: the bit index to read
///
/// returns: value of the bit
///
@ -120,23 +123,23 @@ pub unsafe extern "C" fn sp_bit_vec_dealloc(this: *mut CBitVec) {
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BitVec`
/// - `this` is not written to concurrently
/// - `bit_vec` points to a valid `SPBitVec`
/// - `bit_vec` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bit_vec_get(
this: *const CBitVec,
bit_vec: *const SPBitVec,
index: usize,
) -> bool {
*(*this).actual.get(index).unwrap()
*(*bit_vec).0.get(index).unwrap()
}
/// Sets the value of a bit in the `BitVec`.
/// Sets the value of a bit in the `SPBitVec`.
///
/// # Arguments
///
/// * `this`: instance to write to
/// * `index`: the bit index to edit
/// * `value`: the value to set the bit to
/// - `bit_vec`: instance to write to
/// - `index`: the bit index to edit
/// - `value`: the value to set the bit to
///
/// returns: old value of the bit
///
@ -148,73 +151,86 @@ pub unsafe extern "C" fn sp_bit_vec_get(
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BitVec`
/// - `this` is not written to or read from concurrently
/// - `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_bit_vec_set(
this: *mut CBitVec,
bit_vec: *mut SPBitVec,
index: usize,
value: bool,
) {
(*this).actual.set(index, value)
(*bit_vec).0.set(index, value)
}
/// Sets the value of all bits in the `BitVec`.
/// Sets the value of all bits in the `SPBitVec`.
///
/// # Arguments
///
/// * `value`: the value to set all bits to
/// - `bit_vec`: instance to write to
/// - `value`: the value to set all bits to
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BitVec`
/// - `this` is not written to or read from concurrently
/// - `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_bit_vec_fill(this: *mut CBitVec, value: bool) {
(*this).actual.fill(value)
pub unsafe extern "C" fn sp_bit_vec_fill(bit_vec: *mut SPBitVec, value: bool) {
(*bit_vec).0.fill(value)
}
/// Gets the length of the `BitVec` in bits.
/// Gets the length of the `SPBitVec` in bits.
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BitVec`
/// - `bit_vec` points to a valid `SPBitVec`
#[no_mangle]
pub unsafe extern "C" fn sp_bit_vec_len(this: *const CBitVec) -> usize {
(*this).actual.len()
pub unsafe extern "C" fn sp_bit_vec_len(bit_vec: *const SPBitVec) -> usize {
(*bit_vec).0.len()
}
/// Returns true if length is 0.
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BitVec`
/// - `bit_vec` points to a valid `SPBitVec`
#[no_mangle]
pub unsafe extern "C" fn sp_bit_vec_is_empty(this: *const CBitVec) -> bool {
(*this).actual.is_empty()
pub unsafe extern "C" fn sp_bit_vec_is_empty(bit_vec: *const SPBitVec) -> bool {
(*bit_vec).0.is_empty()
}
/// Gets an unsafe reference to the data of the `BitVec` instance.
/// Gets an unsafe reference to the data of the `SPBitVec` instance.
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BitVec`
/// - the returned memory range is never accessed after the passed `BitVec` has been freed
/// - the returned memory range is never accessed concurrently, either via the `BitVec` or directly
/// - `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_bit_vec_unsafe_data_ref(
this: *mut CBitVec,
) -> CByteSlice {
let data = (*this).actual.as_raw_mut_slice();
CByteSlice {
bit_vec: *mut SPBitVec,
) -> SPByteSlice {
let data = (*bit_vec).0.as_raw_mut_slice();
SPByteSlice {
start: data.as_mut_ptr_range().start,
length: data.len(),
}

View file

@ -1,37 +1,55 @@
//! C functions for interacting with `BrightnessGrid`s
//! C functions for interacting with `SPBrightnessGrid`s
//!
//! prefix `sp_brightness_grid_`
use servicepoint::{Brightness, BrightnessGrid, DataRef, Grid, PrimitiveGrid};
use crate::SPByteSlice;
use servicepoint::{Brightness, DataRef, Grid, PrimitiveGrid};
use std::intrinsics::transmute;
use crate::c_slice::CByteSlice;
/// C-wrapper for grid containing brightness values.
#[derive(Clone)]
pub struct CBrightnessGrid(pub(crate) BrightnessGrid);
/// Creates a new `BrightnessGrid` with the specified dimensions.
/// A grid containing brightness values.
///
/// returns: `BrightnessGrid` initialized to 0.
/// # 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);
/// ```
pub struct SPBrightnessGrid(pub(crate) servicepoint::BrightnessGrid);
impl Clone for SPBrightnessGrid {
fn clone(&self) -> Self {
SPBrightnessGrid(self.0.clone())
}
}
/// Creates a new `SPBrightnessGrid` with the specified dimensions.
///
/// returns: `SPBrightnessGrid` initialized to 0. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_dealloc`.
/// by explicitly calling `sp_brightness_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_new(
width: usize,
height: usize,
) -> *mut CBrightnessGrid {
Box::into_raw(Box::new(CBrightnessGrid(BrightnessGrid::new(
width, height,
))))
) -> *mut SPBrightnessGrid {
Box::into_raw(Box::new(SPBrightnessGrid(
servicepoint::BrightnessGrid::new(width, height),
)))
}
/// Loads a `BrightnessGrid` with the specified dimensions from the provided data.
/// Loads a `SPBrightnessGrid` with the specified dimensions from the provided data.
///
/// # Panics
///
@ -44,60 +62,68 @@ pub unsafe extern "C" fn sp_brightness_grid_new(
/// - `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_dealloc`.
/// by explicitly calling `sp_brightness_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
) -> *mut CBrightnessGrid {
) -> *mut SPBrightnessGrid {
let data = std::slice::from_raw_parts(data, data_length);
let grid = PrimitiveGrid::load(width, height, data);
let grid =
BrightnessGrid::try_from(grid).expect("invalid brightness value");
Box::into_raw(Box::new(CBrightnessGrid(grid)))
let grid = servicepoint::BrightnessGrid::try_from(grid)
.expect("invalid brightness value");
Box::into_raw(Box::new(SPBrightnessGrid(grid)))
}
/// Clones a `BrightnessGrid`.
/// Clones a `SPBrightnessGrid`.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BrightnessGrid`
/// - `this` is not written to concurrently
/// - `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_dealloc`.
/// by explicitly calling `sp_brightness_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_clone(
this: *const CBrightnessGrid,
) -> *mut CBrightnessGrid {
Box::into_raw(Box::new((*this).clone()))
brightness_grid: *const SPBrightnessGrid,
) -> *mut SPBrightnessGrid {
Box::into_raw(Box::new((*brightness_grid).clone()))
}
/// Deallocates a `BrightnessGrid`.
/// Deallocates a `SPBrightnessGrid`.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BrightnessGrid`
/// - `this` is not used concurrently or after this call
/// - `this` was not passed to another consuming function, e.g. to create a `Command`
/// - `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`
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_dealloc(
this: *mut CBrightnessGrid,
pub unsafe extern "C" fn sp_brightness_grid_free(
brightness_grid: *mut SPBrightnessGrid,
) {
_ = Box::from_raw(this);
_ = Box::from_raw(brightness_grid);
}
/// Gets the current value at the specified position.
///
/// # Arguments
///
/// * `this`: instance to read from
/// * `x` and `y`: position of the cell to read
/// - `brightness_grid`: instance to read from
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
@ -107,126 +133,135 @@ pub unsafe extern "C" fn sp_brightness_grid_dealloc(
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BrightnessGrid`
/// - `this` is not written to concurrently
/// - `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(
this: *const CBrightnessGrid,
brightness_grid: *const SPBrightnessGrid,
x: usize,
y: usize,
) -> u8 {
(*this).0.get(x, y).into()
(*brightness_grid).0.get(x, y).into()
}
/// Sets the value of the specified position in the `BrightnessGrid`.
/// Sets the value of the specified position in the `SPBrightnessGrid`.
///
/// # Arguments
///
/// * `this`: instance to write to
/// * `x` and `y`: position of the cell
/// * `value`: the value to write to the cell
/// - `brightness_grid`: instance to write to
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
/// # Panics
///
/// When accessing `x` or `y` out of bounds.
/// - When accessing `x` or `y` out of bounds.
/// - When providing an invalid brightness value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BitVec`
/// - `this` is not written to or read from concurrently
/// - `brightness_grid` points to a valid `SPBitVec`
/// - `brightness_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_set(
this: *mut CBrightnessGrid,
brightness_grid: *mut SPBrightnessGrid,
x: usize,
y: usize,
value: u8,
) {
let brightness =
Brightness::try_from(value).expect("invalid brightness value");
(*this).0.set(x, y, brightness);
(*brightness_grid).0.set(x, y, brightness);
}
/// Sets the value of all cells in the `BrightnessGrid`.
/// Sets the value of all cells in the `SPBrightnessGrid`.
///
/// # Arguments
///
/// * `this`: instance to write to
/// * `value`: the value to set all cells to
/// - `brightness_grid`: instance to write to
/// - `value`: the value to set all cells to
///
/// # Panics
///
/// - When providing an invalid brightness value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BrightnessGrid`
/// - `this` is not written to or read from concurrently
/// - `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(
this: *mut CBrightnessGrid,
brightness_grid: *mut SPBrightnessGrid,
value: u8,
) {
let brightness =
Brightness::try_from(value).expect("invalid brightness value");
(*this).0.fill(brightness);
(*brightness_grid).0.fill(brightness);
}
/// Gets the width of the `BrightnessGrid` instance.
/// Gets the width of the `SPBrightnessGrid` instance.
///
/// # Arguments
///
/// * `this`: instance to read from
/// - `brightness_grid`: instance to read from
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BrightnessGrid`
/// - `brightness_grid` points to a valid `SPBrightnessGrid`
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_width(
this: *const CBrightnessGrid,
brightness_grid: *const SPBrightnessGrid,
) -> usize {
(*this).0.width()
(*brightness_grid).0.width()
}
/// Gets the height of the `BrightnessGrid` instance.
/// Gets the height of the `SPBrightnessGrid` instance.
///
/// # Arguments
///
/// * `this`: instance to read from
/// - `brightness_grid`: instance to read from
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BrightnessGrid`
/// - `brightness_grid` points to a valid `SPBrightnessGrid`
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_height(
this: *const CBrightnessGrid,
brightness_grid: *const SPBrightnessGrid,
) -> usize {
(*this).0.height()
(*brightness_grid).0.height()
}
/// Gets an unsafe reference to the data of the `BrightnessGrid` instance.
/// Gets an unsafe reference to the data of the `SPBrightnessGrid` instance.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BrightnessGrid`
/// - the returned memory range is never accessed after the passed `BrightnessGrid` has been freed
/// - the returned memory range is never accessed concurrently, either via the `BrightnessGrid` or directly
/// - `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(
this: *mut CBrightnessGrid,
) -> CByteSlice {
assert_eq!(std::mem::size_of::<Brightness>(), 1);
brightness_grid: *mut SPBrightnessGrid,
) -> SPByteSlice {
assert_eq!(core::mem::size_of::<Brightness>(), 1);
let data = (*this).0.data_ref_mut();
let data = (*brightness_grid).0.data_ref_mut();
let data: &mut [u8] = transmute(data);
CByteSlice {
SPByteSlice {
start: data.as_mut_ptr_range().start,
length: data.len(),
}

View file

@ -3,6 +3,8 @@
#[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.
///
/// # Safety
///
/// The caller has to make sure that:
@ -10,7 +12,9 @@
/// - 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.
pub struct CByteSlice {
/// - 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
pub start: *mut u8,
/// The amount of memory in bytes

View file

@ -1,99 +1,137 @@
//! C functions for interacting with `Command`s
//! C functions for interacting with `SPCommand`s
//!
//! prefix `sp_command_`
use std::ptr::null_mut;
use servicepoint::{
Brightness, Command, CompressionCode, Offset, Origin, Packet, PixelGrid,
use servicepoint::{Brightness, Origin};
use crate::{
SPBitVec, SPBrightnessGrid, SPCompressionCode, SPCp437Grid, SPPacket,
SPPixelGrid,
};
use crate::bit_vec::CBitVec;
use crate::brightness_grid::CBrightnessGrid;
use crate::cp437_grid::CCp437Grid;
/// Tries to turn a `Packet` into a `Command`. The packet is deallocated in the process.
/// A low-level display command.
///
/// Returns: pointer to new `Command` instance or NULL
/// 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));
/// ```
pub struct SPCommand(pub(crate) servicepoint::Command);
impl Clone for SPCommand {
fn clone(&self) -> Self {
SPCommand(self.0.clone())
}
}
/// Tries to turn a `SPPacket` into a `SPCommand`.
///
/// The packet is deallocated in the process.
///
/// Returns: pointer to new `SPCommand` instance or NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `packet` points to a valid instance of `Packet`
/// - `packet` is not used concurrently or after this call
/// - `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 `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - 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 Command {
packet: *mut SPPacket,
) -> *mut SPCommand {
let packet = *Box::from_raw(packet);
match Command::try_from(packet) {
match servicepoint::Command::try_from(packet.0) {
Err(_) => null_mut(),
Ok(command) => Box::into_raw(Box::new(command)),
Ok(command) => Box::into_raw(Box::new(SPCommand(command))),
}
}
/// Clones a `Command` instance.
/// Clones a `SPCommand` instance.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid instance of `Command`
/// - `this` is not written to concurrently
/// - the returned `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - `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(
original: *const Command,
) -> *mut Command {
Box::into_raw(Box::new((*original).clone()))
command: *const SPCommand,
) -> *mut SPCommand {
Box::into_raw(Box::new((*command).clone()))
}
/// Allocates a new `Command::Clear` instance.
/// Set all pixels to the off state.
///
/// Does not affect brightness.
///
/// Returns: a new `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 `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - 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() -> *mut Command {
Box::into_raw(Box::new(Command::Clear))
pub unsafe extern "C" fn sp_command_clear() -> *mut SPCommand {
Box::into_raw(Box::new(SPCommand(servicepoint::Command::Clear)))
}
/// Allocates a new `Command::HardReset` instance.
/// Kills the udp daemon on the display, which usually results in a restart.
///
/// Please do not send this in your normal program flow.
///
/// Returns: a new `Command::HardReset` instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - 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() -> *mut Command {
Box::into_raw(Box::new(Command::HardReset))
pub unsafe extern "C" fn sp_command_hard_reset() -> *mut SPCommand {
Box::into_raw(Box::new(SPCommand(servicepoint::Command::HardReset)))
}
/// Allocates a new `Command::FadeOut` instance.
/// A yet-to-be-tested command.
///
/// Returns: a new `Command::FadeOut` instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - 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() -> *mut Command {
Box::into_raw(Box::new(Command::FadeOut))
pub unsafe extern "C" fn sp_command_fade_out() -> *mut SPCommand {
Box::into_raw(Box::new(SPCommand(servicepoint::Command::FadeOut)))
}
/// Allocates a new `Command::Brightness` instance for setting the brightness of all tiles to the
/// same value.
/// Set the brightness of all tiles to the same value.
///
/// Returns: a new `Command::Brightness` instance. Will never return NULL.
///
/// # Panics
///
@ -103,201 +141,263 @@ pub unsafe extern "C" fn sp_command_fade_out() -> *mut Command {
///
/// The caller has to make sure that:
///
/// - the returned `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - 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) -> *mut Command {
pub unsafe extern "C" fn sp_command_brightness(
brightness: u8,
) -> *mut SPCommand {
let brightness =
Brightness::try_from(brightness).expect("invalid brightness");
Box::into_raw(Box::new(Command::Brightness(brightness)))
Box::into_raw(Box::new(SPCommand(servicepoint::Command::Brightness(
brightness,
))))
}
/// Allocates a new `Command::CharBrightness` instance.
/// The passed `ByteGrid` gets consumed.
/// Set the brightness of individual tiles in a rectangular area of the display.
///
/// The passed `SPBrightnessGrid` gets consumed.
///
/// Returns: a new `Command::CharBrightness` instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `byte_grid` points to a valid instance of `ByteGrid`
/// - `byte_grid` is not used concurrently or after this call
/// - the returned `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - `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,
byte_grid: *mut CBrightnessGrid,
) -> *mut Command {
let byte_grid = *Box::from_raw(byte_grid);
Box::into_raw(Box::new(Command::CharBrightness(
grid: *mut SPBrightnessGrid,
) -> *mut SPCommand {
let byte_grid = *Box::from_raw(grid);
Box::into_raw(Box::new(SPCommand(servicepoint::Command::CharBrightness(
Origin::new(x, y),
byte_grid.0,
)))
))))
}
/// Allocates a new `Command::BitmapLinear` instance.
/// The passed `BitVec` gets consumed.
/// 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 `Command::BitmapLinear` instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of `BitVec`
/// - `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 `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - 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: Offset,
bit_vec: *mut CBitVec,
compression: CompressionCode,
) -> *mut Command {
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> *mut SPCommand {
let bit_vec = *Box::from_raw(bit_vec);
Box::into_raw(Box::new(Command::BitmapLinear(
Box::into_raw(Box::new(SPCommand(servicepoint::Command::BitmapLinear(
offset,
bit_vec.into(),
compression,
)))
compression.try_into().expect("invalid compression code"),
))))
}
/// Allocates a new `Command::BitmapLinearAnd` instance.
/// The passed `BitVec` gets consumed.
/// 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 `Command::BitmapLinearAnd` instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of `BitVec`
/// - `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 `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - 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: Offset,
bit_vec: *mut CBitVec,
compression: CompressionCode,
) -> *mut Command {
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> *mut SPCommand {
let bit_vec = *Box::from_raw(bit_vec);
Box::into_raw(Box::new(Command::BitmapLinearAnd(
Box::into_raw(Box::new(SPCommand(servicepoint::Command::BitmapLinearAnd(
offset,
bit_vec.into(),
compression,
)))
compression.try_into().expect("invalid compression code"),
))))
}
/// Allocates a new `Command::BitmapLinearOr` instance.
/// The passed `BitVec` gets consumed.
/// 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 `Command::BitmapLinearOr` instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of `BitVec`
/// - `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 `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - 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: Offset,
bit_vec: *mut CBitVec,
compression: CompressionCode,
) -> *mut Command {
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> *mut SPCommand {
let bit_vec = *Box::from_raw(bit_vec);
Box::into_raw(Box::new(Command::BitmapLinearOr(
Box::into_raw(Box::new(SPCommand(servicepoint::Command::BitmapLinearOr(
offset,
bit_vec.into(),
compression,
)))
compression.try_into().expect("invalid compression code"),
))))
}
/// Allocates a new `Command::BitmapLinearXor` instance.
/// The passed `BitVec` gets consumed.
/// 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 `Command::BitmapLinearXor` instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of `BitVec`
/// - `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 `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - 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: Offset,
bit_vec: *mut CBitVec,
compression: CompressionCode,
) -> *mut Command {
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> *mut SPCommand {
let bit_vec = *Box::from_raw(bit_vec);
Box::into_raw(Box::new(Command::BitmapLinearXor(
Box::into_raw(Box::new(SPCommand(servicepoint::Command::BitmapLinearXor(
offset,
bit_vec.into(),
compression,
)))
compression.try_into().expect("invalid compression code"),
))))
}
/// Allocates a new `Command::Cp437Data` instance.
/// The passed `ByteGrid` gets consumed.
/// Show text on the screen.
///
/// <div class="warning">
/// The library does not currently convert between UTF-8 and CP-437.
/// Because Rust expects UTF-8 strings, it might be necessary to only send ASCII for now.
/// </div>
///
/// The passed `SPCp437Grid` gets consumed.///
///
/// Returns: a new `Command::Cp437Data` instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `byte_grid` points to a valid instance of `ByteGrid`
/// - `byte_grid` is not used concurrently or after this call
/// - the returned `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - `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,
byte_grid: *mut CCp437Grid,
) -> *mut Command {
let byte_grid = *Box::from_raw(byte_grid);
Box::into_raw(Box::new(Command::Cp437Data(Origin::new(x, y), byte_grid.0)))
grid: *mut SPCp437Grid,
) -> *mut SPCommand {
let grid = *Box::from_raw(grid);
Box::into_raw(Box::new(SPCommand(servicepoint::Command::Cp437Data(
Origin::new(x, y),
grid.0,
))))
}
/// Allocates a new `Command::BitmapLinearWin` instance.
/// The passed `PixelGrid` gets consumed.
/// Sets a window of pixels to the specified values.
///
/// The passed `SPPixelGrid` gets consumed.
///
/// Returns: a new `Command::BitmapLinearWin` instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `pixel_grid` points to a valid instance of `PixelGrid`
/// - `pixel_grid` points to a valid instance of `SPPixelGrid`
/// - `pixel_grid` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned `Command` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_dealloc`.
/// - 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,
pixel_grid: *mut PixelGrid,
compression_code: CompressionCode,
) -> *mut Command {
let byte_grid = *Box::from_raw(pixel_grid);
Box::into_raw(Box::new(Command::BitmapLinearWin(
pixel_grid: *mut SPPixelGrid,
compression_code: SPCompressionCode,
) -> *mut SPCommand {
let byte_grid = (*Box::from_raw(pixel_grid)).0;
Box::into_raw(Box::new(SPCommand(servicepoint::Command::BitmapLinearWin(
Origin::new(x, y),
byte_grid,
compression_code,
)))
compression_code
.try_into()
.expect("invalid compression code"),
))))
}
/// Deallocates a `Command`.
/// Deallocates a `SPCommand`.
///
/// # Examples
///
/// ```C
/// SPCommand c = sp_command_clear();
/// sp_command_free(c);
/// ```
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `Command`
/// - `this` is not used concurrently or after this call
/// - `this` was not passed to another consuming function, e.g. to create a `Packet`
/// - `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_dealloc(ptr: *mut Command) {
_ = Box::from_raw(ptr);
pub unsafe extern "C" fn sp_command_free(command: *mut SPCommand) {
_ = Box::from_raw(command);
}

View file

@ -1,13 +1,24 @@
//! C functions for interacting with `Connection`s
//! C functions for interacting with `SPConnection`s
//!
//! prefix `sp_connection_`
use std::ffi::{c_char, CStr};
use std::ptr::null_mut;
use servicepoint::{Connection, Packet};
use crate::{SPCommand, SPPacket};
/// Creates a new instance of `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());
/// ```
pub struct SPConnection(pub(crate) servicepoint::Connection);
/// Creates a new instance of `SPConnection`.
///
/// returns: NULL if connection fails, or connected instance
///
@ -20,22 +31,23 @@ use servicepoint::{Connection, Packet};
/// 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_dealloc`.
/// by explicitly calling `sp_connection_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_connection_open(
host: *const c_char,
) -> *mut Connection {
) -> *mut SPConnection {
let host = CStr::from_ptr(host).to_str().expect("Bad encoding");
let connection = match Connection::open(host) {
let connection = match servicepoint::Connection::open(host) {
Err(_) => return null_mut(),
Ok(value) => value,
};
Box::into_raw(Box::new(connection))
Box::into_raw(Box::new(SPConnection(connection)))
}
/// Sends a `Packet` to the display using the `Connection`.
/// The passed `Packet` gets consumed.
/// Sends a `SPPacket` to the display using the `SPConnection`.
///
/// The passed `packet` gets consumed.
///
/// returns: true in case of success
///
@ -43,27 +55,49 @@ pub unsafe extern "C" fn sp_connection_open(
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid instance of `Connection`
/// - `packet` points to a valid instance of `Packet`
/// - `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(
connection: *const Connection,
packet: *mut Packet,
pub unsafe extern "C" fn sp_connection_send_packet(
connection: *const SPConnection,
packet: *mut SPPacket,
) -> bool {
let packet = Box::from_raw(packet);
(*connection).send(*packet).is_ok()
(*connection).0.send((*packet).0).is_ok()
}
/// Closes and deallocates a `Connection`.
/// Sends a `SPCommand` to the display using the `SPConnection`.
///
/// The passed `command` gets consumed.
///
/// returns: true in case of success
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `Connection`
/// - `this` is not used concurrently or after this call
/// - `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_dealloc(ptr: *mut Connection) {
_ = Box::from_raw(ptr);
pub unsafe extern "C" fn sp_connection_send_command(
connection: *const SPConnection,
command: *mut SPCommand,
) -> bool {
let command = (*Box::from_raw(command)).0;
(*connection).0.send(command).is_ok()
}
/// Closes and deallocates a `SPConnection`.
///
/// # 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 SPConnection) {
_ = Box::from_raw(connection);
}

View file

@ -0,0 +1,48 @@
//! 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)
}
}

View file

@ -1,36 +1,51 @@
//! C functions for interacting with `Cp437Grid`s
//! C functions for interacting with `SPCp437Grid`s
//!
//! prefix `sp_cp437_grid_`
use servicepoint::{Cp437Grid, DataRef, Grid};
use crate::c_slice::CByteSlice;
use crate::SPByteSlice;
use servicepoint::{DataRef, Grid};
/// A C-wrapper for grid containing codepage 437 characters.
///
/// The encoding is currently not enforced.
#[derive(Clone)]
pub struct CCp437Grid(pub(crate) Cp437Grid);
/// Creates a new `Cp437Grid` with the specified dimensions.
///
/// returns: `Cp437Grid` initialized to 0.
/// # Examples
///
/// ```C
/// Cp437Grid grid = sp_cp437_grid_new(4, 3);
/// sp_cp437_grid_fill(grid, '?');
/// sp_cp437_grid_set(grid, 0, 0, '!');
/// sp_cp437_grid_free(grid);
/// ```
pub struct SPCp437Grid(pub(crate) servicepoint::Cp437Grid);
impl Clone for SPCp437Grid {
fn clone(&self) -> Self {
SPCp437Grid(self.0.clone())
}
}
/// Creates a new `SPCp437Grid` with the specified dimensions.
///
/// returns: `SPCp437Grid` initialized to 0.
///
/// # 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_dealloc`.
/// by explicitly calling `sp_cp437_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_new(
width: usize,
height: usize,
) -> *mut CCp437Grid {
Box::into_raw(Box::new(CCp437Grid(Cp437Grid::new(width, height))))
) -> *mut SPCp437Grid {
Box::into_raw(Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height))))
}
/// Loads a `Cp437Grid` with the specified dimensions from the provided data.
/// Loads a `SPCp437Grid` with the specified dimensions from the provided data.
///
/// Will never return NULL.
///
/// # Panics
///
@ -43,55 +58,57 @@ pub unsafe extern "C" fn sp_cp437_grid_new(
/// - `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_dealloc`.
/// by explicitly calling `sp_cp437_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
) -> *mut CCp437Grid {
) -> *mut SPCp437Grid {
let data = std::slice::from_raw_parts(data, data_length);
Box::into_raw(Box::new(CCp437Grid(Cp437Grid::load(width, height, data))))
Box::into_raw(Box::new(SPCp437Grid(servicepoint::Cp437Grid::load(width, height, data))))
}
/// Clones a `Cp437Grid`.
/// Clones a `SPCp437Grid`.
///
/// Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `Cp437Grid`
/// - `this` is not written to concurrently
/// - `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_dealloc`.
/// by explicitly calling `sp_cp437_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_clone(
this: *const CCp437Grid,
) -> *mut CCp437Grid {
Box::into_raw(Box::new((*this).clone()))
cp437_grid: *const SPCp437Grid,
) -> *mut SPCp437Grid {
Box::into_raw(Box::new((*cp437_grid).clone()))
}
/// Deallocates a `Cp437Grid`.
/// Deallocates a `SPCp437Grid`.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `Cp437Grid`
/// - `this` is not used concurrently or after this call
/// - `this` was not passed to another consuming function, e.g. to create a `Command`
/// - `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`
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_dealloc(this: *mut CCp437Grid) {
_ = Box::from_raw(this);
pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: *mut SPCp437Grid) {
_ = Box::from_raw(cp437_grid);
}
/// Gets the current value at the specified position.
///
/// # Arguments
///
/// * `this`: instance to read from
/// * `x` and `y`: position of the cell to read
/// - `cp437_grid`: instance to read from
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
@ -101,24 +118,24 @@ pub unsafe extern "C" fn sp_cp437_grid_dealloc(this: *mut CCp437Grid) {
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `Cp437Grid`
/// - `this` is not written to concurrently
/// - `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(
this: *const CCp437Grid,
cp437_grid: *const SPCp437Grid,
x: usize,
y: usize,
) -> u8 {
(*this).0.get(x, y)
(*cp437_grid).0.get(x, y)
}
/// Sets the value of the specified position in the `Cp437Grid`.
/// Sets the value of the specified position in the `SPCp437Grid`.
///
/// # Arguments
///
/// * `this`: instance to write to
/// * `x` and `y`: position of the cell
/// * `value`: the value to write to the cell
/// - `cp437_grid`: instance to write to
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
@ -130,85 +147,92 @@ pub unsafe extern "C" fn sp_cp437_grid_get(
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `BitVec`
/// - `this` is not written to or read from concurrently
/// - `cp437_grid` points to a valid `SPBitVec`
/// - `cp437_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_set(
this: *mut CCp437Grid,
cp437_grid: *mut SPCp437Grid,
x: usize,
y: usize,
value: u8,
) {
(*this).0.set(x, y, value);
(*cp437_grid).0.set(x, y, value);
}
/// Sets the value of all cells in the `Cp437Grid`.
/// Sets the value of all cells in the `SPCp437Grid`.
///
/// # Arguments
///
/// * `this`: instance to write to
/// * `value`: the value to set all cells to
/// - `cp437_grid`: instance to write to
/// - `value`: the value to set all cells to
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `Cp437Grid`
/// - `this` is not written to or read from concurrently
/// - `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(this: *mut CCp437Grid, value: u8) {
(*this).0.fill(value);
pub unsafe extern "C" fn sp_cp437_grid_fill(
cp437_grid: *mut SPCp437Grid,
value: u8,
) {
(*cp437_grid).0.fill(value);
}
/// Gets the width of the `Cp437Grid` instance.
/// Gets the width of the `SPCp437Grid` instance.
///
/// # Arguments
///
/// * `this`: instance to read from
/// - `cp437_grid`: instance to read from
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `Cp437Grid`
/// - `cp437_grid` points to a valid `SPCp437Grid`
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_width(this: *const CCp437Grid) -> usize {
(*this).0.width()
pub unsafe extern "C" fn sp_cp437_grid_width(
cp437_grid: *const SPCp437Grid,
) -> usize {
(*cp437_grid).0.width()
}
/// Gets the height of the `Cp437Grid` instance.
/// Gets the height of the `SPCp437Grid` instance.
///
/// # Arguments
///
/// * `this`: instance to read from
/// - `cp437_grid`: instance to read from
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `Cp437Grid`
/// - `cp437_grid` points to a valid `SPCp437Grid`
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_height(
this: *const CCp437Grid,
cp437_grid: *const SPCp437Grid,
) -> usize {
(*this).0.height()
(*cp437_grid).0.height()
}
/// Gets an unsafe reference to the data of the `Cp437Grid` instance.
/// Gets an unsafe reference to the data of the `SPCp437Grid` instance.
///
/// Will never return NULL.
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `Cp437Grid`
/// - the returned memory range is never accessed after the passed `Cp437Grid` has been freed
/// - the returned memory range is never accessed concurrently, either via the `Cp437Grid` or directly
/// - `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
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_unsafe_data_ref(
this: *mut CCp437Grid,
) -> CByteSlice {
let data = (*this).0.data_ref_mut();
CByteSlice {
cp437_grid: *mut SPCp437Grid,
) -> SPByteSlice {
let data = (*cp437_grid).0.data_ref_mut();
SPByteSlice {
start: data.as_mut_ptr_range().start,
length: data.len(),
}

View file

@ -1,27 +1,46 @@
//! C API wrapper for the `servicepoint` crate.
//! C API wrapper for the [servicepoint](https://docs.rs/servicepoint/latest/servicepoint/) crate.
//!
//! # Examples
//!
//! Make sure to check out [this GitHub repo](https://github.com/arfst23/ServicePoint) as well!
//!
//! ```C
//! #include <stdio.h>
//! #include "servicepoint.h"
//!
//! int main(void) {
//! SPConnection *connection = sp_connection_open("172.23.42.29:2342");
//! if (connection == NULL)
//! return 1;
//!
//! SPPixelGrid *pixels = sp_pixel_grid_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
//! sp_pixel_grid_fill(pixels, true);
//!
//! SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
//! while (sp_connection_send_command(connection, sp_command_clone(command)));
//!
//! sp_command_free(command);
//! sp_connection_free(connection);
//! return 0;
//! }
//! ```
pub use servicepoint::{
CompressionCode, PIXEL_COUNT, PIXEL_HEIGHT, PIXEL_WIDTH, TILE_HEIGHT,
TILE_SIZE, TILE_WIDTH,
};
pub use crate::bit_vec::*;
pub use crate::brightness_grid::*;
pub use crate::byte_slice::*;
pub use crate::command::*;
pub use crate::connection::*;
pub use crate::constants::*;
pub use crate::cp437_grid::*;
pub use crate::packet::*;
pub use crate::pixel_grid::*;
pub use crate::c_slice::CByteSlice;
pub mod bit_vec;
pub mod brightness_grid;
pub mod command;
pub mod connection;
pub mod packet;
pub mod pixel_grid;
pub mod c_slice;
pub mod cp437_grid;
/// The minimum time needed for the display to refresh the screen in ms.
pub const FRAME_PACING_MS: u32 = servicepoint::FRAME_PACING.as_millis() as u32;
mod bit_vec;
mod brightness_grid;
mod byte_slice;
mod command;
mod connection;
mod constants;
mod cp437_grid;
mod packet;
mod pixel_grid;

View file

@ -1,32 +1,37 @@
//! C functions for interacting with `Packet`s
//! C functions for interacting with `SPPacket`s
//!
//! prefix `sp_packet_`
use std::ptr::null_mut;
use servicepoint::{Command, Packet};
use crate::SPCommand;
/// Turns a `Command` into a `Packet`.
/// The `Command` gets consumed.
/// The raw packet
pub struct SPPacket(pub(crate) servicepoint::Packet);
/// Turns a `SPCommand` into a `SPPacket`.
/// The `SPCommand` gets consumed.
///
/// Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `command` points to a valid instance of `Command`
/// - `command` is not used concurrently or after this call
/// - the returned `Packet` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_dealloc`.
/// - `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`.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_from_command(
command: *mut Command,
) -> *mut Packet {
command: *mut SPCommand,
) -> *mut SPPacket {
let command = *Box::from_raw(command);
let packet = command.into();
let packet = SPPacket(command.0.into());
Box::into_raw(Box::new(packet))
}
/// Tries to load a `Packet` from the passed array with the specified length.
/// Tries to load a `SPPacket` from the passed array with the specified length.
///
/// returns: NULL in case of an error, pointer to the allocated packet otherwise
///
@ -36,29 +41,48 @@ pub unsafe extern "C" fn sp_packet_from_command(
///
/// - `data` points to a valid memory region of at least `length` bytes
/// - `data` is not written to concurrently
/// - the returned `Packet` instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_dealloc`.
/// - 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 {
) -> *mut SPPacket {
let data = std::slice::from_raw_parts(data, length);
match Packet::try_from(data) {
match servicepoint::Packet::try_from(data) {
Err(_) => null_mut(),
Ok(packet) => Box::into_raw(Box::new(packet)),
Ok(packet) => Box::into_raw(Box::new(SPPacket(packet))),
}
}
/// Deallocates a `Packet`.
/// Clones a `SPPacket`.
///
/// Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `Packet`
/// - `this` is not used concurrently or after this call
/// - `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`.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_dealloc(this: *mut Packet) {
_ = Box::from_raw(this)
pub unsafe extern "C" fn sp_packet_clone(
packet: *const SPPacket,
) -> *mut SPPacket {
Box::into_raw(Box::new(SPPacket((*packet).0.clone())))
}
/// Deallocates a `SPPacket`.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `packet` points to a valid `SPPacket`
/// - `packet` is not used concurrently or after this call
#[no_mangle]
pub unsafe extern "C" fn sp_packet_free(packet: *mut SPPacket) {
_ = Box::from_raw(packet)
}

View file

@ -1,19 +1,31 @@
//! C functions for interacting with `PixelGrid`s
//! C functions for interacting with `SPPixelGrid`s
//!
//! prefix `sp_pixel_grid_`
use servicepoint::{DataRef, Grid, PixelGrid};
use servicepoint::{DataRef, Grid};
use crate::c_slice::CByteSlice;
use crate::byte_slice::SPByteSlice;
/// Creates a new `PixelGrid` with the specified dimensions.
/// A grid of pixels.
///
/// # Examples
///
/// ```C
/// Cp437Grid grid = sp_pixel_grid_new(8, 3);
/// sp_pixel_grid_fill(grid, true);
/// sp_pixel_grid_set(grid, 0, 0, false);
/// sp_pixel_grid_free(grid);
/// ```
pub struct SPPixelGrid(pub(crate) servicepoint::PixelGrid);
/// Creates a new `SPPixelGrid` with the specified dimensions.
///
/// # Arguments
///
/// * `width`: size in pixels in x-direction
/// * `height`: size in pixels in y-direction
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: `PixelGrid` initialized to all pixels off
/// returns: `SPPixelGrid` initialized to all pixels off. Will never return NULL.
///
/// # Panics
///
@ -24,23 +36,25 @@ use crate::c_slice::CByteSlice;
/// 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_pixel_grid_dealloc`.
/// by explicitly calling `sp_pixel_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_pixel_grid_new(
width: usize,
height: usize,
) -> *mut PixelGrid {
Box::into_raw(Box::new(PixelGrid::new(width, height)))
) -> *mut SPPixelGrid {
Box::into_raw(Box::new(SPPixelGrid(servicepoint::PixelGrid::new(
width, height,
))))
}
/// Loads a `PixelGrid` with the specified dimensions from the provided data.
/// Loads a `SPPixelGrid` with the specified dimensions from the provided data.
///
/// # Arguments
///
/// * `width`: size in pixels in x-direction
/// * `height`: size in pixels in y-direction
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: `PixelGrid` that contains a copy of the provided data
/// returns: `SPPixelGrid` that contains a copy of the provided data. Will never return NULL.
///
/// # Panics
///
@ -53,55 +67,59 @@ pub unsafe extern "C" fn sp_pixel_grid_new(
///
/// - `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_pixel_grid_dealloc`.
/// by explicitly calling `sp_pixel_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_pixel_grid_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
) -> *mut PixelGrid {
) -> *mut SPPixelGrid {
let data = std::slice::from_raw_parts(data, data_length);
Box::into_raw(Box::new(PixelGrid::load(width, height, data)))
Box::into_raw(Box::new(SPPixelGrid(servicepoint::PixelGrid::load(
width, height, data,
))))
}
/// Clones a `PixelGrid`.
/// Clones a `SPPixelGrid`.
///
/// Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `PixelGrid`
/// - `this` is not written to concurrently
/// - `pixel_grid` points to a valid `SPPixelGrid`
/// - `pixel_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_pixel_grid_dealloc`.
/// by explicitly calling `sp_pixel_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_pixel_grid_clone(
this: *const PixelGrid,
) -> *mut PixelGrid {
Box::into_raw(Box::new((*this).clone()))
pixel_grid: *const SPPixelGrid,
) -> *mut SPPixelGrid {
Box::into_raw(Box::new(SPPixelGrid((*pixel_grid).0.clone())))
}
/// Deallocates a `PixelGrid`.
/// Deallocates a `SPPixelGrid`.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `PixelGrid`
/// - `this` is not used concurrently or after this call
/// - `this` was not passed to another consuming function, e.g. to create a `Command`
/// - `pixel_grid` points to a valid `SPPixelGrid`
/// - `pixel_grid` is not used concurrently or after pixel_grid call
/// - `pixel_grid` was not passed to another consuming function, e.g. to create a `SPCommand`
#[no_mangle]
pub unsafe extern "C" fn sp_pixel_grid_dealloc(this: *mut PixelGrid) {
_ = Box::from_raw(this);
pub unsafe extern "C" fn sp_pixel_grid_free(pixel_grid: *mut SPPixelGrid) {
_ = Box::from_raw(pixel_grid);
}
/// Gets the current value at the specified position in the `PixelGrid`.
/// Gets the current value at the specified position in the `SPPixelGrid`.
///
/// # Arguments
///
/// * `this`: instance to read from
/// * `x` and `y`: position of the cell to read
/// - `pixel_grid`: instance to read from
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
@ -111,24 +129,24 @@ pub unsafe extern "C" fn sp_pixel_grid_dealloc(this: *mut PixelGrid) {
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `PixelGrid`
/// - `this` is not written to concurrently
/// - `pixel_grid` points to a valid `SPPixelGrid`
/// - `pixel_grid` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_pixel_grid_get(
this: *const PixelGrid,
pixel_grid: *const SPPixelGrid,
x: usize,
y: usize,
) -> bool {
(*this).get(x, y)
(*pixel_grid).0.get(x, y)
}
/// Sets the value of the specified position in the `PixelGrid`.
/// Sets the value of the specified position in the `SPPixelGrid`.
///
/// # Arguments
///
/// * `this`: instance to write to
/// * `x` and `y`: position of the cell
/// * `value`: the value to write to the cell
/// - `pixel_grid`: instance to write to
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
@ -140,83 +158,90 @@ pub unsafe extern "C" fn sp_pixel_grid_get(
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `PixelGrid`
/// - `this` is not written to or read from concurrently
/// - `pixel_grid` points to a valid `SPPixelGrid`
/// - `pixel_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_pixel_grid_set(
this: *mut PixelGrid,
pixel_grid: *mut SPPixelGrid,
x: usize,
y: usize,
value: bool,
) {
(*this).set(x, y, value);
(*pixel_grid).0.set(x, y, value);
}
/// Sets the state of all pixels in the `PixelGrid`.
/// Sets the state of all pixels in the `SPPixelGrid`.
///
/// # Arguments
///
/// * `this`: instance to write to
/// * `value`: the value to set all pixels to
/// - `pixel_grid`: instance to write to
/// - `value`: the value to set all pixels to
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `PixelGrid`
/// - `this` is not written to or read from concurrently
/// - `pixel_grid` points to a valid `SPPixelGrid`
/// - `pixel_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_pixel_grid_fill(this: *mut PixelGrid, value: bool) {
(*this).fill(value);
pub unsafe extern "C" fn sp_pixel_grid_fill(
pixel_grid: *mut SPPixelGrid,
value: bool,
) {
(*pixel_grid).0.fill(value);
}
/// Gets the width in pixels of the `PixelGrid` instance.
/// Gets the width in pixels of the `SPPixelGrid` instance.
///
/// # Arguments
///
/// * `this`: instance to read from
/// - `pixel_grid`: instance to read from
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `PixelGrid`
/// - `pixel_grid` points to a valid `SPPixelGrid`
#[no_mangle]
pub unsafe extern "C" fn sp_pixel_grid_width(this: *const PixelGrid) -> usize {
(*this).width()
pub unsafe extern "C" fn sp_pixel_grid_width(
pixel_grid: *const SPPixelGrid,
) -> usize {
(*pixel_grid).0.width()
}
/// Gets the height in pixels of the `PixelGrid` instance.
/// Gets the height in pixels of the `SPPixelGrid` instance.
///
/// # Arguments
///
/// * `this`: instance to read from
/// - `pixel_grid`: instance to read from
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `PixelGrid`
/// - `pixel_grid` points to a valid `SPPixelGrid`
#[no_mangle]
pub unsafe extern "C" fn sp_pixel_grid_height(this: *const PixelGrid) -> usize {
(*this).height()
pub unsafe extern "C" fn sp_pixel_grid_height(
pixel_grid: *const SPPixelGrid,
) -> usize {
(*pixel_grid).0.height()
}
/// Gets an unsafe reference to the data of the `PixelGrid` instance.
/// Gets an unsafe reference to the data of the `SPPixelGrid` instance.
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `this` points to a valid `PixelGrid`
/// - the returned memory range is never accessed after the passed `PixelGrid` has been freed
/// - the returned memory range is never accessed concurrently, either via the `PixelGrid` or directly
/// - `pixel_grid` points to a valid `SPPixelGrid`
/// - the returned memory range is never accessed after the passed `SPPixelGrid` has been freed
/// - the returned memory range is never accessed concurrently, either via the `SPPixelGrid` or directly
#[no_mangle]
pub unsafe extern "C" fn sp_pixel_grid_unsafe_data_ref(
this: *mut PixelGrid,
) -> CByteSlice {
let data = (*this).data_ref_mut();
CByteSlice {
pixel_grid: *mut SPPixelGrid,
) -> SPByteSlice {
let data = (*pixel_grid).0.data_ref_mut();
SPByteSlice {
start: data.as_mut_ptr_range().start,
length: data.len(),
}

View file

@ -10,11 +10,11 @@ crate-type = ["cdylib"]
test = false
[build-dependencies]
csbindgen = "1.8.0"
csbindgen = "1.9.3"
[dependencies]
servicepoint_binding_c = { version = "0.7.0", path = "../servicepoint_binding_c" }
servicepoint = { version = "0.7.0", path = "../servicepoint" }
servicepoint_binding_c = { version = "0.8.0", path = "../servicepoint_binding_c" }
servicepoint = { version = "0.8.0", path = "../servicepoint" }
[lints]
workspace = true

View file

@ -39,7 +39,7 @@ Because of that, there is no NuGet package you can use directly.
Including this repository as a submodule and building from source is the recommended way of using the library.
```bash
git submodule add https://github.com/kaesaecracker/servicepoint.git
git submodule add https://github.com/cccb/servicepoint.git
git commit -m "add servicepoint submodule"
```

View file

@ -2,7 +2,7 @@ using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class BitVec : SpNativeInstance<BindGen.CBitVec>
public sealed class BitVec : SpNativeInstance<BindGen.BitVec>
{
public static BitVec New(int size)
{
@ -80,12 +80,9 @@ public sealed class BitVec : SpNativeInstance<BindGen.CBitVec>
}
}
private unsafe BitVec(BindGen.CBitVec* instance) : base(instance)
private unsafe BitVec(BindGen.BitVec* instance) : base(instance)
{
}
private protected override unsafe void Dealloc()
{
NativeMethods.sp_bit_vec_dealloc(Instance);
}
private protected override unsafe void Free() => NativeMethods.sp_bit_vec_free(Instance);
}

View file

@ -2,7 +2,7 @@ using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class BrightnessGrid : SpNativeInstance<BindGen.CBrightnessGrid>
public sealed class BrightnessGrid : SpNativeInstance<BindGen.BrightnessGrid>
{
public static BrightnessGrid New(int width, int height)
{
@ -92,12 +92,9 @@ public sealed class BrightnessGrid : SpNativeInstance<BindGen.CBrightnessGrid>
}
}
private unsafe BrightnessGrid(BindGen.CBrightnessGrid* instance) : base(instance)
private unsafe BrightnessGrid(BindGen.BrightnessGrid* instance) : base(instance)
{
}
private protected override unsafe void Dealloc()
{
NativeMethods.sp_brightness_grid_dealloc(Instance);
}
private protected override unsafe void Free() => NativeMethods.sp_brightness_grid_free(Instance);
}

View file

@ -125,8 +125,5 @@ public sealed class Command : SpNativeInstance<BindGen.Command>
{
}
private protected override unsafe void Dealloc()
{
NativeMethods.sp_command_dealloc(Instance);
}
private protected override unsafe void Free() => NativeMethods.sp_command_free(Instance);
}

View file

@ -20,14 +20,19 @@ public sealed class Connection : SpNativeInstance<BindGen.Connection>
{
unsafe
{
return NativeMethods.sp_connection_send(Instance, packet.Into());
return NativeMethods.sp_connection_send_packet(Instance, packet.Into());
}
}
private protected override unsafe void Dealloc()
public bool Send(Command command)
{
NativeMethods.sp_connection_dealloc(Instance);
unsafe
{
return NativeMethods.sp_connection_send_command(Instance, command.Into());
}
}
private protected override unsafe void Free() => NativeMethods.sp_connection_free(Instance);
private unsafe Connection(BindGen.Connection* instance) : base(instance)
{

View file

@ -3,7 +3,7 @@ using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class Cp437Grid : SpNativeInstance<BindGen.CCp437Grid>
public sealed class Cp437Grid : SpNativeInstance<BindGen.Cp437Grid>
{
public static Cp437Grid New(int width, int height)
{
@ -123,12 +123,9 @@ public sealed class Cp437Grid : SpNativeInstance<BindGen.CCp437Grid>
}
}
private unsafe Cp437Grid(BindGen.CCp437Grid* instance) : base(instance)
private unsafe Cp437Grid(BindGen.Cp437Grid* instance) : base(instance)
{
}
private protected override unsafe void Dealloc()
{
NativeMethods.sp_cp437_grid_dealloc(Instance);
}
private protected override unsafe void Free() => NativeMethods.sp_cp437_grid_free(Instance);
}

View file

@ -32,8 +32,5 @@ public sealed class Packet : SpNativeInstance<BindGen.Packet>
{
}
private protected override unsafe void Dealloc()
{
NativeMethods.sp_packet_dealloc(Instance);
}
private protected override unsafe void Free() => NativeMethods.sp_packet_free(Instance);
}

View file

@ -96,8 +96,5 @@ public sealed class PixelGrid : SpNativeInstance<BindGen.PixelGrid>
{
}
private protected override unsafe void Dealloc()
{
NativeMethods.sp_pixel_grid_dealloc(Instance);
}
private protected override unsafe void Free() => NativeMethods.sp_pixel_grid_free(Instance);
}

View file

@ -11,7 +11,7 @@
<PropertyGroup>
<PackageId>ServicePoint</PackageId>
<Version>0.7.0</Version>
<Version>0.8.0</Version>
<Authors>Repository Authors</Authors>
<Company>None</Company>
<Product>ServicePoint</Product>

View file

@ -22,7 +22,7 @@ public abstract class SpNativeInstance<T>
_instance = instance;
}
private protected abstract void Dealloc();
private protected abstract void Free();
internal unsafe T* Into()
{
@ -34,7 +34,7 @@ public abstract class SpNativeInstance<T>
private unsafe void ReleaseUnmanagedResources()
{
if (_instance != null)
Dealloc();
Free();
_instance = null;
}

View file

@ -1,29 +1,35 @@
//! Build script generating the C# code needed to call methods from the `servicepoint` C library.
use std::fs;
fn main() {
println!("cargo::rerun-if-changed=../servicepoint_binding_c/src");
println!("cargo::rerun-if-changed=build.rs");
csbindgen::Builder::default()
.input_extern_file("../servicepoint_binding_c/src/bit_vec.rs")
.input_extern_file("../servicepoint_binding_c/src/brightness_grid.rs")
.input_extern_file("../servicepoint_binding_c/src/cp437_grid.rs")
.input_extern_file("../servicepoint_binding_c/src/command.rs")
.input_extern_file("../servicepoint_binding_c/src/connection.rs")
.input_extern_file("../servicepoint_binding_c/src/pixel_grid.rs")
.input_extern_file("../servicepoint_binding_c/src/lib.rs")
.input_extern_file("../servicepoint_binding_c/src/c_slice.rs")
.input_extern_file("../servicepoint_binding_c/src/packet.rs")
.input_extern_file("../servicepoint/src/command.rs")
.input_extern_file("../servicepoint/src/connection.rs")
.input_extern_file("../servicepoint/src/pixel_grid.rs")
.input_extern_file("../servicepoint/src/lib.rs")
.input_extern_file("../servicepoint/src/packet.rs")
.input_extern_file("../servicepoint/src/compression_code.rs")
let mut builder = csbindgen::Builder::default();
for source in fs::read_dir("../servicepoint_binding_c/src").unwrap() {
let path = source.unwrap().path();
println!("cargo:rerun-if-changed={}", path.display());
builder = builder.input_extern_file(path);
}
builder
.csharp_dll_name("servicepoint_binding_c")
.csharp_namespace("ServicePoint.BindGen")
.csharp_use_nint_types(true)
.csharp_class_accessibility("public")
.csharp_generate_const_filter(|_| true)
.csharp_type_rename(move |name| {
if name.len() > 2
&& name.starts_with("SP")
&& name.chars().nth(2).unwrap().is_uppercase()
{
name[2..].to_string()
} else {
name
}
})
.generate_csharp_file("ServicePoint/BindGen/ServicePoint.g.cs")
.unwrap();
}

View file

@ -1,12 +1,20 @@
{pkgs ? import <nixpkgs> {}}:
{pkgs ? import <nixpkgs> {}}: let
rust-toolchain = pkgs.symlinkJoin {
name = "rust-toolchain";
paths = with pkgs; [rustc cargo rustPlatform.rustcSrc rustfmt clippy];
};
in
pkgs.mkShell {
nativeBuildInputs = with pkgs.buildPackages; [
rustc cargo gcc rustfmt clippy
rust-toolchain
pkg-config
xe
lzma
cargo-tarpaulin
gcc
gnumake
# dotnet-sdk_8