Compare commits

...

12 Commits

Author SHA1 Message Date
DrSmugleaf
5af7e60043 Add KeyValuePair YAML serialization (#1538) 2021-02-08 21:49:08 +01:00
DrSmugleaf
ab823dcd12 Add component registration lookup with case insensitive names (#1535) 2021-02-08 18:07:23 +01:00
Paul Ritter
b3976eb8d7 Analyzer Bongaloo 2: The Return (#1512)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-02-08 18:05:27 +01:00
Pieter-Jan Briers
91759cdd3c Harder DynamicTree against NaN.
B2DynamicTree<T> has asserts, DynamicTree<T> handles them gracefully.

NaNs in DynamicTree were causing the internal state to corrupt resulting in the sprite flickering issues.

Fixes https://github.com/space-wizards/space-station-14/issues/2896
2021-02-08 05:03:56 +01:00
Pieter-Jan Briers
b000c3178b FileLogHandler is now internal.
Sandboxing thing.
2021-02-07 23:07:57 +01:00
Vera Aguilera Puerto
63b3ecdd13 Fix SpawnRepeating timer extensions 2021-02-07 17:04:43 +01:00
DrSmugleaf
a183a98f75 Add support for deserializing nullable enum types (#1537) 2021-02-07 14:31:38 +01:00
Acruid
bded7ad228 You can now provide a factory delegate to IoC register calls. 2021-02-06 23:00:08 -08:00
metalgearsloth
75c4f48496 Fix NPCs getting stuck (#1533)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-02-05 20:38:01 +01:00
Acruid
49fe2d59cf Removes hard-coded color support for writing console shells. Strings with formatting codes can replace this feature.
Added a separate WriteError function to console shell, to mimic writing to stderr.
2021-02-05 01:35:06 -08:00
DrSmugleaf
8a5b7f5146 Add AudioSystem extension methods for the client and server (#1532) 2021-02-05 17:23:36 +11:00
Pieter-Jan Briers
ae4f470e1f Fix duplicate .xaml files. 2021-02-04 15:22:18 +01:00
55 changed files with 1066 additions and 190 deletions

View File

@@ -0,0 +1,5 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Analyzers\Robust.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
</Project>

View File

@@ -1,2 +1,3 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
<Import Project="Robust.Analyzers.targets" />
</Project>

View File

@@ -1,5 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
<PropertyGroup>
<!-- Avoid MSBuild adding a None entry for XAML files because they'd show up TWICE in the project view. -->
<DefaultItemExcludes>**/*.xaml</DefaultItemExcludes>
<RobustUseExternalMSBuild>true</RobustUseExternalMSBuild>
<_RobustUseExternalMSBuild>$(RobustUseExternalMSBuild)</_RobustUseExternalMSBuild>
<_RobustUseExternalMSBuild Condition="'$(_RobustForceInternalMSBuild)' == 'true'">false</_RobustUseExternalMSBuild>

View File

@@ -0,0 +1,92 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Document = Microsoft.CodeAnalysis.Document;
namespace Robust.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ExplicitInterfaceAnalyzer : DiagnosticAnalyzer
{
public readonly SyntaxKind[] ExcludedModifiers =
{
SyntaxKind.VirtualKeyword,
SyntaxKind.AbstractKeyword,
SyntaxKind.OverrideKeyword
};
public const string DiagnosticId = "RA0000";
private const string Title = "No explicit interface specified";
private const string MessageFormat = "No explicit interface specified";
private const string Description = "Make sure to specify the interface in your method-declaration.";
private const string Category = "Usage";
[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
private const string RequiresExplicitImplementationAttributeMetadataName =
"Robust.Shared.Analyzers.RequiresExplicitImplementationAttribute";
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.PropertyDeclaration);
}
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
ISymbol symbol;
Location location;
switch (context.Node)
{
//we already have a explicit interface specified, no need to check further
case MethodDeclarationSyntax methodDecl when methodDecl.ExplicitInterfaceSpecifier != null || methodDecl.Modifiers.Any(m => ExcludedModifiers.Contains(m.Kind())):
return;
case PropertyDeclarationSyntax propertyDecl when propertyDecl.ExplicitInterfaceSpecifier != null || propertyDecl.Modifiers.Any(m => ExcludedModifiers.Contains(m.Kind())):
return;
case MethodDeclarationSyntax methodDecl:
symbol = context.SemanticModel.GetDeclaredSymbol(methodDecl);
location = methodDecl.Identifier.GetLocation();
break;
case PropertyDeclarationSyntax propertyDecl:
symbol = context.SemanticModel.GetDeclaredSymbol(propertyDecl);
location = propertyDecl.Identifier.GetLocation();
break;
default:
return;
}
var attrSymbol = context.Compilation.GetTypeByMetadataName(RequiresExplicitImplementationAttributeMetadataName);
var isInterfaceMember = symbol?.ContainingType.AllInterfaces.Any(
i =>
i.GetMembers().Any(m => SymbolEqualityComparer.Default.Equals(symbol, symbol.ContainingType.FindImplementationForInterfaceMember(m)))
&& i.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol))
) ?? false;
if (isInterfaceMember)
{
//we do not have an explicit interface specified. bad!
var diagnostic = Diagnostic.Create(
Rule,
location);
context.ReportDiagnostic(diagnostic);
}
}
}
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="3.8.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,150 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Robust.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SerializableAnalyzer : DiagnosticAnalyzer
{
// Metadata of the analyzer
public const string DiagnosticId = "RA0001";
// You could use LocalizedString but it's a little more complicated for this sample
private const string Title = "Class not marked as (Net)Serializable";
private const string MessageFormat = "Class not marked as (Net)Serializable";
private const string Description = "The class should be marked as (Net)Serializable.";
private const string Category = "Usage";
private const string RequiresSerializableAttributeMetadataName = "Robust.Shared.Analyzers.RequiresSerializableAttribute";
private const string SerializableAttributeMetadataName = "System.SerializableAttribute";
private const string NetSerializableAttributeMetadataName = "Robust.Shared.Serialization.NetSerializableAttribute";
[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration);
}
private bool Marked(INamedTypeSymbol namedTypeSymbol, INamedTypeSymbol attrSymbol)
{
if (namedTypeSymbol == null) return false;
if (namedTypeSymbol.GetAttributes()
.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol))) return true;
return Marked(namedTypeSymbol.BaseType, attrSymbol);
}
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var attrSymbol = context.Compilation.GetTypeByMetadataName(RequiresSerializableAttributeMetadataName);
var classDecl = (ClassDeclarationSyntax) context.Node;
var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDecl);
if (classSymbol == null) return;
if (Marked(classSymbol, attrSymbol))
{
var attributes = classSymbol.GetAttributes();
var serAttr = context.Compilation.GetTypeByMetadataName(SerializableAttributeMetadataName);
var netSerAttr = context.Compilation.GetTypeByMetadataName(NetSerializableAttributeMetadataName);
var hasSerAttr = attributes.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, serAttr));
var hasNetSerAttr =
attributes.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, netSerAttr));
if (!hasSerAttr || !hasNetSerAttr)
{
var requiredAttributes = new List<string>();
if(!hasSerAttr) requiredAttributes.Add(SerializableAttributeMetadataName);
if(!hasNetSerAttr) requiredAttributes.Add(NetSerializableAttributeMetadataName);
context.ReportDiagnostic(
Diagnostic.Create(
Rule,
classDecl.Identifier.GetLocation(),
ImmutableDictionary.CreateRange(new Dictionary<string, string>()
{
{
"requiredAttributes", string.Join(",", requiredAttributes)
}
})));
}
}
}
}
[ExportCodeFixProvider(LanguageNames.CSharp)]
public class SerializableCodeFixProvider : CodeFixProvider
{
private const string Title = "Annotate class as (Net)Serializable.";
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
foreach (var diagnostic in context.Diagnostics)
{
var span = diagnostic.Location.SourceSpan;
var classDecl = root.FindToken(span.Start).Parent.AncestorsAndSelf().OfType<ClassDeclarationSyntax>().First();
if(!diagnostic.Properties.TryGetValue("requiredAttributes", out var requiredAttributes)) return;
context.RegisterCodeFix(
CodeAction.Create(
Title,
c => FixAsync(context.Document, classDecl, requiredAttributes, c),
Title),
diagnostic);
}
}
private async Task<Document> FixAsync(Document document, ClassDeclarationSyntax classDecl,
string requiredAttributes, CancellationToken cancellationToken)
{
var attributes = new List<AttributeSyntax>();
var namespaces = new List<string>();
foreach (var attribute in requiredAttributes.Split(','))
{
var tempSplit = attribute.Split('.');
namespaces.Add(string.Join(".",tempSplit.Take(tempSplit.Length-1)));
var @class = tempSplit.Last();
@class = @class.Substring(0, @class.Length - 9); //cut out "Attribute" at the end
attributes.Add(SyntaxFactory.Attribute(SyntaxFactory.ParseName(@class)));
}
var newClassDecl =
classDecl.AddAttributeLists(SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(attributes)));
var root = (CompilationUnitSyntax) await document.GetSyntaxRootAsync(cancellationToken);
root = root.ReplaceNode(classDecl, newClassDecl);
foreach (var ns in namespaces)
{
if(root.Usings.Any(u => u.Name.ToString() == ns)) continue;
root = root.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns)));
}
return document.WithSyntaxRoot(root);
}
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(SerializableAnalyzer.DiagnosticId);
public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using Robust.Client.Log;
using Robust.Shared.Console;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
@@ -14,13 +13,17 @@ namespace Robust.Client.Console
{
public class AddStringArgs : EventArgs
{
public Color Color { get; }
public string Text { get; }
public AddStringArgs(string text, Color color)
public bool Local { get; }
public bool Error { get; }
public AddStringArgs(string text, bool local, bool error)
{
Text = text;
Color = color;
Local = local;
Error = error;
}
}
@@ -37,8 +40,6 @@ namespace Robust.Client.Console
/// <inheritdoc cref="IClientConsoleHost" />
internal class ClientConsoleHost : ConsoleHost, IClientConsoleHost
{
private static readonly Color _msgColor = new(65, 105, 225);
private bool _requestedCommands;
/// <inheritdoc />
@@ -63,46 +64,51 @@ namespace Robust.Client.Console
SendServerCommandRequest();
}
/// <inheritdoc />
public event EventHandler<AddStringArgs>? AddString;
/// <inheritdoc />
public event EventHandler<AddFormattedMessageArgs>? AddFormatted;
/// <inheritdoc />
public void AddFormattedLine(FormattedMessage message)
{
AddFormatted?.Invoke(this, new AddFormattedMessageArgs(message));
}
public override void WriteLine(ICommonSession? session, string text, Color color)
/// <inheritdoc />
public override void WriteError(ICommonSession? session, string text)
{
AddString?.Invoke(this, new AddStringArgs(text, color));
OutputText(text, true, true);
}
/// <inheritdoc />
public override void ExecuteCommand(ICommonSession? session, string command)
{
if (string.IsNullOrWhiteSpace(command))
return;
// echo the command locally
WriteLine(null, "> " + command, Color.Lime);
WriteError(null, "> " + command);
//Commands are processed locally and then sent to the server to be processed there again.
var args = new List<string>();
CommandParsing.ParseArguments(command, args);
var commandname = args[0];
var commandName = args[0];
if (AvailableCommands.ContainsKey(commandname))
if (AvailableCommands.ContainsKey(commandName))
{
var command1 = AvailableCommands[commandname];
var command1 = AvailableCommands[commandName];
args.RemoveAt(0);
command1.Execute(new ConsoleShell(this, null), command, args.ToArray());
}
else if (!NetManager.IsConnected) WriteLine(null, "Unknown command: " + commandname, Color.Red);
else if (!NetManager.IsConnected)
WriteError(null, "Unknown command: " + commandName);
}
/// <summary>
/// Sends a command directly to the server.
/// </summary>
/// <inheritdoc />
public override void RemoteExecuteCommand(ICommonSession? session, string command)
{
if (!NetManager.IsConnected)
@@ -113,9 +119,10 @@ namespace Robust.Client.Console
NetManager.ClientSendMessage(msg);
}
/// <inheritdoc />
public override void WriteLine(ICommonSession? session, string text)
{
WriteLine(null, text, Color.White);
OutputText(text, true, false);
}
/// <inheritdoc />
@@ -124,6 +131,11 @@ namespace Robust.Client.Console
// We don't have anything to dispose.
}
private void OutputText(string text, bool local, bool error)
{
AddString?.Invoke(this, new AddStringArgs(text, local, error));
}
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
{
SendServerCommandRequest();
@@ -131,14 +143,14 @@ namespace Robust.Client.Console
private void HandleConCmdAck(MsgConCmdAck msg)
{
WriteLine(null, "< " + msg.Text, _msgColor);
OutputText("< " + msg.Text, false, msg.Error);
}
private void HandleConCmdReg(MsgConCmdReg msg)
{
foreach (var cmd in msg.Commands)
{
var commandName = cmd.Name;
string? commandName = cmd.Name;
// Do not do duplicate commands.
if (AvailableCommands.ContainsKey(commandName))
@@ -176,12 +188,6 @@ namespace Robust.Client.Console
[Reflect(false)]
internal class ServerDummyCommand : IConsoleCommand
{
public string Command { get; }
public string Description { get; }
public string Help { get; }
internal ServerDummyCommand(string command, string help, string description)
{
Command = command;
@@ -189,6 +195,12 @@ namespace Robust.Client.Console
Description = description;
}
public string Command { get; }
public string Description { get; }
public string Help { get; }
// Always forward to server.
public void Execute(IConsoleShell shell, string argStr, string[] args)
{

View File

@@ -16,7 +16,7 @@ namespace Robust.Client.Console.Commands
{
if (args.Length < 1 || args.Length > 2)
{
shell.WriteLine("Must provide exactly one or two arguments.", Color.Red);
shell.WriteError("Must provide exactly one or two arguments.");
return;
}
@@ -32,7 +32,7 @@ namespace Robust.Client.Console.Commands
if (!configManager.IsCVarRegistered(name))
{
shell.WriteLine($"CVar '{name}' is not registered. Use 'cvar ?' to get a list of all registered CVars.", Color.Red);
shell.WriteError($"CVar '{name}' is not registered. Use 'cvar ?' to get a list of all registered CVars.");
return;
}

View File

@@ -30,11 +30,9 @@ namespace Robust.Client.Console.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
Color[] colors = { Color.Green, Color.Blue, Color.Red };
var random = IoCManager.Resolve<IRobustRandom>();
for (int x = 0; x < 50; x++)
{
shell.WriteLine("filling...", colors[random.Next(0, colors.Length)]);
shell.WriteLine("filling...");
}
}
}

View File

@@ -55,7 +55,7 @@ namespace Robust.Client.Console.Commands
foreach (var e in entityManager.GetEntities().OrderBy(e => e.Uid))
{
shell.WriteLine($"entity {e.Uid}, {e.Prototype?.ID}, {e.Transform.Coordinates}.", Color.White);
shell.WriteLine($"entity {e.Uid}, {e.Prototype?.ID}, {e.Transform.Coordinates}.");
}
}
}
@@ -92,16 +92,16 @@ namespace Robust.Client.Console.Commands
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
shell.WriteLine(message.ToString(), Color.White);
shell.WriteLine(message.ToString());
foreach (var type in registration.References)
{
shell.WriteLine($" {type}", Color.White);
shell.WriteLine($" {type}");
}
}
catch (UnknownComponentException)
{
shell.WriteLine($"No registration found for '{args[0]}'", Color.Red);
shell.WriteError($"No registration found for '{args[0]}'");
}
}
}
@@ -215,13 +215,13 @@ namespace Robust.Client.Console.Commands
if (!int.TryParse(args[0], out var id))
{
shell.WriteLine($"{args[0]} is not a valid integer.",Color.Red);
shell.WriteError($"{args[0]} is not a valid integer.");
return;
}
var mgr = IoCManager.Resolve<IDebugDrawingManager>();
mgr.DebugDrawRays = !mgr.DebugDrawRays;
shell.WriteLine("Toggled showing rays to:" + mgr.DebugDrawRays.ToString(), Color.Green);
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays.ToString());
mgr.DebugRayLifetime = TimeSpan.FromSeconds((double)int.Parse(args[0], CultureInfo.InvariantCulture));
}
}
@@ -257,7 +257,7 @@ namespace Robust.Client.Console.Commands
if ((!new Regex(@"^c?[0-9]+$").IsMatch(args[0])))
{
shell.WriteLine("Malformed UID", Color.Red);
shell.WriteError("Malformed UID");
return;
}
@@ -265,7 +265,7 @@ namespace Robust.Client.Console.Commands
var entmgr = IoCManager.Resolve<IEntityManager>();
if (!entmgr.TryGetEntity(uid, out var entity))
{
shell.WriteLine("That entity does not exist. Sorry lad.", Color.Red);
shell.WriteError("That entity does not exist. Sorry lad.");
return;
}
@@ -310,13 +310,13 @@ namespace Robust.Client.Console.Commands
if (!int.TryParse(args[0], out var id))
{
shell.WriteLine($"{args[0]} is not a valid integer.",Color.Red);
shell.WriteError($"{args[0]} is not a valid integer.");
return;
}
if (!new Regex(@"^-?[0-9]+,-?[0-9]+$").IsMatch(indices))
{
shell.WriteLine("mapIndicies must be of form x<int>,y<int>", Color.Red);
shell.WriteError("mapIndicies must be of form x<int>,y<int>");
return;
}
@@ -327,7 +327,7 @@ namespace Robust.Client.Console.Commands
}
else
{
shell.WriteLine("given offset type is not defined", Color.Red);
shell.WriteError("given offset type is not defined");
return;
}
@@ -347,7 +347,7 @@ namespace Robust.Client.Console.Commands
}
else
{
shell.WriteLine("grid does not exist", Color.Red);
shell.WriteError("grid does not exist");
}
}
}
@@ -368,7 +368,7 @@ namespace Robust.Client.Console.Commands
var client = IoCManager.Resolve<IBaseClient>();
client.PlayerNameOverride = args[0];
shell.WriteLine($"Overriding player name to \"{args[0]}\".", Color.White);
shell.WriteLine($"Overriding player name to \"{args[0]}\".");
}
}
@@ -395,7 +395,7 @@ namespace Robust.Client.Console.Commands
}
catch(ArgumentException)
{
shell.WriteLine("Unable to find type", Color.Red);
shell.WriteError("Unable to find type");
return;
}
@@ -432,7 +432,7 @@ namespace Robust.Client.Console.Commands
}
catch(ArgumentException)
{
shell.WriteLine("Unable to find type", Color.Red);
shell.WriteError("Unable to find type");
return;
}
@@ -472,7 +472,7 @@ namespace Robust.Client.Console.Commands
}
else
{
shell.WriteLine($"No grid exists with id {id}",Color.Red);
shell.WriteError($"No grid exists with id {id}");
}
}
}
@@ -759,7 +759,7 @@ namespace Robust.Client.Console.Commands
if (int.TryParse(args[0], out int result))
GC.Collect(result);
else
shell.WriteLine("Failed to parse argument.",Color.Red);
shell.WriteError("Failed to parse argument.");
}
}
}

View File

@@ -18,7 +18,7 @@ namespace Robust.Client.Console.Commands
if (type == null)
{
shell.WriteLine("That type does not exist", Color.Red);
shell.WriteError("That type does not exist");
return;
}

View File

@@ -27,7 +27,7 @@ namespace Robust.Client.Console.Commands
if (!IoCManager.Resolve<IClientNetManager>().IsConnected)
{
// No server so nothing to respond with unknown command.
shell.WriteLine("Unknown command: " + commandname, Color.Red);
shell.WriteError("Unknown command: " + commandname);
return;
}
// TODO: Maybe have a server side help?
@@ -39,7 +39,7 @@ namespace Robust.Client.Console.Commands
break;
default:
shell.WriteLine("Invalid amount of arguments.", Color.Red);
shell.WriteError("Invalid amount of arguments.");
break;
}
}

View File

@@ -17,7 +17,7 @@ namespace Robust.Client.Console.Commands
{
if (args.Length != 2)
{
shell.WriteLine("Invalid argument amount. Expected 2 arguments.", Color.Red);
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
return;
}
@@ -54,7 +54,7 @@ namespace Robust.Client.Console.Commands
{
if (args.Length != 3)
{
shell.WriteLine("Invalid argument amount. Expected 3 arguments.", Color.Red);
shell.WriteError("Invalid argument amount. Expected 3 arguments.");
return;
}

View File

@@ -42,7 +42,7 @@ namespace Robust.Client.Console.Commands
var mgr = IoCManager.Resolve<IScriptClient>();
if (!mgr.CanScript)
{
shell.WriteLine(Loc.GetString("You do not have server side scripting permission."), Color.Red);
shell.WriteError(Loc.GetString("You do not have server side scripting permission."));
return;
}

View File

@@ -356,4 +356,76 @@ namespace Robust.Client.GameObjects.EntitySystems
event Action PlaybackDone;
}
public static class AudioSystemExtensions
{
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this IEntity entity,
string filename,
AudioParams? audioParams,
AudioSystem? audioSystem = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(filename, entity, audioParams);
}
/// <summary>
/// Play an audio stream following an entity.
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this IEntity entity,
AudioStream stream,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(stream, entity, audioParams);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this EntityCoordinates coordinates,
string filename,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(filename, coordinates, audioParams);
}
/// <summary>
/// Play an audio stream at a static position.
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this EntityCoordinates coordinates,
AudioStream stream,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(stream, coordinates, audioParams);
}
}
}

View File

@@ -233,13 +233,13 @@ namespace Robust.Client.GameStates
{
if (args.Length != 1)
{
shell.WriteLine("Invalid argument amount. Expected 2 arguments.", Color.Red);
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
return;
}
if (!byte.TryParse(args[0], out var iValue))
{
shell.WriteLine("Invalid argument: Needs to be 0 or 1.", Color.Red);
shell.WriteError("Invalid argument: Needs to be 0 or 1.");
return;
}

View File

@@ -176,7 +176,7 @@ namespace Robust.Client.GameStates
{
if (args.Length != 1)
{
shell.WriteLine("Invalid argument amount. Expected 2 arguments.", Color.Red);
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
return;
}

View File

@@ -77,7 +77,7 @@ namespace Robust.Client.GameStates
{
if (args.Length != 1)
{
shell.WriteLine("Invalid argument amount. Expected 2 arguments.", Color.Red);
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
return;
}

View File

@@ -18,7 +18,7 @@ namespace Robust.Client.Interfaces.Input
public bool CanRepeat;
public bool AllowSubCombs;
public void ExposeData(ObjectSerializer serializer)
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Function, "function", default);
serializer.DataField(ref Type, "type", KeyBindingType.State);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -94,7 +94,7 @@ namespace Robust.Client.UserInterface.CustomControls
CommandBar.OnTextEntered += CommandEntered;
CommandBar.OnHistoryChanged += OnHistoryChanged;
_consoleHost.AddString += (_, args) => AddLine(args.Text, args.Color);
_consoleHost.AddString += (_, args) => AddLine(args.Text, DetermineColor(args.Local, args.Error));
_consoleHost.AddFormatted += (_, args) => AddFormattedLine(args.Message);
_consoleHost.ClearText += (_, args) => Clear();
@@ -103,6 +103,11 @@ namespace Robust.Client.UserInterface.CustomControls
searchResults = new List<string>();
}
private Color DetermineColor(bool local, bool error)
{
return Color.White;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);

View File

@@ -5,7 +5,6 @@ using Robust.Server.Interfaces.Player;
using Robust.Shared.Console;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Utility;
@@ -15,9 +14,9 @@ namespace Robust.Server.Console
/// <inheritdoc cref="IServerConsoleHost" />
internal class ServerConsoleHost : ConsoleHost, IServerConsoleHost
{
[Dependency] private readonly IConGroupController _groupController = default!;
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
[Dependency] private readonly IConGroupController _groupController = default!;
/// <inheritdoc />
public override void ExecuteCommand(ICommonSession? session, string command)
@@ -26,6 +25,53 @@ namespace Robust.Server.Console
ExecuteInShell(shell, command);
}
/// <inheritdoc />
public override void RemoteExecuteCommand(ICommonSession? session, string command)
{
//TODO: Server -> Client remote execute, just like how the client forwards the command
}
/// <inheritdoc />
public override void WriteLine(ICommonSession? session, string text)
{
if (session is IPlayerSession playerSession)
OutputText(playerSession, text, false);
else
OutputText(null, text, false);
}
/// <inheritdoc />
public override void WriteError(ICommonSession? session, string text)
{
if (session is IPlayerSession playerSession)
OutputText(playerSession, text, true);
else
OutputText(null, text, true);
}
/// <inheritdoc />
public void Initialize()
{
RegisterCommand("sudo", "sudo make me a sandwich", "sudo <command>",(shell, _, args) =>
{
string command = args[0];
var cArgs = args[1..].Select(CommandParsing.Escape);
var localShell = shell.ConsoleHost.LocalShell;
var sudoShell = new SudoShell(this, localShell, shell);
ExecuteInShell(sudoShell, $"{command} {string.Join(' ', cArgs)}");
});
LoadConsoleCommands();
// setup networking with clients
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, ProcessCommand);
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME);
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME,
message => HandleRegistrationRequest(message.MsgChannel));
}
private void ExecuteInShell(IConsoleShell shell, string command)
{
try
@@ -37,7 +83,7 @@ namespace Robust.Server.Console
if (args.Count == 0)
return;
var cmdName = args[0];
string? cmdName = args[0];
if (AvailableCommands.TryGetValue(cmdName, out var conCmd)) // command registered
{
@@ -49,7 +95,7 @@ namespace Robust.Server.Console
conCmd.Execute(shell, command, args.ToArray());
}
else
shell.WriteLine($"Unknown command: '{cmdName}'");
shell.WriteError($"Unknown command: '{cmdName}'");
}
else // system console
{
@@ -58,56 +104,15 @@ namespace Robust.Server.Console
}
}
else
shell.WriteLine($"Unknown command: '{cmdName}'");
shell.WriteError($"Unknown command: '{cmdName}'");
}
catch (Exception e)
{
LogManager.GetSawmill(SawmillName).Warning($"{FormatPlayerString(shell.Player)}: ExecuteError - {command}:\n{e}");
shell.WriteLine($"There was an error while executing the command: {e}");
shell.WriteError($"There was an error while executing the command: {e}");
}
}
public override void RemoteExecuteCommand(ICommonSession? session, string command)
{
//TODO: Server -> Client remote execute, just like how the client forwards the command
}
public override void WriteLine(ICommonSession? session, string text)
{
if (session is IPlayerSession playerSession)
SendText(playerSession, text);
else
SendText(null as IPlayerSession, text);
}
public override void WriteLine(ICommonSession? session, string text, Color color)
{
//TODO: Make colors work.
WriteLine(session, text);
}
/// <inheritdoc />
public void Initialize()
{
RegisterCommand("sudo", "sudo make me a sandwich", "sudo <command>", (shell, _, args) =>
{
var command = args[0];
var cArgs = args[1..].Select(CommandParsing.Escape);
var localShell = shell.ConsoleHost.LocalShell;
var sudoShell = new SudoShell(this, localShell, shell);
ExecuteInShell(sudoShell, $"{command} {string.Join(' ', cArgs)}");
});
LoadConsoleCommands();
// setup networking with clients
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, ProcessCommand);
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME);
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME,
message => HandleRegistrationRequest(message.MsgChannel));
}
private void HandleRegistrationRequest(INetChannel senderConnection)
{
var netMgr = IoCManager.Resolve<IServerNetManager>();
@@ -115,6 +120,7 @@ namespace Robust.Server.Console
var counter = 0;
message.Commands = new MsgConCmdReg.Command[RegisteredCommands.Count];
foreach (var command in RegisteredCommands.Values)
{
message.Commands[counter++] = new MsgConCmdReg.Command
@@ -130,7 +136,7 @@ namespace Robust.Server.Console
private void ProcessCommand(MsgConCmd message)
{
var text = message.Text;
string? text = message.Text;
var sender = message.MsgChannel;
var session = _players.GetSessionByChannel(sender);
@@ -139,34 +145,19 @@ namespace Robust.Server.Console
ExecuteCommand(session, text);
}
/// <summary>
/// Sends a text string to the remote player.
/// </summary>
/// <param name="session">
/// Remote player to send the text message to. If this is null, the text is sent to the local
/// console.
/// </param>
/// <param name="text">Text message to send.</param>
private void SendText(IPlayerSession? session, string text)
private void OutputText(IPlayerSession? session, string text, bool error)
{
if (session != null)
SendText(session.ConnectedClient, text);
{
var replyMsg = NetManager.CreateNetMessage<MsgConCmdAck>();
replyMsg.Error = error;
replyMsg.Text = text;
NetManager.ServerSendMessage(replyMsg, session.ConnectedClient);
}
else
_systemConsole.Print(text + "\n");
}
/// <summary>
/// Sends a text string to the remote console.
/// </summary>
/// <param name="target">Net channel to send the text string to.</param>
/// <param name="text">Text message to send.</param>
private void SendText(INetChannel target, string text)
{
var replyMsg = NetManager.CreateNetMessage<MsgConCmdAck>();
replyMsg.Text = text;
NetManager.ServerSendMessage(replyMsg, target);
}
private static string FormatPlayerString(IBaseSession? session)
{
return session != null ? $"{session.Name}" : "[HOST]";
@@ -205,10 +196,10 @@ namespace Robust.Server.Console
_sudoer.WriteLine(text);
}
public void WriteLine(string text, Color color)
public void WriteError(string text)
{
_owner.WriteLine(text, color);
_sudoer.WriteLine(text, color);
_owner.WriteError(text);
_sudoer.WriteError(text);
}
public void Clear()

View File

@@ -289,7 +289,7 @@ namespace Robust.Server.GameObjects.Components.Container
Type = type;
}
public void ExposeData(ObjectSerializer serializer)
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Entities, "entities", new List<EntityUid>());
serializer.DataField(ref Type, "type", null);

View File

@@ -7,6 +7,7 @@ using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using static Robust.Server.GameObjects.EntitySystems.AudioSystem;
namespace Robust.Server.GameObjects.EntitySystems
{
@@ -43,13 +44,15 @@ namespace Robust.Server.GameObjects.EntitySystems
Identifier = id
};
if(sessions == null)
if (sessions == null)
RaiseNetworkEvent(msg);
else
{
foreach (var session in sessions)
{
RaiseNetworkEvent(msg, session.ConnectedClient);
}
}
}
private uint CacheIdentifier()
@@ -90,7 +93,7 @@ namespace Robust.Server.GameObjects.EntitySystems
players.RemoveAt(i);
continue;
}
RaiseNetworkEvent(msg, player.ConnectedClient);
}
@@ -135,7 +138,7 @@ namespace Robust.Server.GameObjects.EntitySystems
players.RemoveAt(i);
continue;
}
RaiseNetworkEvent(msg, player.ConnectedClient);
}
@@ -177,7 +180,7 @@ namespace Robust.Server.GameObjects.EntitySystems
players.RemoveAt(i);
continue;
}
RaiseNetworkEvent(msg, player.ConnectedClient);
}
@@ -223,4 +226,49 @@ namespace Robust.Server.GameObjects.EntitySystems
#endregion
}
public static class AudioSystemExtensions
{
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="range">The max range at which the audio will be heard. Less than or equal to 0 to send to every player.</param>
/// <param name="excludedSession">Sessions that won't receive the audio message.</param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static void PlaySoundFrom(
this IEntity entity,
string filename,
AudioParams? audioParams = null,
int range = AudioDistanceRange,
IPlayerSession? excludedSession = null,
AudioSystem? audioSystem = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
audioSystem.PlayFromEntity(filename, entity, audioParams, range, excludedSession);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="range">The max range at which the audio will be heard. Less than or equal to 0 to send to every player.</param>
/// <param name="excludedSession">Session that won't receive the audio message.</param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static void PlaySoundFrom(
this EntityCoordinates coordinates,
string filename,
AudioParams? audioParams = null,
int range = AudioDistanceRange,
IPlayerSession? excludedSession = null,
AudioSystem? audioSystem = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
audioSystem.PlayAtCoords(filename, coordinates, audioParams, range, excludedSession);
}
}
}

View File

@@ -0,0 +1,7 @@
using System;
namespace Robust.Shared.Analyzers
{
[AttributeUsage(AttributeTargets.Interface)]
public class RequiresExplicitImplementationAttribute : Attribute { }
}

View File

@@ -0,0 +1,7 @@
using System;
namespace Robust.Shared.Analyzers
{
[AttributeUsage(AttributeTargets.Class)]
public class RequiresSerializableAttribute : Attribute { }
}

View File

@@ -55,7 +55,7 @@ namespace Robust.Shared.Audio
/// </summary>
public static readonly AudioParams Default = new(0, 1, "Master", 62.5f, 1, AudioMixTarget.Stereo, false, 0f);
public void ExposeData(ObjectSerializer serializer)
void IExposeData.ExposeData(ObjectSerializer serializer)
{
Volume = serializer.ReadDataField("volume", 0f);
PitchScale = serializer.ReadDataField("pitchscale", 1f);

View File

@@ -1,4 +1,4 @@
using System.IO;
using System.IO;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.Resources;
@@ -23,14 +23,14 @@ namespace Robust.Shared.Console.Commands
if (args.Length < 1)
{
shell.WriteLine("No file specified!", Color.Red);
shell.WriteError("No file specified!");
return;
}
var path = new ResourcePath(args[0]).ToRootedPath();
if (!res.UserData.Exists(path))
{
shell.WriteLine("File does not exist.", Color.Red);
shell.WriteError("File does not exist.");
return;
}

View File

@@ -75,7 +75,7 @@ namespace Robust.Shared.Console
//TODO: IConsoleOutput for [e#1225]
public abstract void WriteLine(ICommonSession? session, string text);
public abstract void WriteLine(ICommonSession? session, string text, Color color);
public abstract void WriteError(ICommonSession? session, string text);
/// <inheritdoc />
public void ClearLocalConsole()

View File

@@ -1,4 +1,3 @@
using Robust.Shared.Maths;
using Robust.Shared.Players;
namespace Robust.Shared.Console
@@ -6,6 +5,18 @@ namespace Robust.Shared.Console
/// <inheritdoc />
public class ConsoleShell : IConsoleShell
{
/// <summary>
/// Constructs a new instance of <see cref="ConsoleShell"/>.
/// </summary>
/// <param name="host">Console Host that owns this shell.</param>
/// <param name="session">Player Session that this shell represents. If this is null, then
/// the shell is representing the local console.</param>
public ConsoleShell(IConsoleHost host, ICommonSession? session)
{
ConsoleHost = host;
Player = session;
}
/// <inheritdoc />
public IConsoleHost ConsoleHost { get; }
@@ -15,12 +26,6 @@ namespace Robust.Shared.Console
/// <inheritdoc />
public ICommonSession? Player { get; }
public ConsoleShell(IConsoleHost host, ICommonSession? session)
{
ConsoleHost = host;
Player = session;
}
/// <inheritdoc />
public void ExecuteCommand(string command)
{
@@ -40,9 +45,9 @@ namespace Robust.Shared.Console
}
/// <inheritdoc />
public void WriteLine(string text, Color color)
public void WriteError(string text)
{
ConsoleHost.WriteLine(Player, text, color);
ConsoleHost.WriteError(Player, text);
}
/// <inheritdoc />

View File

@@ -110,8 +110,7 @@ namespace Robust.Shared.Console
/// console.
/// </param>
/// <param name="text">Text message to send.</param>
/// <param name="color">Foreground color of the text.</param>
void WriteLine(ICommonSession? session, string text, Color color);
void WriteError(ICommonSession? session, string text);
/// <summary>
/// Removes all text from the local console.

View File

@@ -56,11 +56,10 @@ namespace Robust.Shared.Console
void WriteLine(string text);
/// <summary>
/// Write a line with a specific color to the console window.
/// Write an error line to the console window.
/// </summary>
/// <param name="text">Line of text to write.</param>
/// <param name="color">Foreground color of the string of text.</param>
void WriteLine(string text, Color color);
void WriteError(string text);
/// <summary>
/// Clears the entire console of text.

View File

@@ -45,6 +45,11 @@ namespace Robust.Shared.GameObjects
/// </summary>
private readonly Dictionary<string, ComponentRegistration> names = new();
/// <summary>
/// Mapping of lowercase component names to their registration.
/// </summary>
private readonly Dictionary<string, string> _lowerCaseNames = new();
/// <summary>
/// Mapping of network ID to type.
/// </summary>
@@ -70,7 +75,7 @@ namespace Robust.Shared.GameObjects
Register(typeof(T), overwrite);
}
private void Register(Type type, bool overwrite=false)
private void Register(Type type, bool overwrite = false)
{
if (types.ContainsKey(type))
{
@@ -82,6 +87,7 @@ namespace Robust.Shared.GameObjects
var dummy = (IComponent)Activator.CreateInstance(type)!;
var name = dummy.Name;
var lowerCaseName = name.ToLowerInvariant();
var netID = dummy.NetID;
var netSyncExist = dummy.NetworkSynchronizeExistence;
@@ -105,6 +111,14 @@ namespace Robust.Shared.GameObjects
RemoveComponent(name);
}
if (_lowerCaseNames.ContainsKey(lowerCaseName))
{
if (!overwrite)
{
throw new InvalidOperationException($"{lowerCaseName} is already registered, previous: {_lowerCaseNames[lowerCaseName]}");
}
}
if (netID != null && netIDs.ContainsKey(netID.Value))
{
if (!overwrite)
@@ -117,6 +131,7 @@ namespace Robust.Shared.GameObjects
var registration = new ComponentRegistration(name, type, netID, netSyncExist);
names[name] = registration;
_lowerCaseNames[lowerCaseName] = name;
types[type] = registration;
if (netID != null)
{
@@ -169,6 +184,7 @@ namespace Robust.Shared.GameObjects
var registration = names[name];
names.Remove(registration.Name);
_lowerCaseNames.Remove(registration.Name.ToLowerInvariant());
types.Remove(registration.Type);
if (registration.NetID != null)
{
@@ -176,8 +192,13 @@ namespace Robust.Shared.GameObjects
}
}
public ComponentAvailability GetComponentAvailability(string componentName)
public ComponentAvailability GetComponentAvailability(string componentName, bool ignoreCase = false)
{
if (ignoreCase && _lowerCaseNames.TryGetValue(componentName, out var lowerCaseName))
{
componentName = lowerCaseName;
}
if (names.ContainsKey(componentName))
{
return ComponentAvailability.Available;
@@ -209,8 +230,13 @@ namespace Robust.Shared.GameObjects
return _typeFactory.CreateInstanceUnchecked<T>(types[typeof(T)].Type);
}
public IComponent GetComponent(string componentName)
public IComponent GetComponent(string componentName, bool ignoreCase = false)
{
if (ignoreCase && _lowerCaseNames.TryGetValue(componentName, out var lowerCaseName))
{
componentName = lowerCaseName;
}
return _typeFactory.CreateInstanceUnchecked<IComponent>(GetRegistration(componentName).Type);
}
@@ -219,8 +245,13 @@ namespace Robust.Shared.GameObjects
return _typeFactory.CreateInstanceUnchecked<IComponent>(GetRegistration(netId).Type);
}
public IComponentRegistration GetRegistration(string componentName)
public IComponentRegistration GetRegistration(string componentName, bool ignoreCase = false)
{
if (ignoreCase && _lowerCaseNames.TryGetValue(componentName, out var lowerCaseName))
{
componentName = lowerCaseName;
}
try
{
return names[componentName];
@@ -265,8 +296,13 @@ namespace Robust.Shared.GameObjects
return GetRegistration(component.GetType());
}
public bool TryGetRegistration(string componentName, [NotNullWhen(true)] out IComponentRegistration? registration)
public bool TryGetRegistration(string componentName, [NotNullWhen(true)] out IComponentRegistration? registration, bool ignoreCase = false)
{
if (ignoreCase && _lowerCaseNames.TryGetValue(componentName, out var lowerCaseName))
{
componentName = lowerCaseName;
}
if (names.TryGetValue(componentName, out var tempRegistration))
{
registration = tempRegistration;

View File

@@ -1,8 +1,10 @@
using Robust.Shared.Serialization;
using System;
using Robust.Shared.Analyzers;
namespace Robust.Shared.GameObjects
{
[RequiresSerializable]
[Serializable, NetSerializable]
public class ComponentState
{

View File

@@ -80,7 +80,7 @@ namespace Robust.Shared.GameObjects.Components.Renderable
};
}
public void ExposeData(ObjectSerializer serializer)
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Shader, "shader", null);
serializer.DataField(ref TexturePath, "texture", null);

View File

@@ -86,7 +86,7 @@ namespace Robust.Shared.GameObjects.Components.Timers
{
entity
.EnsureComponent<TimerComponent>()
.Spawn(milliseconds, onFired, cancellationToken);
.SpawnRepeating(milliseconds, onFired, cancellationToken);
}
/// <summary>
@@ -100,7 +100,7 @@ namespace Robust.Shared.GameObjects.Components.Timers
{
entity
.EnsureComponent<TimerComponent>()
.Spawn(duration, onFired, cancellationToken);
.SpawnRepeating(duration, onFired, cancellationToken);
}
}
}

View File

@@ -14,7 +14,7 @@ namespace Robust.Shared.GameObjects.Components.UserInterface
public object UiKey { get; private set; } = default!;
public string ClientType { get; private set; } = default!;
public void ExposeData(ObjectSerializer serializer)
void IExposeData.ExposeData(ObjectSerializer serializer)
{
UiKey = serializer.ReadStringEnumKey("key");
ClientType = serializer.ReadDataField<string>("type");

View File

@@ -96,6 +96,8 @@ namespace Robust.Shared.GameObjects.Systems
foreach (var physics in _queuedDeletions)
{
// If an entity was swapped from awake -> sleep -> awake then it's still relevant.
if (!physics.Deleted && physics.Awake) continue;
_awakeBodies.Remove(physics);
_predictedAwakeBodies.Remove(physics);
_controllers.Remove(physics);

View File

@@ -54,12 +54,13 @@ namespace Robust.Shared.Interfaces.GameObjects
/// </summary>
IEnumerable<Type> AllRegisteredTypes { get; }
/// <summary>
/// <summary>
/// Get whether a component is available right now.
/// </summary>
/// <param name="componentName">The name of the component to check.</param>
/// <param name="ignoreCase">Whether or not to ignore casing on <see cref="componentName"/></param>
/// <returns>The availability of the component.</returns>
ComponentAvailability GetComponentAvailability(string componentName);
ComponentAvailability GetComponentAvailability(string componentName, bool ignoreCase = false);
/// <summary>
/// Registers a prototype to be available for spawning.
@@ -109,11 +110,12 @@ namespace Robust.Shared.Interfaces.GameObjects
/// Gets a new component instantiated of the specified <see cref="IComponent.Name"/>.
/// </summary>
/// <param name="componentName">name of component to make</param>
/// <param name="ignoreCase">Whether or not to ignore casing on <see cref="componentName"/></param>
/// <returns>A Component</returns>
/// <exception cref="UnknownComponentException">
/// Thrown if no component exists with the given name <see cref="componentName"/>.
/// </exception>
IComponent GetComponent(string componentName);
IComponent GetComponent(string componentName, bool ignoreCase = false);
/// <summary>
/// Gets a new component instantiated of the specified network ID.
@@ -129,10 +131,11 @@ namespace Robust.Shared.Interfaces.GameObjects
/// Gets the registration belonging to a component, throwing an exception if it does not exist.
/// </summary>
/// <param name="componentName">The name of the component.</param>
/// <param name="ignoreCase">Whether or not to ignore casing on <see cref="componentName"/></param>
/// <exception cref="UnknownComponentException">
/// Thrown if no component exists with the given name <see cref="componentName"/>.
/// </exception>
IComponentRegistration GetRegistration(string componentName);
IComponentRegistration GetRegistration(string componentName, bool ignoreCase = false);
/// <summary>
/// Gets the registration belonging to a component, throwing an exception if it does not exist.
@@ -179,8 +182,9 @@ namespace Robust.Shared.Interfaces.GameObjects
/// </summary>
/// <param name="componentName">The name of the component.</param>
/// <param name="registration">The registration if found, null otherwise.</param>
/// <param name="ignoreCase">Whether or not to ignore casing on <see cref="componentName"/></param>
/// <returns>true it found, false otherwise.</returns>
bool TryGetRegistration(string componentName, [NotNullWhen(true)] out IComponentRegistration? registration);
bool TryGetRegistration(string componentName, [NotNullWhen(true)] out IComponentRegistration? registration, bool ignoreCase = false);
/// <summary>
/// Tries to get the registration belonging to a component.

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization;
namespace Robust.Shared.Interfaces.Serialization
@@ -5,6 +6,7 @@ namespace Robust.Shared.Interfaces.Serialization
/// <summary>
/// Interface for the "expose data" system, which is basically our method of handling data serialization.
/// </summary>
[RequiresExplicitImplementation]
public interface IExposeData
{
/// <summary>

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -9,6 +9,9 @@ using Robust.Shared.Utility;
namespace Robust.Shared.IoC
{
public delegate T DependencyFactoryDelegate<out T>()
where T : class;
/// <inheritdoc />
internal class DependencyCollection : IDependencyCollection
{
@@ -26,6 +29,8 @@ namespace Robust.Shared.IoC
/// </summary>
private readonly Dictionary<Type, Type> _resolveTypes = new();
private readonly Dictionary<Type, DependencyFactoryDelegate<object>> _resolveFactories = new();
// To do injection of common types like components, we make DynamicMethods to do the actual injecting.
// This is way faster than reflection and should be allocation free outside setup.
private readonly Dictionary<Type, (InjectorDelegate? @delegate, object[]? services)> _injectorCache =
@@ -34,11 +39,18 @@ namespace Robust.Shared.IoC
/// <inheritdoc />
public void Register<TInterface, TImplementation>(bool overwrite = false)
where TImplementation : class, TInterface, new()
{
Register<TInterface, TImplementation>(() => new TImplementation(), overwrite);
}
public void Register<TInterface, TImplementation>(DependencyFactoryDelegate<TImplementation> factory, bool overwrite = false)
where TImplementation : class, TInterface
{
var interfaceType = typeof(TInterface);
CheckRegisterInterface(interfaceType, typeof(TImplementation), overwrite);
_resolveTypes[interfaceType] = typeof(TImplementation);
_resolveFactories[typeof(TImplementation)] = factory;
}
[AssertionMethod]
@@ -96,6 +108,7 @@ namespace Robust.Shared.IoC
_services.Clear();
_resolveTypes.Clear();
_resolveFactories.Clear();
_injectorCache.Clear();
}
@@ -156,7 +169,8 @@ namespace Robust.Shared.IoC
try
{
var instance = Activator.CreateInstance(value)!;
// Yay for delegate covariance
object instance = _resolveFactories[value].Invoke();
_services[key] = instance;
injectList.Add(instance);
}
@@ -166,6 +180,10 @@ namespace Robust.Shared.IoC
}
}
// Because we only ever construct an instance once per registration, there is no need to keep the factory
// delegates. Also we need to free the delegates because lambdas capture variables.
_resolveFactories.Clear();
// Graph built, go over ones that need injection.
foreach (var implementation in injectList)
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics.Contracts;
using Robust.Shared.IoC.Exceptions;
@@ -43,6 +43,24 @@ namespace Robust.Shared.IoC
void Register<TInterface, TImplementation>(bool overwrite = false)
where TImplementation : class, TInterface, new();
/// <summary>
/// Registers an interface to an implementation, to make it accessible to <see cref="DependencyCollection.Resolve{T}"/>
/// <see cref="IDependencyCollection.BuildGraph"/> MUST be called after this method to make the new interface available.
/// </summary>
/// <typeparam name="TInterface">The type that will be resolvable.</typeparam>
/// <typeparam name="TImplementation">The type that will be constructed as implementation.</typeparam>
/// <param name="factory">A factory method to construct the instance of the implementation.</param>
/// <param name="overwrite">
/// If true, do not throw an <see cref="InvalidOperationException"/> if an interface is already registered,
/// replace the current implementation instead.
/// </param>
/// <exception cref="InvalidOperationException">
/// Thrown if <paramref name="overwrite"/> is false and <typeparamref name="TInterface"/> has been registered before,
/// or if an already instantiated interface (by <see cref="DependencyCollection.BuildGraph"/>) is attempting to be overwritten.
/// </exception>
void Register<TInterface, TImplementation>(DependencyFactoryDelegate<TImplementation> factory, bool overwrite = false)
where TImplementation : class, TInterface;
/// <summary>
/// Registers an interface to an existing instance of an implementation,
/// making it accessible to <see cref="IDependencyCollection.Resolve{T}"/>.

View File

@@ -1,4 +1,4 @@
using Robust.Shared.IoC.Exceptions;
using Robust.Shared.IoC.Exceptions;
using System;
using System.Diagnostics.Contracts;
using System.Threading;
@@ -103,6 +103,29 @@ namespace Robust.Shared.IoC
_container.Value!.Register<TInterface, TImplementation>(overwrite);
}
/// <summary>
/// Registers an interface to an implementation, to make it accessible to <see cref="Resolve{T}"/>
/// <see cref="BuildGraph"/> MUST be called after this method to make the new interface available.
/// </summary>
/// <typeparam name="TInterface">The type that will be resolvable.</typeparam>
/// <typeparam name="TImplementation">The type that will be constructed as implementation.</typeparam>
/// <param name="factory">A factory method to construct the instance of the implementation.</param>
/// <param name="overwrite">
/// If true, do not throw an <see cref="InvalidOperationException"/> if an interface is already registered,
/// replace the current implementation instead.
/// </param>
/// <exception cref="InvalidOperationException">
/// Thrown if <paramref name="overwrite"/> is false and <typeparamref name="TInterface"/> has been registered before,
/// or if an already instantiated interface (by <see cref="BuildGraph"/>) is attempting to be overwritten.
/// </exception>
public static void Register<TInterface, TImplementation>(DependencyFactoryDelegate<TImplementation> factory, bool overwrite = false)
where TImplementation : class, TInterface
{
DebugTools.Assert(_container.IsValueCreated, NoContextAssert);
_container.Value!.Register<TInterface, TImplementation>(factory, overwrite);
}
/// <summary>
/// Registers an interface to an existing instance of an implementation,
/// making it accessible to <see cref="IDependencyCollection.Resolve{T}"/>.

View File

@@ -6,7 +6,7 @@ using Serilog.Events;
namespace Robust.Shared.Log
{
public sealed class FileLogHandler : ILogHandler, IDisposable
internal sealed class FileLogHandler : ILogHandler, IDisposable
{
private readonly TextWriter writer;

View File

@@ -1,5 +1,6 @@
using System;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
@@ -83,7 +84,7 @@ namespace Robust.Shared.Map
}
/// <inheritdoc />
public void ExposeData(ObjectSerializer serializer)
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _gridId, "grid", GridId.Invalid);

View File

@@ -1,4 +1,4 @@
using Lidgren.Network;
using Lidgren.Network;
using Robust.Shared.Interfaces.Network;
#nullable disable
@@ -14,6 +14,7 @@ namespace Robust.Shared.Network.Messages
#endregion
public string Text { get; set; }
public bool Error { get; set; }
public override void ReadFromBuffer(NetIncomingMessage buffer)
{

View File

@@ -18,6 +18,8 @@
* 3. This notice may not be removed or altered from any source distribution.
*/
// #define DEBUG_DYNAMIC_TREE
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -268,6 +270,9 @@ namespace Robust.Shared.Physics
/// </summary>
public Proxy CreateProxy(in Box2 aabb, T userData)
{
// Also catches NaN fuckery.
Assert(aabb.Right >= aabb.Left && aabb.Top >= aabb.Bottom);
ref var proxy = ref AllocateNode(out var proxyId);
// Fatten the aabb.
@@ -292,6 +297,8 @@ namespace Robust.Shared.Physics
public bool MoveProxy(Proxy proxy, in Box2 aabb, Vector2 displacement)
{
Assert(0 <= proxy && proxy < Capacity);
// Also catches NaN fuckery.
Assert(aabb.Right >= aabb.Left && aabb.Top >= aabb.Bottom);
ref var leafNode = ref _nodes[proxy];

View File

@@ -71,7 +71,7 @@ namespace Robust.Shared.Physics
private readonly ExtractAabbDelegate _extractAabb;
// avoids "Collection was modified; enumeration operation may not execute."
private IDictionary<T, Proxy> _nodeLookup;
private Dictionary<T, Proxy> _nodeLookup;
private readonly B2DynamicTree<T> _b2Tree;
public DynamicTree(ExtractAabbDelegate extractAabbFunc, IEqualityComparer<T>? comparer = null, float aabbExtendSize = 1f / 32, int capacity = 256, Func<int, int>? growthFunc = null)
@@ -134,6 +134,12 @@ namespace Robust.Shared.Physics
var box = _extractAabb(item);
if (CheckNaNs(box))
{
_nodeLookup[item] = Proxy.Free;
return true;
}
proxy = _b2Tree.CreateProxy(box, item);
_nodeLookup[item] = proxy;
@@ -162,7 +168,11 @@ namespace Robust.Shared.Physics
return false;
}
_b2Tree.DestroyProxy(proxy);
if (proxy != Proxy.Free)
{
_b2Tree.DestroyProxy(proxy);
}
return true;
}
@@ -178,6 +188,25 @@ namespace Robust.Shared.Physics
}
var newBox = _extractAabb(item);
if (CheckNaNs(newBox))
{
if (proxy == Proxy.Free)
{
return false;
}
_b2Tree.DestroyProxy(proxy);
_nodeLookup[item] = Proxy.Free;
return true;
}
if (proxy == Proxy.Free)
{
_nodeLookup[item] = _b2Tree.CreateProxy(newBox, item);
return true;
}
return _b2Tree.MoveProxy(proxy, newBox, Vector2.Zero);
}
@@ -304,6 +333,14 @@ namespace Robust.Shared.Physics
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AddOrUpdate(T item) => Update(item) || Add(item);
private static bool CheckNaNs(in Box2 box)
{
return float.IsNaN(box.Left)
|| float.IsNaN(box.Top)
|| float.IsNaN(box.Bottom)
|| float.IsNaN(box.Right);
}
[Conditional("DEBUG_DYNAMIC_TREE")]
[Conditional("DEBUG_DYNAMIC_TREE_ASSERTS")]
[DebuggerNonUserCode]

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -80,7 +81,7 @@ namespace Robust.Shared.Physics
}
/// <inheritdoc />
public void ExposeData(ObjectSerializer serializer)
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _collisionLayer, "layer", 0, WithFormat.Flags<CollisionLayer>());
serializer.DataField(ref _collisionMask, "mask", 0, WithFormat.Flags<CollisionMask>());

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -61,7 +62,7 @@ namespace Robust.Shared.Physics
}
/// <inheritdoc />
public void ExposeData(ObjectSerializer serializer)
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _collisionLayer, "layer", 0, WithFormat.Flags<CollisionLayer>());
serializer.DataField(ref _collisionMask, "mask", 0, WithFormat.Flags<CollisionMask>());

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -64,7 +65,7 @@ namespace Robust.Shared.Physics
handle.SetTransform(Matrix3.Identity);
}
public void ExposeData(ObjectSerializer serializer)
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _collisionLayer, "layer", 0, WithFormat.Flags<CollisionLayer>());
serializer.DataField(ref _collisionMask, "mask", 0, WithFormat.Flags<CollisionMask>());

View File

@@ -5,7 +5,6 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using Robust.Shared.ContentPack;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
@@ -478,8 +477,8 @@ namespace Robust.Shared.Serialization
}
// val enum
if (type.IsEnum)
return Enum.Parse(type, node.ToString());
if (underlyingType.IsEnum)
return Enum.Parse(underlyingType, node.ToString());
// IReadOnlyList<T>/IReadOnlyCollection<T>
if (TryGenericReadOnlyCollectionType(type, out var collectionType))
@@ -546,6 +545,33 @@ namespace Robust.Shared.Serialization
return newSet;
}
// KeyValuePair<K, V>
if (TryGenericKeyValuePairType(type, out var kvpKeyType, out var kvpValType))
{
var pairType = typeof(KeyValuePair<,>).MakeGenericType(kvpKeyType, kvpValType);
var pairNode = (YamlMappingNode) node;
switch (pairNode.Children.Count)
{
case 0:
return Activator.CreateInstance(pairType)!;
case 1:
{
using var enumerator = pairNode.GetEnumerator();
enumerator.MoveNext();
var yamlPair = enumerator.Current;
var keyValue = NodeToType(kvpKeyType, yamlPair.Key);
var valValue = NodeToType(kvpValType, yamlPair.Value);
var pair = Activator.CreateInstance(pairType, keyValue, valValue)!;
return pair;
}
default:
throw new InvalidOperationException(
$"Cannot read KeyValuePair from mapping node with more than one child.");
}
}
// Hand it to the context.
if (_context != null && _context.TryNodeToType(node, type, out var contextObj))
{
@@ -749,6 +775,21 @@ namespace Robust.Shared.Serialization
return node;
}
if (TryGenericKeyValuePairType(type, out var kvpKeyType, out var kvpValType))
{
var node = new YamlMappingNode {Tag = TagSkipTag};
dynamic pair = obj;
var keyNode = TypeToNode(pair.Key);
var valNode = TypeToNode(pair.Value);
// write the concrete type tag
AssignTag<object?>(kvpValType, pair, null, valNode);
node.Add(keyNode, valNode);
return node;
}
// Hand it to the context.
if (_context != null && _context.TryTypeToNode(obj, out var contextNode))
{
@@ -896,6 +937,24 @@ namespace Robust.Shared.Serialization
return true;
}
if (TryGenericKeyValuePairType(type!, out _, out _))
{
dynamic tupleA = a;
dynamic tupleB = b!;
if (!IsSerializedEqual(tupleA.Key, tupleB.Key))
{
return false;
}
if (!IsSerializedEqual(tupleA.Value, tupleB.Value))
{
return false;
}
return true;
}
if (typeof(IExposeData).IsAssignableFrom(type))
{
// Serialize both, see if output matches.
@@ -1035,6 +1094,27 @@ namespace Robust.Shared.Serialization
return false;
}
private static bool TryGenericKeyValuePairType(
Type type,
[NotNullWhen(true)] out Type? keyType,
[NotNullWhen(true)] out Type? valType)
{
var isPair = type.GetTypeInfo().IsGenericType &&
type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>);
if (isPair)
{
var genArgs = type.GetGenericArguments();
keyType = genArgs[0];
valType = genArgs[1];
return true;
}
keyType = default;
valType = default;
return false;
}
public abstract class TypeSerializer
{
public abstract object NodeToType(Type type, YamlNode node, YamlObjectSerializer serializer);

View File

@@ -0,0 +1,118 @@
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
// ReSharper disable AccessToStaticMemberViaDerivedType
namespace Robust.UnitTesting.Shared.GameObjects
{
[TestFixture]
[TestOf(typeof(ComponentFactory))]
public class ComponentFactory_Tests : RobustUnitTest
{
private const string TestComponentName = "A";
private const string LowercaseTestComponentName = "a";
private const string NonexistentComponentName = "B";
[OneTimeSetUp]
public void OneTimeSetUp()
{
IoCManager.Resolve<IComponentFactory>().Register<TestComponent>();
}
[Test]
public void GetComponentAvailabilityTest()
{
var componentFactory = IoCManager.Resolve<IComponentFactory>();
// Should not exist
Assert.That(componentFactory.GetComponentAvailability(NonexistentComponentName), Is.EqualTo(ComponentAvailability.Unknown));
Assert.That(componentFactory.GetComponentAvailability(NonexistentComponentName, true), Is.EqualTo(ComponentAvailability.Unknown));
// Normal casing, do not ignore case, should exist
Assert.That(componentFactory.GetComponentAvailability(TestComponentName), Is.EqualTo(ComponentAvailability.Available));
// Normal casing, ignore case, should exist
Assert.That(componentFactory.GetComponentAvailability(TestComponentName, true), Is.EqualTo(ComponentAvailability.Available));
// Lower casing, do not ignore case, should not exist
Assert.That(componentFactory.GetComponentAvailability(LowercaseTestComponentName), Is.EqualTo(ComponentAvailability.Unknown));
// Lower casing, ignore case, should exist
Assert.That(componentFactory.GetComponentAvailability(LowercaseTestComponentName, true), Is.EqualTo(ComponentAvailability.Available));
}
[Test]
public void GetComponentTest()
{
var componentFactory = IoCManager.Resolve<IComponentFactory>();
// Should not exist
Assert.Throws<UnknownComponentException>(() => componentFactory.GetComponent(NonexistentComponentName));
Assert.Throws<UnknownComponentException>(() => componentFactory.GetComponent(NonexistentComponentName, true));
// Normal casing, do not ignore case, should exist
Assert.IsInstanceOf<TestComponent>(componentFactory.GetComponent(TestComponentName));
// Normal casing, ignore case, should exist
Assert.IsInstanceOf<TestComponent>(componentFactory.GetComponent(TestComponentName, true));
// Lower casing, do not ignore case, should not exist
Assert.Throws<UnknownComponentException>(() => componentFactory.GetComponent(LowercaseTestComponentName));
// Lower casing, ignore case, should exist
Assert.IsInstanceOf<TestComponent>(componentFactory.GetComponent(LowercaseTestComponentName, true));
}
[Test]
public void GetRegistrationTest()
{
var componentFactory = IoCManager.Resolve<IComponentFactory>();
// Should not exist
Assert.Throws<UnknownComponentException>(() => componentFactory.GetRegistration(NonexistentComponentName));
Assert.Throws<UnknownComponentException>(() => componentFactory.GetRegistration(NonexistentComponentName, true));
// Normal casing, do not ignore case, should exist
Assert.DoesNotThrow(() => componentFactory.GetRegistration(TestComponentName));
// Normal casing, ignore case, should exist
Assert.DoesNotThrow(() => componentFactory.GetRegistration(TestComponentName, true));
// Lower casing, do not ignore case, should not exist
Assert.Throws<UnknownComponentException>(() => componentFactory.GetRegistration(LowercaseTestComponentName));
// Lower casing, ignore case, should exist
Assert.DoesNotThrow(() => componentFactory.GetRegistration(LowercaseTestComponentName, true));
}
[Test]
public void TryGetRegistrationTest()
{
var componentFactory = IoCManager.Resolve<IComponentFactory>();
// Should not exist
Assert.False(componentFactory.TryGetRegistration(NonexistentComponentName, out _));
Assert.False(componentFactory.TryGetRegistration(NonexistentComponentName, out _, true));
// Normal casing, do not ignore case, should exist
Assert.True(componentFactory.TryGetRegistration(TestComponentName, out _));
// Normal casing, ignore case, should exist
Assert.True(componentFactory.TryGetRegistration(TestComponentName, out _, true));
// Lower casing, do not ignore case, should not exist
Assert.False(componentFactory.TryGetRegistration(LowercaseTestComponentName, out _));
// Lower casing, ignore case, should exist
Assert.True(componentFactory.TryGetRegistration(LowercaseTestComponentName, out _, true));
}
private class TestComponent : Component
{
public override string Name => TestComponentName;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Moq;
using NUnit.Framework;
using Robust.Shared.IoC;
@@ -47,6 +47,18 @@ namespace Robust.UnitTesting.Shared.IoC
Assert.That(IoCManager.ResolveType(typeof(TestFieldInjection)), Is.Not.Null);
}
[Test]
public void IoCRegisterFactory()
{
var newInstance = new TestFieldInjection();
IoCManager.Register<TestFieldInjection, TestFieldInjection>(() => newInstance);
IoCManager.BuildGraph(); // Actually calls the factory
var result = IoCManager.Resolve<TestFieldInjection>();
Assert.That(result, Is.EqualTo(newInstance));
}
[Test]
public void IoCTestOverwrite()
{

View File

@@ -3,15 +3,13 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Moq;
using NUnit.Framework;
using Robust.Shared.ContentPack;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
// ReSharper disable AccessToStaticMemberViaDerivedType
namespace Robust.UnitTesting.Shared.Serialization
{
@@ -327,6 +325,103 @@ namespace Robust.UnitTesting.Shared.Serialization
private readonly string SerializedSetYaml = "dataSet:\n- 1\n- 2\n- 3\n...\n";
private readonly HashSet<int> SerializableSet = new() { 1, 2, 3 };
[Test]
public void SerializePairTest()
{
var mapping = new YamlMappingNode();
var pair = SerializablePair;
var writer = YamlObjectSerializer.NewWriter(mapping);
writer.DataField(ref pair, "dataPair", new KeyValuePair<string, int>("val0", 0));
var result = NodeToYamlText(mapping);
Assert.That(result, Is.EqualTo(SerializedPairYaml));
}
[Test]
public void DeserializePairTest()
{
KeyValuePair<string, int> data = default;
var rootNode = YamlTextToNode(SerializedPairYaml);
var serializer = YamlObjectSerializer.NewReader(rootNode);
serializer.DataField(ref data, "dataPair", new KeyValuePair<string, int>("val0", 0));
Assert.That(data, Is.Not.EqualTo(default(KeyValuePair<string, int>)));
Assert.That(data.Key, Is.EqualTo(SerializablePair.Key));
Assert.That(data.Value, Is.EqualTo(SerializablePair.Value));
}
[Test]
public void SerializeDefaultPairTest()
{
var mapping = new YamlMappingNode();
var pair = SerializableDefaultPair;
var writer = YamlObjectSerializer.NewWriter(mapping);
writer.DataField(ref pair, "dataPair", new KeyValuePair<int, int>(0, 0));
var result = NodeToYamlText(mapping);
Assert.That(result, Is.EqualTo(SerializedDefaultPairYaml));
}
[Test]
public void DeserializeDefaultPairTest()
{
KeyValuePair<int, int> data = default;
var rootNode = YamlTextToNode(SerializedDefaultPairYaml);
var serializer = YamlObjectSerializer.NewReader(rootNode);
serializer.DataField(ref data, "dataPair", new KeyValuePair<int, int>(0, 0));
Assert.That(data, Is.EqualTo(default(KeyValuePair<int, int>)));
Assert.That(data.Key, Is.EqualTo(SerializableDefaultPair.Key));
Assert.That(data.Value, Is.EqualTo(SerializableDefaultPair.Value));
}
[Test]
public void DeserializeNoPairTest()
{
KeyValuePair<int, int> data = default;
var rootNode = YamlTextToNode(SerializedNoPairYaml);
var serializer = YamlObjectSerializer.NewReader(rootNode);
serializer.DataField(ref data, "dataPair", new KeyValuePair<int, int>(0, 0));
Assert.That(data, Is.EqualTo(default(KeyValuePair<int, int>)));
Assert.That(data.Key, Is.EqualTo(SerializedNoPair.Key));
Assert.That(data.Value, Is.EqualTo(SerializedNoPair.Value));
}
[Test]
public void SerializedEqualPairTest()
{
var pair = new KeyValuePair<string, int>("val0", 0);
var pair2 = new KeyValuePair<string, int>("val0", 0);
Assert.That(YamlObjectSerializer.IsSerializedEqual(pair, pair2), Is.True);
}
[Test]
public void SerializedNotEqualPairTest()
{
var pair = new KeyValuePair<string, int>("val0", 0);
var pair2 = new KeyValuePair<string, int>("val0", 1);
Assert.That(YamlObjectSerializer.IsSerializedEqual(pair, pair2), Is.False);
}
private readonly string SerializedPairYaml = "dataPair:\n val1: 1\n...\n";
private readonly KeyValuePair<string, int> SerializablePair = new("val1", 1);
private readonly string SerializedDefaultPairYaml = "{}\n...\n";
private readonly KeyValuePair<int, int> SerializableDefaultPair = new(0, 0);
private readonly string SerializedNoPairYaml = "dataPair: {}\n...\n";
private readonly KeyValuePair<int, int> SerializedNoPair = new(0, 0);
[Test]
public void NullablePrimitiveSerializeNullTest()
{

View File

@@ -32,6 +32,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ManagedHttpListener", "ManagedHttpListener", "{15D28C35-25F6-4EA8-8D53-29DA7C8A24A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Net.HttpListener", "ManagedHttpListener\src\System.Net.HttpListener.csproj", "{C3EB43AF-31FD-48F5-A4FB-552D0F13948B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Client.Injectors", "Robust.Client.Injectors\Robust.Client.Injectors.csproj", "{EEF2C805-5E03-41EA-A916-49C1DD15EF41}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Client.NameGenerator", "Robust.Client.NameGenerator\Robust.Client.NameGenerator.csproj", "{EFB7A05D-71D0-47D1-B7B4-35D4FF661F13}"
@@ -44,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlX.IL.Cecil", "XamlX\src
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlX.Runtime", "XamlX\src\XamlX.Runtime\XamlX.Runtime.csproj", "{B05EFB71-AEC7-4C6E-984A-A1BCC58F9AD1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Analyzers", "Robust.Analyzers\Robust.Analyzers.csproj", "{3173712A-9E75-4685-B657-9AF9B7D54EFB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -188,6 +191,14 @@ Global
{B05EFB71-AEC7-4C6E-984A-A1BCC58F9AD1}.Release|Any CPU.Build.0 = Release|Any CPU
{B05EFB71-AEC7-4C6E-984A-A1BCC58F9AD1}.Release|x64.ActiveCfg = Release|Any CPU
{B05EFB71-AEC7-4C6E-984A-A1BCC58F9AD1}.Release|x64.Build.0 = Release|Any CPU
{3173712A-9E75-4685-B657-9AF9B7D54EFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3173712A-9E75-4685-B657-9AF9B7D54EFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3173712A-9E75-4685-B657-9AF9B7D54EFB}.Debug|x64.ActiveCfg = Debug|Any CPU
{3173712A-9E75-4685-B657-9AF9B7D54EFB}.Debug|x64.Build.0 = Debug|Any CPU
{3173712A-9E75-4685-B657-9AF9B7D54EFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3173712A-9E75-4685-B657-9AF9B7D54EFB}.Release|Any CPU.Build.0 = Release|Any CPU
{3173712A-9E75-4685-B657-9AF9B7D54EFB}.Release|x64.ActiveCfg = Release|Any CPU
{3173712A-9E75-4685-B657-9AF9B7D54EFB}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE