diff --git a/docs/meta/coding-principles.md b/docs/meta/coding-principles.md new file mode 100644 index 0000000..2ae0bb9 --- /dev/null +++ b/docs/meta/coding-principles.md @@ -0,0 +1,2 @@ +- Always check bounds before performing mutations (i.e. CRUD operations). +- Prefer declaring out variables over malloc'd variables. diff --git a/src/packages/game/data/IZ_list.c b/src/packages/game/data/IZ_list.c index d6b868c..8c74bac 100644 --- a/src/packages/game/data/IZ_list.c +++ b/src/packages/game/data/IZ_list.c @@ -1,5 +1,70 @@ +#include #include "IZ_list.h" +void IZ_ListDoDeleteNode(IZ_ListNode* node) { + if (!(node && node->list)) { + // should we raise warnings here? + return; + } + + if (node->previous) { + node->previous->next = node->next; + } else { + if (node->next) { + node->next->previous = NULL; + } + node->list->root = node->next; + } + + if (node->list->length > 0) { + node->list->length -= 1; + } + + IZ_free(node); +} + +void IZ_ListDoAppendNode(IZ_List* list, void* node_value, IZ_ListNode** new_node_ref) { + IZ_ListNode* new_node = IZ_malloc(sizeof(IZ_ListNode)); + new_node->value = node_value; + new_node->next = NULL; + new_node->list = list; + new_node->list->length += 1; + + if (!(new_node->list->root)) { + new_node->previous = NULL; + new_node->list->root = new_node; + if (new_node_ref) { + *new_node_ref = new_node; + } + return; + } + + IZ_ListNode** cursor_node = &new_node->list->root; + while ((*cursor_node)->next) { + cursor_node = &(*cursor_node)->next; + } + + new_node->previous = *cursor_node; + (*cursor_node)->next = new_node; + if (new_node_ref) { + *new_node_ref = new_node; + } +} + +#ifdef IZ_DEBUG +void IZ_ListPrintNodeValues(IZ_List* list) { + list->iterator = &list->root; + u64 index = 0; + printf("\nlist@%p\n", list); + do { + printf(" %llu@%p:%u\n", index, *list->iterator, *((unsigned int*)(*list->iterator)->value)); + index += 1; + list->iterator = &(*list->iterator)->next; + } while (*list->iterator); + list->iterator = NULL; +} +#endif + /** * Initializes a list. * @param list - The list to initialize. @@ -16,7 +81,7 @@ void IZ_ListInitialize(IZ_List* list) { */ void IZ_ListTeardown(IZ_List* list) { while (list->length > 0) { - IZ_ListDeleteNode(list->root); + IZ_ListDoDeleteNode(list->root); } IZ_free(list); } @@ -24,29 +89,11 @@ void IZ_ListTeardown(IZ_List* list) { /** * Appends a node to the end of the list. * @param list - The list to append to. + * @param node_value - The value of the node to append. * @return Pointer to the newly created node. */ -IZ_ListNode** IZ_ListAppendNode(IZ_List* list, void* node_value) { - IZ_ListNode* new_node = IZ_malloc(sizeof(IZ_ListNode)); - new_node->value = node_value; - new_node->next = NULL; - new_node->list = list; - new_node->list->length += 1; - - if (!(list->root)) { - new_node->previous = NULL; - new_node->list->root = new_node; - return &new_node->list->root; - } - - IZ_ListNode *last_node = new_node->list->root; - while (last_node->next) { - last_node = last_node->next; - } - - new_node->previous = last_node; - last_node->next = new_node; - return &last_node->next; +void IZ_ListAppendNode(IZ_List* list, void* node_value, IZ_ListNode** new_node) { + return IZ_ListDoAppendNode(list, node_value, new_node); } /** @@ -54,27 +101,7 @@ IZ_ListNode** IZ_ListAppendNode(IZ_List* list, void* node_value) { * @param node - The node to delete. */ void IZ_ListDeleteNode(IZ_ListNode* node) { - if (!node) { - // should we raise warnings here? - return; - } - - if (node->previous) { - node->previous->next = node->next; - } else { - if (node->next) { - node->next->previous = NULL; - } - if (node->list) { - node->list->root = node->next; - } - } - - if (node->list && node->list->length > 0) { - node->list->length -= 1; - } - - IZ_free(node); + IZ_ListDoDeleteNode(node); } /** @@ -82,13 +109,16 @@ void IZ_ListDeleteNode(IZ_ListNode* node) { * @param list - The list to search. * @param filter - The filter to use to find the node. * @return Pointer to the node that matches the filter. + * @see IZ_ListFindPredicate + * @see IZ_ListFindAllNodes + * @note This function will set the list's iterator to the node that was found. Ensure that the iterator is previously + * set to an existing node in the list before calling this function to know where to begin the search. */ -IZ_ListNode** IZ_ListFindFirstNode(IZ_List* list, IZ_ListFindPredicate filter) { +void IZ_ListFindFirstNode(IZ_List* list, IZ_ListFindPredicate filter, IZ_ListNode** found_node) { if (!(list && list->root)) { - return NULL; + return; } - list->iterator = &list->root; u64 index = 0; do { if (!filter(list->iterator, index, list)) { @@ -96,8 +126,91 @@ IZ_ListNode** IZ_ListFindFirstNode(IZ_List* list, IZ_ListFindPredicate filter) { index += 1; continue; } - return list->iterator; + *found_node = *list->iterator; + return; } while (*list->iterator); - return NULL; +} + +/** + * Finds all nodes in the list that match the filter. + * @param list - The list to search. + * @param filter - The filter to use to find the node. + * @param found_nodes - The list to append the found nodes to. + * @return New list containing nodes that match the filter. + * @see IZ_ListFindPredicate + * @see IZ_ListFindFirstNode + * @note This function will set the list's iterator to the node that was found. Ensure that the iterator is previously + * set to an existing node in the list before calling this function to know where to begin the search. + */ +void IZ_ListFindAllNodes(IZ_List* list, IZ_ListFindPredicate filter, IZ_List* found_nodes) { + if (!(list && list->root)) { + return; + } + + list->iterator = &list->root; + u64 index; + for (index = 0; index < list->length; index += 1) { + if (filter(list->iterator, index, list)) { + IZ_ListDoAppendNode(found_nodes, (*list->iterator)->value, NULL); + } + + list->iterator = &((*list->iterator)->next); + } +} + +/** + * Inserts a node at the specified index. + * @param list - The list to append to. + * @param node_value - The value of the node to append. + * @param dest_index - The index to insert the node at. + * @return Pointer to the newly created node. + */ +void IZ_ListInsertNodeAtIndex(IZ_List* list, void* node_value, u64 dest_index, IZ_ListNode** new_node_ref) { + if (dest_index > list->length) { + // to consumer: check your bounds first! + return; + } + + if (dest_index == list->length) { + return IZ_ListDoAppendNode(list, node_value, new_node_ref); + } + + IZ_ListNode* new_node = IZ_malloc(sizeof(IZ_ListNode)); + new_node->value = node_value; + new_node->list = list; + + if (!(new_node->list->root)) { + new_node->previous = NULL; + new_node->next = NULL; + new_node->list->root = new_node; + if (new_node_ref) { + *new_node_ref = new_node; + } + return; + } + + IZ_ListNode** cursor_node = NULL; + u64 index; + for ( + index = 0, cursor_node = &new_node->list->root; + index < dest_index; + index += 1, cursor_node = &((*cursor_node)->next) + ); + + new_node->next = *cursor_node; + new_node->previous = (*cursor_node)->previous; + + if (dest_index == 0) { + new_node->list->root = new_node; + } else if (dest_index < list->length) { + (*cursor_node)->previous->next = new_node; + } else { + (*cursor_node)->next = new_node; + } + + new_node->list->length += 1; + if (new_node_ref) { + *new_node_ref = new_node; + } } diff --git a/src/packages/game/data/IZ_list.h b/src/packages/game/data/IZ_list.h index 1671a19..85f491a 100644 --- a/src/packages/game/data/IZ_list.h +++ b/src/packages/game/data/IZ_list.h @@ -10,8 +10,13 @@ struct IZ_List; * A node in a linked list. */ typedef struct IZ_ListNode { + /** + * The list that the node belongs to. + */ struct IZ_List* list; - + /** + * The previous node in the list. + */ struct IZ_ListNode* previous; /** * The value of the node. @@ -24,7 +29,7 @@ typedef struct IZ_ListNode { } IZ_ListNode; /** - * A singly-linked list. + * A doubly-linked list. */ typedef struct IZ_List { /** @@ -43,6 +48,10 @@ typedef struct IZ_List { typedef bool IZ_ListFindPredicate(IZ_ListNode**, u64, IZ_List*); +#ifdef IZ_DEBUG +void IZ_ListPrintNodeValues(IZ_List*); +#endif + /** * Initializes a list. */ @@ -57,7 +66,7 @@ void IZ_ListTeardown(IZ_List*); * Appends a node to the end of the list. * @return Pointer to the newly created node. */ -IZ_ListNode** IZ_ListAppendNode(IZ_List*, void*); +void IZ_ListAppendNode(IZ_List*, void*, IZ_ListNode**); /** * Deletes the first node in the list that matches the filter. @@ -67,7 +76,23 @@ void IZ_ListDeleteNode(IZ_ListNode*); /** * Finds the first node in the list that matches the filter. * @return Pointer to the node that matches the filter. + * @see IZ_ListFindPredicate + * @see IZ_ListFindAllNodes + */ +void IZ_ListFindFirstNode(IZ_List*, IZ_ListFindPredicate, IZ_ListNode**); + +/** + * Finds all nodes in the list that match the filter. + * @return New list containing nodes that match the filter. + * @see IZ_ListFindPredicate + * @see IZ_ListFindFirstNode + */ +void IZ_ListFindAllNodes(IZ_List*, IZ_ListFindPredicate, IZ_List*); + +/** + * Inserts a node at the specified index. + * @return Pointer to the newly created node. */ -IZ_ListNode** IZ_ListFindFirstNode(IZ_List*, IZ_ListFindPredicate); +void IZ_ListInsertNodeAtIndex(IZ_List*, void*, u64, IZ_ListNode**); #endif diff --git a/src/packages/game/data/data.test.c b/src/packages/game/data/data.test.c index 5952daa..9503e29 100644 --- a/src/packages/game/data/data.test.c +++ b/src/packages/game/data/data.test.c @@ -11,10 +11,18 @@ bool NodeExists2(IZ_ListNode** node, u64 _index, IZ_List* list) { return *((u64*) (*node)->value) == 69420; } +bool NodeExists3(IZ_ListNode** node, u64 _index, IZ_List* list) { + return *((u64*) (*node)->value) == 69069; +} + bool NodeDoesNotExist(IZ_ListNode** node, u64 _index, IZ_List* list) { return *((u64*) (*node)->value) == 55555; } +bool NodeHasOddValue(IZ_ListNode** node, u64 _index, IZ_List* list) { + return *((u64*) (*node)->value) % 2 == 1; +} + spec("data") { describe("list") { describe("Initialize") { @@ -44,9 +52,9 @@ spec("data") { static u64 value3 = 69069u; IZ_ListInitialize(&list); - IZ_ListAppendNode(&list, &value1); - IZ_ListAppendNode(&list, &value2); - IZ_ListAppendNode(&list, &value3); + IZ_ListAppendNode(&list, &value1, NULL); + IZ_ListAppendNode(&list, &value2, NULL); + IZ_ListAppendNode(&list, &value3, NULL); mock_mode(IZ_free, IZ_FREE_CALLS_TRACKED); } @@ -106,10 +114,12 @@ spec("data") { it("appends new node to empty list and sets it as root") { static u64 value = 69420u; - IZ_ListAppendNode(&list, &value); + static IZ_ListNode* check_inserted_node; + IZ_ListAppendNode(&list, &value, &check_inserted_node); u64 added_value = *((u64*) list.root->value); check(added_value == 69420u, "Node not properly appended. Value: %u", added_value); + check(*((u64*) check_inserted_node->value) == added_value, "Node value not properly set. Value: %u", added_value); check( mock_is_called(IZ_malloc), "Allocator function not called." @@ -119,8 +129,9 @@ spec("data") { it("appends new node to non-empty list") { static u64 value1 = 42069u; - - IZ_ListAppendNode(&list2, &value1); + static IZ_ListNode* check_inserted_node; + IZ_ListAppendNode(&list2, &value1, &check_inserted_node); + check(*((u64*) check_inserted_node->value) == value1, "Node value not properly set. Value: %u", value1); check(*((u64*) list2.root->next->value) == 42069u, "Node not properly appended."); check( @@ -138,8 +149,8 @@ spec("data") { before_each() { IZ_ListInitialize(&list); - IZ_ListAppendNode(&list, &value1); - IZ_ListAppendNode(&list, &value2); + IZ_ListAppendNode(&list, &value1, NULL); + IZ_ListAppendNode(&list, &value2, NULL); } after_each() { @@ -148,14 +159,16 @@ spec("data") { } it("retrieves first node satisfying the filter condition") { - static IZ_ListNode** node; - node = IZ_ListFindFirstNode(&list, NodeExists); - check(*((u64*) (*node)->value) == 42069u, "Existing node not found."); + static IZ_ListNode* node; + list.iterator = &list.root; + IZ_ListFindFirstNode(&list, NodeExists, &node); + check(*((u64*) node->value) == 42069u, "Existing node not found."); } it("returns NULL when all nodes do not satisfy the filter condition") { - static IZ_ListNode** node; - node = IZ_ListFindFirstNode(&list, NodeDoesNotExist); + static IZ_ListNode* node; + list.iterator = &list.root; + IZ_ListFindFirstNode(&list, NodeDoesNotExist, &node); check(node == NULL, "Non-existing node found."); } } @@ -169,9 +182,9 @@ spec("data") { static u64 value3 = 69069u; IZ_ListInitialize(&list); - IZ_ListAppendNode(&list, &value1); - IZ_ListAppendNode(&list, &value2); - IZ_ListAppendNode(&list, &value3); + IZ_ListAppendNode(&list, &value1, NULL); + IZ_ListAppendNode(&list, &value2, NULL); + IZ_ListAppendNode(&list, &value3, NULL); mock_mode(IZ_free, IZ_FREE_CALLS_TRACKED); } @@ -181,26 +194,181 @@ spec("data") { } it("removes first node satisfying the filter condition") { - IZ_ListNode** existing_node = IZ_ListFindFirstNode(&list, NodeExists); - IZ_ListDeleteNode(*existing_node); + list.iterator = &list.root; + static IZ_ListNode* existing_node; + static IZ_ListNode* check_node; + IZ_ListFindFirstNode(&list, NodeExists, &existing_node); + IZ_ListDeleteNode(existing_node); check( mock_is_called(IZ_free), "Deallocator function not called." ); - check( - IZ_ListFindFirstNode(&list, NodeExists2), - "Node supposed to be present in list is absent." - ); + check_node = NULL; + list.iterator = &list.root; + IZ_ListFindFirstNode(&list, NodeExists2, &check_node); + check(check_node, "Node supposed to be present in list is absent."); - check( - !IZ_ListFindFirstNode(&list, NodeExists), - "Deleted node still present in list." - ); + list.iterator = &list.root; + check_node = NULL; + IZ_ListFindFirstNode(&list, NodeExists, &check_node); + check(!check_node, "Deleted node still present in list."); check(list.length == 2, "Length mismatch."); } } + + describe("FindAllNodes") { + static IZ_List list; + static IZ_List out; + + before_each() { + static u64 value1 = 69420u; + static u64 value2 = 42069u; + static u64 value3 = 69069u; + + IZ_ListInitialize(&list); + IZ_ListAppendNode(&list, &value1, NULL); + IZ_ListAppendNode(&list, &value2, NULL); + IZ_ListAppendNode(&list, &value3, NULL); + + mock_mode(IZ_free, IZ_FREE_CALLS_UNTRACKED); + } + + before_each() { + IZ_ListInitialize(&out); + } + + after_each() { + IZ_ListTeardown(&out); + } + + after_each() { + IZ_ListTeardown(&list); + } + + it("finds all nodes satisfying the filter condition") { + list.iterator = &list.root; + IZ_ListFindAllNodes(&list, NodeHasOddValue, &out); + + check(out.length == 2, "Length mismatch. Length: %u", out.length); + + out.iterator = &out.root; + static IZ_ListNode* check_node; + check_node = NULL; + IZ_ListFindFirstNode(&out, NodeExists, &check_node); + check(check_node, "Node supposed to be present in list is absent."); + + out.iterator = &out.root; + check_node = NULL; + IZ_ListFindFirstNode(&out, NodeExists3, &check_node); + check(check_node, "Node supposed to be present in list is absent."); + + out.iterator = &out.root; + check_node = NULL; + IZ_ListFindFirstNode(&out, NodeExists2, &check_node); + check(!check_node, "Node not supposed to be present in list is present."); + } + } + + describe("InsertNodeAtIndex") { + static IZ_List list; + + before_each() { + static u64 value1 = 69420u; + static u64 value2 = 42069u; + static u64 value3 = 69069u; + + IZ_ListInitialize(&list); + IZ_ListAppendNode(&list, &value1, NULL); + IZ_ListAppendNode(&list, &value2, NULL); + IZ_ListAppendNode(&list, &value3, NULL); + + mock_mode(IZ_free, IZ_FREE_CALLS_UNTRACKED); + } + + after_each() { + IZ_ListTeardown(&list); + } + + it("inserts node at the beginning of the list") { + static u64 value = 1337u; + static IZ_ListNode* check_node; + IZ_ListInsertNodeAtIndex(&list, &value, 0, &check_node); + check(*((u64*) check_node->value) == value, "Incorrect value of inserted node."); + static IZ_ListNode* check_other_node; + + list.iterator = &list.root; + check_other_node = NULL; + IZ_ListFindFirstNode(&list, NodeExists, &check_other_node); + check(check_other_node, "Node supposed to be present in list is absent."); + + list.iterator = &list.root; + check_other_node = NULL; + IZ_ListFindFirstNode(&list, NodeExists2, &check_other_node); + check(check_other_node, "Node supposed to be present in list is absent."); + + list.iterator = &list.root; + check_other_node = NULL; + IZ_ListFindFirstNode(&list, NodeExists3, &check_other_node); + check(check_other_node, "Node supposed to be present in list is absent."); + + check(list.length == 4, "Length mismatch."); + //IZ_ListPrintNodeValues(&list); + } + + it("inserts node at the end of the list") { + static u64 value = 1337u; + static IZ_ListNode* check_node; + IZ_ListInsertNodeAtIndex(&list, &value, 3, &check_node); + check(*((u64*) check_node->value) == value, "Incorrect value of inserted node."); + + static IZ_ListNode* check_other_node; + list.iterator = &list.root; + check_other_node = NULL; + IZ_ListFindFirstNode(&list, NodeExists, &check_other_node); + check(check_other_node, "Node supposed to be present in list is absent."); + + list.iterator = &list.root; + check_other_node = NULL; + IZ_ListFindFirstNode(&list, NodeExists2, &check_other_node); + check(check_other_node, "Node supposed to be present in list is absent."); + + list.iterator = &list.root; + check_other_node = NULL; + IZ_ListFindFirstNode(&list, NodeExists3, &check_other_node); + check(check_other_node, "Node supposed to be present in list is absent."); + + check(list.length == 4, "Length mismatch."); + //IZ_ListPrintNodeValues(&list); + } + + it("inserts node in the middle of the list") { + static u64 value = 1337u; + static IZ_ListNode* check_node; + IZ_ListInsertNodeAtIndex(&list, &value, 1, &check_node); + check(*((u64*) check_node->value) == value, "Incorrect value of inserted node."); + + static IZ_ListNode* check_other_node; + list.iterator = &list.root; + check_other_node = NULL; + IZ_ListFindFirstNode(&list, NodeExists, &check_other_node); + check(check_other_node, "Node supposed to be present in list is absent."); + + list.iterator = &list.root; + check_other_node = NULL; + IZ_ListFindFirstNode(&list, NodeExists2, &check_other_node); + check(check_other_node, "Node supposed to be present in list is absent."); + + list.iterator = &list.root; + check_other_node = NULL; + IZ_ListFindFirstNode(&list, NodeExists3, &check_other_node); + check(check_other_node, "Node supposed to be present in list is absent."); + + check(list.length == 4, "Length mismatch."); + //IZ_ListPrintNodeValues(&list); + } + } } } diff --git a/src/packages/game/memory/IZ_pool.c b/src/packages/game/memory/IZ_pool.c index 6162896..5d624cb 100644 --- a/src/packages/game/memory/IZ_pool.c +++ b/src/packages/game/memory/IZ_pool.c @@ -27,15 +27,16 @@ IZ_PoolItem* IZ_PoolAllocate(IZ_Pool* pool, IZ_PoolAllocationArgs args) { } void* pointer = &pool->memory[pool->next_address]; - IZ_ListNode** new_item = IZ_ListAppendNode(&pool->items, &(IZ_PoolItem) { + IZ_ListNode* new_item; + IZ_ListAppendNode(&pool->items, &(IZ_PoolItem) { .pointer = pointer, .args = args, .pool = pool, - }); + }, &new_item); pool->next_address = (pool->next_address + args.size) % POOL_MAX_SIZE; pool->allocated_memory += args.size; - return (*new_item)->value; + return new_item->value; } bool IZ_PoolGetSameItem(IZ_ListNode** node, u64 _index, IZ_List* list) { @@ -43,9 +44,10 @@ bool IZ_PoolGetSameItem(IZ_ListNode** node, u64 _index, IZ_List* list) { } void IZ_PoolDeallocate(IZ_PoolItem* item) { - IZ_ListNode** node = IZ_ListFindFirstNode(&item->pool->items, IZ_PoolGetSameItem); + IZ_ListNode* node; + IZ_ListFindFirstNode(&item->pool->items, IZ_PoolGetSameItem, &node); if (node) { - IZ_ListDeleteNode(*node); + IZ_ListDeleteNode(node); } } diff --git a/src/packages/game/memory/memory.test.c b/src/packages/game/memory/memory.test.c index 5355b28..e5fd428 100644 --- a/src/packages/game/memory/memory.test.c +++ b/src/packages/game/memory/memory.test.c @@ -151,7 +151,7 @@ spec("memory") { "Free memory not properly utilized." ); - void* p4 = IZ_PoolAllocate(&pool, (IZ_PoolAllocationArgs){ + void* p4 = IZ_PoolAllocate(&pool, (IZ_PoolAllocationArgs) { .size = sizeof(u8), .priority = 0, .timestamp = 5, @@ -159,7 +159,7 @@ spec("memory") { check( p4 == p3 + sizeof(u8), - "Free memory not properly utilized." + "Free memory not properly allocated." ); } }