Skip to content

Latest commit

 

History

183 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

COMPAS_PB C# Wrapper

C# latest Target Frameworks License MIT Build Status Runtime Documentation Made with COMPAS

A COMPAS_PB extension which lets you serialize and deserialize COMPAS Data types using protobuf in C#.

Installation

CompasPb is distributed as a NuGet package. Version 1.0.0 contains the generated C# bindings and runtime support for the compas_pb 1.2 wire format; consumers do not need to generate protobuf sources or copy DLLs into their projects manually.

Install from NuGet

From the directory containing your project file, use the .NET CLI:

dotnet add package CompasPb --version 1.0.0

Or use the NuGet Package Manager Console in Visual Studio:

Install-Package CompasPb -Version 1.0.0

You can also add the package reference directly to your .csproj file:

<ItemGroup>
  <PackageReference Include="CompasPb" Version="1.0.0" />
</ItemGroup>

Restore the project after editing the project file directly:

dotnet restore

NuGet restores the two runtime dependencies, Google.Protobuf and Newtonsoft.Json, transitively.

Install in Unity

Unity projects install the package from OpenUPM instead of NuGet:

openupm add dev.compas.compas-pb

Or add the scoped registry to Packages/manifest.json:

{
  "scopedRegistries": [
    {
      "name": "package.openupm.com",
      "url": "https://package.openupm.com",
      "scopes": ["dev.compas"]
    }
  ],
  "dependencies": {
    "dev.compas.compas-pb": "1.0.0"
  }
}

The Unity package requires Unity 2021.3 or newer and pulls in Newtonsoft.Json through the com.unity.nuget.newtonsoft-json dependency. See the package README for details.

Supported target frameworks

The package provides assemblies for:

Target framework Typical consumers
netstandard2.0 Unity, Grasshopper, and other .NET Standard-compatible applications
net48 Rhino and other .NET Framework 4.8 applications
net9.0 Current .NET applications and services

Your application only needs to target one compatible framework. The .NET 9 SDK is required to build this repository itself, but it is not required merely to consume the netstandard2.0 or net48 package assets.

Build from source

Building the repository requires Git, Python 3, and the .NET 9 SDK. The generated C# protobuf files are intentionally not committed; the fetch script downloads the pinned compas_pb 1.2 release assets before the build.

git clone https://github.com/gramaziokohler/compas_pb_csharp.git
cd compas_pb_csharp
python3 fetch_compas_pb.py
dotnet restore
dotnet build src/CompasPb/CompasPb.csproj --configuration Release
dotnet test test/CompasPb.Test.csproj --configuration Release

To create a local NuGet package from a release checkout:

dotnet pack src/CompasPb/CompasPb.csproj \
  --configuration Release \
  --output ./artifacts

Then add ./artifacts as a package source, or reference it for a one-off install:

dotnet add path/to/YourProject.csproj package CompasPb \
  --version 1.0.0 \
  --source ./artifacts

Usage

CompasPbSerializer is the runtime's single entry point, in both directions and in both formats. It lives in the CompasPb namespace; the generated message types it packs live in CompasPb.Data, so most files need both using directives. The Serializer and Deserializer helpers behind it are internal — callers never need to reach for them.

All protobuf message types in the assembly are registered automatically at startup, so no manual type registration is needed for the built-in COMPAS geometry.

PackAsAnyData / UnpackAnyData sit one level below Pack / Unpack, for filling an AnyData field inside a message you are building yourself — see the domain package guide.

Single object

using CompasPb;
using CompasPb.Data;

var serializer = new CompasPbSerializer();

var frame = new FrameData
{
    Name = "myFrame",
    Point = new PointData { X = 1.0f, Y = 2.0f, Z = 3.0f },
    Xaxis = new VectorData { X = 1.0f, Y = 0.0f, Z = 0.0f },
    Yaxis = new VectorData { X = 0.0f, Y = 1.0f, Z = 0.0f },
};

// Binary (protobuf)
byte[] bytes = serializer.Pack(frame);
FrameData? unpacked = serializer.Unpack<FrameData>(bytes);

// JSON
string json = serializer.PackAsJson(frame);
FrameData? fromJson = serializer.UnpackJson<FrameData>(json);

// Dynamic — when you don't know the type ahead of time
object? result = serializer.Unpack(bytes);
object? fromJsonDynamic = serializer.UnpackJson(json);

Nested data structures

Supports arbitrarily nested lists and dictionaries:

using System.Collections.Generic;
using CompasPb;
using CompasPb.Data;

var serializer = new CompasPbSerializer();

var data = new Dictionary<string, object>
{
    ["center"] = new PointData { X = 1.0f, Y = 2.0f, Z = 3.0f },
    ["points"] = new List<object>
    {
        new PointData { X = 0.0f, Y = 0.0f, Z = 0.0f },
        new PointData { X = 10.0f, Y = 0.0f, Z = 0.0f },
    },
};

// Works with both binary and JSON
byte[] bytes   = serializer.Pack(data);
object? result = serializer.Unpack(bytes);

string json       = serializer.PackAsJson(data);
object? fromJson  = serializer.UnpackJson(json);

Dependency Injection

Register ICompasPbSerializer in your DI container:

using CompasPb;
using CompasPb.Data;

// Program.cs / Startup.cs
services.AddSingleton<ICompasPbSerializer, CompasPbSerializer>();

// In any service or controller
public class RobotService(ICompasPbSerializer serializer)
{
    public FrameData? GetFrame(byte[] bytes)
        => serializer.Unpack<FrameData>(bytes);

    public byte[] SendFrame(FrameData frame)
        => serializer.Pack(frame);
}

Register domain-model types

Packages outside this runtime can register conversion functions without changing their model classes. Register each mapping once during application startup; the protobuf type is identified by its descriptor's full name, so identically named messages in different packages cannot collide.

Registry, CompasPbRegistrations and ICompasFallback are in CompasPb.Data, alongside the message types — only the serializer moved to CompasPb.

Registry.Register<Widget, WidgetData>(
    widget => new WidgetData { Name = widget.Name },
    message => new Widget(message.Name)
);

byte[] bytes = serializer.Pack(new Widget("A"));
Widget widget = (Widget)serializer.Unpack(bytes)!;

Serializer lookup follows the C# inheritance chain, so a mapping registered for a base class also handles derived instances. Generated protobuf messages can be registered for identity deserialization with Registry.RegisterAssembly(typeof(WidgetData).Assembly); Registry.DiscoverLoadedAssemblies() scans all assemblies already loaded by the process.

Register automatically from your own package

Calling Registry.Register from application startup means every host has to know about every package. To make a package's conversions apply just by being referenced, put the registrations in a static method and point at it from an assembly-level attribute:

[assembly: CompasPbRegistrations(typeof(WidgetConversions))]

public static class WidgetConversions
{
    public static void Register()
    {
        Registry.Register<Widget, WidgetData>(
            widget => new WidgetData { Name = widget.Name },
            message => new Widget(message.Name)
        );
    }
}

CompasPb invokes each declared registrar at most once, and reads the attribute without enumerating your types, so discovery stays cheap. A referenced assembly is loaded by the runtime only when the process first uses one of its types, so yours may well not be loaded when CompasPb first sweeps — CompasPb watches for later loads and runs your registrar when your assembly arrives. Registry.DiscoverRegistrations() forces a sweep by hand if you ever need one.

The registrations themselves stay explicit Register<,> calls, so the generic instantiations remain statically visible and survive IL2CPP or trimming; only the call into Register is discovered. Under a stripping linker, preserve the registrar type so its method is kept.

For a COMPAS type that has no protobuf schema, register its COMPAS JSON-dump fallback in both directions:

Registry.RegisterFallback<Widget>(
    "my_package/Widget",
    widget => new Dictionary<string, object>
    {
        ["data"] = new Dictionary<string, object> { ["name"] = widget.Name },
    },
    values =>
    {
        var data = (Dictionary<string, object?>)values["data"]!;
        return new Widget((string)data["name"]!);
    }
);

The runtime writes the registered dtype into the fallback envelope. If a fallback dtype has no C# registration, deserialization deliberately returns its JSON-dump dictionary so the payload remains inspectable. The older ICompasFallback interface remains supported for existing model classes.

Documentation

compas_pb is a cross-language format; Python is the authoritative implementation. For the format itself, the runtime contract, and the wider ecosystem, see the upstream compas_pb documentation.

Issue Tracker

If you find a bug or if you have a problem with running the code, please file an issue on the Issue Tracker.

About

A COMPAS_PB extension which lets you serialize and deserialize COMPAS Data types using protobuf in C#

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages