mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
prototype composition (#3070)
* prototype composition * oops * better * fixes build * should fix * misc fixes & circle test * moar circle checks * adds tests Co-authored-by: Paul <ritter.paul1@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ using System.Text.RegularExpressions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
@@ -268,7 +269,9 @@ namespace Robust.Server.Bql
|
||||
if ((metaData.EntityPrototype?.ID == name) ^ isInverted)
|
||||
return true;
|
||||
|
||||
return (metaData.EntityPrototype?.Parent == name) ^ isInverted; // Damn, can't actually do recursive check here.
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
return metaData.EntityPrototype != null && prototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID).Any(x => x.Name == name) ^ isInverted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Linguini.Bundle.Errors;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Localization;
|
||||
@@ -56,70 +57,72 @@ namespace Robust.Shared.Localization
|
||||
string? suffix = null;
|
||||
Dictionary<string, string>? attributes = null;
|
||||
|
||||
_prototype.TryIndex<EntityPrototype>(prototypeId, out var prototype);
|
||||
var locId = prototype?.CustomLocalizationID ?? $"ent-{prototypeId}";
|
||||
|
||||
if (TryGetMessage(locId, out var bundle, out var msg))
|
||||
foreach (var prototype in _prototype.EnumerateParents<EntityPrototype>(prototypeId, true))
|
||||
{
|
||||
// Localization override exists.
|
||||
var msgAttrs = msg.Attributes;
|
||||
var locId = prototype?.CustomLocalizationID ?? $"ent-{prototypeId}";
|
||||
|
||||
if (name == null && msg.Value != null)
|
||||
if (TryGetMessage(locId, out var bundle, out var msg))
|
||||
{
|
||||
// Only set name if the value isn't empty.
|
||||
// So that you can override *only* a desc/suffix.
|
||||
name = bundle.FormatPattern(msg.Value, null, out var fmtErr);
|
||||
WriteWarningForErrs(fmtErr, locId);
|
||||
// Localization override exists.
|
||||
var msgAttrs = msg.Attributes;
|
||||
|
||||
if (name == null && msg.Value != null)
|
||||
{
|
||||
// Only set name if the value isn't empty.
|
||||
// So that you can override *only* a desc/suffix.
|
||||
name = bundle.FormatPattern(msg.Value, null, out var fmtErr);
|
||||
WriteWarningForErrs(fmtErr, locId);
|
||||
}
|
||||
|
||||
if (msgAttrs.Count != 0)
|
||||
{
|
||||
foreach (var (attrId, pattern) in msg.Attributes)
|
||||
{
|
||||
var attrib = attrId.ToString();
|
||||
if (attrib.Equals("desc")
|
||||
|| attrib.Equals("suffix"))
|
||||
continue;
|
||||
|
||||
attributes ??= new Dictionary<string, string>();
|
||||
if (!attributes.ContainsKey(attrib))
|
||||
{
|
||||
var value = bundle.FormatPattern(pattern, null, out var errors);
|
||||
WriteWarningForErrs(errors, locId);
|
||||
attributes[attrib] = value;
|
||||
}
|
||||
}
|
||||
|
||||
var allErrors = new List<FluentError>();
|
||||
if (desc == null
|
||||
&& bundle.TryGetMsg(locId, "desc", null, out var err1, out desc))
|
||||
{
|
||||
allErrors.AddRange(err1);
|
||||
}
|
||||
|
||||
if (suffix == null
|
||||
&& bundle.TryGetMsg(locId, "suffix", null, out var err, out suffix))
|
||||
{
|
||||
allErrors.AddRange(err);
|
||||
}
|
||||
|
||||
WriteWarningForErrs(allErrors, locId);
|
||||
}
|
||||
}
|
||||
|
||||
if (msgAttrs.Count != 0)
|
||||
{
|
||||
foreach (var (attrId, pattern) in msg.Attributes)
|
||||
{
|
||||
var attrib = attrId.ToString();
|
||||
if (attrib.Equals("desc")
|
||||
|| attrib.Equals("suffix"))
|
||||
continue;
|
||||
name ??= prototype?.SetName;
|
||||
desc ??= prototype?.SetDesc;
|
||||
suffix ??= prototype?.SetSuffix;
|
||||
|
||||
if (prototype?.LocProperties != null && prototype.LocProperties.Count != 0)
|
||||
{
|
||||
foreach (var (attrib, value) in prototype.LocProperties)
|
||||
{
|
||||
attributes ??= new Dictionary<string, string>();
|
||||
if (!attributes.ContainsKey(attrib))
|
||||
{
|
||||
var value = bundle.FormatPattern(pattern, null, out var errors);
|
||||
WriteWarningForErrs(errors, locId);
|
||||
attributes[attrib] = value;
|
||||
}
|
||||
}
|
||||
|
||||
var allErrors = new List<FluentError>();
|
||||
if (desc == null
|
||||
&& bundle.TryGetMsg(locId, "desc", null, out var err1, out desc))
|
||||
{
|
||||
allErrors.AddRange(err1);
|
||||
}
|
||||
|
||||
if (suffix == null
|
||||
&& bundle.TryGetMsg(locId, "suffix", null, out var err, out suffix))
|
||||
{
|
||||
allErrors.AddRange(err);
|
||||
}
|
||||
|
||||
WriteWarningForErrs(allErrors, locId);
|
||||
}
|
||||
}
|
||||
|
||||
name ??= prototype?.SetName;
|
||||
desc ??= prototype?.SetDesc;
|
||||
suffix ??= prototype?.SetSuffix;
|
||||
|
||||
if (prototype?.LocProperties != null && prototype.LocProperties.Count != 0)
|
||||
{
|
||||
foreach (var (attrib, value) in prototype.LocProperties)
|
||||
{
|
||||
attributes ??= new Dictionary<string, string>();
|
||||
if (!attributes.ContainsKey(attrib))
|
||||
{
|
||||
attributes[attrib] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
@@ -136,8 +137,8 @@ namespace Robust.Shared.Prototypes
|
||||
/// The prototype we inherit from.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[ParentDataFieldAttribute(typeof(AbstractPrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? Parent { get; private set; }
|
||||
[ParentDataFieldAttribute(typeof(AbstractPrototypeIdArraySerializer<EntityPrototype>))]
|
||||
public string[]? Parents { get; }
|
||||
|
||||
[ViewVariables]
|
||||
[NeverPushInheritance]
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
public interface IInheritingPrototype
|
||||
{
|
||||
string? Parent { get; }
|
||||
string[]? Parents { get; }
|
||||
|
||||
bool Abstract { get; }
|
||||
}
|
||||
|
||||
121
Robust.Shared/Prototypes/MultiRootInheritanceGraph.cs
Normal file
121
Robust.Shared/Prototypes/MultiRootInheritanceGraph.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
public sealed class MultiRootInheritanceGraph<T> where T : notnull
|
||||
{
|
||||
private readonly HashSet<T> _rootNodes = new();
|
||||
private readonly Dictionary<T, HashSet<T>> _edges = new();
|
||||
private readonly Dictionary<T, T[]> _parents = new();
|
||||
|
||||
public bool Add(T id) => _rootNodes.Add(id);
|
||||
|
||||
public IReadOnlySet<T> RootNodes => _rootNodes;
|
||||
|
||||
public IReadOnlySet<T>? GetChildren(T id) => _edges.GetValueOrDefault(id);
|
||||
|
||||
public bool TryGetChildren(T id, [NotNullWhen(true)] out IReadOnlySet<T>? set)
|
||||
{
|
||||
set = GetChildren(id);
|
||||
return set != null;
|
||||
}
|
||||
|
||||
public T[]? GetParents(T id) => _parents.GetValueOrDefault(id);
|
||||
|
||||
public bool TryGetParents(T id, [NotNullWhen(true)] out T[]? parents)
|
||||
{
|
||||
parents = GetParents(id);
|
||||
return parents != null;
|
||||
}
|
||||
|
||||
public void Add(T id, T[] parents)
|
||||
{
|
||||
//check for circular inheritance
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
var parentsL1 = GetParents(parent);
|
||||
if(parentsL1 == null) continue;
|
||||
|
||||
var queue = new Queue<T>(parentsL1);
|
||||
while (queue.TryDequeue(out var parentL1))
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(parentL1,id))
|
||||
throw new InvalidOperationException(
|
||||
$"Circular Inheritance detected for id \"{id}\" and parent \"{parent}\"");
|
||||
var parentsL2 = GetParents(parentL1);
|
||||
if (parentsL2 != null)
|
||||
{
|
||||
foreach (var parentL3 in parentsL2)
|
||||
{
|
||||
queue.Enqueue(parentL3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_rootNodes.Remove(id);
|
||||
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
var edges = _edges.GetOrNew(parent);
|
||||
edges.Add(id);
|
||||
_parents[id] = parents;
|
||||
|
||||
if (!_parents.ContainsKey(parent))
|
||||
_rootNodes.Add(parent);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T id, bool force = false)
|
||||
{
|
||||
if (!force && _edges.ContainsKey(id)) throw new InvalidOperationException("Cannot remove node that has remaining children");
|
||||
|
||||
var result = _rootNodes.Remove(id);
|
||||
|
||||
if (_parents.TryGetValue(id, out var parents))
|
||||
{
|
||||
result = true;
|
||||
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
_edges[parent].Remove(id);
|
||||
}
|
||||
|
||||
_parents.Remove(id);
|
||||
}
|
||||
|
||||
if (force)
|
||||
{
|
||||
if (_edges.TryGetValue(id, out var children))
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
var childParents = _parents[child];
|
||||
var newParents = new T[childParents.Length - 1];
|
||||
var i = 0;
|
||||
foreach (var childParent in childParents)
|
||||
{
|
||||
if(Equals(childParent, id)) continue;
|
||||
newParents[i++] = childParent;
|
||||
}
|
||||
|
||||
if (newParents.Length == 0)
|
||||
{
|
||||
_rootNodes.Add(child);
|
||||
_parents.Remove(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
_parents[child] = newParents;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,16 @@ namespace Robust.Shared.Prototypes
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable to iterate all parents of a prototype of a certain type.
|
||||
/// </summary>
|
||||
IEnumerable<T> EnumerateParents<T>(string id, bool includeSelf = false) where T : class, IPrototype, IInheritingPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable to iterate all parents of a prototype of a certain type.
|
||||
/// </summary>
|
||||
IEnumerable<IPrototype> EnumerateParents(Type type, string id, bool includeSelf = false);
|
||||
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IPrototype"/> by ID.
|
||||
/// </summary>
|
||||
@@ -224,7 +234,7 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
private readonly Dictionary<Type, Dictionary<string, IPrototype>> _prototypes = new();
|
||||
private readonly Dictionary<Type, Dictionary<string, MappingDataNode>> _prototypeResults = new();
|
||||
private readonly Dictionary<Type, PrototypeInheritanceTree> _inheritanceTrees = new();
|
||||
private readonly Dictionary<Type, MultiRootInheritanceGraph<string>> _inheritanceTrees = new();
|
||||
|
||||
private readonly HashSet<string> _ignoredPrototypeTypes = new();
|
||||
|
||||
@@ -269,6 +279,67 @@ namespace Robust.Shared.Prototypes
|
||||
return EnumeratePrototypes(GetVariantType(variant));
|
||||
}
|
||||
|
||||
public IEnumerable<T> EnumerateParents<T>(string id, bool includeSelf = false) where T : class, IPrototype, IInheritingPrototype
|
||||
{
|
||||
if (!_hasEverBeenReloaded)
|
||||
{
|
||||
throw new InvalidOperationException("No prototypes have been loaded yet.");
|
||||
}
|
||||
|
||||
if(!TryIndex<T>(id, out var prototype))
|
||||
yield break;
|
||||
if (includeSelf) yield return prototype;
|
||||
if (prototype.Parents == null) yield break;
|
||||
|
||||
var queue = new Queue<string>(prototype.Parents);
|
||||
while (queue.TryDequeue(out var prototypeId))
|
||||
{
|
||||
if(!TryIndex<T>(prototypeId, out var parent))
|
||||
yield break;
|
||||
yield return parent;
|
||||
if (parent.Parents == null) continue;
|
||||
|
||||
foreach (var parentId in parent.Parents)
|
||||
{
|
||||
queue.Enqueue(parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IPrototype> EnumerateParents(Type type, string id, bool includeSelf = false)
|
||||
{
|
||||
if (!_hasEverBeenReloaded)
|
||||
{
|
||||
throw new InvalidOperationException("No prototypes have been loaded yet.");
|
||||
}
|
||||
|
||||
if (!type.IsAssignableTo(typeof(IInheritingPrototype)))
|
||||
{
|
||||
throw new InvalidOperationException("The provided prototype type is not an inheriting prototype");
|
||||
}
|
||||
|
||||
if(!TryIndex(type, id, out var prototype))
|
||||
yield break;
|
||||
if (includeSelf) yield return prototype;
|
||||
var iPrototype = (IInheritingPrototype)prototype;
|
||||
if (iPrototype.Parents == null) yield break;
|
||||
|
||||
var queue = new Queue<string>(iPrototype.Parents);
|
||||
while (queue.TryDequeue(out var prototypeId))
|
||||
{
|
||||
if (!TryIndex(type, id, out var parent))
|
||||
continue;
|
||||
yield return parent;
|
||||
iPrototype = (IInheritingPrototype)parent;
|
||||
if (iPrototype.Parents == null) continue;
|
||||
|
||||
foreach (var parentId in iPrototype.Parents)
|
||||
{
|
||||
queue.Enqueue(parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T Index<T>(string id) where T : class, IPrototype
|
||||
{
|
||||
if (!_hasEverBeenReloaded)
|
||||
@@ -340,23 +411,42 @@ namespace Robust.Shared.Prototypes
|
||||
continue;
|
||||
}
|
||||
|
||||
var tree = _inheritanceTrees[type];
|
||||
var processQueue = new Queue<string>();
|
||||
foreach (var id in prototypes[type])
|
||||
{
|
||||
if (!pushed.ContainsKey(type))
|
||||
pushed[type] = new HashSet<string>();
|
||||
if (pushed[type].Contains(id))
|
||||
processQueue.Enqueue(id);
|
||||
}
|
||||
|
||||
while(processQueue.TryDequeue(out var id))
|
||||
{
|
||||
var pushedSet = pushed.GetOrNew(type);
|
||||
|
||||
if (tree.TryGetParents(id, out var parents))
|
||||
{
|
||||
continue;
|
||||
var nonPushedParent = false;
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
//our parent has been reloaded and has not been added to the pushedSet yet
|
||||
if (prototypes[type].Contains(parent) && !pushedSet.Contains(parent))
|
||||
{
|
||||
//we re-queue ourselves at the end of the queue
|
||||
processQueue.Enqueue(id);
|
||||
nonPushedParent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(nonPushedParent) continue;
|
||||
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
PushInheritance(type, id, parent);
|
||||
}
|
||||
}
|
||||
|
||||
var set = new HashSet<string>();
|
||||
set.Add(id);
|
||||
PushInheritance(type, id, _inheritanceTrees[type].GetParent(id), set);
|
||||
foreach (var changedId in set)
|
||||
{
|
||||
TryReadPrototype(type, changedId, _prototypeResults[type][changedId]);
|
||||
}
|
||||
pushed[type].UnionWith(set);
|
||||
TryReadPrototype(type, id, _prototypeResults[type][id]);
|
||||
|
||||
pushedSet.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,10 +487,31 @@ namespace Robust.Shared.Prototypes
|
||||
types.Sort(SortPrototypesByPriority);
|
||||
foreach (var type in types)
|
||||
{
|
||||
if(_inheritanceTrees.TryGetValue(type, out var tree)){
|
||||
foreach (var baseNode in tree.BaseNodes)
|
||||
if(_inheritanceTrees.TryGetValue(type, out var tree))
|
||||
{
|
||||
var processed = new HashSet<string>();
|
||||
var workList = new Queue<string>(tree.RootNodes);
|
||||
|
||||
while (workList.TryDequeue(out var id))
|
||||
{
|
||||
PushInheritance(type, baseNode);
|
||||
processed.Add(id);
|
||||
if (tree.TryGetParents(id, out var parents))
|
||||
{
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
PushInheritance(type, id, parent);
|
||||
}
|
||||
}
|
||||
|
||||
if (tree.TryGetChildren(id, out var children))
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
var childParents = tree.GetParents(child)!;
|
||||
if(childParents.All(p => processed.Contains(p)))
|
||||
workList.Enqueue(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,27 +536,10 @@ namespace Robust.Shared.Prototypes
|
||||
}
|
||||
}
|
||||
|
||||
private void PushInheritance(Type type, string id, string? parent = null, HashSet<string>? changed = null)
|
||||
{
|
||||
if (parent != null)
|
||||
{
|
||||
PushInheritanceWithoutRecursion(type, id, parent, changed);
|
||||
}
|
||||
|
||||
if(!_inheritanceTrees[type].HasId(id)) return;
|
||||
|
||||
foreach (var child in _inheritanceTrees[type].Children(id))
|
||||
{
|
||||
PushInheritance(type, child, id, changed);
|
||||
}
|
||||
}
|
||||
|
||||
private void PushInheritanceWithoutRecursion(Type type, string id, string parent,
|
||||
HashSet<string>? changed = null)
|
||||
private void PushInheritance(Type type, string id, string parent)
|
||||
{
|
||||
_prototypeResults[type][id] = _serializationManager.PushCompositionWithGenericNode(type,
|
||||
new[] { _prototypeResults[type][parent] }, _prototypeResults[type][id]);
|
||||
changed?.Add(id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -624,12 +718,13 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
if (_inheritanceTrees.TryGetValue(type, out var tree))
|
||||
{
|
||||
tree.RemoveId(id);
|
||||
tree.Remove(id, true);
|
||||
}
|
||||
|
||||
if (_prototypes.TryGetValue(type, out var prototypeIds))
|
||||
{
|
||||
prototypeIds.Remove(id);
|
||||
_prototypeResults[type].Remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -677,8 +772,15 @@ namespace Robust.Shared.Prototypes
|
||||
_prototypeResults[prototypeType][idNode.Value] = datanode;
|
||||
if (prototypeType.IsAssignableTo(typeof(IInheritingPrototype)))
|
||||
{
|
||||
datanode.TryGet<ValueDataNode>(ParentDataFieldAttribute.Name, out var parentNode);
|
||||
_inheritanceTrees[prototypeType].AddId(idNode.Value, parentNode?.Value, true);
|
||||
if (datanode.TryGet(ParentDataFieldAttribute.Name, out var parentNode))
|
||||
{
|
||||
var parents = _serializationManager.Read<string[]>(parentNode);
|
||||
_inheritanceTrees[prototypeType].Add(idNode.Value, parents);
|
||||
}
|
||||
else
|
||||
{
|
||||
_inheritanceTrees[prototypeType].Add(idNode.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed == null) continue;
|
||||
@@ -872,7 +974,7 @@ namespace Robust.Shared.Prototypes
|
||||
_prototypes[type] = new Dictionary<string, IPrototype>();
|
||||
_prototypeResults[type] = new Dictionary<string, MappingDataNode>();
|
||||
if (typeof(IInheritingPrototype).IsAssignableFrom(type))
|
||||
_inheritanceTrees[type] = new PrototypeInheritanceTree();
|
||||
_inheritanceTrees[type] = new MultiRootInheritanceGraph<string>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Linq;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
|
||||
public sealed class AbstractPrototypeIdArraySerializer<TPrototype> : PrototypeIdArraySerializer<TPrototype> where TPrototype : class, IPrototype, IInheritingPrototype
|
||||
{
|
||||
protected override PrototypeIdSerializer<TPrototype> PrototypeSerializer =>
|
||||
new AbstractPrototypeIdSerializer<TPrototype>();
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
public class PrototypeIdArraySerializer<TPrototype> : ITypeSerializer<string[], SequenceDataNode>,
|
||||
ITypeSerializer<string[], ValueDataNode> where TPrototype : class, IPrototype
|
||||
{
|
||||
protected virtual PrototypeIdSerializer<TPrototype> PrototypeSerializer => new();
|
||||
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, SequenceDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
return new ValidatedSequenceNode(node.Select(x =>
|
||||
x is ValueDataNode valueDataNode
|
||||
? PrototypeSerializer.Validate(serializationManager, valueDataNode, dependencies, context)
|
||||
: new ErrorNode(x, $"Cannot cast node {x} to ValueDataNode.")).ToList());
|
||||
}
|
||||
|
||||
public string[] Read(ISerializationManager serializationManager, SequenceDataNode node, IDependencyCollection dependencies,
|
||||
bool skipHook, ISerializationContext? context = null, string[]? value = default)
|
||||
{
|
||||
return node.Select(x =>
|
||||
PrototypeSerializer.Read(serializationManager, (ValueDataNode)x, dependencies, skipHook, context))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, string[] value, bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return serializationManager.WriteValue(value, alwaysWrite, context);
|
||||
}
|
||||
|
||||
public string[] Copy(ISerializationManager serializationManager, string[] source, string[] target, bool skipHook,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return serializationManager.Copy(source, target, context, skipHook)!;
|
||||
}
|
||||
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null) =>
|
||||
PrototypeSerializer.Validate(serializationManager, node, dependencies, context);
|
||||
|
||||
public string[] Read(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
bool skipHook, ISerializationContext? context = null, string[]? value = default) =>
|
||||
new[] { PrototypeSerializer.Read(serializationManager, node, dependencies, skipHook, context) };
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
|
||||
{
|
||||
|
||||
public sealed class AbstractPrototypeIdDictionarySerializer<TValue, TPrototype> : PrototypeIdDictionarySerializer<TValue,
|
||||
TPrototype> where TPrototype : class, IPrototype
|
||||
TPrototype> where TPrototype : class, IPrototype, IInheritingPrototype
|
||||
{
|
||||
protected override PrototypeIdSerializer<TPrototype> PrototypeSerializer =>
|
||||
new AbstractPrototypeIdSerializer<TPrototype>();
|
||||
|
||||
@@ -10,7 +10,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List
|
||||
{
|
||||
public sealed class AbstractPrototypeIdListSerializer<T> : PrototypeIdListSerializer<T> where T : class, IPrototype
|
||||
public sealed class AbstractPrototypeIdListSerializer<T> : PrototypeIdListSerializer<T> where T : class, IPrototype, IInheritingPrototype
|
||||
{
|
||||
protected override PrototypeIdSerializer<T> PrototypeSerializer => new AbstractPrototypeIdSerializer<T>();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype
|
||||
{
|
||||
public sealed class AbstractPrototypeIdSerializer<TPrototype> : PrototypeIdSerializer<TPrototype>
|
||||
where TPrototype : class, IPrototype
|
||||
where TPrototype : class, IPrototype, IInheritingPrototype
|
||||
{
|
||||
public override ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
|
||||
@@ -11,7 +11,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set
|
||||
{
|
||||
public sealed class AbstractPrototypeIdHashSetSerializer<TPrototype> : PrototypeIdHashSetSerializer<TPrototype>
|
||||
where TPrototype : class, IPrototype
|
||||
where TPrototype : class, IPrototype, IInheritingPrototype
|
||||
{
|
||||
protected override PrototypeIdSerializer<TPrototype> PrototypeSerializer =>
|
||||
new AbstractPrototypeIdSerializer<TPrototype>();
|
||||
|
||||
107
Robust.UnitTesting/Shared/Prototypes/MultiRootGraphTest.cs
Normal file
107
Robust.UnitTesting/Shared/Prototypes/MultiRootGraphTest.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Prototypes;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class MultiRootGraphTest
|
||||
{
|
||||
private const string Id1 = "id1";
|
||||
private const string Id2 = "id2";
|
||||
private const string Id3 = "id3";
|
||||
private const string Id4 = "id4";
|
||||
|
||||
[Test]
|
||||
public void AddAndRemoveRoot()
|
||||
{
|
||||
var graph = new MultiRootInheritanceGraph<string>();
|
||||
graph.Add(Id1);
|
||||
Assert.That(graph.RootNodes.Count, Is.EqualTo(1));
|
||||
Assert.That(graph.RootNodes.Contains(Id1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddAndRemoveRootAndChild()
|
||||
{
|
||||
var graph = new MultiRootInheritanceGraph<string>();
|
||||
graph.Add(Id3, new []{Id1});
|
||||
|
||||
var children = graph.GetChildren(Id1);
|
||||
Assert.That(children, Is.Not.Null);
|
||||
Assert.That(children!.Count, Is.EqualTo(1));
|
||||
Assert.That(children.Contains(Id3));
|
||||
|
||||
var parents = graph.GetParents(Id3);
|
||||
Assert.That(parents, Is.Not.Null);
|
||||
Assert.That(parents!.Count, Is.EqualTo(1));
|
||||
Assert.That(parents.Contains(Id1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddTwoParentsRemoveOne()
|
||||
{
|
||||
var graph = new MultiRootInheritanceGraph<string>();
|
||||
graph.Add(Id3, new []{Id1, Id2});
|
||||
|
||||
var parents = graph.GetParents(Id3);
|
||||
Assert.That(parents, Is.Not.Null);
|
||||
Assert.That(parents!.Count, Is.EqualTo(2));
|
||||
Assert.That(parents.Contains(Id1));
|
||||
Assert.That(parents.Contains(Id2));
|
||||
|
||||
var children = graph.GetChildren(Id1);
|
||||
Assert.That(children, Is.Not.Null);
|
||||
Assert.That(children!.Count, Is.EqualTo(1));
|
||||
Assert.That(children.Contains(Id3));
|
||||
|
||||
children = graph.GetChildren(Id2);
|
||||
Assert.That(children, Is.Not.Null);
|
||||
Assert.That(children!.Count, Is.EqualTo(1));
|
||||
Assert.That(children.Contains(Id3));
|
||||
|
||||
Assert.That(graph.RootNodes.Count, Is.EqualTo(2));
|
||||
Assert.That(graph.RootNodes.Contains(Id1));
|
||||
Assert.That(graph.RootNodes.Contains(Id2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OneParentTwoChildrenRemoveParent()
|
||||
{
|
||||
var graph = new MultiRootInheritanceGraph<string>();
|
||||
graph.Add(Id3, new []{Id1});
|
||||
graph.Add(Id4, new []{Id1});
|
||||
|
||||
var parents = graph.GetParents(Id3);
|
||||
Assert.That(parents, Is.Not.Null);
|
||||
Assert.That(parents!.Count, Is.EqualTo(1));
|
||||
Assert.That(parents.Contains(Id1));
|
||||
|
||||
parents = graph.GetParents(Id4);
|
||||
Assert.That(parents, Is.Not.Null);
|
||||
Assert.That(parents!.Count, Is.EqualTo(1));
|
||||
Assert.That(parents.Contains(Id1));
|
||||
|
||||
var children = graph.GetChildren(Id1);
|
||||
Assert.That(children, Is.Not.Null);
|
||||
Assert.That(children!.Count, Is.EqualTo(2));
|
||||
Assert.That(children.Contains(Id3));
|
||||
Assert.That(children.Contains(Id4));
|
||||
|
||||
Assert.That(graph.RootNodes.Count, Is.EqualTo(1));
|
||||
Assert.That(graph.RootNodes.Contains(Id1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CircleCheckTest()
|
||||
{
|
||||
var graph = new MultiRootInheritanceGraph<string>();
|
||||
graph.Add(Id1, new []{Id2});
|
||||
Assert.Throws<InvalidOperationException>(() => graph.Add(Id2, new []{Id1}));
|
||||
|
||||
graph.Add(Id2, new[] { Id3 });
|
||||
Assert.Throws<InvalidOperationException>(() => graph.Add(Id3, new[] { Id1 }));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -7,6 +8,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Prototypes
|
||||
{
|
||||
@@ -114,6 +116,42 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
Assert.That(prototype.Name, Is.EqualTo(LoadStringTestDummyId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCircleException()
|
||||
{
|
||||
string GenerateCircleTestPrototype(string id, string parent)
|
||||
{
|
||||
return $@"- type: circle
|
||||
id: {id}
|
||||
parent: {parent}";
|
||||
}
|
||||
|
||||
manager.RegisterType(typeof(CircleTestPrototype));
|
||||
|
||||
var directCircle = $@"{GenerateCircleTestPrototype("1", "2")}
|
||||
{GenerateCircleTestPrototype("2", "1")}";
|
||||
|
||||
Assert.Throws<PrototypeLoadException>(() => manager.LoadString(directCircle));
|
||||
manager.RemoveString(directCircle);
|
||||
|
||||
var indirectCircle = $@"{GenerateCircleTestPrototype("1", "2")}
|
||||
{GenerateCircleTestPrototype("2", "3")}
|
||||
{GenerateCircleTestPrototype("3", "1")}";
|
||||
|
||||
Assert.Throws<PrototypeLoadException>(() => manager.LoadString(indirectCircle));
|
||||
}
|
||||
|
||||
[Prototype("circle")]
|
||||
private sealed class CircleTestPrototype : IPrototype, IInheritingPrototype
|
||||
{
|
||||
[IdDataField()]
|
||||
public string ID { get; } = default!;
|
||||
[ParentDataField(typeof(AbstractPrototypeIdArraySerializer<CircleTestPrototype>))]
|
||||
public string[]? Parents { get; }
|
||||
[AbstractDataField]
|
||||
public bool Abstract { get; }
|
||||
}
|
||||
|
||||
public enum YamlTestEnum : byte
|
||||
{
|
||||
Foo,
|
||||
|
||||
51
Robust.UnitTesting/Shared/Serialization/CompositionTest.cs
Normal file
51
Robust.UnitTesting/Shared/Serialization/CompositionTest.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Serialization;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class CompositionTest : SerializationTest
|
||||
{
|
||||
[DataDefinition]
|
||||
private sealed class CompositionTestClass
|
||||
{
|
||||
[DataField("f1")] public int ChildValue;
|
||||
[DataField("f2")] public int Parent1Value;
|
||||
[DataField("f3")] public int Parent2Value;
|
||||
[DataField("f4"), NeverPushInheritance]
|
||||
public int NeverPushValueParent1;
|
||||
[DataField("f5"), NeverPushInheritance]
|
||||
public int NeverPushValueParent2;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPushComposition()
|
||||
{
|
||||
var child = new MappingDataNode { { "f1", "1" } };
|
||||
var parent1 = new MappingDataNode
|
||||
{
|
||||
{ "f1", "2" },
|
||||
{ "f2", "1" },
|
||||
{ "f4", "1" }
|
||||
};
|
||||
var parent2 = new MappingDataNode
|
||||
{
|
||||
{ "f1", "3" },
|
||||
{ "f2", "2" },
|
||||
{ "f3", "1" },
|
||||
{ "f5", "1" }
|
||||
};
|
||||
|
||||
var finalMapping = Serialization.PushComposition<CompositionTestClass, MappingDataNode>(new[] { parent1, parent2 }, child);
|
||||
var val = Serialization.Read<CompositionTestClass>(finalMapping);
|
||||
|
||||
Assert.That(val.ChildValue, Is.EqualTo(1));
|
||||
Assert.That(val.Parent1Value, Is.EqualTo(1));
|
||||
Assert.That(val.Parent2Value, Is.EqualTo(1));
|
||||
Assert.That(val.NeverPushValueParent1, Is.EqualTo(0));
|
||||
Assert.That(val.NeverPushValueParent2, Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user