9#include "common/config.h"
18#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(__EMSCRIPTEN__)
23#include <simplesquirrel/simplesquirrel.hpp>
33#if defined(__EMSCRIPTEN__)
34#include <emscripten.h>
47ssq::VM* gFrameVm =
nullptr;
48ssq::Function* gFrameFunc =
nullptr;
54void webgpuFrameTick() {
55 if (!gFrameVm || !gFrameFunc || gFrameFunc->isEmpty())
return;
58 EM_ASM({
if (
window.hideEVELoading)
window.hideEVELoading(); });
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());
67 if (!keep) emscripten_cancel_main_loop();
80constexpr SQInteger kPlaygroundResultCap = 8192;
81char gPlaygroundResult[kPlaygroundResultCap];
83const char* setPlaygroundResult(
const char*
text) {
85 std::snprintf(gPlaygroundResult, kPlaygroundResultCap,
"%s",
text);
86 return gPlaygroundResult;
92void playgroundApply(
const char*
source) {
93 if (!gPlaygroundVm || !
source)
return;
95 const SQInteger
top = sq_gettop(
vm);
97 std::ofstream out(
"/game/main.nut", std::ios::binary | std::ios::trunc);
99 std::fprintf(stderr,
"playground: cannot open /game/main.nut for writing\n");
102 out.write(
source,
static_cast<std::streamsize
>(std::strlen(
source)));
104 }
catch (
const std::exception& e) {
105 std::fprintf(stderr,
"playground: write /game/main.nut failed: %s\n", e.what());
110 sq_pushroottable(
vm);
111 sq_pushstring(
vm, _SC(
"handle_change"), -1);
112 if (SQ_SUCCEEDED(sq_get(
vm, -2))) {
114 sq_pushroottable(
vm);
115 sq_pushstring(
vm, _SC(
"main.nut"), -1);
118 sq_call(
vm, 2, SQFalse, SQTrue);
125const char* playgroundEval(
const char*
source) {
126 if (!gPlaygroundVm)
return "engine not ready";
129 const SQInteger
top = sq_gettop(
vm);
135 const std::string wrapped = std::string(
"return (") +
source +
");";
136 const char* evalSource = wrapped.c_str();
137 SQInteger evalLen =
static_cast<SQInteger
>(wrapped.size());
139 vm, evalSource, evalLen, _SC(
"playground_eval"), SQTrue));
143 vm,
source,
static_cast<SQInteger
>(std::strlen(
source)), _SC(
"playground_eval"), SQTrue));
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");
150 return setPlaygroundResult(errbuf);
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");
158 return setPlaygroundResult(errbuf);
160 if (sq_gettype(
vm, -1) == OT_NULL) {
162 return setPlaygroundResult(
"null");
164 if (SQ_FAILED(sq_tostring(
vm, -1))) {
166 return setPlaygroundResult(
"(unprintable result)");
168 const SQChar* result =
nullptr;
169 if (SQ_FAILED(sq_getstring(
vm, -1, &result)) || !result) {
171 return setPlaygroundResult(
"(unprintable result)");
173 const char* out = setPlaygroundResult(result);
179const char* playgroundRead(
const char*
path) {
180 if (!gPlaygroundVm || !
path)
return "";
182 std::ifstream in(
path, std::ios::binary);
183 if (!in)
return setPlaygroundResult(
"");
184 std::ostringstream ss;
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;
192 return setPlaygroundResult(
"");
198int playgroundReadBytes(
const char*
path,
void* dst,
int dstCap) {
199 if (!gPlaygroundVm || !
path || !dst || dstCap <= 0)
return 0;
201 std::ifstream in(
path, std::ios::binary);
203 std::ostringstream ss;
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);
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);
222int playgroundReadbackStatus() {
223 if (!gPlaygroundVm)
return 0;
224 auto* gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>(
"Graphics");
225 return gfx ? gfx->frameReadbackStatus() : 0;
230void playgroundSetPaused(
int paused) {
231 if (!gPlaygroundVm)
return;
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);
244void playgroundResetScene() {
245 if (!gPlaygroundVm)
return;
247 const SQInteger
top = sq_gettop(
vm);
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);
254 std::vector<ecs::Entity *>
entities;
255 const uint32_t
n = reg->entity_count();
257 for (uint32_t i = 0; i <
n; ++i) {
258 if (ecs::Entity *e = reg->entity_at(i))
entities.push_back(e);
260 for (ecs::Entity *e :
entities) ecs::DestroyEntity(e);
266 sq_pushroottable(
vm);
267 sq_pushstring(
vm, _SC(
"__pg"), -1);
268 sq_deleteslot(
vm, -2, SQFalse);
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);
282EMSCRIPTEN_KEEPALIVE
const char* eve_playground_read(
const char*
path) {
283 return playgroundRead(
path);
285EMSCRIPTEN_KEEPALIVE
int eve_playground_read_bytes(
const char*
path,
void* dst,
int dstCap) {
286 return playgroundReadBytes(
path, dst, dstCap);
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(); }
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__)
301#define EVE_ANDROID_LOGE(...) do { fprintf(stderr, "EVEngine: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } while (0)
303#define EVE_ANDROID_LOGE(...) ((void)0)
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)");
323 "Start Debug Adapter Protocol server on port (implies --debug)");
325 "Start Model Context Protocol server on port for AI agents (implies --debug)");
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");
333 auto run = app.get_subcommand(
"run");
340 cerr <<
"Cannot open root script: " <<
root_path << endl;
343 std::string load_root((istreambuf_iterator<char>(ifs)), (istreambuf_iterator<char>()));
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);
370 std::string gameDir =
path;
371 std::string archivePath;
372 eve::recordLogEvent(
"info",
"eve run starting: " + (gameDir.empty() ? std::string(
".") : gameDir));
377 gameDir = std::filesystem::current_path(ec).string();
379 gameDir = std::filesystem::absolute(
path, ec).string();
380 if (ec) gameDir =
path;
382 std::filesystem::path
gp(gameDir);
383 if (std::filesystem::is_regular_file(
gp, ec) &&
gp.extension() ==
".eve") {
385 archivePath =
gp.string();
386 gameDir =
gp.parent_path().string();
388 std::filesystem::path bundled =
gp /
"game.eve";
389 if (std::filesystem::is_regular_file(bundled, ec))
390 archivePath = bundled.string();
396 if (!gameDir.empty() && gameDir !=
".") {
398 std::filesystem::current_path(gameDir, 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());
408 auto *fs = eve::filesystem::Filesystem::create();
410 if (!archivePath.empty()) {
412 std::ifstream ifs(archivePath, std::ios::binary);
414 cerr <<
"Cannot open game archive: " << archivePath << endl;
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;
425 auto cwd = std::filesystem::current_path(ec);
428 fs->setSource(cwd.string());
434 Runtime runtime(8192, ssq::Libs::ALL);
437 runtime.initialize();
439#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(__EMSCRIPTEN__)
442 dt.attach(runtime,
true);
443 dt.exposeScriptApi(runtime.vm());
445 const int bound = dt.startDap(
static_cast<uint16_t
>(dapPort));
447 cerr <<
"DAP listening on 127.0.0.1:" << bound << endl;
451 if (!dt.dap().waitUntilConfigured(15000))
452 cerr <<
"DAP: timed out waiting for client; starting anyway" << endl;
454 cerr <<
"Failed to start DAP server on port " << dapPort << endl;
459 dt.mcp().setGameRoot(std::filesystem::current_path().
string());
462 const int bound = dt.startMcp(
static_cast<uint16_t
>(mcpPort));
464 cerr <<
"MCP listening on 127.0.0.1:" << bound
465 <<
" (newline JSON-RPC; use tools/eve-mcp for Cursor stdio)" << endl;
467 cerr <<
"Failed to start MCP server on port " << mcpPort << endl;
478 ssq::Table
eve = runtime.table(
"eve");
483 eve.addFunc(
"log", [](
const std::string&
level,
const std::string& msg) {
486 eve.addFunc(
"reloadScriptModule", [&runtime](
const std::string&
path) {
487 std::string canonical;
490 throw std::runtime_error(
error);
491 return !runtime.scriptModules().reloadAffected(canonical).empty();
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,
true);
499 host->exposeScriptApi(runtime.vm());
500 return std::string(
"ok");
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__)
512 eve.set(
"hostDrivesFrames",
true);
514 eve.set(
"hostDrivesFrames",
false);
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"));
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;
535 editorHostDrive = runtime.root().find(
"config").toTable().find(
"editorHost").toBool();
538 if (editorHostDrive) {
539 auto*
host = eve::cap::query<eve::IEditorHost>();
541 std::cerr <<
"config.editorHost=true but the editor host is unavailable\n";
544 if (!
host->isWindowOpen()) {
545 std::cerr <<
"editor host did not open a window "
546 "(eve_init should call eve.host.applyEditor)\n";
549 while (!
host->exitRequested() &&
host->isWindowOpen())
host->frame();
553#if defined(__EMSCRIPTEN__)
558 gFrameVm = &runtime.vm();
559 gFrameFunc =
new ssq::Function(runtime.vm().find(
"eve_frame").toFunction());
560 gPlaygroundVm = runtime.handle();
563 EM_ASM({
if (window.eveEngineReady) window.eveEngineReady(); });
564 emscripten_set_main_loop(&webgpuFrameTick, 0, 1);
566#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(__EMSCRIPTEN__)
570 }
catch (
const std::exception& e) {
571 std::string what = e.what() ? e.what() :
"unknown exception";
573#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(EVENGINE_WEBGPU)
580 std::string report = dt.lastReport();
581 if (report.empty()) report = dt.notifyError(what);
582 cerr << report << endl;
585 cerr <<
"Run failed: " << what << endl;
588 cerr <<
"Run failed: " << what << endl;
594#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS) && !defined(EVENGINE_WEBGPU)
596 const std::string report =
598 cerr << report << endl;
601 cerr <<
"Run failed: unknown exception" << endl;
604 cerr <<
"Run failed: unknown exception" << endl;
struct SQVM * HSQUIRRELVM
Persistent, thread-safe record of engine crash and problem events.
std::vector< ecs::EntityHandle > entities
#define EVE_ANDROID_LOGE(...)
const UnitySourceAsset & source
EVENGINE_API_FOUNDATION public API.
命令行模块(eve.cmd):run / build / package / test / zip / dev-server 等子命令入口。
static std::string get_remaining(CLI::App *sub, std::string default_path=".")
子命令剩余位置参数。
unsigned getArgc()
Returns the argc.
int Run(std::string path, std::string root, bool debug=false, int dapPort=0, int mcpPort=0, std::string devServer="")
运行游戏(debug 时附加 DAP/MCP 端口)。
EVENGINE_API_BACKENDS public API.
Declarative 3D light. Collected by RenderSystem3D (max 8 per frame). type: "point" | "dir" | "spot" (...
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.
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 中不...
std::unordered_map< std::string, SkillDefinition > & table()
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
Build metadata (engine git commit, build time, third-party version).
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.
const char * demo_content
One eve <subcommand> handler: CLI setup + argument parsing.
void setup(CLI::App &app, std::shared_ptr< CLI::Formatter > formatter) override
Registers options on the CLI11 sub-app.
int parse(CLI::App &app, Cmdline &cmd) override
Parses arguments and runs the command; returns the exit code.