diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/common/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/common/Makefile.in | 2 | ||||
-rw-r--r-- | src/common/atomic.h | 3 | ||||
-rw-r--r-- | src/common/core.c | 5 | ||||
-rw-r--r-- | src/common/mempool.c | 562 | ||||
-rw-r--r-- | src/common/mempool.h | 100 | ||||
-rw-r--r-- | src/common/mutex.c | 2 | ||||
-rw-r--r-- | src/common/mutex.h | 3 | ||||
-rw-r--r-- | src/common/raconf.c | 584 | ||||
-rw-r--r-- | src/common/raconf.h | 59 | ||||
-rw-r--r-- | src/common/thread.c | 7 | ||||
-rw-r--r-- | src/common/thread.h | 3 |
12 files changed, 1333 insertions, 3 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 46c32c236..b32c33611 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -79,6 +79,9 @@ set( COMMON_BASE_HEADERS "${COMMON_SOURCE_DIR}/atomic.h" "${COMMON_SOURCE_DIR}/spinlock.h" "${COMMON_SOURCE_DIR}/thread.h" + "${COMMON_SOURCE_DIR}/mutex.h" + "${COMMON_SOURCE_DIR}/raconf.h" + "${COMMON_SOURCE_DIR}/mempool.h" ${LIBCONFIG_HEADERS} # needed by conf.h/showmsg.h CACHE INTERNAL "common_base headers" ) set( COMMON_BASE_SOURCES @@ -100,6 +103,9 @@ set( COMMON_BASE_SOURCES "${COMMON_SOURCE_DIR}/timer.c" "${COMMON_SOURCE_DIR}/utils.c" "${COMMON_SOURCE_DIR}/thread.c" + "${COMMON_SOURCE_DIR}/mutex.c" + "${COMMON_SOURCE_DIR}/mempool.c" + "${COMMON_SOURCE_DIR}/raconf.c" ${LIBCONFIG_SOURCES} # needed by conf.c/showmsg.c CACHE INTERNAL "common_base sources" ) set( COMMON_BASE_INCLUDE_DIRS diff --git a/src/common/Makefile.in b/src/common/Makefile.in index 3f03982d7..b6713b6a1 100644 --- a/src/common/Makefile.in +++ b/src/common/Makefile.in @@ -3,7 +3,7 @@ COMMON_OBJ = obj_all/core.o obj_all/socket.o obj_all/timer.o obj_all/db.o obj_al obj_all/nullpo.o obj_all/malloc.o obj_all/showmsg.o obj_all/strlib.o obj_all/utils.o \ obj_all/grfio.o obj_all/mapindex.o obj_all/ers.o obj_all/md5calc.o \ obj_all/minicore.o obj_all/minisocket.o obj_all/minimalloc.o obj_all/random.o obj_all/des.o \ - obj_all/conf.o obj_all/thread.o obj_all/mutex.o + obj_all/conf.o obj_all/thread.o obj_all/mutex.o obj_all/raconf.o obj_all/mempool.o COMMON_H = $(shell ls ../common/*.h) diff --git a/src/common/atomic.h b/src/common/atomic.h index 7a9e8c4cc..c09d8d386 100644 --- a/src/common/atomic.h +++ b/src/common/atomic.h @@ -1,3 +1,6 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + #ifndef _rA_ATOMIC_H_ #define _rA_ATOMIC_H_ diff --git a/src/common/core.c b/src/common/core.c index 4e276fcdc..2923b4de7 100644 --- a/src/common/core.c +++ b/src/common/core.c @@ -10,6 +10,7 @@ #include "../common/socket.h" #include "../common/timer.h" #include "../common/thread.h" +#include "../common/mempool.h" #endif #include <stdio.h> @@ -280,7 +281,7 @@ int main (int argc, char **argv) usercheck(); rathread_init(); - + mempool_init(); db_init(); signals_init(); @@ -306,7 +307,7 @@ int main (int argc, char **argv) timer_final(); socket_final(); db_final(); - + mempool_final(); rathread_final(); #endif diff --git a/src/common/mempool.c b/src/common/mempool.c new file mode 100644 index 000000000..ab401f5e0 --- /dev/null +++ b/src/common/mempool.c @@ -0,0 +1,562 @@ + +// +// Memory Pool Implementation (Threadsafe) +// +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifdef WIN32 +#include "../common/winapi.h" +#else +#include <unistd.h> +#endif + +#include "../common/cbasetypes.h" +#include "../common/showmsg.h" +#include "../common/mempool.h" +#include "../common/atomic.h" +#include "../common/spinlock.h" +#include "../common/thread.h" +#include "../common/malloc.h" +#include "../common/mutex.h" + +#define ALIGN16 ra_align(16) +#define ALIGN_TO(x, a) (x + ( a - ( x % a) ) ) +#define ALIGN_TO_16(x) ALIGN_TO(x, 16) + +#undef MEMPOOL_DEBUG +#define MEMPOOLASSERT + + +#define NODE_TO_DATA(x) ( ((char*)x) + sizeof(struct node) ) +#define DATA_TO_NODE(x) ( (struct node*)(((char*)x) - sizeof(struct node)) ) +struct ra_align(16) node{ + void *next; + void *segment; +#ifdef MEMPOOLASSERT + bool used; + uint64 magic; + #define NODE_MAGIC 0xBEEF00EAEACAFE07ll +#endif +}; + + +// The Pointer to this struct is the base address of the segment itself. +struct pool_segment{ + mempool pool; // pool, this segment belongs to + struct pool_segment *next; + int64 num_nodes_total; + int64 num_bytes; +}; + + +struct mempool{ + // Settings + char *name; + uint64 elem_size; + uint64 elem_realloc_step; + int64 elem_realloc_thresh; + + // Callbacks that get called for every node that gets allocated + // Example usage: initialization of mutex/lock for each node. + memPoolOnNodeAllocationProc onalloc; + memPoolOnNodeDeallocationProc ondealloc; + + // Locks + SPIN_LOCK segmentLock; + SPIN_LOCK nodeLock; + + + // Internal + struct pool_segment *segments; + struct node *free_list; + + volatile int64 num_nodes_total; + volatile int64 num_nodes_free; + + volatile int64 num_segments; + volatile int64 num_bytes_total; + + volatile int64 peak_nodes_used; // Peak Node Usage + volatile int64 num_realloc_events; // Number of reallocations done. (allocate additional nodes) + + // list (used for global management such as allocator..) + struct mempool *next; +} ra_align(8); // Dont touch the alignment, otherwise interlocked functions are broken .. + + +/// +// Implementation: +// +static void segment_allocate_add(mempool p, uint64 count); + +static SPIN_LOCK l_mempoolListLock; +static mempool l_mempoolList = NULL; +static rAthread l_async_thread = NULL; +static ramutex l_async_lock = NULL; +static racond l_async_cond = NULL; +static volatile int32 l_async_terminate = 0; + +static void *mempool_async_allocator(void *x){ + mempool p; + + + while(1){ + if(l_async_terminate > 0) + break; + + EnterSpinLock(&l_mempoolListLock); + + for(p = l_mempoolList; p != NULL; p = p->next){ + + if(p->num_nodes_free < p->elem_realloc_thresh){ + // add new segment. + segment_allocate_add(p, p->elem_realloc_step); + // increase stats counter + InterlockedIncrement64(&p->num_realloc_events); + } + + } + + LeaveSpinLock(&l_mempoolListLock); + + ramutex_lock( l_async_lock ); + racond_wait( l_async_cond, l_async_lock, -1 ); + ramutex_unlock( l_async_lock ); + } + + + return NULL; +}//end: mempool_async_allocator() + + +void mempool_init(){ + + if(sizeof(struct node)%16 != 0 ){ + ShowFatalError("mempool_init: struct node alignment failure. %u != multiple of 16\n", sizeof(struct node)); + exit(EXIT_FAILURE); + } + + // Global List start + InitializeSpinLock(&l_mempoolListLock); + l_mempoolList = NULL; + + // Initialize mutex + stuff needed for async allocator worker. + l_async_terminate = 0; + l_async_lock = ramutex_create(); + l_async_cond = racond_create(); + + l_async_thread = rathread_createEx(mempool_async_allocator, NULL, 512*1024, RAT_PRIO_NORMAL); + if(l_async_thread == NULL){ + ShowFatalError("mempool_init: cannot spawn Async Allocator Thread.\n"); + exit(EXIT_FAILURE); + } + +}//end: mempool_init() + + +void mempool_final(){ + mempool p, pn; + + ShowStatus("Mempool: Terminating async. allocation worker and remaining pools.\n"); + + // Terminate worker / wait until its terminated. + InterlockedIncrement(&l_async_terminate); + racond_signal(l_async_cond); + rathread_wait(l_async_thread, NULL); + + // Destroy cond var and mutex. + racond_destroy( l_async_cond ); + ramutex_destroy( l_async_lock ); + + // Free remaining mempools + // ((bugged code! this should halppen, every mempool should + // be freed by the subsystem that has allocated it.) + // + EnterSpinLock(&l_mempoolListLock); + p = l_mempoolList; + while(1){ + if(p == NULL) + break; + + pn = p->next; + + ShowWarning("Mempool [%s] was not properly destroyed - forcing destroy.\n", p->name); + mempool_destroy(p); + + p = pn; + } + LeaveSpinLock(&l_mempoolListLock); + +}//end: mempool_final() + + +static void segment_allocate_add(mempool p, uint64 count){ + + // Required Memory: + // sz( segment ) + // count * sz( real_node_size ) + // + // where real node size is: + // ALIGN_TO_16( sz( node ) ) + p->elem_size + // so the nodes usable address is nodebase + ALIGN_TO_16(sz(node)) + // + size_t total_sz; + struct pool_segment *seg = NULL; + struct node *nodeList = NULL; + struct node *node = NULL; + char *ptr = NULL; + uint64 i; + + total_sz = ALIGN_TO_16( sizeof(struct pool_segment) ) + + ( (size_t)count * (sizeof(struct node) + (size_t)p->elem_size) ) ; + +#ifdef MEMPOOL_DEBUG + ShowDebug("Mempool [%s] Segment AllocateAdd (num: %u, total size: %0.2fMiB)\n", p->name, count, (float)total_sz/1024.f/1024.f); +#endif + + // allocate! (spin forever until weve got the memory.) + i=0; + while(1){ + ptr = (char*)aMalloc(total_sz); + if(ptr != NULL) break; + + i++; // increase failcount. + if(!(i & 7)){ + ShowWarning("Mempool [%s] Segment AllocateAdd => System seems to be Out of Memory (%0.2f MiB). Try #%u\n", (float)total_sz/1024.f/1024.f, i); +#ifdef WIN32 + Sleep(1000); +#else + sleep(1); +#endif + }else{ + rathread_yield(); /// allow/force vuln. ctxswitch + } + }//endwhile: allocation spinloop. + + // Clear Memory. + memset(ptr, 0x00, total_sz); + + // Initialize segment struct. + seg = (struct pool_segment*)ptr; + ptr += ALIGN_TO_16(sizeof(struct pool_segment)); + + seg->pool = p; + seg->num_nodes_total = count; + seg->num_bytes = total_sz; + + + // Initialze nodes! + nodeList = NULL; + for(i = 0; i < count; i++){ + node = (struct node*)ptr; + ptr += sizeof(struct node); + ptr += p->elem_size; + + node->segment = seg; +#ifdef MEMPOOLASSERT + node->used = false; + node->magic = NODE_MAGIC; +#endif + + if(p->onalloc != NULL) p->onalloc( NODE_TO_DATA(node) ); + + node->next = nodeList; + nodeList = node; + } + + + + // Link in Segment. + EnterSpinLock(&p->segmentLock); + seg->next = p->segments; + p->segments = seg; + LeaveSpinLock(&p->segmentLock); + + // Link in Nodes + EnterSpinLock(&p->nodeLock); + nodeList->next = p->free_list; + p->free_list = nodeList; + LeaveSpinLock(&p->nodeLock); + + + // Increase Stats: + InterlockedExchangeAdd64(&p->num_nodes_total, count); + InterlockedExchangeAdd64(&p->num_nodes_free, count); + InterlockedIncrement64(&p->num_segments); + InterlockedExchangeAdd64(&p->num_bytes_total, total_sz); + +}//end: segment_allocate_add() + + +mempool mempool_create(const char *name, + uint64 elem_size, + uint64 initial_count, + uint64 realloc_count, + memPoolOnNodeAllocationProc onNodeAlloc, + memPoolOnNodeDeallocationProc onNodeDealloc){ + //.. + uint64 realloc_thresh; + mempool pool; + pool = (mempool)aCalloc( 1, sizeof(struct mempool) ); + + if(pool == NULL){ + ShowFatalError("mempool_create: Failed to allocate %u bytes memory.\n", sizeof(struct mempool) ); + exit(EXIT_FAILURE); + } + + // Check minimum initial count / realloc count requirements. + if(initial_count < 50) + initial_count = 50; + if(realloc_count < 50) + realloc_count = 50; + + // Set Reallocation threshold to 5% of realloc_count, at least 10. + realloc_thresh = (realloc_count/100)*5; // + if(realloc_thresh < 10) + realloc_thresh = 10; + + // Initialize members.. + pool->name = aStrdup(name); + pool->elem_size = ALIGN_TO_16(elem_size); + pool->elem_realloc_step = realloc_count; + pool->elem_realloc_thresh = realloc_thresh; + pool->onalloc = onNodeAlloc; + pool->ondealloc = onNodeDealloc; + + InitializeSpinLock(&pool->segmentLock); + InitializeSpinLock(&pool->nodeLock); + + // Initial Statistic values: + pool->num_nodes_total = 0; + pool->num_nodes_free = 0; + pool->num_segments = 0; + pool->num_bytes_total = 0; + pool->peak_nodes_used = 0; + pool->num_realloc_events = 0; + + // +#ifdef MEMPOOL_DEBUG + ShowDebug("Mempool [%s] Init (ElemSize: %u, Initial Count: %u, Realloc Count: %u)\n", pool->name, pool->elem_size, initial_count, pool->elem_realloc_step); +#endif + + // Allocate first segment directly :) + segment_allocate_add(pool, initial_count); + + + // Add Pool to the global pool list + EnterSpinLock(&l_mempoolListLock); + pool->next = l_mempoolList; + l_mempoolList = pool; + LeaveSpinLock(&l_mempoolListLock); + + + return pool; +}//end: mempool_create() + + +void mempool_destroy(mempool p){ + struct pool_segment *seg, *segnext; + struct node *niter; + mempool piter, pprev; + char *ptr; + int64 i; + +#ifdef MEMPOOL_DEBUG + ShowDebug("Mempool [%s] Destroy\n", p->name); +#endif + + // Unlink from global list. + EnterSpinLock(&l_mempoolListLock); + piter = l_mempoolList; + pprev = l_mempoolList; + while(1){ + if(piter == NULL) + break; + + + if(piter == p){ + // unlink from list, + // + if(pprev == l_mempoolList){ + // this (p) is list begin. so set next as head. + l_mempoolList = p->next; + }else{ + // replace prevs next wuth our next. + pprev->next = p->next; + } + break; + } + + pprev = piter; + piter = piter->next; + } + + p->next = NULL; + LeaveSpinLock(&l_mempoolListLock); + + + // Get both locks. + EnterSpinLock(&p->segmentLock); + EnterSpinLock(&p->nodeLock); + + + if(p->num_nodes_free != p->num_nodes_total) + ShowWarning("Mempool [%s] Destroy - %u nodes are not freed properly!\n", p->name, (p->num_nodes_total - p->num_nodes_free) ); + + // Free All Segments (this will also free all nodes) + // The segment pointer is the base pointer to the whole segment. + seg = p->segments; + while(1){ + if(seg == NULL) + break; + + segnext = seg->next; + + // .. + if(p->ondealloc != NULL){ + // walk over the segment, and call dealloc callback! + ptr = (char*)seg; + ptr += ALIGN_TO_16(sizeof(struct pool_segment)); + for(i = 0; i < seg->num_nodes_total; i++){ + niter = (struct node*)ptr; + ptr += sizeof(struct node); + ptr += p->elem_size; +#ifdef MEMPOOLASSERT + if(niter->magic != NODE_MAGIC){ + ShowError("Mempool [%s] Destroy - walk over segment - node %p invalid magic!\n", p->name, niter); + continue; + } +#endif + + p->ondealloc( NODE_TO_DATA(niter) ); + + + } + }//endif: ondealloc callback? + + // simple .. + aFree(seg); + + seg = segnext; + } + + // Clear node ptr + p->free_list = NULL; + InterlockedExchange64(&p->num_nodes_free, 0); + InterlockedExchange64(&p->num_nodes_total, 0); + InterlockedExchange64(&p->num_segments, 0); + InterlockedExchange64(&p->num_bytes_total, 0); + + LeaveSpinLock(&p->nodeLock); + LeaveSpinLock(&p->segmentLock); + + // Free pool itself :D + aFree(p->name); + aFree(p); + +}//end: mempool_destroy() + + +void *mempool_node_get(mempool p){ + struct node *node; + int64 num_used; + + if(p->num_nodes_free < p->elem_realloc_thresh) + racond_signal(l_async_cond); + + while(1){ + + EnterSpinLock(&p->nodeLock); + + node = p->free_list; + if(node != NULL) + p->free_list = node->next; + + LeaveSpinLock(&p->nodeLock); + + if(node != NULL) + break; + + rathread_yield(); + } + + InterlockedDecrement64(&p->num_nodes_free); + + // Update peak value + num_used = (p->num_nodes_total - p->num_nodes_free); + if(num_used > p->peak_nodes_used){ + InterlockedExchange64(&p->peak_nodes_used, num_used); + } + +#ifdef MEMPOOLASSERT + node->used = true; +#endif + + return NODE_TO_DATA(node); +}//end: mempool_node_get() + + +void mempool_node_put(mempool p, void *data){ + struct node *node; + + node = DATA_TO_NODE(data); +#ifdef MEMPOOLASSERT + if(node->magic != NODE_MAGIC){ + ShowError("Mempool [%s] node_put failed, given address (%p) has invalid magic.\n", p->name, data); + return; // lost, + } + + { + struct pool_segment *node_seg = node->segment; + if(node_seg->pool != p){ + ShowError("Mempool [%s] node_put faild, given node (data address %p) doesnt belongs to this pool. ( Node Origin is [%s] )\n", p->name, data, node_seg->pool); + return; + } + } + + // reset used flag. + node->used = false; +#endif + + // + EnterSpinLock(&p->nodeLock); + node->next = p->free_list; + p->free_list = node; + LeaveSpinLock(&p->nodeLock); + + InterlockedIncrement64(&p->num_nodes_free); + +}//end: mempool_node_put() + + +mempool_stats mempool_get_stats(mempool pool){ + mempool_stats stats; + + // initialize all with zeros + memset(&stats, 0x00, sizeof(mempool_stats)); + + stats.num_nodes_total = pool->num_nodes_total; + stats.num_nodes_free = pool->num_nodes_free; + stats.num_nodes_used = (stats.num_nodes_total - stats.num_nodes_free); + stats.num_segments = pool->num_segments; + stats.num_realloc_events= pool->num_realloc_events; + stats.peak_nodes_used = pool->peak_nodes_used; + stats.num_bytes_total = pool->num_bytes_total; + + // Pushing such a large block over the stack as return value isnt nice + // but lazy :) and should be okay in this case (Stats / Debug..) + // if you dont like it - feel free and refactor it. + return stats; +}//end: mempool_get_stats() + diff --git a/src/common/mempool.h b/src/common/mempool.h new file mode 100644 index 000000000..aeaebe7fe --- /dev/null +++ b/src/common/mempool.h @@ -0,0 +1,100 @@ +#ifndef _rA_MEMPOOL_H_ +#define _rA_MEMPOOL_H_ + +#include "../common/cbasetypes.h" + +typedef struct mempool *mempool; + +typedef void (*memPoolOnNodeAllocationProc)(void *ptr); +typedef void (*memPoolOnNodeDeallocationProc)(void *ptr); + +typedef struct mempool_stats{ + int64 num_nodes_total; + int64 num_nodes_free; + int64 num_nodes_used; + + int64 num_segments; + int64 num_realloc_events; + + int64 peak_nodes_used; + + int64 num_bytes_total; +} mempool_stats; + + +// +void mempool_init(); +void mempool_final(); + + +/** + * Creates a new Mempool + * + * @param name - Name of the pool (used for debug / error messages) + * @param elem_size - size of each element + * @param initial_count - preallocation count + * @param realloc_count - #no of nodes being allocated when pool is running empty. + * @param onNodeAlloc - Node Allocation callback (see @note!) + * @param onNodeDealloc - Node Deallocation callback (see @note!) + * + * @note: + * The onNode(De)alloc callbacks are only called once during segment allocation + * (pool initialization / rallocation ) + * you can use this callbacks for example to initlaize a mutex or somethingelse + * you definitly need during runtime + * + * @return not NULL + */ +mempool mempool_create(const char *name, + uint64 elem_size, + uint64 initial_count, + uint64 realloc_count, + + memPoolOnNodeAllocationProc onNodeAlloc, + memPoolOnNodeDeallocationProc onNodeDealloc); + + +/** + * Destroys a Mempool + * + * @param pool - the mempool to destroy + * + * @note: + * Everything gets deallocated, regardless if everything was freed properly! + * So you have to ensure that all references are cleared properly! + */ +void mempool_destroy(mempool pool); + + +/** + * Gets a new / empty node from the given mempool. + * + * @param pool - the pool to get an empty node from. + * + * @return Address of empty Node + */ +void *mempool_node_get(mempool pool); + + +/** + * Returns the given node to the given mempool + * + * @param pool - the pool to put the node, to + * @param node - the node to return + */ +void mempool_node_put(mempool pool, void *node); + + +/** + * Returns Statistics for the given mempool + * + * @param pool - the pool to get thats for + * + * @note: i dont like pushing masses of values over the stack, too - but its lazy and okay for stats. (blacksirius) + * + * @return stats struct + */ +mempool_stats mempool_get_stats(mempool pool); + + +#endif diff --git a/src/common/mutex.c b/src/common/mutex.c index 874b81fa2..367574248 100644 --- a/src/common/mutex.c +++ b/src/common/mutex.c @@ -1,3 +1,5 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder #ifdef WIN32 #include "../common/winapi.h" diff --git a/src/common/mutex.h b/src/common/mutex.h index 4a32bcc8a..1999627cd 100644 --- a/src/common/mutex.h +++ b/src/common/mutex.h @@ -1,3 +1,6 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + #ifndef _rA_MUTEX_H_ #define _rA_MUTEX_H_ diff --git a/src/common/raconf.c b/src/common/raconf.c new file mode 100644 index 000000000..2703560ff --- /dev/null +++ b/src/common/raconf.c @@ -0,0 +1,584 @@ +// +// Athena style config parser +// (would be better to have "one" implementation instead of .. 4 :) +// +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) RAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/cbasetypes.h" +#include "../common/showmsg.h" +#include "../common/db.h" +#include "../common/malloc.h" + +#include "../common/raconf.h" + +#define SECTION_LEN 32 +#define VARNAME_LEN 64 + +struct raconf { + DBMap *db; +}; + + +struct conf_value{ + int64 intval; + bool bval; + double floatval; + size_t strval_len; // not includung \0 + char strval[16]; +}; + + + +static struct conf_value *makeValue(const char *key, char *val, size_t val_len){ + struct conf_value *v; + char *p; + size_t sz; + + sz = sizeof(struct conf_value); + if(val_len >= sizeof(v->strval)) + sz += (val_len - sizeof(v->strval) + 1); + + v = (struct conf_value*)aCalloc(1, sizeof(struct conf_value)); + if(v == NULL){ + ShowFatalError("raconf: makeValue => Out of Memory while allocating new node.\n"); + return NULL; + } + + memcpy(v->strval, val, val_len); + v->strval[val_len+1] = '\0'; + v->strval_len = val_len; + + + // Parse boolean value: + if((val_len == 4) && (strncmpi("true", val, 4) == 0)) + v->bval = true; + else if((val_len == 3) && (strncmpi("yes", val, 3) == 0)) + v->bval = true; + else if((val_len == 3) && (strncmpi("oui", val, 3) == 0)) + v->bval = true; + else if((val_len == 2) && (strncmpi("si", val, 2) == 0)) + v->bval = true; + else if((val_len == 2) && (strncmpi("ja", val, 2) == 0)) + v->bval = true; + else if((val_len == 1) && (*val == '1')) + v->bval = true; + else if((val_len == 5) && (strncmpi("false", val, 5) == 0)) + v->bval = false; + else if((val_len == 2) && (strncmpi("no", val, 2) == 0)) + v->bval = false; + else if((val_len == 3) && (strncmpi("non", val, 3) == 0)) + v->bval = false; + else if((val_len == 2) && (strncmpi("no", val, 2) == 0)) + v->bval = false; + else if((val_len == 4) && (strncmpi("nein", val, 4) == 0)) + v->bval = false; + else if((val_len == 1) && (*val == '0')) + v->bval = false; + else + v->bval = false; // assume false. + + // Parse number + // Supported formats: + // prefix: 0x hex . + // postix: h for hex + // b for bin (dual) + if( (val_len >= 1 && (val[val_len] == 'h')) || (val_len >= 2 && (val[0] == '0' && val[1] == 'x')) ){//HEX! + if(val[val_len] == 'h'){ + val[val_len]= '\0'; + v->intval = strtoull(val, NULL, 16); + val[val_len] = 'h'; + }else + v->intval = strtoull(&val[2], NULL, 16); + }else if( val_len >= 1 && (val[val_len] == 'b') ){ //BIN + val[val_len] = '\0'; + v->intval = strtoull(val, NULL, 2); + val[val_len] = 'b'; + }else if( *val >='0' && *val <= '9'){ // begins with normal digit, so assume its dec. + // is it float? + bool is_float = false; + + for(p = val; *p != '\0'; p++){ + if(*p == '.'){ + v->floatval = strtod(val, NULL); + v->intval = (int64) v->floatval; + is_float = true; + break; + } + } + + if(is_float == false){ + v->intval = strtoull(val, NULL, 10); + v->floatval = (double) v->intval; + } + }else{ + // Everything else: lets use boolean for fallback + if(v->bval == true) + v->intval = 1; + else + v->intval = 0; + } + + return v; +}//end: makeValue() + + +static bool configParse(raconf inst, const char *fileName){ + FILE *fp; + char line[4096]; + char currentSection[SECTION_LEN]; + char *p; + char c; + int linecnt; + size_t linelen; + size_t currentSection_len; + + fp = fopen(fileName, "r"); + if(fp == NULL){ + ShowError("configParse: cannot open '%s' for reading.\n", fileName); + return false; + } + + + // Start with empty section: + currentSection[0] = '\0'; + currentSection_len = 0; + + // + linecnt = 0; + while(1){ + linecnt++; + + if(fgets(line, sizeof(line), fp) != line) + break; + + linelen = strlen(line); + p = line; + + // Skip whitespaces from beginning (space and tab) + _line_begin_skip_whities: + c = *p; + if(c == ' ' || c == '\t'){ + p++; + linelen--; + goto _line_begin_skip_whities; + } + + // Remove linebreaks as (cr or lf) and whitespaces from line end! + _line_end_skip_whities_and_breaks: + c = p[linelen-1]; + if(c == '\r' || c == '\n' || c == ' ' || c == '\t'){ + p[--linelen] = '\0'; + goto _line_end_skip_whities_and_breaks; + } + + // Empty line? + // or line starts with comment (commented out)? + if(linelen == 0 || (p[0] == '/' && p[1] == '/') || p[0] == ';') + continue; + + // Variable names can contain: + // A-Za-z-_.0-9 + // + // Sections start with [ .. ] (INI Style) + // + c = *p; + + // check what we have.. :) + if(c == '['){ // got section! + // Got Section! + // Search for ] + char *start = (p+1); + + while(1){ + ++p; + c = *p; + + if(c == '\0'){ + ShowError("Syntax Error: unterminated Section name in %s:%u (expected ']')\n", fileName, linecnt); + fclose(fp); + return false; + }else if(c == ']'){ // closing backet (section name termination) + if( (p - start + 1) > (sizeof(currentSection) ) ){ + ShowError("Syntax Error: Section name in %s:%u is too large (max Supported length: %u chars)\n", fileName, linecnt, sizeof(currentSection)-1); + fclose(fp); + return false; + } + + // Set section! + *p = '\0'; // add termination here. + memcpy(currentSection, start, (p-start)+1 ); // we'll copy \0, too! (we replaced the ] backet with \0.) + currentSection_len = (p-start); + + break; + + }else if( (c >= '0' && c <= '9') || (c == '-') || (c == ' ') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ){ + // skip .. (allowed char / specifier) + continue; + }else{ + ShowError("Syntax Error: Invalid Character '%c' in %s:%u (offset %u) for Section name.\n", c, fileName, linecnt, (p-line)); + fclose(fp); + return false; + } + + }//endwhile: parse section name + + + }else if( (c >= '0' && c <= '9') || (c == '-') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ){ + // Got variable! + // Search for '=' or ':' wich termiantes the name + char *start = p; + char *valuestart = NULL; + size_t start_len; + + while(1){ + ++p; + c = *p; + + if(c == '\0'){ + ShowError("Syntax Error: unterminated Variable name in %s:%u\n", fileName, linecnt); + fclose(fp); + return false; + }else if( (c == '=') || (c == ':') ){ + // got name termination + + *p = '\0'; // Terminate it so (start) will hold the pointer to the name. + + break; + + }else if( (c >= '0' && c <= '9') || (c == '-') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ){ + // skip .. allowed char + continue; + }else{ + ShowError("Syntax Error: Invalid Character '%c' in %s:%u (offset %u) for Variable name.\n", c, fileName, linecnt, (p-line)); + fclose(fp); + return false; + } + + }//endwhile: parse var name + + start_len = (p-start); + if(start_len >= VARNAME_LEN){ + ShowError("%s:%u Variable length exceeds limit of %u Characters.\n", fileName, linecnt, VARNAME_LEN-1); + fclose(fp); + return false; + }else if(start_len == 0){ + ShowError("%s:%u Empty Variable name is not allowed.\n", fileName, linecnt); + fclose(fp); + return false; + } + + + valuestart = (p+1); + + + // Skip whitespace from begin of value (tab and space) + _skip_value_begin_whities: + c = *valuestart; + if(c == ' ' || c == '\t'){ + valuestart++; + goto _skip_value_begin_whities; + } + + // Scan for value termination, + // wich can be \0 or comment start (// or ; (INI) ) + // + p = valuestart; + while(1){ + c = *p; + if(c == '\0'){ + // Terminated by line end. + break; + }else if(c == '/' && p[1] == '/'){ + // terminated by c++ style comment. + *p = '\0'; + break; + }else if(c == ';'){ + // terminated by ini style comment. + *p = '\0'; + break; + } + + p++; + }//endwhile: search var value end. + + + // Strip whitespaces from end of value. + if(valuestart != p){ // not empty! + p--; + _strip_value_end_whities: + c = *p; + if(c == ' ' || c == '\t'){ + *p = '\0'; + p--; + goto _strip_value_end_whities; + } + p++; + } + + + // Buildin Hook: + if( stricmp(start, "import") == 0){ + if( configParse(inst, valuestart) != true){ + ShowError("%s:%u - Import of '%s' failed!\n", fileName, linecnt, valuestart); + } + }else{ + // put it to db. + struct conf_value *v, *o; + char key[ (SECTION_LEN+VARNAME_LEN+1+1) ]; //+1 for delimiter, +1 for termination. + size_t section_len; + + if(*currentSection == '\0'){ // empty / none + strncpy(key, "<unnamed>",9); + section_len = 9; + }else{ + strncpy(key, currentSection, currentSection_len); + section_len = currentSection_len; + } + + key[section_len] = '.'; // Delim + + strncpy(&key[section_len+1], start, start_len); + + key[section_len + start_len + 1] = '\0'; + + + v = makeValue(key, valuestart, (p-valuestart) ); + + // Try to get the old one before + o = strdb_get(inst->db, key); + if(o != NULL){ + strdb_remove(inst->db, key); + aFree(o); // + } + + strdb_put( inst->db, key, v); + } + + + }else{ + ShowError("Syntax Error: unexpected Character '%c' in %s:%u (offset %u)\n", c, fileName, linecnt, (p-line) ); + fclose(fp); + return false; + } + + + + } + + + + fclose(fp); + return true; +}//end: configParse() + + +#define MAKEKEY(dest, section, key) { size_t section_len, key_len; \ + if(section == NULL || *section == '\0'){ \ + strncpy(dest, "<unnamed>", 9); \ + section_len = 9; \ + }else{ \ + section_len = strlen(section); \ + strncpy(dest, section, section_len); \ + } \ + \ + dest[section_len] = '.'; \ + \ + key_len = strlen(key); \ + strncpy(&dest[section_len+1], key, key_len); \ + dest[section_len + key_len + 1] = '\0'; \ + } + + +raconf raconf_parse(const char *file_name){ + struct raconf *rc; + + rc = aCalloc(1, sizeof(struct raconf) ); + if(rc == NULL){ + ShowFatalError("raconf_parse: failed to allocate memory for new handle\n"); + return NULL; + } + + rc->db = strdb_alloc(DB_OPT_BASE | DB_OPT_DUP_KEY, 98); + // + + if(configParse(rc, file_name) != true){ + ShowError("Failed to Parse Configuration file '%s'\n", file_name); + } + + return rc; +}//end: raconf_parse() + + +void raconf_destroy(raconf rc){ + DBIterator *iter; + struct conf_value *v; + + // Clear all entrys in db. + iter = db_iterator(rc->db); + for( v = (struct conf_value*)dbi_first(iter); dbi_exists(iter); v = (struct conf_value*)dbi_next(iter) ){ + aFree(v); + } + dbi_destroy(iter); + + db_destroy(rc->db); + + aFree(rc); + +}//end: raconf_destroy() + +bool raconf_getbool(raconf rc, const char *section, const char *key, bool _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return v->bval; +}//end: raconf_getbool() + + +float raconf_getfloat(raconf rc,const char *section, const char *key, float _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return (float)v->floatval; +}//end: raconf_getfloat() + + +int64 raconf_getint(raconf rc, const char *section, const char *key, int64 _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return v->intval; + +}//end: raconf_getint() + + +const char* raconf_getstr(raconf rc, const char *section, const char *key, const char *_default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return v->strval; +}//end: raconf_getstr() + + +bool raconf_getboolEx(raconf rc, const char *section, const char *fallback_section, const char *key, bool _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return v->bval; + } + + }else{ + return v->bval; + } +}//end: raconf_getboolEx() + + +float raconf_getfloatEx(raconf rc,const char *section, const char *fallback_section, const char *key, float _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return (float)v->floatval; + } + + }else{ + return (float)v->floatval; + } + +}//end: raconf_getfloatEx() + + +int64 raconf_getintEx(raconf rc, const char *section, const char *fallback_section, const char *key, int64 _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return v->intval; + } + + }else{ + return v->intval; + } + +}//end: raconf_getintEx() + + +const char* raconf_getstrEx(raconf rc, const char *section, const char *fallback_section, const char *key, const char *_default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return v->strval; + } + + }else{ + return v->strval; + } + +}//end: raconf_getstrEx() diff --git a/src/common/raconf.h b/src/common/raconf.h new file mode 100644 index 000000000..68a2b51b2 --- /dev/null +++ b/src/common/raconf.h @@ -0,0 +1,59 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _rA_CONF_H_ +#define _rA_CONF_H_ + +#include "../common/cbasetypes.h" + +// rAthena generic configuration file parser +// +// Config file Syntax is athena style +// extended with ini style support (including sections) +// +// Comments are started with // or ; (ini style) +// + +typedef struct raconf *raconf; + + +/** + * Parses a rAthna Configuration file + * + * @param file_name path to the file to parse + * + * @returns not NULL incase of success + */ +raconf raconf_parse(const char *file_name); + + +/** + * Frees a Handle received from raconf_parse + * + * @param rc - the handle to free + */ +void raconf_destroy(raconf rc); + + +/** + * Gets the value for Section / Key pair, if key not exists returns _default! + * + */ +bool raconf_getbool(raconf rc, const char *section, const char *key, bool _default); +float raconf_getfloat(raconf rc,const char *section, const char *key, float _default); +int64 raconf_getint(raconf rc, const char *section, const char *key, int64 _default); +const char* raconf_getstr(raconf rc, const char *section, const char *key, const char *_default); + +/** + * Gets the value for Section / Key pair, but has fallback section option if not found in section, + * if not found in both - default gets returned. + * + */ +bool raconf_getboolEx(raconf rc, const char *section, const char *fallback_section, const char *key, bool _default); +float raconf_getfloatEx(raconf rc,const char *section, const char *fallback_section, const char *key, float _default); +int64 raconf_getintEx(raconf rc, const char *section, const char *fallback_section, const char *key, int64 _default); +const char* raconf_getstrEx(raconf rc, const char *section, const char *fallback_section, const char *key, const char *_default); + + + +#endif diff --git a/src/common/thread.c b/src/common/thread.c index 728c6c66a..baf4171da 100644 --- a/src/common/thread.c +++ b/src/common/thread.c @@ -1,3 +1,10 @@ +// +// Basic Threading abstraction (for pthread / win32 based systems) +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder #ifdef WIN32 #include "../common/winapi.h" diff --git a/src/common/thread.h b/src/common/thread.h index 8d3441868..a5a66e954 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -1,3 +1,6 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + #pragma once #ifndef _rA_THREAD_H_ #define _rA_THREAD_H_ |