载入中...
搜索中...
未找到
Module.cpp
浏览该文件的文档.
1#include "common/Module.h"
2#include "common/Assert.h"
3#include "common/ECS.h"
4#include "common/Runtime.h"
6
7#include <simplesquirrel/simplesquirrel.hpp>
8#include <squirrel.h>
9
10#include <algorithm>
11#include <chrono>
12#include <cstdio>
13#include <cstring>
14#include <exception>
15#include <string>
16#include <vector>
17
18namespace eve
19{
20
22 static auto& modules() { return ModuleManager::inst().registered_modules; }
23};
24
25namespace {
26
27bool eveRawHas(HSQUIRRELVM vm, SQInteger tableIdx, const char* key) {
28 if (tableIdx < 0) tableIdx = sq_gettop(vm) + tableIdx + 1;
29 sq_pushstring(vm, key, -1);
30 if (SQ_SUCCEEDED(sq_rawget(vm, tableIdx))) {
31 sq_pop(vm, 1);
32 return true;
33 }
34 return false;
35}
36
37ssq::Table eveTableFromStack(HSQUIRRELVM vm, SQInteger tableIdx) {
38 ssq::Object wrapped(vm);
39 sq_getstackobj(vm, tableIdx, &wrapped.getRaw());
40 sq_addref(vm, &wrapped.getRaw());
41 return ssq::Table(wrapped);
42}
43
44bool isPascalCaseName(const char* key) {
45 return key && key[0] >= 'A' && key[0] <= 'Z';
46}
47
48bool isScriptEcsName(const char* key) {
49 if (!key) return false;
50 return std::strcmp(key, "Component") == 0 || std::strcmp(key, "Entity") == 0 ||
51 std::strcmp(key, "EntityContainer") == 0 || std::strcmp(key, "System") == 0 ||
52 std::strcmp(key, "ShaderSystem") == 0 || std::strcmp(key, "view") == 0 ||
53 std::strcmp(key, "ecsReady") == 0;
54}
55
56void ensureScriptEcsInTable(ssq::Table& table) {
57 HSQUIRRELVM vm = table.getHandle();
58 sq_pushobject(vm, table.getRaw());
59 const bool hasSystem = eveRawHas(vm, -1, "System");
60 const bool ready = eveRawHas(vm, -1, "Component") && eveRawHas(vm, -1, "Entity") && hasSystem;
61 if (!ready && hasSystem) {
62 // The host-information module historically owns eve.System until the
63 // script ECS is injected. Remove that one namespace collision so the
64 // canonical script System class can be created exactly once.
65 sq_pushstring(vm, "System", -1);
66 sq_deleteslot(vm, -2, SQFalse);
67 }
68 sq_pop(vm, 1);
69 if (!ready) exposeECS(table);
70}
71
72template <typename Info>
73void runExposer(Info& info, ssq::Table& table, const char* name) {
74 if (info.exposed || !info.exposer) {
75 info.exposed = true;
76 return;
77 }
78 info.exposed = true;
79 const auto t0 = std::chrono::steady_clock::now();
80 info.exposer(table);
81 const double ms =
82 std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - t0).count();
83 if (ms >= 20.0)
84 std::fprintf(stderr, "[startup] bind %s: %.1f ms\n", name ? name : "?", ms);
85 flushPostEcsHooks(table);
86}
87
88void bindNamedModule(const std::string& name, ssq::Table& table) {
89 auto& modules = ModuleBindAccess::modules();
90 auto it = modules.find(name);
91 if (it == modules.end()) return;
92 runExposer(it->second, table, name.c_str());
93}
94
95void bindUntilKeyExists(HSQUIRRELVM vm, SQInteger tableIdx, const char* key, ssq::Table& table) {
96 if (eveRawHas(vm, tableIdx, key)) return;
97 auto& modules = ModuleBindAccess::modules();
98 std::vector<std::string> pending;
99 pending.reserve(modules.size());
100 for (auto& entry : modules) {
101 if (!entry.second.exposed && entry.second.exposer) pending.push_back(entry.first);
102 }
103 std::sort(pending.begin(), pending.end());
104 for (const std::string& name : pending) {
105 auto it = modules.find(name);
106 if (it == modules.end() || it->second.exposed) continue;
107 runExposer(it->second, table, name.c_str());
108 if (eveRawHas(vm, tableIdx, key)) return;
109 }
110}
111
112void bindOnDemand(HSQUIRRELVM vm, SQInteger tableIdx, const char* key) {
113 if (!key || eveRawHas(vm, tableIdx, key)) return;
114 ssq::Table table = eveTableFromStack(vm, tableIdx);
115 if (isScriptEcsName(key)) {
116 ensureScriptEcsInTable(table);
117 return;
118 }
119 bindNamedModule(key, table);
120 if (eveRawHas(vm, tableIdx, key)) return;
121 // Nested types (WindowSettings, PlacementWorld, …) live on a parent
122 // module's exposer. Scan remaining PascalCase misses only — lowercase
123 // optional keys such as "dev" must not pull in every native class.
124 if (isPascalCaseName(key)) bindUntilKeyExists(vm, tableIdx, key, table);
125}
126
127SQInteger eveTableGet(HSQUIRRELVM vm) {
128 if (sq_gettop(vm) < 2 || sq_gettype(vm, 2) != OT_STRING)
129 return sq_throwerror(vm, _SC("the index doesn't exist"));
130 const SQChar* key = nullptr;
131 if (SQ_FAILED(sq_getstring(vm, 2, &key)) || !key)
132 return sq_throwerror(vm, _SC("the index doesn't exist"));
133 const std::string name(key);
134 // Functions declared as `function eve::name` execute with the eve table as
135 // their environment. Preserve the historical root lookup of `eve` itself
136 // instead of treating it as a native-class miss in the lazy delegate.
137 if (name == "eve") {
138 sq_push(vm, 1);
139 return 1;
140 }
141 try {
142 bindOnDemand(vm, 1, name.c_str());
143 } catch (const std::exception& error) {
144 return sq_throwerror(vm, error.what());
145 }
146 sq_push(vm, 1);
147 sq_pushstring(vm, name.c_str(), static_cast<SQInteger>(name.size()));
148 if (SQ_FAILED(sq_rawget(vm, -2))) {
149 const std::string message = "the index '" + name + "' does not exist";
150 return sq_throwerror(vm, message.c_str());
151 }
152 return 1;
153}
154
155void installLazyClassGet(ssq::Table& eveTable) {
156 HSQUIRRELVM vm = eveTable.getHandle();
157 sq_pushobject(vm, eveTable.getRaw());
158 sq_newtable(vm);
159 sq_pushstring(vm, _SC("_get"), -1);
160 sq_newclosure(vm, &eveTableGet, 0);
161 sq_newslot(vm, -3, SQFalse);
162 sq_setdelegate(vm, -2);
163 sq_pop(vm, 1);
164}
165
166} // namespace
167
168
169
171 static ModuleManager instance;
172 return instance;
173}
174
176 EV_PARAM_CHECK(name != nullptr, "module name must not be null");
177 auto p = inst().registered_modules.find(name);
178 if (p == inst().registered_modules.end() || p->second.instance == nullptr) return nullptr;
179 return p->second.instance;
180}
181
182Module* ModuleManager::getInstanceRaw(const char* name) {
183 EV_PARAM_CHECK(name != nullptr, "module name must not be null");
184 return inst().registered_modules[name].instance;
185}
186
187Module* ModuleManager::requireInstanceRaw(const char* name) {
188 EV_PARAM_CHECK(name != nullptr, "module name must not be null");
189 auto& module = inst().registered_modules[name];
190 if (module.instance) return module.instance;
191 return module.creator();
192}
193
194void ModuleManager::insert(const char* name, Module* instance) {
195 EV_PARAM_CHECK(name != nullptr, "module name must not be null");
196 EV_PARAM_CHECK(instance != nullptr, "module instance must not be null");
197 inst().registered_modules[name].instance = instance;
198}
199
201 for (auto& entry : inst().registered_modules) {
202 if (!entry.second.instance && entry.second.creator)
203 entry.second.instance = entry.second.creator();
204 }
205}
206
207void ModuleManager::register_module(const char* name, creator_t c, exposer_t e) {
208 EV_PARAM_CHECK(name != nullptr, "module name must not be null");
209 const bool hasCreator = c != nullptr; // function pointers can't be printed by zeroerr
210 EV_PARAM_CHECK(hasCreator, "module creator must not be null");
211 auto p = inst().registered_modules.find(name);
213 if (inst().plugin_registration_error_.empty())
215 std::string("module name already registered: ") + name;
216 return;
217 }
218 if (p == inst().registered_modules.end()) {
219 inst().registered_modules.insert(
220 std::make_pair(std::string(name), ModuleInfo{c, e, nullptr}));
222 inst().plugin_registration_added_.emplace_back(name);
223 } else {
224 p->second = {c, e, nullptr};
225 }
226}
227
229 auto& manager = inst();
230 if (manager.plugin_registration_active_)
232 eve::DiagnosticCode::Conflict, "a plugin registration transaction is already active", {}, {}, "module"));
233 manager.plugin_registration_active_ = true;
234 manager.plugin_registration_added_.clear();
235 manager.plugin_registration_error_.clear();
237}
238
240 auto& manager = inst();
241 if (!manager.plugin_registration_active_)
243 eve::DiagnosticCode::Conflict, "no plugin registration transaction is active", {}, {}, "module"));
244
245 const std::string error = manager.plugin_registration_error_;
246 if (!commit || !error.empty()) {
247 for (const auto& name : manager.plugin_registration_added_) {
248 auto it = manager.registered_modules.find(name);
249 if (it == manager.registered_modules.end())
250 continue;
251 delete it->second.instance;
252 manager.registered_modules.erase(it);
253 }
254 }
255 manager.plugin_registration_active_ = false;
256 manager.plugin_registration_added_.clear();
257 manager.plugin_registration_error_.clear();
258 if (!error.empty())
262}
263
265 for (auto& entry : inst().registered_modules) entry.second.exposed = false;
267
268 ssq::Table table;
269 // Runtime::initialize() guarantees expose() runs once, so addTable never
270 // sees an existing "eve" slot. Avoid find()+catch here: ASYNCIFY-wrapped
271 // frames can drop the catch and leak NotFoundException to the JS boundary.
272 table = vm.addTable("eve");
273 // Result/Status/Diagnostic projection is a common script contract, not a
274 // domain-module convenience. Expose it before module constructors run so
275 // every checked binding observes the same `eve.result` helper.
277 installLazyClassGet(table);
278 table.addFunc("_bindAllNativeClasses", []() { return ModuleManager::exposeAllForCompatibility(); });
279 table.addFunc("_ensureScriptECS", []() { return ModuleManager::ensureScriptEcs(); });
280}
281
286
287void ModuleManager::expose(ssq::VM& vm) {
288 inst().active_runtime_ = nullptr;
289 exposeVM(vm);
290}
291
293
296 return active ? &active->vm() : nullptr;
297}
298
300 if (inst().active_runtime_ == runtime) inst().active_runtime_ = nullptr;
301}
302
305 if (!active)
306 return -1;
307 int count = 0;
308 try {
309 auto scope = active->enter();
310 auto stack = active->guard();
311 ssq::Table table = active->table("eve");
312 for (auto& D : inst().registered_modules) {
313 if (D.second.exposed || !D.second.exposer)
314 continue;
315 D.second.exposer(table);
316 D.second.exposed = true;
317 flushPostEcsHooks(table);
318 ++count;
319 }
320 } catch (...) {
321 return -1;
322 }
323 return count;
324}
325
326int ModuleManager::ensureScriptEcs() {
328 if (!active) return -1;
329 try {
330 auto scope = active->enter();
331 auto stack = active->guard();
332 ssq::Table table = active->table("eve");
333 ensureScriptEcsInTable(table);
334 } catch (...) {
335 return -1;
336 }
337 return 0;
338}
339
340int ModuleManager::exposeAllForCompatibility() {
341 const int count = expose_pending();
342 if (count < 0) return count;
343 if (ensureScriptEcs() < 0) return -1;
344 return count;
345}
346
351
352
353} // namespace eve
bool & active
EVEngine assertion entry point, backed by zeroerr.
#define EV_PARAM_CHECK(cond,...)
Validate a function parameter / public API precondition.
Definition Assert.h:30
std::vector< QuestEvent > pending
struct SQVM * HSQUIRRELVM
glm::vec4 p[6]
HSQUIRRELVM vm
Definition ECS.cpp:20
std::string message
std::uint32_t key
std::int32_t c
JobScope scope
std::string name
std::string error
Definition Package.cpp:60
std::uint32_t count
The single Squirrel projection for common Result, Status and Value.
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
EVENGINE_API_FOUNDATION public API.
Definition Module.h:55
std::unordered_map< std::string, ModuleInfo > registered_modules
Definition Module.h:192
Module *(* creator_t)()
Definition Module.h:57
static void requireAll()
Instantiates every registered module through its factory.
Definition Module.cpp:200
static void detach(Runtime *runtime)
Clears the active runtime if it matches; called during shutdown.
Definition Module.cpp:299
static void exposeVM(ssq::VM &vm)
Expose vm.
Definition Module.cpp:264
void(* exposer_t)(ssq::Table &)
Definition Module.h:58
static void register_module(const char *name, creator_t c, exposer_t e)
Registers a module factory (creator + script exposer) by name.
Definition Module.cpp:207
static Module * find(const char *name)
Finds a live module instance by name, or nullptr. @ownership Borrowed; ModuleManager owns the returne...
Definition Module.cpp:175
static Runtime * runtime()
Active runtime associated with the last expose() call, or nullptr. @ownership Borrowed; the embedding...
Definition Module.cpp:292
static void expose(Runtime &runtime)
Installs the eve table and defers native class method bindings.
Definition Module.cpp:282
std::string plugin_registration_error_
Definition Module.h:195
Runtime * active_runtime_
Definition Module.h:196
static eve::Result< void > beginPluginRegistration()
Begins an atomic native-plugin module registration transaction.
Definition Module.cpp:228
bool plugin_registration_active_
Definition Module.h:193
static int expose_pending()
Expose pending.
Definition Module.cpp:303
static ModuleManager & inst()
Singleton registry holding every registered engine module.
Definition Module.cpp:170
static eve::Result< void > finishPluginRegistration(bool commit)
Commits or rolls back the active plugin registration transaction.
Definition Module.cpp:239
static ssq::VM * vm()
Script VM of the active runtime, or nullptr. @ownership Borrowed; Runtime owns the VM....
Definition Module.cpp:294
static void insert(const char *name, Module *inst)
Stores a module instance under a name (used by Module_IMPL).
Definition Module.cpp:194
std::vector< std::string > plugin_registration_added_
Definition Module.h:194
EVENGINE_API_FOUNDATION public API.
Definition Module.h:46
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:189
Scope enter()
Pushes this runtime on the thread-local runtime stack (RAII pop).
Definition Runtime.h:279
ssq::VM & vm() noexcept
Underlying SimpleSquirrel VM (requires a live, initialized runtime).
Definition Runtime.cpp:479
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
std::unordered_map< std::string, SkillDefinition > & table()
Definition Skill.cpp:65
void exposeResultBindings(ssq::Table &eveTable)
Expose the common script helpers under eve.result.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
void prepareEcsScriptInjection()
Resets per-VM script injection bookkeeping before a new VM is exposed.
Definition ECS.cpp:853
void flushPostEcsHooks(ssq::Table &table)
Run post-ECS hooks that were registered after exposeECS already ran.
Definition ECS.cpp:869
void exposeECS(ssq::Table &table)
脚本侧 ECS:Entity / Component / System / ShaderSystem / view(见 ECS.cpp)。 C++ 游戏实体仍直接用 ECS....
Definition ECS.cpp:829
static auto & modules()
Definition Module.cpp:22
ModuleInfo public API.
Definition Module.h:185
ModuleRegister(const char *name, ModuleManager::creator_t c, ModuleManager::exposer_t e)
Module register.
Definition Module.cpp:347
glm::uvec4 info