|
// src: https://gist.github.com/andrew-raphael-lukasik/e02f21cc507da9e5eb7569b8793e70f4 |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using IO = System.IO; |
|
using UnityEngine; |
|
using UnityEngine.Assertions; |
|
using UnityEngine.UIElements; |
|
#if UNITY_EDITOR |
|
using UnityEditor.UIElements; |
|
#endif |
|
using DateTime = System.DateTime; |
|
public class Persistence |
|
{ |
|
static Dictionary<string,int> _integers; |
|
static Dictionary<string,float> _floats; |
|
static Dictionary<string,Vector3> _vectors; |
|
static Dictionary<string,string> _strings; |
|
public static DateTime lastChangeTime { get; private set; } |
|
|
|
public static event System.Action onBeforeSave = ()=> {}; |
|
public static event System.Action onAfterLoad = ()=> {}; |
|
|
|
static Persistence () => Reset();// static constructor for initialization |
|
|
|
/// IMPORTANT: value will be 0 when entry doesn't exist yet |
|
/// hence use if(GetInteger(key,out int val)) { _value = val; } |
|
public static bool GetInteger ( string key , out int value ) |
|
{ |
|
return _integers.TryGetValue( key, out value ); |
|
} |
|
public static void SetInteger ( string key , int value ) |
|
{ |
|
if( _integers.ContainsKey(key) ) _integers[key] = value; |
|
else _integers.Add( key , value ); |
|
lastChangeTime = DateTime.Now; |
|
} |
|
public static void RemoveInteger ( string key ) |
|
{ |
|
if( _integers.ContainsKey(key) ) _integers.Remove(key); |
|
} |
|
|
|
public static bool GetVector ( string key , out Vector3 value ) |
|
{ |
|
return _vectors.TryGetValue( key, out value ); |
|
} |
|
public static void SetVector ( string key , Vector3 value ) |
|
{ |
|
if( _vectors.ContainsKey(key) ) _vectors[key] = value; |
|
else _vectors.Add( key , value ); |
|
lastChangeTime = DateTime.Now; |
|
} |
|
public static void RemoveVector ( string key ) |
|
{ |
|
if( _vectors.ContainsKey(key) ) _vectors.Remove(key); |
|
} |
|
|
|
public static bool GetFloat ( string key , out float value ) |
|
{ |
|
return _floats.TryGetValue( key, out value ); |
|
} |
|
public static void SetFloat ( string key , float value ) |
|
{ |
|
if( _floats.ContainsKey(key) ) _floats[key] = value; |
|
else _floats.Add( key , value ); |
|
lastChangeTime = DateTime.Now; |
|
} |
|
public static void RemoveFloat ( string key ) |
|
{ |
|
if( _floats.ContainsKey(key) ) _floats.Remove(key); |
|
} |
|
|
|
public static bool GetBoolean ( string key , out bool value ) |
|
{ |
|
bool valueExists = GetInteger( key, out int i ); |
|
value = i==0 ? false : true; |
|
return valueExists; |
|
} |
|
public static void SetBoolean ( string key , bool value ) => SetInteger( key , value ? 1 : 0 ); |
|
public static void RemoveBoolean ( string key ) => RemoveInteger( key ); |
|
|
|
public static bool GetString ( string key , out string value ) |
|
{ |
|
return _strings.TryGetValue( key, out value ); |
|
} |
|
public static void SetString ( string key , string value ) |
|
{ |
|
if( _strings.ContainsKey(key) ) _strings[key] = value; |
|
else _strings.Add( key , value ); |
|
lastChangeTime = DateTime.Now; |
|
} |
|
public static void RemoveString ( string key ) |
|
{ |
|
if( _strings.ContainsKey(key) ) _strings.Remove(key); |
|
} |
|
|
|
public static bool Load ( string directory , string fileName ) |
|
{ |
|
string filePath = GetSaveFilePath(directory,fileName); |
|
if( !IO.File.Exists(filePath) ) |
|
return false; |
|
|
|
var json = IO.File.ReadAllText( filePath ); |
|
var data = JsonUtility.FromJson<SerializableData>( json ); |
|
Load( data ); |
|
onAfterLoad(); |
|
|
|
return true; |
|
} |
|
public static void Load ( SerializableData data ) |
|
{ |
|
Reset(); |
|
lastChangeTime = DateTime.Now; |
|
|
|
if( data.integer_keys!=null ) |
|
{ |
|
Assert.AreEqual( data.integer_keys.Length , data.integer_values.Length ); |
|
for( int i=0 ; i<data.integer_keys.Length ; i++ ) |
|
_integers.Add( data.integer_keys[i] , data.integer_values[i] ); |
|
} |
|
if( data.vector_keys!=null ) |
|
{ |
|
Assert.AreEqual( data.vector_keys.Length , data.vector_values.Length ); |
|
for( int i=0 ; i<data.vector_keys.Length ; i++ ) |
|
_vectors.Add( data.vector_keys[i] , data.vector_values[i] ); |
|
} |
|
if( data.float_keys!=null ) |
|
{ |
|
Assert.AreEqual( data.float_keys.Length , data.float_values.Length ); |
|
for( int i=0 ; i<data.float_keys.Length ; i++ ) |
|
_floats.Add( data.float_keys[i] , data.float_values[i] ); |
|
} |
|
if( data.string_keys!=null ) |
|
{ |
|
Assert.AreEqual( data.string_keys.Length , data.string_values.Length ); |
|
for( int i=0 ; i<data.string_keys.Length ; i++ ) |
|
_strings.Add( data.string_keys[i] , data.string_values[i] ); |
|
} |
|
} |
|
|
|
public static void Save ( string directory , string fileName ) |
|
{ |
|
onBeforeSave(); |
|
|
|
Save( out var data ); |
|
string json = JsonUtility.ToJson( data ); |
|
|
|
string fileDir = GetSaveDirectoryPath(directory); |
|
if( !IO.Directory.Exists(fileDir) ) IO.Directory.CreateDirectory(fileDir); |
|
string filePath = GetSaveFilePath(directory,fileName); |
|
IO.File.WriteAllText( path:filePath , contents:json ); |
|
} |
|
public static void Save ( out SerializableData data ) |
|
{ |
|
data = new SerializableData{ |
|
integer_keys = _integers.Keys.ToArray() , |
|
integer_values = _integers.Values.ToArray() , |
|
|
|
vector_keys = _vectors.Keys.ToArray() , |
|
vector_values = _vectors.Values.ToArray() , |
|
|
|
float_keys = _floats.Keys.ToArray() , |
|
float_values = _floats.Values.ToArray() , |
|
|
|
string_keys = _strings.Keys.ToArray() , |
|
string_values = _strings.Values.ToArray() , |
|
}; |
|
} |
|
|
|
static string GetSaveDirectoryPath ( string directory ) => IO.Path.Combine( Application.persistentDataPath , directory ); |
|
static string GetSaveFilePath ( string directory , string fileName ) => IO.Path.Combine( GetSaveDirectoryPath(directory) , fileName ); |
|
|
|
/// call when new game starts |
|
public static void Reset () |
|
{ |
|
_integers = new Dictionary<string,int>(); |
|
_vectors = new Dictionary<string,Vector3>(); |
|
_floats = new Dictionary<string,float>(); |
|
_strings = new Dictionary<string,string>(); |
|
lastChangeTime = DateTime.Now; |
|
} |
|
|
|
/// call from OnValidate methods |
|
public static void ValidateID ( IMember owner ) |
|
{ |
|
// validate prefabs: |
|
#if UNITY_EDITOR |
|
if( owner is Object && UnityEditor.PrefabUtility.IsPartOfPrefabAsset(owner as Object) ) |
|
{ |
|
// GameObject.Intantiate(thisPrefab) will duplicate this IDs likely causing mayhem as a result, so lets clear it |
|
owner.ID = null; |
|
owner.persistent = false; |
|
return; |
|
} |
|
#endif |
|
|
|
// validate scene instances: |
|
if( owner.persistent && string.IsNullOrEmpty(owner.ID) ) |
|
{ |
|
if( Application.isPlaying )// runtime |
|
{ |
|
// no persistence for prefab instances |
|
owner.persistent = false; |
|
owner.ID = $"TEMPORARY.{GenerateUniqueID()}";// mark it, so seeing this appear in data means a bug |
|
} |
|
else// editor |
|
{ |
|
owner.ID = GenerateUniqueID(); |
|
|
|
#if UNITY_EDITOR |
|
if( owner is Object ) |
|
UnityEditor.Undo.RecordObject( owner as Object , "ID generated" ); |
|
#endif |
|
} |
|
} |
|
} |
|
public static string GenerateUniqueID () => System.Guid.NewGuid().ToString(); |
|
|
|
[System.Serializable] |
|
public class SerializableData |
|
{ |
|
public string[] integer_keys; |
|
public int[] integer_values; |
|
|
|
public string[] vector_keys; |
|
public Vector3[] vector_values; |
|
|
|
public string[] float_keys; |
|
public float[] float_values; |
|
|
|
public string[] string_keys; |
|
public string[] string_values; |
|
} |
|
|
|
public interface IMember |
|
{ |
|
string ID { get; set; } |
|
bool persistent { get; set; } |
|
} |
|
|
|
#if UNITY_EDITOR |
|
public class DataInspector : UnityEditor.EditorWindow |
|
{ |
|
ListView _integersView, _vectorsView, _floatsView, _stringsView; |
|
DateTime _lastRebindTime; |
|
void OnEnable () => Rebuild(); |
|
void Rebuild () |
|
{ |
|
rootVisualElement.Clear(); |
|
|
|
var FOLDOUT_INT = new Foldout{ text="Integers" }; |
|
_integersView = CreateListView( _integers , () => new IntegerField() , (e,i) => { |
|
string key = (string) _integersView.itemsSource[i]; |
|
var field = e as IntegerField; |
|
field.label = key; |
|
field.value = _integers[key]; |
|
field.RegisterValueChangedCallback( (e)=> { |
|
_integers[key] = e.newValue; |
|
} ); |
|
} ); |
|
FOLDOUT_INT.style.flexGrow = 1; |
|
FOLDOUT_INT.Add( _integersView ); |
|
|
|
var FOLDOUT_FLT = new Foldout{ text="Floats" }; |
|
_floatsView = CreateListView( _floats , ()=>new FloatField() , (e,i) => { |
|
string key = (string) _floatsView.itemsSource[i]; |
|
var field = e as FloatField; |
|
field.label = key; |
|
field.value = _floats[key]; |
|
field.RegisterValueChangedCallback( (e)=> { |
|
_floats[key] = e.newValue; |
|
} ); |
|
} ); |
|
FOLDOUT_FLT.style.flexGrow = 1; |
|
FOLDOUT_FLT.Add( _floatsView ); |
|
|
|
var FOLDOUT_VEC = new Foldout{ text="Vectors" }; |
|
_vectorsView = CreateListView( _vectors , ()=>new Vector3Field() , (e,i) => { |
|
string key = (string) _vectorsView.itemsSource[i]; |
|
var field = e as Vector3Field; |
|
field.label = $"{i} {key}"; |
|
field.value = _vectors[key]; |
|
field.RegisterValueChangedCallback( (e)=> { |
|
_vectors[key] = e.newValue; |
|
} ); |
|
} ); |
|
FOLDOUT_VEC.style.flexGrow = 1; |
|
FOLDOUT_VEC.Add( _vectorsView ); |
|
|
|
var FOLDOUT_STR = new Foldout{ text="Strings" }; |
|
_stringsView = CreateListView( _strings , ()=>new TextField() , (e,i) => { |
|
string key = (string) _stringsView.itemsSource[i]; |
|
var field = e as TextField; |
|
field.label = $"{i} {key}"; |
|
field.value = _strings[key]; |
|
field.RegisterValueChangedCallback( (e)=> { |
|
_strings[key] = e.newValue; |
|
} ); |
|
} ); |
|
FOLDOUT_STR.style.flexGrow = 1; |
|
FOLDOUT_STR.Add( _stringsView ); |
|
|
|
Rebind(); |
|
rootVisualElement.Add( FOLDOUT_INT ); |
|
rootVisualElement.Add( FOLDOUT_FLT ); |
|
rootVisualElement.Add( FOLDOUT_VEC ); |
|
rootVisualElement.Add( FOLDOUT_STR ); |
|
} |
|
ListView CreateListView <K,V> ( Dictionary<K,V> dict , System.Func<VisualElement> makeItem , System.Action<VisualElement,int> bindItem ) |
|
{ |
|
var LIST = new ListView(); |
|
{ |
|
var style = LIST.style; |
|
style.flexGrow = 1.0f; |
|
style.minHeight = LIST.itemHeight * 3; |
|
} |
|
LIST.itemHeight = 16; |
|
LIST.makeItem = makeItem; |
|
LIST.bindItem = bindItem; |
|
LIST.selectionType = SelectionType.Single;//SelectionType.None; |
|
return LIST; |
|
} |
|
void Rebind () |
|
{ |
|
_integersView.itemsSource = _integers.Keys.ToArray(); |
|
_floatsView.itemsSource = _floats.Keys.ToArray(); |
|
_vectorsView.itemsSource = _vectors.Keys.ToArray(); |
|
_stringsView.itemsSource = _strings.Keys.ToArray(); |
|
_lastRebindTime = lastChangeTime; |
|
} |
|
public void Update () |
|
{ |
|
if( _lastRebindTime!=lastChangeTime ) |
|
Rebind(); |
|
} |
|
|
|
[UnityEditor.MenuItem("Game/Persistence/Inspector",false,0)] |
|
public static void CreateWindow () => UnityEditor.EditorWindow.GetWindow<DataInspector>(); |
|
} |
|
#endif |
|
|
|
} |