Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AssetMigration.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 
4 using System;
5 using System.Globalization;
6 using System.IO;
7 using System.Linq;
8 using SharpYaml;
9 using SharpYaml.Events;
10 using SharpYaml.Serialization;
11 using SiliconStudio.Assets.Serializers;
12 using SiliconStudio.Core.Diagnostics;
13 using SiliconStudio.Core.Yaml;
14 
15 namespace SiliconStudio.Assets
16 {
17  /// <summary>
18  /// Helper for migrating asset to newer versions.
19  /// </summary>
20  static class AssetMigration
21  {
22  public static bool MigrateAssetIfNeeded(ILogger log, string assetFullPath)
23  {
24  // Determine if asset was Yaml or not
25  var assetFileExtension = Path.GetExtension(assetFullPath);
26  if (assetFileExtension == null)
27  return false;
28 
29  assetFileExtension = assetFileExtension.ToLowerInvariant();
30 
31  var serializer = AssetSerializer.FindSerializer(assetFileExtension);
32  if (!(serializer is AssetYamlSerializer))
33  return false;
34 
35  // We've got a Yaml asset, let's get expected and serialized versions
36  var serializedVersion = 0;
37  var expectedVersion = 0;
38  Type assetType;
39 
40  // Read from Yaml file the asset version and its type (to get expected version)
41  // Note: It tries to read as few as possible (SerializedVersion is expected to be right after Id, so it shouldn't try to read further than that)
42  using (var streamReader = new StreamReader(assetFullPath))
43  {
44  var yamlEventReader = new EventReader(new Parser(streamReader));
45 
46  // Skip header
47  yamlEventReader.Expect<StreamStart>();
48  yamlEventReader.Expect<DocumentStart>();
49  var mappingStart = yamlEventReader.Expect<MappingStart>();
50 
51  var yamlSerializerSettings = YamlSerializer.GetSerializerSettings();
52  var tagTypeRegistry = yamlSerializerSettings.TagTypeRegistry;
53  assetType = tagTypeRegistry.TypeFromTag(mappingStart.Tag);
54 
55  expectedVersion = AssetRegistry.GetFormatVersion(assetType);
56 
57  Scalar assetKey;
58  while ((assetKey = yamlEventReader.Allow<Scalar>()) != null)
59  {
60  // Only allow Id before SerializedVersion
61  if (assetKey.Value == "Id")
62  {
63  yamlEventReader.Skip();
64  continue;
65  }
66  if (assetKey.Value == "SerializedVersion")
67  {
68  serializedVersion = Convert.ToInt32(yamlEventReader.Expect<Scalar>().Value, CultureInfo.InvariantCulture);
69  break;
70  }
71  }
72  }
73 
74  if (serializedVersion > expectedVersion)
75  {
76  // Try to open an asset newer than what we support (probably generated by a newer Paradox)
77  throw new InvalidOperationException(string.Format("Asset of type {0} has been serialized with newer version {1}, but only version {2} is supported. Was this asset created with a newer version of Paradox?", assetType, serializedVersion, expectedVersion));
78  }
79 
80  if (serializedVersion < expectedVersion)
81  {
82  // Perform asset upgrade
83  log.Info("{0} needs update, from version {0} to version {1}", Path.GetFullPath(assetFullPath), serializedVersion, expectedVersion);
84 
85  // Load the asset as a YamlNode object
86  var input = new StringReader(File.ReadAllText(assetFullPath));
87  var yamlStream = new YamlStream();
88  yamlStream.Load(input);
89  var yamlRootNode = (YamlMappingNode)yamlStream.Documents[0].RootNode;
90 
91  // Check if there is any asset updater
92  var assetUpdaterTypes = AssetRegistry.GetFormatVersionUpdaterTypes(assetType);
93  if (assetUpdaterTypes == null)
94  {
95  throw new InvalidOperationException(string.Format("Asset of type {0} should be updated from version {1} to {2}, but no asset migration path was found", assetType, serializedVersion, expectedVersion));
96  }
97 
98  // Instantiate asset updaters
99  var assetUpgraders = assetUpdaterTypes.Select(x => (IAssetUpgrader)Activator.CreateInstance(x)).ToArray();
100 
101  // TODO: Select best asset updater if more than one (need to check from what to what version they update, score, if multiple need to be chained, etc...)
102  // I think it's better to wait for some actual scenarios to implement this right the first time
103  if (assetUpgraders.Length != 1)
104  {
105  throw new InvalidOperationException(string.Format("Asset of type {0} has multiple migration paths, but selecting the right one is not implemented yet.", assetType));
106  }
107 
108  // Perform upgrade
109  assetUpgraders[0].Upgrade(log, yamlRootNode);
110 
111  // Make sure asset is updated to latest version
112  YamlNode serializedVersionNode;
113  serializedVersion = 0;
114  if (yamlRootNode.Children.TryGetValue(new YamlScalarNode("SerializedVersion"), out serializedVersionNode))
115  {
116  serializedVersion = Convert.ToInt32(((YamlScalarNode)serializedVersionNode).Value);
117  }
118 
119  if (serializedVersion != expectedVersion)
120  {
121  throw new InvalidOperationException(string.Format("Asset of type {0} was migrated, but still its new version {1} doesn't match expected version {2}.", assetType, serializedVersion, expectedVersion));
122  }
123 
124  var preferredIndent = YamlSerializer.GetSerializerSettings().PreferredIndent;
125 
126  // Save asset back to disk
127  using (var streamWriter = new StreamWriter(assetFullPath))
128  yamlStream.Save(streamWriter, true, preferredIndent);
129 
130  return true;
131  }
132 
133  return false;
134  }
135  }
136 }
A registry for file extensions, IAssetImporter, IAssetFactory and aliases associated with assets...
Helper for migrating asset to newer versions.
System.IO.File File
static bool MigrateAssetIfNeeded(ILogger log, string assetFullPath)
static Type[] GetFormatVersionUpdaterTypes(Type assetType)
Gets the current format version of an asset.
Interface for logging.
Definition: ILogger.cs:8