From 4dfe2e6e2f4badbaa79158fd318c31890dcd20be Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 28 Mar 2019 16:03:42 +0100 Subject: [PATCH] broadway: Initial restructuring of node tree diffing This goes back to a very naive diff, but that reuses nodes from previous frames using the node id. This will be a bettter base to start from. --- gdk/broadway/broadway-output.c | 106 ++++++++++++------- gdk/broadway/broadway-output.h | 3 +- gdk/broadway/broadway-protocol.h | 15 +-- gdk/broadway/broadway-server.c | 46 ++++++--- gdk/broadway/broadway-server.h | 8 ++ gdk/broadway/broadway.js | 169 +++++++++++++++++-------------- 6 files changed, 211 insertions(+), 136 deletions(-) diff --git a/gdk/broadway/broadway-output.c b/gdk/broadway/broadway-output.c index 6ee3229933..479a7a48da 100644 --- a/gdk/broadway/broadway-output.c +++ b/gdk/broadway/broadway-output.c @@ -326,6 +326,16 @@ append_type (BroadwayOutput *output, guint32 type, BroadwayNode *node) append_uint32 (output, type); } +static BroadwayNode * +lookup_old_node (GHashTable *old_node_lookup, + guint32 id) +{ + if (old_node_lookup) + return g_hash_table_lookup (old_node_lookup, GINT_TO_POINTER (id)); + + return NULL; +} + /*********************************** * This outputs the tree to the client, while at the same time diffing @@ -347,59 +357,83 @@ append_type (BroadwayOutput *output, guint32 type, BroadwayNode *node) static void append_node (BroadwayOutput *output, BroadwayNode *node, - BroadwayNode *old_node, - gboolean all_parents_are_kept) + GHashTable *old_node_lookup) { guint32 i; + BroadwayNode *reused_node; append_node_depth++; - if (old_node != NULL && broadway_node_equal (node, old_node)) + reused_node = lookup_old_node (old_node_lookup, node->id); + if (reused_node) { - if (broadway_node_deep_equal (node, old_node)) - { - append_type (output, BROADWAY_NODE_KEEP_ALL, node); - goto out; - } - - if (all_parents_are_kept) - { - append_type (output, BROADWAY_NODE_KEEP_THIS, node); - append_uint32 (output, node->n_children); - for (i = 0; i < node->n_children; i++) - append_node (output, node->children[i], - i < old_node->n_children ? old_node->children[i] : NULL, - TRUE); - - goto out; - } + append_type (output, BROADWAY_NODE_REUSE, node); + append_uint32 (output, node->id); + } + else + { + append_type (output, node->type, node); + append_uint32 (output, node->id); + for (i = 0; i < node->n_data; i++) + append_uint32 (output, node->data[i]); + for (i = 0; i < node->n_children; i++) + append_node (output, + node->children[i], + old_node_lookup); } - append_type (output, node->type, node); - for (i = 0; i < node->n_data; i++) - append_uint32 (output, node->data[i]); - for (i = 0; i < node->n_children; i++) - append_node (output, - node->children[i], - (old_node != NULL && i < old_node->n_children) ? old_node->children[i] : NULL, - FALSE); - - out: append_node_depth--; } + +static void +append_node_ops (BroadwayOutput *output, + BroadwayNode *node, + BroadwayNode *old_node, + GHashTable *old_node_lookup) +{ + GPtrArray *to_remove = g_ptr_array_new (); + guint32 i; + + if (old_node != NULL) + g_ptr_array_add (to_remove, old_node); + + append_uint32 (output, BROADWAY_NODE_OP_APPEND_NODE); + append_uint32 (output, 0); /* Parent */ + + append_node(output, node, old_node_lookup); + + for (i = 0; i < to_remove->len; i++) + { + BroadwayNode *removed_node = g_ptr_array_index (to_remove, i); + if (!removed_node->used) + { + // TODO: Use an array of nodes instead + append_uint32 (output, BROADWAY_NODE_OP_REMOVE_NODE); + append_uint32 (output, old_node->id); + } + } + g_ptr_array_unref (to_remove); +} + + void broadway_output_surface_set_nodes (BroadwayOutput *output, int id, BroadwayNode *root, - BroadwayNode *old_root) + BroadwayNode *old_root, + GHashTable *old_node_lookup) { gsize size_pos, start, end; - /* Early return if nothing changed */ - if (old_root != NULL && - broadway_node_deep_equal (root, old_root)) - return; + /* Mark old nodes as unused, so we can use them once */ + if (old_root) + broadway_node_mark_deep_used (old_root, FALSE); + /* Mark new nodes as used, causing any shared nodes to be marked as + unused. This way we won't accidentally reuse them for something + else. However, this means that is safe to reuse nodes marked as + used for the specific case of where the id matches. */ + broadway_node_mark_deep_used (root, TRUE); write_header (output, BROADWAY_OP_SET_NODES); @@ -412,7 +446,7 @@ broadway_output_surface_set_nodes (BroadwayOutput *output, #ifdef DEBUG_NODE_SENDING g_print ("====== node tree for %d =======\n", id); #endif - append_node (output, root, old_root, TRUE); + append_node_ops (output, root, old_root, old_node_lookup); end = output->buf->len; patch_uint32 (output, (end - start) / 4, size_pos); } diff --git a/gdk/broadway/broadway-output.h b/gdk/broadway/broadway-output.h index 7270f188cc..036aa05c37 100644 --- a/gdk/broadway/broadway-output.h +++ b/gdk/broadway/broadway-output.h @@ -60,7 +60,8 @@ void broadway_output_set_transient_for (BroadwayOutput *output, void broadway_output_surface_set_nodes (BroadwayOutput *output, int id, BroadwayNode *root, - BroadwayNode *old_root); + BroadwayNode *old_root, + GHashTable *old_node_lookup); void broadway_output_upload_texture (BroadwayOutput *output, guint32 id, GBytes *texture); diff --git a/gdk/broadway/broadway-protocol.h b/gdk/broadway/broadway-protocol.h index a78d09080f..1ea5ff9766 100644 --- a/gdk/broadway/broadway-protocol.h +++ b/gdk/broadway/broadway-protocol.h @@ -20,13 +20,16 @@ typedef enum { /* Sync changes with broadway.js */ BROADWAY_NODE_SHADOW = 8, BROADWAY_NODE_OPACITY = 9, BROADWAY_NODE_CLIP = 10, - BROADWAY_NODE_KEEP_ALL = 11, - BROADWAY_NODE_KEEP_THIS = 12, - BROADWAY_NODE_TRANSFORM = 13, - BROADWAY_NODE_DEBUG = 14, - BROADWAY_NODE_REUSE = 15, + BROADWAY_NODE_TRANSFORM = 11, + BROADWAY_NODE_DEBUG = 12, + BROADWAY_NODE_REUSE = 13, } BroadwayNodeType; +typedef enum { /* Sync changes with broadway.js */ + BROADWAY_NODE_OP_APPEND_NODE = 0, + BROADWAY_NODE_OP_REMOVE_NODE = 1, +} BroadwayNodeOpType; + static const char *broadway_node_type_names[] G_GNUC_UNUSED = { "TEXTURE", "CONTAINER", @@ -39,8 +42,6 @@ static const char *broadway_node_type_names[] G_GNUC_UNUSED = { "SHADOW", "OPACITY", "CLIP", - "KEEP_ALL", - "KEEP_THIS", "TRANSFORM", "DEBUG", "REUSE", diff --git a/gdk/broadway/broadway-server.c b/gdk/broadway/broadway-server.c index 70a236ef46..0d789a660d 100644 --- a/gdk/broadway/broadway-server.c +++ b/gdk/broadway/broadway-server.c @@ -183,6 +183,10 @@ broadway_node_equal (BroadwayNode *a, { int i; + // Early fast return for reused nodes + if (a == b) + return TRUE; + if (a->type != b->type) return FALSE; @@ -207,6 +211,10 @@ broadway_node_deep_equal (BroadwayNode *a, { int i; + // Early fast return for reused nodes + if (a == b) + return TRUE; + if (a->hash != b->hash) return FALSE; @@ -224,6 +232,24 @@ broadway_node_deep_equal (BroadwayNode *a, } +void +broadway_node_mark_deep_used (BroadwayNode *node, + gboolean used) +{ + node->used = used; + for (int i = 0; i < node->n_children; i++) + broadway_node_mark_deep_used (node->children[i], used); +} + +void +broadway_node_add_to_lookup (BroadwayNode *node, + GHashTable *node_lookup) +{ + g_hash_table_insert (node_lookup, GINT_TO_POINTER(node->id), node); + for (int i = 0; i < node->n_children; i++) + broadway_node_add_to_lookup (node->children[i], node_lookup); +} + static void broadway_server_init (BroadwayServer *server) { @@ -1784,17 +1810,6 @@ decode_nodes (BroadwayServer *server, return node; } -static void -init_node_lookup (BroadwaySurface *surface, - BroadwayNode *node) -{ - int i; - - g_hash_table_insert (surface->node_lookup, GINT_TO_POINTER(node->id), node); - for (i = 0; i < node->n_children; i++) - init_node_lookup (surface, node->children[i]); -} - /* passes ownership of nodes */ void broadway_server_surface_update_nodes (BroadwayServer *server, @@ -1816,7 +1831,8 @@ broadway_server_surface_update_nodes (BroadwayServer *server, if (server->output != NULL) broadway_output_surface_set_nodes (server->output, surface->id, root, - surface->nodes); + surface->nodes, + surface->node_lookup); if (surface->nodes) broadway_node_unref (server, surface->nodes); @@ -1824,8 +1840,7 @@ broadway_server_surface_update_nodes (BroadwayServer *server, surface->nodes = root; g_hash_table_remove_all (surface->node_lookup); - - init_node_lookup (surface, surface->nodes); + broadway_node_add_to_lookup (root, surface->node_lookup); } guint32 @@ -2097,7 +2112,8 @@ broadway_server_resync_surfaces (BroadwayServer *server) if (surface->nodes) broadway_output_surface_set_nodes (server->output, surface->id, - surface->nodes, NULL); + surface->nodes, + NULL, NULL); if (surface->visible) broadway_output_show_surface (server->output, surface->id); diff --git a/gdk/broadway/broadway-server.h b/gdk/broadway/broadway-server.h index 1e125bd065..a5624ff6fa 100644 --- a/gdk/broadway/broadway-server.h +++ b/gdk/broadway/broadway-server.h @@ -29,6 +29,10 @@ struct _BroadwayNode { guint32 n_children; BroadwayNode **children; guint32 texture_id; + + /* Scratch stuff used during diff */ + gboolean used; + guint32 n_data; guint32 data[1]; }; @@ -37,6 +41,10 @@ gboolean broadway_node_equal (BroadwayNode * BroadwayNode *b); gboolean broadway_node_deep_equal (BroadwayNode *a, BroadwayNode *b); +void broadway_node_mark_deep_used (BroadwayNode *node, + gboolean used); +void broadway_node_add_to_lookup (BroadwayNode *node, + GHashTable *node_lookup); BroadwayServer *broadway_server_new (char *address, int port, const char *ssl_cert, diff --git a/gdk/broadway/broadway.js b/gdk/broadway/broadway.js index 96ed67ce4f..f5ccd78740 100644 --- a/gdk/broadway/broadway.js +++ b/gdk/broadway/broadway.js @@ -11,11 +11,12 @@ const BROADWAY_NODE_LINEAR_GRADIENT = 7; const BROADWAY_NODE_SHADOW = 8; const BROADWAY_NODE_OPACITY = 9; const BROADWAY_NODE_CLIP = 10; -const BROADWAY_NODE_KEEP_ALL = 11; -const BROADWAY_NODE_KEEP_THIS = 12; -const BROADWAY_NODE_TRANSFORM = 13; -const BROADWAY_NODE_DEBUG = 14; -const BROADWAY_NODE_REUSE = 15; +const BROADWAY_NODE_TRANSFORM = 11; +const BROADWAY_NODE_DEBUG = 12; +const BROADWAY_NODE_REUSE = 13; + +const BROADWAY_NODE_OP_APPEND_NODE = 0; +const BROADWAY_NODE_OP_REMOVE_NODE = 1; const BROADWAY_OP_GRAB_POINTER = 'g'; const BROADWAY_OP_UNGRAB_POINTER = 'u'; @@ -56,14 +57,15 @@ const BROADWAY_EVENT_ROUNDTRIP_NOTIFY = 'F'; const DISPLAY_OP_REPLACE_CHILD = 0; const DISPLAY_OP_APPEND_CHILD = 1; -const DISPLAY_OP_APPEND_ROOT = 2; -const DISPLAY_OP_SHOW_SURFACE = 3; -const DISPLAY_OP_HIDE_SURFACE = 4; -const DISPLAY_OP_DELETE_NODE = 5; -const DISPLAY_OP_MOVE_NODE = 6; -const DISPLAY_OP_RESIZE_NODE = 7; -const DISPLAY_OP_RESTACK_SURFACES = 8; -const DISPLAY_OP_DELETE_SURFACE = 9; +const DISPLAY_OP_INSERT_AFTER_CHILD = 2; +const DISPLAY_OP_APPEND_ROOT = 3; +const DISPLAY_OP_SHOW_SURFACE = 4; +const DISPLAY_OP_HIDE_SURFACE = 5; +const DISPLAY_OP_DELETE_NODE = 6; +const DISPLAY_OP_MOVE_NODE = 7; +const DISPLAY_OP_RESIZE_NODE = 8; +const DISPLAY_OP_RESTACK_SURFACES = 9; +const DISPLAY_OP_DELETE_SURFACE = 10; // GdkCrossingMode const GDK_CROSSING_NORMAL = 0; @@ -234,6 +236,7 @@ function cmdCreateSurface(id, x, y, width, height, isTemp) surface.transientParent = 0; surface.visible = false; surface.imageData = null; + surface.old_nodes = {}; var div = document.createElement('div'); div.surface = surface; @@ -296,12 +299,14 @@ function cmdLowerSurface(id) moveToHelper(surface, 0); } -function TransformNodes(node_data, div, display_commands) { +function TransformNodes(node_data, div, old_nodes, display_commands) { this.node_data = node_data; this.display_commands = display_commands; this.data_pos = 0; this.div = div; this.outstanding = 1; + this.old_nodes = old_nodes; + this.new_nodes = {}; } TransformNodes.prototype.decode_uint32 = function() { @@ -474,20 +479,32 @@ function set_rrect_style (div, rrect) { div.style["border-bottom-left-radius"] = args(px(rrect.sizes[3].width), px(rrect.sizes[3].height)); } -TransformNodes.prototype.insertNode = function(parent, posInParent, oldNode) +function addOldIds(node, node_map) { + node_map[node.node_id] = node; + + var child = node.firstChild; + while (child) { + addOldIds(child, node_map); + child = child.nextSibling; + } +} + + +TransformNodes.prototype.insertNode = function(parent, previousSibling, is_toplevel) { var type = this.decode_uint32(); + var id = this.decode_uint32(); var newNode = null; - - // We need to dup this because as we reuse children the original order is lost - var oldChildren = []; - if (oldNode) { - for (var i = 0; i < oldNode.children.length; i++) - oldChildren[i] = oldNode.children[i]; - } + var oldNode = null; switch (type) { + /* Reuse divs from last frame */ + case BROADWAY_NODE_REUSE: + { + oldNode = this.old_nodes[id]; + } + break; /* Leaf nodes */ case BROADWAY_NODE_TEXTURE: @@ -656,7 +673,7 @@ TransformNodes.prototype.insertNode = function(parent, posInParent, oldNode) div.style["transform"] = transform_string; div.style["transform-origin"] = "0px 0px"; - this.insertNode(div, -1, oldChildren[0]); + this.insertNode(div, null, false); newNode = div; } break; @@ -668,7 +685,7 @@ TransformNodes.prototype.insertNode = function(parent, posInParent, oldNode) div.style["position"] = "absolute"; set_rect_style(div, rect); div.style["overflow"] = "hidden"; - this.insertNode(div, -1, oldChildren[0]); + this.insertNode(div, null, false); newNode = div; } break; @@ -680,7 +697,7 @@ TransformNodes.prototype.insertNode = function(parent, posInParent, oldNode) div.style["position"] = "absolute"; set_rrect_style(div, rrect); div.style["overflow"] = "hidden"; - this.insertNode(div, -1, oldChildren[0]); + this.insertNode(div, null, false); newNode = div; } break; @@ -694,7 +711,7 @@ TransformNodes.prototype.insertNode = function(parent, posInParent, oldNode) div.style["top"] = px(0); div.style["opacity"] = opacity; - this.insertNode(div, -1, oldChildren[0]); + this.insertNode(div, null, false); newNode = div; } break; @@ -716,7 +733,7 @@ TransformNodes.prototype.insertNode = function(parent, posInParent, oldNode) div.style["top"] = px(0); div.style["filter"] = filters; - this.insertNode(div, -1, oldChildren[0]); + this.insertNode(div, null, false); newNode = div; } break; @@ -726,7 +743,7 @@ TransformNodes.prototype.insertNode = function(parent, posInParent, oldNode) var str = this.decode_string(); var div = document.createElement('div'); div.setAttribute('debug', str); - this.insertNode(div, -1, oldChildren[0]); + this.insertNode(div, null, false); newNode = div; } break; @@ -737,71 +754,59 @@ TransformNodes.prototype.insertNode = function(parent, posInParent, oldNode) { var div = document.createElement('div'); var len = this.decode_uint32(); + var lastChild = null; for (var i = 0; i < len; i++) { - this.insertNode(div, -1, oldChildren[i]); + lastChild = this.insertNode(div, lastChild, false); } newNode = div; } break; - case BROADWAY_NODE_KEEP_ALL: - { - if (!oldNode) - alert("KEEP_ALL with no oldNode"); - - if (oldNode.parentNode != parent) - newNode = oldNode; - else - newNode = null; - } - break; - - case BROADWAY_NODE_KEEP_THIS: - { - if (!oldNode) - alert("KEEP_THIS with no oldNode "); - - /* We only get keep-this if all parents were kept, check this */ - if (oldNode.parentNode != parent) - alert("Got KEEP_THIS for non-kept parent"); - - var len = this.decode_uint32(); - var i; - - for (i = 0; i < len; i++) { - this.insertNode(oldNode, i, - oldChildren[i]); - } - - /* Remove children that are after the new length */ - for (i = oldChildren.length - 1; i > len - 1; i--) - this.display_commands.push([DISPLAY_OP_DELETE_NODE, oldChildren[i]]); - - /* NOTE: No need to modify the parent, we're keeping this node as is */ - newNode = null; - } - break; - default: alert("Unexpected node type " + type); } if (newNode) { - if (posInParent >= 0 && parent.children[posInParent]) - this.display_commands.push([DISPLAY_OP_REPLACE_CHILD, parent, newNode, parent.children[posInParent]]); - else + newNode.node_id = id; + this.new_nodes[id] = newNode; + if (is_toplevel) this.display_commands.push([DISPLAY_OP_APPEND_CHILD, parent, newNode]); + else // Here it is safe to display directly because we have not added the toplevel to the document yet + parent.appendChild(newNode); + return newNode; + } else if (oldNode) { + addOldIds(oldNode, this.new_nodes); + // This must be delayed until display ops, because it will remove from the old parent + this.display_commands.push([DISPLAY_OP_INSERT_AFTER_CHILD, parent, previousSibling, oldNode]); + return oldNode; } } TransformNodes.prototype.execute = function(display_commands) { - var div = this.div; - this.insertNode(div, 0, div.firstChild, display_commands); - if (this.data_pos != this.node_data.byteLength) - alert ("Did not consume entire array (len " + this.node_data.byteLength + ")"); -} + var root = this.div; + while (this.data_pos < this.node_data.byteLength) { + var op = this.decode_uint32(); + + switch (op) { + case BROADWAY_NODE_OP_APPEND_NODE: + var parentId = this.decode_uint32(); + var parent; + if (parentId == 0) + parent = root; + else + parent = this.old_nodes[parentId]; + this.insertNode(parent, null, true); + break; + case BROADWAY_NODE_OP_REMOVE_NODE: + var removeId = this.decode_uint32(); + var remove = this.old_nodes[removeId]; + this.display_commands.push([DISPLAY_OP_DELETE_NODE, remove]); + break; + } + } +} function cmdGrabPointer(id, ownerEvents) { @@ -818,7 +823,7 @@ function cmdUngrabPointer() function handleDisplayCommands(display_commands) { - var div; + var div, parent; var len = display_commands.length; for (var i = 0; i < len; i++) { var cmd = display_commands[i]; @@ -830,6 +835,15 @@ function handleDisplayCommands(display_commands) case DISPLAY_OP_APPEND_CHILD: cmd[1].appendChild(cmd[2]); break; + case DISPLAY_OP_INSERT_AFTER_CHILD: + parent = cmd[1]; + var afterThis = cmd[2]; + div = cmd[3]; + if (afterThis == null) // First + parent.insertBefore(div, parent.firstChild); + else + parent.insertBefore(div, afterThis.nextSibling); + break; case DISPLAY_OP_APPEND_ROOT: document.body.appendChild(cmd[1]); break; @@ -1014,8 +1028,9 @@ function handleCommands(cmd, display_commands, new_textures, modified_trees) var node_data = cmd.get_nodes (); surface = surfaces[id]; - var transform_nodes = new TransformNodes (node_data, surface.div, display_commands); + var transform_nodes = new TransformNodes (node_data, surface.div, surface.old_nodes, display_commands); transform_nodes.execute(); + surface.old_nodes = transform_nodes.new_nodes; } break;