Custom Schema Types
Custom schema types can be defined. A schema is any object that implements the bin.Schema<TIn, TOut> interface,
where TIn is the type of values it accepts when writing, and TOut the type of values it produces when reading.
TOut defaults to TIn, so bin.Schema<number> reads and writes numbers.
import * as bin from 'typed-binary';
/** * A schema storing radians with 2 bytes of precision. */class RadiansSchema implements bin.Schema<number> { read(cursor: bin.ByteCursor): number { const low = bin.u8.read(cursor); const high = bin.u8.read(cursor);
const discrete = (high << 8) | low; return (discrete / 65535) * Math.PI; }
write(cursor: bin.ByteCursor, value: number): void { // The value will be wrapped to be in range of [0, Math.PI) const wrapped = ((value % Math.PI) + Math.PI) % Math.PI; // Quantizing the value to range of [0, 65535] const discrete = Math.min(Math.floor((wrapped / Math.PI) * 65535), 65535);
const low = discrete & 0xff; const high = (discrete >> 8) & 0xff;
bin.u8.write(cursor, low); bin.u8.write(cursor, high); }
measure(_: number | bin.MaxValue): number { // The size of the data serialized by this schema // doesn't depend on the actual value. It's always 2 bytes. return 2; }}
// Creating a singleton instance of the schema,// since it has no configuration properties.export const radians = new RadiansSchema();
// Custom schemas can be used like any other schemaconst Rotation = bin.struct({ roll: radians, pitch: radians, yaw: radians });Schemas read and write through a bin.ByteCursor, which is a DataView along with the current byte offset
and the endianness to use. Custom schemas can delegate to the primitive schemas, like the bin.u8 calls above,
or use the DataView directly:
read(cursor: bin.ByteCursor): number { const value = cursor.view.getUint16(cursor.offset, cursor.littleEndian); cursor.offset += 2; return value;}When measuring with bin.MaxValue, a schema should return the biggest size its values can take. If there is no upper
bound (e.g. for variable-length data), it should return NaN.
Objects that define themselves as schemas
Section titled “Objects that define themselves as schemas”Sometimes the thing you want to encode is already described by an object of your own, like a component definition,
a message type or a class. Instead of keeping a separate schema next to it, the object can define itself as a schema
by holding one under the '~typed-binary' key:
import * as bin from 'typed-binary';
function component<TSchema extends bin.Schema>(name: string, schema: TSchema) { return { name, '~typed-binary': { schema } } as const;}
const Health = component('health', bin.u16);const Position = component('position', bin.struct({ x: bin.f32, y: bin.f32 }));
// The components can be used anywhere a schema is expectedconst Entity = bin.struct({ id: bin.u32, health: Health, position: Position,});
type Entity = bin.ExtractOut<typeof Entity>;// ^? { id: number, health: number, position: { x: number, y: number } }Such objects are accepted by every schema constructor: as struct properties, elements of bin.array, bin.dynamicArray
and bin.tuple, the inner schema of bin.optional, the structs passed to bin.concat, the tag, variants and properties
of bin.union, and the head of bin.pipe (or the schema returned by its action). bin.ExtractIn and bin.ExtractOut
work on them as well, and the bin.WithSchema<TIn, TOut> type describes either a schema or an object defining itself as one.
The object itself is not a schema, so reading and writing is done through the schema it holds, or through a schema that contains it:
const buffer = new ArrayBuffer(Entity.maxSize);Entity.write(bin.cursor(buffer), { id: 1, health: 100, position: { x: 0.5, y: 0.25 },});Classes can define themselves as schemas with a static property:
class Vec2 { static readonly '~typed-binary' = { schema: bin.struct({ x: bin.f32, y: bin.f32 }), };}
const Transform = bin.struct({ position: Vec2, scale: Vec2 });Recursion
Section titled “Recursion”To reference a schema that is defined later (or the object’s own schema), define '~typed-binary' as a getter,
and reference the object from inside a getter of a struct property:
const TreeNode = { kind: 'tree-node', get '~typed-binary'() { return { schema: TreeNodeSchema }; },};
const TreeNodeSchema = bin.struct({ value: bin.i32, get children() { return bin.dynamicArray(TreeNode); },});
type TreeNode = bin.ExtractOut<typeof TreeNodeSchema>;// ^? { value: number, children: TreeNode[] }