载入中...
搜索中...
未找到
Runtime.cpp
浏览该文件的文档.
1#include "common/Runtime.h"
2
3#include "common/Assert.h"
4#include "common/Capability.h"
5#include "common/Module.h"
11
12#include <squirrel.h>
13
14#include <algorithm>
15#include <cstdlib>
16#include <sstream>
17#include <utility>
18
19namespace eve {
20namespace {
21
22thread_local std::vector<Runtime*> runtime_stack;
23
24const char* stageName(ScriptStage stage) {
25 switch (stage) {
26 case ScriptStage::Compile: return "compile";
27 case ScriptStage::Execute: return "execute";
28 case ScriptStage::Reflect: return "reflect";
29 case ScriptStage::Unload: return "unload";
30 case ScriptStage::Shutdown: return "shutdown";
31 }
32 return "unknown";
33}
34
35std::string valueString(HSQUIRRELVM vm, SQInteger index) {
36 switch (sq_gettype(vm, index)) {
37 case OT_NULL: return "null";
38 case OT_BOOL: {
39 SQBool value = SQFalse;
40 return SQ_SUCCEEDED(sq_getbool(vm, index, &value)) && value ? "true" : "false";
41 }
42 case OT_INTEGER: {
43 SQInteger value = 0;
44 if (SQ_SUCCEEDED(sq_getinteger(vm, index, &value))) return std::to_string(value);
45 break;
46 }
47 case OT_FLOAT: {
48 SQFloat value = 0;
49 if (SQ_SUCCEEDED(sq_getfloat(vm, index, &value))) {
51 }
52 break;
53 }
54 case OT_STRING: {
55 const SQChar* value = nullptr;
56 if (SQ_SUCCEEDED(sq_getstring(vm, index, &value)) && value) return value;
57 break;
58 }
59 default: break;
60 }
61 return {};
62}
63
64ReflectedValue valueFromStack(HSQUIRRELVM vm, SQInteger index) {
65 ReflectedValue out;
66 switch (sq_gettype(vm, index)) {
67 case OT_BOOL: {
68 SQBool value = SQFalse;
69 if (SQ_SUCCEEDED(sq_getbool(vm, index, &value))) {
70 out.kind = ReflectedValueKind::Bool;
71 out.boolean = value != SQFalse;
72 }
73 break;
74 }
75 case OT_INTEGER: {
76 SQInteger value = 0;
77 if (SQ_SUCCEEDED(sq_getinteger(vm, index, &value))) {
78 out.kind = ReflectedValueKind::Integer;
79 out.integer = value;
80 }
81 break;
82 }
83 case OT_FLOAT: {
84 SQFloat value = 0;
85 if (SQ_SUCCEEDED(sq_getfloat(vm, index, &value))) {
86 out.kind = ReflectedValueKind::Float;
87 out.floating = value;
88 }
89 break;
90 }
91 case OT_STRING: {
92 const SQChar* value = nullptr;
93 if (SQ_SUCCEEDED(sq_getstring(vm, index, &value)) && value) {
94 out.kind = ReflectedValueKind::String;
95 out.text = value;
96 }
97 break;
98 }
99 case OT_ARRAY: out.kind = ReflectedValueKind::Array; break;
100 case OT_TABLE: out.kind = ReflectedValueKind::Table; break;
101 case OT_INSTANCE: out.kind = ReflectedValueKind::Instance; break;
102 case OT_NULL: break;
103 default: out.kind = ReflectedValueKind::Other; break;
104 }
105 return out;
106}
107
109int64_t valueToInteger(const ReflectedValue& value) {
110 switch (value.kind) {
111 case ReflectedValueKind::Integer: return value.integer;
112 case ReflectedValueKind::Float: return static_cast<int64_t>(value.floating);
113 case ReflectedValueKind::Bool: return value.boolean ? 1 : 0;
114 case ReflectedValueKind::String: {
115 char* end = nullptr;
116 const long long parsed = std::strtoll(value.text.c_str(), &end, 10);
117 return (end && end != value.text.c_str()) ? static_cast<int64_t>(parsed) : 0;
118 }
119 default: return 0;
120 }
121}
122
124double valueToDouble(const ReflectedValue& value) {
125 switch (value.kind) {
126 case ReflectedValueKind::Integer: return static_cast<double>(value.integer);
127 case ReflectedValueKind::Float: return value.floating;
128 case ReflectedValueKind::Bool: return value.boolean ? 1.0 : 0.0;
129 case ReflectedValueKind::String: {
130 char* end = nullptr;
131 const double parsed = std::strtod(value.text.c_str(), &end);
132 return (end && end != value.text.c_str()) ? parsed : 0.0;
133 }
134 default: return 0.0;
135 }
136}
137
139bool valueToBool(const ReflectedValue& value) {
140 switch (value.kind) {
141 case ReflectedValueKind::Bool: return value.boolean;
142 case ReflectedValueKind::Integer: return value.integer != 0;
143 case ReflectedValueKind::Float: return value.floating != 0.0;
144 case ReflectedValueKind::String:
145 return value.text == "true" || value.text == "1";
146 default: return false;
147 }
148}
149
151std::string valueToText(const ReflectedValue& value) {
152 switch (value.kind) {
153 case ReflectedValueKind::String: return value.text;
154 case ReflectedValueKind::Bool: return value.boolean ? "true" : "false";
155 case ReflectedValueKind::Integer: return std::to_string(value.integer);
156 case ReflectedValueKind::Float: {
157 return reflectedFloatString(value.floating);
158 }
159 default: return {};
160 }
161}
162
167void pushConvertedValue(HSQUIRRELVM v, SQObjectType currentType,
168 const ReflectedValue& value) {
169 switch (currentType) {
170 case OT_BOOL:
171 sq_pushbool(v, valueToBool(value) ? SQTrue : SQFalse);
172 return;
173 case OT_INTEGER:
174 sq_pushinteger(v, static_cast<SQInteger>(valueToInteger(value)));
175 return;
176 case OT_FLOAT:
177 sq_pushfloat(v, static_cast<SQFloat>(valueToDouble(value)));
178 return;
179 case OT_STRING: {
180 const std::string text = valueToText(value);
181 sq_pushstring(v, text.c_str(), static_cast<SQInteger>(text.size()));
182 return;
183 }
184 default: break;
185 }
186 switch (value.kind) {
187 case ReflectedValueKind::Bool:
188 sq_pushbool(v, value.asBool() ? SQTrue : SQFalse);
189 return;
190 case ReflectedValueKind::Integer:
191 sq_pushinteger(v, SQInteger(value.asInt()));
192 return;
193 case ReflectedValueKind::Float:
194 sq_pushfloat(v, SQFloat(value.asFloat()));
195 return;
196 case ReflectedValueKind::String:
197 sq_pushstring(v, value.asString().c_str(), -1);
198 return;
199 default:
200 sq_pushnull(v);
201 return;
202 }
203}
204
206std::vector<ReflectedMember> collectClassMembersFromStack(HSQUIRRELVM squirrel,
207 SQInteger classIndex) {
208 std::vector<ReflectedMember> members;
209 const SQInteger top = sq_gettop(squirrel);
210 sq_push(squirrel, classIndex); // duplicate the class for iteration
211 sq_pushnull(squirrel);
212 while (SQ_SUCCEEDED(sq_next(squirrel, -2))) {
213 if (sq_gettype(squirrel, -2) != OT_STRING) {
214 sq_pop(squirrel, 2);
215 continue;
216 }
217 const SQChar* memberName = nullptr;
218 if (!SQ_SUCCEEDED(sq_getstring(squirrel, -2, &memberName)) || !memberName) {
219 sq_pop(squirrel, 2);
220 continue;
221 }
222 ReflectedMember member;
223 member.name = memberName;
224 member.type = static_cast<ssq::Type>(sq_gettype(squirrel, -1));
225 member.method = member.type == ssq::Type::CLOSURE ||
226 member.type == ssq::Type::NATIVECLOSURE;
227
228 // sq_getattributes consumes the key at the stack top and replaces it
229 // with the attribute table (or null).
230 const SQInteger memberTop = sq_gettop(squirrel);
231 sq_push(squirrel, -2);
232 if (SQ_SUCCEEDED(sq_getattributes(squirrel, -5)) &&
233 sq_gettype(squirrel, -1) == OT_TABLE) {
234 sq_pushnull(squirrel);
235 while (SQ_SUCCEEDED(sq_next(squirrel, -2))) {
236 if (sq_gettype(squirrel, -2) == OT_STRING) {
237 const SQChar* attributeName = nullptr;
238 if (SQ_SUCCEEDED(sq_getstring(squirrel, -2, &attributeName)) &&
239 attributeName) {
240 ReflectedAttribute attribute;
241 attribute.name = attributeName;
242 attribute.type =
243 static_cast<ssq::Type>(sq_gettype(squirrel, -1));
244 attribute.value = valueString(squirrel, -1);
245 member.attributes.push_back(std::move(attribute));
246 }
247 }
248 sq_pop(squirrel, 2);
249 }
250 }
251 sq_settop(squirrel, memberTop);
252 std::sort(member.attributes.begin(), member.attributes.end(),
253 [](const ReflectedAttribute& a, const ReflectedAttribute& b) {
254 return a.name < b.name;
255 });
256 members.push_back(std::move(member));
257 sq_pop(squirrel, 2);
258 }
259 sq_settop(squirrel, top);
260 std::sort(members.begin(), members.end(),
261 [](const ReflectedMember& a, const ReflectedMember& b) {
262 return a.name < b.name;
263 });
264 return members;
265}
266
267SQUserPointer objectIdentity(const HSQOBJECT& object) {
268 return reinterpret_cast<SQUserPointer>(object._unVal.pRefCounted);
269}
270
271std::unique_ptr<ssq::VM> createVm(size_t stackSize, ssq::Libs::Flag libraries) {
272 // Validate before constructing the VM: with assertions compiled out the
273 // check must not be the only thing standing between a bad stack size and a
274 // half-constructed runtime.
275 EV_PARAM_CHECK(stackSize > 0, "Runtime stack size must be positive");
276 return std::make_unique<ssq::VM>(stackSize, libraries);
277}
278
280SQInteger scriptErrorHook(HSQUIRRELVM vm) {
281 script::ScriptErrorContext ctx = script::captureScriptError(vm);
282 script::setLastScriptError(vm, std::move(ctx));
283 return 0;
284}
285
286} // namespace
287
290 std::string source_text;
291 bool from_file = false;
292 std::unique_ptr<ssq::Script> compiled;
293 std::unordered_map<std::string, ssq::Class> class_objects;
294};
295
296ScriptException::ScriptException(ScriptStage stage, std::string source, uint64_t scriptId,
297 const std::string& message)
298 : std::runtime_error([&] {
299 std::ostringstream out;
300 out << "Script " << stageName(stage);
301 if (!source.empty()) out << " failed in '" << source << "'";
302 if (scriptId != 0) out << " [id=" << scriptId << "]";
303 out << ": " << message;
304 return out.str();
305 }()),
306 stage_(stage), source_(std::move(source)), script_id_(scriptId) {}
307
308ScriptException::ScriptException(ScriptStage stage, std::string source, uint64_t scriptId,
310 : std::runtime_error([&] {
311 std::ostringstream out;
312 out << "Script " << stageName(stage);
313 if (!source.empty()) out << " failed in '" << source << "'";
314 if (scriptId != 0) out << " [id=" << scriptId << "]";
315 out << ": " << script::formatScriptError(context);
316 return out.str();
317 }()),
318 stage_(stage),
319 source_(std::move(source)),
320 script_id_(scriptId),
321 line_(context.line),
322 column_(context.column),
323 function_(context.function),
324 stack_trace_(script::formatStackTrace(context.stack)),
325 reported_(context.reported) {
326 if (!stack_trace_.empty() && stack_trace_.back() == '\n') stack_trace_.pop_back();
327}
328
329const ReflectedAttribute* ReflectedMember::findAttribute(const std::string& name) const noexcept {
330 for (const auto& attribute : attributes) {
331 if (attribute.name == name) return &attribute;
332 }
333 return nullptr;
334}
335
336float ReflectedMember::attrFloat(const std::string& name, float def) const noexcept {
337 const ReflectedAttribute* attribute = findAttribute(name);
338 if (!attribute) return def;
339 char* end = nullptr;
340 const double value = std::strtod(attribute->value.c_str(), &end);
341 if (end && end != attribute->value.c_str() && *end == '\0')
342 return static_cast<float>(value);
343 return def;
344}
345
346bool ReflectedMember::attrBool(const std::string& name, bool def) const noexcept {
347 const ReflectedAttribute* attribute = findAttribute(name);
348 if (!attribute) return def;
349 if (attribute->value == "true" || attribute->value == "1" ||
350 attribute->value == "yes")
351 return true;
352 if (attribute->value == "false" || attribute->value == "0" ||
353 attribute->value == "no")
354 return false;
355 return def;
356}
357
358std::string ReflectedMember::attrString(const std::string& name,
359 const std::string& def) const {
360 const ReflectedAttribute* attribute = findAttribute(name);
361 return attribute ? attribute->value : def;
362}
363
364std::vector<std::string> ReflectedMember::attrOptions(const std::string& name) const {
365 std::vector<std::string> options;
366 const ReflectedAttribute* attribute = findAttribute(name);
367 if (!attribute) return options;
368 std::string rest = attribute->value;
369 while (true) {
370 const size_t comma = rest.find(',');
371 std::string piece = comma == std::string::npos ? rest : rest.substr(0, comma);
372 const size_t first = piece.find_first_not_of(" \t\r\n");
373 const size_t last = piece.find_last_not_of(" \t\r\n");
374 if (first != std::string::npos)
375 options.push_back(piece.substr(first, last - first + 1));
376 if (comma == std::string::npos) break;
377 rest = rest.substr(comma + 1);
378 }
379 return options;
380}
381
383 : vm_(runtime.handle()), top_(vm_ ? sq_gettop(vm_) : 0) {}
384
386 if (vm_) sq_settop(vm_, top_);
387}
388
390 : vm_(std::exchange(other.vm_, nullptr)), top_(other.top_) {}
391
393 if (this == &other) return *this;
394 if (vm_) sq_settop(vm_, top_);
395 vm_ = std::exchange(other.vm_, nullptr);
396 top_ = other.top_;
397 return *this;
398}
399
400Runtime::Scope::Scope(Runtime& runtime) : runtime_(&runtime) { runtime_stack.push_back(&runtime); }
401
403 if (!runtime_) return;
404 auto it = std::find(runtime_stack.rbegin(), runtime_stack.rend(), runtime_);
405 if (it != runtime_stack.rend()) runtime_stack.erase(std::next(it).base());
406}
407
409 : runtime_(std::exchange(other.runtime_, nullptr)) {}
410
411Runtime::Runtime(size_t stackSize, ssq::Libs::Flag libraries)
412 : vm_(createVm(stackSize, libraries)) {
413 script_modules_ = std::make_unique<script::ScriptModuleResolver>(handle());
414 script_modules_->registerDefaultProviders();
415 script_compiler_ = std::make_unique<script::ScriptCompiler>(*vm_, *script_modules_);
416 installErrorHandler();
417}
418
420
422 if (initialized_) return;
423 auto scope = enter();
424 auto stack = guard();
425 try {
427 exposeReflection(*this, table("eve"));
428 table("eve").addFunc("lastScriptError", [this]() {
430 });
431 initialized_ = true;
432 } catch (const std::exception& error) {
434 fail(ScriptStage::Execute, "<module reflection>", 0, error);
435 }
436}
437
438void Runtime::installErrorHandler() {
439 if (!vm_) return;
441 // Line numbers in sq_stackinfos require debug info at compile time.
442 sq_enabledebuginfo(vm, SQTrue);
443 // Call the hook for try/catch as well, while the throw-site stack is live.
444 // load.nut's eve_frame catch would otherwise print only the error string.
445 sq_notifyallexceptions(vm, SQTrue);
446 sq_newclosure(vm, &scriptErrorHook, 0); // pushes the closure
447 sq_seterrorhandler(vm); // pops the closure
448}
449
450void Runtime::shutdown() noexcept {
451 if (shutting_down_ || stopped_) return;
452 shutting_down_ = true;
453 unloadAll();
454 try {
455 cap::forEach<script::ISquirrelRootReleaser>([](script::ISquirrelRootReleaser* owner) {
456 if (owner) owner->releaseSquirrelRoots();
457 });
458 } catch (...) {
459 }
462 while (true) {
463 auto it = std::find(runtime_stack.begin(), runtime_stack.end(), this);
464 if (it == runtime_stack.end()) break;
465 runtime_stack.erase(it);
466 }
467 classes_.clear();
468 class_owners_.clear();
469 class_identities_.clear();
470 initialized_ = false;
471 script_compiler_.reset();
472 script_modules_.reset();
473 vm_.reset();
474 stopped_ = true;
475 shutting_down_ = false;
476}
477
478bool Runtime::initialized() const noexcept { return initialized_; }
479ssq::VM& Runtime::vm() noexcept { return *vm_; }
480const ssq::VM& Runtime::vm() const noexcept { return *vm_; }
481HSQUIRRELVM Runtime::handle() const noexcept { return vm_ ? vm_->getHandle() : nullptr; }
482script::ScriptModuleResolver& Runtime::scriptModules() noexcept { return *script_modules_; }
484 return *script_modules_;
485}
486script::ScriptCompiler& Runtime::scriptCompiler() noexcept { return *script_compiler_; }
487const script::ScriptCompiler& Runtime::scriptCompiler() const noexcept { return *script_compiler_; }
488ssq::Table Runtime::root() const { return ssq::Table(static_cast<const ssq::Object&>(*vm_)); }
489ssq::Table Runtime::table(const char* name) const {
490 const bool validName = name != nullptr && name[0] != '\0';
491 EV_PARAM_CHECK(validName, "table name must not be null or empty");
492 return ssq::Table(vm_->find(name));
493}
494
496 return runtime_stack.empty() ? nullptr : runtime_stack.back();
497}
498
499size_t Runtime::stackDepth() noexcept { return runtime_stack.size(); }
500
501Runtime::ScriptId Runtime::compileSource(std::string source, std::string sourceName) {
502 if (stopped_) throw ScriptException(ScriptStage::Compile, sourceName, 0, "runtime is shut down");
503 auto scope = enter();
504 auto stack = guard();
505 const ScriptId id = next_script_id_++;
506 auto record = std::make_unique<ScriptRecord>();
507 record->info.id = id;
508 record->info.source = sourceName;
509 record->source_text = std::move(source);
510 try {
511 record->compiled = std::make_unique<ssq::Script>(
512 script_compiler_->compileSource(record->source_text, sourceName));
513 auto [it, inserted] = scripts_.emplace(id, std::move(record));
514 (void)inserted;
515 notifyLifecycle(it->second->info);
516 return id;
517 } catch (const std::exception& error) {
518 script::ScriptErrorContext ctx = compileErrorContext(error.what(), record->source_text);
519 fail(ScriptStage::Compile, sourceName, id, std::move(ctx));
520 }
521}
522
524 const bool validPath = !path.empty();
525 EV_PARAM_CHECK(validPath, "script file path must not be empty");
526 if (stopped_) throw ScriptException(ScriptStage::Compile, path, 0, "runtime is shut down");
527 auto scope = enter();
528 auto stack = guard();
529 const ScriptId id = next_script_id_++;
530 auto record = std::make_unique<ScriptRecord>();
531 record->info.id = id;
532 record->info.source = path;
533 record->source_text = path;
534 record->from_file = true;
535 try {
536 record->compiled = std::make_unique<ssq::Script>(script_compiler_->compileFile(path));
537 auto [it, inserted] = scripts_.emplace(id, std::move(record));
538 (void)inserted;
539 notifyLifecycle(it->second->info);
540 return id;
541 } catch (const std::exception& error) {
542 script::ScriptErrorContext ctx = compileErrorContext(error.what(), {});
543 fail(ScriptStage::Compile, path, id, std::move(ctx));
544 }
545}
546
548 auto found = scripts_.find(id);
549 if (found == scripts_.end())
550 throw ScriptException(ScriptStage::Execute, {}, id, "unknown script");
551 ScriptRecord& record = *found->second;
552 if (!record.compiled || record.info.state == ScriptState::Unloaded)
553 throw ScriptException(ScriptStage::Execute, record.info.source, id, "script is unloaded");
554
555 auto scope = enter();
556 auto stack = guard();
557 const auto before = rootClasses();
558 record.info.state = ScriptState::Running;
559 record.info.error.clear();
560 notifyLifecycle(record.info);
561 try {
562 script_modules_->instantiateDependencies(record.info.source);
563 // Drive the Squirrel call directly instead of ssq::VM::run(): when a
564 // DevTool hook replaces the VM's error handler, ssq never populates its
565 // stored RuntimeException and would dereference a null unique_ptr on
566 // failure. The raw call reports through the installed handler (which
567 // captures the live stack) and lets us throw an enriched ScriptException.
569 sq_pushobject(vm, record.compiled->getRaw());
570 sq_pushroottable(vm);
571 const SQRESULT result = sq_call(vm, 1, SQFalse, SQTrue);
572 if (SQ_FAILED(result)) {
574 if (const auto* pending = script::peekLastScriptError(vm)) ctx = *pending;
575 if (ctx.empty()) ctx.message = "script error";
576 try {
577 discoverClasses(record, before);
578 } catch (...) {
579 }
580 record.info.state = ScriptState::Failed;
581 record.info.error = script::formatScriptError(ctx);
582 notifyLifecycle(record.info);
583 fail(ScriptStage::Execute, record.info.source, id, std::move(ctx));
584 }
585 discoverClasses(record, before);
586 record.info.state = ScriptState::Loaded;
587 notifyLifecycle(record.info);
588 return record.info;
589 } catch (const ScriptException&) {
590 throw;
591 } catch (const std::exception& error) {
592 try {
593 discoverClasses(record, before);
594 } catch (...) {
595 }
596 record.info.state = ScriptState::Failed;
597 record.info.error = error.what();
598 notifyLifecycle(record.info);
599 fail(ScriptStage::Execute, record.info.source, id, error);
600 }
601}
602
603Runtime::ScriptId Runtime::runSource(std::string source, std::string sourceName) {
604 const ScriptId id = compileSource(std::move(source), std::move(sourceName));
605 execute(id);
606 return id;
607}
608
610 const ScriptId id = compileFile(path);
611 execute(id);
612 return id;
613}
614
615const ReflectedClass& Runtime::reflectClass(const std::string& name, const std::string& source) {
616 const bool validName = !name.empty();
617 EV_PARAM_CHECK(validName, "reflected class name must not be empty");
618 auto scope = enter();
619 auto stack = guard();
620 try {
621 ssq::Class cls = vm_->findClass(name.c_str());
622 classes_[name] = inspectClass(name, cls, source);
623 return classes_.at(name);
624 } catch (const std::exception& error) {
625 fail(ScriptStage::Reflect, source.empty() ? name : source, 0, error);
626 }
627}
628
629ssq::Object Runtime::createInstance(const std::string& name, const std::string& source) {
630 const bool validName = !name.empty();
631 EV_PARAM_CHECK(validName, "instance class name must not be empty");
632 auto scope = enter();
633 auto stack = guard();
634 const std::string label = source.empty() ? name : source;
635 HSQUIRRELVM squirrel = handle();
636 const SQInteger top = sq_gettop(squirrel);
637 sq_pushroottable(squirrel); // [root]
638 sq_pushstring(squirrel, name.c_str(), -1); // [root, name]
639 if (SQ_FAILED(sq_get(squirrel, -2)) ||
640 sq_gettype(squirrel, -1) != OT_CLASS) {
641 sq_settop(squirrel, top);
643 ctx.message = "class not found: " + name;
644 fail(ScriptStage::Reflect, label, 0, std::move(ctx));
645 }
646 sq_createinstance(squirrel, -1); // [root, class, instance]
647 ssq::Object instance(squirrel);
648 sq_getstackobj(squirrel, -1, &instance.getRaw());
649 sq_addref(squirrel, &instance.getRaw()); // root the instance
650 // Call the default constructor with `this` = the new instance.
651 sq_pushobject(squirrel, instance.getRaw()); // [.., instance, instance]
652 sq_pushstring(squirrel, "constructor", -1); // [.., instance, instance, "constructor"]
653 if (SQ_SUCCEEDED(sq_get(squirrel, -2)) &&
654 (sq_gettype(squirrel, -1) == OT_CLOSURE ||
655 sq_gettype(squirrel, -1) == OT_NATIVECLOSURE)) {
656 // sq_call expects [closure, this, args...] with params including `this`.
657 sq_pushobject(squirrel, instance.getRaw()); // [.., instance, ctor, this]
658 if (SQ_FAILED(sq_call(squirrel, 1, SQTrue, SQTrue))) {
660 if (ctx.empty()) ctx.message = "constructor failed";
661 sq_settop(squirrel, top);
662 fail(ScriptStage::Reflect, label, 0, std::move(ctx));
663 }
664 }
665 sq_settop(squirrel, top);
666 return instance;
667}
668
669std::vector<ReflectedMember> Runtime::reflectInstance(const ssq::Object& instance) const {
670 std::vector<ReflectedMember> members;
671 if (instance.getType() != ssq::Type::INSTANCE) return members;
672 Runtime* self = const_cast<Runtime*>(this);
673 auto scope = self->enter();
674 auto stack = self->guard();
675 HSQUIRRELVM squirrel = handle();
676 const SQInteger top = sq_gettop(squirrel);
677
678 // Walk the instance's class chain (derived → base) and merge member
679 // metadata; derived members win when a name is overridden.
680 sq_pushobject(squirrel, instance.getRaw());
681 if (SQ_FAILED(sq_getclass(squirrel, -1)) || sq_gettype(squirrel, -1) != OT_CLASS) {
682 sq_settop(squirrel, top);
683 return members;
684 }
685 while (sq_gettype(squirrel, -1) == OT_CLASS) {
686 const SQInteger classTop = sq_gettop(squirrel);
687 for (ReflectedMember& member : collectClassMembersFromStack(squirrel, -1)) {
688 if (!member.method) {
689 const SQInteger readTop = sq_gettop(squirrel);
690 sq_pushobject(squirrel, instance.getRaw());
691 sq_pushstring(squirrel, member.name.c_str(), -1);
692 if (SQ_SUCCEEDED(sq_get(squirrel, -2)))
693 member.value = valueFromStack(squirrel, -1);
694 sq_settop(squirrel, readTop);
695 }
696 auto existing =
697 std::find_if(members.begin(), members.end(),
698 [&](const ReflectedMember& m) { return m.name == member.name; });
699 if (existing != members.end())
700 *existing = std::move(member);
701 else
702 members.push_back(std::move(member));
703 }
704 sq_settop(squirrel, classTop);
705 // Move to the base class; a missing base ends the chain.
706 if (!SQ_SUCCEEDED(sq_getbase(squirrel, -1)) ||
707 sq_gettype(squirrel, -1) != OT_CLASS) {
708 sq_settop(squirrel, top);
709 break;
710 }
711 sq_remove(squirrel, -2); // drop the derived class, keep the base on top
712 }
713 return members;
714}
715
716ReflectedValue Runtime::readProperty(const ssq::Object& instance,
717 const std::string& name) const {
718 if (name.empty()) return {};
719 Runtime* self = const_cast<Runtime*>(this);
720 auto scope = self->enter();
721 auto stack = self->guard();
722 HSQUIRRELVM squirrel = handle();
723 sq_pushobject(squirrel, instance.getRaw());
724 sq_pushstring(squirrel, name.c_str(), -1);
725 if (SQ_FAILED(sq_get(squirrel, -2))) {
726 return {};
727 }
728 ReflectedValue value = valueFromStack(squirrel, -1);
729 return value;
730}
731
732bool Runtime::writeProperty(const ssq::Object& instance, const std::string& name,
733 const ReflectedValue& value) const {
734 if (name.empty() || value.empty()) return false;
735 Runtime* self = const_cast<Runtime*>(this);
736 auto scope = self->enter();
737 auto stack = self->guard();
738 HSQUIRRELVM squirrel = handle();
739 sq_pushobject(squirrel, instance.getRaw()); // [instance]
740 sq_pushstring(squirrel, name.c_str(), -1); // [instance, name]
741 if (SQ_FAILED(sq_get(squirrel, -2))) { // [instance, current]
742 return false;
743 }
744 const SQObjectType currentType = sq_gettype(squirrel, -1);
745 sq_pop(squirrel, 1); // [instance]
746 if (currentType == OT_CLOSURE || currentType == OT_NATIVECLOSURE ||
747 currentType == OT_CLASS) {
748 return false;
749 }
750 sq_pushstring(squirrel, name.c_str(), -1); // [instance, name]
751 switch (currentType) {
752 case OT_BOOL:
753 sq_pushbool(squirrel, valueToBool(value) ? SQTrue : SQFalse);
754 break;
755 case OT_INTEGER:
756 sq_pushinteger(squirrel, static_cast<SQInteger>(valueToInteger(value)));
757 break;
758 case OT_FLOAT:
759 sq_pushfloat(squirrel, static_cast<SQFloat>(valueToDouble(value)));
760 break;
761 case OT_STRING: {
762 const std::string text = valueToText(value);
763 sq_pushstring(squirrel, text.c_str(), static_cast<SQInteger>(text.size()));
764 break;
765 }
766 default: {
767 // Null/unsupported slot: take the type of the incoming value.
768 switch (value.kind) {
769 case ReflectedValueKind::Bool:
770 sq_pushbool(squirrel, value.asBool() ? SQTrue : SQFalse);
771 break;
772 case ReflectedValueKind::Integer:
773 sq_pushinteger(squirrel, SQInteger(value.asInt()));
774 break;
775 case ReflectedValueKind::Float:
776 sq_pushfloat(squirrel, SQFloat(value.asFloat()));
777 break;
778 case ReflectedValueKind::String:
779 sq_pushstring(squirrel, value.asString().c_str(), -1);
780 break;
781 default: sq_pushnull(squirrel); break;
782 }
783 break;
784 }
785 }
786 // [instance, name, value] — sq_set pops the value and assigns the slot.
787 const bool ok = SQ_SUCCEEDED(sq_set(squirrel, -3));
788 return ok;
789}
790
791ssq::Object Runtime::readObjectProperty(const ssq::Object& instance,
792 const std::string& name) const {
793 if (name.empty()) return {};
794 Runtime* self = const_cast<Runtime*>(this);
795 auto scope = self->enter();
796 auto stack = self->guard();
797 HSQUIRRELVM squirrel = handle();
798 sq_pushobject(squirrel, instance.getRaw());
799 sq_pushstring(squirrel, name.c_str(), -1);
800 if (SQ_FAILED(sq_get(squirrel, -2)) ||
801 sq_gettype(squirrel, -1) != OT_INSTANCE) {
802 return {};
803 }
804 ssq::Object out(squirrel);
805 sq_getstackobj(squirrel, -1, &out.getRaw());
806 sq_addref(squirrel, &out.getRaw());
807 return out;
808}
809
810size_t Runtime::arraySize(const ssq::Object& instance,
811 const std::string& name) const {
812 Runtime* self = const_cast<Runtime*>(this);
813 auto scope = self->enter();
814 auto stack = self->guard();
815 HSQUIRRELVM squirrel = handle();
816 sq_pushobject(squirrel, instance.getRaw());
817 sq_pushstring(squirrel, name.c_str(), -1);
818 if (SQ_FAILED(sq_get(squirrel, -2)) ||
819 sq_gettype(squirrel, -1) != OT_ARRAY) {
820 return 0;
821 }
822 const SQInteger len = sq_getsize(squirrel, -1);
823 return len > 0 ? size_t(len) : 0;
824}
825
826ReflectedValue Runtime::arrayGet(const ssq::Object& instance,
827 const std::string& name, size_t index) const {
828 Runtime* self = const_cast<Runtime*>(this);
829 auto scope = self->enter();
830 auto stack = self->guard();
831 HSQUIRRELVM squirrel = handle();
832 sq_pushobject(squirrel, instance.getRaw());
833 sq_pushstring(squirrel, name.c_str(), -1);
834 if (SQ_FAILED(sq_get(squirrel, -2)) ||
835 sq_gettype(squirrel, -1) != OT_ARRAY) {
836 return {};
837 }
838 sq_pushinteger(squirrel, static_cast<SQInteger>(index));
839 if (SQ_FAILED(sq_get(squirrel, -2))) {
840 return {};
841 }
842 const ReflectedValue value = valueFromStack(squirrel, -1);
843 return value;
844}
845
846bool Runtime::arraySet(const ssq::Object& instance, const std::string& name,
847 size_t index, const ReflectedValue& value) const {
848 Runtime* self = const_cast<Runtime*>(this);
849 auto scope = self->enter();
850 auto stack = self->guard();
851 HSQUIRRELVM squirrel = handle();
852 sq_pushobject(squirrel, instance.getRaw()); // [instance]
853 sq_pushstring(squirrel, name.c_str(), -1); // [instance, name]
854 if (SQ_FAILED(sq_get(squirrel, -2)) ||
855 sq_gettype(squirrel, -1) != OT_ARRAY) { // [instance, array]
856 return false;
857 }
858 sq_pushinteger(squirrel, static_cast<SQInteger>(index)); // [.., array, idx]
859 if (SQ_FAILED(sq_get(squirrel, -2))) { // [.., array, current]
860 return false;
861 }
862 const SQObjectType currentType = sq_gettype(squirrel, -1);
863 sq_pop(squirrel, 1); // [.., array]
864 sq_pushinteger(squirrel, static_cast<SQInteger>(index)); // [.., array, idx]
865 pushConvertedValue(squirrel, currentType, value); // [.., array, idx, val]
866 const bool ok = SQ_SUCCEEDED(sq_set(squirrel, -3));
867 return ok;
868}
869
870bool Runtime::arrayAppend(const ssq::Object& instance, const std::string& name,
871 const ReflectedValue& value) const {
872 Runtime* self = const_cast<Runtime*>(this);
873 auto scope = self->enter();
874 auto stack = self->guard();
875 HSQUIRRELVM squirrel = handle();
876 sq_pushobject(squirrel, instance.getRaw());
877 sq_pushstring(squirrel, name.c_str(), -1);
878 if (SQ_FAILED(sq_get(squirrel, -2)) ||
879 sq_gettype(squirrel, -1) != OT_ARRAY) {
880 return false;
881 }
882 pushConvertedValue(squirrel, OT_NULL, value); // value on top
883 const bool ok = SQ_SUCCEEDED(sq_arrayappend(squirrel, -2));
884 return ok;
885}
886
887bool Runtime::arrayRemove(const ssq::Object& instance, const std::string& name,
888 size_t index) const {
889 Runtime* self = const_cast<Runtime*>(this);
890 auto scope = self->enter();
891 auto stack = self->guard();
892 HSQUIRRELVM squirrel = handle();
893 sq_pushobject(squirrel, instance.getRaw());
894 sq_pushstring(squirrel, name.c_str(), -1);
895 if (SQ_FAILED(sq_get(squirrel, -2)) ||
896 sq_gettype(squirrel, -1) != OT_ARRAY) {
897 return false;
898 }
899 // arr.remove(index) via the Squirrel array method.
900 sq_pushstring(squirrel, "remove", -1);
901 if (SQ_FAILED(sq_get(squirrel, -2))) { // [.., array, remove]
902 return false;
903 }
904 sq_push(squirrel, -2); // [.., array, remove, this]
905 sq_pushinteger(squirrel, static_cast<SQInteger>(index)); // [.., this, idx]
906 const bool ok = SQ_SUCCEEDED(sq_call(squirrel, 2, SQFalse, SQTrue));
907 return ok;
908}
909
910std::vector<std::string> Runtime::tableKeys(const ssq::Object& instance,
911 const std::string& name) const {
912 std::vector<std::string> keys;
913 Runtime* self = const_cast<Runtime*>(this);
914 auto scope = self->enter();
915 auto stack = self->guard();
916 HSQUIRRELVM squirrel = handle();
917 sq_pushobject(squirrel, instance.getRaw());
918 sq_pushstring(squirrel, name.c_str(), -1);
919 if (SQ_FAILED(sq_get(squirrel, -2)) ||
920 sq_gettype(squirrel, -1) != OT_TABLE) {
921 return keys;
922 }
923 sq_pushnull(squirrel);
924 while (SQ_SUCCEEDED(sq_next(squirrel, -2))) {
925 if (sq_gettype(squirrel, -2) == OT_STRING) {
926 const SQChar* key = nullptr;
927 if (SQ_SUCCEEDED(sq_getstring(squirrel, -2, &key)) && key)
928 keys.push_back(key);
929 }
930 sq_pop(squirrel, 2);
931 }
932 std::sort(keys.begin(), keys.end());
933 return keys;
934}
935
936ReflectedValue Runtime::tableGet(const ssq::Object& instance,
937 const std::string& name,
938 const std::string& key) const {
939 Runtime* self = const_cast<Runtime*>(this);
940 auto scope = self->enter();
941 auto stack = self->guard();
942 HSQUIRRELVM squirrel = handle();
943 sq_pushobject(squirrel, instance.getRaw());
944 sq_pushstring(squirrel, name.c_str(), -1);
945 if (SQ_FAILED(sq_get(squirrel, -2)) ||
946 sq_gettype(squirrel, -1) != OT_TABLE) {
947 return {};
948 }
949 sq_pushstring(squirrel, key.c_str(), -1);
950 if (SQ_FAILED(sq_get(squirrel, -2))) {
951 return {};
952 }
953 const ReflectedValue value = valueFromStack(squirrel, -1);
954 return value;
955}
956
957bool Runtime::tableSet(const ssq::Object& instance, const std::string& name,
958 const std::string& key, const ReflectedValue& value) const {
959 Runtime* self = const_cast<Runtime*>(this);
960 auto scope = self->enter();
961 auto stack = self->guard();
962 HSQUIRRELVM squirrel = handle();
963 sq_pushobject(squirrel, instance.getRaw());
964 sq_pushstring(squirrel, name.c_str(), -1);
965 if (SQ_FAILED(sq_get(squirrel, -2)) ||
966 sq_gettype(squirrel, -1) != OT_TABLE) {
967 return false;
968 }
969 sq_pushstring(squirrel, key.c_str(), -1);
970 SQObjectType currentType = OT_NULL;
971 if (SQ_SUCCEEDED(sq_get(squirrel, -2))) { // [.., table, current]
972 currentType = sq_gettype(squirrel, -1);
973 sq_pop(squirrel, 1); // [.., table]
974 }
975 sq_pushstring(squirrel, key.c_str(), -1); // [.., table, key]
976 pushConvertedValue(squirrel, currentType, value); // [.., table, key, val]
977 bool ok = SQ_SUCCEEDED(sq_set(squirrel, -3));
978 if (!ok) {
979 // Squirrel 3.1's sq_set only updates existing keys; a missing key
980 // needs sq_newslot to insert (v->Set raises "index does not exist").
981 sq_settop(squirrel, stack.top());
982 sq_pushobject(squirrel, instance.getRaw());
983 sq_pushstring(squirrel, name.c_str(), -1);
984 if (SQ_FAILED(sq_get(squirrel, -2)) ||
985 sq_gettype(squirrel, -1) != OT_TABLE) {
986 return false;
987 }
988 sq_pushstring(squirrel, key.c_str(), -1);
989 pushConvertedValue(squirrel, OT_NULL, value);
990 ok = SQ_SUCCEEDED(sq_newslot(squirrel, -3, SQFalse));
991 }
992 return ok;
993}
994
995bool Runtime::tableRemove(const ssq::Object& instance, const std::string& name,
996 const std::string& key) const {
997 Runtime* self = const_cast<Runtime*>(this);
998 auto scope = self->enter();
999 auto stack = self->guard();
1000 HSQUIRRELVM squirrel = handle();
1001 sq_pushobject(squirrel, instance.getRaw());
1002 sq_pushstring(squirrel, name.c_str(), -1);
1003 if (SQ_FAILED(sq_get(squirrel, -2)) ||
1004 sq_gettype(squirrel, -1) != OT_TABLE) {
1005 return false;
1006 }
1007 sq_pushstring(squirrel, key.c_str(), -1);
1008 const bool ok = SQ_SUCCEEDED(sq_deleteslot(squirrel, -2, SQFalse));
1009 return ok;
1010}
1011
1013 auto found = scripts_.find(id);
1014 if (found == scripts_.end())
1015 throw ScriptException(ScriptStage::Compile, {}, id, "unknown script");
1016 const bool fromFile = found->second->from_file;
1017 const std::string input = found->second->source_text;
1018 const std::string source = found->second->info.source;
1019 unload(id);
1020 return fromFile ? runFile(input) : runSource(input, source);
1021}
1022
1024 auto found = scripts_.find(id);
1025 if (found == scripts_.end()) return false;
1026 ScriptRecord& record = *found->second;
1027 if (record.info.state == ScriptState::Unloaded) return false;
1028 auto scope = enter();
1029 auto stack = guard();
1031 notifyLifecycle(record.info);
1032 try {
1033 for (const auto& pair : record.class_objects) {
1034 auto owner = class_owners_.find(pair.first);
1035 if (owner == class_owners_.end() || owner->second != id) continue;
1036 ssq::Object currentObject = vm_->find(pair.first.c_str());
1037 if (currentObject.getType() == ssq::Type::CLASS &&
1038 objectIdentity(currentObject.getRaw()) == objectIdentity(pair.second.getRaw())) {
1039 sq_pushroottable(handle());
1040 sq_pushstring(handle(), pair.first.c_str(), static_cast<SQInteger>(pair.first.size()));
1041 sq_rawdeleteslot(handle(), -2, SQFalse);
1042 sq_pop(handle(), 1);
1043 }
1044 classes_.erase(pair.first);
1045 class_owners_.erase(owner);
1046 class_identities_.erase(pair.first);
1047 }
1048 record.class_objects.clear();
1049 record.compiled.reset();
1051 notifyLifecycle(record.info);
1052 return true;
1053 } catch (const std::exception& error) {
1055 record.info.error = error.what();
1056 notifyLifecycle(record.info);
1057 fail(ScriptStage::Unload, record.info.source, id, error);
1058 }
1059}
1060
1061void Runtime::unloadAll() noexcept {
1062 std::vector<ScriptId> ids;
1063 ids.reserve(scripts_.size());
1064 for (const auto& pair : scripts_) ids.push_back(pair.first);
1065 std::reverse(ids.begin(), ids.end());
1066 for (ScriptId id : ids) {
1067 try {
1068 unload(id);
1069 } catch (...) {
1070 }
1071 }
1072 scripts_.clear();
1073}
1074
1075bool Runtime::contains(ScriptId id) const noexcept { return scripts_.count(id) != 0; }
1076
1077const ScriptInfo* Runtime::script(ScriptId id) const noexcept {
1078 auto found = scripts_.find(id);
1079 return found == scripts_.end() ? nullptr : &found->second->info;
1080}
1081
1082std::vector<ScriptInfo> Runtime::scripts() const {
1083 std::vector<ScriptInfo> result;
1084 result.reserve(scripts_.size());
1085 for (const auto& pair : scripts_) result.push_back(pair.second->info);
1086 std::sort(result.begin(), result.end(), [](const ScriptInfo& a, const ScriptInfo& b) {
1087 return a.id < b.id;
1088 });
1089 return result;
1090}
1091
1092const ReflectedClass* Runtime::reflectedClass(const std::string& name) const noexcept {
1093 auto found = classes_.find(name);
1094 return found == classes_.end() ? nullptr : &found->second;
1095}
1096
1097std::vector<ReflectedClass> Runtime::reflectedClasses() const {
1098 std::vector<ReflectedClass> result;
1099 result.reserve(classes_.size());
1100 for (const auto& pair : classes_) result.push_back(pair.second);
1101 std::sort(result.begin(), result.end(), [](const ReflectedClass& a, const ReflectedClass& b) {
1102 return a.name < b.name;
1103 });
1104 return result;
1105}
1106
1108 auto scope = enter();
1109 auto stack = guard();
1110 const auto after = rootClasses();
1111 size_t scanned = 0;
1112 for (const auto& pair : after) {
1113 auto old = class_identities_.find(pair.first);
1114 if (old != class_identities_.end() && old->second == pair.second) continue;
1115 try {
1116 ssq::Class cls = vm_->findClass(pair.first.c_str());
1117 std::string source;
1118 auto clsInfo = classes_.find(pair.first);
1119 if (clsInfo != classes_.end()) source = clsInfo->second.source;
1120 if (source.empty()) source = "<runtime scan>";
1121 classes_[pair.first] = inspectClass(pair.first, cls, source);
1122 class_identities_[pair.first] = pair.second;
1123 if (!class_owners_.count(pair.first)) class_owners_[pair.first] = 0;
1124 ++scanned;
1125 } catch (...) {
1126 // Not a script class (e.g. native class): skip.
1127 }
1128 }
1129 // Resolve base names against the current root set (also covers classes
1130 // replaced by hot reload whose base relationship changed).
1131 for (const auto& pair : after) {
1132 auto info = classes_.find(pair.first);
1133 if (info == classes_.end()) continue;
1134 try {
1135 const ssq::Class cls = vm_->findClass(pair.first.c_str());
1136 StackGuard stackGuard(*this);
1137 sq_pushobject(handle(), cls.getRaw());
1138 if (SQ_SUCCEEDED(sq_getbase(handle(), -1)) &&
1139 sq_gettype(handle(), -1) == OT_CLASS) {
1140 HSQOBJECT baseObject;
1141 if (SQ_SUCCEEDED(sq_getstackobj(handle(), -1, &baseObject))) {
1142 const SQUserPointer identity = objectIdentity(baseObject);
1143 for (const auto& candidate : after) {
1144 if (candidate.second == identity) {
1145 info->second.base = candidate.first;
1146 break;
1147 }
1148 }
1149 }
1150 }
1151 } catch (...) {
1152 }
1153 }
1154 return scanned;
1155}
1156
1157ssq::Class Runtime::findClass(const std::string& name) const {
1158 const bool validName = !name.empty();
1159 EV_PARAM_CHECK(validName, "class name must not be empty");
1160 return vm_->findClass(name.c_str());
1161}
1162
1163std::string Runtime::classNameOf(const ssq::Object& instance) const {
1164 if (instance.getType() != ssq::Type::INSTANCE) return {};
1165 Runtime* self = const_cast<Runtime*>(this);
1166 auto scope = self->enter();
1167 auto stack = self->guard();
1168 HSQUIRRELVM squirrel = handle();
1169 const SQInteger top = sq_gettop(squirrel);
1170 sq_pushobject(squirrel, instance.getRaw());
1171 if (SQ_FAILED(sq_getclass(squirrel, -1)) ||
1172 sq_gettype(squirrel, -1) != OT_CLASS) {
1173 sq_settop(squirrel, top);
1174 return {};
1175 }
1176 HSQOBJECT classObject;
1177 sq_getstackobj(squirrel, -1, &classObject);
1178 sq_settop(squirrel, top);
1179 const SQUserPointer identity = objectIdentity(classObject);
1180 for (const auto& pair : rootClasses()) {
1181 if (pair.second == identity) return pair.first;
1182 }
1183 return {};
1184}
1185
1186void Runtime::notifyLifecycle(const ScriptInfo& info) noexcept {
1187 if (!lifecycle_handler_) return;
1188 try {
1189 lifecycle_handler_(info);
1190 } catch (...) {
1191 }
1192}
1193
1194script::ScriptErrorContext Runtime::compileErrorContext(const std::string& what,
1195 const std::string& sourceText) {
1196 script::ScriptErrorContext ctx;
1197 ctx.message = what;
1198 if (!script::parseCompileError(what, &ctx.source, &ctx.line, &ctx.column, &ctx.message))
1199 return ctx;
1200 const std::string lineText = script::sourceLineText(sourceText, ctx.line);
1201 if (lineText.empty()) return ctx;
1202 std::string hint = std::to_string(ctx.line) + " | " + lineText;
1203 if (ctx.column > 0) {
1204 hint += "\n";
1205 const size_t caret = static_cast<size_t>(ctx.column > 1 ? ctx.column - 1 : 0);
1206 hint.append(caret, ' ');
1207 hint += '^';
1208 }
1209 ctx.hint = std::move(hint);
1210 return ctx;
1211}
1212
1213[[noreturn]] void Runtime::fail(ScriptStage stage, const std::string& source, ScriptId id,
1214 script::ScriptErrorContext context) {
1215 ScriptException wrapped(stage, source, id, context);
1216 if (error_handler_) {
1217 try {
1218 error_handler_(wrapped);
1219 } catch (...) {
1220 }
1221 }
1222 throw wrapped;
1223}
1224
1225[[noreturn]] void Runtime::fail(ScriptStage stage, const std::string& source, ScriptId id,
1226 const std::exception& error) {
1227 script::ScriptErrorContext ctx = script::takeLastScriptError(handle());
1228 const char* what = error.what();
1229 const std::string message = what ? what : "";
1230 // notifyallexceptions records every caught throw (e.g. file_exists). Only
1231 // keep that snapshot when it actually describes this failure.
1232 if (ctx.empty() ||
1233 (ctx.message != message && message.find(ctx.message) == std::string::npos &&
1234 ctx.message.find(message) == std::string::npos)) {
1235 ctx = {};
1236 ctx.message = message;
1237 }
1238 fail(stage, source, id, std::move(ctx));
1239}
1240
1241std::unordered_map<std::string, SQUserPointer> Runtime::rootClasses() const {
1242 std::unordered_map<std::string, SQUserPointer> result;
1243 StackGuard stack(*const_cast<Runtime*>(this));
1244 HSQUIRRELVM squirrel = handle();
1245 sq_pushroottable(squirrel);
1246 sq_pushnull(squirrel);
1247 while (SQ_SUCCEEDED(sq_next(squirrel, -2))) {
1248 if (sq_gettype(squirrel, -2) == OT_STRING && sq_gettype(squirrel, -1) == OT_CLASS) {
1249 const SQChar* name = nullptr;
1250 HSQOBJECT object;
1251 if (SQ_SUCCEEDED(sq_getstring(squirrel, -2, &name)) && name &&
1252 SQ_SUCCEEDED(sq_getstackobj(squirrel, -1, &object)))
1253 result[name] = objectIdentity(object);
1254 }
1255 sq_pop(squirrel, 2);
1256 }
1257 return result;
1258}
1259
1260void Runtime::discoverClasses(
1261 ScriptRecord& record, const std::unordered_map<std::string, SQUserPointer>& before) {
1262 const auto after = rootClasses();
1263 for (const auto& pair : after) {
1264 auto old = before.find(pair.first);
1265 if (old != before.end() && old->second == pair.second) continue;
1266 ssq::Class cls = vm_->findClass(pair.first.c_str());
1267 record.class_objects.insert_or_assign(pair.first, cls);
1268 if (std::find(record.info.classes.begin(), record.info.classes.end(), pair.first) ==
1269 record.info.classes.end())
1270 record.info.classes.push_back(pair.first);
1271 classes_[pair.first] = inspectClass(pair.first, cls, record.info.source);
1272 class_owners_[pair.first] = record.info.id;
1273 class_identities_[pair.first] = pair.second;
1274 }
1275 std::sort(record.info.classes.begin(), record.info.classes.end());
1276
1277 // Resolve base class names after every newly defined class is registered.
1278 for (const std::string& name : record.info.classes) {
1279 auto info = classes_.find(name);
1280 auto cls = record.class_objects.find(name);
1281 if (info == classes_.end() || cls == record.class_objects.end()) continue;
1282 StackGuard stack(*this);
1283 sq_pushobject(handle(), cls->second.getRaw());
1284 if (SQ_SUCCEEDED(sq_getbase(handle(), -1)) && sq_gettype(handle(), -1) == OT_CLASS) {
1285 HSQOBJECT baseObject;
1286 if (SQ_SUCCEEDED(sq_getstackobj(handle(), -1, &baseObject))) {
1287 const SQUserPointer identity = objectIdentity(baseObject);
1288 for (const auto& candidate : after) {
1289 if (candidate.second == identity) {
1290 info->second.base = candidate.first;
1291 break;
1292 }
1293 }
1294 }
1295 }
1296 }
1297}
1298
1299ReflectedClass Runtime::inspectClass(const std::string& name, const ssq::Class& cls,
1300 const std::string& source) const {
1301 ReflectedClass info;
1302 info.name = name;
1303 info.source = source;
1304 info.members = collectClassMembers(cls);
1305 return info;
1306}
1307
1308std::vector<ReflectedMember> Runtime::collectClassMembers(const ssq::Class& cls) const {
1309 std::vector<ReflectedMember> members;
1310 Runtime* self = const_cast<Runtime*>(this);
1311 auto scope = self->enter();
1312 auto stack = self->guard();
1313 HSQUIRRELVM squirrel = handle();
1314 const SQInteger top = sq_gettop(squirrel);
1315 sq_pushobject(squirrel, cls.getRaw());
1316 members = collectClassMembersFromStack(squirrel, -1);
1317 sq_settop(squirrel, top);
1318 return members;
1319}
1320
1321} // namespace eve
double value
SQInteger top
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
std::string label
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
EvpackChunkInput input
Definition Evpack.cpp:170
std::string message
std::uint32_t key
glm::uvec4 ids
float v
std::int32_t first
JobScope scope
std::string text
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
bool found
const SquirrelValueOptions & options
Explicit Value/Owned/Borrowed semantics at the Squirrel boundary.
Json object
float(ui::Theme::* member)[4]
std::map< std::string, std::span< const std::uint8_t > > members
uint32_t index
const UnitySourceAsset & source
const VegetationPresetContext & context
float m[16]
static void detach(Runtime *runtime)
Clears the active runtime if it matches; called during shutdown.
Definition Module.cpp:299
static void expose(Runtime &runtime)
Installs the eve table and defers native class method bindings.
Definition Module.cpp:282
Pushes this Runtime on the current thread's runtime stack.
Definition Runtime.h:220
Scope(Runtime &runtime)
Scope.
Definition Runtime.cpp:400
Restores the native Squirrel stack when a binding operation leaves scope.
Definition Runtime.h:196
StackGuard & operator=(StackGuard &&other) noexcept
Operator =.
Definition Runtime.cpp:392
~StackGuard()
Stack guard.
Definition Runtime.cpp:385
StackGuard(Runtime &runtime) noexcept
Stack guard.
Definition Runtime.cpp:382
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:189
void shutdown() noexcept
Tears down the VM and detaches the runtime; idempotent and noexcept.
Definition Runtime.cpp:450
ssq::Class findClass(const std::string &name) const
Finds a script class by name (throws if it does not exist).
Definition Runtime.cpp:1157
ScriptId runFile(const std::string &path)
Convenience: compileFile() then execute().
Definition Runtime.cpp:609
void unloadAll() noexcept
Unloads every script.
Definition Runtime.cpp:1061
static Runtime * current() noexcept
The runtime currently at the top of this thread's runtime stack, or nullptr.
Definition Runtime.cpp:495
ssq::Table table(const char *name) const
Looks up a named global table.
Definition Runtime.cpp:489
bool contains(ScriptId id) const noexcept
True if a script with the given id is tracked.
Definition Runtime.cpp:1075
ScriptId compileSource(std::string source, std::string sourceName="buffer")
Compiles script source without running it.
Definition Runtime.cpp:501
bool arrayRemove(const ssq::Object &instance, const std::string &name, size_t index) const
Removes one element of an array member (shifts the rest).
Definition Runtime.cpp:887
bool unload(ScriptId id)
Unloads a script and removes its declared classes; false if unknown/unloaded.
Definition Runtime.cpp:1023
ScriptId reload(ScriptId id)
Recompiles and re-runs a script from its original source.
Definition Runtime.cpp:1012
Runtime(size_t stackSize=2048, ssq::Libs::Flag libraries=ssq::Libs::ALL)
Creates a script runtime with its own Squirrel VM.
Definition Runtime.cpp:411
std::vector< ReflectedMember > reflectInstance(const ssq::Object &instance) const
Inspects a live instance: own + inherited members with current values.
Definition Runtime.cpp:669
std::vector< ReflectedClass > reflectedClasses() const
All reflected classes, sorted by name.
Definition Runtime.cpp:1097
script::ScriptModuleResolver & scriptModules() noexcept
Script module provider registry and dependency resolver for this VM.
Definition Runtime.cpp:482
ssq::Object createInstance(const std::string &name, const std::string &source={})
Creates a live instance of a script class (default constructor).
Definition Runtime.cpp:629
const ReflectedClass * reflectedClass(const std::string &name) const noexcept
Reflected class by name, or nullptr.
Definition Runtime.cpp:1092
ReflectedValue tableGet(const ssq::Object &instance, const std::string &name, const std::string &key) const
Value of a table member key; empty when missing.
Definition Runtime.cpp:936
bool writeProperty(const ssq::Object &instance, const std::string &name, const ReflectedValue &value) const
Writes one property of a live instance.
Definition Runtime.cpp:732
ssq::Object readObjectProperty(const ssq::Object &instance, const std::string &name) const
Reads a live object-valued property (nested instance).
Definition Runtime.cpp:791
static size_t stackDepth() noexcept
Depth of the thread-local runtime stack.
Definition Runtime.cpp:499
ScriptId runSource(std::string source, std::string sourceName="buffer")
Convenience: compileSource() then execute().
Definition Runtime.cpp:603
StackGuard guard() noexcept
RAII guard that restores the Squirrel stack top on scope exit.
Definition Runtime.h:277
bool initialized() const noexcept
True once initialize() has completed successfully.
Definition Runtime.cpp:478
bool tableRemove(const ssq::Object &instance, const std::string &name, const std::string &key) const
Removes a key from a table member.
Definition Runtime.cpp:995
size_t scanClasses()
Scans the root table for script classes and refreshes reflection.
Definition Runtime.cpp:1107
script::ScriptCompiler & scriptCompiler() noexcept
Unified EveScript compiler, metadata store, and binding contracts.
Definition Runtime.cpp:486
ScriptId compileFile(const std::string &path)
Compiles a script file without running it.
Definition Runtime.cpp:523
bool tableSet(const ssq::Object &instance, const std::string &name, const std::string &key, const ReflectedValue &value) const
Sets a key of a table member (creates the key when missing).
Definition Runtime.cpp:957
void initialize()
Exposes registered engine modules into the script root table. Safe to call more than once.
Definition Runtime.cpp:421
bool arrayAppend(const ssq::Object &instance, const std::string &name, const ReflectedValue &value) const
Appends an element to an array member.
Definition Runtime.cpp:870
uint64_t ScriptId
Definition Runtime.h:191
const ReflectedClass & reflectClass(const std::string &name, const std::string &source={})
Reflects a class by name, storing its inspected members.
Definition Runtime.cpp:615
const ScriptInfo * script(ScriptId id) const noexcept
Script metadata for an id, or nullptr if unknown.
Definition Runtime.cpp:1077
std::string classNameOf(const ssq::Object &instance) const
Name of the script class of a live instance ("" when unknown).
Definition Runtime.cpp:1163
HSQUIRRELVM handle() const noexcept
Raw Squirrel VM handle; nullptr after shutdown.
Definition Runtime.cpp:481
ReflectedValue readProperty(const ssq::Object &instance, const std::string &name) const
Reads one property of a live instance.
Definition Runtime.cpp:716
size_t arraySize(const ssq::Object &instance, const std::string &name) const
Element count of an array member (0 when missing/not an array).
Definition Runtime.cpp:810
bool arraySet(const ssq::Object &instance, const std::string &name, size_t index, const ReflectedValue &value) const
Replaces one element of an array member.
Definition Runtime.cpp:846
ssq::Table root() const
Root script table of the VM.
Definition Runtime.cpp:488
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
ReflectedValue arrayGet(const ssq::Object &instance, const std::string &name, size_t index) const
Element value of an array member; empty when out of range.
Definition Runtime.cpp:826
std::vector< ScriptInfo > scripts() const
Metadata for all tracked scripts, sorted by id.
Definition Runtime.cpp:1082
std::vector< std::string > tableKeys(const ssq::Object &instance, const std::string &name) const
String keys of a table member.
Definition Runtime.cpp:910
const ScriptInfo & execute(ScriptId id)
Runs a previously compiled script; throws ScriptException on failure.
Definition Runtime.cpp:547
Exception raised at the public Runtime boundary.
Definition Runtime.h:61
ScriptStage stage() const noexcept
Stage.
Definition Runtime.h:77
const std::string & source() const noexcept
Source.
Definition Runtime.h:79
ScriptException(ScriptStage stage, std::string source, uint64_t scriptId, const std::string &message)
Script exception.
Definition Runtime.cpp:296
uint64_t scriptId() const noexcept
Script id.
Definition Runtime.h:81
Releases C++-owned Squirrel roots before Runtime destroys its VM.
virtual void releaseSquirrelRoots() noexcept=0
Drops every C++-owned Squirrel root this listener still holds.
Unified Runtime compiler facade for metadata, diagnostics, and module preparation.
Owns providers, the static dependency graph, compiled closures, and module instances.
std::string formatScriptError(const ScriptErrorContext &ctx)
Formats a context into a human-readable multi-line report.
void clearLastScriptError(HSQUIRRELVM vm)
Drops any recorded error for a VM.
std::string sourceLineText(const std::string &sourceText, int line)
Extracts one 1-based line from script source text.
ScriptErrorContext takeLastScriptError(HSQUIRRELVM vm)
Consumes and clears the last recorded error for a VM.
std::string formatLastScriptError(HSQUIRRELVM vm)
Formats the last captured error for a VM without consuming it.
const ScriptErrorContext * peekLastScriptError(HSQUIRRELVM vm)
Peeks at the last recorded error for a VM without clearing it.
bool parseCompileError(const std::string &text, std::string *source, int *line, int *column, std::string *message)
Parses the ssq compile message "Compile error at source:line:column msg".
void setLastScriptError(HSQUIRRELVM vm, ScriptErrorContext ctx)
Records the last error for a VM (thread-local, synchronous consumers).
std::string formatStackTrace(const std::vector< ScriptFrame > &frames)
Formats just the call-stack portion of a context.
ScriptErrorContext captureScriptError(HSQUIRRELVM vm)
Captures the pending runtime error from inside the Squirrel error handler.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
std::string reflectedFloatString(double value)
Shortest round-trip float formatting (portable).
Definition Runtime.h:33
ScriptStage
ScriptStage public API.
Definition Runtime.h:52
void exposeReflection(Runtime &runtime, ssq::Table eveTable)
Exposes the Runtime reflection API to scripts as eve.reflect.
EVENGINE_API_FOUNDATION_INLINE public API.
Definition Runtime.h:107
EVENGINE_API_FOUNDATION_INLINE public API.
Definition Runtime.h:172
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:152
bool attrBool(const std::string &name, bool def=false) const noexcept
Boolean attribute value; def when missing or not boolean.
Definition Runtime.cpp:346
float attrFloat(const std::string &name, float def=0.f) const noexcept
Numeric attribute value; def when missing or not numeric.
Definition Runtime.cpp:336
const ReflectedAttribute * findAttribute(const std::string &name) const noexcept
Looks up an attribute by name; nullptr when absent.
Definition Runtime.cpp:329
std::string name
Definition Runtime.h:153
std::string attrString(const std::string &name, const std::string &def={}) const
Raw string attribute value; def when missing.
Definition Runtime.cpp:358
std::vector< std::string > attrOptions(const std::string &name) const
Comma-separated string attribute split into trimmed options.
Definition Runtime.cpp:364
Typed snapshot of one live instance property.
Definition Runtime.h:132
std::unique_ptr< ssq::Script > compiled
Definition Runtime.cpp:292
std::unordered_map< std::string, ssq::Class > class_objects
Definition Runtime.cpp:293
EVENGINE_API_FOUNDATION_INLINE public API.
Definition Runtime.h:180
std::string source
Definition Runtime.h:182
ScriptState state
Definition Runtime.h:183
std::string error
Definition Runtime.h:185
Structured snapshot of a script error: message, throw site and stack.
Definition ScriptError.h:25
bool empty() const noexcept
True when no error payload was captured.
Definition ScriptError.h:36
std::string message
Raw error value ("kaboom", "42", ...).
Definition ScriptError.h:26
glm::uvec4 info