22thread_local std::vector<Runtime*> runtime_stack;
36 switch (sq_gettype(
vm,
index)) {
37 case OT_NULL:
return "null";
39 SQBool
value = SQFalse;
55 const SQChar*
value =
nullptr;
66 switch (sq_gettype(
vm,
index)) {
68 SQBool
value = SQFalse;
70 out.kind = ReflectedValueKind::Bool;
71 out.boolean =
value != SQFalse;
78 out.kind = ReflectedValueKind::Integer;
86 out.kind = ReflectedValueKind::Float;
92 const SQChar*
value =
nullptr;
94 out.kind = ReflectedValueKind::String;
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;
103 default: out.kind = ReflectedValueKind::Other;
break;
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: {
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;
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: {
131 const double parsed = std::strtod(
value.text.c_str(), &
end);
132 return (
end &&
end !=
value.text.c_str()) ? parsed : 0.0;
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;
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: {
167void pushConvertedValue(
HSQUIRRELVM v, SQObjectType currentType,
168 const ReflectedValue&
value) {
169 switch (currentType) {
171 sq_pushbool(
v, valueToBool(
value) ? SQTrue : SQFalse);
174 sq_pushinteger(
v,
static_cast<SQInteger
>(valueToInteger(
value)));
177 sq_pushfloat(
v,
static_cast<SQFloat
>(valueToDouble(
value)));
180 const std::string
text = valueToText(
value);
181 sq_pushstring(
v,
text.c_str(),
static_cast<SQInteger
>(
text.size()));
186 switch (
value.kind) {
187 case ReflectedValueKind::Bool:
188 sq_pushbool(
v,
value.asBool() ? SQTrue : SQFalse);
190 case ReflectedValueKind::Integer:
191 sq_pushinteger(
v, SQInteger(
value.asInt()));
193 case ReflectedValueKind::Float:
194 sq_pushfloat(
v, SQFloat(
value.asFloat()));
196 case ReflectedValueKind::String:
197 sq_pushstring(
v,
value.asString().c_str(), -1);
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);
211 sq_pushnull(squirrel);
212 while (SQ_SUCCEEDED(sq_next(squirrel, -2))) {
213 if (sq_gettype(squirrel, -2) != OT_STRING) {
217 const SQChar* memberName =
nullptr;
218 if (!SQ_SUCCEEDED(sq_getstring(squirrel, -2, &memberName)) || !memberName) {
224 member.type =
static_cast<ssq::Type
>(sq_gettype(squirrel, -1));
226 member.type == ssq::Type::NATIVECLOSURE;
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)) &&
240 ReflectedAttribute attribute;
241 attribute.name = attributeName;
243 static_cast<ssq::Type
>(sq_gettype(squirrel, -1));
244 attribute.value = valueString(squirrel, -1);
245 member.attributes.push_back(std::move(attribute));
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;
259 sq_settop(squirrel,
top);
261 [](
const ReflectedMember&
a,
const ReflectedMember&
b) {
262 return a.name < b.name;
267SQUserPointer objectIdentity(
const HSQOBJECT&
object) {
268 return reinterpret_cast<SQUserPointer
>(
object._unVal.pRefCounted);
271std::unique_ptr<ssq::VM> createVm(
size_t stackSize, ssq::Libs::Flag libraries) {
275 EV_PARAM_CHECK(stackSize > 0,
"Runtime stack size must be positive");
276 return std::make_unique<ssq::VM>(stackSize, libraries);
298 :
std::runtime_error([&] {
299 std::ostringstream out;
300 out <<
"Script " << stageName(
stage);
301 if (!
source.empty()) out <<
" failed in '" <<
source <<
"'";
306 stage_(stage), source_(std::move(
source)), script_id_(scriptId) {}
310 :
std::runtime_error([&] {
311 std::ostringstream out;
312 out <<
"Script " << stageName(
stage);
313 if (!
source.empty()) out <<
" failed in '" <<
source <<
"'";
319 source_(std::move(
source)),
320 script_id_(scriptId),
326 if (!stack_trace_.empty() && stack_trace_.back() ==
'\n') stack_trace_.pop_back();
330 for (
const auto& attribute : attributes) {
331 if (attribute.name ==
name)
return &attribute;
338 if (!attribute)
return def;
340 const double value = std::strtod(attribute->
value.c_str(), &
end);
342 return static_cast<float>(
value);
348 if (!attribute)
return def;
349 if (attribute->
value ==
"true" || attribute->
value ==
"1" ||
350 attribute->
value ==
"yes")
352 if (attribute->
value ==
"false" || attribute->
value ==
"0" ||
353 attribute->
value ==
"no")
359 const std::string& def)
const {
361 return attribute ? attribute->
value : def;
365 std::vector<std::string>
options;
367 if (!attribute)
return options;
368 std::string rest = attribute->
value;
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)
376 if (comma == std::string::npos)
break;
377 rest = rest.substr(comma + 1);
383 : vm_(runtime.handle()), top_(vm_ ? sq_gettop(vm_) : 0) {}
386 if (vm_) sq_settop(vm_, top_);
390 : vm_(std::exchange(other.vm_,
nullptr)), top_(other.top_) {}
393 if (
this == &other)
return *
this;
394 if (vm_) sq_settop(vm_, top_);
395 vm_ = std::exchange(other.vm_,
nullptr);
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());
409 : runtime_(std::exchange(other.runtime_,
nullptr)) {}
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();
422 if (initialized_)
return;
424 auto stack =
guard();
428 table(
"eve").addFunc(
"lastScriptError", [
this]() {
432 }
catch (
const std::exception&
error) {
438void Runtime::installErrorHandler() {
442 sq_enabledebuginfo(
vm, SQTrue);
445 sq_notifyallexceptions(
vm, SQTrue);
446 sq_newclosure(
vm, &scriptErrorHook, 0);
447 sq_seterrorhandler(
vm);
451 if (shutting_down_ || stopped_)
return;
452 shutting_down_ =
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);
468 class_owners_.clear();
469 class_identities_.clear();
470 initialized_ =
false;
471 script_compiler_.reset();
472 script_modules_.reset();
475 shutting_down_ =
false;
484 return *script_modules_;
488ssq::Table
Runtime::root()
const {
return ssq::Table(
static_cast<const ssq::Object&
>(*vm_)); }
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));
496 return runtime_stack.empty() ? nullptr : runtime_stack.back();
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);
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));
515 notifyLifecycle(it->second->info);
517 }
catch (
const std::exception&
error) {
524 const bool validPath = !
path.empty();
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;
536 record->compiled = std::make_unique<ssq::Script>(script_compiler_->compileFile(
path));
537 auto [it, inserted] = scripts_.emplace(
id, std::move(record));
539 notifyLifecycle(it->second->info);
541 }
catch (
const std::exception&
error) {
548 auto found = scripts_.find(
id);
549 if (
found == scripts_.end())
556 auto stack =
guard();
557 const auto before = rootClasses();
559 record.info.error.clear();
560 notifyLifecycle(record.info);
562 script_modules_->instantiateDependencies(record.info.source);
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)) {
577 discoverClasses(record, before);
582 notifyLifecycle(record.info);
585 discoverClasses(record, before);
587 notifyLifecycle(record.info);
591 }
catch (
const std::exception&
error) {
593 discoverClasses(record, before);
597 record.info.error =
error.what();
598 notifyLifecycle(record.info);
616 const bool validName = !
name.empty();
617 EV_PARAM_CHECK(validName,
"reflected class name must not be empty");
619 auto stack =
guard();
621 ssq::Class
cls = vm_->findClass(
name.c_str());
623 return classes_.at(
name);
624 }
catch (
const std::exception&
error) {
630 const bool validName = !
name.empty();
631 EV_PARAM_CHECK(validName,
"instance class name must not be empty");
633 auto stack =
guard();
636 const SQInteger
top = sq_gettop(squirrel);
637 sq_pushroottable(squirrel);
638 sq_pushstring(squirrel,
name.c_str(), -1);
639 if (SQ_FAILED(sq_get(squirrel, -2)) ||
640 sq_gettype(squirrel, -1) != OT_CLASS) {
641 sq_settop(squirrel,
top);
646 sq_createinstance(squirrel, -1);
647 ssq::Object instance(squirrel);
648 sq_getstackobj(squirrel, -1, &instance.getRaw());
649 sq_addref(squirrel, &instance.getRaw());
651 sq_pushobject(squirrel, instance.getRaw());
652 sq_pushstring(squirrel,
"constructor", -1);
653 if (SQ_SUCCEEDED(sq_get(squirrel, -2)) &&
654 (sq_gettype(squirrel, -1) == OT_CLOSURE ||
655 sq_gettype(squirrel, -1) == OT_NATIVECLOSURE)) {
657 sq_pushobject(squirrel, instance.getRaw());
658 if (SQ_FAILED(sq_call(squirrel, 1, SQTrue, SQTrue))) {
661 sq_settop(squirrel,
top);
665 sq_settop(squirrel,
top);
670 std::vector<ReflectedMember>
members;
671 if (instance.getType() != ssq::Type::INSTANCE)
return members;
674 auto stack = self->
guard();
676 const SQInteger
top = sq_gettop(squirrel);
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);
685 while (sq_gettype(squirrel, -1) == OT_CLASS) {
686 const SQInteger classTop = sq_gettop(squirrel);
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);
700 *existing = std::move(
member);
704 sq_settop(squirrel, classTop);
706 if (!SQ_SUCCEEDED(sq_getbase(squirrel, -1)) ||
707 sq_gettype(squirrel, -1) != OT_CLASS) {
708 sq_settop(squirrel,
top);
711 sq_remove(squirrel, -2);
717 const std::string&
name)
const {
718 if (
name.empty())
return {};
721 auto stack = self->
guard();
723 sq_pushobject(squirrel, instance.getRaw());
724 sq_pushstring(squirrel,
name.c_str(), -1);
725 if (SQ_FAILED(sq_get(squirrel, -2))) {
734 if (
name.empty() ||
value.empty())
return false;
737 auto stack = self->
guard();
739 sq_pushobject(squirrel, instance.getRaw());
740 sq_pushstring(squirrel,
name.c_str(), -1);
741 if (SQ_FAILED(sq_get(squirrel, -2))) {
744 const SQObjectType currentType = sq_gettype(squirrel, -1);
746 if (currentType == OT_CLOSURE || currentType == OT_NATIVECLOSURE ||
747 currentType == OT_CLASS) {
750 sq_pushstring(squirrel,
name.c_str(), -1);
751 switch (currentType) {
753 sq_pushbool(squirrel, valueToBool(
value) ? SQTrue : SQFalse);
756 sq_pushinteger(squirrel,
static_cast<SQInteger
>(valueToInteger(
value)));
759 sq_pushfloat(squirrel,
static_cast<SQFloat
>(valueToDouble(
value)));
762 const std::string
text = valueToText(
value);
763 sq_pushstring(squirrel,
text.c_str(),
static_cast<SQInteger
>(
text.size()));
768 switch (
value.kind) {
769 case ReflectedValueKind::Bool:
770 sq_pushbool(squirrel,
value.asBool() ? SQTrue : SQFalse);
772 case ReflectedValueKind::Integer:
773 sq_pushinteger(squirrel, SQInteger(
value.asInt()));
775 case ReflectedValueKind::Float:
776 sq_pushfloat(squirrel, SQFloat(
value.asFloat()));
778 case ReflectedValueKind::String:
779 sq_pushstring(squirrel,
value.asString().c_str(), -1);
781 default: sq_pushnull(squirrel);
break;
787 const bool ok = SQ_SUCCEEDED(sq_set(squirrel, -3));
792 const std::string&
name)
const {
793 if (
name.empty())
return {};
796 auto stack = self->
guard();
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) {
804 ssq::Object out(squirrel);
805 sq_getstackobj(squirrel, -1, &out.getRaw());
806 sq_addref(squirrel, &out.getRaw());
811 const std::string&
name)
const {
814 auto stack = self->
guard();
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) {
822 const SQInteger len = sq_getsize(squirrel, -1);
823 return len > 0 ? size_t(len) : 0;
827 const std::string&
name,
size_t index)
const {
830 auto stack = self->
guard();
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) {
838 sq_pushinteger(squirrel,
static_cast<SQInteger
>(
index));
839 if (SQ_FAILED(sq_get(squirrel, -2))) {
850 auto stack = self->
guard();
852 sq_pushobject(squirrel, instance.getRaw());
853 sq_pushstring(squirrel,
name.c_str(), -1);
854 if (SQ_FAILED(sq_get(squirrel, -2)) ||
855 sq_gettype(squirrel, -1) != OT_ARRAY) {
858 sq_pushinteger(squirrel,
static_cast<SQInteger
>(
index));
859 if (SQ_FAILED(sq_get(squirrel, -2))) {
862 const SQObjectType currentType = sq_gettype(squirrel, -1);
864 sq_pushinteger(squirrel,
static_cast<SQInteger
>(
index));
865 pushConvertedValue(squirrel, currentType,
value);
866 const bool ok = SQ_SUCCEEDED(sq_set(squirrel, -3));
874 auto stack = self->
guard();
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) {
882 pushConvertedValue(squirrel, OT_NULL,
value);
883 const bool ok = SQ_SUCCEEDED(sq_arrayappend(squirrel, -2));
888 size_t index)
const {
891 auto stack = self->
guard();
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) {
900 sq_pushstring(squirrel,
"remove", -1);
901 if (SQ_FAILED(sq_get(squirrel, -2))) {
904 sq_push(squirrel, -2);
905 sq_pushinteger(squirrel,
static_cast<SQInteger
>(
index));
906 const bool ok = SQ_SUCCEEDED(sq_call(squirrel, 2, SQFalse, SQTrue));
911 const std::string&
name)
const {
912 std::vector<std::string> keys;
915 auto stack = self->
guard();
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) {
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)
932 std::sort(keys.begin(), keys.end());
937 const std::string&
name,
938 const std::string&
key)
const {
941 auto stack = self->
guard();
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) {
949 sq_pushstring(squirrel,
key.c_str(), -1);
950 if (SQ_FAILED(sq_get(squirrel, -2))) {
961 auto stack = self->
guard();
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) {
969 sq_pushstring(squirrel,
key.c_str(), -1);
970 SQObjectType currentType = OT_NULL;
971 if (SQ_SUCCEEDED(sq_get(squirrel, -2))) {
972 currentType = sq_gettype(squirrel, -1);
975 sq_pushstring(squirrel,
key.c_str(), -1);
976 pushConvertedValue(squirrel, currentType,
value);
977 bool ok = SQ_SUCCEEDED(sq_set(squirrel, -3));
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) {
988 sq_pushstring(squirrel,
key.c_str(), -1);
989 pushConvertedValue(squirrel, OT_NULL,
value);
990 ok = SQ_SUCCEEDED(sq_newslot(squirrel, -3, SQFalse));
996 const std::string&
key)
const {
999 auto stack = self->
guard();
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) {
1007 sq_pushstring(squirrel,
key.c_str(), -1);
1008 const bool ok = SQ_SUCCEEDED(sq_deleteslot(squirrel, -2, SQFalse));
1013 auto found = scripts_.find(
id);
1014 if (
found == scripts_.end())
1016 const bool fromFile =
found->second->from_file;
1017 const std::string
input =
found->second->source_text;
1024 auto found = scripts_.find(
id);
1025 if (
found == scripts_.end())
return false;
1029 auto stack =
guard();
1031 notifyLifecycle(record.
info);
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);
1044 classes_.erase(pair.first);
1045 class_owners_.erase(owner);
1046 class_identities_.erase(pair.first);
1051 notifyLifecycle(record.
info);
1053 }
catch (
const std::exception&
error) {
1056 notifyLifecycle(record.
info);
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());
1078 auto found = scripts_.find(
id);
1079 return found == scripts_.end() ? nullptr : &
found->second->info;
1083 std::vector<ScriptInfo> result;
1084 result.reserve(scripts_.size());
1085 for (
const auto& pair : scripts_) result.push_back(pair.second->info);
1094 return found == classes_.end() ? nullptr : &
found->second;
1098 std::vector<ReflectedClass> result;
1099 result.reserve(classes_.size());
1100 for (
const auto& pair : classes_) result.push_back(pair.second);
1102 return a.name < b.name;
1109 auto stack =
guard();
1110 const auto after = rootClasses();
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;
1116 ssq::Class
cls = vm_->findClass(pair.first.c_str());
1118 auto clsInfo = classes_.find(pair.first);
1119 if (clsInfo != classes_.end())
source = clsInfo->second.source;
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;
1131 for (
const auto& pair : after) {
1132 auto info = classes_.find(pair.first);
1133 if (
info == classes_.end())
continue;
1135 const ssq::Class
cls = vm_->findClass(pair.first.c_str());
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;
1158 const bool validName = !
name.empty();
1160 return vm_->findClass(
name.c_str());
1164 if (instance.getType() != ssq::Type::INSTANCE)
return {};
1167 auto stack = self->
guard();
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);
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;
1187 if (!lifecycle_handler_)
return;
1189 lifecycle_handler_(
info);
1194script::ScriptErrorContext Runtime::compileErrorContext(
const std::string& what,
1195 const std::string& sourceText) {
1196 script::ScriptErrorContext ctx;
1201 if (lineText.empty())
return ctx;
1202 std::string hint = std::to_string(ctx.line) +
" | " + lineText;
1203 if (ctx.column > 0) {
1205 const size_t caret =
static_cast<size_t>(ctx.column > 1 ? ctx.column - 1 : 0);
1206 hint.append(caret,
' ');
1209 ctx.hint = std::move(hint);
1213[[noreturn]]
void Runtime::fail(
ScriptStage stage,
const std::string&
source, ScriptId
id,
1214 script::ScriptErrorContext
context) {
1216 if (error_handler_) {
1218 error_handler_(wrapped);
1225[[noreturn]]
void Runtime::fail(
ScriptStage stage,
const std::string&
source, ScriptId
id,
1226 const std::exception&
error) {
1228 const char* what =
error.what();
1229 const std::string
message = what ? what :
"";
1233 (ctx.message !=
message &&
message.find(ctx.message) == std::string::npos &&
1234 ctx.message.find(
message) == std::string::npos)) {
1238 fail(stage,
source,
id, std::move(ctx));
1241std::unordered_map<std::string, SQUserPointer> Runtime::rootClasses()
const {
1242 std::unordered_map<std::string, SQUserPointer> result;
1243 StackGuard stack(*
const_cast<Runtime*
>(
this));
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;
1251 if (SQ_SUCCEEDED(sq_getstring(squirrel, -2, &
name)) &&
name &&
1252 SQ_SUCCEEDED(sq_getstackobj(squirrel, -1, &
object)))
1253 result[
name] = objectIdentity(
object);
1255 sq_pop(squirrel, 2);
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;
1275 std::sort(record.info.classes.begin(), record.info.classes.end());
1278 for (
const std::string&
name : record.
info.classes) {
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;
1299ReflectedClass Runtime::inspectClass(
const std::string&
name,
const ssq::Class&
cls,
1300 const std::string&
source)
const {
1301 ReflectedClass
info;
1304 info.members = collectClassMembers(
cls);
1308std::vector<ReflectedMember> Runtime::collectClassMembers(
const ssq::Class&
cls)
const {
1309 std::vector<ReflectedMember>
members;
1311 auto scope = self->enter();
1312 auto stack = self->guard();
1314 const SQInteger
top = sq_gettop(squirrel);
1315 sq_pushobject(squirrel,
cls.getRaw());
1316 members = collectClassMembersFromStack(squirrel, -1);
1317 sq_settop(squirrel,
top);
EVEngine assertion entry point, backed by zeroerr.
#define EV_PARAM_CHECK(cond,...)
Validate a function parameter / public API precondition.
std::vector< QuestEvent > pending
struct SQVM * HSQUIRRELVM
const SquirrelValueOptions & options
Explicit Value/Owned/Borrowed semantics at the Squirrel boundary.
float(ui::Theme::* member)[4]
std::map< std::string, std::span< const std::uint8_t > > members
const UnitySourceAsset & source
const VegetationPresetContext & context
static void detach(Runtime *runtime)
Clears the active runtime if it matches; called during shutdown.
static void expose(Runtime &runtime)
Installs the eve table and defers native class method bindings.
Pushes this Runtime on the current thread's runtime stack.
Scope(Runtime &runtime)
Scope.
Restores the native Squirrel stack when a binding operation leaves scope.
StackGuard & operator=(StackGuard &&other) noexcept
Operator =.
~StackGuard()
Stack guard.
StackGuard(Runtime &runtime) noexcept
Stack guard.
EVENGINE_API_FOUNDATION public API.
void shutdown() noexcept
Tears down the VM and detaches the runtime; idempotent and noexcept.
ssq::Class findClass(const std::string &name) const
Finds a script class by name (throws if it does not exist).
ScriptId runFile(const std::string &path)
Convenience: compileFile() then execute().
void unloadAll() noexcept
Unloads every script.
static Runtime * current() noexcept
The runtime currently at the top of this thread's runtime stack, or nullptr.
ssq::Table table(const char *name) const
Looks up a named global table.
bool contains(ScriptId id) const noexcept
True if a script with the given id is tracked.
ScriptId compileSource(std::string source, std::string sourceName="buffer")
Compiles script source without running it.
bool arrayRemove(const ssq::Object &instance, const std::string &name, size_t index) const
Removes one element of an array member (shifts the rest).
bool unload(ScriptId id)
Unloads a script and removes its declared classes; false if unknown/unloaded.
ScriptId reload(ScriptId id)
Recompiles and re-runs a script from its original source.
Runtime(size_t stackSize=2048, ssq::Libs::Flag libraries=ssq::Libs::ALL)
Creates a script runtime with its own Squirrel VM.
std::vector< ReflectedMember > reflectInstance(const ssq::Object &instance) const
Inspects a live instance: own + inherited members with current values.
std::vector< ReflectedClass > reflectedClasses() const
All reflected classes, sorted by name.
script::ScriptModuleResolver & scriptModules() noexcept
Script module provider registry and dependency resolver for this VM.
ssq::Object createInstance(const std::string &name, const std::string &source={})
Creates a live instance of a script class (default constructor).
const ReflectedClass * reflectedClass(const std::string &name) const noexcept
Reflected class by name, or nullptr.
ReflectedValue tableGet(const ssq::Object &instance, const std::string &name, const std::string &key) const
Value of a table member key; empty when missing.
bool writeProperty(const ssq::Object &instance, const std::string &name, const ReflectedValue &value) const
Writes one property of a live instance.
ssq::Object readObjectProperty(const ssq::Object &instance, const std::string &name) const
Reads a live object-valued property (nested instance).
static size_t stackDepth() noexcept
Depth of the thread-local runtime stack.
ScriptId runSource(std::string source, std::string sourceName="buffer")
Convenience: compileSource() then execute().
StackGuard guard() noexcept
RAII guard that restores the Squirrel stack top on scope exit.
bool initialized() const noexcept
True once initialize() has completed successfully.
bool tableRemove(const ssq::Object &instance, const std::string &name, const std::string &key) const
Removes a key from a table member.
size_t scanClasses()
Scans the root table for script classes and refreshes reflection.
script::ScriptCompiler & scriptCompiler() noexcept
Unified EveScript compiler, metadata store, and binding contracts.
ScriptId compileFile(const std::string &path)
Compiles a script file without running it.
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).
void initialize()
Exposes registered engine modules into the script root table. Safe to call more than once.
bool arrayAppend(const ssq::Object &instance, const std::string &name, const ReflectedValue &value) const
Appends an element to an array member.
const ReflectedClass & reflectClass(const std::string &name, const std::string &source={})
Reflects a class by name, storing its inspected members.
const ScriptInfo * script(ScriptId id) const noexcept
Script metadata for an id, or nullptr if unknown.
std::string classNameOf(const ssq::Object &instance) const
Name of the script class of a live instance ("" when unknown).
HSQUIRRELVM handle() const noexcept
Raw Squirrel VM handle; nullptr after shutdown.
ReflectedValue readProperty(const ssq::Object &instance, const std::string &name) const
Reads one property of a live instance.
size_t arraySize(const ssq::Object &instance, const std::string &name) const
Element count of an array member (0 when missing/not an array).
bool arraySet(const ssq::Object &instance, const std::string &name, size_t index, const ReflectedValue &value) const
Replaces one element of an array member.
ssq::Table root() const
Root script table of the VM.
Scope enter()
Pushes this runtime on the thread-local runtime stack (RAII pop).
ssq::VM & vm() noexcept
Underlying SimpleSquirrel VM (requires a live, initialized runtime).
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.
std::vector< ScriptInfo > scripts() const
Metadata for all tracked scripts, sorted by id.
std::vector< std::string > tableKeys(const ssq::Object &instance, const std::string &name) const
String keys of a table member.
const ScriptInfo & execute(ScriptId id)
Runs a previously compiled script; throws ScriptException on failure.
Exception raised at the public Runtime boundary.
ScriptStage stage() const noexcept
Stage.
const std::string & source() const noexcept
Source.
ScriptException(ScriptStage stage, std::string source, uint64_t scriptId, const std::string &message)
Script exception.
uint64_t scriptId() const noexcept
Script id.
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).
std::string reflectedFloatString(double value)
Shortest round-trip float formatting (portable).
ScriptStage
ScriptStage public API.
void exposeReflection(Runtime &runtime, ssq::Table eveTable)
Exposes the Runtime reflection API to scripts as eve.reflect.
EVENGINE_API_FOUNDATION_INLINE public API.
EVENGINE_API_FOUNDATION_INLINE public API.
EVENGINE_API_FOUNDATION public API.
bool attrBool(const std::string &name, bool def=false) const noexcept
Boolean attribute value; def when missing or not boolean.
float attrFloat(const std::string &name, float def=0.f) const noexcept
Numeric attribute value; def when missing or not numeric.
const ReflectedAttribute * findAttribute(const std::string &name) const noexcept
Looks up an attribute by name; nullptr when absent.
std::string attrString(const std::string &name, const std::string &def={}) const
Raw string attribute value; def when missing.
std::vector< std::string > attrOptions(const std::string &name) const
Comma-separated string attribute split into trimmed options.
Typed snapshot of one live instance property.
std::unique_ptr< ssq::Script > compiled
std::unordered_map< std::string, ssq::Class > class_objects
EVENGINE_API_FOUNDATION_INLINE public API.
Structured snapshot of a script error: message, throw site and stack.
bool empty() const noexcept
True when no error payload was captured.
std::string message
Raw error value ("kaboom", "42", ...).