private async Task LoadAddonAsync(string path, Action <AddonLoadData> onLoadedMethod) { AddonLoadContext context = new AddonLoadContext(); context.Path = path; context.ResourcePriority = ResourcePriority.Addon; context.AddonManager = this; context.OnLoadedCallback = onLoadedMethod; context.Manifest = ReadAddonManifest(path); if (context.Manifest.IgnoreSingleFileErrors) { context.AbortOnSingleFileFailure = false; } LoadMainAssembly(context); CreateAddonBase(context); await context.AddonBase.LoadAddonAsync(context); //if we get here, loading has succeeded LoadedAddons.Add(context.Manifest.Name, context); }
private void CreateAddonBase(AddonLoadContext context) { if (context.MainAssembly != null) { var types = context.MainAssembly.GetTypes(); //scan for addonbase derived type, instantiate var abType = types.FirstOrDefault(t => t.IsSubclassOf(typeof(AddonBase))); if (abType != null) { context.AddonBaseType = abType; } else { context.AddonBaseType = typeof(AddonBase); //if there is no addonbase derived type just use addonbase } } else { context.AddonBaseType = typeof(AddonBase); } context.AddonBase = (AddonBase)Activator.CreateInstance(context.AddonBaseType); }
public virtual async Task LoadAddonAsync(AddonLoadContext context) { InitialLoadContext = context; LocalResourcePath = context.MountPath; await context.AddonManager.LoadAssembliesAsync(context); await context.AddonManager.LoadResourcesFromPathAsync(context); context.AddonManager.RegisterLoadedScenes(context); RunOnLoadedCallback(context); }
private void LoadMainAssembly(AddonLoadContext context) { if (!string.IsNullOrEmpty(context.Manifest.MainAssembly)) //we do allow addons with no assembly { string dllPath = Path.Combine(context.Path, "managed", context.Manifest.MainAssembly + ".dll"); var assembly = Assembly.LoadFile(dllPath); context.MainAssembly = assembly; context.LoadedAssemblies.Add(assembly); if (ConfigState.Instance.UseVerboseLogging) { Debug.Log($"[AddonManager] Loaded assembly {assembly.FullName}"); } } }
public async Task LoadStreamingAssetsAsync(Action <AddonLoadData> onLoadedMethod) { if (!(CoreParams.LoadAddons && CanLoadAddons)) { return; } var context = new AddonLoadContext(); //set various things in context context.Path = CoreParams.StreamingAssetsPath; context.AddonManager = this; context.MountPathOverride = "Streaming"; context.ResourcePriority = ResourcePriority.Streaming; context.AbortOnSingleFileFailure = false; await LoadResourcesFromPathAsync(context); onLoadedMethod(new AddonLoadData(context.LoadedAssemblies, context.LoadedResources)); }
public async Task LoadAssembliesAsync(AddonLoadContext context) { string dirPath = Path.Combine(context.Path, "managed"); if (Directory.Exists(dirPath)) { var dirEnumerable = Directory.EnumerateFiles(dirPath); foreach (var file in dirEnumerable) { try { if (!Path.GetFileNameWithoutExtension(file).Equals(context.Manifest.MainAssembly) && Path.GetExtension(file).Equals(".dll", StringComparison.OrdinalIgnoreCase)) { //okay to load assembly! var assembly = Assembly.LoadFile(file); context.LoadedAssemblies.Add(assembly); if (ConfigState.Instance.UseVerboseLogging) { Debug.Log($"[AddonManager] Loaded assembly {assembly.FullName}"); } } } catch (Exception e) { Debug.LogError($"[AddonManager] Failed to load assembly \"{file}\" ({e.GetType().Name})"); if (ConfigState.Instance.UseVerboseLogging) { Debug.LogException(e); } if (context.AbortOnSingleFileFailure) { throw e; } } await Task.Yield(); //don't try to load every dll in one frame } } }
//TODO resilience, or should we just throw? public async Task LoadResourcesFromPathAsync(AddonLoadContext context) { //load elocal, then expand //load assetbundles, then loose files string elocalTargetPath = context.MountPath; string elocalPath = Path.Combine(context.Path, "elocal"); string elocalBundlePath = Path.Combine(context.Path, "elocal.assetbundle"); await tryLoadResourceFromAssetBundleAsync(elocalBundlePath, elocalTargetPath); await LoadResourcesInFolderRecurseAsync(context, elocalPath, elocalTargetPath); string expandTargetPath = ""; //I think this is actually correct string expandPath = Path.Combine(context.Path, "expand"); string expandBundlePath = Path.Combine(context.Path, "expand.assetbundle"); await tryLoadResourceFromAssetBundleAsync(expandBundlePath, expandTargetPath); await LoadResourcesInFolderRecurseAsync(context, expandPath, expandTargetPath); async Task tryLoadResourceFromAssetBundleAsync(string bundlePath, string targetPath) { try { await LoadResourcesFromAssetBundleAsync(context, bundlePath, targetPath); } catch (Exception e) { Debug.LogError($"Failed to load assetbundle \"{bundlePath}\" ({e.GetType().Name})"); if (ConfigState.Instance.UseVerboseLogging) { Debug.LogException(e); } if (context.AbortOnSingleFileFailure) { throw e; } } } }
private async Task LoadResourcesFromAssetBundleAsync(AddonLoadContext context, string bundlePath, string targetPath) { if (!File.Exists(bundlePath)) { return; } if (ConfigState.Instance.UseVerboseLogging) { Debug.Log($"loading assetbundle \"{bundlePath}\" to \"{targetPath}\""); } //load from AssetBundles var bundleLoadRequest = AssetBundle.LoadFromFileAsync(bundlePath); while (!bundleLoadRequest.isDone) { await Task.Yield(); //ugly but should work } var assetBundle = bundleLoadRequest.assetBundle; if (assetBundle == null) { Debug.LogError($"[AddonManager] failed to load assetbundle \"{bundlePath}\""); throw new FileLoadException("Failed to load assetbundle"); //TODO change this to a more appropriate exception } var scenes = assetBundle.GetAllScenePaths(); if (ConfigState.Instance.UseVerboseLogging && scenes != null && scenes.Length > 0) { Debug.Log(scenes.ToNiceString()); } if (scenes != null && scenes.Length > 0) { context.LoadedScenes.AddRange(scenes); } var names = assetBundle.GetAllAssetNames(); if (ConfigState.Instance.UseVerboseLogging && names != null && names.Length > 0) { Debug.Log(names.ToNiceString()); } if (names != null && names.Length > 0) { bool useAssetBundlePaths = false; if (context.Manifest != null && context.Manifest.UseAssetBundlePaths) { Debug.LogWarning($"[AddonManager] Addon {context.Manifest.Name} is set to use full asset bundle paths, which is considered experimental!"); //throw new NotImplementedException("Full asset bundle paths are not yet supported"); //we can actually add this with minimal difficulty //so we'll just use the full paths from the asset bundle minus the "assets/" part and tack that on after the targetPrefix //which will mount elocal.assetbundle at Addons/<package name> and expand.assetbundle at the root //and really you should either use two pathed assetbundles or a lot of flat assetbundles, not multiple pathed assetbundles useAssetBundlePaths = true; } string targetPrefix = targetPath.Replace('\\', '/'); if (!targetPrefix.EndsWith("/", StringComparison.Ordinal)) { targetPrefix = targetPrefix + "/"; } foreach (var name in names) { string objectName = useAssetBundlePaths ? GetObjectPartialPathFromBundledName(name) : GetObjectNameFromBundledName(name); string objectTargetPath = targetPrefix + objectName; var rh = await CCBase.ResourceManager.AddResourceFromAssetBundleAsync(objectTargetPath, name, assetBundle, context.ResourcePriority); context.LoadedResources.Add(objectTargetPath, rh); } } }
private async Task LoadResourcesInFolderRecurseAsync(AddonLoadContext context, string folderPath, string targetPath) { if (!Directory.Exists(folderPath)) { return; } //Debug.Log($"load things in \"{folderPath}\" to \"{targetPath}\""); //handle both assetbundles and loose resources var files = Directory.EnumerateFiles(folderPath); foreach (var file in files) { string fileTargetPath = getTargetPath(file); try { if (Path.GetExtension(file).Equals(".assetbundle", StringComparison.OrdinalIgnoreCase)) { await LoadResourcesFromAssetBundleAsync(context, file, fileTargetPath); } else { var rh = await CCBase.ResourceManager.AddResourceFromFileAsync(fileTargetPath, file, context.ResourcePriority); context.LoadedResources.Add(fileTargetPath, rh); } } catch (Exception e) { Debug.LogError($"Failed to load \"{file}\" ({e.GetType().Name})"); if (ConfigState.Instance.UseVerboseLogging) { Debug.LogException(e); } if (context.AbortOnSingleFileFailure) { throw e; } } } var subdirs = Directory.EnumerateDirectories(folderPath); foreach (var subdir in subdirs) { string subdirTargetPath = getTargetPath(subdir); await LoadResourcesInFolderRecurseAsync(context, subdir, subdirTargetPath); } string getTargetPath(string objectPath) { string fullBasePath = Path.GetFullPath(folderPath); string fullObjectPath = Path.GetFullPath(objectPath); string partialObjectPath = fullObjectPath.Substring(fullBasePath.Length); //watch the off-by-ones! string newTargetPath = targetPath + partialObjectPath; if (newTargetPath.StartsWith("/", StringComparison.OrdinalIgnoreCase) || newTargetPath.StartsWith("\\", StringComparison.OrdinalIgnoreCase)) { newTargetPath = newTargetPath.Substring(1); } if (Path.HasExtension(newTargetPath)) { newTargetPath = Path.ChangeExtension(newTargetPath, null); } newTargetPath = newTargetPath.Replace('\\', '/'); //Debug.Log($"{targetPath} + {partialObjectPath} = {newTargetPath}"); return(newTargetPath); } }
public void RegisterLoadedScenes(AddonLoadContext context) { LoadedScenes.UnionWith(context.LoadedScenes); }
protected void RunOnLoadedCallback(AddonLoadContext context) { context.OnLoadedCallback(new AddonLoadData(context.LoadedAssemblies, context.LoadedResources)); }
protected async Task LoadResources(AddonLoadContext context) { await context.AddonManager.LoadResourcesFromPathAsync(context); }
protected async Task LoadAssemblies(AddonLoadContext context) { await context.AddonManager.LoadAssembliesAsync(context); }