snapshot_data.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /**************************************************************************/
  2. /* snapshot_data.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "snapshot_data.h"
  31. #include "core/core_bind.h"
  32. #include "core/io/compression.h"
  33. #include "core/object/script_language.h"
  34. #include "scene/debugger/scene_debugger_object.h"
  35. #if defined(MODULE_GDSCRIPT_ENABLED) && defined(DEBUG_ENABLED)
  36. #include "modules/gdscript/gdscript.h"
  37. #endif
  38. SnapshotDataObject::SnapshotDataObject(SceneDebuggerObject &p_obj, GameStateSnapshot *p_snapshot, ResourceCache &resource_cache) :
  39. snapshot(p_snapshot) {
  40. remote_object_id = p_obj.id;
  41. type_name = p_obj.class_name;
  42. for (const SceneDebuggerObject::SceneDebuggerProperty &prop : p_obj.properties) {
  43. PropertyInfo pinfo = prop.first;
  44. Variant pvalue = prop.second;
  45. if (pinfo.type == Variant::OBJECT && pvalue.is_string()) {
  46. String path = pvalue;
  47. // If a resource is followed by a ::, it is a nested resource (like a sub_resource in a .tscn file).
  48. // To get a reference to it, first we load the parent resource (the .tscn, for example), then,
  49. // we load the child resource. The parent resource (dependency) should not be destroyed before the child
  50. // resource (pvalue) is loaded.
  51. if (path.is_resource_file()) {
  52. // Built-in resource.
  53. String base_path = path.get_slice("::", 0);
  54. if (!resource_cache.cache.has(base_path)) {
  55. if (ResourceLoader::exists(path)) {
  56. resource_cache.cache[base_path] = ResourceLoader::load(base_path);
  57. }
  58. resource_cache.misses++;
  59. } else {
  60. resource_cache.hits++;
  61. }
  62. }
  63. if (!resource_cache.cache.has(path)) {
  64. if (ResourceLoader::exists(path)) {
  65. resource_cache.cache[path] = ResourceLoader::load(path);
  66. }
  67. resource_cache.misses++;
  68. } else {
  69. resource_cache.hits++;
  70. }
  71. pvalue = resource_cache.cache[path];
  72. if (pinfo.hint_string == "Script") {
  73. if (get_script() != pvalue) {
  74. set_script(Ref<RefCounted>());
  75. Ref<Script> scr(pvalue);
  76. if (scr.is_valid()) {
  77. ScriptInstance *scr_instance = scr->placeholder_instance_create(this);
  78. if (scr_instance) {
  79. set_script_instance(scr_instance);
  80. }
  81. }
  82. }
  83. }
  84. }
  85. prop_list.push_back(pinfo);
  86. prop_values[pinfo.name] = pvalue;
  87. }
  88. }
  89. bool SnapshotDataObject::_get(const StringName &p_name, Variant &r_ret) const {
  90. String name = p_name;
  91. if (name.begins_with("Metadata/")) {
  92. name = name.replace_first("Metadata/", "metadata/");
  93. }
  94. if (!prop_values.has(name)) {
  95. return false;
  96. }
  97. r_ret = prop_values[p_name];
  98. return true;
  99. }
  100. void SnapshotDataObject::_get_property_list(List<PropertyInfo> *p_list) const {
  101. p_list->clear(); // Sorry, don't want any categories.
  102. for (const PropertyInfo &prop : prop_list) {
  103. if (prop.name == "script") {
  104. // Skip the script property, it's always added by the non-virtual method.
  105. continue;
  106. }
  107. p_list->push_back(prop);
  108. }
  109. }
  110. void SnapshotDataObject::_bind_methods() {
  111. ClassDB::bind_method(D_METHOD("_is_read_only"), &SnapshotDataObject::_is_read_only);
  112. }
  113. String SnapshotDataObject::get_node_path() {
  114. if (!is_node()) {
  115. return "";
  116. }
  117. SnapshotDataObject *current = this;
  118. String path;
  119. while (true) {
  120. String current_node_name = current->extra_debug_data["node_name"];
  121. if (current_node_name != "") {
  122. if (path != "") {
  123. path = current_node_name + "/" + path;
  124. } else {
  125. path = current_node_name;
  126. }
  127. }
  128. if (!current->extra_debug_data.has("node_parent")) {
  129. break;
  130. }
  131. current = snapshot->objects[current->extra_debug_data["node_parent"]];
  132. }
  133. return path;
  134. }
  135. String SnapshotDataObject::_get_script_name(Ref<Script> p_script) {
  136. #if defined(MODULE_GDSCRIPT_ENABLED) && defined(DEBUG_ENABLED)
  137. // GDScripts have more specific names than base scripts, so use those names if possible.
  138. return GDScript::debug_get_script_name(p_script);
  139. #else
  140. // Otherwise fallback to the base script's name.
  141. return p_script->get_global_name();
  142. #endif
  143. }
  144. String SnapshotDataObject::get_name() {
  145. String found_type_name = type_name;
  146. // Ideally, we will name it after the script attached to it.
  147. Ref<Script> maybe_script = get_script();
  148. if (maybe_script.is_valid()) {
  149. String full_name;
  150. while (maybe_script.is_valid()) {
  151. String global_name = _get_script_name(maybe_script);
  152. if (global_name != "") {
  153. if (full_name != "") {
  154. full_name = global_name + "/" + full_name;
  155. } else {
  156. full_name = global_name;
  157. }
  158. }
  159. maybe_script = maybe_script->get_base_script().ptr();
  160. }
  161. found_type_name = type_name + "/" + full_name;
  162. }
  163. return found_type_name + "_" + uitos(remote_object_id);
  164. }
  165. bool SnapshotDataObject::is_refcounted() {
  166. return is_class(RefCounted::get_class_static());
  167. }
  168. bool SnapshotDataObject::is_node() {
  169. return is_class(Node::get_class_static());
  170. }
  171. bool SnapshotDataObject::is_class(const String &p_base_class) {
  172. return ClassDB::is_parent_class(type_name, p_base_class);
  173. }
  174. HashSet<ObjectID> SnapshotDataObject::_unique_references(const HashMap<String, ObjectID> &p_refs) {
  175. HashSet<ObjectID> obj_set;
  176. for (const KeyValue<String, ObjectID> &pair : p_refs) {
  177. obj_set.insert(pair.value);
  178. }
  179. return obj_set;
  180. }
  181. HashSet<ObjectID> SnapshotDataObject::get_unique_outbound_refernces() {
  182. return _unique_references(outbound_references);
  183. }
  184. HashSet<ObjectID> SnapshotDataObject::get_unique_inbound_references() {
  185. return _unique_references(inbound_references);
  186. }
  187. void GameStateSnapshot::_get_outbound_references(Variant &p_var, HashMap<String, ObjectID> &r_ret_val, const String &p_current_path) {
  188. String path_divider = p_current_path.size() > 0 ? "/" : ""; // Make sure we don't start with a /.
  189. switch (p_var.get_type()) {
  190. case Variant::Type::INT:
  191. case Variant::Type::OBJECT: { // Means ObjectID.
  192. ObjectID as_id = ObjectID((uint64_t)p_var);
  193. if (!objects.has(as_id)) {
  194. return;
  195. }
  196. r_ret_val[p_current_path] = as_id;
  197. break;
  198. }
  199. case Variant::Type::DICTIONARY: {
  200. Dictionary dict = (Dictionary)p_var;
  201. LocalVector<Variant> keys = dict.get_key_list();
  202. for (Variant &k : keys) {
  203. // The dictionary key _could be_ an object. If it is, we name the key property with the same name as the value, but with _key appended to it.
  204. _get_outbound_references(k, r_ret_val, p_current_path + path_divider + (String)k + "_key");
  205. Variant v = dict.get(k, Variant());
  206. _get_outbound_references(v, r_ret_val, p_current_path + path_divider + (String)k);
  207. }
  208. break;
  209. }
  210. case Variant::Type::ARRAY: {
  211. Array arr = (Array)p_var;
  212. int i = 0;
  213. for (Variant &v : arr) {
  214. _get_outbound_references(v, r_ret_val, p_current_path + path_divider + itos(i));
  215. i++;
  216. }
  217. break;
  218. }
  219. default: {
  220. break;
  221. }
  222. }
  223. }
  224. void GameStateSnapshot::_get_rc_cycles(
  225. SnapshotDataObject *p_obj,
  226. SnapshotDataObject *p_source_obj,
  227. HashSet<SnapshotDataObject *> p_traversed_objs,
  228. LocalVector<String> &r_ret_val,
  229. const String &p_current_path) {
  230. // We're at the end of this branch and it was a cycle.
  231. if (p_obj == p_source_obj && p_current_path != "") {
  232. r_ret_val.push_back(p_current_path);
  233. return;
  234. }
  235. // Go through each of our children and try traversing them.
  236. for (const KeyValue<String, ObjectID> &next_child : p_obj->outbound_references) {
  237. SnapshotDataObject *next_obj = p_obj->snapshot->objects[next_child.value];
  238. String next_name = next_obj == p_source_obj ? "self" : next_obj->get_name();
  239. String current_name = p_obj == p_source_obj ? "self" : p_obj->get_name();
  240. String child_path = current_name + "[\"" + next_child.key + U"\"] → " + next_name;
  241. if (p_current_path != "") {
  242. child_path = p_current_path + "\n" + child_path;
  243. }
  244. SnapshotDataObject *next = objects[next_child.value];
  245. if (next != nullptr && next->is_class(RefCounted::get_class_static()) && !next->is_class(WeakRef::get_class_static()) && !p_traversed_objs.has(next)) {
  246. HashSet<SnapshotDataObject *> traversed_copy = p_traversed_objs;
  247. if (p_obj != p_source_obj) {
  248. traversed_copy.insert(p_obj);
  249. }
  250. _get_rc_cycles(next, p_source_obj, traversed_copy, r_ret_val, child_path);
  251. }
  252. }
  253. }
  254. void GameStateSnapshot::recompute_references() {
  255. for (const KeyValue<ObjectID, SnapshotDataObject *> &obj : objects) {
  256. Dictionary values;
  257. for (const KeyValue<StringName, Variant> &kv : obj.value->prop_values) {
  258. // Should only ever be one entry in this context.
  259. values[kv.key] = kv.value;
  260. }
  261. Variant values_variant(values);
  262. HashMap<String, ObjectID> refs;
  263. _get_outbound_references(values_variant, refs);
  264. obj.value->outbound_references = refs;
  265. for (const KeyValue<String, ObjectID> &kv : refs) {
  266. // Get the guy we are pointing to, and indicate the name of _our_ property that is pointing to them.
  267. if (objects.has(kv.value)) {
  268. objects[kv.value]->inbound_references[kv.key] = obj.key;
  269. }
  270. }
  271. }
  272. for (const KeyValue<ObjectID, SnapshotDataObject *> &obj : objects) {
  273. if (!obj.value->is_class(RefCounted::get_class_static()) || obj.value->is_class(WeakRef::get_class_static())) {
  274. continue;
  275. }
  276. HashSet<SnapshotDataObject *> traversed_objs;
  277. LocalVector<String> cycles;
  278. _get_rc_cycles(obj.value, obj.value, traversed_objs, cycles, "");
  279. Array cycles_array;
  280. for (const String &cycle : cycles) {
  281. cycles_array.push_back(cycle);
  282. }
  283. obj.value->extra_debug_data["ref_cycles"] = cycles_array;
  284. }
  285. }
  286. Ref<GameStateSnapshot> GameStateSnapshot::create_ref(const String &p_snapshot_name, const Vector<uint8_t> &p_snapshot_buffer) {
  287. Ref<GameStateSnapshot> snapshot;
  288. snapshot.instantiate();
  289. snapshot->name = p_snapshot_name;
  290. // Snapshots may have been created by an older version of the editor. Handle parsing old snapshot versions here based on the version number.
  291. Vector<uint8_t> snapshot_buffer_decompressed;
  292. int success = Compression::decompress_dynamic(&snapshot_buffer_decompressed, -1, p_snapshot_buffer.ptr(), p_snapshot_buffer.size(), Compression::MODE_DEFLATE);
  293. ERR_FAIL_COND_V_MSG(success != Z_OK, nullptr, "ObjectDB Snapshot could not be parsed. Failed to decompress snapshot.");
  294. CoreBind::Marshalls *m = CoreBind::Marshalls::get_singleton();
  295. Array snapshot_data = m->base64_to_variant(m->raw_to_base64(snapshot_buffer_decompressed));
  296. ERR_FAIL_COND_V_MSG(snapshot_data.is_empty(), nullptr, "ObjectDB Snapshot could not be parsed. Variant array is empty.");
  297. const Variant &first_item = snapshot_data[0];
  298. ERR_FAIL_COND_V_MSG(first_item.get_type() != Variant::DICTIONARY, nullptr, "ObjectDB Snapshot could not be parsed. First item is not a Dictionary.");
  299. snapshot->snapshot_context = first_item;
  300. SnapshotDataObject::ResourceCache resource_cache;
  301. for (int i = 1; i < snapshot_data.size(); i += 4) {
  302. SceneDebuggerObject obj;
  303. obj.deserialize(uint64_t(snapshot_data[i + 0]), snapshot_data[i + 1], snapshot_data[i + 2]);
  304. ERR_FAIL_COND_V_MSG(snapshot_data[i + 3].get_type() != Variant::DICTIONARY, nullptr, "ObjectDB Snapshot could not be parsed. Extra debug data is not a Dictionary.");
  305. if (obj.id.is_null()) {
  306. continue;
  307. }
  308. snapshot->objects[obj.id] = memnew(SnapshotDataObject(obj, snapshot.ptr(), resource_cache));
  309. snapshot->objects[obj.id]->extra_debug_data = (Dictionary)snapshot_data[i + 3];
  310. }
  311. snapshot->recompute_references();
  312. print_verbose("Resource cache hits: " + String::num(resource_cache.hits) + ". Resource cache misses: " + String::num(resource_cache.misses));
  313. return snapshot;
  314. }
  315. GameStateSnapshot::~GameStateSnapshot() {
  316. for (const KeyValue<ObjectID, SnapshotDataObject *> &item : objects) {
  317. memdelete(item.value);
  318. }
  319. }