载入中...
搜索中...
未找到
Run.cpp
浏览该文件的文档.
1#include "cmdline.h"
2#include "scripts.h"
3#include "common/Capability.h"
4#include "common/EditorHost.h"
5#include "common/Module.h"
6#include "common/Runtime.h"
9#include "common/config.h"
10#include "common/ECS.h"
11#include "common/BootWarmup.h"
12#include "common/CrashLog.h"
16#include "graphics/Light.h"
18#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(__EMSCRIPTEN__)
19#include "devtools/DevTool.hpp"
21#endif
22
23#include <simplesquirrel/simplesquirrel.hpp>
24#include <CLI11.hpp>
25#include <cstdint>
26#include <cstdlib>
27#include <cstdio>
28#include <ctime>
29#include <string>
30#include <vector>
31#include <filesystem>
32
33#if defined(__EMSCRIPTEN__)
34#include <emscripten.h>
35#include <squirrel.h>
36
37#include <algorithm>
38#include <cstring>
39#include <fstream>
40#include <sstream>
41
42#include "graphics/Graphics.h"
43
44namespace {
45// Global frame-loop state: the root script (load.nut) defines a global
46// eve_frame() function; emscripten_set_main_loop drives it per animation frame.
47ssq::VM* gFrameVm = nullptr;
48ssq::Function* gFrameFunc = nullptr;
49// Raw Squirrel handle for the browser playground bridges below. Set right
50// after load.nut finishes; Run() never returns (emscripten_set_main_loop with
51// simulateInfiniteLoop=1), so it stays valid for the page lifetime.
52HSQUIRRELVM gPlaygroundVm = nullptr;
53
54void webgpuFrameTick() {
55 if (!gFrameVm || !gFrameFunc || gFrameFunc->isEmpty()) return;
56 // First frame is presented by the browser, so the shell's loading overlay
57 // can be dismissed now (it covered the canvas until this point).
58 EM_ASM({ if (window.hideEVELoading) window.hideEVELoading(); });
59 bool keep = true;
60 try {
61 ssq::Object r = gFrameVm->callFunc(*gFrameFunc, *gFrameVm);
62 if (r.getType() == ssq::Type::BOOL) keep = r.toBool();
63 } catch (const std::exception& e) {
64 fprintf(stderr, "EVEngine: eve_frame error: %s\n", e.what());
65 keep = false;
66 }
67 if (!keep) emscripten_cancel_main_loop();
68}
69
70// ---------------------------------------------------------------------------
71// Playground bridge (browser only).
72//
73// The shell page (platform/webgpu/shell.html) calls these exported functions
74// to push a new main.nut into the running engine and trigger the same soft
75// hot-reload path used by the desktop file watcher (load.nut handle_change).
76// They run on the browser main thread only, so a single shared result buffer
77// is safe.
78// ---------------------------------------------------------------------------
79
80constexpr SQInteger kPlaygroundResultCap = 8192;
81char gPlaygroundResult[kPlaygroundResultCap];
82
83const char* setPlaygroundResult(const char* text) {
84 if (!text) text = "";
85 std::snprintf(gPlaygroundResult, kPlaygroundResultCap, "%s", text);
86 return gPlaygroundResult;
87}
88
89// Write /game/main.nut and soft-reload it (dofile + optional eve_reload hook).
90// The reload error path prints through the same channels as the desktop
91// watcher, so the page console shows compile/runtime errors.
92void playgroundApply(const char* source) {
93 if (!gPlaygroundVm || !source) return;
94 HSQUIRRELVM vm = gPlaygroundVm;
95 const SQInteger top = sq_gettop(vm);
96 try {
97 std::ofstream out("/game/main.nut", std::ios::binary | std::ios::trunc);
98 if (!out) {
99 std::fprintf(stderr, "playground: cannot open /game/main.nut for writing\n");
100 return;
101 }
102 out.write(source, static_cast<std::streamsize>(std::strlen(source)));
103 out.close();
104 } catch (const std::exception& e) {
105 std::fprintf(stderr, "playground: write /game/main.nut failed: %s\n", e.what());
106 return;
107 }
108 // Route through load.nut's handle_change("main.nut") so scripts are
109 // tracked and re-dofiled exactly like an on-disk change.
110 sq_pushroottable(vm);
111 sq_pushstring(vm, _SC("handle_change"), -1);
112 if (SQ_SUCCEEDED(sq_get(vm, -2))) { // [root, fn]
113 sq_remove(vm, -2); // [fn]
114 sq_pushroottable(vm); // [fn, root] (env/this param)
115 sq_pushstring(vm, _SC("main.nut"), -1);
116 // UI.cpp callScriptHandler convention: the roottable is the first
117 // parameter, so 2 params = [root, "main.nut"].
118 sq_call(vm, 2, SQFalse, SQTrue);
119 }
120 sq_settop(vm, top);
121}
122
123// Evaluate one Squirrel snippet in the running VM (REPL). Returns the
124// stringified result, or a "compile error:" / "eval error:" message.
125const char* playgroundEval(const char* source) {
126 if (!gPlaygroundVm) return "engine not ready";
127 if (!source) return "";
128 HSQUIRRELVM vm = gPlaygroundVm;
129 const SQInteger top = sq_gettop(vm);
130 char errbuf[640];
131 // REPL semantics: expressions like `__pg.frames` should return their value.
132 // A bare expression statement yields null in Squirrel, so first try
133 // `return (...)`; if that does not compile (e.g. an assignment or a
134 // multi-statement block), fall back to executing the raw source.
135 const std::string wrapped = std::string("return (") + source + ");";
136 const char* evalSource = wrapped.c_str();
137 SQInteger evalLen = static_cast<SQInteger>(wrapped.size());
138 bool compileOk = SQ_SUCCEEDED(eve::script::ScriptCompiler::compileBuffer(
139 vm, evalSource, evalLen, _SC("playground_eval"), SQTrue));
140 if (!compileOk) {
141 sq_settop(vm, top);
142 compileOk = SQ_SUCCEEDED(eve::script::ScriptCompiler::compileBuffer(
143 vm, source, static_cast<SQInteger>(std::strlen(source)), _SC("playground_eval"), SQTrue));
144 }
145 if (!compileOk) {
146 const SQChar* msg = nullptr;
147 if (sq_gettype(vm, -1) == OT_STRING) sq_getstring(vm, -1, &msg);
148 std::snprintf(errbuf, sizeof(errbuf), "compile error: %s", msg ? msg : "unknown");
149 sq_settop(vm, top);
150 return setPlaygroundResult(errbuf);
151 }
152 sq_pushroottable(vm);
153 if (SQ_FAILED(sq_call(vm, 1, SQTrue, SQTrue))) {
154 const SQChar* msg = nullptr;
155 if (sq_gettype(vm, -1) == OT_STRING) sq_getstring(vm, -1, &msg);
156 std::snprintf(errbuf, sizeof(errbuf), "eval error: %s", msg ? msg : "unknown");
157 sq_settop(vm, top);
158 return setPlaygroundResult(errbuf);
159 }
160 if (sq_gettype(vm, -1) == OT_NULL) {
161 sq_settop(vm, top);
162 return setPlaygroundResult("null");
163 }
164 if (SQ_FAILED(sq_tostring(vm, -1))) {
165 sq_settop(vm, top);
166 return setPlaygroundResult("(unprintable result)");
167 }
168 const SQChar* result = nullptr;
169 if (SQ_FAILED(sq_getstring(vm, -1, &result)) || !result) {
170 sq_settop(vm, top);
171 return setPlaygroundResult("(unprintable result)");
172 }
173 const char* out = setPlaygroundResult(result);
174 sq_settop(vm, top);
175 return out;
176}
177
178// Read a file from the browser VFS (relative to /game, which is the CWD).
179const char* playgroundRead(const char* path) {
180 if (!gPlaygroundVm || !path) return "";
181 try {
182 std::ifstream in(path, std::ios::binary);
183 if (!in) return setPlaygroundResult("");
184 std::ostringstream ss;
185 ss << in.rdbuf();
186 const std::string text = ss.str();
187 const size_t n = std::min(text.size(), static_cast<size_t>(kPlaygroundResultCap - 1));
188 std::memcpy(gPlaygroundResult, text.data(), n);
189 gPlaygroundResult[n] = '\0';
190 return gPlaygroundResult;
191 } catch (...) {
192 return setPlaygroundResult("");
193 }
194}
195
196// Binary-safe VFS read (e.g. PNG screenshots): copies up to dstCap bytes into
197// the JS-provided buffer and returns the byte count written.
198int playgroundReadBytes(const char* path, void* dst, int dstCap) {
199 if (!gPlaygroundVm || !path || !dst || dstCap <= 0) return 0;
200 try {
201 std::ifstream in(path, std::ios::binary);
202 if (!in) return 0;
203 std::ostringstream ss;
204 ss << in.rdbuf();
205 const std::string text = ss.str();
206 const int n = std::min<int>(static_cast<int>(text.size()), dstCap);
207 std::memcpy(dst, text.data(), n);
208 return n;
209 } catch (...) {
210 return 0;
211 }
212}
213
214// Queue an async frame readback (PNG). The webgpu backend pumps it from
215// present() so we never sleep inside this JS-initiated call (ASYNCIFY).
216void playgroundCapture(const char* path) {
217 if (!gPlaygroundVm || !path) return;
218 auto* gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>("Graphics");
219 if (gfx) gfx->beginFrameReadback(path);
220}
221
222int playgroundReadbackStatus() {
223 if (!gPlaygroundVm) return 0;
224 auto* gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>("Graphics");
225 return gfx ? gfx->frameReadbackStatus() : 0;
226}
227
228// Pause/freeze game updates (eve_update) while the render keeps presenting.
229// load.nut's eve_frame checks the eve_playground_paused root flag.
230void playgroundSetPaused(int paused) {
231 if (!gPlaygroundVm) return;
232 HSQUIRRELVM vm = gPlaygroundVm;
233 const SQInteger top = sq_gettop(vm);
234 sq_pushroottable(vm);
235 sq_pushstring(vm, _SC("eve_playground_paused"), -1);
236 sq_pushbool(vm, paused != 0);
237 sq_newslot(vm, -3, SQFalse);
238 sq_settop(vm, top);
239}
240
241// Destroy the ECS entities demos create (renderables, lights, cameras) and
242// drop the playground state table, so applying a *different* demo (or pressing
243// "重置状态") starts from a clean scene instead of stacking entities.
244void playgroundResetScene() {
245 if (!gPlaygroundVm) return;
246 HSQUIRRELVM vm = gPlaygroundVm;
247 const SQInteger top = sq_gettop(vm);
248 try {
249 auto *table = ecs::current();
250 auto destroyAll = [&](auto *mgr) {
251 if (!mgr || !mgr->registy) return;
252 auto *reg = dynamic_cast<ecs::IRegistryComponentBuffer *>(mgr->registy);
253 if (!reg) return;
254 std::vector<ecs::Entity *> entities;
255 const uint32_t n = reg->entity_count();
256 entities.reserve(n);
257 for (uint32_t i = 0; i < n; ++i) {
258 if (ecs::Entity *e = reg->entity_at(i)) entities.push_back(e);
259 }
260 for (ecs::Entity *e : entities) ecs::DestroyEntity(e);
261 };
262 destroyAll(table->getManager<eve::graphics::Renderable3D>());
263 destroyAll(table->getManager<eve::graphics::Light3D>());
264 destroyAll(table->getManager<eve::graphics::Camera3D>());
265
266 sq_pushroottable(vm);
267 sq_pushstring(vm, _SC("__pg"), -1);
268 sq_deleteslot(vm, -2, SQFalse);
269 sq_settop(vm, top);
270 } catch (...) {
271 sq_settop(vm, top);
272 }
273}
274} // namespace
275
276extern "C" {
277
278EMSCRIPTEN_KEEPALIVE void eve_playground_apply(const char* source) { playgroundApply(source); }
279EMSCRIPTEN_KEEPALIVE const char* eve_playground_eval(const char* source) {
280 return playgroundEval(source);
281}
282EMSCRIPTEN_KEEPALIVE const char* eve_playground_read(const char* path) {
283 return playgroundRead(path);
284}
285EMSCRIPTEN_KEEPALIVE int eve_playground_read_bytes(const char* path, void* dst, int dstCap) {
286 return playgroundReadBytes(path, dst, dstCap);
287}
288EMSCRIPTEN_KEEPALIVE void eve_playground_capture(const char* path) { playgroundCapture(path); }
289EMSCRIPTEN_KEEPALIVE int eve_playground_readback_status() { return playgroundReadbackStatus(); }
290EMSCRIPTEN_KEEPALIVE void eve_playground_set_paused(int paused) { playgroundSetPaused(paused); }
291EMSCRIPTEN_KEEPALIVE void eve_playground_reset() { playgroundResetScene(); }
292
293} // extern "C"
294#endif
295
296#if defined(EVENGINE_ANDROID)
297#include <android/log.h>
298#define EVE_ANDROID_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "EVEngine", __VA_ARGS__)
299#elif defined(EVENGINE_IOS) || defined(__EMSCRIPTEN__)
300#include <cstdio>
301#define EVE_ANDROID_LOGE(...) do { fprintf(stderr, "EVEngine: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } while (0)
302#else
303#define EVE_ANDROID_LOGE(...) ((void)0)
304#endif
305
306using namespace std;
307
308namespace eve::cmd
309{
310
311struct RunArgs : Handler {
313 bool no_window = false, debug = false;
314 int dap_port = 0;
315 int mcp_port = 0;
316
317 void setup(CLI::App& app, std::shared_ptr<CLI::Formatter> formatter) override {
318 auto run = app.add_subcommand("run", "Run game under current path");
319 run->allow_extras()->formatter(formatter);
320 run->add_flag("--no-window", no_window, "Run script only, no window mode");
321 run->add_flag("--debug", debug, "debug mode (slicer + pause/breakpoints/snapshot/MCP)");
322 run->add_option("--dap-port", dap_port,
323 "Start Debug Adapter Protocol server on port (implies --debug)");
324 run->add_option("--mcp-port", mcp_port,
325 "Start Model Context Protocol server on port for AI agents (implies --debug)");
326 run->add_option("--dev-server", dev_server,
327 "Remote hot-reload dev server URL (eve dev), e.g. http://192.168.1.5:8765");
328 run->add_option("-l,--log", log_path, "log messages into a file");
329 run->add_option("-r,--root", root_path, "give a entry script instead of using the system default one");
330 }
331
332 int parse(CLI::App& app, Cmdline& cmd) override {
333 auto run = app.get_subcommand("run");
334 if (run->parsed() || cmd.getArgc() == 1) {
335 if (dap_port > 0 || mcp_port > 0) debug = true;
336 std::string current_path = cmd.get_remaining(run);
337 if (root_path != "") {
338 ifstream ifs(root_path);
339 if (!ifs) {
340 cerr << "Cannot open root script: " << root_path << endl;
341 return -1;
342 }
343 std::string load_root((istreambuf_iterator<char>(ifs)), (istreambuf_iterator<char>()));
344 return cmd.Run(current_path, load_root, debug, dap_port, mcp_port, dev_server);
345 } else {
346 // One root script for every platform: it binds whatever modules
347 // the build contains from eve.moduleList, and leaves the frame
348 // driver to eve.hostDrivesFrames.
349 return cmd.Run(current_path, load_content, debug, dap_port, mcp_port, dev_server);
350 }
351 }
352 return -1; // not handle
353 }
354};
355
357
358
359// create a new project
360int Cmdline::Run(std::string path, std::string root, bool debug, int dapPort, int mcpPort,
361 std::string devServer) {
362 std::fprintf(stderr, "[startup] Run() begins at process clock %.1f ms\n",
363 (double) std::clock() * 1000.0 / (double) CLOCKS_PER_SEC);
364 // Overlap VkInstance creation with VM/script boot: the loader+ICD init is
365 // typically 100ms+ and does not need a window.
367 try {
368 // Resolve the game directory. A packaged game ships a game.eve archive next to
369 // the executable; we mount it into memory and run without extracting to disk.
370 std::string gameDir = path;
371 std::string archivePath;
372 eve::recordLogEvent("info", "eve run starting: " + (gameDir.empty() ? std::string(".") : gameDir));
373
374 {
375 std::error_code ec;
376 if (path.empty() || path == ".")
377 gameDir = std::filesystem::current_path(ec).string();
378 else
379 gameDir = std::filesystem::absolute(path, ec).string();
380 if (ec) gameDir = path;
381
382 std::filesystem::path gp(gameDir);
383 if (std::filesystem::is_regular_file(gp, ec) && gp.extension() == ".eve") {
384 // `eve run <path>.eve` — the archive itself is the game.
385 archivePath = gp.string();
386 gameDir = gp.parent_path().string();
387 } else {
388 std::filesystem::path bundled = gp / "game.eve";
389 if (std::filesystem::is_regular_file(bundled, ec))
390 archivePath = bundled.string();
391 }
392 }
393
394 // Switch to the game directory so relative plugin/asset paths resolve next to
395 // the executable (the packaged game's scripts live in the memory-mounted archive).
396 if (!gameDir.empty() && gameDir != ".") {
397 std::error_code ec;
398 std::filesystem::current_path(gameDir, ec);
399 if (ec) {
400 cerr << "Cannot chdir to game path '" << gameDir << "': " << ec.message() << endl;
401 EVE_ANDROID_LOGE("Cannot chdir to game path '%s': %s", gameDir.c_str(), ec.message().c_str());
402 return 2;
403 }
404 }
405
406 // Mount game source for PhysFS so relative watch/read resolve (hot reload).
407 {
408 auto *fs = eve::filesystem::Filesystem::create();
409 if (fs) {
410 if (!archivePath.empty()) {
411 // Read the packaged archive into memory and mount it there.
412 std::ifstream ifs(archivePath, std::ios::binary);
413 if (!ifs) {
414 cerr << "Cannot open game archive: " << archivePath << endl;
415 return 2;
416 }
417 std::vector<char> bytes((std::istreambuf_iterator<char>(ifs)),
418 std::istreambuf_iterator<char>());
419 if (bytes.empty() || !fs->setSourceFromMemory(bytes.data(), bytes.size())) {
420 cerr << "Failed to mount game archive from memory: " << archivePath << endl;
421 return 2;
422 }
423 } else {
424 std::error_code ec;
425 auto cwd = std::filesystem::current_path(ec);
426 if (!ec) {
427 // setSource only succeeds once; ignore failure if already mounted.
428 fs->setSource(cwd.string());
429 }
430 }
431 }
432 }
433
434 Runtime runtime(8192, ssq::Libs::ALL);
435 {
436 StartupStage stage("runtime: ModuleManager::expose");
437 runtime.initialize();
438 }
439#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(__EMSCRIPTEN__)
440 if (debug) {
441 auto& dt = eve::dev::DevTool::instance();
442 dt.attach(runtime, /*sampleLocals=*/true);
443 dt.exposeScriptApi(runtime.vm());
444 if (dapPort > 0) {
445 const int bound = dt.startDap(static_cast<uint16_t>(dapPort));
446 if (bound > 0) {
447 cerr << "DAP listening on 127.0.0.1:" << bound << endl;
448 // Wait for the IDE to finish setBreakpoints / configurationDone
449 // before loading scripts, otherwise early breakpoints are missed
450 // and stack paths are not registered yet.
451 if (!dt.dap().waitUntilConfigured(15000))
452 cerr << "DAP: timed out waiting for client; starting anyway" << endl;
453 } else {
454 cerr << "Failed to start DAP server on port " << dapPort << endl;
455 }
456 }
457 if (mcpPort > 0) {
458 try {
459 dt.mcp().setGameRoot(std::filesystem::current_path().string());
460 } catch (...) {
461 }
462 const int bound = dt.startMcp(static_cast<uint16_t>(mcpPort));
463 if (bound > 0) {
464 cerr << "MCP listening on 127.0.0.1:" << bound
465 << " (newline JSON-RPC; use tools/eve-mcp for Cursor stdio)" << endl;
466 } else {
467 cerr << "Failed to start MCP server on port " << mcpPort << endl;
468 }
469 }
470 }
471#else
472 (void)debug;
473 (void)dapPort;
474 (void)mcpPort;
475#endif
476 // Embedded default demo (src/scripts/demo.nut); load.nut runs it when no main.nut.
477 {
478 ssq::Table eve = runtime.table("eve");
479 eve.set("demoScript", std::string(demo_content ? demo_content : ""));
480 eve.set("asyncScript", std::string(async_content ? async_content : ""));
481 // Route script-side milestones into the same crash/error log
482 // (eve.log) so a crash shows how far the boot sequence got.
483 eve.addFunc("log", [](const std::string& level, const std::string& msg) {
485 });
486 eve.addFunc("reloadScriptModule", [&runtime](const std::string& path) {
487 std::string canonical;
488 std::string error;
489 if (!eve::script::ScriptModuleResolver::canonicalize({"game:/main.nut", path}, canonical, error))
490 throw std::runtime_error(error);
491 return !runtime.scriptModules().reloadAffected(canonical).empty();
492 });
493 // Editor apps (`config.editorHost=true`) start the same MVVM host
494 // `eve mcp` uses, then C++ pumps EditorHost::frame after load.nut.
495 eve.addFunc("startEditorHost", [&runtime, gameDir]() {
496 auto* host = eve::cap::query<eve::IEditorHost>();
497 if (!host) return std::string("error: editor host unavailable");
498 host->start(runtime.vm(), gameDir, /*allowWindow=*/true);
499 host->exposeScriptApi(runtime.vm());
500 return std::string("ok");
501 });
502 // Scene-director authoring kit (src/scripts/scene_director.nut). Host
503 // games load it via `compilestring(eve.sceneDirectorScript)()`; the
504 // MCP tools auto-install it on demand.
505 eve.set("sceneDirectorScript", std::string(scene_director_content ? scene_director_content : ""));
506 eve.set("devServerArg", devServer);
507 const char* bench = std::getenv("EVE_BOOT_BENCH");
508 eve.set("bootBench", bench && bench[0] != '\0' && bench[0] != '0');
509#if defined(__EMSCRIPTEN__)
510 // The browser has no blocking loop; load.nut defines eve_frame and
511 // returns, and emscripten_set_main_loop drives it below.
512 eve.set("hostDrivesFrames", true);
513#else
514 eve.set("hostDrivesFrames", false);
515#endif
516 }
517 // The generated slot -> class table load.nut iterates over.
519 runtime.runSource(module_list_content, "module_list.nut");
520 {
521 ssq::Table eve = runtime.table("eve");
522 eve.set("moduleList", runtime.root().find("eve_modules"));
523 eve.set("moduleContract", runtime.root().find("eve_module_contract"));
524 }
525 // Name the embedded root so DAP stack frames map to load.nut (not "buffer").
526 // Route file/dofile/loadfile through PhysFS so a packaged game (mounted in
527 // memory) can load its scripts without extracting to disk.
529 std::fprintf(stderr, "[startup] load.nut begins at process clock %.1f ms\n",
530 (double) std::clock() * 1000.0 / (double) CLOCKS_PER_SEC);
531 runtime.runSource(root, "load.nut");
532#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(__EMSCRIPTEN__)
533 bool editorHostDrive = false;
534 try {
535 editorHostDrive = runtime.root().find("config").toTable().find("editorHost").toBool();
536 } catch (...) {
537 }
538 if (editorHostDrive) {
539 auto* host = eve::cap::query<eve::IEditorHost>();
540 if (!host) {
541 std::cerr << "config.editorHost=true but the editor host is unavailable\n";
542 return 3;
543 }
544 if (!host->isWindowOpen()) {
545 std::cerr << "editor host did not open a window "
546 "(eve_init should call eve.host.applyEditor)\n";
547 return 3;
548 }
549 while (!host->exitRequested() && host->isWindowOpen()) host->frame();
550 host->stop();
551 }
552#endif
553#if defined(__EMSCRIPTEN__)
554 // Instead of a blocking while(running) Squirrel loop (which the browser
555 // never composites), drive the global eve_frame() function from an
556 // Emscripten requestAnimationFrame main loop. simulateInfiniteLoop=1
557 // keeps main() alive; `runtime` stays in scope because Run() never returns.
558 gFrameVm = &runtime.vm();
559 gFrameFunc = new ssq::Function(runtime.vm().find("eve_frame").toFunction());
560 gPlaygroundVm = runtime.handle();
561 // Let the shell page enable the editor once the engine VM is live
562 // (onRuntimeInitialized fires before main(), so it is not enough).
563 EM_ASM({ if (window.eveEngineReady) window.eveEngineReady(); });
564 emscripten_set_main_loop(&webgpuFrameTick, 0, /*simulateInfiniteLoop=*/1);
565#endif
566#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(__EMSCRIPTEN__)
567 if (debug) eve::dev::DevTool::instance().detach();
568#endif
569 return 0;
570 } catch (const std::exception& e) {
571 std::string what = e.what() ? e.what() : "unknown exception";
572 eve::recordLogEvent("error", "Run failed: " + what);
573#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(EVENGINE_WEBGPU)
574 if (debug) {
575 auto& dt = eve::dev::DevTool::instance();
576 // The VM error hook and the Runtime error-handler sink (wired in
577 // DevTool::attach(Runtime&)) already slice script errors; only
578 // synthesize a report when none was produced (e.g. a native
579 // std::exception with no script involvement).
580 std::string report = dt.lastReport();
581 if (report.empty()) report = dt.notifyError(what);
582 cerr << report << endl;
583 dt.detach();
584 } else {
585 cerr << "Run failed: " << what << endl;
586 }
587#else
588 cerr << "Run failed: " << what << endl;
589#endif
590 EVE_ANDROID_LOGE("Run failed: %s", what.c_str());
591 return 3;
592 } catch (...) {
593 eve::recordLogEvent("error", "Run failed: unknown exception");
594#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(EVENGINE_WEBGPU)
595 if (debug) {
596 const std::string report =
597 eve::dev::DevTool::instance().notifyError("unknown exception");
598 cerr << report << endl;
600 } else {
601 cerr << "Run failed: unknown exception" << endl;
602 }
603#else
604 cerr << "Run failed: unknown exception" << endl;
605#endif
606 EVE_ANDROID_LOGE("Run failed: unknown exception");
607 return 3;
608 }
609}
610
611
612
613} // namespace eve
SQInteger top
int root
Definition AnimSmr.cpp:119
struct SQVM * HSQUIRRELVM
Persistent, thread-safe record of engine crash and problem events.
HSQUIRRELVM vm
Definition ECS.cpp:20
glm::vec3 n
Definition Grass.cpp:63
double r
std::string text
std::uint64_t bytes
int level
gpgpu::Gpgpu & gp
Definition OnnxGpgpu.cpp:42
std::weak_ptr< Run > run
Definition OnnxGpgpu.cpp:25
std::string error
Definition Package.cpp:60
std::string path
Definition PlayHost.cpp:110
std::vector< ecs::EntityHandle > entities
#define EVE_ANDROID_LOGE(...)
Definition Run.cpp:303
UIHostHandle host
const UnitySourceAsset & source
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:189
StartupStage public API.
命令行模块(eve.cmd):run / build / package / test / zip / dev-server 等子命令入口。
Definition cmdline.h:33
static std::string get_remaining(CLI::App *sub, std::string default_path=".")
子命令剩余位置参数。
Definition cmdline.cpp:80
unsigned getArgc()
Returns the argc.
Definition cmdline.h:83
int Run(std::string path, std::string root, bool debug=false, int dapPort=0, int mcpPort=0, std::string devServer="")
运行游戏(debug 时附加 DAP/MCP 端口)。
Definition Run.cpp:360
std::string notifyError(const std::string &errorMessage, const std::vector< std::string > &hintVars={})
Record an error; includes script slice and render-pipeline slice when enabled.
Definition DevTool.cpp:807
static DevTool & instance()
Definition DevTool.cpp:132
EVENGINE_API_BACKENDS public API.
Declarative 3D light. Collected by RenderSystem3D (max 8 per frame). type: "point" | "dir" | "spot" (...
Definition Light.h:191
EVENGINE_API_BACKENDS public API.
static SQRESULT compileBuffer(HSQUIRRELVM vm, const SQChar *source, SQInteger size, const SQChar *sourceName, SQBool raiseError)
Unified raw-VM entry used by REPL, debugger, MCP, and editor adapters.
static bool canonicalize(const ScriptModuleRequest &request, std::string &output, std::string &error)
Normalizes a logical module URI and rejects root traversal.
#define CMD_REG(name)
Definition cmdline.h:101
EVENGINE_API_FOUNDATION void startVulkanInstanceWarmup()
Kick off asynchronous VkInstance creation if a backend registered.
void installScriptFileApi(ssq::VM &vm)
用 PhysFS 版本覆盖 Squirrel 的 file / dofile / loadfile 全局, 使脚本与资源从已挂载的游戏源(真实目录或内存挂载的 .eve 归档)解析; PhysFS 中不...
Definition FileApi.cpp:213
std::unordered_map< std::string, SkillDefinition > & table()
Definition Skill.cpp:65
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
Definition Widget.cpp:292
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
const char * async_content
const char * load_content
const char * module_list_content
const char * scene_director_content
void recordLogEvent(const std::string &level, const std::string &message)
Appends a timestamped event line to the crash/error log.
Definition CrashLog.cpp:70
const char * demo_content
One eve <subcommand> handler: CLI setup + argument parsing.
Definition cmdline.h:23
std::string dev_server
Definition Run.cpp:312
void setup(CLI::App &app, std::shared_ptr< CLI::Formatter > formatter) override
Registers options on the CLI11 sub-app.
Definition Run.cpp:317
std::string root_path
Definition Run.cpp:312
std::string log_path
Definition Run.cpp:312
int parse(CLI::App &app, Cmdline &cmd) override
Parses arguments and runs the command; returns the exit code.
Definition Run.cpp:332