public static void Load() { using (ProfilingUtility.SampleBlock("Load Icons")) { Language.Load(); } }
private static void DeserializeJson(fsSerializer serializer, string json, ref object instance, bool forceReflected) { using (ProfilingUtility.SampleBlock("DeserializeJson")) { fsResult result = DeserializeJsonUtil(serializer, json, ref instance, forceReflected); HandleResult("Deserialization", result, instance as UnityObject); } }
public static EditorTexture Load(IResourceProvider resources, string path, TextureResolution[] resolutions, CreateTextureOptions options, bool required) { using (ProfilingUtility.SampleBlock("Load Editor Texture")) { Ensure.That(nameof(resources)).IsNotNull(resources); Ensure.That(nameof(path)).IsNotNull(path); Ensure.That(nameof(resolutions)).HasItems(resolutions); var set = new EditorTexture(); // Try with explicit resolutions first foreach (var resolution in resolutions) { var width = resolution.width; // var height = resolution.height; var personalPath = String.Empty; var professionalPath = String.Empty; personalPath = resources.GetPersonalPath(path, width); professionalPath = resources.GetProfessionalPath(path, width); if (resources.FileExists(personalPath)) { var tex = resources.LoadTexture(personalPath, options); set.personal.Add(width, tex); } if (resources.FileExists(professionalPath)) { var tex = resources.LoadTexture(professionalPath, options); set.professional.Add(width, tex); } } if (set.personal.Count == 0) { if (required) { Debug.LogWarning($"Missing editor texture: {path}\n{resources.DebugPath(path)}"); } // Never return an empty set; the codebase assumes this guarantee return(null); } set.textureName = path; return(set); } }
public IUnitOption ToOption() { using (ProfilingUtility.SampleBlock("Row to option")) { var optionType = Codebase.DeserializeType(this.optionType); IUnitOption option; option = (IUnitOption)Activator.CreateInstance(optionType); option.Deserialize(this); return(option); } }
public static void Dump(TimeSpan threshold) { if (ProfilingUtility.rootSegment.children.Count == 0) { return; } var sb = new StringBuilder(); Append(sb, 0, ProfilingUtility.rootSegment, threshold); ProfilingUtility.Clear(); Debug.Log(sb); }
public static void UpdateSettings() { using (ProfilingUtility.SampleBlock("Codebase settings update")) { var typeOptionsHashSet = new HashSet <Type>(BoltCore.Configuration.typeOptions); var assemblyOptionsHashSet = new HashSet <LooseAssemblyName>(BoltCore.Configuration.assemblyOptions); _settingsAssemblies = new List <Assembly>(); _settingsAssembliesTypes = new List <Type>(); _settingsTypes = new List <Type>(); foreach (var assembly in _assemblies) { var couldHaveIncludeInSettingsAttribute = ludiqAssemblies.Contains(assembly); // It's important not to provide types outside the settings assemblies, // because only those assemblies will be added to the linker to preserve stripping. if (IncludeInSettings(assembly, assemblyOptionsHashSet)) { _settingsAssemblies.Add(assembly); foreach (var type in assembly.GetTypesSafely()) { // Apparently void can be returned somehow: // http://support.ludiq.io/topics/483 if (type == typeof(void)) { continue; } _settingsAssembliesTypes.Add(type); // For optimization, we bypass [IncludeInSettings] for assemblies // that could logically never have it. if (IncludeInSettings(type, couldHaveIncludeInSettingsAttribute, typeOptionsHashSet)) { _settingsTypes.Add(type); } } } } settingsAssemblies = _settingsAssemblies.AsReadOnly(); settingsAssembliesTypes = _settingsAssembliesTypes.AsReadOnly(); settingsTypes = _settingsTypes.AsReadOnly(); settingsChanged?.Invoke(); } }
public static EditorTexture Load(IResourceProvider resources, string path, TextureResolution[] resolutions, CreateTextureOptions options, bool required) { using (ProfilingUtility.SampleBlock("Load Editor Texture")) { Ensure.That(nameof(resources)).IsNotNull(resources); Ensure.That(nameof(path)).IsNotNull(path); Ensure.That(nameof(resolutions)).HasItems(resolutions); var set = new EditorTexture(); var name = Path.GetFileNameWithoutExtension(path).PartBefore('@'); var extension = Path.GetExtension(path); var directory = Path.GetDirectoryName(path); // Try with explicit resolutions first foreach (var resolution in resolutions) { var width = resolution.width; // var height = resolution.height; var personalPath = Path.Combine(directory, $"{name}@{width}x{extension}"); var professionalPath = Path.Combine(directory, $"{name}_Pro@{width}x{extension}"); if (resources.FileExists(personalPath)) { set.personal.Add(width, resources.LoadTexture(personalPath, options)); } if (resources.FileExists(professionalPath)) { set.professional.Add(width, resources.LoadTexture(professionalPath, options)); } } if (set.personal.Count == 0) { if (required) { Debug.LogWarning($"Missing editor texture: {name}\n{resources.DebugPath(path)}"); } // Never return an empty set; the codebase assumes this guarantee return(null); } return(set); } }
private static void UpdateCodebase(IEnumerable <Type> typeSet = null) { using var profilerScope = ProfilingUtility.SampleBlock("UpdateCodebase"); if (typeSet == null) { typeSet = Codebase.settingsTypes; } else { typeSet = typeSet.Where(t => Codebase.settingsTypes.Contains(t)); } Codebase.UpdateSettings(); codebase = Codebase.Subset(typeSet, TypeFilter.Any.Configured(), MemberFilter.Any.Configured(), TypeFilter.Any.Configured(false)); codebase.Cache(); }
public static EditorTexture Load(IResourceProvider resources, string path, CreateTextureOptions options, bool required) { using (ProfilingUtility.SampleBlock("Load Editor Texture")) { Ensure.That(nameof(resources)).IsNotNull(resources); Ensure.That(nameof(path)).IsNotNull(path); var set = new EditorTexture(); var name = Path.GetFileNameWithoutExtension(path).PartBefore('@'); var extension = Path.GetExtension(path); var directory = Path.GetDirectoryName(path); var personalPath = Path.Combine(directory, $"{name}{extension}"); var professionalPath = Path.Combine(directory, $"{name}_Pro{extension}"); var texture = resources.LoadTexture(personalPath, options); if (texture != null) { set.personal.Add(texture.width, texture); } texture = resources.LoadTexture(professionalPath, options); if (texture != null) { set.professional.Add(texture.width, texture); } if (set.personal.Count == 0) { if (required) { Debug.LogWarning($"Missing editor texture: {name}\n{resources.DebugPath(path)}"); } // Never return an empty set; the codebase assumes this guarantee return(null); } set.textureName = path; return(set); } }
private static void UpdateTypeMappings() { using var profilerScope = ProfilingUtility.SampleBlock("UpdateTypeMappings"); typesToGuids = new Dictionary <Type, HashSet <string> >(); guidsToTypes = new Dictionary <string, HashSet <Type> >(); UnityAPI.AwaitForever(() => { foreach (var script in UnityEngine.Resources.FindObjectsOfTypeAll <MonoScript>()) { var type = script.GetClass(); // Skip scripts without types if (type == null) { continue; } var path = AssetDatabase.GetAssetPath(script); // Skip built-in Unity plugins, which are referenced by full path if (!path.StartsWith("Assets")) { continue; } var guid = AssetDatabase.AssetPathToGUID(path); // Add the GUID to the list, even if it doesn't have any type if (!guidsToTypes.ContainsKey(guid)) { guidsToTypes.Add(guid, new HashSet <Type>()); } if (!typesToGuids.ContainsKey(type)) { typesToGuids.Add(type, new HashSet <string>()); } typesToGuids[type].Add(guid); guidsToTypes[guid].Add(type); } }); }
private static string SerializeJson(fsSerializer serializer, object instance, bool forceReflected) { using (ProfilingUtility.SampleBlock("SerializeJson")) { fsData data; fsResult result; if (forceReflected) { result = serializer.TrySerialize(instance.GetType(), typeof(fsReflectedConverter), instance, out data); } else { result = serializer.TrySerialize(instance, out data); } HandleResult("Serialization", result, instance as UnityObject); return(fsJsonPrinter.CompressedJson(data)); } }
private static void Load() { if (IsUnitOptionsBuilt()) { // Update before loading if required, ensuring no "in-between" state // where the loaded options are not yet loaded. // The update code will not touch the options array if it is null. if (BoltFlow.Configuration.updateNodesAutomatically) { try { ProgressUtility.DisplayProgressBar("Checking for codebase changes...", null, 0); if (requiresUpdate) { Update(); } } catch (Exception ex) { Debug.LogError($"Failed to update node options.\nRetry with '{UnitOptionUtility.GenerateUnitDatabasePath}'.\n{ex}"); } finally { ProgressUtility.ClearProgressBar(); } } lock (@lock) { using (ProfilingUtility.SampleBlock("Load Node Database")) { using (NativeUtility.Module("sqlite3.dll")) { ProgressUtility.DisplayProgressBar("Loading node database...", null, 0); SQLiteConnection database = null; try { database = new SQLiteConnection(BoltFlow.Paths.unitOptions, SQLiteOpenFlags.ReadOnly); int total; total = database.Table <UnitOptionRow>().Count(); var progress = 0f; options = new HashSet <IUnitOption>(); var failedOptions = new Dictionary <UnitOptionRow, Exception>(); foreach (var row in database.Table <UnitOptionRow>()) { try { var option = row.ToOption(); options.Add(option); } catch (Exception rowEx) { failedOptions.Add(row, rowEx); } ProgressUtility.DisplayProgressBar("Loading node database...", BoltCore.Configuration.humanNaming ? row.labelHuman : row.labelProgrammer, progress++ / total); } if (failedOptions.Count > 0) { var sb = new StringBuilder(); sb.AppendLine($"{failedOptions.Count} node options failed to load and were skipped."); sb.AppendLine($"Try rebuilding the node options with '{UnitOptionUtility.GenerateUnitDatabasePath}' to purge outdated nodes."); sb.AppendLine(); foreach (var failedOption in failedOptions) { sb.AppendLine(failedOption.Key.favoriteKey); } sb.AppendLine(); foreach (var failedOption in failedOptions) { sb.AppendLine(failedOption.Key.favoriteKey + ": "); sb.AppendLine(failedOption.Value.ToString()); sb.AppendLine(); } Debug.LogWarning(sb.ToString()); } } catch (Exception ex) { options = new HashSet <IUnitOption>(); Debug.LogError($"Failed to load node options.\nTry to rebuild them with '{UnitOptionUtility.GenerateUnitDatabasePath}'.\n\n{ex}"); } finally { database?.Close(); //ConsoleProfiler.Dump(); ProgressUtility.ClearProgressBar(); } } } } } }
public static void Update() { if (!IsUnitOptionsBuilt()) { Build(); return; } lock (@lock) { using (ProfilingUtility.SampleBlock("Update Node Database")) { using (NativeUtility.Module("sqlite3.dll")) { var progressTitle = "Updating node database..."; SQLiteConnection database = null; try { VersionControlUtility.Unlock(BoltFlow.Paths.unitOptions); var steps = 7f; var step = 0f; ProgressUtility.DisplayProgressBar(progressTitle, "Connecting to database...", step++ / steps); database = new SQLiteConnection(BoltFlow.Paths.unitOptions); ProgressUtility.DisplayProgressBar(progressTitle, "Updating type mappings...", step++ / steps); UpdateTypeMappings(); ProgressUtility.DisplayProgressBar(progressTitle, "Fetching modified scripts...", step++ / steps); var modifiedScriptGuids = GetModifiedScriptGuids().Distinct().ToHashSet(); ProgressUtility.DisplayProgressBar(progressTitle, "Fetching deleted scripts...", step++ / steps); var deletedScriptGuids = GetDeletedScriptGuids().Distinct().ToHashSet(); ProgressUtility.DisplayProgressBar(progressTitle, "Updating codebase...", step++ / steps); var modifiedScriptTypes = modifiedScriptGuids.SelectMany(GetScriptTypes).ToArray(); UpdateCodebase(modifiedScriptTypes); var outdatedScriptGuids = new HashSet <string>(); outdatedScriptGuids.UnionWith(modifiedScriptGuids); outdatedScriptGuids.UnionWith(deletedScriptGuids); ProgressUtility.DisplayProgressBar(progressTitle, "Removing outdated node options...", step++ / steps); options?.RemoveWhere(option => outdatedScriptGuids.Overlaps(option.sourceScriptGuids)); // We want to use the database level WHERE here for speed, // so we'll run multiple queries, one for each outdated script GUID. foreach (var outdatedScriptGuid in outdatedScriptGuids) { foreach (var outdatedRowId in database.Table <UnitOptionRow>() .Where(row => row.sourceScriptGuids.Contains(outdatedScriptGuid)) .Select(row => row.id)) { database.Delete <UnitOptionRow>(outdatedRowId); } } ProgressUtility.DisplayProgressBar(progressTitle, "Converting codebase to node options...", step++ / steps); var newOptions = new HashSet <IUnitOption>(modifiedScriptGuids.SelectMany(GetScriptTypes) .Distinct() .SelectMany(GetIncrementalOptions)); var rows = new HashSet <UnitOptionRow>(); float progress = 0; foreach (var newOption in newOptions) { options?.Add(newOption); try { ProgressUtility.DisplayProgressBar(progressTitle, newOption.label, (step / steps) + ((1 / step) * (progress / newOptions.Count))); rows.Add(newOption.Serialize()); } catch (Exception ex) { Debug.LogError($"Failed to serialize option '{newOption.GetType()}'.\n{ex}"); } progress++; } ProgressUtility.DisplayProgressBar(progressTitle, "Writing to database...", 1); try { database.InsertAll(rows); } catch (Exception ex) { Debug.LogError($"Failed to write options to database.\n{ex}"); } // Make sure the database is touched to the current date, // even if we didn't do any change. This will avoid unnecessary // analysis in future update checks. File.SetLastWriteTimeUtc(BoltFlow.Paths.unitOptions, DateTime.UtcNow); } finally { database?.Close(); ProgressUtility.ClearProgressBar(); UnityAPI.Async(AssetDatabase.Refresh); //ConsoleProfiler.Dump(); } } } } }
public static void Build(bool initialBuild = false) { if (IsUnitOptionsBuilt()) { return; } if (initialBuild) { ProgressUtility.SetTitleOverride("Visual Scripting: Initial Node Generation..."); } const string progressTitle = "Visual Scripting: Building node database..."; lock (@lock) { using (ProfilingUtility.SampleBlock("Update Node Database")) { using (NativeUtility.Module("sqlite3.dll")) { SQLiteConnection database = null; try { ProgressUtility.DisplayProgressBar(progressTitle, "Creating database...", 0); PathUtility.CreateParentDirectoryIfNeeded(BoltFlow.Paths.unitOptions); database = new SQLiteConnection(BoltFlow.Paths.unitOptions); database.CreateTable <UnitOptionRow>(); ProgressUtility.DisplayProgressBar(progressTitle, "Updating codebase...", 0); UpdateCodebase(); ProgressUtility.DisplayProgressBar(progressTitle, "Updating type mappings...", 0); UpdateTypeMappings(); ProgressUtility.DisplayProgressBar(progressTitle, "Converting codebase to node options...", 0); options = new HashSet <IUnitOption>(GetStaticOptions()); var rows = new HashSet <UnitOptionRow>(); var progress = 0; var lastShownProgress = 0f; foreach (var option in options) { try { var shownProgress = (float)progress / options.Count; if (shownProgress > lastShownProgress + 0.01f) { ProgressUtility.DisplayProgressBar(progressTitle, "Converting codebase to node options...", shownProgress); lastShownProgress = shownProgress; } rows.Add(option.Serialize()); } catch (Exception ex) { Debug.LogError($"Failed to save option '{option.GetType()}'.\n{ex}"); } progress++; } ProgressUtility.DisplayProgressBar(progressTitle, "Writing to database...", 1); try { database.CreateTable <UnitOptionRow>(); database.InsertAll(rows); } catch (Exception ex) { Debug.LogError($"Failed to write options to database.\n{ex}"); } } finally { database?.Close(); ProgressUtility.ClearProgressBar(); ProgressUtility.ClearTitleOverride(); AssetDatabase.Refresh(); //ConsoleProfiler.Dump(); } } } } }
public ProfilingScope(string name) { ProfilingUtility.BeginSample(name); }
static Codebase() { using (ProfilingUtility.SampleBlock("Codebase initialization")) { _assemblies = new List <Assembly>(); _runtimeAssemblies = new List <Assembly>(); _editorAssemblies = new List <Assembly>(); _ludiqAssemblies = new List <Assembly>(); _ludiqRuntimeAssemblies = new List <Assembly>(); _ludiqEditorAssemblies = new List <Assembly>(); _types = new List <Type>(); _runtimeTypes = new List <Type>(); _editorTypes = new List <Type>(); _ludiqTypes = new List <Type>(); _ludiqRuntimeTypes = new List <Type>(); _ludiqEditorTypes = new List <Type>(); foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { try { #if NET_4_6 if (assembly.IsDynamic) { continue; } #endif _assemblies.Add(assembly); var isRuntimeAssembly = IsRuntimeAssembly(assembly); var isEditorAssembly = IsEditorAssembly(assembly, new HashSet <string>()); var isLudiqRuntimeDependentAssembly = IsLudiqRuntimeDependentAssembly(assembly); var isLudiqEditorDependentAssembly = IsLudiqEditorDependentAssembly(assembly); var isLudiqAssembly = isLudiqRuntimeDependentAssembly || isLudiqEditorDependentAssembly; var isLudiqEditorAssembly = isLudiqEditorDependentAssembly; var isLudiqRuntimeAssembly = isLudiqRuntimeDependentAssembly && !isLudiqEditorDependentAssembly; if (isRuntimeAssembly) { _runtimeAssemblies.Add(assembly); } if (isEditorAssembly) { _editorAssemblies.Add(assembly); } if (isLudiqAssembly) { _ludiqAssemblies.Add(assembly); } if (isLudiqEditorAssembly) { _ludiqEditorAssemblies.Add(assembly); } if (isLudiqRuntimeAssembly) { _ludiqRuntimeAssemblies.Add(assembly); } foreach (var type in assembly.GetTypesSafely()) { _types.Add(type); if (isRuntimeAssembly) { _runtimeTypes.Add(type); } if (isEditorAssembly) { _editorTypes.Add(type); } if (isLudiqAssembly) { _ludiqTypes.Add(type); } if (isLudiqEditorAssembly) { _ludiqEditorTypes.Add(type); } if (isLudiqRuntimeAssembly) { _ludiqRuntimeTypes.Add(type); } } } catch (Exception ex) { Debug.LogWarning($"Failed to analyze assembly '{assembly}':\n{ex}"); } } assemblies = _assemblies.AsReadOnly(); runtimeAssemblies = _runtimeAssemblies.AsReadOnly(); editorAssemblies = _editorAssemblies.AsReadOnly(); ludiqAssemblies = _ludiqAssemblies.AsReadOnly(); ludiqRuntimeAssemblies = _ludiqRuntimeAssemblies.AsReadOnly(); ludiqEditorAssemblies = _ludiqEditorAssemblies.AsReadOnly(); types = _types.AsReadOnly(); runtimeTypes = _runtimeTypes.AsReadOnly(); editorTypes = _editorTypes.AsReadOnly(); ludiqTypes = _ludiqTypes.AsReadOnly(); ludiqRuntimeTypes = _ludiqRuntimeTypes.AsReadOnly(); ludiqEditorTypes = _ludiqEditorTypes.AsReadOnly(); } }
private static void Initialize() { EditorApplication.delayCall -= initializeCallbackFunction; using (ProfilingUtility.SampleBlock("Plugin Container Initialization")) { initializing = true; pluginTypesById = Codebase.ludiqEditorTypes .Where(t => typeof(Plugin).IsAssignableFrom(t) && t.IsConcrete()) .ToDictionary(GetPluginID); pluginDependencies = new Dictionary <string, HashSet <string> >(); foreach (var pluginTypeById in pluginTypesById) { pluginDependencies.Add(pluginTypeById.Key, pluginTypeById.Value.GetAttributes <PluginDependencyAttribute>().Select(pda => pda.id).ToHashSet()); } var moduleTypes = Codebase.ludiqEditorTypes .Where(t => typeof(IPluginModule).IsAssignableFrom(t) && t.HasAttribute <PluginModuleAttribute>(false)) .OrderByDependencies(t => t.GetAttributes <PluginModuleDependencyAttribute>().Select(pmda => pmda.moduleType)) .ToArray(); pluginsById = new Dictionary <string, Plugin>(); var allModules = new List <IPluginModule>(); foreach (var pluginId in pluginTypesById.Keys.OrderByDependencies(pluginId => pluginDependencies[pluginId])) { var pluginType = pluginTypesById[pluginId]; Plugin plugin; try { using (ProfilingUtility.SampleBlock($"{pluginType.Name} (Instantiation)")) { plugin = (Plugin)pluginType.Instantiate(); } } catch (Exception ex) { throw new TargetInvocationException($"Could not instantiate plugin '{pluginId}' ('{pluginType.CSharpName()}').", ex); } var modules = new List <IPluginModule>(); foreach (var moduleType in moduleTypes) { try { var required = moduleType.GetAttribute <PluginModuleAttribute>(false).required; var moduleProperty = pluginType.GetProperties().FirstOrDefault(p => p.PropertyType.IsAssignableFrom(moduleType)); if (moduleProperty == null) { continue; } IPluginModule module = null; var moduleOverrideType = Codebase.ludiqEditorTypes .FirstOrDefault(t => moduleType.IsAssignableFrom(t) && t.IsConcrete() && t.HasAttribute <PluginAttribute>() && t.GetAttribute <PluginAttribute>().id == pluginId); if (moduleOverrideType != null) { try { using (ProfilingUtility.SampleBlock($"{moduleOverrideType.Name} (Instantiation)")) { module = (IPluginModule)InstantiateLinkedType(moduleOverrideType, plugin); } } catch (Exception ex) { throw new TargetInvocationException($"Failed to instantiate user-defined plugin module '{moduleOverrideType.CSharpName()}' for '{pluginId}'.", ex); } } else if (moduleType.IsConcrete()) { try { using (ProfilingUtility.SampleBlock($"{moduleType.Name} (Instantiation)")) { module = (IPluginModule)InstantiateLinkedType(moduleType, plugin); } } catch (Exception ex) { throw new TargetInvocationException($"Failed to instantiate built-in plugin module '{moduleType.CSharpName()}' for '{pluginId}'.", ex); } } else if (required) { throw new InvalidImplementationException($"Missing implementation of plugin module '{moduleType.CSharpName()}' for '{pluginId}'."); } if (module != null) { moduleProperty.SetValue(plugin, module, null); modules.Add(module); allModules.Add(module); } } catch (Exception ex) { Debug.LogException(ex); } } pluginsById.Add(plugin.id, plugin); foreach (var module in modules) { try { using (ProfilingUtility.SampleBlock($"{module.GetType().Name} (Initialization)")) { module.Initialize(); } } catch (Exception ex) { Debug.LogException(new Exception($"Failed to initialize plugin module '{plugin.id}.{module.GetType().CSharpName()}'.", ex)); } } if (plugin.manifest.versionMismatch) { anyVersionMismatch = true; } } foreach (var module in allModules) { try { using (ProfilingUtility.SampleBlock($"{module.GetType().Name} (Late Initialization)")) { module.LateInitialize(); } } catch (Exception ex) { Debug.LogException(new Exception($"Failed to late initialize plugin module '{module.plugin.id}.{module.GetType().CSharpName()}'.", ex)); } } var afterPluginTypes = Codebase.ludiqEditorTypes .Where(t => t.HasAttribute <InitializeAfterPluginsAttribute>()); using (ProfilingUtility.SampleBlock($"BeforeInitializeAfterPlugins")) { EditorApplicationUtility.BeforeInitializeAfterPlugins(); } foreach (var afterPluginType in afterPluginTypes) { using (ProfilingUtility.SampleBlock($"{afterPluginType.Name} (Static Initializer)")) { RuntimeHelpers.RunClassConstructor(afterPluginType.TypeHandle); } } using (ProfilingUtility.SampleBlock($"AfterInitializeAfterPlugins")) { EditorApplicationUtility.AfterInitializeAfterPlugins(); } using (ProfilingUtility.SampleBlock($"Delayed Calls")) { lock (delayQueue) { while (delayQueue.Count > 0) { delayQueue.Dequeue().Invoke(); } } } InternalEditorUtility.RepaintAllViews(); ProfilingUtility.Clear(); using (ProfilingUtility.SampleBlock($"Product Container Initialization")) { ProductContainer.Initialize(); } initializing = false; initialized = true; using (ProfilingUtility.SampleBlock($"Update Process")) { // Automatically show update wizard if (!EditorApplication.isPlayingOrWillChangePlaymode && plugins.Any(plugin => plugin.manifest.versionMismatch)) { // Delay call seems to be needed here to avoid arcane exceptions... // Too lazy to debug why, it works that way. EditorApplication.delayCall += PerformUpdate; } } } }
public void Dispose() { ProfilingUtility.EndSample(); }