2019-01-26 00:39:37 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Text;
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2019-02-01 17:19:20 +00:00
|
|
|
|
namespace RobloxFiles
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// <summary>
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// Describes an object in Roblox's DataModel hierarchy.
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// Instances can have sets of properties loaded from *.rbxl/*.rbxm files.
|
|
|
|
|
/// </summary>
|
2019-02-03 13:28:54 +00:00
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
public class Instance
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
public Instance()
|
|
|
|
|
{
|
|
|
|
|
Name = ClassName;
|
2021-01-20 20:45:58 +00:00
|
|
|
|
RefreshProperties();
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// <summary>The ClassName of this Instance.</summary>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
public string ClassName => GetType().Name;
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
2019-07-03 14:48:05 +00:00
|
|
|
|
/// <summary>Internal list of properties that are under this Instance.</summary>
|
2020-09-14 16:20:34 +00:00
|
|
|
|
private readonly Dictionary<string, Property> props = new Dictionary<string, Property>();
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>A list of properties that are defined under this Instance.</summary>
|
2019-05-19 04:44:51 +00:00
|
|
|
|
public IReadOnlyDictionary<string, Property> Properties => props;
|
2019-05-17 12:08:06 +00:00
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// <summary>The raw list of children for this Instance.</summary>
|
2019-12-23 03:23:06 +00:00
|
|
|
|
internal HashSet<Instance> Children = new HashSet<Instance>();
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2020-09-17 17:14:57 +00:00
|
|
|
|
/// <summary>The raw unsafe value of the Instance's parent.</summary>
|
|
|
|
|
private Instance ParentUnsafe;
|
2019-01-29 09:50:55 +00:00
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// <summary>The name of this Instance.</summary>
|
|
|
|
|
public string Name;
|
|
|
|
|
|
|
|
|
|
/// <summary>Indicates whether this Instance should be serialized.</summary>
|
|
|
|
|
public bool Archivable = true;
|
2020-09-17 17:14:57 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>The source AssetId this instance was created in.</summary>
|
|
|
|
|
public long SourceAssetId = -1;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2019-02-04 19:30:33 +00:00
|
|
|
|
/// <summary>The name of this Instance, if a Name property is defined.</summary>
|
2019-01-29 09:50:55 +00:00
|
|
|
|
public override string ToString() => Name;
|
|
|
|
|
|
2019-06-08 03:43:28 +00:00
|
|
|
|
/// <summary>A unique identifier for this instance when being serialized.</summary>
|
2020-09-14 16:20:34 +00:00
|
|
|
|
public string Referent { get; set; }
|
2019-05-17 12:08:06 +00:00
|
|
|
|
|
2019-05-19 04:44:51 +00:00
|
|
|
|
/// <summary>Indicates whether the parent of this object is locked.</summary>
|
2019-06-08 03:43:28 +00:00
|
|
|
|
public bool ParentLocked { get; internal set; }
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// <summary>Indicates whether this Instance is a Service.</summary>
|
2019-06-08 03:43:28 +00:00
|
|
|
|
public bool IsService { get; internal set; }
|
|
|
|
|
|
2020-09-21 18:29:31 +00:00
|
|
|
|
/// <summary>Indicates whether this Instance has been destroyed.</summary>
|
|
|
|
|
public bool Destroyed { get; internal set; }
|
|
|
|
|
|
2021-02-18 19:15:08 +00:00
|
|
|
|
/// <summary>A hashset of CollectionService tags assigned to this Instance.</summary>
|
2021-06-05 22:21:12 +00:00
|
|
|
|
public readonly HashSet<string> Tags = new HashSet<string>();
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2021-02-25 20:09:54 +00:00
|
|
|
|
/// <summary>The public readonly access point of the attributes on this Instance.</summary>
|
2021-06-05 22:21:12 +00:00
|
|
|
|
public readonly RbxAttributes Attributes = new RbxAttributes();
|
2019-11-01 02:40:31 +00:00
|
|
|
|
|
2019-10-30 23:33:00 +00:00
|
|
|
|
/// <summary>The internal serialized data of this Instance's attributes</summary>
|
2019-11-01 02:40:31 +00:00
|
|
|
|
internal byte[] AttributesSerialize
|
|
|
|
|
{
|
2021-06-05 22:21:12 +00:00
|
|
|
|
get => Attributes.Save();
|
|
|
|
|
set => Attributes.Load(value);
|
2019-11-01 02:40:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Internal format of the Instance's CollectionService tags.
|
|
|
|
|
/// Property objects will look to this member for serializing the Tags property.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal byte[] SerializedTags
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2021-05-03 22:26:55 +00:00
|
|
|
|
if (Tags.Count == 0)
|
|
|
|
|
return null;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2021-05-03 22:26:55 +00:00
|
|
|
|
string fullString = string.Join("\0", Tags);
|
|
|
|
|
char[] buffer = fullString.ToCharArray();
|
|
|
|
|
return Encoding.UTF8.GetBytes(buffer);
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
int length = value.Length;
|
|
|
|
|
|
2021-02-18 19:15:08 +00:00
|
|
|
|
var buffer = new List<byte>();
|
2019-06-30 22:01:19 +00:00
|
|
|
|
Tags.Clear();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
byte id = value[i];
|
|
|
|
|
|
|
|
|
|
if (id != 0)
|
|
|
|
|
buffer.Add(id);
|
|
|
|
|
|
|
|
|
|
if (id == 0 || i == (length - 1))
|
|
|
|
|
{
|
|
|
|
|
byte[] data = buffer.ToArray();
|
|
|
|
|
buffer.Clear();
|
|
|
|
|
|
|
|
|
|
string tag = Encoding.UTF8.GetString(data);
|
|
|
|
|
Tags.Add(tag);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
2021-02-25 20:09:54 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to get the value of an attribute whose type is T.
|
|
|
|
|
/// Returns false if no attribute was found with that type.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T">The expected type of the attribute.</typeparam>
|
|
|
|
|
/// <param name="key">The name of the attribute.</param>
|
|
|
|
|
/// <param name="value">The out value to set.</param>
|
|
|
|
|
/// <returns>True if the attribute could be read and the out value was set, false otherwise.</returns>
|
|
|
|
|
public bool GetAttribute<T>(string key, out T value)
|
|
|
|
|
{
|
2021-06-05 22:21:12 +00:00
|
|
|
|
if (Attributes.TryGetValue(key, out RbxAttribute attr))
|
2021-02-25 20:09:54 +00:00
|
|
|
|
{
|
2021-03-20 02:38:13 +00:00
|
|
|
|
if (attr.Value is T result)
|
2021-02-25 20:09:54 +00:00
|
|
|
|
{
|
2021-03-20 02:38:13 +00:00
|
|
|
|
value = result;
|
2021-02-25 20:09:54 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
value = default;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to set an attribute to the provided value. The provided key must be no longer than 100 characters.
|
|
|
|
|
/// Returns false if the key is too long or the provided type is not supported by Roblox.
|
|
|
|
|
/// If an attribute with the provide key already exists, it will be overwritten.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
/// <param name="key">The name of the attribute.</param>
|
|
|
|
|
/// <param name="value">The value to be assigned to the attribute.</param>
|
|
|
|
|
/// <returns>True if the attribute was set, false otherwise.</returns>
|
|
|
|
|
public bool SetAttribute<T>(string key, T value)
|
|
|
|
|
{
|
|
|
|
|
if (key.Length > 100)
|
|
|
|
|
return false;
|
|
|
|
|
|
2021-06-05 22:21:12 +00:00
|
|
|
|
if (key.StartsWith("RBX", StringComparison.InvariantCulture))
|
|
|
|
|
return false;
|
|
|
|
|
|
2021-05-05 00:45:00 +00:00
|
|
|
|
Type type = value.GetType();
|
|
|
|
|
|
2021-06-05 22:21:12 +00:00
|
|
|
|
if (!RbxAttribute.SupportsType(type))
|
2021-02-25 20:09:54 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
2021-06-05 22:21:12 +00:00
|
|
|
|
var attr = new RbxAttribute(value);
|
|
|
|
|
Attributes[key] = attr;
|
2021-02-25 20:09:54 +00:00
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// <summary>Returns true if this Instance is an ancestor to the provided Instance.</summary>
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// <param name="descendant">The instance whose descendance will be tested against this Instance.</param>
|
|
|
|
|
public bool IsAncestorOf(Instance descendant)
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2019-01-29 09:50:55 +00:00
|
|
|
|
while (descendant != null)
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2019-01-29 09:50:55 +00:00
|
|
|
|
if (descendant == this)
|
2019-01-26 00:39:37 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
descendant = descendant.Parent;
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// <summary>Returns true if this Instance is a descendant of the provided Instance.</summary>
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// <param name="ancestor">The instance whose ancestry will be tested against this Instance.</param>
|
|
|
|
|
public bool IsDescendantOf(Instance ancestor)
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2021-03-20 02:38:13 +00:00
|
|
|
|
return ancestor?.IsAncestorOf(this) ?? false;
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns true if the provided instance inherits from the provided instance type.
|
|
|
|
|
/// </summary>
|
2020-08-21 15:31:12 +00:00
|
|
|
|
[Obsolete("Use the `is` operator instead.")]
|
2019-06-30 22:01:19 +00:00
|
|
|
|
public bool IsA<T>() where T : Instance
|
2019-05-19 04:44:51 +00:00
|
|
|
|
{
|
2021-03-20 02:38:13 +00:00
|
|
|
|
return this is T;
|
2019-05-19 04:44:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-06 07:18:42 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to cast this Instance to an inherited class of type '<typeparamref name="T"/>'.
|
|
|
|
|
/// Returns null if the instance cannot be casted to the provided type.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T">The type of Instance to cast to.</typeparam>
|
|
|
|
|
/// <returns>The instance as the type '<typeparamref name="T"/>' if it can be converted, or null.</returns>
|
2021-03-20 02:38:13 +00:00
|
|
|
|
[Obsolete("Use the `as` operator instead.")]
|
2019-10-06 07:18:42 +00:00
|
|
|
|
public T Cast<T>() where T : Instance
|
|
|
|
|
{
|
2021-03-20 02:38:13 +00:00
|
|
|
|
return this as T;
|
2019-10-06 07:18:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The parent of this Instance, or null if the instance is the root of a tree.<para/>
|
|
|
|
|
/// Setting the value of this property will throw an exception if:<para/>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// - The parent is currently locked.<para/>
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// - The value is set to itself.<para/>
|
|
|
|
|
/// - The value is a descendant of the Instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Instance Parent
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2020-09-17 17:14:57 +00:00
|
|
|
|
get => ParentUnsafe;
|
|
|
|
|
|
2019-01-26 00:39:37 +00:00
|
|
|
|
set
|
|
|
|
|
{
|
2019-05-19 04:44:51 +00:00
|
|
|
|
if (ParentLocked)
|
2020-09-17 17:14:57 +00:00
|
|
|
|
{
|
|
|
|
|
string newParent = value?.Name ?? "NULL",
|
|
|
|
|
currParent = Parent?.Name ?? "NULL";
|
|
|
|
|
|
|
|
|
|
throw new InvalidOperationException($"The Parent property of {Name} is locked, current parent: {currParent}, new parent {newParent}");
|
|
|
|
|
}
|
2019-05-19 04:44:51 +00:00
|
|
|
|
|
2019-01-26 00:39:37 +00:00
|
|
|
|
if (IsAncestorOf(value))
|
2020-09-17 17:14:57 +00:00
|
|
|
|
{
|
|
|
|
|
string pathA = GetFullName("."),
|
|
|
|
|
pathB = value.GetFullName(".");
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2020-09-17 17:14:57 +00:00
|
|
|
|
throw new InvalidOperationException($"Attempt to set parent of {pathA} to {pathB} would result in circular reference");
|
|
|
|
|
}
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2020-09-17 17:14:57 +00:00
|
|
|
|
if (Parent == this)
|
|
|
|
|
throw new InvalidOperationException($"Attempt to set {Name} as its own parent");
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2020-09-21 18:29:31 +00:00
|
|
|
|
ParentUnsafe?.Children.Remove(this);
|
|
|
|
|
value?.Children.Add(this);
|
|
|
|
|
ParentUnsafe = value;
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// <summary>
|
2019-10-06 07:18:42 +00:00
|
|
|
|
/// Returns an array containing all the children of this Instance.
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
public Instance[] GetChildren()
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2019-01-30 06:36:56 +00:00
|
|
|
|
return Children.ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2019-10-06 07:18:42 +00:00
|
|
|
|
/// Returns an array containing all the children of this Instance, whose type is '<typeparamref name="T"/>'.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public T[] GetChildrenOfType<T>() where T : Instance
|
|
|
|
|
{
|
|
|
|
|
T[] ofType = GetChildren()
|
2020-08-21 15:31:12 +00:00
|
|
|
|
.Where(child => child is T)
|
2019-10-06 07:18:42 +00:00
|
|
|
|
.Cast<T>()
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
return ofType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns an array containing all the descendants of this Instance.
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
public Instance[] GetDescendants()
|
|
|
|
|
{
|
2020-08-21 15:31:12 +00:00
|
|
|
|
var results = new List<Instance>();
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
2019-02-04 19:30:33 +00:00
|
|
|
|
foreach (Instance child in Children)
|
2019-01-30 06:36:56 +00:00
|
|
|
|
{
|
2019-02-04 19:30:33 +00:00
|
|
|
|
// Add this child to the results.
|
|
|
|
|
results.Add(child);
|
|
|
|
|
|
|
|
|
|
// Add its descendants to the results.
|
|
|
|
|
Instance[] descendants = child.GetDescendants();
|
|
|
|
|
results.AddRange(descendants);
|
2019-01-30 06:36:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-04 19:30:33 +00:00
|
|
|
|
return results.ToArray();
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-06 07:18:42 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns an array containing all the descendants of this Instance, whose type is '<typeparamref name="T"/>'.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public T[] GetDescendantsOfType<T>() where T : Instance
|
|
|
|
|
{
|
|
|
|
|
T[] ofType = GetDescendants()
|
2020-08-21 15:31:12 +00:00
|
|
|
|
.Where(desc => desc is T)
|
2019-10-06 07:18:42 +00:00
|
|
|
|
.Cast<T>()
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
return ofType;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// <summary>
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// Returns the first child of this Instance whose Name is the provided string name.
|
|
|
|
|
/// If the instance is not found, this returns null.
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// </summary>
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// <param name="name">The Name of the Instance to find.</param>
|
|
|
|
|
/// <param name="recursive">Indicates if we should search descendants as well.</param>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
public T FindFirstChild<T>(string name, bool recursive = false) where T : Instance
|
2019-01-29 09:50:55 +00:00
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
T result = null;
|
|
|
|
|
|
|
|
|
|
var query = Children
|
|
|
|
|
.Where(child => child is T)
|
|
|
|
|
.Where(child => name == child.Name)
|
|
|
|
|
.Cast<T>();
|
2019-01-29 09:50:55 +00:00
|
|
|
|
|
2020-08-17 05:33:59 +00:00
|
|
|
|
if (query.Any())
|
2019-02-04 19:30:33 +00:00
|
|
|
|
{
|
2019-01-29 09:50:55 +00:00
|
|
|
|
result = query.First();
|
2019-02-04 19:30:33 +00:00
|
|
|
|
}
|
|
|
|
|
else if (recursive)
|
|
|
|
|
{
|
|
|
|
|
foreach (Instance child in Children)
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
T found = child.FindFirstChild<T>(name, true);
|
2019-02-04 19:30:33 +00:00
|
|
|
|
|
|
|
|
|
if (found != null)
|
|
|
|
|
{
|
|
|
|
|
result = found;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-29 09:50:55 +00:00
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the first child of this Instance whose Name is the provided string name.
|
|
|
|
|
/// If the instance is not found, this returns null.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">The Name of the Instance to find.</param>
|
|
|
|
|
/// <param name="recursive">Indicates if we should search descendants as well.</param>
|
|
|
|
|
public Instance FindFirstChild(string name, bool recursive = false)
|
|
|
|
|
{
|
|
|
|
|
return FindFirstChild<Instance>(name, recursive);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-04 19:30:33 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the first ancestor of this Instance whose Name is the provided string name.
|
|
|
|
|
/// If the instance is not found, this returns null.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">The Name of the Instance to find.</param>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
public T FindFirstAncestor<T>(string name) where T : Instance
|
2019-02-04 19:30:33 +00:00
|
|
|
|
{
|
|
|
|
|
Instance ancestor = Parent;
|
|
|
|
|
|
|
|
|
|
while (ancestor != null)
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (ancestor is T && ancestor.Name == name)
|
2020-08-17 05:33:59 +00:00
|
|
|
|
return ancestor as T;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2019-02-04 19:30:33 +00:00
|
|
|
|
ancestor = ancestor.Parent;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the first ancestor of this Instance whose Name is the provided string name.
|
|
|
|
|
/// If the instance is not found, this returns null.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">The Name of the Instance to find.</param>
|
|
|
|
|
public Instance FindFirstAncestor(string name)
|
|
|
|
|
{
|
|
|
|
|
return FindFirstAncestor<Instance>(name);
|
2019-02-04 19:30:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-19 04:44:51 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the first ancestor of this Instance whose ClassName is the provided string className.
|
|
|
|
|
/// If the instance is not found, this returns null.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">The Name of the Instance to find.</param>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
public T FindFirstAncestorOfClass<T>() where T : Instance
|
2019-02-04 19:30:33 +00:00
|
|
|
|
{
|
|
|
|
|
Instance ancestor = Parent;
|
|
|
|
|
|
|
|
|
|
while (ancestor != null)
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (ancestor is T)
|
2020-08-17 05:33:59 +00:00
|
|
|
|
return ancestor as T;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
|
|
|
|
ancestor = ancestor.Parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2019-10-06 07:18:42 +00:00
|
|
|
|
/// Returns the first ancestor of this Instance which derives from the provided type <typeparamref name="T"/>.
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// If the instance is not found, this returns null.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">The Name of the Instance to find.</param>
|
|
|
|
|
public T FindFirstAncestorWhichIsA<T>() where T : Instance
|
|
|
|
|
{
|
|
|
|
|
T ancestor = null;
|
|
|
|
|
Instance check = Parent;
|
|
|
|
|
|
|
|
|
|
while (check != null)
|
|
|
|
|
{
|
2020-08-21 15:31:12 +00:00
|
|
|
|
if (check is T)
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{
|
2020-08-21 15:31:12 +00:00
|
|
|
|
ancestor = check as T;
|
2019-02-04 19:30:33 +00:00
|
|
|
|
break;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
2019-02-04 19:30:33 +00:00
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
check = check.Parent;
|
2019-02-04 19:30:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ancestor;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// <summary>
|
2019-05-19 04:44:51 +00:00
|
|
|
|
/// Returns the first Instance whose ClassName is the provided string className.
|
|
|
|
|
/// If the instance is not found, this returns null.
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="className">The ClassName of the Instance to find.</param>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
public T FindFirstChildOfClass<T>(bool recursive = false) where T : Instance
|
2019-01-30 06:36:56 +00:00
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
var query = Children
|
|
|
|
|
.Where(child => child is T)
|
|
|
|
|
.Cast<T>();
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
T result = null;
|
|
|
|
|
|
2020-08-17 05:33:59 +00:00
|
|
|
|
if (query.Any())
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{
|
2019-01-30 06:36:56 +00:00
|
|
|
|
result = query.First();
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
|
|
|
|
else if (recursive)
|
|
|
|
|
{
|
|
|
|
|
foreach (Instance child in Children)
|
|
|
|
|
{
|
|
|
|
|
T found = child.FindFirstChildOfClass<T>(true);
|
|
|
|
|
|
|
|
|
|
if (found != null)
|
|
|
|
|
{
|
|
|
|
|
result = found;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-21 18:29:31 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Disposes of this instance and its descendants, and locks its parent.
|
|
|
|
|
/// All property bindings, tags, and attributes are cleared.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Destroy()
|
|
|
|
|
{
|
|
|
|
|
Destroyed = true;
|
|
|
|
|
props.Clear();
|
|
|
|
|
|
|
|
|
|
Parent = null;
|
|
|
|
|
ParentLocked = true;
|
|
|
|
|
|
2021-06-05 22:21:12 +00:00
|
|
|
|
Tags.Clear();
|
|
|
|
|
Attributes.Clear();
|
2020-09-21 18:29:31 +00:00
|
|
|
|
|
|
|
|
|
while (Children.Any())
|
|
|
|
|
{
|
|
|
|
|
var child = Children.First();
|
|
|
|
|
child.Destroy();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// <summary>
|
2019-10-06 07:18:42 +00:00
|
|
|
|
/// Returns the first child of this Instance which derives from the provided type <typeparamref name="T"/>.
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// If the instance is not found, this returns null.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="recursive">Whether this should search descendants as well.</param>
|
|
|
|
|
public T FindFirstChildWhichIsA<T>(bool recursive = false) where T : Instance
|
|
|
|
|
{
|
|
|
|
|
var query = Children
|
2020-08-21 15:31:12 +00:00
|
|
|
|
.Where(child => child is T)
|
2019-06-30 22:01:19 +00:00
|
|
|
|
.Cast<T>();
|
|
|
|
|
|
2020-08-17 05:33:59 +00:00
|
|
|
|
if (query.Any())
|
2020-08-21 15:31:12 +00:00
|
|
|
|
return query.First();
|
|
|
|
|
|
|
|
|
|
if (recursive)
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{
|
|
|
|
|
foreach (Instance child in Children)
|
|
|
|
|
{
|
|
|
|
|
T found = child.FindFirstChildWhichIsA<T>(true);
|
|
|
|
|
|
2020-08-21 15:31:12 +00:00
|
|
|
|
if (found == null)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
return found;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-30 06:36:56 +00:00
|
|
|
|
|
2020-08-21 15:31:12 +00:00
|
|
|
|
return null;
|
2019-01-30 06:36:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2019-10-06 09:08:46 +00:00
|
|
|
|
/// Returns a string describing the index traversal of this Instance, starting from its root ancestor.
|
2019-01-30 06:36:56 +00:00
|
|
|
|
/// </summary>
|
2020-09-14 16:20:34 +00:00
|
|
|
|
public string GetFullName(string separator = "\\")
|
2019-01-30 06:36:56 +00:00
|
|
|
|
{
|
|
|
|
|
string fullName = Name;
|
|
|
|
|
Instance at = Parent;
|
|
|
|
|
|
|
|
|
|
while (at != null)
|
|
|
|
|
{
|
2019-12-23 03:23:06 +00:00
|
|
|
|
fullName = at.Name + separator + fullName;
|
2019-01-30 06:36:56 +00:00
|
|
|
|
at = at.Parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fullName;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// <summary>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// Returns a Property object whose name is the provided string name.
|
2019-05-19 04:44:51 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
public Property GetProperty(string name)
|
|
|
|
|
{
|
|
|
|
|
Property result = null;
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (props.ContainsKey(name))
|
|
|
|
|
result = props[name];
|
2019-05-19 04:44:51 +00:00
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2019-05-19 04:44:51 +00:00
|
|
|
|
/// <summary>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// Adds a property by reference to this Instance's property list.
|
2019-05-19 04:44:51 +00:00
|
|
|
|
/// </summary>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// <param name="prop">A reference to the property that will be added.</param>
|
|
|
|
|
internal void AddProperty(ref Property prop)
|
2019-05-19 04:44:51 +00:00
|
|
|
|
{
|
2021-01-20 20:45:58 +00:00
|
|
|
|
string name = prop.Name;
|
|
|
|
|
RemoveProperty(name);
|
2019-05-19 04:44:51 +00:00
|
|
|
|
|
2021-01-20 20:45:58 +00:00
|
|
|
|
prop.Instance = this;
|
|
|
|
|
props.Add(name, prop);
|
2019-01-29 09:50:55 +00:00
|
|
|
|
}
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// <summary>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// Removes a property with the provided name if a property with the provided name exists.
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// </summary>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// <param name="name">The name of the property to be removed.</param>
|
|
|
|
|
/// <returns>True if a property with the provided name was removed.</returns>
|
|
|
|
|
internal bool RemoveProperty(string name)
|
2019-01-29 09:50:55 +00:00
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (props.ContainsKey(name))
|
2019-01-29 09:50:55 +00:00
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
Property prop = Properties[name];
|
|
|
|
|
prop.Instance = null;
|
2019-01-29 09:50:55 +00:00
|
|
|
|
}
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
return props.Remove(name);
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// <summary>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
/// Ensures that all serializable properties of this Instance have
|
|
|
|
|
/// a registered Property object with the correct PropertyType.
|
2019-01-29 09:50:55 +00:00
|
|
|
|
/// </summary>
|
2019-06-30 22:01:19 +00:00
|
|
|
|
internal IReadOnlyDictionary<string, Property> RefreshProperties()
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
Type instType = GetType();
|
|
|
|
|
FieldInfo[] fields = instType.GetFields(Property.BindingFlags);
|
2019-02-03 13:28:54 +00:00
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
foreach (FieldInfo field in fields)
|
2019-02-03 13:28:54 +00:00
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
string fieldName = field.Name;
|
|
|
|
|
Type fieldType = field.FieldType;
|
2019-02-03 13:28:54 +00:00
|
|
|
|
|
2020-09-14 16:20:34 +00:00
|
|
|
|
// A few specific edge case hacks. I wish these didn't need to exist :(
|
2020-09-13 01:16:19 +00:00
|
|
|
|
if (fieldName == "Archivable" || fieldName.EndsWith("k__BackingField"))
|
|
|
|
|
continue;
|
|
|
|
|
else if (fieldName == "Bevel_Roundness")
|
|
|
|
|
fieldName = "Bevel Roundness";
|
|
|
|
|
|
2019-07-04 23:26:53 +00:00
|
|
|
|
PropertyType propType = PropertyType.Unknown;
|
2020-07-13 01:19:30 +00:00
|
|
|
|
|
|
|
|
|
if (Property.Types.ContainsKey(fieldType))
|
2019-07-04 23:26:53 +00:00
|
|
|
|
propType = Property.Types[fieldType];
|
2020-07-13 01:19:30 +00:00
|
|
|
|
else if (fieldType.IsEnum)
|
|
|
|
|
propType = PropertyType.Enum;
|
2021-05-05 18:06:20 +00:00
|
|
|
|
|
2019-07-04 23:26:53 +00:00
|
|
|
|
if (propType != PropertyType.Unknown)
|
2019-02-03 13:28:54 +00:00
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (fieldName.EndsWith("_"))
|
|
|
|
|
fieldName = instType.Name;
|
|
|
|
|
|
2020-08-18 01:12:24 +00:00
|
|
|
|
string xmlToken = fieldType.Name;
|
|
|
|
|
|
|
|
|
|
if (fieldType.IsEnum)
|
|
|
|
|
xmlToken = "token";
|
|
|
|
|
|
|
|
|
|
switch (xmlToken)
|
|
|
|
|
{
|
|
|
|
|
case "String":
|
|
|
|
|
case "Double":
|
2020-08-21 15:31:12 +00:00
|
|
|
|
case "Int64":
|
2021-05-05 18:06:20 +00:00
|
|
|
|
{
|
2020-08-18 01:12:24 +00:00
|
|
|
|
xmlToken = xmlToken.ToLowerInvariant();
|
|
|
|
|
break;
|
2021-05-05 18:06:20 +00:00
|
|
|
|
}
|
2020-08-18 01:12:24 +00:00
|
|
|
|
case "Boolean":
|
2021-05-05 18:06:20 +00:00
|
|
|
|
{
|
2020-08-18 01:12:24 +00:00
|
|
|
|
xmlToken = "bool";
|
|
|
|
|
break;
|
2021-05-05 18:06:20 +00:00
|
|
|
|
}
|
2020-08-18 01:12:24 +00:00
|
|
|
|
case "Single":
|
2021-05-05 18:06:20 +00:00
|
|
|
|
{
|
2020-08-18 01:12:24 +00:00
|
|
|
|
xmlToken = "float";
|
|
|
|
|
break;
|
2021-05-05 18:06:20 +00:00
|
|
|
|
}
|
2020-08-18 01:12:24 +00:00
|
|
|
|
case "Int32":
|
2021-05-05 18:06:20 +00:00
|
|
|
|
{
|
2020-08-18 01:12:24 +00:00
|
|
|
|
xmlToken = "int";
|
|
|
|
|
break;
|
2021-05-05 18:06:20 +00:00
|
|
|
|
}
|
2020-08-18 01:12:24 +00:00
|
|
|
|
case "Rect":
|
2021-05-05 18:06:20 +00:00
|
|
|
|
{
|
2020-08-18 01:12:24 +00:00
|
|
|
|
xmlToken = "Rect2D";
|
|
|
|
|
break;
|
2021-05-05 18:06:20 +00:00
|
|
|
|
}
|
2020-08-18 01:12:24 +00:00
|
|
|
|
case "CFrame":
|
2021-05-05 18:06:20 +00:00
|
|
|
|
{
|
2020-08-18 01:12:24 +00:00
|
|
|
|
xmlToken = "CoordinateFrame";
|
|
|
|
|
break;
|
2021-05-05 18:06:20 +00:00
|
|
|
|
}
|
2021-05-05 18:22:18 +00:00
|
|
|
|
case "Optional`1":
|
|
|
|
|
{
|
|
|
|
|
// TODO: If more optional types are added,
|
|
|
|
|
// this needs disambiguation.
|
|
|
|
|
|
|
|
|
|
xmlToken = "OptionalCoordinateFrame";
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-08-18 01:12:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (!props.ContainsKey(fieldName))
|
|
|
|
|
{
|
2020-08-21 15:31:12 +00:00
|
|
|
|
var newProp = new Property()
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{
|
|
|
|
|
Value = field.GetValue(this),
|
2020-08-18 01:12:24 +00:00
|
|
|
|
XmlToken = xmlToken,
|
2019-06-30 22:01:19 +00:00
|
|
|
|
Name = fieldName,
|
2019-07-04 23:26:53 +00:00
|
|
|
|
Type = propType,
|
2019-06-30 22:01:19 +00:00
|
|
|
|
Instance = this
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AddProperty(ref newProp);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Property prop = props[fieldName];
|
|
|
|
|
prop.Value = field.GetValue(this);
|
2020-08-18 01:12:24 +00:00
|
|
|
|
prop.XmlToken = xmlToken;
|
2019-07-04 23:26:53 +00:00
|
|
|
|
prop.Type = propType;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
2019-05-19 04:44:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-17 06:14:04 +00:00
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
Property tags = GetProperty("Tags");
|
2019-11-01 02:40:31 +00:00
|
|
|
|
Property attributes = GetProperty("AttributesSerialize");
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
|
|
|
|
if (tags == null)
|
|
|
|
|
{
|
|
|
|
|
tags = new Property("Tags", PropertyType.String);
|
|
|
|
|
AddProperty(ref tags);
|
|
|
|
|
}
|
2019-02-03 13:28:54 +00:00
|
|
|
|
|
2019-11-01 02:40:31 +00:00
|
|
|
|
if (attributes == null)
|
|
|
|
|
{
|
|
|
|
|
attributes = new Property("AttributesSerialize", PropertyType.String);
|
|
|
|
|
AddProperty(ref attributes);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
return Properties;
|
2019-02-03 13:28:54 +00:00
|
|
|
|
}
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
2019-10-06 09:08:46 +00:00
|
|
|
|
}
|