projectEli/Assets/NeoFPS/Core/NeoSaveGames/Serialization/NeoSerializedGameObjectContainerBase.cs
2022-11-06 20:28:33 -05:00

454 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace NeoSaveGames.Serialization
{
[Serializable]
public abstract class NeoSerializedGameObjectContainerBase : INeoSerializedGameObjectContainer
{
[SerializeField]
private List<int> m_Keys = new List<int>();
[SerializeField]
private List<NeoSerializedGameObject> m_Values = new List<NeoSerializedGameObject>();
private static readonly NeoSerializationKey k_DestroyedKey = new NeoSerializationKey("destroyedObjects");
private static readonly NeoSerializationKey k_RuntimeKey = new NeoSerializationKey("runtimeObjects");
private Dictionary<int, NeoSerializedGameObject> m_TrackedObjects = null;
private List<int> m_DestroyedObjects = null;
public abstract Transform rootTransform
{
get;
}
public abstract bool isValid
{
get;
}
protected bool isBuildingHierarchy
{
get;
private set;
}
public NeoSerializedGameObject this[int key]
{
get
{
NeoSerializedGameObject result;
m_TrackedObjects.TryGetValue(key, out result);
return result;
}
}
public void Awake()
{
if (m_TrackedObjects == null && isValid)
RebuildDictionary();
}
public void OnDestroy()
{
m_TrackedObjects = null;
m_DestroyedObjects = null;
}
public bool Contains(NeoSerializedGameObject nsgo)
{
return m_Values.Contains(nsgo);
}
public int GetSerializationKeyForObject(NeoSerializedGameObject nsgo)
{
if (Application.isPlaying)
{
foreach (var pair in m_TrackedObjects)
{
if (pair.Value == nsgo)
return pair.Key;
}
}
else
{
for (int i = 0; i < m_Values.Count; ++i)
{
if (m_Values[i] == nsgo)
return m_Keys[i];
}
}
return 0;
}
void RebuildDictionary()
{
int count = Math.Min(m_Keys.Count, m_Values.Count);
m_TrackedObjects = new Dictionary<int, NeoSerializedGameObject>(count);
for (int i = 0; i != count; i++)
{
if (m_Values[i] != null)
{
m_TrackedObjects.Add(m_Keys[i], m_Values[i]);
m_Values[i].serializationKey = m_Keys[i];
m_Values[i].onDestroyed += UnregisterObject;
}
}
m_DestroyedObjects = new List<int>();
if (Application.isPlaying)
{
m_Keys = null;
m_Values = null;
}
}
protected int GenerateKey()
{
int key = 0;
while (key == 0 || m_TrackedObjects.ContainsKey(key))
key = UnityEngine.Random.Range(int.MinValue, int.MaxValue);
return key;
}
public virtual void RegisterObject(NeoSerializedGameObject nsgo)
{
if (m_TrackedObjects == null)
RebuildDictionary();
// Check if child object
if (nsgo.transform.parent != rootTransform)
{
Debug.LogError("Attempting to register object that is not a child of the container object.", nsgo.gameObject);
return;
}
// Use existing serialization key (if instantiated on load) or generate a new one
int key = nsgo.serializationKey;
if (key == 0)
key = GenerateKey();
else
{
if (m_TrackedObjects.ContainsKey(key))
{
Debug.LogError(string.Format("Attempting to register object with key ({0}) that is already registered. This object will not be tracked: {1}", key, nsgo.name), nsgo.gameObject);
return;
}
}
m_TrackedObjects.Add(key, nsgo);
nsgo.serializationKey = key;
if (Application.isPlaying)
nsgo.onDestroyed += UnregisterObject;
}
public virtual void UnregisterObject(NeoSerializedGameObject nsgo)
{
if (m_TrackedObjects == null)
return;
NeoSerializedGameObject found = null;
if (m_TrackedObjects.TryGetValue(nsgo.serializationKey, out found) && found == nsgo)
{
m_TrackedObjects.Remove(nsgo.serializationKey);
nsgo.onDestroyed -= UnregisterObject;
}
// Add to destroyed list
if (!nsgo.wasRuntimeInstantiated)
m_DestroyedObjects.Add(nsgo.serializationKey);
}
public NeoSerializedGameObject GetChildObject(int serializationKey)
{
NeoSerializedGameObject result;
if (m_TrackedObjects.TryGetValue(serializationKey, out result))
return result;
else
return null;
}
public NeoSerializedGameObject CreateChildObject(string name)
{
return CreateChildObject(name, Vector3.zero, Quaternion.identity, 0);
}
public NeoSerializedGameObject CreateChildObject(string name, int serializationKey)
{
return CreateChildObject(name, Vector3.zero, Quaternion.identity, serializationKey);
}
public NeoSerializedGameObject CreateChildObject(string name, Vector3 localPosition, Quaternion localRotation)
{
return CreateChildObject(name, localPosition, localRotation, 0);
}
public NeoSerializedGameObject CreateChildObject(string name, Vector3 localPosition, Quaternion localRotation, int serializationKey)
{
// Create object and add NeoSerializedGameObject
var go = new GameObject(name);
var result = go.AddComponent<NeoSerializedGameObject>();
//result.serializeName = true;
// Position, etc
var t = result.transform;
t.SetParent(rootTransform);
t.localPosition = localPosition;
t.localRotation = localRotation;
t.localScale = Vector3.one;
// Register with object
result.serializationKey = serializationKey;
RegisterObject(result);
result.wasRuntimeInstantiated = true;
return result;
}
public void WriteGameObjects(INeoSerializer writer, SaveMode saveMode)
{
WriteGameObjects(writer, NeoSerializationFilter.Exclude, null, saveMode);
}
public void WriteGameObjects(INeoSerializer writer, NeoSerializationFilter filter, NeoSerializedGameObject[] objects, SaveMode saveMode)
{
// Check if awake
Awake();
// Write list of destroyed objects (non-runtime initialised)
if (m_DestroyedObjects.Count > 0)
writer.WriteValues(k_DestroyedKey, m_DestroyedObjects);
if (m_TrackedObjects.Count > 0)
{
// Runtime initialised prefabs & objects
var runtime = new List<Vector2Int>(m_TrackedObjects.Count);
var nsgos = new List<NeoSerializedGameObject>(m_TrackedObjects.Count);
// Populate
foreach (var nsgo in m_TrackedObjects.Values)
{
bool serializeObject = false;
if (filter == NeoSerializationFilter.Include)
{
// Add if in objects list
if (objects != null && Array.IndexOf(objects, nsgo) != -1)
serializeObject = true;
}
else
{
// Add if not in objects list
if (objects == null || Array.IndexOf(objects, nsgo) == -1)
serializeObject = true;
}
if (serializeObject)
{
nsgos.Add(nsgo);
if (nsgo.wasRuntimeInstantiated)
{
//Debug.Log(string.Format("Writing runtime object: {0}, key: {1}", nsgo.name, nsgo.serializationKey));
runtime.Add(new Vector2Int(nsgo.prefabStrongID, nsgo.serializationKey));
}
}
}
// write list of runtime initialised prefabs
if (runtime.Count > 0)
writer.WriteValues(k_RuntimeKey, runtime);
// Write all tracked gameobjects
for (int i = 0; i < nsgos.Count; ++i)
{
if (nsgos[i] != null)
{
writer.PushContext(SerializationContext.GameObject, nsgos[i].serializationKey);
nsgos[i].WriteGameObject(writer, saveMode);
writer.PopContext(SerializationContext.GameObject);
}
}
}
}
public void ReadGameObjectHierarchy(INeoDeserializer reader)
{
// Check if awake
Awake();
isBuildingHierarchy = true;
// Should check non-dynamic children are already registered??
// Destroy any non-runtime instantiated objects that were removed
int[] destroyed;
if (reader.TryReadValues(k_DestroyedKey, out destroyed, null))
{
for (int i = 0; i < destroyed.Length; ++i)
{
NeoSerializedGameObject destroy;
if (m_TrackedObjects.TryGetValue(destroyed[i], out destroy))
UnityEngine.Object.Destroy(destroy.gameObject);
}
}
// Instantiate runtime objects
Vector2Int[] runtime;
if (reader.TryReadValues(k_RuntimeKey, out runtime, null))
{
for (int i = 0; i < runtime.Length; ++i)
{
if (runtime[i].x == 0)
CreateChildObject("Pending", runtime[i].y);
else
{
//try
//{
NeoSerializedObjectFactory.Instantiate(runtime[i].x, runtime[i].y, this);
//}
//catch (Exception e)
//{
// Debug.LogError("Failed to instantiate runtime object due to error: " + e.Message);
//}
}
}
}
// Read all tracked objects
foreach (var nsgo in m_TrackedObjects.Values)
{
if (reader.PushContext(SerializationContext.GameObject, nsgo.serializationKey))
{
nsgo.ReadGameObjectHierarchy(reader);
reader.PopContext(SerializationContext.GameObject, nsgo.serializationKey);
}
}
isBuildingHierarchy = false;
}
public void ReadGameObjectProperties(INeoDeserializer reader)
{
// Read all tracked objects
foreach (var nsgo in m_TrackedObjects.Values)
{
if (reader.PushContext(SerializationContext.GameObject, nsgo.serializationKey))
{
nsgo.ReadGameObjectProperties(reader);
reader.PopContext(SerializationContext.GameObject, nsgo.serializationKey);
}
}
}
#if UNITY_EDITOR
private static List<NeoSerializedGameObject> s_StoredObjects = new List<NeoSerializedGameObject>();
public void Validate(NeoSerializedGameObject nsgo)
{
if (!isValid || Application.isPlaying)
return;
// Check if object is registered
for (int i = 0; i < m_Keys.Count && i < m_Values.Count; ++i)
{
if (m_Values[i] == nsgo)
return;
}
// Add if not
m_Values.Add(nsgo);
m_Keys.Add(EditorGenerateKey());
}
public bool Validate(List<NeoSerializedGameObject> gathered)
{
if (!isValid || Application.isPlaying)
return false;
bool result = false;
// Check keys lengths
if (m_Keys.Count != m_Values.Count)
{
// Trim keys array
if (m_Keys.Count > m_Values.Count)
m_Keys.RemoveRange(m_Values.Count, m_Keys.Count - m_Values.Count);
else
{
// Extend keys array
for (int i = m_Keys.Count; i < m_Values.Count; ++i)
m_Keys.Add(EditorGenerateKey());
result = true;
}
}
// Check values validity
for (int i = m_Values.Count - 1; i >= 0; --i)
{
if (m_Values[i] == null || m_Keys[i] == 0)
{
m_Values.RemoveAt(i);
m_Keys.RemoveAt(i);
result = true;
}
}
// Record values to check which are still valid
if (s_StoredObjects == null)
s_StoredObjects = new List<NeoSerializedGameObject>(m_Values.Count);
for (int i = 0; i < m_Values.Count; ++i)
s_StoredObjects.Add(m_Values[i]);
// Gather scene objects
for (int i = 0; i < gathered.Count; ++i)
{
// If already known, remove from remaining list
if (m_Values.Contains(gathered[i]))
s_StoredObjects.Remove(gathered[i]);
else
{
// Else add to container
m_Values.Add(gathered[i]);
m_Keys.Add(EditorGenerateKey());
result = true;
}
}
// Remove remaining objects that weren't found
// They must have moved in the hierarchy
for (int i = 0; i < s_StoredObjects.Count; ++i)
{
if (!Application.isPlaying)
Debug.LogWarning(string.Format("A NeoSerializedGameObject was registered with the wrong parent: {0}. If you insert a new NSGO between 2 others in the hierarchy, any old game saves will skip loading the old child object.", s_StoredObjects[i].name));
int index = m_Values.IndexOf(s_StoredObjects[i]);
if (index != -1)
{
m_Values.RemoveAt(index);
m_Keys.RemoveAt(index);
result = true;
}
}
// Check child object hierarchy
foreach (var nsgo in m_Values)
result |= nsgo.CheckPrefabHierarchy();
s_StoredObjects.Clear();
return result;
}
protected int EditorGenerateKey()
{
int result = 0;
while (result == 0 || m_Keys.Contains(result))
result = UnityEngine.Random.Range(int.MinValue, int.MaxValue);
return result;
}
#endif
}
}