Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AssetDependencyManager.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.Collections.Specialized;
6 using System.Diagnostics;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using System.Threading.Tasks;
11 using SiliconStudio.Assets.Visitors;
12 using SiliconStudio.Core;
13 using SiliconStudio.Core.Diagnostics;
14 using SiliconStudio.Core.IO;
15 using SiliconStudio.Core.Reflection;
16 using SiliconStudio.Core.Serialization;
17 using SiliconStudio.Core.Storage;
18 
19 namespace SiliconStudio.Assets.Analysis
20 {
21  /// <summary>
22  /// A class responsible for providing asset dependencies for a <see cref="PackageSession"/> and file tracking dependency.
23  /// </summary>
24  /// <remarks>
25  /// This class provides methods to:
26  /// <ul>
27  /// <li>Find assets referencing a particular asset (recursively or not)</li>
28  /// <li>Find assets referenced by a particular asset (recursively or not)</li>
29  /// <li>Find missing references</li>
30  /// <li>Find missing references for a particular asset</li>
31  /// <li>Find assets file changed events that have changed on the disk</li>
32  /// </ul>
33  /// </remarks>
34  public sealed class AssetDependencyManager : IDisposable
35  {
36  private readonly PackageSession session;
37  internal readonly object ThisLock = new object();
38  internal readonly HashSet<Package> Packages;
39  internal readonly Dictionary<Guid, AssetDependencySet> Dependencies;
40  internal readonly Dictionary<Guid, AssetDependencySet> AssetsWithMissingReferences;
41  internal readonly Dictionary<Guid, HashSet<AssetDependencySet>> MissingReferencesToParent;
42 
43  // Objects used to track directories
45  private readonly Dictionary<Package, string> packagePathsTracked = new Dictionary<Package, string>();
46  private readonly Dictionary<Guid, HashSet<UFile>> mapAssetToInputDependencies = new Dictionary<Guid, HashSet<UFile>>();
47  private readonly Dictionary<string, HashSet<Guid>> mapInputDependencyToAssets = new Dictionary<string, HashSet<Guid>>(StringComparer.OrdinalIgnoreCase);
48  private readonly List<FileEvent> fileEvents = new List<FileEvent>();
49  private readonly List<FileEvent> fileEventsWorkingCopy = new List<FileEvent>();
50  private readonly ManualResetEvent threadWatcherEvent;
51  private readonly List<AssetFileChangedEvent> currentAssetFileChangedEvents = new List<AssetFileChangedEvent>();
52  private readonly CancellationTokenSource tokenSourceForImportHash;
53  private readonly List<AssetFileChangedEvent> sourceImportFileChangedEventsToAdd = new List<AssetFileChangedEvent>();
54  private Thread fileEventThreadHandler;
55  private int trackingSleepTime;
56  private bool isDisposed;
57  private bool isDisposing;
58  private bool isTrackingPaused;
59  private bool isSessionSaving;
60  private readonly HashSet<string> assetsBeingSaved = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
61  private readonly AssetFileChangedEventSquasher assetFileChangedEventSquasher = new AssetFileChangedEventSquasher();
62  private bool isInitialized;
63  //private Task initializingTask;
64 
65  /// <summary>
66  /// Occurs when a asset changed. This event is called in the critical section of the dependency manager,
67  /// meaning that dependencies can be safely computed via <see cref="ComputeDependencies"/> method from this callback.
68  /// </summary>
69  public event Action<AssetItem> AssetChanged;
70 
71  /// <summary>
72  /// Initializes a new instance of the <see cref="AssetDependencyManager" /> class.
73  /// </summary>
74  /// <param name="session">The session.</param>
75  /// <exception cref="System.ArgumentNullException">session</exception>
76  internal AssetDependencyManager(PackageSession session)
77  {
78  if (session == null) throw new ArgumentNullException("session");
79  this.session = session;
80  this.session.Packages.CollectionChanged += Packages_CollectionChanged;
81  session.AssetDirtyChanged += Session_AssetDirtyChanged;
82  AssetsWithMissingReferences = new Dictionary<Guid, AssetDependencySet>();
83  MissingReferencesToParent = new Dictionary<Guid, HashSet<AssetDependencySet>>();
84  Packages = new HashSet<Package>();
85  Dependencies = new Dictionary<Guid, AssetDependencySet>();
86  TrackingSleepTime = 1000;
87  tokenSourceForImportHash = new CancellationTokenSource();
88  threadWatcherEvent = new ManualResetEvent(false);
89 
90  // If the session has already a root package, then initialize the dependency manager directly
91  if (session.LocalPackages.Any())
92  {
93  Initialize();
94  }
95  }
96 
97  //internal void InitializeDeferred()
98  //{
99  // initializingTask = Task.Run(() => Initialize());
100  //}
101 
102  /// <summary>
103  /// Gets or sets a value indicating whether this instance should track file disk changed events. Default is <c>false</c>
104  /// </summary>
105  /// <value><c>true</c> if this instance should track file disk changed events; otherwise, <c>false</c>.</value>
106  public bool EnableTracking
107  {
108  get
109  {
110  return fileEventThreadHandler != null;
111  }
112  set
113  {
114  if (isDisposed)
115  {
116  throw new InvalidOperationException("Cannot enable tracking when this instance is disposed");
117  }
118 
119  lock (ThisLock)
120  {
121  if (value)
122  {
123  bool activateTracking = false;
124  if (DirectoryWatcher == null)
125  {
127  DirectoryWatcher.Modified += directoryWatcher_Modified;
128  activateTracking = true;
129  }
130 
131  if (fileEventThreadHandler == null)
132  {
133  fileEventThreadHandler = new Thread(SafeAction.Wrap(RunChangeWatcher)) { IsBackground = true, Name = "RunChangeWatcher thread" };
134  fileEventThreadHandler.Start();
135  }
136 
137  if (activateTracking)
138  {
139  ActivateTracking();
140  }
141  }
142  else
143  {
144  if (DirectoryWatcher != null)
145  {
146  DirectoryWatcher.Dispose();
147  DirectoryWatcher = null;
148  }
149 
150  if (fileEventThreadHandler != null)
151  {
152  threadWatcherEvent.Set();
153  fileEventThreadHandler.Join();
154  fileEventThreadHandler = null;
155  }
156  }
157  }
158  }
159  }
160 
161  /// <summary>
162  /// Gets a value indicating whether this instance is initialized. See remarks.
163  /// </summary>
164  /// <value><c>true</c> if this instance is initialized; otherwise, <c>false</c>.</value>
165  /// <remarks>
166  /// If this instance is not initialized, all public methods may block until the full initialization of this instance.
167  /// </remarks>
168  public bool IsInitialized
169  {
170  get
171  {
172  return isInitialized;
173  }
174  }
175 
176  /// <summary>
177  /// Gets or sets a value indicating whether this instance is processing tracking events or it is paused. Default is <c>false</c>.
178  /// </summary>
179  /// <value><c>true</c> if this instance is tracking paused; otherwise, <c>false</c>.</value>
180  public bool IsTrackingPaused
181  {
182  get
183  {
184  return isTrackingPaused;
185  }
186  set
187  {
188  if (!EnableTracking)
189  return;
190 
191  isTrackingPaused = value;
192  }
193  }
194 
195  /// <summary>
196  /// Gets or sets the number of ms the file tracker should sleep before checking changes. Default is 1000ms.
197  /// </summary>
198  /// <value>The tracking sleep time.</value>
199  public int TrackingSleepTime
200  {
201  get
202  {
203  return trackingSleepTime;
204  }
205  set
206  {
207  if (value <= 0)
208  {
209  throw new ArgumentOutOfRangeException("value", "TrackingSleepTime must be > 0");
210  }
211  trackingSleepTime = value;
212  }
213  }
214 
215  /// <summary>
216  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
217  /// </summary>
218  public void Dispose()
219  {
220  if (isDisposed)
221  return;
222 
223  isDisposing = true;
224  tokenSourceForImportHash.Cancel();
225  EnableTracking = false; // Will terminate the thread if running
226 
227  if (DirectoryWatcher != null)
228  {
229  DirectoryWatcher.Dispose();
230  }
231  //if (initializingTask != null)
232  //{
233  // initializingTask.Wait();
234  //}
235  isDisposed = true;
236  }
237 
238  /// <summary>
239  /// Finds the changed events that have occured, only valid if <see cref="EnableTracking"/> is set to <c>true</c>.
240  /// </summary>
241  /// <returns>List of events.</returns>
243  {
244  lock (Initialize())
245  {
246  var eventsCopy = assetFileChangedEventSquasher.Squash(currentAssetFileChangedEvents);
247  currentAssetFileChangedEvents.Clear();
248  return eventsCopy;
249  }
250  }
251 
252  /// <summary>
253  /// Finds the dependencies for the specified asset.
254  /// </summary>
255  /// <param name="assetId">The asset identifier.</param>
256  /// <returns>The dependencies or null if not found.</returns>
258  {
259  AssetDependencySet dependencySet;
260  lock (Initialize())
261  {
262  if (Dependencies.TryGetValue(assetId, out dependencySet))
263  {
264  // Create a copy
265  dependencySet = new AssetDependencySet(dependencySet);
266  }
267  }
268  return dependencySet;
269  }
270 
271  /// <summary>
272  /// Finds the assets inheriting from the specified asset id (this is a direct inheritance, not indirect).
273  /// </summary>
274  /// <param name="assetId">The asset identifier.</param>
275  /// <returns>A list of asset inheriting from the specified asset id.</returns>
277  {
278  var list = new List<AssetItem>();
279  lock (Initialize())
280  {
281  AssetDependencySet dependencySet;
282  if (Dependencies.TryGetValue(assetId, out dependencySet))
283  {
284  list.AddRange(dependencySet.Parents.Where(parent => parent.Asset.Base != null && parent.Asset.Base.Id == assetId).Select(item => item.Clone(true)));
285  }
286  }
287  return list;
288  }
289 
290  /// <summary>
291  /// Finds the asset items by their input/import file.
292  /// </summary>
293  /// <param name="importFile">The import file.</param>
294  /// <returns>A list of assets that are imported from the specified import file.</returns>
295  /// <exception cref="System.ArgumentNullException">importFile</exception>
296  public HashSet<Guid> FindAssetIdsByInput(string importFile)
297  {
298  if (importFile == null) throw new ArgumentNullException("importFile");
299  lock (Initialize())
300  {
301  HashSet<Guid> assets;
302  if (mapInputDependencyToAssets.TryGetValue(importFile, out assets))
303  {
304  return new HashSet<Guid>(assets);
305  }
306  return new HashSet<Guid>();
307  }
308  }
309 
310  /// <summary>
311  /// Finds the asset items by their input/import file.
312  /// </summary>
313  /// <param name="importFile">The import file.</param>
314  /// <returns>A list of assets that are imported from the specified import file.</returns>
315  /// <exception cref="System.ArgumentNullException">importFile</exception>
316  public HashSet<AssetItem> FindAssetItemsByInput(string importFile)
317  {
318  if (importFile == null) throw new ArgumentNullException("importFile");
319  lock (Initialize())
320  {
321  var ids = FindAssetIdsByInput(importFile);
322  var items = new HashSet<AssetItem>(AssetItem.DefaultComparerById);
323  foreach (var id in ids)
324  {
325  items.Add(Dependencies[id].Item.Clone(true));
326  }
327  return items;
328  }
329  }
330 
331  /// <summary>
332  /// Computes the dependencies for the specified asset.
333  /// </summary>
334  /// <param name="assetItem">The asset item.</param>
335  /// <param name="dependenciesOptions">The dependencies options.</param>
336  /// <returns>The dependencies.</returns>
337  public AssetDependencySet ComputeDependencies(AssetItem assetItem, AssetDependencySearchOptions dependenciesOptions = AssetDependencySearchOptions.All, HashSet<Guid> visited = null)
338  {
339  if (assetItem == null) throw new ArgumentNullException("assetItem");
340  bool recursive = (dependenciesOptions & AssetDependencySearchOptions.Recursive) != 0;
341  if (visited == null && recursive)
342  visited = new HashSet<Guid>();
343 
344  //var clock = Stopwatch.StartNew();
345 
346  lock (Initialize())
347  {
348  var dependencySet = new AssetDependencySet(assetItem);
349 
350  int inCount = 0, outCount = 0;
351 
352  if ((dependenciesOptions & AssetDependencySearchOptions.In) != 0)
353  {
354  CollectInputReferences(dependencySet, assetItem, visited, recursive, ref inCount);
355  }
356 
357  if ((dependenciesOptions & AssetDependencySearchOptions.Out) != 0)
358  {
359  if (visited != null)
360  {
361  visited.Clear();
362  }
363  CollectOutputReferences(dependencySet, assetItem, visited, recursive, ref outCount);
364  }
365 
366  //Console.WriteLine("Time to compute dependencies: {0}ms in: {1} out:{2}", clock.ElapsedMilliseconds, inCount, outCount);
367 
368  return dependencySet;
369  }
370 
371  }
372 
373  /// <summary>
374  /// Gets a value indicating whether there is any missing references.
375  /// </summary>
376  /// <value><c>true</c> if this instance has missing references; otherwise, <c>false</c>.</value>
377  public bool HasMissingReferences
378  {
379  get
380  {
381  lock (Initialize())
382  {
383  return AssetsWithMissingReferences.Count > 0;
384  }
385  }
386  }
387 
388  /// <summary>
389  /// Finds the assets with missing references.
390  /// </summary>
391  /// <returns>An enumeration of asset guid that have missing references.</returns>
393  {
394  lock (Initialize())
395  {
396  return AssetsWithMissingReferences.Keys.ToList();
397  }
398  }
399 
400  /// <summary>
401  /// Finds the missing references for a particular asset.
402  /// </summary>
403  /// <param name="assetId">The asset identifier.</param>
404  /// <returns>IEnumerable{IContentReference}.</returns>
405  /// <exception cref="System.ArgumentNullException">item</exception>
407  {
408  lock (Initialize())
409  {
410  AssetDependencySet dependencySet;
411  if (AssetsWithMissingReferences.TryGetValue(assetId, out dependencySet))
412  {
413  return dependencySet.MissingReferences.ToList();
414  }
415  }
416 
417  return Enumerable.Empty<IContentReference>();
418  }
419 
420  private object Initialize()
421  {
422  lock (ThisLock)
423  {
424  if (isInitialized)
425  {
426  return ThisLock;
427  }
428 
429  // If the package is cancelled, don't try to do anything
430  // A cancellation means that the package session will be destroyed
431  if (isDisposing)
432  {
433  return ThisLock;
434  }
435 
436  // Initialize with the list of packages
437  foreach (var package in session.Packages)
438  {
439  // If the package is cancelled, don't try to do anything
440  // A cancellation means that the package session will be destroyed
441  if (isDisposing)
442  {
443  return ThisLock;
444  }
445 
446  TrackPackage(package);
447  }
448 
449  isInitialized = true;
450  }
451  return ThisLock;
452  }
453 
454  /// <summary>
455  /// Collects all references of an asset dynamically.
456  /// </summary>
457  /// <param name="result">The result.</param>
458  /// <param name="packageSession">The package session.</param>
459  /// <param name="isRecursive">if set to <c>true</c> [is recursive].</param>
460  /// <exception cref="System.ArgumentNullException">packageSession</exception>
461  private static void CollectDynamicOutReferences(AssetDependencySet result, PackageSession packageSession, bool isRecursive, bool keepParents)
462  {
463  if (packageSession == null) throw new ArgumentNullException("packageSession");
464  CollectDynamicOutReferences(result, packageSession.FindAsset, isRecursive, keepParents);
465  }
466 
467  /// <summary>
468  /// Collects all references of an asset dynamically.
469  /// </summary>
470  /// <param name="result">The result.</param>
471  /// <param name="assetResolver">The asset resolver.</param>
472  /// <param name="isRecursive">if set to <c>true</c> collects references recursively.</param>
473  /// <exception cref="System.ArgumentNullException">
474  /// result
475  /// or
476  /// assetResolver
477  /// </exception>
478  private static void CollectDynamicOutReferences(AssetDependencySet result, Func<Guid, AssetItem> assetResolver, bool isRecursive, bool keepParents)
479  {
480  if (result == null) throw new ArgumentNullException("result");
481  if (assetResolver == null) throw new ArgumentNullException("assetResolver");
482 
483  var addedReferences = new HashSet<Guid>();
484  var itemsToAnalyze = new Queue<AssetItem>();
485  var referenceCollector = new DependenciesCollector();
486 
487  result.Reset(keepParents);
488 
489  var assetItem = result.Item;
490 
491  // marked as processed to not add it again
492  addedReferences.Add(assetItem.Id);
493  itemsToAnalyze.Enqueue(assetItem);
494 
495  while (itemsToAnalyze.Count > 0)
496  {
497  var item = itemsToAnalyze.Dequeue();
498 
499  foreach (var contentReference in referenceCollector.GetDependencies(item))
500  {
501  if (addedReferences.Contains(contentReference.Id))
502  continue;
503 
504  // marked as processed to not add it again
505  addedReferences.Add(contentReference.Id);
506 
507  // add the location to the reference location list
508  var nextItem = assetResolver(contentReference.Id);
509  if (nextItem != null)
510  {
511  result.Add(nextItem);
512 
513  // add current element to analyze list, to analyze dependencies recursively
514  if (isRecursive)
515  {
516  itemsToAnalyze.Enqueue(nextItem);
517  }
518  }
519  else
520  {
521  result.AddMissingReference(contentReference);
522  }
523  }
524 
525  if (!isRecursive)
526  {
527  break;
528  }
529  }
530  }
531 
532  private AssetItem FindAssetFromDependencyOrSession(Guid guid)
533  {
534  // We cannot return the item from the session but we can only return assets currently tracked by the dependency
535  // manager
536  var item = session.FindAsset(guid);
537  if (item != null)
538  {
539  var dependencies = TrackAsset(guid);
540  return dependencies.Item;
541  }
542  return null;
543  }
544 
545  /// <summary>
546  /// This methods is called when a session is about to being saved.
547  /// </summary>
548  public void BeginSavingSession()
549  {
550  isSessionSaving = true;
551  }
552 
553  /// <summary>
554  /// Adds the file being save during session save.
555  /// </summary>
556  /// <param name="file">The file.</param>
557  /// <exception cref="System.ArgumentNullException">file</exception>
558  internal void AddFileBeingSaveDuringSessionSave(UFile file)
559  {
560  if (file == null) throw new ArgumentNullException("file");
561  if (!isSessionSaving) throw new InvalidOperationException("Cannot call this method outside a BeginSavingSession/EndSavingSession");
562 
563  lock (Initialize())
564  {
565  assetsBeingSaved.Add(file);
566  }
567  }
568 
569  /// <summary>
570  /// This methods is called when a session has been saved.
571  /// </summary>
572  public void EndSavingSession()
573  {
574  isSessionSaving = false;
575 
576  // After saving, we must double-check that all packages are tracked correctly
577  lock (Initialize())
578  {
579  foreach (var package in Packages)
580  {
581  UpdatePackagePathTracked(package, true);
582  }
583  }
584  }
585 
586  /// <summary>
587  /// Calculate the dependencies for the specified asset either by using the internal cache if the asset is already in the session
588  /// or by calculating
589  /// </summary>
590  /// <param name="assetItem">The asset item.</param>
591  /// <returns>The dependencies.</returns>
592  private AssetDependencySet CalculateDependencies(AssetItem assetItem)
593  {
594  AssetDependencySet dependencySet;
595  if (!Dependencies.TryGetValue(assetItem.Id, out dependencySet))
596  {
597  // If the asset is not followed by this instance (this could not be part of the session)
598  // We are allocating a new dependency on the fly and calculating first level dependencies
599  dependencySet = new AssetDependencySet(assetItem);
600  CollectDynamicOutReferences(dependencySet, FindAssetFromDependencyOrSession, false, false);
601  }
602  return dependencySet;
603  }
604 
605  /// <summary>
606  /// This method is called when a package needs to be tracked
607  /// </summary>
608  /// <param name="package">The package to track.</param>
609  private void TrackPackage(Package package)
610  {
611  lock (ThisLock)
612  {
613  if (Packages.Contains(package))
614  return;
615 
616  Packages.Add(package);
617 
618  foreach (var asset in package.Assets)
619  {
620  // If the package is cancelled, don't try to do anything
621  // A cancellation means that the package session will be destroyed
622  if (isDisposing)
623  {
624  return;
625  }
626 
627  TrackAsset(asset);
628  }
629 
630  package.Assets.CollectionChanged += Assets_CollectionChanged;
631  UpdatePackagePathTracked(package, true);
632  }
633  }
634 
635  /// <summary>
636  /// This method is called when a package needs to be un-tracked
637  /// </summary>
638  /// <param name="package">The package to un-track.</param>
639  private void UnTrackPackage(Package package)
640  {
641  lock (ThisLock)
642  {
643  if (!Packages.Contains(package))
644  return;
645 
646  package.Assets.CollectionChanged -= Assets_CollectionChanged;
647 
648  foreach (var asset in package.Assets)
649  {
650  UnTrackAsset(asset);
651  }
652 
653  Packages.Remove(package);
654  UpdatePackagePathTracked(package, false);
655  }
656  }
657 
658  /// <summary>
659  /// This method is called when an asset needs to be tracked
660  /// </summary>
661  /// <param name="assetItemSource">The asset item source.</param>
662  /// <returns>AssetDependencySet.</returns>
663  private AssetDependencySet TrackAsset(AssetItem assetItemSource)
664  {
665  return TrackAsset(assetItemSource.Id);
666  }
667 
668  /// <summary>
669  /// This method is called when an asset needs to be tracked
670  /// </summary>
671  /// <returns>AssetDependencySet.</returns>
672  private AssetDependencySet TrackAsset(Guid assetId)
673  {
674  lock (ThisLock)
675  {
676  AssetDependencySet dependencies;
677  if (Dependencies.TryGetValue(assetId, out dependencies))
678  return dependencies;
679 
680  // TODO provide an optimized version of TrackAsset method
681  // taking directly a well known asset (loaded from a Package...etc.)
682  // to avoid session.FindAsset
683  var assetItem = session.FindAsset(assetId);
684  if (assetItem == null)
685  {
686  return null;
687  }
688 
689  // Clone the asset before using it in this instance to make sure that
690  // we have some kind of immutable state
691  // TODO: This is not handling shadow registry
692 
693  // No need to clone assets from readonly package
694  var assetItemCloned = assetItem.Package.IsSystem
695  ? assetItem
696  : new AssetItem(assetItem.Location, (Asset)AssetCloner.Clone(assetItem.Asset))
697  {
698  Package = assetItem.Package,
699  SourceFolder = assetItem.SourceFolder
700  };
701 
702  dependencies = new AssetDependencySet(assetItemCloned);
703 
704  // Adds to global list
705  Dependencies.Add(assetId, dependencies);
706 
707  // Update dependencies
708  UpdateAssetDependencies(dependencies);
709  CheckAllDependencies();
710 
711  return dependencies;
712  }
713  }
714 
715  private void CheckAllDependencies()
716  {
717  //foreach (var dependencies in Dependencies.Values)
718  //{
719  // foreach (var outDependencies in dependencies)
720  // {
721  // if (outDependencies.Package == null)
722  // {
723  // System.Diagnostics.Debugger.Break();
724  // }
725  // }
726  //}
727  }
728 
729  /// <summary>
730  /// This method is called when an asset needs to be un-tracked
731  /// </summary>
732  /// <param name="assetItemSource">The asset item source.</param>
733  private void UnTrackAsset(AssetItem assetItemSource)
734  {
735  lock (ThisLock)
736  {
737  var assetId = assetItemSource.Id;
738  AssetDependencySet dependencySet;
739  if (!Dependencies.TryGetValue(assetId, out dependencySet))
740  return;
741 
742  // Remove from global list
743  Dependencies.Remove(assetId);
744 
745  // Remove previous missing dependencies
746  RemoveMissingDependencies(dependencySet);
747 
748  // Update [In] dependencies for children
749  foreach (var childItem in dependencySet)
750  {
751  AssetDependencySet childDependencyItem;
752  if (Dependencies.TryGetValue(childItem.Id, out childDependencyItem))
753  {
754  childDependencyItem.Parents.Remove(dependencySet.Item);
755  }
756  }
757 
758  // Update [Out] dependencies for parents
759  var missingReference = dependencySet.Item.ToReference();
760  foreach (var parentItem in dependencySet.Parents)
761  {
762  var parentDependencySet = Dependencies[parentItem.Id];
763  parentDependencySet.Remove(dependencySet.Item);
764  parentDependencySet.AddMissingReference(missingReference);
765 
766  UpdateMissingDependencies(parentDependencySet);
767  }
768 
769  // Track asset import paths
770  UpdateAssetImportPathsTracked(dependencySet.Item, false);
771  }
772 
773  CheckAllDependencies();
774  }
775 
776  private void UpdateAssetDependencies(AssetDependencySet dependencySet)
777  {
778  lock (ThisLock)
779  {
780  // Track asset import paths
781  UpdateAssetImportPathsTracked(dependencySet.Item, true);
782 
783  // Remove previous missing dependencies
784  RemoveMissingDependencies(dependencySet);
785 
786  // Remove [In] dependencies from previous children
787  foreach (var referenceAsset in dependencySet)
788  {
789  var childDependencyItem = TrackAsset(referenceAsset);
790  if (childDependencyItem != null)
791  {
792  childDependencyItem.Parents.Remove(dependencySet.Item);
793  }
794  }
795 
796  // Recalculate [Out] depedencies
797  CollectDynamicOutReferences(dependencySet, FindAssetFromDependencyOrSession, false, true);
798 
799  // Add [In] dependencies to new children
800  foreach (var referenceAsset in dependencySet)
801  {
802  var childDependencyItem = TrackAsset(referenceAsset);
803  if (childDependencyItem != null)
804  {
805  childDependencyItem.Parents.Add(dependencySet.Item);
806  }
807  }
808 
809  // Update missing dependencies
810  UpdateMissingDependencies(dependencySet);
811  }
812  }
813 
814  private void RemoveMissingDependencies(AssetDependencySet dependencySet)
815  {
816  if (AssetsWithMissingReferences.ContainsKey(dependencySet.Item.Id))
817  {
818  AssetsWithMissingReferences.Remove(dependencySet.Item.Id);
819  foreach (var reference in dependencySet.MissingReferences)
820  {
821  var list = MissingReferencesToParent[reference.Id];
822  list.Remove(dependencySet);
823  if (list.Count == 0)
824  {
825  MissingReferencesToParent.Remove(reference.Id);
826  }
827  }
828  }
829  }
830 
831  private void UpdateMissingDependencies(AssetDependencySet dependencySet)
832  {
833  HashSet<AssetDependencySet> parentDependencyItems;
834  // If the asset has any missing dependencies, update the fast lookup tables
835  if (dependencySet.HasMissingReferences)
836  {
837  AssetsWithMissingReferences[dependencySet.Item.Id] = dependencySet;
838 
839  foreach (var reference in dependencySet.MissingReferences)
840  {
841  if (!MissingReferencesToParent.TryGetValue(reference.Id, out parentDependencyItems))
842  {
843  parentDependencyItems = new HashSet<AssetDependencySet>();
844  MissingReferencesToParent.Add(reference.Id, parentDependencyItems);
845  }
846 
847  parentDependencyItems.Add(dependencySet);
848  }
849  }
850 
851  var item = dependencySet.Item;
852 
853  // If the new asset was a missing reference, remove all missing references for this asset
854  if (MissingReferencesToParent.TryGetValue(item.Id, out parentDependencyItems))
855  {
856  MissingReferencesToParent.Remove(item.Id);
857  foreach (var dependency in parentDependencyItems)
858  {
859  // Remove missing dependency from parent
860  dependency.RemoveMissingReference(item.Id);
861 
862  // Update [Out] dependency to parent
863  dependency.Add(item);
864 
865  // Update [In] dependency to current
866  dependencySet.Parents.Add(dependency.Item);
867 
868  // Remove global cache for assets with missing references
869  if (!dependency.HasMissingReferences)
870  {
871  AssetsWithMissingReferences.Remove(dependency.Item.Id);
872  }
873  }
874  }
875  }
876 
877  private void UpdatePackagePathTracked(Package package, bool isTracking)
878  {
879  // Don't try to track system package
880  if (package.IsSystem)
881  {
882  return;
883  }
884 
885  lock (ThisLock)
886  {
887  if (isTracking)
888  {
889  string previousLocation;
890  packagePathsTracked.TryGetValue(package, out previousLocation);
891 
892  string newLocation = package.RootDirectory;
893  bool trackNewLocation = newLocation != null && Directory.Exists(newLocation);
894  if (previousLocation != null)
895  {
896  bool unTrackPreviousLocation = false;
897  // If the package has no longer any directory, we have to only remove it from previous tracked
898  if (package.RootDirectory == null)
899  {
900  unTrackPreviousLocation = true;
901  trackNewLocation = false;
902  }
903  else
904  {
905  newLocation = package.RootDirectory;
906  if (string.Compare(previousLocation, newLocation, StringComparison.OrdinalIgnoreCase) != 0)
907  {
908  unTrackPreviousLocation = true;
909  }
910  else
911  {
912  // Nothing to do, previous location is the same
913  trackNewLocation = false;
914  }
915  }
916 
917  // Untrack the previous location if different
918  if (unTrackPreviousLocation)
919  {
920  packagePathsTracked.Remove(package);
921  if (DirectoryWatcher != null)
922  {
923  DirectoryWatcher.UnTrack(previousLocation);
924  }
925  }
926  }
927 
928  if (trackNewLocation)
929  {
930  // Track the location
931  packagePathsTracked[package] = newLocation;
932  if (DirectoryWatcher != null)
933  {
934  DirectoryWatcher.Track(newLocation);
935  }
936  }
937  }
938  else
939  {
940  string previousLocation;
941  if (packagePathsTracked.TryGetValue(package, out previousLocation))
942  {
943  // Untrack the previous location
944  if (DirectoryWatcher != null)
945  {
946  DirectoryWatcher.UnTrack(previousLocation);
947  }
948  packagePathsTracked.Remove(package);
949  }
950  }
951  }
952  }
953 
954  private void TrackAssetImportInput(AssetItem assetItem, string inputPath)
955  {
956  lock (ThisLock)
957  {
958  HashSet<Guid> assetsTrackedByPath;
959  if (!mapInputDependencyToAssets.TryGetValue(inputPath, out assetsTrackedByPath))
960  {
961  assetsTrackedByPath = new HashSet<Guid>();
962  mapInputDependencyToAssets.Add(inputPath, assetsTrackedByPath);
963  if (DirectoryWatcher != null)
964  {
965  DirectoryWatcher.Track(inputPath);
966  }
967  }
968  assetsTrackedByPath.Add(assetItem.Id);
969  }
970 
971  // We will always issue a compute of the hash in order to verify SourceHash haven't changed
972  FileVersionManager.Instance.ComputeFileHashAsync(inputPath, SourceImportFileHashCallback, tokenSourceForImportHash.Token);
973  }
974 
975  private void ActivateTracking()
976  {
977  List<string> files;
978  lock (ThisLock)
979  {
980  files = mapInputDependencyToAssets.Keys.ToList();
981  }
982  foreach (var inputPath in files)
983  {
984  DirectoryWatcher.Track(inputPath);
985  FileVersionManager.Instance.ComputeFileHashAsync(inputPath, SourceImportFileHashCallback, tokenSourceForImportHash.Token);
986  }
987  }
988 
989  private void UnTrackAssetImportInput(AssetItem assetItem, string inputPath)
990  {
991  lock (ThisLock)
992  {
993  HashSet<Guid> assetsTrackedByPath;
994  if (mapInputDependencyToAssets.TryGetValue(inputPath, out assetsTrackedByPath))
995  {
996  assetsTrackedByPath.Remove(assetItem.Id);
997  if (assetsTrackedByPath.Count == 0)
998  {
999  mapInputDependencyToAssets.Remove(inputPath);
1000  if (DirectoryWatcher != null)
1001  {
1002  DirectoryWatcher.UnTrack(inputPath);
1003  }
1004  }
1005  }
1006  }
1007  }
1008 
1009  private void UpdateAssetImportPathsTracked(AssetItem assetItem, bool isTracking)
1010  {
1011  // Only handle AssetImport
1012  var assetImport = assetItem.Asset as AssetImport;
1013  if (assetImport == null)
1014  {
1015  return;
1016  }
1017 
1018  if (isTracking)
1019  {
1020  // Currently an AssetImport is linked only to a single entry, but it could have probably have multiple input dependencies in the future
1021  var newInputPathDependencies = new HashSet<UFile>();
1022  if (assetImport.Base != null && assetImport.Base.IsRootImport)
1023  {
1024  var pathToSourceRawAsset = assetImport.Source;
1025  if (pathToSourceRawAsset == null)
1026  {
1027  return;
1028  }
1029  if (!pathToSourceRawAsset.IsAbsolute)
1030  {
1031  pathToSourceRawAsset = UPath.Combine(assetItem.FullPath.GetParent(), pathToSourceRawAsset);
1032  }
1033 
1034  newInputPathDependencies.Add(pathToSourceRawAsset);
1035  }
1036 
1037  HashSet<UFile> inputPaths;
1038  if (mapAssetToInputDependencies.TryGetValue(assetItem.Id, out inputPaths))
1039  {
1040  // Untrack previous paths
1041  foreach (var inputPath in inputPaths)
1042  {
1043  if (!newInputPathDependencies.Contains(inputPath))
1044  {
1045  UnTrackAssetImportInput(assetItem, inputPath);
1046  }
1047  }
1048 
1049  // Track new paths
1050  foreach (var inputPath in newInputPathDependencies)
1051  {
1052  if (!inputPaths.Contains(inputPath))
1053  {
1054  TrackAssetImportInput(assetItem, inputPath);
1055  }
1056  }
1057  }
1058  else
1059  {
1060  // Track new paths
1061  foreach (var inputPath in newInputPathDependencies)
1062  {
1063  TrackAssetImportInput(assetItem, inputPath);
1064  }
1065  }
1066 
1067  mapAssetToInputDependencies[assetItem.Id] = newInputPathDependencies;
1068  }
1069  else
1070  {
1071  HashSet<UFile> inputPaths;
1072  if (mapAssetToInputDependencies.TryGetValue(assetItem.Id, out inputPaths))
1073  {
1074  mapAssetToInputDependencies.Remove(assetItem.Id);
1075  foreach (var inputPath in inputPaths)
1076  {
1077  UnTrackAssetImportInput(assetItem, inputPath);
1078  }
1079  }
1080  }
1081  }
1082 
1083  private void directoryWatcher_Modified(object sender, FileEvent e)
1084  {
1085  // If trakcing is not enabled, don't bother to track files on disk
1086  if (!EnableTracking)
1087  return;
1088 
1089  // Store only the most recent events
1090  lock (fileEvents)
1091  {
1092  fileEvents.Add(e);
1093  }
1094  }
1095 
1096  private void Session_AssetDirtyChanged(Asset asset)
1097  {
1098  // Don't update assets while saving
1099  // This is to avoid updating the dependency manager when saving an asset
1100  // TODO: We should handle assets modification while saving differently
1101  if (isSessionSaving)
1102  {
1103  return;
1104  }
1105 
1106  lock (ThisLock)
1107  {
1108  AssetDependencySet dependencySet;
1109  if (Dependencies.TryGetValue(asset.Id, out dependencySet))
1110  {
1111  dependencySet.Item.Asset = (Asset)AssetCloner.Clone(asset);
1112  UpdateAssetDependencies(dependencySet);
1113 
1114  // Notify an asset changed
1115  OnAssetChanged(dependencySet.Item);
1116  }
1117  else
1118  {
1119  var package = asset as Package;
1120  if (package != null)
1121  {
1122  UpdatePackagePathTracked(package, true);
1123  }
1124  }
1125  }
1126 
1127  CheckAllDependencies();
1128  }
1129 
1130  private void Packages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
1131  {
1132  switch (e.Action)
1133  {
1134  case NotifyCollectionChangedAction.Add:
1135  TrackPackage((Package)e.NewItems[0]);
1136  break;
1137  case NotifyCollectionChangedAction.Remove:
1138  UnTrackPackage((Package)e.OldItems[0]);
1139  break;
1140 
1141  case NotifyCollectionChangedAction.Replace:
1142  foreach (var oldPackage in e.OldItems.OfType<Package>())
1143  {
1144  UnTrackPackage(oldPackage);
1145  }
1146 
1147  foreach (var packageToCopy in session.Packages)
1148  {
1149  TrackPackage(packageToCopy);
1150  }
1151  break;
1152  }
1153  }
1154 
1155  private void Assets_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
1156  {
1157  switch (e.Action)
1158  {
1159  case NotifyCollectionChangedAction.Add:
1160  TrackAsset(((AssetItem)e.NewItems[0]));
1161  break;
1162  case NotifyCollectionChangedAction.Remove:
1163  UnTrackAsset(((AssetItem)e.OldItems[0]));
1164  break;
1165 
1166  case NotifyCollectionChangedAction.Reset:
1167  var collection = (PackageAssetCollection)sender;
1168 
1169  var items = Dependencies.Values.Where(item => ReferenceEquals(item.Item.Package, collection.Package)).ToList();
1170  foreach (var assetItem in items)
1171  {
1172  UnTrackAsset(assetItem.Item);
1173  }
1174  foreach (var assetItem in collection)
1175  {
1176  TrackAsset(assetItem);
1177  }
1178  break;
1179  }
1180  }
1181 
1182  /// <summary>
1183  /// This method is running in a separate thread and process file events received from <see cref="Core.IO.DirectoryWatcher"/>
1184  /// in order to generate the appropriate list of <see cref="AssetFileChangedEvent"/>.
1185  /// </summary>
1186  private void RunChangeWatcher()
1187  {
1188  // Only check every minute
1189  while (true)
1190  {
1191  if (threadWatcherEvent.WaitOne(TrackingSleepTime))
1192  break;
1193 
1194  // Use a working copy in order to limit the locking
1195  fileEventsWorkingCopy.Clear();
1196  lock (fileEvents)
1197  {
1198  fileEventsWorkingCopy.AddRange(fileEvents);
1199  fileEvents.Clear();
1200  }
1201 
1202  if (fileEventsWorkingCopy.Count == 0 || isTrackingPaused)
1203  continue;
1204 
1205  var assetEvents = new List<AssetFileChangedEvent>();
1206 
1207  // If this an asset belonging to a package
1208  lock (ThisLock)
1209  {
1210  var packages = Packages;
1211 
1212  // File event
1213  foreach (var fileEvent in fileEventsWorkingCopy)
1214  {
1215  var file = new UFile(fileEvent.FullPath);
1216 
1217  // When the session is being saved, we should not process events are they are false-positive alerts
1218  // So we just skip the file
1219  if (assetsBeingSaved.Contains(file.FullPath))
1220  {
1221  continue;
1222  }
1223 
1224  // 1) Check if this is related to imported assets
1225  // Asset imports are handled slightly differently as we need to compute the
1226  // hash of the source file
1227  if (mapInputDependencyToAssets.ContainsKey(file.FullPath))
1228  {
1229  // Prepare the hash of the import file in advance for later re-import
1230  FileVersionManager.Instance.ComputeFileHashAsync(file.FullPath, SourceImportFileHashCallback, tokenSourceForImportHash.Token);
1231  continue;
1232  }
1233 
1234  // 2) else check that the file is a supported extension
1235  if (!AssetRegistry.IsAssetFileExtension(file.GetFileExtension()))
1236  {
1237  continue;
1238  }
1239 
1240  // Find the parent package of the file that has been updated
1241  UDirectory parentPackagePath = null;
1242  Package parentPackage = null;
1243  foreach (var package in packages)
1244  {
1245  var rootDirectory = package.RootDirectory;
1246  if (rootDirectory == null)
1247  continue;
1248 
1249  if (rootDirectory.Contains(file) && (parentPackagePath == null || parentPackagePath.FullPath.Length < rootDirectory.FullPath.Length))
1250  {
1251  parentPackagePath = rootDirectory;
1252  parentPackage = package;
1253  }
1254  }
1255 
1256  // If we found a parent package, create an associated asset event
1257  if (parentPackage != null)
1258  {
1259  var relativeLocation = file.MakeRelative(parentPackagePath);
1260 
1261  var item = parentPackage.Assets.Find(relativeLocation);
1262  AssetFileChangedEvent evt = null;
1263  switch (fileEvent.ChangeType)
1264  {
1265  case FileEventChangeType.Created:
1266  evt = new AssetFileChangedEvent(parentPackage, AssetFileChangedType.Added, relativeLocation);
1267  break;
1268  case FileEventChangeType.Deleted:
1269  evt = new AssetFileChangedEvent(parentPackage, AssetFileChangedType.Deleted, relativeLocation);
1270  break;
1271  case FileEventChangeType.Changed:
1272  evt = new AssetFileChangedEvent(parentPackage, AssetFileChangedType.Updated, relativeLocation);
1273  break;
1274  }
1275  if (evt != null)
1276  {
1277  if (item != null)
1278  {
1279  evt.AssetId = item.Id;
1280  }
1281  assetEvents.Add(evt);
1282  }
1283  }
1284  }
1285 
1286 
1287  // After all events have been processed, we
1288  // remove the file assetBeingSaved
1289  foreach (var fileEvent in fileEventsWorkingCopy)
1290  {
1291  var file = new UFile(fileEvent.FullPath);
1292 
1293  // When the session is being saved, we should not process events are they are false-positive alerts
1294  // So we just skip the file
1295  if (assetsBeingSaved.Remove(file))
1296  {
1297  if (assetsBeingSaved.Count == 0)
1298  {
1299  break;
1300  }
1301  }
1302  }
1303 
1304 
1305  // If we have any new events, copy them back
1306  if (assetEvents.Count > 0 && !isTrackingPaused)
1307  {
1308  currentAssetFileChangedEvents.AddRange(assetEvents);
1309  }
1310  }
1311  }
1312  }
1313 
1314  /// <summary>
1315  /// This callback is receiving hash calculated from asset source file. If the source hash is changing from what
1316  /// we had previously stored, we can emit a <see cref="AssetFileChangedType.SourceUpdated" /> event.
1317  /// </summary>
1318  /// <param name="sourceFile">The source file.</param>
1319  /// <param name="hash">The object identifier hash calculated from this source file.</param>
1320  private void SourceImportFileHashCallback(UFile sourceFile, ObjectId hash)
1321  {
1322  lock (ThisLock)
1323  {
1324  HashSet<Guid> items;
1325  if (!mapInputDependencyToAssets.TryGetValue(sourceFile, out items))
1326  {
1327  return;
1328  }
1329  foreach (var itemId in items)
1330  {
1331  AssetDependencySet dependencySet;
1332  Dependencies.TryGetValue(itemId, out dependencySet);
1333  if (dependencySet == null)
1334  {
1335  continue;
1336  }
1337 
1338  var item = dependencySet.Item;
1339 
1340  var assetImport = item.Asset as AssetImportTracked;
1341  if (assetImport != null && assetImport.Base != null && assetImport.Base.IsRootImport && assetImport.SourceHash != hash)
1342  {
1343  // If the hash is empty, the source file has been deleted
1344  var changeType = (hash == ObjectId.Empty) ? AssetFileChangedType.SourceDeleted : AssetFileChangedType.SourceUpdated;
1345 
1346  // Transmit the hash in the event as well, so that we can check again if the asset has not been updated during the async round-trip
1347  // (it happens when reimporting multiple assets at once).
1348  sourceImportFileChangedEventsToAdd.Add(new AssetFileChangedEvent(item.Package, changeType, item.Location) { AssetId = assetImport.Id, Hash = hash });
1349  }
1350  }
1351 
1352  if (sourceImportFileChangedEventsToAdd.Count > 0 && !isTrackingPaused)
1353  {
1354  currentAssetFileChangedEvents.AddRange(sourceImportFileChangedEventsToAdd);
1355  }
1356  sourceImportFileChangedEventsToAdd.Clear();
1357  }
1358  }
1359 
1360  private void CollectInputReferences(AssetDependencySet dependencyRoot, AssetItem assetItem, HashSet<Guid> visited, bool recursive, ref int count)
1361  {
1362  var assetId = assetItem.Id;
1363  if (visited != null)
1364  {
1365  if (visited.Contains(assetId))
1366  return;
1367 
1368  visited.Add(assetId);
1369  }
1370 
1371  count++;
1372 
1373  AssetDependencySet dependencies;
1374  Dependencies.TryGetValue(assetId, out dependencies);
1375  if (dependencies != null)
1376  {
1377  foreach (var parentItem in dependencies.Parents)
1378  {
1379  dependencyRoot.Parents.Add(parentItem.Clone(true));
1380 
1381  if (visited != null && recursive)
1382  {
1383  CollectInputReferences(dependencyRoot, parentItem, visited, recursive, ref count);
1384  }
1385  }
1386  }
1387  }
1388 
1389  private void CollectOutputReferences(AssetDependencySet dependencyRoot, AssetItem assetItem, HashSet<Guid> visited, bool recursive, ref int count)
1390  {
1391  var assetId = assetItem.Id;
1392  if (visited != null)
1393  {
1394  if (visited.Contains(assetId))
1395  return;
1396 
1397  visited.Add(assetId);
1398  }
1399 
1400  count++;
1401 
1402  var dependencies = CalculateDependencies(assetItem);
1403 
1404  // Add missing references
1405  foreach (var missingRef in dependencies.MissingReferences)
1406  {
1407  dependencyRoot.AddMissingReference(missingRef);
1408  }
1409 
1410  // Add output references
1411  foreach (var child in dependencies)
1412  {
1413  dependencyRoot.Add(child.Clone(true));
1414 
1415  if (visited != null && recursive)
1416  {
1417  CollectOutputReferences(dependencyRoot, child, visited, recursive, ref count);
1418  }
1419  }
1420  }
1421 
1422  /// <summary>
1423  /// An interface providing methods to collect of asset references from an <see cref="AssetItem"/>.
1424  /// </summary>
1425  private interface IDependenciesCollector
1426  {
1427  /// <summary>
1428  /// Get the asset references of an <see cref="AssetItem"/>. This function is not recursive.
1429  /// </summary>
1430  /// <param name="item">The item we when the references of</param>
1431  /// <returns></returns>
1432  IEnumerable<IContentReference> GetDependencies(AssetItem item);
1433  }
1434 
1435  /// <summary>
1436  /// Visitor that collect all asset references.
1437  /// </summary>
1438  private class DependenciesCollector : AssetVisitorBase, IDependenciesCollector
1439  {
1440  private readonly HashSet<IContentReference> collectedReferences = new HashSet<IContentReference>();
1441 
1442  public IEnumerable<IContentReference> GetDependencies(AssetItem item)
1443  {
1444  collectedReferences.Clear();
1445 
1446  Visit(item);
1447 
1448  return collectedReferences;
1449  }
1450  public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers)
1451  {
1452  var reference = obj as IContentReference;
1453  if (reference != null)
1454  {
1455  // Don't record base import
1456  if (reference is AssetBase && ((AssetBase)reference).IsRootImport)
1457  {
1458  return;
1459  }
1460 
1461  collectedReferences.Add(reference);
1462  }
1463  else // recursive visit
1464  {
1465  base.VisitObject(obj, descriptor, visitMembers);
1466  }
1467  }
1468  }
1469 
1470  private void OnAssetChanged(AssetItem obj)
1471  {
1472  Action<AssetItem> handler = AssetChanged;
1473  // Make sure we clone the item here only if it is necessary
1474  // Cloning the AssetItem is mandatory in order to make sure
1475  // the asset item won't change
1476  if (handler != null) handler(obj.Clone(true));
1477  }
1478  }
1479 }
IEnumerable< Package > LocalPackages
Gets the user packages (excluding system packages).
void EndSavingSession()
This methods is called when a session has been saved.
Guid Id
Gets the unique identifier of this asset.
Definition: AssetItem.cs:85
Default implementation of a ITypeDescriptor.
void Dispose()
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resourc...
static ThreadStart Wrap(ThreadStart action, [CallerFilePath] string sourceFilePath="", [CallerMemberName] string memberName="", [CallerLineNumber] int sourceLineNumber=0)
Definition: SafeAction.cs:13
The template can be applied to an existing Assets.Package.
An asset item part of a Package accessible through SiliconStudio.Assets.Package.Assets.
Definition: AssetItem.cs:17
IEnumerable< AssetFileChangedEvent > FindAssetFileChangedEvents()
Finds the changed events that have occured, only valid if EnableTracking is set to true...
void BeginSavingSession()
This methods is called when a session is about to being saved.
HashSet< AssetItem > FindAssetItemsByInput(string importFile)
Finds the asset items by their input/import file.
A session for editing a package.
Describes dependencies (in/out/miss) for a specific asset.
_In_ size_t count
Definition: DirectXTexP.h:174
AssetDependencySet FindDependencySet(Guid assetId)
Finds the dependencies for the specified asset.
Defines a normalized directory path. See UPath for details. This class cannot be inherited.
Definition: UDirectory.cs:13
List< AssetItem > FindAssetsInheritingFrom(Guid assetId)
Finds the assets inheriting from the specified asset id (this is a direct inheritance, not indirect).
HashSet< Guid > FindAssetIdsByInput(string importFile)
Finds the asset items by their input/import file.
Track file system events from several directories.
A class responsible for providing asset dependencies for a PackageSession and file tracking dependenc...
AssetFileChangedType
Type of a change event for an asset.
A hash to uniquely identify data.
Definition: ObjectId.cs:13
An interface that provides a reference to an asset.
IEnumerable< Guid > FindAssetsWithMissingReferences()
Finds the assets with missing references.
Ä file event used notified by DirectoryWatcher
Definition: FileEvent.cs:10
AssetDependencySearchOptions
Options used when searching asset dependencies.
Action< AssetItem > AssetChanged
Occurs when a asset changed. This event is called in the critical section of the dependency manager...
AssetDependencySet ComputeDependencies(AssetItem assetItem, AssetDependencySearchOptions dependenciesOptions=AssetDependencySearchOptions.All, HashSet< Guid > visited=null)
Computes the dependencies for the specified asset.
Defines a normalized file path. See UPath for details. This class cannot be inherited.
Definition: UFile.cs:13
IEnumerable< IContentReference > FindMissingReferences(Guid assetId)
Finds the missing references for a particular asset.