Compare commits

...
Sign in to create a new pull request.

19 commits

Author SHA1 Message Date
Vinzenz Schroeter
b0bfd341b6 add sp_packet_from_parts, regenerate C header 2024-10-20 13:34:21 +02:00
Vinzenz Schroeter
66823d0676 add unit tests for null handling, fix assertion in rust code 2024-10-20 13:33:12 +02:00
Vinzenz Schroeter
9cdb5e0dbf replace Span with ReadOnlySpan where appropriate 2024-10-20 13:31:41 +02:00
Vinzenz Schroeter
52b1ce83d9 add sp_bitmap_new_screen_sized 2024-10-20 12:35:14 +02:00
Vinzenz Schroeter
bcd65fb4f0 add first C# unit test 2024-10-20 12:29:45 +02:00
Vinzenz Schroeter
474848e2ea update C# README 2024-10-20 12:29:26 +02:00
Vinzenz Schroeter
05aff8b2dd add fake connection to C API 2024-10-20 12:29:08 +02:00
Vinzenz Schroeter
240766dc11 add dunder to prevent name clashes 2024-10-20 12:08:19 +02:00
Vinzenz Schroeter
c3022e567c checkout submodules in github action 2024-10-19 16:30:36 +02:00
Vinzenz Schroeter
0b80ce4968 add AggressiveInlining attribute 2024-10-19 16:19:09 +02:00
Vinzenz Schroeter
f968f92917 add Load methods with Spans 2024-10-19 16:04:01 +02:00
Vinzenz Schroeter
f836220259 annotate which parameters are consumed, move constants 2024-10-19 15:31:54 +02:00
Vinzenz Schroeter
57c66d9d31 move extensions into classes 2024-10-19 14:45:21 +02:00
Vinzenz Schroeter
1f23bc8afc add docs to public methods instead of native ones 2024-10-19 14:35:03 +02:00
Vinzenz Schroeter
67969d5b43 generate most of the c# binding with a fork of csbindgen 2024-10-19 14:21:50 +02:00
Vinzenz Schroeter
91cc982394 use GroupedNativeMethods 2024-10-16 22:46:34 +02:00
Vinzenz Schroeter
01f2f90121 csbindgen one file per file 2024-10-16 21:59:35 +02:00
Vinzenz Schroeter
55aa7ecf4c pass through real types to c# 2024-10-16 20:56:55 +02:00
Vinzenz Schroeter
db98709ca7 fix second target with same name replaces first 2024-10-16 20:41:26 +02:00
51 changed files with 2847 additions and 2158 deletions

View file

@ -16,6 +16,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
submodules: 'true'
- name: build default features
run: cargo build --all --verbose

4
.gitmodules vendored Normal file
View file

@ -0,0 +1,4 @@
[submodule "crates/servicepoint_csbindgen"]
path = crates/servicepoint_csbindgen
url = https://github.com/kaesaecracker/servicepoint_csbindgen.git
branch = "main"

19
Cargo.lock generated
View file

@ -208,6 +208,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cpufeatures"
version = "0.2.14"
@ -239,9 +248,8 @@ dependencies = [
[[package]]
name = "csbindgen"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c26b9831049b947d154bba920e4124053def72447be6fb106a96f483874b482a"
dependencies = [
"convert_case",
"regex",
"syn",
]
@ -636,6 +644,7 @@ dependencies = [
name = "servicepoint_binding_cs"
version = "0.10.0"
dependencies = [
"convert_case",
"csbindgen",
"servicepoint",
"servicepoint_binding_c",
@ -778,6 +787,12 @@ version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "utf-8"
version = "0.7.6"

View file

@ -1,8 +1,11 @@
[workspace]
resolver = "2"
members = [
"crates/*",
"crates/servicepoint_binding_c/examples/lang_c"
"crates/servicepoint",
"crates/servicepoint_binding_c",
"crates/servicepoint_binding_c/examples/lang_c",
"crates/servicepoint_binding_cs",
"crates/servicepoint_csbindgen/csbindgen"
]
[workspace.package]

View file

@ -31,11 +31,8 @@ fn main() {
let mut filled_grid = Bitmap::max_sized();
filled_grid.fill(true);
let command = BitmapLinearWin(
Origin::ZERO,
filled_grid,
CompressionCode::Lzma,
);
let command =
BitmapLinearWin(Origin::ZERO, filled_grid, CompressionCode::Lzma);
connection.send(command).expect("send failed");
}

View file

@ -266,6 +266,8 @@ void sp_bitmap_fill(SPBitmap *bitmap, bool value);
* - `bitmap` points to a valid [SPBitmap]
* - `bitmap` is not used concurrently or after bitmap call
* - `bitmap` was not passed to another consuming function, e.g. to create a [SPCommand]
*
* servicepoint_csbindgen_consumes: bitmap
*/
void sp_bitmap_free(SPBitmap *bitmap);
@ -363,6 +365,20 @@ SPBitmap *sp_bitmap_load(size_t width,
SPBitmap *sp_bitmap_new(size_t width,
size_t height);
/**
* Creates a new [SPBitmap] with a size matching the screen.
*
* returns: [SPBitmap] initialized to all pixels off. Will never return NULL.
*
* # Safety
*
* The caller has to make sure that:
*
* - the returned instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_bitmap_free`.
*/
SPBitmap *sp_bitmap_new_screen_sized(void);
/**
* Sets the value of the specified position in the [SPBitmap].
*
@ -695,6 +711,8 @@ void sp_brightness_grid_fill(SPBrightnessGrid *brightness_grid, uint8_t value);
* - `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]
*
* servicepoint_csbindgen_consumes: brightness_grid
*/
void sp_brightness_grid_free(SPBrightnessGrid *brightness_grid);
@ -883,6 +901,8 @@ size_t sp_brightness_grid_width(const SPBrightnessGrid *brightness_grid);
* - `compression` matches one of the allowed enum values
* - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_command_free`.
*
* servicepoint_csbindgen_consumes: bit_vec
*/
SPCommand *sp_command_bitmap_linear(size_t offset,
SPBitVec *bit_vec,
@ -914,6 +934,8 @@ SPCommand *sp_command_bitmap_linear(size_t offset,
* - `compression` matches one of the allowed enum values
* - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_command_free`.
*
* servicepoint_csbindgen_consumes: bit_vec
*/
SPCommand *sp_command_bitmap_linear_and(size_t offset,
SPBitVec *bit_vec,
@ -945,6 +967,8 @@ SPCommand *sp_command_bitmap_linear_and(size_t offset,
* - `compression` matches one of the allowed enum values
* - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_command_free`.
*
* servicepoint_csbindgen_consumes: bit_vec
*/
SPCommand *sp_command_bitmap_linear_or(size_t offset,
SPBitVec *bit_vec,
@ -971,6 +995,8 @@ SPCommand *sp_command_bitmap_linear_or(size_t offset,
* - `compression` matches one of the allowed enum values
* - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_command_free`.
*
* servicepoint_csbindgen_consumes: bitmap
*/
SPCommand *sp_command_bitmap_linear_win(size_t x,
size_t y,
@ -1003,6 +1029,8 @@ SPCommand *sp_command_bitmap_linear_win(size_t x,
* - `compression` matches one of the allowed enum values
* - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_command_free`.
*
* servicepoint_csbindgen_consumes: bit_vec
*/
SPCommand *sp_command_bitmap_linear_xor(size_t offset,
SPBitVec *bit_vec,
@ -1045,6 +1073,8 @@ SPCommand *sp_command_brightness(uint8_t brightness);
* - `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`.
*
* servicepoint_csbindgen_consumes: grid
*/
SPCommand *sp_command_char_brightness(size_t x,
size_t y,
@ -1111,6 +1141,8 @@ SPCommand *sp_command_clone(const SPCommand *command);
* - `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`.
*
* servicepoint_csbindgen_consumes: grid
*/
SPCommand *sp_command_cp437_data(size_t x,
size_t y,
@ -1151,6 +1183,8 @@ SPCommand *sp_command_fade_out(void);
* - `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]
*
* servicepoint_csbindgen_consumes: command
*/
void sp_command_free(SPCommand *command);
@ -1190,9 +1224,25 @@ SPCommand *sp_command_hard_reset(void);
* - the result is checked for NULL
* - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_command_free`.
*
* servicepoint_csbindgen_consumes: packet
*/
SPCommand *sp_command_try_from_packet(SPPacket *packet);
/**
* Creates a new instance of [SPConnection] for testing that does not actually send anything.
*
* returns: a new instance. Will never return NULL.
*
* # Safety
*
* The caller has to make sure that:
*
* - the returned instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_connection_free`.
*/
SPConnection *sp_connection_fake(void);
/**
* Closes and deallocates a [SPConnection].
*
@ -1206,11 +1256,13 @@ SPCommand *sp_command_try_from_packet(SPPacket *packet);
*
* - `connection` points to a valid [SPConnection]
* - `connection` is not used concurrently or after this call
*
* servicepoint_csbindgen_consumes: connection
*/
void sp_connection_free(SPConnection *connection);
/**
* Creates a new instance of [SPConnection].
* Creates a new instance of [SPConnection] that uses UDP to send.
*
* returns: NULL if connection fails, or connected instance
*
@ -1246,6 +1298,8 @@ SPConnection *sp_connection_open(const char *host);
* - `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
*
* servicepoint_csbindgen_consumes: command
*/
bool sp_connection_send_command(const SPConnection *connection,
SPCommand *command);
@ -1269,6 +1323,8 @@ bool sp_connection_send_command(const SPConnection *connection,
* - `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
*
* servicepoint_csbindgen_consumes: packet
*/
bool sp_connection_send_packet(const SPConnection *connection,
SPPacket *packet);
@ -1328,6 +1384,8 @@ void sp_cp437_grid_fill(SPCp437Grid *cp437_grid, uint8_t value);
* - `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]
*
* servicepoint_csbindgen_consumes: cp437_grid
*/
void sp_cp437_grid_free(SPCp437Grid *cp437_grid);
@ -1502,7 +1560,7 @@ SPPacket *sp_packet_clone(const SPPacket *packet);
*
* # Panics
*
* - when `sp_packet_free` is NULL
* - when `packet` is NULL
*
* # Safety
*
@ -1510,6 +1568,8 @@ SPPacket *sp_packet_clone(const SPPacket *packet);
*
* - `packet` points to a valid [SPPacket]
* - `packet` is not used concurrently or after this call
*
* servicepoint_csbindgen_consumes: packet
*/
void sp_packet_free(SPPacket *packet);
@ -1531,9 +1591,45 @@ void sp_packet_free(SPPacket *packet);
* - [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`.
*
* servicepoint_csbindgen_consumes: command
*/
SPPacket *sp_packet_from_command(SPCommand *command);
/**
* Creates a raw [SPPacket] from parts.
*
* # Arguments
*
* - `command_code` specifies which command this packet contains
* - `a`, `b`, `c` and `d` are command-specific header values
* - `payload` is the optional data that is part of the command
* - `payload_len` is the size of the payload
*
* returns: new instance. Will never return null.
*
* # Panics
*
* - when `payload` is null, but `payload_len` is not zero
* - when `payload_len` is zero, but `payload` is nonnull
*
* # Safety
*
* The caller has to make sure that:
*
* - `payload` points to a valid memory region of at least `payload_len` bytes
* - `payload` is not written to concurrently
* - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_packet_free`.
*/
SPPacket *sp_packet_from_parts(uint16_t command_code,
uint16_t a,
uint16_t b,
uint16_t c,
uint16_t d,
const uint8_t *payload,
size_t payload_len);
/**
* Tries to load a [SPPacket] from the passed array with the specified length.
*

View file

@ -2,9 +2,9 @@
//!
//! prefix `sp_bitvec_`
use std::ptr::NonNull;
use crate::SPByteSlice;
use servicepoint::bitvec::prelude::{BitVec, Msb0};
use std::ptr::NonNull;
/// A vector of bits
///

View file

@ -2,8 +2,8 @@
//!
//! prefix `sp_bitmap_`
use std::ptr::NonNull;
use servicepoint::{DataRef, Grid};
use std::ptr::NonNull;
use crate::byte_slice::SPByteSlice;
@ -43,9 +43,23 @@ pub unsafe extern "C" fn sp_bitmap_new(
width: usize,
height: usize,
) -> NonNull<SPBitmap> {
let result = Box::new(SPBitmap(servicepoint::Bitmap::new(
width, height,
)));
let result = Box::new(SPBitmap(servicepoint::Bitmap::new(width, height)));
NonNull::from(Box::leak(result))
}
/// Creates a new [SPBitmap] with a size matching the screen.
///
/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_new_screen_sized() -> NonNull<SPBitmap> {
let result = Box::new(SPBitmap(servicepoint::Bitmap::max_sized()));
NonNull::from(Box::leak(result))
}
@ -80,9 +94,8 @@ pub unsafe extern "C" fn sp_bitmap_load(
) -> NonNull<SPBitmap> {
assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, data_length);
let result = Box::new(SPBitmap(servicepoint::Bitmap::load(
width, height, data,
)));
let result =
Box::new(SPBitmap(servicepoint::Bitmap::load(width, height, data)));
NonNull::from(Box::leak(result))
}
@ -124,6 +137,8 @@ pub unsafe extern "C" fn sp_bitmap_clone(
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not used concurrently or after bitmap call
/// - `bitmap` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// servicepoint_csbindgen_consumes: bitmap
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_free(bitmap: *mut SPBitmap) {
assert!(!bitmap.is_null());

View file

@ -8,13 +8,6 @@ use std::convert::Into;
use std::intrinsics::transmute;
use std::ptr::NonNull;
/// see [Brightness::MIN]
pub const SP_BRIGHTNESS_MIN: u8 = 0;
/// see [Brightness::MAX]
pub const SP_BRIGHTNESS_MAX: u8 = 11;
/// Count of possible brightness values
pub const SP_BRIGHTNESS_LEVELS: u8 = 12;
/// A grid containing brightness values.
///
/// # Examples
@ -48,9 +41,9 @@ pub unsafe extern "C" fn sp_brightness_grid_new(
width: usize,
height: usize,
) -> NonNull<SPBrightnessGrid> {
let result = Box::new(SPBrightnessGrid(
servicepoint::BrightnessGrid::new(width, height),
));
let result = Box::new(SPBrightnessGrid(servicepoint::BrightnessGrid::new(
width, height,
)));
NonNull::from(Box::leak(result))
}
@ -133,6 +126,8 @@ pub unsafe extern "C" fn sp_brightness_grid_clone(
/// - `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]
///
/// servicepoint_csbindgen_consumes: brightness_grid
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_free(
brightness_grid: *mut SPBrightnessGrid,

View file

@ -50,6 +50,8 @@ impl Clone for SPCommand {
/// - the result is checked for NULL
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: packet
#[no_mangle]
pub unsafe extern "C" fn sp_command_try_from_packet(
packet: *mut SPPacket,
@ -164,9 +166,8 @@ pub unsafe extern "C" fn sp_command_brightness(
) -> NonNull<SPCommand> {
let brightness =
Brightness::try_from(brightness).expect("invalid brightness");
let result = Box::new(SPCommand(
servicepoint::Command::Brightness(brightness),
));
let result =
Box::new(SPCommand(servicepoint::Command::Brightness(brightness)));
NonNull::from(Box::leak(result))
}
@ -188,6 +189,8 @@ pub unsafe extern "C" fn sp_command_brightness(
/// - `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`.
///
/// servicepoint_csbindgen_consumes: grid
#[no_mangle]
pub unsafe extern "C" fn sp_command_char_brightness(
x: usize,
@ -196,9 +199,10 @@ pub unsafe extern "C" fn sp_command_char_brightness(
) -> NonNull<SPCommand> {
assert!(!grid.is_null());
let byte_grid = *Box::from_raw(grid);
let result = Box::new(SPCommand(
servicepoint::Command::CharBrightness(Origin::new(x, y), byte_grid.0),
));
let result = Box::new(SPCommand(servicepoint::Command::CharBrightness(
Origin::new(x, y),
byte_grid.0,
)));
NonNull::from(Box::leak(result))
}
@ -227,6 +231,8 @@ pub unsafe extern "C" fn sp_command_char_brightness(
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: bit_vec
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear(
offset: usize,
@ -235,13 +241,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear(
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(
servicepoint::Command::BitmapLinear(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
),
));
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinear(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
@ -270,6 +274,8 @@ pub unsafe extern "C" fn sp_command_bitmap_linear(
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: bit_vec
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_and(
offset: usize,
@ -278,13 +284,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_and(
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(
servicepoint::Command::BitmapLinearAnd(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
),
));
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearAnd(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
@ -313,6 +317,8 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_and(
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: bit_vec
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_or(
offset: usize,
@ -321,13 +327,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_or(
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(
servicepoint::Command::BitmapLinearOr(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
),
));
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearOr(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
@ -356,6 +360,8 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_or(
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: bit_vec
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_xor(
offset: usize,
@ -364,13 +370,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_xor(
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(
servicepoint::Command::BitmapLinearXor(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
),
));
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearXor(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
@ -392,6 +396,8 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_xor(
/// - `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`.
///
/// servicepoint_csbindgen_consumes: grid
#[no_mangle]
pub unsafe extern "C" fn sp_command_cp437_data(
x: usize,
@ -400,9 +406,10 @@ pub unsafe extern "C" fn sp_command_cp437_data(
) -> NonNull<SPCommand> {
assert!(!grid.is_null());
let grid = *Box::from_raw(grid);
let result = Box::new(SPCommand(
servicepoint::Command::Cp437Data(Origin::new(x, y), grid.0),
));
let result = Box::new(SPCommand(servicepoint::Command::Cp437Data(
Origin::new(x, y),
grid.0,
)));
NonNull::from(Box::leak(result))
}
@ -426,6 +433,8 @@ pub unsafe extern "C" fn sp_command_cp437_data(
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: bitmap
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_win(
x: usize,
@ -435,15 +444,13 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_win(
) -> NonNull<SPCommand> {
assert!(!bitmap.is_null());
let byte_grid = (*Box::from_raw(bitmap)).0;
let result = Box::new(SPCommand(
servicepoint::Command::BitmapLinearWin(
Origin::new(x, y),
byte_grid,
compression_code
.try_into()
.expect("invalid compression code"),
),
));
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearWin(
Origin::new(x, y),
byte_grid,
compression_code
.try_into()
.expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
@ -467,6 +474,8 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_win(
/// - `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]
///
/// servicepoint_csbindgen_consumes: command
#[no_mangle]
pub unsafe extern "C" fn sp_command_free(command: *mut SPCommand) {
assert!(!command.is_null());

View file

@ -3,7 +3,7 @@
//! prefix `sp_connection_`
use std::ffi::{c_char, CStr};
use std::ptr::null_mut;
use std::ptr::{null_mut, NonNull};
use crate::{SPCommand, SPPacket};
@ -18,7 +18,7 @@ use crate::{SPCommand, SPPacket};
/// ```
pub struct SPConnection(pub(crate) servicepoint::Connection);
/// Creates a new instance of [SPConnection].
/// Creates a new instance of [SPConnection] that uses UDP to send.
///
/// returns: NULL if connection fails, or connected instance
///
@ -46,6 +46,22 @@ pub unsafe extern "C" fn sp_connection_open(
Box::into_raw(Box::new(SPConnection(connection)))
}
/// Creates a new instance of [SPConnection] for testing that does not actually send anything.
///
/// returns: a new instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_connection_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_connection_fake() -> NonNull<SPConnection> {
let result = Box::new(SPConnection(servicepoint::Connection::Fake));
NonNull::from(Box::leak(result))
}
/// Sends a [SPPacket] to the display using the [SPConnection].
///
/// The passed `packet` gets consumed.
@ -64,6 +80,8 @@ pub unsafe extern "C" fn sp_connection_open(
/// - `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
///
/// servicepoint_csbindgen_consumes: packet
#[no_mangle]
pub unsafe extern "C" fn sp_connection_send_packet(
connection: *const SPConnection,
@ -93,6 +111,8 @@ pub unsafe extern "C" fn sp_connection_send_packet(
/// - `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
///
/// servicepoint_csbindgen_consumes: command
#[no_mangle]
pub unsafe extern "C" fn sp_connection_send_command(
connection: *const SPConnection,
@ -116,6 +136,8 @@ pub unsafe extern "C" fn sp_connection_send_command(
///
/// - `connection` points to a valid [SPConnection]
/// - `connection` is not used concurrently or after this call
///
/// servicepoint_csbindgen_consumes: connection
#[no_mangle]
pub unsafe extern "C" fn sp_connection_free(connection: *mut SPConnection) {
assert!(!connection.is_null());

View file

@ -24,6 +24,13 @@ 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();
/// see [Brightness::MIN]
pub const SP_BRIGHTNESS_MIN: u8 = 0;
/// see [Brightness::MAX]
pub const SP_BRIGHTNESS_MAX: u8 = 11;
/// Count of possible brightness values
pub const SP_BRIGHTNESS_LEVELS: u8 = 12;
/// Specifies the kind of compression to use.
#[repr(u16)]
pub enum SPCompressionCode {

View file

@ -2,9 +2,9 @@
//!
//! prefix `sp_cp437_grid_`
use std::ptr::NonNull;
use crate::SPByteSlice;
use servicepoint::{DataRef, Grid};
use std::ptr::NonNull;
/// A C-wrapper for grid containing codepage 437 characters.
///
@ -41,9 +41,8 @@ pub unsafe extern "C" fn sp_cp437_grid_new(
width: usize,
height: usize,
) -> NonNull<SPCp437Grid> {
let result = Box::new(SPCp437Grid(
servicepoint::Cp437Grid::new(width, height),
));
let result =
Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height)));
NonNull::from(Box::leak(result))
}
@ -71,11 +70,11 @@ pub unsafe extern "C" fn sp_cp437_grid_load(
data: *const u8,
data_length: usize,
) -> NonNull<SPCp437Grid> {
assert!(data.is_null());
assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, data_length);
let result = Box::new(SPCp437Grid(
servicepoint::Cp437Grid::load(width, height, data),
));
let result = Box::new(SPCp437Grid(servicepoint::Cp437Grid::load(
width, height, data,
)));
NonNull::from(Box::leak(result))
}
@ -117,6 +116,8 @@ pub unsafe extern "C" fn sp_cp437_grid_clone(
/// - `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]
///
/// servicepoint_csbindgen_consumes: cp437_grid
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: *mut SPCp437Grid) {
assert!(!cp437_grid.is_null());

View file

@ -25,7 +25,7 @@
//! }
//! ```
pub use crate::bitvec::*;
pub use crate::bit_vec::*;
pub use crate::bitmap::*;
pub use crate::brightness_grid::*;
pub use crate::byte_slice::*;
@ -35,7 +35,7 @@ pub use crate::constants::*;
pub use crate::cp437_grid::*;
pub use crate::packet::*;
mod bitvec;
mod bit_vec;
mod bitmap;
mod brightness_grid;
mod byte_slice;

View file

@ -26,6 +26,8 @@ pub struct SPPacket(pub(crate) servicepoint::packet::Packet);
/// - [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`.
///
/// servicepoint_csbindgen_consumes: command
#[no_mangle]
pub unsafe extern "C" fn sp_packet_from_command(
command: *mut SPCommand,
@ -36,6 +38,63 @@ pub unsafe extern "C" fn sp_packet_from_command(
NonNull::from(Box::leak(result))
}
/// Creates a raw [SPPacket] from parts.
///
/// # Arguments
///
/// - `command_code` specifies which command this packet contains
/// - `a`, `b`, `c` and `d` are command-specific header values
/// - `payload` is the optional data that is part of the command
/// - `payload_len` is the size of the payload
///
/// returns: new instance. Will never return null.
///
/// # Panics
///
/// - when `payload` is null, but `payload_len` is not zero
/// - when `payload_len` is zero, but `payload` is nonnull
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `payload` points to a valid memory region of at least `payload_len` bytes
/// - `payload` is not written to concurrently
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_from_parts(
command_code: u16,
a: u16,
b: u16,
c: u16,
d: u16,
payload: *const u8,
payload_len: usize,
) -> NonNull<SPPacket> {
assert_eq!(payload.is_null(), payload_len == 0);
let payload = if payload.is_null() {
vec![]
} else {
let payload = std::slice::from_raw_parts(payload, payload_len);
Vec::from(payload)
};
let packet = servicepoint::packet::Packet {
header: servicepoint::packet::Header {
command_code,
a,
b,
c,
d,
},
payload,
};
let result = Box::new(SPPacket(packet));
NonNull::from(Box::leak(result))
}
/// Tries to load a [SPPacket] from the passed array with the specified length.
///
/// returns: NULL in case of an error, pointer to the allocated packet otherwise
@ -94,7 +153,7 @@ pub unsafe extern "C" fn sp_packet_clone(
///
/// # Panics
///
/// - when `sp_packet_free` is NULL
/// - when `packet` is NULL
///
/// # Safety
///
@ -102,6 +161,8 @@ pub unsafe extern "C" fn sp_packet_clone(
///
/// - `packet` points to a valid [SPPacket]
/// - `packet` is not used concurrently or after this call
///
/// servicepoint_csbindgen_consumes: packet
#[no_mangle]
pub unsafe extern "C" fn sp_packet_free(packet: *mut SPPacket) {
assert!(!packet.is_null());

View file

@ -10,7 +10,8 @@ crate-type = ["cdylib"]
test = false
[build-dependencies]
csbindgen = "1.9.3"
csbindgen = { path = "../servicepoint_csbindgen/csbindgen" }
convert_case = "0.6.0"
[dependencies]
servicepoint_binding_c = { version = "0.10.0", path = "../servicepoint_binding_c" }

View file

@ -9,9 +9,8 @@ This crate contains C# bindings for the `servicepoint` library, enabling users t
```csharp
using ServicePoint;
// using statement calls Dispose() on scope exit, which frees unmanaged instances
using var connection = Connection.Open("127.0.0.1:2342");
using var pixels = Bitmap.New(Constants.PixelWidth, Constants.PixelHeight);
using var pixels = new Bitmap(Constants.PixelWidth, Constants.PixelHeight);
while (true)
{
@ -20,17 +19,14 @@ while (true)
Thread.Sleep(5000);
pixels.Fill(false);
connection.Send(Command.BitmapLinearWin(0, 0, pixels.Clone()));
connection.Send(Command.BitmapLinearWin(0, 0, pixels));
Thread.Sleep(5000);
}
```
A full example including project files is available as part of this crate.
An example including project files is available as part of this crate.
## Note on stability
This library is still in early development.
You can absolutely use it, and it works, but expect minor breaking changes with every version bump.
You can also check out the unit tests for usage examples for some things.
## Installation
@ -43,22 +39,39 @@ git submodule add https://github.com/cccb/servicepoint.git
git commit -m "add servicepoint submodule"
```
You can now reference `servicepoint-bindings-cs/src/ServicePoint.csproj` in your project.
You can now reference `servicepoint_binding_cs/src/ServicePoint.csproj` in your project.
The rust library will automatically be built.
Please provide more information in the form of an issue if you need the build to copy a different library file for your platform.
## Notes on differences to rust library
## Note on stability
Uses C bindings internally to provide a similar API to rust. Things to keep in mind:
This library is still in early development.
You can absolutely use it, and it works, but expect minor breaking changes with every version bump.
- You will get a `NullPointerException` when trying to call a method where the native instance has been consumed already (e.g. when `Send`ing a command instance twice). Send a clone instead of the original if you want to keep using it.
- Some lower-level APIs _will_ panic in native code when used improperly.
Example: manipulating the `Span<byte>` of an object after freeing the instance.
- C# specifics are documented in the library. Use the rust documentation for everything else. Naming and semantics are the same apart from CamelCase instead of kebab_case.
- You will only get rust backtraces in debug builds of the native code.
- F# is not explicitly tested. If there are usability or functionality problems, please open an issue.
- Reading and writing to instances concurrently is not safe. Only reading concurrently is safe.
## Documentation
There are multiple suboptimal ways to read the documentation for this.
You can read the [rust docs](https://docs.rs/servicepoint/latest/servicepoint/), as the types and methods in C# should have the same names as those in rust.
You can also read the documentation comments on each method.
Those are copied from the C API, which means they will include the `this` parameter in the description.
They are markdown formatted and may render in one line in your IDE - this is a known [issue](https://github.com/cccb/servicepoint/issues/17).
### Differences to other supported languages
C# does have some differences, especially regarding safety.
In rust, the compiler will tell you when trying to use an object that has already been dropped or moved.
In the C API, the user promises to keep things like that in mind,
and will get assertion failures or segmentation faults if you are lucky when doing something wrong.
The C# compiler will not help you to keep track of the lifetime of rust objects, but you also do not have to worry about
unnoticed memory corruption in most cases, as the library knows when you pass objects to methods that consume them and
raises a `NullReferenceException` instead.
When objects are garbage collected on the C# side, the rust object is freed as well.
Currently, lifetime tracking may not work reliably in multithreaded code. You should prevent concurrent write access.
## Everything else

View file

@ -0,0 +1,12 @@
namespace ServicePoint.Tests;
public class BitVecTests
{
[Fact]
public void UseAfterFree()
{
var bitvec = new BitVec(8);
_ = Command.BitmapLinear(0, bitvec, CompressionCode.Uncompressed);
Assert.Throws<NullReferenceException>(() => _ = Command.BitmapLinear(0, bitvec, CompressionCode.Uncompressed));
}
}

View file

@ -0,0 +1,12 @@
namespace ServicePoint.Tests;
public class BitmapTests
{
[Fact]
public void UseAfterFree()
{
var bitmap = Bitmap.NewScreenSized();
_ = Command.BitmapLinearWin(0, 0, bitmap, CompressionCode.Uncompressed);
Assert.Throws<NullReferenceException>(() => _ = Command.BitmapLinearWin(0, 0, bitmap, CompressionCode.Uncompressed));
}
}

View file

@ -0,0 +1,12 @@
namespace ServicePoint.Tests;
public class BBrightnessGridTests
{
[Fact]
public void UseAfterFree()
{
var grid = new BrightnessGrid(23, 42);
_ = Command.CharBrightness(0, 0, grid);
Assert.Throws<NullReferenceException>(() => _ = Command.CharBrightness(0, 0, grid));
}
}

View file

@ -0,0 +1,15 @@
namespace ServicePoint.Tests;
public class CommandTests
{
private Connection _fakeConnection = Connection.Fake();
[Fact]
public void UseAfterSend()
{
var command = Command.Clear();
_fakeConnection.Send(command);
Assert.Throws<NullReferenceException>(() => _fakeConnection.Send(command));
_fakeConnection.Send(Command.Clear());
}
}

View file

@ -0,0 +1,32 @@
using System.Runtime.CompilerServices;
namespace ServicePoint.Tests;
public class Cp437GridTests
{
[Fact]
public void UseAfterFree()
{
var grid = new Cp437Grid(2, 3);
_ = Command.Cp437Data(0, 0, grid.Clone());
_ = Command.Cp437Data(0, 0, grid);
Assert.Throws<NullReferenceException>(() => _ = Command.Cp437Data(0, 0, grid));
}
[Fact]
public void ReadAndWriteString()
{
var grid = new Cp437Grid(3, 2);
grid[1] = "abc";
Assert.Equal("abc", grid[1]);
}
[Fact]
public void LoadSpan()
{
var ascii_str = "abc123"u8;
var grid = Cp437Grid.Load(3, 2, ascii_str);
Assert.Equal("abc", grid[0]);
Assert.Equal("123", grid[1]);
}
}

View file

@ -0,0 +1,3 @@
global using System;
global using Xunit;
global using ServicePoint;

View file

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ServicePoint\ServicePoint.csproj" />
</ItemGroup>
</Project>

View file

@ -1,8 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePoint", "ServicePoint/ServicePoint.csproj", "{70EFFA3F-012A-4518-9627-466BEAE4252E}"
#
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePoint", "ServicePoint\ServicePoint.csproj", "{70EFFA3F-012A-4518-9627-466BEAE4252E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "lang-cs", "examples/lang_cs/lang_cs.csproj", "{DA3B8B6E-993A-47DA-844B-F92AF520FF59}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "lang-cs", "examples\lang_cs\lang_cs.csproj", "{DA3B8B6E-993A-47DA-844B-F92AF520FF59}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{C2F8EC4A-2426-4DC3-990F-C43810B183F5}"
ProjectSection(SolutionItems) = preProject
@ -10,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{C2F8EC
..\..\.envrc = ..\..\.envrc
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePoint.Tests", "ServicePoint.Tests\ServicePoint.Tests.csproj", "{CDA70D8F-E0EC-46E3-AF96-511267DF0BA0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -24,5 +27,9 @@ Global
{DA3B8B6E-993A-47DA-844B-F92AF520FF59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA3B8B6E-993A-47DA-844B-F92AF520FF59}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA3B8B6E-993A-47DA-844B-F92AF520FF59}.Release|Any CPU.Build.0 = Release|Any CPU
{CDA70D8F-E0EC-46E3-AF96-511267DF0BA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CDA70D8F-E0EC-46E3-AF96-511267DF0BA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CDA70D8F-E0EC-46E3-AF96-511267DF0BA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CDA70D8F-E0EC-46E3-AF96-511267DF0BA0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View file

@ -1,88 +1,23 @@
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class BitVec : SpNativeInstance<BindGen.BitVec>
public sealed partial class BitVec
{
public static BitVec New(int size)
{
unsafe
{
return new BitVec(NativeMethods.sp_bitvec_new((nuint)size));
}
}
public static BitVec Load(Span<byte> bytes)
public static BitVec Load(ReadOnlySpan<byte> bytes)
{
unsafe
{
fixed (byte* bytesPtr = bytes)
{
return new BitVec(NativeMethods.sp_bitvec_load(bytesPtr, (nuint)bytes.Length));
return Load(bytesPtr, (nuint)bytes.Length);
}
}
}
public BitVec Clone()
public bool this[nuint index]
{
unsafe
{
return new BitVec(NativeMethods.sp_bitvec_clone(Instance));
}
get => Get(index);
set => Set(index, value);
}
public bool this[int index]
{
get
{
unsafe
{
return NativeMethods.sp_bitvec_get(Instance, (nuint)index);
}
}
set
{
unsafe
{
NativeMethods.sp_bitvec_set(Instance, (nuint)index, value);
}
}
}
public void Fill(bool value)
{
unsafe
{
NativeMethods.sp_bitvec_fill(Instance, value);
}
}
public int Length
{
get
{
unsafe
{
return (int)NativeMethods.sp_bitvec_len(Instance);
}
}
}
public Span<byte> Data
{
get
{
unsafe
{
var slice = NativeMethods.sp_bitvec_unsafe_data_ref(Instance);
return new Span<byte>(slice.start, (int)slice.length);
}
}
}
private unsafe BitVec(BindGen.BitVec* instance) : base(instance)
{
}
private protected override unsafe void Free() => NativeMethods.sp_bitvec_free(Instance);
public Span<byte> Data => UnsafeDataRef().AsSpan();
}

View file

@ -0,0 +1,326 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace ServicePoint
{
public unsafe sealed partial class BitVec: IDisposable
{
#nullable enable
/// <summary>
/// Creates a new [SPBitVec] instance.
///
/// # Arguments
///
/// - `size`: size in bits.
///
/// returns: [SPBitVec] with all bits set to false. Will never return NULL.
///
/// # Panics
///
/// - when `size` is not divisible by 8.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
/// </summary>
public BitVec(nuint size) : this(sp_bitvec_new(size)) {}
/// <summary>
/// Interpret the data as a series of bits and load then into a new [SPBitVec] instance.
///
/// returns: [SPBitVec] instance containing data. Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static BitVec Load(byte* data, nuint data_length)
{
return new BitVec(BitVec.sp_bitvec_load(data, data_length));
}
/// <summary>
/// Clones a [SPBitVec].
///
/// returns: new [SPBitVec] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public BitVec Clone()
{
return new BitVec(BitVec.sp_bitvec_clone(this.__Instance));
}
/// <summary>
/// Gets the value of a bit from the [SPBitVec].
///
/// # Arguments
///
/// - `bit_vec`: instance to read from
/// - `index`: the bit index to read
///
/// returns: value of the bit
///
/// # Panics
///
/// - when `bit_vec` is NULL
/// - when accessing `index` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool Get(nuint index)
{
return BitVec.sp_bitvec_get(this.__Instance, index);
}
/// <summary>
/// Sets the value of a bit in the [SPBitVec].
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
/// - `index`: the bit index to edit
/// - `value`: the value to set the bit to
///
/// # Panics
///
/// - when `bit_vec` is NULL
/// - when accessing `index` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to or read from concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Set(nuint index, bool value)
{
BitVec.sp_bitvec_set(this.__Instance, index, value);
}
/// <summary>
/// Sets the value of all bits in the [SPBitVec].
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
/// - `value`: the value to set all bits to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to or read from concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Fill(bool value)
{
BitVec.sp_bitvec_fill(this.__Instance, value);
}
/// <summary>
/// Gets the length of the [SPBitVec] in bits.
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public nuint Len()
{
return BitVec.sp_bitvec_len(this.__Instance);
}
/// <summary>
/// Returns true if length is 0.
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool IsEmpty()
{
return BitVec.sp_bitvec_is_empty(this.__Instance);
}
/// <summary>
/// Gets an unsafe reference to the data of the [SPBitVec] instance.
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - the returned memory range is never accessed after the passed [SPBitVec] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBitVec] or directly
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public SPByteSlice UnsafeDataRef()
{
return BitVec.sp_bitvec_unsafe_data_ref(this.__Instance);
}
#region internal machinery
private SPBitVec* _instance;
internal SPBitVec* __Instance
{
get
{
if (_instance == null)
throw new NullReferenceException("instance is null");
return _instance;
}
}
private BitVec(SPBitVec* instance)
{
ArgumentNullException.ThrowIfNull(instance);
_instance = instance;
}
internal SPBitVec* __Into()
{
var instance = __Instance;
_instance = null;
return instance;
}
private void __Free()
{
if (_instance != null)
BitVec.sp_bitvec_free(__Into());
}
public void Dispose()
{
__Free();
GC.SuppressFinalize(this);
}
~BitVec() => __Free();
#endregion
#nullable restore
#region native methods
const string __DllName = "servicepoint_binding_c";
[DllImport(__DllName, EntryPoint = "sp_bitvec_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPBitVec* sp_bitvec_new(nuint size);
[DllImport(__DllName, EntryPoint = "sp_bitvec_load", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPBitVec* sp_bitvec_load(byte* data, nuint data_length);
[DllImport(__DllName, EntryPoint = "sp_bitvec_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPBitVec* sp_bitvec_clone(SPBitVec* bit_vec);
[DllImport(__DllName, EntryPoint = "sp_bitvec_free", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_bitvec_free(SPBitVec* bit_vec);
[DllImport(__DllName, EntryPoint = "sp_bitvec_get", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool sp_bitvec_get(SPBitVec* bit_vec, nuint index);
[DllImport(__DllName, EntryPoint = "sp_bitvec_set", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_bitvec_set(SPBitVec* bit_vec, nuint index, [MarshalAs(UnmanagedType.U1)] bool value);
[DllImport(__DllName, EntryPoint = "sp_bitvec_fill", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_bitvec_fill(SPBitVec* bit_vec, [MarshalAs(UnmanagedType.U1)] bool value);
[DllImport(__DllName, EntryPoint = "sp_bitvec_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern nuint sp_bitvec_len(SPBitVec* bit_vec);
[DllImport(__DllName, EntryPoint = "sp_bitvec_is_empty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool sp_bitvec_is_empty(SPBitVec* bit_vec);
[DllImport(__DllName, EntryPoint = "sp_bitvec_unsafe_data_ref", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPByteSlice sp_bitvec_unsafe_data_ref(SPBitVec* bit_vec);
#endregion
}
[StructLayout(LayoutKind.Sequential)]
public unsafe partial struct SPBitVec
{
}
}

View file

@ -1,100 +1,23 @@
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class Bitmap : SpNativeInstance<BindGen.Bitmap>
public sealed partial class Bitmap
{
public static Bitmap New(int width, int height)
{
unsafe
{
return new Bitmap(NativeMethods.sp_bitmap_new((nuint)width, (nuint)height));
}
}
public static Bitmap Load(int width, int height, Span<byte> bytes)
public static Bitmap Load(nuint width, nuint height, ReadOnlySpan<byte> bytes)
{
unsafe
{
fixed (byte* bytesPtr = bytes)
{
return new Bitmap(NativeMethods.sp_bitmap_load((nuint)width, (nuint)height, bytesPtr,
(nuint)bytes.Length));
return Load(width, height, bytesPtr, (nuint)bytes.Length);
}
}
}
public Bitmap Clone()
public bool this[nuint x, nuint y]
{
unsafe
{
return new Bitmap(NativeMethods.sp_bitmap_clone(Instance));
}
get => Get(x, y);
set => Set(x, y, value);
}
public bool this[int x, int y]
{
get
{
unsafe
{
return NativeMethods.sp_bitmap_get(Instance, (nuint)x, (nuint)y);
}
}
set
{
unsafe
{
NativeMethods.sp_bitmap_set(Instance, (nuint)x, (nuint)y, value);
}
}
}
public void Fill(bool value)
{
unsafe
{
NativeMethods.sp_bitmap_fill(Instance, value);
}
}
public int Width
{
get
{
unsafe
{
return (int)NativeMethods.sp_bitmap_width(Instance);
}
}
}
public int Height
{
get
{
unsafe
{
return (int)NativeMethods.sp_bitmap_height(Instance);
}
}
}
public Span<byte> Data
{
get
{
unsafe
{
var slice = NativeMethods.sp_bitmap_unsafe_data_ref(Instance);
return new Span<byte>(slice.start, (int)slice.length);
}
}
}
private unsafe Bitmap(BindGen.Bitmap* instance) : base(instance)
{
}
private protected override unsafe void Free() => NativeMethods.sp_bitmap_free(Instance);
public Span<byte> Data => UnsafeDataRef().AsSpan();
}

View file

@ -0,0 +1,349 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace ServicePoint
{
public unsafe sealed partial class Bitmap: IDisposable
{
#nullable enable
/// <summary>
/// Creates a new [SPBitmap] with the specified dimensions.
///
/// # Arguments
///
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL.
///
/// # Panics
///
/// - when the width is not dividable by 8
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
/// </summary>
public Bitmap(nuint width, nuint height) : this(sp_bitmap_new(width, height)) {}
/// <summary>
/// Creates a new [SPBitmap] with a size matching the screen.
///
/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Bitmap NewScreenSized()
{
return new Bitmap(Bitmap.sp_bitmap_new_screen_sized());
}
/// <summary>
/// Loads a [SPBitmap] with the specified dimensions from the provided data.
///
/// # Arguments
///
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: [SPBitmap] that contains a copy of the provided data. Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the dimensions and data size do not match exactly.
/// - when the width is not dividable by 8
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length` bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Bitmap Load(nuint width, nuint height, byte* data, nuint data_length)
{
return new Bitmap(Bitmap.sp_bitmap_load(width, height, data, data_length));
}
/// <summary>
/// Clones a [SPBitmap].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public Bitmap Clone()
{
return new Bitmap(Bitmap.sp_bitmap_clone(this.__Instance));
}
/// <summary>
/// Gets the current value at the specified position in the [SPBitmap].
///
/// # Arguments
///
/// - `bitmap`: instance to read from
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
/// - when `bitmap` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool Get(nuint x, nuint y)
{
return Bitmap.sp_bitmap_get(this.__Instance, x, y);
}
/// <summary>
/// Sets the value of the specified position in the [SPBitmap].
///
/// # Arguments
///
/// - `bitmap`: 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 `bitmap` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to or read from concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Set(nuint x, nuint y, bool value)
{
Bitmap.sp_bitmap_set(this.__Instance, x, y, value);
}
/// <summary>
/// Sets the state of all pixels in the [SPBitmap].
///
/// # Arguments
///
/// - `bitmap`: instance to write to
/// - `value`: the value to set all pixels to
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to or read from concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Fill(bool value)
{
Bitmap.sp_bitmap_fill(this.__Instance, value);
}
/// <summary>
/// Gets the width in pixels of the [SPBitmap] instance.
///
/// # Arguments
///
/// - `bitmap`: instance to read from
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public nuint Width()
{
return Bitmap.sp_bitmap_width(this.__Instance);
}
/// <summary>
/// Gets the height in pixels of the [SPBitmap] instance.
///
/// # Arguments
///
/// - `bitmap`: instance to read from
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public nuint Height()
{
return Bitmap.sp_bitmap_height(this.__Instance);
}
/// <summary>
/// Gets an unsafe reference to the data of the [SPBitmap] instance.
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - the returned memory range is never accessed after the passed [SPBitmap] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBitmap] or directly
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public SPByteSlice UnsafeDataRef()
{
return Bitmap.sp_bitmap_unsafe_data_ref(this.__Instance);
}
#region internal machinery
private SPBitmap* _instance;
internal SPBitmap* __Instance
{
get
{
if (_instance == null)
throw new NullReferenceException("instance is null");
return _instance;
}
}
private Bitmap(SPBitmap* instance)
{
ArgumentNullException.ThrowIfNull(instance);
_instance = instance;
}
internal SPBitmap* __Into()
{
var instance = __Instance;
_instance = null;
return instance;
}
private void __Free()
{
if (_instance != null)
Bitmap.sp_bitmap_free(__Into());
}
public void Dispose()
{
__Free();
GC.SuppressFinalize(this);
}
~Bitmap() => __Free();
#endregion
#nullable restore
#region native methods
const string __DllName = "servicepoint_binding_c";
[DllImport(__DllName, EntryPoint = "sp_bitmap_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPBitmap* sp_bitmap_new(nuint width, nuint height);
[DllImport(__DllName, EntryPoint = "sp_bitmap_new_screen_sized", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPBitmap* sp_bitmap_new_screen_sized();
[DllImport(__DllName, EntryPoint = "sp_bitmap_load", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPBitmap* sp_bitmap_load(nuint width, nuint height, byte* data, nuint data_length);
[DllImport(__DllName, EntryPoint = "sp_bitmap_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPBitmap* sp_bitmap_clone(SPBitmap* bitmap);
[DllImport(__DllName, EntryPoint = "sp_bitmap_free", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_bitmap_free(SPBitmap* bitmap);
[DllImport(__DllName, EntryPoint = "sp_bitmap_get", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool sp_bitmap_get(SPBitmap* bitmap, nuint x, nuint y);
[DllImport(__DllName, EntryPoint = "sp_bitmap_set", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_bitmap_set(SPBitmap* bitmap, nuint x, nuint y, [MarshalAs(UnmanagedType.U1)] bool value);
[DllImport(__DllName, EntryPoint = "sp_bitmap_fill", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_bitmap_fill(SPBitmap* bitmap, [MarshalAs(UnmanagedType.U1)] bool value);
[DllImport(__DllName, EntryPoint = "sp_bitmap_width", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern nuint sp_bitmap_width(SPBitmap* bitmap);
[DllImport(__DllName, EntryPoint = "sp_bitmap_height", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern nuint sp_bitmap_height(SPBitmap* bitmap);
[DllImport(__DllName, EntryPoint = "sp_bitmap_unsafe_data_ref", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPByteSlice sp_bitmap_unsafe_data_ref(SPBitmap* bitmap);
#endregion
}
[StructLayout(LayoutKind.Sequential)]
public unsafe partial struct SPBitmap
{
}
}

View file

@ -1,100 +1,23 @@
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class BrightnessGrid : SpNativeInstance<BindGen.BrightnessGrid>
public sealed partial class BrightnessGrid
{
public static BrightnessGrid New(int width, int height)
{
unsafe
{
return new BrightnessGrid(NativeMethods.sp_brightness_grid_new((nuint)width, (nuint)height));
}
}
public static BrightnessGrid Load(int width, int height, Span<byte> bytes)
public static BrightnessGrid Load(nuint width, nuint height, ReadOnlySpan<byte> bytes)
{
unsafe
{
fixed (byte* bytesPtr = bytes)
{
return new BrightnessGrid(NativeMethods.sp_brightness_grid_load((nuint)width, (nuint)height, bytesPtr,
(nuint)bytes.Length));
return Load(width, height, bytesPtr, (nuint)bytes.Length);
}
}
}
public BrightnessGrid Clone()
public byte this[nuint x, nuint y]
{
unsafe
{
return new BrightnessGrid(NativeMethods.sp_brightness_grid_clone(Instance));
}
get => Get(x, y);
set => Set(x, y, value);
}
public byte this[int x, int y]
{
get
{
unsafe
{
return NativeMethods.sp_brightness_grid_get(Instance, (nuint)x, (nuint)y);
}
}
set
{
unsafe
{
NativeMethods.sp_brightness_grid_set(Instance, (nuint)x, (nuint)y, value);
}
}
}
public void Fill(byte value)
{
unsafe
{
NativeMethods.sp_brightness_grid_fill(Instance, value);
}
}
public int Width
{
get
{
unsafe
{
return (int)NativeMethods.sp_brightness_grid_width(Instance);
}
}
}
public int Height
{
get
{
unsafe
{
return (int)NativeMethods.sp_brightness_grid_height(Instance);
}
}
}
public Span<byte> Data
{
get
{
unsafe
{
var slice = NativeMethods.sp_brightness_grid_unsafe_data_ref(Instance);
return new Span<byte>(slice.start, (int)slice.length);
}
}
}
private unsafe BrightnessGrid(BindGen.BrightnessGrid* instance) : base(instance)
{
}
private protected override unsafe void Free() => NativeMethods.sp_brightness_grid_free(Instance);
public Span<byte> Data => UnsafeDataRef().AsSpan();
}

View file

@ -0,0 +1,331 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace ServicePoint
{
public unsafe sealed partial class BrightnessGrid: IDisposable
{
#nullable enable
/// <summary>
/// Creates a new [SPBrightnessGrid] with the specified dimensions.
///
/// returns: [SPBrightnessGrid] initialized to 0. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
/// </summary>
public BrightnessGrid(nuint width, nuint height) : this(sp_brightness_grid_new(width, height)) {}
/// <summary>
/// Loads a [SPBrightnessGrid] with the specified dimensions from the provided data.
///
/// returns: new [SPBrightnessGrid] instance. Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static BrightnessGrid Load(nuint width, nuint height, byte* data, nuint data_length)
{
return new BrightnessGrid(BrightnessGrid.sp_brightness_grid_load(width, height, data, data_length));
}
/// <summary>
/// Clones a [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: new [SPBrightnessGrid] instance. Will never return NULL.
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public BrightnessGrid Clone()
{
return new BrightnessGrid(BrightnessGrid.sp_brightness_grid_clone(this.__Instance));
}
/// <summary>
/// Gets the current value at the specified position.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
/// - `x` and `y`: position of the cell to read
///
/// returns: value at position
///
/// # Panics
///
/// - when `brightness_grid` is NULL
/// - When accessing `x` or `y` out of bounds.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public byte Get(nuint x, nuint y)
{
return BrightnessGrid.sp_brightness_grid_get(this.__Instance, x, y);
}
/// <summary>
/// Sets the value of the specified position in the [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to write to
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
/// # Panics
///
/// - when `brightness_grid` is NULL
/// - When accessing `x` or `y` out of bounds.
/// - When providing an invalid brightness value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBitVec]
/// - `brightness_grid` is not written to or read from concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Set(nuint x, nuint y, byte value)
{
BrightnessGrid.sp_brightness_grid_set(this.__Instance, x, y, value);
}
/// <summary>
/// Sets the value of all cells in the [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to write to
/// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `brightness_grid` is NULL
/// - When providing an invalid brightness value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to or read from concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Fill(byte value)
{
BrightnessGrid.sp_brightness_grid_fill(this.__Instance, value);
}
/// <summary>
/// Gets the width of the [SPBrightnessGrid] instance.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: width
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public nuint Width()
{
return BrightnessGrid.sp_brightness_grid_width(this.__Instance);
}
/// <summary>
/// Gets the height of the [SPBrightnessGrid] instance.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: height
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public nuint Height()
{
return BrightnessGrid.sp_brightness_grid_height(this.__Instance);
}
/// <summary>
/// Gets an unsafe reference to the data of the [SPBrightnessGrid] instance.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: slice of bytes underlying the `brightness_grid`.
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - the returned memory range is never accessed after the passed [SPBrightnessGrid] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBrightnessGrid] or directly
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public SPByteSlice UnsafeDataRef()
{
return BrightnessGrid.sp_brightness_grid_unsafe_data_ref(this.__Instance);
}
#region internal machinery
private SPBrightnessGrid* _instance;
internal SPBrightnessGrid* __Instance
{
get
{
if (_instance == null)
throw new NullReferenceException("instance is null");
return _instance;
}
}
private BrightnessGrid(SPBrightnessGrid* instance)
{
ArgumentNullException.ThrowIfNull(instance);
_instance = instance;
}
internal SPBrightnessGrid* __Into()
{
var instance = __Instance;
_instance = null;
return instance;
}
private void __Free()
{
if (_instance != null)
BrightnessGrid.sp_brightness_grid_free(__Into());
}
public void Dispose()
{
__Free();
GC.SuppressFinalize(this);
}
~BrightnessGrid() => __Free();
#endregion
#nullable restore
#region native methods
const string __DllName = "servicepoint_binding_c";
[DllImport(__DllName, EntryPoint = "sp_brightness_grid_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPBrightnessGrid* sp_brightness_grid_new(nuint width, nuint height);
[DllImport(__DllName, EntryPoint = "sp_brightness_grid_load", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPBrightnessGrid* sp_brightness_grid_load(nuint width, nuint height, byte* data, nuint data_length);
[DllImport(__DllName, EntryPoint = "sp_brightness_grid_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPBrightnessGrid* sp_brightness_grid_clone(SPBrightnessGrid* brightness_grid);
[DllImport(__DllName, EntryPoint = "sp_brightness_grid_free", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_brightness_grid_free(SPBrightnessGrid* brightness_grid);
[DllImport(__DllName, EntryPoint = "sp_brightness_grid_get", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern byte sp_brightness_grid_get(SPBrightnessGrid* brightness_grid, nuint x, nuint y);
[DllImport(__DllName, EntryPoint = "sp_brightness_grid_set", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_brightness_grid_set(SPBrightnessGrid* brightness_grid, nuint x, nuint y, byte value);
[DllImport(__DllName, EntryPoint = "sp_brightness_grid_fill", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_brightness_grid_fill(SPBrightnessGrid* brightness_grid, byte value);
[DllImport(__DllName, EntryPoint = "sp_brightness_grid_width", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern nuint sp_brightness_grid_width(SPBrightnessGrid* brightness_grid);
[DllImport(__DllName, EntryPoint = "sp_brightness_grid_height", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern nuint sp_brightness_grid_height(SPBrightnessGrid* brightness_grid);
[DllImport(__DllName, EntryPoint = "sp_brightness_grid_unsafe_data_ref", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPByteSlice sp_brightness_grid_unsafe_data_ref(SPBrightnessGrid* brightness_grid);
#endregion
}
[StructLayout(LayoutKind.Sequential)]
public unsafe partial struct SPBrightnessGrid
{
}
}

View file

@ -0,0 +1,6 @@
namespace ServicePoint;
public partial struct SPByteSlice
{
public unsafe Span<byte> AsSpan() => new (start, (int)length);
}

View file

@ -0,0 +1,24 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace ServicePoint
{
[StructLayout(LayoutKind.Sequential)]
public unsafe partial struct SPByteSlice
{
public byte* start;
public nuint length;
}
}

View file

@ -1,129 +1,16 @@
using System.Diagnostics.CodeAnalysis;
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class Command : SpNativeInstance<BindGen.Command>
public sealed partial class Command
{
public static bool TryFromPacket(Packet packet, [MaybeNullWhen(false)] out Command command)
{
unsafe
{
var result = NativeMethods.sp_command_try_from_packet(packet.Into());
if (result == null)
{
command = null;
return false;
}
command = new Command(result);
return true;
}
return (command = TryFromPacket(packet)) != null;
}
public Command Clone()
public Packet IntoPacket()
{
unsafe
{
return new Command(NativeMethods.sp_command_clone(Instance));
}
return Packet.FromCommand(this);
}
public static Command Clear()
{
unsafe
{
return new Command(NativeMethods.sp_command_clear());
}
}
public static Command HardReset()
{
unsafe
{
return new Command(NativeMethods.sp_command_hard_reset());
}
}
public static Command FadeOut()
{
unsafe
{
return new Command(NativeMethods.sp_command_fade_out());
}
}
public static Command Brightness(byte brightness)
{
unsafe
{
return new Command(NativeMethods.sp_command_brightness(brightness));
}
}
public static Command CharBrightness(int x, int y, BrightnessGrid grid)
{
unsafe
{
return new Command(NativeMethods.sp_command_char_brightness((ushort)x, (ushort)y, grid.Into()));
}
}
public static Command BitmapLinear(int offset, BitVec bitVec, CompressionCode compressionCode)
{
unsafe
{
return new Command(
NativeMethods.sp_command_bitmap_linear((ushort)offset, bitVec.Into(), compressionCode));
}
}
public static Command BitmapLinearAnd(int offset, BitVec bitVec, CompressionCode compressionCode)
{
unsafe
{
return new Command(
NativeMethods.sp_command_bitmap_linear_and((ushort)offset, bitVec.Into(), compressionCode));
}
}
public static Command BitmapLinearOr(int offset, BitVec bitVec, CompressionCode compressionCode)
{
unsafe
{
return new Command(
NativeMethods.sp_command_bitmap_linear_or((ushort)offset, bitVec.Into(), compressionCode));
}
}
public static Command BitmapLinearXor(int offset, BitVec bitVec, CompressionCode compressionCode)
{
unsafe
{
return new Command(
NativeMethods.sp_command_bitmap_linear_xor((ushort)offset, bitVec.Into(), compressionCode));
}
}
public static Command BitmapLinearWin(int x, int y, Bitmap bitmap, CompressionCode compression)
{
unsafe
{
return new Command(NativeMethods.sp_command_bitmap_linear_win((ushort)x, (ushort)y, bitmap.Into(), compression));
}
}
public static Command Cp437Data(int x, int y, Cp437Grid byteGrid)
{
unsafe
{
return new Command(NativeMethods.sp_command_cp437_data((ushort)x, (ushort)y, byteGrid.Into()));
}
}
private unsafe Command(BindGen.Command* instance) : base(instance)
{
}
private protected override unsafe void Free() => NativeMethods.sp_command_free(Instance);
}

View file

@ -0,0 +1,481 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace ServicePoint
{
public unsafe sealed partial class Command: IDisposable
{
#nullable enable
/// <summary>
/// Tries to turn a [SPPacket] into a [SPCommand].
///
/// The packet is deallocated in the process.
///
/// Returns: pointer to new [SPCommand] instance or NULL if parsing failed.
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - [SPPacket] points to a valid instance of [SPPacket]
/// - [SPPacket] is not used concurrently or after this call
/// - the result is checked for NULL
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: packet
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command? TryFromPacket(Packet packet)
{
var native = Command.sp_command_try_from_packet(packet.__Into());
return native == null ? null : new Command(native);
}
/// <summary>
/// Clones a [SPCommand] instance.
///
/// returns: new [SPCommand] instance. Will never return NULL.
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `command` points to a valid instance of [SPCommand]
/// - `command` is not written to concurrently
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public Command Clone()
{
return new Command(Command.sp_command_clone(this.__Instance));
}
/// <summary>
/// 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 [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command Clear()
{
return new Command(Command.sp_command_clear());
}
/// <summary>
/// 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 [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command HardReset()
{
return new Command(Command.sp_command_hard_reset());
}
/// <summary>
/// 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 [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command FadeOut()
{
return new Command(Command.sp_command_fade_out());
}
/// <summary>
/// Set the brightness of all tiles to the same value.
///
/// Returns: a new [Command::Brightness] instance. Will never return NULL.
///
/// # Panics
///
/// - When the provided brightness value is out of range (0-11).
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command Brightness(byte brightness)
{
return new Command(Command.sp_command_brightness(brightness));
}
/// <summary>
/// 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.
///
/// # Panics
///
/// - when `grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPBrightnessGrid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: grid
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command CharBrightness(nuint x, nuint y, BrightnessGrid grid)
{
return new Command(Command.sp_command_char_brightness(x, y, grid.__Into()));
}
/// <summary>
/// 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.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: bit_vec
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command BitmapLinear(nuint offset, BitVec bit_vec, CompressionCode compression)
{
return new Command(Command.sp_command_bitmap_linear(offset, bit_vec.__Into(), compression));
}
/// <summary>
/// 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.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: bit_vec
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command BitmapLinearAnd(nuint offset, BitVec bit_vec, CompressionCode compression)
{
return new Command(Command.sp_command_bitmap_linear_and(offset, bit_vec.__Into(), compression));
}
/// <summary>
/// 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.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: bit_vec
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command BitmapLinearOr(nuint offset, BitVec bit_vec, CompressionCode compression)
{
return new Command(Command.sp_command_bitmap_linear_or(offset, bit_vec.__Into(), compression));
}
/// <summary>
/// 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.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: bit_vec
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command BitmapLinearXor(nuint offset, BitVec bit_vec, CompressionCode compression)
{
return new Command(Command.sp_command_bitmap_linear_xor(offset, bit_vec.__Into(), compression));
}
/// <summary>
/// Show text on the screen.
///
/// The passed [SPCp437Grid] gets consumed.
///
/// Returns: a new [Command::Cp437Data] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is null
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPCp437Grid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: grid
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command Cp437Data(nuint x, nuint y, Cp437Grid grid)
{
return new Command(Command.sp_command_cp437_data(x, y, grid.__Into()));
}
/// <summary>
/// Sets a window of pixels to the specified values.
///
/// The passed [SPBitmap] gets consumed.
///
/// Returns: a new [Command::BitmapLinearWin] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bitmap` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid instance of [SPBitmap]
/// - `bitmap` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
///
/// servicepoint_csbindgen_consumes: bitmap
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Command BitmapLinearWin(nuint x, nuint y, Bitmap bitmap, CompressionCode compression_code)
{
return new Command(Command.sp_command_bitmap_linear_win(x, y, bitmap.__Into(), compression_code));
}
#region internal machinery
private SPCommand* _instance;
internal SPCommand* __Instance
{
get
{
if (_instance == null)
throw new NullReferenceException("instance is null");
return _instance;
}
}
private Command(SPCommand* instance)
{
ArgumentNullException.ThrowIfNull(instance);
_instance = instance;
}
internal SPCommand* __Into()
{
var instance = __Instance;
_instance = null;
return instance;
}
private void __Free()
{
if (_instance != null)
Command.sp_command_free(__Into());
}
public void Dispose()
{
__Free();
GC.SuppressFinalize(this);
}
~Command() => __Free();
#endregion
#nullable restore
#region native methods
const string __DllName = "servicepoint_binding_c";
[DllImport(__DllName, EntryPoint = "sp_command_try_from_packet", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_try_from_packet(SPPacket* packet);
[DllImport(__DllName, EntryPoint = "sp_command_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_clone(SPCommand* command);
[DllImport(__DllName, EntryPoint = "sp_command_clear", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_clear();
[DllImport(__DllName, EntryPoint = "sp_command_hard_reset", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_hard_reset();
[DllImport(__DllName, EntryPoint = "sp_command_fade_out", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_fade_out();
[DllImport(__DllName, EntryPoint = "sp_command_brightness", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_brightness(byte brightness);
[DllImport(__DllName, EntryPoint = "sp_command_char_brightness", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_char_brightness(nuint x, nuint y, SPBrightnessGrid* grid);
[DllImport(__DllName, EntryPoint = "sp_command_bitmap_linear", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_bitmap_linear(nuint offset, SPBitVec* bit_vec, CompressionCode compression);
[DllImport(__DllName, EntryPoint = "sp_command_bitmap_linear_and", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_bitmap_linear_and(nuint offset, SPBitVec* bit_vec, CompressionCode compression);
[DllImport(__DllName, EntryPoint = "sp_command_bitmap_linear_or", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_bitmap_linear_or(nuint offset, SPBitVec* bit_vec, CompressionCode compression);
[DllImport(__DllName, EntryPoint = "sp_command_bitmap_linear_xor", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_bitmap_linear_xor(nuint offset, SPBitVec* bit_vec, CompressionCode compression);
[DllImport(__DllName, EntryPoint = "sp_command_cp437_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_cp437_data(nuint x, nuint y, SPCp437Grid* grid);
[DllImport(__DllName, EntryPoint = "sp_command_bitmap_linear_win", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCommand* sp_command_bitmap_linear_win(nuint x, nuint y, SPBitmap* bitmap, CompressionCode compression_code);
[DllImport(__DllName, EntryPoint = "sp_command_free", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_command_free(SPCommand* command);
#endregion
}
[StructLayout(LayoutKind.Sequential)]
public unsafe partial struct SPCommand
{
}
}

View file

@ -1,40 +1,21 @@
using System.Text;
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class Connection : SpNativeInstance<BindGen.Connection>
public sealed partial class Connection
{
public static Connection Open(string host)
public static Connection? Open(string host)
{
unsafe
{
fixed (byte* bytePtr = Encoding.UTF8.GetBytes(host))
{
return new Connection(NativeMethods.sp_connection_open(bytePtr));
return Open(bytePtr);
}
}
}
public bool Send(Packet packet)
{
unsafe
{
return NativeMethods.sp_connection_send_packet(Instance, packet.Into());
}
}
public bool Send(Packet packet) => SendPacket(packet);
public bool Send(Command command)
{
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)
{
}
public bool Send(Command command) => SendCommand(command);
}

View file

@ -0,0 +1,187 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace ServicePoint
{
public unsafe sealed partial class Connection: IDisposable
{
#nullable enable
/// <summary>
/// Creates a new instance of [SPConnection] that uses UDP to send.
///
/// returns: NULL if connection fails, or connected instance
///
/// # Panics
///
/// - when `host` is null or an invalid host
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_connection_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Connection? Open(byte* host)
{
var native = Connection.sp_connection_open(host);
return native == null ? null : new Connection(native);
}
/// <summary>
/// Creates a new instance of [SPConnection] for testing that does not actually send anything.
///
/// returns: a new instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_connection_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Connection Fake()
{
return new Connection(Connection.sp_connection_fake());
}
/// <summary>
/// Sends a [SPPacket] to the display using the [SPConnection].
///
/// The passed `packet` gets consumed.
///
/// returns: true in case of success
///
/// # Panics
///
/// - when `connection` is NULL
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid instance of [SPConnection]
/// - `packet` points to a valid instance of [SPPacket]
/// - `packet` is not used concurrently or after this call
///
/// servicepoint_csbindgen_consumes: packet
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool SendPacket(Packet packet)
{
return Connection.sp_connection_send_packet(this.__Instance, packet.__Into());
}
/// <summary>
/// Sends a [SPCommand] to the display using the [SPConnection].
///
/// The passed `command` gets consumed.
///
/// returns: true in case of success
///
/// # Panics
///
/// - when `connection` is NULL
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid instance of [SPConnection]
/// - `command` points to a valid instance of [SPPacket]
/// - `command` is not used concurrently or after this call
///
/// servicepoint_csbindgen_consumes: command
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool SendCommand(Command command)
{
return Connection.sp_connection_send_command(this.__Instance, command.__Into());
}
#region internal machinery
private SPConnection* _instance;
internal SPConnection* __Instance
{
get
{
if (_instance == null)
throw new NullReferenceException("instance is null");
return _instance;
}
}
private Connection(SPConnection* instance)
{
ArgumentNullException.ThrowIfNull(instance);
_instance = instance;
}
internal SPConnection* __Into()
{
var instance = __Instance;
_instance = null;
return instance;
}
private void __Free()
{
if (_instance != null)
Connection.sp_connection_free(__Into());
}
public void Dispose()
{
__Free();
GC.SuppressFinalize(this);
}
~Connection() => __Free();
#endregion
#nullable restore
#region native methods
const string __DllName = "servicepoint_binding_c";
[DllImport(__DllName, EntryPoint = "sp_connection_open", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPConnection* sp_connection_open(byte* host);
[DllImport(__DllName, EntryPoint = "sp_connection_fake", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPConnection* sp_connection_fake();
[DllImport(__DllName, EntryPoint = "sp_connection_send_packet", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool sp_connection_send_packet(SPConnection* connection, SPPacket* packet);
[DllImport(__DllName, EntryPoint = "sp_connection_send_command", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool sp_connection_send_command(SPConnection* connection, SPCommand* command);
[DllImport(__DllName, EntryPoint = "sp_connection_free", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_connection_free(SPConnection* connection);
#endregion
}
[StructLayout(LayoutKind.Sequential)]
public unsafe partial struct SPConnection
{
}
}

View file

@ -1,17 +1,15 @@
using ServicePoint.BindGen;
namespace ServicePoint;
public static class Constants
{
/// size of a single tile in one dimension
public const nuint TileSize = NativeMethods.SP_TILE_SIZE;
public const nuint TileSize = ConstantsNative.SP_TILE_SIZE;
/// tile count in the x-direction
public const nuint TileWidth = NativeMethods.SP_TILE_WIDTH;
public const nuint TileWidth = ConstantsNative.SP_TILE_WIDTH;
/// tile count in the y-direction
public const nuint TileHeight = NativeMethods.SP_TILE_SIZE;
public const nuint TileHeight = ConstantsNative.SP_TILE_SIZE;
/// screen width in pixels
public const nuint PixelWidth = TileWidth * TileSize;
@ -21,4 +19,8 @@ public static class Constants
/// pixel count on whole screen
public const nuint PixelCount = PixelWidth * PixelHeight;
public const byte MinBrightness = ConstantsNative.SP_BRIGHTNESS_MIN;
public const byte MaxBrightness = ConstantsNative.SP_BRIGHTNESS_MAX;
public const byte BrightnessLevels = ConstantsNative.SP_BRIGHTNESS_LEVELS;
}

View file

@ -0,0 +1,40 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace ServicePoint
{
public static unsafe partial class ConstantsNative
{
const string __DllName = "servicepoint_binding_c";
public const nuint SP_TILE_SIZE = 8;
public const nuint SP_TILE_WIDTH = 56;
public const nuint SP_TILE_HEIGHT = 20;
public const byte SP_BRIGHTNESS_MIN = 0;
public const byte SP_BRIGHTNESS_MAX = 11;
public const byte SP_BRIGHTNESS_LEVELS = 12;
}
public enum CompressionCode : ushort
{
Uncompressed = 0,
Zlib = 26490,
Bzip2 = 25210,
Lzma = 27770,
Zstd = 31347,
}
}

View file

@ -1,66 +1,36 @@
using System.Text;
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class Cp437Grid : SpNativeInstance<BindGen.Cp437Grid>
public sealed partial class Cp437Grid
{
public static Cp437Grid New(int width, int height)
{
unsafe
{
return new Cp437Grid(NativeMethods.sp_cp437_grid_new((nuint)width, (nuint)height));
}
}
public static Cp437Grid Load(int width, int height, Span<byte> bytes)
public static Cp437Grid Load(nuint width, nuint height, ReadOnlySpan<byte> bytes)
{
unsafe
{
fixed (byte* bytesPtr = bytes)
{
return new Cp437Grid(NativeMethods.sp_cp437_grid_load((nuint)width, (nuint)height, bytesPtr,
(nuint)bytes.Length));
return Load(width, height, bytesPtr, (nuint)bytes.Length);
}
}
}
public Cp437Grid Clone()
public byte this[nuint x, nuint y]
{
unsafe
{
return new Cp437Grid(NativeMethods.sp_cp437_grid_clone(Instance));
}
get => Get(x, y);
set => Set(x, y, value);
}
public byte this[int x, int y]
{
get
{
unsafe
{
return NativeMethods.sp_cp437_grid_get(Instance, (nuint)x, (nuint)y);
}
}
set
{
unsafe
{
NativeMethods.sp_cp437_grid_set(Instance, (nuint)x, (nuint)y, value);
}
}
}
public string this[int y]
public string this[nuint y]
{
set
{
var width = Width;
ArgumentOutOfRangeException.ThrowIfGreaterThan(value.Length, width);
var width = Width();
ArgumentOutOfRangeException.ThrowIfGreaterThan((nuint)value.Length, width);
var x = 0;
for (; x < value.Length; x++)
this[x, y] = (byte)value[x];
nuint x = 0;
for (; x < (nuint)value.Length; x++)
this[x, y] = (byte)value[(int)x];
for (; x < width; x++)
this[x, y] = 0;
@ -69,7 +39,8 @@ public sealed class Cp437Grid : SpNativeInstance<BindGen.Cp437Grid>
get
{
var sb = new StringBuilder();
for (int x = 0; x < Width; x++)
var width = Width();
for (nuint x = 0; x < width; x++)
{
var val = this[x, y];
if (val == 0)
@ -81,51 +52,5 @@ public sealed class Cp437Grid : SpNativeInstance<BindGen.Cp437Grid>
}
}
public void Fill(byte value)
{
unsafe
{
NativeMethods.sp_cp437_grid_fill(Instance, value);
}
}
public int Width
{
get
{
unsafe
{
return (int)NativeMethods.sp_cp437_grid_width(Instance);
}
}
}
public int Height
{
get
{
unsafe
{
return (int)NativeMethods.sp_cp437_grid_height(Instance);
}
}
}
public Span<byte> Data
{
get
{
unsafe
{
var slice = NativeMethods.sp_cp437_grid_unsafe_data_ref(Instance);
return new Span<byte>(slice.start, (int)slice.length);
}
}
}
private unsafe Cp437Grid(BindGen.Cp437Grid* instance) : base(instance)
{
}
private protected override unsafe void Free() => NativeMethods.sp_cp437_grid_free(Instance);
public Span<byte> Data => UnsafeDataRef().AsSpan();
}

View file

@ -0,0 +1,315 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace ServicePoint
{
public unsafe sealed partial class Cp437Grid: IDisposable
{
#nullable enable
/// <summary>
/// Creates a new [SPCp437Grid] with the specified dimensions.
///
/// returns: [SPCp437Grid] initialized to 0. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
/// </summary>
public Cp437Grid(nuint width, nuint height) : this(sp_cp437_grid_new(width, height)) {}
/// <summary>
/// Loads a [SPCp437Grid] with the specified dimensions from the provided data.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Cp437Grid Load(nuint width, nuint height, byte* data, nuint data_length)
{
return new Cp437Grid(Cp437Grid.sp_cp437_grid_load(width, height, data, data_length));
}
/// <summary>
/// Clones a [SPCp437Grid].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public Cp437Grid Clone()
{
return new Cp437Grid(Cp437Grid.sp_cp437_grid_clone(this.__Instance));
}
/// <summary>
/// Gets the current value at the specified position.
///
/// # Arguments
///
/// - `cp437_grid`: instance to read from
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
/// - when `cp437_grid` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public byte Get(nuint x, nuint y)
{
return Cp437Grid.sp_cp437_grid_get(this.__Instance, x, y);
}
/// <summary>
/// Sets the value of the specified position in the [SPCp437Grid].
///
/// # Arguments
///
/// - `cp437_grid`: instance to write to
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
/// # Panics
///
/// - when `cp437_grid` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPBitVec]
/// - `cp437_grid` is not written to or read from concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Set(nuint x, nuint y, byte value)
{
Cp437Grid.sp_cp437_grid_set(this.__Instance, x, y, value);
}
/// <summary>
/// Sets the value of all cells in the [SPCp437Grid].
///
/// # Arguments
///
/// - `cp437_grid`: instance to write to
/// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to or read from concurrently
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Fill(byte value)
{
Cp437Grid.sp_cp437_grid_fill(this.__Instance, value);
}
/// <summary>
/// Gets the width of the [SPCp437Grid] instance.
///
/// # Arguments
///
/// - `cp437_grid`: instance to read from
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public nuint Width()
{
return Cp437Grid.sp_cp437_grid_width(this.__Instance);
}
/// <summary>
/// Gets the height of the [SPCp437Grid] instance.
///
/// # Arguments
///
/// - `cp437_grid`: instance to read from
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public nuint Height()
{
return Cp437Grid.sp_cp437_grid_height(this.__Instance);
}
/// <summary>
/// Gets an unsafe reference to the data of the [SPCp437Grid] instance.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - the returned memory range is never accessed after the passed [SPCp437Grid] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPCp437Grid] or directly
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public SPByteSlice UnsafeDataRef()
{
return Cp437Grid.sp_cp437_grid_unsafe_data_ref(this.__Instance);
}
#region internal machinery
private SPCp437Grid* _instance;
internal SPCp437Grid* __Instance
{
get
{
if (_instance == null)
throw new NullReferenceException("instance is null");
return _instance;
}
}
private Cp437Grid(SPCp437Grid* instance)
{
ArgumentNullException.ThrowIfNull(instance);
_instance = instance;
}
internal SPCp437Grid* __Into()
{
var instance = __Instance;
_instance = null;
return instance;
}
private void __Free()
{
if (_instance != null)
Cp437Grid.sp_cp437_grid_free(__Into());
}
public void Dispose()
{
__Free();
GC.SuppressFinalize(this);
}
~Cp437Grid() => __Free();
#endregion
#nullable restore
#region native methods
const string __DllName = "servicepoint_binding_c";
[DllImport(__DllName, EntryPoint = "sp_cp437_grid_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCp437Grid* sp_cp437_grid_new(nuint width, nuint height);
[DllImport(__DllName, EntryPoint = "sp_cp437_grid_load", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCp437Grid* sp_cp437_grid_load(nuint width, nuint height, byte* data, nuint data_length);
[DllImport(__DllName, EntryPoint = "sp_cp437_grid_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPCp437Grid* sp_cp437_grid_clone(SPCp437Grid* cp437_grid);
[DllImport(__DllName, EntryPoint = "sp_cp437_grid_free", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_cp437_grid_free(SPCp437Grid* cp437_grid);
[DllImport(__DllName, EntryPoint = "sp_cp437_grid_get", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern byte sp_cp437_grid_get(SPCp437Grid* cp437_grid, nuint x, nuint y);
[DllImport(__DllName, EntryPoint = "sp_cp437_grid_set", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_cp437_grid_set(SPCp437Grid* cp437_grid, nuint x, nuint y, byte value);
[DllImport(__DllName, EntryPoint = "sp_cp437_grid_fill", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_cp437_grid_fill(SPCp437Grid* cp437_grid, byte value);
[DllImport(__DllName, EntryPoint = "sp_cp437_grid_width", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern nuint sp_cp437_grid_width(SPCp437Grid* cp437_grid);
[DllImport(__DllName, EntryPoint = "sp_cp437_grid_height", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern nuint sp_cp437_grid_height(SPCp437Grid* cp437_grid);
[DllImport(__DllName, EntryPoint = "sp_cp437_grid_unsafe_data_ref", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPByteSlice sp_cp437_grid_unsafe_data_ref(SPCp437Grid* cp437_grid);
#endregion
}
[StructLayout(LayoutKind.Sequential)]
public unsafe partial struct SPCp437Grid
{
}
}

View file

@ -1 +1 @@
global using System;
global using System;

View file

@ -1,36 +1,23 @@
using System.Diagnostics.CodeAnalysis;
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class Packet : SpNativeInstance<BindGen.Packet>
public sealed partial class Packet
{
public static Packet FromCommand(Command command)
{
unsafe
{
return new Packet(NativeMethods.sp_packet_from_command(command.Into()));
}
}
public static bool TryFromBytes(Span<byte> bytes, [MaybeNullWhen(false)] out Packet packet)
public static bool TryLoad(ReadOnlySpan<byte> bytes, [MaybeNullWhen(false)] out Packet packet)
{
unsafe
{
fixed (byte* bytesPtr = bytes)
{
var instance = NativeMethods.sp_packet_try_load(bytesPtr, (nuint)bytes.Length);
packet = instance == null
? null
: new Packet(instance);
packet = TryLoad(bytesPtr, (nuint)bytes.Length);
return packet != null;
}
}
}
private unsafe Packet(BindGen.Packet* instance) : base(instance)
public bool TryIntoCommand([MaybeNullWhen(false)] out Command command)
{
return Command.TryFromPacket(this, out command);
}
private protected override unsafe void Free() => NativeMethods.sp_packet_free(Instance);
}

View file

@ -0,0 +1,196 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace ServicePoint
{
public unsafe sealed partial class Packet: IDisposable
{
#nullable enable
/// <summary>
/// Turns a [SPCommand] into a [SPPacket].
/// The [SPCommand] gets consumed.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - [SPCommand] points to a valid instance of [SPCommand]
/// - [SPCommand] is not used concurrently or after this call
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
///
/// servicepoint_csbindgen_consumes: command
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Packet FromCommand(Command command)
{
return new Packet(Packet.sp_packet_from_command(command.__Into()));
}
/// <summary>
/// Creates a raw [SPPacket] from parts.
///
/// # Arguments
///
/// - `command_code` specifies which command this packet contains
/// - `a`, `b`, `c` and `d` are command-specific header values
/// - `payload` is the optional data that is part of the command
/// - `payload_len` is the size of the payload
///
/// returns: new instance. Will never return null.
///
/// # Panics
///
/// - when `payload` is null, but `payload_len` is not zero
/// - when `payload_len` is zero, but `payload` is nonnull
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `payload` points to a valid memory region of at least `payload_len` bytes
/// - `payload` is not written to concurrently
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Packet FromParts(ushort command_code, ushort a, ushort b, ushort c, ushort d, byte* payload, nuint payload_len)
{
return new Packet(Packet.sp_packet_from_parts(command_code, a, b, c, d, payload, payload_len));
}
/// <summary>
/// Tries to load a [SPPacket] from the passed array with the specified length.
///
/// returns: NULL in case of an error, pointer to the allocated packet otherwise
///
/// # Panics
///
/// - when `data` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory region of at least `length` bytes
/// - `data` is not written to concurrently
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static Packet? TryLoad(byte* data, nuint length)
{
var native = Packet.sp_packet_try_load(data, length);
return native == null ? null : new Packet(native);
}
/// <summary>
/// Clones a [SPPacket].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `packet` points to a valid [SPPacket]
/// - `packet` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
/// </summary>
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public Packet Clone()
{
return new Packet(Packet.sp_packet_clone(this.__Instance));
}
#region internal machinery
private SPPacket* _instance;
internal SPPacket* __Instance
{
get
{
if (_instance == null)
throw new NullReferenceException("instance is null");
return _instance;
}
}
private Packet(SPPacket* instance)
{
ArgumentNullException.ThrowIfNull(instance);
_instance = instance;
}
internal SPPacket* __Into()
{
var instance = __Instance;
_instance = null;
return instance;
}
private void __Free()
{
if (_instance != null)
Packet.sp_packet_free(__Into());
}
public void Dispose()
{
__Free();
GC.SuppressFinalize(this);
}
~Packet() => __Free();
#endregion
#nullable restore
#region native methods
const string __DllName = "servicepoint_binding_c";
[DllImport(__DllName, EntryPoint = "sp_packet_from_command", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPPacket* sp_packet_from_command(SPCommand* command);
[DllImport(__DllName, EntryPoint = "sp_packet_from_parts", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPPacket* sp_packet_from_parts(ushort command_code, ushort a, ushort b, ushort c, ushort d, byte* payload, nuint payload_len);
[DllImport(__DllName, EntryPoint = "sp_packet_try_load", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPPacket* sp_packet_try_load(byte* data, nuint length);
[DllImport(__DllName, EntryPoint = "sp_packet_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern SPPacket* sp_packet_clone(SPPacket* packet);
[DllImport(__DllName, EntryPoint = "sp_packet_free", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private static extern void sp_packet_free(SPPacket* packet);
#endregion
}
[StructLayout(LayoutKind.Sequential)]
public unsafe partial struct SPPacket
{
}
}

View file

@ -9,6 +9,11 @@
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<PropertyGroup>
<PackageId>ServicePoint</PackageId>
<Version>0.10.0</Version>
@ -26,11 +31,11 @@
</PropertyGroup>
<!-- generate C# bindings -->
<Target Name="BuildBindings" Condition="'$(Configuration)'=='Release'" BeforeTargets="PrepareForBuild">
<Target Name="BuildBindingsRelease" Condition="'$(Configuration)'=='Release'" BeforeTargets="PrepareForBuild">
<Exec Command="cargo build --release"/>
<Exec Command="cargo build --manifest-path ../../../crates/servicepoint_binding_c/Cargo.toml --release"/>
</Target>
<Target Name="BuildBindings" Condition="'$(Configuration)'=='Debug'" BeforeTargets="PrepareForBuild">
<Target Name="BuildBindingsDebug" Condition="'$(Configuration)'=='Debug'" BeforeTargets="PrepareForBuild">
<Exec Command="cargo build"/>
<Exec Command="cargo build --manifest-path ../../../crates/servicepoint_binding_c/Cargo.toml"/>
</Target>

View file

@ -1,16 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace ServicePoint;
public static class ServicePointExtensions
{
public static Packet IntoPacket(this Command command)
{
return Packet.FromCommand(command);
}
public static bool TryIntoCommand(this Packet packet, [MaybeNullWhen(false)] out Command command)
{
return Command.TryFromPacket(packet, out command);
}
}

View file

@ -1,51 +0,0 @@
namespace ServicePoint;
public abstract class SpNativeInstance<T>
: IDisposable
where T : unmanaged
{
private unsafe T* _instance;
internal unsafe T* Instance
{
get
{
if (_instance == null)
throw new NullReferenceException("instance is null");
return _instance;
}
}
private protected unsafe SpNativeInstance(T* instance)
{
ArgumentNullException.ThrowIfNull(instance);
_instance = instance;
}
private protected abstract void Free();
internal unsafe T* Into()
{
var instance = _instance;
_instance = null;
return instance;
}
private unsafe void ReleaseUnmanagedResources()
{
if (_instance != null)
Free();
_instance = null;
}
public void Dispose()
{
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
~SpNativeInstance()
{
ReleaseUnmanagedResources();
}
}

View file

@ -1,39 +1,59 @@
//! Build script generating the C# code needed to call methods from the `servicepoint` C library.
use std::fs;
use std::{fs, path::Path};
use convert_case::{Case, Casing};
fn main() {
println!("cargo::rerun-if-changed=../servicepoint_binding_c/src");
println!("cargo::rerun-if-changed=build.rs");
let mut builder = csbindgen::Builder::default();
let mut paths = fs::read_dir("../servicepoint_binding_c/src").unwrap()
let mut paths = fs::read_dir("../servicepoint_binding_c/src")
.unwrap()
.map(|x| x.unwrap().path())
.collect::<Vec<_>>();
paths.sort();
for path in paths {
for path in &paths {
println!("cargo:rerun-if-changed={}", path.display());
builder = builder.input_extern_file(path);
}
let file: &str = Path::new(path).file_stem().unwrap().to_str().unwrap();
if file == "lib" {
continue;
}
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();
let class = file.to_case(Case::UpperCamel);
csbindgen::Builder::default()
.input_extern_file(path)
.csharp_class_name(format!("{class}Native"))
.csharp_dll_name("servicepoint_binding_c")
.csharp_namespace("ServicePoint")
.csharp_use_nint_types(true)
.csharp_class_accessibility("public")
.csharp_generate_const_filter(|_| true)
.always_included_types(["SPByteSlice", "SPCompressionCode"])
.csharp_group_methods("sp_bitmap_", "Bitmap", "SPBitmap")
.csharp_group_methods("sp_bitvec_", "BitVec", "SPBitVec")
.csharp_group_methods("sp_command_", "Command", "SPCommand")
.csharp_group_methods(
"sp_connection_",
"Connection",
"SPConnection",
)
.csharp_group_methods("sp_cp437_grid_", "Cp437Grid", "SPCp437Grid")
.csharp_group_methods("sp_packet_", "Packet", "SPPacket")
.csharp_group_methods(
"sp_brightness_grid_",
"BrightnessGrid",
"SPBrightnessGrid",
)
.csharp_type_rename(move |name| {
if name == "SPCompressionCode" {
"CompressionCode".to_string()
} else {
name
}
})
.generate_csharp_file(format!("ServicePoint/{class}.g.cs"))
.unwrap();
}
}

View file

@ -1,20 +1,24 @@
using ServicePoint;
using CompressionCode = ServicePoint.BindGen.CompressionCode;
using var connection = Connection.Open("127.0.0.1:2342");
if (connection == null)
{
Console.Error.WriteLine("could not connect");
return;
}
connection.Send(Command.Clear().IntoPacket());
connection.Send(Command.Brightness(128).IntoPacket());
connection.Send(Command.Clear());
connection.Send(Command.Brightness(Constants.MaxBrightness));
using var pixels = Bitmap.New(Constants.PixelWidth, Constants.PixelHeight);
using var pixels = new Bitmap(Constants.PixelWidth, Constants.PixelHeight);
for (var offset = 0; offset < int.MaxValue; offset++)
for (nuint offset = 0; offset < nuint.MaxValue; offset++)
{
pixels.Fill(false);
for (var y = 0; y < pixels.Height; y++)
for (nuint y = 0; y < pixels.Height(); y++)
pixels[(y + offset) % Constants.PixelWidth, y] = true;
connection.Send(Command.BitmapLinearWin(0, 0, pixels.Clone(), CompressionCode.Lzma).IntoPacket());
connection.Send(Command.BitmapLinearWin(0, 0, pixels.Clone(), CompressionCode.Lzma));
Thread.Sleep(14);
}

@ -0,0 +1 @@
Subproject commit 55eb87936923e8fb3eb11cfc7be864c1f4833c98