From 4876d3d456930e42b9ffb0cb4b035c4e8df34ca2 Mon Sep 17 00:00:00 2001 From: Ryan Daum Date: Mon, 26 Aug 2024 09:17:05 -0400 Subject: [PATCH] Incremental improvements in the web server/client * Begin support for parsing flexbuffers in the client, just the initial addition of the flexbuffer parser. * Begin support for invoking verbs from the client. --- crates/daemon/src/rpc_server.rs | 6 +- .../src/client/flexbuffers/bit-width-util.js | 28 + .../src/client/flexbuffers/bit-width.js | 7 + .../src/client/flexbuffers/builder.js | 678 ++++++++++++++++++ .../client/flexbuffers/flexbuffers-util.js | 8 + .../src/client/flexbuffers/reference-util.js | 101 +++ .../src/client/flexbuffers/reference.js | 295 ++++++++ .../src/client/flexbuffers/stack-value.js | 125 ++++ .../src/client/flexbuffers/value-type-util.js | 42 ++ .../src/client/flexbuffers/value-type.js | 31 + crates/web-host/src/client/mod.rs | 14 + crates/web-host/src/client/root.html | 25 +- crates/web-host/src/client/var.js | 0 crates/web-host/src/host/mod.rs | 51 +- crates/web-host/src/host/web_host.rs | 76 +- crates/web-host/src/main.rs | 34 + 16 files changed, 1513 insertions(+), 8 deletions(-) create mode 100644 crates/web-host/src/client/flexbuffers/bit-width-util.js create mode 100644 crates/web-host/src/client/flexbuffers/bit-width.js create mode 100644 crates/web-host/src/client/flexbuffers/builder.js create mode 100644 crates/web-host/src/client/flexbuffers/flexbuffers-util.js create mode 100644 crates/web-host/src/client/flexbuffers/reference-util.js create mode 100644 crates/web-host/src/client/flexbuffers/reference.js create mode 100644 crates/web-host/src/client/flexbuffers/stack-value.js create mode 100644 crates/web-host/src/client/flexbuffers/value-type-util.js create mode 100644 crates/web-host/src/client/flexbuffers/value-type.js create mode 100644 crates/web-host/src/client/var.js diff --git a/crates/daemon/src/rpc_server.rs b/crates/daemon/src/rpc_server.rs index e389b5ac..29763a87 100644 --- a/crates/daemon/src/rpc_server.rs +++ b/crates/daemon/src/rpc_server.rs @@ -38,9 +38,9 @@ use moor_values::model::{Named, ObjectRef, PropFlag, ValSet, VerbFlag}; use moor_values::tasks::SchedulerError::CommandExecutionError; use moor_values::tasks::{CommandError, NarrativeEvent, SchedulerError, TaskId}; use moor_values::util::parse_into_words; -use moor_values::var::Symbol; +use moor_values::var::v_objid; use moor_values::var::Variant; -use moor_values::var::{v_objid, v_string}; +use moor_values::var::{v_str, Symbol}; use moor_values::var::{Objid, Var}; use moor_values::SYSTEM_OBJECT; use rpc_common::RpcResponse::{LoginResult, NewConnection}; @@ -604,7 +604,7 @@ impl RpcServer { connection, ObjectRef::Id(SYSTEM_OBJECT), Symbol::mk("do_login_command"), - args.iter().map(|s| v_string(s.clone())).collect(), + args.iter().map(|s| v_str(&s)).collect(), args.join(" "), SYSTEM_OBJECT, session, diff --git a/crates/web-host/src/client/flexbuffers/bit-width-util.js b/crates/web-host/src/client/flexbuffers/bit-width-util.js new file mode 100644 index 00000000..6ab3e823 --- /dev/null +++ b/crates/web-host/src/client/flexbuffers/bit-width-util.js @@ -0,0 +1,28 @@ +import { BitWidth } from './bit-width.js'; +export function toByteWidth(bitWidth) { + return 1 << bitWidth; +} +export function iwidth(value) { + if (value >= -128 && value <= 127) return BitWidth.WIDTH8; + if (value >= -32768 && value <= 32767) return BitWidth.WIDTH16; + if (value >= -2147483648 && value <= 2147483647) return BitWidth.WIDTH32; + return BitWidth.WIDTH64; +} +export function fwidth(value) { + return value === Math.fround(value) ? BitWidth.WIDTH32 : BitWidth.WIDTH64; +} +export function uwidth(value) { + if (value <= 255) return BitWidth.WIDTH8; + if (value <= 65535) return BitWidth.WIDTH16; + if (value <= 4294967295) return BitWidth.WIDTH32; + return BitWidth.WIDTH64; +} +export function fromByteWidth(value) { + if (value === 1) return BitWidth.WIDTH8; + if (value === 2) return BitWidth.WIDTH16; + if (value === 4) return BitWidth.WIDTH32; + return BitWidth.WIDTH64; +} +export function paddingSize(bufSize, scalarSize) { + return ~bufSize + 1 & scalarSize - 1; +} diff --git a/crates/web-host/src/client/flexbuffers/bit-width.js b/crates/web-host/src/client/flexbuffers/bit-width.js new file mode 100644 index 00000000..48008325 --- /dev/null +++ b/crates/web-host/src/client/flexbuffers/bit-width.js @@ -0,0 +1,7 @@ +export var BitWidth; +(function(BitWidth) { + BitWidth[BitWidth["WIDTH8"] = 0] = "WIDTH8"; + BitWidth[BitWidth["WIDTH16"] = 1] = "WIDTH16"; + BitWidth[BitWidth["WIDTH32"] = 2] = "WIDTH32"; + BitWidth[BitWidth["WIDTH64"] = 3] = "WIDTH64"; +})(BitWidth || (BitWidth = {})); diff --git a/crates/web-host/src/client/flexbuffers/builder.js b/crates/web-host/src/client/flexbuffers/builder.js new file mode 100644 index 00000000..ef034aa1 --- /dev/null +++ b/crates/web-host/src/client/flexbuffers/builder.js @@ -0,0 +1,678 @@ +function _instanceof(left, right) { + if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { + return !!right[Symbol.hasInstance](left); + } else { + return left instanceof right; + } +} +function _class_call_check(instance, Constructor) { + if (!_instanceof(instance, Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} +function _defineProperties(target, props) { + for(var i = 0; i < props.length; i++){ + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } +} +function _create_class(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; +} +function _define_property(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; +} +function _instanceof1(left, right) { + if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { + return !!right[Symbol.hasInstance](left); + } else { + return _instanceof(left, right); + } +} +function _type_of(obj) { + "@swc/helpers - typeof"; + return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; +} +import { BitWidth } from './bit-width.js'; +import { paddingSize, iwidth, uwidth, fwidth, toByteWidth, fromByteWidth } from './bit-width-util.js'; +import { toUTF8Array } from './flexbuffers-util.js'; +import { ValueType } from './value-type.js'; +import { isNumber, isTypedVectorElement, toTypedVector } from './value-type-util.js'; +import { StackValue } from './stack-value.js'; +export var Builder = /*#__PURE__*/ function() { + "use strict"; + function Builder() { + var size = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 2048, dedupStrings = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : true, dedupKeys = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : true, dedupKeyVectors = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : true; + _class_call_check(this, Builder); + _define_property(this, "dedupStrings", void 0); + _define_property(this, "dedupKeys", void 0); + _define_property(this, "dedupKeyVectors", void 0); + _define_property(this, "buffer", void 0); + _define_property(this, "view", void 0); + _define_property(this, "stack", void 0); + _define_property(this, "stackPointers", void 0); + _define_property(this, "offset", void 0); + _define_property(this, "finished", void 0); + _define_property(this, "stringLookup", void 0); + _define_property(this, "keyLookup", void 0); + _define_property(this, "keyVectorLookup", void 0); + _define_property(this, "indirectIntLookup", void 0); + _define_property(this, "indirectUIntLookup", void 0); + _define_property(this, "indirectFloatLookup", void 0); + this.dedupStrings = dedupStrings; + this.dedupKeys = dedupKeys; + this.dedupKeyVectors = dedupKeyVectors; + this.stack = []; + this.stackPointers = []; + this.offset = 0; + this.finished = false; + this.stringLookup = {}; + this.keyLookup = {}; + this.keyVectorLookup = {}; + this.indirectIntLookup = {}; + this.indirectUIntLookup = {}; + this.indirectFloatLookup = {}; + this.buffer = new ArrayBuffer(size > 0 ? size : 2048); + this.view = new DataView(this.buffer); + } + _create_class(Builder, [ + { + key: "align", + value: function align(width) { + var byteWidth = toByteWidth(width); + this.offset += paddingSize(this.offset, byteWidth); + return byteWidth; + } + }, + { + key: "computeOffset", + value: function computeOffset(newValueSize) { + var targetOffset = this.offset + newValueSize; + var size = this.buffer.byteLength; + var prevSize = size; + while(size < targetOffset){ + size <<= 1; + } + if (prevSize < size) { + var prevBuffer = this.buffer; + this.buffer = new ArrayBuffer(size); + this.view = new DataView(this.buffer); + new Uint8Array(this.buffer).set(new Uint8Array(prevBuffer), 0); + } + return targetOffset; + } + }, + { + key: "pushInt", + value: function pushInt(value, width) { + if (width === BitWidth.WIDTH8) { + this.view.setInt8(this.offset, value); + } else if (width === BitWidth.WIDTH16) { + this.view.setInt16(this.offset, value, true); + } else if (width === BitWidth.WIDTH32) { + this.view.setInt32(this.offset, value, true); + } else if (width === BitWidth.WIDTH64) { + this.view.setBigInt64(this.offset, BigInt(value), true); + } else { + throw "Unexpected width: ".concat(width, " for value: ").concat(value); + } + } + }, + { + key: "pushUInt", + value: function pushUInt(value, width) { + if (width === BitWidth.WIDTH8) { + this.view.setUint8(this.offset, value); + } else if (width === BitWidth.WIDTH16) { + this.view.setUint16(this.offset, value, true); + } else if (width === BitWidth.WIDTH32) { + this.view.setUint32(this.offset, value, true); + } else if (width === BitWidth.WIDTH64) { + this.view.setBigUint64(this.offset, BigInt(value), true); + } else { + throw "Unexpected width: ".concat(width, " for value: ").concat(value); + } + } + }, + { + key: "writeInt", + value: function writeInt(value, byteWidth) { + var newOffset = this.computeOffset(byteWidth); + this.pushInt(value, fromByteWidth(byteWidth)); + this.offset = newOffset; + } + }, + { + key: "writeUInt", + value: function writeUInt(value, byteWidth) { + var newOffset = this.computeOffset(byteWidth); + this.pushUInt(value, fromByteWidth(byteWidth)); + this.offset = newOffset; + } + }, + { + key: "writeBlob", + value: function writeBlob(arrayBuffer) { + var length = arrayBuffer.byteLength; + var bitWidth = uwidth(length); + var byteWidth = this.align(bitWidth); + this.writeUInt(length, byteWidth); + var blobOffset = this.offset; + var newOffset = this.computeOffset(length); + new Uint8Array(this.buffer).set(new Uint8Array(arrayBuffer), blobOffset); + this.stack.push(this.offsetStackValue(blobOffset, ValueType.BLOB, bitWidth)); + this.offset = newOffset; + } + }, + { + key: "writeString", + value: function writeString(str) { + if (this.dedupStrings && Object.prototype.hasOwnProperty.call(this.stringLookup, str)) { + this.stack.push(this.stringLookup[str]); + return; + } + var utf8 = toUTF8Array(str); + var length = utf8.length; + var bitWidth = uwidth(length); + var byteWidth = this.align(bitWidth); + this.writeUInt(length, byteWidth); + var stringOffset = this.offset; + var newOffset = this.computeOffset(length + 1); + new Uint8Array(this.buffer).set(utf8, stringOffset); + var stackValue = this.offsetStackValue(stringOffset, ValueType.STRING, bitWidth); + this.stack.push(stackValue); + if (this.dedupStrings) { + this.stringLookup[str] = stackValue; + } + this.offset = newOffset; + } + }, + { + key: "writeKey", + value: function writeKey(str) { + if (this.dedupKeys && Object.prototype.hasOwnProperty.call(this.keyLookup, str)) { + this.stack.push(this.keyLookup[str]); + return; + } + var utf8 = toUTF8Array(str); + var length = utf8.length; + var newOffset = this.computeOffset(length + 1); + new Uint8Array(this.buffer).set(utf8, this.offset); + var stackValue = this.offsetStackValue(this.offset, ValueType.KEY, BitWidth.WIDTH8); + this.stack.push(stackValue); + if (this.dedupKeys) { + this.keyLookup[str] = stackValue; + } + this.offset = newOffset; + } + }, + { + key: "writeStackValue", + value: function writeStackValue(value, byteWidth) { + var newOffset = this.computeOffset(byteWidth); + if (value.isOffset()) { + var relativeOffset = this.offset - value.offset; + if (byteWidth === 8 || BigInt(relativeOffset) < BigInt(1) << BigInt(byteWidth * 8)) { + this.writeUInt(relativeOffset, byteWidth); + } else { + throw "Unexpected size ".concat(byteWidth, ". This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new"); + } + } else { + value.writeToBuffer(byteWidth); + } + this.offset = newOffset; + } + }, + { + key: "integrityCheckOnValueAddition", + value: function integrityCheckOnValueAddition() { + if (this.finished) { + throw "Adding values after finish is prohibited"; + } + if (this.stackPointers.length !== 0 && this.stackPointers[this.stackPointers.length - 1].isVector === false) { + if (this.stack[this.stack.length - 1].type !== ValueType.KEY) { + throw "Adding value to a map before adding a key is prohibited"; + } + } + } + }, + { + key: "integrityCheckOnKeyAddition", + value: function integrityCheckOnKeyAddition() { + if (this.finished) { + throw "Adding values after finish is prohibited"; + } + if (this.stackPointers.length === 0 || this.stackPointers[this.stackPointers.length - 1].isVector) { + throw "Adding key before starting a map is prohibited"; + } + } + }, + { + key: "startVector", + value: function startVector() { + this.stackPointers.push({ + stackPosition: this.stack.length, + isVector: true + }); + } + }, + { + key: "startMap", + value: function startMap() { + var presorted = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : false; + this.stackPointers.push({ + stackPosition: this.stack.length, + isVector: false, + presorted: presorted + }); + } + }, + { + key: "endVector", + value: function endVector(stackPointer) { + var vecLength = this.stack.length - stackPointer.stackPosition; + var vec = this.createVector(stackPointer.stackPosition, vecLength, 1); + this.stack.splice(stackPointer.stackPosition, vecLength); + this.stack.push(vec); + } + }, + { + key: "endMap", + value: function endMap(stackPointer) { + if (!stackPointer.presorted) { + this.sort(stackPointer); + } + var keyVectorHash = ""; + for(var i = stackPointer.stackPosition; i < this.stack.length; i += 2){ + keyVectorHash += ",".concat(this.stack[i].offset); + } + var vecLength = this.stack.length - stackPointer.stackPosition >> 1; + if (this.dedupKeyVectors && !Object.prototype.hasOwnProperty.call(this.keyVectorLookup, keyVectorHash)) { + this.keyVectorLookup[keyVectorHash] = this.createVector(stackPointer.stackPosition, vecLength, 2); + } + var keysStackValue = this.dedupKeyVectors ? this.keyVectorLookup[keyVectorHash] : this.createVector(stackPointer.stackPosition, vecLength, 2); + var valuesStackValue = this.createVector(stackPointer.stackPosition + 1, vecLength, 2, keysStackValue); + this.stack.splice(stackPointer.stackPosition, vecLength << 1); + this.stack.push(valuesStackValue); + } + }, + { + key: "sort", + value: function sort(stackPointer) { + var view = this.view; + var stack = this.stack; + function shouldFlip(v1, v2) { + if (v1.type !== ValueType.KEY || v2.type !== ValueType.KEY) { + throw "Stack values are not keys ".concat(v1, " | ").concat(v2, ". Check if you combined [addKey] with add... method calls properly."); + } + var c1, c2; + var index = 0; + do { + c1 = view.getUint8(v1.offset + index); + c2 = view.getUint8(v2.offset + index); + if (c2 < c1) return true; + if (c1 < c2) return false; + index += 1; + }while (c1 !== 0 && c2 !== 0); + return false; + } + function swap(stack, flipIndex, i) { + if (flipIndex === i) return; + var k = stack[flipIndex]; + var v = stack[flipIndex + 1]; + stack[flipIndex] = stack[i]; + stack[flipIndex + 1] = stack[i + 1]; + stack[i] = k; + stack[i + 1] = v; + } + function selectionSort() { + for(var i = stackPointer.stackPosition; i < stack.length; i += 2){ + var flipIndex = i; + for(var j = i + 2; j < stack.length; j += 2){ + if (shouldFlip(stack[flipIndex], stack[j])) { + flipIndex = j; + } + } + if (flipIndex !== i) { + swap(stack, flipIndex, i); + } + } + } + function smaller(v1, v2) { + if (v1.type !== ValueType.KEY || v2.type !== ValueType.KEY) { + throw "Stack values are not keys ".concat(v1, " | ").concat(v2, ". Check if you combined [addKey] with add... method calls properly."); + } + if (v1.offset === v2.offset) { + return false; + } + var c1, c2; + var index = 0; + do { + c1 = view.getUint8(v1.offset + index); + c2 = view.getUint8(v2.offset + index); + if (c1 < c2) return true; + if (c2 < c1) return false; + index += 1; + }while (c1 !== 0 && c2 !== 0); + return false; + } + function quickSort(left, right) { + if (left < right) { + var mid = left + (right - left >> 2) * 2; + var pivot = stack[mid]; + var left_new = left; + var right_new = right; + do { + while(smaller(stack[left_new], pivot)){ + left_new += 2; + } + while(smaller(pivot, stack[right_new])){ + right_new -= 2; + } + if (left_new <= right_new) { + swap(stack, left_new, right_new); + left_new += 2; + right_new -= 2; + } + }while (left_new <= right_new); + quickSort(left, right_new); + quickSort(left_new, right); + } + } + var sorted = true; + for(var i = stackPointer.stackPosition; i < this.stack.length - 2; i += 2){ + if (shouldFlip(this.stack[i], this.stack[i + 2])) { + sorted = false; + break; + } + } + if (!sorted) { + if (this.stack.length - stackPointer.stackPosition > 40) { + quickSort(stackPointer.stackPosition, this.stack.length - 2); + } else { + selectionSort(); + } + } + } + }, + { + key: "end", + value: function end() { + if (this.stackPointers.length < 1) return; + var pointer = this.stackPointers.pop(); + if (pointer.isVector) { + this.endVector(pointer); + } else { + this.endMap(pointer); + } + } + }, + { + key: "createVector", + value: function createVector(start, vecLength, step) { + var keys = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : null; + var bitWidth = uwidth(vecLength); + var prefixElements = 1; + if (keys !== null) { + var elementWidth = keys.elementWidth(this.offset, 0); + if (elementWidth > bitWidth) { + bitWidth = elementWidth; + } + prefixElements += 2; + } + var vectorType = ValueType.KEY; + var typed = keys === null; + for(var i = start; i < this.stack.length; i += step){ + var elementWidth1 = this.stack[i].elementWidth(this.offset, i + prefixElements); + if (elementWidth1 > bitWidth) { + bitWidth = elementWidth1; + } + if (i === start) { + vectorType = this.stack[i].type; + typed = typed && isTypedVectorElement(vectorType); + } else { + if (vectorType !== this.stack[i].type) { + typed = false; + } + } + } + var byteWidth = this.align(bitWidth); + var fix = typed && isNumber(vectorType) && vecLength >= 2 && vecLength <= 4; + if (keys !== null) { + this.writeStackValue(keys, byteWidth); + this.writeUInt(1 << keys.width, byteWidth); + } + if (!fix) { + this.writeUInt(vecLength, byteWidth); + } + var vecOffset = this.offset; + for(var i1 = start; i1 < this.stack.length; i1 += step){ + this.writeStackValue(this.stack[i1], byteWidth); + } + if (!typed) { + for(var i2 = start; i2 < this.stack.length; i2 += step){ + this.writeUInt(this.stack[i2].storedPackedType(), 1); + } + } + if (keys !== null) { + return this.offsetStackValue(vecOffset, ValueType.MAP, bitWidth); + } + if (typed) { + var vType = toTypedVector(vectorType, fix ? vecLength : 0); + return this.offsetStackValue(vecOffset, vType, bitWidth); + } + return this.offsetStackValue(vecOffset, ValueType.VECTOR, bitWidth); + } + }, + { + key: "nullStackValue", + value: function nullStackValue() { + return new StackValue(this, ValueType.NULL, BitWidth.WIDTH8); + } + }, + { + key: "boolStackValue", + value: function boolStackValue(value) { + return new StackValue(this, ValueType.BOOL, BitWidth.WIDTH8, value); + } + }, + { + key: "intStackValue", + value: function intStackValue(value) { + return new StackValue(this, ValueType.INT, iwidth(value), value); + } + }, + { + key: "uintStackValue", + value: function uintStackValue(value) { + return new StackValue(this, ValueType.UINT, uwidth(value), value); + } + }, + { + key: "floatStackValue", + value: function floatStackValue(value) { + return new StackValue(this, ValueType.FLOAT, fwidth(value), value); + } + }, + { + key: "offsetStackValue", + value: function offsetStackValue(offset, valueType, bitWidth) { + return new StackValue(this, valueType, bitWidth, null, offset); + } + }, + { + key: "finishBuffer", + value: function finishBuffer() { + if (this.stack.length !== 1) { + throw "Stack has to be exactly 1, but it is ".concat(this.stack.length, ". You have to end all started vectors and maps before calling [finish]"); + } + var value = this.stack[0]; + var byteWidth = this.align(value.elementWidth(this.offset, 0)); + this.writeStackValue(value, byteWidth); + this.writeUInt(value.storedPackedType(), 1); + this.writeUInt(byteWidth, 1); + this.finished = true; + } + }, + { + key: "add", + value: function add(value) { + this.integrityCheckOnValueAddition(); + if (typeof value === 'undefined') { + throw "You need to provide a value"; + } + if (value === null) { + this.stack.push(this.nullStackValue()); + } else if (typeof value === "boolean") { + this.stack.push(this.boolStackValue(value)); + } else if ((typeof value === "undefined" ? "undefined" : _type_of(value)) === "bigint") { + this.stack.push(this.intStackValue(value)); + } else if (typeof value == 'number') { + if (Number.isInteger(value)) { + this.stack.push(this.intStackValue(value)); + } else { + this.stack.push(this.floatStackValue(value)); + } + } else if (ArrayBuffer.isView(value)) { + this.writeBlob(value.buffer); + } else if (typeof value === 'string' || _instanceof1(value, String)) { + this.writeString(value); + } else if (Array.isArray(value)) { + this.startVector(); + for(var i = 0; i < value.length; i++){ + this.add(value[i]); + } + this.end(); + } else if ((typeof value === "undefined" ? "undefined" : _type_of(value)) === 'object') { + var properties = Object.getOwnPropertyNames(value).sort(); + this.startMap(true); + for(var i1 = 0; i1 < properties.length; i1++){ + var key = properties[i1]; + this.addKey(key); + this.add(value[key]); + } + this.end(); + } else { + throw "Unexpected value input ".concat(value); + } + } + }, + { + key: "finish", + value: function finish() { + if (!this.finished) { + this.finishBuffer(); + } + var result = this.buffer.slice(0, this.offset); + return new Uint8Array(result); + } + }, + { + key: "isFinished", + value: function isFinished() { + return this.finished; + } + }, + { + key: "addKey", + value: function addKey(key) { + this.integrityCheckOnKeyAddition(); + this.writeKey(key); + } + }, + { + key: "addInt", + value: function addInt(value) { + var indirect = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false, deduplicate = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false; + this.integrityCheckOnValueAddition(); + if (!indirect) { + this.stack.push(this.intStackValue(value)); + return; + } + if (deduplicate && Object.prototype.hasOwnProperty.call(this.indirectIntLookup, value)) { + this.stack.push(this.indirectIntLookup[value]); + return; + } + var stackValue = this.intStackValue(value); + var byteWidth = this.align(stackValue.width); + var newOffset = this.computeOffset(byteWidth); + var valueOffset = this.offset; + stackValue.writeToBuffer(byteWidth); + var stackOffset = this.offsetStackValue(valueOffset, ValueType.INDIRECT_INT, stackValue.width); + this.stack.push(stackOffset); + this.offset = newOffset; + if (deduplicate) { + this.indirectIntLookup[value] = stackOffset; + } + } + }, + { + key: "addUInt", + value: function addUInt(value) { + var indirect = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false, deduplicate = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false; + this.integrityCheckOnValueAddition(); + if (!indirect) { + this.stack.push(this.uintStackValue(value)); + return; + } + if (deduplicate && Object.prototype.hasOwnProperty.call(this.indirectUIntLookup, value)) { + this.stack.push(this.indirectUIntLookup[value]); + return; + } + var stackValue = this.uintStackValue(value); + var byteWidth = this.align(stackValue.width); + var newOffset = this.computeOffset(byteWidth); + var valueOffset = this.offset; + stackValue.writeToBuffer(byteWidth); + var stackOffset = this.offsetStackValue(valueOffset, ValueType.INDIRECT_UINT, stackValue.width); + this.stack.push(stackOffset); + this.offset = newOffset; + if (deduplicate) { + this.indirectUIntLookup[value] = stackOffset; + } + } + }, + { + key: "addFloat", + value: function addFloat(value) { + var indirect = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false, deduplicate = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false; + this.integrityCheckOnValueAddition(); + if (!indirect) { + this.stack.push(this.floatStackValue(value)); + return; + } + if (deduplicate && Object.prototype.hasOwnProperty.call(this.indirectFloatLookup, value)) { + this.stack.push(this.indirectFloatLookup[value]); + return; + } + var stackValue = this.floatStackValue(value); + var byteWidth = this.align(stackValue.width); + var newOffset = this.computeOffset(byteWidth); + var valueOffset = this.offset; + stackValue.writeToBuffer(byteWidth); + var stackOffset = this.offsetStackValue(valueOffset, ValueType.INDIRECT_FLOAT, stackValue.width); + this.stack.push(stackOffset); + this.offset = newOffset; + if (deduplicate) { + this.indirectFloatLookup[value] = stackOffset; + } + } + } + ]); + return Builder; +}(); diff --git a/crates/web-host/src/client/flexbuffers/flexbuffers-util.js b/crates/web-host/src/client/flexbuffers/flexbuffers-util.js new file mode 100644 index 00000000..81b1294a --- /dev/null +++ b/crates/web-host/src/client/flexbuffers/flexbuffers-util.js @@ -0,0 +1,8 @@ +export function fromUTF8Array(data) { + var decoder = new TextDecoder(); + return decoder.decode(data); +} +export function toUTF8Array(str) { + var encoder = new TextEncoder(); + return encoder.encode(str); +} diff --git a/crates/web-host/src/client/flexbuffers/reference-util.js b/crates/web-host/src/client/flexbuffers/reference-util.js new file mode 100644 index 00000000..757801e9 --- /dev/null +++ b/crates/web-host/src/client/flexbuffers/reference-util.js @@ -0,0 +1,101 @@ +import { BitWidth } from './bit-width.js'; +import { toByteWidth, fromByteWidth } from './bit-width-util.js'; +import { toUTF8Array, fromUTF8Array } from './flexbuffers-util.js'; +export function validateOffset(dataView, offset, width) { + if (dataView.byteLength <= offset + width || (offset & toByteWidth(width) - 1) !== 0) { + throw "Bad offset: " + offset + ", width: " + width; + } +} +export function readInt(dataView, offset, width) { + if (width < 2) { + if (width < 1) { + return dataView.getInt8(offset); + } else { + return dataView.getInt16(offset, true); + } + } else { + if (width < 3) { + return dataView.getInt32(offset, true); + } else { + if (dataView.setBigInt64 === undefined) { + return BigInt(dataView.getUint32(offset, true)) + (BigInt(dataView.getUint32(offset + 4, true)) << BigInt(32)); + } + return dataView.getBigInt64(offset, true); + } + } +} +export function readUInt(dataView, offset, width) { + if (width < 2) { + if (width < 1) { + return dataView.getUint8(offset); + } else { + return dataView.getUint16(offset, true); + } + } else { + if (width < 3) { + return dataView.getUint32(offset, true); + } else { + if (dataView.getBigUint64 === undefined) { + return BigInt(dataView.getUint32(offset, true)) + (BigInt(dataView.getUint32(offset + 4, true)) << BigInt(32)); + } + return dataView.getBigUint64(offset, true); + } + } +} +export function readFloat(dataView, offset, width) { + if (width < BitWidth.WIDTH32) { + throw "Bad width: " + width; + } + if (width === BitWidth.WIDTH32) { + return dataView.getFloat32(offset, true); + } + return dataView.getFloat64(offset, true); +} +export function indirect(dataView, offset, width) { + var step = readUInt(dataView, offset, width); + return offset - step; +} +export function keyIndex(key, dataView, offset, parentWidth, byteWidth, length) { + var input = toUTF8Array(key); + var keysVectorOffset = indirect(dataView, offset, parentWidth) - byteWidth * 3; + var bitWidth = fromByteWidth(byteWidth); + var indirectOffset = keysVectorOffset - Number(readUInt(dataView, keysVectorOffset, bitWidth)); + var _byteWidth = Number(readUInt(dataView, keysVectorOffset + byteWidth, bitWidth)); + var low = 0; + var high = length - 1; + while(low <= high){ + var mid = high + low >> 1; + var dif = diffKeys(input, mid, dataView, indirectOffset, _byteWidth); + if (dif === 0) return mid; + if (dif < 0) { + high = mid - 1; + } else { + low = mid + 1; + } + } + return null; +} +export function diffKeys(input, index, dataView, offset, width) { + var keyOffset = offset + index * width; + var keyIndirectOffset = keyOffset - Number(readUInt(dataView, keyOffset, fromByteWidth(width))); + for(var i = 0; i < input.length; i++){ + var dif = input[i] - dataView.getUint8(keyIndirectOffset + i); + if (dif !== 0) { + return dif; + } + } + return dataView.getUint8(keyIndirectOffset + input.length) === 0 ? 0 : -1; +} +export function keyForIndex(index, dataView, offset, parentWidth, byteWidth) { + var keysVectorOffset = indirect(dataView, offset, parentWidth) - byteWidth * 3; + var bitWidth = fromByteWidth(byteWidth); + var indirectOffset = keysVectorOffset - Number(readUInt(dataView, keysVectorOffset, bitWidth)); + var _byteWidth = Number(readUInt(dataView, keysVectorOffset + byteWidth, bitWidth)); + var keyOffset = indirectOffset + index * _byteWidth; + var keyIndirectOffset = keyOffset - Number(readUInt(dataView, keyOffset, fromByteWidth(_byteWidth))); + var length = 0; + while(dataView.getUint8(keyIndirectOffset + length) !== 0){ + length++; + } + return fromUTF8Array(new Uint8Array(dataView.buffer, keyIndirectOffset, length)); +} diff --git a/crates/web-host/src/client/flexbuffers/reference.js b/crates/web-host/src/client/flexbuffers/reference.js new file mode 100644 index 00000000..4fcd3491 --- /dev/null +++ b/crates/web-host/src/client/flexbuffers/reference.js @@ -0,0 +1,295 @@ +function _class_call_check(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} +function _defineProperties(target, props) { + for(var i = 0; i < props.length; i++){ + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } +} +function _create_class(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; +} +function _define_property(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; +} +import { fromByteWidth } from './bit-width-util.js'; +import { ValueType } from './value-type.js'; +import { isNumber, isIndirectNumber, isAVector, fixedTypedVectorElementSize, isFixedTypedVector, isTypedVector, typedVectorElementType, packedType, fixedTypedVectorElementType } from './value-type-util.js'; +import { indirect, keyForIndex, keyIndex, readFloat, readInt, readUInt } from './reference-util.js'; +import { fromUTF8Array } from './flexbuffers-util.js'; +import { BitWidth } from './bit-width.js'; +export function toReference(buffer) { + var len = buffer.byteLength; + if (len < 3) { + throw "Buffer needs to be bigger than 3"; + } + var dataView = new DataView(buffer); + var byteWidth = dataView.getUint8(len - 1); + var packedType = dataView.getUint8(len - 2); + var parentWidth = fromByteWidth(byteWidth); + var offset = len - byteWidth - 2; + return new Reference(dataView, offset, parentWidth, packedType, "/"); +} +function valueForIndexWithKey(index, key, dataView, offset, parentWidth, byteWidth, length, path) { + var _indirect = indirect(dataView, offset, parentWidth); + var elementOffset = _indirect + index * byteWidth; + var packedType = dataView.getUint8(_indirect + length * byteWidth + index); + return new Reference(dataView, elementOffset, fromByteWidth(byteWidth), packedType, "".concat(path, "/").concat(key)); +} +export var Reference = /*#__PURE__*/ function() { + "use strict"; + function Reference(dataView, offset, parentWidth, packedType, path) { + _class_call_check(this, Reference); + _define_property(this, "dataView", void 0); + _define_property(this, "offset", void 0); + _define_property(this, "parentWidth", void 0); + _define_property(this, "packedType", void 0); + _define_property(this, "path", void 0); + _define_property(this, "byteWidth", void 0); + _define_property(this, "valueType", void 0); + _define_property(this, "_length", void 0); + this.dataView = dataView; + this.offset = offset; + this.parentWidth = parentWidth; + this.packedType = packedType; + this.path = path; + this._length = -1; + this.byteWidth = 1 << (packedType & 3); + this.valueType = packedType >> 2; + } + _create_class(Reference, [ + { + key: "isNull", + value: function isNull() { + return this.valueType === ValueType.NULL; + } + }, + { + key: "isNumber", + value: function isNumber1() { + return isNumber(this.valueType) || isIndirectNumber(this.valueType); + } + }, + { + key: "isFloat", + value: function isFloat() { + return ValueType.FLOAT === this.valueType || ValueType.INDIRECT_FLOAT === this.valueType; + } + }, + { + key: "isInt", + value: function isInt() { + return this.isNumber() && !this.isFloat(); + } + }, + { + key: "isString", + value: function isString() { + return ValueType.STRING === this.valueType || ValueType.KEY === this.valueType; + } + }, + { + key: "isBool", + value: function isBool() { + return ValueType.BOOL === this.valueType; + } + }, + { + key: "isBlob", + value: function isBlob() { + return ValueType.BLOB === this.valueType; + } + }, + { + key: "isVector", + value: function isVector() { + return isAVector(this.valueType); + } + }, + { + key: "isMap", + value: function isMap() { + return ValueType.MAP === this.valueType; + } + }, + { + key: "boolValue", + value: function boolValue() { + if (this.isBool()) { + return readInt(this.dataView, this.offset, this.parentWidth) > 0; + } + return null; + } + }, + { + key: "intValue", + value: function intValue() { + if (this.valueType === ValueType.INT) { + return readInt(this.dataView, this.offset, this.parentWidth); + } + if (this.valueType === ValueType.UINT) { + return readUInt(this.dataView, this.offset, this.parentWidth); + } + if (this.valueType === ValueType.INDIRECT_INT) { + return readInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth)); + } + if (this.valueType === ValueType.INDIRECT_UINT) { + return readUInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth)); + } + return null; + } + }, + { + key: "floatValue", + value: function floatValue() { + if (this.valueType === ValueType.FLOAT) { + return readFloat(this.dataView, this.offset, this.parentWidth); + } + if (this.valueType === ValueType.INDIRECT_FLOAT) { + return readFloat(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth)); + } + return null; + } + }, + { + key: "numericValue", + value: function numericValue() { + return this.floatValue() || this.intValue(); + } + }, + { + key: "stringValue", + value: function stringValue() { + if (this.valueType === ValueType.STRING || this.valueType === ValueType.KEY) { + var begin = indirect(this.dataView, this.offset, this.parentWidth); + return fromUTF8Array(new Uint8Array(this.dataView.buffer, begin, this.length())); + } + return null; + } + }, + { + key: "blobValue", + value: function blobValue() { + if (this.isBlob()) { + var begin = indirect(this.dataView, this.offset, this.parentWidth); + return new Uint8Array(this.dataView.buffer, begin, this.length()); + } + return null; + } + }, + { + key: "get", + value: function get(key) { + var length = this.length(); + if (Number.isInteger(key) && isAVector(this.valueType)) { + if (key >= length || key < 0) { + throw "Key: [".concat(key, "] is not applicable on ").concat(this.path, " of ").concat(this.valueType, " length: ").concat(length); + } + var _indirect = indirect(this.dataView, this.offset, this.parentWidth); + var elementOffset = _indirect + key * this.byteWidth; + var _packedType = this.dataView.getUint8(_indirect + length * this.byteWidth + key); + if (isTypedVector(this.valueType)) { + var _valueType = typedVectorElementType(this.valueType); + _packedType = packedType(_valueType, BitWidth.WIDTH8); + } else if (isFixedTypedVector(this.valueType)) { + var _valueType1 = fixedTypedVectorElementType(this.valueType); + _packedType = packedType(_valueType1, BitWidth.WIDTH8); + } + return new Reference(this.dataView, elementOffset, fromByteWidth(this.byteWidth), _packedType, "".concat(this.path, "[").concat(key, "]")); + } + if (typeof key === 'string') { + var index = keyIndex(key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length); + if (index !== null) { + return valueForIndexWithKey(index, key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length, this.path); + } + } + throw "Key [".concat(key, "] is not applicable on ").concat(this.path, " of ").concat(this.valueType); + } + }, + { + key: "length", + value: function length() { + var size; + if (this._length > -1) { + return this._length; + } + if (isFixedTypedVector(this.valueType)) { + this._length = fixedTypedVectorElementSize(this.valueType); + } else if (this.valueType === ValueType.BLOB || this.valueType === ValueType.MAP || isAVector(this.valueType)) { + this._length = readUInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth) - this.byteWidth, fromByteWidth(this.byteWidth)); + } else if (this.valueType === ValueType.NULL) { + this._length = 0; + } else if (this.valueType === ValueType.STRING) { + var _indirect = indirect(this.dataView, this.offset, this.parentWidth); + var sizeByteWidth = this.byteWidth; + size = readUInt(this.dataView, _indirect - sizeByteWidth, fromByteWidth(this.byteWidth)); + while(this.dataView.getInt8(_indirect + size) !== 0){ + sizeByteWidth <<= 1; + size = readUInt(this.dataView, _indirect - sizeByteWidth, fromByteWidth(this.byteWidth)); + } + this._length = size; + } else if (this.valueType === ValueType.KEY) { + var _indirect1 = indirect(this.dataView, this.offset, this.parentWidth); + size = 1; + while(this.dataView.getInt8(_indirect1 + size) !== 0){ + size++; + } + this._length = size; + } else { + this._length = 1; + } + return Number(this._length); + } + }, + { + key: "toObject", + value: function toObject() { + var length = this.length(); + if (this.isVector()) { + var result = []; + for(var i = 0; i < length; i++){ + result.push(this.get(i).toObject()); + } + return result; + } + if (this.isMap()) { + var result1 = {}; + for(var i1 = 0; i1 < length; i1++){ + var key = keyForIndex(i1, this.dataView, this.offset, this.parentWidth, this.byteWidth); + result1[key] = valueForIndexWithKey(i1, key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length, this.path).toObject(); + } + return result1; + } + if (this.isNull()) { + return null; + } + if (this.isBool()) { + return this.boolValue(); + } + if (this.isNumber()) { + return this.numericValue(); + } + return this.blobValue() || this.stringValue(); + } + } + ]); + return Reference; +}(); diff --git a/crates/web-host/src/client/flexbuffers/stack-value.js b/crates/web-host/src/client/flexbuffers/stack-value.js new file mode 100644 index 00000000..a617cc78 --- /dev/null +++ b/crates/web-host/src/client/flexbuffers/stack-value.js @@ -0,0 +1,125 @@ +function _instanceof(left, right) { + if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { + return !!right[Symbol.hasInstance](left); + } else { + return left instanceof right; + } +} +function _class_call_check(instance, Constructor) { + if (!_instanceof(instance, Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} +function _defineProperties(target, props) { + for(var i = 0; i < props.length; i++){ + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } +} +function _create_class(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; +} +function _define_property(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; +} +import { BitWidth } from './bit-width.js'; +import { paddingSize, uwidth, fromByteWidth } from './bit-width-util.js'; +import { ValueType } from './value-type.js'; +import { isInline, packedType } from './value-type-util.js'; +export var StackValue = /*#__PURE__*/ function() { + "use strict"; + function StackValue(builder, type, width) { + var value = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : null, offset = arguments.length > 4 && arguments[4] !== void 0 ? arguments[4] : 0; + _class_call_check(this, StackValue); + _define_property(this, "builder", void 0); + _define_property(this, "type", void 0); + _define_property(this, "width", void 0); + _define_property(this, "value", void 0); + _define_property(this, "offset", void 0); + this.builder = builder; + this.type = type; + this.width = width; + this.value = value; + this.offset = offset; + } + _create_class(StackValue, [ + { + key: "elementWidth", + value: function elementWidth(size, index) { + if (isInline(this.type)) return this.width; + for(var i = 0; i < 4; i++){ + var width = 1 << i; + var offsetLoc = size + paddingSize(size, width) + index * width; + var offset = offsetLoc - this.offset; + var bitWidth = uwidth(offset); + if (1 << bitWidth === width) { + return bitWidth; + } + } + throw "Element is unknown. Size: ".concat(size, " at index: ").concat(index, ". This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new"); + } + }, + { + key: "writeToBuffer", + value: function writeToBuffer(byteWidth) { + var newOffset = this.builder.computeOffset(byteWidth); + if (this.type === ValueType.FLOAT) { + if (this.width === BitWidth.WIDTH32) { + this.builder.view.setFloat32(this.builder.offset, this.value, true); + } else { + this.builder.view.setFloat64(this.builder.offset, this.value, true); + } + } else if (this.type === ValueType.INT) { + var bitWidth = fromByteWidth(byteWidth); + this.builder.pushInt(this.value, bitWidth); + } else if (this.type === ValueType.UINT) { + var bitWidth1 = fromByteWidth(byteWidth); + this.builder.pushUInt(this.value, bitWidth1); + } else if (this.type === ValueType.NULL) { + this.builder.pushInt(0, this.width); + } else if (this.type === ValueType.BOOL) { + this.builder.pushInt(this.value ? 1 : 0, this.width); + } else { + throw "Unexpected type: ".concat(this.type, ". This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new"); + } + this.offset = newOffset; + } + }, + { + key: "storedWidth", + value: function storedWidth() { + var width = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : BitWidth.WIDTH8; + return isInline(this.type) ? Math.max(width, this.width) : this.width; + } + }, + { + key: "storedPackedType", + value: function storedPackedType() { + var width = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : BitWidth.WIDTH8; + return packedType(this.type, this.storedWidth(width)); + } + }, + { + key: "isOffset", + value: function isOffset() { + return !isInline(this.type); + } + } + ]); + return StackValue; +}(); diff --git a/crates/web-host/src/client/flexbuffers/value-type-util.js b/crates/web-host/src/client/flexbuffers/value-type-util.js new file mode 100644 index 00000000..5ed05ce8 --- /dev/null +++ b/crates/web-host/src/client/flexbuffers/value-type-util.js @@ -0,0 +1,42 @@ +import { ValueType } from './value-type.js'; +export function isInline(value) { + return value === ValueType.BOOL || value <= ValueType.FLOAT; +} +export function isNumber(value) { + return value >= ValueType.INT && value <= ValueType.FLOAT; +} +export function isIndirectNumber(value) { + return value >= ValueType.INDIRECT_INT && value <= ValueType.INDIRECT_FLOAT; +} +export function isTypedVectorElement(value) { + return value === ValueType.BOOL || value >= ValueType.INT && value <= ValueType.STRING; +} +export function isTypedVector(value) { + return value === ValueType.VECTOR_BOOL || value >= ValueType.VECTOR_INT && value <= ValueType.VECTOR_STRING_DEPRECATED; +} +export function isFixedTypedVector(value) { + return value >= ValueType.VECTOR_INT2 && value <= ValueType.VECTOR_FLOAT4; +} +export function isAVector(value) { + return isTypedVector(value) || isFixedTypedVector(value) || value === ValueType.VECTOR; +} +export function toTypedVector(valueType, length) { + if (length === 0) return valueType - ValueType.INT + ValueType.VECTOR_INT; + if (length === 2) return valueType - ValueType.INT + ValueType.VECTOR_INT2; + if (length === 3) return valueType - ValueType.INT + ValueType.VECTOR_INT3; + if (length === 4) return valueType - ValueType.INT + ValueType.VECTOR_INT4; + throw "Unexpected length " + length; +} +export function typedVectorElementType(valueType) { + return valueType - ValueType.VECTOR_INT + ValueType.INT; +} +export function fixedTypedVectorElementType(valueType) { + return (valueType - ValueType.VECTOR_INT2) % 3 + ValueType.INT; +} +export function fixedTypedVectorElementSize(valueType) { + // The x / y >> 0 trick is to have an int division. Suppose to be faster than Math.floor() + return ((valueType - ValueType.VECTOR_INT2) / 3 >> 0) + 2; +} +export function packedType(valueType, bitWidth) { + return bitWidth | valueType << 2; +} diff --git a/crates/web-host/src/client/flexbuffers/value-type.js b/crates/web-host/src/client/flexbuffers/value-type.js new file mode 100644 index 00000000..c60c54c7 --- /dev/null +++ b/crates/web-host/src/client/flexbuffers/value-type.js @@ -0,0 +1,31 @@ +export var ValueType; +(function(ValueType) { + ValueType[ValueType["NULL"] = 0] = "NULL"; + ValueType[ValueType["INT"] = 1] = "INT"; + ValueType[ValueType["UINT"] = 2] = "UINT"; + ValueType[ValueType["FLOAT"] = 3] = "FLOAT"; + ValueType[ValueType["KEY"] = 4] = "KEY"; + ValueType[ValueType["STRING"] = 5] = "STRING"; + ValueType[ValueType["INDIRECT_INT"] = 6] = "INDIRECT_INT"; + ValueType[ValueType["INDIRECT_UINT"] = 7] = "INDIRECT_UINT"; + ValueType[ValueType["INDIRECT_FLOAT"] = 8] = "INDIRECT_FLOAT"; + ValueType[ValueType["MAP"] = 9] = "MAP"; + ValueType[ValueType["VECTOR"] = 10] = "VECTOR"; + ValueType[ValueType["VECTOR_INT"] = 11] = "VECTOR_INT"; + ValueType[ValueType["VECTOR_UINT"] = 12] = "VECTOR_UINT"; + ValueType[ValueType["VECTOR_FLOAT"] = 13] = "VECTOR_FLOAT"; + ValueType[ValueType["VECTOR_KEY"] = 14] = "VECTOR_KEY"; + ValueType[ValueType["VECTOR_STRING_DEPRECATED"] = 15] = "VECTOR_STRING_DEPRECATED"; + ValueType[ValueType["VECTOR_INT2"] = 16] = "VECTOR_INT2"; + ValueType[ValueType["VECTOR_UINT2"] = 17] = "VECTOR_UINT2"; + ValueType[ValueType["VECTOR_FLOAT2"] = 18] = "VECTOR_FLOAT2"; + ValueType[ValueType["VECTOR_INT3"] = 19] = "VECTOR_INT3"; + ValueType[ValueType["VECTOR_UINT3"] = 20] = "VECTOR_UINT3"; + ValueType[ValueType["VECTOR_FLOAT3"] = 21] = "VECTOR_FLOAT3"; + ValueType[ValueType["VECTOR_INT4"] = 22] = "VECTOR_INT4"; + ValueType[ValueType["VECTOR_UINT4"] = 23] = "VECTOR_UINT4"; + ValueType[ValueType["VECTOR_FLOAT4"] = 24] = "VECTOR_FLOAT4"; + ValueType[ValueType["BLOB"] = 25] = "BLOB"; + ValueType[ValueType["BOOL"] = 26] = "BOOL"; + ValueType[ValueType["VECTOR_BOOL"] = 36] = "VECTOR_BOOL"; +})(ValueType || (ValueType = {})); diff --git a/crates/web-host/src/client/mod.rs b/crates/web-host/src/client/mod.rs index b2b1b101..53c728cb 100644 --- a/crates/web-host/src/client/mod.rs +++ b/crates/web-host/src/client/mod.rs @@ -76,6 +76,20 @@ macro_rules! static_css_handler { static_html_handler!(root_handler, "root.html"); static_js_handler!(js_handler, "moor.js"); +static_js_handler!(var_handler, "var.js"); static_js_handler!(editor_handler, "editor.js"); static_js_handler!(rpc_handler, "rpc.js"); static_css_handler!(css_handler, "moor.css"); + +static_js_handler!(flexbuffers_bit_width, "flexbuffers/bit-width.js"); +static_js_handler!(flexbuffers_bit_width_util, "flexbuffers/bit-width-util.js"); +static_js_handler!(flexbuffers_builder, "flexbuffers/builder.js"); +static_js_handler!(flexbuffers_util, "flexbuffers/flexbuffers-util.js"); +static_js_handler!(flexbuffers_reference, "flexbuffers/reference.js"); +static_js_handler!(flexbuffers_reference_util, "flexbuffers/reference-util.js"); +static_js_handler!(flexbuffers_stack_value, "flexbuffers/stack-value.js"); +static_js_handler!(flexbuffers_value_type, "flexbuffers/value-type.js"); +static_js_handler!( + flexbuffers_value_type_util, + "flexbuffers/value-type-util.js" +); diff --git a/crates/web-host/src/client/root.html b/crates/web-host/src/client/root.html index eaef3542..0231a483 100644 --- a/crates/web-host/src/client/root.html +++ b/crates/web-host/src/client/root.html @@ -9,7 +9,19 @@ - + + + + + + + + + + + + + \ No newline at end of file diff --git a/crates/web-host/src/client/var.js b/crates/web-host/src/client/var.js new file mode 100644 index 00000000..e69de29b diff --git a/crates/web-host/src/host/mod.rs b/crates/web-host/src/host/mod.rs index 38a95d88..a8bd03bd 100644 --- a/crates/web-host/src/host/mod.rs +++ b/crates/web-host/src/host/mod.rs @@ -15,12 +15,11 @@ pub mod web_host; mod ws_connection; -use moor_values::var::Var; -use moor_values::var::Variant; +use moor_values::var::{v_float, v_int, v_none, v_str, Var}; +use moor_values::var::{v_listv, Variant}; use serde::Serialize; use serde_derive::Deserialize; use serde_json::{json, Number}; - pub use web_host::WebHost; pub use web_host::{ connect_auth_handler, create_auth_handler, eval_handler, properties_handler, @@ -40,6 +39,24 @@ struct Error { error_msg: String, } +// JSON representation for Var is complicated by the following things: +// object references (#1234 or $name) +// maps (new in Stunt, etc.) which do not correspond to JSON objects because they can have +// non-string keys +// +// In order to translate, a special JSON structure is used which adds a unique header to each +// document containing a tables of object references and map keys. +// The entries are UUIDs, which are used to reference the objects in the document. +// The structure of this is the following: +// +// { +// "reference-table": { +// "oids" : { "" : 1234, .. } +// "sysobjrefs" : { "" : "name[.name]", .. } +// "MAP-": , +// } +// } + pub fn var_as_json(v: &Var) -> serde_json::Value { match v.variant() { Variant::None => serde_json::Value::Null, @@ -69,6 +86,34 @@ pub fn var_as_json(v: &Var) -> serde_json::Value { } } +pub fn json_as_var(j: &serde_json::Value) -> Option { + match j { + serde_json::Value::Null => Some(v_none()), + serde_json::Value::String(s) => Some(v_str(&s)), + serde_json::Value::Number(n) => Some(if n.is_i64() { + v_int(n.as_i64().unwrap()) + } else { + v_float(n.as_f64().unwrap()) + }), + serde_json::Value::Object(_o) => { + // Object references in JSON are encoded as one of: + // { "oid": 1234 } + // { "sysobj": "name[.name]" } + // { "match": "name[.name]" } + // Not valid. + unimplemented!("Object references in JSON"); + } + serde_json::Value::Array(a) => { + let mut v = Vec::new(); + for e in a.iter() { + v.push(json_as_var(e)?); + } + Some(v_listv(v)) + } + _ => None, + } +} + pub fn serialize_var(v: &Var, s: S) -> Result where S: serde::Serializer, diff --git a/crates/web-host/src/host/web_host.rs b/crates/web-host/src/host/web_host.rs index f10d46ba..4d4302bd 100644 --- a/crates/web-host/src/host/web_host.rs +++ b/crates/web-host/src/host/web_host.rs @@ -12,8 +12,8 @@ // this program. If not, see . // -use crate::host::var_as_json; use crate::host::ws_connection::WebSocketConnection; +use crate::host::{json_as_var, var_as_json}; use axum::body::{Body, Bytes}; use axum::extract::{ConnectInfo, Path, State, WebSocketUpgrade}; use axum::http::{HeaderMap, HeaderValue, StatusCode}; @@ -449,6 +449,80 @@ pub async fn eval_handler( response } +// RpcRequest::InvokeVerb(ClientToken, AuthToken, ObjectRef, Symbol, Vec) +// POST /verb/invoke/{object}/{verb}, body: {args} as JSON list +pub async fn verb_invoke_handler( + State(host): State, + ConnectInfo(addr): ConnectInfo, + header_map: HeaderMap, + Path((object, verb)): Path<(String, String)>, + Json(args): Json>, +) -> Response { + let (auth_token, client_id, client_token, mut rpc_client) = + match auth_auth(host.clone(), addr, header_map.clone()).await { + Ok(connection_details) => connection_details, + Err(status) => return status.into_response(), + }; + + let Some(object) = ObjectRef::parse_curie(&object) else { + return StatusCode::BAD_REQUEST.into_response(); + }; + + let verb = Symbol::mk(&verb); + + let mut varargs = vec![]; + for arg in args { + if let Some(var) = json_as_var(&arg) { + varargs.push(var); + } else { + return StatusCode::BAD_REQUEST.into_response(); + } + } + + let response = match rpc_call( + client_id, + &mut rpc_client, + RpcRequest::InvokeVerb( + client_token.clone(), + auth_token.clone(), + object, + verb, + varargs, + ), + ) + .await + { + Ok(RpcResponse::InvokeResult(Ok(value))) => { + debug!("Invoke verb result: {:?}", value); + let result_json = var_as_json(&value); + Json(json!({ + "result": result_json + })) + .into_response() + } + Ok(RpcResponse::InvokeResult(Err(e))) => { + error!("Invoke verb error: {:?}", e); + Json(json!({ + "error": e.to_string() + })) + .into_response() + } + Ok(r) => { + error!("Unexpected response from RPC server: {:?}", r); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + } + Err(status) => status.into_response(), + }; + + // We're done with this RPC connection, so we detach it. + let _ = rpc_client + .make_rpc_call(client_id, RpcRequest::Detach(client_token.clone())) + .await + .expect("Unable to send detach to RPC server"); + + response +} + pub async fn verb_program_handler( State(host): State, ConnectInfo(addr): ConnectInfo, diff --git a/crates/web-host/src/main.rs b/crates/web-host/src/main.rs index 85ecd0eb..37d24a12 100644 --- a/crates/web-host/src/main.rs +++ b/crates/web-host/src/main.rs @@ -69,6 +69,40 @@ fn mk_routes(web_host: WebHost) -> eyre::Result { .route("/rpc.js", get(client::rpc_handler)) .route("/editor.js", get(editor_handler)) .route("/moor.css", get(client::css_handler)) + .route("/var.js", get(client::var_handler)) + .route( + "/flexbuffers/bit-width.js", + get(client::flexbuffers_bit_width), + ) + .route( + "/flexbuffers/bit-width-util.js", + get(client::flexbuffers_bit_width_util), + ) + .route("/flexbuffers/builder.js", get(client::flexbuffers_builder)) + .route( + "/flexbuffers/flexbuffers-util.js", + get(client::flexbuffers_util), + ) + .route( + "/flexbuffers/reference.js", + get(client::flexbuffers_reference), + ) + .route( + "/flexbuffers/reference-util.js", + get(client::flexbuffers_reference_util), + ) + .route( + "/flexbuffers/stack-value.js", + get(client::flexbuffers_stack_value), + ) + .route( + "/flexbuffers/value-type.js", + get(client::flexbuffers_value_type), + ) + .route( + "/flexbuffers/value-type-util.js", + get(client::flexbuffers_value_type_util), + ) .route("/auth/connect", post(host::connect_auth_handler)) .route("/auth/create", post(host::create_auth_handler)) .route("/welcome", get(host::welcome_message_handler))