public async Task UnloadAsset(AssetId id) { GameDispatcher.EnsureAccess(); // Unload this object if it has already been loaded. using (await database.LockAsync()) { string url; if (AssetLoadingTimeUrls.TryGetValue(id, out url)) { UnloadContent(url); // 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); } } } }
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; } })); }