Explorar o código

- added some missing copyrights
- merged (bs-coreoptimize->trunk) generic athena style configuration parser (raconf)
- merged (bs-coreoptimize->trunk) threadsafe memory pool (mempool) [i need it for the new 'socket' system]
- set svn:eol-style property on newer files were it was missing



git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@16263 54d463be-8e91-2dee-dedb-b68131a5f0ec

blacksirius %!s(int64=13) %!d(string=hai) anos
pai
achega
3471d3f35e

+ 6 - 0
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

+ 1 - 1
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)
 

+ 3 - 0
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_
 

+ 3 - 2
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
 

+ 562 - 0
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()
+

+ 100 - 0
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

+ 2 - 0
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"

+ 3 - 0
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_
 

+ 584 - 0
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()

+ 59 - 0
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

+ 7 - 0
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"

+ 3 - 0
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_