private Task CheckAssetsToReload() { return(GameDispatcher.InvokeTask(async() => { List <ReloadingAsset> assets; // Get assets to reload from queue lock (assetsToReloadLock) { // Nothing left, early exit if (assetsToReloadQueue.Count == 0) { return; } // Copy locally and clear queue assets = assetsToReloadQueue.ToList(); assetsToReloadQueue.Clear(); assetsToReloadMapping.Clear(); } // Update the colorspace Game.UpdateColorSpace(currentColorSpace); var objToFastReload = new Dictionary <string, object>(); using (await database.MountInCurrentMicroThread()) { // First, unload assets foreach (var assetToUnload in assets) { if (FastReloadTypes.Contains(assetToUnload.AssetItem.Asset.GetType()) && IsCurrentlyLoaded(assetToUnload.AssetItem.Asset.Id)) { // If this type supports fast reload, retrieve the current (old) value via a load var type = AssetRegistry.GetContentType(assetToUnload.AssetItem.Asset.GetType()); string url = GetLoadingTimeUrl(assetToUnload.AssetItem); var oldValue = Game.Content.Get(type, url); if (oldValue != null) { logger?.Debug($"Preparing fast-reload of {assetToUnload.AssetItem.Location}"); objToFastReload.Add(url, oldValue); } } else if (IsCurrentlyLoaded(assetToUnload.AssetItem.Asset.Id, true)) { // Unload this object if it has already been loaded. logger?.Debug($"Unloading {assetToUnload.AssetItem.Location}"); await UnloadAsset(assetToUnload.AssetItem.Asset.Id); } } // Process fast-reload objects var nonFastReloadAssets = new List <ReloadingAsset>(); foreach (var assetToLoad in assets) { object oldValue; string url = GetLoadingTimeUrl(assetToLoad.AssetItem); if (FastReloadTypes.Contains(assetToLoad.AssetItem.Asset.GetType()) && objToFastReload.TryGetValue(url, out oldValue)) { // Fill oldValue with the values from the database without reloading the object. // As a result, no reference needs to be updated. logger?.Debug($"Fast-reloading {assetToLoad.AssetItem.Location}"); ReloadContent(oldValue, assetToLoad.AssetItem); var loadedObject = oldValue; // This fast-reloaded content might have been already loaded through private reference, but if we're reloading it here, // it means that we expect a public reference (eg. it has just been referenced publicly). Reload() won't increase public reference count // so we have to do it manually. if (!IsCurrentlyLoaded(assetToLoad.AssetItem.Id, true)) { var type = AssetRegistry.GetContentType(assetToLoad.AssetItem.Asset.GetType()); LoadContent(type, url); } await Manager.ReplaceContent(assetToLoad.AssetItem.Asset.Id, loadedObject); assetToLoad.Result.SetResult(loadedObject); } else { nonFastReloadAssets.Add(assetToLoad); } } // Load all async object in a separate task // We avoid Game.Content.LoadAsync, which would wait next frame between every loaded asset var microThread = Scheduler.CurrentMicroThread; var bufferBlock = new BufferBlock <KeyValuePair <ReloadingAsset, object> >(); var task = Task.Run(() => { var initialContext = SynchronizationContext.Current; // This synchronization context gives access to any MicroThreadLocal values. The database to use might actually be micro thread local. SynchronizationContext.SetSynchronizationContext(new MicrothreadProxySynchronizationContext(microThread)); foreach (var assetToLoad in nonFastReloadAssets) { var type = AssetRegistry.GetContentType(assetToLoad.AssetItem.Asset.GetType()); string url = GetLoadingTimeUrl(assetToLoad.AssetItem); object loadedObject = null; try { loadedObject = LoadContent(type, url); } catch (Exception e) { logger?.Error($"Unable to load asset [{assetToLoad.AssetItem.Location}].", e); } // Post it in BufferBlock so that the game-side loop can process results incrementally bufferBlock.Post(new KeyValuePair <ReloadingAsset, object>(assetToLoad, loadedObject)); } bufferBlock.Complete(); SynchronizationContext.SetSynchronizationContext(initialContext); }); while (await bufferBlock.OutputAvailableAsync()) { var item = await bufferBlock.ReceiveAsync(); var assetToLoad = item.Key; var loadedObject = item.Value; if (loadedObject != null) { // If it's the first load of this asset, keep its loading url if (!AssetLoadingTimeUrls.ContainsKey(assetToLoad.AssetItem.Asset.Id)) { AssetLoadingTimeUrls.Add(assetToLoad.AssetItem.Asset.Id, assetToLoad.AssetItem.Location); } // Add assets that were not previously loaded to the assetLoadingTimeUrls map. var dependencyManager = Asset.AssetItem.Package.Session.DependencyManager; var dependencies = dependencyManager.ComputeDependencies(Asset.AssetItem.Id, AssetDependencySearchOptions.Out | AssetDependencySearchOptions.Recursive, ContentLinkType.Reference); if (dependencies != null) { foreach (var dependency in dependencies.LinksOut) { if (!AssetLoadingTimeUrls.ContainsKey(dependency.Item.Id)) { AssetLoadingTimeUrls.Add(dependency.Item.Id, dependency.Item.Location); } } } // Remove assets that were previously loaded but are not anymore from the assetLoadingTimeUrls map. foreach (var loadedUrls in AssetLoadingTimeUrls.Where(x => !Game.Content.IsLoaded(x.Value)).ToList()) { AssetLoadingTimeUrls.Remove(loadedUrls.Key); } } await Manager.ReplaceContent(assetToLoad.AssetItem.Asset.Id, loadedObject); assetToLoad.Result.SetResult(loadedObject); } // Make sure everything is complete before we return await task; } })); }
private async void AssetPropertiesChanged(object sender, AssetChangedEventArgs e) { // Get the list of assets directly referenced by entities, that reference one of the modified asset. (eg. get models when a material is changed) var allAssetsToRebuild = new HashSet <AssetViewModel>(); // Don't propagate property changes until we're fully initialized. await Asset.EditorInitialized; // If GameSettingsAssets.ColorSpace was changed, rebuild the whole scene var assets = e.Assets.ToList(); var references = await ComputeReferences(); var assetsToProcess = new Queue <AssetViewModel>(assets); var processedAssets = new HashSet <AssetViewModel>(assets); // Recurse through assets that depend on this one (recursively) while (assetsToProcess.Count > 0) { var assetToProcess = assetsToProcess.Dequeue(); HashSet <AssetId> modifiedAssetReferencers; // Check if the asset is referenced in the scene. if (!references.TryGetValue(assetToProcess.Id, out modifiedAssetReferencers)) { continue; } // We wait for a lock of the database. The lock we retrieve is synchronous, do not await in this using block! using ((await database.ReserveSyncLock()).Lock()) { // Use fast reload if supported and the asset is currently loaded if (FastReloadTypes.Contains(assetToProcess.AssetType) && IsCurrentlyLoaded(assetToProcess.Id)) { // Allow fast-reload only if the has not been added as non-fast-reloadable. allAssetsToRebuild.Add(assetToProcess); // Find dependent assets foreach (var referencer in assetToProcess.Dependencies.ReferencerAssets) { var node = database.AssetDependenciesCompiler.BuildDependencyManager.FindOrCreateNode(referencer.AssetItem, typeof(AssetCompilationContext)); node.Analyze(database.CompilerContext); foreach (var reference in node.References) { // Check if this reference is actually a compile-time dependency if (reference.Target.AssetItem.Id == assetToProcess.Id && reference.HasOne(BuildDependencyType.CompileContent | BuildDependencyType.Runtime)) { // If yes, process this asset later if (processedAssets.Add(referencer)) { assetsToProcess.Enqueue(referencer); } } } } } else { // Otherwise, rebuild the objects that are referenced by entities from the scene and that references this asset foreach (var assetViewModel in modifiedAssetReferencers.Select(x => Session.GetAssetById(x)).NotNull()) { allAssetsToRebuild.Add(assetViewModel); } } } } await BuildAndReloadAssets(allAssetsToRebuild.Select(x => x.AssetItem)); }
private async void AssetPropertiesChanged(object sender, AssetChangedEventArgs e) { // Get the list of assets directly referenced by entities, that reference one of the modified asset. (eg. get models when a material is changed) var allAssetsToRebuild = new HashSet <AssetViewModel>(); // Don't propagate property changes until we're fully initialized. await Asset.EditorInitialized; // If GameSettingsAssets.ColorSpace was changed, rebuild the whole scene var assets = e.Assets.ToList(); var references = await ComputeReferences(); var assetsToProcess = new Queue <AssetViewModel>(assets); var processedAssets = new HashSet <AssetViewModel>(assets); // Recurse through assets that depend on this one (recursively) while (assetsToProcess.Count > 0) { var assetToProcess = assetsToProcess.Dequeue(); HashSet <AssetId> modifiedAssetReferencers; // Check if the asset is referenced in the scene. if (!references.TryGetValue(assetToProcess.Id, out modifiedAssetReferencers)) { continue; } // We wait for a lock of the database. The lock we retrieve is synchronous, do not await in this using block! using ((await database.ReserveSyncLock()).Lock()) { // There is two patterns: // - Object is a fast-reloadable & already loaded object: we can replace its content internally without loading a new object and recreating any of its referencers // Note that we still need to process referencers in case it is used as a compile-time dependency (i.e. Material layer) // - Object is not a fast-reloadable object: we need to find its referencers (recursively) until we find node directly referenced by the scene (part of modifiedAssetReferencers) and reload this one var isFastReloadCurrentlyLoaded = FastReloadTypes.Contains(assetToProcess.AssetType) && IsCurrentlyLoaded(assetToProcess.Id); if (modifiedAssetReferencers.Contains(assetToProcess.Id) || isFastReloadCurrentlyLoaded) { allAssetsToRebuild.Add(assetToProcess); } // Find dependent assets foreach (var referencer in assetToProcess.Dependencies.ReferencerAssets) { var node = database.AssetDependenciesCompiler.BuildDependencyManager.FindOrCreateNode(referencer.AssetItem, typeof(AssetCompilationContext)); node.Analyze(database.CompilerContext); foreach (var reference in node.References) { // Check if this reference is actually a compile-time dependency // Or if it's not a fast reloadable type (in which case we also need to process its references) if (reference.Target.AssetItem.Id == assetToProcess.Id && (reference.HasOne(BuildDependencyType.CompileContent | BuildDependencyType.CompileAsset) || !isFastReloadCurrentlyLoaded)) { // If yes, process this asset later if (processedAssets.Add(referencer)) { assetsToProcess.Enqueue(referencer); } } } } } } await BuildAndReloadAssets(allAssetsToRebuild.Select(x => x.AssetItem)); }