summaryrefslogtreecommitdiff
path: root/xC-gen-proto-js.awk
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2022-09-15 22:45:14 +0200
committerPřemysl Eric Janouch <p@janouch.name>2022-09-16 00:51:11 +0200
commit6f39aa66156f53b27e3b9cfe8457fc2f64129e56 (patch)
tree0119fd13384bdcdffacfb9d05e10616e6342ca15 /xC-gen-proto-js.awk
parente87cc90b5e20ba90d4f5c9ea349d9cf41b5ae58c (diff)
downloadxK-6f39aa66156f53b27e3b9cfe8457fc2f64129e56.tar.gz
xK-6f39aa66156f53b27e3b9cfe8457fc2f64129e56.tar.xz
xK-6f39aa66156f53b27e3b9cfe8457fc2f64129e56.zip
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.
Diffstat (limited to 'xC-gen-proto-js.awk')
-rw-r--r--xC-gen-proto-js.awk223
1 files changed, 223 insertions, 0 deletions
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 <p@janouch.name>
+# 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]
+}