bubel-ecs/tests/basic.d
Mergul 8ac9fa5dbd Fix crash in commit() when all components were removed from entity
Fix crash from issue #2 and add unittest case for it. Also implements general "empty" entity support
2023-04-24 23:46:16 +02:00

1828 lines
48 KiB
D

module tests.basic;
import bubel.ecs.core;
import bubel.ecs.manager;
import bubel.ecs.system;
import bubel.ecs.attributes;
version(GNU)
{
pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a)
{
return a;
}
}
else import std.array : staticArray;
struct CInt
{
// mixin ECS.Component;
alias value this;
int value = 1;
}
struct CFloat
{
// mixin ECS.Component;
alias value this;
float value = 2.0;
}
struct CDouble
{
// mixin ECS.Component;
alias value this;
double value = 3.0;
}
struct CLong
{
// mixin ECS.Component;
alias value this;
long value = 10;
}
struct CShort
{
// mixin ECS.Component;
alias value this;
short value = 12;
}
struct CUnregistered
{
// mixin ECS.Component;
alias value this;
short value = 12;
}
struct CFlag
{
// mixin ECS.Component;
}
struct LongAddSystem
{
mixin ECS.System;
struct EntitiesData
{
int length;
CLong[] long_;
}
void onUpdate(EntitiesData data)
{
updates_count++;
foreach(i;0..data.length)
{
data.long_[i] += 1;
}
}
int updates_count = 0;
}
struct EmptySystem
{
mixin ECS.System!16;
struct EntitiesData
{
int thread_id;
}
void onUpdate(EntitiesData data)
{
count++;
}
int count = 0;
}
struct EntityCounterSystem
{
mixin ECS.System!1;
struct EntitiesData
{
int thread_id;
uint length;
Entity[] entity;
}
bool onBegin()
{
count = 0;
return true;
}
void onUpdate(EntitiesData data)
{
count += data.length;
}
int count = 0;
}
void beforeEveryTest()
{
becsID!CUnregistered = ushort.max;
gEntityManager.initialize(0);
gEntityManager.beginRegister();
gEntityManager.registerComponent!CInt;
gEntityManager.registerComponent!CFloat;
gEntityManager.registerComponent!CDouble;
gEntityManager.registerComponent!CLong;
gEntityManager.registerComponent!CShort;
gEntityManager.registerComponent!CFlag;
gEntityManager.endRegister();
}
void afterEveryTest()
{
gEntityManager.destroy();
}
@("EntityMeta")
unittest
{
EntityTemplate* tmpl_ = gEntityManager.allocateTemplate([becsID!CInt, becsID!CFloat, becsID!CFlag].staticArray);
scope(exit)gEntityManager.freeTemplate(tmpl_);
Entity* entity = gEntityManager.addEntity(tmpl_);
EntityMeta meta = entity.getMeta();
assert(meta.hasComponent(becsID!CInt));
assert(meta.getComponent!CInt);
assert(meta.hasComponent(becsID!CFloat));
assert(meta.getComponent!CFloat);
assert(!meta.getComponent!CLong);
assert(!meta.hasComponent(becsID!CLong));
assert(!meta.getComponent!CUnregistered);
assert(!meta.hasComponent(becsID!CUnregistered));
assert(*meta.getComponent!CInt == 1);
assert(*meta.getComponent!CFloat == 2.0);
}
@("AddEntity")
unittest
{
EntityTemplate* tmpl_ = gEntityManager.allocateTemplate([becsID!CInt, becsID!CFloat, becsID!CFlag].staticArray);
scope(exit)gEntityManager.freeTemplate(tmpl_);
assert(tmpl_.info.components.length == 3);
assert(tmpl_.info.size == (CInt.sizeof + CFloat.sizeof + EntityID.sizeof));
assert(tmpl_.getComponent!CInt);
assert(tmpl_.getComponent!CFloat);
assert(tmpl_.getComponent!CFlag);
assert(!tmpl_.getComponent!CLong);
assert(!tmpl_.getComponent!CUnregistered);
assert(*tmpl_.getComponent!CInt == 1);
assert(*tmpl_.getComponent!CFloat == 2.0);
Entity* entity = gEntityManager.addEntity(tmpl_);
assert(entity.getComponent!CInt);
assert(entity.getComponent!CFloat);
assert(*entity.getComponent!CInt == 1);
assert(*entity.getComponent!CFloat == 2.0);
*entity.getComponent!CInt = 2;
Entity* entity2 = gEntityManager.addEntityCopy(entity.id);
assert(entity2.getComponent!CInt);
assert(entity2.getComponent!CFloat);
assert(*entity2.getComponent!CInt == 2);
assert(*entity2.getComponent!CFloat == 2.0);
CInt int1 = CInt(10);
CLong long1 = CLong();
CFlag flag1 = CFlag();
Entity* entity3 = gEntityManager.addEntity(tmpl_, [ComponentRef(&int1, becsID(int1)), ComponentRef(&long1, becsID(long1)), ComponentRef(&flag1, becsID(flag1))].staticArray);
EntityID id = entity3.id;
assert(entity3.hasComponent(becsID!CInt));
assert(entity3.hasComponent(becsID!CFloat));
assert(*entity3.getComponent!CInt == 10);
assert(*entity3.getComponent!CFloat == 2.0);
CShort short1 = CShort(2);
gEntityManager.addComponents(entity3.id, [ComponentRef(&flag1, becsID(flag1)),ComponentRef(&short1, becsID(short1))].staticArray);
gEntityManager.commit();
entity3 = gEntityManager.getEntity(id);
assert(entity3.getComponent!CInt);
assert(entity3.getComponent!CFloat);
assert(entity3.getComponent!CFlag);
assert(entity3.getComponent!CShort);
assert(*entity3.getComponent!CInt == 10);
assert(*entity3.getComponent!CFloat == 2.0);
assert(*entity3.getComponent!CShort == 2);
gEntityManager.removeComponents(entity3.id, [becsID!CFlag,becsID!CShort].staticArray);
gEntityManager.commit();
entity3 = gEntityManager.getEntity(id);
assert(entity3.getComponent!CInt);
assert(entity3.getComponent!CFloat);
assert(!entity3.getComponent!CFlag);
assert(!entity3.getComponent!CShort);
assert(*entity3.getComponent!CInt == 10);
assert(*entity3.getComponent!CFloat == 2.0);
gEntityManager.addComponents(entity3.id, [ComponentRef(&flag1, becsID(flag1)),ComponentRef(&short1, becsID(short1))].staticArray);
gEntityManager.removeComponents(entity3.id, [becsID!CUnregistered].staticArray);
gEntityManager.commit();
entity3 = gEntityManager.getEntity(id);
assert(entity3.getComponent!CInt);
assert(entity3.getComponent!CFloat);
assert(entity3.getComponent!CFlag);
assert(entity3.getComponent!CShort);
assert(*entity3.getComponent!CInt == 10);
assert(*entity3.getComponent!CFloat == 2.0);
assert(*entity3.getComponent!CShort == 2);
gEntityManager.beginRegister();
gEntityManager.registerComponent!CUnregistered;
gEntityManager.endRegister();
CUnregistered unregistered1 = CUnregistered(4);
gEntityManager.addComponents(entity3.id, [ComponentRef(&unregistered1, becsID(unregistered1))].staticArray);
gEntityManager.commit();
entity3 = gEntityManager.getEntity(id);
assert(entity3.getComponent!CUnregistered);
assert(*entity3.getComponent!CUnregistered == 4);
gEntityManager.removeComponents(entity3.id, [becsID!CUnregistered].staticArray);
gEntityManager.commit();
entity3 = gEntityManager.getEntity(id);
assert(!entity3.getComponent!CUnregistered);
}
@("AddEmptyEntity")
unittest
{
struct OnAddRemoveChangeCounter
{
mixin ECS.System!1;
struct EntitiesData
{
int thread_id;
uint length;
Entity[] entity;
}
void onAddEntity(EntitiesData data)
{
add += data.length;
}
void onRemoveEntity(EntitiesData data)
{
assert(0, "It's impossible to remove entity from being updated by system which accept empty entity");
}
int add = 0;
}
gEntityManager.beginRegister();
gEntityManager.registerSystem!EntityCounterSystem(0);
gEntityManager.registerSystem!OnAddRemoveChangeCounter(1);
gEntityManager.endRegister();
CLong long_component = CLong(3);
Entity* entity = null;
EntityID entity_id = gEntityManager.addEntity(null).id;
EntityCounterSystem* system = gEntityManager.getSystem!EntityCounterSystem;
assert(system !is null);
assert(system.count == 0);
OnAddRemoveChangeCounter* add_remove_change_system = gEntityManager.getSystem!OnAddRemoveChangeCounter;
assert(add_remove_change_system !is null);
assert(add_remove_change_system.add == 0);
gEntityManager.commit();
assert(add_remove_change_system.add == 1);
entity = gEntityManager.getEntity(entity_id);
assert(!entity.hasComponent(becsID!CLong));
assert(entity.getComponent(becsID!CLong) is null);
gEntityManager.begin();
gEntityManager.update();
assert(system.count == 1);
gEntityManager.end();
gEntityManager.addEntityCopy(entity_id);
gEntityManager.addEntityCopy(entity_id);
gEntityManager.addComponents(entity_id, [ComponentRef(&long_component, becsID(long_component))].staticArray);
gEntityManager.commit();
assert(add_remove_change_system.add == 3, "onAddEntity missed");
entity = gEntityManager.getEntity(entity_id);
assert(entity.hasComponent(becsID!CLong));
assert(*entity.getComponent!CLong == 3);
gEntityManager.begin();
gEntityManager.update();
assert(system.count == 3);
gEntityManager.end();
}
//allocate templates
@("AllocateTemplates")
unittest
{
//basic template allocation
ushort[2] ids = [becsID!CInt, becsID!CFloat];
EntityTemplate* tmpl_ = gEntityManager.allocateTemplate(ids);
EntityTemplate* tmpl_d = gEntityManager.allocateTemplate([becsID!CFloat, becsID!CInt, becsID!CFloat].staticArray);
EntityTemplate* tmpl_cp = gEntityManager.allocateTemplate(tmpl_);
assert(tmpl_d.info == tmpl_.info);
assert(tmpl_cp.info == tmpl_cp.info);
assert(tmpl_.info.components.length == 2);
assert(tmpl_.getComponent!CInt);
assert(tmpl_.getComponent!CFloat);
assert(*tmpl_.getComponent!CInt == 1);
assert(*tmpl_.getComponent!CFloat == 2.0);
assert(tmpl_cp.getComponent!CFloat);
assert(tmpl_cp.getComponent!CInt);
assert(tmpl_.getComponent!CInt != tmpl_cp.getComponent!CInt);
assert(tmpl_.getComponent!CFloat != tmpl_cp.getComponent!CFloat);
assert(*tmpl_.getComponent!CInt == *tmpl_cp.getComponent!CInt);
assert(*tmpl_.getComponent!CFloat == *tmpl_cp.getComponent!CFloat);
*tmpl_.getComponent!CInt = 4;
*tmpl_.getComponent!CFloat = 5.0;
//allocate template from template with additional components
ushort[2] ids2 = [becsID!CDouble,becsID!CFlag];
EntityTemplate* tmpl_2 = gEntityManager.allocateTemplate(tmpl_, ids2);
assert(tmpl_2.info.components.length == 4);
assert(tmpl_2.getComponent!CInt);
assert(tmpl_2.getComponent!CFloat);
assert(tmpl_2.getComponent!CDouble);
assert(tmpl_2.getComponent!CFlag);
assert(*tmpl_2.getComponent!CInt == 4);
assert(*tmpl_2.getComponent!CFloat == 5.0);
assert(*tmpl_2.getComponent!CDouble == 3.0);
assert(tmpl_.info.blocksCount() == 0);
Entity* entity = gEntityManager.addEntity(tmpl_);
gEntityManager.addComponents(entity.id, CFloat(100));
gEntityManager.addComponents(entity.id, CDouble(8.0), CFloat(100));
assert(tmpl_.info.blocksCount() == 1);
//apply entity changes
gEntityManager.commit();
assert(tmpl_.info.blocksCount() == 0);
//allocate template as entity copy
EntityTemplate* tmpl_3 = gEntityManager.allocateTemplate(entity.id);
assert(tmpl_3.info.components.length == 3);
assert(tmpl_3.getComponent!CInt);
assert(tmpl_3.getComponent!CFloat);
assert(tmpl_3.getComponent!CDouble);
assert(*tmpl_3.getComponent!CInt == 4);
assert(*tmpl_3.getComponent!CFloat == 5.0);
assert(*tmpl_3.getComponent!CDouble == 8.0);
//allocate template with entity data but default values
EntityTemplate* tmpl_4 = gEntityManager.allocateTemplate(entity.id, true);
assert(tmpl_4.info.components.length == 3);
assert(tmpl_4.getComponent!CInt);
assert(tmpl_4.getComponent!CFloat);
assert(tmpl_4.getComponent!CDouble);
assert(*tmpl_4.getComponent!CInt == 1);
assert(*tmpl_4.getComponent!CFloat == 2.0);
assert(*tmpl_4.getComponent!CDouble == 3.0);
//allocate template from template with three additional component
ushort[3] ids3 = [becsID!CDouble, becsID!CLong, becsID!CShort];
EntityTemplate* tmpl_5 = gEntityManager.allocateTemplate(tmpl_2, ids3);
assert(tmpl_5.info.components.length == 6);
assert(tmpl_5.getComponent!CInt);
assert(tmpl_5.getComponent!CFloat);
assert(tmpl_5.getComponent!CDouble);
assert(tmpl_5.getComponent!CLong);
assert(tmpl_5.getComponent!CShort);
assert(*tmpl_5.getComponent!CInt == 4);
assert(*tmpl_5.getComponent!CFloat == 5.0);
assert(*tmpl_5.getComponent!CDouble == 3.0);
assert(*tmpl_5.getComponent!CLong == 10);
assert(*tmpl_5.getComponent!CShort == 12);
//allocate template from template without one component
ushort[1] rem_ids = [becsID!CFloat];
EntityTemplate* tmpl_6 = gEntityManager.allocateTemplate(tmpl_, null, rem_ids);
assert(tmpl_6.info.components.length == 1);
assert(tmpl_6.getComponent!CInt);
assert(*tmpl_6.getComponent!CInt == 4);
//allocate template from template without one component and two additional
EntityTemplate* tmpl_7 = gEntityManager.allocateTemplate(tmpl_, ids3, rem_ids);
assert(tmpl_7.info.components.length == 4);
assert(tmpl_7.getComponent!CInt);
assert(tmpl_7.getComponent!CDouble);
assert(tmpl_7.getComponent!CLong);
assert(*tmpl_7.getComponent!CInt == 4);
assert(*tmpl_7.getComponent!CDouble == 3.0);
assert(*tmpl_7.getComponent!CLong == 10);
gEntityManager.freeTemplate(tmpl_d);
gEntityManager.freeTemplate(tmpl_cp);
gEntityManager.freeTemplate(tmpl_);
gEntityManager.freeTemplate(tmpl_2);
gEntityManager.freeTemplate(tmpl_3);
gEntityManager.freeTemplate(tmpl_4);
gEntityManager.freeTemplate(tmpl_5);
gEntityManager.freeTemplate(tmpl_6);
gEntityManager.freeTemplate(tmpl_7);
}
@("UnsortedComponentIDs")
unittest
{
//basic template allocation
ushort[2] ids = [becsID!CFloat, becsID!CInt];
ushort[2] ids2 = [becsID!CInt, becsID!CFloat];
EntityTemplate* tmpl_ = gEntityManager.allocateTemplate(ids);
EntityTemplate* tmpl_2 = gEntityManager.allocateTemplate(ids2);
assert(tmpl_.info.components.length == 2);
assert(tmpl_.getComponent!CInt);
assert(tmpl_.getComponent!CFloat);
assert(*tmpl_.getComponent!CInt == 1);
assert(*tmpl_.getComponent!CFloat == 2.0);
assert(tmpl_.info == tmpl_2.info);
gEntityManager.freeTemplate(tmpl_);
gEntityManager.freeTemplate(tmpl_2);
}
@("MultiRegister")
unittest
{
gEntityManager.beginRegister();
gEntityManager.endRegister();
gEntityManager.beginRegister();
gEntityManager.registerComponent!CLong;
gEntityManager.registerComponent!CShort;
gEntityManager.endRegister();
}
@("EmptySystem")
unittest
{
gEntityManager.beginRegister();
gEntityManager.registerSystem!EmptySystem(0);
gEntityManager.endRegister();
EmptySystem* system = gEntityManager.getSystem!EmptySystem;
assert(system !is null);
assert(system.count == 0);
System* ecs_system = gEntityManager.getSystem(becsID!EmptySystem);
assert(ecs_system !is null);
assert(ecs_system.id == becsID!EmptySystem);
assert(ecs_system.name == "tests.basic.EmptySystem");
gEntityManager.begin();
gEntityManager.update();
gEntityManager.end();
assert(system.count == 1);
}
@("SystemCallbacks")
unittest
{
struct TestSystem
{
mixin ECS.System!16;
mixin ECS.ExcludedComponents!(CShort);
struct EntitiesData
{
int length;
CLong[] long_;
@optional CInt[] int_;
}
void onCreate()
{
create++;
}
void onDestroy()
{
if(destroy)(*destroy)++;
}
void onEnable()
{
enable++;
}
void onDisable()
{
disable++;
}
bool onBegin()
{
begin++;
update = 0;
return pass;
}
void onEnd()
{
end++;
}
void onUpdate(EntitiesData data)
{
update++;
}
int create = 0;
int* destroy;
int update = 0;
int begin = 0;
int end = 0;
int enable = 0;
int disable = 0;
bool pass = true;
}
gEntityManager.beginRegister();
gEntityManager.registerSystem!TestSystem(0);
gEntityManager.endRegister();
TestSystem* system = gEntityManager.getSystem!TestSystem;
int destroy = 0;
system.destroy = &destroy;
gEntityManager.beginRegister();
gEntityManager.registerSystem!TestSystem(0);
gEntityManager.endRegister();
system = gEntityManager.getSystem!TestSystem;
system.destroy = &destroy;
assert(system !is null);
assert(system.create == 1);
assert(system.begin == 0);
assert(system.end == 0);
assert(system.enable == 1);
assert(system.disable == 0);
//FIXME: currently destroy is only called with Manager.destory which is bug, but there is no workaround for this by now
//assert(destroy == 1);
System* ecs_system = gEntityManager.getSystem(system.becsID);
ecs_system.enable();
assert(system.enable == 1);
ecs_system.disable();
ecs_system.disable();
ecs_system.enable();
assert(system.enable == 2);
assert(system.disable == 1);
ushort[2] ids = [becsID!CLong,becsID!CFloat];
EntityTemplate* tmpl = gEntityManager.allocateTemplate(ids);
scope (exit) gEntityManager.freeTemplate(tmpl);
gEntityManager.addEntity(tmpl);
gEntityManager.begin();
assert(system.begin == 1);
gEntityManager.update();
assert(system.update == 1);
gEntityManager.end();
assert(system.end == 1);
ushort[2] ids2 = [becsID!CLong, becsID!CInt];
EntityTemplate* tmpl2 = gEntityManager.allocateTemplate(ids2);
scope (exit) gEntityManager.freeTemplate(tmpl2);
gEntityManager.addEntity(tmpl2);
gEntityManager.addEntity(tmpl2);
gEntityManager.begin();
assert(system.begin == 2);
gEntityManager.update();
assert(system.update == 2);//system is updated number of entity blocks times
gEntityManager.end();
assert(system.end == 2);
ushort[2] ids3 = [becsID!CLong, becsID!CShort];
EntityTemplate* tmpl3 = gEntityManager.allocateTemplate(ids3);
scope (exit) gEntityManager.freeTemplate(tmpl3);
gEntityManager.addEntity(tmpl3);
//entity with excluded component shouldn't be updated
gEntityManager.begin();
assert(system.begin == 3);
gEntityManager.update();
assert(system.update == 2);
gEntityManager.end();
assert(system.end == 3);
//system can be disable form update in onBegin() callback, onEnd() callback is called
system.pass = false;
gEntityManager.begin();
assert(system.begin == 4);
gEntityManager.update();
assert(system.update == 0);
gEntityManager.end();
assert(system.end == 4);
system.pass = true;
//disabled system is't called
ecs_system.disable();
gEntityManager.begin();
assert(system.begin == 4);
gEntityManager.update();
assert(system.update == 0);
gEntityManager.end();
assert(system.end == 4);
ecs_system.enable();
system.destroy = null;
}
@("CustomPass")
unittest
{
gEntityManager.beginRegister();
gEntityManager.registerPass("custom");
gEntityManager.registerSystem!LongAddSystem(-1,"custom");
gEntityManager.endRegister();
assert(gEntityManager.getPass("custom"));
assert(!gEntityManager.getPass("custommm"));
LongAddSystem* system = gEntityManager.getSystem!LongAddSystem;
assert(system !is null);
assert(system.updates_count == 0);
System* ecs_system = gEntityManager.getSystem(becsID!LongAddSystem);
assert(ecs_system !is null);
assert(ecs_system.id == becsID!LongAddSystem);
assert(ecs_system.priority == -1);
assert(ecs_system.name == "tests.basic.LongAddSystem");
ushort[1] ids = [becsID!CLong];
EntityTemplate* tmpl = gEntityManager.allocateTemplate(ids);
scope (exit) gEntityManager.freeTemplate(tmpl);
gEntityManager.addEntity(tmpl);
gEntityManager.begin();
gEntityManager.update();
assert(system.updates_count == 0);
gEntityManager.update("custom");
assert(system.updates_count == 1);
gEntityManager.end();
}
@("SystemEntityCallbacks")
unittest
{
static int add_order = 0;
static int rem_order = 0;
static int change_order = 0;
struct TestSystem
{
mixin ECS.System!16;
mixin ECS.ExcludedComponents!(CShort);
struct EntitiesData
{
int length;
CLong[] long_;
@optional CInt[] int_;
}
void onAddEntity(EntitiesData data)
{
add++;
assert(add_order == 1);
add_order++;
}
void onRemoveEntity(EntitiesData data)
{
remove++;
assert(rem_order == 1);
rem_order++;
}
void onChangeEntity(EntitiesData data)
{
change++;
assert(change_order == 1);
change_order++;
}
void onUpdate(EntitiesData data)
{
}
int add = 0;
int remove = 0;
int change = 0;
}
struct TestSystem2
{
mixin ECS.System!16;
mixin ECS.ExcludedComponents!(CShort);
struct EntitiesData
{
int length;
CLong[] long_;
@optional CInt[] int_;
}
void onAddEntity(EntitiesData data)
{
assert(add_order == 2);
add_order = 0;
}
void onRemoveEntity(EntitiesData data)
{
assert(rem_order == 2);
rem_order = 0 ;
}
void onChangeEntity(EntitiesData data)
{
assert(change_order == 2);
change_order = 0;
}
void onUpdate(EntitiesData data)
{
}
}
struct TestSystem3
{
mixin ECS.System!16;
mixin ECS.ExcludedComponents!(CShort);
struct EntitiesData
{
int length;
CLong[] long_;
@optional CInt[] int_;
}
void onAddEntity(EntitiesData data)
{
assert(add_order == 0);
add_order++;
}
void onRemoveEntity(EntitiesData data)
{
assert(rem_order == 0);
rem_order++;
}
void onChangeEntity(EntitiesData data)
{
assert(change_order == 0);
change_order++;
}
void onUpdate(EntitiesData data)
{
}
}
gEntityManager.beginRegister();
gEntityManager.registerSystem!TestSystem3(-1);
gEntityManager.registerSystem!TestSystem(0);
gEntityManager.registerSystem!TestSystem2(1);
gEntityManager.endRegister();
TestSystem* system = gEntityManager.getSystem!TestSystem;
assert(system !is null);
assert(system.add == 0);
assert(system.remove == 0);
assert(system.change == 0);
EntityTemplate* tmpl = gEntityManager.allocateTemplate([becsID!CLong,becsID!CFloat].staticArray);
scope (exit) gEntityManager.freeTemplate(tmpl);
EntityID id0 = gEntityManager.addEntity(tmpl).id;
gEntityManager.commit();
assert(system.add == 1);
EntityTemplate* tmpl2 = gEntityManager.allocateTemplate([becsID!CLong, becsID!CInt].staticArray);
scope (exit) gEntityManager.freeTemplate(tmpl2);
EntityID id1 = gEntityManager.addEntity(tmpl2).id;
gEntityManager.commit();
assert(system.add == 2);
EntityTemplate* tmpl3 = gEntityManager.allocateTemplate([becsID!CLong, becsID!CShort].staticArray);
scope (exit) gEntityManager.freeTemplate(tmpl3);
EntityID id2 = gEntityManager.addEntity(tmpl3).id;
gEntityManager.commit();
assert(system.add == 2);
gEntityManager.beginRegister();
gEntityManager.endRegister();
gEntityManager.removeComponents(id0, [becsID!CFloat].staticArray);
gEntityManager.commit();
assert(system.add == 2);
assert(system.remove == 0);
assert(system.change == 0);
gEntityManager.removeComponents(id1, [becsID!CInt].staticArray);
gEntityManager.commit();
assert(system.add == 2);
assert(system.remove == 0);
assert(system.change == 1);
gEntityManager.removeComponents(id2, [becsID!CShort].staticArray);
gEntityManager.commit();
assert(system.add == 3);
assert(system.remove == 0);
assert(system.change == 1);
gEntityManager.addComponents(id2, CShort(1));
gEntityManager.commit();
assert(system.add == 3);
assert(system.remove == 1);
assert(system.change == 1);
gEntityManager.removeEntity(id0);
gEntityManager.commit();
assert(system.add == 3);
assert(system.remove == 2);
assert(system.change == 1);
gEntityManager.addComponents(id1, CInt(1));
gEntityManager.commit();
assert(system.add == 3);
assert(system.remove == 2);
assert(system.change == 2);
gEntityManager.addComponents(id0, CFloat(1));
gEntityManager.commit();
assert(system.add == 3);
assert(system.remove == 2);
assert(system.change == 2);
gEntityManager.removeEntity(id1);
gEntityManager.commit();
assert(system.add == 3);
assert(system.remove == 3);
assert(system.change == 2);
gEntityManager.removeEntity(id2);
gEntityManager.commit();
assert(system.add == 3);
assert(system.remove == 3);
assert(system.change == 2);
}
@("TemplateCoverage")
unittest
{
struct TestSystem
{
mixin ECS.System;
struct EntitiesData
{
int length;
Entity[] entity;
@readonly CLong[] long_;
@optional CInt[] int_;
@readonly CFloat[] float_;
}
mixin ECS.ExcludedComponents!(CUnregistered);
void onUpdate(EntitiesData data)
{
}
}
gEntityManager.beginRegister();
gEntityManager.registerComponent!CUnregistered;
gEntityManager.registerSystem!TestSystem(0);
gEntityManager.endRegister();
}
@("UnregisteredSystem")
unittest
{
struct TestSystem
{
mixin ECS.System;
struct EntitiesData
{
int length;
Entity[] entity;
@readonly CLong[] long_;
@optional CInt[] int_;
@readonly CFloat[] float_;
}
void onUpdate(EntitiesData data)
{
}
}
assert(gEntityManager.getSystem!TestSystem is null);
assert(gEntityManager.getSystem(becsID!TestSystem) is null);
}
@("MultithreadedUpdate")
unittest
{
struct TestSystem
{
mixin ECS.System!64;
struct EntitiesData
{
int length;
Entity[] entity;
@readonly CLong[] long_;
@optional CInt[] int_;
@readonly CFloat[] float_;
}
void onUpdate(EntitiesData data)
{
update++;
entities += data.length;
}
int update = 0;
int entities = 0;
}
struct TestEmptySystem
{
mixin ECS.System;
struct EntitiesData
{
int length;
}
void onUpdate(EntitiesData data)
{
update++;
}
int update = 0;
}
void dispatch(EntityManager.JobGroup grp)
{
foreach(job; grp.jobs)
{
job.execute();
}
}
uint getID()
{
return 0;
}
gEntityManager.setMultithreadingCallbacks(&dispatch, &getID);
gEntityManager.beginRegister();
gEntityManager.registerPass("custom");
gEntityManager.registerSystem!TestSystem(-1,"custom");
gEntityManager.registerSystem!TestEmptySystem(1,"custom");
gEntityManager.endRegister();
TestSystem* system = gEntityManager.getSystem!TestSystem;
TestEmptySystem* empty_system = gEntityManager.getSystem!TestEmptySystem;
ushort[2] ids = [becsID!CLong,becsID!CFloat];
EntityTemplate* tmpl = gEntityManager.allocateTemplate(ids);
scope (exit) gEntityManager.freeTemplate(tmpl);
EntityTemplate* tmpl2 = gEntityManager.allocateTemplate([becsID!CLong,becsID!CInt,becsID!CShort,becsID!CFloat].staticArray);
scope (exit) gEntityManager.freeTemplate(tmpl2);
gEntityManager.begin();
gEntityManager.updateMT("custom");
gEntityManager.end();
assert(system.update == 0);
assert(system.entities == 0);
assert(empty_system.update == 1);
gEntityManager.addEntity(tmpl);
gEntityManager.begin();
gEntityManager.updateMT("custom");
gEntityManager.end();
assert(system.update == 1);
assert(system.entities == 1);
assert(empty_system.update == 2);
system.entities = 0;
foreach(i;0..2000)gEntityManager.addEntity(tmpl);
gEntityManager.begin();
gEntityManager.updateMT("custom");
gEntityManager.end();
assert(system.update > 2);
assert(system.entities == 2001);
assert(empty_system.update == 3);
system.entities = 0;
// foreach(i;0..10000)gEntityManager.addEntity(tmpl);
// gEntityManager.begin();
// gEntityManager.updateMT("custom");
// gEntityManager.end();
// assert(system.entities == 12001);
void clearEntities(TestSystem.EntitiesData data)
{
foreach(i;0..data.length)
{
gEntityManager.removeEntity(data.entity[i].id);
}
}
gEntityManager.callEntitiesFunction!TestSystem(&clearEntities);
gEntityManager.commit();
foreach(i;0..2000)
{
gEntityManager.addEntity(tmpl);
gEntityManager.begin();
gEntityManager.updateMT("custom");
gEntityManager.end();
assert(system.entities == i+1);
system.entities = 0;
}
foreach(i;0..90000)gEntityManager.addEntity(tmpl);
foreach(i;0..2000)
{
gEntityManager.addEntity(tmpl);
gEntityManager.begin();
gEntityManager.updateMT("custom");
gEntityManager.end();
assert(system.entities == i+92001);
system.entities = 0;
}
}
unittest
{
assert(gEntityManager.pageSize == 32768);
assert(gEntityManager.pagesInBlock == 128);
}
@("AddRemoveEntities")
unittest
{
ushort[3] ids = [becsID!CLong,becsID!CFloat,becsID!CShort];
EntityTemplate* tmpl = gEntityManager.allocateTemplate(ids);
scope (exit) gEntityManager.freeTemplate(tmpl);
EntityID[5000] entities;
foreach(i;0..4)
{
foreach(j;0..5000)
{
entities[j] = gEntityManager.addEntity(tmpl).id;
}
gEntityManager.commit();
foreach(j;0..5000)
{
gEntityManager.removeEntity(entities[j]);
}
gEntityManager.commit();
}
}
@("ChangeEntityComponents")
unittest
{
gEntityManager.beginRegister();
gEntityManager.registerComponent!CUnregistered;
gEntityManager.endRegister();
ushort[1] ids = [becsID!CLong];
EntityTemplate* tmpl = gEntityManager.allocateTemplate(ids);
scope (exit) gEntityManager.freeTemplate(tmpl);
EntityID id = gEntityManager.addEntity(tmpl).id;
gEntityManager.commit();
Entity* entity = gEntityManager.getEntity(id);
assert(entity.id == id);
assert(entity.getComponent!CLong !is null);
assert(entity.getComponent!CFloat is null);
assert(entity.getComponent!CUnregistered is null);
assert(entity.getComponent!CShort is null);
assert(entity.getComponent!CInt is null);
assert(*entity.getComponent!CLong == 10);
gEntityManager.addComponents(id, CShort(15), CFloat(13));
gEntityManager.commit();
entity = gEntityManager.getEntity(id);
assert(entity.id == id);
assert(entity.getComponent!CLong !is null);
assert(entity.getComponent!CFloat !is null);
assert(entity.getComponent!CUnregistered is null);
assert(entity.getComponent!CShort !is null);
assert(entity.getComponent!CInt is null);
assert(*entity.getComponent!CLong == 10);
assert(*entity.getComponent!CShort == 15);
assert(*entity.getComponent!CFloat == 13);
ushort[3] ids2 = [becsID!CFloat, becsID!CLong, becsID!CUnregistered];
gEntityManager.removeComponents(id, ids2);
gEntityManager.commit();
entity = gEntityManager.getEntity(id);
assert(entity.id == id);
assert(entity.getComponent!CLong is null);
assert(entity.getComponent!CFloat is null);
assert(entity.getComponent!CUnregistered is null);
assert(entity.getComponent!CShort !is null);
assert(entity.getComponent!CInt is null);
assert(*entity.getComponent!CShort == 15);
gEntityManager.removeComponents(id, ids2);
gEntityManager.addComponents(id, CShort(11), CLong(2)); //wrong order of components
gEntityManager.commit();
entity = gEntityManager.getEntity(id);
assert(entity.id == id);
assert(entity.getComponent!CLong !is null);
assert(entity.getComponent!CFloat is null);
assert(entity.getComponent!CUnregistered is null);
assert(entity.getComponent!CShort !is null);
assert(entity.getComponent!CInt is null);
assert(*entity.getComponent!CLong == 2);
assert(*entity.getComponent!CShort == 15);
gEntityManager.removeEntity(id);
entity = gEntityManager.getEntity(id);
assert(entity !is null);
assert(entity.id == id);
gEntityManager.commit();
entity = gEntityManager.getEntity(id);
assert(entity is null);
}
@("EventCallbacks")
unittest
{
struct ETest
{
// mixin ECS.Event;
}
struct ETest2
{
// mixin ECS.Event;
void onDestroy()
{
destory++;
}
int super_liczba = 0;
static int destory = 0;
}
struct TestSystem
{
mixin ECS.System;
struct EntitiesData
{
int length;
Entity[] entity;
@readonly CLong[] long_;
@optional CInt[] int_;
}
void onUpdate(EntitiesData data)
{
}
void handleEvent(Entity* entity, ETest event)
{
CLong* long_ = entity.getComponent!CLong;
CInt* int_ = entity.getComponent!CInt;
*long_ += 16;
if(int_)*int_ += 6;
}
void handleEvent(Entity* entity, ETest2 event)
{
CLong* long_ = entity.getComponent!CLong;
CInt* int_ = entity.getComponent!CInt;
*long_ += event.super_liczba * 2;
if(int_)*int_ += event.super_liczba * 4;
}
}
struct TestSystem2
{
mixin ECS.System;
struct EntitiesData
{
int length;
Entity[] entity;
CShort[] short_;
@optional CInt[] int_;
}
void handleEvent(Entity* entity, ETest event)
{
CShort* short_ = entity.getComponent!CShort;
CInt* int_ = entity.getComponent!CInt;
*short_ += 8;
if(int_)*int_ += 2;
}
void handleEvent(Entity* entity, ETest2 event)
{
CShort* short_ = entity.getComponent!CShort;
CInt* int_ = entity.getComponent!CInt;
*short_ += event.super_liczba;
if(int_)*int_ *= event.super_liczba;
}
}
gEntityManager.beginRegister();
gEntityManager.registerEvent!ETest;
gEntityManager.registerEvent!ETest2;
gEntityManager.registerEvent!ETest;
gEntityManager.registerEvent!ETest2;
gEntityManager.registerSystem!TestSystem2(1);
gEntityManager.registerSystem!TestSystem(0);
gEntityManager.endRegister();
ushort[1] ids = [becsID!CLong];
EntityTemplate* tmpl = gEntityManager.allocateTemplate(ids);
scope (exit) gEntityManager.freeTemplate(tmpl);
ushort[1] ids2 = [becsID!CShort];
EntityTemplate* tmpl2 = gEntityManager.allocateTemplate(ids2);
scope (exit) gEntityManager.freeTemplate(tmpl2);
Entity* entity = gEntityManager.addEntity(tmpl);
EntityID id = entity.id;
assert(*entity.getComponent!CLong == 10);
Entity* entity2 = gEntityManager.addEntity(tmpl2);
EntityID id2 = entity2.id;
assert(*entity2.getComponent!CShort == 12);
gEntityManager.sendEvent(id,ETest());
gEntityManager.sendEvent(id,ETest2(10));
gEntityManager.sendEvent(id2,ETest());
gEntityManager.sendEvent(id2,ETest2(12));
gEntityManager.commit();
assert(ETest2.destory == 2);
entity = gEntityManager.getEntity(id);
entity2 = gEntityManager.getEntity(id2);
assert(*entity.getComponent!CLong == 46);
assert(*entity2.getComponent!CShort == 32);
gEntityManager.addComponents(id, CInt(2), CShort(1));
gEntityManager.sendEvent(id,ETest());
gEntityManager.sendEvent(id,ETest2(2));
gEntityManager.commit();
assert(ETest2.destory == 3);
entity = gEntityManager.getEntity(id);
assert(*entity.getComponent!CLong == 66);
assert(*entity.getComponent!CInt == 2);//36);
//test for multiple event blocks
long result = *entity.getComponent!CLong;
foreach(i;0..10000)
{
gEntityManager.sendEvent(id,ETest());
gEntityManager.sendEvent(id,ETest2(4));
result += 16;
result += 8;
}
gEntityManager.commit();
assert(ETest2.destory == 10003);
entity = gEntityManager.getEntity(id);
assert(*entity.getComponent!CLong == result);
//cover funcion to clearEvents before destroying manager
gEntityManager.sendEvent(id,ETest());
}
@("EntitiesFunction")
unittest
{
struct TestSystem
{
mixin ECS.System;
struct EntitiesData
{
uint length;
CInt[] int_;
}
void onUpdate(EntitiesData entities)
{
}
}
void func1(TestSystem.EntitiesData entities)
{
foreach(i;0 .. entities.length)
{
entities.int_[i] += entities.int_[i] / 2;
}
}
void func2(TestSystem.EntitiesData entities)
{
foreach(i;0 .. entities.length)
{
entities.int_[i] += 8;
}
}
gEntityManager.beginRegister();
gEntityManager.registerSystem!TestSystem(1);
gEntityManager.endRegister();
EntityTemplate* tmpl = gEntityManager.allocateTemplate([becsID!CInt].staticArray);
scope (exit) gEntityManager.freeTemplate(tmpl);
EntityID id1 = gEntityManager.addEntity(tmpl).id;
EntityTemplate* tmpl2 = gEntityManager.allocateTemplate([becsID!CInt, becsID!CLong].staticArray);
scope (exit) gEntityManager.freeTemplate(tmpl2);
EntityID id2 = gEntityManager.addEntity(tmpl2).id;
gEntityManager.begin();
Entity* entity1 = gEntityManager.getEntity(id1);
Entity* entity2 = gEntityManager.getEntity(id2);
assert(*entity1.getComponent!CInt == 1);
assert(*entity2.getComponent!CInt == 1);
gEntityManager.callEntitiesFunction!TestSystem(&func2);
assert(*entity1.getComponent!CInt == 9);
assert(*entity2.getComponent!CInt == 9);
gEntityManager.callEntitiesFunction!TestSystem(&func1);
assert(*entity1.getComponent!CInt == 13);
assert(*entity2.getComponent!CInt == 13);
gEntityManager.end();
}
@("SystemDependencies")
unittest
{
struct TestSystem
{
mixin ECS.System;
struct EntitiesData
{
uint length;
@readonly CInt[] int_;
}
void onUpdate(EntitiesData entities)
{
}
}
struct TestSystem2
{
mixin ECS.System;
struct EntitiesData
{
uint length;
CInt[] int_;
}
void onUpdate(EntitiesData entities)
{
}
}
struct TestSystem3
{
mixin ECS.System;
struct EntitiesData
{
uint length;
@readonly CInt[] int_;
}
void onUpdate(EntitiesData entities)
{
}
}
struct TestSystem4
{
mixin ECS.System;
struct EntitiesData
{
uint length;
CInt[] int_;
CLong[] long_;
}
void onUpdate(EntitiesData entities)
{
}
}
struct TestSystem5
{
mixin ECS.System;
struct EntitiesData
{
uint length;
@readonly CLong[] int_;
}
void onUpdate(EntitiesData entities)
{
}
}
gEntityManager.beginRegister();
gEntityManager.registerSystem!TestSystem(0);
gEntityManager.registerSystem!TestSystem2(1);
gEntityManager.registerSystem!TestSystem3(2);
gEntityManager.registerSystem!TestSystem4(3);
gEntityManager.registerSystem!TestSystem5(4);
gEntityManager.endRegister();
const (EntityManager.UpdatePass)* pass = gEntityManager.getPass("update");
assert(pass != null);
assert(pass.system_callers.length == 5);
assert(pass.system_callers[0].system_id == becsID!TestSystem);
assert(pass.system_callers[1].system_id == becsID!TestSystem2);
assert(pass.system_callers[2].system_id == becsID!TestSystem3);
assert(pass.system_callers[3].system_id == becsID!TestSystem4);
assert(pass.system_callers[4].system_id == becsID!TestSystem5);
assert(pass.system_callers[0].dependencies.length == 0);
assert(pass.system_callers[1].dependencies.length == 1);
assert(pass.system_callers[2].dependencies.length == 1);
assert(pass.system_callers[3].dependencies.length == 3);
assert(pass.system_callers[4].dependencies.length == 1);
assert(pass.system_callers[1].dependencies[0].system_id == becsID!TestSystem);
assert(pass.system_callers[2].dependencies[0].system_id == becsID!TestSystem2);
assert(pass.system_callers[3].dependencies[0].system_id == becsID!TestSystem);
assert(pass.system_callers[3].dependencies[1].system_id == becsID!TestSystem2);
assert(pass.system_callers[3].dependencies[2].system_id == becsID!TestSystem3);
assert(pass.system_callers[4].dependencies[0].system_id == becsID!TestSystem4);
}
@("ExternalSystemDependencies")
unittest
{
enum TestDependency = "TestDepencency";
struct TestSystem
{
mixin ECS.System;
mixin ECS.ReadOnlyDependencies!(TestDependency);
struct EntitiesData
{
uint length;
@readonly CInt[] int_;
}
void onUpdate(EntitiesData entities)
{
}
}
struct TestSystem2
{
mixin ECS.System;
mixin ECS.WritableDependencies!(TestDependency);
struct EntitiesData
{
uint length;
@readonly CInt[] int_;
}
void onUpdate(EntitiesData entities)
{
}
}
struct TestSystem3
{
mixin ECS.System;
mixin ECS.ReadOnlyDependencies!(TestDependency);
struct EntitiesData
{
uint thread_id;
}
void onUpdate(EntitiesData entities)
{
}
}
struct TestSystem4
{
mixin ECS.System;
mixin ECS.WritableDependencies!(TestDependency);
struct EntitiesData
{
uint length;
@readonly CInt[] int_;
}
void onUpdate(EntitiesData entities)
{
}
}
struct TestSystem5
{
mixin ECS.System;
mixin ECS.ReadOnlyDependencies!(TestDependency);
struct EntitiesData
{
uint length;
@readonly CLong[] int_;
}
void onUpdate(EntitiesData entities)
{
}
}
gEntityManager.beginRegister();
gEntityManager.registerDependency(TestDependency);
gEntityManager.registerSystem!TestSystem(0);
gEntityManager.registerSystem!TestSystem2(1);
gEntityManager.registerSystem!TestSystem3(2);
gEntityManager.registerSystem!TestSystem4(3);
gEntityManager.registerSystem!TestSystem5(4);
gEntityManager.endRegister();
const (EntityManager.UpdatePass)* pass = gEntityManager.getPass("update");
assert(pass != null);
assert(pass.system_callers.length == 5);
assert(pass.system_callers[0].system_id == becsID!TestSystem);
assert(pass.system_callers[1].system_id == becsID!TestSystem2);
assert(pass.system_callers[2].system_id == becsID!TestSystem3);
assert(pass.system_callers[3].system_id == becsID!TestSystem4);
assert(pass.system_callers[4].system_id == becsID!TestSystem5);
assert(pass.system_callers[0].dependencies.length == 0);
assert(pass.system_callers[1].dependencies.length == 1);
assert(pass.system_callers[2].dependencies.length == 1);
assert(pass.system_callers[3].dependencies.length == 3);
assert(pass.system_callers[4].dependencies.length == 2);
assert(pass.system_callers[1].dependencies[0].system_id == becsID!TestSystem);
assert(pass.system_callers[2].dependencies[0].system_id == becsID!TestSystem2);
assert(pass.system_callers[3].dependencies[0].system_id == becsID!TestSystem);
assert(pass.system_callers[3].dependencies[1].system_id == becsID!TestSystem2);
assert(pass.system_callers[3].dependencies[2].system_id == becsID!TestSystem3);
assert(pass.system_callers[4].dependencies[0].system_id == becsID!TestSystem2);
assert(pass.system_callers[4].dependencies[1].system_id == becsID!TestSystem4);
}
@("CustomFilter")
unittest
{
struct TestSystem
{
mixin ECS.System;
struct EntitiesData
{
uint length;
@optional CInt[] int_;
@optional CLong[] long_;
@optional CFloat[] float_;
@optional CDouble[] double_;
}
bool filterEntity(EntityManager.EntityInfo* info)
{
if(!info.hasComponent(becsID!CInt))return false;
int one_from = 0;
if(info.hasComponent(becsID!CLong))one_from++;
if(info.hasComponent(becsID!CFloat))one_from++;
if(info.hasComponent(becsID!CDouble))one_from++;
if(one_from == 1)return true;
return false;
}
void onUpdate(EntitiesData entities)
{
updates++;
}
uint updates = 0;
}
struct TestSystem2
{
mixin ECS.System;
struct EntitiesData
{
uint length;
@optional CInt[] int_;
@optional CLong[] long_;
@optional CFloat[] float_;
@optional CDouble[] double_;
}
bool filterEntity(EntityManager.EntityInfo* info)
{
if(info.hasComponent(becsID!CInt) && info.hasComponent(becsID!CFloat) && !info.hasComponent(becsID!CLong) && !info.hasComponent(becsID!CDouble))return true;
if(info.hasComponent(becsID!CLong) && info.hasComponent(becsID!CDouble) && !info.hasComponent(becsID!CInt) && !info.hasComponent(becsID!CFloat))return true;
return false;
}
void onUpdate(EntitiesData entities)
{
updates++;
}
uint updates = 0;
}
gEntityManager.beginRegister();
gEntityManager.registerSystem!TestSystem(0);
gEntityManager.registerSystem!TestSystem2(1);
gEntityManager.endRegister();
EntityTemplate* tmpl_ = gEntityManager.allocateTemplate([becsID!CInt, becsID!CLong, becsID!CFloat, becsID!CDouble].staticArray);
scope(exit)gEntityManager.freeTemplate(tmpl_);
EntityTemplate* tmpl_2 = gEntityManager.allocateTemplate([becsID!CInt, becsID!CFloat].staticArray);
scope(exit)gEntityManager.freeTemplate(tmpl_2);
EntityTemplate* tmpl_3 = gEntityManager.allocateTemplate([becsID!CLong, becsID!CDouble].staticArray);
scope(exit)gEntityManager.freeTemplate(tmpl_3);
EntityTemplate* tmpl_4 = gEntityManager.allocateTemplate([becsID!CInt, becsID!CLong, becsID!CDouble].staticArray);
scope(exit)gEntityManager.freeTemplate(tmpl_4);
EntityTemplate* tmpl_5 = gEntityManager.allocateTemplate([becsID!CInt, becsID!CDouble].staticArray);
scope(exit)gEntityManager.freeTemplate(tmpl_5);
EntityTemplate* tmpl_6 = gEntityManager.allocateTemplate([becsID!CDouble].staticArray);
scope(exit)gEntityManager.freeTemplate(tmpl_6);
gEntityManager.addEntity(tmpl_);
gEntityManager.addEntity(tmpl_2);
gEntityManager.addEntity(tmpl_3);
gEntityManager.addEntity(tmpl_4);
gEntityManager.addEntity(tmpl_5);
gEntityManager.addEntity(tmpl_6);
TestSystem* test_system = gEntityManager.getSystem!TestSystem;
TestSystem2* test_system2 = gEntityManager.getSystem!TestSystem2;
gEntityManager.begin();
gEntityManager.update();
gEntityManager.end();
assert(test_system.updates == 2);
assert(test_system2.updates == 2);
}