mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48dbcf7fd4 | ||
|
|
686c47a193 | ||
|
|
ecfaa68ae6 | ||
|
|
fd27f315cb | ||
|
|
fe1648d290 | ||
|
|
ec0c667c33 | ||
|
|
75d0c29973 | ||
|
|
5d6dbc18e3 | ||
|
|
5e160e26ee | ||
|
|
0e54fa7329 | ||
|
|
2722448474 | ||
|
|
e7f75ab35d | ||
|
|
57361e8ffd | ||
|
|
8449015cf8 | ||
|
|
72d6a42c27 | ||
|
|
f509405022 | ||
|
|
7bb516f0bf | ||
|
|
521e7981bc | ||
|
|
4c87e6185f | ||
|
|
aaf5003fcf | ||
|
|
3bec89aaa5 |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ END TEMPLATE-->
|
||||
|
||||
### New features
|
||||
|
||||
*None yet*
|
||||
* If a sandbox error is caused by a compiler-generated method, the engine will now attempt to point out which using code is responsible.
|
||||
* Added `OrderedDictionary<TKey, TValue>` and `System.StringComparer` to the sandbox whitelist.
|
||||
* Added more overloads to `MapLoaderSystem` taking `TextReader`/`TextWriter` where appropriate.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -47,13 +49,32 @@ END TEMPLATE-->
|
||||
|
||||
### Other
|
||||
|
||||
*None yet*
|
||||
* Public APIs involving `System.Random` have been obsoleted. Use `IRobustRandom`/`RobustRandom` and such instead.
|
||||
|
||||
### Internal
|
||||
|
||||
*None yet*
|
||||
|
||||
|
||||
## 272.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Reversed an undocumented breaking change from `v267.3.0`: entity spawning with a `MapCoordinates` now takes the rotation as relative to the map again instead of relative to the grid the entity was attached to.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `ProfManager.Value` guard method.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `ValidateMemberAnalyzer` taking a ridiculous amount of compile time.
|
||||
|
||||
### Other
|
||||
|
||||
* `ProfManager` is now initialized on the server.
|
||||
|
||||
|
||||
## 271.2.0
|
||||
|
||||
### New features
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
@@ -29,16 +27,15 @@ public sealed class ValidateMemberAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeExpression, SyntaxKind.InvocationExpression);
|
||||
context.RegisterOperationAction(AnalyzeOperation, OperationKind.Invocation);
|
||||
}
|
||||
|
||||
private void AnalyzeExpression(SyntaxNodeAnalysisContext context)
|
||||
private void AnalyzeOperation(OperationAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not InvocationExpressionSyntax node)
|
||||
if (context.Operation is not IInvocationOperation node)
|
||||
return;
|
||||
|
||||
if (context.SemanticModel.GetSymbolInfo(node.Expression).Symbol is not IMethodSymbol methodSymbol)
|
||||
return;
|
||||
var methodSymbol = node.TargetMethod;
|
||||
|
||||
// We need at least one type argument for context
|
||||
if (methodSymbol.TypeArguments.Length < 1)
|
||||
@@ -48,16 +45,12 @@ public sealed class ValidateMemberAnalyzer : DiagnosticAnalyzer
|
||||
if (methodSymbol.TypeArguments[0] is not INamedTypeSymbol targetType)
|
||||
return;
|
||||
|
||||
// We defer building this set until we need it later, so we don't have to build it for every single method invocation!
|
||||
ImmutableHashSet<ISymbol>? members = null;
|
||||
|
||||
// Check each parameter of the method
|
||||
foreach (var parameterContext in node.ArgumentList.Arguments)
|
||||
foreach (var op in node.Arguments)
|
||||
{
|
||||
|
||||
// Get the symbol for this parameter
|
||||
if (context.SemanticModel.GetOperation(parameterContext) is not IArgumentOperation op || op.Parameter is null)
|
||||
if (op.Parameter is null)
|
||||
continue;
|
||||
|
||||
var parameterSymbol = op.Parameter.OriginalDefinition;
|
||||
|
||||
// Make sure the parameter has the ValidateMember attribute
|
||||
@@ -66,15 +59,12 @@ public sealed class ValidateMemberAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
// Find the value passed for this parameter.
|
||||
// We use GetConstantValue to resolve compile-time values - i.e. the result of nameof()
|
||||
if (context.SemanticModel.GetConstantValue(parameterContext.Expression).Value is not string fieldName)
|
||||
if (op.Value.ConstantValue is not { HasValue: true, Value: string fieldName})
|
||||
continue;
|
||||
|
||||
// Get a set containing all the members of the target type and its ancestors
|
||||
members ??= targetType.GetBaseTypesAndThis().SelectMany(n => n.GetMembers()).ToImmutableHashSet(SymbolEqualityComparer.Default);
|
||||
|
||||
// Check each member of the target type to see if it matches our passed in value
|
||||
var found = false;
|
||||
foreach (var member in members)
|
||||
foreach (var member in targetType.GetMembers())
|
||||
{
|
||||
if (member.Name == fieldName)
|
||||
{
|
||||
@@ -84,12 +74,14 @@ public sealed class ValidateMemberAnalyzer : DiagnosticAnalyzer
|
||||
}
|
||||
// If we didn't find it, report the violation
|
||||
if (!found)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
ValidateMemberDescriptor,
|
||||
parameterContext.GetLocation(),
|
||||
op.Syntax.GetLocation(),
|
||||
fieldName,
|
||||
targetType.Name
|
||||
));
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,7 +439,8 @@ internal partial class Clyde
|
||||
|
||||
private static void WinThreadWinSetSize(CmdWinSetSize cmd)
|
||||
{
|
||||
SDL.SDL_SetWindowSize(cmd.Window, cmd.W, cmd.H);
|
||||
var density = SDL.SDL_GetWindowPixelDensity(cmd.Window);
|
||||
SDL.SDL_SetWindowSize(cmd.Window, (int)(cmd.W / density), (int)(cmd.H / density));
|
||||
}
|
||||
|
||||
private static void WinThreadWinSetVisible(CmdWinSetVisible cmd)
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
SetPositionFirst();
|
||||
|
||||
// Resize the window by our UIScale
|
||||
ClydeWindow.Size = new((int)(ClydeWindow.Size.X * UIScale), (int)(ClydeWindow.Size.Y * UIScale));
|
||||
ClydeWindow.Size = new((int)(parameters.Width * UIScale), (int)(parameters.Height * UIScale));
|
||||
return ClydeWindow;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,15 +14,6 @@ namespace Robust.Client.Utility
|
||||
{
|
||||
NativeLibrary.SetDllImportResolver(typeof(ClientDllMap).Assembly, (name, assembly, path) =>
|
||||
{
|
||||
if (name == "swnfd.dll")
|
||||
{
|
||||
#if LINUX || FREEBSD
|
||||
return NativeLibrary.Load("libswnfd.so", assembly, path);
|
||||
#elif MACOS
|
||||
return NativeLibrary.Load("libswnfd.dylib", assembly, path);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (name == "libEGL.dll")
|
||||
{
|
||||
#if LINUX || FREEBSD
|
||||
|
||||
@@ -279,6 +279,7 @@ namespace Robust.Server
|
||||
|
||||
// Load metrics really early so that we can profile startup times in the future maybe.
|
||||
_metricsManager.Initialize();
|
||||
_prof.Initialize();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -22,6 +23,7 @@ namespace Robust.Server.Console
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
|
||||
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
||||
[Dependency] private readonly ProfManager _prof = default!;
|
||||
|
||||
public ServerConsoleHost() : base(isServer: true) {}
|
||||
|
||||
@@ -108,7 +110,8 @@ namespace Robust.Server.Console
|
||||
if (args.Count == 0)
|
||||
return;
|
||||
|
||||
string? cmdName = args[0];
|
||||
var cmdName = args[0];
|
||||
using var _ = _prof.Group(cmdName);
|
||||
|
||||
if (RegisteredCommands.TryGetValue(cmdName, out var conCmd)) // command registered
|
||||
{
|
||||
|
||||
@@ -217,6 +217,10 @@ namespace Robust.Server.Placement
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes any existing entity.
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
private void HandleEntRemoveReq(MsgPlacement msg)
|
||||
{
|
||||
//TODO: Some form of admin check
|
||||
@@ -225,26 +229,61 @@ namespace Robust.Server.Placement
|
||||
if (!_entityManager.EntityExists(entity))
|
||||
return;
|
||||
|
||||
var placementEraseEvent = new PlacementEntityEvent(entity, _entityManager.GetComponent<TransformComponent>(entity).Coordinates, PlacementEventAction.Erase, msg.MsgChannel.UserId);
|
||||
var placementEraseEvent = new PlacementEntityEvent(entity,
|
||||
_entityManager.GetComponent<TransformComponent>(entity).Coordinates,
|
||||
PlacementEventAction.Erase,
|
||||
msg.MsgChannel.UserId);
|
||||
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
|
||||
_entityManager.DeleteEntity(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes almost any existing entity within a selection box.
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
private void HandleRectRemoveReq(MsgPlacement msg)
|
||||
{
|
||||
EntityCoordinates start = _entityManager.GetCoordinates(msg.NetCoordinates);
|
||||
Vector2 rectSize = msg.RectSize;
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(_xformSystem.GetMapId(start),
|
||||
new Box2(start.Position, start.Position + rectSize)))
|
||||
var start = _entityManager.GetCoordinates(msg.NetCoordinates);
|
||||
var rectSize = msg.RectSize;
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(_xformSystem.GetMapId(start), new Box2(start.Position, start.Position + rectSize)))
|
||||
{
|
||||
if (_entityManager.Deleted(entity) ||
|
||||
_entityManager.HasComponent<MapGridComponent>(entity) ||
|
||||
_entityManager.HasComponent<ActorComponent>(entity))
|
||||
{
|
||||
if (_entityManager.Deleted(entity)
|
||||
|| _entityManager.HasComponent<MapGridComponent>(entity)
|
||||
|| _entityManager.HasComponent<ActorComponent>(entity))
|
||||
continue;
|
||||
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(entity);
|
||||
var parent = xform.ParentUid;
|
||||
var isChildOfActor = false;
|
||||
|
||||
while (parent.IsValid())
|
||||
{
|
||||
if (_entityManager.HasComponent<ActorComponent>(parent))
|
||||
{
|
||||
isChildOfActor = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_entityManager.TryGetComponent<TransformComponent>(parent, out var parentXform))
|
||||
{
|
||||
parent = parentXform.ParentUid;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var placementEraseEvent = new PlacementEntityEvent(entity, _entityManager.GetComponent<TransformComponent>(entity).Coordinates, PlacementEventAction.Erase, msg.MsgChannel.UserId);
|
||||
if (isChildOfActor)
|
||||
continue;
|
||||
|
||||
var placementEraseEvent = new PlacementEntityEvent(entity,
|
||||
_entityManager.GetComponent<TransformComponent>(entity).Coordinates,
|
||||
PlacementEventAction.Erase,
|
||||
msg.MsgChannel.UserId);
|
||||
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
|
||||
_entityManager.DeleteEntity(entity);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.Configuration;
|
||||
@@ -88,7 +85,6 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
deps.Register<IDynamicTypeFactoryInternal, DynamicTypeFactory>();
|
||||
deps.RegisterInstance<IModLoader>(new Mock<IModLoader>().Object);
|
||||
deps.Register<IEntitySystemManager, EntitySystemManager>();
|
||||
deps.RegisterInstance<IEntityManager>(new Mock<IEntityManager>().Object);
|
||||
// WHEN WILL THE SUFFERING END
|
||||
deps.RegisterInstance<IReplayRecordingManager>(new Mock<IReplayRecordingManager>().Object);
|
||||
|
||||
@@ -104,6 +100,15 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
|
||||
deps.RegisterInstance<IReflectionManager>(reflectionMock.Object);
|
||||
|
||||
// Never
|
||||
var componentFactoryMock = new Mock<IComponentFactory>();
|
||||
componentFactoryMock.Setup(p => p.AllRegisteredTypes).Returns(Enumerable.Empty<Type>());
|
||||
deps.RegisterInstance<IComponentFactory>(componentFactoryMock.Object);
|
||||
|
||||
var entityManagerMock = new Mock<IEntityManager>();
|
||||
entityManagerMock.Setup(p => p.ComponentFactory).Returns(componentFactoryMock.Object);
|
||||
deps.RegisterInstance<IEntityManager>(entityManagerMock.Object);
|
||||
|
||||
deps.BuildGraph();
|
||||
|
||||
IoCManager.InitThread(deps, true);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.IoC.Exceptions;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
@@ -39,6 +38,14 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
[Dependency] public readonly ESystemDepA ESystemDepA = default!;
|
||||
}
|
||||
|
||||
internal sealed class ESystemDepAll : EntitySystem
|
||||
{
|
||||
[Dependency] public readonly ESystemDepA ESystemDepA = default!;
|
||||
[Dependency] public readonly IConfigurationManager Config = default!;
|
||||
[Dependency] public readonly EntityQuery<TransformComponent> TransformQuery = default!;
|
||||
[Dependency] public readonly EntityQuery<PhysicsComponent> PhysicsQuery = default!;
|
||||
}
|
||||
|
||||
/*
|
||||
ESystemBase (Abstract)
|
||||
- ESystemA
|
||||
@@ -58,6 +65,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
syssy.LoadExtraSystemType<ESystemC>();
|
||||
syssy.LoadExtraSystemType<ESystemDepA>();
|
||||
syssy.LoadExtraSystemType<ESystemDepB>();
|
||||
syssy.LoadExtraSystemType<ESystemDepAll>();
|
||||
syssy.Initialize(false);
|
||||
}
|
||||
|
||||
@@ -103,5 +111,16 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
Assert.That(sysB.ESystemDepA, Is.EqualTo(sysA));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DependencyInjectionTest()
|
||||
{
|
||||
var esm = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var sys = esm.GetEntitySystem<ESystemDepAll>();
|
||||
|
||||
Assert.That(sys.ESystemDepA, Is.Not.Null);
|
||||
Assert.That(sys.Config, Is.Not.Null);
|
||||
Assert.That(sys.TransformQuery, Is.Not.Default);
|
||||
Assert.That(sys.PhysicsQuery, Is.Not.Default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,9 @@ namespace Robust.Shared.ContentPack;
|
||||
internal sealed partial class AssemblyTypeChecker
|
||||
{
|
||||
// This part of the code tries to find the originator of bad sandbox references.
|
||||
|
||||
private void ReportBadReferences(PEReader peReader, MetadataReader reader, IEnumerable<EntityHandle> reference)
|
||||
private IEnumerable<(EntityHandle Referenced, MethodDefinitionHandle SourceMethod, int InstructionOffset)> FindReference(PEReader peReader, MetadataReader reader, params IEnumerable<EntityHandle> handles)
|
||||
{
|
||||
_sawmill.Info("Started search for originator of bad references...");
|
||||
|
||||
var refs = reference.ToHashSet();
|
||||
var refs = handles.ToHashSet();
|
||||
ExpandReferences(reader, refs);
|
||||
|
||||
foreach (var methodDefHandle in reader.MethodDefinitions)
|
||||
@@ -28,8 +25,6 @@ internal sealed partial class AssemblyTypeChecker
|
||||
if (methodDef.RelativeVirtualAddress == 0)
|
||||
continue;
|
||||
|
||||
var methodName = reader.GetString(methodDef.Name);
|
||||
|
||||
var body = peReader.GetMethodBody(methodDef.RelativeVirtualAddress);
|
||||
var bytes = body.GetILBytes()!;
|
||||
|
||||
@@ -41,9 +36,7 @@ internal sealed partial class AssemblyTypeChecker
|
||||
{
|
||||
if (refs.Overlaps(ExpandHandle(reader, handle)))
|
||||
{
|
||||
var type = GetTypeFromDefinition(reader, methodDef.GetDeclaringType());
|
||||
_sawmill.Error(
|
||||
$"Found reference to {DisplayHandle(reader, handle)} in method {type}.{methodName} at IL 0x{prefPosition:X4}");
|
||||
yield return (handle, methodDefHandle, prefPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +45,19 @@ internal sealed partial class AssemblyTypeChecker
|
||||
}
|
||||
}
|
||||
|
||||
private void ReportBadReferences(PEReader peReader, MetadataReader reader, IEnumerable<EntityHandle> reference)
|
||||
{
|
||||
foreach (var (referenced, method, ilOffset) in FindReference(peReader, reader, reference))
|
||||
{
|
||||
var methodDef = reader.GetMethodDefinition(method);
|
||||
var methodName = reader.GetString(methodDef.Name);
|
||||
|
||||
var type = GetTypeFromDefinition(reader, methodDef.GetDeclaringType());
|
||||
_sawmill.Error(
|
||||
$"Found reference to {DisplayHandle(reader, referenced)} in method {type}.{methodName} at IL 0x{ilOffset:X4}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string DisplayHandle(MetadataReader reader, EntityHandle handle)
|
||||
{
|
||||
switch (handle.Kind)
|
||||
|
||||
@@ -227,6 +227,8 @@ namespace Robust.Shared.ContentPack
|
||||
#if TOOLS
|
||||
if (!badRefs.IsEmpty)
|
||||
{
|
||||
_sawmill.Info("Started search for originator of bad references...");
|
||||
|
||||
ReportBadReferences(peReader, reader, badRefs);
|
||||
}
|
||||
#endif
|
||||
@@ -298,6 +300,9 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
verifyErrors = true;
|
||||
_sawmill.Error(msg);
|
||||
|
||||
if (!res.Method.IsNil)
|
||||
PrintCompilerGeneratedMethodUsage(peReader, reader, res.Method);
|
||||
}
|
||||
|
||||
_sawmill.Debug($"{name}: Verified IL in {sw.Elapsed.TotalMilliseconds}ms");
|
||||
@@ -310,6 +315,24 @@ namespace Robust.Shared.ContentPack
|
||||
return true;
|
||||
}
|
||||
|
||||
private void PrintCompilerGeneratedMethodUsage(
|
||||
PEReader peReader,
|
||||
MetadataReader reader,
|
||||
MethodDefinitionHandle method)
|
||||
{
|
||||
#if TOOLS
|
||||
var methodDef = reader.GetMethodDefinition(method);
|
||||
var type = GetTypeFromDefinition(reader, methodDef.GetDeclaringType());
|
||||
|
||||
if (!type.Name.Contains('<'))
|
||||
return;
|
||||
|
||||
_sawmill.Error("Hint: method is compiler-generated. Check for params collections and/or collection expressions:");
|
||||
|
||||
ReportBadReferences(peReader, reader, [method]);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string FormatMethodName(MetadataReader reader, MethodDefinition method)
|
||||
{
|
||||
var methodSig = method.DecodeSignature(new TypeProvider(), 0);
|
||||
|
||||
@@ -444,6 +444,7 @@ Types:
|
||||
LinkedList`1: { All: True }
|
||||
LinkedListNode`1: { All: True }
|
||||
List`1: { All: True }
|
||||
OrderedDictionary`2: { All: True }
|
||||
Queue`1: { All: True }
|
||||
ReferenceEqualityComparer: { All: True }
|
||||
SortedDictionary`2: { All: True }
|
||||
@@ -1462,6 +1463,7 @@ Types:
|
||||
- "void .ctor(char[], int, int)"
|
||||
- "void .ctor(System.ReadOnlySpan`1<char>)"
|
||||
- "void CopyTo(int, char[], int, int)"
|
||||
StringComparer: { All: True }
|
||||
StringComparison: { } # Enum
|
||||
StringSplitOptions: { } # Enum
|
||||
TimeOnly: { All: True }
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace Robust.Shared.EntitySerialization.Systems;
|
||||
public sealed partial class MapLoaderSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to load entities from a yaml file. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// Tries to load entities from a YAML file. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// </summary>
|
||||
public bool TryLoadGeneric(
|
||||
ResPath file,
|
||||
@@ -30,6 +30,7 @@ public sealed partial class MapLoaderSystem
|
||||
{
|
||||
grids = null;
|
||||
maps = null;
|
||||
|
||||
if (!TryLoadGeneric(file, out var data, options))
|
||||
return false;
|
||||
|
||||
@@ -39,33 +40,29 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load entities from a YAML file, taking in a raw byte stream.
|
||||
/// Tries to load entities from a YAML text stream. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="file">The file contents to load from.</param>
|
||||
/// <param name="fileName">
|
||||
/// The name of the file being loaded. This is used purely for logging/informational purposes.
|
||||
/// </param>
|
||||
/// <param name="result">The result of the load operation.</param>
|
||||
/// <param name="options">Options for the load operation.</param>
|
||||
/// <returns>True if the load succeeded, false otherwise.</returns>
|
||||
/// <seealso cref="M:Robust.Shared.EntitySerialization.Systems.MapLoaderSystem.TryLoadGeneric(Robust.Shared.Utility.ResPath,Robust.Shared.EntitySerialization.LoadResult@,System.Nullable{Robust.Shared.EntitySerialization.MapLoadOptions})"/>
|
||||
public bool TryLoadGeneric(
|
||||
Stream file,
|
||||
string fileName,
|
||||
[NotNullWhen(true)] out LoadResult? result,
|
||||
TextReader reader,
|
||||
string source,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapComponent>>? maps,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
MapLoadOptions? options = null)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (!TryReadFile(new StreamReader(file), out var data))
|
||||
grids = null;
|
||||
maps = null;
|
||||
if (!TryLoadGeneric(reader, source, out var data, options))
|
||||
return false;
|
||||
|
||||
return TryLoadGeneric(data, fileName, out result, options);
|
||||
maps = data.Maps;
|
||||
grids = data.Grids;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load entities from a yaml file. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// Tries to load entities from a YAML file. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to load.</param>
|
||||
/// <param name="result">Data class containing information about the loaded entities</param>
|
||||
@@ -74,15 +71,51 @@ public sealed partial class MapLoaderSystem
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (!TryReadFile(file, out var data))
|
||||
if (!TryReadFile(file.ToRootedPath(), out var data))
|
||||
return false;
|
||||
|
||||
return TryLoadGeneric(data, file.ToString(), out result, options);
|
||||
}
|
||||
|
||||
private bool TryLoadGeneric(
|
||||
/// <summary>
|
||||
/// Tries to load entities from a YAML text stream. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="reader">The text to load.</param>
|
||||
/// <param name="source">The name of the source, if any. This should be your file path (for example)</param>
|
||||
/// <param name="result">Data class containing information about the loaded entities</param>
|
||||
/// <param name="options">Optional Options for configuring loading behaviour.</param>
|
||||
public bool TryLoadGeneric(TextReader reader, string source, [NotNullWhen(true)] out LoadResult? result, MapLoadOptions? options = null)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (!TryReadFile(reader, out var data))
|
||||
return false;
|
||||
|
||||
return TryLoadGeneric(data, source, out result, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load entities from a YAML text stream. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream containing the text to load.</param>
|
||||
/// <param name="source">The name of the source, if any. This should be your file path (for example)</param>
|
||||
/// <param name="result">Data class containing information about the loaded entities</param>
|
||||
/// <param name="options">Optional Options for configuring loading behaviour.</param>
|
||||
public bool TryLoadGeneric(Stream stream, string source, [NotNullWhen(true)] out LoadResult? result, MapLoadOptions? options = null)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (!TryReadFile(new StreamReader(stream, leaveOpen: true), out var data))
|
||||
return false;
|
||||
|
||||
return TryLoadGeneric(data, source, out result, options);
|
||||
}
|
||||
|
||||
public bool TryLoadGeneric(
|
||||
MappingDataNode data,
|
||||
string fileName,
|
||||
string source,
|
||||
[NotNullWhen(true)] out LoadResult? result,
|
||||
MapLoadOptions? options = null)
|
||||
{
|
||||
@@ -118,7 +151,7 @@ public sealed partial class MapLoaderSystem
|
||||
|
||||
if (!deserializer.TryProcessData())
|
||||
{
|
||||
Log.Debug($"Failed to process entity data in {fileName}");
|
||||
Log.Debug($"Failed to process entity data in {source}");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -128,7 +161,7 @@ public sealed partial class MapLoaderSystem
|
||||
&& deserializer.Result.Category != FileCategory.Unknown)
|
||||
{
|
||||
// Did someone try to load a map file as a grid or vice versa?
|
||||
Log.Error($"Map {fileName} does not contain the expected data. Expected {expected} but got {deserializer.Result.Category}");
|
||||
Log.Error($"Map {source} does not contain the expected data. Expected {expected} but got {deserializer.Result.Category}");
|
||||
Delete(deserializer.Result);
|
||||
return false;
|
||||
}
|
||||
@@ -139,7 +172,7 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while creating entities for map {fileName}: {e}");
|
||||
Log.Error($"Caught exception while creating entities for map {source}: {e}");
|
||||
Delete(deserializer.Result);
|
||||
throw;
|
||||
}
|
||||
@@ -149,7 +182,7 @@ public sealed partial class MapLoaderSystem
|
||||
if (opts.ExpectedCategory is { } exp && exp != deserializer.Result.Category)
|
||||
{
|
||||
// Did someone try to load a map file as a grid or vice versa?
|
||||
Log.Error($"Map {fileName} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
|
||||
Log.Error($"Map {source} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
|
||||
Delete(deserializer.Result);
|
||||
return false;
|
||||
}
|
||||
@@ -184,12 +217,33 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a regular (non-map, non-grid) entity from a file.
|
||||
/// The loaded entity will initially be in null-space.
|
||||
/// If the file does not contain exactly one orphaned entity, this will return false and delete loaded entities.
|
||||
/// Tries to load a regular (non-map, non-grid) entity from a YAML file.
|
||||
/// The loaded entity will initially be in null-space.
|
||||
/// If the file does not contain exactly one orphaned entity, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadEntity(
|
||||
ResPath path,
|
||||
ResPath file,
|
||||
[NotNullWhen(true)] out Entity<TransformComponent>? entity,
|
||||
DeserializationOptions? options = null)
|
||||
{
|
||||
entity = null;
|
||||
if (!TryGetReader(file.ToRootedPath(), out var reader))
|
||||
return false;
|
||||
|
||||
using (reader)
|
||||
{
|
||||
return TryLoadEntity(reader, file.ToString(), out entity, options);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a regular (non-map, non-grid) entity from a YAML text stream.
|
||||
/// The loaded entity will initially be in null-space.
|
||||
/// If the file does not contain exactly one orphaned entity, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadEntity(
|
||||
TextReader reader,
|
||||
string source,
|
||||
[NotNullWhen(true)] out Entity<TransformComponent>? entity,
|
||||
DeserializationOptions? options = null)
|
||||
{
|
||||
@@ -200,7 +254,7 @@ public sealed partial class MapLoaderSystem
|
||||
};
|
||||
|
||||
entity = null;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
if (!TryLoadGeneric(reader, source, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (result.Orphans.Count == 1)
|
||||
@@ -215,12 +269,35 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a grid entity from a file and parent it to the given map.
|
||||
/// If the file does not contain exactly one grid, this will return false and delete loaded entities.
|
||||
/// Tries to load a grid entity from a YAML file and parent it to the given map.
|
||||
/// If the file does not contain exactly one grid, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadGrid(
|
||||
MapId map,
|
||||
ResPath path,
|
||||
ResPath file,
|
||||
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
grid = null;
|
||||
if (!TryGetReader(file.ToRootedPath(), out var reader))
|
||||
return false;
|
||||
|
||||
using (reader)
|
||||
{
|
||||
return TryLoadGrid(map, reader, file.ToString(), out grid, options, offset, rot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a grid entity from a YAML text stream and parent it to the given map.
|
||||
/// If the file does not contain exactly one grid, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadGrid(
|
||||
MapId map,
|
||||
TextReader reader,
|
||||
string source,
|
||||
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
@@ -236,7 +313,7 @@ public sealed partial class MapLoaderSystem
|
||||
};
|
||||
|
||||
grid = null;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
if (!TryLoadGeneric(reader, source, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (result.Grids.Count == 1)
|
||||
@@ -250,11 +327,35 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a grid entity from a file and parent it to a newly created map.
|
||||
/// Tries to load a grid entity from a YAML file and parent it to a newly created map.
|
||||
/// If the file does not contain exactly one grid, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadGrid(
|
||||
ResPath path,
|
||||
ResPath file,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
grid = null;
|
||||
map = null;
|
||||
if (!TryGetReader(file.ToRootedPath(), out var reader))
|
||||
return false;
|
||||
|
||||
using (reader)
|
||||
{
|
||||
return TryLoadGrid(reader, file.ToString(), out map, out grid, options, offset, rot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a grid entity from a YAML text stream and parent it to a newly created map.
|
||||
/// If the file does not contain exactly one grid, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadGrid(
|
||||
TextReader reader,
|
||||
string source,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
|
||||
DeserializationOptions? options = null,
|
||||
@@ -267,7 +368,7 @@ public sealed partial class MapLoaderSystem
|
||||
if (opts.PauseMaps)
|
||||
_mapSystem.SetPaused(mapUid, true);
|
||||
|
||||
if (!TryLoadGrid(mapId, path, out grid, options, offset, rot))
|
||||
if (!TryLoadGrid(mapId, reader, source, out grid, options, offset, rot))
|
||||
{
|
||||
Del(mapUid);
|
||||
map = null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -15,14 +16,41 @@ namespace Robust.Shared.EntitySerialization.Systems;
|
||||
public sealed partial class MapLoaderSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to load a file containing a single map.
|
||||
/// If the file does not contain exactly one map, this will return false and delete all loaded entities.
|
||||
/// Attempts to load a YAML file containing a single map.
|
||||
/// If the file does not contain exactly one map, this will return false and delete all loaded entities.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this will not automatically initialize the map, unless specified via the <see cref="DeserializationOptions"/>.
|
||||
/// Note that this will not automatically initialize the map, unless specified via the <see cref="DeserializationOptions"/>.
|
||||
/// </remarks>
|
||||
public bool TryLoadMap(
|
||||
ResPath path,
|
||||
ResPath file,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
map = null;
|
||||
grids = null;
|
||||
if (!TryGetReader(file.ToRootedPath(), out var reader))
|
||||
return false;
|
||||
|
||||
using (reader)
|
||||
{
|
||||
return TryLoadMap(reader, file.ToString(), out map, out grids, options, offset, rot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a YAML stream containing a single map.
|
||||
/// If the file does not contain exactly one map, this will return false and delete all loaded entities.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this will not automatically initialize the map, unless specified via the <see cref="DeserializationOptions"/>.
|
||||
/// </remarks>
|
||||
public bool TryLoadMap(
|
||||
TextReader reader,
|
||||
string source,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
@@ -39,7 +67,7 @@ public sealed partial class MapLoaderSystem
|
||||
|
||||
map = null;
|
||||
grids = null;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
if (!TryLoadGeneric(reader, source, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (result.Maps.Count == 1)
|
||||
@@ -54,17 +82,47 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a file containing a single map, assign it the given map id.
|
||||
/// Attempts to load a YAML file containing a single map, assign it the given map id.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If possible, it is better to use <see cref="TryLoadMap"/> which automatically assigns a <see cref="MapId"/>.
|
||||
/// If possible, it is better to use <see cref="TryLoadMap"/> which automatically assigns a <see cref="MapId"/>.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Note that this will not automatically initialize the map, unless specified via the <see cref="DeserializationOptions"/>.
|
||||
/// Note that this will not automatically initialize the map, unless specified via the <see cref="DeserializationOptions"/>.
|
||||
/// </remarks>
|
||||
public bool TryLoadMapWithId(
|
||||
MapId mapId,
|
||||
ResPath path,
|
||||
ResPath file,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
map = null;
|
||||
grids = null;
|
||||
if (!TryGetReader(file.ToRootedPath(), out var reader))
|
||||
return false;
|
||||
|
||||
using (reader)
|
||||
{
|
||||
return TryLoadMapWithId(mapId, reader, file.ToString(), out map, out grids, options, offset, rot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a YAML text stream containing a single map, assign it the given map id.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If possible, it is better to use <see cref="TryLoadMap"/> which automatically assigns a <see cref="MapId"/>.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Note that this will not automatically initialize the map, unless specified via the <see cref="DeserializationOptions"/>.
|
||||
/// </remarks>
|
||||
public bool TryLoadMapWithId(
|
||||
MapId mapId,
|
||||
TextReader reader,
|
||||
string source,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
@@ -86,7 +144,7 @@ public sealed partial class MapLoaderSystem
|
||||
throw new Exception($"Target map already exists");
|
||||
|
||||
opts.ForceMapId = mapId;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
if (!TryLoadGeneric(reader, source, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (!_mapSystem.TryGetMap(mapId, out var uid) || !TryComp(uid, out MapComponent? comp))
|
||||
@@ -98,12 +156,35 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a file containing a single map, and merge its children onto another map. After which the
|
||||
/// loaded map gets deleted.
|
||||
/// Attempts to load a YAML text stream containing a single map, and merge its children onto another map. After which
|
||||
/// the loaded map gets deleted.
|
||||
/// </summary>
|
||||
public bool TryMergeMap(
|
||||
MapId mapId,
|
||||
ResPath path,
|
||||
ResPath file,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
grids = null;
|
||||
if (!TryGetReader(file.ToRootedPath(), out var reader))
|
||||
return false;
|
||||
|
||||
using (reader)
|
||||
{
|
||||
return TryMergeMap(mapId, reader, file.ToString(), out grids, options, offset, rot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a YAML file containing a single map, and merge its children onto another map. After which
|
||||
/// the loaded map gets deleted.
|
||||
/// </summary>
|
||||
public bool TryMergeMap(
|
||||
MapId mapId,
|
||||
TextReader reader,
|
||||
string source,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
@@ -123,7 +204,7 @@ public sealed partial class MapLoaderSystem
|
||||
throw new Exception($"Target map {mapId} does not exist");
|
||||
|
||||
opts.MergeMap = mapId;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
if (!TryLoadGeneric(reader, source, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (!_mapSystem.TryGetMap(mapId, out var uid) || !TryComp(uid, out MapComponent? comp))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
@@ -59,10 +60,19 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a standard (non-grid, non-map) entity and all of its children and write the result to a
|
||||
/// yaml file.
|
||||
/// Serialize a standard (non-grid, non-map) entity and all of its children and write the result to a YAML file.
|
||||
/// </summary>
|
||||
public bool TrySaveEntity(EntityUid entity, ResPath path, SerializationOptions? options = null)
|
||||
public bool TrySaveEntity(EntityUid entity, ResPath target, SerializationOptions? options = null)
|
||||
{
|
||||
using var writer = GetWriterForPath(target);
|
||||
return TrySaveEntity(entity, writer, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a standard (non-grid, non-map) entity and all of its children and write the result to a YAML text
|
||||
/// stream.
|
||||
/// </summary>
|
||||
public bool TrySaveEntity(EntityUid entity, TextWriter target, SerializationOptions? options = null)
|
||||
{
|
||||
if (_mapQuery.HasComp(entity))
|
||||
{
|
||||
@@ -97,12 +107,12 @@ public sealed partial class MapLoaderSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
Write(target, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a map and all of its children and write the result to a yaml file.
|
||||
/// Serialize a map and all of its children and write the result to a YAML file.
|
||||
/// </summary>
|
||||
public bool TrySaveMap(MapId mapId, ResPath path, SerializationOptions? options = null)
|
||||
{
|
||||
@@ -114,9 +124,18 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a map and all of its children and write the result to a yaml file.
|
||||
/// Serialize a map and all of its children and write the result to a YAML file.
|
||||
/// </summary>
|
||||
public bool TrySaveMap(EntityUid map, ResPath path, SerializationOptions? options = null)
|
||||
public bool TrySaveMap(EntityUid map, ResPath target, SerializationOptions? options = null)
|
||||
{
|
||||
using var writer = GetWriterForPath(target);
|
||||
return TrySaveMap(map, writer, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a map and all of its children and write the result to a YAML text stream.
|
||||
/// </summary>
|
||||
public bool TrySaveMap(EntityUid map, TextWriter target, SerializationOptions? options = null)
|
||||
{
|
||||
if (!_mapQuery.HasComp(map))
|
||||
{
|
||||
@@ -145,14 +164,23 @@ public sealed partial class MapLoaderSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
Write(target, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a grid and all of its children and write the result to a yaml file.
|
||||
/// Serialize a grid and all of its children and write the result to a YAML file.
|
||||
/// </summary>
|
||||
public bool TrySaveGrid(EntityUid grid, ResPath path, SerializationOptions? options = null)
|
||||
public bool TrySaveGrid(EntityUid map, ResPath target, SerializationOptions? options = null)
|
||||
{
|
||||
using var writer = GetWriterForPath(target);
|
||||
return TrySaveGrid(map, writer, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a grid and all of its children and write the result to a YAML text stream.
|
||||
/// </summary>
|
||||
public bool TrySaveGrid(EntityUid grid, TextWriter target, SerializationOptions? options = null)
|
||||
{
|
||||
if (!_gridQuery.HasComp(grid))
|
||||
{
|
||||
@@ -187,32 +215,62 @@ public sealed partial class MapLoaderSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
Write(target, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an entities and all of their children to a yaml file.
|
||||
/// This makes no assumptions about the expected entity or resulting file category.
|
||||
/// If possible, use the map/grid specific variants instead.
|
||||
/// Serialize an entity and all of their children to a YAML file.
|
||||
/// This makes no assumptions about the expected entity or resulting file category.
|
||||
/// If possible, use the map/grid specific variants instead.
|
||||
/// </summary>
|
||||
public bool TrySaveGeneric(
|
||||
EntityUid uid,
|
||||
ResPath path,
|
||||
ResPath target,
|
||||
out FileCategory category,
|
||||
SerializationOptions? options = null)
|
||||
{
|
||||
return TrySaveGeneric([uid], path, out category, options);
|
||||
using var writer = GetWriterForPath(target);
|
||||
return TrySaveGeneric(uid, writer, out category, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize one or more entities and all of their children to a yaml file.
|
||||
/// This makes no assumptions about the expected entity or resulting file category.
|
||||
/// If possible, use the map/grid specific variants instead.
|
||||
/// Serialize an entity and all of their children to a YAML text stream.
|
||||
/// This makes no assumptions about the expected entity or resulting file category.
|
||||
/// If possible, use the map/grid specific variants instead.
|
||||
/// </summary>
|
||||
public bool TrySaveGeneric(
|
||||
EntityUid uid,
|
||||
TextWriter target,
|
||||
out FileCategory category,
|
||||
SerializationOptions? options = null)
|
||||
{
|
||||
return TrySaveGeneric([uid], target, out category, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize one or more entities and all of their children to a YAML file.
|
||||
/// This makes no assumptions about the expected entity or resulting file category.
|
||||
/// If possible, use the map/grid specific variants instead.
|
||||
/// </summary>
|
||||
public bool TrySaveGeneric(
|
||||
HashSet<EntityUid> uid,
|
||||
ResPath target,
|
||||
out FileCategory category,
|
||||
SerializationOptions? options = null)
|
||||
{
|
||||
using var writer = GetWriterForPath(target);
|
||||
return TrySaveGeneric(uid, writer, out category, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize one or more entities and all of their children to a YAML text stream.
|
||||
/// This makes no assumptions about the expected entity or resulting file category.
|
||||
/// If possible, use the map/grid specific variants instead.
|
||||
/// </summary>
|
||||
public bool TrySaveGeneric(
|
||||
HashSet<EntityUid> entities,
|
||||
ResPath path,
|
||||
TextWriter target,
|
||||
out FileCategory category,
|
||||
SerializationOptions? options = null)
|
||||
{
|
||||
@@ -233,10 +291,21 @@ public sealed partial class MapLoaderSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
Write(target, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TrySerializeAllEntities(out MappingDataNode, SerializationOptions?)"/>
|
||||
public bool TrySaveAllEntities(TextWriter target, SerializationOptions? options = null)
|
||||
{
|
||||
if (!TrySerializeAllEntities(out var data, options))
|
||||
return false;
|
||||
|
||||
Write(target, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="TrySerializeAllEntities(out MappingDataNode, SerializationOptions?)"/>
|
||||
public bool TrySaveAllEntities(ResPath path, SerializationOptions? options = null)
|
||||
{
|
||||
|
||||
@@ -42,17 +42,29 @@ public sealed partial class MapLoaderSystem : EntitySystem
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
}
|
||||
|
||||
private void Write(TextWriter target, MappingDataNode data)
|
||||
{
|
||||
var document = new YamlDocument(data.ToYaml());
|
||||
var stream = new YamlStream {document};
|
||||
stream.Save(new YamlMappingFix(new Emitter(target)), false);
|
||||
}
|
||||
|
||||
private StreamWriter GetWriterForPath(ResPath path)
|
||||
{
|
||||
Log.Info($"Saving serialized results to {path}");
|
||||
path = path.ToRootedPath();
|
||||
_resourceManager.UserData.CreateDir(path.Directory);
|
||||
return _resourceManager.UserData.OpenWriteText(path);
|
||||
}
|
||||
|
||||
private void Write(ResPath path, MappingDataNode data)
|
||||
{
|
||||
Log.Info($"Saving serialized results to {path}");
|
||||
path = path.ToRootedPath();
|
||||
var document = new YamlDocument(data.ToYaml());
|
||||
_resourceManager.UserData.CreateDir(path.Directory);
|
||||
using var writer = _resourceManager.UserData.OpenWriteText(path);
|
||||
{
|
||||
var stream = new YamlStream {document};
|
||||
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
|
||||
}
|
||||
|
||||
Write(writer, data);
|
||||
}
|
||||
|
||||
public bool TryReadFile(ResPath file, [NotNullWhen(true)] out MappingDataNode? data)
|
||||
|
||||
@@ -32,7 +32,8 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
protected BoundUserInterface(EntityUid owner, Enum uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
IoCManager.Resolve(ref EntMan);
|
||||
EntMan.EntitySysManager.DependencyCollection.InjectDependencies(this);
|
||||
UiSystem = EntMan.System<SharedUserInterfaceSystem>();
|
||||
|
||||
Owner = owner;
|
||||
|
||||
@@ -838,18 +838,16 @@ namespace Robust.Shared.GameObjects
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool EnsureComponent<T>(ref Entity<T?> entity) where T : IComponent, new()
|
||||
{
|
||||
if (entity.Comp != null)
|
||||
{
|
||||
// Check for deferred component removal.
|
||||
if (entity.Comp.LifeStage <= ComponentLifeStage.Running)
|
||||
{
|
||||
DebugTools.AssertOwner(entity, entity.Comp);
|
||||
return true;
|
||||
}
|
||||
if (entity.Comp == null)
|
||||
return EnsureComponent<T>(entity.Owner, out entity.Comp);
|
||||
|
||||
RemoveComponent(entity, entity.Comp);
|
||||
}
|
||||
DebugTools.AssertOwner(entity, entity.Comp);
|
||||
|
||||
// Check for deferred component removal.
|
||||
if (entity.Comp.LifeStage <= ComponentLifeStage.Running)
|
||||
return true;
|
||||
|
||||
RemoveComponent(entity, entity.Comp);
|
||||
entity.Comp = AddComponent<T>(entity);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -366,7 +366,8 @@ namespace Robust.Shared.GameObjects
|
||||
&& meta.EntityLifeStage < EntityLifeStage.Terminating)
|
||||
{
|
||||
coords = new EntityCoordinates(gridUid, _mapSystem.WorldToLocal(gridUid, grid, coordinates.Position));
|
||||
_xforms.SetCoordinates(newEntity, transform, coords, rotation, unanchor: false);
|
||||
var relativeRotation = rotation - _xforms.GetWorldRotation(gridUid);
|
||||
_xforms.SetCoordinates(newEntity, transform, coords, relativeRotation, unanchor: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Prometheus;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.IoC.Exceptions;
|
||||
@@ -192,6 +193,14 @@ namespace Robust.Shared.GameObjects
|
||||
_systemTypes.Remove(baseType);
|
||||
}
|
||||
|
||||
var queryMethod = typeof(EntityManager).GetMethod(nameof(EntityManager.GetEntityQuery), 1, [])!;
|
||||
SystemDependencyCollection.RegisterBaseGenericLazy(
|
||||
typeof(EntityQuery<>),
|
||||
(queryType, dep) => queryMethod
|
||||
.MakeGenericMethod(queryType.GetGenericArguments()[0])
|
||||
.Invoke(dep.Resolve<IEntityManager>(), null)!
|
||||
);
|
||||
|
||||
SystemDependencyCollection.BuildGraph();
|
||||
|
||||
foreach (var systemType in _systemTypes)
|
||||
@@ -314,9 +323,10 @@ namespace Robust.Shared.GameObjects
|
||||
try
|
||||
{
|
||||
#endif
|
||||
var sw = ProfSampler.StartNew();
|
||||
updReg.System.Update(frameTime);
|
||||
_profManager.WriteValue(updReg.System.GetType().Name, sw);
|
||||
using (_profManager.Value(updReg.System.GetType().Name))
|
||||
{
|
||||
updReg.System.Update(frameTime);
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -341,9 +351,10 @@ namespace Robust.Shared.GameObjects
|
||||
try
|
||||
{
|
||||
#endif
|
||||
var sw = ProfSampler.StartNew();
|
||||
system.FrameUpdate(frameTime);
|
||||
_profManager.WriteValue(system.GetType().Name, sw);
|
||||
using (_profManager.Value(system.GetType().Name))
|
||||
{
|
||||
system.FrameUpdate(frameTime);
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="prototypeName">Name of the <see cref="EntityPrototype"/> to spawn.</param>
|
||||
/// <param name="coordinates">Coordinates to place the newly spawned entity.</param>
|
||||
/// <param name="overrides">Overrides to add or remove components that differ from the prototype.</param>
|
||||
/// <param name="rotation">Local rotation to set the newly spawned entity to.</param>
|
||||
/// <param name="rotation">World rotation to set the newly spawned entity to.</param>
|
||||
/// <returns>A new uninitialized entity.</returns>
|
||||
/// <remarks>If there is a grid at the <paramref name="coordinates"/>, the entity will be parented to the grid.
|
||||
/// Otherwise, it will be parented to the map.</remarks>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -17,6 +18,11 @@ namespace Robust.Shared.IoC
|
||||
public delegate T DependencyFactoryDelegate<out T>()
|
||||
where T : class;
|
||||
|
||||
public delegate T DependencyFactoryBaseGenericLazyDelegate<out T>(
|
||||
Type type,
|
||||
IDependencyCollection services)
|
||||
where T : class;
|
||||
|
||||
/// <inheritdoc />
|
||||
internal sealed class DependencyCollection : IDependencyCollection
|
||||
{
|
||||
@@ -37,6 +43,12 @@ namespace Robust.Shared.IoC
|
||||
/// </remarks>
|
||||
private FrozenDictionary<Type, object> _services = FrozenDictionary<Type, object>.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary that maps the types passed to <see cref="Resolve{T}()"/> to their implementation
|
||||
/// for any types registered through <see cref="RegisterBaseGenericLazy"/>.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<Type, object> _lazyServices = new();
|
||||
|
||||
// Start fields used for building new services.
|
||||
|
||||
/// <summary>
|
||||
@@ -48,6 +60,8 @@ namespace Robust.Shared.IoC
|
||||
private readonly Dictionary<Type, DependencyFactoryDelegateInternal<object>> _resolveFactories = new();
|
||||
private readonly Queue<Type> _pendingResolves = new();
|
||||
|
||||
private readonly ConcurrentDictionary<Type, DependencyFactoryBaseGenericLazyDelegate<object>> _baseGenericLazyFactories = new();
|
||||
|
||||
private readonly object _serviceBuildLock = new();
|
||||
|
||||
// End fields for building new services.
|
||||
@@ -79,8 +93,8 @@ namespace Robust.Shared.IoC
|
||||
public IEnumerable<Type> GetRegisteredTypes()
|
||||
{
|
||||
return _parentCollection != null
|
||||
? _services.Keys.Concat(_parentCollection.GetRegisteredTypes())
|
||||
: _services.Keys;
|
||||
? _services.Keys.Concat(_lazyServices.Keys).Concat(_parentCollection.GetRegisteredTypes())
|
||||
: _services.Keys.Concat(_lazyServices.Keys);
|
||||
}
|
||||
|
||||
public Type[] GetCachedInjectorTypes()
|
||||
@@ -116,10 +130,7 @@ namespace Robust.Shared.IoC
|
||||
FrozenDictionary<Type, object> services,
|
||||
[MaybeNullWhen(false)] out object instance)
|
||||
{
|
||||
if (!services.TryGetValue(objectType, out instance))
|
||||
return _parentCollection is not null && _parentCollection.TryResolveType(objectType, out instance);
|
||||
|
||||
return true;
|
||||
return TryResolveType(objectType, (IReadOnlyDictionary<Type, object>) services, out instance);
|
||||
}
|
||||
|
||||
private bool TryResolveType(
|
||||
@@ -128,7 +139,16 @@ namespace Robust.Shared.IoC
|
||||
[MaybeNullWhen(false)] out object instance)
|
||||
{
|
||||
if (!services.TryGetValue(objectType, out instance))
|
||||
{
|
||||
if (objectType.IsGenericType &&
|
||||
_baseGenericLazyFactories.TryGetValue(objectType.GetGenericTypeDefinition(), out var factory))
|
||||
{
|
||||
instance = _lazyServices.GetOrAdd(objectType, type => factory(type, this));
|
||||
return true;
|
||||
}
|
||||
|
||||
return _parentCollection is not null && _parentCollection.TryResolveType(objectType, out instance);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -267,7 +287,7 @@ namespace Robust.Shared.IoC
|
||||
_pendingResolves.Enqueue(interfaceType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void CheckRegisterInterface(Type interfaceType, Type implementationType, bool overwrite)
|
||||
{
|
||||
lock (_serviceBuildLock)
|
||||
@@ -312,15 +332,24 @@ namespace Robust.Shared.IoC
|
||||
Register(type, implementation.GetType(), () => implementation, overwrite);
|
||||
}
|
||||
|
||||
public void RegisterBaseGenericLazy(Type interfaceType, DependencyFactoryBaseGenericLazyDelegate<object> factory)
|
||||
{
|
||||
lock (_serviceBuildLock)
|
||||
{
|
||||
_baseGenericLazyFactories[interfaceType] = factory;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var service in _services.Values.OfType<IDisposable>().Distinct())
|
||||
foreach (var service in _services.Values.Concat(_lazyServices.Values).OfType<IDisposable>().Distinct())
|
||||
{
|
||||
service.Dispose();
|
||||
}
|
||||
|
||||
_services = FrozenDictionary<Type, object>.Empty;
|
||||
_lazyServices.Clear();
|
||||
|
||||
lock (_serviceBuildLock)
|
||||
{
|
||||
|
||||
@@ -132,6 +132,15 @@ namespace Robust.Shared.IoC
|
||||
/// </param>
|
||||
void RegisterInstance(Type type, object implementation, bool overwrite = false);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a callback to be called when attempting to resolve an unresolved type that matches the specified
|
||||
/// base generic type, making it accessible to <see cref="IDependencyCollection.Resolve{T}"/>.
|
||||
/// This instance will only be created the first time that it is attempted to be resolved.
|
||||
/// </summary>
|
||||
/// <param name="genericType">The base generic type of the type that will be resolvable.</param>
|
||||
/// <param name="factory">The callback to call to get an instance of the implementation for that generic type.</param>
|
||||
void RegisterBaseGenericLazy(Type genericType, DependencyFactoryBaseGenericLazyDelegate<object> factory);
|
||||
|
||||
/// <summary>
|
||||
/// Clear all services and types.
|
||||
/// Use this between unit tests and on program shutdown.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -12,10 +13,12 @@ namespace Robust.Shared.Map
|
||||
{
|
||||
Tile GetVariantTile(string name, IRobustRandom random);
|
||||
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
Tile GetVariantTile(string name, System.Random random);
|
||||
|
||||
Tile GetVariantTile(ITileDefinition tileDef, IRobustRandom random);
|
||||
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
Tile GetVariantTile(ITileDefinition tileDef, System.Random random);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -132,6 +132,14 @@ public sealed class ProfManager
|
||||
/// <returns>The absolute position of the written log entry.</returns>
|
||||
public long WriteValue(string text, long int64) => WriteValue(text, ProfData.Int64(int64));
|
||||
|
||||
/// <summary>
|
||||
/// Make a guarded value for usage with using blocks.
|
||||
/// </summary>
|
||||
public ValueGuard Value(string text)
|
||||
{
|
||||
return new ValueGuard(this, text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the start of a new log group.
|
||||
/// </summary>
|
||||
@@ -250,4 +258,23 @@ public sealed class ProfManager
|
||||
_mgr.WriteGroupEnd(_startIndex, _groupName, _sampler);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ValueGuard : IDisposable
|
||||
{
|
||||
private readonly ProfManager _mgr;
|
||||
private readonly string _text;
|
||||
private readonly ProfSampler _sampler;
|
||||
|
||||
public ValueGuard(ProfManager mgr, string text)
|
||||
{
|
||||
_mgr = mgr;
|
||||
_text = text;
|
||||
_sampler = ProfSampler.StartNew();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mgr.WriteValue(_text, _sampler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,14 @@ public partial class PrototypeManager
|
||||
private bool TryGetIds(FieldInfo field, [NotNullWhen(true)] out string[]? ids)
|
||||
{
|
||||
ids = null;
|
||||
var value = field.GetValue(null);
|
||||
if (field.DeclaringType?.IsGenericTypeDefinition == true)
|
||||
{
|
||||
// field's class has generic parameters, rethrow to say what the field is because
|
||||
// c# is a great language and doesn't tell you anything in its exception in GetValue
|
||||
throw new InvalidOperationException($"Field {field.FieldType} {field.Name} cannot be a static field inside a generic class");
|
||||
}
|
||||
|
||||
object? value = field.GetValue(null);
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -176,8 +176,10 @@ public interface IRobustRandom
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static class RandomHelpers
|
||||
{
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static void Shuffle<T>(this System.Random random, IList<T> list)
|
||||
{
|
||||
var n = list.Count;
|
||||
@@ -189,24 +191,28 @@ public static class RandomHelpers
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static bool Prob(this System.Random random, double chance)
|
||||
{
|
||||
return random.NextDouble() < chance;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static byte NextByte(this System.Random random, byte maxValue)
|
||||
{
|
||||
return NextByte(random, 0, maxValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static byte NextByte(this System.Random random)
|
||||
{
|
||||
return NextByte(random, byte.MaxValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static byte NextByte(this System.Random random, byte minValue, byte maxValue)
|
||||
{
|
||||
return (byte)random.Next(minValue, maxValue);
|
||||
|
||||
@@ -19,7 +19,13 @@ public static class RandomExtensions
|
||||
/// <param name="σ">The standard deviation of the normal distribution.</param>
|
||||
public static double NextGaussian(this IRobustRandom random, double μ = 0, double σ = 1)
|
||||
{
|
||||
return random.GetRandom().NextGaussian(μ, σ);
|
||||
// https://stackoverflow.com/a/218600
|
||||
var α = random.NextDouble();
|
||||
var β = random.NextDouble();
|
||||
|
||||
var randStdNormal = Math.Sqrt(-2.0 * Math.Log(α)) * Math.Sin(2.0 * Math.PI * β);
|
||||
|
||||
return μ + σ * randStdNormal;
|
||||
}
|
||||
|
||||
/// <summary>Picks a random element from a collection.</summary>
|
||||
@@ -78,6 +84,7 @@ public static class RandomExtensions
|
||||
/// Picks a random element from a set and returns it.
|
||||
/// This is O(n) as it has to iterate the collection until the target index.
|
||||
/// </summary>
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static T Pick<T>(this System.Random random, ICollection<T> collection)
|
||||
{
|
||||
var index = random.Next(collection.Count);
|
||||
@@ -97,6 +104,7 @@ public static class RandomExtensions
|
||||
/// Picks a random from a collection then removes it and returns it.
|
||||
/// This is O(n) as it has to iterate the collection until the target index.
|
||||
/// </summary>
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static T PickAndTake<T>(this System.Random random, ICollection<T> set)
|
||||
{
|
||||
var tile = Pick(random, set);
|
||||
@@ -110,6 +118,7 @@ public static class RandomExtensions
|
||||
/// <param name="random">The random object to generate the number from.</param>
|
||||
/// <param name="μ">The average or "center" of the normal distribution.</param>
|
||||
/// <param name="σ">The standard deviation of the normal distribution.</param>
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static double NextGaussian(this System.Random random, double μ = 0, double σ = 1)
|
||||
{
|
||||
// https://stackoverflow.com/a/218600
|
||||
@@ -121,17 +130,21 @@ public static class RandomExtensions
|
||||
return μ + σ * randStdNormal;
|
||||
}
|
||||
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static Angle NextAngle(this System.Random random) => NextFloat(random) * MathF.Tau;
|
||||
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static Angle NextAngle(this System.Random random, Angle minAngle, Angle maxAngle)
|
||||
{
|
||||
DebugTools.Assert(minAngle < maxAngle);
|
||||
return minAngle + (maxAngle - minAngle) * random.NextDouble();
|
||||
}
|
||||
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static Vector2 NextPolarVector2(this System.Random random, float minMagnitude, float maxMagnitude)
|
||||
=> random.NextAngle().RotateVec(new Vector2(random.NextFloat(minMagnitude, maxMagnitude), 0));
|
||||
|
||||
[Obsolete("Exists as a method directly on IRobustRandom.")]
|
||||
public static float NextFloat(this IRobustRandom random)
|
||||
{
|
||||
// This is pretty much the CoreFX implementation.
|
||||
@@ -140,11 +153,13 @@ public static class RandomExtensions
|
||||
return random.Next() * 4.6566128752458E-10f;
|
||||
}
|
||||
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static float NextFloat(this System.Random random)
|
||||
{
|
||||
return random.Next() * 4.6566128752458E-10f;
|
||||
}
|
||||
|
||||
[Obsolete("Always use RobustRandom/IRobustRandom, System.Random does not provide any extra functionality.")]
|
||||
public static float NextFloat(this System.Random random, float minValue, float maxValue)
|
||||
=> random.NextFloat() * (maxValue - minValue) + minValue;
|
||||
|
||||
|
||||
@@ -4,15 +4,23 @@ using Robust.Shared.Utility;
|
||||
namespace Robust.Shared.Random;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for <see cref="Random"/>.
|
||||
/// Provides random numbers, can be constructed in user code or used as a dependency in the form of
|
||||
/// <see cref="IRobustRandom"/>. Methods that take RNG as input should take an IRobustRandom instead.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should not contain any logic, not directly related to calling specific methods of <see cref="Random"/>.
|
||||
/// To write additional logic, attached to random roll, please create interface-implemented methods on <see cref="IRobustRandom"/>
|
||||
/// or add it to <see cref="RandomExtensions"/>.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var myRng = new RobustRandom();
|
||||
/// // Optionally, seed your RNG. By default, the RNG is seeded randomly.
|
||||
/// myRng.SetSeed(17);
|
||||
/// <br/>
|
||||
/// var fairDiceRoll = myRng.Next(1, 6); // Will be 4 with this seed.
|
||||
/// </code>
|
||||
/// </example>
|
||||
public sealed class RobustRandom : IRobustRandom
|
||||
{
|
||||
// This should not contain any logic, not directly related to calling specific methods of <see cref="Random"/>.
|
||||
// To write additional logic, attached to random roll, please create interface-implemented methods on <see cref="IRobustRandom"/>
|
||||
// or add it to <see cref="RandomExtensions"/>.
|
||||
private System.Random _random = new();
|
||||
|
||||
public System.Random GetRandom() => _random;
|
||||
@@ -24,7 +32,10 @@ public sealed class RobustRandom : IRobustRandom
|
||||
|
||||
public float NextFloat()
|
||||
{
|
||||
return _random.NextFloat();
|
||||
// This is pretty much the CoreFX implementation.
|
||||
// So credits to that.
|
||||
// Except using float instead of double.
|
||||
return Next() * 4.6566128752458E-10f;
|
||||
}
|
||||
|
||||
public int Next()
|
||||
|
||||
Reference in New Issue
Block a user