From 091bb0a57b4a1213be970607a7132c57a8da668e Mon Sep 17 00:00:00 2001 From: hippoz <10706925-hippoz@users.noreply.gitlab.com> Date: Sat, 29 Apr 2023 05:35:09 +0300 Subject: [PATCH] improve performance by a large margin by using a hash table for dispatch targets --- src/background-node.c | 5 +++ src/box-layout-node.c | 5 +++ src/defs.h | 18 ++++++++ src/dispatcher-node.c | 5 +++ src/main.c | 2 +- src/node.c | 96 ++++++++++++++++++++++++++++++------------- src/node.h | 38 +++++++++++++++-- src/scrollable-node.c | 10 ++++- src/text-input-node.c | 4 ++ src/text-node.c | 4 ++ 10 files changed, 152 insertions(+), 35 deletions(-) diff --git a/src/background-node.c b/src/background-node.c index 5be2ec2..389baba 100644 --- a/src/background-node.c +++ b/src/background-node.c @@ -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) { diff --git a/src/box-layout-node.c b/src/box-layout-node.c index f2886e0..6355042 100644 --- a/src/box-layout-node.c +++ b/src/box-layout-node.c @@ -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]; diff --git a/src/defs.h b/src/defs.h index 914c949..375ad39 100644 --- a/src/defs.h +++ b/src/defs.h @@ -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 diff --git a/src/dispatcher-node.c b/src/dispatcher-node.c index 8b75e11..92aedf5 100644 --- a/src/dispatcher-node.c +++ b/src/dispatcher-node.c @@ -1,5 +1,6 @@ #include #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); } diff --git a/src/main.c b/src/main.c index 5158102..26de939 100644 --- a/src/main.c +++ b/src/main.c @@ -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"); diff --git a/src/node.c b/src/node.c index 524f828..80abf2b 100644 --- a/src/node.c +++ b/src/node.c @@ -1,11 +1,28 @@ #include #include +#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; diff --git a/src/node.h b/src/node.h index a813ebd..d1521a9 100644 --- a/src/node.h +++ b/src/node.h @@ -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 diff --git a/src/scrollable-node.c b/src/scrollable-node.c index a579e1a..1923a7e 100644 --- a/src/scrollable-node.c +++ b/src/scrollable-node.c @@ -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; diff --git a/src/text-input-node.c b/src/text-input-node.c index 0d693ff..d1aaba2 100644 --- a/src/text-input-node.c +++ b/src/text-input-node.c @@ -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; diff --git a/src/text-node.c b/src/text-node.c index b9093ba..311258d 100644 --- a/src/text-node.c +++ b/src/text-node.c @@ -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;