6#include <Poco/Dynamic/Var.h>
7#include <Poco/JSON/Array.h>
8#include <Poco/JSON/Object.h>
9#include <Poco/JSON/Parser.h>
10#include <Poco/JSON/Stringifier.h>
19#include <unordered_map>
20#include <unordered_set>
26bool isEngineName(
const std::string&
name) {
27 static const std::unordered_set<std::string> kSkip = {
72 "soft_reload_scripts",
78 "async_dispatch_event",
87 "dev_notify_frame_done",
90 return kSkip.count(
name) > 0;
93using SeenSet = std::unordered_set<const void*>;
98 sq_getstackobj(
vm,
idx, &obj);
99 return reinterpret_cast<const void*
>(obj._unVal.pRefCounted);
102const char* sqTypeName(SQObjectType t) {
104 case OT_CLOSURE:
return "function";
105 case OT_NATIVECLOSURE:
return "native function";
106 case OT_CLASS:
return "class";
107 case OT_INSTANCE:
return "class instance";
108 case OT_USERDATA:
return "userdata";
109 case OT_USERPOINTER:
return "userpointer";
110 case OT_THREAD:
return "thread";
111 case OT_GENERATOR:
return "generator";
112 case OT_WEAKREF:
return "weakref";
113 default:
return "value";
117StateValue sqToStateValue(
HSQUIRRELVM vm, SQInteger
idx, SeenSet& seen,
int depth, std::string* firstError) {
121 const SQObjectType t = sq_gettype(
vm,
idx);
126 sq_getinteger(
vm,
idx, &
v);
140 const SQChar*
s =
nullptr;
141 sq_getstring(
vm,
idx, &
s);
145 const void*
id = objectId(
vm,
idx);
148 const SQInteger top = sq_gettop(
vm);
149 const SQInteger size = sq_getsize(
vm,
idx);
150 for (SQInteger i = 0; i < size; ++i) {
151 sq_pushinteger(
vm, i);
152 if (SQ_SUCCEEDED(sq_get(
vm,
idx))) {
153 arr.pushBack(sqToStateValue(
vm, -1, seen,
depth + 1, firstError));
164 const void*
id = objectId(
vm,
idx);
167 const SQInteger top = sq_gettop(
vm);
169 while (SQ_SUCCEEDED(sq_next(
vm,
idx))) {
170 if (sq_gettype(
vm, -2) == OT_STRING) {
171 const SQChar* key =
nullptr;
172 sq_getstring(
vm, -2, &key);
174 const SQObjectType vt = sq_gettype(
vm, -1);
175 if (vt == OT_CLOSURE || vt == OT_NATIVECLOSURE || vt == OT_CLASS || vt == OT_INSTANCE ||
176 vt == OT_USERDATA || vt == OT_USERPOINTER || vt == OT_THREAD || vt == OT_GENERATOR ||
178 if (firstError && firstError->empty()) {
179 *firstError = std::string(
"'") + key +
"' (" + sqTypeName(vt) +
")";
182 obj.set(std::string(key), sqToStateValue(
vm, -1, seen,
depth + 1, firstError));
200 switch (var.kind()) {
206 const std::string&
s = var.asString();
207 sq_pushstring(
vm,
s.c_str(),
static_cast<SQInteger
>(
s.size()));
212 for (
size_t i = 0; i < var.arraySize(); ++i) {
213 if (!pushStateValue(
vm, var.at(i),
depth + 1))
return false;
214 sq_arrayappend(
vm, -2);
219 for (
const auto& key : var.keys()) {
220 sq_pushstring(
vm, key.c_str(),
static_cast<SQInteger
>(key.size()));
221 if (!pushStateValue(
vm, *var.find(key),
depth + 1))
return false;
222 sq_newslot(
vm, -3, SQFalse);
229Poco::Dynamic::Var stateToVar(
const StateValue& var) {
230 switch (var.kind()) {
237 Poco::JSON::Array::Ptr arr(
new Poco::JSON::Array());
238 for (
size_t i = 0; i < var.arraySize(); ++i) arr->add(stateToVar(var.at(i)));
239 return Poco::Dynamic::Var(arr);
242 Poco::JSON::Object::Ptr obj(
new Poco::JSON::Object());
243 for (
const auto& key : var.keys()) obj->set(key, stateToVar(*var.find(key)));
244 return Poco::Dynamic::Var(obj);
247 return Poco::Dynamic::Var();
250StateValue varToState(
const Poco::Dynamic::Var& var) {
253 if (var.isInteger())
return StateValue::integer(
static_cast<int64_t
>(var.convert<Poco::Int64>()));
258 if (var.type() ==
typeid(Poco::JSON::Array::Ptr)) {
260 Poco::JSON::Array::Ptr
a = var.extract<Poco::JSON::Array::Ptr>();
262 for (
size_t i = 0; i <
a->size(); ++i) arr.pushBack(varToState(
a->get(i)));
265 }
catch (
const Poco::BadCastException&) {
270 if (var.type() ==
typeid(Poco::JSON::Object::Ptr)) {
272 Poco::JSON::Object::Ptr o = var.extract<Poco::JSON::Object::Ptr>();
274 for (
const auto&
name : o->getNames()) obj.set(
name, varToState(o->get(
name)));
277 }
catch (
const Poco::BadCastException&) {
293 if (
name.empty())
return;
294 for (
const auto& r : marked_) {
295 if (r ==
name)
return;
297 marked_.push_back(std::move(
name));
301 marked_.erase(std::remove(marked_.begin(), marked_.end(),
name), marked_.end());
308std::vector<std::string> Snapshot::resolveRoots(
HSQUIRRELVM vm)
const {
309 if (!marked_.empty())
return marked_;
310 std::vector<std::string> out;
313 for (
const char* pref : {
"eve_state",
"gameState",
"state"}) {
314 const SQInteger top = sq_gettop(
vm);
315 sq_pushroottable(
vm);
316 sq_pushstring(
vm, pref, -1);
317 if (SQ_SUCCEEDED(sq_get(
vm, -2))) {
318 const SQObjectType t = sq_gettype(
vm, -1);
319 if (t == OT_TABLE || t == OT_INSTANCE || t == OT_ARRAY) out.emplace_back(pref);
323 if (!out.empty())
return out;
325 const SQInteger top = sq_gettop(
vm);
326 sq_pushroottable(
vm);
327 const SQInteger rootIdx = sq_gettop(
vm);
329 while (SQ_SUCCEEDED(sq_next(
vm, rootIdx))) {
330 if (sq_gettype(
vm, -2) == OT_STRING) {
331 const SQChar* key =
nullptr;
332 sq_getstring(
vm, -2, &key);
333 if (key && !isEngineName(key)) {
334 const SQObjectType vt = sq_gettype(
vm, -1);
335 if (vt == OT_INTEGER || vt == OT_FLOAT || vt == OT_BOOL || vt == OT_STRING || vt == OT_TABLE ||
336 vt == OT_ARRAY || vt == OT_INSTANCE) {
337 out.emplace_back(key);
353 auto rootNames = resolveRoots(
vm);
355 for (
const auto&
name : rootNames) {
357 const SQInteger top = sq_gettop(
vm);
358 sq_pushroottable(
vm);
359 sq_pushstring(
vm,
name.c_str(), -1);
360 if (SQ_SUCCEEDED(sq_get(
vm, -2))) {
361 const SQObjectType rt = sq_gettype(
vm, -1);
362 if (rt == OT_CLOSURE || rt == OT_NATIVECLOSURE || rt == OT_CLASS || rt == OT_INSTANCE ||
363 rt == OT_USERDATA || rt == OT_USERPOINTER || rt == OT_THREAD || rt == OT_GENERATOR ||
367 *
error =
"root '" +
name +
"' is a " + sqTypeName(rt) +
" (state roots must be plain data)";
371 std::string firstError;
372 roots.set(
name, sqToStateValue(
vm, -1, seen, 0, &firstError));
373 if (!firstError.empty()) {
376 *
error =
"root '" +
name +
"' contains non-serializable value: " + firstError;
387 if (
p->captureState(captured)) native.
set(
p->stateKind(), std::move(captured));
392 out.set(
"roots", std::move(
roots));
393 out.set(
"native", std::move(native));
395 }
catch (
const std::exception& e) {
405 Poco::JSON::Object::Ptr doc = stateToVar(
state).extract<Poco::JSON::Object::Ptr>();
406 std::ostringstream oss;
409 }
catch (
const Poco::Exception& e) {
412 }
catch (
const std::exception& e) {
426 if (
error) *
error =
"invalid snapshot: missing roots";
430 const SQInteger top = sq_gettop(
vm);
431 sq_pushroottable(
vm);
432 sq_pushstring(
vm,
name.c_str(),
static_cast<SQInteger
>(
name.size()));
438 sq_newslot(
vm, -3, SQFalse);
445 std::string nativeErr;
450 if (!
p->restoreState(*
v, &perr)) {
452 nativeErr += std::string(
p->stateKind()) +
": " + perr +
"; ";
453 p->resetToDefaults();
462 }
catch (
const std::exception& e) {
470 Poco::JSON::Parser parser;
471 Poco::Dynamic::Var result = parser.parse(json);
473 }
catch (
const Poco::Exception& e) {
476 }
catch (
const std::exception& e) {
484 if (json.empty())
return false;
485 std::ofstream ofs(path, std::ios::binary);
491 return static_cast<bool>(ofs);
495 std::ifstream ifs(path, std::ios::binary);
500 std::ostringstream oss;
struct SQVM * HSQUIRRELVM
JobSystemThreadPool::State * state
JSON-compatible state value tree used by state hot reload.
static StateValue object()
Empty object.
static StateValue boolean(bool v)
Boolean value.
static StateValue array()
Empty array.
static StateValue integer(int64_t v)
Integer value.
static StateValue number(double v)
Floating-point value.
const StateValue * find(const std::string &key) const
Look up key; nullptr when absent.
static StateValue null()
Null value.
static StateValue string(std::string v)
String value.
void set(const std::string &key, StateValue v)
Insert or replace key; only valid on objects.
Runtime-state serialization for state hot reload.
Script + native state snapshot for state hot reload.
bool captureState(HSQUIRRELVM vm, StateValue &out, std::string *error=nullptr) const
Capture script roots plus every registered IStateProvider.
bool restore(HSQUIRRELVM vm, const std::string &json, std::string *error=nullptr) const
bool restoreState(HSQUIRRELVM vm, const StateValue &state, std::string *error=nullptr) const
Restore a captured state; v1-shaped values restore roots only.
std::string capture(HSQUIRRELVM vm, std::string *error=nullptr) const
Capture marked roots, or heuristic roots when none marked.
bool loadFile(HSQUIRRELVM vm, const std::string &path, std::string *error=nullptr) const
std::vector< std::string > roots() const
static bool isEngineBinding(const std::string &name)
Built-in names never auto-captured (modules / boot bindings).
void markRoot(std::string name)
bool saveFile(HSQUIRRELVM vm, const std::string &path, std::string *error=nullptr) const
static Snapshot & instance()
void unmarkRoot(const std::string &name)