git » sdk » commit b4d815e

Speed up stanza serialization

author Stephen Paul Weber
2025-10-10 03:05:20 UTC
committer Stephen Paul Weber
2025-10-10 03:05:20 UTC
parent b6f8a669851159b5e0c80e63e4b50ea193b93065

Speed up stanza serialization

borogove/Stanza.hx +35 -10
borogove/streams/XmppJsStream.hx +2 -2
borogove/streams/XmppStropheStream.hx +22 -2

diff --git a/borogove/Stanza.hx b/borogove/Stanza.hx
index 91e68be..52bc9c6 100644
--- a/borogove/Stanza.hx
+++ b/borogove/Stanza.hx
@@ -3,7 +3,9 @@ package borogove;
 import haxe.DynamicAccess;
 import haxe.Exception;
 import haxe.ds.StringMap;
+#if (!cpp && !js)
 import Xml;
+#end
 
 enum Node {
 	Element(stanza:Stanza);
@@ -81,11 +83,12 @@ class StanzaError {
 
 @:expose
 class Stanza implements NodeInterface {
-	public var name(default, null):String = null;
-	public var attr(default, null):DynamicAccess<String> = {};
+	public final name:String = null;
+	public final attr:DynamicAccess<String> = {};
 	public var children(default, null):Array<Node> = [];
 	private var last_added(null, null):Stanza;
 	private var last_added_stack(null, null):Array<Stanza> = [];
+	private var serialized: Null<String> = null;
 
 	public function new(name:String, ?attr:DynamicAccess<String>) {
 		this.name = name;
@@ -96,17 +99,23 @@ class Stanza implements NodeInterface {
 	};
 
 	public function serialize():String {
-		var el = Xml.createElement(name);
+		#if cpp
+		return (serialized = borogove.streams.XmppStropheStream.serializeStanza(this));
+		#elseif js
+		final el = borogove.streams.XmppJsStream.convertFromStanza(this);
+		return (serialized = el.toString());
+		#else
+		final el = Xml.createElement(name);
 		for (attr_k in this.attr.keys()) {
 			el.set(attr_k, this.attr.get(attr_k));
 		}
 
 		if (this.children.length == 0) {
-			return el.toString();
+			return (serialized = el.toString());
 		}
-		var serialized = el.toString();
-		var buffer = new StringBuf();
-		buffer.addSub(serialized, 0, serialized.length-2);
+		final start = el.toString();
+		final buffer = new StringBuf();
+		buffer.addSub(start, 0, start.length-2);
 		buffer.add(">");
 		for (child in children) {
 			buffer.add(switch (child) {
@@ -117,7 +126,8 @@ class Stanza implements NodeInterface {
 		buffer.add("</");
 		buffer.add(name);
 		buffer.add(">");
-		return buffer.toString();
+		return (serialized = buffer.toString());
+		#end
 	}
 
 	public function toString():String {
@@ -126,12 +136,17 @@ class Stanza implements NodeInterface {
 
 	public static function parse(s:String):Stanza {
 		#if cpp
-		return borogove.streams.XmppStropheStream.parseStanza(s);
+		final stanza = borogove.streams.XmppStropheStream.parseStanza(s);
+		#elseif js
+		final stanza = borogove.streams.XmppJsStream.parseStanza(s);
 		#else
-		return fromXml(Xml.parse(s));
+		final stanza = fromXml(Xml.parse(s));
 		#end
+		stanza.serialized = s;
+		return stanza;
 	}
 
+	#if (!cpp && !js)
 	@:allow(borogove)
 	@:allow(test)
 	private static function fromXml(el:Xml):Stanza {
@@ -155,8 +170,10 @@ class Stanza implements NodeInterface {
 		}
 		return stanza;
 	}
+	#end
 
 	public function tag(name:String, ?attr:DynamicAccess<String>) {
+		serialized = null;
 		var child = new Stanza(name, attr);
 		this.last_added.addDirectChild(Element(child));
 		this.last_added_stack.push(this.last_added);
@@ -165,11 +182,13 @@ class Stanza implements NodeInterface {
 	}
 
 	public function text(content:String) {
+		serialized = null;
 		this.last_added.addDirectChild(CData(new TextNode(content)));
 		return this;
 	}
 
 	public function textTag(tagName:String, textContent:String, ?attr:DynamicAccess<String>) {
+		serialized = null;
 		this.last_added.addDirectChild(Element(new Stanza(tagName, attr ?? {}).text(textContent)));
 		return this;
 	}
@@ -188,6 +207,7 @@ class Stanza implements NodeInterface {
 
 	@:allow(borogove)
 	private function addChildren(children:Iterable<Stanza>) {
+		serialized = null;
 		for (child in children) {
 			addChild(child);
 		}
@@ -196,6 +216,7 @@ class Stanza implements NodeInterface {
 
 	@:allow(borogove)
 	private function addChildNodes(children:Iterable<Node>) {
+		serialized = null;
 		for (child in children) {
 			addDirectChild(child);
 		}
@@ -203,11 +224,13 @@ class Stanza implements NodeInterface {
 	}
 
 	public function addChild(stanza:Stanza) {
+		serialized = null;
 		this.last_added.children.push(Element(stanza));
 		return this;
 	}
 
 	public function addDirectChild(child:Node) {
+		serialized = null;
 		this.children.push(child);
 		return this;
 	}
@@ -220,6 +243,7 @@ class Stanza implements NodeInterface {
 				case CData(c): CData(c.clone());
 			});
 		}
+		clone.serialized = serialized;
 		return clone;
 	}
 
@@ -381,6 +405,7 @@ class Stanza implements NodeInterface {
 	}
 
 	public function removeChildren(?name: String, ?xmlns_:String):Void {
+		serialized = null;
 		final xmlns = xmlns_??attr.get("xmlns");
 		children = children.filter((child:Node) -> {
 			switch(child) {
diff --git a/borogove/streams/XmppJsStream.hx b/borogove/streams/XmppJsStream.hx
index 361a777..4befcce 100644
--- a/borogove/streams/XmppJsStream.hx
+++ b/borogove/streams/XmppJsStream.hx
@@ -314,7 +314,7 @@ class XmppJsStream extends GenericStream {
 		client.stop();
 	}
 
-	private function convertFromStanza(el:Stanza):XmppJsXml {
+	public static function convertFromStanza(el:Stanza):XmppJsXml {
 		var xml = new XmppJsXml(el.name, el.attr);
 		if(el.children.length > 0) {
 			for(child in el.children) {
@@ -339,7 +339,7 @@ class XmppJsStream extends GenericStream {
 		return stanza;
 	}
 
-	public static function parse(input:String):Stanza {
+	public static function parseStanza(input:String):Stanza {
 		return convertToStanza(XmppJsLtx.parse(input));
 	}
 
diff --git a/borogove/streams/XmppStropheStream.hx b/borogove/streams/XmppStropheStream.hx
index ad123b0..37ef314 100644
--- a/borogove/streams/XmppStropheStream.hx
+++ b/borogove/streams/XmppStropheStream.hx
@@ -10,6 +10,7 @@ import cpp.Function;
 import cpp.NativeArray;
 import cpp.NativeGc;
 import cpp.NativeString;
+import cpp.Pointer;
 import cpp.RawConstPointer;
 import cpp.RawPointer;
 
@@ -42,6 +43,9 @@ extern class StropheCtx {
 	@:native("xmpp_ctx_free")
 	static function free(ctx:StropheCtx):Void;
 
+	@:native("xmpp_free")
+	static function release(ctx:StropheCtx, p:RawPointer<Void>):Void;
+
 	@:native("xmpp_initialize")
 	static function initialize():Void;
 
@@ -163,6 +167,9 @@ extern class StropheStanza {
 	@:native("xmpp_stanza_set_text")
 	static function set_text(stanza:StropheStanza, text:ConstPointer<Char>):Void;
 
+	@:native("xmpp_stanza_to_text")
+	static function to_text(stanza:StropheStanza, buf:Pointer<RawPointer<Char>>, buflen: Pointer<cpp.SizeT>):Int;
+
 	@:native("xmpp_stanza_release")
 	static function release(stanza:StropheStanza):Void;
 }
@@ -352,7 +359,20 @@ class XmppStropheStream extends GenericStream {
 		return stanza;
 	}
 
-	public static function convertToStanza(el:StropheStanza, dummy:RawPointer<Void>):Stanza {
+	public static function serializeStanza(stanza:Stanza):String {
+		final sstanza = convertFromStanza(stanza);
+		var buf: RawPointer<Char> = null;
+		var bufsize: cpp.SizeT = -1;
+		final err = StropheStanza.to_text(sstanza, Pointer.addressOf(buf), Pointer.addressOf(bufsize));
+		StropheStanza.release(sstanza);
+		if (err != 0) return null;
+		final bufPtr = ConstPointer.fromRaw(buf);
+		final s =  NativeString.fromPointer(bufPtr);
+		StropheCtx.release(ctx, bufPtr.rawCast());
+		return s;
+	}
+
+	private static function convertToStanza(el:StropheStanza, dummy:RawPointer<Void>):Stanza {
 		var name = StropheStanza.get_name(el);
 		var attrlen = StropheStanza.get_attribute_count(el);
 		var attrsraw: RawPointer<cpp.Void> = NativeGc.allocGcBytesRaw(attrlen * 2 * untyped __cpp__("sizeof(char*)"), false);
@@ -380,7 +400,7 @@ class XmppStropheStream extends GenericStream {
 		return stanza;
 	}
 
-	private function convertFromStanza(el:Stanza):StropheStanza {
+	private static function convertFromStanza(el:Stanza):StropheStanza {
 		var xml = StropheStanza.create(ctx);
 		StropheStanza.set_name(xml, NativeString.c_str(el.name));
 		for (attr in el.attr.keyValueIterator()) {