internal static void ShutdownBackend <T>(ref T backend) where T : class, IDualityBackend { if (backend == null) { return; } Logs.Core.Write("Shutting down {0}...", backend.Name); Logs.Core.PushIndent(); { try { backend.Shutdown(); TypeInfo backendType = backend.GetType().GetTypeInfo(); pluginManager.UnlockPlugin(backendType.Assembly); backend = null; } catch (Exception e) { Logs.Core.WriteError("Failed: {0}", LogFormat.Exception(e)); } } Logs.Core.PopIndent(); }
protected T LoadPlugin(string pluginFilePath) { // Check for already loaded plugins first string asmName = PathOp.GetFileNameWithoutExtension(pluginFilePath); T plugin = this.pluginRegistry.Values.FirstOrDefault(p => p.AssemblyName == asmName); if (plugin != null) { return(plugin); } // Load the assembly from the specified path Assembly pluginAssembly = null; try { pluginAssembly = this.assemblyLoader.LoadAssembly(pluginFilePath); } catch (Exception e) { this.pluginLog.WriteError("Error loading plugin Assembly: {0}", LogFormat.Exception(e)); plugin = null; } // If we succeeded, register the loaded assembly as a plugin if (pluginAssembly != null) { plugin = this.LoadPlugin(pluginAssembly, pluginFilePath); } return(plugin); }
/// <summary> /// Disposes all loaded plugins and discards all related content / data. /// </summary> public void ClearPlugins() { T[] oldPlugins = this.LoadedPlugins.ToArray(); if (oldPlugins.Length == 0) { return; } foreach (T plugin in oldPlugins) { this.disposedPlugins.Add(plugin.PluginAssembly); } this.OnPluginsRemoving(oldPlugins); foreach (T plugin in oldPlugins) { try { plugin.Dispose(); } catch (Exception e) { this.pluginLog.WriteError("Error disposing plugin {1}: {0}", LogFormat.Exception(e), plugin.AssemblyName); } } this.OnPluginsRemoved(oldPlugins); this.pluginRegistry.Clear(); }
/// <summary> /// Loads the Resource from the specified <see cref="Stream"/>. You shouldn't need this method in almost all cases. /// Only use it when you know exactly what you're doing. Consider requesting the Resource from the <see cref="ContentProvider"/> instead. /// </summary> /// <typeparam name="T"> /// Desired Type of the returned reference. Does not affect the loaded Resource in any way - it is simply returned as T. /// Results in returning null if the loaded Resource's Type isn't assignable to T. /// </typeparam> /// <param name="formatter"></param> /// <param name="resPath">The path that is assumed as the loaded Resource's origin.</param> /// <param name="loadCallback">An optional callback that is invoked right after loading the Resource, but before initializing it.</param> /// <param name="initResource"> /// Specifies whether or not the Resource is initialized by calling <see cref="Resource.OnLoaded"/>. Never attempt to use /// uninitialized Resources or register them in the <see cref="ContentProvider"/>. /// </param> /// <returns>The Resource that has been loaded.</returns> public static T Load <T>(Serializer formatter, string resPath = null, Action <T> loadCallback = null, bool initResource = true) where T : Resource { T newContent = null; try { Resource res = formatter.ReadObject <Resource>(); if (res == null) { throw new Exception("Deserializing Resource failed."); } res.initState = InitState.Initializing; res.path = resPath; if (loadCallback != null) { loadCallback(res as T); // Callback before initializing. } if (initResource) { Init(res); } newContent = res as T; } catch (Exception e) { Logs.Core.WriteError("Can't load {0} from '{1}', because an error occurred: {3}{2}", LogFormat.Type(typeof(T)), resPath ?? formatter.ToString(), LogFormat.Exception(e), Environment.NewLine); } return(newContent); }
/// <summary> /// Reloads the specified plugin. Does not initialize it. /// </summary> /// <param name="pluginFilePath"></param> public T ReloadPlugin(string pluginFilePath) { // If we're trying to reload an active backend plugin, stop foreach (var pair in this.pluginRegistry) { T plugin = pair.Value; if (PathOp.ArePathsEqual(plugin.FilePath, pluginFilePath)) { foreach (Assembly lockedAssembly in this.lockedPlugins) { if (plugin.PluginAssembly == lockedAssembly) { this.pluginLog.WriteError( "Can't reload plugin {0}, because it has been locked by the runtime. " + "This usually happens for plugins that implement a currently active backend.", LogFormat.Assembly(lockedAssembly)); return(null); } } break; } } // Load the updated plugin Assembly Assembly pluginAssembly = null; try { pluginAssembly = this.assemblyLoader.LoadAssembly(pluginFilePath); } catch (Exception e) { this.pluginLog.WriteError("Error loading plugin Assembly: {0}", LogFormat.Exception(e)); return(null); } // If we're overwriting an old plugin here, add the old version to the "disposed" blacklist string assemblyName = pluginAssembly.GetShortAssemblyName(); T oldPlugin; if (this.pluginRegistry.TryGetValue(assemblyName, out oldPlugin)) { this.pluginRegistry.Remove(assemblyName); this.disposedPlugins.Add(oldPlugin.PluginAssembly); this.OnPluginsRemoving(new[] { oldPlugin }); oldPlugin.Dispose(); } // Load the new plugin from the updated Assembly T updatedPlugin = this.LoadPlugin(pluginAssembly, pluginFilePath); // Discard temporary plugin-related data (cached Types, etc.) this.OnPluginsRemoved(new[] { oldPlugin }); return(updatedPlugin); }
/// <summary> /// Initializes the specified plugin. This concludes a manual plugin load or reload operation /// using API like <see cref="LoadPlugin(Assembly, string)"/> and <see cref="ReloadPlugin"/>. /// </summary> /// <param name="plugin"></param> public void InitPlugin(T plugin) { try { this.OnInitPlugin(plugin); this.OnPluginsReady(new[] { plugin }); } catch (Exception e) { this.pluginLog.WriteError("Error initializing plugin {1}: {0}", LogFormat.Exception(e), plugin.AssemblyName); this.RemovePlugin(plugin); } }
/// <summary> /// Adds an already loaded plugin Assembly to the internal Duality T registry. /// You shouldn't need to call this method in general, since Duality manages its plugins /// automatically. /// </summary> /// <remarks> /// This method can be useful in certain cases when it is necessary to treat an Assembly as a /// Duality plugin, even though it isn't located in the Plugins folder, or is not available /// as a file at all. A typical case for this is Unit Testing where the testing Assembly may /// specify additional Duality types such as Components, Resources, etc. /// </remarks> /// <param name="pluginAssembly"></param> /// <param name="pluginFilePath"></param> /// <returns></returns> public T LoadPlugin(Assembly pluginAssembly, string pluginFilePath) { this.disposedPlugins.Remove(pluginAssembly); string asmName = pluginAssembly.GetShortAssemblyName(); T plugin = this.pluginRegistry.Values.FirstOrDefault(p => p.AssemblyName == asmName); if (plugin != null) { return(plugin); } try { TypeInfo pluginType = pluginAssembly.ExportedTypes .Select(t => t.GetTypeInfo()) .FirstOrDefault(t => typeof(T).GetTypeInfo().IsAssignableFrom(t)); if (pluginType == null) { throw new Exception(string.Format( "Plugin does not contain a public {0} class.", typeof(T).Name)); } plugin = (T)pluginType.CreateInstanceOf(); if (plugin == null) { throw new Exception(string.Format( "Failed to instantiate {0} class.", LogFormat.Type(pluginType.GetType()))); } plugin.FilePath = pluginFilePath; plugin.FileHash = this.assemblyLoader.GetAssemblyHash(pluginFilePath); this.pluginRegistry.Add(plugin.AssemblyName, plugin); } catch (Exception e) { this.pluginLog.WriteError("Error loading plugin: {0}", LogFormat.Exception(e)); this.disposedPlugins.Add(pluginAssembly); plugin = null; } return(plugin); }
/// <summary> /// When executed from within the editor environment, this method wraps the specified /// action in a safe try-catch block in order to be able to recover gracefully. In regular /// game execution, it will simply invoke the action without safety measures. /// </summary> /// <param name="action"></param> public static void EditorGuard(Action action) { if (ExecEnvironment == ExecutionEnvironment.Editor) { try { action(); } catch (Exception e) { Logs.Editor.WriteError("An error occurred: {0}", LogFormat.Exception(e)); } } else { action(); } }
protected void RemovePlugin(T plugin) { // Dispose plugin and discard plugin related data this.disposedPlugins.Add(plugin.PluginAssembly); this.OnPluginsRemoving(new[] { plugin }); this.pluginRegistry.Remove(plugin.AssemblyName); try { plugin.Dispose(); } catch (Exception e) { this.pluginLog.WriteError("Error disposing plugin {1}: {0}", LogFormat.Exception(e), plugin.AssemblyName); } // Discard temporary plugin-related data (cached Types, etc.) this.OnPluginsRemoved(new[] { plugin }); }
private bool CheckedOnSaved(string saveAsPath) { if (this.initState != InitState.Initialized) { return(true); } try { this.OnSaved(saveAsPath); if (ResourceSaved != null) { ResourceSaved(this, new ResourceSaveEventArgs(this, saveAsPath)); } return(true); } catch (Exception e) { Logs.Core.WriteError("OnSaved() of {0} failed: {1}", this, LogFormat.Exception(e)); return(false); } }
private string FormatMessage(string format, object[] obj) { if (obj == null || obj.Length == 0) { return(format); } string msg; try { msg = string.Format(System.Globalization.CultureInfo.InvariantCulture, format, obj); } catch (Exception e) { // Don't allow log message formatting to throw unhandled exceptions, // because they would result in another log - and probably more exceptions. // Instead, embed format, arguments and the exception in the resulting // log message, so the user can retrieve all necessary information for // fixing his log call. msg = format + Environment.NewLine; if (obj != null) { try { msg += obj.ToString(", ") + Environment.NewLine; } catch (Exception) { msg += "(Error in ToString call)" + Environment.NewLine; } } msg += LogFormat.Exception(e); } return(msg); }
internal static void InitBackend <T>(out T target, Func <Type, IEnumerable <TypeInfo> > typeFinder = null) where T : class, IDualityBackend { if (typeFinder == null) { typeFinder = GetAvailDualityTypes; } Logs.Core.Write("Initializing {0}...", LogFormat.Type(typeof(T))); Logs.Core.PushIndent(); // Generate a list of available backends for evaluation List <IDualityBackend> backends = new List <IDualityBackend>(); foreach (TypeInfo backendType in typeFinder(typeof(IDualityBackend))) { if (backendType.IsInterface) { continue; } if (backendType.IsAbstract) { continue; } if (!backendType.IsClass) { continue; } if (!typeof(T).GetTypeInfo().IsAssignableFrom(backendType)) { continue; } IDualityBackend backend = backendType.CreateInstanceOf() as IDualityBackend; if (backend == null) { Logs.Core.WriteWarning("Unable to create an instance of {0}. Skipping it.", backendType.FullName); continue; } backends.Add(backend); } // Sort backends from best to worst backends.StableSort((a, b) => b.Priority > a.Priority ? 1 : -1); // Try to initialize each one and select the first that works T selectedBackend = null; foreach (T backend in backends) { if (DualityApp.AppData.Instance?.SkipBackends != null && DualityApp.AppData.Instance.SkipBackends.Any(s => string.Equals(s, backend.Id, StringComparison.OrdinalIgnoreCase))) { Logs.Core.Write("Backend '{0}' skipped because of AppData settings.", backend.Name); continue; } bool available = false; try { available = backend.CheckAvailable(); if (!available) { Logs.Core.Write("Backend '{0}' reports to be unavailable. Skipping it.", backend.Name); } } catch (Exception e) { available = false; Logs.Core.WriteWarning("Backend '{0}' failed the availability check with an exception: {1}", backend.Name, LogFormat.Exception(e)); } if (!available) { continue; } Logs.Core.Write("{0}...", backend.Name); Logs.Core.PushIndent(); { try { backend.Init(); selectedBackend = backend; } catch (Exception e) { Logs.Core.WriteError("Failed: {0}", LogFormat.Exception(e)); } } Logs.Core.PopIndent(); if (selectedBackend != null) { break; } } // If we found a proper backend and initialized it, add it to the list of active backends if (selectedBackend != null) { target = selectedBackend; TypeInfo selectedBackendType = selectedBackend.GetType().GetTypeInfo(); pluginManager.LockPlugin(selectedBackendType.Assembly); } else { target = null; } Logs.Core.PopIndent(); }
private static Resource ResolveContent(string path) { if (DualityApp.ExecContext == DualityApp.ExecutionContext.Terminated) { return(null); } if (string.IsNullOrEmpty(path) || ResourceResolve == null) { return(null); } ResourceResolveEventArgs args = new ResourceResolveEventArgs(path); try { ResourceResolve(null, args); } catch (Exception e) { Logs.Core.WriteError("An error occurred in custom ResourceResolve code: {0}", LogFormat.Exception(e)); } if (args.Handled) { if (string.IsNullOrEmpty(args.Result.Path)) { args.Result.Path = path; } AddContent(path, args.Result); return(args.Result); } else { return(null); } }
private static CreateMethod CreateObjectActivator(TypeInfo typeInfo, out object firstResult) { Exception lastError = null; CreateMethod activator; firstResult = null; // Filter out non-instantiatable Types if (typeInfo.IsAbstract || typeInfo.IsInterface || typeInfo.IsGenericTypeDefinition) { activator = nullObjectActivator; } // If the caller wants a string, just return an empty one else if (typeInfo.AsType() == typeof(string)) { activator = () => ""; } // If the caller wants an array, create an empty one else if (typeInfo.IsArray && typeInfo.GetArrayRank() == 1) { activator = () => Array.CreateInstance(typeInfo.GetElementType(), 0); } // For structs, boxing a default(T) is sufficient else if (typeInfo.IsValueType) { var lambda = Expression.Lambda <CreateMethod>(Expression.Convert(Expression.Default(typeInfo.AsType()), typeof(object))); activator = lambda.Compile(); } else { activator = nullObjectActivator; // Retrieve constructors, sorted from trivial to parameter-rich ConstructorInfo[] constructors = typeInfo.DeclaredConstructors .Where(c => !c.IsStatic) .Select(c => new { Info = c, ParamCount = c.GetParameters().Length }) .OrderBy(s => s.ParamCount) .Select(s => s.Info) .ToArray(); foreach (ConstructorInfo con in constructors) { // Prepare constructor argument values - just use default(T) for all of them. ParameterInfo[] conParams = con.GetParameters(); Expression[] args = new Expression[conParams.Length]; for (int i = 0; i < args.Length; i++) { Type paramType = conParams[i].ParameterType; args[i] = Expression.Default(paramType); } // Compile a lambda method invoking the constructor var lambda = Expression.Lambda <CreateMethod>(Expression.New(con, args)); activator = lambda.Compile(); // Does it work? firstResult = CheckActivator(activator, out lastError); if (firstResult != null) { break; } } // If there were no suitable constructors, log a generic warning. if (constructors.Length == 0) { Logs.Core.WriteWarning( "Failed to create object of Type {0}. Make sure there is a trivial constructor.", LogFormat.Type(typeInfo)); } } // Test whether our activation method really works, replace with dummy if not if (firstResult == null) { // If we didn't yet try to create an object instance or value, do it now. if (lastError == null) { firstResult = CheckActivator(activator, out lastError); } // If there was an error / Exception thrown while creating the object, inform someone. if (lastError != null) { // If it's a problem in a static constructor, get the inner exception to know what's actually wrong. if (lastError is TypeInitializationException) { Logs.Core.WriteError("Failed to initialize Type {0}: {1}", LogFormat.Type(typeInfo), LogFormat.Exception(lastError.InnerException)); } // Otherwise, just do a regular error log. else { Logs.Core.WriteError("Failed to create object of Type {0}: {1}", LogFormat.Type(typeInfo), LogFormat.Exception(lastError)); } } } // If we still don't have anything, just use a dummy. if (firstResult == null) { activator = nullObjectActivator; } return(activator); }