Skip to content

Commit

Permalink
Updated documentation, fixed buffer unwrapping bug (#5)
Browse files Browse the repository at this point in the history
* Added test for socket-io
* Renamed IStableSchema type for convenience.
* Applying outer buffer offset when unwrapping. Updated documentation.
  • Loading branch information
iwoplaza authored Dec 25, 2023
1 parent b94ee46 commit 22ec3cf
Show file tree
Hide file tree
Showing 27 changed files with 679 additions and 191 deletions.
92 changes: 44 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ To properly enable type inference, **TypeScript 4.5** and up is required because
# Basic usage

```ts
import { Parsed, object, arrayOf, i32, string, bool } from "typed-binary";
import { Parsed, object, arrayOf, i32, string, bool } from 'typed-binary';

const GameState = object({
nickname: string, // Variable-length string
Expand All @@ -84,22 +84,22 @@ type GameState = Parsed<typeof GameState>;

//...

import { BufferReader, BufferWriter } from "typed-binary";
import { BufferReader, BufferWriter } from 'typed-binary';

/**
* Responsible for retrieving the saved game state.
* If none can be found, returns a default starting state.
*/
async function loadGameState(): Promise<GameState> {
try {
const buffer = await fs.readFile("./savedState.bin");
const buffer = await fs.readFile('./savedState.bin');
const reader = new BufferReader(buffer);

return GameState.read(reader);
} catch (e) {
// Returning the default state if no saved state found.
return {
nickname: "Default",
nickname: 'Default',
stage: 1,
newGamePlus: false,
collectables: [],
Expand All @@ -117,11 +117,11 @@ async function loadGameState(): Promise<GameState> {
*/
async function saveGameState(state: GameState): Promise<void> {
try {
const buffer = Buffer.alloc(GameState.sizeOf(state));
const buffer = Buffer.alloc(GameState.measure(state).size);
const writer = new BufferWriter(buffer);

GameState.write(writer, state);
await fs.writeFile("./savedState.bin", buffer);
await fs.writeFile('./savedState.bin', buffer);
} catch (e) {
console.error(`Error occurred during the saving process.`);
console.error(e);
Expand Down Expand Up @@ -153,16 +153,16 @@ There's a couple primitives to choose from:
- A string of characters followed by a '\0' terminal character.

```ts
import { BufferWriter, BufferReader, byte, string } from "typed-binary";
import { BufferWriter, BufferReader, byte, string } from 'typed-binary';

const buffer = Buffer.alloc(16);
const writer = new BufferWriter(buffer);
const reader = new BufferReader(buffer);

// Writing four bytes into the buffer
byte.write(writer, "W".charCodeAt(0));
byte.write(writer, "o".charCodeAt(0));
byte.write(writer, "w".charCodeAt(0));
byte.write(writer, 'W'.charCodeAt(0));
byte.write(writer, 'o'.charCodeAt(0));
byte.write(writer, 'w'.charCodeAt(0));
byte.write(writer, 0);

console.log(string.read(reader)); // Wow
Expand All @@ -175,7 +175,7 @@ Objects store their properties in key-ascending-alphabetical order, one next to
### Simple objects

```ts
import { BufferWriter, BufferReader, i32, string, object } from "typed-binary";
import { BufferWriter, BufferReader, i32, string, object } from 'typed-binary';

const buffer = Buffer.alloc(16);
const writer = new BufferWriter(buffer);
Expand All @@ -190,8 +190,8 @@ const Person = object({

// Writing a Person
Person.write(writer, {
firstName: "John",
lastName: "Doe",
firstName: 'John',
lastName: 'Doe',
age: 43,
});

Expand All @@ -213,7 +213,7 @@ import {
bool,
generic,
object,
} from "typed-binary";
} from 'typed-binary';

// Generic object schema
const Animal = generic(
Expand All @@ -230,7 +230,7 @@ const Animal = generic(
// Animal can be a cat
striped: bool,
}),
}
},
);

// A buffer to serialize into/out of
Expand All @@ -240,10 +240,10 @@ const reader = new BufferReader(buffer);

// Writing an Animal
Animal.write(writer, {
type: "cat", // We're specyfing which concrete type we want this object to be.
type: 'cat', // We're specyfing which concrete type we want this object to be.

// Base properties
nickname: "James",
nickname: 'James',
age: 5,

// Concrete type specific properties
Expand All @@ -257,11 +257,11 @@ console.log(JSON.stringify(animal)); // { "age": 5, "striped": true ... }

// -- Type checking works here! --
// animal.type => 'cat' | 'dog'
if (animal.type === "cat") {
if (animal.type === 'cat') {
// animal.type => 'cat'
console.log("It's a cat!");
// animal.striped => bool
console.log(animal.striped ? "Striped" : "Not striped");
console.log(animal.striped ? 'Striped' : 'Not striped');
} else {
// animal.type => 'dog'
console.log("It's a dog!");
Expand Down Expand Up @@ -358,7 +358,7 @@ import {
string,
object,
optional,
} from "typed-binary";
} from 'typed-binary';

const buffer = Buffer.alloc(16);
const writer = new BufferWriter(buffer);
Expand All @@ -381,20 +381,20 @@ const Person = object({

// Writing a Person (no address)
Person.write(writer, {
firstName: "John",
lastName: "Doe",
firstName: 'John',
lastName: 'Doe',
age: 43,
});

// Writing a Person (with an address)
Person.write(writer, {
firstName: "Jesse",
lastName: "Doe",
firstName: 'Jesse',
lastName: 'Doe',
age: 38,
address: {
city: "New York",
street: "Binary St.",
postalCode: "11-111",
city: 'New York',
street: 'Binary St.',
postalCode: '11-111',
},
});

Expand Down Expand Up @@ -422,21 +422,21 @@ If you want an object type to be able to contain one of itself (recursion), then
* This is because references are resolved recursively once the method
* passed as the 2nd argument to 'keyed' returns the schema.
*/
const Recursive = keyed("recursive-key", (Recursive) =>
const Recursive = keyed('recursive-key', (Recursive) =>
object({
value: i32,
next: optional(Recursive),
})
}),
);
```

### Recursive types alongside generics

```ts
import { i32, string, object, keyed } from "typed-binary";
import { i32, string, object, keyed } from 'typed-binary';

type Expression = Parsed<typeof Expression>;
const Expression = keyed("expression", (Expression) =>
const Expression = keyed('expression', (Expression) =>
generic(
{},
{
Expand All @@ -450,21 +450,21 @@ const Expression = keyed("expression", (Expression) =>
int_literal: object({
value: i32,
}),
}
)
},
),
);

const expr: Parsed<typeof Expression> = {
type: "multiply",
type: 'multiply',
a: {
type: "negate",
type: 'negate',
inner: {
type: "int_literal",
type: 'int_literal',
value: 15,
},
},
b: {
type: "int_literal",
type: 'int_literal',
value: 2,
},
};
Expand All @@ -480,16 +480,12 @@ import {
ISerialOutput,
Schema,
IRefResolver,
} from "typed-binary";
} from 'typed-binary';

/**
* A schema storing radians with 2 bytes of precision.
*/
class RadiansSchema extends Schema<number> {
resolve(ctx: IRefResolver): void {
// No inner references to resolve
}

read(input: ISerialInput): number {
const low = input.readByte();
const high = input.readByte();
Expand All @@ -501,7 +497,7 @@ class RadiansSchema extends Schema<number> {
write(output: ISerialOutput, 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;
// Discretising the value to be ints in range of [0, 65535]
// Quantizing the value to range of [0, 65535]
const discrete = Math.min(Math.floor((wrapped / Math.PI) * 65535), 65535);

const low = discrete & 0xff;
Expand All @@ -511,16 +507,16 @@ class RadiansSchema extends Schema<number> {
output.writeByte(high);
}

sizeOf(_: number): number {
measure(_: number, measurer: IMeasurer = new Measurer()): IMeasurer {
// The size of the data serialized by this schema
// doesn't depend on the actual value. It's always 2 bytes.
return 2;
return measurer.add(2);
}
}

// Creating a singleton instance of the schema,
// since it has no configuration properties.
export const RADIANS = new RadiansSchema();
export const radians = new RadiansSchema();
```

# Serialization and Deserialization
Expand All @@ -539,13 +535,13 @@ read(input: ISerialInput): T;
/**
* Estimates the size of the value (according to the schema's structure)
*/
sizeOf(value: T): number;
measure(value: T | MaxValue, measurer: IMeasurer): IMeasurer;
```

The `ISerialInput/Output` interfaces have a basic built-in implementation that reads/writes to a buffer:

```ts
import { BufferReader, BufferWriter } from "typed-binary";
import { BufferReader, BufferWriter } from 'typed-binary';

// Creating a fixed-length buffer of arbitrary size (64 bytes).
const buffer = Buffer.alloc(64); // Or new ArrayBuffer(64); on browsers.
Expand Down
Loading

0 comments on commit 22ec3cf

Please sign in to comment.