4 using System.Collections.Generic;
7 using SiliconStudio.Core;
8 using SiliconStudio.Core.Diagnostics;
9 using SiliconStudio.Core.IO;
10 using SiliconStudio.Core.Serialization;
11 using SiliconStudio.Core.Serialization.Assets;
12 using SiliconStudio.Core.Serialization.Contents;
13 using SiliconStudio.Core.Storage;
15 namespace SiliconStudio.Assets.CompilerApp
36 if (logger == null)
throw new ArgumentNullException(
"logger");
37 if (packageSession == null)
throw new ArgumentNullException(
"packageSession");
38 if (indexName == null)
throw new ArgumentNullException(
"indexName");
39 if (outputDirectory == null)
throw new ArgumentNullException(
"outputDirectory");
40 if (disableCompressionIds == null)
throw new ArgumentNullException(
"disableCompressionIds");
43 var objDatabase =
new ObjectDatabase(
"/data/db", indexName, loadDefaultBundle:
false);
45 logger.Info(
"Generate bundles: Scan assets and their dependencies...");
50 foreach (var project
in packageSession.Packages)
52 bundles.AddRange(project.Bundles);
56 AssetManager.GetFileProvider = () => databaseFileProvider;
59 var resolvedBundles =
new Dictionary<string, ResolvedBundle>();
60 foreach (var bundle
in bundles)
62 if (resolvedBundles.ContainsKey(bundle.Name))
64 throw new InvalidOperationException(
string.Format(
"Two bundles with name {0} found", bundle.Name));
71 var bundleAssets =
new HashSet<string>();
73 foreach (var bundle
in resolvedBundles)
77 foreach (var assetSelector
in bundle.Value.Source.Selectors)
79 foreach (var assetLocation
in assetSelector.Select(packageSession, objDatabase.AssetIndexMap))
81 bundle.Value.AssetUrls.Add(assetLocation);
86 foreach (var assetUrl
in bundle.Value.AssetUrls)
88 CollectReferences(bundle.Value.Source, bundleAssets, assetUrl, objDatabase.AssetIndexMap);
93 var defaultBundle =
new Bundle { Name =
"default" };
95 bundles.Add(defaultBundle);
96 resolvedBundles.Add(defaultBundle.Name, resolvedDefaultBundle);
97 foreach (var asset
in objDatabase.AssetIndexMap.GetMergedIdMap())
99 if (!bundleAssets.Contains(asset.Key))
101 resolvedDefaultBundle.AssetUrls.Add(asset.Key);
106 foreach (var bundle
in resolvedBundles)
109 if (bundle.Key !=
"default")
111 bundle.Value.Dependencies.Add(resolvedBundles[
"default"]);
115 foreach (var dependencyName
in bundle.Value.Source.Dependencies)
118 if (!resolvedBundles.TryGetValue(dependencyName, out dependency))
119 throw new InvalidOperationException(
string.Format(
"Could not find dependency {0} when processing bundle {1}", dependencyName, bundle.Value.Name));
121 bundle.Value.Dependencies.Add(dependency);
125 logger.Info(
"Generate bundles: Assign assets to bundles...");
129 var sortedBundles = TopologicalSort(resolvedBundles.Values, assetBundle => assetBundle.Dependencies);
132 foreach (var bundle
in sortedBundles)
135 foreach (var dep
in bundle.Dependencies)
138 bundle.DependencyObjectIds.UnionWith(dep.DependencyObjectIds);
139 bundle.DependencyObjectIds.UnionWith(dep.ObjectIds);
142 foreach (var asset
in dep.DependencyIndexMap.Concat(dep.IndexMap))
144 if (!bundle.DependencyIndexMap.ContainsKey(asset.Key))
145 bundle.DependencyIndexMap.Add(asset.Key, asset.Value);
151 foreach (var assetUrl
in bundle.AssetUrls)
153 CollectBundle(bundle, assetUrl, objDatabase.AssetIndexMap);
157 logger.Info(
"Generate bundles: Compress and save bundles to HDD...");
160 VirtualFileSystem.MountFileSystem(
"/data_output", outputDirectory);
161 VirtualFileSystem.CreateDirectory(
"/data_output/db");
164 var outputDatabase =
new ObjectDatabase(
"/data_output/db", loadDefaultBundle:
false);
167 outputDatabase.LoadBundle(
"default").GetAwaiter().GetResult();
171 logger.Info(
"Generate bundles: Tried to load previous 'default' bundle but it was invalid. Deleting it...");
172 outputDatabase.BundleBackend.DeleteBundles(x => Path.GetFileNameWithoutExtension(x) ==
"default");
174 var outputBundleBackend = outputDatabase.BundleBackend;
176 var outputGroupBundleBackends =
new Dictionary<string, BundleOdbBackend>();
178 if (profile != null && profile.OutputGroupDirectories != null)
180 var rootPackage = packageSession.LocalPackages.First();
182 foreach (var item
in profile.OutputGroupDirectories)
184 var path = Path.Combine(rootPackage.RootDirectory, item.Value);
185 var vfsPath =
"/data_group_" + item.Key;
186 var vfsDatabasePath = vfsPath +
"/db";
189 VirtualFileSystem.MountFileSystem(vfsPath, path);
190 VirtualFileSystem.CreateDirectory(vfsDatabasePath);
192 outputGroupBundleBackends.Add(item.Key,
new BundleOdbBackend(vfsDatabasePath));
197 foreach (var bundle
in sortedBundles)
200 if (bundle.Source.OutputGroup == null)
203 bundleBackend = outputBundleBackend;
205 else if (!outputGroupBundleBackends.TryGetValue(bundle.Source.OutputGroup, out bundleBackend))
208 logger.Warning(
"Generate bundles: Could not find OutputGroup {0} for bundle {1} in ProjectBuildProfile.OutputGroupDirectories", bundle.Source.OutputGroup, bundle.Name);
209 bundleBackend = outputBundleBackend;
212 bundle.BundleBackend = bundleBackend;
215 CleanUnknownBundles(outputBundleBackend, resolvedBundles);
217 foreach (var bundleBackend
in outputGroupBundleBackends)
219 CleanUnknownBundles(bundleBackend.Value, resolvedBundles);
223 foreach (var bundle
in sortedBundles)
226 var dependencies = bundle.Dependencies.Select(x => x.Name).Distinct().ToList();
229 if (bundle.Source.OutputGroup == null)
232 bundleBackend = outputBundleBackend;
234 else if (!outputGroupBundleBackends.TryGetValue(bundle.Source.OutputGroup, out bundleBackend))
237 logger.Warning(
"Generate bundles: Could not find OutputGroup {0} for bundle {1} in ProjectBuildProfile.OutputGroupDirectories", bundle.Source.OutputGroup, bundle.Name);
238 bundleBackend = outputBundleBackend;
241 objDatabase.CreateBundle(bundle.ObjectIds.ToArray(), bundle.Name, bundleBackend, disableCompressionIds, bundle.IndexMap, dependencies);
244 logger.Info(
"Generate bundles: Done");
247 private static void CleanUnknownBundles(
BundleOdbBackend outputBundleBackend, Dictionary<string, ResolvedBundle> resolvedBundles)
250 outputBundleBackend.DeleteBundles(bundleFile =>
253 if (Path.GetExtension(bundleFile) != BundleOdbBackend.BundleExtension)
258 var bundleName = Path.GetFileNameWithoutExtension(bundleFile);
259 return !resolvedBundles.TryGetValue(Path.GetFileNameWithoutExtension(bundleFile), out bundle)
260 || bundle.BundleBackend != outputBundleBackend;
264 private Dictionary<ObjectId, List<string>> referencesByObjectId =
new Dictionary<ObjectId, List<string>>();
271 private List<string> GetChunkReferences(ref
ObjectId objectId)
273 List<string> references;
276 if (!referencesByObjectId.TryGetValue(objectId, out references))
279 referencesByObjectId[objectId] = references =
new List<string>();
282 using (var stream = AssetManager.FileProvider.OpenStream(
"obj/" + objectId, VirtualFileMode.Open, VirtualFileAccess.Read))
286 var header = ChunkHeader.Read(streamReader);
291 if (header.OffsetToReferences != -1)
294 streamReader.NativeStream.Seek(header.OffsetToReferences, SeekOrigin.Begin);
296 List<ChunkReference> chunkReferences = null;
297 streamReader.Serialize(ref chunkReferences, ArchiveMode.Deserialize);
299 foreach (var chunkReference
in chunkReferences)
301 references.Add(chunkReference.Location);
311 private void CollectReferences(Bundle bundle, HashSet<string> assets,
string assetUrl,
IAssetIndexMap assetIndexMap)
314 if (!assets.Add(assetUrl))
318 if (!assetIndexMap.
TryGetValue(assetUrl, out objectId))
319 throw new InvalidOperationException(
string.Format(
"Could not find asset {0} for bundle {1}", assetUrl, bundle.Name));
322 foreach (var reference
in GetChunkReferences(ref objectId))
324 CollectReferences(bundle, assets, reference, assetIndexMap);
328 private void CollectBundle(ResolvedBundle resolvedBundle,
string assetUrl,
IAssetIndexMap assetIndexMap)
331 if (resolvedBundle.DependencyIndexMap.ContainsKey(assetUrl) || resolvedBundle.IndexMap.ContainsKey(assetUrl))
335 if (!assetIndexMap.
TryGetValue(assetUrl, out objectId))
336 throw new InvalidOperationException(
string.Format(
"Could not find asset {0} for bundle {1}", assetUrl, resolvedBundle.Name));
339 resolvedBundle.IndexMap.Add(assetUrl, objectId);
343 if (resolvedBundle.DependencyObjectIds.Contains(objectId) || !resolvedBundle.ObjectIds.Add(objectId))
346 foreach (var reference
in GetChunkReferences(ref objectId))
348 CollectBundle(resolvedBundle, reference, assetIndexMap);
359 private static List<T> TopologicalSort<T>(
IEnumerable<T> source, Func<T, IEnumerable<T>> dependencies)
361 var result =
new List<T>();
365 foreach (var item
in source)
366 TopologicalSortVisit(item, temporaryMark, mark, result, dependencies);
371 private static void TopologicalSortVisit<T>(
T item,
HashSet<T> temporaryMark,
HashSet<T> mark, List<T> result, Func<T, IEnumerable<T>> dependencies)
374 if (mark.Contains(item))
377 if (temporaryMark.Contains(item))
378 throw new InvalidOperationException(
string.Format(
"Cyclic dependency found, involving {0}", item));
380 temporaryMark.Add(item);
382 foreach (var dep
in dependencies(item))
383 TopologicalSortVisit(dep, temporaryMark, mark, result, dependencies);
void Build(Logger logger, PackageSession packageSession, PackageProfile profile, string indexName, string outputDirectory, ISet< ObjectId > disableCompressionIds)
Builds bundles. It will automatically analyze assets and chunks to determine dependencies and what sh...
Description of an asset bundle.
Helper class that represents additional data added to a Bundle when resolving asset.
A session for editing a package.
Gives access to the object database.
Base implementation for ILogger.
Implements SerializationStream as a binary reader.
Object Database Backend (ODB) implementation that bundles multiple chunks into a .bundle files, optionally compressed with LZ4.
This is a minimal implementation of the missing HashSet from Silverlight BCL It's nowhere near the re...
A hash to uniquely identify data.
Describes buld parameters used when building assets.
bool TryGetValue(string url, out ObjectId objectId)
Class that will help generate package bundles.