Common update:
-added multiple new function to allocate template and add entity -updated README.md (complete initial version) -empty components now don't take memory -fixedd small bug with TestRunner -added many new tests (HashMap, Vector, EntityMeta, ...) -added default hashing function to HashMap -fixed critical bug with adding entities -fixed small bug with adding entity with remplacement components -added asserts into code to better bug detection -small performance improvement for events -added ComponentRef structure which contain data pointer and componentID -remove EntityID from Event structure -now events are handled before removing entiteis -fixed GDC compilation -fixed rendering of rotated sprites -added weapons as separate entities to space ship and others -added Tower enemy to SpaceInvaders demo -added Boss to SpaceInvaders demo (boss has four tower attached to it) -Boss towers shoot multiple bullets upon death -fixed critical bug with demos switching -fixed critical bug related to adding/removing entities form inside onAdd/onRemove entity callback -added animation support -added particles sypport and particles for firing and explostions, and more -multithreaded rendering now has same rendering order as singlethreaded -application automaticallu detect host CPU threads count -added upgrades to SPaceInvaders demo -fixed texture memory freeing -improved documentation -improved multithreaded performance -improve shader code -fixed registration issue -some additional performance improvements -added depth and colors to rendering parameters -jobs now has names corresponding to their systems -change execute() -> willExecute() -added EntityMeta structure to speedup getting fetching components form entity -improved multithreading rendering -added possibility tio change number of threads runtime -added bullets collision detection in SpaceInvaders demo -some CI changes -added VBO batch rendering (current default, no render mode switch yet) -fixed camera positioning calculation -fixed buffer issue with WebGL -added viewport scalling (at least 300 pixels height). Pixels are scalled if screen is bigger. -center demos gameplay area -added fullpage html template for Emscripten build -added many new sprites to atlas -fixed critical bug with CPU usage in multithreaded mode -snake render tile coresponding to body part -snake is destroyed after collision and emit some particles -added some functionality to vectors -fixed documentation issue in Manager.d -more minor code changes and cleanup
This commit is contained in:
parent
2ddb97e9ce
commit
024356df9b
62 changed files with 5918 additions and 1673 deletions
370
source/bubel/ecs/hash_map.d
Executable file
370
source/bubel/ecs/hash_map.d
Executable file
|
|
@ -0,0 +1,370 @@
|
|||
module bubel.ecs.hash_map;
|
||||
|
||||
import std.traits;
|
||||
|
||||
import bubel.ecs.vector;
|
||||
import bubel.ecs.traits;
|
||||
|
||||
enum doNotInline = "version(DigitalMars)pragma(inline,false);version(LDC)pragma(LDC_never_inline);";
|
||||
|
||||
private enum HASH_EMPTY = 0;
|
||||
private enum HASH_DELETED = 0x1;
|
||||
private enum HASH_FILLED_MARK = ulong(1) << 8 * ulong.sizeof - 1;
|
||||
|
||||
export ulong defaultHashFunc(T)(auto ref T t) nothrow @nogc
|
||||
{
|
||||
static if (isIntegral!(T))
|
||||
{
|
||||
return hashInt(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
static if(isArray!T)return hashInt(hash((cast(byte*)t.ptr)[0 .. t.length * ForeachType!(T).sizeof]));
|
||||
else return hashInt(hash((cast(byte*)&t)[0 .. T.sizeof])); //hashInt(t.hashOf); // hashOf is not giving proper distribution between H1 and H2 hash parts
|
||||
}
|
||||
}
|
||||
|
||||
ulong hash(byte[] array) nothrow @nogc
|
||||
{
|
||||
ulong hash = 0;
|
||||
|
||||
foreach(c;array)
|
||||
hash = c + (hash << 6) + (hash << 16) - hash;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Can turn bad hash function to good one
|
||||
export ulong hashInt(ulong x) nothrow @nogc @safe
|
||||
{
|
||||
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
|
||||
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
|
||||
x = x ^ (x >> 31);
|
||||
return x;
|
||||
}
|
||||
|
||||
struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc)
|
||||
{
|
||||
alias Key = KeyPar;
|
||||
alias Value = ValuePar;
|
||||
nothrow:
|
||||
|
||||
enum rehashFactor = 0.75;
|
||||
enum size_t getIndexEmptyValue = size_t.max;
|
||||
|
||||
static struct KeyVal
|
||||
{
|
||||
Key key;
|
||||
Value value;
|
||||
}
|
||||
|
||||
static struct Bucket
|
||||
{
|
||||
ulong hash;
|
||||
KeyVal keyValue;
|
||||
}
|
||||
|
||||
Vector!Bucket elements; // Length should be always power of 2
|
||||
size_t length; // Used to compute loadFactor
|
||||
size_t markerdDeleted;
|
||||
|
||||
export void clear()
|
||||
{
|
||||
elements.clear();
|
||||
length = 0;
|
||||
markerdDeleted = 0;
|
||||
}
|
||||
|
||||
export void reset()
|
||||
{
|
||||
elements.reset();
|
||||
length = 0;
|
||||
markerdDeleted = 0;
|
||||
}
|
||||
|
||||
export bool isIn(ref Key el)
|
||||
{
|
||||
return getIndex(el) != getIndexEmptyValue;
|
||||
}
|
||||
|
||||
export bool isIn(Key el)
|
||||
{
|
||||
return getIndex(el) != getIndexEmptyValue;
|
||||
}
|
||||
|
||||
export Value* getPtr()(auto ref Key k)
|
||||
{
|
||||
size_t index = getIndex(k);
|
||||
if (index == getIndexEmptyValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return &elements[index].keyValue.value;
|
||||
}
|
||||
}
|
||||
|
||||
export ref Value get()(auto ref Key k)
|
||||
{
|
||||
size_t index = getIndex(k);
|
||||
assert(index != getIndexEmptyValue);
|
||||
return elements[index].keyValue.value;
|
||||
}
|
||||
|
||||
deprecated("Use get with second parameter.") export auto ref Value getDefault()(
|
||||
auto ref Key k, auto ref Value defaultValue)
|
||||
{
|
||||
return get(k, defaultValue);
|
||||
}
|
||||
|
||||
export auto ref Value get()(auto ref Key k, auto ref Value defaultValue)
|
||||
{
|
||||
size_t index = getIndex(k);
|
||||
if (index == getIndexEmptyValue)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return elements[index].keyValue.value;
|
||||
}
|
||||
}
|
||||
|
||||
export ref Value getInsertDefault()(auto ref Key k, auto ref Value defaultValue)
|
||||
{
|
||||
size_t index = getIndex(k);
|
||||
if (index == getIndexEmptyValue)
|
||||
{
|
||||
add(k, defaultValue);
|
||||
}
|
||||
index = getIndex(k);
|
||||
assert(index != getIndexEmptyValue);
|
||||
return elements[index].keyValue.value;
|
||||
|
||||
}
|
||||
|
||||
export bool tryRemove(Key el)
|
||||
{
|
||||
size_t index = getIndex(el);
|
||||
if (index == getIndexEmptyValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
length--;
|
||||
elements[index].hash = HASH_DELETED;
|
||||
markerdDeleted++;
|
||||
return true;
|
||||
}
|
||||
|
||||
export void remove(Key el)
|
||||
{
|
||||
bool ok = tryRemove(el);
|
||||
assert(ok);
|
||||
}
|
||||
|
||||
export ref Value opIndex()(auto ref Key key)
|
||||
{
|
||||
return get(key);
|
||||
}
|
||||
|
||||
export void opIndexAssign()(auto ref Value value, auto ref Key key)
|
||||
{
|
||||
add(key, value);
|
||||
}
|
||||
|
||||
export void add()(auto ref Key key, auto ref Value value)
|
||||
{
|
||||
size_t index = getIndex(key);
|
||||
if (index != getIndexEmptyValue)
|
||||
{
|
||||
elements[index].keyValue.value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (getLoadFactor(length + 1) > rehashFactor
|
||||
|| getLoadFactor(length + markerdDeleted) > rehashFactor)
|
||||
{
|
||||
rehash();
|
||||
}
|
||||
length++;
|
||||
|
||||
immutable ulong hash = hashFunc(key) | HASH_FILLED_MARK;
|
||||
immutable size_t rotateMask = elements.length - 1;
|
||||
index = hash & rotateMask; // Starting point
|
||||
|
||||
while (true)
|
||||
{
|
||||
Bucket* gr = &elements[index];
|
||||
if ((gr.hash & HASH_FILLED_MARK) == 0)
|
||||
{
|
||||
if (gr.hash == HASH_DELETED)
|
||||
{
|
||||
markerdDeleted--;
|
||||
}
|
||||
gr.hash = hash;
|
||||
gr.keyValue.key = key;
|
||||
gr.keyValue.value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
index++;
|
||||
index = index & rotateMask;
|
||||
}
|
||||
}
|
||||
|
||||
// For debug
|
||||
//int numA;
|
||||
//int numB;
|
||||
|
||||
export size_t getIndex(Key el)
|
||||
{
|
||||
return getIndex(el);
|
||||
}
|
||||
|
||||
export size_t getIndex(ref Key el)
|
||||
{
|
||||
mixin(doNotInline);
|
||||
|
||||
immutable size_t groupsLength = elements.length;
|
||||
if (groupsLength == 0)
|
||||
{
|
||||
return getIndexEmptyValue;
|
||||
}
|
||||
|
||||
immutable ulong hash = hashFunc(el) | HASH_FILLED_MARK;
|
||||
immutable size_t rotateMask = groupsLength - 1;
|
||||
size_t index = hash & rotateMask; // Starting point
|
||||
|
||||
//numA++;
|
||||
while (true)
|
||||
{
|
||||
//numB++;
|
||||
Bucket* gr = &elements[index];
|
||||
if (gr.hash == hash && gr.keyValue.key == el)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
if (gr.hash == HASH_EMPTY)
|
||||
{
|
||||
return getIndexEmptyValue;
|
||||
}
|
||||
|
||||
index++;
|
||||
index = index & rotateMask;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export float getLoadFactor(size_t forElementsNum)
|
||||
{
|
||||
if (elements.length == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return cast(float) forElementsNum / (elements.length);
|
||||
}
|
||||
|
||||
export void rehash()()
|
||||
{
|
||||
mixin(doNotInline);
|
||||
// Get all elements
|
||||
Vector!KeyVal allElements;
|
||||
allElements.reserve(elements.length);
|
||||
|
||||
foreach (ref Bucket el; elements)
|
||||
{
|
||||
if ((el.hash & HASH_FILLED_MARK) == 0)
|
||||
{
|
||||
el.hash = HASH_EMPTY;
|
||||
continue;
|
||||
}
|
||||
el.hash = HASH_EMPTY;
|
||||
allElements ~= el.keyValue;
|
||||
|
||||
}
|
||||
|
||||
if (getLoadFactor(length + 1) > rehashFactor)
|
||||
{ // Reallocate
|
||||
elements.length = (elements.length ? elements.length : 4) << 1; // Power of two, initially 8 elements
|
||||
}
|
||||
|
||||
// Insert elements
|
||||
foreach (i, ref el; allElements)
|
||||
{
|
||||
add(el.key, el.value);
|
||||
}
|
||||
length = allElements.length;
|
||||
markerdDeleted = 0;
|
||||
}
|
||||
|
||||
// foreach support
|
||||
export int opApply(DG)(scope DG dg)
|
||||
{
|
||||
int result;
|
||||
foreach (ref Bucket gr; elements)
|
||||
{
|
||||
if ((gr.hash & HASH_FILLED_MARK) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
static if (isForeachDelegateWithTypes!(DG, Key))
|
||||
{
|
||||
result = dg(gr.keyValue.key);
|
||||
}
|
||||
else static if (isForeachDelegateWithTypes!(DG, Value))
|
||||
{
|
||||
result = dg(gr.keyValue.value);
|
||||
}
|
||||
else static if (isForeachDelegateWithTypes!(DG, Key, Value))
|
||||
{
|
||||
result = dg(gr.keyValue.key, gr.keyValue.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
static assert(0);
|
||||
}
|
||||
if (result)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export int byKey(scope int delegate(Key k) nothrow dg)
|
||||
{
|
||||
int result;
|
||||
foreach (ref Key k; this)
|
||||
{
|
||||
result = dg(k);
|
||||
if (result)
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export int byValue(scope int delegate(ref Value k) nothrow dg)
|
||||
{
|
||||
int result;
|
||||
foreach (ref Value v; this)
|
||||
{
|
||||
result = dg(v);
|
||||
if (result)
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export int byKeyValue(scope int delegate(ref Key k, ref Value v) nothrow dg)
|
||||
{
|
||||
int result;
|
||||
foreach (ref Key k, ref Value v; this)
|
||||
{
|
||||
result = dg(k, v);
|
||||
if (result)
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue