Editor/HumanoidRetargeter/Embedded/Datamodel/Attribute.cs
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Collections.Generic;
using System.Numerics;
using AttrKVP = System.Collections.Generic.KeyValuePair<string, object?>;
namespace HumanoidRetargeterDmx
{
/// <summary>
/// A name/value pair associated with an <see cref="Element"/>.
/// </summary>
class Attribute
{
/// <summary>
/// Creates a new Attribute with a specified name and value.
/// </summary>
/// <param name="name">The name of the Attribute, which must be unique to its owner.</param>
/// <param name="value">The value of the Attribute, which must be of a supported HumanoidRetargeterDmx type.</param>
public Attribute(string name, AttributeList owner, object? value)
{
ArgumentNullException.ThrowIfNull(name);
Name = name;
_Owner = owner;
Value = value;
}
/// <summary>
/// Creates a new Attribute with deferred loading.
/// </summary>
/// <param name="name">The name of the Attribute, which must be unique to its owner.</param>
/// <param name="owner">The AttributeList which owns this Attribute.</param>
/// <param name="defer_offset">The location in the encoded DMX stream at which this Attribute's value can be found.</param>
public Attribute(string name, AttributeList owner, long defer_offset)
: this(name, owner, null)
{
ArgumentNullException.ThrowIfNull(owner);
Offset = defer_offset;
}
#region Properties
/// <summary>
/// Gets the name of this Attribute.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets the Type of this Attribute's Value.
/// </summary>
public Type ValueType { get; private set; } = typeof(Element);
/// <summary>
/// Gets or sets the OverrideType of this Attributes.
/// </summary>
public AttributeList.OverrideType? OverrideType
{
get
{
return _OverrideType;
}
set
{
switch (value)
{
case null:
break;
case AttributeList.OverrideType.Angle:
if (ValueType != typeof(global::System.Numerics.Vector3))
throw new AttributeTypeException("OverrideType.Angle can only be applied to global::System.Numerics.Vector3 attributes");
break;
case AttributeList.OverrideType.Binary:
if (ValueType != typeof(byte[]))
throw new AttributeTypeException("OverrideType.Binary can only be applied to byte[] attributes");
break;
default:
throw new NotImplementedException();
}
_OverrideType = value;
}
}
AttributeList.OverrideType? _OverrideType;
/// <summary>
/// Gets the <see cref="AttributeList"/> which this Attribute is a member of.
/// </summary>
public AttributeList? Owner
{
get { return _Owner; }
internal set
{
if (_Owner == value) return;
if (Deferred && _Owner != null) DeferredLoad();
_Owner = value;
}
}
AttributeList? _Owner;
HumanoidRetargeterDmx? OwnerHumanoidRetargeterDmx { get { return Owner?.Owner; } }
/// <summary>
/// Gets whether the value of this Attribute has yet to be decoded.
/// </summary>
public bool Deferred { get { return Offset != 0; } }
/// <summary>
/// Loads the value of this Attribute from the encoded source HumanoidRetargeterDmx.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when the Attribute has already been loaded.</exception>
/// <exception cref="CodecException">Thrown when the deferred load fails.</exception>
public void DeferredLoad()
{
if (Offset == 0) throw new InvalidOperationException("Attribute already loaded.");
if (OwnerHumanoidRetargeterDmx == null || OwnerHumanoidRetargeterDmx.Codec == null)
throw new CodecException("Trying to load a deferred Attribute, but could not find codec.");
try
{
lock (OwnerHumanoidRetargeterDmx.Codec)
{
_Value = OwnerHumanoidRetargeterDmx.Codec.DeferredDecodeAttribute(OwnerHumanoidRetargeterDmx, Offset);
}
}
catch (Exception err)
{
throw new CodecException($"Deferred loading of attribute \"{Name}\" on element {((Element?)Owner)?.ID} using {OwnerHumanoidRetargeterDmx.Codec} codec threw an exception.", err);
}
Offset = 0;
if (_Value is ElementArray elem_array)
elem_array.Owner = Owner;
}
/// <summary>
/// Gets or sets the value held by this Attribute.
/// </summary>
/// <exception cref="CodecException">Thrown when deferred value loading fails.</exception>
/// <exception cref="DestubException">Thrown when Element destubbing fails.</exception>
public object? Value
{
get
{
if (Offset > 0)
DeferredLoad();
if (OwnerHumanoidRetargeterDmx != null)
{
// expand stubs
if (_Value is Element elem && elem.Stub)
{
try { _Value = OwnerHumanoidRetargeterDmx.OnStubRequest(elem.ID) ?? _Value; }
catch (Exception err) { throw new DestubException(this, err); }
}
}
return _Value;
}
set
{
ValueType = value == null ? typeof(Element) : value.GetType();
if (!HumanoidRetargeterDmx.IsHumanoidRetargeterDmxType(ValueType))
throw new AttributeTypeException(String.Format("{0} is not a valid HumanoidRetargeterDmx attribute type. (If this is an array, it must implement IList<T>).", ValueType.FullName));
if (value is Element elem)
{
if (elem.Owner == null)
elem.Owner = OwnerHumanoidRetargeterDmx;
else if (elem.Owner != OwnerHumanoidRetargeterDmx)
throw new ElementOwnershipException();
}
if (value is IEnumerable<Element> elem_enumerable)
{
if (elem_enumerable is not ElementArray)
throw new InvalidOperationException("Element array objects must derive from HumanoidRetargeterDmx.ElementArray");
var elem_array = (ElementArray)value;
if (elem_array.Owner == null)
elem_array.Owner = Owner;
else if (elem_array.Owner != Owner)
throw new InvalidOperationException("ElementArray is already owned by a different HumanoidRetargeterDmx.");
foreach (var arr_elem in elem_array)
{
if (arr_elem == null) continue;
else if (arr_elem.Owner == null)
arr_elem.Owner = OwnerHumanoidRetargeterDmx;
// todo: remove ownership from values
// this is being printed on a debuggerdisplay output for some reason
// else if (arr_elem.Owner != OwnerHumanoidRetargeterDmx)
// throw new ElementOwnershipException("One or more Elements in the assigned collection are from a different HumanoidRetargeterDmx. Use ImportElement() to copy them to this one before assigning.");
}
}
_Value = value;
Offset = 0;
}
}
object? _Value = null;
/// <summary>
/// Gets the Attribute's Value without attempting deferred loading or destubbing.
/// </summary>
public object? RawValue { get { return _Value; } }
#endregion
long Offset;
public override string ToString()
{
var type = Value != null ? Value.GetType() : typeof(Element);
var inner_type = HumanoidRetargeterDmx.GetArrayInnerType(type);
return String.Format("{0} <{1}>", Name, inner_type != null ? inner_type.FullName + "[]" : type.FullName);
}
public AttrKVP ToKeyValuePair()
{
return new AttrKVP(Name, Value);
}
}
}