Skip to content

Commit

Permalink
TypeScript -> AssemblyScript; seems to work but not tested
Browse files Browse the repository at this point in the history
  • Loading branch information
sjml committed Dec 13, 2024
1 parent 4deb8fb commit 508cd53
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 229 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

[![PyPI](https://img.shields.io/pypi/v/beschi)](https://pypi.org/project/beschi/) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/sjml/beschi/ci.yml)](https://github.com/sjml/beschi/actions/workflows/ci.yml)

This is a custom bit-packing and unpacking code generator for C, C#, Go, Rust, Swift, TypeScript, and Zig. You feed it a data description and it generates source files for writing/reading buffers of that data, along the lines of [FlatBuffers](https://google.github.io/flatbuffers/) or [Cap'n Proto](https://capnproto.org), but with much less functionality for much simpler use cases. It was initially written for a larger project that was passing data back and forth between a Unity game, a Go server, and a web client, but I extracted it into its own thing. If all you need is a simple way to pack a data structure into a compact, portable binary form, this might be useful for you.
This is a custom bit-packing and unpacking code generator for C, C#, Go, Rust, Swift, TypeScript, and Zig.[^1] You feed it a data description and it generates source files for writing/reading buffers of that data, along the lines of [FlatBuffers](https://google.github.io/flatbuffers/) or [Cap'n Proto](https://capnproto.org), but with much less functionality for much simpler use cases. It was initially written for a larger project that was passing data back and forth between a Unity game, a Go server, and a web client, but I extracted it into its own thing. If all you need is a simple way to pack a data structure into a compact, portable binary form, this might be useful for you.

I go into more explanation for why this exists [in the documentation](https://github.com/sjml/beschi/tree/main/docs/), but I'll be honest, too: it **was** kind of fun to write a code generator. 😝

[^1]: There is also experimental support for AssemblyScript, but it's not run through the test suite so THERE BE DRAGONS. 🐉

## Documentation

Expand Down
4 changes: 4 additions & 0 deletions beschi/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ def __init__(self, name: str, value_spec: list[str | dict[str, int|str] ]):
def get_default_pair(self) -> tuple[str,int]:
return next(iter(self.values.items()))

def get_minimum_pair(self) -> tuple[str,int]:
k = min(self.values, key=self.values.get)
return (k, self.values[k])

class Struct():
def __init__(self, name: str):
self.name = name
Expand Down
323 changes: 186 additions & 137 deletions beschi/writers/boilerplate/AssemblyScript.ts
Original file line number Diff line number Diff line change
@@ -1,145 +1,194 @@

const _textDec = new TextDecoder('utf-8');
const _textEnc = new TextEncoder();

export class DataAccess {
buffer: DataView;
currentOffset: usize;

constructor(buffer: ArrayBuffer|DataView) {
this.currentOffset = 0;
if (buffer instanceof ArrayBuffer) {
this.buffer = new DataView(buffer);
}
else {
this.buffer = buffer;
}
}

isFinished(): boolean {
return this.currentOffset >= this.buffer.byteLength;
}

getByte(): u8 {
const ret = this.buffer.getUint8(this.currentOffset);
this.currentOffset += 1;
return ret;
}

getBool(): boolean {
return this.GetByte() > 0;
}

getInt16(): i16 {
const ret = this.buffer.getInt16(this.currentOffset, true);
this.currentOffset += 2;
return ret;
}

getUint16(): u16 {
const ret = this.buffer.getUint16(this.currentOffset, true);
this.currentOffset += 2;
return ret;
}

getInt32(): i32 {
const ret = this.buffer.getInt32(this.currentOffset, true);
this.currentOffset += 4;
return ret;
}

getUint32(): u32 {
const ret = this.buffer.getUint32(this.currentOffset, true);
this.currentOffset += 4;
return ret;
}

getInt64(): i64 {
const ret = this.buffer.getBigInt64(this.currentOffset, true);
this.currentOffset += 8;
return ret;
}

getUint64(): u64 {
const ret = this.buffer.getBigUint64(this.currentOffset, true);
this.currentOffset += 8;
return ret;
}

getFloat32(): f32 {
const ret = this.buffer.getFloat32(this.currentOffset, true);
this.currentOffset += 4;
return Math.fround(ret);
}

getFloat64(): f64 {
const ret = this.buffer.getFloat64(this.currentOffset, true);
this.currentOffset += 8;
return ret;
}

getString(): string {
const len = this.Get{# STRING_SIZE_TYPE #}();
const strBuffer = new Uint8Array(this.buffer.buffer, this.currentOffset, len);
this.currentOffset += len;
return _textDec.decode(strBuffer);
}


setByte(val: u8) {
this.buffer.setUint8(this.currentOffset, val);
this.currentOffset += 1;
}

setBool(val: boolean) {
this.setByte(val ? 1 : 0);
}

setInt16(val: i16) {
this.buffer.setInt16(this.currentOffset, val, true);
this.currentOffset += 2;
}

setUint16(val: u16) {
this.buffer.setUint16(this.currentOffset, val, true);
this.currentOffset += 2;
}

setInt32(val: i32) {
this.buffer.setInt32(this.currentOffset, val, true);
this.currentOffset += 4;
}

setUint32(val: u32) {
this.buffer.setUint32(this.currentOffset, val, true);
this.currentOffset += 4;
}
data: DataView;
currentOffset: u32;

constructor(buffer: DataView) {
this.currentOffset = 0;
this.data = buffer;
}

isFinished(): boolean {
return this.currentOffset >= this.data.byteLength;
}

getByte(): u8 {
const ret = this.data.getUint8(this.currentOffset);
this.currentOffset += 1;
return ret;
}

getBool(): boolean {
return this.getByte() > 0;
}

getInt16(): i16 {
const ret = this.data.getInt16(this.currentOffset, true);
this.currentOffset += 2;
return ret;
}

getUint16(): u16 {
const ret = this.data.getUint16(this.currentOffset, true);
this.currentOffset += 2;
return ret;
}

getInt32(): i32 {
const ret = this.data.getInt32(this.currentOffset, true);
this.currentOffset += 4;
return ret;
}

getUint32(): u32 {
const ret = this.data.getUint32(this.currentOffset, true);
this.currentOffset += 4;
return ret;
}

getInt64(): i64 {
const ret = this.data.getBigInt64(this.currentOffset, true);
this.currentOffset += 8;
return ret;
}

getUint64(): u64 {
const ret = this.data.getBigUint64(this.currentOffset, true);
this.currentOffset += 8;
return ret;
}

getFloat32(): f32 {
const ret = this.data.getFloat32(this.currentOffset, true);
this.currentOffset += 4;
return Math.fround(ret);
}

getFloat64(): f64 {
const ret = this.data.getFloat64(this.currentOffset, true);
this.currentOffset += 8;
return ret;
}

getString(): string {
const len = this.get{# STRING_SIZE_TYPE #}();
const strBuffer = new Uint8Array(this.data.buffer, this.currentOffset, len);
this.currentOffset += len;
return String.UTF8.decode(strBuffer, false);
}


setByte(val: u8): void {
this.data.setUint8(this.currentOffset, val);
this.currentOffset += 1;
}

setBool(val: boolean): void {
this.setByte(val ? 1 : 0);
}

setInt16(val: i16): void {
this.data.setInt16(this.currentOffset, val, true);
this.currentOffset += 2;
}

setUint16(val: u16): void {
this.data.setUint16(this.currentOffset, val, true);
this.currentOffset += 2;
}

setInt32(val: i32): void {
this.data.setInt32(this.currentOffset, val, true);
this.currentOffset += 4;
}

setUint32(val: u32): void {
this.data.setUint32(this.currentOffset, val, true);
this.currentOffset += 4;
}

setInt64(val: i64): void {
this.data.setBigInt64(this.currentOffset, val, true);
this.currentOffset += 8;
}

setUint64(val: u64): void {
this.data.setBigUint64(this.currentOffset, val, true);
this.currentOffset += 8;
}

setFloat32(val: f32): void {
this.data.setFloat32(this.currentOffset, val, true);
this.currentOffset += 4;
}

setFloat64(val: f64): void {
this.data.setFloat64(this.currentOffset, val, true);
this.currentOffset += 8;
}

setString(val: string): void {
const strBuffer = String.UTF8.encode(val, false);
const bufferArray = Uint8Array.wrap(strBuffer);
this.setByte(strBuffer.byteLength as u8);
for (let i = 0; i < bufferArray.byteLength; i++) {
this.setByte(bufferArray[i] as u8);
}
}
}

setInt64(val: i64) {
this.buffer.setBigInt64(this.currentOffset, val, true);
this.currentOffset += 8;
}
export abstract class Message {
abstract getMessageType(): MessageType;
abstract writeBytes(dv: DataView, tag: boolean): void;
abstract getSizeInBytes(): number;

setUint64(val: u64) {
this.buffer.setBigUint64(this.currentOffset, val, true);
this.currentOffset += 8;
}
static fromBytes(data: DataView): Message | null {
throw new Error("Cannot read abstract Message from bytes.");
};
}

setFloat32(val: f32) {
this.buffer.setFloat32(this.currentOffset, val, true);
this.currentOffset += 4;
}
export function GetPackedSize(msgList: Message[]): usize {
let size = 0;
for (const msg of msgList) {
size += msg.getSizeInBytes();
}
size += msgList.length;
size += 9;
return size;
}

setFloat64(val: f64) {
this.buffer.setFloat64(this.currentOffset, val, true);
this.currentOffset += 8;
}
export function PackMessages(msgList: Message[], data: DataView): void {
const da = new DataAccess(data);
const headerBytes = _textEnc.encode("BSCI");
const arr = new Uint8Array(da.data.buffer);
arr.set(headerBytes, da.currentOffset);
da.currentOffset += headerBytes.byteLength;
da.setUint32(msgList.length);
for (const msg of msgList) {
msg.writeBytes(da, true);
}
da.setByte(0);
}

setString(val: string) {
const strBuffer = _textEnc.encode(val);
this.Set{# STRING_SIZE_TYPE #}(strBuffer.byteLength);
const arr = new Uint8Array(this.buffer.buffer);
arr.set(strBuffer, this.currentOffset);
this.currentOffset += strBuffer.byteLength;
}
export function UnpackMessages(data: DataView): Message[] {
const da = new DataAccess(data);
const headerBuffer = new Uint8Array(da.data.buffer, da.currentOffset, 4);
da.currentOffset += 4;
const headerLabel = _textDec.decode(headerBuffer);
if (headerLabel !== "BSCI") {
throw new Error("Packed message buffer has invalid header.");
}
const msgCount = da.getUint32();
if (msgCount == 0) {
return [];
}
const msgList = ProcessRawBytes(da, msgCount);
if (msgList.length == 0) {
throw new Error("No messages in buffer.");
}
if (msgList.length != msgCount) {
throw new Error("Unexpected number of messages in buffer.");
}
return msgList;
}

Loading

0 comments on commit 508cd53

Please sign in to comment.