improve performance by a large margin by using a hash table for dispatch targets

This commit is contained in:
hippoz 2023-04-29 05:35:09 +03:00
parent 1e2e11cd2b
commit 091bb0a57b
Signed by: hippoz
GPG key ID: 56C4E02A85F2FBED
10 changed files with 152 additions and 35 deletions

View file

@ -1,4 +1,5 @@
#include "background-node.h"
#include "node.h"
#include "window.h"
#ifndef M_PI
@ -17,6 +18,10 @@ int background_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
UIBackgroundNode *n = (UIBackgroundNode*)node;
switch (ev) {
case UI_EVENT_ATTACHED: {
node_notify_subscribe(node->parent, node, (enum UIEvent[]){ UI_EVENT_REPAINT, UI_EVENT_HOVERED, UI_EVENT_UNHOVERED, UI_EVENT_PRESSED, UI_EVENT_UNPRESSED, 0 });
break;
}
case UI_EVENT_REPAINT: {
UIRGBA *color = &n->normal;
if (n->update_on_status) {

View file

@ -29,6 +29,11 @@ int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p)
int growing_widgets = 0;
int current_index = 0;
if (ev == UI_EVENT_ATTACHED) {
node_notify_subscribe(component->parent, component, (enum UIEvent[]){ UI_EVENT_RELAYOUT, UI_EVENT_GET_EXTENTS, 0 });
return 0;
}
if (ev == UI_EVENT_RELAYOUT || ev == UI_EVENT_GET_EXTENTS) {
for (int i = 0; i < node->nodes_count; i++) {
UINode *current = node->nodes[i];

View file

@ -5,4 +5,22 @@
#define _UI_DEBUG
#endif
#define UI_ENABLE_BRANCH_HINTS
#ifdef UI_ENABLE_BRANCH_HINTS
# ifdef __has_builtin
# if __has_builtin(__builtin_expect)
# define __UI_UTIL_CAN_USE_BRANCH_HINTS
# endif
# endif
#endif
#ifdef __UI_UTIL_CAN_USE_BRANCH_HINTS
# define unlikely(M_expr) __builtin_expect(!!(M_expr), 0)
# define likely(M_expr) __builtin_expect(!!(M_expr), 1)
#else
# define unlikely(M_expr) (M_expr)
# define likely(M_expr) (M_expr)
#endif
#endif // _UI__DEFS_H

View file

@ -1,5 +1,6 @@
#include <stdlib.h>
#include "dispatcher-node.h"
#include "node.h"
int dispatcher_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
{
@ -9,6 +10,10 @@ int dispatcher_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
UIDispatcherNode *n = (UIDispatcherNode*)node;
if (ev == UI_EVENT_ATTACHED) {
node_notify_subscribe(node->parent, node, (enum UIEvent[]){ n->node_event_type, 0 });
}
if (ev == n->node_event_type) {
(n->handle)(node->parent, n->data, n->event_type, d, p);
}

View file

@ -130,7 +130,7 @@ int app_handle(struct UINode *node, void *data, int event_type, size_t d, void *
box_layout_new(buttons, UI_DIRECTION_VERTICAL);
for (int i = 0; i < 3; i++) {
for (int i = 0; i < 10000; i++) {
UINode *button = node_new(buttons, "button");
state_background_node_new(button, UIPurple600, UIPurple700, UIPurple800, 6.0);
text_node_new(button, state->font, UINeutral50, "button");

View file

@ -1,11 +1,28 @@
#include <stdlib.h>
#include <stdio.h>
#include "defs.h"
#include "node.h"
#include "rect.h"
typedef struct UIWindow UIWindow;
int window_invalidate_node(UIWindow *window, UINode *node);
static inline uint32_t hash_ui_event(enum UIEvent ev, int count)
{
if (ev < UI_EVENT__LAST) {
return UI_EVENT_BUCKET_INDEX[ev];
}
// FNV-1a
uint32_t hash = 0x811c9dc5;
for (size_t i = 0; i < sizeof(enum UIEvent); i++) {
hash ^= ((char*)&ev)[i];
hash *= 0x01000193;
}
return hash % count;
}
UIRect node_get_computed_rect(UINode *node) {
UIRect rect = node->rect;
UIRect extents = node->rect;
@ -26,7 +43,7 @@ UIRect node_get_computed_rect(UINode *node) {
int node_send(UINode *node, enum UIEvent ev, size_t d, void *p)
{
if (node->flags & UI_NODE_DISABLED) {
if (unlikely(node->flags & UI_NODE_DISABLED)) {
return 0;
}
@ -42,34 +59,36 @@ int node_send(UINode *node, enum UIEvent ev, size_t d, void *p)
int node_dispatch(UINode *node, enum UIEvent ev, size_t d, void *p)
{
if (node->flags & UI_NODE_DISABLED) {
if (unlikely(!node || node->flags & UI_NODE_DISABLED)) {
return 0;
}
int result = 0;
int component_index = UI_NODE_LAST_COMPONENT_INDEX_NONE;
for (int i = 0; i < node->nodes_count; i++) {
if (node->last_component_index == UI_NODE_LAST_COMPONENT_INDEX_NONE || (node->last_component_index >= 0 && i > node->last_component_index)) {
break;
}
if (node->nodes[i]->flags & UI_NODE_COMPONENT) {
component_index = i;
result = node_send(node->nodes[i], ev, d, p);
if (result) break;
}
}
if (node->last_component_index < 0) {
node->last_component_index = component_index;
}
if (result) return result;
if (node->handle) {
result = (node->handle)(node, ev, d, p);
if (result) return result;
}
if (node->handle_proto) {
return (node->handle_proto)(node, ev, d, p);
result = (node->handle_proto)(node, ev, d, p);
if (result) return result;
}
uint32_t hash = hash_ui_event(ev, UI_NODE_DISPATCH_BUCKETS);
UINodeDispatchBucket *bucket = &node->dispatch_buckets[hash];
for (int i = 0; i < bucket->count; i++) {
UINode *n = bucket->elements[i];
// We prioritize handle_proto for components
if (n->handle_proto) {
result = (n->handle_proto)(n, ev, d, p);
if (result) return result;
}
if (n->handle) {
result = (n->handle)(n, ev, d, p);
if (result) return result;
}
}
return 0;
}
@ -100,8 +119,36 @@ void node_init(UINode *node)
node->height_policy = UI_SIZE_POLICY_COMPUTED;
node->handle = NULL;
node->handle_proto = NULL;
node->last_component_index = UI_NODE_LAST_COMPONENT_INDEX_NONE;
node->cached_computed_extents = false;
for (int i = 0; i < UI_NODE_DISPATCH_BUCKETS; i++) {
node->dispatch_buckets[i].capacity = 0;
node->dispatch_buckets[i].count = 0;
node->dispatch_buckets[i].elements = NULL;
}
}
static inline void node_add_dispatch_target(UINode *node, enum UIEvent ev, UINode *target)
{
uint32_t index = hash_ui_event(ev, UI_NODE_DISPATCH_BUCKETS);
UINodeDispatchBucket *bucket = &node->dispatch_buckets[index];
if (bucket->count >= bucket->capacity) {
bucket->capacity += 2;
bucket->elements = realloc(bucket->elements, bucket->capacity * sizeof(bucket->elements));
if (!bucket->elements) {
return;
}
}
int node_index = bucket->count++;
bucket->elements[node_index] = target;
}
void node_notify_subscribe(UINode *node, UINode *target, enum UIEvent *event)
{
for (; *event; event++) {
node_add_dispatch_target(node, *event, target);
}
}
void node_free(UINode *node)
@ -110,11 +157,6 @@ void node_free(UINode *node)
// TODO: properly handle removing this node from its parent
// Invalidate component index cache
if ((node->flags & UI_NODE_COMPONENT) && node->parent) {
node->parent->last_component_index = UI_NODE_LAST_COMPONENT_INDEX_INVALIDATED;
}
node_dispatch(node, UI_EVENT_DESTROY, 0, NULL);
for (int i = 0; i < node->nodes_count; i++) {
@ -176,11 +218,7 @@ UINode *node_attach(UINode *parent, UINode *node)
int index = parent->nodes_count++;
parent->nodes[index] = node;
// Let's save the index of the last component so that we can exit the loop early
// if there's no components left when we're in node_dispatch()
if ((node->flags & UI_NODE_COMPONENT) && index > parent->last_component_index) {
parent->last_component_index = index;
}
node_send(node, UI_EVENT_ATTACHED, 0, 0);
}
return node;

View file

@ -9,6 +9,11 @@
#define UI_NODE_COMPUTED_EXTENTS_OK (1)
#define UI_NODE_COMPUTED_EXTENTS_CACHED (2)
enum UIEvent {
UI_EVENT_NONE = 0,
UI_EVENT_ATTACHED,
UI_EVENT_DESTROY,
UI_EVENT_REPAINT,
UI_EVENT_RELAYOUT,
UI_EVENT_GET_EXTENTS,
@ -16,7 +21,6 @@ enum UIEvent {
UI_EVENT_UNHOVERED,
UI_EVENT_PRESSED,
UI_EVENT_UNPRESSED,
UI_EVENT_DESTROY,
UI_EVENT_TIMER_END,
@ -24,6 +28,26 @@ enum UIEvent {
UI_EVENT_BUTTON_RIGHT_UPDATE, // d == 1 if pressed, d == 0 if released
UI_EVENT_SCROLL, // p (double*) = delta
UI_EVENT_KEY_DOWN, // d (unsigned int) = keycode, p (struct xkb_state*) = xkb state
UI_EVENT__LAST,
};
static const int UI_EVENT_BUCKET_INDEX[] = {
[UI_EVENT_NONE] = 0,
[UI_EVENT_ATTACHED] = 0,
[UI_EVENT_DESTROY] = 0,
[UI_EVENT_TIMER_END] = 0,
[UI_EVENT_RELAYOUT] = 1,
[UI_EVENT_GET_EXTENTS] = 2,
[UI_EVENT_REPAINT] = 3,
[UI_EVENT_HOVERED] = 4,
[UI_EVENT_UNHOVERED] = 4,
[UI_EVENT_PRESSED] = 5,
[UI_EVENT_UNPRESSED] = 5,
[UI_EVENT_SCROLL] = 5,
[UI_EVENT_BUTTON_LEFT_UPDATE] = 6,
[UI_EVENT_BUTTON_RIGHT_UPDATE] = 6,
[UI_EVENT_KEY_DOWN] = 6,
};
enum UISizePolicy {
@ -38,12 +62,16 @@ enum UIDirection {
UI_DIRECTION_VERTICAL
};
typedef struct UINodeDispatchBucket {
int capacity, count;
struct UINode **elements;
} UINodeDispatchBucket;
#define UI_NODE_COMPONENT (1 << 0)
#define UI_NODE_RETAINS_PRESS (1 << 1)
#define UI_NODE_DISABLED (1 << 2)
#define UI_NODE_LAST_COMPONENT_INDEX_INVALIDATED (-1)
#define UI_NODE_LAST_COMPONENT_INDEX_NONE (-2)
#define UI_NODE_DISPATCH_BUCKETS (7)
typedef struct UINode {
const char *text;
UIRect rect;
@ -56,8 +84,9 @@ typedef struct UINode {
uint32_t flags;
enum UISizePolicy width_policy;
enum UISizePolicy height_policy;
int last_component_index;
bool cached_computed_extents;
UINodeDispatchBucket dispatch_buckets[UI_NODE_DISPATCH_BUCKETS];
int (*handle)(struct UINode *node, enum UIEvent ev, size_t d, void *p);
int (*handle_proto)(struct UINode *node, enum UIEvent ev, size_t d, void *p);
@ -74,5 +103,6 @@ UINode *node_attach(UINode *parent, UINode *node);
UINode *node_new(UINode *parent, const char *text);
void node_dump(UINode *node, int depth);
void node_request_relayout(UINode *node);
void node_notify_subscribe(UINode *node, UINode *target, enum UIEvent *events);
#endif // _UI__NODE_H

View file

@ -27,13 +27,19 @@ int scrollable_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
UIScrollableNode *n = (UIScrollableNode *)node;
UINode *target = n->target;
if (!n->node.parent || !target) {
if (!node->parent) {
return 0;
}
// TODO: horizontal scroll
switch (ev) {
case UI_EVENT_ATTACHED: {
node_notify_subscribe(node->parent, node, (enum UIEvent[]){ UI_EVENT_SCROLL, UI_EVENT_RELAYOUT, 0 });
break;
}
case UI_EVENT_SCROLL: {
if (!target) return 0;
double scroll_end = target->rect.h - node->parent->rect.h;
if (scroll_end < 0) scroll_end = 0;
@ -50,6 +56,8 @@ int scrollable_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
break;
}
case UI_EVENT_RELAYOUT: {
if (!target) return 0;
UIRect rect = {0};
node_dispatch(target, UI_EVENT_GET_EXTENTS, 0, &rect);
target->rect.w = target->width_policy == UI_SIZE_POLICY_GROW ? node->parent->rect.w : rect.w;

View file

@ -25,6 +25,10 @@ int text_input_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
UITextInputNode *n = (UITextInputNode *)node;
switch (ev) {
case UI_EVENT_ATTACHED: {
node_notify_subscribe(node->parent, node, (enum UIEvent[]){ UI_EVENT_KEY_DOWN, UI_EVENT_PRESSED, UI_EVENT_UNPRESSED, 0 });
break;
}
case UI_EVENT_KEY_DOWN: {
xkb_keycode_t keycode = d;
struct xkb_state *xkb_state = (struct xkb_state*)p;

View file

@ -52,6 +52,10 @@ int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
}
switch (ev) {
case UI_EVENT_ATTACHED: {
node_notify_subscribe(node->parent, node, (enum UIEvent[]){ UI_EVENT_REPAINT, UI_EVENT_GET_EXTENTS, UI_EVENT_RELAYOUT, UI_EVENT_DESTROY, 0 });
break;
}
case UI_EVENT_REPAINT: {
if (!n->layout) {
break;