#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <cairo.h>
#include "broadway-output.h"
//#define DEBUG_NODE_SENDING
//#define DEBUG_NODE_SENDING_REMOVE
/************************************************************************
* Basic I / O primitives *
************************************************************************/
struct BroadwayOutput {
GOutputStream *out;
GString *buf;
int error;
guint32 serial;
};
static void
broadway_output_send_cmd (BroadwayOutput *output,
gboolean fin, BroadwayWSOpCode code,
const void *buf, gsize count)
{
gboolean mask = FALSE ;
guchar header[16 ];
size_t p;
gboolean mid_header = count > 125 && count <= 65535 ;
gboolean long_header = count > 65535 ;
/* NB. big-endian spec => bit 0 == MSB */
header[0 ] = ( (fin ? 0 x80 : 0 ) | (code & 0 x0f) );
header[1 ] = ( (mask ? 0 x80 : 0 ) |
(mid_header ? 126 : long_header ? 127 : count) );
p = 2 ;
if (mid_header)
{
*(guint16 *)(header + p) = GUINT16_TO_BE( (guint16)count );
p += 2 ;
}
else if (long_header)
{
*(guint64 *)(header + p) = GUINT64_TO_BE( count );
p += 8 ;
}
// FIXME: if we are paranoid we should 'mask' the data
// FIXME: we should really emit these as a single write
g_output_stream_write_all (output->out, header, p, NULL, NULL, NULL);
g_output_stream_write_all (output->out, buf, count, NULL, NULL, NULL);
}
void broadway_output_pong (BroadwayOutput *output)
{
broadway_output_send_cmd (output, TRUE , BROADWAY_WS_CNX_PONG, NULL, 0 );
}
int
broadway_output_flush (BroadwayOutput *output)
{
if (output->buf->len == 0 )
return TRUE ;
broadway_output_send_cmd (output, TRUE , BROADWAY_WS_BINARY,
output->buf->str, output->buf->len);
g_string_set_size (output->buf, 0 );
return !output->error;
}
BroadwayOutput *
broadway_output_new (GOutputStream *out, guint32 serial)
{
BroadwayOutput *output;
output = g_new0 (BroadwayOutput, 1 );
output->out = g_object_ref (out);
output->buf = g_string_new ("" );
output->serial = serial;
return output;
}
void
broadway_output_free (BroadwayOutput *output)
{
g_object_unref (output->out);
free (output);
}
guint32
broadway_output_get_next_serial (BroadwayOutput *output)
{
return output->serial;
}
void
broadway_output_set_next_serial (BroadwayOutput *output,
guint32 serial)
{
output->serial = serial;
}
/************************************************************************
* Core rendering operations *
************************************************************************/
static void
append_uint8 (BroadwayOutput *output, guint8 c)
{
g_string_append_c (output->buf, c);
}
static void
append_bool (BroadwayOutput *output, gboolean val)
{
g_string_append_c (output->buf, val ? 1 : 0 );
}
static void
append_flags (BroadwayOutput *output, guint32 val)
{
g_string_append_c (output->buf, val);
}
static void
append_uint16 (BroadwayOutput *output, guint32 v)
{
gsize old_len = output->buf->len;
guint8 *buf;
g_string_set_size (output->buf, old_len + 2 );
buf = (guint8 *)output->buf->str + old_len;
buf[0 ] = (v >> 0 ) & 0 xff;
buf[1 ] = (v >> 8 ) & 0 xff;
}
static void
append_uint32 (BroadwayOutput *output, guint32 v)
{
gsize old_len = output->buf->len;
guint8 *buf;
g_string_set_size (output->buf, old_len + 4 );
buf = (guint8 *)output->buf->str + old_len;
buf[0 ] = (v >> 0 ) & 0 xff;
buf[1 ] = (v >> 8 ) & 0 xff;
buf[2 ] = (v >> 16 ) & 0 xff;
buf[3 ] = (v >> 24 ) & 0 xff;
}
static void
patch_uint32 (BroadwayOutput *output, guint32 v, gsize offset)
{
guint8 *buf;
buf = (guint8 *)output->buf->str + offset;
buf[0 ] = (v >> 0 ) & 0 xff;
buf[1 ] = (v >> 8 ) & 0 xff;
buf[2 ] = (v >> 16 ) & 0 xff;
buf[3 ] = (v >> 24 ) & 0 xff;
}
static void
write_header(BroadwayOutput *output, char op)
{
append_uint8 (output, op);
append_uint32 (output, output->serial++);
}
void
broadway_output_grab_pointer (BroadwayOutput *output,
int id,
gboolean owner_event)
{
write_header (output, BROADWAY_OP_GRAB_POINTER);
append_uint16 (output, id);
append_bool (output, owner_event);
}
guint32
broadway_output_ungrab_pointer (BroadwayOutput *output)
{
guint32 serial;
serial = output->serial;
write_header (output, BROADWAY_OP_UNGRAB_POINTER);
return serial;
}
void
broadway_output_new_surface(BroadwayOutput *output,
int id, int x, int y, int w, int h)
{
write_header (output, BROADWAY_OP_NEW_SURFACE);
append_uint16 (output, id);
append_uint16 (output, x);
append_uint16 (output, y);
append_uint16 (output, w);
append_uint16 (output, h);
}
void
broadway_output_disconnected (BroadwayOutput *output)
{
write_header (output, BROADWAY_OP_DISCONNECTED);
}
void
broadway_output_show_surface(BroadwayOutput *output, int id)
{
write_header (output, BROADWAY_OP_SHOW_SURFACE);
append_uint16 (output, id);
}
void
broadway_output_hide_surface(BroadwayOutput *output, int id)
{
write_header (output, BROADWAY_OP_HIDE_SURFACE);
append_uint16 (output, id);
}
void
broadway_output_raise_surface(BroadwayOutput *output, int id)
{
write_header (output, BROADWAY_OP_RAISE_SURFACE);
append_uint16 (output, id);
}
void
broadway_output_lower_surface(BroadwayOutput *output, int id)
{
write_header (output, BROADWAY_OP_LOWER_SURFACE);
append_uint16 (output, id);
}
void
broadway_output_destroy_surface(BroadwayOutput *output, int id)
{
write_header (output, BROADWAY_OP_DESTROY_SURFACE);
append_uint16 (output, id);
}
void
broadway_output_roundtrip (BroadwayOutput *output,
int id,
guint32 tag)
{
write_header (output, BROADWAY_OP_ROUNDTRIP);
append_uint16 (output, id);
append_uint32 (output, tag);
}
void
broadway_output_set_show_keyboard (BroadwayOutput *output,
gboolean show)
{
write_header (output, BROADWAY_OP_SET_SHOW_KEYBOARD);
append_uint16 (output, show);
}
void
broadway_output_move_resize_surface (BroadwayOutput *output,
int id,
gboolean has_pos,
int x,
int y,
gboolean has_size,
int w,
int h)
{
int val;
if (!has_pos && !has_size)
return ;
write_header (output, BROADWAY_OP_MOVE_RESIZE);
val = (!!has_pos) | ((!!has_size) << 1 );
append_uint16 (output, id);
append_flags (output, val);
if (has_pos)
{
append_uint16 (output, x);
append_uint16 (output, y);
}
if (has_size)
{
append_uint16 (output, w);
append_uint16 (output, h);
}
}
void
broadway_output_set_transient_for (BroadwayOutput *output,
int id,
int parent_id)
{
write_header (output, BROADWAY_OP_SET_TRANSIENT_FOR);
append_uint16 (output, id);
append_uint16 (output, parent_id);
}
static int append_node_depth = -1 ;
static void
append_type (BroadwayOutput *output, guint32 type, BroadwayNode *node)
{
#ifdef DEBUG_NODE_SENDING
g_print ("%*s%s(%d/%d)" , append_node_depth*2 , "" , broadway_node_type_names[type], node->id, node->output_id);
if (type == BROADWAY_NODE_TEXTURE)
g_print (" tx=%u" , node->data[4 ]);
g_print ("\n" );
#endif
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
* against the old tree . This allows us to avoid sending certain
* parts .
*
* Reusing existing dom nodes are problematic because doing so
* automatically inherits all their children . There are two cases
* where we do this :
*
* If the entire sub tree is identical we emit a KEEP_ALL node which
* just reuses the entire old dom subtree .
*
* If a the node is unchanged ( but some descendant may have changed ) ,
* and all parents are also unchanged , then we can just avoid
* changing the dom node at all , and we emit a KEEP_THIS node .
*
***********************************/
static void
append_node (BroadwayOutput *output,
BroadwayNode *node,
GHashTable *old_node_lookup)
{
guint32 i;
BroadwayNode *reused_node;
append_node_depth++;
reused_node = lookup_old_node (old_node_lookup, node->id);
if (reused_node)
{
broadway_node_mark_deep_consumed (reused_node, TRUE );
append_type (output, BROADWAY_NODE_REUSE, node);
append_uint32 (output, node->output_id);
}
else
{
append_type (output, node->type, node);
append_uint32 (output, node->output_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_node_depth--;
}
static gboolean
should_reuse_node (BroadwayOutput *output,
BroadwayNode *node,
BroadwayNode *old_node)
{
int i;
guint32 new_texture;
if (old_node->reused)
return FALSE ;
if (node->type != old_node->type)
return FALSE ;
if (broadway_node_equal (node, old_node))
return TRUE ;
switch (node->type) {
case BROADWAY_NODE_TRANSFORM:
#ifdef DEBUG_NODE_SENDING
g_print ("Patching transform node %d/%d\n" ,
old_node->id, old_node->output_id);
#endif
append_uint32 (output, BROADWAY_NODE_OP_PATCH_TRANSFORM);
append_uint32 (output, old_node->output_id);
for (i = 0 ; i < node->n_data; i++)
append_uint32 (output, node->data[i]);
return TRUE ;
case BROADWAY_NODE_TEXTURE:
/* Check that the size, etc is the same */
for (i = 0 ; i < 4 ; i++)
if (node->data[i] != old_node->data[i])
return FALSE ;
new_texture = node->data[4 ];
#ifdef DEBUG_NODE_SENDING
g_print ("Patching texture node %d/%d to tx=%d\n" ,
old_node->id, old_node->output_id,
new_texture);
#endif
append_uint32 (output, BROADWAY_NODE_OP_PATCH_TEXTURE);
append_uint32 (output, old_node->output_id);
append_uint32 (output, new_texture);
return TRUE ;
break ;
default :
return FALSE ;
}
}
static BroadwayNode *
append_node_ops (BroadwayOutput *output,
BroadwayNode *node,
BroadwayNode *parent,
BroadwayNode *previous_sibling,
BroadwayNode *old_node,
GHashTable *old_node_lookup)
{
BroadwayNode *reused_node;
guint32 i;
/* Maybe can be reused from the last tree. */
reused_node = lookup_old_node (old_node_lookup, node->id);
if (reused_node)
{
g_assert (node == reused_node);
g_assert (reused_node->reused);
g_assert (!reused_node->consumed); /* Should only be once in the tree, and not consumed otherwise */
broadway_node_mark_deep_consumed (reused_node, TRUE );
if (node == old_node)
{
/* The node in the old tree at the current position is the same, so
we need to do nothing , just don ' t delete it ( which we won ' t since
its marked used) */
}
else
{
/* We can reuse it, bu it comes from a different place or
order, if so we need to move it in place */
#ifdef DEBUG_NODE_SENDING
g_print ("Move old node %d/%d to parent %d/%d after %d/%d\n" ,
reused_node->id, reused_node->output_id,
parent ? parent->id : 0 ,
parent ? parent->output_id : 0 ,
previous_sibling ? previous_sibling->id : 0 ,
previous_sibling ? previous_sibling->output_id : 0 );
#endif
append_uint32 (output, BROADWAY_NODE_OP_MOVE_AFTER_CHILD);
append_uint32 (output, parent ? parent->output_id : 0 );
append_uint32 (output, previous_sibling ? previous_sibling->output_id : 0 );
append_uint32 (output, reused_node->output_id);
}
return reused_node;
}
/* If the next node in place is shallowly equal (but not necessarily
* deep equal ) we reuse it and tweak its children as needed .
* Except we avoid this for reused node as those make more sense to reuse deeply .
*/
if (old_node && should_reuse_node (output, node, old_node))
{
int old_i = 0 ;
BroadwayNode *last_child = NULL;
old_node->consumed = TRUE ; // Don't reuse again
// We rewrite this new node as it now represents the old node in the browser
node->output_id = old_node->output_id;
/* However, we might need to rewrite then children of old_node */
for (i = 0 ; i < node->n_children; i++)
{
BroadwayNode *child = node->children[i];
/* Find the next (or first) non-consumed old child, if any */
while (old_i < old_node->n_children &&
old_node->children[old_i]->consumed)
old_i++;
last_child =
append_node_ops (output,
child,
node, /* parent */
last_child,
(old_i < old_node->n_children) ? old_node->children[old_i] : NULL,
old_node_lookup);
}
/* Remaining old nodes are either reused elsewhere, or end up marked not consumed so are deleted at the end */
return old_node;
}
/* Fallback to create a new tree */
#ifdef DEBUG_NODE_SENDING
g_print ("Insert nodes in parent %d/%d, after sibling %d/%d\n" ,
parent ? parent->id : 0 ,
parent ? parent->output_id : 0 ,
previous_sibling ? previous_sibling->id : 0 ,
previous_sibling ? previous_sibling->output_id : 0 );
#endif
append_uint32 (output, BROADWAY_NODE_OP_INSERT_NODE);
append_uint32 (output, parent ? parent->output_id : 0 );
append_uint32 (output, previous_sibling ? previous_sibling->output_id : 0 );
append_node(output, node, old_node_lookup);
return node;
}
/* Remove non-consumed nodes */
static void
append_node_removes (BroadwayOutput *output,
BroadwayNode *node)
{
// TODO: Use an array of nodes instead
if (!node->consumed)
{
#ifdef DEBUG_NODE_SENDING_REMOVE
g_print ("Remove old node non-consumed node %d/%d\n" ,
node->id, node->output_id);
#endif
append_uint32 (output, BROADWAY_NODE_OP_REMOVE_NODE);
append_uint32 (output, node->output_id);
}
for (int i = 0 ; i < node->n_children; i++)
append_node_removes (output, node->children[i]);
}
void
broadway_output_surface_set_nodes (BroadwayOutput *output,
int id,
BroadwayNode *root,
BroadwayNode *old_root,
GHashTable *old_node_lookup)
{
gsize size_pos, start, end;
if (old_root)
{
broadway_node_mark_deep_consumed (old_root, FALSE );
broadway_node_mark_deep_reused (old_root, FALSE );
/* This will modify children of old_root if any are shared */
broadway_node_mark_deep_reused (root, TRUE );
}
write_header (output, BROADWAY_OP_SET_NODES);
append_uint16 (output, id);
size_pos = output->buf->len;
append_uint32 (output, 0 );
start = output->buf->len;
#ifdef DEBUG_NODE_SENDING
g_print ("====== node ops for surface %d =======\n" , id);
#endif
append_node_ops (output, root, NULL, NULL, old_root, old_node_lookup);
if (old_root)
append_node_removes (output, old_root);
end = output->buf->len;
patch_uint32 (output, (end - start) / 4 , size_pos);
}
void
broadway_output_upload_texture (BroadwayOutput *output,
guint32 id,
GBytes *texture)
{
gsize len = g_bytes_get_size (texture);
write_header (output, BROADWAY_OP_UPLOAD_TEXTURE);
append_uint32 (output, id);
append_uint32 (output, (guint32)len);
g_string_append_len (output->buf, g_bytes_get_data (texture, NULL), len);
}
void
broadway_output_release_texture (BroadwayOutput *output,
guint32 id)
{
write_header (output, BROADWAY_OP_RELEASE_TEXTURE);
append_uint32 (output, id);
}
Messung V0.5 in Prozent C=98 H=96 G=96
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland