Files
2026-02-28 12:43:44 +08:00

246 lines
9.5 KiB
C#

// Copyright © 2018 Procedural Worlds Pty Limited. All Rights Reserved.
using UnityEngine;
using System;
using System.IO;
using System.Xml.Serialization;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace GeNa.Core
{
/// <summary>
/// Interface class for the GeNa settings. Handles storing and retrieving all scoped setting, including Editor scope settings.
/// </summary>
[XmlRoot("GeNaPreferences")]
public class Preferences
{
#region Variables
private const string ID = "GeNa";
private const string PREF_FILE = "Preferences-" + ID;
private const string PREF_EXTENSION = ".txt";
private const string GENA_FOLDER_NAME = "GeNa";
private const string GENA_RES_INTERNAL_PATH = "Resources";
private static bool ms_dirty = false;
private static Preferences m_instance;
public bool m_defSpawnToTarget = true;
#endregion
#region Properties
/// <summary>
/// The instance that will hold the values in memory.
/// </summary>
private static Preferences Instance
{
get
{
if (m_instance == null)
Load();
return m_instance;
}
}
/// <summary>
/// The default 'Spawn To Target' value for new spawners.
/// </summary>
public static bool DefaultSpawnToTarget
{
get => Instance.m_defSpawnToTarget;
set
{
if (value != Instance.m_defSpawnToTarget)
{
Instance.m_defSpawnToTarget = value;
#if UNITY_EDITOR
Save();
EditorPrefs.SetBool(Defaults.DEF_SPAWN_TO_TARGET_KEY, Instance.m_defSpawnToTarget);
#endif
}
}
}
#endregion
#region Constructors
/// <summary>
/// Instances of this mean nothing to users. This should be only used by deserialisation.
/// </summary>
private Preferences()
{
//Removes warning
if (!ms_dirty)
ms_dirty = false;
}
#endregion
#region Methods
/// <summary>
/// Syncronises settings of an instance with the Editor.
/// See login in <seealso cref="EditorNeedsUpdate{T}(T, ref T, T)"/>.
/// </summary>
protected static void SyncWithEditor()
{
#if UNITY_EDITOR
if (m_instance == null)
{
Debug.LogErrorFormat("[GeNa] Can't call Editor syncing on null Preferences. Aborting.");
return;
}
// Undo Settings
// Advanced Settings
SyncSetting(Defaults.DEF_SPAWN_TO_TARGET_KEY, ref m_instance.m_defSpawnToTarget, true);
if (ms_dirty)
Save();
#endif
}
/// <summary>
/// Load the preferences.
/// </summary>
public static void Load()
{
TextAsset asset = Resources.Load<TextAsset>(PREF_FILE);
if (asset != null)
{
m_instance = Deserialize(asset.text);
if (m_instance == null)
{
Debug.LogError("[GeNa] Unable to get data from preference storage. Creating a new one.");
m_instance = new Preferences();
// Force save to save the new storage.
ms_dirty = true;
}
}
else
{
Debug.LogWarningFormat("[GeNa] Unable to locate GeNa preferences. Falling back to the defaults.");
m_instance = new Preferences();
// Force save to save to fix the corrupted storage.
ms_dirty = true;
}
// Sync once loaded or created
SyncWithEditor();
}
/// <summary>
/// Deserialize preferences from XML string
/// </summary>
/// <param name="xmlString">XML code to deserialize</param>
internal static Preferences Deserialize(string xmlString)
{
XmlSerializer serializer = new XmlSerializer(typeof(Preferences));
return serializer.Deserialize(new StringReader(xmlString)) as Preferences;
}
#if UNITY_EDITOR
/// <summary>
/// Syncs the Editor settings with the ones stored in the project, so they can be used runtime.
/// </summary>
private static void SyncSetting(string key, ref bool value, bool nonSetValue)
{
bool editorVal = EditorPrefs.GetBool(key, nonSetValue);
if (EditorNeedsUpdate(editorVal, ref value, nonSetValue))
EditorPrefs.SetBool(key, value);
}
/// <summary>
/// Syncs the Editor settings with the ones stored in the project, so they can be used runtime.
/// </summary>
private static void SyncSetting(string key, ref float value, float nonSetValue)
{
float editorVal = EditorPrefs.GetFloat(key, nonSetValue);
if (EditorNeedsUpdate(editorVal, ref value, nonSetValue))
EditorPrefs.SetFloat(key, value);
}
/// <summary>
/// Syncs the Editor settings with the ones stored in the project, so they can be used runtime.
/// </summary>
private static void SyncSetting(string key, ref int value, int nonSetValue)
{
int editorVal = EditorPrefs.GetInt(key, nonSetValue);
if (EditorNeedsUpdate(editorVal, ref value, nonSetValue))
EditorPrefs.SetInt(key, value);
}
/// <summary>
/// Syncs the Editor settings with the ones stored in the project, so they can be used runtime.
/// </summary>
private static void SyncSetting(string key, ref string value, string nonSetValue)
{
string editorVal = EditorPrefs.GetString(key, nonSetValue);
if (EditorNeedsUpdate(editorVal, ref value, nonSetValue))
EditorPrefs.SetString(key, value);
}
/// <summary>
/// This is where the logic is implemented, which decides how we sync. Returns true if the Editor needs to be updated and handles everything else.
/// </summary>
/// <returns>Returns true if the Editor needs to be updated.</returns>
private static bool EditorNeedsUpdate<T>(T editorVal, ref T storedVal, T nonSetValue) where T : IComparable
{
if (editorVal.CompareTo(storedVal) == 0)
return false;
// Editor setting takes precedence if it has been set
if (editorVal.CompareTo(nonSetValue) != 0)
{
storedVal = editorVal;
// Set dirty
ms_dirty = true;
return false;
}
// Let's update the editor setting otherwise.
// For example, this could be a computer reinstall and we are getting the settings from version control.
return true;
}
/// <summary>
/// Save the preferences.
/// </summary>
public static void Save()
{
bool needDBRefresh = false;
TextAsset asset = Resources.Load<TextAsset>(PREF_FILE);
string path = AssetDatabase.GetAssetPath(asset);
if (string.IsNullOrEmpty(path))
{
Debug.LogWarningFormat("Unable to locate GeNa preferences to save to. " +
"Creating a new storage to store the settings.");
path = GetPreferencesPath();
needDBRefresh = true;
}
if (IsPathCorrect(path) == false)
{
Debug.LogErrorFormat("[GeNa] Preferences saving aborted. Were GeNa preferences moved or copied? Path is inccorrect: '{0}'.", path);
return;
}
XmlSerializer serializer = new XmlSerializer(typeof(Preferences));
using (FileStream stream = new FileStream(path, FileMode.Create))
serializer.Serialize(stream, m_instance);
if (needDBRefresh)
AssetDatabase.Refresh();
}
/// <summary>
/// Gets path for a new preference file.
/// </summary>
private static string GetPreferencesPath()
{
string resFolder = GetResFolder();
if (string.IsNullOrEmpty(resFolder))
{
Debug.LogErrorFormat("Unable to locate the GeNa resources folder at '{0}/{1}'", GENA_FOLDER_NAME, GENA_RES_INTERNAL_PATH);
return null;
}
return resFolder + "/" + PREF_FILE + PREF_EXTENSION;
}
/// <summary>
/// Checks if the path m_points to the desired folder.
/// </summary>
private static bool IsPathCorrect(string prefPath)
{
string resFolder = GetResFolder();
if (string.IsNullOrEmpty(resFolder))
{
Debug.LogErrorFormat("Unable to locate the GeNa resources folder at '{0}/{1}'", GENA_FOLDER_NAME, GENA_RES_INTERNAL_PATH);
return false;
}
if (Path.GetFullPath(prefPath) == Path.GetFullPath(resFolder + "/" + PREF_FILE + PREF_EXTENSION))
return true;
return false;
}
/// <summary>
/// Returns the GeNa Resources folder or null.
/// </summary>
private static string GetResFolder() => PWCommon5.Utils.GetAppsSubfolder(GENA_FOLDER_NAME, GENA_RES_INTERNAL_PATH);
#endif
#endregion
}
}