Compare commits

...

16 Commits

Author SHA1 Message Date
DrSmugleaf
a40c4a435c Fix file not found exceptions when starting up the game with a debugger (#1562)
* Fix exceptions when starting up the game

* Remove try catches
2021-02-16 20:05:22 +01:00
DrSmugleaf
17182dd0e8 Engine PR for enabling nullability in Content.Client (#1565) 2021-02-16 20:05:06 +01:00
DrSmugleaf
d8b50044a2 Add (de)serialization for immutable lists (#1549) 2021-02-16 20:04:28 +01:00
Pieter-Jan Briers
4dc396e73d Fixes warning in TypePropertySerialization_Test.cs 2021-02-16 09:20:06 +01:00
Pieter-Jan Briers
6ae0b0e892 Fix [GenerateTypedNameReferences] with sealed types.
Fixes #1546
2021-02-16 09:19:57 +01:00
Pieter-Jan Briers
7162ca3456 Probably fix the bug where people get locked out of the server due to duplicate connction attempts. 2021-02-16 09:02:14 +01:00
Pieter-Jan Briers
1b44c1a1b8 Allow NotImplementedException in sandbox. 2021-02-15 17:57:38 +01:00
Clyybber
5b80b33e00 Change GetFileSystemInfos to EnumerateFileSystemInfos for iteration (#1561) 2021-02-15 16:26:16 +01:00
DrSmugleaf
f05c1b2395 Add Attribute generic constraint to IReflectionManager.FindTypesWithAttribute (#1548) 2021-02-14 02:14:00 +01:00
Pieter-Jan Briers
d9b2c73440 XamlUI improvements.
1. Add XAML namespaces https://spacestation14.io with Avalonia hacks.
2. Make markup extensions work with Avalonia hacks.
3. Add LocExtension for localized strings.
4. Add Vector2 parsing type converter to XAML compilation.
5. Make SS14Window better thanks to these improvements.
2021-02-14 02:09:37 +01:00
Pieter-Jan Briers
29a39f8e0a Adds LocExtension markup extension for XAML.
Does Loc.GetString().
2021-02-13 15:13:05 +01:00
Pieter-Jan Briers
2d72a2bdb5 Server timing improvements.
Add helpers.
Fix desynchronized NetTime.
2021-02-13 11:42:12 +01:00
Pieter-Jan Briers
91da635631 Fix Robust.Shared.Interfaces namespace in ScriptInstanceShared 2021-02-13 11:42:12 +01:00
chairbender
68ab3d504a Various fixes to support new chatbox (#1547)
* #272 no arrow, actually change id on channel changer

* #272 ability to apply style class to child

* #272 try methods for selecting items in OptionButton.cs

* #272 allow escaping right angle bracket in formatted message

* #272 ability to detect when local player is set / unset

* #272 make RemoveModal public since PushModal is as well, so modals can be removed on-demand if needed rather than relying on a click elsewhere

* #272 revert
2021-02-12 18:20:29 -08:00
Pieter-Jan Briers
5187040a64 Remove unsafe code from GrowableStack 2021-02-13 00:47:05 +01:00
DrSmugleaf
e0c63e7ce6 Add SerializedType attribute to specify the id used in !type tags (#1545)
* Add SerializedType attribute to serialize types without the !type tag

* Fix nulls in tests

* Fix null in tests maybe

* Return to type tags

* Fix imports
2021-02-11 13:50:55 -08:00
45 changed files with 909 additions and 144 deletions

View File

@@ -0,0 +1,37 @@
using System.Linq;
using Pidgin;
using static Pidgin.Parser;
namespace Robust.Build.Tasks
{
public static class MathParsing
{
public static Parser<char, float> Single { get; } = Real.Select(c => (float) c);
public static Parser<char, float> Single1 { get; }
= Single.Between(SkipWhitespaces);
public static Parser<char, (float, float)> Single2 { get; }
= Single.Before(SkipWhitespaces).Repeat(2).Select(e =>
{
var arr = e.ToArray();
return (arr[0], arr[1]);
});
public static Parser<char, (float, float, float, float)> Single4 { get; }
= Single.Before(SkipWhitespaces).Repeat(4).Select(e =>
{
var arr = e.ToArray();
return (arr[0], arr[1], arr[2], arr[3]);
});
public static Parser<char, float[]> Thickness { get; }
= SkipWhitespaces.Then(
OneOf(
Try(Single1.Select(c => new[] {c})),
Try(Single2.Select(c => new[] {c.Item1, c.Item2})),
Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4}))
));
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Linq;
using System.Reflection.Emit;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.TypeSystem;
namespace Robust.Build.Tasks
{
public abstract class RXamlVecLikeConstAstNode<T>
: XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
where T : unmanaged
{
private readonly IXamlConstructor _constructor;
protected readonly T[] Values;
public RXamlVecLikeConstAstNode(
IXamlLineInfo lineInfo,
IXamlType type, IXamlConstructor constructor,
IXamlType componentType, T[] values)
: base(lineInfo)
{
_constructor = constructor;
Values = values;
var @params = constructor.Parameters;
if (@params.Count != values.Length)
throw new ArgumentException("Invalid amount of parameters");
if (@params.Any(c => c != componentType))
throw new ArgumentException("Invalid constructor: not all parameters match component type");
Type = new XamlAstClrTypeReference(lineInfo, type, false);
}
public IXamlAstTypeReference Type { get; }
public virtual XamlILNodeEmitResult Emit(
XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter codeGen)
{
codeGen.Newobj(_constructor);
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
}
public sealed class RXamlSingleVecLikeConstAstNode : RXamlVecLikeConstAstNode<float>
{
public RXamlSingleVecLikeConstAstNode(
IXamlLineInfo lineInfo,
IXamlType type, IXamlConstructor constructor,
IXamlType componentType, float[] values)
: base(lineInfo, type, constructor, componentType, values)
{
}
public override XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter codeGen)
{
foreach (var value in Values)
{
codeGen.Emit(OpCodes.Ldc_R4, value);
}
return base.Emit(context, codeGen);
}
}
public sealed class RXamlInt32VecLikeConstAstNode : RXamlVecLikeConstAstNode<int>
{
public RXamlInt32VecLikeConstAstNode(
IXamlLineInfo lineInfo,
IXamlType type, IXamlConstructor constructor,
IXamlType componentType, int[] values)
: base(lineInfo, type, constructor, componentType, values)
{
}
public override XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter codeGen)
{
foreach (var value in Values)
{
codeGen.Emit(OpCodes.Ldc_I4, value);
}
return base.Emit(context, codeGen);
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Linq;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Robust.Build.Tasks
{
class RXamlWellKnownTypes
{
public XamlTypeWellKnownTypes XamlIlTypes { get; }
public IXamlType Single { get; }
public IXamlType Int32 { get; }
public IXamlType Vector2 { get; }
public IXamlConstructor Vector2ConstructorFull { get; }
public IXamlType Vector2i { get; }
public IXamlConstructor Vector2iConstructorFull { get; }
public RXamlWellKnownTypes(TransformerConfiguration cfg)
{
var ts = cfg.TypeSystem;
XamlIlTypes = cfg.WellKnownTypes;
Single = ts.GetType("System.Single");
Int32 = ts.GetType("System.Int32");
(Vector2, Vector2ConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Vector2", Single, 2);
(Vector2i, Vector2iConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Vector2i", Int32, 2);
(IXamlType, IXamlConstructor) GetNumericTypeInfo(string name, IXamlType componentType, int componentCount)
{
var type = cfg.TypeSystem.GetType(name);
var ctor = type.GetConstructor(Enumerable.Repeat(componentType, componentCount).ToList());
return (type, ctor);
}
}
}
static class RXamlWellKnownTypesExtensions
{
public static RXamlWellKnownTypes GetRobustTypes(this AstTransformationContext ctx)
{
if (ctx.TryGetItem<RXamlWellKnownTypes>(out var rv))
return rv;
ctx.SetItem(rv = new RXamlWellKnownTypes(ctx.Configuration));
return rv;
}
}
}

View File

@@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="16.8.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
<PackageReference Include="Pidgin" Version="2.5.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -6,6 +6,7 @@ using Microsoft.Build.Framework;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Pidgin;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
@@ -70,7 +71,7 @@ namespace Robust.Build.Tasks
{
XmlnsAttributes =
{
typeSystem.GetType("Robust.Client.UserInterface.XAML.XmlnsDefinitionAttribute"),
typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"),
},
ContentAttributes =
@@ -98,7 +99,7 @@ namespace Robust.Build.Tasks
typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage));
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage), CustomValueConverter);
var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
@@ -297,6 +298,41 @@ namespace Robust.Build.Tasks
return true;
}
private static bool CustomValueConverter(
AstTransformationContext context,
IXamlAstValueNode node,
IXamlType type,
out IXamlAstValueNode result)
{
if (!(node is XamlAstTextNode textNode))
{
result = null;
return false;
}
var text = textNode.Text;
var types = context.GetRobustTypes();
if (type.Equals(types.Vector2))
{
var foo = MathParsing.Single2.Parse(text);
if (!foo.Success)
throw new XamlLoadException($"Unable to parse \"{text}\" as a Vector2", node);
var (x, y) = foo.Value;
result = new RXamlSingleVecLikeConstAstNode(
node,
types.Vector2, types.Vector2ConstructorFull,
types.Single, new[] {x, y});
return true;
}
result = null;
return false;
}
public const string ContextNameScopeFieldName = "RobustNameScope";
private static void EmitNameScopeField(XamlLanguageTypeMappings xamlLanguage, CecilTypeSystem typeSystem, IXamlTypeBuilder<IXamlILEmitter> typeBuilder, IXamlILEmitter constructor)

View File

@@ -24,6 +24,7 @@ namespace Robust.Client.NameGenerator
{
private const string AttributeName = "Robust.Client.AutoGenerated.GenerateTypedNameReferencesAttribute";
private const string AttributeFile = "GenerateTypedNameReferencesAttribute";
private const string AttributeCode = @"// <auto-generated />
using System;
namespace Robust.Client.AutoGenerated
@@ -54,8 +55,8 @@ namespace Robust.Client.AutoGenerated
{
var clrtype = objectNode.Type.GetClrType();
var isControl = IsControl(clrtype);
//clrtype.Interfaces.Any(i =>
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
//clrtype.Interfaces.Any(i =>
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
if (!isControl)
return node;
@@ -80,9 +81,13 @@ namespace Robust.Client.AutoGenerated
return node;
}
public void Push(IXamlAstNode node) { }
public void Push(IXamlAstNode node)
{
}
public void Pop() { }
public void Pop()
{
}
}
private static string GenerateSourceCode(
@@ -97,16 +102,20 @@ namespace Robust.Client.AutoGenerated
var compiler =
new XamlILCompiler(
new TransformerConfiguration(typeSystem, typeSystem.Assemblies[0],
new XamlLanguageTypeMappings(typeSystem)),
new XamlLanguageTypeMappings(typeSystem)
{
XmlnsAttributes = {typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute")}
}),
new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>(), false);
compiler.Transformers.Add(new TypeReferenceResolver());
compiler.Transform(parsed);
var initialRoot = (XamlAstObjectNode) parsed.Root;
var names = NameVisitor.GetNames(initialRoot);
var fieldAccess = classSymbol.IsSealed ? "private" : "protected";
//var names = NameVisitor.GetNames((XamlAstObjectNode)XDocumentXamlParser.Parse(xamlFile).Root);
var namedControls = names.Select(info => " " +
$"protected global::{info.type} {info.name} => " +
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
$"{fieldAccess} global::{info.type} {info.name} => " +
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
return $@"// <auto-generated />
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -124,7 +133,7 @@ namespace {nameSpace}
public void Execute(GeneratorExecutionContext context)
{
var comp = (CSharpCompilation) context.Compilation;
if(comp.GetTypeByMetadataName(AttributeName) == null)
if (comp.GetTypeByMetadataName(AttributeName) == null)
context.AddSource(AttributeFile, SourceText.From(AttributeCode, Encoding.UTF8));
if (!(context.SyntaxReceiver is NameReferenceSyntaxReceiver receiver))
{
@@ -132,7 +141,7 @@ namespace {nameSpace}
}
var symbols = UnpackAnnotatedTypes(context, comp, receiver);
if(symbols == null)
if (symbols == null)
return;
foreach (var typeSymbol in symbols)
@@ -168,7 +177,8 @@ namespace {nameSpace}
"Usage",
DiagnosticSeverity.Error,
true),
Location.Create(xamlFileName, new TextSpan(0,0), new LinePositionSpan(new LinePosition(0,0),new LinePosition(0,0)))));
Location.Create(xamlFileName, new TextSpan(0, 0),
new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0)))));
continue;
}
@@ -194,7 +204,8 @@ namespace {nameSpace}
}
}
private IReadOnlyList<INamedTypeSymbol> UnpackAnnotatedTypes(in GeneratorExecutionContext context, CSharpCompilation comp, NameReferenceSyntaxReceiver receiver)
private IReadOnlyList<INamedTypeSymbol> UnpackAnnotatedTypes(in GeneratorExecutionContext context,
CSharpCompilation comp, NameReferenceSyntaxReceiver receiver)
{
var options = (CSharpParseOptions) comp.SyntaxTrees[0].Options;
var compilation =

View File

@@ -40,7 +40,7 @@ namespace Robust.Client.GameObjects
/// <param name="message">Arguments for this event.</param>
/// <param name="replay">if true, current cmd state will not be checked or updated - use this for "replaying" an
/// old input that was saved or buffered until further processing could be done</param>
public bool HandleInputCommand(ICommonSession session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false)
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false)
{
#if DEBUG

View File

@@ -12,6 +12,11 @@ namespace Robust.Client.Player
LocalPlayer? LocalPlayer { get; }
/// <summary>
/// Invoked after LocalPlayer is changed
/// </summary>
event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
int PlayerCount { get; }
int MaxPlayers { get; }
event EventHandler PlayerListUpdated;
@@ -23,4 +28,15 @@ namespace Robust.Client.Player
void ApplyPlayerStates(IEnumerable<PlayerState>? list);
}
public class LocalPlayerChangedEventArgs : EventArgs
{
public readonly LocalPlayer? OldPlayer;
public readonly LocalPlayer? NewPlayer;
public LocalPlayerChangedEventArgs(LocalPlayer? oldPlayer, LocalPlayer? newPlayer)
{
OldPlayer = oldPlayer;
NewPlayer = newPlayer;
}
}
}

View File

@@ -37,7 +37,21 @@ namespace Robust.Client.Player
public int MaxPlayers => _client.GameInfo?.ServerMaxPlayers ?? 0;
/// <inheritdoc />
[ViewVariables] public LocalPlayer? LocalPlayer { get; private set; }
[ViewVariables]
public LocalPlayer? LocalPlayer
{
get => _localPlayer;
private set
{
if (_localPlayer == value) return;
var oldValue = _localPlayer;
_localPlayer = value;
LocalPlayerChanged?.Invoke(new LocalPlayerChangedEventArgs(oldValue, _localPlayer));
}
}
private LocalPlayer? _localPlayer;
public event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
/// <inheritdoc />
[ViewVariables] public IEnumerable<IPlayerSession> Sessions => _sessions.Values;

View File

@@ -15,15 +15,36 @@ namespace Robust.Client.UserInterface.Controls
private readonly Popup _popup;
private readonly VBoxContainer _popupVBox;
private readonly Label _label;
private readonly TextureRect _triangle;
public int ItemCount => _buttonData.Count;
/// <summary>
/// If true, hides the triangle that normally appears to the right of the button label
/// </summary>
public bool HideTriangle
{
get => _hideTriangle;
set
{
_hideTriangle = value;
_triangle.Visible = !_hideTriangle;
}
}
private bool _hideTriangle;
/// <summary>
/// StyleClasses to apply to the options that popup when clicking this button.
/// </summary>
public ICollection<string> OptionStyleClasses { get; }
public event Action<ItemSelectedEventArgs>? OnItemSelected;
public string Prefix { get; set; }
public OptionButton()
{
OptionStyleClasses = new List<string>();
AddStyleClass(StyleClassButton);
Prefix = "";
OnPressed += OnPressedInternal;
@@ -43,12 +64,13 @@ namespace Robust.Client.UserInterface.Controls
};
hBox.AddChild(_label);
var textureRect = new TextureRect
_triangle = new TextureRect
{
StyleClasses = { StyleClassOptionTriangle },
SizeFlagsVertical = SizeFlags.ShrinkCenter,
Visible = !HideTriangle
};
hBox.AddChild(textureRect);
hBox.AddChild(_triangle);
}
public void AddItem(Texture icon, string label, int? id = null)
@@ -73,6 +95,10 @@ namespace Robust.Client.UserInterface.Controls
Text = label,
ToggleMode = true
};
foreach (var styleClass in OptionStyleClasses)
{
button.AddStyleClass(styleClass);
}
button.OnPressed += ButtonOnPressed;
var data = new ButtonData(label, button)
{
@@ -170,6 +196,10 @@ namespace Robust.Client.UserInterface.Controls
}
}
/// <summary>
/// Select by index rather than id. Throws exception if item with that index
/// not in this control.
/// </summary>
public void Select(int idx)
{
if (_idMap.TryGetValue(SelectedId, out var prevIdx))
@@ -182,11 +212,29 @@ namespace Robust.Client.UserInterface.Controls
data.Button.Pressed = true;
}
/// <summary>
/// Select by index rather than id.
/// </summary>
/// <returns>false if item with that index not in this control</returns>
public bool TrySelect(int idx)
{
if (idx < 0 || idx >= _buttonData.Count) return false;
Select(idx);
return true;
}
/// throws exception if item with this ID is not in this control
public void SelectId(int id)
{
Select(GetIdx(id));
}
/// <returns>false if item with id not in this control</returns>
public bool TrySelectId(int id)
{
return _idMap.TryGetValue(id, out var idx) && TrySelect(idx);
}
public int GetIdx(int id)
{
return _idMap[id];

View File

@@ -1,15 +1,17 @@
<cc:SS14Window xmlns:cc="clr-namespace:Robust.Client.UserInterface.CustomControls"
xmlns:c="clr-namespace:Robust.Client.UserInterface.Controls">
<c:PanelContainer StyleClasses="windowPanel"/>
<c:VBoxContainer SeparationOverride="0">
<c:PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
<c:HBoxContainer>
<c:MarginContainer MarginLeftOverride="5" SizeFlagsHorizontal="FillExpand">
<c:Label Name="TitleLabel" StyleIdentifier="foo" ClipText="True" Text="Exemplary Window Title Here" VAlign="Center" StyleClasses="windowTitle"/>
</c:MarginContainer>
<c:TextureButton Name="CloseButton" StyleClasses="windowCloseButton" SizeFlagsVertical="ShrinkCenter"></c:TextureButton>
</c:HBoxContainer>
</c:PanelContainer>
<c:MarginContainer Name="ContentsContainer" MarginBottomOverride="10" MarginLeftOverride="10" MarginRightOverride="10" MarginTopOverride="10" RectClipContent="True" SizeFlagsVertical="FillExpand" />
</c:VBoxContainer>
</cc:SS14Window>
<SS14Window xmlns="https://spacestation14.io">
<PanelContainer StyleClasses="windowPanel" />
<VBoxContainer SeparationOverride="0">
<PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
<HBoxContainer>
<MarginContainer MarginLeftOverride="5" SizeFlagsHorizontal="FillExpand">
<Label Name="TitleLabel" StyleIdentifier="foo" ClipText="True"
Text="{Loc Exemplary Window Title Here}" VAlign="Center" StyleClasses="windowTitle" />
</MarginContainer>
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton" SizeFlagsVertical="ShrinkCenter" />
</HBoxContainer>
</PanelContainer>
<MarginContainer Name="ContentsContainer" MarginBottomOverride="10" MarginLeftOverride="10"
MarginRightOverride="10" MarginTopOverride="10" RectClipContent="True"
SizeFlagsVertical="FillExpand" />
</VBoxContainer>
</SS14Window>

View File

@@ -0,0 +1,23 @@
using JetBrains.Annotations;
using Robust.Shared.Localization;
namespace Robust.Client.UserInterface
{
// TODO: Code a XAML compiler transformer to remove references to this type at compile time.
// And just replace them with the Loc.GetString() call.
[PublicAPI]
public class LocExtension
{
public string Key { get; }
public LocExtension(string key)
{
Key = key;
}
public object ProvideValue()
{
return Loc.GetString(Key);
}
}
}

View File

@@ -6,13 +6,6 @@ namespace Robust.Client.UserInterface.XAML
{
}
public class XmlnsDefinitionAttribute : Attribute
{
public XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace)
{
}
}
public class UsableDuringInitializationAttribute : Attribute
{
public UsableDuringInitializationAttribute(bool usable)

View File

@@ -0,0 +1,16 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Avalonia.Data
{
[UsedImplicitly]
public class Binding
{
public Binding()
{
throw new InvalidOperationException(
"Data binding is not currently supported and this type exists only to make Rider work.");
}
}
}

View File

@@ -0,0 +1,6 @@
using Avalonia.Metadata;
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "Robust.Client.UserInterface.XAML.XNamespace")]
[assembly: XmlnsDefinition("https://spacestation14.io", "Robust.Client.UserInterface")]
[assembly: XmlnsDefinition("https://spacestation14.io", "Robust.Client.UserInterface.Controls")]
[assembly: XmlnsDefinition("https://spacestation14.io", "Robust.Client.UserInterface.CustomControls")]

View File

@@ -0,0 +1,12 @@
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Robust.Client.UserInterface.XAML.XNamespace
{
// x:Null
[UsedImplicitly]
public sealed class NullExtension
{
}
}

View File

@@ -0,0 +1,28 @@
# XamlUI notes about Rider
XAML support in both Rider and VS is pretty hardcoded for all supported frameworks. Rider seems less bad about this but they still hardcode a lot of type paths and do other silly stuff. Luckily, they support Avalonia. So... let's pretend to be Avalonia!
**JetBrains, if you're reading this, please don't sue me or take away our licenses pls I only reverse engineered your program to avoid wasting time on your issue tracker.**
## Where to find this info
The primary XAML support in Rider appears to be in `lib/ReSharperHost/JetBrains.ReSharper.Psi.Xaml.dll`. I recommend you decompile it with DotPeek to a project for navigating around. You can then look around the decompiled code (it's not obfuscated at all) to figure out how Rider does all this stuff.
**JetBrains, if you're reading this, please don't sue me or take away our licenses pls I only reverse engineered your program to avoid wasting time on your issue tracker.**
## Attributes
Attributes like `XmlnsAttributionAttribute` are hardcoded for full name with namespace. That's also where it stops, so we just define our own `Avalonia.Metadata.XmlnsDefinitionAttribute` and this fools Rider into recognizing it and working. Hooray!
## `http://schemas.microsoft.com/winfx/2006/xaml` Namespace
As far as I can tell, at least in Avalonia, the things in this namespace are not backed by any real .NET symbols and are just made up by Rider. This is a problem because it only does this making up if it detects that you're using Avalonia.
The heuristic for "are we using Avalonia" appears to be finding an assembly or project reference for `Avalonia.Base`. You can see this in `XamlModulePlatformCache.cs` in the decompiled ReSharper source code and also the `XamlPlatform` flags enum.
For the time being I decided to hold off on actually fully pretending we're Avalonia so did *not* commit to a reference to `Avalonia.Base` yet. Instead we just manually created stuff like a dummy `StaticExtension` class (with a constructor that always throws) and put it into the `http://schemas.microsoft.com/winfx/2006/xaml` namespace. This seems to satisfy Rider without interfering with the actual XamlIL compilation.
## Markup Extensions
Markup extensions have to be classes with a method of signature either `object ProvideValue(IServiceProvider)` or `object ProvideValue()`.
It should be noted that Rider refuses to acknowledge markup extensions unless it finds Avalonia's `Avalonia.Data.Binding` type (or `Avalonia.Markup.Xaml.Data.Binding`). Yes, this is an actual requirement.

View File

@@ -0,0 +1,22 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Robust.Client.UserInterface.XAML.XNamespace
{
// x:Static
[UsedImplicitly]
public sealed class StaticExtension
{
public StaticExtension(string _)
{
throw new InvalidOperationException(
"This type only exists to make Rider work and should never be instantiated.");
}
public static object ProvideValue()
{
throw new InvalidOperationException();
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Robust.Client.UserInterface.XAML.XNamespace
{
// x:Type
[UsedImplicitly]
public sealed class TypeExtension
{
public TypeExtension(string _)
{
throw new InvalidOperationException(
"This type only exists to make Rider work and should never be instantiated.");
}
public static object ProvideValue()
{
throw new InvalidOperationException();
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Avalonia.Metadata
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
[PublicAPI]
public sealed class XmlnsDefinitionAttribute : Attribute
{
public XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace)
{
XmlNamespace = xmlNamespace;
ClrNamespace = clrNamespace;
}
public string XmlNamespace { get; }
public string ClrNamespace { get; }
}
}

View File

@@ -278,7 +278,7 @@ namespace Robust.Server
// Initialize Tier 2 services
IoCManager.Resolve<IGameTiming>().InSimulation = true;
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_stateManager.Initialize();

View File

@@ -152,21 +152,21 @@ namespace Robust.Server.ServerStatus
private void RegisterCVars()
{
try
var path = PathHelpers.ExecutableRelativeFile("build.json");
if (!File.Exists(path))
{
var buildInfo = File.ReadAllText(PathHelpers.ExecutableRelativeFile("build.json"));
var info = JsonConvert.DeserializeObject<BuildInfo>(buildInfo);
return;
}
// Don't replace cvars with contents of build.json if overriden by --cvar or such.
SetCVarIfUnmodified(CVars.BuildEngineVersion, info.EngineVersion);
SetCVarIfUnmodified(CVars.BuildForkId, info.ForkId);
SetCVarIfUnmodified(CVars.BuildVersion, info.Version);
SetCVarIfUnmodified(CVars.BuildDownloadUrl, info.Download ?? "");
SetCVarIfUnmodified(CVars.BuildHash, info.Hash ?? "");
}
catch (FileNotFoundException)
{
}
var buildInfo = File.ReadAllText(path);
var info = JsonConvert.DeserializeObject<BuildInfo>(buildInfo);
// Don't replace cvars with contents of build.json if overriden by --cvar or such.
SetCVarIfUnmodified(CVars.BuildEngineVersion, info.EngineVersion);
SetCVarIfUnmodified(CVars.BuildForkId, info.ForkId);
SetCVarIfUnmodified(CVars.BuildVersion, info.Version);
SetCVarIfUnmodified(CVars.BuildDownloadUrl, info.Download ?? "");
SetCVarIfUnmodified(CVars.BuildHash, info.Hash ?? "");
void SetCVarIfUnmodified(CVarDef<string> cvar, string val)
{

View File

@@ -32,7 +32,6 @@ namespace Robust.Shared.Scripting
"Robust.Shared.IoC",
"Robust.Shared.Maths",
"Robust.Shared.GameObjects",
"Robust.Shared.Interfaces.GameObjects",
"Robust.Shared.Map",
"Robust.Shared.Prototypes"
};

View File

@@ -767,14 +767,14 @@ namespace Robust.Shared.ContentPack
var dllName = $"{simpleName}.dll";
foreach (var diskLoadPath in _diskLoadPaths)
{
try
{
var path = Path.Combine(diskLoadPath, dllName);
return new PEReader(File.OpenRead(path));
}
catch (FileNotFoundException)
var path = Path.Combine(diskLoadPath, dllName);
if (!File.Exists(path))
{
continue;
}
return new PEReader(File.OpenRead(path));
}
var extraStream = _parent.ExtraRobustLoader?.Invoke(dllName);

View File

@@ -90,7 +90,7 @@ namespace Robust.Shared.ContentPack
{
var prevDir = new DirectoryInfo(prevPath);
var found = false;
foreach (var info in prevDir.GetFileSystemInfos())
foreach (var info in prevDir.EnumerateFileSystemInfos())
{
if (!string.Equals(info.Name, segment, StringComparison.InvariantCultureIgnoreCase))
{

View File

@@ -894,6 +894,7 @@ Types:
MidpointRounding: { } # Enum
MulticastDelegate:
Inherit: Allow
NotImplementedException: { All: True }
NotSupportedException: { All: True }
Nullable: { All: True }
Nullable`1: { All: True }

View File

@@ -179,9 +179,23 @@ namespace Robust.Shared.Network
// Well they're in. Kick a connected client with the same GUID if we have to.
if (_assignedUserIds.TryGetValue(userId, out var existing))
{
existing.Disconnect("Another connection has been made with your account.");
// Have to wait until they're properly off the server to avoid any collisions.
await AwaitDisconnectAsync(existing);
if (_awaitingDisconnectToConnect.Contains(userId))
{
connection.Disconnect("Stop trying to connect multiple times at once.");
return;
}
_awaitingDisconnectToConnect.Add(userId);
try
{
existing.Disconnect("Another connection has been made with your account.");
// Have to wait until they're properly off the server to avoid any collisions.
await AwaitDisconnectAsync(existing);
}
finally
{
_awaitingDisconnectToConnect.Remove(userId);
}
}
var msg = peer.Peer.CreateMessage();
@@ -242,8 +256,12 @@ namespace Robust.Shared.Network
private Task AwaitDisconnectAsync(NetConnection connection)
{
var tcs = new TaskCompletionSource<object?>();
_awaitingDisconnect.Add(connection, tcs);
if (!_awaitingDisconnect.TryGetValue(connection, out var tcs))
{
tcs = new TaskCompletionSource<object?>();
_awaitingDisconnect.Add(connection, tcs);
}
return tcs.Task;
}

View File

@@ -135,6 +135,8 @@ namespace Robust.Shared.Network
private readonly Dictionary<NetConnection, TaskCompletionSource<object?>> _awaitingDisconnect
= new();
private readonly HashSet<NetUserId> _awaitingDisconnectToConnect = new HashSet<NetUserId>();
/// <inheritdoc />
public int Port => _config.GetCVar(CVars.NetPort);

View File

@@ -870,7 +870,7 @@ namespace Robust.Shared.Physics
public void Query<TState>(ref TState state, QueryCallback<TState> callback, in Box2 aabb)
{
using var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
stack.Push(_root);
ref var baseRef = ref _nodes[0];
@@ -915,7 +915,7 @@ namespace Robust.Shared.Physics
{
// NOTE: This is not Box2D's normal ray cast function, since our rays have infinite length.
using var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
stack.Push(_root);

View File

@@ -69,7 +69,7 @@ namespace Robust.Shared.Reflection
/// </summary>
/// <typeparam name="T">Attribute to search for.</typeparam>
/// <returns>Enumeration of all types with the specified attribute.</returns>
IEnumerable<Type> FindTypesWithAttribute<T>();
IEnumerable<Type> FindTypesWithAttribute<T>() where T : Attribute;
/// <summary>
/// Loads assemblies into the manager and get all the types.

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Reflection
@@ -158,7 +159,7 @@ namespace Robust.Shared.Reflection
}
/// <inheritdoc />
public IEnumerable<Type> FindTypesWithAttribute<T>()
public IEnumerable<Type> FindTypesWithAttribute<T>() where T : Attribute
{
var types = new List<Type>();
@@ -217,7 +218,21 @@ namespace Robust.Shared.Reflection
Type? found = null;
foreach (var derivedType in GetAllChildren(baseType))
{
if (derivedType.Name == typeName && (derivedType.IsPublic))
if (!derivedType.IsPublic)
{
continue;
}
if (derivedType.Name == typeName)
{
found = derivedType;
break;
}
var serializedAttribute = derivedType.GetCustomAttribute<SerializedTypeAttribute>();
if (serializedAttribute != null &&
serializedAttribute.SerializeName == typeName)
{
found = derivedType;
break;

View File

@@ -0,0 +1,18 @@
using System;
namespace Robust.Shared.Serialization
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class SerializedTypeAttribute : Attribute
{
/// <summary>
/// Name of this type in serialized files.
/// </summary>
public string SerializeName { get; }
public SerializedTypeAttribute(string serializeName)
{
SerializeName = serializeName;
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Robust.Shared.Serialization
_formatter = formatter;
}
public override object NodeToType(Type _type, YamlNode node, YamlObjectSerializer serializer)
public override object NodeToType(Type type, YamlNode node, YamlObjectSerializer serializer)
{
return _formatter.FromCustomFormat(serializer.NodeToType(_formatter.Format, node));
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -510,6 +511,24 @@ namespace Robust.Shared.Serialization
return newList;
}
if (TryGenericImmutableListType(type, out var immutableListType))
{
var listNode = (YamlSequenceNode) node;
var elems = listNode.Children;
var newList = Array.CreateInstance(immutableListType, elems.Count);
for (var i = 0; i < elems.Count; i++)
{
newList.SetValue(NodeToType(immutableListType, elems[i]), i);
}
var list = typeof(ImmutableList);
var add = list.GetMethod("CreateRange")!.MakeGenericMethod(immutableListType);
return add.Invoke(null, new object?[] {newList})!;
}
// Dictionary<K,V>/IReadOnlyDictionary<K,V>
if (TryGenericReadDictType(type, out var keyType, out var valType, out var dictType))
{
@@ -584,12 +603,8 @@ namespace Robust.Shared.Serialization
// IExposeData.
if (typeof(IExposeData).IsAssignableFrom(type))
{
if (!(node is YamlMappingNode mapNode))
{
throw new InvalidOperationException($"Cannot read from IExposeData on non-mapping node. Type: '{type}'");
}
var concreteType = type;
if (type.IsAbstract || type.IsInterface)
{
var tag = node.Tag;
@@ -607,6 +622,12 @@ namespace Robust.Shared.Serialization
}
var instance = (IExposeData)Activator.CreateInstance(concreteType)!;
if (node is not YamlMappingNode mapNode)
{
return instance;
}
// TODO: Might be worth it to cut down on allocations here by using ourselves instead of creating a fork.
// Seems doable.
if (_context != null)
@@ -639,7 +660,7 @@ namespace Robust.Shared.Serialization
throw new ArgumentException($"Type {type.FullName} is not supported.", nameof(type));
}
public T NodeToType<T>(YamlNode node)
public T NodeToType<T>(YamlNode node, string name)
{
return (T) NodeToType(typeof(T), node);
}
@@ -723,6 +744,28 @@ namespace Robust.Shared.Serialization
return node;
}
if (TryGenericImmutableListType(type, out var immutableListType))
{
var node = new YamlSequenceNode {Tag = TagSkipTag};
foreach (var entry in (IEnumerable) obj)
{
if (entry == null)
{
throw new ArgumentException("Cannot serialize null value inside list.");
}
var entryNode = TypeToNode(entry);
// write the concrete type tag
AssignTag<object?>(immutableListType, entry, null, entryNode);
node.Add(entryNode);
}
return node;
}
// Dictionary<K,V>
if (TryGenericDictType(type, out var keyType, out var valType)
|| TryGenericReadOnlyDictType(type, out keyType, out valType))
@@ -861,7 +904,8 @@ namespace Robust.Shared.Serialization
return true;
}
if (TryGenericListType(type!, out _))
if (TryGenericListType(type!, out _) ||
TryGenericImmutableListType(type!, out _))
{
var listA = (IList) a;
var listB = (IList) b!;
@@ -1023,6 +1067,21 @@ namespace Robust.Shared.Serialization
return false;
}
private static bool TryGenericImmutableListType(Type type, [NotNullWhen(true)] out Type? listType)
{
var isImmutableList = type.GetTypeInfo().IsGenericType &&
type.GetGenericTypeDefinition() == typeof(ImmutableList<>);
if (isImmutableList)
{
listType = type.GetGenericArguments()[0];
return true;
}
listType = default;
return false;
}
private static bool TryGenericReadOnlyDictType(Type type, [NotNullWhen(true)] out Type? keyType, [NotNullWhen(true)] out Type? valType)
{
var isDict = type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>);

View File

@@ -123,8 +123,6 @@ namespace Robust.Shared.Timing
FrameEventArgs realFrameEvent;
FrameEventArgs simFrameEvent;
_timing.ResetRealTime();
while (Running)
{
// maximum number of ticks to queue before the loop slows down.

View File

@@ -92,18 +92,13 @@ namespace Robust.Shared.Timing
{
get
{
if (_netManager.IsServer)
{
return RealTime;
}
var clientNetManager = (IClientNetManager) _netManager;
if (clientNetManager.ServerChannel == null)
var offset = GetServerOffset();
if (offset == null)
{
return TimeSpan.Zero;
}
return clientNetManager.ServerChannel.RemoteTime;
return RealTime + offset.Value;
}
}
@@ -236,15 +231,6 @@ namespace Robust.Shared.Timing
_cachedCurTimeInfo = (newTime, CurTick);
}
/// <summary>
/// Resets the real uptime of the server.
/// </summary>
public void ResetRealTime()
{
_realTimer.Restart();
_lastRealTime = TimeSpan.Zero;
}
/// <summary>
/// Resets the simulation time.
/// </summary>
@@ -256,6 +242,35 @@ namespace Robust.Shared.Timing
Paused = true;
}
public TimeSpan RealLocalToServer(TimeSpan local)
{
var offset = GetServerOffset();
if (offset == null)
return TimeSpan.Zero;
return local + offset.Value;
}
public TimeSpan RealServerToLocal(TimeSpan server)
{
var offset = GetServerOffset();
if (offset == null)
return TimeSpan.Zero;
return server - offset.Value;
}
private TimeSpan? GetServerOffset()
{
if (_netManager.IsServer)
{
return TimeSpan.Zero;
}
var clientNetManager = (IClientNetManager) _netManager;
return clientNetManager.ServerChannel?.RemoteTimeOffset;
}
public bool IsFirstTimePredicted { get; private set; } = true;
/// <inheritdoc />

View File

@@ -123,11 +123,6 @@ namespace Robust.Shared.Timing
/// </summary>
void StartFrame();
/// <summary>
/// Resets the real uptime of the server.
/// </summary>
void ResetRealTime();
/// <summary>
/// Is this the first time CurTick has been predicted?
/// </summary>
@@ -162,5 +157,8 @@ namespace Robust.Shared.Timing
/// Resets the simulation time. This should be called on round restarts.
/// </summary>
void ResetSimTime();
TimeSpan RealLocalToServer(TimeSpan local);
TimeSpan RealServerToLocal(TimeSpan server);
}
}

View File

@@ -15,7 +15,8 @@ namespace Robust.Shared.Utility
private static readonly Parser<char, char> ParseEscapeSequence =
Char('\\').Then(OneOf(
Char('\\'),
Char(TagBegin)));
Char(TagBegin),
Char(TagEnd)));
private static readonly Parser<char, TagText> ParseTagText =
ParseEscapeSequence.Or(Token(c => c != TagBegin && c != '\\'))

View File

@@ -1,5 +1,4 @@
using System;
using System.Runtime.InteropServices;
namespace Robust.Shared.Utility
{
@@ -10,62 +9,31 @@ namespace Robust.Shared.Utility
/// If the stack size exceeds the initial capacity, the heap is used
/// to increase the size of the stack.
/// </summary>
/// <remarks>
/// You MUST call <see cref="Dispose"/> when you are done with this stack or else you will have a memory leak.
/// </remarks>
/// <typeparam name="T">The type of elements in the stack.</typeparam>
internal unsafe ref struct GrowableStack<T> where T : unmanaged
internal ref struct GrowableStack<T> where T : unmanaged
{
// TODO: Switch to managed arrays for the on-heap alloc when we have .NET 5.
// Because with .NET 5 we can use the Pinned Object Heap.
private T* _stack;
private bool _wasReallocated;
private Span<T> _stack;
private int _count;
private int _capacity;
/// <summary>
/// Creates the growable stack with the allocated space as stack space.
/// </summary>
/// <remarks>
/// <paramref name="stackSpace"/> MUST BE A PIECE OF PINNED MEMORY,
/// OR ELSE YOU HAVE A MASSIVE GC BUG ON YOUR HAND.
/// </remarks>
public GrowableStack(Span<T> stackSpace)
{
fixed (T* ap = stackSpace)
{
_stack = ap;
}
_stack = stackSpace;
_capacity = stackSpace.Length;
_wasReallocated = false;
_count = 0;
}
public void Dispose()
{
if (_wasReallocated)
{
Marshal.FreeHGlobal((IntPtr) _stack);
_stack = null;
}
}
public void Push(in T element)
{
if (_count == _capacity)
{
var old = _stack;
_capacity *= 2;
var dstSize = _capacity * sizeof(T);
_stack = (T*) Marshal.AllocHGlobal(dstSize);
Buffer.MemoryCopy(old, _stack, dstSize, _count * sizeof(T));
if (_wasReallocated)
{
Marshal.FreeHGlobal((IntPtr) old);
}
_wasReallocated = true;
var oldStack = _stack;
_stack = GC.AllocateUninitializedArray<T>(_capacity);
oldStack.CopyTo(_stack);
}
_stack[_count] = element;
@@ -74,7 +42,6 @@ namespace Robust.Shared.Utility
public T Pop()
{
DebugTools.Assert(_count > 0);
--_count;
return _stack[_count];
}

View File

@@ -1,5 +1,6 @@
using NUnit.Framework;
using Robust.Shared.Serialization;
using Robust.UnitTesting.Shared.Serialization.YamlObjectSerializerTests;
using YamlDotNet.RepresentationModel;
namespace Robust.UnitTesting.Shared.Serialization

View File

@@ -1,6 +1,7 @@
using System;
using NUnit.Framework;
using Robust.Shared.Serialization;
using Robust.UnitTesting.Shared.Serialization.YamlObjectSerializerTests;
using YamlDotNet.RepresentationModel;
namespace Robust.UnitTesting.Shared.Serialization

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using NUnit.Framework;
using Robust.Shared.Serialization;
using YamlDotNet.RepresentationModel;
using static Robust.UnitTesting.Shared.Serialization.YamlObjectSerializerTests.YamlObjectSerializer_Test;
// ReSharper disable AccessToStaticMemberViaDerivedType
namespace Robust.UnitTesting.Shared.Serialization.YamlObjectSerializerTests
{
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
[TestOf(typeof(YamlObjectSerializer))]
public class ImmutableListSerializationTest
{
[Test]
public void SerializeListTest()
{
// Arrange
var data = _serializableList;
var mapping = new YamlMappingNode();
var serializer = YamlObjectSerializer.NewWriter(mapping);
// Act
serializer.DataField(ref data, "datalist", ImmutableList<int>.Empty);
// Assert
var result = NodeToYamlText(mapping);
Assert.That(result, Is.EqualTo(_serializedListYaml));
}
[Test]
public void DeserializeListTest()
{
// Arrange
ImmutableList<int> data = null!;
var rootNode = YamlTextToNode(_serializedListYaml);
var serializer = YamlObjectSerializer.NewReader(rootNode);
// Act
serializer.DataField(ref data, "datalist", ImmutableList<int>.Empty);
// Assert
Assert.That(data, Is.Not.Null);
Assert.That(data.Count, Is.EqualTo(_serializableList.Count));
for (var i = 0; i < _serializableList.Count; i++)
{
Assert.That(data[i], Is.EqualTo(_serializableList[i]));
}
}
private readonly string _serializedListYaml = "datalist:\n- 1\n- 2\n- 3\n...\n";
private readonly ImmutableList<int> _serializableList = ImmutableList.Create<int>(1, 2, 3);
}
}

View File

@@ -0,0 +1,101 @@
using System.IO;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using YamlDotNet.RepresentationModel;
// ReSharper disable AccessToStaticMemberViaDerivedType
namespace Robust.UnitTesting.Shared.Serialization.YamlObjectSerializerTests
{
[TestFixture]
[TestOf(typeof(YamlObjectSerializer))]
public class TypePropertySerialization_Test : RobustUnitTest
{
[Test]
public void SerializeTypePropertiesTest()
{
ITestType? type = new TestTypeTwo
{
TestPropertyOne = "B",
TestPropertyTwo = 10
};
var mapping = new YamlMappingNode();
var writer = YamlObjectSerializer.NewWriter(mapping);
writer.DataField(ref type, "test", null!);
Assert.IsNotEmpty(mapping.Children);
var testPropertyOne = (YamlScalarNode) mapping["test"]["testPropertyOne"];
var testPropertyTwo = (YamlScalarNode) mapping["test"]["testPropertyTwo"];
Assert.That(testPropertyOne.Value, Is.EqualTo("B"));
Assert.That(testPropertyTwo.Value, Is.EqualTo("10"));
}
[Test]
public void DeserializeTypePropertiesTest()
{
ITestType? type = null;
var yaml = @"
- test:
!type:testtype2
testPropertyOne: A
testPropertyTwo: 5
";
using var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(yaml);
writer.Flush();
stream.Position = 0;
var streamReader = new StreamReader(stream);
var yamlStream = new YamlStream();
yamlStream.Load(streamReader);
var mapping = (YamlMappingNode) yamlStream.Documents[0].RootNode[0];
var reader = YamlObjectSerializer.NewReader(mapping);
reader.DataField(ref type, "test", null);
Assert.NotNull(type);
Assert.IsInstanceOf<TestTypeTwo>(type);
var testTypeTwo = (TestTypeTwo) type!;
Assert.That(testTypeTwo.TestPropertyOne, Is.EqualTo("A"));
Assert.That(testTypeTwo.TestPropertyTwo, Is.EqualTo(5));
}
}
[SerializedType("testtype2")]
public class TestTypeTwo : ITestType
{
public string? TestPropertyOne { get; set; }
public int TestPropertyTwo { get; set; }
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => TestPropertyOne, "testPropertyOne", null);
serializer.DataField(this, x => TestPropertyTwo, "testPropertyTwo", 0);
}
}
[RegisterComponent]
public class TestComponent : Component
{
public override string Name => "Test";
public ITestType? TestType { get; set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.TestType, "testType", null);
}
}
}

View File

@@ -0,0 +1,66 @@
using System.IO;
using NUnit.Framework;
using Robust.Shared.Serialization;
using YamlDotNet.RepresentationModel;
// ReSharper disable AccessToStaticMemberViaDerivedType
namespace Robust.UnitTesting.Shared.Serialization.YamlObjectSerializerTests
{
[TestFixture]
[TestOf(typeof(YamlObjectSerializer))]
public class TypeSerialization_Test : RobustUnitTest
{
[Test]
public void SerializeTypeTest()
{
ITestType? type = new TestTypeOne();
var mapping = new YamlMappingNode();
var writer = YamlObjectSerializer.NewWriter(mapping);
writer.DataField(ref type, "type", null!);
Assert.IsNotEmpty(mapping.Children);
Assert.IsInstanceOf<YamlScalarNode>(mapping.Children[0].Key);
var scalar = (YamlScalarNode) mapping.Children[0].Key;
Assert.That(scalar.Value, Is.EqualTo("type"));
}
[Test]
public void DeserializeTypeTest()
{
ITestType? type = null;
var yaml = @"
test:
!type:testtype1";
using var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(yaml);
writer.Flush();
stream.Position = 0;
var streamReader = new StreamReader(stream);
var yamlStream = new YamlStream();
yamlStream.Load(streamReader);
var mapping = (YamlMappingNode) yamlStream.Documents[0].RootNode;
var reader = YamlObjectSerializer.NewReader(mapping);
reader.DataField(ref type, "test", null);
Assert.NotNull(type);
Assert.IsInstanceOf<TestTypeOne>(type);
}
}
public interface ITestType : IExposeData { }
[SerializedType("testtype1")]
public class TestTypeOne : ITestType
{
void IExposeData.ExposeData(ObjectSerializer serializer) { }
}
}

View File

@@ -8,9 +8,10 @@ using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
// ReSharper disable AccessToStaticMemberViaDerivedType
namespace Robust.UnitTesting.Shared.Serialization
namespace Robust.UnitTesting.Shared.Serialization.YamlObjectSerializerTests
{
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]