From 6f39aa66156f53b27e3b9cfe8457fc2f64129e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Thu, 15 Sep 2022 22:45:14 +0200 Subject: xP: use the binary protocol for incoming events And batch event messages together as much as possible. JSON has proven itself to be really slow (for example, encoding/json.Marshaler is a slow interface), and browsers have significant overhead per WS message. Commands are still sent as JSON, sending them in binary would be a laborious rewrite without measurable merits. The xP server now only prints debug output when requested, because that was another source of major slowdowns. --- xC-gen-proto-js.awk | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 xC-gen-proto-js.awk (limited to 'xC-gen-proto-js.awk') diff --git a/xC-gen-proto-js.awk b/xC-gen-proto-js.awk new file mode 100644 index 0000000..752fd18 --- /dev/null +++ b/xC-gen-proto-js.awk @@ -0,0 +1,223 @@ +# xC-gen-proto-js.awk: Javascript backend for xC-gen-proto.awk. +# +# Copyright (c) 2022, Přemysl Eric Janouch +# SPDX-License-Identifier: 0BSD +# +# This backend is currently for decoding the binary format only. +# (JSON is way too expensive to process and transfer.) +# +# Import the resulting script as a Javascript module. + +function define_internal(name) { + Types[name] = "internal" +} + +function define_sint(size, shortname) { + shortname = "i" size + define_internal(shortname) + CodegenDeserialize[shortname] = "\t%s = r." shortname "()\n" + + print "" + print "\t" shortname "() {" + if (size == "64") { + # XXX: 2^53 - 1 must be enough for anyone. BigInts are a PITA. + print "\t\tconst " shortname \ + " = Number(this.getBigInt" size "(this.offset))" + } else { + print "\t\tconst " shortname " = this.getInt" size "(this.offset)" + } + print "\t\tthis.offset += " (size / 8) + print "\t\treturn " shortname + print "\t}" +} + +function define_uint(size, shortname) { + shortname = "u" size + define_internal(shortname) + CodegenDeserialize[shortname] = "\t%s = r." shortname "()\n" + + print "" + print "\t" shortname "() {" + if (size == "64") { + # XXX: 2^53 - 1 must be enough for anyone. BigInts are a PITA. + print "\t\tconst " shortname \ + " = Number(this.getBigUint" size "(this.offset))" + } else { + print "\t\tconst " shortname " = this.getUint" size "(this.offset)" + } + print "\t\tthis.offset += " (size / 8) + print "\t\treturn " shortname + print "\t}" +} + +function codegen_begin() { + print "export class Reader extends DataView {" + print "\tconstructor() {" + print "\t\tsuper(...arguments)" + print "\t\tthis.offset = 0" + print "\t\tthis.decoder = new TextDecoder('utf-8', {fatal: true})" + print "\t}" + print "" + print "\tget empty() {" + print "\t\treturn this.byteLength <= this.offset" + print "\t}" + print "" + print "\trequire(len) {" + print "\t\tif (this.byteLength - this.offset < len)" + print "\t\t\tthrow `Premature end of data`" + print "\t\treturn this.byteOffset + this.offset" + print "\t}" + + define_internal("string") + CodegenDeserialize["string"] = "\t%s = r.string()\n" + + print "" + print "\tstring() {" + print "\t\tconst len = this.getUint32(this.offset)" + print "\t\tthis.offset += 4" + print "\t\tconst array = new Uint8Array(" + print "\t\t\tthis.buffer, this.require(len), len)" + print "\t\tthis.offset += len" + print "\t\treturn this.decoder.decode(array)" + print "\t}" + + define_internal("bool") + CodegenDeserialize["bool"] = "\t%s = r.bool()\n" + + print "" + print "\tbool() {" + print "\t\tconst u8 = this.getUint8(this.offset)" + print "\t\tthis.offset += 1" + print "\t\treturn u8 != 0" + print "\t}" + + define_sint("8") + define_sint("16") + define_sint("32") + define_sint("64") + define_uint("8") + define_uint("16") + define_uint("32") + define_uint("64") + + print "}" +} + +function codegen_constant(name, value) { + print "" + print "export const " decapitalize(snaketocamel(name)) " = " value +} + +function codegen_enum_value(name, subname, value, cg) { + append(cg, "fields", "\t" snaketocamel(subname) ": " value ",\n") +} + +function codegen_enum(name, cg) { + print "" + print "export const " name " = Object.freeze({" + print cg["fields"] "})" + + CodegenDeserialize[name] = "\t%s = r.i8()\n" + for (i in cg) + delete cg[i] +} + +function codegen_struct_field(d, cg, camel, f, deserialize) { + camel = decapitalize(snaketocamel(d["name"])) + f = "s." camel + append(cg, "fields", "\t" camel "\n") + + deserialize = CodegenDeserialize[d["type"]] + if (!d["isarray"]) { + append(cg, "deserialize", sprintf(deserialize, f)) + return + } + + append(cg, "deserialize", + "\t{\n" \ + indent(sprintf(CodegenDeserialize["u32"], "const len"))) + if (d["type"] == "u8") { + append(cg, "deserialize", + "\t\t" f " = new Uint8Array(\n" \ + "\t\t\tr.buffer, r.require(len), len)\n" \ + "\t\tr.offset += len\n" \ + "\t}\n") + return + } + if (d["type"] == "i8") { + append(cg, "deserialize", + "\t\t" f " = new Int8Array(\n" \ + "\t\t\tr.buffer, r.require(len), len)\n" \ + "\t\tr.offset += len\n" \ + "\t}\n") + return + } + + append(cg, "deserialize", + "\t\t" f " = new Array(len)\n" \ + "\t}\n" \ + "\tfor (let i = 0; i < " f ".length; i++)\n" \ + indent(sprintf(deserialize, f "[i]"))) +} + +function codegen_struct_tag(d, cg) { + append(cg, "fields", "\t" decapitalize(snaketocamel(d["name"])) "\n") + # Do not deserialize here, that is already done by the containing union. +} + +function codegen_struct(name, cg) { + print "" + print "export class " name " {" + print cg["fields"] cg["methods"] + print "\tstatic deserialize(r) {" + print "\t\tconst s = new " name "()" + print indent(cg["deserialize"]) "\t\treturn s" + print "\t}" + print "}" + + CodegenDeserialize[name] = "\t%s = " name ".deserialize(r)\n" + for (i in cg) + delete cg[i] +} + +function codegen_union_tag(d, cg) { + cg["tagtype"] = d["type"] + cg["tagname"] = d["name"] +} + +function codegen_union_struct(name, casename, cg, scg, structname) { + append(scg, "methods", + "\n" \ + "\tconstructor() {\n" \ + "\t\tthis." decapitalize(snaketocamel(cg["tagname"])) \ + " = " cg["tagtype"] "." snaketocamel(casename) "\n" \ + "\t}\n") + + # And thus not all generated structs are present in Types. + structname = name snaketocamel(casename) + codegen_struct(structname, scg) + + append(cg, "deserialize", + "\tcase " cg["tagtype"] "." snaketocamel(casename) ":\n" \ + "\t{\n" \ + indent(sprintf(CodegenDeserialize[structname], "const s")) \ + "\t\treturn s\n" \ + "\t}\n") +} + +function codegen_union(name, cg, tagvar) { + tagvar = decapitalize(snaketocamel(cg["tagname"])) + + print "" + print "export function deserialize" name "(r) {" + print sprintf(CodegenDeserialize[cg["tagtype"]], "const " tagvar) \ + "\tswitch (" tagvar ") {" + print cg["deserialize"] "\tdefault:" + print "\t\tthrow `Unknown " cg["tagtype"] " (${tagvar})`" + print "\t}" + print "}" + + CodegenDeserialize[name] = "\t%s = deserialize" name "(r)\n" + for (i in cg) + delete cg[i] +} -- cgit v1.2.3