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; } 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 cint = CInt(10); //CLong clong; //Entity* entity3 = gEntityManager.addEntity(tmpl_, [cint.ref_, clong.ref_].staticArray); Entity* entity3 = gEntityManager.addEntity(tmpl_, [CInt(10).ref_, CLong().ref_, CFlag().ref_].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); gEntityManager.addComponents(entity3.id, [CFlag().ref_,CShort(2).ref_].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, [CFlag().ref_,CShort(2).ref_].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(); gEntityManager.addComponents(entity3.id, [CUnregistered(4).ref_].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); } //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) { foreach(i; 0..data.length) data.long_[i] += 1; 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); }