commit 17aae0617c6a7236b084fd67a854ab2ca626b80e Author: En Yi Date: Sat Nov 26 18:11:51 2022 +0800 First Commit of HATPC remake Internal Changelog: - Implement Basic ECS - Tag system not yet implemented - Use SC for common data structures diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfd2159 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.cache/ +build/ +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..676313d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +set(PROJECT_NAME HATPC_remake) + +cmake_minimum_required(VERSION 3.22.1) +project(${PROJECT_NAME} C) +set(CMAKE_C_STANDARD 99) + +add_subdirectory(sc) +add_executable(${PROJECT_NAME} + main.c + entManager.c + mempool.c +) + +target_include_directories(${PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) +target_link_libraries(${PROJECT_NAME} + sc_queue + sc_map +) diff --git a/Conventions.txt b/Conventions.txt new file mode 100644 index 0000000..bc550ab --- /dev/null +++ b/Conventions.txt @@ -0,0 +1,14 @@ +Trivial Variables: i,n,c,etc... (Only one letter. If one letter isn't clear, then make it a Local Variable) +Local Variables: snake_case +Global Variables: g_snake_case +Const Variables: ALL_CAPS +Pointer Variables: add a p_ to the prefix. For global variables it would be gp_var, for local variables p_var, for const variables p_VAR. If far pointers are used then use an fp_ instead of p_. +Typedef Values: suffix _t + +Structs: PascalCase +Enums: PascalCase +Struct Member Variables: camelCase +Enum Values: ALL_CAPS +Functions: snake_case +Macros: ALL_CAPS + diff --git a/components.h b/components.h new file mode 100644 index 0000000..2f60f2f --- /dev/null +++ b/components.h @@ -0,0 +1,20 @@ +#ifndef __COMPONENTS_H +#define __COMPONENTS_H + +// TODO: Look at sc to use macros to auto generate functions + +#define N_COMPONENTS 1 +enum ComponentEnum +{ + CBBOX_COMP_T, +}; +typedef enum ComponentEnum ComponentEnum_t; + +typedef struct _CBBox_t +{ + int x; + int y; + int width; + int height; +}CBBox_t; +#endif // __COMPONENTS_H diff --git a/entManager.c b/entManager.c new file mode 100644 index 0000000..418a833 --- /dev/null +++ b/entManager.c @@ -0,0 +1,121 @@ +#include "entManager.h" + +void init_entity_manager(EntityManager_t *p_manager) +{ + sc_map_init_64v(&p_manager->entities, MAX_COMP_POOL_SIZE, 0); + for (size_t i=0; icomponent_map + i, MAX_COMP_POOL_SIZE, 0); + } + sc_queue_init(&p_manager->to_add); + sc_queue_init(&p_manager->to_remove); +} +; +void update_entity_manager(EntityManager_t *p_manager) +{ + // This will only update the entity map of the manager + // It does not make new entities, but will free entity + // New entities are assigned during add_entity + unsigned long e_idx; + unsigned long comp_type_idx; + unsigned long comp_idx; + + sc_queue_foreach (&p_manager->to_remove, e_idx) + { + Entity_t *p_entity = (Entity_t *)sc_map_get_64v(&p_manager->entities, e_idx); + if (!p_entity) continue; + sc_map_foreach (&p_entity->components, comp_type_idx, comp_idx) + { + free_component_to_mempool((ComponentEnum_t)comp_type_idx, comp_idx); + sc_map_del_64v(&p_manager->component_map[comp_type_idx], comp_idx); + } + free_entity_to_mempool(e_idx); + sc_map_del_64v(&p_manager->entities, e_idx); + } + sc_queue_clear(&p_manager->to_remove); + sc_queue_foreach (&p_manager->to_add, e_idx) + { + Entity_t *ent = get_entity_wtih_id(e_idx); + sc_map_put_64v(&p_manager->entities, e_idx, (void *)ent); + } + sc_queue_clear(&p_manager->to_add); + +} + +void clear_entity_manager(EntityManager_t *p_manager) +{ + unsigned long e_id; + Entity_t *p_ent; + sc_map_foreach (&p_manager->entities, e_id, p_ent) + { + remove_entity(p_manager, e_id); + } + update_entity_manager(p_manager); +} + +void free_entity_manager(EntityManager_t *p_manager) +{ + clear_entity_manager(p_manager); + sc_map_term_64v(&p_manager->entities); + for (size_t i=0; icomponent_map + i); + } + sc_queue_term(&p_manager->to_add); + sc_queue_term(&p_manager->to_remove); +} + +Entity_t *add_entity(EntityManager_t *p_manager, const char *tag) +{ + unsigned long e_idx = 0; + Entity_t * p_ent = new_entity_from_mempool(&e_idx); + if (p_ent) + { + sc_queue_add_last(&p_manager->to_add, e_idx); + } + return p_ent; +} + +void remove_entity(EntityManager_t *p_manager, unsigned long id) +{ + unsigned long comp_type, comp_id; + + Entity_t *p_entity = sc_map_get_64v(&p_manager->entities, id); + if(!sc_map_found(&p_manager->entities)) return; + // This only marks the entity for deletion + // Does not free entity. This is done during the update + p_entity->m_alive = false; + sc_queue_add_last(&p_manager->to_remove, id); +} + +// Components are not expected to be removed +// So, no need to extra steps to deal with iterator invalidation +void *add_component(EntityManager_t *p_manager, Entity_t *p_entity, ComponentEnum_t comp_type) +{ + unsigned long comp_type_idx = (unsigned long)comp_type; + unsigned long comp_idx = 0; + void * p_comp = new_component_from_mempool(comp_type, &comp_idx); + if (p_comp) + { + sc_map_put_64(&p_entity->components, comp_type_idx, comp_idx); + sc_map_put_64v(&p_manager->component_map[comp_type_idx], comp_idx, p_comp); + } + return p_comp; +} + +void *get_component(EntityManager_t *p_manager, Entity_t *p_entity, ComponentEnum_t comp_type) +{ + unsigned long comp_type_idx = (unsigned long)comp_type; + unsigned long comp_idx = sc_map_get_64(&p_entity->components, comp_type_idx); + if (!sc_map_found(&p_entity->components)) return NULL; + return sc_map_get_64v(&p_manager->component_map[comp_type_idx], comp_idx); +} + +void remove_component(EntityManager_t *p_manager, Entity_t *p_entity, ComponentEnum_t comp_type) +{ + unsigned long comp_type_idx = (unsigned long)comp_type; + unsigned long comp_idx = sc_map_del_64(&p_entity->components, comp_type_idx); + if (!sc_map_found(&p_entity->components)) return; + sc_map_del_64v(&p_manager->component_map[comp_type_idx], comp_idx); + free_component_to_mempool(comp_type, comp_idx); +} diff --git a/entManager.h b/entManager.h new file mode 100644 index 0000000..b660413 --- /dev/null +++ b/entManager.h @@ -0,0 +1,28 @@ +#ifndef __ENTITY_MANAGER_H +#define __ENTITY_MANAGER_H +#include "sc/queue/sc_queue.h" +#include "sc/map/sc_map.h" +#include "mempool.h" // includes entity and components + +typedef struct EntityManager +{ + // All fields are Read-Only + struct sc_map_64v entities; + struct sc_map_64v component_map[N_COMPONENTS]; + struct sc_queue_uint to_add; + struct sc_queue_uint to_remove; +}EntityManager_t; + +void init_entity_manager(EntityManager_t *manager); +void update_entity_manager(EntityManager_t *manager); +void clear_entity_manager(EntityManager_t *manager); +void free_entity_manager(EntityManager_t *manager); + +Entity_t *add_entity(EntityManager_t *manager, const char *tag); +void remove_entity(EntityManager_t *manager, unsigned long id); + +void *add_component(EntityManager_t *manager, Entity_t *entity, ComponentEnum_t comp_type); +void *get_component(EntityManager_t *manager, Entity_t *entity, ComponentEnum_t comp_type); +void remove_component(EntityManager_t *manager, Entity_t *entity, ComponentEnum_t comp_type); + +#endif // __ENTITY_MANAGER_H diff --git a/entity.h b/entity.h new file mode 100644 index 0000000..361d818 --- /dev/null +++ b/entity.h @@ -0,0 +1,13 @@ +#ifndef __ENTITY_H +#define __ENTITY_H +#include +#include "sc/map/sc_map.h" +#define MAX_TAG_LEN 32 +typedef struct Entity +{ + unsigned long m_id; + char m_tag[MAX_TAG_LEN]; + bool m_alive; + struct sc_map_64 components; +}Entity_t; +#endif // __ENTITY_H diff --git a/main.c b/main.c new file mode 100644 index 0000000..0090e94 --- /dev/null +++ b/main.c @@ -0,0 +1,47 @@ +#include "mempool.h" +#include "entManager.h" +#include + +int main(void) +{ + init_memory_pools(); + + puts("Init-ing manager and memory pool"); + EntityManager_t manager; + init_entity_manager(&manager); + + puts("Creating two entities"); + Entity_t *p_ent = add_entity(&manager, "player"); + CBBox_t * p_bbox = (CBBox_t *)add_component(&manager, p_ent, CBBOX_COMP_T); + p_bbox->x = 15; + p_ent = add_entity(&manager, "enemy"); + p_bbox = (CBBox_t *)add_component(&manager, p_ent, CBBOX_COMP_T); + p_bbox->x = 40; + update_entity_manager(&manager); + + puts("Print and remove the entities"); + unsigned long idx = 0; + sc_map_foreach(&manager.entities, idx, p_ent) + { + p_bbox = (CBBox_t *)get_component(&manager, p_ent, CBBOX_COMP_T); + printf("BBOX x: %d\n", p_bbox->x); + remove_entity(&manager, idx); + } + puts(""); + update_entity_manager(&manager); + + puts("Print again, should show nothing"); + sc_map_foreach(&manager.entities, idx, p_ent) + { + p_bbox = (CBBox_t *)get_component(&manager, p_ent, CBBOX_COMP_T); + printf("BBOX x: %d\n", p_bbox->x); + remove_entity(&manager, idx); + } + puts(""); + + puts("Freeing manager and memory pool"); + free_entity_manager(&manager); + + free_memory_pools(); + return 0; +} diff --git a/mempool.c b/mempool.c new file mode 100644 index 0000000..6d5ba5e --- /dev/null +++ b/mempool.c @@ -0,0 +1,144 @@ +#include "mempool.h" +#include "sc/queue/sc_queue.h" +#include "sc/map/sc_map.h" + +// Use hashmap as a Set +// Use list will be used to check if an object exist +// The alternative method to check the free list if idx is not there +// requires bound checking +// It's just easier on the mind overall +// If need to optimise memory, replace free_list with set and remove use_list +static struct EntityMemPool +{ + Entity_t entity_buffer[MAX_COMP_POOL_SIZE]; + struct sc_map_64 use_list; + struct sc_queue_uint free_list; +}entity_mem_pool; + +static struct BBoxMemPool +{ + CBBox_t bbox_buffer[MAX_COMP_POOL_SIZE]; + struct sc_map_64 use_list; + struct sc_queue_uint free_list; +}bbox_mem_pool; + +static bool pool_inited = false; +void init_memory_pools(void) +{ + if (!pool_inited) + { + memset(bbox_mem_pool.bbox_buffer, 0, sizeof(bbox_mem_pool.bbox_buffer)); + memset(entity_mem_pool.entity_buffer, 0, sizeof(entity_mem_pool.entity_buffer)); + sc_queue_init(&bbox_mem_pool.free_list); + for (int i=0;icomponents); + ent->m_alive = true; + memset(ent->m_tag, 0, MAX_TAG_LEN); + return ent; +} + +Entity_t * get_entity_wtih_id(unsigned long idx) +{ + sc_map_get_64(&entity_mem_pool.use_list, idx); + if (!sc_map_found(&entity_mem_pool.use_list)) return NULL; + return entity_mem_pool.entity_buffer + idx; +} + +void free_entity_to_mempool(unsigned long idx) +{ + sc_map_del_64(&entity_mem_pool.use_list, idx); + if (sc_map_found(&entity_mem_pool.use_list)) + { + sc_queue_add_first(&entity_mem_pool.free_list, idx); + } +} + +void* new_component_from_mempool(ComponentEnum_t comp_type, unsigned long *idx) +{ + void * comp = NULL; + switch(comp_type) + { + case CBBOX_COMP_T: + if(sc_queue_empty(&bbox_mem_pool.free_list)) break; + *idx = sc_queue_del_first(&bbox_mem_pool.free_list); + sc_map_put_64(&bbox_mem_pool.use_list, *idx, *idx); + comp = bbox_mem_pool.bbox_buffer + *idx; + memset(comp, 0, sizeof(CBBox_t)); + break; + default: + break; + } + return comp; +} + +void* get_component_wtih_id(ComponentEnum_t comp_type, unsigned long idx) +{ + void * comp = NULL; + switch(comp_type) + { + case CBBOX_COMP_T: + sc_map_get_64(&bbox_mem_pool.use_list, idx); + if (!sc_map_found(&bbox_mem_pool.use_list)) break; + comp = bbox_mem_pool.bbox_buffer + idx; + break; + default: + break; + } + return comp; +} + +void free_component_to_mempool(ComponentEnum_t comp_type, unsigned long idx) +{ + // This just free the component from the memory pool + switch(comp_type) + { + case CBBOX_COMP_T: + sc_map_del_64(&bbox_mem_pool.use_list, idx); + if (sc_map_found(&bbox_mem_pool.use_list)) + { + sc_queue_add_first(&bbox_mem_pool.free_list, idx); + } + break; + default: + break; + } +} diff --git a/mempool.h b/mempool.h new file mode 100644 index 0000000..a33c637 --- /dev/null +++ b/mempool.h @@ -0,0 +1,16 @@ +#ifndef __MEMPOOL_H +#define __MEMPOOL_H +#include "entity.h" +#include "components.h" +#define MAX_COMP_POOL_SIZE 256 +void init_memory_pools(void); +void free_memory_pools(void); + +Entity_t* new_entity_from_mempool(unsigned long *idx); +Entity_t* get_entity_wtih_id(unsigned long idx); +void free_entity_to_mempool(unsigned long idx); + +void* new_component_from_mempool(ComponentEnum_t comp_type, unsigned long *idx); +void* get_component_wtih_id(ComponentEnum_t comp_type, unsigned long idx); +void free_component_to_mempool(ComponentEnum_t comp_type, unsigned long idx); +#endif //__MEMPOOL_H diff --git a/sc/CMakeLists.txt b/sc/CMakeLists.txt new file mode 100644 index 0000000..56ae783 --- /dev/null +++ b/sc/CMakeLists.txt @@ -0,0 +1,34 @@ + +cmake_minimum_required(VERSION 3.5.1) +project(sc_lib C) +include(CTest) +include(CheckCCompilerFlag) + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif () + +message(STATUS "Build type ${CMAKE_BUILD_TYPE}") + +add_subdirectory(array) +#add_subdirectory(buffer) +#add_subdirectory(condition) +#add_subdirectory(crc32) +add_subdirectory(heap) +#add_subdirectory(ini) +#add_subdirectory(linked-list) +#add_subdirectory(logger) +add_subdirectory(map) +#add_subdirectory(memory-map) +#add_subdirectory(mutex) +#add_subdirectory(option) +add_subdirectory(queue) +#add_subdirectory(perf) +#add_subdirectory(sc) +#add_subdirectory(signal) +#add_subdirectory(socket) +#add_subdirectory(string) +#add_subdirectory(time) +#add_subdirectory(timer) +#add_subdirectory(thread) +#add_subdirectory(uri) diff --git a/sc/LICENSE b/sc/LICENSE new file mode 100644 index 0000000..0ddc59e --- /dev/null +++ b/sc/LICENSE @@ -0,0 +1,29 @@ + +BSD-3-Clause + +Copyright 2021 Ozan Tezcan +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/sc/array/CMakeLists.txt b/sc/array/CMakeLists.txt new file mode 100644 index 0000000..456e539 --- /dev/null +++ b/sc/array/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_array C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_library(sc_array STATIC sc_array.h) +set_target_properties(sc_array PROPERTIES LINKER_LANGUAGE C) + +target_include_directories(sc_array PUBLIC ${CMAKE_CURRENT_LIST_DIR}) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -Wextra -pedantic -Werror") +endif () diff --git a/sc/array/README.md b/sc/array/README.md new file mode 100644 index 0000000..1383a2a --- /dev/null +++ b/sc/array/README.md @@ -0,0 +1,61 @@ +### Generic array + +### Overview + +- Growable array/vector. +- It comes with predefined types, check out predefined types at the bottom of + sc_array.h You can add more types there if you need. + +### Usage + +```c +#include "sc_array.h" + +#include + +void example_str(void) +{ + const char *it; + struct sc_array_str arr; + + sc_array_init(&arr); + + sc_array_add(&arr, "item0"); + sc_array_add(&arr, "item1"); + sc_array_add(&arr, "item2"); + + printf("\nDelete first element \n\n"); + sc_array_del(&arr, 0); + + sc_array_foreach (&arr, it) { + printf("Elem = %s \n", it); + } + + sc_array_term(&arr); +} + +void example_int(void) +{ + struct sc_array_int arr; + + sc_array_init(&arr); + + sc_array_add(&arr, 0); + sc_array_add(&arr, 1); + sc_array_add(&arr, 2); + + for (size_t i = 0; i < sc_array_size(&arr); i++) { + printf("Elem = %d \n", arr.elems[i]); + } + + sc_array_term(&arr); +} + +int main(void) +{ + example_int(); + example_str(); + + return 0; +} +``` diff --git a/sc/array/sc_array.h b/sc/array/sc_array.h new file mode 100644 index 0000000..ffa3c3b --- /dev/null +++ b/sc/array/sc_array.h @@ -0,0 +1,223 @@ +/* + * BSD-3-Clause + * + * Copyright 2021 Ozan Tezcan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SC_ARRAY_H +#define SC_ARRAY_H + +#include +#include +#include +#include +#include +#include + +#define SC_ARRAY_VERSION "2.0.0" + +#ifdef SC_HAVE_CONFIG_H +#include "config.h" +#else +#define sc_array_realloc realloc +#define sc_array_free free +#endif + +#ifndef SC_ARRAY_MAX +#define SC_ARRAY_MAX SIZE_MAX +#endif + +#define sc_array_def(T, name) \ + struct sc_array_##name { \ + bool oom; \ + size_t cap; \ + size_t size; \ + /* NOLINTNEXTLINE */ \ + T *elems; \ + } +/** + * Init array + * @param a array + */ +#define sc_array_init(a) \ + do { \ + memset((a), 0, sizeof(*(a))); \ + } while (0) + +/** + * Term array + * @param a array + */ +#define sc_array_term(a) \ + do { \ + sc_array_free((a)->elems); \ + sc_array_init(a); \ + } while (0) + +/** + * Add elem to array, call sc_array_oom(v) to see if 'add' failed because of out + * of memory. + * + * @param a array + * @param k elem + */ +#define sc_array_add(a, k) \ + do { \ + const size_t _max = SC_ARRAY_MAX / sizeof(*(a)->elems); \ + size_t _cap; \ + void *_p; \ + \ + if ((a)->cap == (a)->size) { \ + if ((a)->cap > _max / 2) { \ + (a)->oom = true; \ + break; \ + } \ + _cap = (a)->cap == 0 ? 8 : (a)->cap * 2; \ + _p = sc_array_realloc((a)->elems, \ + _cap * sizeof(*((a)->elems))); \ + if (_p == NULL) { \ + (a)->oom = true; \ + break; \ + } \ + (a)->cap = _cap; \ + (a)->elems = _p; \ + } \ + (a)->oom = false; \ + (a)->elems[(a)->size++] = k; \ + } while (0) + +/** + * Deletes items from the array without deallocating underlying memory + * @param a array + */ +#define sc_array_clear(a) \ + do { \ + (a)->size = 0; \ + (a)->oom = false; \ + } while (0) + +/** + * @param a array + * @return true if last add operation failed, false otherwise. + */ +#define sc_array_oom(a) ((a)->oom) + +/** + * Get element at index i, if 'i' is out of range, result is undefined. + * + * @param a array + * @param i index + * @return element at index 'i' + */ +#define sc_array_at(a, i) ((a)->elems[i]) + +/** + * @param a array + * @return element count + */ +#define sc_array_size(a) ((a)->size) + +/** + * @param a array + * @param i element index, If 'i' is out of the range, result is undefined. + */ +#define sc_array_del(a, i) \ + do { \ + size_t idx = (i); \ + assert(idx < (a)->size); \ + \ + const size_t _cnt = (a)->size - (idx) - 1; \ + if (_cnt > 0) { \ + memmove(&((a)->elems[idx]), &((a)->elems[idx + 1]), \ + _cnt * sizeof(*((a)->elems))); \ + } \ + (a)->size--; \ + } while (0) + +/** + * Deletes the element at index i, replaces last element with the deleted + * element unless deleted element is the last element. This is faster than + * moving elements but elements will no longer be in the 'add order' + * + * arr[a,b,c,d,e,f] -> sc_array_del_unordered(arr, 2) - > arr[a,b,f,d,e] + * + * @param a array + * @param i index. If 'i' is out of the range, result is undefined. + */ +#define sc_array_del_unordered(a, i) \ + do { \ + size_t idx = (i); \ + assert(idx < (a)->size); \ + (a)->elems[idx] = (a)->elems[(--(a)->size)]; \ + } while (0) + +/** + * Deletes the last element. If current size is zero, result is undefined. + * @param a array + */ +#define sc_array_del_last(a) \ + do { \ + assert((a)->size != 0); \ + (a)->size--; \ + } while (0) + +/** + * Sorts the array using qsort() + * @param a array + * @param cmp comparator, check qsort() documentation for details + */ +#define sc_array_sort(a, cmp) \ + (qsort((a)->elems, (a)->size, sizeof(*(a)->elems), cmp)) + +/** + * Returns last element. If array is empty, result is undefined. + * @param a array + */ +#define sc_array_last(a) (a)->elems[(a)->size - 1] + +/** + * @param a array + * @param elem elem + */ +#define sc_array_foreach(a, elem) \ + for (size_t _k = 1, _i = 0; _k && _i != (a)->size; _k = !_k, _i++) \ + for ((elem) = (a)->elems[_i]; _k; _k = !_k) + +// (type, name) +sc_array_def(int, int); +sc_array_def(unsigned int, uint); +sc_array_def(long, long); +sc_array_def(unsigned long, ulong); +sc_array_def(unsigned long long, ull); +sc_array_def(uint32_t, 32); +sc_array_def(uint64_t, 64); +sc_array_def(double, double); +sc_array_def(const char *, str); +sc_array_def(void *, ptr); + +#endif diff --git a/sc/heap/CMakeLists.txt b/sc/heap/CMakeLists.txt new file mode 100644 index 0000000..68ca2e3 --- /dev/null +++ b/sc/heap/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_heap C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_library( + sc_heap STATIC + sc_heap.c + sc_heap.h) + +target_include_directories(sc_heap PUBLIC ${CMAKE_CURRENT_LIST_DIR}) diff --git a/sc/heap/README.md b/sc/heap/README.md new file mode 100644 index 0000000..245b759 --- /dev/null +++ b/sc/heap/README.md @@ -0,0 +1,60 @@ +### Heap + +### Overview + +- Min-heap implementation, it can be used as Max-heap/priority queue as well. + +### Usage + +```c + +#include "sc_heap.h" + +#include + + +int main(int argc, char *argv[]) +{ + struct data { + int priority; + char *data; + }; + + struct data n[] = {{1, "first"}, + {4, "fourth"}, + {5, "fifth"}, + {3, "third"}, + {2, "second"}}; + + struct sc_heap_data *elem; + struct sc_heap heap; + + sc_heap_init(&heap, 0); + + // Min-heap usage + for (int i = 0; i < 5; i++) { + sc_heap_add(&heap, n[i].priority, n[i].data); + } + + while ((elem = sc_heap_pop(&heap)) != NULL) { + printf("key = %d, data = %s \n", + (int) elem->key, (char*) elem->data); + } + printf("---------------- \n"); + + // Max-heap usage, negate when adding into heap and negate back after + // pop : + for (int i = 0; i < 5; i++) { + sc_heap_add(&heap, -(n[i].priority), n[i].data); + } + + while ((elem = sc_heap_pop(&heap)) != NULL) { + printf("key = %d, data = %s \n", + (int) elem->key, (char*) elem->data); + } + + sc_heap_term(&heap); + + return 0; +} +``` \ No newline at end of file diff --git a/sc/heap/sc_heap.c b/sc/heap/sc_heap.c new file mode 100644 index 0000000..437e459 --- /dev/null +++ b/sc/heap/sc_heap.c @@ -0,0 +1,152 @@ +/* + * BSD-3-Clause + * + * Copyright 2021 Ozan Tezcan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sc_heap.h" + +#include + +#ifndef SC_HEAP_MAX +#define SC_HEAP_MAX SIZE_MAX / sizeof(struct sc_heap_data) +#endif + +bool sc_heap_init(struct sc_heap *h, size_t cap) +{ + void *e; + const size_t sz = cap * sizeof(struct sc_heap_data); + + *h = (struct sc_heap){0}; + + if (cap == 0) { + return true; + } + + if (cap > SC_HEAP_MAX || (e = sc_heap_malloc(sz)) == NULL) { + return false; + } + + h->elems = e; + h->cap = cap; + + return true; +} + +void sc_heap_term(struct sc_heap *h) +{ + sc_heap_free(h->elems); + + *h = (struct sc_heap){ + .elems = NULL, + }; +} + +size_t sc_heap_size(struct sc_heap *h) +{ + return h->size; +} + +void sc_heap_clear(struct sc_heap *h) +{ + h->size = 0; +} + +bool sc_heap_add(struct sc_heap *h, int64_t key, void *data) +{ + size_t i, cap, m; + void *exp; + + if (++h->size >= h->cap) { + cap = h->cap != 0 ? h->cap * 2 : 4; + m = cap * 2 * sizeof(*h->elems); + + if (h->cap >= SC_HEAP_MAX / 2 || + (exp = sc_heap_realloc(h->elems, m)) == NULL) { + return false; + } + + h->elems = exp; + h->cap = cap; + } + + i = h->size; + while (i != 1 && key < h->elems[i / 2].key) { + h->elems[i] = h->elems[i / 2]; + i /= 2; + } + + h->elems[i].key = key; + h->elems[i].data = data; + + return true; +} + +struct sc_heap_data *sc_heap_peek(struct sc_heap *h) +{ + if (h->size == 0) { + return NULL; + } + + // Top element is always at heap->elems[1]. + return &h->elems[1]; +} + +struct sc_heap_data *sc_heap_pop(struct sc_heap *h) +{ + size_t i = 1, child = 2; + struct sc_heap_data last; + + if (h->size == 0) { + return NULL; + } + + // Top element is always at heap->elems[1]. + h->elems[0] = h->elems[1]; + + last = h->elems[h->size--]; + while (child <= h->size) { + if (child < h->size && + h->elems[child].key > h->elems[child + 1].key) { + child++; + }; + + if (last.key <= h->elems[child].key) { + break; + } + + h->elems[i] = h->elems[child]; + + i = child; + child *= 2; + } + + h->elems[i] = last; + + return &h->elems[0]; +} diff --git a/sc/heap/sc_heap.h b/sc/heap/sc_heap.h new file mode 100644 index 0000000..d470ca0 --- /dev/null +++ b/sc/heap/sc_heap.h @@ -0,0 +1,111 @@ +/* + * BSD-3-Clause + * + * Copyright 2021 Ozan Tezcan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SC_HEAP_H +#define SC_HEAP_H + +#include +#include +#include + +#define SC_HEAP_VERSION "2.0.0" + +#ifdef SC_HAVE_CONFIG_H +#include "config.h" +#else +#define sc_heap_malloc malloc +#define sc_heap_realloc realloc +#define sc_heap_free free +#endif + +struct sc_heap_data { + int64_t key; + void *data; +}; + +struct sc_heap { + size_t cap; + size_t size; + struct sc_heap_data *elems; +}; + +/** + * @param h heap + * @param cap initial capacity, pass '0' for no initial memory allocation + * @return 'true' on success, 'false' on out of memory + */ +bool sc_heap_init(struct sc_heap *h, size_t cap); + +/** + * Destroys heap, frees memory + * @param h heap + */ +void sc_heap_term(struct sc_heap *h); + +/** + * @param h heap + * @return element count + */ +size_t sc_heap_size(struct sc_heap *h); + +/** + * Clears elements from the queue, does not free the allocated memory. + * @param h heap + */ +void sc_heap_clear(struct sc_heap *h); + +/** + * @param h heap + * @param key key + * @param data data + * @return 'false' on out of memory. + */ +bool sc_heap_add(struct sc_heap *h, int64_t key, void *data); + +/** + * Read top element without removing from the heap. + * + * @param h heap + * @return pointer to data holder(valid until next heap operation) + * NULL if heap is empty. + */ +struct sc_heap_data *sc_heap_peek(struct sc_heap *h); + +/** + * Read top element and remove it from the heap. + * + * @param h heap + * @return pointer to data holder(valid until next heap operation) + * NULL if heap is empty. + */ +struct sc_heap_data *sc_heap_pop(struct sc_heap *h); + +#endif diff --git a/sc/map/CMakeLists.txt b/sc/map/CMakeLists.txt new file mode 100644 index 0000000..64db69d --- /dev/null +++ b/sc/map/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_map C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_library( + sc_map STATIC + sc_map.c + sc_map.h) + +target_include_directories(sc_map PUBLIC ${CMAKE_CURRENT_LIST_DIR}) diff --git a/sc/map/README.md b/sc/map/README.md new file mode 100644 index 0000000..6180290 --- /dev/null +++ b/sc/map/README.md @@ -0,0 +1,108 @@ +### Hashmap + +### Overview + +- Open addressing hashmap with linear probing. +- Requires postfix naming, e.g sc_map_str, sc_map_int. It's ugly but necessary + for better performance. + +- Comes with predefined key value pairs : + +``` + name key type value type + sc_map_of_scalar(32, uint32_t, uint32_t) + sc_map_of_scalar(64, uint64_t, uint64_t) + sc_map_of_scalar(64v, uint64_t, void *) + sc_map_of_scalar(64s, uint64_t, const char *) + sc_map_of_strkey(str, const char *, const char *) + sc_map_of_strkey(sv, const char *, void*) + sc_map_of_strkey(s64, const char *, uint64_t) +``` + +- This is a very fast hashmap. + - Single array allocation for all data. + - Linear probing over an array. + - Deletion without tombstones. + - Macros generate functions in sc_map.c. So, inlining is upto the compiler. + +### Note + +Key and value types can be integers(32bit/64bit) or pointers only. +Other types can be added but must be scalar types, not structs. This is a +design decision, I don't remember when was the last time I wanted to store +struct as a key or value. I use hashmap for fast look-ups and small key-value +pairs with linear probing play well with cache lines and hardware-prefetcher. +If you want to use structs anyway, you need to change the code a little. + +### Usage + +```c +#include "sc_map.h" + +#include + +void example_str(void) +{ + const char *key, *value; + struct sc_map_str map; + + sc_map_init_str(&map, 0, 0); + + sc_map_put_str(&map, "jack", "chicago"); + sc_map_put_str(&map, "jane", "new york"); + sc_map_put_str(&map, "janie", "atlanta"); + + sc_map_foreach (&map, key, value) { + printf("Key:[%s], Value:[%s] \n", key, value); + } + + sc_map_term_str(&map); +} + +void example_int_to_str(void) +{ + uint32_t key; + const char *value; + struct sc_map_64s map; + + sc_map_init_64s(&map, 0, 0); + + sc_map_put_64s(&map, 100, "chicago"); + sc_map_put_64s(&map, 200, "new york"); + sc_map_put_64s(&map, 300, "atlanta"); + + value = sc_map_get_64s(&map, 200); + if (sc_map_found(&map)) { + printf("Found Value:[%s] \n", value); + } + + value = sc_map_del_64s(&map, 100); + if (sc_map_found(&map)) { + printf("Deleted : %s \n", value); + } + + sc_map_foreach (&map, key, value) { + printf("Key:[%d], Value:[%s] \n", key, value); + } + + value = sc_map_del_64s(&map, 200); + if (sc_map_found(&map)) { + printf("Found : %s \n", value); + } + + value = sc_map_put_64s(&map, 300, "los angeles"); + if (sc_map_found(&map)) { + printf("overridden : %s \n", value); + } + + sc_map_term_64s(&map); +} + +int main(void) +{ + example_str(); + example_int_to_str(); + + return 0; +} +``` diff --git a/sc/map/sc_map.c b/sc/map/sc_map.c new file mode 100644 index 0000000..4e3d23c --- /dev/null +++ b/sc/map/sc_map.c @@ -0,0 +1,405 @@ +/* + * BSD-3-Clause + * + * Copyright 2021 Ozan Tezcan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sc_map.h" + +#include + +#ifndef SC_MAP_MAX +#define SC_MAP_MAX UINT32_MAX +#endif + +#define sc_map_def_strkey(name, K, V, cmp, hash_fn) \ + bool sc_map_cmp_##name(struct sc_map_item_##name *t, K key, \ + uint32_t hash) \ + { \ + return t->hash == hash && cmp(t->key, key); \ + } \ + \ + void sc_map_assign_##name(struct sc_map_item_##name *t, K key, \ + V value, uint32_t hash) \ + { \ + t->key = key; \ + t->value = value; \ + t->hash = hash; \ + } \ + \ + uint32_t sc_map_hashof_##name(struct sc_map_item_##name *t) \ + { \ + return t->hash; \ + } \ + \ + sc_map_def(name, K, V, cmp, hash_fn) + +#define sc_map_def_scalar(name, K, V, cmp, hash_fn) \ + bool sc_map_cmp_##name(struct sc_map_item_##name *t, K key, \ + uint32_t hash) \ + { \ + (void) hash; \ + return cmp(t->key, key); \ + } \ + \ + void sc_map_assign_##name(struct sc_map_item_##name *t, K key, \ + V value, uint32_t hash) \ + { \ + (void) hash; \ + t->key = key; \ + t->value = value; \ + } \ + \ + uint32_t sc_map_hashof_##name(struct sc_map_item_##name *t) \ + { \ + return hash_fn(t->key); \ + } \ + \ + sc_map_def(name, K, V, cmp, hash_fn) + +#define sc_map_def(name, K, V, cmp, hash_fn) \ + \ + static const struct sc_map_item_##name empty_items_##name[2]; \ + \ + static const struct sc_map_##name sc_map_empty_##name = { \ + .cap = 1, \ + .mem = (struct sc_map_item_##name *) &empty_items_##name[1]}; \ + \ + static void *sc_map_alloc_##name(uint32_t *cap, uint32_t factor) \ + { \ + uint32_t v = *cap; \ + struct sc_map_item_##name *t; \ + \ + if (*cap > SC_MAP_MAX / factor) { \ + return NULL; \ + } \ + \ + /* Find next power of two */ \ + v = v < 8 ? 8 : (v * factor); \ + v--; \ + for (uint32_t i = 1; i < sizeof(v) * 8; i *= 2) { \ + v |= v >> i; \ + } \ + v++; \ + \ + *cap = v; \ + t = sc_map_calloc(sizeof(*t), v + 1); \ + return t ? &t[1] : NULL; \ + } \ + \ + bool sc_map_init_##name(struct sc_map_##name *m, uint32_t cap, \ + uint32_t load_fac) \ + { \ + void *t; \ + uint32_t f = (load_fac == 0) ? 75 : load_fac; \ + \ + if (f > 95 || f < 25) { \ + return false; \ + } \ + \ + if (cap == 0) { \ + *m = sc_map_empty_##name; \ + m->load_fac = f; \ + return true; \ + } \ + \ + t = sc_map_alloc_##name(&cap, 1); \ + if (t == NULL) { \ + return false; \ + } \ + \ + m->mem = t; \ + m->size = 0; \ + m->used = false; \ + m->cap = cap; \ + m->load_fac = f; \ + m->remap = (uint32_t) (m->cap * ((double) m->load_fac / 100)); \ + \ + return true; \ + } \ + \ + void sc_map_term_##name(struct sc_map_##name *m) \ + { \ + if (m->mem != sc_map_empty_##name.mem) { \ + sc_map_free(&m->mem[-1]); \ + *m = sc_map_empty_##name; \ + } \ + } \ + \ + uint32_t sc_map_size_##name(struct sc_map_##name *m) \ + { \ + return m->size; \ + } \ + \ + void sc_map_clear_##name(struct sc_map_##name *m) \ + { \ + if (m->size > 0) { \ + for (uint32_t i = 0; i < m->cap; i++) { \ + m->mem[i].key = 0; \ + } \ + \ + m->used = false; \ + m->size = 0; \ + } \ + } \ + \ + static bool sc_map_remap_##name(struct sc_map_##name *m) \ + { \ + uint32_t pos, cap, mod; \ + struct sc_map_item_##name *new; \ + \ + if (m->size < m->remap) { \ + return true; \ + } \ + \ + cap = m->cap; \ + new = sc_map_alloc_##name(&cap, 2); \ + if (new == NULL) { \ + return false; \ + } \ + \ + mod = cap - 1; \ + \ + for (uint32_t i = 0; i < m->cap; i++) { \ + if (m->mem[i].key != 0) { \ + pos = sc_map_hashof_##name(&m->mem[i]) & mod; \ + \ + while (true) { \ + if (new[pos].key == 0) { \ + new[pos] = m->mem[i]; \ + break; \ + } \ + \ + pos = (pos + 1) & (mod); \ + } \ + } \ + } \ + \ + if (m->mem != sc_map_empty_##name.mem) { \ + new[-1] = m->mem[-1]; \ + sc_map_free(&m->mem[-1]); \ + } \ + \ + m->mem = new; \ + m->cap = cap; \ + m->remap = (uint32_t) (m->cap * ((double) m->load_fac / 100)); \ + \ + return true; \ + } \ + \ + V sc_map_put_##name(struct sc_map_##name *m, K key, V value) \ + { \ + V ret; \ + uint32_t pos, mod, h; \ + \ + m->oom = false; \ + \ + if (!sc_map_remap_##name(m)) { \ + m->oom = true; \ + return 0; \ + } \ + \ + if (key == 0) { \ + ret = (m->used) ? m->mem[-1].value : 0; \ + m->found = m->used; \ + m->size += !m->used; \ + m->used = true; \ + m->mem[-1].value = value; \ + \ + return ret; \ + } \ + \ + mod = m->cap - 1; \ + h = hash_fn(key); \ + pos = h & (mod); \ + \ + while (true) { \ + if (m->mem[pos].key == 0) { \ + m->size++; \ + } else if (!sc_map_cmp_##name(&m->mem[pos], key, h)) { \ + pos = (pos + 1) & (mod); \ + continue; \ + } \ + \ + m->found = m->mem[pos].key != 0; \ + ret = m->found ? m->mem[pos].value : 0; \ + sc_map_assign_##name(&m->mem[pos], key, value, h); \ + \ + return ret; \ + } \ + } \ + \ + /** NOLINTNEXTLINE */ \ + V sc_map_get_##name(struct sc_map_##name *m, K key) \ + { \ + const uint32_t mod = m->cap - 1; \ + uint32_t h, pos; \ + \ + if (key == 0) { \ + m->found = m->used; \ + return m->used ? m->mem[-1].value : 0; \ + } \ + \ + h = hash_fn(key); \ + pos = h & mod; \ + \ + while (true) { \ + if (m->mem[pos].key == 0) { \ + m->found = false; \ + return 0; \ + } else if (!sc_map_cmp_##name(&m->mem[pos], key, h)) { \ + pos = (pos + 1) & (mod); \ + continue; \ + } \ + \ + m->found = true; \ + return m->mem[pos].value; \ + } \ + } \ + \ + /** NOLINTNEXTLINE */ \ + V sc_map_del_##name(struct sc_map_##name *m, K key) \ + { \ + const uint32_t mod = m->cap - 1; \ + uint32_t pos, prev, it, p, h; \ + V ret; \ + \ + if (key == 0) { \ + m->found = m->used; \ + m->size -= m->used; \ + m->used = false; \ + \ + return m->found ? m->mem[-1].value : 0; \ + } \ + \ + h = hash_fn(key); \ + pos = h & (mod); \ + \ + while (true) { \ + if (m->mem[pos].key == 0) { \ + m->found = false; \ + return 0; \ + } else if (!sc_map_cmp_##name(&m->mem[pos], key, h)) { \ + pos = (pos + 1) & (mod); \ + continue; \ + } \ + \ + m->found = true; \ + ret = m->mem[pos].value; \ + \ + m->size--; \ + m->mem[pos].key = 0; \ + prev = pos; \ + it = pos; \ + \ + while (true) { \ + it = (it + 1) & (mod); \ + if (m->mem[it].key == 0) { \ + break; \ + } \ + \ + p = sc_map_hashof_##name(&m->mem[it]) & (mod); \ + \ + if ((p > it && (p <= prev || it >= prev)) || \ + (p <= prev && it >= prev)) { \ + \ + m->mem[prev] = m->mem[it]; \ + m->mem[it].key = 0; \ + prev = it; \ + } \ + } \ + \ + return ret; \ + } \ + } + +static uint32_t sc_map_hash_32(uint32_t a) +{ + return a; +} + +static uint32_t sc_map_hash_64(uint64_t a) +{ + return ((uint32_t) a) ^ (uint32_t) (a >> 32u); +} + +// clang-format off +uint32_t murmurhash(const char *key) +{ + const uint64_t m = UINT64_C(0xc6a4a7935bd1e995); + const size_t len = strlen(key); + const unsigned char* p = (const unsigned char*) key; + const unsigned char *end = p + (len & ~(uint64_t) 0x7); + uint64_t h = (len * m); + + while (p != end) { + uint64_t k; + memcpy(&k, p, sizeof(k)); + + k *= m; + k ^= k >> 47u; + k *= m; + + h ^= k; + h *= m; + p += 8; + } + + switch (len & 7u) { + case 7: h ^= (uint64_t) p[6] << 48ul; // fall through + case 6: h ^= (uint64_t) p[5] << 40ul; // fall through + case 5: h ^= (uint64_t) p[4] << 32ul; // fall through + case 4: h ^= (uint64_t) p[3] << 24ul; // fall through + case 3: h ^= (uint64_t) p[2] << 16ul; // fall through + case 2: h ^= (uint64_t) p[1] << 8ul; // fall through + case 1: h ^= (uint64_t) p[0]; // fall through + h *= m; + default: + break; + }; + + h ^= h >> 47u; + h *= m; + h ^= h >> 47u; + + return (uint32_t) h; +} + +#define sc_map_eq(a, b) ((a) == (b)) +#define sc_map_streq(a, b) (!strcmp(a, b)) + +// name, key type, value type, cmp hash +sc_map_def_scalar(32, uint32_t, uint32_t, sc_map_eq, sc_map_hash_32) +sc_map_def_scalar(64, uint64_t, uint64_t, sc_map_eq, sc_map_hash_64) +sc_map_def_scalar(64v, uint64_t, void *, sc_map_eq, sc_map_hash_64) +sc_map_def_scalar(64s, uint64_t, const char *, sc_map_eq, sc_map_hash_64) +sc_map_def_strkey(str, const char *, const char *, sc_map_streq, murmurhash) +sc_map_def_strkey(sv, const char *, void *, sc_map_streq, murmurhash) +sc_map_def_strkey(s64, const char *, uint64_t, sc_map_streq, murmurhash) + +// clang-format on diff --git a/sc/map/sc_map.h b/sc/map/sc_map.h new file mode 100644 index 0000000..fc42d71 --- /dev/null +++ b/sc/map/sc_map.h @@ -0,0 +1,225 @@ +/* + * BSD-3-Clause + * + * Copyright 2021 Ozan Tezcan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SC_MAP_H +#define SC_MAP_H + +#include +#include +#include +#include +#include + +#define SC_MAP_VERSION "2.0.0" + +#ifdef SC_HAVE_CONFIG_H +#include "config.h" +#else +#define sc_map_calloc calloc +#define sc_map_free free +#endif + +#define sc_map_dec_strkey(name, K, V) \ + struct sc_map_item_##name { \ + K key; \ + V value; \ + uint32_t hash; \ + }; \ + \ + sc_map_of(name, K, V) + +#define sc_map_dec_scalar(name, K, V) \ + struct sc_map_item_##name { \ + K key; \ + V value; \ + }; \ + \ + sc_map_of(name, K, V) + +#define sc_map_of(name, K, V) \ + struct sc_map_##name { \ + struct sc_map_item_##name *mem; \ + uint32_t cap; \ + uint32_t size; \ + uint32_t load_fac; \ + uint32_t remap; \ + bool used; \ + bool oom; \ + bool found; \ + }; \ + \ + /** \ + * Create map \ + * \ + * @param map map \ + * @param cap initial capacity, zero is accepted \ + * @param load_factor must be >25 and <95. Pass 0 for default value. \ + * @return 'true' on success, \ + * 'false' on out of memory or if 'load_factor' value is \ + * invalid. \ + */ \ + bool sc_map_init_##name(struct sc_map_##name *map, uint32_t cap, \ + uint32_t load_factor); \ + \ + /** \ + * Destroy map. \ + * \ + * @param map map \ + */ \ + void sc_map_term_##name(struct sc_map_##name *map); \ + \ + /** \ + * Get map element count \ + * \ + * @param map map \ + * @return element count \ + */ \ + uint32_t sc_map_size_##name(struct sc_map_##name *map); \ + \ + /** \ + * Clear map \ + * \ + * @param map map \ + */ \ + void sc_map_clear_##name(struct sc_map_##name *map); \ + \ + /** \ + * Put element to the map \ + * \ + * struct sc_map_str map; \ + * sc_map_put_str(&map, "key", "value"); \ + * \ + * @param map map \ + * @param K key \ + * @param V value \ + * @return previous value if exists \ + * call sc_map_found() to see if the returned value is valid. \ + */ \ + V sc_map_put_##name(struct sc_map_##name *map, K key, V val); \ + \ + /** \ + * Get element \ + * \ + * @param map map \ + * @param K key \ + * @return current value if exists. \ + * call sc_map_found() to see if the returned value is valid. \ + */ \ + /** NOLINTNEXTLINE */ \ + V sc_map_get_##name(struct sc_map_##name *map, K key); \ + \ + /** \ + * Delete element \ + * \ + * @param map map \ + * @param K key \ + * @return current value if exists. \ + * call sc_map_found() to see if the returned value is valid. \ + */ \ + /** NOLINTNEXTLINE */ \ + V sc_map_del_##name(struct sc_map_##name *map, K key); + +/** + * @param map map + * @return - if put operation overrides a value, returns true + * - if get operation finds the key, returns true + * - if del operation deletes a key, returns true + */ +#define sc_map_found(map) ((map)->found) + +/** + * @param map map + * @return true if put operation failed with out of memory + */ +#define sc_map_oom(map) ((map)->oom) + +// clang-format off + +/** + * Foreach loop + * + * char *key, *value; + * struct sc_map_str map; + * + * sc_map_foreach(&map, key, value) { + * printf("key = %s, value = %s \n"); + * } + */ +#define sc_map_foreach(map, K, V) \ + for (int64_t _i = -1, _b = 0; !_b && _i < (map)->cap; _i++) \ + for ((V) = (map)->mem[_i].value, (K) = (map)->mem[_i].key, _b = 1; \ + _b && ((_i == -1 && (map)->used) || (K) != 0) ? 1 : (_b = 0); \ + _b = 0) + +/** + * Foreach loop for keys + * + * char *key; + * struct sc_map_str map; + * + * sc_map_foreach_key(&map, key) { + * printf("key = %s \n"); + * } + */ +#define sc_map_foreach_key(map, K) \ + for (int64_t _i = -1, _b = 0; !_b && _i < (map)->cap; _i++) \ + for ((K) = (map)->mem[_i].key, _b = 1; \ + _b && ((_i == -1 && (map)->used) || (K) != 0) ? 1 : (_b = 0); \ + _b = 0) + +/** + * Foreach loop for values + * + * char *value; + * struct sc_map_str map; + * + * sc_map_foreach_value(&map, value) { + * printf("value = %s \n"); + * } + */ +#define sc_map_foreach_value(map, V) \ + for (int64_t _i = -1, _b = 0; !_b && _i < (map)->cap; _i++) \ + for ((V) = (map)->mem[_i].value, _b = 1; \ + _b && ((_i == -1 && (map)->used) || (map)->mem[_i].key != 0) ? 1 : (_b = 0); \ + _b = 0) + +// name key type value type +sc_map_dec_scalar(32, uint32_t, uint32_t) +sc_map_dec_scalar(64, uint64_t, uint64_t) +sc_map_dec_scalar(64v, uint64_t, void *) +sc_map_dec_scalar(64s, uint64_t, const char *) +sc_map_dec_strkey(str, const char *, const char *) +sc_map_dec_strkey(sv, const char *, void*) +sc_map_dec_strkey(s64, const char *, uint64_t) + +// clang-format on + +#endif diff --git a/sc/queue/CMakeLists.txt b/sc/queue/CMakeLists.txt new file mode 100644 index 0000000..afa7572 --- /dev/null +++ b/sc/queue/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_queue C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_library( + sc_queue STATIC + sc_queue.h) + +set_target_properties(sc_queue PROPERTIES LINKER_LANGUAGE C) + +target_include_directories(sc_queue PUBLIC ${CMAKE_CURRENT_LIST_DIR}) diff --git a/sc/queue/README.md b/sc/queue/README.md new file mode 100644 index 0000000..13fcb76 --- /dev/null +++ b/sc/queue/README.md @@ -0,0 +1,45 @@ +### Generic queue + +### Overview + +- Queue implementation which grows when you add elements. +- Add/remove from head/tail is possible so it can be used as list, stack, + queue, dequeue etc. +- It comes with predefined types, check out at the end of sc_queue.h, you can + add there (sc_queue_def) if you need more. + + +### Usage + + +```c +#include "sc_queue.h" + +#include + +int main(int argc, char *argv[]) +{ + int elem; + struct sc_queue_int queue; + + sc_queue_init(&queue); + + sc_queue_add_last(&queue, 2); + sc_queue_add_last(&queue, 3); + sc_queue_add_last(&queue, 4); + sc_queue_add_first(&queue, 1); + + sc_queue_foreach (&queue, elem) { + printf("elem = [%d] \n", elem); + } + + elem = sc_queue_del_last(&queue); + printf("Last element was : [%d] \n", elem); + + elem = sc_queue_del_first(&queue); + printf("First element was : [%d] \n", elem); + + sc_queue_term(&queue); + return 0; +} +``` diff --git a/sc/queue/sc_queue.h b/sc/queue/sc_queue.h new file mode 100644 index 0000000..9eebb16 --- /dev/null +++ b/sc/queue/sc_queue.h @@ -0,0 +1,287 @@ +/* + * BSD-3-Clause + * + * Copyright 2021 Ozan Tezcan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SC_QUEUE_H +#define SC_QUEUE_H + +#include +#include +#include +#include +#include +#include + +#define SC_QUEUE_VERSION "2.0.0" + +#ifdef SC_HAVE_CONFIG_H +#include "config.h" +#else +#define sc_queue_calloc calloc +#define sc_queue_free free +#endif + +#ifndef SC_QUEUE_MAX +#define SC_QUEUE_MAX (SIZE_MAX) +#endif + +#define sc_queue_def(T, name) \ + struct sc_queue_##name { \ + bool oom; \ + size_t cap; \ + size_t first; \ + size_t last; \ + /* NOLINTNEXTLINE */ \ + T *elems; \ + } + +#define sc_queue_expand(q) \ + do { \ + size_t _cap, _len, _off; \ + size_t _pos = ((q)->last + 1) & ((q)->cap - 1); \ + void *_dst, *_src; \ + \ + if (_pos == (q)->first) { \ + if ((q)->cap > SC_QUEUE_MAX / 2ul) { \ + (q)->oom = true; \ + break; \ + } \ + _cap = (q)->cap * 2; \ + _dst = sc_queue_calloc(_cap, sizeof(*((q)->elems))); \ + if (_dst == NULL) { \ + (q)->oom = true; \ + break; \ + } \ + _len = ((q)->cap - (q)->first) * sizeof(*(q)->elems); \ + _off = ((q)->first * sizeof(*((q)->elems))); \ + _src = ((char *) (q)->elems) + _off; \ + \ + memcpy(_dst, _src, _len); \ + memcpy(((char *) _dst) + _len, (q)->elems, _off); \ + (q)->oom = false; \ + (q)->last = (q)->cap - 1; \ + (q)->first = 0; \ + (q)->cap = _cap; \ + sc_queue_free((q)->elems); \ + (q)->elems = _dst; \ + } \ + } while (0) + +/** + * Init queue. Call sc_queue_oom(q) to see if memory allocation succeeded. + * @param q queue + */ +#define sc_queue_init(q) \ + do { \ + (q)->oom = false; \ + (q)->cap = 8; \ + (q)->first = 0; \ + (q)->last = 0; \ + (q)->elems = sc_queue_calloc(1, sizeof(*(q)->elems) * 8); \ + if ((q)->elems == NULL) { \ + (q)->oom = true; \ + } \ + } while (0) + +/** + * Term queue + * @param q queue + */ +#define sc_queue_term(q) \ + do { \ + sc_queue_free((q)->elems); \ + (q)->elems = NULL; \ + (q)->cap = 0; \ + (q)->first = 0; \ + (q)->last = 0; \ + (q)->oom = false; \ + } while (0) + +/** + * @param q queue + * @return true if last add operation failed, false otherwise. + */ +#define sc_queue_oom(q) ((q)->oom) + +/** + * @param q queue + * @return element count + */ +#define sc_queue_size(q) (((q)->last - (q)->first) & ((q)->cap - 1)) + +/** + * Clear the queue without deallocating underlying memory. + * @param q queue + */ +#define sc_queue_clear(q) \ + do { \ + (q)->first = 0; \ + (q)->last = 0; \ + (q)->oom = false; \ + } while (0) + +/** + * @param q queue + * @return true if queue is empty + */ +#define sc_queue_empty(q) (((q)->last == (q)->first)) + +/** + * @param q queue + * @return index of the first element. If queue is empty, result is undefined. + */ +#define sc_queue_first(q) ((q)->first) + +/** + * @param q queue + * @return index of the last element. If queue is empty, result is undefined. + */ +#define sc_queue_last(q) ((q)->last) + +/** + * @param q queue + * @param i index + * @return index of the next element after i, if i is the last element, + * result is undefined. + */ +#define sc_queue_next(q, i) (((i) + 1) & ((q)->cap - 1)) + +/** + * Returns element at index 'i', so regular loops are possible : + * + * for (size_t i = 0; i < sc_queue_size(q); i++) { + * printf("%d" \n, sc_queue_at(q, i)); + * } + * + * @param q queue + * @return element at index i + */ +#define sc_queue_at(q, i) (q)->elems[(((q)->first) + (i)) & ((q)->cap - 1)] + +/** + * @param q queue + * @return peek first element, if queue is empty, result is undefined + */ +#define sc_queue_peek_first(q) ((q)->elems[(q)->first]) + +/** + * @param q queue + * @return peek last element, if queue is empty, result is undefined + */ +#define sc_queue_peek_last(q) (q)->elems[((q)->last - 1) & ((q)->cap - 1)] + +/** + * Call sc_queue_oom(q) after this function to check out of memory condition. + * + * @param q queue + * @param elem elem to be added at the end of the list + */ +#define sc_queue_add_last(q, elem) \ + do { \ + sc_queue_expand(q); \ + if ((q)->oom) { \ + break; \ + } \ + (q)->oom = false; \ + (q)->elems[(q)->last] = elem; \ + (q)->last = ((q)->last + 1) & ((q)->cap - 1); \ + } while (0) + +/** + * @param q queue + * @return delete the last element from the queue and return its value. + * If queue is empty, result is undefined. + */ +#define sc_queue_del_last(q) \ + ((q)->elems[((q)->last = ((q)->last - 1) & ((q)->cap - 1))]) + +/** + * Call sc_queue_oom(q) after this function to check out of memory condition. + * + * @param q queue. + * @param elem elem to be added at the head of the list. + */ +#define sc_queue_add_first(q, elem) \ + do { \ + sc_queue_expand(q); \ + if ((q)->oom) { \ + break; \ + } \ + (q)->oom = false; \ + (q)->first = ((q)->first - 1) & ((q)->cap - 1); \ + (q)->elems[(q)->first] = elem; \ + } while (0) + +static inline size_t sc_queue_inc_first(size_t *first, size_t cap) +{ + size_t tmp = *first; + + *first = (*first + 1) & (cap - 1); + return tmp; +} + +/** + * @param q queue + * @return delete the first element from the queue and return its value. + * If queue is empty, result is undefined. + */ +#define sc_queue_del_first(q) \ + (q)->elems[sc_queue_inc_first(&(q)->first, (q)->cap)] + +/** + * For each loop, + * + * int *queue; + * sc_queue_create(queue, 4);" + * + * int elem; + * sc_queue_foreach(queue, elem) { + * printf("Elem : %d \n, elem); + * } + */ +#define sc_queue_foreach(q, elem) \ + for (size_t _k = 1, _i = sc_queue_first(q); \ + _k && _i != sc_queue_last(q); \ + _k = !_k, _i = sc_queue_next(q, _i)) \ + for ((elem) = (q)->elems[_i]; _k; _k = !_k) + +// (type, name) +sc_queue_def(int, int); +sc_queue_def(unsigned int, uint); +sc_queue_def(long, long); +sc_queue_def(unsigned long, ulong); +sc_queue_def(unsigned long long, ull); +sc_queue_def(uint32_t, 32); +sc_queue_def(uint64_t, 64); +sc_queue_def(double, double); +sc_queue_def(const char *, str); +sc_queue_def(void *, ptr); + +#endif