载入中...
搜索中...
未找到
Run.cpp
浏览该文件的文档.
1#include "cmdline.h"
2#include "scripts.h"
3#include "common/Module.h"
4#include "common/Runtime.h"
5#include "common/config.h"
8#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(EVENGINE_WEBGPU)
11#endif
12
13#include <simplesquirrel/simplesquirrel.hpp>
14#include <CLI11.hpp>
15#include <cstdint>
16#include <cstdlib>
17#include <cstdio>
18#include <ctime>
19#include <string>
20#include <vector>
21#include <filesystem>
22
23#if defined(EVENGINE_WEBGPU)
24#include <emscripten.h>
25
26namespace {
27// Global frame-loop state: the root script (load.nut) defines a global
28// eve_frame() function; emscripten_set_main_loop drives it per animation frame.
29ssq::VM* gFrameVm = nullptr;
30ssq::Function* gFrameFunc = nullptr;
31
32void webgpuFrameTick() {
33 if (!gFrameVm || !gFrameFunc || gFrameFunc->isEmpty()) return;
34 // First frame is presented by the browser, so the shell's loading overlay
35 // can be dismissed now (it covered the canvas until this point).
36 EM_ASM({ if (window.hideEVELoading) window.hideEVELoading(); });
37 bool keep = true;
38 try {
39 ssq::Object r = gFrameVm->callFunc(*gFrameFunc, *gFrameVm);
40 if (r.getType() == ssq::Type::BOOL) keep = r.toBool();
41 } catch (const std::exception& e) {
42 fprintf(stderr, "EVEngine: eve_frame error: %s\n", e.what());
43 keep = false;
44 }
45 if (!keep) emscripten_cancel_main_loop();
46}
47} // namespace
48#endif
49
50#if defined(EVENGINE_ANDROID)
51#include <android/log.h>
52#define EVE_ANDROID_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "EVEngine", __VA_ARGS__)
53#elif defined(EVENGINE_IOS) || defined(EVENGINE_WEBGPU)
54#include <cstdio>
55#define EVE_ANDROID_LOGE(...) do { fprintf(stderr, "EVEngine: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } while (0)
56#else
57#define EVE_ANDROID_LOGE(...) ((void)0)
58#endif
59
60using namespace std;
61
62namespace eve::cmd
63{
64
65struct RunArgs : Handler {
67 bool no_window = false, debug = false;
68 int dap_port = 0;
69 int mcp_port = 0;
70
71 void setup(CLI::App& app, std::shared_ptr<CLI::Formatter> formatter) override {
72 auto run = app.add_subcommand("run", "Run game under current path");
73 run->allow_extras()->formatter(formatter);
74 run->add_flag("--no-window", no_window, "Run script only, no window mode");
75 run->add_flag("--debug", debug, "debug mode (slicer + pause/breakpoints/snapshot/MCP)");
76 run->add_option("--dap-port", dap_port,
77 "Start Debug Adapter Protocol server on port (implies --debug)");
78 run->add_option("--mcp-port", mcp_port,
79 "Start Model Context Protocol server on port for AI agents (implies --debug)");
80 run->add_option("--dev-server", dev_server,
81 "Remote hot-reload dev server URL (eve dev), e.g. http://192.168.1.5:8765");
82 run->add_option("-l,--log", log_path, "log messages into a file");
83 run->add_option("-r,--root", root_path, "give a entry script instead of using the system default one");
84 }
85
86 int parse(CLI::App& app, Cmdline& cmd) override {
87 auto run = app.get_subcommand("run");
88 if (run->parsed() || cmd.getArgc() == 1) {
89 if (dap_port > 0 || mcp_port > 0) debug = true;
90 std::string current_path = cmd.get_remaining(run);
91 if (root_path != "") {
92 ifstream ifs(root_path);
93 if (!ifs) {
94 cerr << "Cannot open root script: " << root_path << endl;
95 return -1;
96 }
97 std::string load_root((istreambuf_iterator<char>(ifs)), (istreambuf_iterator<char>()));
98 return cmd.Run(current_path, load_root, debug, dap_port, mcp_port, dev_server);
99 } else {
100 // One root script for every platform: it binds whatever modules
101 // the build contains from eve.moduleList, and leaves the frame
102 // driver to eve.hostDrivesFrames.
103 return cmd.Run(current_path, load_content, debug, dap_port, mcp_port, dev_server);
104 }
105 }
106 return -1; // not handle
107 }
108};
109
111
112
113// create a new project
114int Cmdline::Run(std::string path, std::string root, bool debug, int dapPort, int mcpPort,
115 std::string devServer) {
116 std::fprintf(stderr, "[startup] Run() begins at process clock %.1f ms\n",
117 (double) std::clock() * 1000.0 / (double) CLOCKS_PER_SEC);
118 try {
119 // Resolve the game directory. A packaged game ships a game.eve archive next to
120 // the executable; we mount it into memory and run without extracting to disk.
121 std::string gameDir = path;
122 std::string archivePath;
123
124 {
125 std::error_code ec;
126 if (path.empty() || path == ".")
127 gameDir = std::filesystem::current_path(ec).string();
128 else
129 gameDir = std::filesystem::absolute(path, ec).string();
130 if (ec) gameDir = path;
131
132 std::filesystem::path gp(gameDir);
133 if (std::filesystem::is_regular_file(gp, ec) && gp.extension() == ".eve") {
134 // `eve run <path>.eve` — the archive itself is the game.
135 archivePath = gp.string();
136 gameDir = gp.parent_path().string();
137 } else {
138 std::filesystem::path bundled = gp / "game.eve";
139 if (std::filesystem::is_regular_file(bundled, ec))
140 archivePath = bundled.string();
141 }
142 }
143
144 // Switch to the game directory so relative plugin/asset paths resolve next to
145 // the executable (the packaged game's scripts live in the memory-mounted archive).
146 if (!gameDir.empty() && gameDir != ".") {
147 std::error_code ec;
148 std::filesystem::current_path(gameDir, ec);
149 if (ec) {
150 cerr << "Cannot chdir to game path '" << gameDir << "': " << ec.message() << endl;
151 EVE_ANDROID_LOGE("Cannot chdir to game path '%s': %s", gameDir.c_str(), ec.message().c_str());
152 return 2;
153 }
154 }
155
156 // Mount game source for PhysFS so relative watch/read resolve (hot reload).
157 {
158 auto *fs = eve::filesystem::Filesystem::create();
159 if (fs) {
160 if (!archivePath.empty()) {
161 // Read the packaged archive into memory and mount it there.
162 std::ifstream ifs(archivePath, std::ios::binary);
163 if (!ifs) {
164 cerr << "Cannot open game archive: " << archivePath << endl;
165 return 2;
166 }
167 std::vector<char> bytes((std::istreambuf_iterator<char>(ifs)),
168 std::istreambuf_iterator<char>());
169 if (bytes.empty() || !fs->setSourceFromMemory(bytes.data(), bytes.size())) {
170 cerr << "Failed to mount game archive from memory: " << archivePath << endl;
171 return 2;
172 }
173 } else {
174 std::error_code ec;
175 auto cwd = std::filesystem::current_path(ec);
176 if (!ec) {
177 // setSource only succeeds once; ignore failure if already mounted.
178 fs->setSource(cwd.string());
179 }
180 }
181 }
182 }
183
184 Runtime runtime(2048, ssq::Libs::ALL);
185 runtime.initialize();
186#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(EVENGINE_WEBGPU)
187 if (debug) {
188 auto& dt = eve::dev::DevTool::instance();
189 dt.attach(runtime.vm(), /*sampleLocals=*/true);
190 dt.exposeScriptApi(runtime.vm());
191 if (dapPort > 0) {
192 const int bound = dt.startDap(static_cast<uint16_t>(dapPort));
193 if (bound > 0) {
194 cerr << "DAP listening on 127.0.0.1:" << bound << endl;
195 // Wait for the IDE to finish setBreakpoints / configurationDone
196 // before loading scripts, otherwise early breakpoints are missed
197 // and stack paths are not registered yet.
198 if (!dt.dap().waitUntilConfigured(15000))
199 cerr << "DAP: timed out waiting for client; starting anyway" << endl;
200 } else {
201 cerr << "Failed to start DAP server on port " << dapPort << endl;
202 }
203 }
204 if (mcpPort > 0) {
205 try {
206 dt.mcp().setGameRoot(std::filesystem::current_path().string());
207 } catch (...) {
208 }
209 const int bound = dt.startMcp(static_cast<uint16_t>(mcpPort));
210 if (bound > 0) {
211 cerr << "MCP listening on 127.0.0.1:" << bound
212 << " (newline JSON-RPC; use tools/eve-mcp for Cursor stdio)" << endl;
213 } else {
214 cerr << "Failed to start MCP server on port " << mcpPort << endl;
215 }
216 }
217 }
218#else
219 (void)debug;
220 (void)dapPort;
221 (void)mcpPort;
222#endif
223 // Embedded default demo (src/scripts/demo.nut); load.nut runs it when no main.nut.
224 {
225 ssq::Table eve = runtime.table("eve");
226 eve.set("demoScript", std::string(demo_content ? demo_content : ""));
227 eve.set("asyncScript", std::string(async_content ? async_content : ""));
228 // Scene-director authoring kit (src/scripts/scene_director.nut). Host
229 // games load it via `compilestring(eve.sceneDirectorScript)()`; the
230 // MCP tools auto-install it on demand.
231 eve.set("sceneDirectorScript", std::string(scene_director_content ? scene_director_content : ""));
232 eve.set("devServerArg", devServer);
233 const char* bench = std::getenv("EVE_BOOT_BENCH");
234 eve.set("bootBench", bench && bench[0] != '\0' && bench[0] != '0');
235#if defined(EVENGINE_WEBGPU)
236 // The browser has no blocking loop; load.nut defines eve_frame and
237 // returns, and emscripten_set_main_loop drives it below.
238 eve.set("hostDrivesFrames", true);
239#else
240 eve.set("hostDrivesFrames", false);
241#endif
242 }
243 // The generated slot -> class table load.nut iterates over.
245 runtime.runSource(module_list_content, "module_list.nut");
246 {
247 ssq::Table eve = runtime.table("eve");
248 eve.set("moduleList", runtime.root().find("eve_modules"));
249 }
250 // Name the embedded root so DAP stack frames map to load.nut (not "buffer").
251 // Route file/dofile/loadfile through PhysFS so a packaged game (mounted in
252 // memory) can load its scripts without extracting to disk.
254 std::fprintf(stderr, "[startup] load.nut begins at process clock %.1f ms\n",
255 (double) std::clock() * 1000.0 / (double) CLOCKS_PER_SEC);
256 runtime.runSource(root, "load.nut");
257#if defined(EVENGINE_WEBGPU)
258 // Instead of a blocking while(running) Squirrel loop (which the browser
259 // never composites), drive the global eve_frame() function from an
260 // Emscripten requestAnimationFrame main loop. simulateInfiniteLoop=1
261 // keeps main() alive; `runtime` stays in scope because Run() never returns.
262 gFrameVm = &runtime.vm();
263 gFrameFunc = new ssq::Function(runtime.vm().find("eve_frame").toFunction());
264 emscripten_set_main_loop(&webgpuFrameTick, 0, /*simulateInfiniteLoop=*/1);
265#endif
266#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(EVENGINE_WEBGPU)
267 if (debug) eve::dev::DevTool::instance().detach();
268#endif
269 return 0;
270 } catch (const std::exception& e) {
271#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(EVENGINE_WEBGPU)
272 if (debug) {
273 auto& dt = eve::dev::DevTool::instance();
274 // The runtime error hook already reported uncaught script errors
275 // (including the break-on-error pause); do not slice/report twice.
276 const auto* scriptError = dynamic_cast<const eve::ScriptException*>(&e);
277 std::string report = dt.lastReport();
278 if (!(scriptError && scriptError->reported()) || report.empty())
279 report = dt.notifyError(e.what());
280 cerr << report << endl;
281 dt.detach();
282 } else {
283 cerr << "Run failed: " << e.what() << endl;
284 }
285#else
286 cerr << "Run failed: " << e.what() << endl;
287#endif
288 EVE_ANDROID_LOGE("Run failed: %s", e.what());
289 return 3;
290 } catch (...) {
291#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(EVENGINE_WEBGPU)
292 if (debug) {
293 const std::string report =
294 eve::dev::DevTool::instance().notifyError("unknown exception");
295 cerr << report << endl;
297 } else {
298 cerr << "Run failed: unknown exception" << endl;
299 }
300#else
301 cerr << "Run failed: unknown exception" << endl;
302#endif
303 EVE_ANDROID_LOGE("Run failed: unknown exception");
304 return 3;
305 }
306}
307
308
309
310} // namespace eve
#define EVE_ANDROID_LOGE(...)
Definition Run.cpp:57
ssq::Table table(const char *name) const
Looks up a named global table.
Definition Runtime.cpp:455
ScriptId runSource(std::string source, std::string sourceName="buffer")
Convenience: compileSource() then execute().
Definition Runtime.cpp:567
void initialize()
Exposes registered engine modules into the script root table. Safe to call more than once.
Definition Runtime.cpp:410
ssq::Table root() const
Root script table of the VM.
Definition Runtime.cpp:454
ssq::VM & vm() noexcept
Underlying SimpleSquirrel VM (requires a live, initialized runtime).
Definition Runtime.cpp:451
Exception raised at the public Runtime boundary.
Definition Runtime.h:53
命令行模块(eve.cmd):run / build / package / test / zip / dev-server 等子命令入口。
Definition cmdline.h:30
static std::string get_remaining(CLI::App *sub, std::string default_path=".")
子命令剩余位置参数。
Definition cmdline.cpp:79
unsigned getArgc()
Definition cmdline.h:76
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:114
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:628
static DevTool & instance()
Definition DevTool.cpp:118
#define CMD_REG(name)
Definition cmdline.h:92
void installScriptFileApi(ssq::VM &vm)
用 PhysFS 版本覆盖 Squirrel 的 file / dofile / loadfile 全局, 使脚本与资源从已挂载的游戏源(真实目录或内存挂载的 .eve 归档)解析; PhysFS 中不...
Definition FileApi.cpp:172
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
Definition Widget.cpp:225
Definition Build.cpp:11
const char * async_content
const char * load_content
const char * module_list_content
const char * scene_director_content
const char * demo_content
One eve <subcommand> handler: CLI setup + argument parsing.
Definition cmdline.h:21
std::string dev_server
Definition Run.cpp:66
void setup(CLI::App &app, std::shared_ptr< CLI::Formatter > formatter) override
Registers options on the CLI11 sub-app.
Definition Run.cpp:71
std::string root_path
Definition Run.cpp:66
std::string log_path
Definition Run.cpp:66
int parse(CLI::App &app, Cmdline &cmd) override
Parses arguments and runs the command; returns the exit code.
Definition Run.cpp:86