Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
BundleOdbBackend.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;
7 using System.Linq;
8 using System.Threading.Tasks;
9 using SiliconStudio.Core.Extensions;
10 using SiliconStudio.Core.IO;
11 using SiliconStudio.Core.LZ4;
12 using SiliconStudio.Core.Serialization;
13 using SiliconStudio.Core.Serialization.Assets;
14 using SiliconStudio.Core.Serialization.Serializers;
15 
16 namespace SiliconStudio.Core.Storage
17 {
18  /// <summary>
19  /// Object Database Backend (ODB) implementation that bundles multiple chunks into a .bundle files, optionally compressed with LZ4.
20  /// </summary>
21  [DataSerializerGlobal(null, typeof(List<string>))]
22  [DataSerializerGlobal(null, typeof(List<KeyValuePair<ObjectId, BundleOdbBackend.ObjectInfo>>))]
23  [DataSerializerGlobal(null, typeof(List<KeyValuePair<string, ObjectId>>))]
25  {
26  /// <summary>
27  /// The bundle file extension.
28  /// </summary>
29  public const string BundleExtension = ".bundle";
30 
31  /// <summary>
32  /// The default directory where bundle are stored.
33  /// </summary>
34  private readonly string vfsBundleDirectory;
35 
36  private readonly Dictionary<ObjectId, ObjectLocation> objects = new Dictionary<ObjectId, ObjectLocation>();
37 
38  // Stream pool to avoid reopening same file multiple time
39  private readonly Dictionary<string, Stream> bundleStreams = new Dictionary<string, Stream>();
40 
41  // Bundle name => Bundle VFS URL
42  private readonly Dictionary<string, string> resolvedBundles = new Dictionary<string, string>();
43 
44  private readonly List<LoadedBundle> loadedBundles = new List<LoadedBundle>();
45 
46  private readonly ObjectDatabaseAssetIndexMap assetIndexMap = new ObjectDatabaseAssetIndexMap();
47 
48  public delegate Task<string> BundleResolveDelegate(string bundleName);
49 
50  /// <summary>
51  /// Bundle resolve event asynchronous handler.
52  /// </summary>
53  public BundleResolveDelegate BundleResolve { get; set; }
54 
55  /// <inheritdoc/>
57  {
58  get { return assetIndexMap; }
59  }
60 
61  public string BundleDirectory { get { return vfsBundleDirectory; } }
62 
63  public BundleOdbBackend(string vfsRootUrl)
64  {
65  BundleResolve += BundleResolve;
66 
67  vfsBundleDirectory = vfsRootUrl + "/bundles/";
68 
69  if (!VirtualFileSystem.DirectoryExists(vfsBundleDirectory))
70  VirtualFileSystem.CreateDirectory(vfsBundleDirectory);
71 
72  BundleResolve += DefaultBundleResolve;
73  }
74 
75  public Dictionary<ObjectId, ObjectInfo> GetObjectInfos()
76  {
77  lock (objects)
78  {
79  return objects.ToDictionary(pair => pair.Key, value => value.Value.Info);
80  }
81  }
82 
83  private Task<string> DefaultBundleResolve(string bundleName)
84  {
85  // Try to find [bundleName].bundle
86  var bundleFile = VirtualFileSystem.Combine(vfsBundleDirectory, bundleName + BundleExtension);
87  if (VirtualFileSystem.FileExists(bundleFile))
88  return Task.FromResult(bundleFile);
89 
90  return Task.FromResult<string>(null);
91  }
92 
93  private async Task<string> ResolveBundle(string bundleName, bool throwExceptionIfNotFound)
94  {
95  string bundleUrl;
96 
97  lock (resolvedBundles)
98  {
99  if (resolvedBundles.TryGetValue(bundleName, out bundleUrl))
100  {
101  if (bundleUrl == null)
102  throw new InvalidOperationException(string.Format("Bundle {0} is being loaded twice (either cyclic dependency or concurrency issue)", bundleName));
103  return bundleUrl;
104  }
105 
106  // Store null until resolved (to detect cyclic dependencies)
107  resolvedBundles[bundleName] = null;
108  }
109 
110  if (BundleResolve != null)
111  {
112  // Iterate over each handler and find the first one that returns non-null result
113  foreach (BundleResolveDelegate bundleResolvedHandler in BundleResolve.GetInvocationList())
114  {
115  // Use handler to resolve package
116  bundleUrl = await bundleResolvedHandler(bundleName);
117  if (bundleUrl != null)
118  break;
119  }
120  }
121 
122  // Check if it has been properly resolved
123  if (bundleUrl == null)
124  {
125  // Remove from resolved bundles
126  lock (resolvedBundles)
127  {
128  resolvedBundles.Remove(bundleName);
129  }
130 
131  if (!throwExceptionIfNotFound)
132  return null;
133 
134  throw new FileNotFoundException(string.Format("Bundle {0} could not be resolved", bundleName));
135  }
136 
137  // Register resolved package
138  lock (resolvedBundles)
139  {
140  resolvedBundles[bundleName] = bundleUrl;
141  }
142 
143  return bundleUrl;
144  }
145 
146  /// <summary>
147  /// Loads the specified bundle.
148  /// </summary>
149  /// <param name="bundleName">Name of the bundle.</param>
150  /// <param name="objectDatabaseAssetIndexMap">The object database asset index map, where newly loaded assets will be merged (ignored if null).</param>
151  /// <returns></returns>
152  public async Task LoadBundle(string bundleName, ObjectDatabaseAssetIndexMap objectDatabaseAssetIndexMap)
153  {
154  if (bundleName == null) throw new ArgumentNullException("bundleName");
155 
156  // Check loaded bundles
157  lock (loadedBundles)
158  {
159  foreach (var currentBundle in loadedBundles)
160  {
161  if (currentBundle.BundleName == bundleName)
162  {
163  currentBundle.ReferenceCount++;
164  return;
165  }
166  }
167  }
168 
169  // Resolve package
170  var vfsUrl = await ResolveBundle(bundleName, true);
171 
172  BundleDescription bundle;
173 
174  using (var packStream = VirtualFileSystem.OpenStream(vfsUrl, VirtualFileMode.Open, VirtualFileAccess.Read))
175  {
176  bundle = ReadBundleDescription(packStream);
177  }
178 
179  // Read and resolve dependencies
180  foreach (var dependency in bundle.Dependencies)
181  {
182  await LoadBundle(dependency, objectDatabaseAssetIndexMap);
183  }
184 
185  lock (loadedBundles)
186  {
187  LoadedBundle loadedBundle = null;
188 
189  foreach (var currentBundle in loadedBundles)
190  {
191  if (currentBundle.BundleName == bundleName)
192  {
193  loadedBundle = currentBundle;
194  break;
195  }
196  }
197 
198  if (loadedBundle == null)
199  {
200  loadedBundle = new LoadedBundle
201  {
202  BundleName = bundleName,
203  BundleUrl = vfsUrl,
204  Description = bundle,
205  ReferenceCount = 1
206  };
207 
208  loadedBundles.Add(loadedBundle);
209  }
210  else
211  {
212  loadedBundle.ReferenceCount++;
213  }
214  }
215 
216  // Read objects
217  lock (objects)
218  {
219  foreach (var objectEntry in bundle.Objects)
220  {
221  objects[objectEntry.Key] = new ObjectLocation { Info = objectEntry.Value, BundleUrl = vfsUrl };
222  }
223  }
224 
225  // Merge with local (asset bundles) index map
226  assetIndexMap.Merge(bundle.Assets);
227 
228  // Merge with global object database map
229  objectDatabaseAssetIndexMap.Merge(bundle.Assets);
230  }
231 
232  /// <summary>
233  /// Unload the specified bundle.
234  /// </summary>
235  /// <param name="bundleName">Name of the bundle.</param>
236  /// <param name="objectDatabaseAssetIndexMap">The object database asset index map, where newly loaded assets will be merged (ignored if null).</param>
237  /// <returns></returns>
238  public void UnloadBundle(string bundleName, ObjectDatabaseAssetIndexMap objectDatabaseAssetIndexMap)
239  {
240  lock (loadedBundles)
241  lock (objects)
242  {
243  // Unload package
244  UnloadBundleRecursive(bundleName, objectDatabaseAssetIndexMap);
245 
246  // Remerge previously loaded packages
247  foreach (var otherLoadedBundle in loadedBundles)
248  {
249  var bundle = otherLoadedBundle.Description;
250 
251  // Read objects
252  foreach (var objectEntry in bundle.Objects)
253  {
254  objects[objectEntry.Key] = new ObjectLocation { Info = objectEntry.Value, BundleUrl = otherLoadedBundle.BundleUrl };
255  }
256 
257  assetIndexMap.Merge(bundle.Assets);
258  objectDatabaseAssetIndexMap.Merge(bundle.Assets);
259  }
260  }
261  }
262 
263  private void UnloadBundleRecursive(string bundleName, ObjectDatabaseAssetIndexMap objectDatabaseAssetIndexMap)
264  {
265  if (bundleName == null) throw new ArgumentNullException("bundleName");
266 
267  lock (loadedBundles)
268  {
269  int loadedBundleIndex = -1;
270 
271  for (int index = 0; index < loadedBundles.Count; index++)
272  {
273  var currentBundle = loadedBundles[index];
274  if (currentBundle.BundleName == bundleName)
275  {
276  loadedBundleIndex = index;
277  break;
278  }
279  }
280 
281  if (loadedBundleIndex == -1)
282  throw new InvalidOperationException("Bundle has not been loaded.");
283 
284  var loadedBundle = loadedBundles[loadedBundleIndex];
285  var bundle = loadedBundle.Description;
286  if (--loadedBundle.ReferenceCount == 0)
287  {
288  // Remove and dispose stream from pool
289  lock (bundleStreams)
290  {
291  Stream stream;
292  if (bundleStreams.TryGetValue(loadedBundle.BundleUrl, out stream))
293  {
294  bundleStreams.Remove(loadedBundle.BundleUrl);
295  stream.Dispose();
296  }
297  }
298 
299  // Actually unload bundle
300  loadedBundles.RemoveAt(loadedBundleIndex);
301 
302  // Unload objects from index map (if possible, replace with objects of other bundles
303  var removedObjects = new HashSet<ObjectId>();
304  foreach (var objectEntry in bundle.Objects)
305  {
306  objects.Remove(objectEntry.Key);
307  removedObjects.Add(objectEntry.Key);
308  }
309 
310  // Unmerge with local (asset bundles) index map
311  assetIndexMap.Unmerge(bundle.Assets);
312 
313  // Unmerge with global object database map
314  objectDatabaseAssetIndexMap.Unmerge(bundle.Assets);
315 
316  // Remove dependencies too
317  foreach (var dependency in bundle.Dependencies)
318  {
319  UnloadBundleRecursive(dependency, objectDatabaseAssetIndexMap);
320  }
321  }
322  }
323  }
324 
325  /// <summary>
326  /// Reads the bundle description.
327  /// </summary>
328  /// <param name="stream">The stream.</param>
329  /// <returns></returns>
330  /// <exception cref="System.InvalidOperationException">
331  /// Invalid bundle header
332  /// or
333  /// Bundle has not been properly written
334  /// </exception>
336  {
337  var binaryReader = new BinarySerializationReader(stream);
338 
339  // Read header
340  var header = binaryReader.Read<Header>();
341 
342  var result = new BundleDescription();
343  result.Header = header;
344 
345  // Check magic header
346  if (header.MagicHeader != Header.MagicHeaderValid)
347  {
348  throw new InvalidOperationException("Invalid bundle header");
349  }
350 
351  // Ensure size has properly been set
352  if (header.Size != stream.Length)
353  {
354  throw new InvalidOperationException("Bundle has not been properly written");
355  }
356 
357  // Read dependencies
358  List<string> dependencies = result.Dependencies;
359  binaryReader.Serialize(ref dependencies, ArchiveMode.Deserialize);
360 
361  // Read objects
362  List<KeyValuePair<ObjectId, ObjectInfo>> objects = result.Objects;
363  binaryReader.Serialize(ref objects, ArchiveMode.Deserialize);
364 
365  // Read assets
366  List<KeyValuePair<string, ObjectId>> assets = result.Assets;
367  binaryReader.Serialize(ref assets, ArchiveMode.Deserialize);
368 
369  return result;
370  }
371 
372  public static void CreateBundle(string vfsUrl, IOdbBackend backend, ObjectId[] objectIds, ISet<ObjectId> disableCompressionIds, Dictionary<string, ObjectId> indexMap, IList<string> dependencies)
373  {
374  if (objectIds.Length == 0)
375  throw new InvalidOperationException("Nothing to pack.");
376 
377  // Early exit if package didn't change (header-check only)
378  if (VirtualFileSystem.FileExists(vfsUrl))
379  {
380  try
381  {
382  using (var packStream = VirtualFileSystem.OpenStream(vfsUrl, VirtualFileMode.Open, VirtualFileAccess.Read))
383  {
384  var bundle = ReadBundleDescription(packStream);
385 
386  // If package didn't change since last time, early exit!
387  if (ArrayExtensions.ArraysEqual(bundle.Dependencies, dependencies)
388  && ArrayExtensions.ArraysEqual(bundle.Assets.OrderBy(x => x.Key).ToList(), indexMap.OrderBy(x => x.Key).ToList())
389  && ArrayExtensions.ArraysEqual(bundle.Objects.Select(x => x.Key).OrderBy(x => x).ToList(), objectIds.OrderBy(x => x).ToList()))
390  {
391  return;
392  }
393  }
394  }
395  catch (Exception)
396  {
397  // Could not read previous bundle (format changed?)
398  // Let's just mute this error as new bundle will overwrite it anyway
399  }
400  }
401 
402  using (var packStream = VirtualFileSystem.OpenStream(vfsUrl, VirtualFileMode.Create, VirtualFileAccess.Write))
403  {
404  var header = new Header();
405  header.MagicHeader = Header.MagicHeaderValid;
406 
407  var binaryWriter = new BinarySerializationWriter(packStream);
408  binaryWriter.Write(header);
409 
410  // Write dependencies
411  binaryWriter.Write(dependencies.ToList());
412 
413  // Save location of object ids
414  var objectIdPosition = packStream.Position;
415 
416  // Write empty object ids (reserve space, will be rewritten later)
417  var objects = new List<KeyValuePair<ObjectId, ObjectInfo>>();
418  for (int i = 0; i < objectIds.Length; ++i)
419  {
420  objects.Add(new KeyValuePair<ObjectId, ObjectInfo>(objectIds[i], new ObjectInfo()));
421  }
422 
423  binaryWriter.Write(objects);
424  objects.Clear();
425 
426  // Write index
427  binaryWriter.Write(indexMap.ToList());
428 
429  for (int i = 0; i < objectIds.Length; ++i)
430  {
431  using (var objectStream = backend.OpenStream(objectIds[i]))
432  {
433  // Prepare object info
434  var objectInfo = new ObjectInfo { StartOffset = packStream.Position, SizeNotCompressed = objectStream.Length };
435 
436  // re-order the file content so that it is not necessary to seek while reading the input stream (header/object/refs -> header/refs/object)
437  var inputStream = objectStream;
438  var originalStreamLength = objectStream.Length;
439  var streamReader = new BinarySerializationReader(inputStream);
440  var chunkHeader = ChunkHeader.Read(streamReader);
441  if (chunkHeader != null)
442  {
443  // create the reordered stream
444  var reorderedStream = new MemoryStream((int)originalStreamLength);
445 
446  // copy the header
447  var streamWriter = new BinarySerializationWriter(reorderedStream);
448  chunkHeader.Write(streamWriter);
449 
450  // copy the references
451  var newOffsetReferences = reorderedStream.Position;
452  inputStream.Position = chunkHeader.OffsetToReferences;
453  inputStream.CopyTo(reorderedStream);
454 
455  // copy the object
456  var newOffsetObject = reorderedStream.Position;
457  inputStream.Position = chunkHeader.OffsetToObject;
458  inputStream.CopyTo(reorderedStream, chunkHeader.OffsetToReferences - chunkHeader.OffsetToObject);
459 
460  // rewrite the chunk header with correct offsets
461  chunkHeader.OffsetToObject = (int)newOffsetObject;
462  chunkHeader.OffsetToReferences = (int)newOffsetReferences;
463  reorderedStream.Position = 0;
464  chunkHeader.Write(streamWriter);
465 
466  // change the input stream to use reordered stream
467  inputStream = reorderedStream;
468  inputStream.Position = 0;
469  }
470 
471  // compress the stream
472  if (!disableCompressionIds.Contains(objectIds[i]))
473  {
474  objectInfo.IsCompressed = true;
475 
476  var lz4OutputStream = new LZ4Stream(packStream, CompressionMode.Compress);
477  inputStream.CopyTo(lz4OutputStream);
478  lz4OutputStream.Flush();
479  }
480  else // copy the stream "as is"
481  {
482  // Write stream
483  inputStream.CopyTo(packStream);
484  }
485 
486  // release the reordered created stream
487  if (chunkHeader != null)
488  inputStream.Dispose();
489 
490  // Add updated object info
491  objectInfo.EndOffset = packStream.Position;
492  objects.Add(new KeyValuePair<ObjectId, ObjectInfo>(objectIds[i], objectInfo));
493  }
494  }
495 
496  // Rewrite header
497  header.Size = packStream.Length;
498  packStream.Position = 0;
499  binaryWriter.Write(header);
500 
501  // Rewrite object locations
502  packStream.Position = objectIdPosition;
503  binaryWriter.Write(objects);
504  }
505  }
506 
508  {
509  ObjectLocation objectLocation;
510  lock (objects)
511  {
512  if (!objects.TryGetValue(objectId, out objectLocation))
513  throw new FileNotFoundException();
514  }
515 
516  Stream stream;
517 
518  // Try to reuse same streams
519  lock (bundleStreams)
520  {
521  // Available stream?
522  if (bundleStreams.TryGetValue(objectLocation.BundleUrl, out stream))
523  {
524  // Remove from available streams
525  bundleStreams.Remove(objectLocation.BundleUrl);
526  }
527  else
528  {
529  stream = VirtualFileSystem.OpenStream(objectLocation.BundleUrl, VirtualFileMode.Open, VirtualFileAccess.Read);
530  }
531  }
532 
533  if (objectLocation.Info.IsCompressed)
534  {
535  stream.Position = objectLocation.Info.StartOffset;
536  return new PackageFileStreamLZ4(this, objectLocation.BundleUrl, stream, CompressionMode.Decompress, objectLocation.Info.SizeNotCompressed, objectLocation.Info.EndOffset - objectLocation.Info.StartOffset);
537  }
538 
539  return new PackageFileStream(this, objectLocation.BundleUrl, stream, objectLocation.Info.StartOffset, objectLocation.Info.EndOffset, false);
540  }
541 
542  public int GetSize(ObjectId objectId)
543  {
544  lock (objects)
545  {
546  var objectInfo = objects[objectId].Info;
547  return (int)(objectInfo.EndOffset - objectInfo.StartOffset);
548  }
549  }
550 
551  public ObjectId Write(ObjectId objectId, Stream dataStream, int length, bool forceWrite)
552  {
553  throw new NotSupportedException();
554  }
555 
557  {
558  throw new NotSupportedException();
559  }
560 
561  public bool Exists(ObjectId objectId)
562  {
563  lock (objects)
564  {
565  return objects.ContainsKey(objectId);
566  }
567  }
568 
570  {
571  lock (objects)
572  {
573  return objects.Select(x => x.Key).ToList();
574  }
575  }
576 
577  public void Delete(ObjectId objectId)
578  {
579  throw new NotSupportedException();
580  }
581 
582  public string GetFilePath(ObjectId objectId)
583  {
584  throw new NotSupportedException();
585  }
586 
587  private struct ObjectLocation
588  {
589  public ObjectInfo Info;
590  public string BundleUrl;
591  }
592 
593  private class LoadedBundle
594  {
595  public string BundleName;
596  public string BundleUrl;
597  public int ReferenceCount;
598  public BundleDescription Description;
599  }
600 
601  internal void ReleasePackageStream(string packageLocation, Stream stream)
602  {
603  lock (bundleStreams)
604  {
605  if (!bundleStreams.ContainsKey(packageLocation))
606  {
607  bundleStreams.Add(packageLocation, stream);
608  }
609  else
610  {
611  stream.Dispose();
612  }
613  }
614  }
615 
616  [DataContract]
617  [DataSerializer(typeof(Serializer))]
618  public struct ObjectInfo
619  {
620  public long StartOffset;
621  public long EndOffset;
622  public long SizeNotCompressed;
623  public bool IsCompressed;
624 
625  internal class Serializer : DataSerializer<ObjectInfo>
626  {
627  public override void Serialize(ref ObjectInfo obj, ArchiveMode mode, SerializationStream stream)
628  {
629  stream.Serialize(ref obj.StartOffset);
630  stream.Serialize(ref obj.EndOffset);
631  stream.Serialize(ref obj.SizeNotCompressed);
632  stream.Serialize(ref obj.IsCompressed);
633  }
634  }
635  }
636 
637  [DataContract]
638  [DataSerializer(typeof(Header.Serializer))]
639  public struct Header
640  {
641  public const uint MagicHeaderValid = 0x42584450; // "PDXB"
642 
643  public uint MagicHeader;
644  public long Size;
645  public uint Crc; // currently unused
646 
647  internal class Serializer : DataSerializer<Header>
648  {
649  public override void Serialize(ref Header obj, ArchiveMode mode, SerializationStream stream)
650  {
651  stream.Serialize(ref obj.MagicHeader);
652  stream.Serialize(ref obj.Size);
653  stream.Serialize(ref obj.Crc);
654  }
655  }
656  }
658  {
659  private readonly BundleOdbBackend bundleOdbBackend;
660  private readonly string packageLocation;
661  private readonly Stream innerStream;
662 
663  public PackageFileStreamLZ4(BundleOdbBackend bundleOdbBackend, string packageLocation, Stream innerStream, CompressionMode compressionMode, long uncompressedStreamSize, long compressedSize)
664  : base(innerStream, compressionMode, uncompressedSize: uncompressedStreamSize, compressedSize: compressedSize, disposeInnerStream: false)
665  {
666  this.bundleOdbBackend = bundleOdbBackend;
667  this.packageLocation = packageLocation;
668  this.innerStream = innerStream;
669  }
670 
671  protected override void Dispose(bool disposing)
672  {
673  bundleOdbBackend.ReleasePackageStream(packageLocation, innerStream);
674 
675  base.Dispose(disposing);
676  }
677  }
678 
680  {
681  private readonly BundleOdbBackend bundleOdbBackend;
682  private readonly string packageLocation;
683 
684  public PackageFileStream(BundleOdbBackend bundleOdbBackend, string packageLocation, Stream internalStream, long startPosition = 0, long endPosition = -1, bool disposeInternalStream = true, bool seekToBeginning = true)
685  : base(internalStream, startPosition, endPosition, disposeInternalStream, seekToBeginning)
686  {
687  this.bundleOdbBackend = bundleOdbBackend;
688  this.packageLocation = packageLocation;
689  }
690 
691  protected override void Dispose(bool disposing)
692  {
693  bundleOdbBackend.ReleasePackageStream(packageLocation, virtualFileStream ?? InternalStream);
694 
695  // If there was a VirtualFileStream, we don't want it to be released as it has been pushed back in the stream pool
696  virtualFileStream = null;
697 
698  base.Dispose(disposing);
699  }
700  }
701 
702  public void DeleteBundles(Func<string, bool> bundleFileDeletePredicate)
703  {
704  var bundleFiles = VirtualFileSystem.ListFiles(vfsBundleDirectory, "*.bundle", VirtualSearchOption.TopDirectoryOnly).Result;
705 
706  // Obsolete: Android used to have .bundle.mp3 to avoid compression. Still here so that they get deleted on next build.
707  // This can be removed later.
708  bundleFiles = bundleFiles.Union(VirtualFileSystem.ListFiles(vfsBundleDirectory, "*.mp3", VirtualSearchOption.TopDirectoryOnly).Result).ToArray();
709 
710  foreach (var bundleFile in bundleFiles)
711  {
712  var bundleRealFile = VirtualFileSystem.GetAbsolutePath(bundleFile);
713 
714  // Remove ".mp3" (Android only)
715  if (bundleRealFile.EndsWith(".mp3", StringComparison.CurrentCultureIgnoreCase))
716  bundleRealFile = bundleRealFile.Substring(0, bundleRealFile.Length - 4);
717 
718  if (bundleFileDeletePredicate(bundleRealFile))
719  {
720  NativeFile.FileDelete(bundleRealFile);
721  }
722  }
723  }
724  }
725 }
Virtual abstraction over a file system. It handles access to files, http, packages, path rewrite, etc...
async Task LoadBundle(string bundleName, ObjectDatabaseAssetIndexMap objectDatabaseAssetIndexMap)
Loads the specified bundle.
int GetSize(ObjectId objectId)
Requests that this backend read an object's length (but not its contents).
ObjectId Write(ObjectId objectId, Stream dataStream, int length, bool forceWrite)
Writes an object to the backing store. The backend may need to compute the object ID and return it to...
Asset Index Map implementation which regroups all the asset index maps of every loaded file backend a...
static bool FileExists(string path)
Checks the existence of a file.
VirtualFileShare
File share capabilities, equivalent of System.IO.FileShare.
Implements SerializationStream as a binary writer.
Stream OpenStream(ObjectId objectId, VirtualFileMode mode=VirtualFileMode.Open, VirtualFileAccess access=VirtualFileAccess.Read, VirtualFileShare share=VirtualFileShare.Read)
Opens a NativeStream of the object with the specified ObjectId.
Base class for custom object database backends (ODB).
Definition: IOdbBackend.cs:13
OdbStreamWriter CreateStream()
Creates a stream that will be saved to database when closed and/or disposed.
static bool DirectoryExists(string path)
Checks the existence of a directory.
Dictionary< ObjectId, ObjectInfo > GetObjectInfos()
string GetFilePath(ObjectId objectId)
Returns the file path corresponding to the given id (in the VFS domain), if appliable.
Base class for implementation of SerializationStream.
Implements SerializationStream as a binary reader.
Object Database Backend (ODB) implementation that bundles multiple chunks into a .bundle files, optionally compressed with LZ4.
void UnloadBundle(string bundleName, ObjectDatabaseAssetIndexMap objectDatabaseAssetIndexMap)
Unload the specified bundle.
bool Exists(ObjectId objectId)
Determines weither the object with the specified ObjectId exists.
VirtualFileAccess
File access equivalent of System.IO.FileAccess.
static void CreateBundle(string vfsUrl, IOdbBackend backend, ObjectId[] objectIds, ISet< ObjectId > disableCompressionIds, Dictionary< string, ObjectId > indexMap, IList< string > dependencies)
VirtualFileMode
File mode equivalent of System.IO.FileMode.
Block compression stream. Allows to use LZ4 for stream compression.
Definition: LZ4Stream.cs:32
PackageFileStreamLZ4(BundleOdbBackend bundleOdbBackend, string packageLocation, Stream innerStream, CompressionMode compressionMode, long uncompressedStreamSize, long compressedSize)
IEnumerable< ObjectId > EnumerateObjects()
Enumerates the object stored in this backend.
Description of a bundle: header, dependencies, objects and assets.
static void CreateDirectory(string path)
Creates all directories so that path exists.
_Use_decl_annotations_ bool IsCompressed(DXGI_FORMAT fmt)
Definition: DirectXTex.inl:31
Compression
Compression method enumeration
Definition: Compression.cs:20
A hash to uniquely identify data.
Definition: ObjectId.cs:13
Describes how to serialize and deserialize an object without knowing its type. Used as a common base ...
ArchiveMode
Enumerates the different mode of serialization (either serialization or deserialization).
Definition: ArchiveMode.cs:8
void Delete(ObjectId objectId)
Deletes the specified ObjectId.
document false
A multithreaded wrapper over a Stream, used by the VirtualFileSystem. It also allows restricted acces...
PackageFileStream(BundleOdbBackend bundleOdbBackend, string packageLocation, Stream internalStream, long startPosition=0, long endPosition=-1, bool disposeInternalStream=true, bool seekToBeginning=true)
void DeleteBundles(Func< string, bool > bundleFileDeletePredicate)
static BundleDescription ReadBundleDescription(Stream stream)
Reads the bundle description.