载入中...
搜索中...
未找到
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 "path_endswith",
76 "normalize_path",
77 "async_pump",
78 "async_dispatch_event",
79 "Promise",
80 "setTimeout",
81 "clearTimeout",
82 "nextTick",
83 "setImmediate",
84 "has_dev",
85 "dev_poll",
86 "dev_should_update",
87 "dev_notify_frame_done",
88 "handle_dev_key",
89 };
90 return kSkip.count(name) > 0;
91}
92
93using SeenSet = std::unordered_set<const void*>;
94
95const void* objectId(HSQUIRRELVM vm, SQInteger idx) {
96 HSQOBJECT obj;
97 sq_resetobject(&obj);
98 sq_getstackobj(vm, idx, &obj);
99 return reinterpret_cast<const void*>(obj._unVal.pRefCounted);
100}
101
102const char* sqTypeName(SQObjectType t) {
103 switch (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";
114 }
115}
116
117StateValue sqToStateValue(HSQUIRRELVM vm, SQInteger idx, SeenSet& seen, int depth, std::string* firstError) {
118 if (depth > 32) return StateValue::null();
119 if (idx < 0) idx = sq_gettop(vm) + idx + 1;
120
121 const SQObjectType t = sq_gettype(vm, idx);
122 switch (t) {
123 case OT_NULL: return StateValue::null();
124 case OT_INTEGER: {
125 SQInteger v = 0;
126 sq_getinteger(vm, idx, &v);
127 return StateValue::integer(static_cast<int64_t>(v));
128 }
129 case OT_FLOAT: {
130 SQFloat v = 0;
131 sq_getfloat(vm, idx, &v);
132 return StateValue::number(static_cast<double>(v));
133 }
134 case OT_BOOL: {
135 SQBool v = SQFalse;
136 sq_getbool(vm, idx, &v);
137 return StateValue::boolean(v != SQFalse);
138 }
139 case OT_STRING: {
140 const SQChar* s = nullptr;
141 sq_getstring(vm, idx, &s);
142 return StateValue::string(s ? std::string(s) : std::string{});
143 }
144 case OT_ARRAY: {
145 const void* id = objectId(vm, idx);
146 if (id && !seen.insert(id).second) return StateValue::null(); // cycle -> null
147 StateValue arr = StateValue::array();
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));
154 sq_poptop(vm);
155 } else {
156 arr.pushBack(StateValue::null());
157 }
158 }
159 sq_settop(vm, top);
160 return arr;
161 }
162 case OT_TABLE:
163 case OT_INSTANCE: {
164 const void* id = objectId(vm, idx);
165 if (id && !seen.insert(id).second) return StateValue::null();
166 StateValue obj = StateValue::object();
167 const SQInteger top = sq_gettop(vm);
168 sq_pushnull(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);
173 if (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 ||
177 vt == OT_WEAKREF) {
178 if (firstError && firstError->empty()) {
179 *firstError = std::string("'") + key + "' (" + sqTypeName(vt) + ")";
180 }
181 } else {
182 obj.set(std::string(key), sqToStateValue(vm, -1, seen, depth + 1, firstError));
183 }
184 }
185 }
186 sq_pop(vm, 2);
187 }
188 sq_settop(vm, top);
189 return obj;
190 }
191 default: return StateValue::null();
192 }
193}
194
195bool pushStateValue(HSQUIRRELVM vm, const StateValue& var, int depth) {
196 if (depth > 32) {
197 sq_pushnull(vm);
198 return true;
199 }
200 switch (var.kind()) {
201 case StateValue::Kind::Null: sq_pushnull(vm); return true;
202 case StateValue::Kind::Bool: sq_pushbool(vm, var.asBool() ? SQTrue : SQFalse); return true;
203 case StateValue::Kind::Int: sq_pushinteger(vm, static_cast<SQInteger>(var.asInt())); return true;
204 case StateValue::Kind::Float: sq_pushfloat(vm, static_cast<SQFloat>(var.asDouble())); return true;
206 const std::string& s = var.asString();
207 sq_pushstring(vm, s.c_str(), static_cast<SQInteger>(s.size()));
208 return true;
209 }
211 sq_newarray(vm, 0);
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);
215 }
216 return true;
218 sq_newtable(vm);
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);
223 }
224 return true;
225 }
226 return false;
227}
228
229Poco::Dynamic::Var stateToVar(const StateValue& var) {
230 switch (var.kind()) {
231 case StateValue::Kind::Null: return Poco::Dynamic::Var();
232 case StateValue::Kind::Bool: return Poco::Dynamic::Var(var.asBool());
233 case StateValue::Kind::Int: return Poco::Dynamic::Var(static_cast<Poco::Int64>(var.asInt()));
234 case StateValue::Kind::Float: return Poco::Dynamic::Var(var.asDouble());
235 case StateValue::Kind::String: return Poco::Dynamic::Var(var.asString());
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);
240 }
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);
245 }
246 }
247 return Poco::Dynamic::Var();
248}
249
250StateValue varToState(const Poco::Dynamic::Var& var) {
251 if (var.isEmpty()) return StateValue::null();
252 if (var.isBoolean()) return StateValue::boolean(var.convert<bool>());
253 if (var.isInteger()) return StateValue::integer(static_cast<int64_t>(var.convert<Poco::Int64>()));
254 if (var.isNumeric()) return StateValue::number(var.convert<double>());
255 if (var.isString()) return StateValue::string(var.convert<std::string>());
256
257 try {
258 if (var.type() == typeid(Poco::JSON::Array::Ptr)) {
259 StateValue arr = StateValue::array();
260 Poco::JSON::Array::Ptr a = var.extract<Poco::JSON::Array::Ptr>();
261 if (a)
262 for (size_t i = 0; i < a->size(); ++i) arr.pushBack(varToState(a->get(i)));
263 return arr;
264 }
265 } catch (const Poco::BadCastException&) {
266 // fall through
267 }
268
269 try {
270 if (var.type() == typeid(Poco::JSON::Object::Ptr)) {
271 StateValue obj = StateValue::object();
272 Poco::JSON::Object::Ptr o = var.extract<Poco::JSON::Object::Ptr>();
273 if (o)
274 for (const auto& name : o->getNames()) obj.set(name, varToState(o->get(name)));
275 return obj;
276 }
277 } catch (const Poco::BadCastException&) {
278 // fall through
279 }
280 return StateValue::null();
281}
282
283} // namespace
284
286 static Snapshot inst;
287 return inst;
288}
289
290bool Snapshot::isEngineBinding(const std::string& name) { return isEngineName(name); }
291
292void Snapshot::markRoot(std::string name) {
293 if (name.empty()) return;
294 for (const auto& r : marked_) {
295 if (r == name) return;
296 }
297 marked_.push_back(std::move(name));
298}
299
300void Snapshot::unmarkRoot(const std::string& name) {
301 marked_.erase(std::remove(marked_.begin(), marked_.end(), name), marked_.end());
302}
303
304void Snapshot::clearRoots() { marked_.clear(); }
305
306std::vector<std::string> Snapshot::roots() const { return marked_; }
307
308std::vector<std::string> Snapshot::resolveRoots(HSQUIRRELVM vm) const {
309 if (!marked_.empty()) return marked_;
310 std::vector<std::string> out;
311 if (!vm) return out;
312
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);
320 }
321 sq_settop(vm, top);
322 }
323 if (!out.empty()) return out;
324
325 const SQInteger top = sq_gettop(vm);
326 sq_pushroottable(vm);
327 const SQInteger rootIdx = sq_gettop(vm);
328 sq_pushnull(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);
338 }
339 }
340 }
341 sq_pop(vm, 2);
342 }
343 sq_settop(vm, top);
344 return out;
345}
346
347bool Snapshot::captureState(HSQUIRRELVM vm, StateValue& out, std::string* error) const {
348 if (!vm) {
349 if (error) *error = "no vm";
350 return false;
351 }
352 try {
353 auto rootNames = resolveRoots(vm);
355 for (const auto& name : rootNames) {
356 SeenSet seen;
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 ||
364 rt == OT_WEAKREF) {
365 sq_settop(vm, top);
366 if (error) {
367 *error = "root '" + name + "' is a " + sqTypeName(rt) + " (state roots must be plain data)";
368 }
369 return false;
370 }
371 std::string firstError;
372 roots.set(name, sqToStateValue(vm, -1, seen, 0, &firstError));
373 if (!firstError.empty()) {
374 sq_settop(vm, top);
375 if (error) {
376 *error = "root '" + name + "' contains non-serializable value: " + firstError;
377 }
378 return false;
379 }
380 }
381 sq_settop(vm, top);
382 }
383
386 StateValue captured;
387 if (p->captureState(captured)) native.set(p->stateKind(), std::move(captured));
388 });
389
390 out = StateValue::object();
391 out.set("version", StateValue::integer(2));
392 out.set("roots", std::move(roots));
393 out.set("native", std::move(native));
394 return true;
395 } catch (const std::exception& e) {
396 if (error) *error = e.what();
397 return false;
398 }
399}
400
401std::string Snapshot::capture(HSQUIRRELVM vm, std::string* error) const {
403 if (!captureState(vm, state, error)) return {};
404 try {
405 Poco::JSON::Object::Ptr doc = stateToVar(state).extract<Poco::JSON::Object::Ptr>();
406 std::ostringstream oss;
407 doc->stringify(oss);
408 return oss.str();
409 } catch (const Poco::Exception& e) {
410 if (error) *error = e.displayText();
411 return {};
412 } catch (const std::exception& e) {
413 if (error) *error = e.what();
414 return {};
415 }
416}
417
418bool Snapshot::restoreState(HSQUIRRELVM vm, const StateValue& state, std::string* error) const {
419 if (!vm) {
420 if (error) *error = "no vm";
421 return false;
422 }
423 try {
424 const StateValue* roots = state.find("roots");
425 if (!roots || !roots->isObject()) {
426 if (error) *error = "invalid snapshot: missing roots";
427 return false;
428 }
429 for (const auto& name : roots->keys()) {
430 const SQInteger top = sq_gettop(vm);
431 sq_pushroottable(vm);
432 sq_pushstring(vm, name.c_str(), static_cast<SQInteger>(name.size()));
433 if (!pushStateValue(vm, *roots->find(name), 0)) {
434 sq_settop(vm, top);
435 if (error) *error = "failed to restore " + name;
436 return false;
437 }
438 sq_newslot(vm, -3, SQFalse);
439 sq_settop(vm, top);
440 }
441
442 const StateValue* native = state.find("native");
443 if (native && native->isObject()) {
444 bool failed = false;
445 std::string nativeErr;
447 const StateValue* v = native->find(p->stateKind());
448 if (!v) return; // provider not present in the snapshot
449 std::string perr;
450 if (!p->restoreState(*v, &perr)) {
451 failed = true;
452 nativeErr += std::string(p->stateKind()) + ": " + perr + "; ";
453 p->resetToDefaults();
454 }
455 });
456 if (failed) {
457 if (error) *error = nativeErr;
458 return false;
459 }
460 }
461 return true;
462 } catch (const std::exception& e) {
463 if (error) *error = e.what();
464 return false;
465 }
466}
467
468bool Snapshot::restore(HSQUIRRELVM vm, const std::string& json, std::string* error) const {
469 try {
470 Poco::JSON::Parser parser;
471 Poco::Dynamic::Var result = parser.parse(json);
472 return restoreState(vm, varToState(result), error);
473 } catch (const Poco::Exception& e) {
474 if (error) *error = e.displayText();
475 return false;
476 } catch (const std::exception& e) {
477 if (error) *error = e.what();
478 return false;
479 }
480}
481
482bool Snapshot::saveFile(HSQUIRRELVM vm, const std::string& path, std::string* error) const {
483 const std::string json = capture(vm, error);
484 if (json.empty()) return false;
485 std::ofstream ofs(path, std::ios::binary);
486 if (!ofs) {
487 if (error) *error = "cannot write " + path;
488 return false;
489 }
490 ofs << json;
491 return static_cast<bool>(ofs);
492}
493
494bool Snapshot::loadFile(HSQUIRRELVM vm, const std::string& path, std::string* error) const {
495 std::ifstream ifs(path, std::ios::binary);
496 if (!ifs) {
497 if (error) *error = "cannot read " + path;
498 return false;
499 }
500 std::ostringstream oss;
501 oss << ifs.rdbuf();
502 return restore(vm, oss.str(), error);
503}
504
505} // namespace eve::dev
struct SQVM * HSQUIRRELVM
HSQUIRRELVM vm
Definition ECS.cpp:20
std::string error
JobSystemThreadPool::State * state
float depth
uint32_t a
int idx
glm::vec4 p[6]
const char * name
Definition RockMesh.cpp:21
int v
uint32_t s
Definition Weather.cpp:28
JSON-compatible state value tree used by state hot reload.
Definition StateValue.h:20
static StateValue object()
Empty object.
Definition StateValue.h:39
static StateValue boolean(bool v)
Boolean value.
static StateValue array()
Empty array.
Definition StateValue.h:37
static StateValue integer(int64_t v)
Integer value.
bool isObject() const
Definition StateValue.h:49
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:27
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:28
bool captureState(HSQUIRRELVM vm, StateValue &out, std::string *error=nullptr) const
Capture script roots plus every registered IStateProvider.
Definition Snapshot.cpp:347
bool restore(HSQUIRRELVM vm, const std::string &json, std::string *error=nullptr) const
Definition Snapshot.cpp:468
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:418
std::string capture(HSQUIRRELVM vm, std::string *error=nullptr) const
Capture marked roots, or heuristic roots when none marked.
Definition Snapshot.cpp:401
bool loadFile(HSQUIRRELVM vm, const std::string &path, std::string *error=nullptr) const
Definition Snapshot.cpp:494
std::vector< std::string > roots() const
Definition Snapshot.cpp:306
static bool isEngineBinding(const std::string &name)
Built-in names never auto-captured (modules / boot bindings).
Definition Snapshot.cpp:290
void markRoot(std::string name)
Definition Snapshot.cpp:292
bool saveFile(HSQUIRRELVM vm, const std::string &path, std::string *error=nullptr) const
Definition Snapshot.cpp:482
static Snapshot & instance()
Definition Snapshot.cpp:285
void unmarkRoot(const std::string &name)
Definition Snapshot.cpp:300
I * query()
Definition Capability.h:77