Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ObjectDatabase.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.Linq;
7 using System.Threading.Tasks;
8 using SiliconStudio.Core.IO;
9 
10 namespace SiliconStudio.Core.Storage
11 {
12  /// <summary>
13  /// Gives access to the object database.
14  /// </summary>
15  public class ObjectDatabase
16  {
17  // Loaded Hash => Blob mapping
18  private static readonly Dictionary<ObjectId, Blob> LoadedBlobs = new Dictionary<ObjectId, Blob>();
19 
20  // When reading, first try backendRead2, then backendRead1.
21  // When writing, try backendWrite.
22  private readonly IOdbBackend backendRead1, backendRead2, backendWrite;
23  private readonly BundleOdbBackend bundleBackend;
24 
25  public BundleOdbBackend BundleBackend
26  {
27  get { return bundleBackend; }
28  }
29 
30  /// <summary>
31  /// Initializes a new instance of the <see cref="ObjectDatabase" /> class.
32  /// </summary>
33  /// <param name="vfsMainUrl">The VFS main URL.</param>
34  /// <param name="indexName">Name of the index file.</param>
35  /// <param name="vfsAdditionalUrl">The VFS additional URL. It will be used only if vfsMainUrl is read-only.</param>
36  public ObjectDatabase(string vfsMainUrl, string indexName = "index", string vfsAdditionalUrl = null, bool loadDefaultBundle = true)
37  {
38  if (vfsMainUrl == null) throw new ArgumentNullException("vfsMainUrl");
39 
40  // Create the merged asset index map
41  AssetIndexMap = new ObjectDatabaseAssetIndexMap();
42 
43  // Try to open file backends
44  bool isReadOnly = Platform.Type != PlatformType.Windows;
45  var backend = new FileOdbBackend(vfsMainUrl, isReadOnly, indexName);
46  AssetIndexMap.Merge(backend.AssetIndexMap);
47  if (backend.IsReadOnly)
48  {
49  backendRead1 = backend;
50  if (vfsAdditionalUrl != null)
51  {
52  backendWrite = backendRead2 = new FileOdbBackend(vfsAdditionalUrl, false);
53  AssetIndexMap.Merge(backendWrite.AssetIndexMap);
54  }
55  }
56  else
57  {
58  backendWrite = backendRead1 = backend;
59  }
60 
61  AssetIndexMap.WriteableAssetIndexMap = backendWrite.AssetIndexMap;
62 
63  bundleBackend = new BundleOdbBackend(vfsMainUrl);
64 
65  // Try to open "default" pack file synchronously
66  if (loadDefaultBundle)
67  {
68  try
69  {
70  bundleBackend.LoadBundle("default", AssetIndexMap).GetAwaiter().GetResult();
71  }
72  catch (FileNotFoundException)
73  {
74  }
75  }
76  }
77 
78  public ObjectDatabaseAssetIndexMap AssetIndexMap { get; private set; }
79 
80  public void CreateBundle(ObjectId[] objectIds, string bundleName, BundleOdbBackend bundleBackend, ISet<ObjectId> disableCompressionIds, Dictionary<string, ObjectId> indexMap, IList<string> dependencies)
81  {
82  if (bundleBackend == null)
83  throw new InvalidOperationException("Can't pack files.");
84 
85  if (objectIds.Length == 0)
86  return;
87 
88  var packUrl = bundleBackend.BundleDirectory + bundleName + BundleOdbBackend.BundleExtension; // we don't want the pack to be compressed in the APK on android
89 
90  // Create pack
91  BundleOdbBackend.CreateBundle(packUrl, backendRead1, objectIds, disableCompressionIds, indexMap, dependencies);
92  }
93 
94  /// <summary>
95  /// Loads the specified bundle.
96  /// </summary>
97  /// <param name="bundleName">Name of the bundle.</param>
98  /// <returns></returns>
99  public Task LoadBundle(string bundleName)
100  {
101  return bundleBackend.LoadBundle(bundleName, AssetIndexMap);
102  }
103 
104  /// <summary>
105  /// Loads the specified bundle.
106  /// </summary>
107  /// <param name="bundleName">Name of the bundle.</param>
108  /// <returns></returns>
109  public void UnloadBundle(string bundleName)
110  {
111  bundleBackend.UnloadBundle(bundleName, AssetIndexMap);
112  }
113 
115  {
116  var result = backendRead1.EnumerateObjects();
117 
118  if (bundleBackend != null)
119  result = result.Union(bundleBackend.EnumerateObjects());
120 
121  if (backendRead2 != null)
122  result = result.Union(backendRead2.EnumerateObjects());
123 
124  return result;
125  }
126 
128  {
129  return backendRead1.EnumerateObjects();
130  }
131 
132  public void Delete(ObjectId objectId)
133  {
134  if (backendWrite == null)
135  throw new InvalidOperationException("Read-only object database.");
136 
137  backendWrite.Delete(objectId);
138  }
139 
140  public bool Exists(ObjectId objectId)
141  {
142  return (bundleBackend != null && bundleBackend.Exists(objectId)) || backendRead1.Exists(objectId) || (backendRead2 != null && backendRead2.Exists(objectId));
143  }
144 
145  public int GetSize(ObjectId objectId)
146  {
147  if (bundleBackend != null && bundleBackend.Exists(objectId))
148  return bundleBackend.GetSize(objectId);
149 
150  if (backendRead1.Exists(objectId))
151  return backendRead1.GetSize(objectId);
152 
153  if (backendRead2 == null)
154  throw new FileNotFoundException();
155 
156  return backendRead2.GetSize(objectId);
157  }
158 
159  public string GetFilePath(ObjectId objectId)
160  {
161  if (bundleBackend != null && bundleBackend.Exists(objectId))
162  throw new InvalidOperationException();
163 
164  if (backendRead1.Exists(objectId))
165  return backendRead1.GetFilePath(objectId);
166 
167  if (backendRead2 != null && backendRead2.Exists(objectId))
168  return backendRead2.GetFilePath(objectId);
169 
170  return backendWrite.GetFilePath(objectId);
171  }
172 
173  /// <summary>
174  /// Writes the specified data using the active <see cref="IOdbBackend"/>.
175  /// </summary>
176  /// <param name="data">The data.</param>
177  /// <param name="size">The size.</param>
178  /// <param name="forceWrite">Set to true to force writing the datastream even if a content is already stored with the same id. Default is false.</param>
179  /// <returns>The <see cref="ObjectId"/> of the given data.</returns>
180  public ObjectId Write(IntPtr data, int size, bool forceWrite = false)
181  {
182  if (backendWrite == null)
183  throw new InvalidOperationException("Read-only object database.");
184 
185  return backendWrite.Write(ObjectId.Empty, new NativeMemoryStream(data, size), size, forceWrite);
186  }
187 
188  /// <summary>
189  /// Writes the specified data using the active <see cref="IOdbBackend"/>.
190  /// </summary>
191  /// <param name="stream">The data stream.</param>
192  /// <returns>The <see cref="ObjectId"/> of the given data.</returns>
193  public ObjectId Write(Stream stream)
194  {
195  if (backendWrite == null)
196  throw new InvalidOperationException("Read-only object database.");
197 
198  return backendWrite.Write(ObjectId.Empty, stream, (int)stream.Length);
199  }
200 
201  /// <summary>
202  /// Writes the specified data using the active <see cref="IOdbBackend"/> and a precomputer <see cref="ObjectId"/>.
203  /// </summary>
204  /// <param name="stream">The data stream.</param>
205  /// <param name="objectId">The precomputed objectId.</param>
206  /// <param name="forceWrite">Set to true to force writing the datastream even if a content is already stored with the same id. Default is false.</param>
207  /// <returns>The <see cref="ObjectId"/> of the given data, which is the same that the passed one.</returns>
208  public ObjectId Write(Stream stream, ObjectId objectId, bool forceWrite = false)
209  {
210  if (backendWrite == null)
211  throw new InvalidOperationException("Read-only object database.");
212 
213  return backendWrite.Write(objectId, stream, (int)stream.Length, forceWrite);
214  }
215 
216  /// <summary>
217  /// Opens a stream for the specified <see cref="ObjectId"/>.
218  /// </summary>
219  /// <param name="objectId">The object identifier.</param>
220  /// <param name="mode">The mode.</param>
221  /// <param name="access">The access.</param>
222  /// <param name="share">The share.</param>
223  /// <returns>A Stream.</returns>
224  /// <exception cref="System.InvalidOperationException">Read-only object database.</exception>
226  {
227  if (access == VirtualFileAccess.Read)
228  {
229  return OpenStreamForRead(objectId, mode, access, share);
230  }
231 
232  if (backendWrite == null)
233  throw new InvalidOperationException("Read-only object database.");
234 
235  if (backendRead1 == backendWrite)
236  {
237  return backendWrite.OpenStream(objectId, mode, access, share);
238  }
239  else
240  {
241  using (var streamRead = OpenStreamForRead(objectId, VirtualFileMode.Open, VirtualFileAccess.ReadWrite, VirtualFileShare.ReadWrite))
242  {
243  var stream = backendWrite.OpenStream(objectId, mode, access, share);
244 
245  if (streamRead != null)
246  {
247  streamRead.CopyTo(stream);
248  }
249  stream.Position = 0;
250  return stream;
251  }
252  }
253  }
254 
255  /// <summary>
256  /// Returns a data stream of the data specified <see cref="ObjectId"/>.
257  /// </summary>
258  /// <param name="objectId">The <see cref="ObjectId"/>.</param>
259  /// <param name="checkCache">if set to <c>true</c> [check cache for existing blobs].</param>
260  /// <returns>A <see cref="NativeStream"/> of the requested data.</returns>
261  public Stream Read(ObjectId objectId, bool checkCache = false)
262  {
263  if (checkCache)
264  {
265  lock (LoadedBlobs)
266  {
267  // Check if there is already an in-memory blob that we can use.
268  Blob blob;
269  if (LoadedBlobs.TryGetValue(objectId, out blob))
270  {
271  return new BlobStream(blob);
272  }
273  }
274  }
275 
276  return OpenStream(objectId);
277  }
278 
279  /// <summary>
280  /// Creates a stream that can then be saved directly in the database using <see cref="SaveStream"/>.
281  /// </summary>
282  /// <returns>a stream writer that should be passed to <see cref="SaveStream"/> in order to be stored in the database</returns>
284  {
285  return backendWrite.CreateStream();
286  }
287 
288  /// <summary>
289  /// Creates a in-memory binary blob as a <see cref="Blob"/> that will also be stored using the active <see cref="IOdbBackend"/>.
290  /// Even if <see cref="Blob"/> is new (not in the ODB), memory will be copied.
291  /// </summary>
292  /// <param name="data">The data.</param>
293  /// <param name="size">The size.</param>
294  /// <returns>The <see cref="Blob"/> containing given data, with its reference count incremented.</returns>
295  public Blob CreateBlob(IntPtr data, int size)
296  {
297  // Generate hash
298  ObjectId objectId;
299  var nativeMemoryStream = new NativeMemoryStream(data, size);
300 
301  using (var digestStream = new DigestStream(Stream.Null))
302  {
303  nativeMemoryStream.CopyTo(digestStream);
304  objectId = digestStream.CurrentHash;
305  }
306 
307  lock (LoadedBlobs)
308  {
309  var blob = Lookup(objectId);
310 
311  // Blob doesn't exist yet, so let's create it and save it to ODB.
312  if (blob == null)
313  {
314  // Let's go back to beginning of stream after previous hash
315  nativeMemoryStream.Position = 0;
316 
317  // Create blob
318  blob = new Blob(this, objectId, data, size);
319  blob.AddReference();
320 
321  // Write to disk
322  backendWrite.Write(objectId, nativeMemoryStream, size, false);
323 
324  // Add blob to cache
325  LoadedBlobs.Add(objectId, blob);
326  }
327 
328  return blob;
329  }
330  }
331 
332  /// <summary>
333  /// Lookups the <see cref="Blob"/> with the specified <see cref="ObjectId"/>.
334  /// Any object returned will have its reference count incremented.
335  /// </summary>
336  /// <param name="objectId">The object id.</param>
337  /// <returns>The <see cref="Blob"/> matching this <see cref="ObjectId"/> with an incremented reference count if it exists; [null] otherwise.</returns>
338  public Blob Lookup(ObjectId objectId)
339  {
340  Blob blob;
341  lock (LoadedBlobs)
342  {
343  if (!LoadedBlobs.TryGetValue(objectId, out blob))
344  {
345  // Try to load blob if not cached
346  var stream = OpenStream(objectId).ToNativeStream();
347  if (stream == null)
348  return null;
349 
350  // Create blob and add to cache
351  blob = new Blob(this, objectId, stream);
352  LoadedBlobs.Add(objectId, blob);
353 
354  // Dispose the previously opened stream.
355  stream.Dispose();
356  }
357 
358  // Lookup adds a reference
359  blob.AddReference();
360  }
361 
362  return blob;
363  }
364 
365  internal void DestroyBlob(Blob blob)
366  {
367  // Remove blob from cache when destroyed
368  lock (LoadedBlobs)
369  {
370  if (!LoadedBlobs.Remove(blob.ObjectId))
371  throw new InvalidOperationException("Destroying a blob not created through ObjectDatabase.CreateBlob.");
372  }
373  }
374 
375  private Stream OpenStreamForRead(ObjectId objectId, VirtualFileMode mode, VirtualFileAccess access, VirtualFileShare share)
376  {
377  if (bundleBackend != null && bundleBackend.Exists(objectId))
378  return bundleBackend.OpenStream(objectId, mode, access, share);
379 
380  if (backendRead1.Exists(objectId))
381  return backendRead1.OpenStream(objectId, mode, access, share);
382 
383  if (backendRead2 != null && backendRead2.Exists(objectId))
384  return backendRead2.OpenStream(objectId, mode, access, share);
385 
386  return null;
387  }
388  }
389 }
Stores immutable binary content.
Definition: Blob.cs:13
ObjectId Write(Stream stream, ObjectId objectId, bool forceWrite=false)
Writes the specified data using the active IOdbBackend and a precomputer ObjectId.
Stream Read(ObjectId objectId, bool checkCache=false)
Returns a data stream of the data specified ObjectId.
Asset Index Map implementation which regroups all the asset index maps of every loaded file backend a...
A read-only NativeMemoryStream that will properly keep references on its underlying Blob...
Definition: BlobStream.cs:11
VirtualFileShare
File share capabilities, equivalent of System.IO.FileShare.
Base class for custom object database backends (ODB).
Definition: IOdbBackend.cs:13
Gives access to the object database.
Blob Lookup(ObjectId objectId)
Lookups the Blob with the specified ObjectId. Any object returned will have its reference count incre...
Object Database Backend (ODB) implementation using VirtualFileSystem
IEnumerable< ObjectId > EnumerateLooseObjects()
Stream OpenStream(ObjectId objectId, VirtualFileMode mode=VirtualFileMode.Open, VirtualFileAccess access=VirtualFileAccess.Read, VirtualFileShare share=VirtualFileShare.Read)
Opens a stream for the specified ObjectId.
ObjectId ObjectId
Gets the ObjectId.
Definition: Blob.cs:71
Object Database Backend (ODB) implementation that bundles multiple chunks into a .bundle files, optionally compressed with LZ4.
VirtualFileAccess
File access equivalent of System.IO.FileAccess.
VirtualFileMode
File mode equivalent of System.IO.FileMode.
void CreateBundle(ObjectId[] objectIds, string bundleName, BundleOdbBackend bundleBackend, ISet< ObjectId > disableCompressionIds, Dictionary< string, ObjectId > indexMap, IList< string > dependencies)
void UnloadBundle(string bundleName)
Loads the specified bundle.
A MemoryStream over a native memory region.
ObjectDatabase(string vfsMainUrl, string indexName="index", string vfsAdditionalUrl=null, bool loadDefaultBundle=true)
Initializes a new instance of the ObjectDatabase class.
OdbStreamWriter CreateStream()
Creates a stream that can then be saved directly in the database using SaveStream.
IEnumerable< ObjectId > EnumerateObjects()
A hash to uniquely identify data.
Definition: ObjectId.cs:13
ObjectId Write(Stream stream)
Writes the specified data using the active IOdbBackend.
Task LoadBundle(string bundleName)
Loads the specified bundle.
Blob CreateBlob(IntPtr data, int size)
Creates a in-memory binary blob as a Blob that will also be stored using the active IOdbBackend...
_In_ size_t _In_ size_t size
Definition: DirectXTexP.h:175
ObjectId Write(IntPtr data, int size, bool forceWrite=false)
Writes the specified data using the active IOdbBackend.