Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AssetManager.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 using System;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Reflection;
7 using System.Threading.Tasks;
8 using SiliconStudio.Core.Diagnostics;
9 using SiliconStudio.Core.IO;
10 using SiliconStudio.Core.Serialization.Contents;
11 using SiliconStudio.Core.Serialization.Converters;
12 using SiliconStudio.Core.Storage;
13 
14 namespace SiliconStudio.Core.Serialization.Assets
15 {
16  public sealed partial class AssetManager : IAssetManager
17  {
18  public static DatabaseFileProvider FileProvider { get { return GetFileProvider(); } }
19 
20  public static Func<DatabaseFileProvider> GetFileProvider { get; set; }
21 
22  public AssetSerializer Serializer { get; private set; }
23 
24  // If multiple object shares the same Url, they will be stored as a linked list (AssetReference.Next).
25  private readonly Dictionary<ObjectId, AssetReference> loadedAssetsByUrl = new Dictionary<ObjectId, AssetReference>();
26 
27  private readonly Dictionary<object, AssetReference> loadedAssetsUrl = new Dictionary<object, AssetReference>();
28 
29  public AssetManager()
30  {
31  Serializer = new AssetSerializer { LowLevelSerializerSelector = SerializerSelector.Default };
32  }
33 
34  public void Save(string url, object asset)
35  {
36  if (url == null) throw new ArgumentNullException("url");
37  if (asset == null) throw new ArgumentNullException("asset");
38 
39  lock (loadedAssetsByUrl)
40  {
41  using (var profile = Profiler.Begin(AssetProfilingKeys.AssetSave))
42  {
43  SerializeObject(url, asset, true);
44  }
45  }
46  }
47 
48  public Stream OpenAsStream(string url, StreamFlags streamFlags)
49  {
50  return FileProvider.OpenStream(url, VirtualFileMode.Open, VirtualFileAccess.Read, streamFlags:streamFlags);
51  }
52 
53  public T Load<T>(string url, AssetManagerLoaderSettings settings = null) where T : class
54  {
55  if (settings == null)
56  settings = AssetManagerLoaderSettings.Default;
57 
58  if (url == null) throw new ArgumentNullException("url");
59 
60  lock (loadedAssetsByUrl)
61  {
62  using (var profile = Profiler.Begin(AssetProfilingKeys.AssetLoad, url))
63  {
64  AssetReference assetReference;
65  return (T)DeserializeObject(null, out assetReference, url, typeof(T), settings);
66  }
67  }
68  }
69 
70  public Task<T> LoadAsync<T>(string url, AssetManagerLoaderSettings settings = null) where T : class
71  {
72  return Task.Factory.StartNew(() => Load<T>(url, settings));
73  }
74 
75  public bool TryGetAssetUrl(object obj, out string url)
76  {
77  if (obj == null) throw new ArgumentNullException("obj");
78  lock (loadedAssetsByUrl)
79  {
80  AssetReference assetReference;
81  if (!loadedAssetsUrl.TryGetValue(obj, out assetReference))
82  {
83  url = null;
84  return false;
85  }
86 
87  url = assetReference.Url;
88  return true;
89  }
90  }
91 
92  public void Unload(object obj)
93  {
94  lock (loadedAssetsByUrl)
95  {
96  AssetReference assetReference;
97  if (!loadedAssetsUrl.TryGetValue(obj, out assetReference))
98  throw new InvalidOperationException("Asset not loaded through this AssetManager.");
99 
100  // Release reference
101  DecrementReference(assetReference, true);
102  }
103  }
104 
105  private void PrepareSerializerContext(ContentSerializerContext contentSerializerContext, SerializerContext context)
106  {
107  context.Set(ContentSerializerContext.ContentSerializerContextProperty, contentSerializerContext);
108 
109  // Duplicate context from SerializerContextTags
110  foreach (var property in Serializer.SerializerContextTags)
111  {
112  context.Tags.SetObject(property.Key, property.Value);
113  }
114  }
115 
116  internal object DeserializeObject(AssetReference parentAssetReference, out AssetReference assetReference, string url, Type objType, AssetManagerLoaderSettings settings, ConverterContext converterContext = null)
117  {
118  // Resolve URL
119  ObjectId objectId;
120  if (!FileProvider.AssetIndexMap.TryGetValue(url, out objectId))
121  throw new InvalidOperationException(string.Format("Asset [{0}] not found.", url));
122 
123  // Try to find already loaded object
124  if (loadedAssetsByUrl.TryGetValue(objectId, out assetReference))
125  {
126  while (assetReference != null && !objType.GetTypeInfo().IsAssignableFrom(assetReference.Object.GetType().GetTypeInfo()))
127  {
128  assetReference = assetReference.Next;
129  }
130 
131  if (assetReference != null)
132  {
133  // Add reference
134  bool isRoot = parentAssetReference == null;
135  if (isRoot || parentAssetReference.References.Add(assetReference))
136  {
137  IncrementReference(assetReference, isRoot);
138  }
139 
140  return assetReference.Object;
141  }
142  }
143 
144  if (!FileProvider.FileExists(url))
145  throw new InvalidOperationException(string.Format("Asset [{0}] not found.", url));
146 
147  ContentSerializerContext contentSerializerContext;
148  object result;
149 
150  // Open asset binary stream
151  using (var stream = FileProvider.OpenStream(url, VirtualFileMode.Open, VirtualFileAccess.Read))
152  {
153  // File does not exist
154  // TODO/Benlitz: Add a log entry for that, it's not expected to happen
155  if (stream == null)
156  return null;
157 
158  Type headerObjType = null;
159 
160  // Read header
161  var streamReader = new BinarySerializationReader(stream);
162  var chunkHeader = ChunkHeader.Read(streamReader);
163  if (chunkHeader != null)
164  {
165  headerObjType = Type.GetType(chunkHeader.Type);
166  }
167 
168  // Find serializer
169  var serializer = Serializer.GetSerializer(headerObjType, objType);
170  if (serializer == null)
171  throw new InvalidOperationException(string.Format("Content serializer for {0}/{1} could not be found.", headerObjType, objType));
172  contentSerializerContext = new ContentSerializerContext(url, ArchiveMode.Deserialize, this);
173 
174  // Read chunk references
175  if (chunkHeader != null && chunkHeader.OffsetToReferences != -1)
176  {
177  // Seek to where references are stored and deserialize them
178  streamReader.NativeStream.Seek(chunkHeader.OffsetToReferences, SeekOrigin.Begin);
179  contentSerializerContext.SerializeReferences(streamReader);
180  streamReader.NativeStream.Seek(chunkHeader.OffsetToObject, SeekOrigin.Begin);
181  }
182 
183  // Create AssetReference
184  assetReference = new AssetReference(objectId, url, parentAssetReference == null);
185  contentSerializerContext.AssetReference = assetReference;
186 
187  result = serializer.Construct(contentSerializerContext);
188 
189  PrepareSerializerContext(contentSerializerContext, streamReader.Context);
190  contentSerializerContext.ConverterContext = converterContext;
191 
192  result = contentSerializerContext.SerializeContent(streamReader, serializer, result);
193 
194  SetAssetObject(assetReference, result);
195 
196  // Add reference
197  if (parentAssetReference != null)
198  {
199  parentAssetReference.References.Add(assetReference);
200  }
201  }
202 
203  if (settings.LoadContentReferences)
204  {
205  // Process content references
206  // TODO: Should we work at ChunkReference level?
207  foreach (var contentReference in contentSerializerContext.ContentReferences)
208  {
209  bool shouldBeLoaded = true;
210 
211  AssetReference childReference;
212 
213  if (settings.ContentFilter != null)
214  settings.ContentFilter(contentReference, ref shouldBeLoaded);
215 
216  if (shouldBeLoaded)
217  {
218  contentReference.ObjectValue = DeserializeObject(assetReference, out childReference, contentReference.Location, contentReference.Type, settings);
219  }
220  }
221  }
222 
223  return result;
224  }
225 
226  internal object DeserializeObjectRecursive(AssetReference parentAssetReference, out AssetReference assetReference,
227  string url, Type objType, AssetManagerLoaderSettings settings, ContentSerializerContext otherContext, Stream stream, Type headerObjType, ConverterContext converterContext = null)
228  {
229  // Resolve URL
230  ObjectId objectId;
231  if (!FileProvider.AssetIndexMap.TryGetValue(url, out objectId))
232  throw new InvalidOperationException(string.Format("Asset [{0}] not found.", url));
233 
234  // Find serializer
235  var serializer = Serializer.GetSerializer(headerObjType, objType);
236  if (serializer == null)
237  throw new InvalidOperationException(string.Format("Content serializer for {0}/{1} could not be found.", headerObjType, objType));
238  var contentSerializerContext = new ContentSerializerContext(url, ArchiveMode.Deserialize, this);
239 
240  contentSerializerContext.chunkReferences.AddRange(otherContext.chunkReferences);
241 
242  // Create AssetReference
243  assetReference = new AssetReference(objectId, url, parentAssetReference == null);
244  contentSerializerContext.AssetReference = assetReference;
245 
246  var result = serializer.Construct(contentSerializerContext);
247 
248  var streamReader = new BinarySerializationReader(stream);
249 
250  PrepareSerializerContext(contentSerializerContext, streamReader.Context);
251  contentSerializerContext.ConverterContext = converterContext;
252 
253  result = contentSerializerContext.SerializeContent(streamReader, serializer, result);
254 
255  SetAssetObject(assetReference, result);
256 
257  // Add reference
258  if (parentAssetReference != null)
259  {
260  parentAssetReference.References.Add(assetReference);
261  }
262 
263  return result;
264  }
265 
266  private void SerializeObject(string url, object obj, bool publicReference)
267  {
268  // Don't create context in case we don't want to serialize referenced objects
269  //if (!SerializeReferencedObjects && obj != RootObject)
270  // return null;
271 
272  // Already saved?
273  // TODO: Ref counting? Should we change it on save? Probably depends if we cache or not.
274  if (loadedAssetsUrl.ContainsKey(obj))
275  return;
276 
277  var serializer = Serializer.GetSerializer(null, obj.GetType());
278  if (serializer == null)
279  throw new InvalidOperationException(string.Format("Content serializer for {0} could not be found.", obj.GetType()));
280 
281  var contentSerializerContext = new ContentSerializerContext(url, ArchiveMode.Serialize, this);
282 
283  using (var stream = FileProvider.OpenStream(url, VirtualFileMode.Create, VirtualFileAccess.Write))
284  {
285  var streamWriter = new BinarySerializationWriter(stream);
286  PrepareSerializerContext(contentSerializerContext, streamWriter.Context);
287 
288  ChunkHeader header = null;
289 
290  // Allocate space in the stream, and also include header version in the hash computation, which is better
291  // If serialization type is null, it means there should be no header.
292  var serializationType = serializer.SerializationType;
293  if (serializationType != null)
294  {
295  header = new ChunkHeader();
296  header.Type = serializer.SerializationType.AssemblyQualifiedName;
297  header.Write(streamWriter);
298  header.OffsetToObject = (int)streamWriter.NativeStream.Position;
299  }
300 
301  contentSerializerContext.SerializeContent(streamWriter, serializer, obj);
302 
303  // Write references and updated header
304  if (header != null)
305  {
306  header.OffsetToReferences = (int)streamWriter.NativeStream.Position;
307  contentSerializerContext.SerializeReferences(streamWriter);
308 
309  // Move back to the pre-allocated header position in the steam
310  stream.Seek(0, SeekOrigin.Begin);
311 
312  // Write actual header.
313  header.Write(new BinarySerializationWriter(stream));
314  }
315  }
316 
317  // Resolve URL
318  ObjectId objectId;
319  if (!FileProvider.AssetIndexMap.TryGetValue(url, out objectId))
320  throw new InvalidOperationException(string.Format("Asset [{0}] not found.", url));
321 
322  var assetReference = new AssetReference(objectId, url, publicReference);
323  contentSerializerContext.AssetReference = assetReference;
324  SetAssetObject(assetReference, obj);
325 
326  // Process content references
327  // TODO: Should we work at ChunkReference level?
328  foreach (var contentReference in contentSerializerContext.ContentReferences)
329  {
330  if (contentReference.ObjectValue != null)
331  SerializeObject(contentReference.Location, contentReference.ObjectValue, false);
332  }
333  }
334 
335  /// <summary>
336  /// Sets AssetReference.Object, and updates loadedAssetByUrl collection.
337  /// </summary>
338  internal void SetAssetObject(AssetReference assetReference, object obj)
339  {
340  if (obj == null) throw new ArgumentNullException("obj");
341 
342  if (assetReference.Object != null)
343  {
344  if (assetReference.Object != obj)
345  throw new InvalidOperationException("SetAssetObject has already been called with a different object");
346 
347  return;
348  }
349 
350  var objectId = assetReference.ObjectId;
351  assetReference.Object = obj;
352 
353  lock (loadedAssetsByUrl)
354  {
355  AssetReference previousAssetReference;
356 
357  if (loadedAssetsByUrl.TryGetValue(objectId, out previousAssetReference))
358  {
359  assetReference.Next = previousAssetReference.Next;
360  assetReference.Prev = previousAssetReference;
361 
362  if (previousAssetReference.Next != null)
363  previousAssetReference.Next.Prev = assetReference;
364  previousAssetReference.Next = assetReference;
365  }
366  else
367  {
368  loadedAssetsByUrl[objectId] = assetReference;
369  }
370 
371  loadedAssetsUrl[obj] = assetReference;
372 
373  // TODO: Currently here so that ContentReference.ObjectValue later keeps its Url.
374  // Need some reorganization?
375  UrlServices.SetUrl(obj, assetReference.Url);
376  }
377  }
378  }
379 }
void Unload(object obj)
Unloads the specified object.
Definition: AssetManager.cs:92
Specifies settings for AssetManager.Load{T} operations.
The type of the serialized type will be passed as a generic arguments of the serializer. Example: serializer of A becomes instantiated as Serializer{A}.
bool TryGetAssetUrl(object obj, out string url)
Definition: AssetManager.cs:75
A hash to uniquely identify data.
Definition: ObjectId.cs:13
ArchiveMode
Enumerates the different mode of serialization (either serialization or deserialization).
Definition: ArchiveMode.cs:8
Stream OpenAsStream(string url, StreamFlags streamFlags)
Opens the specified URL as a stream used for custom raw asset loading.
Definition: AssetManager.cs:48
StreamFlags
Describes the different type of streams.
Definition: StreamFlags.cs:11