Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cmake/Figura.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function(figura_compile)
cmake_parse_arguments(
ARG
"CLIENT;SERVER"
"LANG;PROTO;NAMESPACE;OUTPUT"
"LANG;PROTO;NAMESPACE;OUTPUT;WIRE"
""
${ARGN}
)
Expand All @@ -30,6 +30,10 @@ function(figura_compile)
list(APPEND CMD_ARGS --namespace ${ARG_NAMESPACE})
endif()

if (DEFINED ARG_WIRE)
list(APPEND CMD_ARGS --wire ${ARG_WIRE})
endif()

add_custom_command(
OUTPUT ${GENFILE}
COMMAND ${CMD_ARGS}
Expand Down
2 changes: 1 addition & 1 deletion cmake/Glaze.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function(import_glaze)
FetchContent_Declare(
glaze
GIT_REPOSITORY https://github.com/stephenberry/glaze.git
GIT_TAG v7.2.0
GIT_TAG v8.0.0
GIT_SHALLOW TRUE
EXCLUDE_FROM_ALL
OVERRIDE_FIND_PACKAGE
Expand Down
4 changes: 2 additions & 2 deletions figura/manager-extension.fig
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ struct LaunchEventData {
service Lifecycle {
fn launch(data: LaunchEventData) => bool;
fn shutdown() => bool;
fn send_message(msg: string) => bool;
event extension_message(msg: string);
fn send_message(msg: raw) => bool;
event extension_message(msg: raw);

// worker can explicitly request to be unloaded. only applies to `no-view` commands which have a predictable termination point.
event unload_requested();
Expand Down
4 changes: 2 additions & 2 deletions figura/manager.fig
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ service Manager {
// extension loads too fast.
fn ready(session_id: string) => bool;

fn messageExtension(session_id: string, payload: string) => bool;
fn messageExtension(session_id: string, payload: raw) => bool;

event extensionMessage(session_id: string, payload: string);
event extensionMessage(session_id: string, payload: raw);
event extensionCrash(session_id: string, reason: string);
}
2 changes: 1 addition & 1 deletion figura/tsapi.fig
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ struct DesktopNotificationPayload {
};

service UI {
fn render(json: string) => void;
fn render(json: raw) => void;
fn showToast(id: string, title: string, message: string, style: ToastStyle) => void;
fn updateToast(id: string, title: string) => void;
fn hideToast(id: string) => void;
Expand Down
1 change: 1 addition & 0 deletions src/file-indexer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ figura_compile(
LANG glaze
NAMESPACE file_indexer_gen
OUTPUT file-indexer-server.hpp
WIRE beve
)


Expand Down
293 changes: 293 additions & 0 deletions src/lib/figura/src/codegen/beve-ts.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
#pragma once
#include <string_view>

inline constexpr std::string_view FIGURA_BEVE_TS = R"ts(
export type WireData = Uint8Array;

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

class BeveWriter {
private buf = new Uint8Array(4096);
private view = new DataView(this.buf.buffer);
private len = 0;

value(v: unknown): void {
if (v === null || v === undefined) {
this.u8(0x00);
return;
}

switch (typeof v) {
case "boolean":
this.u8(v ? 0x18 : 0x08);
return;
case "number":
if (Number.isSafeInteger(v)) {
this.u8(0x69);
this.ensure(8);
this.view.setBigInt64(this.len, BigInt(v), true);
this.len += 8;
} else {
this.u8(0x61);
this.ensure(8);
this.view.setFloat64(this.len, v, true);
this.len += 8;
}
return;
case "string":
this.u8(0x02);
this.str(v);
return;
case "object":
break;
default:
this.u8(0x00);
return;
}

if (v instanceof Uint8Array) {
this.u8(0x14);
this.size(v.length);
this.ensure(v.length);
this.buf.set(v, this.len);
this.len += v.length;
return;
}

if (Array.isArray(v)) {
this.u8(0x05);
this.size(v.length);
for (const item of v) {
this.value(item === undefined || typeof item === "function" ? null : item);
}
return;
}

const asJson = (v as { toJSON?: unknown }).toJSON;
if (typeof asJson === "function") {
this.value(asJson.call(v));
return;
}

const obj = v as Record<string, unknown>;
const keys = Object.keys(obj).filter(
(k) => obj[k] !== undefined && typeof obj[k] !== "function",
);
this.u8(0x03);
this.size(keys.length);
for (const key of keys) {
this.str(key);
this.value(obj[key]);
}
}

finish(): Uint8Array {
return this.buf.slice(0, this.len);
}

private ensure(extra: number) {
if (this.len + extra <= this.buf.length) return;
let capacity = this.buf.length * 2;
while (capacity < this.len + extra) capacity *= 2;
const next = new Uint8Array(capacity);
next.set(this.buf.subarray(0, this.len));
this.buf = next;
this.view = new DataView(next.buffer);
}

private u8(v: number) {
this.ensure(1);
this.buf[this.len++] = v;
}

private size(v: number) {
if (v < 64) {
this.u8(v << 2);
} else if (v < 16384) {
this.ensure(2);
this.view.setUint16(this.len, (v << 2) | 1, true);
this.len += 2;
} else if (v < 1073741824) {
this.ensure(4);
this.view.setUint32(this.len, ((v << 2) | 2) >>> 0, true);
this.len += 4;
} else {
this.ensure(8);
this.view.setBigUint64(this.len, (BigInt(v) << 2n) | 3n, true);
this.len += 8;
}
}

private str(s: string) {
const bytes = textEncoder.encode(s);
this.size(bytes.length);
this.ensure(bytes.length);
this.buf.set(bytes, this.len);
this.len += bytes.length;
}
}

class BeveReader {
private view: DataView;
private pos = 0;

constructor(private readonly buf: Uint8Array) {
this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
}

value(): any {
const tag = this.buf[this.pos];

switch (tag & 7) {
case 0: {
this.pos++;
if (tag === 0x00) return null;
return (tag & 0x10) !== 0;
}
case 1: {
this.pos++;
return this.number(tag);
}
case 2: {
this.pos++;
return this.str();
}
case 3: {
this.pos++;
if (((tag >> 3) & 3) !== 0) {
throw new Error("BEVE: only string-keyed objects are supported");
}
const count = this.size();
const obj: Record<string, any> = {};
for (let i = 0; i < count; i++) {
const key = this.str();
const value = this.value();
if (key === "__proto__") {
Object.defineProperty(obj, key, {
value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
}
return obj;
}
case 4: {
this.pos++;
return this.typedArray(tag);
}
case 5: {
this.pos++;
const count = this.size();
const arr = new Array(count);
for (let i = 0; i < count; i++) arr[i] = this.value();
return arr;
}
default:
throw new Error(`BEVE: unsupported tag 0x${tag.toString(16)}`);
}
}

private number(tag: number): number {
const kind = (tag >> 3) & 3;
const width = 1 << ((tag >> 5) & 7);
const at = this.pos;
this.pos += width;

if (kind === 0) {
if (width === 4) return this.view.getFloat32(at, true);
if (width === 8) return this.view.getFloat64(at, true);
throw new Error(`BEVE: unsupported float width ${width}`);
}

if (kind === 1) {
if (width === 1) return this.view.getInt8(at);
if (width === 2) return this.view.getInt16(at, true);
if (width === 4) return this.view.getInt32(at, true);
if (width === 8) return Number(this.view.getBigInt64(at, true));
} else {
if (width === 1) return this.view.getUint8(at);
if (width === 2) return this.view.getUint16(at, true);
if (width === 4) return this.view.getUint32(at, true);
if (width === 8) return Number(this.view.getBigUint64(at, true));
}

throw new Error(`BEVE: unsupported integer width ${width}`);
}

private typedArray(tag: number): any {
const kind = (tag >> 3) & 3;
const exponent = (tag >> 5) & 7;
const count = this.size();

if (kind === 3) {
if (exponent === 1) {
const arr = new Array(count);
for (let i = 0; i < count; i++) arr[i] = this.str();
return arr;
}
const arr = new Array(count);
for (let i = 0; i < count; i++) {
const byte = this.buf[this.pos + (i >> 3)];
arr[i] = (byte & (1 << (i & 7))) !== 0;
}
this.pos += (count + 7) >> 3;
return arr;
}

const width = 1 << exponent;

if (kind === 2 && width === 1) {
const bytes = this.buf.slice(this.pos, this.pos + count);
this.pos += count;
return bytes;
}

const arr = new Array(count);
for (let i = 0; i < count; i++) {
const numberTag = (exponent << 5) | (kind << 3) | 1;
arr[i] = this.number(numberTag);
}
return arr;
}

private size(): number {
const config = this.buf[this.pos] & 3;
if (config === 0) return this.buf[this.pos++] >> 2;
if (config === 1) {
const v = this.view.getUint16(this.pos, true) >> 2;
this.pos += 2;
return v;
}
if (config === 2) {
const v = this.view.getUint32(this.pos, true) >>> 2;
this.pos += 4;
return v;
}
const v = this.view.getBigUint64(this.pos, true) >> 2n;
this.pos += 8;
return Number(v);
}

private str(): string {
const n = this.size();
const s = textDecoder.decode(this.buf.subarray(this.pos, this.pos + n));
this.pos += n;
return s;
}
}

function encodeMessage(msg: JsonRpcMessage): WireData {
const writer = new BeveWriter();
writer.value(msg);
return writer.finish();
}

function decodeMessage(data: WireData): JsonRpcMessage {
return new BeveReader(data).value() as JsonRpcMessage;
}
)ts";
Loading
Loading