From 849a9f0059a367df47cf99ede638f74fcab613ae Mon Sep 17 00:00:00 2001 From: En Yi Date: Sun, 29 Dec 2024 16:13:35 +0800 Subject: [PATCH] Implement component pools --- CMakeLists.txt | 1 + engine/CMakeLists.txt | 10 +- engine/base/memory_arena.c | 6 +- engine/engine_conf.h | 2 + engine/memory.c | 83 ++++++++++++-- engine/memory.h | 50 ++++++++- engine/tests/base/manual/CMakeLists.txt | 9 ++ engine/tests/base/manual/comp_sample.c | 31 ++++++ engine/tests/base/unit/CMakeLists.txt | 12 +++ engine/tests/base/unit/comp_pool_unit.c | 138 ++++++++++++++++++++++++ 10 files changed, 326 insertions(+), 16 deletions(-) create mode 100644 engine/tests/base/manual/comp_sample.c create mode 100644 engine/tests/base/unit/comp_pool_unit.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a4a1e92..3ded991 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ cmake_minimum_required(VERSION 3.22.1) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project(${PROJECT_NAME} C CXX) set(CMAKE_C_STANDARD 17) +set(RAYLIB_DIR /usr/local/lib CACHE FILEPATH "directory to Raylib") if (EMSCRIPTEN) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index fc31650..00475ce 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -6,11 +6,19 @@ add_library(engine target_include_directories(engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + ${RAYLIB_DIR}/include +) + +target_link_directories(engine + PUBLIC + ${RAYLIB_DIR}/lib ) target_link_libraries(engine PUBLIC - cc + base + raylib + m ) #if (BUILD_TESTING) diff --git a/engine/base/memory_arena.c b/engine/base/memory_arena.c index 1e3b4be..5e9a867 100644 --- a/engine/base/memory_arena.c +++ b/engine/base/memory_arena.c @@ -74,10 +74,10 @@ void mem_arena_print() printf("O1heap Memory Arena Info\n"); printf("--------------------\n"); printf("Capacity: %.3f MB\n", diag.capacity * 1.0 / 1024 / 1024); - printf("Allocated: %lu B\n", diag.allocated); - printf("Peak allocated: %lu B\n", diag.peak_allocated); + printf("Allocated: %.3f MB\n", diag.allocated * 1.0 /1024/1024); + printf("Peak allocated: %.3f MB\n", diag.peak_allocated * 1.0 /1024/1024); printf("Peak request: %lu\n", diag.peak_request_size); - printf("OOM count: %lu\n", diag.oom_count); + printf("OOM count: %lu\n\n", diag.oom_count); } size_t mem_arena_get_allocated(void) diff --git a/engine/engine_conf.h b/engine/engine_conf.h index e1153c7..f4ef7b2 100644 --- a/engine/engine_conf.h +++ b/engine/engine_conf.h @@ -1,6 +1,8 @@ #ifndef _ENGINE_CONF_H #define _ENGINE_CONF_H +#define MEMORY_ARENA_SIZE_MB 16 + // Take care tuning these params. Web build doesn't work // if memory used too high #define MAX_SCENES_TO_RENDER 8 diff --git a/engine/memory.c b/engine/memory.c index 7fc2496..e287a62 100644 --- a/engine/memory.c +++ b/engine/memory.c @@ -1,20 +1,81 @@ #include "memory.h" -//#include "cmc/utl/futils.h" -/* Function implementation */ -//#include "cmc/treeset/code.h" +#include "engine_conf.h" +#include +#include -void init_memory_system(void) { - for (uint32_t i = 0; i < mem_impl.n_components; ++i) { - //mem_impl.comp_mempools[i].free_set = idxSet_new( - // &(struct idxSet_fval){ .cmp = cmc_u32_cmp, NULL} - //); +void init_memory_system(void) +{ + mem_arena_init(MEMORY_ARENA_SIZE_MB); + for (uint32_t i = 0; i < mem_impl.n_components; ++i) + { + memset(mem_impl.comp_mempools[i].buffer, 0, mem_impl.comp_mempools[i].elem_size * mem_impl.comp_mempools[i].capacity); cc_init(&mem_impl.comp_mempools[i].free_set); + for (size_t j = 0; j < mem_impl.comp_mempools[i].capacity; ++j) + { + cc_insert(&mem_impl.comp_mempools[i].free_set, j); + } } } -void free_memory_system(void) { - for (uint32_t i = 0; i < mem_impl.n_components; ++i) { - //idxSet_free(mem_impl.comp_mempools[i].free_set); +void free_memory_system(void) +{ + for (uint32_t i = 0; i < mem_impl.n_components; ++i) + { cc_cleanup(&mem_impl.comp_mempools[i].free_set); } + mem_arena_deinit(); +} + +void print_memory_info() +{ + mem_arena_print(); + printf("Component Free Pool\n"); + printf("===================\n"); + for (size_t i = 0; i < mem_impl.n_components; ++i) + { + printf( + "%lu: %lu\n", + i, cc_size(&mem_impl.comp_mempools[i].free_set) + ); + } + printf("===================\n"); +} + +void* new_component_generic(unsigned int comp_type, unsigned int* idx) +{ + assert(comp_type < N_COMPONENTS); + assert(comp_type < mem_impl.n_components); + + if (cc_size(&mem_impl.comp_mempools[comp_type].free_set) == 0) return NULL; + + *idx = *cc_first(&mem_impl.comp_mempools[comp_type].free_set); + cc_erase(&mem_impl.comp_mempools[comp_type].free_set, *idx); + + void* comp = mem_impl.comp_mempools[comp_type].buffer + + (*idx * mem_impl.comp_mempools[comp_type].elem_size); + memset(comp, 0, mem_impl.comp_mempools[comp_type].elem_size); + return comp; +} + +void* get_component_generic(unsigned int comp_type, unsigned int idx) +{ + void * comp = NULL; + assert(comp_type < mem_impl.n_components); + if (idx >= mem_impl.comp_mempools->capacity) return NULL; + + if (cc_get(&mem_impl.comp_mempools[comp_type].free_set, idx) == NULL) + { + comp = mem_impl.comp_mempools[comp_type].buffer + (idx * mem_impl.comp_mempools[comp_type].elem_size); + } + return comp; +} + +void free_component_generic(unsigned int comp_type, unsigned int idx) +{ + assert(comp_type < N_COMPONENTS); + // This just free the component from the memory pool + assert(comp_type < mem_impl.n_components); + if (idx >= mem_impl.comp_mempools->capacity) return; + + cc_get_or_insert(&mem_impl.comp_mempools[comp_type].free_set, idx); } diff --git a/engine/memory.h b/engine/memory.h index e96361d..b2167bd 100644 --- a/engine/memory.h +++ b/engine/memory.h @@ -3,20 +3,68 @@ #include #define CC_NO_SHORT_NAMES -#include "cc.h" +#include "base.h" typedef struct memoryPool { //struct idxSet* free_set; + void * const buffer; + const unsigned long capacity; + const unsigned long elem_size; cc_oset( uint32_t ) free_set; } memoryPool; void init_memory_system(void); void free_memory_system(void); +void* new_component_generic(unsigned int comp_type, unsigned int* idx); +void* get_component_generic(unsigned int comp_type, unsigned int idx); +void free_component_generic(unsigned int comp_type, unsigned int idx); + +void print_memory_info(); extern struct memoryImpl { memoryPool* comp_mempools; uint32_t n_components; } mem_impl; +#define DEFINE_COMPONENT_HEADERS(name, comp_id, type) \ + static const unsigned int CID_##name = comp_id; \ + static inline type* new_component_##name(unsigned int* idx) { \ + return (type*)new_component_generic(comp_id, idx); \ + }\ + static inline type* get_component_##name(unsigned int idx) { \ + return (type*)get_component_generic(comp_id, idx); \ + }\ + static inline void free_component_##name(unsigned int idx) { \ + free_component_generic(comp_id, idx); \ + }\ + + +#define DEFINE_COMP_MEMPOOL_BUF(type, cap) \ + static type type##_buf[cap]; \ + const unsigned long type##_CNT = cap; \ + +#define BEGIN_DEFINE_COMP_MEMPOOL \ + static memoryPool _memPool[] = { + +#define ADD_COMP_MEMPOOL(name, type) \ + [CID_##name] = (memoryPool){\ + .buffer = type##_buf,\ + .capacity = type##_CNT,\ + .elem_size = sizeof(type),\ + 0\ + }, \ + +#define END_DEFINE_COMP_MEMPOOL\ + }; \ + struct memoryImpl mem_impl = { \ + .comp_mempools = _memPool, \ + .n_components = USER_N_COMPONENTS, \ + };\ + +#define STATIC_ASSERT_COMP_POOL \ + static_assert( \ + (sizeof(_memPool) / sizeof(_memPool[0])) == USER_N_COMPONENTS, "Insufficnet component pool definition" \ + ); \ + #endif // ENGINE_MEMORY_H diff --git a/engine/tests/base/manual/CMakeLists.txt b/engine/tests/base/manual/CMakeLists.txt index 52c5e32..7ca88e4 100644 --- a/engine/tests/base/manual/CMakeLists.txt +++ b/engine/tests/base/manual/CMakeLists.txt @@ -14,3 +14,12 @@ target_link_libraries(ccTest PRIVATE set_target_properties(ccTest PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/manual) + +add_executable(compSample comp_sample.c) +target_link_libraries(compSample PRIVATE + engine +) +set_target_properties(compSample + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/manual) + diff --git a/engine/tests/base/manual/comp_sample.c b/engine/tests/base/manual/comp_sample.c new file mode 100644 index 0000000..fc80d6e --- /dev/null +++ b/engine/tests/base/manual/comp_sample.c @@ -0,0 +1,31 @@ +#include "memory.h" +#include + +typedef struct dummyComp { + unsigned int num; +}dummyComp; +DEFINE_COMPONENT_HEADERS(dummy, 0, dummyComp) + +DEFINE_COMP_MEMPOOL_BUF(dummyComp, 16) + +#define USER_N_COMPONENTS 1 +BEGIN_DEFINE_COMP_MEMPOOL +ADD_COMP_MEMPOOL(dummy, dummyComp) +END_DEFINE_COMP_MEMPOOL + + +int main(void) { + init_memory_system(); + STATIC_ASSERT_COMP_POOL; + + print_memory_info(); + unsigned int idx = 0; + dummyComp* comp = new_component_dummy(&idx); + assert(comp != NULL); + print_memory_info(); + dummyComp* getcomp = get_component_dummy(idx); + assert(getcomp == comp); + free_component_dummy(idx); + print_memory_info(); + free_memory_system(); +} diff --git a/engine/tests/base/unit/CMakeLists.txt b/engine/tests/base/unit/CMakeLists.txt index a206b31..f3d7c19 100644 --- a/engine/tests/base/unit/CMakeLists.txt +++ b/engine/tests/base/unit/CMakeLists.txt @@ -8,4 +8,16 @@ set_target_properties(MemArenaUnitTest PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/unit) +add_executable(CompPoolUnitTest comp_pool_unit.c) +#target_compile_features(MemPoolTest PRIVATE c_std_99) +target_link_libraries(CompPoolUnitTest + PRIVATE + cmocka + engine +) +set_target_properties(CompPoolUnitTest + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/unit) + add_test(NAME MemArenaUnitTest COMMAND MemArenaUnitTest) +add_test(NAME CompPoolUnitTest COMMAND CompPoolUnitTest) diff --git a/engine/tests/base/unit/comp_pool_unit.c b/engine/tests/base/unit/comp_pool_unit.c new file mode 100644 index 0000000..4a5b159 --- /dev/null +++ b/engine/tests/base/unit/comp_pool_unit.c @@ -0,0 +1,138 @@ +#include "memory.h" +#include + +#include +#include +#include +#include +#include + +typedef struct dummyComp { + unsigned int num; +}dummyComp; +DEFINE_COMPONENT_HEADERS(dummy, 0, dummyComp) + +DEFINE_COMP_MEMPOOL_BUF(dummyComp, 16) + +#define USER_N_COMPONENTS 1 +BEGIN_DEFINE_COMP_MEMPOOL +ADD_COMP_MEMPOOL(dummy, dummyComp) +END_DEFINE_COMP_MEMPOOL + + +/** + * Component Pool Test + */ + +static int setup_mem_pool(void** state) +{ + (void)state; + + init_memory_system(); + return 0; +} + +static int teardown_mem_pool(void** state) +{ + (void)state; + + free_memory_system(); + return 0; +} + +static void test_simple_component_alloc(void **state) +{ + // Typical usage + (void)state; + + unsigned int idx = 0; + dummyComp* comp = new_component_dummy(&idx); + assert_non_null(comp); + dummyComp* check = get_component_dummy(idx); + assert_ptr_equal(comp, check); + free_component_dummy(idx); + check = get_component_dummy(idx); + assert_null(check); +} + +static void test_component_OOB_get(void **state) +{ + // OOB indice will result in NULL for get function + (void)state; + + dummyComp* comp = get_component_dummy(16); + assert_null(comp); + comp = get_component_dummy(65535); + assert_null(comp); +} + +static void test_component_reuse(void **state) +{ + // Component pool must reuse idx ASAP + (void)state; + + unsigned int idx = 0; + dummyComp* comp = new_component_dummy(&idx); + assert_non_null(comp); + + free_component_dummy(idx); + + unsigned int new_idx = 0; + new_component_dummy(&new_idx); + assert_int_equal(new_idx, idx); +} + +static void test_component_double_free(void **state) +{ + // A double free is okay and does not affect anything + // Double freeing the same idx will not cause it to + // be allocated twice + (void)state; + + unsigned int idx = 0; + dummyComp* comp = new_component_dummy(&idx); + assert_non_null(comp); + + free_component_dummy(idx); + dummyComp* check = get_component_dummy(idx); + assert_null(check); + + free_component_dummy(idx); + check = get_component_dummy(idx); + assert_null(check); + + unsigned int new_idx = 0; + new_component_dummy(&new_idx); + new_component_dummy(&new_idx); + assert_int_not_equal(new_idx, idx); +} + +static void test_component_new_clear(void** state) { + (void)state; + unsigned int idx = 0; + dummyComp* comp = new_component_dummy(&idx); + assert_non_null(comp); + assert_int_equal(comp->num, 0); + + comp->num = 16; + dummyComp* check = get_component_dummy(idx); + assert_int_equal(check->num, comp->num); + + free_component_dummy(idx); + comp = new_component_dummy(&idx); + assert_non_null(comp); + assert_int_equal(comp->num, 0); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_simple_component_alloc, setup_mem_pool, teardown_mem_pool), + cmocka_unit_test_setup_teardown(test_component_reuse, setup_mem_pool, teardown_mem_pool), + cmocka_unit_test_setup_teardown(test_component_OOB_get, setup_mem_pool, teardown_mem_pool), + cmocka_unit_test_setup_teardown(test_component_new_clear, setup_mem_pool, teardown_mem_pool), + cmocka_unit_test_setup_teardown(test_component_double_free, setup_mem_pool, teardown_mem_pool), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +}