using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace Unity.VisualScripting
{
[PluginModule(required = true)]
public class PluginConfiguration : IPluginModule, IEnumerable<PluginConfigurationItemMetadata>
{
protected PluginConfiguration(Plugin plugin)
{
this.plugin = plugin;
}
public virtual void Initialize()
{
Load();
#if VISUAL_SCRIPT_DEBUG_MIGRATION
Debug.Log(
$"Plugin Configuration for {this.plugin.id} init, load complete. Saved version is {savedVersion}");
#endif
if (savedVersion != "0.0.0")
return;
// If our savedVersion is still 0.0.0, it means we didn't load an existing project savedVersion
// Run any deprecatedSavedVersionLoaders we have to see if we can detect and load any older savedVersion formats in the project
// Order by descending to find the latest possible savedVersion in the project first
deprecatedSavedVersionLoaders = PluginContainer.InstantiateLinkedTypes(typeof(PluginDeprecatedSavedVersionLoader), plugin).
Cast<PluginDeprecatedSavedVersionLoader>().ToArray().OrderByDescending(m => m.@from).ToList().AsReadOnly();
foreach (var migration in deprecatedSavedVersionLoaders)
{
var success = migration.Run(out var loadedVersion);
if (success)
{
// Once we've found a valid savedVersion, we can break out and let the pluginMigrations run
savedVersion = loadedVersion;
#if VISUAL_SCRIPT_DEBUG_MIGRATION
Debug.Log($"Found legacy version, loaded as {loadedVersion}");
#endif
return;
}
}
// If we get here, it means we couldn't find any savedVersion in the project (legacy or not), it must be a fresh project
#if VISUAL_SCRIPT_DEBUG_MIGRATION
Debug.Log($"Found no legacy versions for {this.plugin.id}, setting to {plugin.manifest.version}");
#endif
projectSettingsAssetDirty = true;
savedVersion = plugin.manifest.version;
}
public virtual void LateInitialize() { }
public Plugin plugin { get; }
public virtual string header => plugin.manifest.name;
public ReadOnlyCollection<PluginDeprecatedSavedVersionLoader> deprecatedSavedVersionLoaders { get; private set; }
#region Lifecycle
private void Load()
{
LoadEditorPrefs();
LoadProjectSettings();
}
internal void SetDirty()
{
projectSettingsAssetDirty = true;
}
public void Reset()
{
foreach (var item in allItems)
{
item.Reset();
}
}
public void Save()
{
foreach (var item in allItems)
{
item.Save();
}
}
#endregion
#region All Items
private IEnumerable<PluginConfigurationItemMetadata> allItems => LinqUtility.Concat<PluginConfigurationItemMetadata>(editorPrefs, projectSettings);
public IEnumerator<PluginConfigurationItemMetadata> GetEnumerator()
{
return allItems.OrderBy(i => i.member.MetadataToken).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public PluginConfigurationItemMetadata GetMetadata(string memberName)
{
return allItems.First(metadata => metadata.member.Name == memberName);
}
#endregion
#region Editor Prefs
internal List<EditorPrefMetadata> editorPrefs;
private void LoadEditorPrefs()
{
editorPrefs = new List<EditorPrefMetadata>();
var metadata = Metadata.Root();
foreach (var memberInfo in GetType().GetMembers().Where(f => f.HasAttribute<EditorPrefAttribute>()).OrderBy(m => m.MetadataToken))
{
editorPrefs.Add(metadata.EditorPref(this, memberInfo));
}
}
#endregion
#region Project Settings
public List<ProjectSettingMetadata> projectSettings;
private string projectSettingsStoragePath => PluginPaths.projectSettings;
private bool projectSettingsAssetDirty = false;
internal DictionaryAsset projectSettingsAsset { get; set; }
internal void LoadProjectSettings()
{
LoadOrCreateProjectSettingsAsset();
ResetProjectSettingsMetadata();
}
internal void ResetProjectSettingsMetadata()
{
projectSettings = new List<ProjectSettingMetadata>();
var metadata = Metadata.Root();
foreach (var memberInfo in GetType().GetMembers().Where(f => f.HasAttribute<ProjectSettingAttribute>()).OrderBy(m => m.MetadataToken))
{
projectSettings.Add(metadata.ProjectSetting(this, memberInfo));
}
}
internal void LoadOrCreateProjectSettingsAsset()
{
if (File.Exists(projectSettingsStoragePath))
{
// Try loading the existing asset file.
var objects = InternalEditorUtility.LoadSerializedFileAndForget(projectSettingsStoragePath);
if (objects.Length <= 0 || objects[0] == null)
{
// The file exists, but it isn't a valid asset.
// Warn and leave the asset as is to prevent losing its serialized contents
// because we might be able to salvage them by deserializing later on.
// Return a new empty instance in the mean time.
Debug.LogWarning($"Loading visual scripting project settings failed!");
projectSettingsAsset = ScriptableObject.CreateInstance<DictionaryAsset>();
return;
}
projectSettingsAsset = (DictionaryAsset)objects[0];
}
else
{
// The file doesn't exist, so create a new asset
projectSettingsAsset = ScriptableObject.CreateInstance<DictionaryAsset>();
}
}
public void SaveProjectSettingsAsset(bool immediately = false)
{
if (projectSettingsAssetDirty || immediately)
{
EditorApplication.delayCall += SerializeProjectSettingsAssetToDisk;
}
}
private void SerializeProjectSettingsAssetToDisk()
{
if (VSUsageUtility.isVisualScriptingUsed)
{
// make sure the path exists or file write will fail
PathUtility.CreateParentDirectoryIfNeeded(projectSettingsStoragePath);
const bool saveAsText = true;
InternalEditorUtility.SaveToSerializedFileAndForget(new UnityEngine.Object[] { projectSettingsAsset },
projectSettingsStoragePath, saveAsText);
}
}
#endregion
#region Items
/// <summary>
/// Whether the plugin was properly setup.
/// </summary>
[ProjectSetting(visibleCondition = nameof(developerMode), resettable = false)]
public bool projectSetupCompleted { get; internal set; }
/// <summary>
/// Whether the plugin was properly setup.
/// </summary>
[EditorPref(visibleCondition = nameof(developerMode), resettable = false)]
public bool editorSetupCompleted { get; internal set; }
/// <summary>
/// The last version to which the plugin successfully upgraded.
/// </summary>
[ProjectSetting(visibleCondition = nameof(developerMode), resettable = false)]
public SemanticVersion savedVersion { get; internal set; }
protected bool developerMode => BoltCore.Configuration.developerMode;
#endregion
#region Menu
#if VISUAL_SCRIPT_INTERNAL
[MenuItem("Tools/Bolt/Internal/Delete All Project Settings", priority = LudiqProduct.DeveloperToolsMenuPriority + 401)]
#endif
public static void DeleteAllProjectSettings()
{
foreach (var plugin in PluginContainer.plugins)
{
AssetDatabase.DeleteAsset(PathUtility.FromProject(plugin.configuration.projectSettingsStoragePath));
}
}
#if VISUAL_SCRIPT_INTERNAL
[MenuItem("Tools/Bolt/Internal/Delete All Editor Prefs", priority = LudiqProduct.DeveloperToolsMenuPriority + 402)]
#endif
public static void DeleteAllEditorPrefs()
{
foreach (var plugin in PluginContainer.plugins)
{
// Delete all current editor prefs for this plugin
foreach (var editorPref in plugin.configuration.editorPrefs)
{
EditorPrefs.DeleteKey(editorPref.namespacedKey);
}
// If our plugin was renamed, delete all editor pref keys for the plugin using its previous names
IEnumerable<RenamedFromAttribute> renamedFromAttributes;
var fieldInfo = plugin.GetType().GetField("ID", BindingFlags.Public | BindingFlags.Static);
renamedFromAttributes = fieldInfo.GetCustomAttributes(typeof(RenamedFromAttribute), true).Cast<RenamedFromAttribute>();
foreach (var renamed in renamedFromAttributes)
{
foreach (var editorPref in plugin.configuration.editorPrefs)
{
EditorPrefs.DeleteKey(EditorPrefMetadata.GetNamespacedKey(renamed.previousName, editorPref.key));
}
}
}
}
#if VISUAL_SCRIPT_INTERNAL
[MenuItem("Tools/Bolt/Internal/Delete All Player Prefs", priority = LudiqProduct.DeveloperToolsMenuPriority + 403)]
#endif
public static void DeleteAllPlayerPrefs()
{
PlayerPrefs.DeleteAll();
}
#endregion
}
}