Enhanced C#
Loyc library documentation
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Events Pages
Nested classes | Static Public Member Functions | List of all members
SyncProtobuf Class Reference

Convenience methods and shared definitions for a pair of ISyncManager implementations (Reader and Writer) that read and write the Protocol Buffers wire format. Call Write{T}(T, SyncObjectFunc{Writer, T}, Options?) or Read{T}(ReadOnlyMemory{byte}, SyncObjectFunc{Reader, T}, Options?) to (de)serialize an object, NewWriter / NewReader(ReadOnlyMemory{byte}, Options?) to obtain a low-level (de)serializer, or WriteSchema{T}(SyncObjectFunc{SchemaWriter, T}, Options?) to generate a .proto schema describing the output. More...


Source files:

Remarks

Convenience methods and shared definitions for a pair of ISyncManager implementations (Reader and Writer) that read and write the Protocol Buffers wire format. Call Write{T}(T, SyncObjectFunc{Writer, T}, Options?) or Read{T}(ReadOnlyMemory{byte}, SyncObjectFunc{Reader, T}, Options?) to (de)serialize an object, NewWriter / NewReader(ReadOnlyMemory{byte}, Options?) to obtain a low-level (de)serializer, or WriteSchema{T}(SyncObjectFunc{SchemaWriter, T}, Options?) to generate a .proto schema describing the output.

The output is standard, valid Protocol Buffers: any Protobuf implementation (such as protoc-generated code or protobuf-net) can parse it using the proto3 schema produced by SyncProtobuf.SchemaWriter, and Reader can parse messages produced by other Protobuf implementations from the same schema.

Unlike SyncBinary, this format identifies every field by an integer ID. Therefore Reader and Writer report ISyncManager.NeedsIntegerIds = true and ISyncManager.SupportsReordering = true: fields may be read in any order, and unknown fields are skipped.

Field numbers

Each call to a Sync method carries a FieldId. If the FieldId specifies an integer ID (i.e. FieldId.Id != int.MinValue, as produced by the (name, id) tuple conversion or by a private Symbol pool), that ID becomes the Protobuf field number. Otherwise the field number is auto-assigned as N + 1, where N is the last field number used in the current message (starting from 0). The auto-numbering advances for every field synchronized (whether or not a value is physically written), so the reader and writer stay in agreement as long as they synchronize the same fields in the same order. Valid field numbers are 1 to 536,870,909; the range 19000-19999 (reserved by Protobuf) and the two highest numbers (reserved by SyncProtobuf, see below) are rejected.

Scalar wire format

Every field is preceded by a tag: a varint equal to (fieldNumber << 3) | wireType, using the standard Protobuf wire types:

Null and absent fields. A field whose value is null (a null nullable scalar, string, byte array, list or sub-object) is simply omitted, and the reader returns null when a requested nullable field is absent — matching Protobuf's "absent means default" convention. Reading an absent field as a non-nullable primitive returns the type's default value, exactly like Protobuf. To preserve round-trip fidelity, non-null values are always written, even zero (in schema terms, every scalar field is optional, i.e. it has explicit presence).

The root object

The root object is written as a bare message body — no envelope — just like a message serialized by any other Protobuf library. Because of this, the root must be an object (not a list or tuple), a null root is encoded as zero bytes, and a non-null root that happens to contain no fields is marked with a reserved boolean field (number 536,870,910, called _present in generated schemas) so that it remains distinguishable from null.

Sub-objects, lists and tuples

A sub-object is a nested message: a LEN field containing the concatenation of its (tag, value) fields, which can be read in any order.

A tuple is also a nested message; its elements are stored as fields auto-numbered 1, 2, 3, ... (a null element is an omitted field, which stays position-safe because element numbering advances regardless).

A list field is a nested list container message in which all elements are stored in field 1 (in generated schemas this container is a message type like Int32List { repeated int32 items = 1; }). This one level of nesting is what allows SyncLib to distinguish a null list (field omitted) from an empty one (empty container), and to nest lists inside lists. Within the container:

byte[] and other byte lists are not stored as list containers at all; they are written as a single Protobuf bytes value.

Deduplication and cyclic graphs

A field or element synchronized with ObjectMode.Deduplicate is stored as a reference wrapper message { uint64 id = 1; T value = 2; }: the first occurrence of an object stores both its ID and its value, and each later occurrence stores only the ID. This lets SyncProtobuf serialize shared references and cyclic object graphs (see the Jack-and-Jill example in ISyncManager) while remaining valid Protobuf. Deduplication also works for strings and byte arrays.

Caution: because the wrapper is only present when Deduplicate is requested, the writer and reader must agree on which fields use Deduplicate. Unlike SyncBinary (whose optional markers make the flag self-describing), toggling ObjectMode.Deduplicate is a breaking change to the data stream.

Type tags

ISyncManager.SyncTypeTag(string?) stores the tag as a string field with the reserved number 536,870,911 (_type in generated schemas). Protobuf parsers that don't know about it simply skip it.

Nested classes

class  Options
 Options that control the behavior of SyncProtobuf.Reader and SyncProtobuf.Writer. More...
 
struct  Reader
 The ISyncManager implementation that reads the Protocol Buffers wire format (see SyncProtobuf). Because fields are keyed by integer ID, this reader supports reordering (SupportsReordering) and skips unknown fields. Call Read{T}(ReadOnlyMemory{byte}, SyncObjectFunc{Reader, T}, Options?) to deserialize, or NewReader(ReadOnlyMemory{byte}, Options?) to construct one directly. More...
 
struct  SchemaWriter
 An implementation of ISyncManager that produces a Protocol Buffers proto3 .proto definition describing the output of SyncProtobuf.Writer. More...
 
struct  Writer
 The ISyncManager implementation that writes the Protocol Buffers wire format (see SyncProtobuf for the format and guidelines). Call Write{T}(T, SyncObjectFunc{Writer, T}, Options?) to serialize an object, or NewWriter to construct one of these directly. More...
 

Static Public Member Functions

static SyncProtobuf.Reader NewReader (ReadOnlyMemory< byte > input, Options?options=null)
 
static SyncProtobuf.Reader NewReader (byte[] input, Options?options=null)
 
static SyncProtobuf.Reader NewReader (IScanner< byte > input, Options?options=null)
 
static T Read< T > (ReadOnlyMemory< byte > input, SyncObjectFunc< Reader, T > sync, Options?options=null)
 
static T ReadI< T > (ReadOnlyMemory< byte > input, SyncObjectFunc< ISyncManager, T > sync, Options?options=null)
 
static T Read< T, SyncObject > (ReadOnlyMemory< byte > input, SyncObject sync, Options?options=null)
 
static T Read< T > (byte[] input, SyncObjectFunc< Reader, T > sync, Options?options=null)
 
static T ReadI< T > (byte[] input, SyncObjectFunc< ISyncManager, T > sync, Options?options=null)
 
static T Read< T, SyncObject > (byte[] input, SyncObject sync, Options?options=null)
 
static SchemaWriter NewSchema (Options?options=null)
 Creates a SyncProtobuf.SchemaWriter, an object that produces a Protocol Buffers .proto (proto3) definition describing the messages that SyncProtobuf.Writer would produce. After using it to "synchronize" one root object, call SchemaWriter.Finish (or ToString) to get the schema document. More...
 
static ReadOnlyMemory< byte > WriteSchema< T > (SyncObjectFunc< SchemaWriter, T > sync, Options?options=null)
 Generates a proto3 .proto schema (UTF-8) describing the Protobuf output that SyncProtobuf.Write{T}(T, SyncObjectFunc{Writer, T}, Options?) produces for type T with the same synchronizer. More...
 
static ReadOnlyMemory< byte > WriteSchemaI< T > (SyncObjectFunc< ISyncManager, T > sync, Options?options=null)
 
static ReadOnlyMemory< byte > WriteSchema< T, SyncObject > (SyncObject sync, Options?options=null)
 
static string WriteSchemaString< T > (SyncObjectFunc< SchemaWriter, T > sync, Options?options=null)
 
static string WriteSchemaStringI< T > (SyncObjectFunc< ISyncManager, T > sync, Options?options=null)
 
static string WriteSchemaString< T, SyncObject > (SyncObject sync, Options?options=null)
 
static ReadOnlyMemory< byte > Write< T > (T value, SyncObjectFunc< Writer, T > sync, Options?options=null)
 
static ReadOnlyMemory< byte > WriteI< T > (T value, SyncObjectFunc< ISyncManager, T > sync, Options?options=null)
 
static ReadOnlyMemory< byte > Write< T, SyncObject > (T value, SyncObject sync, Options?options=null)
 
static SyncProtobuf.Writer NewWriter (IBufferWriter< byte >?output=null, Options?options=null)
 

Member Function Documentation

static SchemaWriter SyncProtobuf.NewSchema ( Options options = null)
static

Creates a SyncProtobuf.SchemaWriter, an object that produces a Protocol Buffers .proto (proto3) definition describing the messages that SyncProtobuf.Writer would produce. After using it to "synchronize" one root object, call SchemaWriter.Finish (or ToString) to get the schema document.

Referenced by WriteSchema< T >(), WriteSchema< T, SyncObject >(), and WriteSchemaI< T >().

static ReadOnlyMemory<byte> SyncProtobuf.WriteSchema< T > ( SyncObjectFunc< SchemaWriter, T >  sync,
Options options = null 
)
inlinestatic

Generates a proto3 .proto schema (UTF-8) describing the Protobuf output that SyncProtobuf.Write{T}(T, SyncObjectFunc{Writer, T}, Options?) produces for type T with the same synchronizer.

The synchronizer runs once in SyncMode.Schema mode, in which there is no data: it receives a default/null value of T, and the Sync methods return default values. Field numbers in the schema are assigned exactly as the writer assigns them (from FieldId.Id, else auto N+1).

References NewSchema().