JsonSerializer.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. using System;
  2. using System.IO;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using Pathfinding.Util;
  6. using Pathfinding.WindowsStore;
  7. #if ASTAR_NO_ZIP
  8. using Pathfinding.Serialization.Zip;
  9. #elif NETFX_CORE
  10. // For Universal Windows Platform
  11. using ZipEntry = System.IO.Compression.ZipArchiveEntry;
  12. using ZipFile = System.IO.Compression.ZipArchive;
  13. #else
  14. using Pathfinding.Ionic.Zip;
  15. #endif
  16. namespace Pathfinding.Serialization {
  17. /// <summary>Holds information passed to custom graph serializers</summary>
  18. public class GraphSerializationContext {
  19. private readonly GraphNode[] id2NodeMapping;
  20. /// <summary>
  21. /// Deserialization stream.
  22. /// Will only be set when deserializing
  23. /// </summary>
  24. public readonly BinaryReader reader;
  25. /// <summary>
  26. /// Serialization stream.
  27. /// Will only be set when serializing
  28. /// </summary>
  29. public readonly BinaryWriter writer;
  30. /// <summary>
  31. /// Index of the graph which is currently being processed.
  32. /// Version: uint instead of int after 3.7.5
  33. /// </summary>
  34. public readonly uint graphIndex;
  35. /// <summary>Metadata about graphs being deserialized</summary>
  36. public readonly GraphMeta meta;
  37. public GraphSerializationContext (BinaryReader reader, GraphNode[] id2NodeMapping, uint graphIndex, GraphMeta meta) {
  38. this.reader = reader;
  39. this.id2NodeMapping = id2NodeMapping;
  40. this.graphIndex = graphIndex;
  41. this.meta = meta;
  42. }
  43. public GraphSerializationContext (BinaryWriter writer) {
  44. this.writer = writer;
  45. }
  46. public void SerializeNodeReference (GraphNode node) {
  47. writer.Write(node == null ? -1 : node.NodeIndex);
  48. }
  49. public GraphNode DeserializeNodeReference () {
  50. var id = reader.ReadInt32();
  51. if (id2NodeMapping == null) throw new Exception("Calling DeserializeNodeReference when not deserializing node references");
  52. if (id == -1) return null;
  53. GraphNode node = id2NodeMapping[id];
  54. if (node == null) throw new Exception("Invalid id ("+id+")");
  55. return node;
  56. }
  57. /// <summary>Write a Vector3</summary>
  58. public void SerializeVector3 (Vector3 v) {
  59. writer.Write(v.x);
  60. writer.Write(v.y);
  61. writer.Write(v.z);
  62. }
  63. /// <summary>Read a Vector3</summary>
  64. public Vector3 DeserializeVector3 () {
  65. return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
  66. }
  67. /// <summary>Write an Int3</summary>
  68. public void SerializeInt3 (Int3 v) {
  69. writer.Write(v.x);
  70. writer.Write(v.y);
  71. writer.Write(v.z);
  72. }
  73. /// <summary>Read an Int3</summary>
  74. public Int3 DeserializeInt3 () {
  75. return new Int3(reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32());
  76. }
  77. public int DeserializeInt (int defaultValue) {
  78. if (reader.BaseStream.Position <= reader.BaseStream.Length-4) {
  79. return reader.ReadInt32();
  80. } else {
  81. return defaultValue;
  82. }
  83. }
  84. public float DeserializeFloat (float defaultValue) {
  85. if (reader.BaseStream.Position <= reader.BaseStream.Length-4) {
  86. return reader.ReadSingle();
  87. } else {
  88. return defaultValue;
  89. }
  90. }
  91. /// <summary>Read a UnityEngine.Object</summary>
  92. public UnityEngine.Object DeserializeUnityObject ( ) {
  93. int inst = reader.ReadInt32();
  94. if (inst == int.MaxValue) {
  95. return null;
  96. }
  97. string name = reader.ReadString();
  98. string typename = reader.ReadString();
  99. string guid = reader.ReadString();
  100. System.Type type = System.Type.GetType(typename);
  101. if (type == null) {
  102. Debug.LogError("Could not find type '"+typename+"'. Cannot deserialize Unity reference");
  103. return null;
  104. }
  105. if (!string.IsNullOrEmpty(guid)) {
  106. UnityReferenceHelper[] helpers = UnityEngine.Object.FindObjectsOfType(typeof(UnityReferenceHelper)) as UnityReferenceHelper[];
  107. for (int i = 0; i < helpers.Length; i++) {
  108. if (helpers[i].GetGUID() == guid) {
  109. if (type == typeof(GameObject)) {
  110. return helpers[i].gameObject;
  111. } else {
  112. return helpers[i].GetComponent(type);
  113. }
  114. }
  115. }
  116. }
  117. //Try to load from resources
  118. UnityEngine.Object[] objs = Resources.LoadAll(name, type);
  119. for (int i = 0; i < objs.Length; i++) {
  120. if (objs[i].name == name || objs.Length == 1) {
  121. return objs[i];
  122. }
  123. }
  124. return null;
  125. }
  126. }
  127. /// <summary>
  128. /// Handles low level serialization and deserialization of graph settings and data.
  129. /// Mostly for internal use. You can use the methods in the AstarData class for
  130. /// higher level serialization and deserialization.
  131. ///
  132. /// See: AstarData
  133. /// </summary>
  134. public class AstarSerializer {
  135. private AstarData data;
  136. /// <summary>Zip which the data is loaded from</summary>
  137. private ZipFile zip;
  138. /// <summary>Memory stream with the zip data</summary>
  139. private MemoryStream zipStream;
  140. /// <summary>Graph metadata</summary>
  141. private GraphMeta meta;
  142. /// <summary>Settings for serialization</summary>
  143. private SerializeSettings settings;
  144. /// <summary>
  145. /// Root GameObject used for deserialization.
  146. /// This should be the GameObject which holds the AstarPath component.
  147. /// Important when deserializing when the component is on a prefab.
  148. /// </summary>
  149. private GameObject contextRoot;
  150. /// <summary>Graphs that are being serialized or deserialized</summary>
  151. private NavGraph[] graphs;
  152. /// <summary>
  153. /// Index used for the graph in the file.
  154. /// If some graphs were null in the file then graphIndexInZip[graphs[i]] may not equal i.
  155. /// Used for deserialization.
  156. /// </summary>
  157. private Dictionary<NavGraph, int> graphIndexInZip;
  158. private int graphIndexOffset;
  159. /// <summary>Extension to use for binary files</summary>
  160. const string binaryExt = ".binary";
  161. /// <summary>Extension to use for json files</summary>
  162. const string jsonExt = ".json";
  163. /// <summary>
  164. /// Checksum for the serialized data.
  165. /// Used to provide a quick equality check in editor code
  166. /// </summary>
  167. private uint checksum = 0xffffffff;
  168. System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
  169. /// <summary>Cached StringBuilder to avoid excessive allocations</summary>
  170. static System.Text.StringBuilder _stringBuilder = new System.Text.StringBuilder();
  171. /// <summary>
  172. /// Returns a cached StringBuilder.
  173. /// This function only has one string builder cached and should
  174. /// thus only be called from a single thread and should not be called while using an earlier got string builder.
  175. /// </summary>
  176. static System.Text.StringBuilder GetStringBuilder () { _stringBuilder.Length = 0; return _stringBuilder; }
  177. /// <summary>Cached version object for 3.8.3</summary>
  178. public static readonly System.Version V3_8_3 = new System.Version(3, 8, 3);
  179. /// <summary>Cached version object for 3.9.0</summary>
  180. public static readonly System.Version V3_9_0 = new System.Version(3, 9, 0);
  181. /// <summary>Cached version object for 4.1.0</summary>
  182. public static readonly System.Version V4_1_0 = new System.Version(4, 1, 0);
  183. public AstarSerializer (AstarData data, GameObject contextRoot) : this(data, SerializeSettings.Settings, contextRoot) {
  184. }
  185. public AstarSerializer (AstarData data, SerializeSettings settings, GameObject contextRoot) {
  186. this.data = data;
  187. this.contextRoot = contextRoot;
  188. this.settings = settings;
  189. }
  190. public void SetGraphIndexOffset (int offset) {
  191. graphIndexOffset = offset;
  192. }
  193. void AddChecksum (byte[] bytes) {
  194. checksum = Checksum.GetChecksum(bytes, checksum);
  195. }
  196. void AddEntry (string name, byte[] bytes) {
  197. #if NETFX_CORE
  198. var entry = zip.CreateEntry(name);
  199. using (var stream = entry.Open()) {
  200. stream.Write(bytes, 0, bytes.Length);
  201. }
  202. #else
  203. zip.AddEntry(name, bytes);
  204. #endif
  205. }
  206. public uint GetChecksum () { return checksum; }
  207. #region Serialize
  208. public void OpenSerialize () {
  209. // Create a new zip file, here we will store all the data
  210. zipStream = new MemoryStream();
  211. #if NETFX_CORE
  212. zip = new ZipFile(zipStream, System.IO.Compression.ZipArchiveMode.Create);
  213. #else
  214. zip = new ZipFile();
  215. zip.AlternateEncoding = System.Text.Encoding.UTF8;
  216. zip.AlternateEncodingUsage = ZipOption.Always;
  217. // Don't use parallel defate
  218. zip.ParallelDeflateThreshold = -1;
  219. #endif
  220. meta = new GraphMeta();
  221. }
  222. public byte[] CloseSerialize () {
  223. // As the last step, serialize metadata
  224. byte[] bytes = SerializeMeta();
  225. AddChecksum(bytes);
  226. AddEntry("meta"+jsonExt, bytes);
  227. #if !ASTAR_NO_ZIP && !NETFX_CORE
  228. // Set dummy dates on every file to prevent the binary data to change
  229. // for identical settings and graphs.
  230. // Prevents the scene from being marked as dirty in the editor
  231. // If ASTAR_NO_ZIP is defined this is not relevant since the replacement zip
  232. // implementation does not even store dates
  233. var dummy = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  234. foreach (var entry in zip.Entries) {
  235. entry.AccessedTime = dummy;
  236. entry.CreationTime = dummy;
  237. entry.LastModified = dummy;
  238. entry.ModifiedTime = dummy;
  239. }
  240. #endif
  241. // Save all entries to a single byte array
  242. #if !NETFX_CORE
  243. zip.Save(zipStream);
  244. #endif
  245. zip.Dispose();
  246. bytes = zipStream.ToArray();
  247. zip = null;
  248. zipStream = null;
  249. return bytes;
  250. }
  251. public void SerializeGraphs (NavGraph[] _graphs) {
  252. if (graphs != null) throw new InvalidOperationException("Cannot serialize graphs multiple times.");
  253. graphs = _graphs;
  254. if (zip == null) throw new NullReferenceException("You must not call CloseSerialize before a call to this function");
  255. if (graphs == null) graphs = new NavGraph[0];
  256. for (int i = 0; i < graphs.Length; i++) {
  257. //Ignore graph if null
  258. if (graphs[i] == null) continue;
  259. // Serialize the graph to a byte array
  260. byte[] bytes = Serialize(graphs[i]);
  261. AddChecksum(bytes);
  262. AddEntry("graph"+i+jsonExt, bytes);
  263. }
  264. }
  265. /// <summary>Serialize metadata about all graphs</summary>
  266. byte[] SerializeMeta () {
  267. if (graphs == null) throw new System.Exception("No call to SerializeGraphs has been done");
  268. meta.version = AstarPath.Version;
  269. meta.graphs = graphs.Length;
  270. meta.guids = new List<string>();
  271. meta.typeNames = new List<string>();
  272. // For each graph, save the guid
  273. // of the graph and the type of it
  274. for (int i = 0; i < graphs.Length; i++) {
  275. if (graphs[i] != null) {
  276. meta.guids.Add(graphs[i].guid.ToString());
  277. meta.typeNames.Add(graphs[i].GetType().FullName);
  278. } else {
  279. meta.guids.Add(null);
  280. meta.typeNames.Add(null);
  281. }
  282. }
  283. // Grab a cached string builder to avoid allocations
  284. var output = GetStringBuilder();
  285. TinyJsonSerializer.Serialize(meta, output);
  286. return encoding.GetBytes(output.ToString());
  287. }
  288. /// <summary>Serializes the graph settings to JSON and returns the data</summary>
  289. public byte[] Serialize (NavGraph graph) {
  290. // Grab a cached string builder to avoid allocations
  291. var output = GetStringBuilder();
  292. TinyJsonSerializer.Serialize(graph, output);
  293. return encoding.GetBytes(output.ToString());
  294. }
  295. /// <summary>
  296. /// Deprecated method to serialize node data.
  297. /// Deprecated: Not used anymore
  298. /// </summary>
  299. [System.Obsolete("Not used anymore. You can safely remove the call to this function.")]
  300. public void SerializeNodes () {
  301. }
  302. static int GetMaxNodeIndexInAllGraphs (NavGraph[] graphs) {
  303. int maxIndex = 0;
  304. for (int i = 0; i < graphs.Length; i++) {
  305. if (graphs[i] == null) continue;
  306. graphs[i].GetNodes(node => {
  307. maxIndex = Math.Max(node.NodeIndex, maxIndex);
  308. if (node.NodeIndex == -1) {
  309. Debug.LogError("Graph contains destroyed nodes. This is a bug.");
  310. }
  311. });
  312. }
  313. return maxIndex;
  314. }
  315. static byte[] SerializeNodeIndices (NavGraph[] graphs) {
  316. var stream = new MemoryStream();
  317. var writer = new BinaryWriter(stream);
  318. int maxNodeIndex = GetMaxNodeIndexInAllGraphs(graphs);
  319. writer.Write(maxNodeIndex);
  320. // While writing node indices, verify that the max node index is the same
  321. // (user written graphs might have gotten it wrong)
  322. int maxNodeIndex2 = 0;
  323. for (int i = 0; i < graphs.Length; i++) {
  324. if (graphs[i] == null) continue;
  325. graphs[i].GetNodes(node => {
  326. maxNodeIndex2 = Math.Max(node.NodeIndex, maxNodeIndex2);
  327. writer.Write(node.NodeIndex);
  328. });
  329. }
  330. // Nice to verify if users are writing their own graph types
  331. if (maxNodeIndex2 != maxNodeIndex) throw new Exception("Some graphs are not consistent in their GetNodes calls, sequential calls give different results.");
  332. byte[] bytes = stream.ToArray();
  333. writer.Close();
  334. return bytes;
  335. }
  336. /// <summary>Serializes info returned by NavGraph.SerializeExtraInfo</summary>
  337. static byte[] SerializeGraphExtraInfo (NavGraph graph) {
  338. var stream = new MemoryStream();
  339. var writer = new BinaryWriter(stream);
  340. var ctx = new GraphSerializationContext(writer);
  341. ((IGraphInternals)graph).SerializeExtraInfo(ctx);
  342. byte[] bytes = stream.ToArray();
  343. writer.Close();
  344. return bytes;
  345. }
  346. /// <summary>
  347. /// Used to serialize references to other nodes e.g connections.
  348. /// Nodes use the GraphSerializationContext.GetNodeIdentifier and
  349. /// GraphSerializationContext.GetNodeFromIdentifier methods
  350. /// for serialization and deserialization respectively.
  351. /// </summary>
  352. static byte[] SerializeGraphNodeReferences (NavGraph graph) {
  353. var stream = new MemoryStream();
  354. var writer = new BinaryWriter(stream);
  355. var ctx = new GraphSerializationContext(writer);
  356. graph.GetNodes(node => node.SerializeReferences(ctx));
  357. writer.Close();
  358. var bytes = stream.ToArray();
  359. return bytes;
  360. }
  361. public void SerializeExtraInfo () {
  362. if (!settings.nodes) return;
  363. if (graphs == null) throw new InvalidOperationException("Cannot serialize extra info with no serialized graphs (call SerializeGraphs first)");
  364. var bytes = SerializeNodeIndices(graphs);
  365. AddChecksum(bytes);
  366. AddEntry("graph_references"+binaryExt, bytes);
  367. for (int i = 0; i < graphs.Length; i++) {
  368. if (graphs[i] == null) continue;
  369. bytes = SerializeGraphExtraInfo(graphs[i]);
  370. AddChecksum(bytes);
  371. AddEntry("graph"+i+"_extra"+binaryExt, bytes);
  372. bytes = SerializeGraphNodeReferences(graphs[i]);
  373. AddChecksum(bytes);
  374. AddEntry("graph"+i+"_references"+binaryExt, bytes);
  375. }
  376. bytes = SerializeNodeLinks();
  377. AddChecksum(bytes);
  378. AddEntry("node_link2" + binaryExt, bytes);
  379. }
  380. byte[] SerializeNodeLinks () {
  381. var stream = new MemoryStream();
  382. #if !ASTAR_NO_LINKS
  383. var writer = new BinaryWriter(stream);
  384. var ctx = new GraphSerializationContext(writer);
  385. NodeLink2.SerializeReferences(ctx);
  386. #endif
  387. return stream.ToArray();
  388. }
  389. #endregion
  390. #region Deserialize
  391. ZipEntry GetEntry (string name) {
  392. #if NETFX_CORE
  393. return zip.GetEntry(name);
  394. #else
  395. return zip[name];
  396. #endif
  397. }
  398. bool ContainsEntry (string name) {
  399. return GetEntry(name) != null;
  400. }
  401. public bool OpenDeserialize (byte[] bytes) {
  402. // Copy the bytes to a stream
  403. zipStream = new MemoryStream();
  404. zipStream.Write(bytes, 0, bytes.Length);
  405. zipStream.Position = 0;
  406. try {
  407. #if NETFX_CORE
  408. zip = new ZipFile(zipStream);
  409. #else
  410. zip = ZipFile.Read(zipStream);
  411. // Don't use parallel defate
  412. zip.ParallelDeflateThreshold = -1;
  413. #endif
  414. } catch (Exception e) {
  415. // Catches exceptions when an invalid zip file is found
  416. Debug.LogError("Caught exception when loading from zip\n"+e);
  417. zipStream.Dispose();
  418. return false;
  419. }
  420. if (ContainsEntry("meta" + jsonExt)) {
  421. meta = DeserializeMeta(GetEntry("meta" + jsonExt));
  422. } else if (ContainsEntry("meta" + binaryExt)) {
  423. meta = DeserializeBinaryMeta(GetEntry("meta" + binaryExt));
  424. } else {
  425. throw new Exception("No metadata found in serialized data.");
  426. }
  427. if (FullyDefinedVersion(meta.version) > FullyDefinedVersion(AstarPath.Version)) {
  428. Debug.LogWarning("Trying to load data from a newer version of the A* Pathfinding Project\nCurrent version: "+AstarPath.Version+" Data version: "+meta.version +
  429. "\nThis is usually fine as the stored data is usually backwards and forwards compatible." +
  430. "\nHowever node data (not settings) can get corrupted between versions (even though I try my best to keep compatibility), so it is recommended " +
  431. "to recalculate any caches (those for faster startup) and resave any files. Even if it seems to load fine, it might cause subtle bugs.\n");
  432. }
  433. return true;
  434. }
  435. /// <summary>
  436. /// Returns a version with all fields fully defined.
  437. /// This is used because by default new Version(3,0,0) > new Version(3,0).
  438. /// This is not the desired behaviour so we make sure that all fields are defined here
  439. /// </summary>
  440. static System.Version FullyDefinedVersion (System.Version v) {
  441. return new System.Version(Mathf.Max(v.Major, 0), Mathf.Max(v.Minor, 0), Mathf.Max(v.Build, 0), Mathf.Max(v.Revision, 0));
  442. }
  443. public void CloseDeserialize () {
  444. zipStream.Dispose();
  445. zip.Dispose();
  446. zip = null;
  447. zipStream = null;
  448. }
  449. NavGraph DeserializeGraph (int zipIndex, int graphIndex, System.Type[] availableGraphTypes) {
  450. // Get the graph type from the metadata we deserialized earlier
  451. var graphType = meta.GetGraphType(zipIndex, availableGraphTypes);
  452. // Graph was null when saving, ignore
  453. if (System.Type.Equals(graphType, null)) return null;
  454. // Create a new graph of the right type
  455. NavGraph graph = data.CreateGraph(graphType);
  456. graph.graphIndex = (uint)(graphIndex);
  457. var jsonName = "graph" + zipIndex + jsonExt;
  458. var binName = "graph" + zipIndex + binaryExt;
  459. if (ContainsEntry(jsonName)) {
  460. // Read the graph settings
  461. TinyJsonDeserializer.Deserialize(GetString(GetEntry(jsonName)), graphType, graph, contextRoot);
  462. } else if (ContainsEntry(binName)) {
  463. var reader = GetBinaryReader(GetEntry(binName));
  464. var ctx = new GraphSerializationContext(reader, null, graph.graphIndex, meta);
  465. ((IGraphInternals)graph).DeserializeSettingsCompatibility(ctx);
  466. } else {
  467. throw new FileNotFoundException("Could not find data for graph " + zipIndex + " in zip. Entry 'graph" + zipIndex + jsonExt + "' does not exist");
  468. }
  469. if (graph.guid.ToString() != meta.guids[zipIndex])
  470. throw new Exception("Guid in graph file not equal to guid defined in meta file. Have you edited the data manually?\n"+graph.guid+" != "+meta.guids[zipIndex]);
  471. return graph;
  472. }
  473. /// <summary>
  474. /// Deserializes graph settings.
  475. /// Note: Stored in files named "graph<see cref=".json"/>" where # is the graph number.
  476. /// </summary>
  477. public NavGraph[] DeserializeGraphs (System.Type[] availableGraphTypes) {
  478. // Allocate a list of graphs to be deserialized
  479. var graphList = new List<NavGraph>();
  480. graphIndexInZip = new Dictionary<NavGraph, int>();
  481. for (int i = 0; i < meta.graphs; i++) {
  482. var newIndex = graphList.Count + graphIndexOffset;
  483. var graph = DeserializeGraph(i, newIndex, availableGraphTypes);
  484. if (graph != null) {
  485. graphList.Add(graph);
  486. graphIndexInZip[graph] = i;
  487. }
  488. }
  489. graphs = graphList.ToArray();
  490. return graphs;
  491. }
  492. bool DeserializeExtraInfo (NavGraph graph) {
  493. var zipIndex = graphIndexInZip[graph];
  494. var entry = GetEntry("graph"+zipIndex+"_extra"+binaryExt);
  495. if (entry == null)
  496. return false;
  497. var reader = GetBinaryReader(entry);
  498. var ctx = new GraphSerializationContext(reader, null, graph.graphIndex, meta);
  499. // Call the graph to process the data
  500. ((IGraphInternals)graph).DeserializeExtraInfo(ctx);
  501. return true;
  502. }
  503. bool AnyDestroyedNodesInGraphs () {
  504. bool result = false;
  505. for (int i = 0; i < graphs.Length; i++) {
  506. graphs[i].GetNodes(node => {
  507. if (node.Destroyed) {
  508. result = true;
  509. }
  510. });
  511. }
  512. return result;
  513. }
  514. GraphNode[] DeserializeNodeReferenceMap () {
  515. // Get the file containing the list of all node indices
  516. // This is correlated with the new indices of the nodes and a mapping from old to new
  517. // is done so that references can be resolved
  518. var entry = GetEntry("graph_references"+binaryExt);
  519. if (entry == null) throw new Exception("Node references not found in the data. Was this loaded from an older version of the A* Pathfinding Project?");
  520. var reader = GetBinaryReader(entry);
  521. int maxNodeIndex = reader.ReadInt32();
  522. var int2Node = new GraphNode[maxNodeIndex+1];
  523. try {
  524. for (int i = 0; i < graphs.Length; i++) {
  525. graphs[i].GetNodes(node => {
  526. var index = reader.ReadInt32();
  527. int2Node[index] = node;
  528. });
  529. }
  530. } catch (Exception e) {
  531. throw new Exception("Some graph(s) has thrown an exception during GetNodes, or some graph(s) have deserialized more or fewer nodes than were serialized", e);
  532. }
  533. #if !NETFX_CORE
  534. // For Windows Store apps the BaseStream.Position property is not supported
  535. // so we have to disable this error check on that platform
  536. if (reader.BaseStream.Position != reader.BaseStream.Length) {
  537. throw new Exception((reader.BaseStream.Length / 4) + " nodes were serialized, but only data for " + (reader.BaseStream.Position / 4) + " nodes was found. The data looks corrupt.");
  538. }
  539. #endif
  540. reader.Close();
  541. return int2Node;
  542. }
  543. void DeserializeNodeReferences (NavGraph graph, GraphNode[] int2Node) {
  544. var zipIndex = graphIndexInZip[graph];
  545. var entry = GetEntry("graph"+zipIndex+"_references"+binaryExt);
  546. if (entry == null) throw new Exception("Node references for graph " + zipIndex + " not found in the data. Was this loaded from an older version of the A* Pathfinding Project?");
  547. var reader = GetBinaryReader(entry);
  548. var ctx = new GraphSerializationContext(reader, int2Node, graph.graphIndex, meta);
  549. graph.GetNodes(node => node.DeserializeReferences(ctx));
  550. }
  551. /// <summary>
  552. /// Deserializes extra graph info.
  553. /// Extra graph info is specified by the graph types.
  554. /// See: Pathfinding.NavGraph.DeserializeExtraInfo
  555. /// Note: Stored in files named "graph<see cref="_extra.binary"/>" where # is the graph number.
  556. /// </summary>
  557. public void DeserializeExtraInfo () {
  558. bool anyDeserialized = false;
  559. // Loop through all graphs and deserialize the extra info
  560. // if there is any such info in the zip file
  561. for (int i = 0; i < graphs.Length; i++) {
  562. anyDeserialized |= DeserializeExtraInfo(graphs[i]);
  563. }
  564. if (!anyDeserialized) {
  565. return;
  566. }
  567. // Sanity check
  568. // Make sure the graphs don't contain destroyed nodes
  569. if (AnyDestroyedNodesInGraphs()) {
  570. Debug.LogError("Graph contains destroyed nodes. This is a bug.");
  571. }
  572. // Deserialize map from old node indices to new nodes
  573. var int2Node = DeserializeNodeReferenceMap();
  574. // Deserialize node references
  575. for (int i = 0; i < graphs.Length; i++) {
  576. DeserializeNodeReferences(graphs[i], int2Node);
  577. }
  578. DeserializeNodeLinks(int2Node);
  579. }
  580. void DeserializeNodeLinks (GraphNode[] int2Node) {
  581. #if !ASTAR_NO_LINKS
  582. var entry = GetEntry("node_link2"+binaryExt);
  583. if (entry == null)
  584. return;
  585. var reader = GetBinaryReader(entry);
  586. var ctx = new GraphSerializationContext(reader, int2Node, 0, meta);
  587. NodeLink2.DeserializeReferences(ctx);
  588. #endif
  589. }
  590. /// <summary>Calls PostDeserialization on all loaded graphs</summary>
  591. public void PostDeserialization () {
  592. for (int i = 0; i < graphs.Length; i++) {
  593. var ctx = new GraphSerializationContext(null, null, 0, meta);
  594. ((IGraphInternals)graphs[i]).PostDeserialization(ctx);
  595. }
  596. }
  597. /// <summary>
  598. /// Deserializes graph editor settings.
  599. /// For future compatibility this method does not assume that the graphEditors array matches the <see cref="graphs"/> array in order and/or count.
  600. /// It searches for a matching graph (matching if graphEditor.target == graph) for every graph editor.
  601. /// Multiple graph editors should not refer to the same graph.
  602. /// Note: Stored in files named "graph<see cref="_editor.json"/>" where # is the graph number.
  603. ///
  604. /// Note: This method is only used for compatibility, newer versions store everything in the graph.serializedEditorSettings field which is already serialized.
  605. /// </summary>
  606. public void DeserializeEditorSettingsCompatibility () {
  607. for (int i = 0; i < graphs.Length; i++) {
  608. var zipIndex = graphIndexInZip[graphs[i]];
  609. ZipEntry entry = GetEntry("graph"+zipIndex+"_editor"+jsonExt);
  610. if (entry == null) continue;
  611. (graphs[i] as IGraphInternals).SerializedEditorSettings = GetString(entry);
  612. }
  613. }
  614. /// <summary>Returns a binary reader for the data in the zip entry</summary>
  615. private static BinaryReader GetBinaryReader (ZipEntry entry) {
  616. #if NETFX_CORE
  617. return new BinaryReader(entry.Open());
  618. #else
  619. var stream = new System.IO.MemoryStream();
  620. entry.Extract(stream);
  621. stream.Position = 0;
  622. return new System.IO.BinaryReader(stream);
  623. #endif
  624. }
  625. /// <summary>Returns the data in the zip entry as a string</summary>
  626. private static string GetString (ZipEntry entry) {
  627. #if NETFX_CORE
  628. var reader = new StreamReader(entry.Open());
  629. #else
  630. var buffer = new MemoryStream();
  631. entry.Extract(buffer);
  632. buffer.Position = 0;
  633. var reader = new StreamReader(buffer);
  634. #endif
  635. string s = reader.ReadToEnd();
  636. reader.Dispose();
  637. return s;
  638. }
  639. private GraphMeta DeserializeMeta (ZipEntry entry) {
  640. return TinyJsonDeserializer.Deserialize(GetString(entry), typeof(GraphMeta)) as GraphMeta;
  641. }
  642. private GraphMeta DeserializeBinaryMeta (ZipEntry entry) {
  643. var meta = new GraphMeta();
  644. var reader = GetBinaryReader(entry);
  645. if (reader.ReadString() != "A*") throw new System.Exception("Invalid magic number in saved data");
  646. int major = reader.ReadInt32();
  647. int minor = reader.ReadInt32();
  648. int build = reader.ReadInt32();
  649. int revision = reader.ReadInt32();
  650. // Required because when saving a version with a field not set, it will save it as -1
  651. // and then the Version constructor will throw an exception (which we do not want)
  652. if (major < 0) meta.version = new Version(0, 0);
  653. else if (minor < 0) meta.version = new Version(major, 0);
  654. else if (build < 0) meta.version = new Version(major, minor);
  655. else if (revision < 0) meta.version = new Version(major, minor, build);
  656. else meta.version = new Version(major, minor, build, revision);
  657. meta.graphs = reader.ReadInt32();
  658. meta.guids = new List<string>();
  659. int count = reader.ReadInt32();
  660. for (int i = 0; i < count; i++) meta.guids.Add(reader.ReadString());
  661. meta.typeNames = new List<string>();
  662. count = reader.ReadInt32();
  663. for (int i = 0; i < count; i++) meta.typeNames.Add(reader.ReadString());
  664. reader.Close();
  665. return meta;
  666. }
  667. #endregion
  668. #region Utils
  669. /// <summary>Save the specified data at the specified path</summary>
  670. public static void SaveToFile (string path, byte[] data) {
  671. #if NETFX_CORE
  672. throw new System.NotSupportedException("Cannot save to file on this platform");
  673. #else
  674. using (var stream = new FileStream(path, FileMode.Create)) {
  675. stream.Write(data, 0, data.Length);
  676. }
  677. #endif
  678. }
  679. /// <summary>Load the specified data from the specified path</summary>
  680. public static byte[] LoadFromFile (string path) {
  681. #if NETFX_CORE
  682. throw new System.NotSupportedException("Cannot load from file on this platform");
  683. #else
  684. using (var stream = new FileStream(path, FileMode.Open)) {
  685. var bytes = new byte[(int)stream.Length];
  686. stream.Read(bytes, 0, (int)stream.Length);
  687. return bytes;
  688. }
  689. #endif
  690. }
  691. #endregion
  692. }
  693. /// <summary>Metadata for all graphs included in serialization</summary>
  694. public class GraphMeta {
  695. /// <summary>Project version it was saved with</summary>
  696. public Version version;
  697. /// <summary>Number of graphs serialized</summary>
  698. public int graphs;
  699. /// <summary>Guids for all graphs</summary>
  700. public List<string> guids;
  701. /// <summary>Type names for all graphs</summary>
  702. public List<string> typeNames;
  703. /// <summary>Returns the Type of graph number index</summary>
  704. public Type GetGraphType (int index, System.Type[] availableGraphTypes) {
  705. // The graph was null when saving. Ignore it
  706. if (String.IsNullOrEmpty(typeNames[index])) return null;
  707. for (int j = 0; j < availableGraphTypes.Length; j++) {
  708. if (availableGraphTypes[j].FullName == typeNames[index]) return availableGraphTypes[j];
  709. }
  710. throw new Exception("No graph of type '" + typeNames[index] + "' could be created, type does not exist");
  711. }
  712. }
  713. /// <summary>Holds settings for how graphs should be serialized</summary>
  714. public class SerializeSettings {
  715. /// <summary>
  716. /// Enable to include node data.
  717. /// If false, only settings will be saved
  718. /// </summary>
  719. public bool nodes = true;
  720. /// <summary>
  721. /// Use pretty printing for the json data.
  722. /// Good if you want to open up the saved data and edit it manually
  723. /// </summary>
  724. [System.Obsolete("There is no support for pretty printing the json anymore")]
  725. public bool prettyPrint;
  726. /// <summary>
  727. /// Save editor settings.
  728. /// Warning: Only applicable when saving from the editor using the AstarPathEditor methods
  729. /// </summary>
  730. public bool editorSettings;
  731. /// <summary>Serialization settings for only saving graph settings</summary>
  732. public static SerializeSettings Settings {
  733. get {
  734. return new SerializeSettings {
  735. nodes = false
  736. };
  737. }
  738. }
  739. }
  740. }