Merge branch 'uniffi2'

This commit is contained in:
Vinzenz Schroeter 2024-11-23 22:57:46 +01:00
commit 93657c9f85
66 changed files with 7298 additions and 2469 deletions

2
.gitignore vendored
View file

@ -1,8 +1,6 @@
target
.idea
out
bin
obj
.direnv
.envrc
result

902
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,15 +3,15 @@ resolver = "2"
members = [
"crates/servicepoint",
"crates/servicepoint_binding_c",
"crates/servicepoint_binding_cs",
"crates/servicepoint_binding_c/examples/lang_c"
"crates/servicepoint_binding_c/examples/lang_c",
"crates/servicepoint_binding_uniffi"
]
[workspace.package]
version = "0.11.0"
version = "0.12.0"
[workspace.lints.rust]
missing-docs = "warn"
[workspace.dependencies]
thiserror = "1.0.69"
thiserror = "1.0.69"

View file

@ -1,5 +1,10 @@
# servicepoint
[![crates.io](https://img.shields.io/crates/v/servicepoint.svg)](https://crates.io/crates/servicepoint)
[![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint)](https://crates.io/crates/servicepoint)
[![docs.rs](https://img.shields.io/docsrs/servicepoint)](https://docs.rs/servicepoint/latest/servicepoint/)
[![GPLv3 licensed](https://img.shields.io/crates/l/servicepoint)](../../LICENSE)
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
Display" or "Airport Display".
This repository contains a library for parsing, encoding and sending packets to this display via UDP in multiple
@ -7,20 +12,20 @@ programming languages.
Take a look at the contained crates for language specific information:
| Language | Readme |
|-----------|---------------------------------------------------------------------|
| Rust | [servicepoint](crates/servicepoint/README.md) |
| C / C++ | [servicepoint_binding_c](crates/servicepoint_binding_c/README.md) |
| .NET (C#) | [servicepoint_binding_cs](crates/servicepoint_binding_cs/README.md) |
| Crate | Languages | Readme |
|-----------------------------|-----------------------------------|-------------------------------------------------------------------------|
| servicepoint | Rust | [servicepoint](crates/servicepoint/README.md) |
| servicepoint_binding_c | C / C++ | [servicepoint_binding_c](crates/servicepoint_binding_c/README.md) |
| servicepoint_binding_uniffi | C# / Python / Go / Kotlin / Swift | [servicepoint_binding_cs](crates/servicepoint_binding_uniffi/README.md) |
## Projects using the library
- screen simulator (rust): [servicepoint-simulator](https://github.com/kaesaecracker/servicepoint-simulator)
- A bunch of projects (C): [arfst23/ServicePoint](https://github.com/arfst23/ServicePoint), including
- a CLI tool to display image files on the display or use the display as a TTY
- a BSD games robots clone
- a split-flap-display simulator
- animations that play on the display
- a CLI tool to display image files on the display or use the display as a TTY
- a BSD games robots clone
- a split-flap-display simulator
- animations that play on the display
- tanks game (C#): [servicepoint-tanks](https://github.com/kaesaecracker/cccb-tanks-cs)
- cellular automata slideshow (rust): [servicepoint-life](https://github.com/kaesaecracker/servicepoint-life)

View file

@ -17,7 +17,7 @@ cargo add servicepoint
or
```toml
[dependencies]
servicepoint = "0.11.0"
servicepoint = "0.12.0"
```
## Examples

View file

@ -17,7 +17,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
cbindgen = "0.27.0"
[dependencies.servicepoint]
version = "0.11.0"
version = "0.12.0"
path = "../servicepoint"
features = ["all_compressions"]

View file

@ -1201,6 +1201,20 @@ SPCommand *sp_command_hard_reset(void);
*/
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].
*

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};
@ -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.

View file

@ -1,20 +0,0 @@
[package]
name = "servicepoint_binding_cs"
version.workspace = true
edition = "2021"
publish = false
readme = "README.md"
[lib]
crate-type = ["cdylib"]
test = false
[build-dependencies]
csbindgen = "1.9.3"
[dependencies]
servicepoint_binding_c = { version = "0.11.0", path = "../servicepoint_binding_c" }
servicepoint = { version = "0.11.0", path = "../servicepoint" }
[lints]
workspace = true

View file

@ -1,65 +0,0 @@
# ServicePoint
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
Display" or "Airport Display".
This crate contains C# bindings for the `servicepoint` library, enabling users to parse, encode and send packets to this display via UDP.
## Examples
```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);
while (true)
{
pixels.Fill(true);
connection.Send(Command.BitmapLinearWin(0, 0, pixels.Clone()));
Thread.Sleep(5000);
pixels.Fill(false);
connection.Send(Command.BitmapLinearWin(0, 0, pixels.Clone()));
Thread.Sleep(5000);
}
```
A full 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.
## Installation
NuGet packages are not a good way to distribute native projects ([relevant issue](https://github.com/dotnet/sdk/issues/33845)).
Because of that, there is no NuGet package you can use directly.
Including this repository as a submodule and building from source is the recommended way of using the library.
```bash
git submodule add https://github.com/cccb/servicepoint.git
git commit -m "add servicepoint submodule"
```
You can now reference `servicepoint-bindings-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
Uses C bindings internally to provide a similar API to rust. Things to keep in mind:
- 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.
## Everything else
Look at the main project [README](https://github.com/cccb/servicepoint/blob/main/README.md) for further information.

View file

@ -1,28 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
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}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{C2F8EC4A-2426-4DC3-990F-C43810B183F5}"
ProjectSection(SolutionItems) = preProject
..\..\shell.nix = ..\..\shell.nix
..\..\.envrc = ..\..\.envrc
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{70EFFA3F-012A-4518-9627-466BEAE4252E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70EFFA3F-012A-4518-9627-466BEAE4252E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70EFFA3F-012A-4518-9627-466BEAE4252E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70EFFA3F-012A-4518-9627-466BEAE4252E}.Release|Any CPU.Build.0 = Release|Any CPU
{DA3B8B6E-993A-47DA-844B-F92AF520FF59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
EndGlobal

View file

@ -1,88 +0,0 @@
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class BitVec : SpNativeInstance<BindGen.BitVec>
{
public static BitVec New(int size)
{
unsafe
{
return new BitVec(NativeMethods.sp_bitvec_new((nuint)size));
}
}
public static BitVec Load(Span<byte> bytes)
{
unsafe
{
fixed (byte* bytesPtr = bytes)
{
return new BitVec(NativeMethods.sp_bitvec_load(bytesPtr, (nuint)bytes.Length));
}
}
}
public BitVec Clone()
{
unsafe
{
return new BitVec(NativeMethods.sp_bitvec_clone(Instance));
}
}
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);
}

View file

@ -1,100 +0,0 @@
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class Bitmap : SpNativeInstance<BindGen.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)
{
unsafe
{
fixed (byte* bytesPtr = bytes)
{
return new Bitmap(NativeMethods.sp_bitmap_load((nuint)width, (nuint)height, bytesPtr,
(nuint)bytes.Length));
}
}
}
public Bitmap Clone()
{
unsafe
{
return new Bitmap(NativeMethods.sp_bitmap_clone(Instance));
}
}
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);
}

View file

@ -1,100 +0,0 @@
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class BrightnessGrid : SpNativeInstance<BindGen.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)
{
unsafe
{
fixed (byte* bytesPtr = bytes)
{
return new BrightnessGrid(NativeMethods.sp_brightness_grid_load((nuint)width, (nuint)height, bytesPtr,
(nuint)bytes.Length));
}
}
}
public BrightnessGrid Clone()
{
unsafe
{
return new BrightnessGrid(NativeMethods.sp_brightness_grid_clone(Instance));
}
}
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);
}

View file

@ -1,129 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class Command : SpNativeInstance<BindGen.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;
}
}
public Command Clone()
{
unsafe
{
return new Command(NativeMethods.sp_command_clone(Instance));
}
}
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

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

View file

@ -1,24 +0,0 @@
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;
/// tile count in the x-direction
public const nuint TileWidth = NativeMethods.SP_TILE_WIDTH;
/// tile count in the y-direction
public const nuint TileHeight = NativeMethods.SP_TILE_SIZE;
/// screen width in pixels
public const nuint PixelWidth = TileWidth * TileSize;
/// screen height in pixels
public const nuint PixelHeight = TileHeight * TileSize;
/// pixel count on whole screen
public const nuint PixelCount = PixelWidth * PixelHeight;
}

View file

@ -1,131 +0,0 @@
using System.Text;
using ServicePoint.BindGen;
namespace ServicePoint;
public sealed class Cp437Grid : SpNativeInstance<BindGen.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)
{
unsafe
{
fixed (byte* bytesPtr = bytes)
{
return new Cp437Grid(NativeMethods.sp_cp437_grid_load((nuint)width, (nuint)height, bytesPtr,
(nuint)bytes.Length));
}
}
}
public Cp437Grid Clone()
{
unsafe
{
return new Cp437Grid(NativeMethods.sp_cp437_grid_clone(Instance));
}
}
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]
{
set
{
var width = Width;
ArgumentOutOfRangeException.ThrowIfGreaterThan(value.Length, width);
var x = 0;
for (; x < value.Length; x++)
this[x, y] = (byte)value[x];
for (; x < width; x++)
this[x, y] = 0;
}
get
{
var sb = new StringBuilder();
for (int x = 0; x < Width; x++)
{
var val = this[x, y];
if (val == 0)
break;
sb.Append((char)val);
}
return sb.ToString();
}
}
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);
}

View file

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

Some files were not shown because too many files have changed in this diff Show more