Files
EVEngine/test/ScriptECS.cpp
T
西风逍遥游 73f4034cf4 feat(ecosystem): enhance ECS functionality with dynamic entity management and new test cases
- Updated the ECS release method to ensure proper entity destruction.
- Introduced a countNodes function to track active entities in the ECS.
- Added new test cases for dynamic entity creation and destruction, ensuring correct behavior during updates.
- Enhanced existing tests to validate entity visibility and lifecycle management in various scenarios.
2026-08-13 17:18:32 +08:00

288 lines
7.9 KiB
C++

#include "ScriptTest.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
static const char* kScriptEcsContent = R"SQ(
function testEcsReady() {
return eve.ecsReady()
}
function testDefineClassAndComponent() {
class Position extends eve.Component {
x = 0.0
y = 0.0
}
class Velocity extends eve.Component {
x = 1.0
y = 2.0
}
class Moveable extends eve.Entity {
pos = Position
vel = Velocity
}
local e = Moveable.create()
if (e == null) return false
if (!e.isAlive()) return false
if (e.getId() <= 0) return false
if (e.pos.x != 0.0 || e.pos.y != 0.0) return false
if (e.vel.x != 1.0 || e.vel.y != 2.0) return false
e.pos.x = 10.0
e.vel.x = 3.0
if (e.pos.x != 10.0) return false
if (e.getComponent(Position).x != 10.0) return false
if (!e.hasComponent(Velocity)) return false
return true
}
function testViewIncludesSubclass() {
class Health extends eve.Component {
hp = 100
}
class Actor extends eve.Entity {
hp = Health
}
class Enemy extends Actor {
}
local a = Actor.create()
local b = Enemy.create()
a.hp.hp = 50
b.hp.hp = 80
local actors = eve.view(Actor)
if (actors.len() < 2) return false
local enemies = eve.view(Enemy)
if (enemies.len() < 1) return false
local foundEnemy = false
foreach (e in actors) {
if (e == b) foundEnemy = true
}
if (!foundEnemy) return false
return true
}
function testSystemUpdate() {
class Position extends eve.Component {
x = 0.0
}
class Velocity extends eve.Component {
x = 5.0
}
class Mover extends eve.Entity {
pos = Position
vel = Velocity
}
class MoveSys extends eve.System {
constructor() {
base.constructor(Mover)
}
function update(dt) {
foreach (e in entities()) {
e.pos.x += e.vel.x * dt
}
}
}
local e = Mover.create()
local sys = MoveSys()
sys.update(2.0)
if (e.pos.x != 10.0) return false
e.destroy()
if (e.isAlive()) return false
// destroyed entities leave the view
foreach (ent in eve.view(Mover)) {
if (ent == e) return false
}
return true
}
function testSystemDynamicSpawnDespawn() {
class Position extends eve.Component { x = 0.0 }
class Velocity extends eve.Component { x = 1.0 }
class Mover extends eve.Entity {
pos = Position
vel = Velocity
}
class MoveSys extends eve.System {
constructor() { base.constructor(Mover) }
function update(dt) {
local dying = []
foreach (e in entities()) {
e.pos.x += e.vel.x * dt
if (e.pos.x >= 2.0) dying.push(e)
}
foreach (e in dying) e.destroy()
}
}
local sys = MoveSys()
local a = Mover.create()
local b = Mover.create()
a.vel.x = 1.0
b.vel.x = 1.0
if (eve.view(Mover).len() != 2) return false
sys.update(1.0)
if (a.pos.x != 1.0 || b.pos.x != 1.0) return false
if (eve.view(Mover).len() != 2) return false
sys.update(1.0)
if (a.isAlive() || b.isAlive()) return false
if (eve.view(Mover).len() != 0) return false
local c = Mover.create()
c.vel.x = 1.0
sys.update(0.5)
if (!c.isAlive() || c.pos.x != 0.5) return false
if (eve.view(Mover).len() != 1) return false
// A later entities() call in the same update sees newly created instances.
class SpawnSys extends eve.System {
snapshot = 0
constructor() { base.constructor(Mover) }
function update(dt) {
snapshot = entities().len()
local extra = Mover.create()
extra.pos.x = 99.0
if (entities().len() != snapshot + 1) return false
foreach (e in entities()) {
if (e.pos.x >= 2.0) e.destroy()
}
return true
}
}
local spawnSys = SpawnSys()
if (!spawnSys.update(0.0)) return false
if (spawnSys.snapshot != 1) return false
if (eve.view(Mover).len() != 1) return false
foreach (e in eve.view(Mover)) {
if (e != c) return false
e.destroy()
}
return eve.view(Mover).len() == 0
}
function testSystemQueryArrayAndSubclass() {
class Hp extends eve.Component { v = 1 }
class Actor extends eve.Entity { hp = Hp }
class Enemy extends Actor {}
class Prop extends eve.Entity { hp = Hp }
class TickSys extends eve.System {
constructor() { base.constructor([Actor, Prop]) }
function update(dt) {
foreach (e in entities()) e.hp.v += 1
}
}
local actor = Actor.create()
local enemy = Enemy.create()
local prop = Prop.create()
local sys = TickSys()
sys.update(0.0)
if (actor.hp.v != 2 || enemy.hp.v != 2 || prop.hp.v != 2) return false
sys.setQuery(Enemy)
sys.update(0.0)
if (actor.hp.v != 2 || enemy.hp.v != 3 || prop.hp.v != 2) return false
local emptySys = eve.System()
if (emptySys.entities().len() != 0) return false
actor.destroy()
enemy.destroy()
prop.destroy()
return true
}
function testTypeMarkersAndEntityContainer() {
class Status extends eve.Component {
alive = eve.Boolean
name = eve.String
score = eve.Number
}
class Thing extends eve.EntityContainer {
status = Status
}
local t = Thing.create()
if (t.status.alive != false) return false
if (t.status.name != "") return false
if (t.status.score != 0.0) return false
t.status.alive = true
t.status.name = "orb"
t.status.score = 3.5
if (!t.status.alive || t.status.name != "orb" || t.status.score != 3.5) return false
return eve.EntityContainer == eve.Entity
}
function testStaticComponentsTable() {
class Pos extends eve.Component {
x = 1.0
}
class Node extends eve.Entity {
static components = { pos = Pos }
pos = null
}
local n = Node.create()
if (n.pos == null) return false
if (n.pos.x != 1.0) return false
return true
}
function testShaderSystemClassExists() {
if (!("ShaderSystem" in eve)) return false
// Construction without GPU is allowed; update() no-ops until setGpgpu.
class Pos extends eve.Component { x = 0.0; y = 0.0 }
class Vel extends eve.Component { x = 1.0; y = 0.0 }
class Mover extends eve.Entity { pos = Pos; vel = Vel }
local sys = eve.ShaderSystem(Mover)
sys.bindFields(0, "pos", ["x", "y"])
sys.bindFields(1, "vel", ["x", "y"])
sys.update(0.016)
return true
}
)SQ";
UnitSciptTest(ScriptEcsTest, kScriptEcsContent);
TEST_CASE_FIXTURE(ScriptEcsTest, "ScriptECS.ready") {
CHECK(vm.callFunc(vm.findFunc("testEcsReady"), vm).toBool());
}
TEST_CASE_FIXTURE(ScriptEcsTest, "ScriptECS.defineClassAndComponent") {
CHECK(vm.callFunc(vm.findFunc("testDefineClassAndComponent"), vm).toBool());
}
TEST_CASE_FIXTURE(ScriptEcsTest, "ScriptECS.viewIncludesSubclass") {
CHECK(vm.callFunc(vm.findFunc("testViewIncludesSubclass"), vm).toBool());
}
TEST_CASE_FIXTURE(ScriptEcsTest, "ScriptECS.systemUpdate") {
CHECK(vm.callFunc(vm.findFunc("testSystemUpdate"), vm).toBool());
}
TEST_CASE_FIXTURE(ScriptEcsTest, "ScriptECS.systemDynamicSpawnDespawn") {
CHECK(vm.callFunc(vm.findFunc("testSystemDynamicSpawnDespawn"), vm).toBool());
}
TEST_CASE_FIXTURE(ScriptEcsTest, "ScriptECS.systemQueryArrayAndSubclass") {
CHECK(vm.callFunc(vm.findFunc("testSystemQueryArrayAndSubclass"), vm).toBool());
}
TEST_CASE_FIXTURE(ScriptEcsTest, "ScriptECS.typeMarkersAndEntityContainer") {
CHECK(vm.callFunc(vm.findFunc("testTypeMarkersAndEntityContainer"), vm).toBool());
}
TEST_CASE_FIXTURE(ScriptEcsTest, "ScriptECS.staticComponentsTable") {
CHECK(vm.callFunc(vm.findFunc("testStaticComponentsTable"), vm).toBool());
}
TEST_CASE_FIXTURE(ScriptEcsTest, "ScriptECS.shaderSystemClassExists") {
CHECK(vm.callFunc(vm.findFunc("testShaderSystemClassExists"), vm).toBool());
}