diff trunk/chipmunkd/cpHashSet.d @ 4:7ebbd4d05553

initial commit
author Extrawurst
date Thu, 02 Dec 2010 02:11:26 +0100
parents
children df4ebc8add66
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/chipmunkd/cpHashSet.d	Thu Dec 02 02:11:26 2010 +0100
@@ -0,0 +1,277 @@
+
+// written in the D programming language
+
+module chipmunkd.cpHashSet;
+
+import chipmunkd.chipmunk;
+import chipmunkd.chipmunk_types_h;
+import chipmunkd.cpArray;
+import chipmunkd.prime;
+
+// cpHashSet uses a chained hashtable implementation.
+// Other than the transformation functions, there is nothing fancy going on.
+
+// cpHashSetBin's form the linked lists in the chained hash table.
+struct cpHashSetBin {
+	// Pointer to the element.
+	void *elt;
+	// Hash value of the element.
+	cpHashValue hash;
+	// Next element in the chain.
+	cpHashSetBin *next;
+}
+
+// Equality function. Returns true if ptr is equal to elt.
+alias cpBool function(void *ptr, void *elt) cpHashSetEqlFunc;
+// Used by cpHashSetInsert(). Called to transform the ptr into an element.
+alias void* function(void *ptr, void *data) cpHashSetTransFunc;
+
+struct cpHashSet {
+	// Number of elements stored in the table.
+	int entries;
+	// Number of cells in the table.
+	int size;
+	
+	cpHashSetEqlFunc eql;
+	cpHashSetTransFunc trans;
+	
+	// Default value returned by cpHashSetFind() when no element is found.
+	// Defaults to null.
+	void *default_value;
+	
+	// The table and recycled bins
+	cpHashSetBin **table;
+	cpHashSetBin *pooledBins;
+	
+	cpArray *allocatedBuffers;
+}
+
+alias void function(void *elt, void *data) cpHashSetIterFunc;
+alias cpBool function(void *elt, void *data) cpHashSetFilterFunc;
+
+static void freeWrap(void *ptr, void *unused){cpfree(ptr);}
+
+void
+cpHashSetDestroy(cpHashSet *set)
+{
+	// Free the table.
+	cpfree(set.table);
+	
+	cpArrayEach(set.allocatedBuffers, &freeWrap, null);
+	cpArrayFree(set.allocatedBuffers);
+}
+
+void
+cpHashSetFree(cpHashSet *set)
+{
+	if(set){
+		cpHashSetDestroy(set);
+		cpfree(set);
+	}
+}
+
+cpHashSet *
+cpHashSetAlloc()
+{
+	return cast(cpHashSet *)cpcalloc(1, cpHashSet.sizeof);
+}
+
+cpHashSet *
+cpHashSetInit(cpHashSet *set, int size, cpHashSetEqlFunc eqlFunc, cpHashSetTransFunc trans)
+{
+	set.size = next_prime(size);
+	set.entries = 0;
+	
+	set.eql = eqlFunc;
+	set.trans = trans;
+	
+	set.default_value = null;
+	
+	set.table = cast(cpHashSetBin **)cpcalloc(set.size, (cpHashSetBin *).sizeof);
+	set.pooledBins = null;
+	
+	set.allocatedBuffers = cpArrayNew(0);
+	
+	return set;
+}
+
+cpHashSet *
+cpHashSetNew(int size, cpHashSetEqlFunc eqlFunc, cpHashSetTransFunc trans)
+{
+	return cpHashSetInit(cpHashSetAlloc(), size, eqlFunc, trans);
+}
+
+static int
+setIsFull(cpHashSet *set)
+{
+	return (set.entries >= set.size);
+}
+
+static void
+cpHashSetResize(cpHashSet *set)
+{
+	// Get the next approximate doubled prime.
+	int newSize = next_prime(set.size + 1);
+	// Allocate a new table.
+	cpHashSetBin **newTable = cast(cpHashSetBin **)cpcalloc(newSize, (cpHashSetBin *).sizeof);
+	
+	// Iterate over the chains.
+	for(int i=0; i<set.size; i++){
+		// Rehash the bins into the new table.
+		cpHashSetBin *bin = set.table[i];
+		while(bin){
+			cpHashSetBin *next = bin.next;
+			
+			int idx = bin.hash%newSize;
+			bin.next = newTable[idx];
+			newTable[idx] = bin;
+			
+			bin = next;
+		}
+	}
+	
+	cpfree(set.table);
+	
+	set.table = newTable;
+	set.size = newSize;
+}
+
+static void
+recycleBin(cpHashSet *set, cpHashSetBin *bin)
+{
+	bin.next = set.pooledBins;
+	set.pooledBins = bin;
+	bin.elt = null;
+}
+
+static cpHashSetBin *
+getUnusedBin(cpHashSet *set)
+{
+	cpHashSetBin *bin = set.pooledBins;
+	
+	if(bin){
+		set.pooledBins = bin.next;
+		return bin;
+	} else {
+		// Pool is exhausted, make more
+		int count = CP_BUFFER_BYTES/cpHashSetBin.sizeof;
+		assert(count!=0, "Buffer size is too small.");
+		
+		cpHashSetBin *buffer = cast(cpHashSetBin *)cpmalloc(CP_BUFFER_BYTES);
+		cpArrayPush(set.allocatedBuffers, buffer);
+		
+		// push all but the first one, return the first instead
+		for(int i=1; i<count; i++) recycleBin(set, buffer + i);
+		return buffer;
+	}
+}
+
+void *
+cpHashSetInsert(cpHashSet *set, cpHashValue hash, void *ptr, void *data)
+{
+	int idx = hash%set.size;
+	
+	// Find the bin with the matching element.
+	cpHashSetBin *bin = set.table[idx];
+	while(bin && !set.eql(ptr, bin.elt))
+		bin = bin.next;
+	
+	// Create it necessary.
+	if(!bin){
+		bin = getUnusedBin(set);
+		bin.hash = hash;
+		bin.elt = set.trans(ptr, data); // Transform the pointer.
+		
+		bin.next = set.table[idx];
+		set.table[idx] = bin;
+		
+		set.entries++;
+		
+		// Resize the set if it's full.
+		if(setIsFull(set))
+			cpHashSetResize(set);
+	}
+	
+	return bin.elt;
+}
+
+void *
+cpHashSetRemove(cpHashSet *set, cpHashValue hash, void *ptr)
+{
+	int idx = hash%set.size;
+	
+	// Pointer to the previous bin pointer.
+	cpHashSetBin **prev_ptr = &set.table[idx];
+	// Pointer the the current bin.
+	cpHashSetBin *bin = set.table[idx];
+	
+	// Find the bin
+	while(bin && !set.eql(ptr, bin.elt)){
+		prev_ptr = &bin.next;
+		bin = bin.next;
+	}
+	
+	// Remove it if it exists.
+	if(bin){
+		// Update the previous bin pointer to point to the next bin.
+		(*prev_ptr) = bin.next;
+		set.entries--;
+		
+		void *return_value = bin.elt;
+		
+		recycleBin(set, bin);
+		
+		return return_value;
+	}
+	
+	return null;
+}
+
+void *
+cpHashSetFind(cpHashSet *set, cpHashValue hash, void *ptr)
+{	
+	int idx = hash%set.size;
+	cpHashSetBin *bin = set.table[idx];
+	while(bin && !set.eql(ptr, bin.elt))
+		bin = bin.next;
+		
+	return (bin ? bin.elt : set.default_value);
+}
+
+void
+cpHashSetEach(cpHashSet *set, cpHashSetIterFunc func, void *data)
+{
+	for(int i=0; i<set.size; i++){
+		cpHashSetBin *bin = set.table[i];
+		while(bin){
+			cpHashSetBin *next = bin.next;
+			func(bin.elt, data);
+			bin = next;
+		}
+	}
+}
+
+void
+cpHashSetFilter(cpHashSet *set, cpHashSetFilterFunc func, void *data)
+{
+	// Iterate over all the chains.
+	for(int i=0; i<set.size; i++){
+		// The rest works similarly to cpHashSetRemove() above.
+		cpHashSetBin **prev_ptr = &set.table[i];
+		cpHashSetBin *bin = set.table[i];
+		while(bin){
+			cpHashSetBin *next = bin.next;
+			
+			if(func(bin.elt, data)){
+				prev_ptr = &bin.next;
+			} else {
+				(*prev_ptr) = next;
+
+				set.entries--;
+				recycleBin(set, bin);
+			}
+			
+			bin = next;
+		}
+	}
+}