diff -r afe83f4642cc -r 67a590cf2d15 src/scene_node.c --- a/src/scene_node.c Tue Apr 07 20:18:39 2026 +0200 +++ b/src/scene_node.c Tue Apr 07 20:33:29 2026 +0200 @@ -45,17 +45,86 @@ } static void asc_scene_node_dict_add(AscSceneNodeDict *dict, AscSceneNode *node) { - if (dict == NULL || !asc_test_flag(node->flags, ASC_SCENE_NODE_HAS_NAME)) { + if (dict == NULL) return; + + // set a reference to the dict in the node + node->dict = dict; + + // also add child nodes which have a dedicated name + AscSceneNode *child = node->children; + while (child != NULL) { + asc_scene_node_dict_add(dict, child); + child = child->next; + } + + if (!asc_test_flag(node->flags, ASC_SCENE_NODE_HAS_NAME)) return; + + AscSceneNodeDictEntry *entry = cxMapGet(dict, node->name); + if (entry == NULL) { + entry = cxMapEmplace(dict, node->name); + entry->count = 1; + entry->node = cxMallocDefault(sizeof(AscSceneNode*)); + entry->node[0] = node; + asc_dprintf("Dict add: %" CX_PRIstr, + CX_SFMT(node->name)); return; } - // TODO: add a new dict entry or add the node the the list if it's not already present + if (cxReallocateArrayDefault(&entry->node, entry->count + 1, sizeof(AscSceneNode*))) { + asc_error("Reallocating dict entry for node %" CX_PRIstr " failed.", + CX_SFMT(node->name)); + } + entry->node[entry->count++] = node; + asc_dprintf("Dict add: %" CX_PRIstr " (now %z entries).", + CX_SFMT(node->name), entry->count); } -static void asc_scene_node_dict_remove(AscSceneNodeDict *dict, AscSceneNode *node) { - if (dict == NULL || !asc_test_flag(node->flags, ASC_SCENE_NODE_HAS_NAME)) { +static void asc_scene_node_dict_remove(AscSceneNode *node) { + // fast exit, node (and its children) do not belong to a dict + if (node->dict == NULL) return; + + // remove the children, first (use standard recursion, depth will be manageable) + // TODO: think about adding a flag for indicating if ANY child has a name, so that we can skip subtrees here + AscSceneNode *child = node->children; + while (child != NULL) { + asc_scene_node_dict_remove(child); + child = child->next; + } + + if (!asc_test_flag(node->flags, ASC_SCENE_NODE_HAS_NAME)) return; + + AscSceneNodeDictEntry *entry = cxMapGet(node->dict, node->name); + if (entry == NULL) return; + + // last node with that name, remove node + if (entry->count == 1) { + if (*entry->node == node) { + cxMapRemove(node->dict, node->name); + asc_dprintf("Dict remove: %" CX_PRIstr, + CX_SFMT(node->name)); + } else { + asc_error("BUG: Node %" CX_PRIstr " not found in its own dict.", + CX_SFMT(node->name)); + } return; } - // TODO: find entries for node's name and remove it from the list - erase the entry when it was the last + + // find index of the particular node + unsigned idx; + for (idx = 0 ; idx < entry->count ; idx++) { + if (entry->node[idx] == node) break; + } + if (idx == entry->count) { + asc_error("BUG: Node %" CX_PRIstr " not found in its own dict.", + CX_SFMT(node->name)); + return; + } + entry->node[idx] = entry->node[--entry->count]; + if (cxReallocateArrayDefault(&entry->node, entry->count, sizeof(AscSceneNode*))) { + asc_error("Shrinking dict for node %" CX_PRIstr " failed.", + CX_SFMT(node->name)); + } + asc_dprintf("Dict remove: %" CX_PRIstr " (%z others left).", + CX_SFMT(node->name), entry->count); } static CxTreeIterator asc_scene_node_iterator( @@ -166,7 +235,7 @@ } void asc_scene_node_name(AscSceneNode *node, const char *name) { - asc_scene_node_dict_remove(node->dict, node); + asc_scene_node_dict_remove(node); cx_strfree(&node->name); if (name == NULL) { asc_clear_flag(node->flags, ASC_SCENE_NODE_HAS_NAME); @@ -209,7 +278,7 @@ } void asc_scene_node_remove(AscSceneNode *node) { - asc_scene_node_dict_remove(node->dict, node); + asc_scene_node_dict_remove(node); cx_tree_remove( node, offsetof(AscSceneNode, parent),