Compare commits

..

1 Commits

Author SHA1 Message Date
Pieter-Jan Briers
c65f141790 Revert "make SharedAudioSystem.Stop not return early when the current tick ha…"
This reverts commit c41d63be27.
2026-01-09 22:07:34 +01:00
126 changed files with 6070 additions and 3971 deletions

View File

@@ -59,7 +59,7 @@
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.2.0" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.Sdl" Version="1.1.1" />
<PackageVersion Include="SpaceWizards.Sdl" Version="1.0.0" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.1.0" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.3.0" />
<PackageVersion Include="SpaceWizards.Fontconfig.Interop" Version="1.0.0" />

View File

@@ -1,4 +1 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<Project>

View File

@@ -11,7 +11,7 @@
<Target Name="_RTCheckForDirectReferences" BeforeTargets="BeforeResolveReferences"
Condition="'$(AllowDirectRobustReferences)' != 'true'">
<Error File="%(ProjectReference.DefiningProjectFullPath)"
Text="Direct reference to %(Filename) is not allowed. Use RobustToolbox/Imports/*.props instead (e.g., Shared.props, Client.props, Server.props)"
Text="Content may not reference %(Filename) directly"
Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(Identity)', 'RobustToolbox')) and !$([System.Text.RegularExpressions.Regex]::IsMatch('%(DefiningProjectFullPath)', '([Mm]icrosoft|RobustToolbox)'))" />
</Target>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,8 @@
#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;
@@ -27,15 +29,16 @@ public sealed class ValidateMemberAnalyzer : DiagnosticAnalyzer
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterOperationAction(AnalyzeOperation, OperationKind.Invocation);
context.RegisterSyntaxNodeAction(AnalyzeExpression, SyntaxKind.InvocationExpression);
}
private void AnalyzeOperation(OperationAnalysisContext context)
private void AnalyzeExpression(SyntaxNodeAnalysisContext context)
{
if (context.Operation is not IInvocationOperation node)
if (context.Node is not InvocationExpressionSyntax node)
return;
var methodSymbol = node.TargetMethod;
if (context.SemanticModel.GetSymbolInfo(node.Expression).Symbol is not IMethodSymbol methodSymbol)
return;
// We need at least one type argument for context
if (methodSymbol.TypeArguments.Length < 1)
@@ -45,12 +48,16 @@ public sealed class ValidateMemberAnalyzer : DiagnosticAnalyzer
if (methodSymbol.TypeArguments[0] is not INamedTypeSymbol targetType)
return;
// Check each parameter of the method
foreach (var op in node.Arguments)
{
if (op.Parameter is null)
continue;
// 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)
{
// Get the symbol for this parameter
if (context.SemanticModel.GetOperation(parameterContext) is not IArgumentOperation op || op.Parameter is null)
continue;
var parameterSymbol = op.Parameter.OriginalDefinition;
// Make sure the parameter has the ValidateMember attribute
@@ -59,12 +66,15 @@ 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 (op.Value.ConstantValue is not { HasValue: true, Value: string fieldName})
if (context.SemanticModel.GetConstantValue(parameterContext.Expression).Value is not 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 targetType.GetMembers())
foreach (var member in members)
{
if (member.Name == fieldName)
{
@@ -74,14 +84,12 @@ public sealed class ValidateMemberAnalyzer : DiagnosticAnalyzer
}
// If we didn't find it, report the violation
if (!found)
{
context.ReportDiagnostic(Diagnostic.Create(
ValidateMemberDescriptor,
op.Syntax.GetLocation(),
parameterContext.GetLocation(),
fieldName,
targetType.Name
));
}
));
}
}
}

View File

@@ -16,11 +16,7 @@
<ProjectReference Include="..\Robust.UnitTesting\Robust.UnitTesting.csproj" />
</ItemGroup>
<ItemGroup>
<!-- BenchmarkDotNet autogenerates files that attempt to reference BenchmarkDotNet through Robust.Benchmarks.
By default the RT project privates these files, so we have to explicitly state that these files should be made available
to the BenchmarkDotNet project so it can build the runner that executes the benchmark. -->
<PackageReference Include="BenchmarkDotNet" PrivateAssets="none" />
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="YamlDotNet" />
<PackageReference Include="prometheus-net" />

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using JetBrains.Annotations;
using OpenTK.Audio.OpenAL;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
@@ -16,6 +17,7 @@ using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
@@ -180,16 +182,14 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
// If playback position changed then update it.
var totalLen = GetAudioLengthImpl(entity.Comp.FileName).TotalSeconds;
var position = CalculateAudioPosition(entity, (float) totalLen);
var position = (float) ((entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart).TotalSeconds;
var currentPosition = entity.Comp.Source.PlaybackPosition;
var diff = Math.Abs(position - currentPosition);
// Don't try to set the audio too far ahead.
if (!string.IsNullOrEmpty(entity.Comp.FileName))
{
if (position > totalLen - _audioEndBuffer)
if (position > GetAudioLengthImpl(entity.Comp.FileName).TotalSeconds - _audioEndBuffer)
{
entity.Comp.StopPlaying();
return;
@@ -251,7 +251,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
length ??= GetAudioLength(component.FileName);
// If audio came into range then start playback at the correct position.
var offset = CalculateAudioPosition(entity, (float) length.Value.TotalSeconds);
var offset = ((entity.Comp.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
if (TryAudioLimit(component.FileName))
{
@@ -289,7 +289,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
if (offset > 0)
{
component.PlaybackPosition = offset;
component.PlaybackPosition = (float) offset;
}
}
@@ -755,7 +755,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
var comp = entity.Comp;
var source = comp.Source;
var offset = CalculateAudioPosition(entity, (float)stream.Length.TotalSeconds, audioP.PlayOffsetSeconds);
// TODO clamp the offset inside of SetPlaybackPosition() itself.
var offset = audioP.PlayOffsetSeconds;
var maxOffset = Math.Max((float) stream.Length.TotalSeconds - 0.01f, 0f);
offset = Math.Clamp(offset, 0f, maxOffset);
source.PlaybackPosition = offset;
source.StartPlaying();

View File

@@ -13,7 +13,6 @@ using Robust.Client.HWId;
using Robust.Client.Input;
using Robust.Client.Localization;
using Robust.Client.Map;
using Robust.Client.Network.Transfer;
using Robust.Client.Placement;
using Robust.Client.Player;
using Robust.Client.Profiling;
@@ -42,7 +41,6 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Physics;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
@@ -176,8 +174,6 @@ namespace Robust.Client
deps.Register<IXamlProxyHelper, XamlProxyHelper>();
deps.Register<MarkupTagManager>();
deps.Register<IHWId, BasicHWId>();
deps.Register<ITransferManager, ClientTransferManager>();
deps.Register<ClientTransferTestManager>();
}
}
}

View File

@@ -1,9 +1,6 @@
#if TOOLS
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Sqlite;
using Robust.Client.Utility;
using Robust.Shared.Console;
@@ -23,7 +20,15 @@ namespace Robust.Client.Console.Commands
{
var wantName = args.Length > 0 ? args[0] : null;
using var con = GetDb();
var basePath = UserDataDir.GetRootUserDataDir(_gameController);
var launcherDirName = Environment.GetEnvironmentVariable("SS14_LAUNCHER_APPDATA_NAME") ?? "launcher";
var dbPath = Path.Combine(basePath, launcherDirName, "settings.db");
#if USE_SYSTEM_SQLITE
SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());
#endif
using var con = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly");
con.Open();
using var cmd = con.CreateCommand();
cmd.CommandText = "SELECT UserId, UserName, Token FROM Login WHERE Expires > datetime('NOW')";
@@ -52,51 +57,6 @@ namespace Robust.Client.Console.Commands
shell.WriteLine($"Logged into account {userName}");
}
public override async ValueTask<CompletionResult> GetCompletionAsync(
IConsoleShell shell,
string[] args,
string argStr,
CancellationToken cancel)
{
if (args.Length != 1)
return CompletionResult.Empty;
return await Task.Run(() =>
{
using var con = GetDb();
using var cmd = con.CreateCommand();
cmd.CommandText = "SELECT UserName FROM Login WHERE Expires > datetime('NOW')";
var options = new List<CompletionOption>();
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var name = reader.GetString(0);
options.Add(new CompletionOption(name));
}
return CompletionResult.FromOptions(options);
},
cancel);
}
private SqliteConnection GetDb()
{
var basePath = UserDataDir.GetRootUserDataDir(_gameController);
var launcherDirName = Environment.GetEnvironmentVariable("SS14_LAUNCHER_APPDATA_NAME") ?? "launcher";
var dbPath = Path.Combine(basePath, launcherDirName, "settings.db");
#if USE_SYSTEM_SQLITE
SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());
#endif
var con = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly");
con.Open();
return con;
}
}
}

View File

@@ -11,7 +11,6 @@ using Robust.Client.GameObjects;
using Robust.Client.GameStates;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Network.Transfer;
using Robust.Client.Placement;
using Robust.Client.Replays.Loading;
using Robust.Client.Replays.Playback;
@@ -36,7 +35,6 @@ using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
@@ -100,8 +98,6 @@ namespace Robust.Client
[Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] private readonly ISystemFontManagerInternal _systemFontManager = default!;
[Dependency] private readonly LoadingScreenManager _loadscr = default!;
[Dependency] private readonly ITransferManager _transfer = default!;
[Dependency] private readonly ClientTransferTestManager _transferTest = default!;
private IWebViewManagerHook? _webViewHook;
@@ -205,14 +201,7 @@ namespace Robust.Client
_logManager.GetSawmill("res"));
_loadscr.LoadingStep(_resourceCache.PreloadTextures, "Texture preload");
_loadscr.LoadingStep(() =>
{
_networkManager.Initialize(false);
_transfer.Initialize();
_transferTest.Initialize();
},
_networkManager);
_loadscr.LoadingStep(() => { _networkManager.Initialize(false); }, _networkManager);
_loadscr.LoadingStep(_configurationManager.SetupNetworking, _configurationManager);
_loadscr.LoadingStep(_serializer.Initialize, _serializer);
_loadscr.LoadingStep(_inputManager.Initialize, _inputManager);
@@ -696,11 +685,6 @@ namespace Robust.Client
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
}
using (_prof.Group("Transfer"))
{
_transfer.FrameUpdate();
}
_audio.FlushALDisposeQueues();
}

View File

@@ -1336,10 +1336,11 @@ namespace Robust.Client.GameObjects
public Layer(Layer toClone, SpriteComponent parent) : this(parent)
{
if (toClone.Shader != null)
{
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
UnShaded = toClone.UnShaded;
ShaderPrototype = toClone.ShaderPrototype;
UnShaded = toClone.UnShaded;
ShaderPrototype = toClone.ShaderPrototype;
}
Texture = toClone.Texture;
RSI = toClone.RSI;
State = toClone.State;

View File

@@ -14,9 +14,9 @@ namespace Robust.Client.GameObjects
private EntityQuery<AnimationPlayerComponent> _playerQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
#if DEBUG
#pragma warning disable CS0414
[Dependency] private readonly IComponentFactory _compFact = default!;
#endif
#pragma warning restore CS0414
public override void Initialize()
{
@@ -154,9 +154,6 @@ namespace Robust.Client.GameObjects
}
ent.Comp.PlayingAnimations.Add(key, playback);
var startedEvent = new AnimationStartedEvent(ent.Owner, ent.Comp, key);
RaiseLocalEvent(ent.Owner, startedEvent, true);
}
public bool HasRunningAnimation(EntityUid uid, string key)
@@ -202,34 +199,6 @@ namespace Robust.Client.GameObjects
}
}
/// <summary>
/// Raised whenever an animation started playing.
/// </summary>
public sealed class AnimationStartedEvent : EntityEventArgs
{
/// <summary>
/// The entity associated with the event.
/// </summary>
public EntityUid Uid { get; init; }
/// <summary>
/// The animation player component associated with the entity this event was raised on.
/// </summary>
public AnimationPlayerComponent AnimationPlayer { get; init; }
/// <summary>
/// The key associated with the animation that was started.
/// </summary>
public string Key { get; init; } = string.Empty;
internal AnimationStartedEvent(EntityUid uid, AnimationPlayerComponent animationPlayer, string key)
{
Uid = uid;
AnimationPlayer = animationPlayer;
Key = key;
}
}
/// <summary>
/// Raised whenever an animation stops, either due to running its course or being stopped manually.
/// </summary>

View File

@@ -493,9 +493,6 @@ namespace Robust.Client.Graphics.Clyde
{
if (viewport.Eye == null || viewport.Eye.Position.MapId == MapId.Nullspace)
{
if (viewport.ClearWhenMissingEye)
RenderInRenderTarget(viewport.RenderTarget, () => { }, viewport.ClearColor);
return;
}

View File

@@ -138,7 +138,6 @@ namespace Robust.Client.Graphics.Clyde
public Vector2i Size { get; set; }
public event Action<ClearCachedViewportResourcesEvent>? ClearCachedResources;
public Color? ClearColor { get; set; } = Color.Black;
public bool ClearWhenMissingEye { get; set; }
public Vector2 RenderScale { get; set; } = Vector2.One;
public bool AutomaticRender { get; set; }
public long Id { get; }

View File

@@ -601,11 +601,6 @@ namespace Robust.Client.Graphics.Clyde
_clyde._windowing!.TextInputStop(Reg);
}
public void SetWindowProgress(WindowProgressState state, float value)
{
_clyde._windowing!.WindowSetProgress(Reg, state, value);
}
public nint? WindowsHWnd => _clyde._windowing!.WindowGetWin32Window(Reg);
}

View File

@@ -91,8 +91,6 @@ namespace Robust.Client.Graphics.Clyde
private bool _earlyGLInit;
private bool _threadWindowApi;
public bool IsInitialized { get; private set; }
public Clyde()
{
_currentBoundRenderTarget = default!;
@@ -185,8 +183,6 @@ namespace Robust.Client.Graphics.Clyde
if (!InitMainWindowAndRenderer())
return false;
IsInitialized = true;
return true;
}

View File

@@ -27,7 +27,6 @@ namespace Robust.Client.Graphics.Clyde
internal sealed class ClydeHeadless : IClydeInternal
{
// Would it make sense to report a fake resolution like 720p here so code doesn't break? idk.
public bool IsInitialized { get; private set; }
public IClydeWindow MainWindow { get; }
public Vector2i ScreenSize => (1280, 720);
public IEnumerable<IClydeWindow> AllWindows => _windows;
@@ -173,7 +172,6 @@ namespace Robust.Client.Graphics.Clyde
public bool InitializePostWindowing()
{
IsInitialized = true;
return true;
}
@@ -526,7 +524,6 @@ namespace Robust.Client.Graphics.Clyde
public Vector2i Size { get; }
public event Action<ClearCachedViewportResourcesEvent>? ClearCachedResources;
public Color? ClearColor { get; set; } = Color.Black;
public bool ClearWhenMissingEye { get; set; }
public Vector2 RenderScale { get; set; }
public bool AutomaticRender { get; set; }
@@ -585,11 +582,6 @@ namespace Robust.Client.Graphics.Clyde
public event Action<WindowDestroyedEventArgs>? Destroyed;
public event Action<WindowResizedEventArgs>? Resized { add { } remove { } }
public void SetWindowProgress(WindowProgressState state, float value)
{
// Nop.
}
public void TextInputSetRect(UIBox2i rect, int cursor)
{
// Nop.

View File

@@ -42,7 +42,6 @@ namespace Robust.Client.Graphics.Clyde
void WindowSetSize(WindowReg window, Vector2i size);
void WindowSetVisible(WindowReg window, bool visible);
void WindowRequestAttention(WindowReg window);
void WindowSetProgress(WindowReg reg, WindowProgressState state, float value);
void WindowSwapBuffers(WindowReg window);
uint? WindowGetX11Id(WindowReg window);
nint? WindowGetX11Display(WindowReg window);

View File

@@ -38,7 +38,7 @@ internal partial class Clyde
fixed (Rgba32* pixPtr = img.GetPixelSpan())
{
var surface = (nint) SDL.SDL_CreateSurfaceFrom(
var surface = SDL.SDL_CreateSurfaceFrom(
img.Width,
img.Height,
SDL.SDL_PixelFormat.SDL_PIXELFORMAT_ABGR8888,

View File

@@ -21,7 +21,6 @@ internal partial class Clyde
private sealed partial class Sdl3WindowingImpl
{
private int _nextWindowId = 1;
private bool _progressUnavailable;
public (WindowReg?, string? error) WindowCreate(
GLContextSpec? spec,
@@ -439,8 +438,7 @@ internal partial class Clyde
private static void WinThreadWinSetSize(CmdWinSetSize cmd)
{
var density = SDL.SDL_GetWindowPixelDensity(cmd.Window);
SDL.SDL_SetWindowSize(cmd.Window, (int)(cmd.W / density), (int)(cmd.H / density));
SDL.SDL_SetWindowSize(cmd.Window, cmd.W, cmd.H);
}
private static void WinThreadWinSetVisible(CmdWinSetVisible cmd)
@@ -463,42 +461,6 @@ internal partial class Clyde
_sawmill.Error("Failed to flash window: {error}", SDL.SDL_GetError());
}
public void WindowSetProgress(WindowReg window, WindowProgressState state, float value)
{
SendCmd(new CmdWinSetProgress
{
Window = WinPtr(window),
State = (SDL.SDL_ProgressState)state,
Value = value
});
}
private void WinThreadWinSetProgress(CmdWinSetProgress cmd)
{
if (_progressUnavailable)
return;
try
{
var res = SDL.SDL_SetWindowProgressState(cmd.Window, cmd.State);
if (!res)
{
_sawmill.Error("Failed to set window progress state: {error}", SDL.SDL_GetError());
return;
}
res = SDL.SDL_SetWindowProgressValue(cmd.Window, cmd.Value);
if (!res)
_sawmill.Error("Failed to set window progress value: {error}", SDL.SDL_GetError());
}
catch (EntryPointNotFoundException)
{
// Allowing it to fail means I don't have to update the launcher immediately :)
_progressUnavailable = true;
_sawmill.Debug("SDL3 progress APIs unavailable");
}
}
public unsafe void WindowSwapBuffers(WindowReg window)
{
var reg = (Sdl3WindowReg)window;

View File

@@ -75,7 +75,7 @@ internal partial class Clyde
IntPtr surface;
fixed (byte* ptr = copied)
{
surface = (nint) SDL.SDL_CreateSurfaceFrom(
surface = SDL.SDL_CreateSurfaceFrom(
img.Width,
img.Height,
SDL.SDL_PixelFormat.SDL_PIXELFORMAT_ABGR8888,

View File

@@ -93,10 +93,6 @@ internal partial class Clyde
WinThreadWinRequestAttention(cmd);
break;
case CmdWinSetProgress cmd:
WinThreadWinSetProgress(cmd);
break;
case CmdWinSetSize cmd:
WinThreadWinSetSize(cmd);
break;
@@ -265,13 +261,6 @@ internal partial class Clyde
public nint Window;
}
private sealed class CmdWinSetProgress : CmdBase
{
public nint Window;
public SDL.SDL_ProgressState State;
public float Value;
}
private sealed class CmdWinSetSize : CmdBase
{
public nint Window;

View File

@@ -18,8 +18,6 @@ namespace Robust.Client.Graphics
[NotContentImplementable]
public interface IClyde
{
internal bool IsInitialized { get; }
IClydeWindow MainWindow { get; }
IRenderTarget MainWindowRenderTarget => MainWindow.RenderTarget;

View File

@@ -43,11 +43,6 @@ namespace Robust.Client.Graphics
/// </summary>
Color? ClearColor { get; set; }
/// <summary>
/// On frames where Eye is null or in nullspace, whether the viewport may clear.
/// </summary>
bool ClearWhenMissingEye { get; set; }
/// <summary>
/// This is, effectively, a multiplier to the eye's zoom.
/// </summary>

View File

@@ -2,7 +2,6 @@
using System.Numerics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using SDL3;
namespace Robust.Client.Graphics
{
@@ -44,8 +43,6 @@ namespace Robust.Client.Graphics
/// </summary>
event Action<WindowResizedEventArgs> Resized;
internal void SetWindowProgress(WindowProgressState state, float value);
/// <summary>
/// Set the active text input area in window pixel coordinates.
/// </summary>
@@ -71,15 +68,6 @@ namespace Robust.Client.Graphics
void TextInputStop();
}
internal enum WindowProgressState : byte
{
None = SDL.SDL_ProgressState.SDL_PROGRESS_STATE_NONE,
Indeterminate = SDL.SDL_ProgressState.SDL_PROGRESS_STATE_INDETERMINATE,
Normal = SDL.SDL_ProgressState.SDL_PROGRESS_STATE_NORMAL,
Paused = SDL.SDL_ProgressState.SDL_PROGRESS_STATE_PAUSED,
Error = SDL.SDL_ProgressState.SDL_PROGRESS_STATE_ERROR
}
[NotContentImplementable]
internal interface IClydeWindowInternal : IClydeWindow
{

View File

@@ -118,13 +118,6 @@ internal sealed class LoadingScreenManager : ILoadingScreenManager
_currentSectionName = sectionName;
if (_clyde.IsInitialized)
{
_clyde.MainWindow.SetWindowProgress(
WindowProgressState.Normal,
_currentSection / (float)_numberOfLoadingSections);
}
if (!dontRender)
{
// This ensures that if the screen was resized or something the new size is properly updated to clyde.
@@ -183,8 +176,6 @@ internal sealed class LoadingScreenManager : ILoadingScreenManager
if (_currentSection != _numberOfLoadingSections)
_sawmill.Error($"The number of seen loading sections isn't equal to the total number of loading sections! Seen: {_currentSection}, Total: {_numberOfLoadingSections}");
_clyde.MainWindow.SetWindowProgress(WindowProgressState.None, 1);
_finished = true;
}

View File

@@ -1,48 +0,0 @@
using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
namespace Robust.Client.Network.Transfer;
internal sealed class ClientTransferImplWebSocket : TransferImplWebSocket
{
private readonly (string EndpointUrl, byte[] Key) _info;
private readonly bool _slow;
public ClientTransferImplWebSocket(
(string EndpointUrl, byte[] Key) info,
ISawmill sawmill,
BaseTransferManager parent,
INetChannel channel,
bool slow)
: base(sawmill, parent, channel)
{
_info = info;
_slow = slow;
}
public override async Task ClientInit(CancellationToken cancel)
{
var clientWs = new ClientWebSocket();
clientWs.Options.SetRequestHeader(KeyHeaderName, Convert.ToBase64String(_info.Key));
clientWs.Options.SetRequestHeader(UserIdHeaderName, Channel.UserId.ToString());
if (_slow)
await Task.Delay(2000, cancel);
await clientWs.ConnectAsync(new Uri(_info.EndpointUrl), cancel);
WebSocket = clientWs;
ReadThread();
}
public override Task ServerInit()
{
throw new NotSupportedException();
}
}

View File

@@ -1,89 +0,0 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages.Transfer;
using Robust.Shared.Network.Transfer;
namespace Robust.Client.Network.Transfer;
internal sealed class ClientTransferManager : BaseTransferManager, ITransferManager
{
private readonly IClientNetManager _netManager;
private readonly IConfigurationManager _cfg;
private BaseTransferImpl? _transferImpl;
public event Action? ClientHandshakeComplete;
internal ClientTransferManager(
IClientNetManager netManager,
ILogManager logManager,
ITaskManager taskManager,
IConfigurationManager cfg)
: base(logManager, NetMessageAccept.Client, taskManager)
{
_netManager = netManager;
_cfg = cfg;
}
public Stream StartTransfer(INetChannel channel, TransferStartInfo startInfo)
{
if (_transferImpl == null)
throw new InvalidOperationException("Not connected yet!");
if (channel != _netManager.ServerChannel)
throw new InvalidOperationException("Invalid channel!");
return _transferImpl.StartTransfer(startInfo);
}
public void Initialize()
{
_netManager.RegisterNetMessage<MsgTransferInit>(RxTransferInit, NetMessageAccept.Client | NetMessageAccept.Handshake);
_netManager.RegisterNetMessage<MsgTransferAckInit>();
_netManager.RegisterNetMessage<MsgTransferData>(RxTransferData, NetMessageAccept.Client | NetMessageAccept.Handshake);
}
private async void RxTransferInit(MsgTransferInit message)
{
BaseTransferImpl impl;
if (message.HttpInfo is { } httpInfo)
{
impl = new ClientTransferImplWebSocket(
httpInfo,
Sawmill,
this,
message.MsgChannel,
_cfg.GetCVar(CVars.TransferArtificialDelay));
}
else
{
impl = new TransferImplLidgren(Sawmill, message.MsgChannel, this, _netManager);
}
_transferImpl = impl;
await _transferImpl.ClientInit(default);
ClientHandshakeComplete?.Invoke();
}
private void RxTransferData(MsgTransferData message)
{
if (_transferImpl is not TransferImplLidgren lidgren)
{
message.MsgChannel.Disconnect("Not lidgren");
return;
}
lidgren.ReceiveData(message);
}
public Task ServerHandshake(INetChannel channel)
{
throw new NotSupportedException();
}
}

View File

@@ -1,14 +0,0 @@
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
namespace Robust.Client.Network.Transfer;
internal sealed class ClientTransferTestManager(ITransferManager manager, ILogManager logManager)
: TransferTestManager(manager, logManager)
{
protected override bool PermissionCheck(INetChannel channel)
{
return true;
}
}

View File

@@ -13,9 +13,9 @@ namespace Robust.Client.Prototypes
public sealed class ClientPrototypeManager : PrototypeManager
{
[Dependency] private readonly INetManager _netManager = default!;
#if TOOLS
#pragma warning disable CS0414
[Dependency] private readonly IClientGameTiming _timing = default!;
#endif
#pragma warning restore CS0414
[Dependency] private readonly IGameControllerInternal _controller = default!;
[Dependency] private readonly IReloadManager _reload = default!;

View File

@@ -279,7 +279,10 @@ public sealed partial class ReplayLoadManager
var path = resUpload.RelativePath.Clean().ToRelativePath();
if (uploadedFiles.Add(path) && !_netResMan.FileExists(path))
{
_netResMan.StoreFile(path, resUpload.Data);
_netMan.DispatchLocalNetMessage(new NetworkResourceUploadMessage
{
RelativePath = path, Data = resUpload.Data
});
message.Messages.RemoveSwap(i);
break;
}

View File

@@ -21,6 +21,7 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly EntityManager _entMan = default!;
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly IClientNetManager _netMan = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly ILocalizationManager _locMan = default!;

View File

@@ -4,6 +4,7 @@ using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Upload;
using Robust.Shared.Utility;
@@ -13,7 +14,7 @@ public sealed class UploadFileCommand : IConsoleCommand
{
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IFileDialogManager _dialog = default!;
[Dependency] private readonly NetworkResourceManager _netRes = default!;
[Dependency] private readonly INetManager _netManager = default!;
public string Command => "uploadfile";
public string Description => "Uploads a resource to the server.";
@@ -54,6 +55,12 @@ public sealed class UploadFileCommand : IConsoleCommand
var data = file.CopyToArray();
_netRes.UploadResources([(path, data)]);
var msg = new NetworkResourceUploadMessage
{
RelativePath = path,
Data = data
};
_netManager.ClientSendMessage(msg);
}
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System;
using System.IO;
using Robust.Shared;
using Robust.Shared.Configuration;
@@ -6,6 +6,7 @@ using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Network;
using Robust.Shared.Upload;
using Robust.Shared.Utility;
@@ -13,9 +14,9 @@ namespace Robust.Client.Upload.Commands;
public sealed class UploadFolderCommand : IConsoleCommand
{
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly NetworkResourceManager _netRes = default!;
[Dependency] private IResourceManager _resourceManager = default!;
[Dependency] private IConfigurationManager _configManager = default!;
[Dependency] private INetManager _netMan = default!;
public string Command => "uploadfolder";
public string Description => Loc.GetString("uploadfolder-command-description");
@@ -49,7 +50,6 @@ public sealed class UploadFolderCommand : IConsoleCommand
}
//Grab all files in specified folder and upload them
var files = new List<(ResPath Relative, byte[] Data)>();
foreach (var filepath in _resourceManager.UserData.Find($"{folderPath.ToRelativePath()}/").files )
{
await using var filestream = _resourceManager.UserData.Open(filepath, FileMode.Open);
@@ -63,14 +63,17 @@ public sealed class UploadFolderCommand : IConsoleCommand
var data = filestream.CopyToArray();
files.Add((filepath.RelativeTo(BaseUploadFolderPath), data));
var msg = new NetworkResourceUploadMessage
{
RelativePath = filepath.RelativeTo(BaseUploadFolderPath),
Data = data
};
_netMan.ClientSendMessage(msg);
fileCount++;
}
}
_netRes.UploadResources(files);
shell.WriteLine( Loc.GetString("uploadfolder-command-success",("fileCount",fileCount)));
}
}

View File

@@ -1,11 +1,5 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Upload;
using Robust.Shared.Utility;
namespace Robust.Client.Upload;
@@ -13,44 +7,10 @@ public sealed class NetworkResourceManager : SharedNetworkResourceManager
{
[Dependency] private readonly IBaseClient _client = default!;
internal override void Initialize()
public override void Initialize()
{
base.Initialize();
_client.RunLevelChanged += OnLevelChanged;
TransferManager.RegisterTransferMessage(TransferKeyNetworkUpload);
TransferManager.RegisterTransferMessage(TransferKeyNetworkDownload, ReceiveDownload);
NetManager.RegisterNetMessage<NetworkResourceAckMessage>();
}
private async void ReceiveDownload(TransferReceivedEvent transfer)
{
Sawmill.Debug("Receiving file download transfer!");
await using var stream = transfer.DataStream;
try
{
var ackKeyBytes = new byte[4];
await stream.ReadExactlyAsync(ackKeyBytes);
var ackKey = BinaryPrimitives.ReadInt32LittleEndian(ackKeyBytes);
await IngestFileStream(stream);
if (ackKey != 0)
{
NetManager.ClientSendMessage(new NetworkResourceAckMessage
{
Key = ackKey
});
}
}
catch (Exception e)
{
Sawmill.Error($"Error while downloading transfer resources: {e}");
}
}
private void OnLevelChanged(object? sender, RunLevelChangedEventArgs e)
@@ -67,20 +27,4 @@ public sealed class NetworkResourceManager : SharedNetworkResourceManager
{
ContentRoot.Clear();
}
internal async void UploadResources(List<(ResPath Relative, byte[] Data)> files)
{
var clientNet = (IClientNetManager)NetManager;
if (clientNet.ServerChannel is not { } channel)
throw new InvalidOperationException("Not connected to server!");
await using var transfer = TransferManager.StartTransfer(
channel,
new TransferStartInfo
{
MessageKey = TransferKeyNetworkUpload,
});
await WriteFileStream(transfer, files);
}
}

View File

@@ -12,7 +12,6 @@ namespace Robust.Client.UserInterface
{
private Dictionary<string, AnimationPlayback>? _playingAnimations;
public Action<string>? AnimationStarted;
public Action<string>? AnimationCompleted;
/// <summary>
@@ -28,7 +27,6 @@ namespace Robust.Client.UserInterface
_playingAnimations ??= new Dictionary<string, AnimationPlayback>();
_playingAnimations.Add(key, playback);
AnimationStarted?.Invoke(key);
}
public bool HasRunningAnimation(string key)

View File

@@ -2,7 +2,6 @@ using System;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Animations;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
@@ -356,7 +355,6 @@ namespace Robust.Client.UserInterface
/// </remarks>
/// <seealso cref="MinWidth"/>
/// <seealso cref="MinHeight"/>
[Animatable]
public Vector2 MinSize
{
get => new(_minWidth, _minHeight);
@@ -380,7 +378,6 @@ namespace Robust.Client.UserInterface
/// </remarks>
/// <seealso cref="SetWidth"/>
/// <seealso cref="SetHeight"/>
[Animatable]
public Vector2 SetSize
{
get => new(_setWidth, _setHeight);
@@ -437,7 +434,6 @@ namespace Robust.Client.UserInterface
/// Width component of <see cref="SetSize"/>.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[Animatable]
public float SetWidth
{
get => _setWidth;
@@ -453,7 +449,6 @@ namespace Robust.Client.UserInterface
/// Height component of <see cref="SetSize"/>.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[Animatable]
public float SetHeight
{
get => _setHeight;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
@@ -44,7 +44,6 @@ namespace Robust.Client.UserInterface.Controls
/// Thrown if <see cref="TextMemory"/> was set directly and there is no backing string instance to fetch.
/// </exception>
[ViewVariables]
[Animatable]
public string? Text
{
get => _text ?? (_textMemory.Length > 0 ? throw new InvalidOperationException("Label uses TextMemory, cannot fetch string text.") : null);

View File

@@ -144,7 +144,7 @@ namespace Robust.Client.UserInterface.Controls
SetPositionFirst();
// Resize the window by our UIScale
ClydeWindow.Size = new((int)(parameters.Width * UIScale), (int)(parameters.Height * UIScale));
ClydeWindow.Size = new((int)(ClydeWindow.Size.X * UIScale), (int)(ClydeWindow.Size.Y * UIScale));
return ClydeWindow;
}

View File

@@ -22,10 +22,8 @@ namespace Robust.Client.UserInterface.Controls
private int _currentTab;
private bool _tabsVisible = true;
// the laid out tabs
private List<TabBox> _tabBoxes = new();
private float _enclosingTabHeight;
// The right-most coordinate of each tab header
private List<float> _tabRight = new();
public int CurrentTab
{
@@ -148,66 +146,41 @@ namespace Robust.Client.UserInterface.Controls
base.Draw(handle);
// First, draw panel.
var headerSize = _enclosingTabHeight;
var headerSize = _getHeaderSize();
var panel = _getPanel();
var panelBox = new UIBox2(0, headerSize, PixelWidth, PixelHeight);
panel?.Draw(handle, panelBox, UIScale);
var font = _getFont();
var boxActive = _getTabBoxActive();
var boxInactive = _getTabBoxInactive();
var fontColorActive = _getTabFontColorActive();
var fontColorInactive = _getTabFontColorInactive();
// Then draw the tabs
foreach (var tabBox in _tabBoxes)
{
if (tabBox.Box is { } styleBox)
{
styleBox.Draw(handle, tabBox.Bounding, UIScale);
}
var headerOffset = 0f;
var baseLine = new Vector2(0, font.GetAscent(UIScale)) + tabBox.Content.TopLeft;
foreach (var rune in tabBox.Title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
continue;
font.DrawChar(handle, rune, baseLine, UIScale, tabBox.Index == _currentTab ? fontColorActive : fontColorInactive);
baseLine += new Vector2(metrics.Advance, 0);
}
}
}
private readonly record struct TabBox(UIBox2 Bounding, UIBox2 Content, StyleBox? Box, string Title, int Index);
private void CalculateTabBoxes(Vector2 availableSize)
{
availableSize *= UIScale;
var tabLeft = 0f;
var tabTop = 0f;
var tabHeight = 0f;
var font = _getFont();
var boxActive = _getTabBoxActive();
var boxInactive = _getTabBoxInactive();
_tabBoxes.Clear();
if (!_tabsVisible)
return;
_tabRight.Clear();
// Then, draw the tabs.
for (var i = 0; i < ChildCount; i++)
{
if (!GetTabVisible(i))
{
_tabRight.Add(headerOffset);
continue;
}
var title = GetActualTabTitle(i);
var titleLength = 0;
// Get string length.
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
titleLength += metrics.Advance;
}
@@ -215,57 +188,50 @@ namespace Robust.Client.UserInterface.Controls
var active = _currentTab == i;
var box = active ? boxActive : boxInactive;
var topLeft = new Vector2(tabLeft, tabTop);
var size = new Vector2(titleLength, font.GetHeight(UIScale));
if (box != null)
{
size = box.GetEnvelopBox(topLeft, size, UIScale).Size;
}
if (tabLeft + size.X > availableSize.X)
{
tabLeft = 0;
tabTop += tabHeight;
tabHeight = 0;
}
topLeft = new(tabLeft, tabTop);
size = new(titleLength, font.GetHeight(UIScale));
UIBox2 boundingBox;
UIBox2 contentBox;
var topLeft = new Vector2(headerOffset, 0);
var size = new Vector2(titleLength, font.GetHeight(UIScale));
float boxAdvance;
if (box != null)
{
boundingBox = box.GetEnvelopBox(topLeft, size, UIScale);
contentBox = box.GetContentBox(boundingBox, UIScale);
var drawBox = box.GetEnvelopBox(topLeft, size, UIScale);
boxAdvance = drawBox.Width;
box.Draw(handle, drawBox, UIScale);
contentBox = box.GetContentBox(drawBox, UIScale);
}
else
{
boxAdvance = size.X;
contentBox = UIBox2.FromDimensions(topLeft, size);
boundingBox = contentBox;
}
tabLeft += boundingBox.Size.X;
tabHeight = Math.Max(tabHeight, boundingBox.Size.Y);
_tabBoxes.Add(new(boundingBox, contentBox, box, title, i));
}
var baseLine = new Vector2(0, font.GetAscent(UIScale)) + contentBox.TopLeft;
if (Math.Abs(_enclosingTabHeight - (tabTop + tabHeight)) >= 0.1)
{
InvalidateArrange();
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
font.DrawChar(handle, rune, baseLine, UIScale, active ? fontColorActive : fontColorInactive);
baseLine += new Vector2(metrics.Advance, 0);
}
headerOffset += boxAdvance;
// Remember the right-most point of this tab, for testing clicked areas
_tabRight.Add(headerOffset);
}
_enclosingTabHeight = tabTop + tabHeight;
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
CalculateTabBoxes(availableSize);
var headerSize = Vector2.Zero;
if (TabsVisible)
{
headerSize = new Vector2(0, _enclosingTabHeight / UIScale);
headerSize = new Vector2(0, _getHeaderSize() / UIScale);
}
var panel = _getPanel();
@@ -288,13 +254,12 @@ namespace Robust.Client.UserInterface.Controls
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
CalculateTabBoxes(finalSize);
if (ChildCount == 0 || _currentTab >= ChildCount)
{
return finalSize;
}
var headerSize = (int)_enclosingTabHeight;
var headerSize = _getHeaderSize();
var panel = _getPanel();
var contentBox = new UIBox2i(0, headerSize, (int) (finalSize.X * UIScale), (int) (finalSize.Y * UIScale));
if (panel != null)
@@ -318,23 +283,50 @@ namespace Robust.Client.UserInterface.Controls
}
// Outside of header size, ignore.
if (args.RelativePixelPosition.Y < 0 || args.RelativePixelPosition.Y > _enclosingTabHeight)
if (args.RelativePixelPosition.Y < 0 || args.RelativePixelPosition.Y > _getHeaderSize())
{
return;
}
args.Handle();
foreach (var box in _tabBoxes)
var relX = args.RelativePixelPosition.X;
float tabLeft = 0;
for (var i = 0; i < ChildCount; i++)
{
if (box.Bounding.Contains(args.RelativePixelPosition))
if (relX > tabLeft && relX <= _tabRight[i])
{
CurrentTab = box.Index;
CurrentTab = i;
return;
}
// Next tab starts here
tabLeft = _tabRight[i];
}
}
// Returns the size of the header, in real pixels
[System.Diagnostics.Contracts.Pure]
private int _getHeaderSize()
{
var headerSize = 0;
if (TabsVisible)
{
var active = _getTabBoxActive();
var inactive = _getTabBoxInactive();
var font = _getFont();
var activeSize = (active?.MinimumSize ?? Vector2.Zero) * UIScale;
var inactiveSize = (inactive?.MinimumSize ?? Vector2.Zero) * UIScale;
headerSize = (int) MathF.Max(activeSize.Y, inactiveSize.Y);
headerSize += font.GetHeight(UIScale);
}
return headerSize;
}
[System.Diagnostics.Contracts.Pure]
private StyleBox? _getTabBoxActive()
{

View File

@@ -14,6 +14,15 @@ 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

View File

@@ -20,9 +20,9 @@ internal sealed class ReloadManager : IReloadManager
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logMan = default!;
[Dependency] private readonly IResourceManagerInternal _res = default!;
#if TOOLS
#pragma warning disable CS0414
[Dependency] private readonly ITaskManager _tasks = default!;
#endif
#pragma warning restore CS0414
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
private CancellationTokenSource _reloadToken = new();

View File

@@ -56,7 +56,7 @@ internal sealed class ViewVariableControlFactory : IViewVariableControlFactory
RegisterForType<TimeSpan>(_ => new VVPropEditorTimeSpan());
RegisterWithCondition(
type => type != typeof(ViewVariablesBlobMembers.ServerValueTypeToken),
type => type != typeof(ViewVariablesBlobMembers.ServerValueTypeToken) && !type.IsValueType,
_ => new VVPropEditorReference()
);
RegisterWithCondition(

View File

@@ -9,13 +9,11 @@ using Robust.Server.Debugging;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Localization;
using Robust.Server.Network.Transfer;
using Robust.Server.Physics;
using Robust.Server.Player;
using Robust.Server.Prototypes;
using Robust.Server.Reflection;
using Robust.Server.Replays;
using Robust.Server.ServerStatus;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
@@ -30,7 +28,6 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Components;
@@ -201,9 +198,6 @@ namespace Robust.UnitTesting.Server
container.Register<HttpClientHolder>();
container.Register<IHttpClientHolder, HttpClientHolder>();
container.Register<IHWId, DummyHWId>();
container.Register<IServerNetManager, NetManager>();
container.Register<IStatusHost, StatusHost>();
container.Register<ITransferManager, ServerTransferManager>();
var realReflection = new ServerReflectionManager();
realReflection.LoadAssemblies(new List<Assembly>(2)
@@ -268,6 +262,7 @@ namespace Robust.UnitTesting.Server
// I just wanted to load pvs system
container.Register<IServerEntityManager, ServerEntityManager>();
container.Register<IServerNetManager, NetManager>();
// god help you if you actually need to test pvs functions
container.RegisterInstance<IPlayerManager>(new Mock<IPlayerManager>().Object);
container.RegisterInstance<ISharedPlayerManager>(new Mock<ISharedPlayerManager>().Object);

View File

@@ -9,7 +9,6 @@ using Robust.Server.DataMetrics;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Log;
using Robust.Server.Network.Transfer;
using Robust.Server.Placement;
using Robust.Server.Player;
using Robust.Server.Scripting;
@@ -30,7 +29,6 @@ using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Player;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
@@ -109,8 +107,6 @@ namespace Robust.Server
[Dependency] private readonly UploadedContentManager _uploadedContMan = default!;
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
[Dependency] private readonly IReflectionManager _refMan = default!;
[Dependency] private readonly ITransferManager _transfer = default!;
[Dependency] private readonly ServerTransferTestManager _transferTest = default!;
private readonly Stopwatch _uptimeStopwatch = new();
@@ -279,7 +275,6 @@ namespace Robust.Server
// Load metrics really early so that we can profile startup times in the future maybe.
_metricsManager.Initialize();
_prof.Initialize();
try
{
@@ -298,9 +293,6 @@ namespace Robust.Server
return true;
}
_transfer.Initialize();
_transferTest.Initialize();
var dataDir = Options.LoadConfigAndUserData
? _commandLineArgs?.DataDir ?? PathHelpers.ExecutableRelativeFile("data")
: null;
@@ -781,8 +773,6 @@ namespace Robust.Server
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
_transfer.FrameUpdate();
_metricsManager.FrameUpdate();
}

View File

@@ -9,7 +9,6 @@ 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;
@@ -23,7 +22,6 @@ 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) {}
@@ -110,8 +108,7 @@ namespace Robust.Server.Console
if (args.Count == 0)
return;
var cmdName = args[0];
using var _ = _prof.Group(cmdName);
string? cmdName = args[0];
if (RegisteredCommands.TryGetValue(cmdName, out var conCmd)) // command registered
{

View File

@@ -1,122 +0,0 @@
using System;
using System.Net;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Robust.Server.ServerStatus;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages.Transfer;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Utility;
namespace Robust.Server.Network.Transfer;
internal sealed class ServerTransferImplWebSocket : TransferImplWebSocket
{
private readonly IConfigurationManager _cfg;
private readonly INetManager _netManager;
private readonly SemaphoreSlim _apiLock = new(1, 1);
private readonly TaskCompletionSource _connectTcs = new();
// To authenticate the client doing the HTTP request,
// we ask that they provide a key we gave them via Lidgren traffic.
public byte[]? Key;
public ServerTransferImplWebSocket(
ISawmill sawmill,
BaseTransferManager parent,
IConfigurationManager cfg,
INetManager netManager,
INetChannel channel)
: base(sawmill, parent, channel)
{
_cfg = cfg;
_netManager = netManager;
}
public override Task ServerInit()
{
Key = RandomNumberGenerator.GetBytes(RandomKeyBytes);
var uriBuilder = new UriBuilder(string.Concat(
_cfg.GetCVar(CVars.TransferHttpEndpoint).TrimEnd("/"),
ServerTransferManager.TransferApiUrl));
uriBuilder.Scheme = uriBuilder.Scheme switch
{
"http" => "ws",
"https" => "wss",
_ => throw new InvalidOperationException($"Invalid API endpoint scheme: {uriBuilder.Scheme}")
};
var url = uriBuilder.ToString();
Sawmill.Verbose($"Transfer API URL is '{url}'");
var initMsg = new MsgTransferInit();
initMsg.HttpInfo = (url, Key);
_netManager.ServerSendMessage(initMsg, Channel);
return _connectTcs.Task;
}
public override Task ClientInit(CancellationToken cancel)
{
throw new NotSupportedException();
}
public async Task HandleApiRequest(NetUserId userId, IStatusHandlerContext context)
{
using var _ = await _apiLock.WaitGuardAsync();
if (Key == null)
{
Sawmill.Warning($"HTTP request failed: UserID '{userId}' tried to connect twice");
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
return;
}
if (!context.RequestHeaders.TryGetValue(KeyHeaderName, out var keyValue) || keyValue is not [{ } keyValueStr])
{
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
return;
}
var buf = new byte[RandomKeyBytes];
if (!Convert.TryFromBase64String(keyValueStr, buf, out var written) || written != RandomKeyBytes)
{
Sawmill.Verbose("HTTP request failed: key is not valid base64 or wrong length");
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
return;
}
if (!CryptographicOperations.FixedTimeEquals(buf, Key))
{
Sawmill.Warning("HTTP request failed: key is wrong");
await context.RespondErrorAsync(HttpStatusCode.Unauthorized);
return;
}
Sawmill.Debug("Client connect to transfer WS channel: {UserId}", userId);
WebSocket = await context.AcceptWebSocketAsync();
// We've connected.
// Clear key so this can't be reconnected to.
Key = null;
_connectTcs.TrySetResult();
ReadThread();
}
public override void Dispose()
{
_connectTcs.TrySetCanceled();
}
}

View File

@@ -1,171 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Robust.Server.ServerStatus;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages.Transfer;
using Robust.Shared.Network.Transfer;
namespace Robust.Server.Network.Transfer;
internal sealed class ServerTransferManager : BaseTransferManager, ITransferManager
{
internal const string TransferApiUrl = "/rt_transfer_init";
private readonly IConfigurationManager _cfg;
private readonly IStatusHost _statusHost;
private readonly IServerNetManager _netManager;
private readonly Dictionary<NetUserId, Player> _onlinePlayers = new();
internal ServerTransferManager(IConfigurationManager cfg, IStatusHost statusHost, IServerNetManager netManager, ILogManager logManager, ITaskManager taskManager)
: base(logManager, NetMessageAccept.Server, taskManager)
{
_cfg = cfg;
_statusHost = statusHost;
_netManager = netManager;
}
public void Initialize()
{
_netManager.RegisterNetMessage<MsgTransferInit>();
_netManager.RegisterNetMessage<MsgTransferData>(RxTransferData, NetMessageAccept.Server | NetMessageAccept.Handshake);
_netManager.RegisterNetMessage<MsgTransferAckInit>(RxTransferAckInit, NetMessageAccept.Server | NetMessageAccept.Handshake);
_statusHost.AddHandler(HandleRequest);
_netManager.Disconnect += NetManagerOnDisconnect;
}
private void RxTransferData(MsgTransferData message)
{
if (!_onlinePlayers.TryGetValue(message.MsgChannel.UserId, out var player)
|| player.Impl is not TransferImplLidgren lidgren)
{
message.MsgChannel.Disconnect("Not lidgren");
return;
}
lidgren.ReceiveData(message);
}
private void RxTransferAckInit(MsgTransferAckInit message)
{
if (!_onlinePlayers.TryGetValue(message.MsgChannel.UserId, out var player)
|| player.Impl is not TransferImplLidgren lidgren)
{
message.MsgChannel.Disconnect("Not lidgren");
return;
}
lidgren.ReceiveInitAck();
}
public Stream StartTransfer(INetChannel channel, TransferStartInfo startInfo)
{
if (!_onlinePlayers.TryGetValue(channel.UserId, out var player))
throw new InvalidOperationException("Player is not connected yet!");
return player.Impl.StartTransfer(startInfo);
}
private async Task<bool> HandleRequest(IStatusHandlerContext context)
{
if (context.Url.AbsolutePath != TransferApiUrl)
return false;
if (!context.IsWebSocketRequest)
{
Sawmill.Verbose("HTTP request failed: not a websocket request");
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
return true;
}
if (!context.RequestHeaders.TryGetValue(TransferImplWebSocket.UserIdHeaderName, out var userIdValue)
|| userIdValue.Count != 1)
{
Sawmill.Verbose("HTTP request failed: missing RT-UserId");
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
return true;
}
if (!Guid.TryParse(userIdValue[0], out var userId))
{
Sawmill.Verbose($"HTTP request failed: UserID '{userId}' invalid");
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
return true;
}
if (!_onlinePlayers.TryGetValue(new NetUserId(userId), out var player))
{
Sawmill.Warning($"HTTP request failed: UserID '{userId}' not online");
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
return true;
}
if (player.Impl is not ServerTransferImplWebSocket serverWs)
{
Sawmill.Warning($"HTTP request failed: UserID '{userId}' is not using websocket transfer");
await context.RespondErrorAsync(HttpStatusCode.Unauthorized);
return true;
}
await serverWs.HandleApiRequest(new NetUserId(userId), context);
return true;
}
public async Task ServerHandshake(INetChannel channel)
{
if (_onlinePlayers.ContainsKey(channel.UserId))
throw new InvalidOperationException("We already have data for this user??");
var transferHttpEnabled = _cfg.GetCVar(CVars.TransferHttp);
BaseTransferImpl impl;
if (transferHttpEnabled)
{
impl = new ServerTransferImplWebSocket(Sawmill, this, _cfg, _netManager, channel);
}
else
{
impl = new TransferImplLidgren(Sawmill, channel, this, _netManager);
}
impl.MaxChannelCount = _cfg.GetCVar(CVars.TransferStreamLimit);
var datum = new Player
{
Impl = impl,
};
_onlinePlayers.Add(channel.UserId, datum);
await impl.ServerInit();
}
public event Action ClientHandshakeComplete
{
add { }
remove { }
}
private void NetManagerOnDisconnect(object? sender, NetDisconnectedArgs e)
{
if (!_onlinePlayers.Remove(e.Channel.UserId, out var player))
return;
Sawmill.Debug("Cleaning up connection for channel {Player} that disconnected", e.Channel);
player.Impl.Dispose();
}
private sealed class Player
{
public required BaseTransferImpl Impl;
}
}

View File

@@ -1,23 +0,0 @@
using Robust.Server.Console;
using Robust.Server.Player;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
namespace Robust.Server.Network.Transfer;
internal sealed class ServerTransferTestManager(
ITransferManager manager,
ILogManager logManager,
IConGroupController controller,
IPlayerManager playerManager)
: TransferTestManager(manager, logManager)
{
protected override bool PermissionCheck(INetChannel channel)
{
if (!playerManager.TryGetSessionByChannel(channel, out var session))
return false;
return controller.CanCommand(session, TransferTestCommand.CommandKey);
}
}

View File

@@ -217,10 +217,6 @@ 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
@@ -229,61 +225,26 @@ 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)
{
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)))
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)))
{
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.Deleted(entity) ||
_entityManager.HasComponent<MapGridComponent>(entity) ||
_entityManager.HasComponent<ActorComponent>(entity))
{
if (_entityManager.HasComponent<ActorComponent>(parent))
{
isChildOfActor = true;
break;
}
if (_entityManager.TryGetComponent<TransformComponent>(parent, out var parentXform))
{
parent = parentXform.ParentUid;
}
else
{
break;
}
continue;
}
if (isChildOfActor)
continue;
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);
}

View File

@@ -1,5 +1,4 @@
using Robust.Shared.Input;
using Robust.Shared.Network;
using Robust.Shared.Player;
namespace Robust.Server.Player;
@@ -11,6 +10,4 @@ namespace Robust.Server.Player;
public interface IPlayerManager : ISharedPlayerManager
{
BoundKeyMap KeyMap { get; }
internal void MarkPlayerResourcesSent(INetChannel channel);
}

View File

@@ -120,34 +120,13 @@ namespace Robust.Server.Player
private void HandlePlayerListReq(MsgPlayerListReq message)
{
var channel = message.MsgChannel;
var session = (CommonSession) GetSessionByChannel(channel);
session.InitialPlayerListReqDone = true;
if (!session.InitialResourcesDone)
return;
SendPlayerList(channel, session);
}
public void MarkPlayerResourcesSent(INetChannel channel)
{
var session = (CommonSession) GetSessionByChannel(channel);
session.InitialResourcesDone = true;
if (!session.InitialPlayerListReqDone)
return;
SendPlayerList(channel, session);
}
private void SendPlayerList(INetChannel channel, CommonSession session)
{
var players = Sessions;
var netMsg = new MsgPlayerList();
// client session is complete, set their status accordingly.
// This is done before the packet is built, so that the client
// can see themselves Connected.
var session = GetSessionByChannel(channel);
session.ConnectedTime = DateTime.UtcNow;
SetStatus(session, SessionStatus.Connected);

View File

@@ -13,10 +13,10 @@ namespace Robust.Server.Prototypes
{
public sealed class ServerPrototypeManager : PrototypeManager
{
#if TOOLS
#pragma warning disable CS0414
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConGroupController _conGroups = default!;
#endif
#pragma warning restore CS0414
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IBaseServerInternal _server = default!;

View File

@@ -5,7 +5,6 @@ using Robust.Server.DataMetrics;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Localization;
using Robust.Server.Network.Transfer;
using Robust.Server.Placement;
using Robust.Server.Player;
using Robust.Server.Prototypes;
@@ -26,7 +25,6 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
@@ -104,8 +102,6 @@ namespace Robust.Server
deps.Register<IHWId, DummyHWId>();
deps.Register<ILocalizationManager, ServerLocalizationManager>();
deps.Register<ILocalizationManagerInternal, ServerLocalizationManager>();
deps.Register<ITransferManager, ServerTransferManager>();
deps.Register<ServerTransferTestManager>();
}
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
@@ -26,8 +25,6 @@ namespace Robust.Server.ServerStatus
IDictionary<string, string> ResponseHeaders { get; }
bool KeepAlive { get; set; }
bool IsWebSocketRequest { get; }
Task<T?> RequestBodyJsonAsync<T>();
Task RespondNoContentAsync();
@@ -57,7 +54,5 @@ namespace Robust.Server.ServerStatus
Task RespondJsonAsync(object jsonData, HttpStatusCode code = HttpStatusCode.OK);
Task<Stream> RespondStreamAsync(HttpStatusCode code = HttpStatusCode.OK);
Task<WebSocket> AcceptWebSocketAsync();
}
}

View File

@@ -14,7 +14,6 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Net.WebSockets;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
@@ -243,7 +242,6 @@ namespace Robust.Server.ServerStatus
public Uri Url => _context.Request.Url!;
public bool IsGetLike => RequestMethod == HttpMethod.Head || RequestMethod == HttpMethod.Get;
public IReadOnlyDictionary<string, StringValues> RequestHeaders { get; }
public bool IsWebSocketRequest => _context.Request.IsWebSocketRequest;
public bool KeepAlive
{
@@ -355,12 +353,6 @@ namespace Robust.Server.ServerStatus
return Task.FromResult(_context.Response.OutputStream);
}
public async Task<WebSocket> AcceptWebSocketAsync()
{
var context = await _context.AcceptWebSocketAsync(null);
return context.WebSocket;
}
private void RespondShared()
{
foreach (var (header, value) in _responseHeaders)

View File

@@ -1,153 +1,77 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Robust.Server.Console;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Player;
using Robust.Shared.Upload;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Server.Upload;
public sealed class NetworkResourcesUploadedEvent
{
public ICommonSession Session { get; }
public ImmutableArray<(ResPath Relative, byte[] Data)> Files { get; }
internal NetworkResourcesUploadedEvent(ICommonSession session, ImmutableArray<(ResPath, byte[])> files)
{
Session = session;
Files = files;
}
}
public sealed class NetworkResourceManager : SharedNetworkResourceManager
{
internal const int AckInitial = 1;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerNetManager _serverNetManager = default!;
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IConGroupController _controller = default!;
[Obsolete("Use ResourcesUploaded instead")]
public event Action<ICommonSession, NetworkResourceUploadMessage>? OnResourceUploaded;
public event Action<NetworkResourcesUploadedEvent>? ResourcesUploaded;
[ViewVariables] public bool Enabled { get; private set; } = true;
[ViewVariables] public float SizeLimit { get; private set; }
internal event Action<INetChannel, int>? AckReceived;
internal override void Initialize()
public override void Initialize()
{
base.Initialize();
TransferManager.RegisterTransferMessage(TransferKeyNetworkDownload);
TransferManager.RegisterTransferMessage(TransferKeyNetworkUpload, ReceiveUpload);
_cfgManager.OnValueChanged(CVars.ResourceUploadingEnabled, value => Enabled = value, true);
_cfgManager.OnValueChanged(CVars.ResourceUploadingLimitMb, value => SizeLimit = value, true);
_serverNetManager.RegisterNetMessage<NetworkResourceAckMessage>(RxAck);
}
private void RxAck(NetworkResourceAckMessage message)
{
AckReceived?.Invoke(message.MsgChannel, message.Key);
}
private async void ReceiveUpload(TransferReceivedEvent transfer)
/// <summary>
/// Callback for when a client attempts to upload a resource.
/// </summary>
/// <param name="msg"></param>
/// <exception cref="NotImplementedException"></exception>
protected override void ResourceUploadMsg(NetworkResourceUploadMessage msg)
{
// Do not allow uploading any new resources if it has been disabled.
// Note: Any resources uploaded before being disabled will still be kept and sent.
if (!Enabled)
{
transfer.Channel.Disconnect("Resource upload not enabled.");
return;
}
if (!_playerManager.TryGetSessionByChannel(transfer.Channel, out var session))
{
transfer.Channel.Disconnect("Not in-game");
if (!_playerManager.TryGetSessionByChannel(msg.MsgChannel, out var session))
return;
}
if (!_controller.CanCommand(session, "uploadfile"))
{
transfer.Channel.Disconnect("Not authorized");
return;
}
Sawmill.Verbose("Ingesting file uploads from {Session}", session);
// Ensure the data is under the current size limit, if it's currently enabled.
if (SizeLimit > 0f && msg.Data.Length * BytesToMegabytes > SizeLimit)
return;
List<(ResPath Relative, byte[] Data)> ingested;
await using (var stream = transfer.DataStream)
{
ingested = await IngestFileStream(stream);
}
Sawmill.Verbose("Ingesting file uploads complete, distributing...");
base.ResourceUploadMsg(msg);
// Now we broadcast the message!
foreach (var channel in _serverNetManager.Channels)
{
SendToPlayer(channel, ingested);
channel.SendMessage(msg);
}
#pragma warning disable CS0618 // Type or member is obsolete
if (OnResourceUploaded != null)
OnResourceUploaded?.Invoke(session, msg);
}
internal void SendToNewUser(INetChannel channel)
{
foreach (var (path, data) in ContentRoot.GetAllFiles())
{
foreach (var (relative, data) in ingested)
{
OnResourceUploaded?.Invoke(session, new NetworkResourceUploadMessage
{
MsgChannel = session.Channel,
Data = data,
RelativePath = relative
});
}
var msg = new NetworkResourceUploadMessage();
msg.RelativePath = path;
msg.Data = data;
channel.SendMessage(msg);
}
#pragma warning restore CS0618 // Type or member is obsolete
ResourcesUploaded?.Invoke(new NetworkResourcesUploadedEvent(session, [..ingested]));
}
protected override void ValidateUpload(uint size)
{
if (SizeLimit > 0f && size * BytesToMegabytes > SizeLimit)
throw new Exception("File upload too large!");
}
internal bool SendToNewUser(INetChannel channel)
{
var allFiles = ContentRoot.GetAllFiles().ToList();
if (allFiles.Count == 0)
return false;
SendToPlayer(channel, allFiles, AckInitial);
return true;
}
private async void SendToPlayer(INetChannel channel, List<(ResPath Relative, byte[] Data)> files, int ack = 0)
{
await using var stream = TransferManager.StartTransfer(channel,
new TransferStartInfo
{
MessageKey = TransferKeyNetworkDownload
});
var ackBytes = new byte[4];
BinaryPrimitives.WriteInt32LittleEndian(ackBytes, ack);
await stream.WriteAsync(ackBytes);
await WriteFileStream(stream, files);
}
}

View File

@@ -1,5 +1,4 @@
using Robust.Server.Player;
using Robust.Shared.IoC;
using Robust.Shared.IoC;
using Robust.Shared.Network;
namespace Robust.Server.Upload;
@@ -10,36 +9,20 @@ namespace Robust.Server.Upload;
internal sealed class UploadedContentManager
{
[Dependency] private readonly IServerNetManager _netManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly GamePrototypeLoadManager _prototypeLoadManager = default!;
[Dependency] private readonly NetworkResourceManager _networkResourceManager = default!;
public void Initialize()
{
_netManager.Connected += NetManagerOnConnected;
_networkResourceManager.AckReceived += OnAckReceived;
}
private void OnAckReceived(INetChannel channel, int ack)
{
if (ack != NetworkResourceManager.AckInitial)
return;
ResourcesReady(channel);
}
private void NetManagerOnConnected(object? sender, NetChannelArgs e)
{
// This just shells out to the other managers, ensuring they are ordered properly.
// Resources must be done before prototypes.
var sentAny = _networkResourceManager.SendToNewUser(e.Channel);
if (!sentAny)
ResourcesReady(e.Channel);
}
private void ResourcesReady(INetChannel channel)
{
_prototypeLoadManager.SendToNewUser(channel);
_playerManager.MarkPlayerResourcesSent(channel);
// Note: both net messages sent here are on the same group and are therefore ordered.
_networkResourceManager.SendToNewUser(e.Channel);
_prototypeLoadManager.SendToNewUser(e.Channel);
}
}

View File

@@ -12,6 +12,5 @@ namespace Robust.Server.ViewVariables
object Object { get; }
uint SessionId { get; }
Type ObjectType { get; }
Action<object>? ObjectChangeDelegate { get; }
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Server.Console;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -137,7 +138,6 @@ namespace Robust.Server.ViewVariables
}
object theObject;
Action<object>? objectChangeDelegate = null;
switch (message.Selector)
{
@@ -200,14 +200,13 @@ namespace Robust.Server.ViewVariables
return;
}
if (value == null)
if (value == null || value.GetType().IsValueType)
{
Deny(ViewVariablesResponseCode.NoObject);
return;
}
theObject = value;
objectChangeDelegate = obj => relSession.Modify(sessionRelativeSelector.PropertyIndex, obj);
break;
}
case ViewVariablesIoCSelector ioCSelector:
@@ -251,7 +250,7 @@ namespace Robust.Server.ViewVariables
}
var sessionId = _nextSessionId++;
var session = new ViewVariablesSession(message.MsgChannel.UserId, theObject, objectChangeDelegate, sessionId, this,
var session = new ViewVariablesSession(message.MsgChannel.UserId, theObject, sessionId, this,
_robustSerializer, _entityManager, Sawmill);
_sessions.Add(sessionId, session);

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
@@ -197,8 +198,6 @@ namespace Robust.Server.ViewVariables.Traits
try
{
field.SetValue(Session.Object, value);
Session.ObjectChangeDelegate?.Invoke(Session.Object);
return true;
}
catch (Exception e)

View File

@@ -22,7 +22,6 @@ namespace Robust.Server.ViewVariables
public object Object { get; }
public uint SessionId { get; }
public Type ObjectType { get; }
public Action<object>? ObjectChangeDelegate { get; }
/// <param name="playerUser">The session ID of the player who opened this session.</param>
/// <param name="o">The object we represent.</param>
@@ -30,14 +29,13 @@ namespace Robust.Server.ViewVariables
/// The session ID for this session. This is what the server and client use to talk about this session.
/// </param>
/// <param name="host">The view variables host owning this session.</param>
public ViewVariablesSession(NetUserId playerUser, object o, Action<object>? objectChangeDelegate, uint sessionId, IServerViewVariablesInternal host,
public ViewVariablesSession(NetUserId playerUser, object o, uint sessionId, IServerViewVariablesInternal host,
IRobustSerializer robustSerializer, IEntityManager entMan, ISawmill logger)
{
PlayerUser = playerUser;
Object = o;
SessionId = sessionId;
ObjectType = o.GetType();
ObjectChangeDelegate = objectChangeDelegate;
Host = host;
RobustSerializer = robustSerializer;
EntityManager = entMan;

View File

@@ -2,14 +2,9 @@ using System;
using System.IO;
using Moq;
using NUnit.Framework;
using Robust.Server;
using Robust.Server.Configuration;
using Robust.Server.Network.Transfer;
using Robust.Server.Player;
using Robust.Server.Reflection;
using Robust.Server.Serialization;
using Robust.Server.ServerStatus;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
@@ -18,7 +13,6 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Network;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Profiling;
using Robust.Shared.Reflection;
using Robust.Shared.Replays;
@@ -49,7 +43,6 @@ namespace Robust.UnitTesting.Shared.GameObjects
container.Register<IAuthManager, AuthManager>();
container.Register<IGameTiming, GameTiming>();
container.Register<ProfManager, ProfManager>();
container.RegisterInstance<ITransferManager>(Mock.Of<ITransferManager>());
container.Register<HttpClientHolder>();
container.RegisterInstance<IReplayRecordingManager>(new Mock<IReplayRecordingManager>().Object);
container.BuildGraph();

View File

@@ -1,3 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using Robust.Server.Configuration;
@@ -85,6 +88,7 @@ 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);
@@ -100,15 +104,6 @@ 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);

View File

@@ -1,10 +1,11 @@
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
{
@@ -38,14 +39,6 @@ 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
@@ -65,7 +58,6 @@ namespace Robust.UnitTesting.Shared.GameObjects
syssy.LoadExtraSystemType<ESystemC>();
syssy.LoadExtraSystemType<ESystemDepA>();
syssy.LoadExtraSystemType<ESystemDepB>();
syssy.LoadExtraSystemType<ESystemDepAll>();
syssy.Initialize(false);
}
@@ -111,16 +103,5 @@ 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);
}
}
}

View File

@@ -1,203 +0,0 @@
using JetBrains.Annotations;
using NUnit.Framework;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.UnitTesting.Shared;
namespace Robust.Shared.IntegrationTests.Serialization;
[Serializable, NetSerializable]
[UsedImplicitly(Reason = "Needed so RobustSerializer is guaranteed to pick up on the unsafe types.")]
internal sealed class MakeTheseSerializable
{
public UnsafeFloat Single;
public UnsafeDouble Double;
public UnsafeHalf Half;
public Half SafeHalf;
}
/// <summary>
/// Tests the serialization behavior of float types when <see cref="IRobustSerializer"/> is *not* set to do anything special.
/// Tests both primitives and Robust's "Unsafe" variants.
/// </summary>
[TestFixture, TestOf(typeof(RobustSerializer)), TestOf(typeof(NetUnsafeFloatSerializer))]
internal sealed class NetSerializerDefaultFloatTest : OurRobustUnitTest
{
private IRobustSerializer _serializer = null!;
[OneTimeSetUp]
public void Setup()
{
_serializer = IoCManager.Resolve<IRobustSerializer>();
_serializer.Initialize();
}
internal static readonly TestCaseData[] PassThroughFloatTests =
[
new TestCaseData(0.0).Returns(0.0),
new TestCaseData(1.0).Returns(1.0),
new TestCaseData(double.NaN).Returns(double.NaN),
new TestCaseData(double.PositiveInfinity).Returns(double.PositiveInfinity),
];
[TestCaseSource(nameof(PassThroughFloatTests))]
public double TestSingle(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, (float)input);
ms.Position = 0;
return _serializer.Deserialize<float>(ms);
}
[TestCaseSource(nameof(PassThroughFloatTests))]
public double TestUnsafeSingle(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, (UnsafeFloat)input);
ms.Position = 0;
return _serializer.Deserialize<UnsafeFloat>(ms);
}
[TestCaseSource(nameof(PassThroughFloatTests))]
public double TestDouble(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, input);
ms.Position = 0;
return _serializer.Deserialize<double>(ms);
}
[TestCaseSource(nameof(PassThroughFloatTests))]
public double TestUnsafeDouble(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, (UnsafeDouble)input);
ms.Position = 0;
return _serializer.Deserialize<UnsafeDouble>(ms);
}
[TestCaseSource(nameof(PassThroughFloatTests))]
public double TestHalf(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, (Half)input);
ms.Position = 0;
return (double)_serializer.Deserialize<Half>(ms);
}
[TestCaseSource(nameof(PassThroughFloatTests))]
public double TestUnsafeHalf(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, (UnsafeHalf)(Half)input);
ms.Position = 0;
return (double)(Half)_serializer.Deserialize<UnsafeHalf>(ms);
}
}
/// <summary>
/// Tests the serialization behavior of float types when <see cref="IRobustSerializer"/> is set to remove NaNs on read.
/// Tests both primitives and Robust's "Unsafe" variants.
/// </summary>
[TestFixture]
[TestOf(typeof(RobustSerializer)), TestOf(typeof(NetUnsafeFloatSerializer)), TestOf(typeof(NetSafeFloatSerializer))]
internal sealed class NetSerializerSafeFloatTest : OurRobustUnitTest
{
private IRobustSerializer _serializer = default!;
[OneTimeSetUp]
public void Setup()
{
_serializer = IoCManager.Resolve<IRobustSerializer>();
_serializer.FloatFlags = SerializerFloatFlags.RemoveReadNan;
_serializer.Initialize();
}
internal static readonly TestCaseData[] SafeFloatTests =
[
new TestCaseData(0.0).Returns(0.0),
new TestCaseData(1.0).Returns(1.0),
new TestCaseData(double.NaN).Returns(0.0),
new TestCaseData(double.PositiveInfinity).Returns(double.PositiveInfinity),
];
[TestCaseSource(nameof(SafeFloatTests))]
public double TestSingle(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, (float)input);
ms.Position = 0;
return _serializer.Deserialize<float>(ms);
}
[TestCaseSource(typeof(NetSerializerDefaultFloatTest), nameof(NetSerializerDefaultFloatTest.PassThroughFloatTests))]
public double TestUnsafeSingle(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, (UnsafeFloat)input);
ms.Position = 0;
return _serializer.Deserialize<UnsafeFloat>(ms);
}
[TestCaseSource(nameof(SafeFloatTests))]
public double TestDouble(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, input);
ms.Position = 0;
return _serializer.Deserialize<double>(ms);
}
[TestCaseSource(typeof(NetSerializerDefaultFloatTest), nameof(NetSerializerDefaultFloatTest.PassThroughFloatTests))]
public double TestUnsafeDouble(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, (UnsafeDouble)input);
ms.Position = 0;
return _serializer.Deserialize<UnsafeDouble>(ms);
}
[TestCaseSource(nameof(SafeFloatTests))]
public double TestHalf(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, (Half)input);
ms.Position = 0;
return (double)_serializer.Deserialize<Half>(ms);
}
[TestCaseSource(typeof(NetSerializerDefaultFloatTest), nameof(NetSerializerDefaultFloatTest.PassThroughFloatTests))]
public double TestUnsafeHalf(double input)
{
var ms = new MemoryStream();
_serializer.Serialize(ms, (UnsafeHalf)(Half)input);
ms.Position = 0;
return (double)(Half)_serializer.Deserialize<UnsafeHalf>(ms);
}
}

View File

@@ -1,53 +0,0 @@
using System;
namespace Robust.Shared.Maths;
/// <summary>
/// Marker type to indicate floating point values that should preserve NaNs across the network.
/// </summary>
/// <remarks>
/// Robust's network serializer may be configured to flush NaN float values to 0,
/// to avoid exploits from lacking input validation. Even if this feature is enabled,
/// NaN values passed in this type are still untouched.
/// </remarks>
/// <param name="Value">The actual inner floating point value</param>
/// <seealso cref="System.Half"/>
public readonly record struct UnsafeHalf(Half Value)
{
public static implicit operator Half(UnsafeHalf f) => f.Value;
public static implicit operator UnsafeHalf(Half f) => new(f);
}
/// <summary>
/// Marker type to indicate floating point values that should preserve NaNs across the network.
/// </summary>
/// <remarks>
/// Robust's network serializer may be configured to flush NaN float values to 0,
/// to avoid exploits from lacking input validation. Even if this feature is enabled,
/// NaN values passed in this type are still untouched.
/// </remarks>
/// <param name="Value">The actual inner floating point value</param>
/// <seealso cref="System.Single"/>
public readonly record struct UnsafeFloat(float Value)
{
public static implicit operator float(UnsafeFloat f) => f.Value;
public static implicit operator UnsafeFloat(float f) => new(f);
}
/// <summary>
/// Marker type to indicate floating point values that should preserve NaNs across the network.
/// </summary>
/// <remarks>
/// Robust's network serializer may be configured to flush NaN float values to 0,
/// to avoid exploits from lacking input validation. Even if this feature is enabled,
/// NaN values passed in this type are still untouched.
/// </remarks>
/// <param name="Value">The actual inner floating point value</param>
/// <seealso cref="System.Double"/>
public readonly record struct UnsafeDouble(double Value)
{
public static implicit operator double(UnsafeDouble f) => f.Value;
public static implicit operator UnsafeDouble(double f) => new(f);
public static implicit operator UnsafeDouble(float f) => new(f);
public static implicit operator UnsafeDouble(UnsafeFloat f) => new(f);
}

View File

@@ -75,7 +75,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
return;
var audioLength = GetAudioLength(entity.Comp.FileName);
position = CalculateAudioPosition(entity!, (float)audioLength.TotalSeconds, position);
if (audioLength.TotalSeconds < position)
{
@@ -87,6 +86,12 @@ public abstract partial class SharedAudioSystem : EntitySystem
return;
}
if (position < 0f)
{
Log.Error($"Tried to set playback position for {ToPrettyString(entity.Owner)} / {entity.Comp.FileName} outside of bounds");
return;
}
// If we're paused then the current position is <pause time - start time>, else it's <cur time - start time>
var currentPos = (entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart;
var timeOffset = TimeSpan.FromSeconds(position - currentPos.TotalSeconds);
@@ -310,36 +315,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
return GetAudioPath(resolved);
}
/// <summary>
/// Calculates the current playback position of an audio entity
/// and clamps it to the range from 0 to (length - 0.01f).
/// </summary>
/// <param name="ent">The audio entity.</param>
/// <param name="length">
/// The total length of the audio file.
/// If null, the method retrieves the length using <see cref="GetAudioLength"/>.
/// </param>
/// <param name="position">
/// A precomputed playback position.
/// If provided, it will be added to the calculation.
/// </param>
/// <returns>The playback position as a float.</returns>
protected float CalculateAudioPosition(Entity<AudioComponent> ent, float? length = null, float? position = null)
{
position ??= (float) ((ent.Comp.PauseTime ?? Timing.CurTime) - ent.Comp.AudioStart).TotalSeconds;
length ??= (float) GetAudioLength(ent.Comp.FileName).TotalSeconds;
// Looped audio has no conceptual start or end.
if (ent.Comp.Params.Loop)
position %= length;
// TODO clamp the offset inside of AudioSource.SetPlaybackPosition() itself.
var maxOffset = Math.Max((float) length - 0.01f, 0f);
position = Math.Clamp(position.Value, 0f, maxOffset);
return position.Value;
}
#region AudioParams
[return: NotNullIfNotNull(nameof(specifier))]

View File

@@ -406,46 +406,6 @@ namespace Robust.Shared
public static readonly CVarDef<bool> NetHWId =
CVarDef.Create("net.hwid", true, CVar.SERVERONLY);
/**
* TRANSFER
*/
/// <summary>
/// If true, enable the WebSocket-based high bandwidth transfer channel.
/// </summary>
/// <remarks>
/// <para>
/// If set, <see cref="TransferHttpEndpoint"/> must be set to the API address of the server,
/// and you must ensure your reverse proxy (if you have one) is configured to allow WebSocket connections.
/// </para>
/// <para>
/// The transfer channel has no additional encryption layer. Unless your API is exposed behind HTTPS,
/// traffic over the channel will not be encrypted, and you are discouraged from enabling it.
/// </para>
/// </remarks>
public static readonly CVarDef<bool> TransferHttp =
CVarDef.Create("transfer.http", false, CVar.SERVERONLY);
/// <summary>
/// The base HTTP URL of the game server, used for the high-bandwidth transfer channel.
/// </summary>
public static readonly CVarDef<string> TransferHttpEndpoint =
CVarDef.Create("transfer.http_endpoint", "http://localhost:1212/", CVar.SERVERONLY);
/// <summary>
/// Amount of concurrent client->server transfer streams allowed.
/// </summary>
/// <remarks>
/// Clients will be disconnected if they exceed this limit.
/// </remarks>
public static readonly CVarDef<int> TransferStreamLimit =
CVarDef.Create("transfer.stream_limit", 10, CVar.SERVERONLY);
/// <summary>
/// Artificially delay transfer operations to simulate slow network. Debug option.
/// </summary>
internal static readonly CVarDef<bool> TransferArtificialDelay =
CVarDef.Create("transfer.artificial_delay", false);
/**
* SUS

View File

@@ -205,67 +205,60 @@ public abstract class ComponentTreeSystem<TTreeComp, TComp> : EntitySystem
/// </summary>
public void UpdateTreePositions()
{
try
if (!CheckEnabled())
return;
if (_updateQueue.Count == 0)
return;
var trees = GetEntityQuery<TTreeComp>();
while (_updateQueue.TryDequeue(out var entry))
{
if (!CheckEnabled())
return;
var (comp, xform) = entry;
if (_updateQueue.Count == 0)
return;
// Was this entity queued multiple times?
DebugTools.Assert(comp.TreeUpdateQueued, "Entity was queued multiple times?");
var trees = GetEntityQuery<TTreeComp>();
comp.TreeUpdateQueued = false;
if (!comp.Running)
continue;
while (_updateQueue.TryDequeue(out var entry))
if (!comp.AddToTree || comp.Deleted || xform.MapUid == null)
{
var (comp, xform) = entry;
// Was this entity queued multiple times?
DebugTools.Assert(comp.TreeUpdateQueued, "Entity was queued multiple times?");
comp.TreeUpdateQueued = false;
if (!comp.Running)
continue;
if (!comp.AddToTree || comp.Deleted || xform.MapUid == null)
{
RemoveFromTree(comp);
continue;
}
var newTree = xform.GridUid ?? xform.MapUid;
if (!trees.TryGetComponent(newTree, out var newTreeComp) && comp.TreeUid == null)
continue;
Vector2 pos;
Angle rot;
if (comp.TreeUid == newTree)
{
(pos, rot) = XformSystem.GetRelativePositionRotation(
entry.Transform,
newTree!.Value);
newTreeComp!.Tree.Update(entry, ExtractAabb(entry, pos, rot));
continue;
}
RemoveFromTree(comp);
continue;
}
if (newTreeComp == null)
return;
comp.TreeUid = newTree;
comp.Tree = newTreeComp.Tree;
var newTree = xform.GridUid ?? xform.MapUid;
if (!trees.TryGetComponent(newTree, out var newTreeComp) && comp.TreeUid == null)
continue;
Vector2 pos;
Angle rot;
if (comp.TreeUid == newTree)
{
(pos, rot) = XformSystem.GetRelativePositionRotation(
entry.Transform,
newTree!.Value);
newTreeComp.Tree.Add(entry, ExtractAabb(entry, pos, rot));
newTreeComp!.Tree.Update(entry, ExtractAabb(entry, pos, rot));
continue;
}
}
finally
{
_updateQueue.Clear();
RemoveFromTree(comp);
if (newTreeComp == null)
return;
comp.TreeUid = newTree;
comp.Tree = newTreeComp.Tree;
(pos, rot) = XformSystem.GetRelativePositionRotation(
entry.Transform,
newTree!.Value);
newTreeComp.Tree.Add(entry, ExtractAabb(entry, pos, rot));
}
}

View File

@@ -1,23 +0,0 @@
using System.Linq;
using Robust.Shared.IoC;
using Robust.Shared.Network;
namespace Robust.Shared.Console.Commands;
internal sealed class DumpStringTableCommand : IConsoleCommand
{
[Dependency] private readonly INetManager _netManager = default!;
public string Command => "net_dumpstringtable";
public string Description => "";
public string Help => "";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var netMgr = (NetManager)_netManager;
foreach (var (k, v) in netMgr.StringTable.Strings.OrderBy(x => x.Key))
{
shell.WriteLine($"{k}: {v}");
}
}
}

View File

@@ -101,7 +101,7 @@ public abstract partial class SharedContainerSystem
RaiseLocalEvent(container.Owner, new EntRemovedFromContainerMessage(toRemove, container), true);
RaiseLocalEvent(toRemove, new EntGotRemovedFromContainerMessage(toRemove, container), false);
DebugTools.Assert(destination == null || xform.Coordinates.Equals(destination.Value), $"Failed to set coordinates of {ToPrettyString(toRemove, meta)} to be inside {ToPrettyString(container.Owner)} container '{container.ID}'");
DebugTools.Assert(destination == null || xform.Coordinates.Equals(destination.Value), "failed to set destination");
Dirty(container.Owner, container.Manager);
return true;

View File

@@ -14,9 +14,12 @@ namespace Robust.Shared.ContentPack;
internal sealed partial class AssemblyTypeChecker
{
// This part of the code tries to find the originator of bad sandbox references.
private IEnumerable<(EntityHandle Referenced, MethodDefinitionHandle SourceMethod, int InstructionOffset)> FindReference(PEReader peReader, MetadataReader reader, params IEnumerable<EntityHandle> handles)
private void ReportBadReferences(PEReader peReader, MetadataReader reader, IEnumerable<EntityHandle> reference)
{
var refs = handles.ToHashSet();
_sawmill.Info("Started search for originator of bad references...");
var refs = reference.ToHashSet();
ExpandReferences(reader, refs);
foreach (var methodDefHandle in reader.MethodDefinitions)
@@ -25,6 +28,8 @@ 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()!;
@@ -36,7 +41,9 @@ internal sealed partial class AssemblyTypeChecker
{
if (refs.Overlaps(ExpandHandle(reader, handle)))
{
yield return (handle, methodDefHandle, prefPosition);
var type = GetTypeFromDefinition(reader, methodDef.GetDeclaringType());
_sawmill.Error(
$"Found reference to {DisplayHandle(reader, handle)} in method {type}.{methodName} at IL 0x{prefPosition:X4}");
}
}
@@ -45,19 +52,6 @@ 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)

View File

@@ -227,8 +227,6 @@ namespace Robust.Shared.ContentPack
#if TOOLS
if (!badRefs.IsEmpty)
{
_sawmill.Info("Started search for originator of bad references...");
ReportBadReferences(peReader, reader, badRefs);
}
#endif
@@ -300,9 +298,6 @@ 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");
@@ -315,24 +310,6 @@ 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);

View File

@@ -444,7 +444,6 @@ 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 }
@@ -1463,7 +1462,6 @@ 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 }

View File

@@ -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,7 +30,6 @@ public sealed partial class MapLoaderSystem
{
grids = null;
maps = null;
if (!TryLoadGeneric(file, out var data, options))
return false;
@@ -40,29 +39,33 @@ public sealed partial class MapLoaderSystem
}
/// <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.
/// Tries to load entities from a YAML file, taking in a raw byte stream.
/// </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(
TextReader reader,
string source,
[NotNullWhen(true)] out HashSet<Entity<MapComponent>>? maps,
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
Stream file,
string fileName,
[NotNullWhen(true)] out LoadResult? result,
MapLoadOptions? options = null)
{
grids = null;
maps = null;
if (!TryLoadGeneric(reader, source, out var data, options))
result = null;
if (!TryReadFile(new StreamReader(file), out var data))
return false;
maps = data.Maps;
grids = data.Grids;
return true;
return TryLoadGeneric(data, fileName, out result, options);
}
/// <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>
@@ -71,51 +74,15 @@ public sealed partial class MapLoaderSystem
{
result = null;
if (!TryReadFile(file.ToRootedPath(), out var data))
if (!TryReadFile(file, out var data))
return false;
return TryLoadGeneric(data, file.ToString(), 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="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(
private bool TryLoadGeneric(
MappingDataNode data,
string source,
string fileName,
[NotNullWhen(true)] out LoadResult? result,
MapLoadOptions? options = null)
{
@@ -151,7 +118,7 @@ public sealed partial class MapLoaderSystem
if (!deserializer.TryProcessData())
{
Log.Debug($"Failed to process entity data in {source}");
Log.Debug($"Failed to process entity data in {fileName}");
return false;
}
@@ -161,7 +128,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 {source} does not contain the expected data. Expected {expected} but got {deserializer.Result.Category}");
Log.Error($"Map {fileName} does not contain the expected data. Expected {expected} but got {deserializer.Result.Category}");
Delete(deserializer.Result);
return false;
}
@@ -172,7 +139,7 @@ public sealed partial class MapLoaderSystem
}
catch (Exception e)
{
Log.Error($"Caught exception while creating entities for map {source}: {e}");
Log.Error($"Caught exception while creating entities for map {fileName}: {e}");
Delete(deserializer.Result);
throw;
}
@@ -182,7 +149,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 {source} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
Log.Error($"Map {fileName} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
Delete(deserializer.Result);
return false;
}
@@ -217,33 +184,12 @@ public sealed partial class MapLoaderSystem
}
/// <summary>
/// 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.
/// 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.
/// </summary>
public bool TryLoadEntity(
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,
ResPath path,
[NotNullWhen(true)] out Entity<TransformComponent>? entity,
DeserializationOptions? options = null)
{
@@ -254,7 +200,7 @@ public sealed partial class MapLoaderSystem
};
entity = null;
if (!TryLoadGeneric(reader, source, out var result, opts))
if (!TryLoadGeneric(path, out var result, opts))
return false;
if (result.Orphans.Count == 1)
@@ -269,35 +215,12 @@ public sealed partial class MapLoaderSystem
}
/// <summary>
/// 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.
/// 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.
/// </summary>
public bool TryLoadGrid(
MapId map,
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,
ResPath path,
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
DeserializationOptions? options = null,
Vector2 offset = default,
@@ -313,7 +236,7 @@ public sealed partial class MapLoaderSystem
};
grid = null;
if (!TryLoadGeneric(reader, source, out var result, opts))
if (!TryLoadGeneric(path, out var result, opts))
return false;
if (result.Grids.Count == 1)
@@ -327,35 +250,11 @@ public sealed partial class MapLoaderSystem
}
/// <summary>
/// Tries to load a grid entity from a YAML file and parent it to a newly created map.
/// Tries to load a grid entity from a 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 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,
ResPath path,
[NotNullWhen(true)] out Entity<MapComponent>? map,
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
DeserializationOptions? options = null,
@@ -368,7 +267,7 @@ public sealed partial class MapLoaderSystem
if (opts.PauseMaps)
_mapSystem.SetPaused(mapUid, true);
if (!TryLoadGrid(mapId, reader, source, out grid, options, offset, rot))
if (!TryLoadGrid(mapId, path, out grid, options, offset, rot))
{
Del(mapUid);
map = null;

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Numerics;
using Robust.Shared.GameObjects;
@@ -16,41 +15,14 @@ namespace Robust.Shared.EntitySerialization.Systems;
public sealed partial class MapLoaderSystem
{
/// <summary>
/// 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.
/// 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.
/// </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 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,
ResPath path,
[NotNullWhen(true)] out Entity<MapComponent>? map,
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
DeserializationOptions? options = null,
@@ -67,7 +39,7 @@ public sealed partial class MapLoaderSystem
map = null;
grids = null;
if (!TryLoadGeneric(reader, source, out var result, opts))
if (!TryLoadGeneric(path, out var result, opts))
return false;
if (result.Maps.Count == 1)
@@ -82,47 +54,17 @@ public sealed partial class MapLoaderSystem
}
/// <summary>
/// Attempts to load a YAML file containing a single map, assign it the given map id.
/// Attempts to load a 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 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,
ResPath path,
[NotNullWhen(true)] out Entity<MapComponent>? map,
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
DeserializationOptions? options = null,
@@ -144,7 +86,7 @@ public sealed partial class MapLoaderSystem
throw new Exception($"Target map already exists");
opts.ForceMapId = mapId;
if (!TryLoadGeneric(reader, source, out var result, opts))
if (!TryLoadGeneric(path, out var result, opts))
return false;
if (!_mapSystem.TryGetMap(mapId, out var uid) || !TryComp(uid, out MapComponent? comp))
@@ -156,35 +98,12 @@ public sealed partial class MapLoaderSystem
}
/// <summary>
/// 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.
/// Attempts to load a 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,
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,
ResPath path,
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
DeserializationOptions? options = null,
Vector2 offset = default,
@@ -204,7 +123,7 @@ public sealed partial class MapLoaderSystem
throw new Exception($"Target map {mapId} does not exist");
opts.MergeMap = mapId;
if (!TryLoadGeneric(reader, source, out var result, opts))
if (!TryLoadGeneric(path, out var result, opts))
return false;
if (!_mapSystem.TryGetMap(mapId, out var uid) || !TryComp(uid, out MapComponent? comp))

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
@@ -60,19 +59,10 @@ 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 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)
public bool TrySaveEntity(EntityUid entity, ResPath path, SerializationOptions? options = null)
{
if (_mapQuery.HasComp(entity))
{
@@ -107,12 +97,12 @@ public sealed partial class MapLoaderSystem
return false;
}
Write(target, data);
Write(path, 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)
{
@@ -124,18 +114,9 @@ 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 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)
public bool TrySaveMap(EntityUid map, ResPath path, SerializationOptions? options = null)
{
if (!_mapQuery.HasComp(map))
{
@@ -164,23 +145,14 @@ public sealed partial class MapLoaderSystem
return false;
}
Write(target, data);
Write(path, 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 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)
public bool TrySaveGrid(EntityUid grid, ResPath path, SerializationOptions? options = null)
{
if (!_gridQuery.HasComp(grid))
{
@@ -215,62 +187,32 @@ public sealed partial class MapLoaderSystem
return false;
}
Write(target, data);
Write(path, data);
return true;
}
/// <summary>
/// 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.
/// 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.
/// </summary>
public bool TrySaveGeneric(
EntityUid uid,
ResPath target,
ResPath path,
out FileCategory category,
SerializationOptions? options = null)
{
using var writer = GetWriterForPath(target);
return TrySaveGeneric(uid, writer, out category, options);
return TrySaveGeneric([uid], path, out category, options);
}
/// <summary>
/// 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.
/// 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> entities,
TextWriter target,
ResPath path,
out FileCategory category,
SerializationOptions? options = null)
{
@@ -291,21 +233,10 @@ public sealed partial class MapLoaderSystem
return false;
}
Write(target, data);
Write(path, 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)
{

View File

@@ -42,29 +42,17 @@ 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);
Write(writer, data);
{
var stream = new YamlStream {document};
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
}
}
public bool TryReadFile(ResPath file, [NotNullWhen(true)] out MappingDataNode? data)

View File

@@ -32,8 +32,7 @@ namespace Robust.Shared.GameObjects
protected BoundUserInterface(EntityUid owner, Enum uiKey)
{
IoCManager.Resolve(ref EntMan);
EntMan.EntitySysManager.DependencyCollection.InjectDependencies(this);
IoCManager.InjectDependencies(this);
UiSystem = EntMan.System<SharedUserInterfaceSystem>();
Owner = owner;

View File

@@ -838,16 +838,18 @@ namespace Robust.Shared.GameObjects
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool EnsureComponent<T>(ref Entity<T?> entity) where T : IComponent, new()
{
if (entity.Comp == null)
return EnsureComponent<T>(entity.Owner, out entity.Comp);
if (entity.Comp != null)
{
// Check for deferred component removal.
if (entity.Comp.LifeStage <= ComponentLifeStage.Running)
{
DebugTools.AssertOwner(entity, entity.Comp);
return true;
}
DebugTools.AssertOwner(entity, entity.Comp);
RemoveComponent(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;
}

View File

@@ -366,8 +366,7 @@ namespace Robust.Shared.GameObjects
&& meta.EntityLifeStage < EntityLifeStage.Terminating)
{
coords = new EntityCoordinates(gridUid, _mapSystem.WorldToLocal(gridUid, grid, coordinates.Position));
var relativeRotation = rotation - _xforms.GetWorldRotation(gridUid);
_xforms.SetCoordinates(newEntity, transform, coords, relativeRotation, unanchor: false);
_xforms.SetCoordinates(newEntity, transform, coords, rotation, unanchor: false);
}
else
{

View File

@@ -3,7 +3,6 @@ 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;
@@ -193,14 +192,6 @@ 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)
@@ -323,10 +314,9 @@ namespace Robust.Shared.GameObjects
try
{
#endif
using (_profManager.Value(updReg.System.GetType().Name))
{
updReg.System.Update(frameTime);
}
var sw = ProfSampler.StartNew();
updReg.System.Update(frameTime);
_profManager.WriteValue(updReg.System.GetType().Name, sw);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
@@ -351,10 +341,9 @@ namespace Robust.Shared.GameObjects
try
{
#endif
using (_profManager.Value(system.GetType().Name))
{
system.FrameUpdate(frameTime);
}
var sw = ProfSampler.StartNew();
system.FrameUpdate(frameTime);
_profManager.WriteValue(system.GetType().Name, sw);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)

View File

@@ -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">World rotation to set the newly spawned entity to.</param>
/// <param name="rotation">Local 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>

View File

@@ -876,12 +876,12 @@ public abstract partial class SharedMapSystem
chunk.SuppressCollisionRegeneration = false;
}
RegenerateCollision(uid, grid, modified);
// Notify of all tile changes in one event
var ev = new TileChangedEvent((uid, grid), tileChanges.ToArray());
RaiseLocalEvent(uid, ref ev, true);
RegenerateCollision(uid, grid, modified);
// Back to normal
MapManager.SuppressOnTileChanged = false;
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -18,11 +17,6 @@ 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
{
@@ -43,12 +37,6 @@ 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>
@@ -60,8 +48,6 @@ 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.
@@ -93,8 +79,8 @@ namespace Robust.Shared.IoC
public IEnumerable<Type> GetRegisteredTypes()
{
return _parentCollection != null
? _services.Keys.Concat(_lazyServices.Keys).Concat(_parentCollection.GetRegisteredTypes())
: _services.Keys.Concat(_lazyServices.Keys);
? _services.Keys.Concat(_parentCollection.GetRegisteredTypes())
: _services.Keys;
}
public Type[] GetCachedInjectorTypes()
@@ -130,7 +116,10 @@ namespace Robust.Shared.IoC
FrozenDictionary<Type, object> services,
[MaybeNullWhen(false)] out object instance)
{
return TryResolveType(objectType, (IReadOnlyDictionary<Type, object>) services, out instance);
if (!services.TryGetValue(objectType, out instance))
return _parentCollection is not null && _parentCollection.TryResolveType(objectType, out instance);
return true;
}
private bool TryResolveType(
@@ -139,16 +128,7 @@ 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;
}
@@ -287,7 +267,7 @@ namespace Robust.Shared.IoC
_pendingResolves.Enqueue(interfaceType);
}
}
private void CheckRegisterInterface(Type interfaceType, Type implementationType, bool overwrite)
{
lock (_serviceBuildLock)
@@ -332,24 +312,15 @@ 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.Concat(_lazyServices.Values).OfType<IDisposable>().Distinct())
foreach (var service in _services.Values.OfType<IDisposable>().Distinct())
{
service.Dispose();
}
_services = FrozenDictionary<Type, object>.Empty;
_lazyServices.Clear();
lock (_serviceBuildLock)
{

View File

@@ -132,15 +132,6 @@ 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.

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Random;
@@ -13,12 +12,10 @@ 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>

View File

@@ -1,6 +1,5 @@
using System;
using System.Net;
using Lidgren.Network;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Network
@@ -103,13 +102,5 @@ namespace Robust.Shared.Network
/// <param name="reason">Reason why it was disconnected.</param>
/// <param name="sendBye">If false, we ghost the remote client and don't tell them they got disconnected properly.</param>
void Disconnect(string reason, bool sendBye);
/// <summary>
/// Check whether the networking layer has space to immediately send a message with the given parameters.
/// </summary>
/// <remarks>
/// If this returns true, messages may still be sent, but they will be queued until there is space available.
/// </remarks>
bool CanSendImmediately(NetDeliveryMethod method, int sequenceChannel);
}
}

View File

@@ -1,19 +0,0 @@
using Lidgren.Network;
using Robust.Shared.Serialization;
namespace Robust.Shared.Network.Messages.Transfer;
internal sealed class MsgTransferAckInit : NetMessage
{
public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableOrdered;
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
// No data needed.
}
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
// No data needed.
}
}

View File

@@ -1,36 +0,0 @@
using System;
using System.Buffers;
using Lidgren.Network;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Serialization;
namespace Robust.Shared.Network.Messages.Transfer;
internal sealed class MsgTransferData : NetMessage
{
internal const NetDeliveryMethod Method = NetDeliveryMethod.ReliableOrdered;
internal const int Channel = SequenceChannels.Transfer;
public override NetDeliveryMethod DeliveryMethod => Method;
public override int SequenceChannel => Channel;
public ArraySegment<byte> Data;
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
var length = buffer.ReadVariableInt32();
if (length > BaseTransferImpl.BufferSize)
throw new Exception("Buffer size is too large");
var arr = ArrayPool<byte>.Shared.Rent(length);
buffer.ReadBytes(arr, 0, length);
Data = new ArraySegment<byte>(arr, 0, length);
}
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
buffer.WriteVariableInt32(Data.Count);
buffer.Write(Data.AsSpan());
}
}

View File

@@ -1,44 +0,0 @@
using Lidgren.Network;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Serialization;
namespace Robust.Shared.Network.Messages.Transfer;
internal sealed class MsgTransferInit : NetMessage
{
public (string EndpointUrl, byte[] Key)? HttpInfo;
public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableOrdered;
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
var httpAvailable = buffer.ReadBoolean();
if (!httpAvailable)
{
HttpInfo = null;
return;
}
buffer.SkipPadBits();
var endpoint = buffer.ReadString();
var key = buffer.ReadBytes(TransferImplWebSocket.RandomKeyBytes);
HttpInfo = (endpoint, key);
}
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
if (HttpInfo is null)
{
buffer.Write(false);
return;
}
buffer.Write(true);
buffer.WritePadBits();
var (ep, key) = HttpInfo.Value;
buffer.Write(ep);
buffer.Write(key);
}
}

View File

@@ -102,11 +102,6 @@ namespace Robust.Shared.Network
_connection.Disconnect(reason, sendBye);
}
public bool CanSendImmediately(NetDeliveryMethod method, int sequenceChannel)
{
return _connection.CanSendImmediately(method, sequenceChannel);
}
public override string ToString()
{
return $"{ConnectionId}/{UserId}";

View File

@@ -64,7 +64,6 @@ public sealed partial class NetManager
var packet = BuildMessage(message, channel.Connection.Peer);
var method = message.DeliveryMethod;
var seqChannel = message.SequenceChannel;
LogSend(message, method, packet);
@@ -72,7 +71,6 @@ public sealed partial class NetManager
{
Message = packet,
Method = method,
SequenceChannel = seqChannel,
Owner = this,
RobustMessage = message,
};
@@ -116,7 +114,7 @@ public sealed partial class NetManager
{
channel.Encryption?.Encrypt(item.Message);
var result = channel.Connection.Peer.SendMessage(item.Message, channel.Connection, item.Method, item.SequenceChannel);
var result = channel.Connection.Peer.SendMessage(item.Message, channel.Connection, item.Method);
if (result is not (NetSendResult.Sent or NetSendResult.Queued))
{
// Logging stack trace here won't be useful as it'll likely be thread pooled on production scenarios.
@@ -129,7 +127,6 @@ public sealed partial class NetManager
{
public required NetOutgoingMessage Message;
public required NetDeliveryMethod Method;
public required int SequenceChannel;
public required NetMessage RobustMessage;
public required NetManager Owner;
}

View File

@@ -12,7 +12,6 @@ using Prometheus;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network.Transfer;
using Robust.Shared.Player;
using Robust.Shared.Profiling;
using Robust.Shared.Serialization;
@@ -113,7 +112,6 @@ namespace Robust.Shared.Network
[Dependency] private readonly ProfManager _prof = default!;
[Dependency] private readonly HttpClientHolder _http = default!;
[Dependency] private readonly IHWId _hwId = default!;
[Dependency] private readonly ITransferManager _transfer = default!;
/// <summary>
/// Holds lookup table for NetMessage.Id -> NetMessage.Type
@@ -142,10 +140,6 @@ namespace Robust.Shared.Network
private ISawmill _loggerPacket = default!;
private ISawmill _authLogger = default!;
private bool _clientSerializerComplete;
private bool _clientTransferComplete;
private bool _clientResetPending;
/// <inheritdoc />
public int Port => _config.GetCVar(CVars.NetPort);
@@ -153,8 +147,6 @@ namespace Robust.Shared.Network
public IReadOnlyDictionary<Type, long> MessageBandwidthUsage => _bandwidthUsage;
internal StringTable StringTable => _strings;
/// <inheritdoc />
public bool IsServer { get; private set; }
@@ -279,7 +271,6 @@ namespace Robust.Shared.Network
_strings.Initialize(() => { _logger.Info("Message string table loaded."); },
UpdateNetMessageFunctions);
_serializer.ClientHandshakeComplete += OnSerializerOnClientHandshakeComplete;
_transfer.ClientHandshakeComplete += OnTransferOnClientHandshakeComplete;
_initialized = true;
@@ -313,21 +304,7 @@ namespace Robust.Shared.Network
private void OnSerializerOnClientHandshakeComplete()
{
_logger.Info("Client completed serializer handshake.");
_clientSerializerComplete = true;
ClientCheckSwitchToConnected();
}
private void OnTransferOnClientHandshakeComplete()
{
_logger.Info("Client completed transfer handshake.");
_clientTransferComplete = true;
ClientCheckSwitchToConnected();
}
private void ClientCheckSwitchToConnected()
{
if (_clientSerializerComplete && _clientTransferComplete)
OnConnected(ServerChannelImpl!);
OnConnected(ServerChannelImpl!);
}
private void SynchronizeNetTime()
@@ -421,10 +398,6 @@ namespace Robust.Shared.Network
public void Reset(string reason)
{
_logger.Info($"Resetting NetManager: {reason}");
_clientResetPending = false;
foreach (var kvChannel in _channels)
{
DisconnectChannel(kvChannel.Value, reason);
@@ -454,9 +427,6 @@ namespace Robust.Shared.Network
_cancelConnectTokenSource?.Cancel();
ClientConnectState = ClientConnectionState.NotConnecting;
_clientSerializerComplete = false;
_clientTransferComplete = false;
}
/// <inheritdoc />
@@ -611,9 +581,6 @@ namespace Robust.Shared.Network
MessagesUnsentMetrics.Set(unsent);
MessagesStoredMetrics.Set(stored);
*/
if (_clientResetPending)
Reset("Channel closed");
}
/// <inheritdoc />
@@ -838,9 +805,7 @@ namespace Robust.Shared.Network
try
{
await Task.WhenAll(
_serializer.Handshake(channel),
_transfer.ServerHandshake(channel));
await _serializer.Handshake(channel);
}
catch (TaskCanceledException)
{
@@ -883,7 +848,14 @@ namespace Robust.Shared.Network
#endif
if (IsClient)
_clientResetPending = true;
{
connection.Peer.Shutdown(reason);
_toCleanNetPeers.Add(connection.Peer);
_strings.Reset();
_cancelConnectTokenSource?.Cancel();
ClientConnectState = ClientConnectionState.NotConnecting;
}
}
/// <inheritdoc />

View File

@@ -105,13 +105,5 @@ namespace Robust.Shared.Network
}
}
}
/// <summary>
/// The lidgren sequence channel to send this message on.
/// </summary>
/// <remarks>
/// Channels 16 and higher are reserved for internal RT usage.
/// </remarks>
public virtual int SequenceChannel => 0;
}
}

View File

@@ -1,8 +0,0 @@
namespace Robust.Shared.Network;
internal static class SequenceChannels
{
public const int EngineBase = 16;
public const int Transfer = EngineBase;
}

View File

@@ -38,8 +38,6 @@ namespace Robust.Shared.Network
private InitCallback? _callback;
private StringTableUpdateCallback? _updateCallback;
internal Dictionary<int, string> Strings => _strings;
public ISawmill Sawmill = default!;
/// <summary>

View File

@@ -1,436 +0,0 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Shared.Network.Transfer;
internal abstract class BaseTransferImpl(ISawmill sawmill, BaseTransferManager parent, INetChannel channel) : IDisposable
{
// Custom framing format is as follows.
// <header message>
// uint8 opcode
// uint8 flags
// int64 transfer ID
// [if start message]:
// uint8 key length
// byte[] key
// <data message>
// just the fucking data lol
internal const int BufferSize = 16384;
internal const int MaxKeySize = 96;
internal const int MaxHeaderSize = 128;
protected readonly INetChannel Channel = channel;
protected readonly ISawmill Sawmill = sawmill;
protected long OutgoingIdCounter;
public int MaxChannelCount = int.MaxValue;
private readonly Dictionary<long, ChannelWriter<ArraySegment<byte>>> _receivingChannels = [];
private readonly SemaphoreSlim _socketSemaphore = new(1, 1);
internal readonly BaseTransferManager Parent = parent;
public abstract Task ServerInit();
public abstract Task ClientInit(CancellationToken cancel);
public abstract Stream StartTransfer(TransferStartInfo startInfo);
protected abstract bool BoundedChannel { get; }
private void TransferReceived(string key, ChannelReader<ArraySegment<byte>> reader)
{
if (_receivingChannels.Count >= MaxChannelCount)
{
Sawmill.Warning($"Disconnecting client {Channel} for breaching max channel count of {_receivingChannels}");
Channel.Disconnect("Reached max transfer channel count");
return;
}
var stream = new ReceiveStream(reader);
Parent.TransferReceived(key, Channel, stream);
}
protected void HandleHeaderReceived(
ReadOnlyMemory<byte> data,
out TransferFlags flags,
out long transferId,
out ChannelWriter<ArraySegment<byte>> channel)
{
ParseHeader(data.Span, out flags, out transferId, out var key);
if (!_receivingChannels.TryGetValue(transferId, out channel!))
{
if ((flags & TransferFlags.Start) == 0)
throw new ProtocolViolationException($"Received data for unknown transfer {transferId}");
DebugTools.Assert(key != null);
Sawmill.Verbose($"Starting transfer stream {transferId} with key {key}");
var fullChannel = BoundedChannel
? System.Threading.Channels.Channel.CreateBounded<ArraySegment<byte>>(
new BoundedChannelOptions(4)
{
SingleReader = true,
SingleWriter = true
})
: System.Threading.Channels.Channel.CreateUnbounded<ArraySegment<byte>>(new UnboundedChannelOptions
{
SingleReader = true,
SingleWriter = true
});
channel = fullChannel.Writer;
_receivingChannels.Add(transferId, channel);
TransferReceived(key, fullChannel.Reader);
}
}
protected void HandlePostData(TransferFlags flags, long transferId, ChannelWriter<ArraySegment<byte>> channel)
{
if ((flags & TransferFlags.Finish) != 0)
{
Sawmill.Verbose($"Finishing transfer stream {transferId}");
channel.Complete();
_receivingChannels.Remove(transferId);
}
}
private static void ParseHeader(
ReadOnlySpan<byte> buf,
out TransferFlags flags,
out long transferId,
out string? key)
{
flags = (TransferFlags)buf[1];
transferId = BinaryPrimitives.ReadInt64LittleEndian(buf[2..10]);
if ((flags & TransferFlags.Start) != 0)
{
var keyLength = buf[10];
key = Encoding.UTF8.GetString(buf.Slice(11, keyLength));
}
else
{
key = null;
}
}
private sealed class ReceiveStream : SaneStream
{
private readonly ChannelReader<ArraySegment<byte>> _bufferChannel;
private ArraySegment<byte> _currentBuffer;
public override bool CanRead => true;
public ReceiveStream(ChannelReader<ArraySegment<byte>> bufferChannel)
{
_bufferChannel = bufferChannel;
}
public override int Read(Span<byte> buffer)
{
var read = 0;
var remainingSpan = buffer;
while (remainingSpan.Length > 0)
{
if (_currentBuffer.Array == null || _currentBuffer.Count <= 0)
{
if (_currentBuffer.Array != null)
{
ArrayPool<byte>.Shared.Return(_currentBuffer.Array);
_currentBuffer = default;
}
if (!_bufferChannel.TryRead(out _currentBuffer))
{
// Only block if we haven't read any bytes yet.
if (read > 0 || !ReadNewBufferSync())
return read;
}
}
DebugTools.Assert(_currentBuffer.Array != null);
var remainingBuffer = _currentBuffer.Count;
var thisRead = Math.Min(remainingSpan.Length, remainingBuffer);
_currentBuffer.AsSpan(0, thisRead).CopyTo(remainingSpan);
remainingSpan = remainingSpan[thisRead..];
_currentBuffer = _currentBuffer[thisRead..];
read += thisRead;
}
return read;
}
public override async ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default)
{
var read = 0;
var remainingSpan = buffer;
while (remainingSpan.Length > 0)
{
if (_currentBuffer.Array == null || _currentBuffer.Count <= 0)
{
if (_currentBuffer.Array != null)
{
ArrayPool<byte>.Shared.Return(_currentBuffer.Array);
_currentBuffer = default;
}
if (!_bufferChannel.TryRead(out _currentBuffer))
{
// Only block if we haven't read any bytes yet.
if (read > 0 || !await ReadNewBufferAsync())
return read;
}
}
DebugTools.Assert(_currentBuffer.Array != null);
var remainingBuffer = _currentBuffer.Count;
var thisRead = Math.Min(remainingSpan.Length, remainingBuffer);
_currentBuffer.AsMemory(0, thisRead).CopyTo(remainingSpan);
remainingSpan = remainingSpan[thisRead..];
_currentBuffer = _currentBuffer[thisRead..];
read += thisRead;
}
return read;
}
private bool ReadNewBufferSync()
{
DebugTools.Assert(_currentBuffer.Array == null);
var waitToRead = _bufferChannel.WaitToReadAsync();
#pragma warning disable RA0004
var waitToReadResult = waitToRead.AsTask().Result;
#pragma warning restore RA0004
if (!waitToReadResult)
return false;
return _bufferChannel.TryRead(out _currentBuffer);
}
private async Task<bool> ReadNewBufferAsync()
{
DebugTools.Assert(_currentBuffer.Array == null);
var waitToRead = await _bufferChannel.WaitToReadAsync();
if (!waitToRead)
return false;
return _bufferChannel.TryRead(out _currentBuffer);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing && _currentBuffer.Array != null)
ArrayPool<byte>.Shared.Return(_currentBuffer.Array);
}
}
protected abstract class ChunkedSendStream : SaneStream
{
protected readonly BaseTransferImpl Parent;
private readonly long _id;
private readonly string _key;
private readonly byte[] _headerBuffer;
private readonly byte[] _dataBuffer;
private bool _isFirstTransmission = true;
private int _bufferPos;
public override bool CanWrite => true;
public ChunkedSendStream(BaseTransferImpl parent, long id, string key)
{
// This just has to be < buffer size & < ushort.MaxValue
// (when accounting for UTF-8 possibly being more code units than UTF-16)
if (Encoding.UTF8.GetByteCount(key) > MaxKeySize)
throw new ArgumentException("Key too long");
Parent = parent;
_id = id;
_key = key;
_headerBuffer = ArrayPool<byte>.Shared.Rent(MaxHeaderSize);
_dataBuffer = ArrayPool<byte>.Shared.Rent(BufferSize);
}
public override void Write(ReadOnlySpan<byte> buffer)
{
while (buffer.Length > 0)
{
var remainingBufferSpace = _dataBuffer.AsSpan(_bufferPos);
var thisChunk = Math.Min(remainingBufferSpace.Length, buffer.Length);
var thisSpan = buffer[..thisChunk];
thisSpan.CopyTo(remainingBufferSpace);
_bufferPos += thisChunk;
if (_bufferPos == _dataBuffer.Length)
Flush();
buffer = buffer[thisChunk..];
}
}
public override async ValueTask WriteAsync(
ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken = default)
{
while (buffer.Length > 0)
{
var remainingBufferSpace = _dataBuffer.AsSpan(_bufferPos);
var thisChunk = Math.Min(remainingBufferSpace.Length, buffer.Length);
var thisSpan = buffer[..thisChunk];
thisSpan.Span.CopyTo(remainingBufferSpace);
_bufferPos += thisChunk;
if (_bufferPos == _dataBuffer.Length)
await FlushAsync(cancellationToken).ConfigureAwait(false);
buffer = buffer[thisChunk..];
}
}
public override void Flush()
{
FlushAsync().Wait();
}
public override async Task FlushAsync(CancellationToken cancellationToken)
{
await FlushAsync(finish: false, cancellationToken).ConfigureAwait(false);
}
private async ValueTask FlushAsync(bool finish, CancellationToken cancel = default)
{
var headerLength = 10;
var opcode = Opcode.Transfer;
var flags = TransferFlags.None;
if (_isFirstTransmission)
flags |= TransferFlags.Start;
if (_bufferPos > 0)
flags |= TransferFlags.HasData;
if (finish)
flags |= TransferFlags.Finish;
if (flags == TransferFlags.None)
{
// Nothing to flush, whatsoever.
return;
}
_headerBuffer[0] = (byte)opcode;
_headerBuffer[1] = (byte)flags;
BinaryPrimitives.WriteInt64LittleEndian(_headerBuffer.AsSpan(2..10), _id);
if (_isFirstTransmission)
{
var written = Encoding.UTF8.GetBytes(_key, _headerBuffer.AsSpan(11..));
DebugTools.Assert(written < byte.MaxValue);
_headerBuffer[10] = (byte)written;
headerLength += 1;
headerLength += written;
}
// Send.
using (await Parent._socketSemaphore.WaitGuardAsync().ConfigureAwait(false))
{
await SendChunkAsync(
new ArraySegment<byte>(_headerBuffer, 0, headerLength),
cancel)
.ConfigureAwait(false);
if (_bufferPos > 0)
{
await SendChunkAsync(
new ArraySegment<byte>(_dataBuffer, 0, _bufferPos),
cancel)
.ConfigureAwait(false);
_bufferPos = 0;
}
}
_isFirstTransmission = false;
}
protected abstract ValueTask SendChunkAsync(
ArraySegment<byte> buffer,
CancellationToken cancellationToken);
protected override void Dispose(bool disposing)
{
FlushAsync(finish: true).AsTask().Wait();
DisposeCore();
}
public override async ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
await FlushAsync(finish: true).ConfigureAwait(false);
DisposeCore();
}
private void DisposeCore()
{
ArrayPool<byte>.Shared.Return(_dataBuffer);
ArrayPool<byte>.Shared.Return(_headerBuffer);
}
~ChunkedSendStream()
{
// Have to do this so the stream isn't permanently hanging on the receiving side.
FlushAsync(finish: true).AsTask().Wait();
}
}
public virtual void Dispose()
{
foreach (var channel in _receivingChannels.Values)
{
channel.Complete();
}
}
protected enum Opcode : byte
{
Transfer = 0,
}
[Flags]
protected enum TransferFlags : byte
{
None = 0,
Start = 1 << 0,
Finish = 1 << 1,
HasData = 1 << 2,
}
}

View File

@@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.Collections;
using Robust.Shared.Network.Messages.Transfer;
namespace Robust.Shared.Network.Transfer;
internal abstract partial class BaseTransferManager
{
private readonly Lock _waitingSendChannelLock = new();
private readonly Dictionary<INetChannel, TaskCompletionSource> _waitingSendChannels = [];
private ValueList<(INetChannel, TaskCompletionSource)> _sendChannelQueue;
public void FrameUpdate()
{
lock (_waitingSendChannelLock)
{
foreach (var (channel, tcs) in _waitingSendChannels)
{
if (!channel.IsConnected || SendCheck(channel))
_sendChannelQueue.Add((channel, tcs));
}
// Remove BEFORE dispatching any TCSes, so we don't try to add to the list from a callback.
foreach (var (channel, _) in _sendChannelQueue)
{
_waitingSendChannels.Remove(channel);
}
}
foreach (var (channel, tcs) in _sendChannelQueue)
{
if (!channel.IsConnected)
tcs.TrySetException(new NetChannelClosedException("Channel closed"));
else
tcs.TrySetResult();
}
_sendChannelQueue.Clear();
}
public async ValueTask WaitToSend(INetChannel channel)
{
if (SendCheck(channel))
return;
TaskCompletionSource tcs;
lock (_waitingSendChannelLock)
{
ref var tcsSlot = ref CollectionsMarshal.GetValueRefOrAddDefault(_waitingSendChannels, channel, out _);
tcsSlot ??= new TaskCompletionSource();
tcs = tcsSlot;
}
await tcs.Task;
}
private static bool SendCheck(INetChannel channel)
{
return channel.CanSendImmediately(MsgTransferData.Method, MsgTransferData.Channel);
}
private sealed class NetChannelClosedException(string message) : Exception(message);
}

Some files were not shown because too many files have changed in this diff Show More