using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace Unity.VisualScripting
{
public sealed class GraphReference : GraphPointer
{
static GraphReference()
{
ReferenceCollector.onSceneUnloaded += FreeInvalidInterns;
}
#region Lifecycle
private GraphReference() { }
public static GraphReference New(IGraphRoot root, bool ensureValid)
{
if (!ensureValid && !IsValidRoot(root))
{
return null;
}
var reference = new GraphReference();
reference.Initialize(root);
reference.Hash();
return reference;
}
public static GraphReference New(IGraphRoot root, IEnumerable<IGraphParentElement> parentElements, bool ensureValid)
{
if (!ensureValid && !IsValidRoot(root))
{
return null;
}
var reference = new GraphReference();
reference.Initialize(root, parentElements, ensureValid);
reference.Hash();
return reference;
}
public static GraphReference New(UnityObject rootObject, IEnumerable<Guid> parentElementGuids, bool ensureValid)
{
if (!ensureValid && !IsValidRoot(rootObject))
{
return null;
}
var reference = new GraphReference();
reference.Initialize(rootObject, parentElementGuids, ensureValid);
reference.Hash();
return reference;
}
private static GraphReference New(GraphPointer model)
{
var reference = new GraphReference();
reference.CopyFrom(model);
return reference;
}
public override void CopyFrom(GraphPointer other)
{
base.CopyFrom(other);
if (other is GraphReference reference)
{
hashCode = reference.hashCode;
}
else
{
Hash();
}
}
public GraphReference Clone()
{
return New(this);
}
#endregion
#region Conversion
public override GraphReference AsReference()
{
return this;
}
public GraphStack ToStackPooled()
{
return GraphStack.New(this);
}
internal void Release()
{
releaseDebugDataBinding?.Invoke(root);
}
#endregion
#region Instantiation
public void CreateGraphData()
{
if (_data != null)
{
throw new GraphPointerException("Graph data already exists.", this);
}
if (isRoot)
{
if (machine != null)
{
// Debug.Log($"Creating root graph data for {this}");
_data = machine.graphData = graph.CreateData();
}
else
{
throw new GraphPointerException("Root graph data can only be created on machines.", this);
}
}
else
{
if (_parentData == null)
{
throw new GraphPointerException("Child graph data can only be created from parent graph data.", this);
}
_data = _parentData.CreateChildGraphData(parentElement);
}
}
public void FreeGraphData()
{
if (_data == null)
{
throw new GraphPointerException("Graph data does not exist.", this);
}
if (isRoot)
{
if (machine != null)
{
// Debug.Log($"Freeing root graph data for {this}");
_data = machine.graphData = null;
}
else
{
throw new GraphPointerException("Root graph data can only be freed on machines.", this);
}
}
else
{
if (_parentData == null)
{
throw new GraphPointerException("Child graph data can only be freed from parent graph data.", this);
}
_parentData.FreeChildGraphData(parentElement);
_data = null;
}
}
#endregion
#region Equality
[DoNotSerialize]
private int hashCode;
public override bool Equals(object obj)
{
if (!(obj is GraphReference other))
{
return false;
}
return InstanceEquals(other);
}
private void Hash()
{
hashCode = ComputeHashCode();
}
public override int GetHashCode()
{
return hashCode;
}
public static bool operator ==(GraphReference x, GraphReference y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
{
return false;
}
return x.Equals(y);
}
public static bool operator !=(GraphReference x, GraphReference y)
{
return !(x == y);
}
#endregion
#region Traversal
public GraphReference ParentReference(bool ensureValid)
{
if (isRoot)
{
if (ensureValid)
{
throw new GraphPointerException("Trying to get parent graph reference of a root.", this);
}
else
{
return null;
}
}
var pointer = Clone();
pointer.ExitParentElement();
pointer.Hash();
return pointer;
}
public GraphReference ChildReference(IGraphParentElement parentElement, bool ensureValid, int? maxRecursionDepth = null)
{
var pointer = Clone();
if (!pointer.TryEnterParentElement(parentElement, out var error, maxRecursionDepth))
{
if (ensureValid)
{
throw new GraphPointerException(error, this);
}
else
{
return null;
}
}
pointer.Hash();
return pointer;
}
#endregion
#region Validation
public GraphReference Revalidate(bool ensureValid)
{
try
{
// Important to recreate by GUIDs to avoid serialization ghosting
return New(rootObject, parentElementGuids, ensureValid);
}
catch (Exception ex)
{
if (ensureValid)
{
throw;
}
Debug.LogWarning("Failed to revalidate graph pointer: \n" + ex);
return null;
}
}
#endregion
#region Navigation
public IEnumerable<GraphReference> GetBreadcrumbs()
{
for (int depth = 0; depth < this.depth; depth++)
{
yield return New(root, parentElementStack.Take(depth), true);
}
}
#endregion
#region Interning
private static readonly Dictionary<int, List<GraphReference>> internPool = new Dictionary<int, List<GraphReference>>();
public static GraphReference Intern(GraphPointer pointer)
{
var hash = pointer.ComputeHashCode();
if (internPool.TryGetValue(hash, out var interns))
{
foreach (var intern in interns)
{
if (intern.InstanceEquals(pointer))
{
return intern;
}
}
var reference = New(pointer);
interns.Add(reference);
return reference;
}
else
{
var reference = New(pointer);
internPool.Add(reference.hashCode, new List<GraphReference>() { reference });
return reference;
}
}
internal static void ClearIntern(GraphPointer pointer)
{
var hash = pointer.ComputeHashCode();
if (!internPool.TryGetValue(hash, out var interns)) return;
for (var i = interns.Count - 1; i >= 0; i--)
{
if (interns[i].InstanceEquals(pointer))
{
interns.RemoveAt(i);
break;
}
}
if (interns.Count == 0)
{
internPool.Remove(hash);
}
}
public static void FreeInvalidInterns()
{
var invalidHashes = ListPool<int>.New();
foreach (var internsByHash in internPool)
{
var hash = internsByHash.Key;
var interns = internsByHash.Value;
var invalidInterns = ListPool<GraphReference>.New();
foreach (var intern in interns)
{
if (!intern.isValid)
{
invalidInterns.Add(intern);
}
}
foreach (var intern in invalidInterns)
{
interns.Remove(intern);
}
if (interns.Count == 0)
{
invalidHashes.Add(hash);
}
invalidInterns.Free();
}
foreach (var hash in invalidHashes)
{
internPool.Remove(hash);
}
invalidHashes.Free();
}
#endregion
}
}