private void TransactionDone(object sender, TransactionEventArgs e)
        {
            if (e.Transaction.Operations.Count == 0)
            {
                return;
            }

            var dirtying   = e.Transaction.Operations.SelectMany(Presentation.Dirtiables.DirtiableManager.GetDirtyingOperations);
            var dirtiables = new HashSet <AssetViewModel>(dirtying.SelectMany(x => x.Dirtiables.OfType <AssetViewModel>()));

            if (dirtiables.Count > 0)
            {
                session.NotifyAssetPropertiesChanged(dirtiables);
            }
            Dispatcher.Invoke(() => transactions.Add(new TransactionViewModel(ServiceProvider, e.Transaction)));
        }
        private async Task PullSourceFileChanges()
        {
            var sourceTracker     = session.SourceTracker;
            var bufferBlock       = new BufferBlock <IReadOnlyList <SourceFileChangedData> >();
            var changedAssets     = new HashSet <AssetViewModel>();
            var sourceFileChanges = new List <SourceFileChangedData>();

            using (sourceTracker.SourceFileChanged.LinkTo(bufferBlock))
            {
                sourceTracker.EnableTracking = true;
                while (!IsDestroyed)
                {
                    // Await for source file changes
                    await bufferBlock.OutputAvailableAsync(cancel.Token);

                    if (cancel.IsCancellationRequested)
                    {
                        return;
                    }

                    // Try as much as possible to process all changes at once
                    IList <IReadOnlyList <SourceFileChangedData> > changes;
                    bufferBlock.TryReceiveAll(out changes);
                    sourceFileChanges.AddRange(changes.SelectMany(x => x));

                    if (sourceFileChanges.Count == 0)
                    {
                        continue;
                    }

                    for (var i = 0; i < sourceFileChanges.Count; i++)
                    {
                        var sourceFileChange = sourceFileChanges[i];
                        // Look first in the assets of the session, then in the list of deleted assets.
                        var asset = sessionViewModel.GetAssetById(sourceFileChange.AssetId)
                                    ?? sessionViewModel.AllPackages.SelectMany(x => x.DeletedAssets).FirstOrDefault(x => x.Id == sourceFileChange.AssetId);

                        if (asset == null)
                        {
                            continue;
                        }

                        // We care only about changes that needs to update
                        if (sourceFileChange.NeedUpdate)
                        {
                            switch (sourceFileChange.Type)
                            {
                            case SourceFileChangeType.Asset:
                                // The asset itself has changed and now references a new source
                                asset.Sources.UpdateUsedHashes(sourceFileChange.Files);
                                break;

                            case SourceFileChangeType.SourceFile:
                                // A source file referenced by the asset has changed
                                asset.Sources.ComputeNeedUpdateFromSource();
                                break;

                            default:
                                throw new ArgumentOutOfRangeException();
                            }
                        }
                        // The asset has been updated, remove the change data from the list
                        sourceFileChanges.RemoveAt(i--);

                        if (!asset.IsDeleted)
                        {
                            // We will notify only for undeleted assets
                            changedAssets.Add(asset);
                        }
                    }

                    // Notify all changed assets at once.
                    if (changedAssets.Count > 0)
                    {
                        sessionViewModel.NotifyAssetPropertiesChanged(changedAssets.ToList());
                        changedAssets.Clear();
                    }
                }
            }
        }