improve performance by a large margin by using a hash table for dispatch targets
This commit is contained in:
parent
1e2e11cd2b
commit
091bb0a57b
10 changed files with 152 additions and 35 deletions
|
@ -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) {
|
||||
|
|
|
@ -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];
|
||||
|
|
18
src/defs.h
18
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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
96
src/node.c
96
src/node.c
|
@ -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;
|
||||
|
|
38
src/node.h
38
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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue