载入中...
搜索中...
未找到
ECS.cpp
浏览该文件的文档.
1#include "common/ECS.h"
3
4#include <simplesquirrel/simplesquirrel.hpp>
5
6#include <cstdio>
7#include <cstring>
8#include <string>
9#include <unordered_map>
10#include <vector>
11
12namespace eve {
13namespace {
14
15// ---------------------------------------------------------------------------
16// Script component registry (name → Squirrel class)
17// ---------------------------------------------------------------------------
18
19struct ScriptComponentReg {
20 HSQUIRRELVM vm = nullptr;
21 HSQOBJECT cls;
22 ScriptComponentReg() { sq_resetobject(&cls); }
23 ScriptComponentReg(HSQUIRRELVM v, HSQOBJECT o) : vm(v), cls(o) {}
24};
25
26std::unordered_map<std::string, ScriptComponentReg>& scriptComponents() {
27 static std::unordered_map<std::string, ScriptComponentReg> m;
28 return m;
29}
30
31int& nextEntityId() {
32 static int id = 1;
33 return id;
34}
35
36int allocEntityId() { return nextEntityId()++; }
37
38// C++ 实体类型(typeid(T*).hash_code())→ 脚本 view() 收集函数。
39std::unordered_map<size_t, CppEntityViewFn>& cppEntityViews() {
40 static std::unordered_map<size_t, CppEntityViewFn> views;
41 return views;
42}
43
44std::vector<std::function<void(ssq::Table&)>>& postEcsHooks() {
45 static std::vector<std::function<void(ssq::Table&)>> hooks;
46 return hooks;
47}
48
49// ---------------------------------------------------------------------------
50// Class helpers
51// ---------------------------------------------------------------------------
52
53bool isSubclassOf(HSQUIRRELVM vm, HSQOBJECT child, HSQOBJECT base) {
54 if (child._type != OT_CLASS || base._type != OT_CLASS) return false;
55 const SQInteger top = sq_gettop(vm);
56 sq_pushobject(vm, child);
57 for (int guard = 0; guard < 64; ++guard) {
58 if (sq_gettype(vm, -1) != OT_CLASS) break;
59 HSQOBJECT cur;
60 sq_getstackobj(vm, -1, &cur);
61 if (cur._unVal.pClass == base._unVal.pClass) {
62 sq_settop(vm, top);
63 return true;
64 }
65 if (SQ_FAILED(sq_getbase(vm, -1))) break;
66 sq_remove(vm, -2); // drop current, keep base
67 if (sq_gettype(vm, -1) != OT_CLASS) break;
68 }
69 sq_settop(vm, top);
70 return false;
71}
72
74void collectClassFields(HSQUIRRELVM vm, HSQOBJECT clsObj, HSQOBJECT outTable) {
75 const SQInteger top = sq_gettop(vm);
76 sq_pushobject(vm, outTable); // out
77 sq_pushobject(vm, clsObj); // out, cls
78 sq_pushnull(vm); // out, cls, iter
79 while (SQ_SUCCEEDED(sq_next(vm, -2))) {
80 // out, cls, iter, key, value
81 if (sq_gettype(vm, -2) == OT_STRING) {
82 const SQChar* key = nullptr;
83 sq_getstring(vm, -2, &key);
84 if (key && key[0] != '_') {
85 const SQObjectType vt = sq_gettype(vm, -1);
86 if (vt != OT_CLOSURE && vt != OT_NATIVECLOSURE && vt != OT_GENERATOR) {
87 sq_push(vm, -5); // out
88 sq_push(vm, -3); // key
89 sq_push(vm, -3); // value
90 sq_rawset(vm, -3);
91 sq_pop(vm, 1); // pop out
92 }
93 }
94 }
95 sq_pop(vm, 2); // key, value
96 }
97 sq_settop(vm, top);
98}
99
100ssq::Table inspectClassFields(ssq::Object clsObj) {
101 HSQUIRRELVM vm = clsObj.getHandle();
102 ssq::Table out(vm);
103 if (clsObj.getType() != ssq::Type::CLASS) return out;
104 collectClassFields(vm, clsObj.getRaw(), out.getRaw());
105 return out;
106}
107
108bool scriptIsSubclass(ssq::Object child, ssq::Object base) {
109 if (child.getType() != ssq::Type::CLASS || base.getType() != ssq::Type::CLASS)
110 return false;
111 return isSubclassOf(child.getHandle(), child.getRaw(), base.getRaw());
112}
113
114ssq::Object getClassBase(ssq::Object cls) {
115 HSQUIRRELVM vm = cls.getHandle();
116 ssq::Object ret(vm);
117 if (cls.getType() != ssq::Type::CLASS) return ret;
118 const SQInteger top = sq_gettop(vm);
119 sq_pushobject(vm, cls.getRaw());
120 if (SQ_FAILED(sq_getbase(vm, -1)) || sq_gettype(vm, -1) != OT_CLASS) {
121 sq_settop(vm, top);
122 return ret;
123 }
124 sq_getstackobj(vm, -1, &ret.getRaw());
125 sq_addref(vm, &ret.getRaw());
126 sq_settop(vm, top);
127 return ret;
128}
129
130void registerScriptComponent(const std::string& name, ssq::Object cls) {
131 if (cls.getType() != ssq::Type::CLASS) return;
132 HSQUIRRELVM vm = cls.getHandle();
133 HSQOBJECT o = cls.getRaw();
134 sq_addref(vm, &o);
135 auto& m = scriptComponents();
136 auto it = m.find(name);
137 if (it != m.end()) {
138 if (it->second.vm) sq_release(it->second.vm, &it->second.cls);
139 it->second = ScriptComponentReg(vm, o);
140 } else {
141 m.emplace(name, ScriptComponentReg(vm, o));
142 }
143}
144
145// ---------------------------------------------------------------------------
146// Injected Squirrel runtime
147// ---------------------------------------------------------------------------
148
149const char* kEcsScript = R"SQ(
150// Script ECS: define Component / Entity classes, create instances, view & System.
151// Mirrors ECS.hpp's class + component model for Squirrel game logic.
152
153if (!("Number" in eve)) eve.Number <- "number"
154if (!("Boolean" in eve)) eve.Boolean <- "boolean"
155if (!("String" in eve)) eve.String <- "string"
156
157eve._ecsTypes <- {} // EntityClass -> { cls, instances }
158
159// --- 性能缓存(万级实体稳态零分配) ---
160// view 结果缓存:create/destroy 时按类链失效;C++ 桥接类(_cppHasView=true)不缓存。
161eve._ecsViewCache <- {} // EntityClass -> array
162eve._ecsSlotsCache <- {} // EntityClass -> { fieldName = ComponentClass }
163eve._ecsCompDefaults <- {} // ComponentClass -> { fieldName = resolvedDefault }
164
165function eve::_ecsResolveMarker(v) {
166 if (v == eve.Number || v == "number") return 0.0
167 if (v == eve.Boolean || v == "boolean") return false
168 if (v == eve.String || v == "string") return ""
169 return v
170}
171
172function eve::_ecsIsComponentClass(obj) {
173 if (typeof obj != "class") return false
174 try { return eve.isSubclass(obj, eve.Component) } catch (e) { return false }
175}
176
177function eve::_ecsInstantiateComponent(compClass) {
178 local c = compClass()
179 if (compClass in eve._ecsCompDefaults) {
180 foreach (name, val in eve._ecsCompDefaults[compClass]) c[name] = val
181 } else {
182 local fields = eve.inspectClassFields(compClass)
183 local resolved = {}
184 foreach (name, val in fields) {
185 local cur = null
186 try { cur = c[name] } catch (e) { cur = null }
187 if (cur == eve.Number || cur == eve.Boolean || cur == eve.String
188 || cur == "number" || cur == "boolean" || cur == "string") {
189 c[name] = eve._ecsResolveMarker(cur)
190 resolved[name] <- c[name]
191 }
192 }
193 eve._ecsCompDefaults[compClass] <- resolved
194 }
195 return c
196}
197
198function eve::_ecsCollectSlots(cls) {
199 if (cls in eve._ecsSlotsCache) return eve._ecsSlotsCache[cls]
200 // { fieldName = ComponentClass } walking base classes (Entity → … → cls).
201 local slots = {}
202 local cur = cls
203 local guard = 0
204 while (cur != null && guard < 64) {
205 if (!eve.isSubclass(cur, eve.Entity)) break
206
207 local declared = null
208 try { declared = cur.components } catch (e) { declared = null }
209 if (typeof declared == "table") {
210 foreach (k, v in declared) {
211 if (!(k in slots) && eve._ecsIsComponentClass(v))
212 slots[k] <- v
213 }
214 }
215
216 local fields = eve.inspectClassFields(cur)
217 foreach (k, v in fields) {
218 if (k == "components") continue
219 if (!(k in slots) && eve._ecsIsComponentClass(v))
220 slots[k] <- v
221 }
222
223 cur = eve.getClassBase(cur)
224 guard += 1
225 }
226 eve._ecsSlotsCache[cls] <- slots
227 return slots
228}
229
230function eve::_ecsInvalidateViews(cls) {
231 local cur = cls
232 local guard = 0
233 while (cur != null && guard < 64) {
234 if (cur in eve._ecsViewCache) delete eve._ecsViewCache[cur]
235 if (!eve.isSubclass(cur, eve.Entity)) break
236 cur = eve.getClassBase(cur)
237 guard += 1
238 }
239}
240
241function eve::_ecsEnsureType(cls) {
242 if (cls in eve._ecsTypes) return eve._ecsTypes[cls]
243 local info = { cls = cls, instances = [] }
244 eve._ecsTypes[cls] <- info
245 return info
246}
247
248function eve::_ecsRegisterInstance(entity, cls) {
249 eve._ecsEnsureType(cls).instances.push(entity)
250 eve._ecsInvalidateViews(cls)
251}
252
253function eve::_ecsUnregisterInstance(entity, cls) {
254 if (!(cls in eve._ecsTypes)) return
255 local arr = eve._ecsTypes[cls].instances
256 for (local i = arr.len() - 1; i >= 0; --i) {
257 if (arr[i] == entity) {
258 // O(1) swap-remove(顺序不保证);视图缓存随后失效重建
259 local last = arr.len() - 1
260 if (i != last) arr[i] = arr[last]
261 arr.pop()
262 break
263 }
264 }
265 eve._ecsInvalidateViews(cls)
266}
267
268function eve::_ecsCollectInstances(cls, out) {
269 foreach (key, info in eve._ecsTypes) {
270 if (info.cls == cls || eve.isSubclass(info.cls, cls)) {
271 foreach (e in info.instances) {
272 if (e != null && e.isAlive()) out.push(e)
273 }
274 }
275 }
276}
277
278eve.Component <- class {
279 constructor() {}
280}
281
282eve.Entity <- class {
283 _eid = 0
284 _alive = false
285 _etype = null
286 _slots = null
287 _comps = null
288
289 constructor() {
290 _eid = 0
291 _alive = false
292 _etype = null
293 _slots = null
294 _comps = null
295 }
296
297 static function create() {
298 local e = this()
299 e._etype = this
300 e._eid = eve.allocEntityId()
301 e._alive = true
302 e._slots = eve._ecsCollectSlots(this)
303 e._comps = {}
304 foreach (name, compClass in e._slots) {
305 local c = eve._ecsInstantiateComponent(compClass)
306 e._comps[name] <- c
307 try { e[name] = c } catch (ex) {}
308 }
309 eve._ecsRegisterInstance(e, this)
310 return e
311 }
312
313 // Declare a component slot on this entity class (optional sugar).
314 static function component(name, compClass) {
315 if (!("components" in this) || typeof this.components != "table")
316 this.components <- {}
317 this.components[name] <- compClass
318 return this
319 }
320
321 function getId() { return _eid }
322 function isAlive() { return _alive }
323
324 function getComponent(compClass) {
325 if (_slots == null || _comps == null) return null
326 foreach (name, cls in _slots) {
327 if (cls == compClass) {
328 if (name in _comps) return _comps[name]
329 return null
330 }
331 }
332 return null
333 }
334
335 function hasComponent(compClass) {
336 return getComponent(compClass) != null
337 }
338
339 function destroy() {
340 if (!_alive) return
341 _alive = false
342 if (_etype != null) eve._ecsUnregisterInstance(this, _etype)
343 }
344}
345
346// Historical name from early game examples
347eve.EntityContainer <- eve.Entity
348
349eve.System <- class {
350 _query = null
351
352 constructor(query = null) {
353 _query = query
354 }
355
356 function setQuery(query) { _query = query }
357
358 function entities() {
359 if (_query == null) return []
360 if (typeof _query == "array") {
361 local out = []
362 foreach (q in _query) {
363 foreach (e in eve.view(q)) out.push(e)
364 }
365 return out
366 }
367 return eve.view(_query)
368 }
369
370 function update(dt) {}
371}
372
373// GPU-backed System: pack float component fields → SSBO → compute shader → unpack.
374// Requires eve.Gpgpu / eve.EcsShaderSystem (gpgpu module). Push constants:
375// pc.data[0] = dt, pc.data[1] = entityCount. Each bindFields() maps one binding.
376eve.ShaderSystem <- class extends eve.System {
377 _gpu = null
378 _backend = null
379 _bindings = null
380 _readback = true
381 _localSize = 64
382
383 constructor(query = null, gpu = null, glsl = null, localSize = 64) {
384 base.constructor(query)
385 _bindings = []
386 _readback = true
387 _localSize = localSize
388 if (gpu != null) setGpgpu(gpu)
389 if (gpu != null && glsl != null) setShaderSource(glsl)
390 }
391
392 function setGpgpu(gpu) {
393 _gpu = gpu
394 if (_backend == null) {
395 if (!("EcsShaderSystem" in eve))
396 throw "eve.ShaderSystem requires gpgpu module (eve.EcsShaderSystem)"
397 _backend = eve.EcsShaderSystem()
398 }
399 _backend.setGpgpu(gpu)
400 _backend.setLocalSize(_localSize)
401 return this
402 }
403
404 function setShaderSource(glsl) {
405 if (_backend == null) throw "eve.ShaderSystem.setShaderSource: call setGpgpu first"
406 _backend.setShaderSource(glsl)
407 return this
408 }
409
410 function setLocalSize(n) {
411 _localSize = n
412 if (_backend != null) _backend.setLocalSize(n)
413 return this
414 }
415
416 function setReadback(enabled) {
417 _readback = enabled
418 return this
419 }
420
421 // binding: SSBO index; slot: entity component field name; fields: ["x","y",...]
422 function bindFields(binding, slot, fields) {
423 _bindings.push({ binding = binding, slot = slot, fields = fields })
424 return this
425 }
426
427 function setFloat(index, value) {
428 if (_backend != null) _backend.setFloat(index, value)
429 return this
430 }
431
432 function getBackend() { return _backend }
433
434 function update(dt) {
435 if (_backend == null || _gpu == null) return
436 if (!("packEcsFloats" in eve) || !("unpackEcsFloats" in eve)) return
437
438 local ents = entities()
439 local n = ents.len()
440 if (n <= 0) return
441
442 foreach (b in _bindings) {
443 local floatsPer = b.fields.len()
444 if (floatsPer <= 0) continue
445 local buf = _backend.ensureBuffer(b.binding, n * floatsPer)
446 eve.packEcsFloats(ents, b.slot, b.fields, buf)
447 }
448
449 _backend.dispatch(n, dt)
450
451 if (_readback) {
452 foreach (b in _bindings) {
453 local floatsPer = b.fields.len()
454 if (floatsPer <= 0) continue
455 local buf = _backend.getBuffer(b.binding)
456 eve.unpackEcsFloats(ents, b.slot, b.fields, buf, n)
457 }
458 }
459 }
460}
461
462function eve::view(entityClass) {
463 if (entityClass == null) return []
464 // C++ 桥接类的实体在脚本外增删(无法通知脚本),不缓存,每次新鲜收集
465 local cppBacked = false
466 if ("_cppHasView" in eve) cppBacked = eve._cppHasView(entityClass)
467 if (!cppBacked && (entityClass in eve._ecsViewCache)) return eve._ecsViewCache[entityClass]
468 local out = []
469 eve._ecsCollectInstances(entityClass, out)
470 if ("_cppCollect" in eve) eve._cppCollect(entityClass, out)
471 if (!cppBacked) eve._ecsViewCache[entityClass] <- out
472 return out
473}
474
475function eve::ecsReady() {
476 return ("Entity" in eve) && ("Component" in eve) && ("System" in eve)
477}
478)SQ";
479
480void injectEcsScript(ssq::Table& eveTable) {
481 HSQUIRRELVM vm = eveTable.getHandle();
482 const SQInteger top = sq_gettop(vm);
483 if (SQ_FAILED(sq_compilebuffer(vm, kEcsScript,
484 static_cast<SQInteger>(std::strlen(kEcsScript)),
485 "ecs.nut", SQTrue))) {
486 sq_settop(vm, top);
487 const script::ScriptErrorContext ctx = script::captureCompileError(vm);
488 std::fprintf(stderr, "EVEngine: ECS bootstrap script failed to compile: %s\n",
489 ctx.empty() ? "unknown error" : ctx.message.c_str());
490 return;
491 }
492 sq_pushroottable(vm);
493 if (SQ_FAILED(sq_call(vm, 1, SQFalse, SQTrue))) {
494 const script::ScriptErrorContext ctx = script::takeLastScriptError(vm);
495 std::fprintf(stderr, "EVEngine: ECS bootstrap script failed to run: %s\n",
496 ctx.empty() ? "unknown error"
497 : script::formatScriptError(ctx).c_str());
498 }
499 sq_settop(vm, top);
500}
501
507void cppCollect(ssq::Class cls, ssq::Array out) {
508 HSQUIRRELVM vm = cls.getHandle();
509 const SQInteger top = sq_gettop(vm);
510 sq_pushobject(vm, cls.getRaw());
511 for (int guard = 0; guard < 64; ++guard) {
512 if (sq_gettype(vm, -1) != OT_CLASS)
513 break;
514 HSQOBJECT cur;
515 sq_getstackobj(vm, -1, &cur);
516 SQUserPointer tag = nullptr;
517 if (SQ_SUCCEEDED(sq_getobjtypetag(&cur, &tag))) {
518 auto it = cppEntityViews().find(reinterpret_cast<size_t>(tag));
519 if (it != cppEntityViews().end()) {
520 sq_settop(vm, top);
521 it->second(out);
522 return;
523 }
524 }
525 if (SQ_FAILED(sq_getbase(vm, -1)))
526 break;
527 sq_remove(vm, -2); // drop current class, keep base
528 }
529 sq_settop(vm, top);
530}
531
537bool cppHasView(ssq::Object cls) {
538 if (cls.getType() != ssq::Type::CLASS) return false;
539 HSQUIRRELVM vm = cls.getHandle();
540 const SQInteger top = sq_gettop(vm);
541 sq_pushobject(vm, cls.getRaw());
542 for (int guard = 0; guard < 64; ++guard) {
543 if (sq_gettype(vm, -1) != OT_CLASS) break;
544 HSQOBJECT cur;
545 sq_getstackobj(vm, -1, &cur);
546 SQUserPointer tag = nullptr;
547 if (SQ_SUCCEEDED(sq_getobjtypetag(&cur, &tag))) {
548 if (cppEntityViews().find(reinterpret_cast<size_t>(tag)) != cppEntityViews().end()) {
549 sq_settop(vm, top);
550 return true;
551 }
552 }
553 if (SQ_FAILED(sq_getbase(vm, -1))) break;
554 sq_remove(vm, -2); // drop current class, keep base
555 }
556 sq_settop(vm, top);
557 return false;
558}
559
560} // namespace
561
562void exposeECS(ssq::Table& table) {
563 table.set("Number", std::string("number"));
564 table.set("Boolean", std::string("boolean"));
565 table.set("String", std::string("string"));
566 // ecsReady / view / Entity / Component are provided by the injected script.
567
568 table.addFunc("inspectClassFields", inspectClassFields);
569 table.addFunc("isSubclass", scriptIsSubclass);
570 table.addFunc("getClassBase", getClassBase);
571 table.addFunc("allocEntityId", allocEntityId);
572 table.addFunc("_cppHasView", cppHasView);
573 table.addFunc("component", [](std::string name, ssq::Object cls) {
574 registerScriptComponent(name, cls);
575 });
576 table.addFunc("_cppCollect", std::function<void(ssq::Class, ssq::Array)>(cppCollect));
577
578 injectEcsScript(table);
579
580 // After script ECS classes exist: run module hooks (e.g. eve.SceneEntity).
581 for (auto &hook : postEcsHooks()) {
582 try {
583 hook(table);
584 } catch (...) {
585 // A failing module hook must not break VM exposure.
586 }
587 }
588}
589
591 cppEntityViews()[typeHash] = std::move(fn);
592}
593
595 postEcsHooks().push_back(std::move(fn));
596}
597
598void exposeECSToVM(ssq::VM& vm) {
599 try {
600 ssq::Table eveTable(vm.find("eve"));
601 exposeECS(eveTable);
602 } catch (...) {
603 }
604}
605
606} // namespace eve
struct SQVM * HSQUIRRELVM
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
std::string id
const char * name
Definition RockMesh.cpp:21
SettlementPipeline::Stage fn
int v
float m[16]
ScriptErrorContext captureCompileError(HSQUIRRELVM vm)
Captures the last compilation error recorded by the VM.
std::string formatScriptError(const ScriptErrorContext &ctx)
Formats a context into a human-readable multi-line report.
ScriptErrorContext takeLastScriptError(HSQUIRRELVM vm)
Consumes and clears the last recorded error for a VM.
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
Definition Widget.cpp:387
Definition Build.cpp:11
void registerPostEcsHook(PostEcsHook fn)
Definition ECS.cpp:594
void exposeECS(ssq::Table &table)
脚本侧 ECS:Entity / Component / System / ShaderSystem / view(见 ECS.cpp)。 C++ 游戏实体仍直接用 ECS....
Definition ECS.cpp:562
std::function< void(ssq::Table &table)> PostEcsHook
在脚本 ECS 基类(eve.Component / eve.Entity / eve.System)注入之后执行的回调。 模块用它注入"extends eve.Entity"的脚本基类(例如 eve....
Definition ECS.h:41
std::function< void(ssq::Array &out)> CppEntityViewFn
C++ 实体 → 脚本 eve.view() 桥接。 用 registerCppEntityView(typeid(T*).hash_code(), fn) 登记 T 的收集函数; 脚本 eve....
Definition ECS.h:33
void exposeECSToVM(ssq::VM &vm)
在 ModuleManager::expose 之后调用;保证 eve.Component 等不被其它模块覆盖。
Definition ECS.cpp:598
void registerCppEntityView(size_t typeHash, CppEntityViewFn fn)
Definition ECS.cpp:590