diff options
| author | Přemysl Eric Janouch <p@janouch.name> | 2022-09-15 22:45:14 +0200 | 
|---|---|---|
| committer | Přemysl Eric Janouch <p@janouch.name> | 2022-09-16 00:51:11 +0200 | 
| commit | 6f39aa66156f53b27e3b9cfe8457fc2f64129e56 (patch) | |
| tree | 0119fd13384bdcdffacfb9d05e10616e6342ca15 /xC-gen-proto-js.awk | |
| parent | e87cc90b5e20ba90d4f5c9ea349d9cf41b5ae58c (diff) | |
| download | xK-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.awk | 223 | 
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] +} | 
