4#include <simplesquirrel/simplesquirrel.hpp>
9#include <unordered_map>
19struct ScriptComponentReg {
22 ScriptComponentReg() { sq_resetobject(&
cls); }
26std::unordered_map<std::string, ScriptComponentReg>& scriptComponents() {
27 static std::unordered_map<std::string, ScriptComponentReg>
m;
36int allocEntityId() {
return nextEntityId()++; }
39std::unordered_map<const void*, CppEntityViewFn>& cppEntityViews() {
40 static std::unordered_map<const void*, CppEntityViewFn> views;
44std::vector<std::function<void(ssq::Table&)>>& postEcsHooks() {
45 static std::vector<std::function<void(ssq::Table&)>> hooks;
49bool& ecsScriptInjected() {
50 static bool injected =
false;
54size_t& postEcsHooksFlushed() {
63bool isSubclassOf(
HSQUIRRELVM vm, HSQOBJECT child, HSQOBJECT base) {
64 if (
child._type != OT_CLASS || base._type != OT_CLASS)
return false;
65 const SQInteger
top = sq_gettop(
vm);
66 sq_pushobject(
vm, child);
67 for (
int guard = 0; guard < 64; ++guard) {
68 if (sq_gettype(
vm, -1) != OT_CLASS)
break;
70 sq_getstackobj(
vm, -1, &cur);
71 if (cur._unVal.pClass == base._unVal.pClass) {
75 if (SQ_FAILED(sq_getbase(
vm, -1)))
break;
77 if (sq_gettype(
vm, -1) != OT_CLASS)
break;
84void collectClassFields(
HSQUIRRELVM vm, HSQOBJECT clsObj, HSQOBJECT outTable) {
85 const SQInteger
top = sq_gettop(
vm);
86 sq_pushobject(
vm, outTable);
87 sq_pushobject(
vm, clsObj);
89 while (SQ_SUCCEEDED(sq_next(
vm, -2))) {
91 if (sq_gettype(
vm, -2) == OT_STRING) {
92 const SQChar*
key =
nullptr;
93 sq_getstring(
vm, -2, &
key);
94 if (
key &&
key[0] !=
'_') {
95 const SQObjectType vt = sq_gettype(
vm, -1);
96 if (vt != OT_CLOSURE && vt != OT_NATIVECLOSURE && vt != OT_GENERATOR) {
110ssq::Table inspectClassFields(ssq::Object clsObj) {
113 if (clsObj.getType() != ssq::Type::CLASS)
return out;
114 collectClassFields(
vm, clsObj.getRaw(), out.getRaw());
118bool scriptIsSubclass(ssq::Object child, ssq::Object base) {
119 if (
child.getType() != ssq::Type::CLASS || base.getType() != ssq::Type::CLASS)
121 return isSubclassOf(
child.getHandle(),
child.getRaw(), base.getRaw());
124ssq::Object getClassBase(ssq::Object
cls) {
127 if (
cls.getType() != ssq::Type::CLASS)
return ret;
128 const SQInteger
top = sq_gettop(
vm);
129 sq_pushobject(
vm,
cls.getRaw());
130 if (SQ_FAILED(sq_getbase(
vm, -1)) || sq_gettype(
vm, -1) != OT_CLASS) {
134 sq_getstackobj(
vm, -1, &ret.getRaw());
135 sq_addref(
vm, &ret.getRaw());
140void registerScriptComponent(
const std::string&
name, ssq::Object
cls) {
141 if (
cls.getType() != ssq::Type::CLASS)
return;
143 HSQOBJECT o =
cls.getRaw();
145 auto&
m = scriptComponents();
146 auto it =
m.find(
name);
148 if (it->second.vm) sq_release(it->second.vm, &it->second.cls);
149 it->second = ScriptComponentReg(
vm, o);
151 m.emplace(
name, ScriptComponentReg(
vm, o));
159const char* kEcsScript = R
"SQ(
160// Script ECS: define Component / Entity classes, create instances, view & System.
161// Mirrors ECS.hpp's class + component model for Squirrel game logic.
163if (!("Number" in eve)) eve.Number <- "number"
164if (!("Boolean" in eve)) eve.Boolean <- "boolean"
165if (!("String" in eve)) eve.String <- "string"
167eve._ecsTypes <- {} // EntityClass -> { cls, instances }
169// --- 性能缓存(万级实体稳态零分配) ---
170// view 结果缓存:create/destroy 时按类链失效;C++ 桥接类(_cppHasView=true)不缓存。
171eve._ecsViewCache <- {} // EntityClass -> array
172eve._ecsSlotsCache <- {} // EntityClass -> { fieldName = ComponentClass }
173eve._ecsCompDefaults <- {} // ComponentClass -> { fieldName = resolvedDefault }
174eve._ecsRevision <- 0 // increments on script entity structural changes
176function eve::_ecsResolveMarker(v) {
177 if (v == this.Number || v == "number") return 0.0
178 if (v == this.Boolean || v == "boolean") return false
179 if (v == this.String || v == "string") return ""
183function eve::_ecsIsComponentClass(obj) {
184 if (typeof obj != "class") return false
185 return this.isSubclass(obj, this.Component)
188function eve::_ecsInstantiateComponent(compClass) {
189 local c = compClass()
190 if (compClass in this._ecsCompDefaults) {
191 foreach (name, val in this._ecsCompDefaults[compClass]) c[name] = val
193 local fields = this.inspectClassFields(compClass)
195 foreach (name, val in fields) {
197 try { cur = c[name] } catch (e) { cur = null }
198 if (cur == this.Number || cur == this.Boolean || cur == this.String
199 || cur == "number" || cur == "boolean" || cur == "string") {
200 c[name] = this._ecsResolveMarker(cur)
201 resolved[name] <- c[name]
204 this._ecsCompDefaults[compClass] <- resolved
209function eve::_ecsCollectSlots(cls) {
210 if (cls in this._ecsSlotsCache) return this._ecsSlotsCache[cls]
211 // { fieldName = ComponentClass } walking base classes (Entity → … → cls).
215 while (cur != null && guard < 64) {
216 if (!this.isSubclass(cur, this.Entity)) break
218 local declared = null
219 try { declared = cur.components } catch (e) { declared = null }
220 if (typeof declared == "table") {
221 foreach (k, v in declared) {
222 if (!(k in slots) && this._ecsIsComponentClass(v))
227 local fields = this.inspectClassFields(cur)
228 foreach (k, v in fields) {
229 if (k == "components") continue
230 if (!(k in slots) && this._ecsIsComponentClass(v))
234 cur = this.getClassBase(cur)
237 this._ecsSlotsCache[cls] <- slots
241function eve::_ecsInvalidateViews(cls) {
244 while (cur != null && guard < 64) {
245 if (cur in this._ecsViewCache) delete this._ecsViewCache[cur]
246 if (!this.isSubclass(cur, this.Entity)) break
247 cur = this.getClassBase(cur)
252function eve::_ecsEnsureType(cls) {
253 if (cls in this._ecsTypes) return this._ecsTypes[cls]
254 local info = { cls = cls, instances = [] }
255 this._ecsTypes[cls] <- info
259function eve::_ecsRegisterInstance(entity, cls) {
260 this._ecsEnsureType(cls).instances.push(entity)
261 this._ecsRevision += 1
262 this._ecsInvalidateViews(cls)
265function eve::_ecsUnregisterInstance(entity, cls) {
266 if (!(cls in this._ecsTypes)) return
267 local arr = this._ecsTypes[cls].instances
268 for (local i = arr.len() - 1; i >= 0; --i) {
269 if (arr[i] == entity) {
270 // O(1) swap-remove(顺序不保证);视图缓存随后失效重建
271 local last = arr.len() - 1
272 if (i != last) arr[i] = arr[last]
274 this._ecsRevision += 1
278 this._ecsInvalidateViews(cls)
281function eve::_ecsCollectInstances(cls, out) {
282 foreach (key, info in this._ecsTypes) {
283 if (info.cls == cls || this.isSubclass(info.cls, cls)) {
284 foreach (e in info.instances) {
285 if (e != null && e.isAlive()) out.push(e)
291eve.Component <- class {
310 static function create() {
313 e._eid = eve.allocEntityId()
315 e._slots = eve._ecsCollectSlots(this)
317 foreach (name, compClass in e._slots) {
318 local c = eve._ecsInstantiateComponent(compClass)
320 try { e[name] = c } catch (ex) {}
322 eve._ecsRegisterInstance(e, this)
326 // Declare a component slot on this entity class (optional sugar).
327 static function component(name, compClass) {
328 if (!("components" in this) || typeof this.components != "table")
329 this.components <- {}
330 this.components[name] <- compClass
334 function getId() { return _eid }
335 function isAlive() { return _alive }
337 function getComponent(compClass) {
338 if (_slots == null || _comps == null) return null
339 foreach (name, cls in _slots) {
340 if (cls == compClass) {
341 if (name in _comps) return _comps[name]
348 function hasComponent(compClass) {
349 return getComponent(compClass) != null
355 if (_etype != null) eve._ecsUnregisterInstance(this, _etype)
359// Historical name from early game examples
360eve.EntityContainer <- eve.Entity
365 constructor(query = null) {
369 function setQuery(query) { _query = query }
371 function entities() {
372 if (_query == null) return []
373 if (typeof _query == "array") {
375 foreach (q in _query) {
376 foreach (e in eve.view(q)) out.push(e)
380 return eve.view(_query)
383 function update(dt) {}
386// GPU-backed System: pack float component fields → SSBO → compute shader → unpack.
387// Requires eve.Gpgpu / eve.EcsShaderSystem (gpgpu module). Push constants:
388// pc.data[0] = dt, pc.data[1] = entityCount. Each bindFields() maps one binding.
389eve.ShaderSystem <- class extends eve.System {
396 constructor(query = null, gpu = null, glsl = null, localSize = 64) {
397 base.constructor(query)
400 _localSize = localSize
401 if (gpu != null) setGpgpu(gpu)
402 if (gpu != null && glsl != null) setShaderSource(glsl)
405 function setGpgpu(gpu) {
407 if (_backend == null) {
408 if (!("EcsShaderSystem" in eve))
409 throw "eve.ShaderSystem requires gpgpu module (eve.EcsShaderSystem)"
410 _backend = eve.EcsShaderSystem()
412 _backend.setGpgpu(gpu)
413 _backend.setLocalSize(_localSize)
417 function setShaderSource(glsl) {
418 if (_backend == null) throw "eve.ShaderSystem.setShaderSource: call setGpgpu first"
419 _backend.setShaderSource(glsl)
423 function setLocalSize(n) {
425 if (_backend != null) _backend.setLocalSize(n)
429 function setReadback(enabled) {
434 // binding: SSBO index; slot: entity component field name; fields: ["x","y",...]
435 // uploadEachUpdate=false keeps the buffer GPU-resident after its initial upload.
436 // readback=false leaves results resident until requestReadback() is called.
437 function bindFields(binding, slot, fields, uploadEachUpdate = true, readback = null) {
438 _bindings.push({ binding = binding, slot = slot, fields = fields,
439 uploadEachUpdate = uploadEachUpdate,
440 readback = readback, uploadedRevision = -1,
441 uploadRequested = false, readbackRequested = false,
442 uploadFirst = 0, uploadCount = 0,
443 readbackFirst = 0, readbackCount = 0,
444 source = null, sourceBinding = -1,
445 initializeFromCpu = true, schema = null })
449 // Typed binding schema: { slot, fields, scalar="f32", access="read_write" }.
450 // write-only bindings allocate storage without an unnecessary CPU upload.
451 function bindSchema(binding, schema, uploadEachUpdate = false, readback = false) {
452 if (typeof schema != "table" || !("slot" in schema) || !("fields" in schema))
453 throw "eve.ShaderSystem.bindSchema: schema requires slot and fields"
454 if (typeof schema.slot != "string" || typeof schema.fields != "array" ||
455 schema.fields.len() <= 0)
456 throw "eve.ShaderSystem.bindSchema: invalid slot or fields"
457 foreach (field in schema.fields)
458 if (typeof field != "string")
459 throw "eve.ShaderSystem.bindSchema: every field must be a string"
460 local scalar = "scalar" in schema ? schema.scalar : "f32"
462 throw "eve.ShaderSystem.bindSchema: only f32 is currently supported"
463 local access = "access" in schema ? schema.access : "read_write"
464 if (access != "read" && access != "write" && access != "read_write")
465 throw "eve.ShaderSystem.bindSchema: access must be read, write, or read_write"
466 local normalized = { slot = schema.slot, fields = schema.fields,
467 scalar = scalar, access = access,
468 strideFloats = schema.fields.len() }
469 _bindings.push({ binding = binding, slot = schema.slot, fields = schema.fields,
470 uploadEachUpdate = access == "write" ? false : uploadEachUpdate,
471 readback = readback, uploadedRevision = -1,
472 uploadRequested = false, readbackRequested = false,
473 uploadFirst = 0, uploadCount = 0,
474 readbackFirst = 0, readbackCount = 0,
475 source = null, sourceBinding = -1,
476 initializeFromCpu = access != "write", schema = normalized })
480 function getBindingSchema(binding) {
481 foreach (b in _bindings) if (b.binding == binding) return b.schema
485 // Generate backend declaration boilerplate; the kernel body remains explicit.
486 function shaderDeclarations(language = "glsl") {
488 foreach (b in _bindings) {
489 if (b.schema == null) continue
490 if (language == "glsl") {
491 local qualifier = b.schema.access == "read" ? "readonly " :
492 b.schema.access == "write" ? "writeonly " : ""
493 out += "layout(set = 0, binding = " + b.binding + ") " + qualifier +
494 "buffer EcsBinding" + b.binding + " { float data[]; } ecs" +
496 } else if (language == "wgsl") {
497 local access = b.schema.access == "read" ? "read" : "read_write"
498 out += "@group(0) @binding(" + b.binding + ") var<storage, " + access +
499 "> ecs" + b.binding + " : array<f32>;\n"
501 throw "eve.ShaderSystem.shaderDeclarations: language must be glsl or wgsl"
507 // Reuse another ShaderSystem's resident buffer without a CPU round trip.
508 // The source system is retained and must be prepared before this consumer.
509 function bindSharedFields(binding, sourceSystem, sourceBinding, slot, fields,
511 if (sourceSystem == null)
512 throw "eve.ShaderSystem.bindSharedFields: source system is null"
513 _bindings.push({ binding = binding, slot = slot, fields = fields,
514 uploadEachUpdate = false, readback = readback,
515 uploadedRevision = -1, uploadRequested = false,
516 readbackRequested = false, uploadFirst = 0, uploadCount = 0,
517 readbackFirst = 0, readbackCount = 0,
518 source = sourceSystem, sourceBinding = sourceBinding,
519 initializeFromCpu = false, schema = null })
523 function setBindingUpload(binding, enabled) {
524 foreach (b in _bindings) if (b.binding == binding) b.uploadEachUpdate = enabled
528 function setBindingReadback(binding, enabled) {
529 foreach (b in _bindings) if (b.binding == binding) b.readback = enabled
533 // Re-upload CPU component values on the next update. binding < 0 selects all.
534 function requestUpload(binding = -1) {
535 foreach (b in _bindings) {
536 if ((binding < 0 || b.binding == binding) && b.source == null) {
537 b.uploadRequested = true
545 // Re-upload a stable contiguous entity range. Repeated requests are coalesced.
546 function requestUploadRange(binding, firstEntity, entityCount) {
547 if (firstEntity < 0 || entityCount <= 0)
548 throw "eve.ShaderSystem.requestUploadRange: invalid entity range"
549 foreach (b in _bindings) {
550 if (b.binding != binding) continue
551 if (b.source != null)
552 throw "eve.ShaderSystem.requestUploadRange: shared binding is GPU-owned"
553 if (!b.uploadRequested || b.uploadCount == 0) {
554 b.uploadFirst = firstEntity
555 b.uploadCount = entityCount
556 } else if (b.uploadCount >= 0) {
557 local oldEnd = b.uploadFirst + b.uploadCount
558 local newEnd = firstEntity + entityCount
559 local end = oldEnd > newEnd ? oldEnd : newEnd
560 b.uploadFirst = b.uploadFirst < firstEntity ? b.uploadFirst : firstEntity
561 b.uploadCount = end - b.uploadFirst
563 b.uploadRequested = true
568 // Read GPU values back after the next update. binding < 0 selects all.
569 function requestReadback(binding = -1) {
570 foreach (b in _bindings) {
571 if (binding < 0 || b.binding == binding) {
572 b.readbackRequested = true
580 // Read back a stable contiguous entity range. Repeated requests are coalesced.
581 function requestReadbackRange(binding, firstEntity, entityCount) {
582 if (firstEntity < 0 || entityCount <= 0)
583 throw "eve.ShaderSystem.requestReadbackRange: invalid entity range"
584 foreach (b in _bindings) {
585 if (b.binding != binding) continue
586 if (!b.readbackRequested || b.readbackCount == 0) {
587 b.readbackFirst = firstEntity
588 b.readbackCount = entityCount
589 } else if (b.readbackCount >= 0) {
590 local oldEnd = b.readbackFirst + b.readbackCount
591 local newEnd = firstEntity + entityCount
592 local end = oldEnd > newEnd ? oldEnd : newEnd
593 b.readbackFirst = b.readbackFirst < firstEntity ? b.readbackFirst : firstEntity
594 b.readbackCount = end - b.readbackFirst
596 b.readbackRequested = true
601 function setFloat(index, value) {
602 if (_backend != null) _backend.setFloat(index, value)
606 function getBackend() { return _backend }
608 // Register renderer resources and submit a standard 80-byte GpuInstance binding
609 // without copying the instance stream back through the CPU.
610 function setGpuDrivenEnabled(enabled) {
611 if (_gpu == null) throw "eve.ShaderSystem.setGpuDrivenEnabled: call setGpgpu first"
612 _gpu.setGpuDrivenEnabled(enabled)
616 function gpuDrivenMeshRecord(mesh) {
617 if (_gpu == null) throw "eve.ShaderSystem.gpuDrivenMeshRecord: call setGpgpu first"
618 return _gpu.gpuDrivenMeshRecord(mesh)
621 function gpuDrivenMaterialRecord(material) {
623 throw "eve.ShaderSystem.gpuDrivenMaterialRecord: call setGpgpu first"
624 return _gpu.gpuDrivenMaterialRecord(material)
627 function submitResidentInstances(binding, buckets, instanceCount = -1,
629 if (_gpu == null || _backend == null)
630 throw "eve.ShaderSystem.submitResidentInstances: call setGpgpu first"
631 local buffer = _backend.getBuffer(binding)
633 throw "eve.ShaderSystem.submitResidentInstances: binding has no buffer"
634 if (instanceCount < 0) instanceCount = entities().len()
635 return _gpu.submitResidentInstances(buffer, buckets, instanceCount, offsetBytes)
638 function _prepareGpuBatch() {
639 if (_backend == null || _gpu == null) return
640 if (!("packEcsFloats" in eve) || !("unpackEcsFloats" in eve)) return
642 local ents = entities()
644 if (n <= 0) return null
646 foreach (b in _bindings) {
647 local floatsPer = b.fields.len()
648 if (floatsPer <= 0) continue
649 if (b.source != null) {
650 local sourceBackend = b.source.getBackend()
651 local sourceBuffer = sourceBackend == null ? null :
652 sourceBackend.getBuffer(b.sourceBinding)
653 if (sourceBuffer == null)
654 throw "eve.ShaderSystem shared source must be prepared before consumer"
655 if (sourceBuffer.getSize() < n * floatsPer * 4)
656 throw "eve.ShaderSystem shared buffer is smaller than consumer view"
657 _backend.attachBuffer(b.binding, sourceBuffer)
660 local buf = _backend.ensureBuffer(b.binding, n * floatsPer)
661 local structureChanged = b.uploadedRevision != eve._ecsRevision
662 local fullUpload = (structureChanged && (b.initializeFromCpu || b.uploadRequested)) ||
663 b.uploadEachUpdate ||
664 (b.uploadRequested && b.uploadCount < 0)
666 eve.packEcsFloats(ents, b.slot, b.fields, buf)
667 b.uploadedRevision = eve._ecsRevision
668 b.uploadRequested = false
670 } else if (structureChanged) {
671 // Output-only schema: capacity/order changed, but the shader is
672 // authoritative and promises to initialize every accessed record.
673 b.uploadedRevision = eve._ecsRevision
674 } else if (b.uploadRequested) {
675 if (!("packEcsFloatsRange" in eve))
676 throw "eve.ShaderSystem.requestUploadRange requires gpgpu range packing"
677 eve.packEcsFloatsRange(ents, b.slot, b.fields, buf,
678 b.uploadFirst, b.uploadCount)
679 b.uploadRequested = false
683 return { entities = ents, count = n }
686 function _completeGpuBatch(batch) {
687 if (batch == null) return
688 local ents = batch.entities
689 local n = batch.count
690 foreach (b in _bindings) {
691 local shouldReadback = b.readback == null ? _readback : b.readback
692 if (shouldReadback || (b.readbackRequested && b.readbackCount < 0)) {
693 local floatsPer = b.fields.len()
694 if (floatsPer <= 0) continue
695 local buf = _backend.getBuffer(b.binding)
696 eve.unpackEcsFloats(ents, b.slot, b.fields, buf, n)
697 b.readbackRequested = false
699 } else if (b.readbackRequested) {
700 if (!("unpackEcsFloatsRange" in eve))
701 throw "eve.ShaderSystem.requestReadbackRange requires gpgpu range unpacking"
702 local buf = _backend.getBuffer(b.binding)
703 eve.unpackEcsFloatsRange(ents, b.slot, b.fields, buf,
704 b.readbackFirst, b.readbackCount)
705 b.readbackRequested = false
711 // Record into a shared GpuSequence. Call completeRecorded(batch) after submit().
712 function record(sequence, dt) {
713 if (sequence == null) throw "eve.ShaderSystem.record: sequence is null"
714 local batch = _prepareGpuBatch()
715 if (batch != null) _backend.recordDispatch(sequence, batch.count, dt)
719 function completeRecorded(batch) {
720 _completeGpuBatch(batch)
724 function update(dt) {
725 local batch = _prepareGpuBatch()
726 if (batch == null) return
727 _backend.dispatch(batch.count, dt)
728 _completeGpuBatch(batch)
732function eve::view(entityClass) {
733 if (entityClass == null) return []
734 // C++ 桥接类的实体在脚本外增删(无法通知脚本),不缓存,每次新鲜收集
735 local cppBacked = false
736 if ("_cppHasView" in this) cppBacked = this._cppHasView(entityClass)
737 if (!cppBacked && (entityClass in this._ecsViewCache)) return this._ecsViewCache[entityClass]
739 this._ecsCollectInstances(entityClass, out)
740 if ("_cppCollect" in this) this._cppCollect(entityClass, out)
741 if (!cppBacked) this._ecsViewCache[entityClass] <- out
745function eve::ecsReady() {
746 // This function is installed only after the complete ECS script has run.
747 // Avoid probing the delegated eve table here: Squirrel's `in` lookup may
748 // invoke its lazy _get handler and turn a readiness query into an error.
753void injectEcsScript(ssq::Table& eveTable) {
755 const SQInteger
top = sq_gettop(
vm);
756 if (SQ_FAILED(sq_compilebuffer(
vm, kEcsScript,
757 static_cast<SQInteger
>(std::strlen(kEcsScript)),
758 "ecs.nut", SQTrue))) {
761 std::fprintf(stderr,
"EVEngine: ECS bootstrap script failed to compile: %s\n",
762 ctx.empty() ?
"unknown error" : ctx.
message.c_str());
765 sq_pushroottable(
vm);
766 if (SQ_FAILED(sq_call(
vm, 1, SQFalse, SQTrue))) {
768 std::fprintf(stderr,
"EVEngine: ECS bootstrap script failed to run: %s\n",
769 ctx.empty() ?
"unknown error"
780void cppCollect(ssq::Class
cls, ssq::Array out) {
782 const SQInteger
top = sq_gettop(
vm);
783 sq_pushobject(
vm,
cls.getRaw());
784 for (
int guard = 0; guard < 64; ++guard) {
785 if (sq_gettype(
vm, -1) != OT_CLASS)
788 sq_getstackobj(
vm, -1, &cur);
789 auto it = cppEntityViews().find(cur._unVal.pClass);
790 if (it != cppEntityViews().
end()) {
795 if (SQ_FAILED(sq_getbase(
vm, -1)))
807bool cppHasView(ssq::Object
cls) {
808 if (
cls.getType() != ssq::Type::CLASS)
return false;
810 const SQInteger
top = sq_gettop(
vm);
811 sq_pushobject(
vm,
cls.getRaw());
812 for (
int guard = 0; guard < 64; ++guard) {
813 if (sq_gettype(
vm, -1) != OT_CLASS)
break;
815 sq_getstackobj(
vm, -1, &cur);
816 if (cppEntityViews().find(cur._unVal.pClass) != cppEntityViews().
end()) {
820 if (SQ_FAILED(sq_getbase(
vm, -1)))
break;
830 table.set(
"Number", std::string(
"number"));
831 table.set(
"Boolean", std::string(
"boolean"));
832 table.set(
"String", std::string(
"string"));
835 table.addFunc(
"inspectClassFields", inspectClassFields);
836 table.addFunc(
"isSubclass", scriptIsSubclass);
837 table.addFunc(
"getClassBase", getClassBase);
838 table.addFunc(
"allocEntityId", allocEntityId);
839 table.addFunc(
"_cppHasView", cppHasView);
840 table.addFunc(
"component", [](std::string
name, ssq::Object
cls) {
841 registerScriptComponent(
name,
cls);
843 table.addFunc(
"_cppCollect", std::function<
void(ssq::Class, ssq::Array)>(cppCollect));
845 injectEcsScript(table);
847 ecsScriptInjected() =
true;
849 postEcsHooksFlushed() = 0;
856 cppEntityViews().clear();
857 ecsScriptInjected() =
false;
858 postEcsHooksFlushed() = 0;
862 cppEntityViews()[
cls.getRaw()._unVal.pClass] = std::move(
fn);
866 postEcsHooks().push_back(std::move(
fn));
870 if (!ecsScriptInjected())
return;
871 auto& hooks = postEcsHooks();
872 auto&
n = postEcsHooksFlushed();
873 while (
n < hooks.size()) {
885 ssq::Table eveTable(
vm.find(
"eve"));
struct SQVM * HSQUIRRELVM
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.
ScriptErrorContext captureCompileError(HSQUIRRELVM vm)
Captures the last compilation error recorded by the VM.
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
Build metadata (engine git commit, build time, third-party version).
void prepareEcsScriptInjection()
Resets per-VM script injection bookkeeping before a new VM is exposed.
void flushPostEcsHooks(ssq::Table &table)
Run post-ECS hooks that were registered after exposeECS already ran.
void exposeECS(ssq::Table &table)
脚本侧 ECS:Entity / Component / System / ShaderSystem / view(见 ECS.cpp)。 C++ 游戏实体仍直接用 ECS....
void registerPostEcsHook(PostEcsHook fn)
Registers post ecs hook.
std::function< void(ssq::Table &table)> PostEcsHook
在脚本 ECS 基类(eve.Component / eve.Entity / eve.System)注入之后执行的回调。 模块用它注入"extends eve.Entity"的脚本基类(例如 eve....
std::function< void(ssq::Array &out)> CppEntityViewFn
C++ 实体 → 脚本 eve.view() 桥接。 用 registerCppEntityView(cls, fn) 登记脚本类的收集函数; 脚本 eve.view(cls) 沿类链找到登记的 C++...
void exposeECSToVM(ssq::VM &vm)
在 ModuleManager::expose 之后调用;保证 eve.Component 等不被其它模块覆盖。
void registerCppEntityView(const ssq::Class &cls, CppEntityViewFn fn)
Registers cpp entity view.
SettlementPipeline::Stage fn