mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b65e0c64ea | ||
|
|
4c388bc03d | ||
|
|
658dee1591 | ||
|
|
b6e5cca127 | ||
|
|
da5416a2da | ||
|
|
021845d956 | ||
|
|
7fab9f3b8d | ||
|
|
69c1161562 | ||
|
|
095fe9d60f | ||
|
|
14138fbcc2 | ||
|
|
48ce24e98b | ||
|
|
9cde21a7b3 | ||
|
|
ae1051e813 | ||
|
|
a3f80ac7dd | ||
|
|
f98ef78a21 | ||
|
|
bf8054b181 | ||
|
|
6b875e6676 | ||
|
|
a687c0a6c0 | ||
|
|
0580cf3ff7 | ||
|
|
590964d5bf | ||
|
|
ceda39813d |
@@ -43,7 +43,7 @@
|
||||
<PackageVersion Include="NUnit.Analyzers" Version="3.10.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
<PackageVersion Include="Nett" Version="0.15.0" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
|
||||
<PackageVersion Include="Pidgin" Version="3.2.2" />
|
||||
@@ -55,8 +55,8 @@
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
@@ -71,4 +71,4 @@
|
||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||
<PackageVersion Include="PolySharp" Version="1.14.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,51 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 226.2.2
|
||||
|
||||
|
||||
## 226.2.1
|
||||
|
||||
|
||||
## 226.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* `Control.VisibilityChanged()` virtual function.
|
||||
* Add some System.Random methods for NextFloat and NextPolarVector2.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes ContainerSystem failing client-side debug asserts when an entity gets unanchored & inserted into a container on the same tick.
|
||||
* Remove potential race condition on server startup from invoking ThreadPool.SetMinThreads.
|
||||
|
||||
### Other
|
||||
|
||||
* Increase default value of res.rsi_atlas_size.
|
||||
* Fix internal networking logic.
|
||||
* Updates of `OutputPanel` contents caused by change in UI scale are now deferred until visible. Especially important to avoid updates from debug console.
|
||||
* Debug console is now limited to only keep `con.max_entries` entries.
|
||||
* Non-existent resources are cached by `IResourceCache.TryGetResource`. This avoids the game constantly trying to re-load non-existent resources in common patterns such as UI theme texture fallbacks.
|
||||
* Default IPv4 MTU has been lowered to 700.
|
||||
* Update Robust.LoaderApi.
|
||||
|
||||
### Internal
|
||||
|
||||
* Split out PVS serialization from compression and sending game states.
|
||||
* Turn broadphase contacts into an IParallelRobustJob and remove unnecessary GetMapEntityIds for every contact.
|
||||
|
||||
|
||||
## 226.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add some GetLocalEntitiesIntersecting methods for `Entity<T>`.
|
||||
|
||||
### Other
|
||||
|
||||
* Fix internal networking logic
|
||||
|
||||
|
||||
## 226.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
@@ -26,7 +26,8 @@ public sealed class DefaultSQLConfig : IConfig
|
||||
|
||||
public IEnumerable<IExporter> GetExporters()
|
||||
{
|
||||
yield return SQLExporter.Default;
|
||||
//yield return SQLExporter.Default;
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<IColumnProvider> GetColumnProviders() => DefaultConfig.Instance.GetColumnProviders();
|
||||
|
||||
@@ -15,11 +15,10 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Npgsql;
|
||||
using Npgsql.Internal;
|
||||
using Npgsql.Internal.TypeHandlers;
|
||||
using Npgsql.Internal.TypeHandling;
|
||||
|
||||
namespace Robust.Benchmarks.Exporters;
|
||||
|
||||
/*
|
||||
public sealed class SQLExporter : IExporter
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions
|
||||
@@ -98,7 +97,9 @@ public sealed class SQLExporter : IExporter
|
||||
|
||||
public string Name => "sql";
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// https://github.com/npgsql/efcore.pg/issues/1107#issuecomment-945126627
|
||||
class JsonOverrideTypeHandlerResolverFactory : TypeHandlerResolverFactory
|
||||
{
|
||||
@@ -138,6 +139,7 @@ class JsonOverrideTypeHandlerResolverFactory : TypeHandlerResolverFactory
|
||||
=> null; // Let the built-in resolver do this
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public sealed class DesignTimeContextFactoryPostgres : IDesignTimeDbContextFactory<BenchmarkContext>
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var type = Type.GetType(args[0]);
|
||||
var type = GetType(args[0]);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
|
||||
shell.WriteLine(sig);
|
||||
}
|
||||
}
|
||||
|
||||
private Type? GetType(string name)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (assembly.GetType(name) is { } type)
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
sawmill.Debug("Preloading textures...");
|
||||
var sw = Stopwatch.StartNew();
|
||||
var resList = GetTypeDict<TextureResource>();
|
||||
var resList = GetTypeData<TextureResource>().Resources;
|
||||
|
||||
var texList = _manager.ContentFindFiles("/Textures/")
|
||||
// Skip PNG files inside RSIs.
|
||||
@@ -119,7 +119,7 @@ namespace Robust.Client.ResourceManagement
|
||||
private void PreloadRsis(ISawmill sawmill)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var resList = GetTypeDict<RSIResource>();
|
||||
var resList = GetTypeData<RSIResource>().Resources;
|
||||
|
||||
var rsiList = _manager.ContentFindFiles("/Textures/")
|
||||
.Where(p => p.ToString().EndsWith(".rsi/meta.json"))
|
||||
|
||||
@@ -17,9 +17,7 @@ namespace Robust.Client.ResourceManagement;
|
||||
/// </summary>
|
||||
internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInternal, IDisposable
|
||||
{
|
||||
private readonly Dictionary<Type, Dictionary<ResPath, BaseResource>> _cachedResources =
|
||||
new();
|
||||
|
||||
private readonly Dictionary<Type, TypeData> _cachedResources = new();
|
||||
private readonly Dictionary<Type, BaseResource> _fallbacks = new();
|
||||
|
||||
public T GetResource<T>(string path, bool useFallback = true) where T : BaseResource, new()
|
||||
@@ -29,8 +27,8 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
|
||||
public T GetResource<T>(ResPath path, bool useFallback = true) where T : BaseResource, new()
|
||||
{
|
||||
var cache = GetTypeDict<T>();
|
||||
if (cache.TryGetValue(path, out var cached))
|
||||
var cache = GetTypeData<T>();
|
||||
if (cache.Resources.TryGetValue(path, out var cached))
|
||||
{
|
||||
return (T) cached;
|
||||
}
|
||||
@@ -40,7 +38,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
{
|
||||
var dependencies = IoCManager.Instance!;
|
||||
resource.Load(dependencies, path);
|
||||
cache[path] = resource;
|
||||
cache.Resources[path] = resource;
|
||||
return resource;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -67,24 +65,31 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
|
||||
public bool TryGetResource<T>(ResPath path, [NotNullWhen(true)] out T? resource) where T : BaseResource, new()
|
||||
{
|
||||
var cache = GetTypeDict<T>();
|
||||
if (cache.TryGetValue(path, out var cached))
|
||||
var cache = GetTypeData<T>();
|
||||
if (cache.Resources.TryGetValue(path, out var cached))
|
||||
{
|
||||
resource = (T) cached;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cache.NonExistent.Contains(path))
|
||||
{
|
||||
resource = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var _resource = new T();
|
||||
try
|
||||
{
|
||||
var dependencies = IoCManager.Instance!;
|
||||
_resource.Load(dependencies, path);
|
||||
resource = _resource;
|
||||
cache[path] = resource;
|
||||
cache.Resources[path] = resource;
|
||||
return true;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
cache.NonExistent.Add(path);
|
||||
resource = null;
|
||||
return false;
|
||||
}
|
||||
@@ -109,9 +114,9 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
|
||||
public void ReloadResource<T>(ResPath path) where T : BaseResource, new()
|
||||
{
|
||||
var cache = GetTypeDict<T>();
|
||||
var cache = GetTypeData<T>();
|
||||
|
||||
if (!cache.TryGetValue(path, out var res))
|
||||
if (!cache.Resources.TryGetValue(path, out var res))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -145,7 +150,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
|
||||
public void CacheResource<T>(ResPath path, T resource) where T : BaseResource, new()
|
||||
{
|
||||
GetTypeDict<T>()[path] = resource;
|
||||
GetTypeData<T>().Resources[path] = resource;
|
||||
}
|
||||
|
||||
public T GetFallback<T>() where T : BaseResource, new()
|
||||
@@ -168,7 +173,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
|
||||
public IEnumerable<KeyValuePair<ResPath, T>> GetAllResources<T>() where T : BaseResource, new()
|
||||
{
|
||||
return GetTypeDict<T>().Select(p => new KeyValuePair<ResPath, T>(p.Key, (T) p.Value));
|
||||
return GetTypeData<T>().Resources.Select(p => new KeyValuePair<ResPath, T>(p.Key, (T) p.Value));
|
||||
}
|
||||
|
||||
public event Action<TextureLoadedEventArgs>? OnRawTextureLoaded;
|
||||
@@ -193,7 +198,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
foreach (var res in _cachedResources.Values.SelectMany(dict => dict.Values))
|
||||
foreach (var res in _cachedResources.Values.SelectMany(dict => dict.Resources.Values))
|
||||
{
|
||||
res.Dispose();
|
||||
}
|
||||
@@ -210,15 +215,9 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
#endregion IDisposable Members
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<ResPath, BaseResource> GetTypeDict<T>()
|
||||
private TypeData GetTypeData<T>()
|
||||
{
|
||||
if (!_cachedResources.TryGetValue(typeof(T), out var ret))
|
||||
{
|
||||
ret = new Dictionary<ResPath, BaseResource>();
|
||||
_cachedResources.Add(typeof(T), ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return _cachedResources.GetOrNew(typeof(T));
|
||||
}
|
||||
|
||||
public void TextureLoaded(TextureLoadedEventArgs eventArgs)
|
||||
@@ -230,4 +229,13 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
{
|
||||
OnRsiLoaded?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
private sealed class TypeData
|
||||
{
|
||||
public readonly Dictionary<ResPath, BaseResource> Resources = new();
|
||||
|
||||
// List of resources which DON'T exist.
|
||||
// Needed to avoid innocuous TryGet calls repeatedly trying to re-load non-existent resources from disk.
|
||||
public readonly HashSet<ResPath> NonExistent = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,9 +212,18 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when this control's visibility in the control tree changed.
|
||||
/// </summary>
|
||||
protected virtual void VisibilityChanged(bool newVisible)
|
||||
{
|
||||
}
|
||||
|
||||
private void _propagateVisibilityChanged(bool newVisible)
|
||||
{
|
||||
VisibilityChanged(newVisible);
|
||||
OnVisibilityChanged?.Invoke(this);
|
||||
|
||||
if (!VisibleInTree)
|
||||
{
|
||||
UserInterfaceManagerInternal.ControlHidden(this);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -20,7 +19,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public const string StylePropertyStyleBox = "stylebox";
|
||||
|
||||
private readonly List<RichTextEntry> _entries = new();
|
||||
private readonly RingBufferList<RichTextEntry> _entries = new();
|
||||
private bool _isAtBottom = true;
|
||||
|
||||
private int _totalContentHeight;
|
||||
@@ -30,6 +29,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public bool ScrollFollowing { get; set; } = true;
|
||||
|
||||
private bool _invalidOnVisible;
|
||||
|
||||
public OutputPanel()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -45,6 +46,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_scrollBar.OnValueChanged += _ => _isAtBottom = _scrollBar.IsAtEnd;
|
||||
}
|
||||
|
||||
public int EntryCount => _entries.Count;
|
||||
|
||||
public StyleBox? StyleBoxOverride
|
||||
{
|
||||
get => _styleBoxOverride;
|
||||
@@ -91,7 +94,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
var entry = new RichTextEntry(message, this, _tagManager, null);
|
||||
|
||||
entry.Update(_getFont(), _getContentBox().Width, UIScale);
|
||||
entry.Update(_tagManager, _getFont(), _getContentBox().Width, UIScale);
|
||||
|
||||
_entries.Add(entry);
|
||||
var font = _getFont();
|
||||
@@ -134,7 +137,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
// So when a new color tag gets hit this stack gets the previous color pushed on.
|
||||
var context = new MarkupDrawingContext(2);
|
||||
|
||||
foreach (ref var entry in CollectionsMarshal.AsSpan(_entries))
|
||||
foreach (ref var entry in _entries)
|
||||
{
|
||||
if (entryOffset + entry.Height < 0)
|
||||
{
|
||||
@@ -147,7 +150,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
break;
|
||||
}
|
||||
|
||||
entry.Draw(handle, font, contentBox, entryOffset, context, UIScale);
|
||||
entry.Draw(_tagManager, handle, font, contentBox, entryOffset, context, UIScale);
|
||||
|
||||
entryOffset += entry.Height + font.GetLineSeparation(UIScale);
|
||||
}
|
||||
@@ -185,9 +188,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_totalContentHeight = 0;
|
||||
var font = _getFont();
|
||||
var sizeX = _getContentBox().Width;
|
||||
foreach (ref var entry in CollectionsMarshal.AsSpan(_entries))
|
||||
foreach (ref var entry in _entries)
|
||||
{
|
||||
entry.Update(font, sizeX, UIScale);
|
||||
entry.Update(_tagManager, font, sizeX, UIScale);
|
||||
_totalContentHeight += entry.Height + font.GetLineSeparation(UIScale);
|
||||
}
|
||||
|
||||
@@ -239,7 +242,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
protected internal override void UIScaleChanged()
|
||||
{
|
||||
_invalidateEntries();
|
||||
// If this control isn't visible, don't invalidate entries immediately.
|
||||
// This saves invalidating the debug console if it's hidden,
|
||||
// which is a huge boon as auto-scaling changes UI scale a lot in that scenario.
|
||||
if (!VisibleInTree)
|
||||
_invalidOnVisible = true;
|
||||
else
|
||||
_invalidateEntries();
|
||||
|
||||
base.UIScaleChanged();
|
||||
}
|
||||
@@ -257,5 +266,14 @@ namespace Robust.Client.UserInterface.Controls
|
||||
// existing ones were valid when the UI scale was set.
|
||||
_invalidateEntries();
|
||||
}
|
||||
|
||||
protected override void VisibilityChanged(bool newVisible)
|
||||
{
|
||||
if (newVisible && _invalidOnVisible)
|
||||
{
|
||||
_invalidateEntries();
|
||||
_invalidOnVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
var font = _getFont();
|
||||
_entry.Update(font, availableSize.X * UIScale, UIScale, LineHeightScale);
|
||||
_entry.Update(_tagManager, font, availableSize.X * UIScale, UIScale, LineHeightScale);
|
||||
|
||||
return new Vector2(_entry.Width / UIScale, _entry.Height / UIScale);
|
||||
}
|
||||
@@ -82,7 +82,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
_entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale);
|
||||
_entry.Draw(_tagManager, handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
|
||||
@@ -13,6 +13,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
public override float UIScale => UIScaleSet;
|
||||
internal float UIScaleSet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set after the window is resized, to batch up UI scale updates on window resizes.
|
||||
/// </summary>
|
||||
internal bool UIScaleUpdateNeeded { get; set; }
|
||||
|
||||
public override IClydeWindow Window { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Input;
|
||||
@@ -51,6 +52,8 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
private readonly ConcurrentQueue<FormattedMessage> _messageQueue = new();
|
||||
private readonly ISawmill _logger;
|
||||
|
||||
private int _maxEntries;
|
||||
|
||||
public DebugConsole()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -78,6 +81,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
_consoleHost.AddString += OnAddString;
|
||||
_consoleHost.AddFormatted += OnAddFormatted;
|
||||
_consoleHost.ClearText += OnClearText;
|
||||
_cfg.OnValueChanged(CVars.ConMaxEntries, MaxEntriesChanged, true);
|
||||
|
||||
UserInterfaceManager.ModalRoot.AddChild(_compPopup);
|
||||
}
|
||||
@@ -89,10 +93,17 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
_consoleHost.AddString -= OnAddString;
|
||||
_consoleHost.AddFormatted -= OnAddFormatted;
|
||||
_consoleHost.ClearText -= OnClearText;
|
||||
_cfg.UnsubValueChanged(CVars.ConMaxEntries, MaxEntriesChanged);
|
||||
|
||||
UserInterfaceManager.ModalRoot.RemoveChild(_compPopup);
|
||||
}
|
||||
|
||||
private void MaxEntriesChanged(int value)
|
||||
{
|
||||
_maxEntries = value;
|
||||
TrimExtraOutputEntries();
|
||||
}
|
||||
|
||||
private void OnClearText(object? _, EventArgs args)
|
||||
{
|
||||
Clear();
|
||||
@@ -165,6 +176,15 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
private void _addFormattedLineInternal(FormattedMessage message)
|
||||
{
|
||||
Output.AddMessage(message);
|
||||
TrimExtraOutputEntries();
|
||||
}
|
||||
|
||||
private void TrimExtraOutputEntries()
|
||||
{
|
||||
while (Output.EntryCount > _maxEntries)
|
||||
{
|
||||
Output.RemoveEntry(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void _flushQueue()
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace Robust.Client.UserInterface
|
||||
internal struct RichTextEntry
|
||||
{
|
||||
private readonly Color _defaultColor;
|
||||
private readonly MarkupTagManager _tagManager;
|
||||
private readonly Type[]? _tagsAllowed;
|
||||
|
||||
public readonly FormattedMessage Message;
|
||||
@@ -37,7 +36,7 @@ namespace Robust.Client.UserInterface
|
||||
/// </summary>
|
||||
public ValueList<int> LineBreaks;
|
||||
|
||||
private readonly Dictionary<int, Control> _tagControls = new();
|
||||
private readonly Dictionary<int, Control>? _tagControls;
|
||||
|
||||
public RichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
@@ -46,23 +45,26 @@ namespace Robust.Client.UserInterface
|
||||
Width = 0;
|
||||
LineBreaks = default;
|
||||
_defaultColor = defaultColor ?? new(200, 200, 200);
|
||||
_tagManager = tagManager;
|
||||
_tagsAllowed = tagsAllowed;
|
||||
Dictionary<int, Control>? tagControls = null;
|
||||
|
||||
var nodeIndex = -1;
|
||||
foreach (var node in Message.Nodes)
|
||||
foreach (var node in Message)
|
||||
{
|
||||
nodeIndex++;
|
||||
|
||||
if (node.Name == null)
|
||||
continue;
|
||||
|
||||
if (!_tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag) || !tag.TryGetControl(node, out var control))
|
||||
if (!tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag) || !tag.TryGetControl(node, out var control))
|
||||
continue;
|
||||
|
||||
parent.Children.Add(control);
|
||||
_tagControls.Add(nodeIndex, control);
|
||||
tagControls ??= new Dictionary<int, Control>();
|
||||
tagControls.Add(nodeIndex, control);
|
||||
}
|
||||
|
||||
_tagControls = tagControls;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,7 +74,7 @@ namespace Robust.Client.UserInterface
|
||||
/// <param name="maxSizeX">The maximum horizontal size of the container of this entry.</param>
|
||||
/// <param name="uiScale"></param>
|
||||
/// <param name="lineHeightScale"></param>
|
||||
public void Update(Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1)
|
||||
public void Update(MarkupTagManager tagManager, Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1)
|
||||
{
|
||||
// This method is gonna suck due to complexity.
|
||||
// Bear with me here.
|
||||
@@ -91,10 +93,10 @@ namespace Robust.Client.UserInterface
|
||||
// Nodes can change the markup drawing context and return additional text.
|
||||
// It's also possible for nodes to return inline controls. They get treated as one large rune.
|
||||
var nodeIndex = -1;
|
||||
foreach (var node in Message.Nodes)
|
||||
foreach (var node in Message)
|
||||
{
|
||||
nodeIndex++;
|
||||
var text = ProcessNode(node, context);
|
||||
var text = ProcessNode(tagManager, node, context);
|
||||
|
||||
if (!context.Font.TryPeek(out var font))
|
||||
font = defaultFont;
|
||||
@@ -113,7 +115,7 @@ namespace Robust.Client.UserInterface
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_tagControls.TryGetValue(nodeIndex, out var control))
|
||||
if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
|
||||
continue;
|
||||
|
||||
if (ProcessRune(ref this, new Rune(' '), out breakLine))
|
||||
@@ -166,6 +168,7 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
|
||||
public readonly void Draw(
|
||||
MarkupTagManager tagManager,
|
||||
DrawingHandleScreen handle,
|
||||
Font defaultFont,
|
||||
UIBox2 drawBox,
|
||||
@@ -184,10 +187,10 @@ namespace Robust.Client.UserInterface
|
||||
var controlYAdvance = 0f;
|
||||
|
||||
var nodeIndex = -1;
|
||||
foreach (var node in Message.Nodes)
|
||||
foreach (var node in Message)
|
||||
{
|
||||
nodeIndex++;
|
||||
var text = ProcessNode(node, context);
|
||||
var text = ProcessNode(tagManager, node, context);
|
||||
if (!context.Color.TryPeek(out var color) || !context.Font.TryPeek(out var font))
|
||||
{
|
||||
color = _defaultColor;
|
||||
@@ -210,7 +213,7 @@ namespace Robust.Client.UserInterface
|
||||
globalBreakCounter += 1;
|
||||
}
|
||||
|
||||
if (!_tagControls.TryGetValue(nodeIndex, out var control))
|
||||
if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
|
||||
continue;
|
||||
|
||||
var invertedScale = 1f / uiScale;
|
||||
@@ -223,24 +226,22 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
private readonly string ProcessNode(MarkupNode node, MarkupDrawingContext context)
|
||||
private readonly string ProcessNode(MarkupTagManager tagManager, MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
// If a nodes name is null it's a text node.
|
||||
if (node.Name == null)
|
||||
return node.Value.StringValue ?? "";
|
||||
|
||||
//Skip the node if there is no markup tag for it.
|
||||
if (!_tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag))
|
||||
if (!tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag))
|
||||
return "";
|
||||
|
||||
if (!node.Closing)
|
||||
{
|
||||
context.Tags.Add(tag);
|
||||
tag.PushDrawContext(node, context);
|
||||
return tag.TextBefore(node);
|
||||
}
|
||||
|
||||
context.Tags.Remove(tag);
|
||||
tag.PopDrawContext(node, context);
|
||||
return tag.TextAfter(node);
|
||||
}
|
||||
|
||||
@@ -123,7 +123,12 @@ internal partial class UserInterfaceManager
|
||||
|
||||
private void UpdateUIScale(WindowRoot root)
|
||||
{
|
||||
root.UIScaleSet = CalculateAutoScale(root);
|
||||
var newScale = CalculateAutoScale(root);
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
if (newScale == root.UIScaleSet)
|
||||
return;
|
||||
|
||||
root.UIScaleSet = newScale;
|
||||
_propagateUIScaleChanged(root);
|
||||
root.InvalidateMeasure();
|
||||
}
|
||||
@@ -142,7 +147,21 @@ internal partial class UserInterfaceManager
|
||||
{
|
||||
if (!_windowsToRoot.TryGetValue(windowResizedEventArgs.Window.Id, out var root))
|
||||
return;
|
||||
UpdateUIScale(root);
|
||||
|
||||
root.UIScaleUpdateNeeded = true;
|
||||
root.InvalidateMeasure();
|
||||
}
|
||||
|
||||
private void CheckRootUIScaleUpdate(WindowRoot root)
|
||||
{
|
||||
if (!root.UIScaleUpdateNeeded)
|
||||
return;
|
||||
|
||||
using (_prof.Group("UIScaleUpdate"))
|
||||
{
|
||||
UpdateUIScale(root);
|
||||
}
|
||||
|
||||
root.UIScaleUpdateNeeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +216,8 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
foreach (var root in _roots)
|
||||
{
|
||||
CheckRootUIScaleUpdate(root);
|
||||
|
||||
using (_prof.Group("Root"))
|
||||
{
|
||||
var totalUpdated = root.DoFrameUpdateRecursive(args);
|
||||
|
||||
Submodule Robust.LoaderApi updated: 99a2f4b880...86a02eef16
@@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -115,6 +117,16 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
|
||||
/// </summary>
|
||||
public GameState? State;
|
||||
|
||||
/// <summary>
|
||||
/// The serialized <see cref="State"/> object.
|
||||
/// </summary>
|
||||
public MemoryStream? StateStream;
|
||||
|
||||
/// <summary>
|
||||
/// Whether we should force reliable sending of the <see cref="MsgState"/>.
|
||||
/// </summary>
|
||||
public bool ForceSendReliably { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears all stored game state data. This should only be used after the game state has been serialized.
|
||||
/// </summary>
|
||||
|
||||
@@ -75,15 +75,15 @@ namespace Robust.Server.GameStates
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CleanupDirty(ICommonSession[] sessions)
|
||||
private void CleanupDirty()
|
||||
{
|
||||
using var _ = Histogram.WithLabels("Clean Dirty").NewTimer();
|
||||
if (!CullingEnabled)
|
||||
{
|
||||
_seenAllEnts.Clear();
|
||||
foreach (var player in sessions)
|
||||
foreach (var player in _sessions)
|
||||
{
|
||||
_seenAllEnts.Add(player);
|
||||
_seenAllEnts.Add(player.Session);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,12 @@ internal sealed partial class PvsSystem
|
||||
{
|
||||
private WaitHandle? _leaveTask;
|
||||
|
||||
private void ProcessLeavePvs(ICommonSession[] sessions)
|
||||
private void ProcessLeavePvs()
|
||||
{
|
||||
if (!CullingEnabled || sessions.Length == 0)
|
||||
if (!CullingEnabled || _sessions.Length == 0)
|
||||
return;
|
||||
|
||||
DebugTools.AssertNull(_leaveTask);
|
||||
_leaveJob.Setup(sessions);
|
||||
|
||||
if (_async)
|
||||
{
|
||||
@@ -76,29 +75,19 @@ internal sealed partial class PvsSystem
|
||||
{
|
||||
public int BatchSize => 2;
|
||||
private PvsSystem _pvs = _pvs;
|
||||
public int Count => _sessions.Length;
|
||||
private PvsSession[] _sessions;
|
||||
public int Count => _pvs._sessions.Length;
|
||||
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pvs.ProcessLeavePvs(_sessions[index]);
|
||||
_pvs.ProcessLeavePvs(_pvs._sessions[index]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_pvs.Log.Log(LogLevel.Error, e, $"Caught exception while processing pvs-leave messages.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(ICommonSession[] sessions)
|
||||
{
|
||||
// Copy references to PvsSession, in case players disconnect while the job is running.
|
||||
Array.Resize(ref _sessions, sessions.Length);
|
||||
for (var i = 0; i < sessions.Length; i++)
|
||||
{
|
||||
_sessions[i] = _pvs.PlayerData[sessions[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
85
Robust.Server/GameStates/PvsSystem.Send.cs
Normal file
85
Robust.Server/GameStates/PvsSystem.Send.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
internal sealed partial class PvsSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Compress and send game states to connected clients.
|
||||
/// </summary>
|
||||
private void SendStates()
|
||||
{
|
||||
// TODO PVS make this async
|
||||
// AFAICT ForEachAsync doesn't support using a threadlocal PvsThreadResources.
|
||||
// Though if it is getting pooled, does it really matter?
|
||||
|
||||
// If this does get run async, then ProcessDisconnections() has to ensure that the job has finished before modifying
|
||||
// the sessions array
|
||||
|
||||
using var _ = Histogram.WithLabels("Send States").NewTimer();
|
||||
var opts = new ParallelOptions {MaxDegreeOfParallelism = _parallelMgr.ParallelProcessCount};
|
||||
Parallel.ForEach(_sessions, opts, _threadResourcesPool.Get, SendSessionState, _threadResourcesPool.Return);
|
||||
}
|
||||
|
||||
private PvsThreadResources SendSessionState(PvsSession data, ParallelLoopState state, PvsThreadResources resource)
|
||||
{
|
||||
try
|
||||
{
|
||||
SendSessionState(data, resource.CompressionContext);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log(LogLevel.Error, e, $"Caught exception while sending mail for {data.Session}.");
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
private void SendSessionState(PvsSession data, ZStdCompressionContext ctx)
|
||||
{
|
||||
DebugTools.AssertEqual(data.State, null);
|
||||
|
||||
// PVS benchmarks use dummy sessions.
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (data.Session.Channel is not DummyChannel)
|
||||
{
|
||||
DebugTools.AssertNotEqual(data.StateStream, null);
|
||||
var msg = new MsgState
|
||||
{
|
||||
StateStream = data.StateStream,
|
||||
ForceSendReliably = data.ForceSendReliably,
|
||||
CompressionContext = ctx
|
||||
};
|
||||
|
||||
_netMan.ServerSendMessage(msg, data.Session.Channel);
|
||||
if (msg.ShouldSendReliably())
|
||||
{
|
||||
data.RequestedFull = false;
|
||||
data.LastReceivedAck = _gameTiming.CurTick;
|
||||
lock (PendingAcks)
|
||||
{
|
||||
PendingAcks.Add(data.Session);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Always "ack" dummy sessions.
|
||||
data.LastReceivedAck = _gameTiming.CurTick;
|
||||
data.RequestedFull = false;
|
||||
lock (PendingAcks)
|
||||
{
|
||||
PendingAcks.Add(data.Session);
|
||||
}
|
||||
}
|
||||
|
||||
data.StateStream?.Dispose();
|
||||
data.StateStream = null;
|
||||
}
|
||||
}
|
||||
73
Robust.Server/GameStates/PvsSystem.Serialize.cs
Normal file
73
Robust.Server/GameStates/PvsSystem.Serialize.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Prometheus;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
internal sealed partial class PvsSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Get and serialize <see cref="GameState"/> objects for each player. Compressing & sending the states is done later.
|
||||
/// </summary>
|
||||
private void SerializeStates()
|
||||
{
|
||||
using var _ = Histogram.WithLabels("Serialize States").NewTimer();
|
||||
var opts = new ParallelOptions {MaxDegreeOfParallelism = _parallelMgr.ParallelProcessCount};
|
||||
_oldestAck = GameTick.MaxValue.Value;
|
||||
Parallel.For(-1, _sessions.Length, opts, SerializeState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get and serialize a <see cref="GameState"/> for a single session (or the current replay).
|
||||
/// </summary>
|
||||
private void SerializeState(int i)
|
||||
{
|
||||
try
|
||||
{
|
||||
var guid = i >= 0 ? _sessions[i].Session.UserId.UserId : default;
|
||||
ServerGameStateManager.PvsEventSource.Log.WorkStart(_gameTiming.CurTick.Value, i, guid);
|
||||
|
||||
if (i >= 0)
|
||||
SerializeSessionState(_sessions[i]);
|
||||
else
|
||||
_replay.Update();
|
||||
|
||||
ServerGameStateManager.PvsEventSource.Log.WorkStop(_gameTiming.CurTick.Value, i, guid);
|
||||
}
|
||||
catch (Exception e) // Catch EVERY exception
|
||||
{
|
||||
var source = i >= 0 ? _sessions[i].Session.ToString() : "replays";
|
||||
Log.Log(LogLevel.Error, e, $"Caught exception while serializing game state for {source}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get and serialize a <see cref="GameState"/> for a single session.
|
||||
/// </summary>
|
||||
private void SerializeSessionState(PvsSession data)
|
||||
{
|
||||
ComputeSessionState(data);
|
||||
InterlockedHelper.Min(ref _oldestAck, data.FromTick.Value);
|
||||
DebugTools.AssertEqual(data.StateStream, null);
|
||||
|
||||
// PVS benchmarks use dummy sessions.
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (data.Session.Channel is not DummyChannel)
|
||||
{
|
||||
data.StateStream = RobustMemoryManager.GetMemoryStream();
|
||||
_serializer.SerializeDirect(data.StateStream, data.State);
|
||||
}
|
||||
|
||||
data.ClearState();
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -27,49 +25,6 @@ internal sealed partial class PvsSystem
|
||||
|
||||
private List<ICommonSession> _disconnected = new();
|
||||
|
||||
private void SendStateUpdate(ICommonSession session, PvsThreadResources resources)
|
||||
{
|
||||
var data = GetOrNewPvsSession(session);
|
||||
ComputeSessionState(data);
|
||||
|
||||
InterlockedHelper.Min(ref _oldestAck, data.FromTick.Value);
|
||||
|
||||
// actually send the state
|
||||
var msg = new MsgState
|
||||
{
|
||||
State = data.State,
|
||||
CompressionContext = resources.CompressionContext
|
||||
};
|
||||
|
||||
// PVS benchmarks use dummy sessions.
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (session.Channel is not DummyChannel)
|
||||
{
|
||||
_netMan.ServerSendMessage(msg, session.Channel);
|
||||
if (msg.ShouldSendReliably())
|
||||
{
|
||||
data.RequestedFull = false;
|
||||
data.LastReceivedAck = _gameTiming.CurTick;
|
||||
lock (PendingAcks)
|
||||
{
|
||||
PendingAcks.Add(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Always "ack" dummy sessions.
|
||||
data.LastReceivedAck = _gameTiming.CurTick;
|
||||
data.RequestedFull = false;
|
||||
lock (PendingAcks)
|
||||
{
|
||||
PendingAcks.Add(session);
|
||||
}
|
||||
}
|
||||
|
||||
data.ClearState();
|
||||
}
|
||||
|
||||
private PvsSession GetOrNewPvsSession(ICommonSession session)
|
||||
{
|
||||
if (!PlayerData.TryGetValue(session, out var pvsSession))
|
||||
@@ -104,7 +59,7 @@ internal sealed partial class PvsSystem
|
||||
session.PlayerStates,
|
||||
_deletedEntities);
|
||||
|
||||
session.State.ForceSendReliably = session.RequestedFull
|
||||
session.ForceSendReliably = session.RequestedFull
|
||||
|| _gameTiming.CurTick > session.LastReceivedAck + (uint) ForceAckThreshold;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Prometheus;
|
||||
using Robust.Server.Configuration;
|
||||
@@ -16,9 +14,6 @@ using Robust.Server.Replays;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
@@ -99,6 +94,10 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
/// </summary>
|
||||
private readonly List<GameTick> _deletedTick = new();
|
||||
|
||||
/// <summary>
|
||||
/// The sessions that are currently being processed. Note that this is in general used by parallel & async tasks.
|
||||
/// Hence player disconnection processing is deferred and only run via <see cref="ProcessDisconnections"/>.
|
||||
/// </summary>
|
||||
private PvsSession[] _sessions = default!;
|
||||
|
||||
private bool _async;
|
||||
@@ -183,52 +182,25 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
/// </summary>
|
||||
internal void SendGameStates(ICommonSession[] players)
|
||||
{
|
||||
// Wait for pending jobs and process disconnected players
|
||||
ProcessDisconnections();
|
||||
|
||||
// Ensure each session has a PvsSession entry before starting any parallel jobs.
|
||||
CacheSessionData(players);
|
||||
|
||||
// Get visible chunks, and update any dirty chunks.
|
||||
BeforeSendState();
|
||||
BeforeSerializeStates();
|
||||
|
||||
// Construct & send the game state to each player.
|
||||
SendStates(players);
|
||||
// Construct & serialize the game state for each player (and for the replay).
|
||||
SerializeStates();
|
||||
|
||||
// Compress & send the states.
|
||||
SendStates();
|
||||
|
||||
// Cull deletion history
|
||||
AfterSendState(players);
|
||||
AfterSerializeStates();
|
||||
|
||||
ProcessLeavePvs(players);
|
||||
}
|
||||
|
||||
private void SendStates(ICommonSession[] players)
|
||||
{
|
||||
using var _ = Histogram.WithLabels("Send States").NewTimer();
|
||||
|
||||
var opts = new ParallelOptions {MaxDegreeOfParallelism = _parallelMgr.ParallelProcessCount};
|
||||
_oldestAck = GameTick.MaxValue.Value;
|
||||
|
||||
// Replays process game states in parallel with players
|
||||
Parallel.For(-1, players.Length, opts, _threadResourcesPool.Get, SendPlayer, _threadResourcesPool.Return);
|
||||
|
||||
PvsThreadResources SendPlayer(int i, ParallelLoopState state, PvsThreadResources resource)
|
||||
{
|
||||
try
|
||||
{
|
||||
var guid = i >= 0 ? players[i].UserId.UserId : default;
|
||||
ServerGameStateManager.PvsEventSource.Log.WorkStart(_gameTiming.CurTick.Value, i, guid);
|
||||
|
||||
if (i >= 0)
|
||||
SendStateUpdate(players[i], resource);
|
||||
else
|
||||
_replay.Update();
|
||||
|
||||
ServerGameStateManager.PvsEventSource.Log.WorkStop(_gameTiming.CurTick.Value, i, guid);
|
||||
}
|
||||
catch (Exception e) // Catch EVERY exception
|
||||
{
|
||||
var source = i >= 0 ? players[i].ToString() : "replays";
|
||||
Log.Log(LogLevel.Error, e, $"Caught exception while generating mail for {source}.");
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
ProcessLeavePvs();
|
||||
}
|
||||
|
||||
private void ResetParallelism(int _) => ResetParallelism();
|
||||
@@ -414,23 +386,11 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void BeforeSendState()
|
||||
private void BeforeSerializeStates()
|
||||
{
|
||||
DebugTools.Assert(_chunks.Values.All(x => Exists(x.Map) && Exists(x.Root)));
|
||||
DebugTools.Assert(_chunkSets.Keys.All(Exists));
|
||||
|
||||
_leaveTask?.WaitOne();
|
||||
_leaveTask = null;
|
||||
|
||||
foreach (var session in _disconnected)
|
||||
{
|
||||
if (PlayerData.Remove(session, out var pvsSession))
|
||||
{
|
||||
ClearSendHistory(pvsSession);
|
||||
FreeSessionDataMemory(pvsSession);
|
||||
}
|
||||
}
|
||||
|
||||
var ackJob = ProcessQueuedAcks();
|
||||
|
||||
// Figure out what chunks players can see and cache some chunk data.
|
||||
@@ -443,6 +403,21 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
ackJob?.WaitOne();
|
||||
}
|
||||
|
||||
internal void ProcessDisconnections()
|
||||
{
|
||||
_leaveTask?.WaitOne();
|
||||
_leaveTask = null;
|
||||
|
||||
foreach (var session in _disconnected)
|
||||
{
|
||||
if (PlayerData.Remove(session, out var pvsSession))
|
||||
{
|
||||
ClearSendHistory(pvsSession);
|
||||
FreeSessionDataMemory(pvsSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void CacheSessionData(ICommonSession[] players)
|
||||
{
|
||||
Array.Resize(ref _sessions, players.Length);
|
||||
@@ -452,9 +427,9 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void AfterSendState(ICommonSession[] players)
|
||||
private void AfterSerializeStates()
|
||||
{
|
||||
CleanupDirty(players);
|
||||
CleanupDirty();
|
||||
|
||||
if (_oldestAck == GameTick.MaxValue.Value)
|
||||
{
|
||||
|
||||
@@ -143,7 +143,6 @@ namespace Robust.Server.Player
|
||||
list.Add(info);
|
||||
}
|
||||
netMsg.Plyrs = list;
|
||||
netMsg.PlyCount = (byte)list.Count;
|
||||
|
||||
channel.SendMessage(netMsg);
|
||||
}
|
||||
|
||||
@@ -39,8 +39,6 @@ namespace Robust.Server
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadPool.SetMinThreads(Environment.ProcessorCount * 2, Environment.ProcessorCount);
|
||||
|
||||
ParsedMain(parsed, contentStart, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ internal sealed class HubManager
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorText = await response.Content.ReadAsStringAsync();
|
||||
_sawmill.Error("Error status while advertising server: [{StatusCode}] {ErrorText}, to {HubUrl}",
|
||||
_sawmill.Error("Error status while advertising server: [{StatusCode}] {ErrorText}, from {HubUrl}",
|
||||
response.StatusCode,
|
||||
errorText,
|
||||
hubUrl);
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Robust.Shared
|
||||
/// <seealso cref="NetMtuExpand"/>
|
||||
/// <seealso cref="NetMtuIpv6"/>
|
||||
public static readonly CVarDef<int> NetMtu =
|
||||
CVarDef.Create("net.mtu", 900, CVar.ARCHIVE);
|
||||
CVarDef.Create("net.mtu", 700, CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum UDP payload size to send by default, for IPv6.
|
||||
@@ -1374,7 +1374,7 @@ namespace Robust.Shared
|
||||
/// the purpose of using an atlas if it gets too small.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> ResRSIAtlasSize =
|
||||
CVarDef.Create("res.rsi_atlas_size", 8192, CVar.CLIENTONLY);
|
||||
CVarDef.Create("res.rsi_atlas_size", 12288, CVar.CLIENTONLY);
|
||||
|
||||
// TODO: Currently unimplemented.
|
||||
/// <summary>
|
||||
@@ -1560,6 +1560,12 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<int> ConCompletionMargin =
|
||||
CVarDef.Create("con.completion_margin", 3, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum amount of entries stored by the debug console.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> ConMaxEntries =
|
||||
CVarDef.Create("con.max_entries", 3_000, CVar.CLIENTONLY);
|
||||
|
||||
/*
|
||||
* THREAD
|
||||
*/
|
||||
|
||||
304
Robust.Shared/Collections/RingBufferList.cs
Normal file
304
Robust.Shared/Collections/RingBufferList.cs
Normal file
@@ -0,0 +1,304 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Utility;
|
||||
using ArgumentNullException = System.ArgumentNullException;
|
||||
|
||||
namespace Robust.Shared.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// Datastructure that acts like a <see cref="List{T}"/>, but is actually stored as a ring buffer internally.
|
||||
/// This facilitates efficient removal from the start.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of item contained in the collection.</typeparam>
|
||||
internal sealed class RingBufferList<T> : IList<T>
|
||||
{
|
||||
private T[] _items;
|
||||
private int _read;
|
||||
private int _write;
|
||||
|
||||
public RingBufferList(int capacity)
|
||||
{
|
||||
_items = new T[capacity];
|
||||
}
|
||||
|
||||
public RingBufferList()
|
||||
{
|
||||
_items = [];
|
||||
}
|
||||
|
||||
public int Capacity => _items.Length;
|
||||
|
||||
private bool IsFull => _items.Length == 0 || NextIndex(_write) == _read;
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
if (IsFull)
|
||||
Expand();
|
||||
|
||||
DebugTools.Assert(!IsFull);
|
||||
|
||||
_items[_write] = item;
|
||||
_write = NextIndex(_write);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_read = 0;
|
||||
_write = 0;
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
||||
Array.Clear(_items);
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return IndexOf(item) >= 0;
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
|
||||
|
||||
CopyTo(array.AsSpan(arrayIndex));
|
||||
}
|
||||
|
||||
private void CopyTo(Span<T> dest)
|
||||
{
|
||||
if (dest.Length < Count)
|
||||
throw new ArgumentException("Not enough elements in destination!");
|
||||
|
||||
var i = 0;
|
||||
foreach (var item in this)
|
||||
{
|
||||
dest[i++] = item;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
var index = IndexOf(item);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
var length = _write - _read;
|
||||
if (length >= 0)
|
||||
return length;
|
||||
|
||||
return length + _items.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var containedItem in this)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(item, containedItem))
|
||||
return i;
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
var length = Count;
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, length);
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
||||
_items[_read] = default!;
|
||||
|
||||
_read = NextIndex(_read);
|
||||
}
|
||||
else if (index == length - 1)
|
||||
{
|
||||
_write = WrapInv(_write - 1);
|
||||
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
||||
_items[_write] = default!;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If past me had better foresight I wouldn't be spending so much effort writing this right now.
|
||||
|
||||
var realIdx = RealIndex(index);
|
||||
var origValue = _items[realIdx];
|
||||
T result;
|
||||
|
||||
if (realIdx < _read)
|
||||
{
|
||||
// Scenario one: to-remove index is after break.
|
||||
// One shift is needed.
|
||||
// v
|
||||
// X X X O X X
|
||||
// W R
|
||||
DebugTools.Assert(_write < _read);
|
||||
|
||||
result = ShiftDown(_items.AsSpan()[realIdx.._write], default!);
|
||||
}
|
||||
else if (_write < _read)
|
||||
{
|
||||
// Scenario two: to-remove index is before break, but write is after.
|
||||
// Two shifts are needed.
|
||||
// v
|
||||
// X O X X X X
|
||||
// W R
|
||||
|
||||
var fromEnd = ShiftDown(_items.AsSpan(0, _write), default!);
|
||||
result = ShiftDown(_items.AsSpan(realIdx), fromEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scenario two: array is contiguous.
|
||||
// One shift is needed.
|
||||
// v
|
||||
// X X X X O O
|
||||
// R W
|
||||
|
||||
result = ShiftDown(_items.AsSpan()[realIdx.._write], default!);
|
||||
}
|
||||
|
||||
// Just make sure we didn't bulldozer something.
|
||||
DebugTools.Assert(EqualityComparer<T>.Default.Equals(origValue, result));
|
||||
|
||||
_write = WrapInv(_write - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static T ShiftDown(Span<T> span, T substitution)
|
||||
{
|
||||
if (span.Length == 0)
|
||||
return substitution;
|
||||
|
||||
var first = span[0];
|
||||
span[1..].CopyTo(span[..^1]);
|
||||
span[^1] = substitution!;
|
||||
return first;
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => GetSlot(index);
|
||||
set => GetSlot(index) = value;
|
||||
}
|
||||
|
||||
private ref T GetSlot(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count);
|
||||
|
||||
return ref _items[RealIndex(index)];
|
||||
}
|
||||
|
||||
private int RealIndex(int index)
|
||||
{
|
||||
return Wrap(index + _read);
|
||||
}
|
||||
|
||||
private int NextIndex(int index) => Wrap(index + 1);
|
||||
|
||||
private int Wrap(int index)
|
||||
{
|
||||
if (index >= _items.Length)
|
||||
index -= _items.Length;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private int WrapInv(int index)
|
||||
{
|
||||
if (index < 0)
|
||||
index = _items.Length - 1;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private void Expand()
|
||||
{
|
||||
var prevSize = _items.Length;
|
||||
var newSize = Math.Max(4, prevSize * 2);
|
||||
Array.Resize(ref _items, newSize);
|
||||
|
||||
if (_write >= _read)
|
||||
return;
|
||||
|
||||
// Write is behind read pointer, so we need to copy the items to be after the read pointer.
|
||||
var toCopy = _items.AsSpan(0, _write);
|
||||
var copyDest = _items.AsSpan(prevSize);
|
||||
toCopy.CopyTo(copyDest);
|
||||
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
||||
toCopy.Clear();
|
||||
|
||||
_write += prevSize;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private readonly RingBufferList<T> _ringBufferList;
|
||||
private int _readPos;
|
||||
|
||||
internal Enumerator(RingBufferList<T> ringBufferList)
|
||||
{
|
||||
_ringBufferList = ringBufferList;
|
||||
_readPos = _ringBufferList._read - 1;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
_readPos = _ringBufferList.NextIndex(_readPos);
|
||||
return _readPos != _ringBufferList._write;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this = new Enumerator(_ringBufferList);
|
||||
}
|
||||
|
||||
public ref T Current => ref _ringBufferList._items[_readPos];
|
||||
|
||||
T IEnumerator<T>.Current => Current;
|
||||
object? IEnumerator.Current => Current;
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,8 @@ public abstract partial class SharedContainerSystem
|
||||
|
||||
DebugTools.AssertOwner(container.Owner, containerXform);
|
||||
DebugTools.AssertOwner(toInsert, physics);
|
||||
DebugTools.Assert(!container.ExpectedEntities.Contains(GetNetEntity(toInsert)));
|
||||
DebugTools.Assert(container.Manager.Containers.ContainsKey(container.ID));
|
||||
DebugTools.Assert(!container.ExpectedEntities.Contains(GetNetEntity(toInsert)), "entity is expected");
|
||||
DebugTools.Assert(container.Manager.Containers.ContainsKey(container.ID), "manager does not own the container");
|
||||
|
||||
// If someone is attempting to insert an entity into a container that is getting deleted, then we will
|
||||
// automatically delete that entity. I.e., the insertion automatically "succeeds" and both entities get deleted.
|
||||
@@ -82,14 +82,14 @@ public abstract partial class SharedContainerSystem
|
||||
}
|
||||
|
||||
// Update metadata first, so that parent change events can check IsInContainer.
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0, "invalid metadata flags before insertion");
|
||||
meta.Flags |= MetaDataFlags.InContainer;
|
||||
|
||||
// Remove the entity and any children from broadphases.
|
||||
// This is done before changing can collide to avoid unecceary updates.
|
||||
// TODO maybe combine with RecursivelyUpdatePhysics to avoid fetching components and iterating parents twice?
|
||||
_lookup.RemoveFromEntityTree(toInsert, transform);
|
||||
DebugTools.Assert(transform.Broadphase == null || !transform.Broadphase.Value.IsValid());
|
||||
DebugTools.Assert(transform.Broadphase == null || !transform.Broadphase.Value.IsValid(), "invalid broadphase");
|
||||
|
||||
// Avoid unnecessary broadphase updates while unanchoring, changing physics collision, and re-parenting.
|
||||
var old = transform.Broadphase;
|
||||
@@ -111,7 +111,7 @@ public abstract partial class SharedContainerSystem
|
||||
transform.Broadphase = old;
|
||||
|
||||
// the transform.AttachParent() could previously result in the flag being unset, so check that this hasn't happened.
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0);
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0, "invalid metadata flags after insertion");
|
||||
|
||||
// Implementation specific insert logic
|
||||
container.InternalInsert(toInsert, EntityManager);
|
||||
@@ -125,11 +125,11 @@ public abstract partial class SharedContainerSystem
|
||||
RaiseLocalEvent(toInsert, new EntGotInsertedIntoContainerMessage(toInsert, container), true);
|
||||
|
||||
// The sheer number of asserts tells you about how little I trust container and parenting code.
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0);
|
||||
DebugTools.Assert(!transform.Anchored);
|
||||
DebugTools.Assert(transform.LocalPosition == Vector2.Zero);
|
||||
DebugTools.Assert(MathHelper.CloseTo(transform.LocalRotation.Theta, Angle.Zero));
|
||||
DebugTools.Assert(!PhysicsQuery.TryGetComponent(toInsert, out var phys) || (!phys.Awake && !phys.CanCollide));
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0, "invalid metadata flags after events");
|
||||
DebugTools.Assert(!transform.Anchored, "entity is anchored");
|
||||
DebugTools.AssertEqual(transform.LocalPosition, Vector2.Zero);
|
||||
DebugTools.Assert(MathHelper.CloseTo(transform.LocalRotation.Theta, Angle.Zero), "Angle is not zero");
|
||||
DebugTools.Assert(!PhysicsQuery.TryGetComponent(toInsert, out var phys) || (!phys.Awake && !phys.CanCollide), "Invalid physics");
|
||||
|
||||
Dirty(container.Owner, container.Manager);
|
||||
return true;
|
||||
|
||||
@@ -41,7 +41,7 @@ public abstract partial class SharedContainerSystem
|
||||
return false;
|
||||
|
||||
DebugTools.AssertNotNull(container.Manager);
|
||||
DebugTools.Assert(Exists(toRemove));
|
||||
DebugTools.Assert(Exists(toRemove), "toRemove does not exist");
|
||||
|
||||
if (!force && !CanRemove(toRemove, container))
|
||||
return false;
|
||||
@@ -60,11 +60,11 @@ public abstract partial class SharedContainerSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
DebugTools.Assert(meta.EntityLifeStage < EntityLifeStage.Terminating || (force && !reparent));
|
||||
DebugTools.Assert(xform.Broadphase == null || !xform.Broadphase.Value.IsValid());
|
||||
DebugTools.Assert(!xform.Anchored);
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0x0);
|
||||
DebugTools.Assert(!TryComp(toRemove, out PhysicsComponent? phys) || (!phys.Awake && !phys.CanCollide));
|
||||
DebugTools.Assert(meta.EntityLifeStage < EntityLifeStage.Terminating || (force && !reparent), "Entity is terminating");
|
||||
DebugTools.Assert(xform.Broadphase == null || !xform.Broadphase.Value.IsValid(), "broadphase is invalid");
|
||||
DebugTools.Assert(!xform.Anchored || _timing.ApplyingState, "anchor is invalid");
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0x0, "metadata is invalid");
|
||||
DebugTools.Assert(!TryComp(toRemove, out PhysicsComponent? phys) || (!phys.Awake && !phys.CanCollide), "physics is invalid");
|
||||
|
||||
// Unset flag (before parent change events are raised).
|
||||
meta.Flags &= ~MetaDataFlags.InContainer;
|
||||
@@ -104,7 +104,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));
|
||||
DebugTools.Assert(destination == null || xform.Coordinates.Equals(destination.Value), "failed to set destination");
|
||||
|
||||
Dirty(container.Owner, container.Manager);
|
||||
return true;
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Shared.ContentPack
|
||||
String("short").ThenReturn(PrimitiveTypeCode.Int16);
|
||||
|
||||
private static readonly Parser<char, PrimitiveTypeCode> UInt16TypeParser =
|
||||
String("ushort").ThenReturn(PrimitiveTypeCode.UInt32);
|
||||
String("ushort").ThenReturn(PrimitiveTypeCode.UInt16);
|
||||
|
||||
private static readonly Parser<char, PrimitiveTypeCode> Int32TypeParser =
|
||||
String("int").ThenReturn(PrimitiveTypeCode.Int32);
|
||||
|
||||
@@ -84,12 +84,146 @@ Types:
|
||||
- "bool get_HasContents()"
|
||||
Lidgren.Network:
|
||||
NetBuffer:
|
||||
All: True
|
||||
Methods:
|
||||
- "byte[] get_Data()"
|
||||
- "void set_Data(byte[])"
|
||||
- "int get_LengthBytes()"
|
||||
- "void set_LengthBytes(int)"
|
||||
- "int get_LengthBits()"
|
||||
- "void set_LengthBits(int)"
|
||||
- "long get_Position()"
|
||||
- "void set_Position(long)"
|
||||
- "int get_PositionInBytes()"
|
||||
- "byte[] PeekDataBuffer()"
|
||||
- "bool PeekBoolean()"
|
||||
- "byte PeekByte()"
|
||||
- "sbyte PeekSByte()"
|
||||
- "byte PeekByte(int)"
|
||||
- "System.Span`1<byte> PeekBytes(System.Span`1<byte>)"
|
||||
- "byte[] PeekBytes(int)"
|
||||
- "void PeekBytes(byte[], int, int)"
|
||||
- "short PeekInt16()"
|
||||
- "ushort PeekUInt16()"
|
||||
- "int PeekInt32()"
|
||||
- "int PeekInt32(int)"
|
||||
- "uint PeekUInt32()"
|
||||
- "uint PeekUInt32(int)"
|
||||
- "ulong PeekUInt64()"
|
||||
- "long PeekInt64()"
|
||||
- "ulong PeekUInt64(int)"
|
||||
- "long PeekInt64(int)"
|
||||
- "float PeekFloat()"
|
||||
- "System.Half PeekHalf()"
|
||||
- "float PeekSingle()"
|
||||
- "double PeekDouble()"
|
||||
- "string PeekString()"
|
||||
- "int PeekStringSize()"
|
||||
- "bool ReadBoolean()"
|
||||
- "byte ReadByte()"
|
||||
- "bool ReadByte(ref byte)"
|
||||
- "sbyte ReadSByte()"
|
||||
- "byte ReadByte(int)"
|
||||
- "System.Span`1<byte> ReadBytes(System.Span`1<byte>)"
|
||||
- "byte[] ReadBytes(int)"
|
||||
- "bool ReadBytes(int, ref byte[])"
|
||||
- "bool TryReadBytes(System.Span`1<byte>)"
|
||||
- "void ReadBytes(byte[], int, int)"
|
||||
- "void ReadBits(System.Span`1<byte>, int)"
|
||||
- "void ReadBits(byte[], int, int)"
|
||||
- "short ReadInt16()"
|
||||
- "ushort ReadUInt16()"
|
||||
- "int ReadInt32()"
|
||||
- "bool ReadInt32(ref int)"
|
||||
- "int ReadInt32(int)"
|
||||
- "uint ReadUInt32()"
|
||||
- "bool ReadUInt32(ref uint)"
|
||||
- "uint ReadUInt32(int)"
|
||||
- "ulong ReadUInt64()"
|
||||
- "long ReadInt64()"
|
||||
- "ulong ReadUInt64(int)"
|
||||
- "long ReadInt64(int)"
|
||||
- "float ReadFloat()"
|
||||
- "System.Half ReadHalf()"
|
||||
- "float ReadSingle()"
|
||||
- "bool ReadSingle(ref float)"
|
||||
- "double ReadDouble()"
|
||||
- "uint ReadVariableUInt32()"
|
||||
- "bool ReadVariableUInt32(ref uint)"
|
||||
- "int ReadVariableInt32()"
|
||||
- "long ReadVariableInt64()"
|
||||
- "ulong ReadVariableUInt64()"
|
||||
- "float ReadSignedSingle(int)"
|
||||
- "float ReadUnitSingle(int)"
|
||||
- "float ReadRangedSingle(float, float, int)"
|
||||
- "int ReadRangedInteger(int, int)"
|
||||
- "long ReadRangedInteger(long, long)"
|
||||
- "string ReadString()"
|
||||
- "bool ReadString(ref string)"
|
||||
- "double ReadTime(Lidgren.Network.NetConnection, bool)"
|
||||
- "System.Net.IPEndPoint ReadIPEndPoint()"
|
||||
- "void SkipPadBits()"
|
||||
- "void ReadPadBits()"
|
||||
- "void SkipPadBits(int)"
|
||||
- "void EnsureBufferSize(int)"
|
||||
- "void Write(bool)"
|
||||
- "void Write(byte)"
|
||||
- "void WriteAt(int, byte)"
|
||||
- "void Write(sbyte)"
|
||||
- "void Write(byte, int)"
|
||||
- "void Write(byte[])"
|
||||
- "void Write(System.ReadOnlySpan`1<byte>)"
|
||||
- "void Write(byte[], int, int)"
|
||||
- "void Write(ushort)"
|
||||
- "void WriteAt(int, ushort)"
|
||||
- "void Write(ushort, int)"
|
||||
- "void Write(short)"
|
||||
- "void WriteAt(int, short)"
|
||||
- "void Write(int)"
|
||||
- "void WriteAt(int, int)"
|
||||
- "void Write(uint)"
|
||||
- "void WriteAt(int, uint)"
|
||||
- "void Write(uint, int)"
|
||||
- "void Write(int, int)"
|
||||
- "void Write(ulong)"
|
||||
- "void WriteAt(int, ulong)"
|
||||
- "void Write(ulong, int)"
|
||||
- "void Write(long)"
|
||||
- "void Write(long, int)"
|
||||
- "void Write(System.Half)"
|
||||
- "void Write(float)"
|
||||
- "void Write(double)"
|
||||
- "int WriteVariableUInt32(uint)"
|
||||
- "int WriteVariableInt32(int)"
|
||||
- "int WriteVariableInt64(long)"
|
||||
- "int WriteVariableUInt64(ulong)"
|
||||
- "void WriteSignedSingle(float, int)"
|
||||
- "void WriteUnitSingle(float, int)"
|
||||
- "void WriteRangedSingle(float, float, float, int)"
|
||||
- "int WriteRangedInteger(int, int, int)"
|
||||
- "int WriteRangedInteger(long, long, long)"
|
||||
- "void Write(string)"
|
||||
- "void Write(System.Net.IPEndPoint)"
|
||||
- "void WriteTime(bool)"
|
||||
- "void WriteTime(double, bool)"
|
||||
- "void WritePadBits()"
|
||||
- "void WritePadBits(int)"
|
||||
- "void Write(Lidgren.Network.NetBuffer)"
|
||||
- "void Zero(int)"
|
||||
- "void .ctor()"
|
||||
NetDeliveryMethod: { }
|
||||
NetIncomingMessage:
|
||||
All: True
|
||||
Methods:
|
||||
- "Lidgren.Network.NetIncomingMessageType get_MessageType()"
|
||||
- "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()"
|
||||
- "int get_SequenceChannel()"
|
||||
- "System.Net.IPEndPoint get_SenderEndPoint()"
|
||||
- "Lidgren.Network.NetConnection get_SenderConnection()"
|
||||
- "double get_ReceiveTime()"
|
||||
- "double ReadTime(bool)"
|
||||
- "string ToString()"
|
||||
NetOutgoingMessage:
|
||||
All: True
|
||||
Methods:
|
||||
- "string ToString()"
|
||||
Nett:
|
||||
CommentLocation: { } # Enum
|
||||
Toml:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
@@ -135,11 +136,37 @@ namespace Robust.Shared.ContentPack
|
||||
path = path.Directory;
|
||||
|
||||
var fullPath = GetFullPath(path);
|
||||
Process.Start(new ProcessStartInfo
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
UseShellExecute = true,
|
||||
FileName = fullPath,
|
||||
});
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\explorer.exe",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "open",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "xdg-open",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Opening OS windows not supported on this OS");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -739,6 +739,47 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
#endregion
|
||||
|
||||
#region Local
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entities intersecting the specified broadphase entity using a local AABB.
|
||||
/// </summary>
|
||||
public void GetLocalEntitiesIntersecting<T>(
|
||||
EntityUid gridUid,
|
||||
Vector2i localTile,
|
||||
HashSet<Entity<T>> intersecting,
|
||||
float enlargement = TileEnlargementRadius,
|
||||
LookupFlags flags = DefaultFlags,
|
||||
MapGridComponent? gridComp = null) where T : IComponent
|
||||
{
|
||||
ushort tileSize = 1;
|
||||
|
||||
if (_gridQuery.Resolve(gridUid, ref gridComp))
|
||||
{
|
||||
tileSize = gridComp.TileSize;
|
||||
}
|
||||
|
||||
var localAABB = GetLocalBounds(localTile, tileSize);
|
||||
localAABB = localAABB.Enlarged(TileEnlargementRadius);
|
||||
GetLocalEntitiesIntersecting(gridUid, localAABB, intersecting, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entities intersecting the specified broadphase entity using a local AABB.
|
||||
/// </summary>
|
||||
public void GetLocalEntitiesIntersecting<T>(
|
||||
EntityUid gridUid,
|
||||
Box2 localAABB,
|
||||
HashSet<Entity<T>> intersecting,
|
||||
LookupFlags flags = DefaultFlags) where T : IComponent
|
||||
{
|
||||
var query = GetEntityQuery<T>();
|
||||
AddLocalEntitiesIntersecting(gridUid, intersecting, localAABB, flags, query);
|
||||
AddContained(intersecting, flags, query);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets entities with the specified component with the specified parent.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,14 +12,13 @@ namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public byte PlyCount { get; set; }
|
||||
public List<SessionState> Plyrs { get; set; }
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
Plyrs = new List<SessionState>();
|
||||
PlyCount = buffer.ReadByte();
|
||||
for (var i = 0; i < PlyCount; i++)
|
||||
var playerCount = buffer.ReadInt32();
|
||||
Plyrs = new List<SessionState>(playerCount);
|
||||
for (var i = 0; i < playerCount; i++)
|
||||
{
|
||||
var plyNfo = new SessionState
|
||||
{
|
||||
@@ -34,7 +33,7 @@ namespace Robust.Shared.Network.Messages
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
buffer.Write(PlyCount);
|
||||
buffer.Write(Plyrs.Count);
|
||||
|
||||
foreach (var ply in Plyrs)
|
||||
{
|
||||
|
||||
@@ -17,15 +17,21 @@ namespace Robust.Shared.Network.Messages
|
||||
// Ideally we would peg this to the actual configured MTU instead of the default constant, but oh well...
|
||||
public const int ReliableThreshold = NetPeerConfiguration.kDefaultMTU - 20;
|
||||
|
||||
// If a state is larger than this, compress it with deflate.
|
||||
// If a state is larger than this, we will compress it
|
||||
// TODO PVS make this a cvar
|
||||
// TODO PVS figure out optimal value
|
||||
public const int CompressionThreshold = 256;
|
||||
|
||||
public override MsgGroups MsgGroup => MsgGroups.Entity;
|
||||
|
||||
public GameState State;
|
||||
public MemoryStream StateStream;
|
||||
|
||||
public ZStdCompressionContext CompressionContext;
|
||||
|
||||
internal bool _hasWritten;
|
||||
internal bool HasWritten;
|
||||
|
||||
internal bool ForceSendReliably;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
@@ -60,26 +66,19 @@ namespace Robust.Shared.Network.Messages
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
using var stateStream = RobustMemoryManager.GetMemoryStream();
|
||||
serializer.SerializeDirect(stateStream, State);
|
||||
buffer.WriteVariableInt32((int)stateStream.Length);
|
||||
buffer.WriteVariableInt32((int)StateStream.Length);
|
||||
|
||||
// We compress the state.
|
||||
if (stateStream.Length > CompressionThreshold)
|
||||
if (StateStream.Length > CompressionThreshold)
|
||||
{
|
||||
// var sw = Stopwatch.StartNew();
|
||||
stateStream.Position = 0;
|
||||
var buf = ArrayPool<byte>.Shared.Rent(ZStd.CompressBound((int)stateStream.Length));
|
||||
var length = CompressionContext.Compress2(buf, stateStream.AsSpan());
|
||||
StateStream.Position = 0;
|
||||
var buf = ArrayPool<byte>.Shared.Rent(ZStd.CompressBound((int)StateStream.Length));
|
||||
var length = CompressionContext.Compress2(buf, StateStream.AsSpan());
|
||||
|
||||
buffer.WriteVariableInt32(length);
|
||||
|
||||
buffer.Write(buf.AsSpan(0, length));
|
||||
|
||||
// var elapsed = sw.Elapsed;
|
||||
// System.Console.WriteLine(
|
||||
// $"From: {State.FromSequence} To: {State.ToSequence} Size: {length} B Before: {stateStream.Length} B time: {elapsed}");
|
||||
|
||||
ArrayPool<byte>.Shared.Return(buf);
|
||||
}
|
||||
// The state is sent as is.
|
||||
@@ -87,10 +86,10 @@ namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
// 0 means that the state isn't compressed.
|
||||
buffer.WriteVariableInt32(0);
|
||||
buffer.Write(stateStream.AsSpan());
|
||||
buffer.Write(StateStream.AsSpan());
|
||||
}
|
||||
|
||||
_hasWritten = true;
|
||||
HasWritten = true;
|
||||
MsgSize = buffer.LengthBytes;
|
||||
}
|
||||
|
||||
@@ -101,21 +100,12 @@ namespace Robust.Shared.Network.Messages
|
||||
/// <returns></returns>
|
||||
public bool ShouldSendReliably()
|
||||
{
|
||||
DebugTools.Assert(_hasWritten, "Attempted to determine sending method before determining packet size.");
|
||||
return State.ForceSendReliably || MsgSize > ReliableThreshold;
|
||||
DebugTools.Assert(HasWritten, "Attempted to determine sending method before determining packet size.");
|
||||
return ForceSendReliably || MsgSize > ReliableThreshold;
|
||||
}
|
||||
|
||||
public override NetDeliveryMethod DeliveryMethod
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ShouldSendReliably())
|
||||
{
|
||||
return NetDeliveryMethod.ReliableUnordered;
|
||||
}
|
||||
|
||||
return base.DeliveryMethod;
|
||||
}
|
||||
}
|
||||
public override NetDeliveryMethod DeliveryMethod => ShouldSendReliably()
|
||||
? NetDeliveryMethod.ReliableUnordered
|
||||
: base.DeliveryMethod;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +31,14 @@ namespace Robust.Shared.Physics.Systems
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private EntityQuery<BroadphaseComponent> _broadphaseQuery;
|
||||
private EntityQuery<FixturesComponent> _fixturesQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<PhysicsMapComponent> _mapQuery;
|
||||
|
||||
private float _broadphaseExpand;
|
||||
|
||||
/*
|
||||
* Okay so Box2D has its own "MoveProxy" stuff so you can easily find new contacts when required.
|
||||
* Our problem is that we have nested broadphases (rather than being on separate maps) which makes this
|
||||
@@ -43,23 +46,21 @@ namespace Robust.Shared.Physics.Systems
|
||||
* Hence we need to check which broadphases it does intersect and checkar for colliding bodies.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// How much to expand bounds by to check cross-broadphase collisions.
|
||||
/// Ideally you want to set this to your largest body size.
|
||||
/// This only has a noticeable performance impact where multiple broadphases are in close proximity.
|
||||
/// </summary>
|
||||
private float _broadphaseExpand;
|
||||
|
||||
private const int PairBufferParallel = 8;
|
||||
|
||||
private ObjectPool<List<FixtureProxy>> _bufferPool =
|
||||
new DefaultObjectPool<List<FixtureProxy>>(new ListPolicy<FixtureProxy>(), 2048);
|
||||
private BroadphaseContactJob _contactJob;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_contactJob = new()
|
||||
{
|
||||
_mapManager = _mapManager,
|
||||
System = this,
|
||||
BroadphaseExpand = _broadphaseExpand,
|
||||
};
|
||||
|
||||
_broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
_fixturesQuery = GetEntityQuery<FixturesComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
@@ -71,7 +72,11 @@ namespace Robust.Shared.Physics.Systems
|
||||
Subs.CVar(_cfg, CVars.BroadphaseExpand, SetBroadphaseExpand, true);
|
||||
}
|
||||
|
||||
private void SetBroadphaseExpand(float value) => _broadphaseExpand = value;
|
||||
private void SetBroadphaseExpand(float value)
|
||||
{
|
||||
_contactJob.BroadphaseExpand = value;
|
||||
_broadphaseExpand = value;
|
||||
}
|
||||
|
||||
#region Find Contacts
|
||||
|
||||
@@ -176,65 +181,34 @@ namespace Robust.Shared.Physics.Systems
|
||||
if (moveBuffer.Count == 0)
|
||||
return;
|
||||
|
||||
var count = moveBuffer.Count;
|
||||
var contactBuffer = ArrayPool<List<FixtureProxy>>.Shared.Rent(count);
|
||||
var pMoveBuffer = ArrayPool<(FixtureProxy Proxy, Box2 AABB)>.Shared.Rent(count);
|
||||
|
||||
var idx = 0;
|
||||
_contactJob.MapUid = _mapManager.GetMapEntityIdOrThrow(mapId);
|
||||
_contactJob.MoveBuffer.Clear();
|
||||
|
||||
foreach (var (proxy, aabb) in moveBuffer)
|
||||
{
|
||||
contactBuffer[idx] = _bufferPool.Get();
|
||||
pMoveBuffer[idx++] = (proxy, aabb);
|
||||
_contactJob.MoveBuffer.Add((proxy, aabb));
|
||||
}
|
||||
|
||||
var options = new ParallelOptions
|
||||
for (var i = _contactJob.ContactBuffer.Count; i < _contactJob.MoveBuffer.Count; i++)
|
||||
{
|
||||
MaxDegreeOfParallelism = _parallel.ParallelProcessCount,
|
||||
};
|
||||
_contactJob.ContactBuffer.Add(new List<FixtureProxy>());
|
||||
}
|
||||
|
||||
var batches = (int)MathF.Ceiling((float) count / PairBufferParallel);
|
||||
var count = moveBuffer.Count;
|
||||
|
||||
Parallel.For(0, batches, options, i =>
|
||||
{
|
||||
var start = i * PairBufferParallel;
|
||||
var end = Math.Min(start + PairBufferParallel, count);
|
||||
|
||||
for (var j = start; j < end; j++)
|
||||
{
|
||||
var (proxy, worldAABB) = pMoveBuffer[j];
|
||||
var buffer = contactBuffer[j];
|
||||
|
||||
var proxyBody = proxy.Body;
|
||||
DebugTools.Assert(!proxyBody.Deleted);
|
||||
|
||||
var state = (this, proxy, worldAABB, buffer);
|
||||
|
||||
// Get every broadphase we may be intersecting.
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_broadphaseExpand), ref state,
|
||||
static (EntityUid uid, MapGridComponent _, ref (
|
||||
SharedBroadphaseSystem system,
|
||||
FixtureProxy proxy,
|
||||
Box2 worldAABB,
|
||||
List<FixtureProxy> pairBuffer) tuple) =>
|
||||
{
|
||||
ref var buffer = ref tuple.pairBuffer;
|
||||
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer);
|
||||
return true;
|
||||
}, approx: true, includeMap: false);
|
||||
|
||||
// Struct ref moment, I have no idea what's fastest.
|
||||
buffer = state.buffer;
|
||||
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), buffer);
|
||||
}
|
||||
});
|
||||
_parallel.ProcessNow(_contactJob, count);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var proxyA = pMoveBuffer[i].Proxy;
|
||||
var proxies = contactBuffer[i];
|
||||
var proxies = _contactJob.ContactBuffer[i];
|
||||
|
||||
if (proxies.Count == 0)
|
||||
continue;
|
||||
|
||||
var proxyA = _contactJob.MoveBuffer[i].Proxy;
|
||||
var proxyABody = proxyA.Body;
|
||||
FixturesComponent? manager = null;
|
||||
|
||||
_fixturesQuery.TryGetComponent(proxyA.Entity, out var manager);
|
||||
|
||||
foreach (var other in proxies)
|
||||
{
|
||||
@@ -253,13 +227,8 @@ namespace Robust.Shared.Physics.Systems
|
||||
|
||||
_physicsSystem.AddPair(proxyA.FixtureId, other.FixtureId, proxyA, other);
|
||||
}
|
||||
|
||||
_bufferPool.Return(contactBuffer[i]);
|
||||
pMoveBuffer[i] = default;
|
||||
}
|
||||
|
||||
ArrayPool<List<FixtureProxy>>.Shared.Return(contactBuffer);
|
||||
ArrayPool<(FixtureProxy Proxy, Box2 AABB)>.Shared.Return(pMoveBuffer);
|
||||
moveBuffer.Clear();
|
||||
movedGrids.Clear();
|
||||
}
|
||||
@@ -516,5 +485,51 @@ namespace Robust.Shared.Physics.Systems
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record struct BroadphaseContactJob() : IParallelRobustJob
|
||||
{
|
||||
public SharedBroadphaseSystem System = default!;
|
||||
public IMapManager _mapManager = default!;
|
||||
|
||||
public float BroadphaseExpand;
|
||||
|
||||
public EntityUid MapUid;
|
||||
|
||||
public List<List<FixtureProxy>> ContactBuffer = new();
|
||||
public List<(FixtureProxy Proxy, Box2 WorldAABB)> MoveBuffer = new();
|
||||
|
||||
public int BatchSize => 8;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var (proxy, worldAABB) = MoveBuffer[index];
|
||||
var buffer = ContactBuffer[index];
|
||||
buffer.Clear();
|
||||
|
||||
var proxyBody = proxy.Body;
|
||||
DebugTools.Assert(!proxyBody.Deleted);
|
||||
|
||||
var state = (System, proxy, worldAABB, buffer);
|
||||
|
||||
// Get every broadphase we may be intersecting.
|
||||
_mapManager.FindGridsIntersecting(MapUid, worldAABB.Enlarged(BroadphaseExpand), ref state,
|
||||
static (EntityUid uid, MapGridComponent _, ref (
|
||||
SharedBroadphaseSystem system,
|
||||
FixtureProxy proxy,
|
||||
Box2 worldAABB,
|
||||
List<FixtureProxy> pairBuffer) tuple) =>
|
||||
{
|
||||
ref var buffer = ref tuple.pairBuffer;
|
||||
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer);
|
||||
return true;
|
||||
},
|
||||
approx: true,
|
||||
includeMap: false);
|
||||
|
||||
// Struct ref moment, I have no idea what's fastest.
|
||||
buffer = state.buffer;
|
||||
System.FindPairs(proxy, worldAABB, MapUid, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -128,6 +129,9 @@ public static class RandomExtensions
|
||||
return minAngle + (maxAngle - minAngle) * random.NextDouble();
|
||||
}
|
||||
|
||||
public static Vector2 NextPolarVector2(this System.Random random, float minMagnitude, float maxMagnitude)
|
||||
=> random.NextAngle().RotateVec(new Vector2(random.NextFloat(minMagnitude, maxMagnitude), 0));
|
||||
|
||||
public static float NextFloat(this IRobustRandom random)
|
||||
{
|
||||
// This is pretty much the CoreFX implementation.
|
||||
@@ -141,6 +145,9 @@ public static class RandomExtensions
|
||||
return random.Next() * 4.6566128752458E-10f;
|
||||
}
|
||||
|
||||
public static float NextFloat(this System.Random random, float minValue, float maxValue)
|
||||
=> random.NextFloat() * (maxValue - minValue) + minValue;
|
||||
|
||||
/// <summary>
|
||||
/// Have a certain chance to return a boolean.
|
||||
/// </summary>
|
||||
|
||||
@@ -220,14 +220,16 @@ namespace Robust.Shared.Utility
|
||||
/// <paramref name="arg" /> is <see langword="null" />.
|
||||
/// </summary>
|
||||
/// <param name="arg">Condition that must be true.</param>
|
||||
/// <param name="message">Exception message.</param>
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertNotNull([AssertionCondition(AssertionConditionType.IS_NOT_NULL)]
|
||||
object? arg)
|
||||
object? arg,
|
||||
string? message = null)
|
||||
{
|
||||
if (arg == null)
|
||||
{
|
||||
throw new DebugAssertException();
|
||||
throw new DebugAssertException(message?? "value cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,14 +238,16 @@ namespace Robust.Shared.Utility
|
||||
/// <paramref name="arg" /> is not <see langword="null" />.
|
||||
/// </summary>
|
||||
/// <param name="arg">Condition that must be true.</param>
|
||||
/// <param name="message">Exception message.</param>
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertNull([AssertionCondition(AssertionConditionType.IS_NULL)]
|
||||
object? arg)
|
||||
object? arg,
|
||||
string? message = null)
|
||||
{
|
||||
if (arg != null)
|
||||
{
|
||||
throw new DebugAssertException();
|
||||
throw new DebugAssertException(message ?? "value should be null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +294,7 @@ namespace Robust.Shared.Utility
|
||||
{
|
||||
}
|
||||
|
||||
public DebugAssertException(string message) : base(message)
|
||||
public DebugAssertException(string? message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,26 +16,30 @@ namespace Robust.Shared.Utility;
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class FormattedMessage
|
||||
public sealed partial class FormattedMessage : IReadOnlyList<MarkupNode>
|
||||
{
|
||||
public static FormattedMessage Empty => new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of nodes the formatted message is made out of
|
||||
/// </summary>
|
||||
public IReadOnlyList<MarkupNode> Nodes => _nodes.AsReadOnly();
|
||||
public IReadOnlyList<MarkupNode> Nodes => _nodes;
|
||||
|
||||
/// <summary>
|
||||
/// true if the formatted message doesn't contain any nodes
|
||||
/// </summary>
|
||||
public bool IsEmpty => _nodes.Count == 0;
|
||||
|
||||
public int Count => _nodes.Count;
|
||||
|
||||
public MarkupNode this[int index] => _nodes[index];
|
||||
|
||||
private readonly List<MarkupNode> _nodes;
|
||||
|
||||
/// <summary>
|
||||
/// Used for inserting the correct closing node when calling <see cref="Pop"/>
|
||||
/// </summary>
|
||||
private readonly Stack<MarkupNode> _openNodeStack = new();
|
||||
private Stack<MarkupNode>? _openNodeStack;
|
||||
|
||||
public FormattedMessage()
|
||||
{
|
||||
@@ -199,6 +203,7 @@ public sealed partial class FormattedMessage
|
||||
return;
|
||||
}
|
||||
|
||||
_openNodeStack ??= new Stack<MarkupNode>();
|
||||
_openNodeStack.Push(markupNode);
|
||||
}
|
||||
|
||||
@@ -207,7 +212,7 @@ public sealed partial class FormattedMessage
|
||||
/// </summary>
|
||||
public void Pop()
|
||||
{
|
||||
if (!_openNodeStack.TryPop(out var node))
|
||||
if (_openNodeStack == null || !_openNodeStack.TryPop(out var node))
|
||||
return;
|
||||
|
||||
_nodes.Add(new MarkupNode(node.Name, null, null, true));
|
||||
@@ -238,6 +243,16 @@ public sealed partial class FormattedMessage
|
||||
return new FormattedMessageRuneEnumerator(this);
|
||||
}
|
||||
|
||||
public NodeEnumerator GetEnumerator()
|
||||
{
|
||||
return new NodeEnumerator(_nodes.GetEnumerator());
|
||||
}
|
||||
|
||||
IEnumerator<MarkupNode> IEnumerable<MarkupNode>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <returns>The string without markup tags.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
@@ -252,6 +267,11 @@ public sealed partial class FormattedMessage
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <returns>The string without filtering out markup tags.</returns>
|
||||
public string ToMarkup()
|
||||
{
|
||||
@@ -261,13 +281,13 @@ public sealed partial class FormattedMessage
|
||||
public struct FormattedMessageRuneEnumerator : IEnumerable<Rune>, IEnumerator<Rune>
|
||||
{
|
||||
private readonly FormattedMessage _msg;
|
||||
private IEnumerator<MarkupNode> _tagEnumerator;
|
||||
private List<MarkupNode>.Enumerator _tagEnumerator;
|
||||
private StringRuneEnumerator _runeEnumerator;
|
||||
|
||||
internal FormattedMessageRuneEnumerator(FormattedMessage msg)
|
||||
{
|
||||
_msg = msg;
|
||||
_tagEnumerator = msg.Nodes.GetEnumerator();
|
||||
_tagEnumerator = msg._nodes.GetEnumerator();
|
||||
// Rune enumerator will immediately give false on first iteration so I dont' need to special case anything.
|
||||
_runeEnumerator = "".EnumerateRunes();
|
||||
}
|
||||
@@ -301,7 +321,7 @@ public sealed partial class FormattedMessage
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_tagEnumerator = _msg.Nodes.GetEnumerator();
|
||||
_tagEnumerator = _msg._nodes.GetEnumerator();
|
||||
_runeEnumerator = "".EnumerateRunes();
|
||||
}
|
||||
|
||||
@@ -313,4 +333,33 @@ public sealed partial class FormattedMessage
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public struct NodeEnumerator : IEnumerator<MarkupNode>
|
||||
{
|
||||
private List<MarkupNode>.Enumerator _enumerator;
|
||||
|
||||
internal NodeEnumerator(List<MarkupNode>.Enumerator enumerator)
|
||||
{
|
||||
_enumerator = enumerator;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
return _enumerator.MoveNext();
|
||||
}
|
||||
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
((IEnumerator) _enumerator).Reset();
|
||||
}
|
||||
|
||||
public MarkupNode Current => _enumerator.Current;
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_enumerator.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class MarkupNode : IComparable<MarkupNode>
|
||||
{
|
||||
public readonly string? Name;
|
||||
public readonly MarkupParameter Value;
|
||||
public readonly Dictionary<string, MarkupParameter> Attributes = new();
|
||||
public readonly Dictionary<string, MarkupParameter> Attributes;
|
||||
public readonly bool Closing;
|
||||
|
||||
/// <summary>
|
||||
@@ -20,6 +20,7 @@ public sealed class MarkupNode : IComparable<MarkupNode>
|
||||
/// <param name="text">The plaintext the tag will consist of</param>
|
||||
public MarkupNode(string text)
|
||||
{
|
||||
Attributes = new Dictionary<string, MarkupParameter>();
|
||||
Value = new MarkupParameter(text);
|
||||
}
|
||||
|
||||
|
||||
95
Robust.UnitTesting/Shared/Collections/RingBufferListTest.cs
Normal file
95
Robust.UnitTesting/Shared/Collections/RingBufferListTest.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Collections;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Collections;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture, TestOf(typeof(RingBufferList<>))]
|
||||
public sealed class RingBufferListTest
|
||||
{
|
||||
[Test]
|
||||
public void TestBasicAdd()
|
||||
{
|
||||
var list = new RingBufferList<int>();
|
||||
list.Add(1);
|
||||
list.Add(2);
|
||||
list.Add(3);
|
||||
|
||||
Assert.That(list, NUnit.Framework.Is.EquivalentTo(new[] {1, 2, 3}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicAddAfterWrap()
|
||||
{
|
||||
var list = new RingBufferList<int>(6);
|
||||
list.Add(1);
|
||||
list.Add(2);
|
||||
list.Add(3);
|
||||
list.RemoveAt(0);
|
||||
list.Add(4);
|
||||
list.Add(5);
|
||||
list.Add(6);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Ensure wrapping properly happened and we didn't expand.
|
||||
// (one slot is wasted by nature of implementation)
|
||||
Assert.That(list.Capacity, NUnit.Framework.Is.EqualTo(6));
|
||||
Assert.That(list, NUnit.Framework.Is.EquivalentTo(new[] { 2, 3, 4, 5, 6 }));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMiddleRemoveAtScenario1()
|
||||
{
|
||||
var list = new RingBufferList<int>(6);
|
||||
list.Add(-1);
|
||||
list.Add(-1);
|
||||
list.Add(-1);
|
||||
list.Add(-1);
|
||||
list.Add(1);
|
||||
list.RemoveAt(0);
|
||||
list.RemoveAt(0);
|
||||
list.RemoveAt(0);
|
||||
list.RemoveAt(0);
|
||||
list.Add(2);
|
||||
list.Add(3);
|
||||
list.Add(4);
|
||||
list.Add(5);
|
||||
list.Remove(4);
|
||||
|
||||
Assert.That(list, NUnit.Framework.Is.EquivalentTo(new[] {1, 2, 3, 5}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMiddleRemoveAtScenario2()
|
||||
{
|
||||
var list = new RingBufferList<int>(6);
|
||||
list.Add(-1);
|
||||
list.Add(-1);
|
||||
list.Add(1);
|
||||
list.RemoveAt(0);
|
||||
list.RemoveAt(0);
|
||||
list.Add(2);
|
||||
list.Add(3);
|
||||
list.Add(4);
|
||||
list.Add(5);
|
||||
list.Remove(3);
|
||||
|
||||
Assert.That(list, NUnit.Framework.Is.EquivalentTo(new[] {1, 2, 4, 5}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMiddleRemoveAtScenario3()
|
||||
{
|
||||
var list = new RingBufferList<int>(6);
|
||||
list.Add(1);
|
||||
list.Add(2);
|
||||
list.Add(3);
|
||||
list.Add(4);
|
||||
list.Add(5);
|
||||
list.Remove(4);
|
||||
|
||||
Assert.That(list, NUnit.Framework.Is.EquivalentTo(new[] {1, 2, 3, 5}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user