mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
1 Commits
master
...
revert-613
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c65f141790 |
@@ -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" />
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
<Project>
|
||||
@@ -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>
|
||||
|
||||
5739
RELEASE-NOTES.md
5739
RELEASE-NOTES.md
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
));
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -18,8 +18,6 @@ namespace Robust.Client.Graphics
|
||||
[NotContentImplementable]
|
||||
public interface IClyde
|
||||
{
|
||||
internal bool IsInitialized { get; }
|
||||
|
||||
IClydeWindow MainWindow { get; }
|
||||
IRenderTarget MainWindowRenderTarget => MainWindow.RenderTarget;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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!;
|
||||
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,5 @@ namespace Robust.Server.ViewVariables
|
||||
object Object { get; }
|
||||
uint SessionId { get; }
|
||||
Type ObjectType { get; }
|
||||
Action<object>? ObjectChangeDelegate { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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))]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Robust.Shared.Network;
|
||||
|
||||
internal static class SequenceChannels
|
||||
{
|
||||
public const int EngineBase = 16;
|
||||
|
||||
public const int Transfer = EngineBase;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user