elementdrawing 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/elementdrawing.min.js +3 -0
- package/dist/elementdrawing.min.js.LICENSE.txt +8 -0
- package/dist/elementdrawing.min.js.map +1 -0
- package/dist/index.html +1 -0
- package/package.json +127 -0
- package/src/core/bridge.h +855 -0
- package/src/core/diff.c +900 -0
- package/src/core/element.c +1078 -0
- package/src/core/event.c +813 -0
- package/src/core/fiber.c +1027 -0
- package/src/core/hooks.c +919 -0
- package/src/core/renderer.c +963 -0
- package/src/core/scheduler.c +702 -0
- package/src/core/state.c +803 -0
- package/src/css/animations.css +779 -0
- package/src/css/base.css +615 -0
- package/src/css/components.css +1311 -0
- package/src/css/tailwind.css +370 -0
- package/src/css/themes.css +517 -0
- package/src/css/utilities.css +475 -0
- package/src/index.js +746 -0
- package/src/js/animation.js +655 -0
- package/src/js/dom.js +665 -0
- package/src/js/events.js +585 -0
- package/src/js/http.js +446 -0
- package/src/js/index.js +26 -0
- package/src/js/router.js +483 -0
- package/src/js/store.js +539 -0
- package/src/js/utils.js +593 -0
- package/src/js/validator.js +529 -0
- package/src/jsx/components/Accordion.jsx +210 -0
- package/src/jsx/components/Alert.jsx +169 -0
- package/src/jsx/components/Avatar.jsx +214 -0
- package/src/jsx/components/Badge.jsx +136 -0
- package/src/jsx/components/Breadcrumb.jsx +200 -0
- package/src/jsx/components/Button.jsx +188 -0
- package/src/jsx/components/Card.jsx +192 -0
- package/src/jsx/components/Carousel.jsx +278 -0
- package/src/jsx/components/Checkbox.jsx +215 -0
- package/src/jsx/components/Dialog.jsx +242 -0
- package/src/jsx/components/Drawer.jsx +190 -0
- package/src/jsx/components/Dropdown.jsx +268 -0
- package/src/jsx/components/Form.jsx +274 -0
- package/src/jsx/components/Input.jsx +285 -0
- package/src/jsx/components/Menu.jsx +276 -0
- package/src/jsx/components/Modal.jsx +274 -0
- package/src/jsx/components/Navbar.jsx +292 -0
- package/src/jsx/components/Pagination.jsx +268 -0
- package/src/jsx/components/Progress.jsx +252 -0
- package/src/jsx/components/Radio.jsx +208 -0
- package/src/jsx/components/Select.jsx +397 -0
- package/src/jsx/components/Sidebar.jsx +250 -0
- package/src/jsx/components/Slider.jsx +310 -0
- package/src/jsx/components/Spinner.jsx +198 -0
- package/src/jsx/components/Switch.jsx +201 -0
- package/src/jsx/components/Table.jsx +332 -0
- package/src/jsx/components/Tabs.jsx +227 -0
- package/src/jsx/components/Textarea.jsx +212 -0
- package/src/jsx/components/Toast.jsx +270 -0
- package/src/jsx/components/Tooltip.jsx +178 -0
- package/src/jsx/components/Typography.jsx +299 -0
- package/src/jsx/components/index.jsx +70 -0
- package/src/jsx/core/element.js +3 -0
- package/src/jsx/hooks/index.js +356 -0
- package/src/jsx/hooks/useCallback.js +472 -0
- package/src/jsx/hooks/useContext.js +586 -0
- package/src/jsx/hooks/useEffect.js +704 -0
- package/src/jsx/hooks/useLayoutEffect.js +508 -0
- package/src/jsx/hooks/useMemo.js +689 -0
- package/src/jsx/hooks/useReducer.js +729 -0
- package/src/jsx/hooks/useRef.js +542 -0
- package/src/jsx/hooks/useState.js +854 -0
- package/src/jsx/runtime/commit.js +903 -0
- package/src/jsx/runtime/createElement.js +860 -0
- package/src/jsx/runtime/index.js +356 -0
- package/src/jsx/runtime/reconcile.js +687 -0
- package/src/jsx/runtime/render.js +914 -0
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* renderer.c - ElementDrawing Core: Renderer Engine
|
|
3
|
+
*
|
|
4
|
+
* Implements the renderer lifecycle, batch creation/commit,
|
|
5
|
+
* update scheduling, paint requests, and effect flushing.
|
|
6
|
+
* Also includes DOM manipulation helpers, style computation,
|
|
7
|
+
* class merging, and attribute setting.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "bridge.h"
|
|
11
|
+
|
|
12
|
+
/* ================================================================
|
|
13
|
+
* Renderer Internal State
|
|
14
|
+
* ================================================================ */
|
|
15
|
+
|
|
16
|
+
typedef struct EDRendererState {
|
|
17
|
+
EDConfiguration config;
|
|
18
|
+
EDMemoryPool* pool;
|
|
19
|
+
EDScheduler* scheduler;
|
|
20
|
+
EDEventSystem* event_system;
|
|
21
|
+
EDStateManager* state_manager;
|
|
22
|
+
|
|
23
|
+
/* Current fiber tree */
|
|
24
|
+
EDFiberNode* current_root;
|
|
25
|
+
EDFiberNode* work_in_progress_root;
|
|
26
|
+
|
|
27
|
+
/* Batch queue */
|
|
28
|
+
EDRenderBatch* batch_head;
|
|
29
|
+
EDRenderBatch* batch_tail;
|
|
30
|
+
|
|
31
|
+
/* Effect lists */
|
|
32
|
+
EDFiberNode* pending_passive_effects;
|
|
33
|
+
EDFiberNode* pending_layout_effects;
|
|
34
|
+
EDFiberNode* pending_insertion_effects;
|
|
35
|
+
|
|
36
|
+
/* Update tracking */
|
|
37
|
+
uint32_t pending_update_count;
|
|
38
|
+
bool has_pending_work;
|
|
39
|
+
bool is_rendering;
|
|
40
|
+
bool is_committing;
|
|
41
|
+
|
|
42
|
+
/* Performance metrics */
|
|
43
|
+
uint64_t total_render_time;
|
|
44
|
+
uint64_t total_commit_time;
|
|
45
|
+
uint32_t total_renders;
|
|
46
|
+
uint32_t total_commits;
|
|
47
|
+
uint32_t total_dom_writes;
|
|
48
|
+
uint32_t total_dom_reads;
|
|
49
|
+
|
|
50
|
+
/* Render callback */
|
|
51
|
+
EDRenderCallback on_render;
|
|
52
|
+
EDCommitCallback on_commit;
|
|
53
|
+
|
|
54
|
+
/* Paint state */
|
|
55
|
+
bool paint_requested;
|
|
56
|
+
uint64_t last_paint_time;
|
|
57
|
+
uint64_t next_paint_deadline;
|
|
58
|
+
} EDRendererState;
|
|
59
|
+
|
|
60
|
+
static EDRendererState g_renderer = {0};
|
|
61
|
+
|
|
62
|
+
/* ================================================================
|
|
63
|
+
* Style Computation Helpers
|
|
64
|
+
* ================================================================ */
|
|
65
|
+
|
|
66
|
+
typedef struct EDStyleEntry {
|
|
67
|
+
char property[ED_MAX_PROP_KEY_LEN];
|
|
68
|
+
char value[ED_MAX_PROP_VALUE_LEN];
|
|
69
|
+
uint32_t priority; /* For !important */
|
|
70
|
+
bool is_important;
|
|
71
|
+
} EDStyleEntry;
|
|
72
|
+
|
|
73
|
+
typedef struct EDComputedStyles {
|
|
74
|
+
EDStyleEntry entries[ED_RENDER_MAX_STYLES];
|
|
75
|
+
uint32_t count;
|
|
76
|
+
} EDComputedStyles;
|
|
77
|
+
|
|
78
|
+
static void ed_style_entry_init(EDStyleEntry* entry, const char* prop, const char* val)
|
|
79
|
+
{
|
|
80
|
+
if (!entry) return;
|
|
81
|
+
if (prop) {
|
|
82
|
+
strncpy(entry->property, prop, ED_MAX_PROP_KEY_LEN - 1);
|
|
83
|
+
entry->property[ED_MAX_PROP_KEY_LEN - 1] = '\0';
|
|
84
|
+
}
|
|
85
|
+
if (val) {
|
|
86
|
+
strncpy(entry->value, val, ED_MAX_PROP_VALUE_LEN - 1);
|
|
87
|
+
entry->value[ED_MAX_PROP_VALUE_LEN - 1] = '\0';
|
|
88
|
+
}
|
|
89
|
+
entry->priority = 0;
|
|
90
|
+
entry->is_important = false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static EDComputedStyles* ed_computed_styles_create(void)
|
|
94
|
+
{
|
|
95
|
+
EDComputedStyles* styles = (EDComputedStyles*)calloc(1, sizeof(EDComputedStyles));
|
|
96
|
+
return styles;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static void ed_computed_styles_destroy(EDComputedStyles* styles)
|
|
100
|
+
{
|
|
101
|
+
free(styles);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static void ed_computed_styles_set(EDComputedStyles* styles, const char* prop, const char* val)
|
|
105
|
+
{
|
|
106
|
+
if (!styles || !prop) return;
|
|
107
|
+
|
|
108
|
+
/* Check for existing property */
|
|
109
|
+
for (uint32_t i = 0; i < styles->count; i++) {
|
|
110
|
+
if (strcmp(styles->entries[i].property, prop) == 0) {
|
|
111
|
+
strncpy(styles->entries[i].value, val ? val : "", ED_MAX_PROP_VALUE_LEN - 1);
|
|
112
|
+
styles->entries[i].value[ED_MAX_PROP_VALUE_LEN - 1] = '\0';
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Add new property */
|
|
118
|
+
if (styles->count >= ED_RENDER_MAX_STYLES) return;
|
|
119
|
+
ed_style_entry_init(&styles->entries[styles->count], prop, val ? val : "");
|
|
120
|
+
styles->count++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static const char* ed_computed_styles_get(EDComputedStyles* styles, const char* prop)
|
|
124
|
+
{
|
|
125
|
+
if (!styles || !prop) return NULL;
|
|
126
|
+
for (uint32_t i = 0; i < styles->count; i++) {
|
|
127
|
+
if (strcmp(styles->entries[i].property, prop) == 0) {
|
|
128
|
+
return styles->entries[i].value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return NULL;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static void ed_computed_styles_remove(EDComputedStyles* styles, const char* prop)
|
|
135
|
+
{
|
|
136
|
+
if (!styles || !prop) return;
|
|
137
|
+
for (uint32_t i = 0; i < styles->count; i++) {
|
|
138
|
+
if (strcmp(styles->entries[i].property, prop) == 0) {
|
|
139
|
+
/* Shift remaining */
|
|
140
|
+
for (uint32_t j = i; j + 1 < styles->count; j++) {
|
|
141
|
+
styles->entries[j] = styles->entries[j + 1];
|
|
142
|
+
}
|
|
143
|
+
styles->count--;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* ================================================================
|
|
150
|
+
* Class Merging Helpers
|
|
151
|
+
* ================================================================ */
|
|
152
|
+
|
|
153
|
+
typedef struct EDClassList {
|
|
154
|
+
char classes[ED_RENDER_MAX_CLASSES][ED_MAX_PROP_KEY_LEN];
|
|
155
|
+
uint32_t count;
|
|
156
|
+
} EDClassList;
|
|
157
|
+
|
|
158
|
+
static void ed_class_list_init(EDClassList* list)
|
|
159
|
+
{
|
|
160
|
+
if (!list) return;
|
|
161
|
+
memset(list, 0, sizeof(EDClassList));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static void ed_class_list_add(EDClassList* list, const char* class_name)
|
|
165
|
+
{
|
|
166
|
+
if (!list || !class_name) return;
|
|
167
|
+
if (list->count >= ED_RENDER_MAX_CLASSES) return;
|
|
168
|
+
|
|
169
|
+
/* Avoid duplicates */
|
|
170
|
+
for (uint32_t i = 0; i < list->count; i++) {
|
|
171
|
+
if (strcmp(list->classes[i], class_name) == 0) return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
strncpy(list->classes[list->count], class_name, ED_MAX_PROP_KEY_LEN - 1);
|
|
175
|
+
list->classes[list->count][ED_MAX_PROP_KEY_LEN - 1] = '\0';
|
|
176
|
+
list->count++;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static void ed_class_list_remove(EDClassList* list, const char* class_name)
|
|
180
|
+
{
|
|
181
|
+
if (!list || !class_name) return;
|
|
182
|
+
for (uint32_t i = 0; i < list->count; i++) {
|
|
183
|
+
if (strcmp(list->classes[i], class_name) == 0) {
|
|
184
|
+
for (uint32_t j = i; j + 1 < list->count; j++) {
|
|
185
|
+
strncpy(list->classes[j], list->classes[j + 1], ED_MAX_PROP_KEY_LEN);
|
|
186
|
+
}
|
|
187
|
+
list->count--;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
static bool ed_class_list_contains(EDClassList* list, const char* class_name)
|
|
194
|
+
{
|
|
195
|
+
if (!list || !class_name) return false;
|
|
196
|
+
for (uint32_t i = 0; i < list->count; i++) {
|
|
197
|
+
if (strcmp(list->classes[i], class_name) == 0) return true;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Parse a space-separated class string into a class list */
|
|
203
|
+
static void ed_class_list_parse(EDClassList* list, const char* class_str)
|
|
204
|
+
{
|
|
205
|
+
if (!list || !class_str) return;
|
|
206
|
+
ed_class_list_init(list);
|
|
207
|
+
|
|
208
|
+
char buffer[ED_MAX_PROP_VALUE_LEN];
|
|
209
|
+
strncpy(buffer, class_str, ED_MAX_PROP_VALUE_LEN - 1);
|
|
210
|
+
buffer[ED_MAX_PROP_VALUE_LEN - 1] = '\0';
|
|
211
|
+
|
|
212
|
+
char* token = strtok(buffer, " \t\n\r");
|
|
213
|
+
while (token && list->count < ED_RENDER_MAX_CLASSES) {
|
|
214
|
+
ed_class_list_add(list, token);
|
|
215
|
+
token = strtok(NULL, " \t\n\r");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static void ed_class_list_merge(EDClassList* result, EDClassList* a, EDClassList* b)
|
|
220
|
+
{
|
|
221
|
+
if (!result) return;
|
|
222
|
+
ed_class_list_init(result);
|
|
223
|
+
for (uint32_t i = 0; i < a->count; i++) {
|
|
224
|
+
ed_class_list_add(result, a->classes[i]);
|
|
225
|
+
}
|
|
226
|
+
for (uint32_t i = 0; i < b->count; i++) {
|
|
227
|
+
ed_class_list_add(result, b->classes[i]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* ================================================================
|
|
232
|
+
* Attribute Setting Helpers
|
|
233
|
+
* ================================================================ */
|
|
234
|
+
|
|
235
|
+
typedef struct EDAttributeMap {
|
|
236
|
+
char keys[ED_RENDER_MAX_ATTRIBUTES][ED_MAX_PROP_KEY_LEN];
|
|
237
|
+
char values[ED_RENDER_MAX_ATTRIBUTES][ED_MAX_PROP_VALUE_LEN];
|
|
238
|
+
uint32_t count;
|
|
239
|
+
} EDAttributeMap;
|
|
240
|
+
|
|
241
|
+
static void ed_attr_map_init(EDAttributeMap* map)
|
|
242
|
+
{
|
|
243
|
+
if (!map) return;
|
|
244
|
+
memset(map, 0, sizeof(EDAttributeMap));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
static void ed_attr_map_set(EDAttributeMap* map, const char* key, const char* value)
|
|
248
|
+
{
|
|
249
|
+
if (!map || !key) return;
|
|
250
|
+
/* Update existing */
|
|
251
|
+
for (uint32_t i = 0; i < map->count; i++) {
|
|
252
|
+
if (strcmp(map->keys[i], key) == 0) {
|
|
253
|
+
if (value) strncpy(map->values[i], value, ED_MAX_PROP_VALUE_LEN - 1);
|
|
254
|
+
else map->values[i][0] = '\0';
|
|
255
|
+
map->values[i][ED_MAX_PROP_VALUE_LEN - 1] = '\0';
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/* Add new */
|
|
260
|
+
if (map->count >= ED_RENDER_MAX_ATTRIBUTES) return;
|
|
261
|
+
strncpy(map->keys[map->count], key, ED_MAX_PROP_KEY_LEN - 1);
|
|
262
|
+
map->keys[map->count][ED_MAX_PROP_KEY_LEN - 1] = '\0';
|
|
263
|
+
if (value) strncpy(map->values[map->count], value, ED_MAX_PROP_VALUE_LEN - 1);
|
|
264
|
+
map->values[map->count][ED_MAX_PROP_VALUE_LEN - 1] = '\0';
|
|
265
|
+
map->count++;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static const char* ed_attr_map_get(EDAttributeMap* map, const char* key)
|
|
269
|
+
{
|
|
270
|
+
if (!map || !key) return NULL;
|
|
271
|
+
for (uint32_t i = 0; i < map->count; i++) {
|
|
272
|
+
if (strcmp(map->keys[i], key) == 0) return map->values[i];
|
|
273
|
+
}
|
|
274
|
+
return NULL;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
static void ed_attr_map_remove(EDAttributeMap* map, const char* key)
|
|
278
|
+
{
|
|
279
|
+
if (!map || !key) return;
|
|
280
|
+
for (uint32_t i = 0; i < map->count; i++) {
|
|
281
|
+
if (strcmp(map->keys[i], key) == 0) {
|
|
282
|
+
for (uint32_t j = i; j + 1 < map->count; j++) {
|
|
283
|
+
strncpy(map->keys[j], map->keys[j + 1], ED_MAX_PROP_KEY_LEN);
|
|
284
|
+
strncpy(map->values[j], map->values[j + 1], ED_MAX_PROP_VALUE_LEN);
|
|
285
|
+
}
|
|
286
|
+
map->count--;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* ================================================================
|
|
293
|
+
* DOM Manipulation Simulation
|
|
294
|
+
* ================================================================ */
|
|
295
|
+
|
|
296
|
+
/* These simulate DOM operations. In a real environment they would
|
|
297
|
+
* call into the actual DOM API or a native UI toolkit. */
|
|
298
|
+
|
|
299
|
+
static void ed_dom_set_attribute(void* node, const char* key, const char* value)
|
|
300
|
+
{
|
|
301
|
+
if (!node || !key) return;
|
|
302
|
+
g_renderer.total_dom_writes++;
|
|
303
|
+
/* Simulated: would call DOM API */
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
static void ed_dom_remove_attribute(void* node, const char* key)
|
|
307
|
+
{
|
|
308
|
+
if (!node || !key) return;
|
|
309
|
+
g_renderer.total_dom_writes++;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
static void ed_dom_set_style(void* node, const char* prop, const char* value)
|
|
313
|
+
{
|
|
314
|
+
if (!node || !prop) return;
|
|
315
|
+
g_renderer.total_dom_writes++;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
static void ed_dom_remove_style(void* node, const char* prop)
|
|
319
|
+
{
|
|
320
|
+
if (!node || !prop) return;
|
|
321
|
+
g_renderer.total_dom_writes++;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
static void ed_dom_insert_child(void* parent, void* child, uint32_t index)
|
|
325
|
+
{
|
|
326
|
+
if (!parent || !child) return;
|
|
327
|
+
g_renderer.total_dom_writes++;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
static void ed_dom_remove_child(void* parent, void* child)
|
|
331
|
+
{
|
|
332
|
+
if (!parent || !child) return;
|
|
333
|
+
g_renderer.total_dom_writes++;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
static void ed_dom_set_text_content(void* node, const char* text)
|
|
337
|
+
{
|
|
338
|
+
if (!node) return;
|
|
339
|
+
g_renderer.total_dom_writes++;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
static void ed_dom_add_class(void* node, const char* class_name)
|
|
343
|
+
{
|
|
344
|
+
if (!node || !class_name) return;
|
|
345
|
+
g_renderer.total_dom_writes++;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
static void ed_dom_remove_class(void* node, const char* class_name)
|
|
349
|
+
{
|
|
350
|
+
if (!node || !class_name) return;
|
|
351
|
+
g_renderer.total_dom_writes++;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
static const char* ed_dom_get_attribute(void* node, const char* key)
|
|
355
|
+
{
|
|
356
|
+
if (!node || !key) return NULL;
|
|
357
|
+
g_renderer.total_dom_reads++;
|
|
358
|
+
return NULL;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* ================================================================
|
|
362
|
+
* Style Diffing and Computation
|
|
363
|
+
* ================================================================ */
|
|
364
|
+
|
|
365
|
+
static EDComputedStyles* ed_compute_styles(EDVirtualNode* node)
|
|
366
|
+
{
|
|
367
|
+
if (!node) return NULL;
|
|
368
|
+
|
|
369
|
+
EDComputedStyles* styles = ed_computed_styles_create();
|
|
370
|
+
if (!styles) return NULL;
|
|
371
|
+
|
|
372
|
+
EDVirtualProp* prop = node->props;
|
|
373
|
+
while (prop) {
|
|
374
|
+
if (prop->is_style && strlen(prop->value) > 0) {
|
|
375
|
+
/* Parse inline style string: "prop1: val1; prop2: val2" */
|
|
376
|
+
char buf[ED_MAX_PROP_VALUE_LEN];
|
|
377
|
+
strncpy(buf, prop->value, ED_MAX_PROP_VALUE_LEN - 1);
|
|
378
|
+
buf[ED_MAX_PROP_VALUE_LEN - 1] = '\0';
|
|
379
|
+
|
|
380
|
+
char* decl = strtok(buf, ";");
|
|
381
|
+
while (decl) {
|
|
382
|
+
char* colon = strchr(decl, ':');
|
|
383
|
+
if (colon) {
|
|
384
|
+
*colon = '\0';
|
|
385
|
+
char* key = decl;
|
|
386
|
+
char* val = colon + 1;
|
|
387
|
+
/* Trim whitespace */
|
|
388
|
+
while (*key == ' ' || *key == '\t') key++;
|
|
389
|
+
while (*val == ' ' || *val == '\t') val++;
|
|
390
|
+
char* key_end = key + strlen(key) - 1;
|
|
391
|
+
while (key_end > key && (*key_end == ' ' || *key_end == '\t')) *key_end-- = '\0';
|
|
392
|
+
ed_computed_styles_set(styles, key, val);
|
|
393
|
+
}
|
|
394
|
+
decl = strtok(NULL, ";");
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
prop = prop->next;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return styles;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
static void ed_diff_styles(EDComputedStyles* old_styles, EDComputedStyles* new_styles,
|
|
404
|
+
void* state_node)
|
|
405
|
+
{
|
|
406
|
+
if (!state_node) return;
|
|
407
|
+
|
|
408
|
+
/* Remove styles that exist in old but not in new */
|
|
409
|
+
for (uint32_t i = 0; i < old_styles->count; i++) {
|
|
410
|
+
const char* val = ed_computed_styles_get(new_styles, old_styles->entries[i].property);
|
|
411
|
+
if (!val) {
|
|
412
|
+
ed_dom_remove_style(state_node, old_styles->entries[i].property);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* Set styles that are new or changed */
|
|
417
|
+
for (uint32_t i = 0; i < new_styles->count; i++) {
|
|
418
|
+
const char* old_val = ed_computed_styles_get(old_styles, new_styles->entries[i].property);
|
|
419
|
+
if (!old_val || strcmp(old_val, new_styles->entries[i].value) != 0) {
|
|
420
|
+
ed_dom_set_style(state_node, new_styles->entries[i].property,
|
|
421
|
+
new_styles->entries[i].value);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/* ================================================================
|
|
427
|
+
* Attribute Diffing
|
|
428
|
+
* ================================================================ */
|
|
429
|
+
|
|
430
|
+
static void ed_diff_attributes(EDVirtualNode* old_node, EDVirtualNode* new_node,
|
|
431
|
+
void* state_node)
|
|
432
|
+
{
|
|
433
|
+
if (!state_node) return;
|
|
434
|
+
|
|
435
|
+
EDAttributeMap old_attrs, new_attrs;
|
|
436
|
+
ed_attr_map_init(&old_attrs);
|
|
437
|
+
ed_attr_map_init(&new_attrs);
|
|
438
|
+
|
|
439
|
+
/* Collect old attributes */
|
|
440
|
+
EDVirtualProp* prop = old_node ? old_node->props : NULL;
|
|
441
|
+
while (prop) {
|
|
442
|
+
if (!prop->is_style && !prop->is_class && !prop->is_event &&
|
|
443
|
+
!prop->is_ref && !prop->is_key && !prop->is_children) {
|
|
444
|
+
ed_attr_map_set(&old_attrs, prop->key, prop->value);
|
|
445
|
+
}
|
|
446
|
+
prop = prop->next;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/* Collect new attributes */
|
|
450
|
+
prop = new_node ? new_node->props : NULL;
|
|
451
|
+
while (prop) {
|
|
452
|
+
if (!prop->is_style && !prop->is_class && !prop->is_event &&
|
|
453
|
+
!prop->is_ref && !prop->is_key && !prop->is_children) {
|
|
454
|
+
ed_attr_map_set(&new_attrs, prop->key, prop->value);
|
|
455
|
+
}
|
|
456
|
+
prop = prop->next;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/* Remove attributes present in old but not in new */
|
|
460
|
+
for (uint32_t i = 0; i < old_attrs.count; i++) {
|
|
461
|
+
if (!ed_attr_map_get(&new_attrs, old_attrs.keys[i])) {
|
|
462
|
+
ed_dom_remove_attribute(state_node, old_attrs.keys[i]);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* Set attributes that are new or changed */
|
|
467
|
+
for (uint32_t i = 0; i < new_attrs.count; i++) {
|
|
468
|
+
const char* old_val = ed_attr_map_get(&old_attrs, new_attrs.keys[i]);
|
|
469
|
+
if (!old_val || strcmp(old_val, new_attrs.values[i]) != 0) {
|
|
470
|
+
ed_dom_set_attribute(state_node, new_attrs.keys[i], new_attrs.values[i]);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/* ================================================================
|
|
476
|
+
* Class Diffing
|
|
477
|
+
* ================================================================ */
|
|
478
|
+
|
|
479
|
+
static void ed_diff_classes(EDVirtualNode* old_node, EDVirtualNode* new_node,
|
|
480
|
+
void* state_node)
|
|
481
|
+
{
|
|
482
|
+
if (!state_node) return;
|
|
483
|
+
|
|
484
|
+
EDClassList old_classes, new_classes;
|
|
485
|
+
ed_class_list_init(&old_classes);
|
|
486
|
+
ed_class_list_init(&new_classes);
|
|
487
|
+
|
|
488
|
+
EDVirtualProp* prop = old_node ? old_node->props : NULL;
|
|
489
|
+
while (prop) {
|
|
490
|
+
if (prop->is_class && strlen(prop->value) > 0) {
|
|
491
|
+
ed_class_list_parse(&old_classes, prop->value);
|
|
492
|
+
}
|
|
493
|
+
prop = prop->next;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
prop = new_node ? new_node->props : NULL;
|
|
497
|
+
while (prop) {
|
|
498
|
+
if (prop->is_class && strlen(prop->value) > 0) {
|
|
499
|
+
ed_class_list_parse(&new_classes, prop->value);
|
|
500
|
+
}
|
|
501
|
+
prop = prop->next;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/* Remove classes in old but not in new */
|
|
505
|
+
for (uint32_t i = 0; i < old_classes.count; i++) {
|
|
506
|
+
if (!ed_class_list_contains(&new_classes, old_classes.classes[i])) {
|
|
507
|
+
ed_dom_remove_class(state_node, old_classes.classes[i]);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* Add classes in new but not in old */
|
|
512
|
+
for (uint32_t i = 0; i < new_classes.count; i++) {
|
|
513
|
+
if (!ed_class_list_contains(&old_classes, new_classes.classes[i])) {
|
|
514
|
+
ed_dom_add_class(state_node, new_classes.classes[i]);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* ================================================================
|
|
520
|
+
* Renderer Initialization and Shutdown
|
|
521
|
+
* ================================================================ */
|
|
522
|
+
|
|
523
|
+
int ed_renderer_init(EDConfiguration* config)
|
|
524
|
+
{
|
|
525
|
+
if (g_renderer.is_rendering) {
|
|
526
|
+
ed_log_error("Cannot reinitialize renderer while rendering");
|
|
527
|
+
return ED_ERR_INVALID_STATE;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
memset(&g_renderer, 0, sizeof(EDRendererState));
|
|
531
|
+
|
|
532
|
+
if (config) {
|
|
533
|
+
memcpy(&g_renderer.config, config, sizeof(EDConfiguration));
|
|
534
|
+
} else {
|
|
535
|
+
/* Default configuration */
|
|
536
|
+
g_renderer.config.concurrent_mode = true;
|
|
537
|
+
g_renderer.config.strict_mode = false;
|
|
538
|
+
g_renderer.config.profiler_enabled = false;
|
|
539
|
+
g_renderer.config.suspense_enabled = true;
|
|
540
|
+
g_renderer.config.error_boundaries_enabled = true;
|
|
541
|
+
g_renderer.config.dev_tools_enabled = false;
|
|
542
|
+
g_renderer.config.trace_updates = false;
|
|
543
|
+
g_renderer.config.validate_props = false;
|
|
544
|
+
g_renderer.config.max_render_time_ms = 16;
|
|
545
|
+
g_renderer.config.max_commit_time_ms = 16;
|
|
546
|
+
g_renderer.config.scheduler_frame_budget_ms = ED_SCHEDULER_FRAME_BUDGET;
|
|
547
|
+
g_renderer.config.auto_batch_updates = true;
|
|
548
|
+
g_renderer.config.legacy_context_api = false;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/* Create memory pool */
|
|
552
|
+
g_renderer.pool = ed_pool_create(ED_INITIAL_POOL_SIZE);
|
|
553
|
+
if (!g_renderer.pool) {
|
|
554
|
+
ed_log_error("Failed to create renderer memory pool");
|
|
555
|
+
return ED_ERR_MEMORY;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/* Create scheduler */
|
|
559
|
+
g_renderer.scheduler = ed_scheduler_create();
|
|
560
|
+
if (!g_renderer.scheduler) {
|
|
561
|
+
ed_pool_destroy(g_renderer.pool);
|
|
562
|
+
ed_log_error("Failed to create renderer scheduler");
|
|
563
|
+
return ED_ERR_MEMORY;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/* Create event system */
|
|
567
|
+
g_renderer.event_system = ed_event_system_create();
|
|
568
|
+
if (!g_renderer.event_system) {
|
|
569
|
+
ed_scheduler_destroy(g_renderer.scheduler);
|
|
570
|
+
ed_pool_destroy(g_renderer.pool);
|
|
571
|
+
ed_log_error("Failed to create renderer event system");
|
|
572
|
+
return ED_ERR_MEMORY;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/* Create state manager */
|
|
576
|
+
g_renderer.state_manager = ed_state_manager_create();
|
|
577
|
+
if (!g_renderer.state_manager) {
|
|
578
|
+
ed_event_system_destroy(g_renderer.event_system);
|
|
579
|
+
ed_scheduler_destroy(g_renderer.scheduler);
|
|
580
|
+
ed_pool_destroy(g_renderer.pool);
|
|
581
|
+
ed_log_error("Failed to create renderer state manager");
|
|
582
|
+
return ED_ERR_MEMORY;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
g_renderer.is_rendering = false;
|
|
586
|
+
g_renderer.is_committing = false;
|
|
587
|
+
g_renderer.has_pending_work = false;
|
|
588
|
+
g_renderer.paint_requested = false;
|
|
589
|
+
g_renderer.pending_update_count = 0;
|
|
590
|
+
|
|
591
|
+
ed_log_info("Renderer initialized (v%s, concurrent=%s)",
|
|
592
|
+
ed_version(),
|
|
593
|
+
g_renderer.config.concurrent_mode ? "true" : "false");
|
|
594
|
+
|
|
595
|
+
return ED_OK;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
void ed_renderer_shutdown(void)
|
|
599
|
+
{
|
|
600
|
+
if (!g_renderer.pool) return; /* Not initialized */
|
|
601
|
+
|
|
602
|
+
ed_log_info("Renderer shutting down");
|
|
603
|
+
|
|
604
|
+
/* Flush any remaining work */
|
|
605
|
+
if (g_renderer.has_pending_work) {
|
|
606
|
+
ed_renderer_flush_sync();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/* Destroy batch queue */
|
|
610
|
+
EDRenderBatch* batch = g_renderer.batch_head;
|
|
611
|
+
while (batch) {
|
|
612
|
+
EDRenderBatch* next = batch->next;
|
|
613
|
+
ed_renderer_destroy_batch(batch);
|
|
614
|
+
batch = next;
|
|
615
|
+
}
|
|
616
|
+
g_renderer.batch_head = NULL;
|
|
617
|
+
g_renderer.batch_tail = NULL;
|
|
618
|
+
|
|
619
|
+
/* Destroy subsystems */
|
|
620
|
+
if (g_renderer.state_manager) {
|
|
621
|
+
ed_state_manager_destroy(g_renderer.state_manager);
|
|
622
|
+
g_renderer.state_manager = NULL;
|
|
623
|
+
}
|
|
624
|
+
if (g_renderer.event_system) {
|
|
625
|
+
ed_event_system_destroy(g_renderer.event_system);
|
|
626
|
+
g_renderer.event_system = NULL;
|
|
627
|
+
}
|
|
628
|
+
if (g_renderer.scheduler) {
|
|
629
|
+
ed_scheduler_destroy(g_renderer.scheduler);
|
|
630
|
+
g_renderer.scheduler = NULL;
|
|
631
|
+
}
|
|
632
|
+
if (g_renderer.pool) {
|
|
633
|
+
ed_pool_destroy(g_renderer.pool);
|
|
634
|
+
g_renderer.pool = NULL;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/* Clear fiber roots */
|
|
638
|
+
g_renderer.current_root = NULL;
|
|
639
|
+
g_renderer.work_in_progress_root = NULL;
|
|
640
|
+
g_renderer.pending_passive_effects = NULL;
|
|
641
|
+
g_renderer.pending_layout_effects = NULL;
|
|
642
|
+
g_renderer.pending_insertion_effects = NULL;
|
|
643
|
+
|
|
644
|
+
memset(&g_renderer, 0, sizeof(EDRendererState));
|
|
645
|
+
|
|
646
|
+
ed_log_info("Renderer shutdown complete");
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/* ================================================================
|
|
650
|
+
* Render Batch
|
|
651
|
+
* ================================================================ */
|
|
652
|
+
|
|
653
|
+
EDRenderBatch* ed_renderer_create_batch(void)
|
|
654
|
+
{
|
|
655
|
+
EDRenderBatch* batch = (EDRenderBatch*)calloc(1, sizeof(EDRenderBatch));
|
|
656
|
+
if (!batch) return NULL;
|
|
657
|
+
|
|
658
|
+
batch->operations = NULL;
|
|
659
|
+
batch->count = 0;
|
|
660
|
+
batch->start_time = ed_get_timestamp_ms();
|
|
661
|
+
batch->end_time = 0;
|
|
662
|
+
batch->dom_writes = 0;
|
|
663
|
+
batch->dom_reads = 0;
|
|
664
|
+
batch->next = NULL;
|
|
665
|
+
|
|
666
|
+
return batch;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
void ed_renderer_destroy_batch(EDRenderBatch* batch)
|
|
670
|
+
{
|
|
671
|
+
if (!batch) return;
|
|
672
|
+
|
|
673
|
+
/* Free all patch operations in the batch */
|
|
674
|
+
EDPatchOperation* op = batch->operations;
|
|
675
|
+
while (op) {
|
|
676
|
+
EDPatchOperation* next = op->next;
|
|
677
|
+
free(op);
|
|
678
|
+
op = next;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
free(batch);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
static void ed_renderer_enqueue_batch(EDRenderBatch* batch)
|
|
685
|
+
{
|
|
686
|
+
if (!batch) return;
|
|
687
|
+
batch->end_time = ed_get_timestamp_ms();
|
|
688
|
+
|
|
689
|
+
if (!g_renderer.batch_head) {
|
|
690
|
+
g_renderer.batch_head = batch;
|
|
691
|
+
g_renderer.batch_tail = batch;
|
|
692
|
+
} else {
|
|
693
|
+
g_renderer.batch_tail->next = batch;
|
|
694
|
+
g_renderer.batch_tail = batch;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
void ed_renderer_commit_batch(EDRenderBatch* batch)
|
|
699
|
+
{
|
|
700
|
+
if (!batch) return;
|
|
701
|
+
|
|
702
|
+
g_renderer.is_committing = true;
|
|
703
|
+
uint64_t commit_start = ed_get_timestamp_ms();
|
|
704
|
+
|
|
705
|
+
/* Apply all patch operations in the batch */
|
|
706
|
+
EDPatchOperation* op = batch->operations;
|
|
707
|
+
while (op) {
|
|
708
|
+
ed_patch_apply(op);
|
|
709
|
+
g_renderer.total_dom_writes++;
|
|
710
|
+
op = op->next;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
uint64_t commit_end = ed_get_timestamp_ms();
|
|
714
|
+
g_renderer.total_commit_time += (commit_end - commit_start);
|
|
715
|
+
g_renderer.total_commits++;
|
|
716
|
+
|
|
717
|
+
if (g_renderer.on_commit) {
|
|
718
|
+
g_renderer.on_commit(NULL, NULL); /* Would pass actual fiber and state_node */
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
g_renderer.is_committing = false;
|
|
722
|
+
|
|
723
|
+
ed_log_debug("Batch committed: %u ops, %llu ms",
|
|
724
|
+
batch->count,
|
|
725
|
+
(unsigned long long)(commit_end - commit_start));
|
|
726
|
+
|
|
727
|
+
ed_renderer_destroy_batch(batch);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/* ================================================================
|
|
731
|
+
* Update Scheduling
|
|
732
|
+
* ================================================================ */
|
|
733
|
+
|
|
734
|
+
uint32_t ed_renderer_schedule_update(EDFiberNode* fiber, EDSchedulerPriority priority)
|
|
735
|
+
{
|
|
736
|
+
if (!fiber) return 0;
|
|
737
|
+
|
|
738
|
+
ed_fiber_set_flag(fiber, ED_FIBER_FLAG_NEEDS_UPDATE);
|
|
739
|
+
g_renderer.pending_update_count++;
|
|
740
|
+
g_renderer.has_pending_work = true;
|
|
741
|
+
|
|
742
|
+
if (g_renderer.scheduler) {
|
|
743
|
+
uint32_t task_id = ed_scheduler_schedule(
|
|
744
|
+
g_renderer.scheduler, priority,
|
|
745
|
+
(EDSchedulerCallback)ed_renderer_process_updates,
|
|
746
|
+
fiber);
|
|
747
|
+
return task_id;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return 0;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
void ed_renderer_process_updates(void)
|
|
754
|
+
{
|
|
755
|
+
if (!g_renderer.has_pending_work) return;
|
|
756
|
+
|
|
757
|
+
g_renderer.is_rendering = true;
|
|
758
|
+
uint64_t render_start = ed_get_timestamp_ms();
|
|
759
|
+
|
|
760
|
+
/* Process scheduler work */
|
|
761
|
+
if (g_renderer.scheduler) {
|
|
762
|
+
ed_scheduler_work(g_renderer.scheduler);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/* Flush state updates */
|
|
766
|
+
if (g_renderer.state_manager) {
|
|
767
|
+
ed_state_flush_updates(g_renderer.state_manager);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
uint64_t render_end = ed_get_timestamp_ms();
|
|
771
|
+
g_renderer.total_render_time += (render_end - render_start);
|
|
772
|
+
g_renderer.total_renders++;
|
|
773
|
+
|
|
774
|
+
g_renderer.pending_update_count = 0;
|
|
775
|
+
g_renderer.has_pending_work = false;
|
|
776
|
+
g_renderer.is_rendering = false;
|
|
777
|
+
|
|
778
|
+
/* Request paint if needed */
|
|
779
|
+
if (g_renderer.paint_requested) {
|
|
780
|
+
ed_renderer_request_paint();
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
void ed_renderer_request_paint(void)
|
|
785
|
+
{
|
|
786
|
+
g_renderer.paint_requested = true;
|
|
787
|
+
g_renderer.next_paint_deadline = ed_get_timestamp_ms() +
|
|
788
|
+
g_renderer.config.scheduler_frame_budget_ms;
|
|
789
|
+
|
|
790
|
+
/* In a real implementation, this would requestAnimationFrame or similar */
|
|
791
|
+
ed_log_debug("Paint requested, deadline=%llu",
|
|
792
|
+
(unsigned long long)g_renderer.next_paint_deadline);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/* ================================================================
|
|
796
|
+
* Effect Flushing
|
|
797
|
+
* ================================================================ */
|
|
798
|
+
|
|
799
|
+
static void ed_renderer_run_effect_list(EDFiberNode* head, EDEffectType type)
|
|
800
|
+
{
|
|
801
|
+
EDFiberNode* fiber = head;
|
|
802
|
+
while (fiber) {
|
|
803
|
+
EDHookNode* hook = fiber->memoized_hooks;
|
|
804
|
+
while (hook) {
|
|
805
|
+
bool should_run = false;
|
|
806
|
+
switch (type) {
|
|
807
|
+
case ED_EFFECT_PASSIVE:
|
|
808
|
+
should_run = hook->is_passive && hook->has_effect;
|
|
809
|
+
break;
|
|
810
|
+
case ED_EFFECT_LAYOUT:
|
|
811
|
+
should_run = hook->is_layout && hook->has_effect;
|
|
812
|
+
break;
|
|
813
|
+
case ED_EFFECT_INSERTION:
|
|
814
|
+
should_run = hook->is_insertion && hook->has_effect;
|
|
815
|
+
break;
|
|
816
|
+
default:
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (should_run) {
|
|
821
|
+
/* Run cleanup first if present */
|
|
822
|
+
if (hook->cleanup) {
|
|
823
|
+
ed_hook_run_cleanup(hook);
|
|
824
|
+
}
|
|
825
|
+
ed_hook_run_effect(hook);
|
|
826
|
+
hook->has_effect = false;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
hook = hook->next;
|
|
830
|
+
}
|
|
831
|
+
fiber = fiber->next_effect;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
void ed_renderer_flush_passive_effects(void)
|
|
836
|
+
{
|
|
837
|
+
if (g_renderer.pending_passive_effects) {
|
|
838
|
+
EDFiberNode* effects = g_renderer.pending_passive_effects;
|
|
839
|
+
g_renderer.pending_passive_effects = NULL;
|
|
840
|
+
ed_renderer_run_effect_list(effects, ED_EFFECT_PASSIVE);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
void ed_renderer_flush_layout_effects(void)
|
|
845
|
+
{
|
|
846
|
+
if (g_renderer.pending_layout_effects) {
|
|
847
|
+
EDFiberNode* effects = g_renderer.pending_layout_effects;
|
|
848
|
+
g_renderer.pending_layout_effects = NULL;
|
|
849
|
+
ed_renderer_run_effect_list(effects, ED_EFFECT_LAYOUT);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
void ed_renderer_flush_sync(void)
|
|
854
|
+
{
|
|
855
|
+
/* Flush all pending work synchronously */
|
|
856
|
+
ed_renderer_process_updates();
|
|
857
|
+
|
|
858
|
+
/* Flush layout effects immediately */
|
|
859
|
+
ed_renderer_flush_layout_effects();
|
|
860
|
+
|
|
861
|
+
/* Flush passive effects */
|
|
862
|
+
ed_renderer_flush_passive_effects();
|
|
863
|
+
|
|
864
|
+
/* Process any remaining scheduler work */
|
|
865
|
+
if (g_renderer.scheduler) {
|
|
866
|
+
ed_scheduler_flush_all(g_renderer.scheduler);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/* Flush remaining state updates */
|
|
870
|
+
if (g_renderer.state_manager) {
|
|
871
|
+
ed_state_flush_updates(g_renderer.state_manager);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
ed_log_debug("Sync flush complete");
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/* ================================================================
|
|
878
|
+
* Complete Render Phase
|
|
879
|
+
* ================================================================ */
|
|
880
|
+
|
|
881
|
+
static void ed_renderer_render_root(EDFiberNode* root)
|
|
882
|
+
{
|
|
883
|
+
if (!root) return;
|
|
884
|
+
|
|
885
|
+
uint64_t start = ed_get_timestamp_ms();
|
|
886
|
+
|
|
887
|
+
/* Create work-in-progress tree */
|
|
888
|
+
g_renderer.work_in_progress_root = root;
|
|
889
|
+
g_renderer.is_rendering = true;
|
|
890
|
+
|
|
891
|
+
/* Process the fiber tree (would call into reconciliation) */
|
|
892
|
+
EDFiberNode* fiber = root;
|
|
893
|
+
while (fiber) {
|
|
894
|
+
ed_fiber_set_flag(fiber, ED_FIBER_FLAG_PERFORMED_WORK);
|
|
895
|
+
|
|
896
|
+
/* Move to child first (depth-first) */
|
|
897
|
+
if (fiber->child) {
|
|
898
|
+
fiber = fiber->child;
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/* No child, try sibling */
|
|
903
|
+
if (fiber->sibling) {
|
|
904
|
+
fiber = fiber->sibling;
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/* No sibling, go back up */
|
|
909
|
+
EDFiberNode* parent = fiber->return_node;
|
|
910
|
+
while (parent) {
|
|
911
|
+
if (parent->sibling) {
|
|
912
|
+
fiber = parent->sibling;
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
parent = parent->return_node;
|
|
916
|
+
}
|
|
917
|
+
if (!parent) fiber = NULL;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
uint64_t end = ed_get_timestamp_ms();
|
|
921
|
+
g_renderer.total_render_time += (end - start);
|
|
922
|
+
g_renderer.total_renders++;
|
|
923
|
+
|
|
924
|
+
g_renderer.is_rendering = false;
|
|
925
|
+
|
|
926
|
+
/* Swap current and WIP */
|
|
927
|
+
g_renderer.current_root = g_renderer.work_in_progress_root;
|
|
928
|
+
g_renderer.work_in_progress_root = NULL;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/* ================================================================
|
|
932
|
+
* Renderer Statistics
|
|
933
|
+
* ================================================================ */
|
|
934
|
+
|
|
935
|
+
static uint32_t ed_renderer_get_total_renders(void)
|
|
936
|
+
{
|
|
937
|
+
return g_renderer.total_renders;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
static uint32_t ed_renderer_get_total_commits(void)
|
|
941
|
+
{
|
|
942
|
+
return g_renderer.total_commits;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
static uint64_t ed_renderer_get_total_render_time(void)
|
|
946
|
+
{
|
|
947
|
+
return g_renderer.total_render_time;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
static uint64_t ed_renderer_get_total_commit_time(void)
|
|
951
|
+
{
|
|
952
|
+
return g_renderer.total_commit_time;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
static uint32_t ed_renderer_get_total_dom_writes(void)
|
|
956
|
+
{
|
|
957
|
+
return g_renderer.total_dom_writes;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
static uint32_t ed_renderer_get_total_dom_reads(void)
|
|
961
|
+
{
|
|
962
|
+
return g_renderer.total_dom_reads;
|
|
963
|
+
}
|