You've already forked flecs_tests
Remove all file-scope mutable state so the codebase compiles cleanly as a single translation unit. State moved into structs owned by userdata_t: - group_index_ctx_t replaces g_group_by_id/g_group_by_id_cap - shape_pool_ctx_t replaces g_shape_pool_dirty/g_shape_data_dirty and g_shape_groups/g_shape_groups_count - panel_log_ctx_t wired through all subsystems explicitly - pipeline_ctx_t owns render pipeline handles - overlay_upload_state_t (per-buffer flags) replaces single bool New features piggybacking on the refactor: - Pen tool: click-to-place anchors, Catmull-Rom preview, finalize into Bezier shapes with control points - Edit mode: anchor/handle hit testing, dragging, pre-drag undo snapshots, dedicated GPU buffers for edit overlays - Frustum culling: spatial-grid-based viewport cull in draw_shapes with linear-scan fallback for oversized viewports - Log dedup: FNV-1a 64-bit hash to skip duplicate messages - Buffer shrink: halve draw buffers after 60 frames of low usage - Shape geometry hashing for instanced-draw vertex-buffer grouping - Group member_indices arrays with O(n) rebuild - Log ring expanded 64→256 entries, added log_filter Debug build: added --profiling-funcs and -sASSERTIONS flags.
151 lines
4.1 KiB
C
151 lines
4.1 KiB
C
#ifndef UTIL_H
|
|
#define UTIL_H
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
typedef struct vector_t {
|
|
uint8_t *data;
|
|
int count;
|
|
int capacity;
|
|
int stride;
|
|
} vector_t;
|
|
|
|
/**
|
|
* Zero-initialize a vector with the given element stride.
|
|
*
|
|
* @param v vector to initialize
|
|
* @param stride byte size of each element
|
|
*/
|
|
static void vec_init(vector_t *v, int stride) {
|
|
memset(v, 0, sizeof(*v));
|
|
v->stride = stride;
|
|
}
|
|
|
|
/**
|
|
* Grow the vector's backing array to at least min_capacity elements.
|
|
* Doubles capacity (starting at 8) or uses min_capacity, whichever is larger.
|
|
*
|
|
* @param v vector to grow
|
|
* @param min_capacity minimum element count required
|
|
*/
|
|
static void vec_grow(vector_t *v, int min_capacity) {
|
|
int new_cap = v->capacity ? v->capacity * 2 : 8;
|
|
if (new_cap < min_capacity) new_cap = min_capacity;
|
|
uint8_t *new_data = (uint8_t*) ALLOC(new_cap * v->stride);
|
|
if (!new_data) {
|
|
EM_ASM({ console.error("vec_grow: ALLOC failed for %d elements of %d bytes", $0, $1); },
|
|
new_cap, v->stride);
|
|
return;
|
|
}
|
|
if (v->data) {
|
|
memcpy(new_data, v->data, v->count * v->stride);
|
|
FREE(v->data);
|
|
}
|
|
v->data = new_data;
|
|
v->capacity = new_cap;
|
|
}
|
|
|
|
/**
|
|
* Append an uninitialized element to the end of the vector. Grows if needed.
|
|
*
|
|
* @param v vector to push into
|
|
* @return pointer to the new (uninitialized) element
|
|
*/
|
|
static void *vec_push(vector_t *v) {
|
|
if (v->count >= v->capacity) vec_grow(v, v->count + 1);
|
|
return v->data + (v->count++) * v->stride;
|
|
}
|
|
|
|
/**
|
|
* Remove the last element from the vector (decrements count, no free).
|
|
*
|
|
* @param v vector to pop from
|
|
*/
|
|
static void vec_pop(vector_t *v) {
|
|
if (v->count > 0) v->count--;
|
|
}
|
|
|
|
static void vec_remove_ordered(vector_t *v, int index) {
|
|
if (index < 0 || index >= v->count) return;
|
|
if (index < v->count - 1) {
|
|
memmove(v->data + index * v->stride,
|
|
v->data + (index + 1) * v->stride,
|
|
(v->count - index - 1) * v->stride);
|
|
}
|
|
v->count--;
|
|
}
|
|
|
|
// Remove `count` elements at given indices in a single compaction pass.
|
|
// Indices must be sorted in ascending order and must be valid.
|
|
static void vec_remove_ordered_bulk(vector_t *v, const int *indices, int count) {
|
|
if (count <= 0) return;
|
|
int write = indices[0];
|
|
for (int k = 0; k < count; k++) {
|
|
int gap_start = indices[k];
|
|
int gap_end = (k + 1 < count) ? indices[k + 1] : v->count;
|
|
int keep = gap_end - gap_start - 1;
|
|
if (keep > 0) {
|
|
memmove(v->data + write * v->stride,
|
|
v->data + (gap_start + 1) * v->stride,
|
|
(size_t)keep * (size_t)v->stride);
|
|
write += keep;
|
|
}
|
|
}
|
|
v->count -= count;
|
|
}
|
|
|
|
static void *vec_insert(vector_t *v, int index) {
|
|
if (index < 0 || index > v->count) return NULL;
|
|
if (v->count >= v->capacity) vec_grow(v, v->count + 1);
|
|
if (index < v->count) {
|
|
memmove(v->data + (index + 1) * v->stride,
|
|
v->data + index * v->stride,
|
|
(v->count - index) * v->stride);
|
|
}
|
|
v->count++;
|
|
return v->data + index * v->stride;
|
|
}
|
|
|
|
/**
|
|
* Remove the element at index by swapping in the last element (O(1)).
|
|
* Order is not preserved.
|
|
*
|
|
* @param v vector to remove from
|
|
* @param index index of the element to remove
|
|
*/
|
|
static void vec_remove(vector_t *v, int index) {
|
|
if (index < 0 || index >= v->count) return;
|
|
if (index < v->count - 1) {
|
|
memcpy(v->data + index * v->stride,
|
|
v->data + (v->count - 1) * v->stride,
|
|
v->stride);
|
|
}
|
|
v->count--;
|
|
}
|
|
|
|
/**
|
|
* Return a pointer to the element at index (no bounds check).
|
|
*
|
|
* @param v vector to access
|
|
* @param index element index
|
|
* @return pointer to the element
|
|
*/
|
|
static void *vec_get(vector_t *v, int index) {
|
|
return v->data + index * v->stride;
|
|
}
|
|
|
|
/**
|
|
* Free the backing array and reset the vector to empty.
|
|
*
|
|
* @param v vector to free
|
|
*/
|
|
static void vec_free(vector_t *v) {
|
|
if (v->data) FREE(v->data);
|
|
v->data = NULL;
|
|
v->count = 0;
|
|
v->capacity = 0;
|
|
}
|
|
|
|
#endif
|