载入中...
搜索中...
未找到
Snapshot.cpp
浏览该文件的文档.
2
3#include "common/Capability.h"
5
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>
11
12#include <squirrel.h>
13
14#include <algorithm>
15#include <cstdint>
16#include <fstream>
17#include <sstream>
18#include <typeinfo>
19#include <unordered_map>
20#include <unordered_set>
21#include <utility>
22
23namespace eve::dev {
24namespace {
25
26bool isEngineName(const std::string& name) {
27 static const std::unordered_set<std::string> kSkip = {
28 "eve",
29 "win",
30 "gfx",
31 "event",
32 "timer",
33 "system",
34 "math",
35 "tf",
36 "ui",
37 "scene",
38 "particles",
39 "map",
40 "gpgpu",
41 "physics",
42 "keyboard",
43 "mouse",
44 "touch",
45 "sound",
46 "audio",
47 "model3d",
48 "font",
49 "thread",
50 "fs",
51 "hot",
52 "config",
53 "require",
54 "path",
55 "exports",
56 "module",
57 "console",
58 "process",
59 "stdin",
60 "stdout",
61 "stderr",
62 "_version_",
63 "ARGV",
64 "eve_init",
65 "eve_update",
66 "eve_render",
67 "eve_quit",
68 "eve_reload",
69 "eve_asset_reload",
70 "watched_scripts",
71 "track_script",
72 "soft_reload_scripts",
73 "poll_hot_reload",
74 "file_exists",
75 "format_script_error",
76 "emit_loop_error",
77 "clear_loop_error",
78 "_loop_errors",
79 "path_endswith",
80 "normalize_path",
81 "async_pump",
82 "async_dispatch_event",
83 "Promise",
84 "setTimeout",
85 "clearTimeout",
86 "nextTick",
87 "setImmediate",
88 "has_dev",
89 "dev_poll",
90 "dev_should_update",
91 "dev_notify_frame_done",
92 "handle_dev_key",
93 };
94 return kSkip.count(name) > 0;
95}
96
97using SeenSet = std::unordered_set<const void*>;
98
99const void* objectId(HSQUIRRELVM vm, SQInteger idx) {
100 HSQOBJECT obj;
101 sq_resetobject(&obj);
102 sq_getstackobj(vm, idx, &obj);
103 return reinterpret_cast<const void*>(obj._unVal.pRefCounted);
104}
105
106const char* sqTypeName(SQObjectType t) {
107 switch (t) {
108 case OT_CLOSURE: return "function";
109 case OT_NATIVECLOSURE: return "native function";
110 case OT_CLASS: return "class";
111 case OT_INSTANCE: return "class instance";
112 case OT_USERDATA: return "userdata";
113 case OT_USERPOINTER: return "userpointer";
114 case OT_THREAD: return "thread";
115 case OT_GENERATOR: return "generator";
116 case OT_WEAKREF: return "weakref";
117 default: return "value";
118 }
119}
120
121StateValue sqToStateValue(HSQUIRRELVM vm, SQInteger idx, SeenSet& seen, int depth, std::string* firstError) {
122 if (depth > 32) return StateValue::null();
123 if (idx < 0) idx = sq_gettop(vm) + idx + 1;
124
125 const SQObjectType t = sq_gettype(vm, idx);
126 switch (t) {
127 case OT_NULL: return StateValue::null();
128 case OT_INTEGER: {
129 SQInteger v = 0;
130 sq_getinteger(vm, idx, &v);
131 return StateValue::integer(static_cast<int64_t>(v));
132 }
133 case OT_FLOAT: {
134 SQFloat v = 0;
135 sq_getfloat(vm, idx, &v);
136 return StateValue::number(static_cast<double>(v));
137 }
138 case OT_BOOL: {
139 SQBool v = SQFalse;
140 sq_getbool(vm, idx, &v);
141 return StateValue::boolean(v != SQFalse);
142 }
143 case OT_STRING: {
144 const SQChar* s = nullptr;
145 sq_getstring(vm, idx, &s);
146 return StateValue::string(s ? std::string(s) : std::string{});
147 }
148 case OT_ARRAY: {
149 const void* id = objectId(vm, idx);
150 if (id && !seen.insert(id).second) return StateValue::null(); // cycle -> null
151 StateValue arr = StateValue::array();
152 const SQInteger top = sq_gettop(vm);
153 const SQInteger size = sq_getsize(vm, idx);
154 for (SQInteger i = 0; i < size; ++i) {
155 sq_pushinteger(vm, i);
156 if (SQ_SUCCEEDED(sq_get(vm, idx))) {
157 arr.pushBack(sqToStateValue(vm, -1, seen, depth + 1, firstError));
158 sq_poptop(vm);
159 } else {
160 arr.pushBack(StateValue::null());
161 }
162 }
163 sq_settop(vm, top);
164 return arr;
165 }
166 case OT_TABLE:
167 case OT_INSTANCE: {
168 const void* id = objectId(vm, idx);
169 if (id && !seen.insert(id).second) return StateValue::null();
170 StateValue obj = StateValue::object();
171 const SQInteger top = sq_gettop(vm);
172 sq_pushnull(vm);
173 while (SQ_SUCCEEDED(sq_next(vm, idx))) {
174 if (sq_gettype(vm, -2) == OT_STRING) {
175 const SQChar* key = nullptr;
176 sq_getstring(vm, -2, &key);
177 if (key) {
178 const SQObjectType vt = sq_gettype(vm, -1);
179 if (vt == OT_CLOSURE || vt == OT_NATIVECLOSURE || vt == OT_CLASS || vt == OT_INSTANCE ||
180 vt == OT_USERDATA || vt == OT_USERPOINTER || vt == OT_THREAD || vt == OT_GENERATOR ||
181 vt == OT_WEAKREF) {
182 if (firstError && firstError->empty()) {
183 *firstError = std::string("'") + key + "' (" + sqTypeName(vt) + ")";
184 }
185 } else {
186 obj.set(std::string(key), sqToStateValue(vm, -1, seen, depth + 1, firstError));
187 }
188 }
189 }
190 sq_pop(vm, 2);
191 }
192 sq_settop(vm, top);
193 return obj;
194 }
195 default: return StateValue::null();
196 }
197}
198
199bool pushStateValue(HSQUIRRELVM vm, const StateValue& var, int depth) {
200 if (depth > 32) {
201 sq_pushnull(vm);
202 return true;
203 }
204 switch (var.kind()) {
205 case StateValue::Kind::Null: sq_pushnull(vm); return true;
206 case StateValue::Kind::Bool: sq_pushbool(vm, var.asBool() ? SQTrue : SQFalse); return true;
207 case StateValue::Kind::Int: sq_pushinteger(vm, static_cast<SQInteger>(var.asInt())); return true;
208 case StateValue::Kind::Float: sq_pushfloat(vm, static_cast<SQFloat>(var.asDouble())); return true;
210 const std::string& s = var.asString();
211 sq_pushstring(vm, s.c_str(), static_cast<SQInteger>(s.size()));
212 return true;
213 }
215 sq_newarray(vm, 0);
216 for (size_t i = 0; i < var.arraySize(); ++i) {
217 if (!pushStateValue(vm, var.at(i), depth + 1)) return false;
218 sq_arrayappend(vm, -2);
219 }
220 return true;
222 sq_newtable(vm);
223 for (const auto& key : var.keys()) {
224 sq_pushstring(vm, key.c_str(), static_cast<SQInteger>(key.size()));
225 if (!pushStateValue(vm, *var.find(key), depth + 1)) return false;
226 sq_newslot(vm, -3, SQFalse);
227 }
228 return true;
229 }
230 return false;
231}
232
233Poco::Dynamic::Var stateToVar(const StateValue& var) {
234 switch (var.kind()) {
235 case StateValue::Kind::Null: return Poco::Dynamic::Var();
236 case StateValue::Kind::Bool: return Poco::Dynamic::Var(var.asBool());
237 case StateValue::Kind::Int: return Poco::Dynamic::Var(static_cast<Poco::Int64>(var.asInt()));
238 case StateValue::Kind::Float: return Poco::Dynamic::Var(var.asDouble());
239 case StateValue::Kind::String: return Poco::Dynamic::Var(var.asString());
241 Poco::JSON::Array::Ptr arr(new Poco::JSON::Array());
242 for (size_t i = 0; i < var.arraySize(); ++i) arr->add(stateToVar(var.at(i)));
243 return Poco::Dynamic::Var(arr);
244 }
246 Poco::JSON::Object::Ptr obj(new Poco::JSON::Object());
247 for (const auto& key : var.keys()) obj->set(key, stateToVar(*var.find(key)));
248 return Poco::Dynamic::Var(obj);
249 }
250 }
251 return Poco::Dynamic::Var();
252}
253
254StateValue varToState(const Poco::Dynamic::Var& var) {
255 if (var.isEmpty()) return StateValue::null();
256 if (var.isBoolean()) return StateValue::boolean(var.convert<bool>());
257 if (var.isInteger()) return StateValue::integer(static_cast<int64_t>(var.convert<Poco::Int64>()));
258 if (var.isNumeric()) return StateValue::number(var.convert<double>());
259 if (var.isString()) return StateValue::string(var.convert<std::string>());
260
261 try {
262 if (var.type() == typeid(Poco::JSON::Array::Ptr)) {
263 StateValue arr = StateValue::array();
264 Poco::JSON::Array::Ptr a = var.extract<Poco::JSON::Array::Ptr>();
265 if (a)
266 for (size_t i = 0; i < a->size(); ++i)
267 arr.pushBack(varToState(a->get(static_cast<unsigned int>(i))));
268 return arr;
269 }
270 } catch (const Poco::BadCastException&) {
271 // fall through
272 }
273
274 try {
275 if (var.type() == typeid(Poco::JSON::Object::Ptr)) {
276 StateValue obj = StateValue::object();
277 Poco::JSON::Object::Ptr o = var.extract<Poco::JSON::Object::Ptr>();
278 if (o)
279 for (const auto& name : o->getNames()) obj.set(name, varToState(o->get(name)));
280 return obj;
281 }
282 } catch (const Poco::BadCastException&) {
283 // fall through
284 }
285 return StateValue::null();
286}
287
288} // namespace
289
291 static Snapshot inst;
292 return inst;
293}
294
295bool Snapshot::isEngineBinding(const std::string& name) { return isEngineName(name); }
296
297void Snapshot::markRoot(std::string name) {
298 if (name.empty()) return;
300 for (const auto& r : marked_) {
301 if (r == name) return;
302 }
303 marked_.push_back(std::move(name));
304}
305
306void Snapshot::unmarkRoot(const std::string& name) {
307 marked_.erase(std::remove(marked_.begin(), marked_.end(), name), marked_.end());
308}
309
311 if (name.empty()) return;
313 for (const auto& r : transient_) {
314 if (r == name) return;
315 }
316 transient_.push_back(std::move(name));
317}
318
319void Snapshot::unmarkTransientRoot(const std::string& name) {
320 transient_.erase(std::remove(transient_.begin(), transient_.end(), name), transient_.end());
321}
322
324 marked_.clear();
325 transient_.clear();
326}
327
328std::vector<std::string> Snapshot::roots() const { return marked_; }
329
330std::vector<std::string> Snapshot::transientRoots() const { return transient_; }
331
332void Snapshot::setRootPolicies(std::vector<std::string> persistent, std::vector<std::string> transient) {
333 marked_.clear();
334 transient_.clear();
335 for (auto& name : persistent) markRoot(std::move(name));
336 for (auto& name : transient) markTransientRoot(std::move(name));
337}
338
339std::vector<std::string> Snapshot::resolveRoots(HSQUIRRELVM vm) const {
340 if (!marked_.empty()) return marked_;
341 std::vector<std::string> out;
342 if (!vm) return out;
343
344 const auto isTransient = [this](const std::string& name) {
345 return std::find(transient_.begin(), transient_.end(), name) != transient_.end();
346 };
347
348 for (const char* pref : {"eve_state", "gameState", "state"}) {
349 if (isTransient(pref)) continue;
350 const SQInteger top = sq_gettop(vm);
351 sq_pushroottable(vm);
352 sq_pushstring(vm, pref, -1);
353 if (SQ_SUCCEEDED(sq_get(vm, -2))) {
354 const SQObjectType t = sq_gettype(vm, -1);
355 if (t == OT_TABLE || t == OT_INSTANCE || t == OT_ARRAY) out.emplace_back(pref);
356 }
357 sq_settop(vm, top);
358 }
359 if (!out.empty()) return out;
360
361 const SQInteger top = sq_gettop(vm);
362 sq_pushroottable(vm);
363 const SQInteger rootIdx = sq_gettop(vm);
364 sq_pushnull(vm);
365 while (SQ_SUCCEEDED(sq_next(vm, rootIdx))) {
366 if (sq_gettype(vm, -2) == OT_STRING) {
367 const SQChar* key = nullptr;
368 sq_getstring(vm, -2, &key);
369 if (key && !isEngineName(key) && !isTransient(key)) {
370 const SQObjectType vt = sq_gettype(vm, -1);
371 if (vt == OT_INTEGER || vt == OT_FLOAT || vt == OT_BOOL || vt == OT_STRING || vt == OT_TABLE ||
372 vt == OT_ARRAY) {
373 out.emplace_back(key);
374 }
375 }
376 }
377 sq_pop(vm, 2);
378 }
379 sq_settop(vm, top);
380 return out;
381}
382
383bool Snapshot::captureState(HSQUIRRELVM vm, StateValue& out, std::string* error) const {
384 if (!vm) {
385 if (error) *error = "no vm";
386 return false;
387 }
388 try {
389 auto rootNames = resolveRoots(vm);
391 for (const auto& name : rootNames) {
392 SeenSet seen;
393 const SQInteger top = sq_gettop(vm);
394 sq_pushroottable(vm);
395 sq_pushstring(vm, name.c_str(), -1);
396 if (SQ_SUCCEEDED(sq_get(vm, -2))) {
397 const SQObjectType rt = sq_gettype(vm, -1);
398 if (rt == OT_CLOSURE || rt == OT_NATIVECLOSURE || rt == OT_CLASS || rt == OT_INSTANCE ||
399 rt == OT_USERDATA || rt == OT_USERPOINTER || rt == OT_THREAD || rt == OT_GENERATOR ||
400 rt == OT_WEAKREF) {
401 sq_settop(vm, top);
402 if (error) {
403 *error = "root '" + name + "' is a " + sqTypeName(rt) + " (state roots must be plain data)";
404 }
405 return false;
406 }
407 std::string firstError;
408 roots.set(name, sqToStateValue(vm, -1, seen, 0, &firstError));
409 if (!firstError.empty()) {
410 sq_settop(vm, top);
411 if (error) {
412 *error = "root '" + name + "' contains non-serializable value: " + firstError;
413 }
414 return false;
415 }
416 }
417 sq_settop(vm, top);
418 }
419
421 eve::cap::forEach<eve::caps::IStateProvider>([&](eve::caps::IStateProvider* p) {
422 if (p->reloadPolicy() == eve::caps::StateReloadPolicy::Reset) return;
423 StateValue captured;
424 if (p->captureState(captured)) native.set(p->stateKind(), std::move(captured));
425 });
426
427 out = StateValue::object();
428 out.set("version", StateValue::integer(2));
429 out.set("roots", std::move(roots));
430 out.set("native", std::move(native));
431 return true;
432 } catch (const std::exception& e) {
433 if (error) *error = e.what();
434 return false;
435 }
436}
437
438std::string Snapshot::capture(HSQUIRRELVM vm, std::string* error) const {
440 if (!captureState(vm, state, error)) return {};
441 try {
442 Poco::JSON::Object::Ptr doc = stateToVar(state).extract<Poco::JSON::Object::Ptr>();
443 std::ostringstream oss;
444 doc->stringify(oss);
445 return oss.str();
446 } catch (const Poco::Exception& e) {
447 if (error) *error = e.displayText();
448 return {};
449 } catch (const std::exception& e) {
450 if (error) *error = e.what();
451 return {};
452 }
453}
454
455bool Snapshot::restoreState(HSQUIRRELVM vm, const StateValue& state, std::string* error) const {
456 if (!vm) {
457 if (error) *error = "no vm";
458 return false;
459 }
460 try {
461 const StateValue* roots = state.find("roots");
462 if (!roots || !roots->isObject()) {
463 if (error) *error = "invalid snapshot: missing roots";
464 return false;
465 }
466 for (const auto& name : roots->keys()) {
467 const SQInteger top = sq_gettop(vm);
468 sq_pushroottable(vm);
469 sq_pushstring(vm, name.c_str(), static_cast<SQInteger>(name.size()));
470 if (!pushStateValue(vm, *roots->find(name), 0)) {
471 sq_settop(vm, top);
472 if (error) *error = "failed to restore " + name;
473 return false;
474 }
475 sq_newslot(vm, -3, SQFalse);
476 sq_settop(vm, top);
477 }
478
479 const StateValue* native = state.find("native");
480 bool failed = false;
481 std::string nativeErr;
482 eve::cap::forEach<eve::caps::IStateProvider>([&](eve::caps::IStateProvider* p) {
483 if (p->reloadPolicy() == eve::caps::StateReloadPolicy::Reset) {
484 if (!p->resetToDefaults()) {
485 failed = true;
486 nativeErr += std::string(p->stateKind()) + ": reset failed; ";
487 }
488 return;
489 }
490 if (native && native->isObject()) {
491 const StateValue* v = native->find(p->stateKind());
492 if (!v) return; // provider not present in the snapshot
493 std::string perr;
494 if (!p->restoreState(*v, &perr)) {
495 failed = true;
496 nativeErr += std::string(p->stateKind()) + ": " + perr + "; ";
497 p->resetToDefaults();
498 }
499 }
500 });
501 if (failed) {
502 if (error) *error = nativeErr;
503 return false;
504 }
505 return true;
506 } catch (const std::exception& e) {
507 if (error) *error = e.what();
508 return false;
509 }
510}
511
512bool Snapshot::restore(HSQUIRRELVM vm, const std::string& json, std::string* error) const {
513 try {
514 Poco::JSON::Parser parser;
515 Poco::Dynamic::Var result = parser.parse(json);
516 return restoreState(vm, varToState(result), error);
517 } catch (const Poco::Exception& e) {
518 if (error) *error = e.displayText();
519 return false;
520 } catch (const std::exception& e) {
521 if (error) *error = e.what();
522 return false;
523 }
524}
525
526bool Snapshot::saveFile(HSQUIRRELVM vm, const std::string& path, std::string* error) const {
527 const std::string json = capture(vm, error);
528 if (json.empty()) return false;
529 std::ofstream ofs(path, std::ios::binary);
530 if (!ofs) {
531 if (error) *error = "cannot write " + path;
532 return false;
533 }
534 ofs << json;
535 return static_cast<bool>(ofs);
536}
537
538bool Snapshot::loadFile(HSQUIRRELVM vm, const std::string& path, std::string* error) const {
539 std::ifstream ifs(path, std::ios::binary);
540 if (!ifs) {
541 if (error) *error = "cannot read " + path;
542 return false;
543 }
544 std::ostringstream oss;
545 oss << ifs.rdbuf();
546 return restore(vm, oss.str(), error);
547}
548
549} // namespace eve::dev
SQInteger top
const std::string & s
struct SQVM * HSQUIRRELVM
glm::vec4 p[6]
HSQUIRRELVM vm
Definition ECS.cpp:20
std::uint32_t key
double r
float v
std::string name
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
int idx
std::string path
Definition PlayHost.cpp:110
float t
std::string string
float size
Definition TreeMesh.cpp:156
std::uint32_t depth
JSON-compatible state value tree used by state hot reload.
Definition StateValue.h:20
static StateValue object()
Empty object.
Definition StateValue.h:40
static StateValue boolean(bool v)
Boolean value.
static StateValue array()
Empty array.
Definition StateValue.h:38
static StateValue integer(int64_t v)
Integer value.
bool isObject() const
Definition StateValue.h:50
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.
Definition StateValue.h:28
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.
Definition Snapshot.hpp:29
bool captureState(HSQUIRRELVM vm, StateValue &out, std::string *error=nullptr) const
Capture script roots plus every Preserve IStateProvider.
Definition Snapshot.cpp:383
bool restoreState(HSQUIRRELVM vm, const StateValue &state, std::string *error=nullptr) const
Restore a captured state; v1-shaped values restore roots only.
Definition Snapshot.cpp:455
void unmarkTransientRoot(const std::string &name)
Remove an explicit transient-root declaration.
Definition Snapshot.cpp:319
std::string capture(HSQUIRRELVM vm, std::string *error=nullptr) const
Capture marked roots, or heuristic roots when none marked.
Definition Snapshot.cpp:438
std::vector< std::string > roots() const
Return the explicit persistent roots.
Definition Snapshot.cpp:328
std::vector< std::string > transientRoots() const
Return the explicit transient roots.
Definition Snapshot.cpp:330
void setRootPolicies(std::vector< std::string > persistent, std::vector< std::string > transient)
Atomically replace explicit persistent/transient root policy.
Definition Snapshot.cpp:332
static bool isEngineBinding(const std::string &name)
Built-in names never auto-captured (modules / boot bindings).
Definition Snapshot.cpp:295
void markTransientRoot(std::string name)
Mark a root for reconstruction from new script definitions.
Definition Snapshot.cpp:310
void markRoot(std::string name)
Mark a root as persistent, replacing a transient declaration.
Definition Snapshot.cpp:297
static Snapshot & instance()
Definition Snapshot.cpp:290
void unmarkRoot(const std::string &name)
Remove an explicit persistent-root declaration.
Definition Snapshot.cpp:306
void clearRoots()
Clear every explicit persistent and transient declaration.
Definition Snapshot.cpp:323