clib Serialization & RPC
capnproto

Cap'n Proto data interchange format and RPC for PascalAI. Zero-copy reads and writes — data lives in its wire format in memory, so encoding costs 0 ns and decoding costs ~0 ns. Schema-driven, typed, with built-in promise-pipelining RPC.

Install ppm install capnproto
What is Cap'n Proto?

Cap'n Proto is a data interchange format and RPC system created by Kenton Varda, the author of Protocol Buffers v2. It was designed from the ground up to eliminate the encode/decode overhead that makes traditional serialization formats slow.

The key innovation is zero-copy reads: Cap'n Proto stores data directly in its wire format inside memory. Reading a field is a single pointer dereference — there is no deserialization step. Writing is direct memory layout — there is no serialization step either.

Cap'n Proto also includes a full RPC system with promise pipelining, capability references, and three-party handoff — features not present in FlatBuffers or Protocol Buffers.

CLib package — Requires libcapnp-dev. Ubuntu/Debian: sudo apt install libcapnp-dev
Performance Comparison

Because Cap'n Proto uses the wire format as the in-memory representation, encoding and decoding approach zero nanoseconds. Compare to formats that require a full object graph construction on every read or write:

FormatEncodeDecodeZero-copyRPC
Cap'n Proto ~0 ns (memory layout) ~0 ns (pointer cast) Yes Built-in
FlatBuffers ~0 ns (memory layout) ~0 ns (pointer cast) Yes None
Protocol Buffers ~100–300 ns/msg ~50–200 ns/msg No gRPC (separate)
MessagePack ~80–200 ns/msg ~60–180 ns/msg No None
JSON >1 μs/msg >1 μs/msg No None
Zero-copy detail: Cap'n Proto messages are kept in their wire format in RAM. Passing a message between threads or processes requires no copy — share the memory pointer. FlatBuffers shares this property but omits RPC. Protobuf always materialises a full object graph on decode.
Schema Language

Cap'n Proto schemas are defined in .capnp files. Each file carries a 64-bit ID (generated by capnp id). Fields are numbered from @0 — numbers are stable identifiers that allow forward/backward compatible evolution.

@0xdeadbeefdeadbeef;  # 64-bit file ID (generate with: capnp id)

struct Person {
  name  @0 :Text;
  age   @1 :UInt32;
  email @2 :Text;
}

struct AddressBook {
  people @0 :List(Person);
}

Compile the schema to a binary blob and to C++ glue with:

# Compile to binary (used at runtime by LoadSchema)
capnp compile -o /dev/null --src-prefix=. schema.capnp  # validates
capnp compile -oc++ schema.capnp                        # emits schema.capnp.h + .c++
Field numbers are forever. Once a field is assigned @N, never reuse that number. Add new fields with the next available number. Absent fields return typed defaults — they are never an error.
Types
TypeUnderlyingDescription
TCapnSchemaInteger (handle)Compiled schema handle returned by LoadSchema. Holds type metadata for all structs in the file.
TCapnMessageInteger (handle)An abstract Cap'n Proto message (parent of builder and reader).
TCapnBuilderInteger (handle)Message builder — used for writing. Allocate with NewBuilder, free with FreeBuilder.
TCapnReaderInteger (handle)Message reader — used for reading serialized bytes. Allocate with NewReader, free with FreeReader.
TCapnStructInteger (handle)A struct builder or reader within a message. Returned by InitRoot / GetRoot.
TCapnListInteger (handle)A list builder or reader within a struct.
TCapnSegmentrecordRaw segment descriptor with Data: string (raw bytes) and Words: Int64 (size in 64-bit words).
Schema API
FunctionSignatureDescription
LoadSchemaLoadSchema(Path: string): TCapnSchemaLoad a compiled .capnp binary schema file from disk. Returns a schema handle used by InitRoot.
LoadSchemaBytesLoadSchemaBytes(Data: string): TCapnSchemaLoad schema from an in-memory byte string (for embedded/bundled schemas).
FreeSchemaFreeSchema(S: TCapnSchema)Release the schema handle and all associated memory.
Building (Writing)
FunctionSignatureDescription
NewBuilderNewBuilder: TCapnBuilderAllocate a new message builder.
FreeBuilderFreeBuilder(B: TCapnBuilder)Release all memory held by the builder. Always call after Serialize.
InitRootInitRoot(B: TCapnBuilder; S: TCapnSchema; TypeName: string): TCapnStructInitialise the root struct. TypeName must match a struct name in the schema (e.g. 'Person').

Struct field setters

FunctionSignatureDescription
SetTextSetText(Struct: TCapnStruct; FieldIndex: Integer; Value: string)Set a Text field at the given 0-based field index.
SetInt32SetInt32(Struct: TCapnStruct; FieldIndex, Value: Integer)Set a 32-bit signed integer field.
SetInt64SetInt64(Struct: TCapnStruct; FieldIndex: Integer; Value: Int64)Set a 64-bit signed integer field.
SetUInt32SetUInt32(Struct: TCapnStruct; FieldIndex: Integer; Value: Int64)Set a UInt32 field (passed as Int64 to avoid sign issues).
SetFloat64SetFloat64(Struct: TCapnStruct; FieldIndex: Integer; Value: Double)Set a Float64 field.
SetBoolSetBool(Struct: TCapnStruct; FieldIndex: Integer; Value: Boolean)Set a Bool field.
SetDataSetData(Struct: TCapnStruct; FieldIndex: Integer; Value: string)Set a Data (raw bytes) field.
InitStructInitStruct(Struct: TCapnStruct; FieldIndex: Integer): TCapnStructInitialise a nested struct field and return its handle for further setters.
InitListInitList(Struct: TCapnStruct; FieldIndex, Count: Integer): TCapnListInitialise a list field with Count pre-allocated slots.

Serialization

FunctionSignatureDescription
SerializeSerialize(B: TCapnBuilder): stringSerialize to standard flat framing format. Suitable for files, sockets, and IPC.
SerializePackedSerializePacked(B: TCapnBuilder): stringSerialize to packed format. Runs of zero words are compressed (~50% savings for sparse structs).
Reading
FunctionSignatureDescription
NewReaderNewReader(Data: string): TCapnReaderCreate a reader from bytes produced by Serialize.
NewReaderPackedNewReaderPacked(Data: string): TCapnReaderCreate a reader from bytes produced by SerializePacked.
FreeReaderFreeReader(R: TCapnReader)Release all memory held by the reader.
GetRootGetRoot(R: TCapnReader): TCapnStructGet the root struct handle for reading fields.

Struct field getters

FunctionSignatureDescription
GetTextGetText(Struct: TCapnStruct; FieldIndex: Integer): stringRead a Text field. Returns empty string if absent.
GetInt32GetInt32(Struct: TCapnStruct; FieldIndex: Integer): IntegerRead a 32-bit signed integer field.
GetInt64GetInt64(Struct: TCapnStruct; FieldIndex: Integer): Int64Read a 64-bit signed integer field.
GetUInt32GetUInt32(Struct: TCapnStruct; FieldIndex: Integer): Int64Read a UInt32 field (returned as Int64).
GetFloat64GetFloat64(Struct: TCapnStruct; FieldIndex: Integer): DoubleRead a Float64 field.
GetBoolGetBool(Struct: TCapnStruct; FieldIndex: Integer): BooleanRead a Bool field.
GetDataGetData(Struct: TCapnStruct; FieldIndex: Integer): stringRead a Data (raw bytes) field.
GetStructGetStruct(Struct: TCapnStruct; FieldIndex: Integer): TCapnStructRead a nested struct field.
GetListGetList(Struct: TCapnStruct; FieldIndex: Integer): TCapnListRead a list field.

List operations

FunctionSignatureDescription
ListSizeListSize(L: TCapnList): IntegerReturn the number of elements in the list.
ListGetTextListGetText(L: TCapnList; Index: Integer): stringGet the Text element at the given 0-based index.
ListSetTextListSetText(L: TCapnList; Index: Integer; Value: string)Set the Text element at the given index (builder only).
ListGetStructListGetStruct(L: TCapnList; Index: Integer): TCapnStructGet the struct element at the given index for further field access.
Code Example — Write and Read a Person Message
uses capnproto;

var
  Schema : TCapnSchema;
  B      : TCapnBuilder;
  Root   : TCapnStruct;
  Data   : string;
  R      : TCapnReader;
begin
  { Load the compiled schema binary }
  Schema := LoadSchema('/path/to/person.capnp.bin');

  { ── Write ───────────────────────────────────────── }
  B    := NewBuilder;
  Root := InitRoot(B, Schema, 'Person');

  SetText(Root, 0, 'Alice');                   { name  @0 }
  SetUInt32(Root, 1, 30);                      { age   @1 }
  SetText(Root, 2, 'alice@example.com');       { email @2 }

  Data := Serialize(B);   { wire format — zero-copy layout }
  FreeBuilder(B);

  { ── Read ────────────────────────────────────────── }
  R    := NewReader(Data);
  Root := GetRoot(R);

  WriteLn(GetText(Root, 0));     { Alice }
  WriteLn(GetUInt32(Root, 1));   { 30    }
  WriteLn(GetText(Root, 2));     { alice@example.com }

  FreeReader(R);
  FreeSchema(Schema);
end.
Field indices match the schema. SetText(Root, 0, ...) writes field @0, SetUInt32(Root, 1, ...) writes field @1, and so on. There is no name lookup at runtime — it is a direct memory offset computation.
Cap'n Proto vs Protocol Buffers

Both formats are schema-driven and produce compact binary output. The fundamental difference is the memory model:

Cap'n ProtoProtocol Buffers
Memory modelWire format = in-memory formatWire format decoded into separate objects
Encode cost~0 ns~100–300 ns / message
Decode cost~0 ns~50–200 ns / message
RPC systemBuilt-in (promise pipelining)Separate (gRPC)
Language supportC++, Python, Java, Go, Rust…20+ languages, wider ecosystem
Tooling maturitySmaller communityDominant in industry
When to choose Cap'n Proto: performance-critical paths, high-frequency IPC, game state, low-latency networking, or when built-in RPC pipelining is needed.
When to choose Protobuf: maximum language ecosystem (20+ languages), large existing infrastructure, or gRPC compatibility.
Utility
FunctionSignatureDescription
CapnProtoVersionCapnProtoVersion: stringReturns the version string of the underlying libcapnp (e.g. '0.10.4').
← Back to Package Index