mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb8fab82d7 | ||
|
|
4930c97819 | ||
|
|
7122cf90e3 | ||
|
|
84d0110477 | ||
|
|
6f28c396cf | ||
|
|
b631f408f2 | ||
|
|
34f4cf9452 | ||
|
|
6a4e4cf3b4 | ||
|
|
8aefa5c53e | ||
|
|
2cfc981aa3 | ||
|
|
4987c324d9 | ||
|
|
5450ddd0ba | ||
|
|
378a10678c | ||
|
|
2e0735b92f | ||
|
|
5756d15333 | ||
|
|
b6f74b8dea | ||
|
|
3800c5707e | ||
|
|
8f49785b4e | ||
|
|
f274de0f10 | ||
|
|
e128338f9d |
@@ -57,7 +57,7 @@
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,43 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 253.0.2
|
||||
|
||||
|
||||
## 253.0.1
|
||||
|
||||
|
||||
## 253.0.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add a new `SerializationManager.PushComposition()` overload that takes in a single parent instead of an array of parents.
|
||||
* `BoundUserInterfaceMessageAttempt` once again gets raised as a broadcast event, in addition to being directed.
|
||||
* This effectively reverts the breaking part of the changes made in v252.0.0
|
||||
* Fix CreateDistanceJoint using an int instead of a float for minimum distance.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix deferred component removal not setting the component's life stage to `ComponentLifeStage.Stopped` if the component has not yet been initialised.
|
||||
* Fix some `EntitySystem.Resolve()` overloads not respecting the optional `logMissing` argument.
|
||||
* Fix screen-space overlays not being useable without first initializing/starting entity manager & systems
|
||||
* ItemList is now significantly optimized. VV's `AddComponent` window in particular should be much faster.
|
||||
* Fix some more MapValidator fields.
|
||||
* Fix popup text overflowing the sides of the screen.
|
||||
* Improve location reporting for non-writeable datafields via analyzer.
|
||||
|
||||
### Other
|
||||
|
||||
* TestPoint now uses generics rather than IPhysShape directly.
|
||||
|
||||
|
||||
## 252.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* BoundUserInterfaceMessageAttempt is raised directed against entities and no longer broadcast.
|
||||
|
||||
|
||||
## 251.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
@@ -87,4 +87,66 @@ public sealed class DataDefinitionAnalyzerTest
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 17, 35, 50).WithArguments("Bad", "Foo")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ReadOnlyFieldTest()
|
||||
{
|
||||
const string code = """
|
||||
using System;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Serialization.Manager.Attributes
|
||||
{
|
||||
public class DataFieldBaseAttribute : Attribute;
|
||||
public class DataFieldAttribute : DataFieldBaseAttribute;
|
||||
public sealed class DataDefinitionAttribute : Attribute;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Foo
|
||||
{
|
||||
[DataField]
|
||||
public readonly int Bad;
|
||||
|
||||
[DataField]
|
||||
public int Good;
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(15,12): error RA0019: Data field Bad in data definition Foo is readonly
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldWritableRule).WithSpan(15, 12, 15, 20).WithArguments("Bad", "Foo")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ReadOnlyPropertyTest()
|
||||
{
|
||||
const string code = """
|
||||
using System;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Serialization.Manager.Attributes
|
||||
{
|
||||
public class DataFieldBaseAttribute : Attribute;
|
||||
public class DataFieldAttribute : DataFieldBaseAttribute;
|
||||
public sealed class DataDefinitionAttribute : Attribute;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Foo
|
||||
{
|
||||
[DataField]
|
||||
public int Bad { get; }
|
||||
|
||||
[DataField]
|
||||
public int Good { get; private set; }
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(15,20): error RA0020: Data field property Bad in data definition Foo does not have a setter
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldPropertyWritableRule).WithSpan(15, 20, 15, 28).WithArguments("Bad", "Foo")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to mark any type containing a nested data definition as partial."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
|
||||
public static readonly DiagnosticDescriptor DataFieldWritableRule = new(
|
||||
Diagnostics.IdDataFieldWritable,
|
||||
"Data field must not be readonly",
|
||||
"Data field {0} in data definition {1} is readonly",
|
||||
@@ -51,7 +51,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to remove the readonly modifier."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
|
||||
public static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
|
||||
Diagnostics.IdDataFieldPropertyWritable,
|
||||
"Data field property must have a setter",
|
||||
"Data field property {0} in data definition {1} does not have a setter",
|
||||
@@ -149,7 +149,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
if (IsReadOnlyDataField(type, fieldSymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
|
||||
TryGetModifierLocation(field, SyntaxKind.ReadOnlyKeyword, out var location);
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, location, fieldSymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(fieldSymbol))
|
||||
@@ -185,7 +186,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
if (IsReadOnlyDataField(type, propertySymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
|
||||
var location = property.AccessorList != null ? property.AccessorList.GetLocation() : property.GetLocation();
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, location, propertySymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(propertySymbol))
|
||||
@@ -285,6 +287,20 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetModifierLocation(MemberDeclarationSyntax syntax, SyntaxKind modifierKind, out Location location)
|
||||
{
|
||||
foreach (var modifier in syntax.Modifiers)
|
||||
{
|
||||
if (modifier.IsKind(modifierKind))
|
||||
{
|
||||
location = modifier.GetLocation();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
location = syntax.GetLocation();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
|
||||
{
|
||||
if (member is IFieldSymbol field)
|
||||
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -384,7 +384,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
|
||||
@@ -123,9 +123,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void RenderSingleWorldOverlay(Overlay overlay, Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
|
||||
{
|
||||
// Check that entity manager has started.
|
||||
// This is required for us to be able to use MapSystem.
|
||||
DebugTools.Assert(_entityManager.Started, "Entity manager should be started/initialized before rendering world-space overlays");
|
||||
|
||||
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
|
||||
|
||||
var mapId = vp.Eye!.Position.MapId;
|
||||
var mapId = vp.Eye?.Position.MapId ?? MapId.Nullspace;
|
||||
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapSystem.GetMapOrInvalid(mapId), mapId, worldBox, worldBounds);
|
||||
|
||||
if (!overlay.BeforeDraw(args))
|
||||
@@ -152,6 +156,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void RenderOverlays(Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
|
||||
{
|
||||
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
|
||||
using (DebugGroup($"Overlays: {space}"))
|
||||
{
|
||||
foreach (var overlay in GetOverlaysForSpace(space))
|
||||
@@ -176,9 +181,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var worldBounds = CalcWorldBounds(vp);
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
var mapId = vp.Eye!.Position.MapId;
|
||||
var mapId = vp.Eye?.Position.MapId ?? MapId.Nullspace;
|
||||
var mapUid = EntityUid.Invalid;
|
||||
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapSystem.GetMapOrInvalid(mapId), mapId, worldAABB, worldBounds);
|
||||
// Screen space overlays may be getting used before entity manager & entity systems have been initialized.
|
||||
// This might mean that _mapSystem is currently null.
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (_entityManager.Started && _mapSystem != null)
|
||||
mapUid = _mapSystem.GetMapOrInvalid(mapId);
|
||||
|
||||
DebugTools.Assert(_mapSystem != null || !_entityManager.Started);
|
||||
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, mapUid, mapId, worldAABB, worldBounds);
|
||||
|
||||
foreach (var overlay in list)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -83,9 +84,23 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_updateScrollbarVisibility();
|
||||
}
|
||||
|
||||
public void Add(IEnumerable<Item> items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
|
||||
|
||||
_itemList.Add(item);
|
||||
|
||||
item.OnSelected += Select;
|
||||
item.OnDeselected += Deselect;
|
||||
}
|
||||
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
public void Add(Item item)
|
||||
{
|
||||
if (item == null) return;
|
||||
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
|
||||
|
||||
_itemList.Add(item);
|
||||
@@ -93,9 +108,19 @@ namespace Robust.Client.UserInterface.Controls
|
||||
item.OnSelected += Select;
|
||||
item.OnDeselected += Deselect;
|
||||
|
||||
RecalculateContentHeight();
|
||||
if (_isAtBottom && ScrollFollowing)
|
||||
_scrollBar.MoveToEnd();
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
public void AddItems(IEnumerable<string> texts, Texture? icon = null, bool selectable = true, object? metadata = null)
|
||||
{
|
||||
var items = new ValueList<Item>();
|
||||
|
||||
foreach (var text in texts)
|
||||
{
|
||||
items.Add(new Item(this) {Text = text, Icon = icon, Selectable = selectable, Metadata = metadata});
|
||||
}
|
||||
|
||||
Add(items);
|
||||
}
|
||||
|
||||
public Item AddItem(string text, Texture? icon = null, bool selectable = true, object? metadata = null)
|
||||
@@ -107,11 +132,15 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var item in _itemList.ToArray())
|
||||
// Handle this manually so we can just clear all at once.
|
||||
foreach (var item in _itemList)
|
||||
{
|
||||
Remove(item);
|
||||
item.OnSelected -= Select;
|
||||
item.OnDeselected -= Deselect;
|
||||
}
|
||||
|
||||
_itemList.Clear();
|
||||
Recalculate();
|
||||
_totalContentHeight = 0;
|
||||
}
|
||||
|
||||
@@ -125,25 +154,35 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_itemList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
private void InternalRemoveAt(int index)
|
||||
{
|
||||
if (_itemList.Count <= index)
|
||||
return;
|
||||
|
||||
// If you modify this then also make sure to update Clear!
|
||||
var item = _itemList[index];
|
||||
_itemList.RemoveAt(index);
|
||||
|
||||
item.OnSelected -= Select;
|
||||
item.OnDeselected -= Deselect;
|
||||
}
|
||||
|
||||
public bool Remove(Item item)
|
||||
{
|
||||
if (item == null) return false;
|
||||
|
||||
var value = _itemList.Remove(item);
|
||||
|
||||
item.OnSelected -= Select;
|
||||
item.OnDeselected -= Deselect;
|
||||
|
||||
RecalculateContentHeight();
|
||||
if (_isAtBottom && ScrollFollowing)
|
||||
_scrollBar.MoveToEnd();
|
||||
Recalculate();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
Remove(this[index]);
|
||||
InternalRemoveAt(index);
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
public IEnumerator<Item> GetEnumerator()
|
||||
@@ -161,16 +200,24 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return _itemList.IndexOf(item);
|
||||
}
|
||||
|
||||
public void Insert(int index, Item item)
|
||||
private void InternalInsert(int index, Item item)
|
||||
{
|
||||
if (item == null) return;
|
||||
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
|
||||
|
||||
_itemList.Insert(index, item);
|
||||
|
||||
item.OnSelected += Select;
|
||||
item.OnDeselected += Deselect;
|
||||
}
|
||||
|
||||
public void Insert(int index, Item item)
|
||||
{
|
||||
InternalInsert(index, item);
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
private void Recalculate()
|
||||
{
|
||||
RecalculateContentHeight();
|
||||
if (_isAtBottom && ScrollFollowing)
|
||||
_scrollBar.MoveToEnd();
|
||||
@@ -191,7 +238,6 @@ namespace Robust.Client.UserInterface.Controls
|
||||
SetItems(newItems, (a,b) => string.Compare(a.Text, b.Text));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// This variant allows for a custom equality operator to compare items, when
|
||||
/// comparing the Item text is not desired.
|
||||
@@ -215,13 +261,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
else if (cmpResult > 0)
|
||||
{
|
||||
// Item exists in our list, but not in `newItems`. Remove it.
|
||||
RemoveAt(i);
|
||||
InternalRemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
else if (cmpResult < 0)
|
||||
{
|
||||
// A new entry which doesn't exist in our list. Insert it.
|
||||
Insert(i + 1, newItems[j]);
|
||||
InternalInsert(i + 1, newItems[j]);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
@@ -229,16 +275,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
// Any remaining items in our list don't exist in `newItems` so remove them
|
||||
while (i >= 0)
|
||||
{
|
||||
RemoveAt(i);
|
||||
InternalRemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
// And finally, any remaining items in `newItems` don't exist in our list. Create them.
|
||||
while (j >= 0)
|
||||
{
|
||||
Insert(0, newItems[j]);
|
||||
InternalInsert(0, newItems[j]);
|
||||
j--;
|
||||
}
|
||||
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
// Without this attribute, this would compile into a property called "Item", causing problems with the Item class.
|
||||
@@ -290,9 +338,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public void ClearSelected(int? except = null)
|
||||
{
|
||||
foreach (var item in GetSelected())
|
||||
for (var i = 0; i < _itemList.Count; i++)
|
||||
{
|
||||
if(IndexOf(item) == except) continue;
|
||||
if (i == except)
|
||||
continue;
|
||||
|
||||
var item = _itemList[i];
|
||||
item.Selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
internal sealed partial class UserInterfaceManager
|
||||
@@ -61,7 +61,7 @@ internal sealed partial class UserInterfaceManager
|
||||
Title = string.IsNullOrEmpty(title) ? Loc.GetString("popup-title") : title,
|
||||
};
|
||||
|
||||
var label = new Label { Text = contents };
|
||||
var label = new RichTextLabel { Text = $"[color=white]{FormattedMessage.EscapeText(contents)}[/color]" };
|
||||
|
||||
var vBox = new BoxContainer
|
||||
{
|
||||
|
||||
@@ -2,11 +2,15 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.ViewVariables
|
||||
{
|
||||
@@ -51,15 +55,19 @@ namespace Robust.Client.ViewVariables
|
||||
_lastSearch = search;
|
||||
EntryItemList.ClearSelected();
|
||||
EntryItemList.Clear();
|
||||
|
||||
AddButton.Disabled = true;
|
||||
var items = new ValueList<string>();
|
||||
|
||||
foreach (var component in _entries)
|
||||
{
|
||||
if(!string.IsNullOrEmpty(search) && !component.Contains(search, StringComparison.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
EntryItemList.AddItem(component);
|
||||
items.Add(component);
|
||||
}
|
||||
|
||||
EntryItemList.AddItems(items);
|
||||
}
|
||||
|
||||
private void OnSearchTextChanged(LineEdit.LineEditEventArgs obj)
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace Robust.Server
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir);
|
||||
_resources.Initialize(dataDir, hideUserDataDir: false);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
@@ -37,7 +37,7 @@ sealed class AddMapCommand : LocalizedEntityCommands
|
||||
|
||||
sealed class RemoveMapCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
|
||||
public override string Command => "rmmap";
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
@@ -51,14 +51,15 @@ sealed class RemoveMapCommand : LocalizedCommands
|
||||
}
|
||||
|
||||
var mapId = new MapId(int.Parse(args[0]));
|
||||
var mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
if (!mapSystem.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError($"Map {mapId.Value} does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
_map.DeleteMap(mapId);
|
||||
mapSystem.DeleteMap(mapId);
|
||||
shell.WriteLine($"Map {mapId.Value} was removed.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
||||
public override string Command => "tp";
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
@@ -46,7 +47,7 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
|
||||
else
|
||||
mapId = transform.MapID;
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
if (!_mapSystem.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError($"Map {mapId} doesn't exist!");
|
||||
return;
|
||||
@@ -60,9 +61,11 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
|
||||
}
|
||||
else
|
||||
{
|
||||
var mapEnt = _map.GetMapEntityIdOrThrow(mapId);
|
||||
_transform.SetWorldPosition((entity, transform), position);
|
||||
_transform.SetParent(entity, transform, mapEnt);
|
||||
if (_mapSystem.TryGetMap(mapId, out var mapEnt))
|
||||
{
|
||||
_transform.SetWorldPosition((entity, transform), position);
|
||||
_transform.SetParent(entity, transform, mapEnt.Value);
|
||||
}
|
||||
}
|
||||
|
||||
shell.WriteLine($"Teleported {shell.Player} to {mapId}:{posX},{posY}.");
|
||||
|
||||
@@ -60,9 +60,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
internal string GetPath(ResPath relPath)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()))
|
||||
// Sanitise platform-specific path and standardize it for engine use.
|
||||
.Replace(Path.DirectorySeparatorChar, '/');
|
||||
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,7 +14,11 @@ namespace Robust.Shared.ContentPack
|
||||
/// The directory to use for user data.
|
||||
/// If null, a virtual temporary file system is used instead.
|
||||
/// </param>
|
||||
void Initialize(string? userData);
|
||||
/// <param name="hideUserDataDir">
|
||||
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
|
||||
/// <see cref="IResourceManager.UserData"/>.
|
||||
/// </param>
|
||||
void Initialize(string? userData, bool hideUserDataDir);
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a single stream as a content file. Useful for unit testing.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// The root path of this provider.
|
||||
/// Can be null if it's a virtual provider.
|
||||
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
|
||||
/// </summary>
|
||||
string? RootDir { get; }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
{
|
||||
@@ -63,5 +64,27 @@ namespace Robust.Shared.ContentPack
|
||||
!OperatingSystem.IsWindows()
|
||||
&& !OperatingSystem.IsMacOS();
|
||||
|
||||
|
||||
internal static string SafeGetResourcePath(string baseDir, ResPath path)
|
||||
{
|
||||
var relSysPath = path.ToRelativeSystemPath();
|
||||
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
|
||||
// better safe than sorry check
|
||||
if (!retPath.StartsWith(baseDir))
|
||||
{
|
||||
// Allow path to match if it's just missing the directory separator at the end.
|
||||
if (retPath != baseDir.TrimEnd(Path.DirectorySeparatorChar))
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return retPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
|
||||
public IWritableDirProvider UserData { get; private set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Initialize(string? userData)
|
||||
public virtual void Initialize(string? userData, bool hideRootDir)
|
||||
{
|
||||
Sawmill = _logManager.GetSawmill("res");
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -381,6 +381,10 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
var rootDir = loader.GetPath(new ResPath(@"/"));
|
||||
|
||||
// TODO: GET RID OF THIS.
|
||||
// This code shouldn't be passing OS disk paths through ResPath.
|
||||
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
|
||||
|
||||
yield return new ResPath(rootDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,22 @@ namespace Robust.Shared.ContentPack
|
||||
/// <inheritdoc />
|
||||
internal sealed class WritableDirProvider : IWritableDirProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
private readonly bool _hideRootDir;
|
||||
|
||||
public string RootDir { get; }
|
||||
|
||||
string? IWritableDirProvider.RootDir => _hideRootDir ? null : RootDir;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="WritableDirProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="rootDir">Root file system directory to allow writing.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir)
|
||||
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
|
||||
{
|
||||
// FullName does not have a trailing separator, and we MUST have a separator.
|
||||
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
|
||||
_hideRootDir = hideRootDir;
|
||||
}
|
||||
|
||||
#region File Access
|
||||
@@ -119,7 +124,7 @@ namespace Robust.Shared.ContentPack
|
||||
throw new FileNotFoundException();
|
||||
|
||||
var dirInfo = new DirectoryInfo(GetFullPath(path));
|
||||
return new WritableDirProvider(dirInfo);
|
||||
return new WritableDirProvider(dirInfo, _hideRootDir);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -180,20 +185,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
path = path.Clean();
|
||||
|
||||
return GetFullPath(RootDir, path);
|
||||
}
|
||||
|
||||
private static string GetFullPath(string root, ResPath path)
|
||||
{
|
||||
var relPath = path.ToRelativeSystemPath();
|
||||
if (relPath.Contains("\\..") || relPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return Path.GetFullPath(Path.Combine(root, relPath));
|
||||
return PathHelpers.SafeGetResourcePath(RootDir, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,9 +155,9 @@ public sealed class EntitySerializer : ISerializationContext,
|
||||
public event IsSerializableDelegate? OnIsSerializeable;
|
||||
public delegate void IsSerializableDelegate(Entity<MetaDataComponent> ent, ref bool serializable);
|
||||
|
||||
public EntitySerializer(IDependencyCollection _dependency, SerializationOptions options)
|
||||
public EntitySerializer(IDependencyCollection dependency, SerializationOptions options)
|
||||
{
|
||||
_dependency.InjectDependencies(this);
|
||||
dependency.InjectDependencies(this);
|
||||
|
||||
_log = _logMan.GetSawmill("entity_serializer");
|
||||
SerializerProvider.RegisterSerializer(this);
|
||||
|
||||
@@ -595,12 +595,23 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (!_deleteSet.Add(component))
|
||||
{
|
||||
// already deferred deletion
|
||||
// Already deferring deletion
|
||||
DebugTools.Assert(component.LifeStage >= ComponentLifeStage.Stopped);
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.LifeStage >= ComponentLifeStage.Initialized && component.LifeStage <= ComponentLifeStage.Running)
|
||||
DebugTools.Assert(component.LifeStage >= ComponentLifeStage.Added);
|
||||
|
||||
if (component.LifeStage is >= ComponentLifeStage.Initialized and < ComponentLifeStage.Stopping)
|
||||
LifeShutdown(uid, component, _componentFactory.GetIndex(component.GetType()));
|
||||
else if (component.LifeStage == ComponentLifeStage.Added)
|
||||
{
|
||||
// The component was added, but never initialized or started. It's kinda weird to add and then
|
||||
// immediately defer-remove a component, but oh well. Let's just set the life stage directly and not
|
||||
// raise shutdown events? The removal events will still get called later.
|
||||
// This is also what LifeShutdown() would also do, albeit behind a DebugAssert.
|
||||
component.LifeStage = ComponentLifeStage.Stopped;
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -16,6 +16,7 @@ public partial class EntityManager
|
||||
/// </summary>
|
||||
internal void LifeAddToEntity(EntityUid uid, IComponent component, CompIdx idx)
|
||||
{
|
||||
DebugTools.Assert(!_deleteSet.Contains(component));
|
||||
DebugTools.Assert(component.LifeStage == ComponentLifeStage.PreAdd);
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
@@ -34,6 +35,7 @@ public partial class EntityManager
|
||||
/// </summary>
|
||||
internal void LifeInitialize(EntityUid uid, IComponent component, CompIdx idx)
|
||||
{
|
||||
DebugTools.Assert(!_deleteSet.Contains(component));
|
||||
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Added);
|
||||
|
||||
component.LifeStage = ComponentLifeStage.Initializing;
|
||||
@@ -47,6 +49,7 @@ public partial class EntityManager
|
||||
/// </summary>
|
||||
internal void LifeStartup(EntityUid uid, IComponent component, CompIdx idx)
|
||||
{
|
||||
DebugTools.Assert(!_deleteSet.Contains(component));
|
||||
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Initialized);
|
||||
|
||||
component.LifeStage = ComponentLifeStage.Starting;
|
||||
|
||||
@@ -37,18 +37,22 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref MetaDataComponent? component,
|
||||
protected bool Resolve(
|
||||
EntityUid uid,
|
||||
[NotNullWhen(true)] ref MetaDataComponent? component,
|
||||
bool logMissing = true)
|
||||
{
|
||||
return EntityManager.MetaQuery.Resolve(uid, ref component);
|
||||
return EntityManager.MetaQuery.Resolve(uid, ref component, logMissing);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TransformComponent? component,
|
||||
protected bool Resolve(
|
||||
EntityUid uid,
|
||||
[NotNullWhen(true)] ref TransformComponent? component,
|
||||
bool logMissing = true)
|
||||
{
|
||||
return EntityManager.TransformQuery.Resolve(uid, ref component);
|
||||
return EntityManager.TransformQuery.Resolve(uid, ref component, logMissing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -121,9 +121,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (msg.Message is not CloseBoundInterfaceMessage && ui.RequireInputValidation)
|
||||
{
|
||||
var attempt = new BoundUserInterfaceMessageAttempt(sender, uid, msg.UiKey, msg.Message);
|
||||
|
||||
RaiseLocalEvent(attempt);
|
||||
if (attempt.Cancelled)
|
||||
return;
|
||||
|
||||
RaiseLocalEvent(uid, attempt);
|
||||
if (attempt.Cancelled)
|
||||
return;
|
||||
}
|
||||
|
||||
// get the wrapped message and populate it with the sender & UI key information.
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Robust.Shared.Map.Commands;
|
||||
/// </summary>
|
||||
public sealed class AmbientLightCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
|
||||
public string Command => $"setambientlight";
|
||||
@@ -32,10 +31,11 @@ public sealed class AmbientLightCommand : IConsoleCommand
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
var mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
|
||||
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
if (!mapSystem.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid"));
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid", ("arg", mapId.Value)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ public sealed class AmbientLightCommand : IConsoleCommand
|
||||
}
|
||||
|
||||
var color = Color.FromSrgb(new Color(r, g, b, a));
|
||||
var mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
|
||||
mapSystem.SetAmbientLight(mapId, color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
/// <summary>
|
||||
/// Tests whether a particular point is contained in the shape.
|
||||
/// </summary>
|
||||
public bool TestPoint(IPhysShape shape, Transform xform, Vector2 worldPoint)
|
||||
public bool TestPoint<T>(T shape, Transform xform, Vector2 worldPoint) where T : IPhysShape
|
||||
{
|
||||
switch (shape)
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private EntityQuery<JointComponent> _jointsQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
@@ -237,7 +238,7 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
string? id = null,
|
||||
TransformComponent? xformA = null,
|
||||
TransformComponent? xformB = null,
|
||||
int? minimumDistance = null)
|
||||
float? minimumDistance = null)
|
||||
{
|
||||
if (!Resolve(bodyA, ref xformA) || !Resolve(bodyB, ref xformB))
|
||||
{
|
||||
@@ -247,8 +248,8 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
anchorA ??= Vector2.Zero;
|
||||
anchorB ??= Vector2.Zero;
|
||||
|
||||
var vecA = Vector2.Transform(anchorA.Value, xformA.WorldMatrix);
|
||||
var vecB = Vector2.Transform(anchorB.Value, xformB.WorldMatrix);
|
||||
var vecA = Vector2.Transform(anchorA.Value, _transform.GetWorldMatrix(xformA));
|
||||
var vecB = Vector2.Transform(anchorB.Value, _transform.GetWorldMatrix(xformB));
|
||||
var length = (vecA - vecB).Length();
|
||||
if (minimumDistance != null)
|
||||
length = Math.Max(minimumDistance.Value, length);
|
||||
|
||||
@@ -408,16 +408,26 @@ namespace Robust.Shared.Prototypes
|
||||
if (nonPushedParent)
|
||||
continue;
|
||||
|
||||
var parentMaps = new MappingDataNode[parents.Length];
|
||||
for (var i = 0; i < parentMaps.Length; i++)
|
||||
if (parents.Length == 1)
|
||||
{
|
||||
parentMaps[i] = kindData.Results[parents[i]];
|
||||
kindData.Results[id] = _serializationManager.PushCompositionWithGenericNode(
|
||||
kind,
|
||||
kindData.Results[parents[0]],
|
||||
kindData.RawResults[id]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var parentMaps = new MappingDataNode[parents.Length];
|
||||
for (var i = 0; i < parentMaps.Length; i++)
|
||||
{
|
||||
parentMaps[i] = kindData.Results[parents[i]];
|
||||
}
|
||||
|
||||
kindData.Results[id] = _serializationManager.PushCompositionWithGenericNode(
|
||||
kind,
|
||||
parentMaps,
|
||||
kindData.RawResults[id]);
|
||||
kindData.Results[id] = _serializationManager.PushCompositionWithGenericNode(
|
||||
kind,
|
||||
parentMaps,
|
||||
kindData.RawResults[id]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -629,16 +639,26 @@ namespace Robust.Shared.Prototypes
|
||||
{
|
||||
if (tree.TryGetParents(id, out var parents))
|
||||
{
|
||||
var parentNodes = new MappingDataNode[parents.Length];
|
||||
for (var i = 0; i < parents.Length; i++)
|
||||
if (parents.Length == 1)
|
||||
{
|
||||
parentNodes[i] = results[parents[i]].Result;
|
||||
datum.Result = _serializationManager.PushCompositionWithGenericNode(
|
||||
kind,
|
||||
results[parents[0]].Result,
|
||||
datum.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
var parentNodes = new MappingDataNode[parents.Length];
|
||||
for (var i = 0; i < parents.Length; i++)
|
||||
{
|
||||
parentNodes[i] = results[parents[i]].Result;
|
||||
}
|
||||
|
||||
datum.Result = _serializationManager.PushCompositionWithGenericNode(
|
||||
kind,
|
||||
parentNodes,
|
||||
datum.Result);
|
||||
datum.Result = _serializationManager.PushCompositionWithGenericNode(
|
||||
kind,
|
||||
parentNodes,
|
||||
datum.Result);
|
||||
}
|
||||
}
|
||||
|
||||
if (tree.TryGetChildren(id, out var children))
|
||||
|
||||
@@ -421,6 +421,7 @@ namespace Robust.Shared.Serialization.Manager
|
||||
#region Composition
|
||||
|
||||
DataNode PushComposition(Type type, DataNode[] parents, DataNode child, ISerializationContext? context = null);
|
||||
DataNode PushComposition(Type type, DataNode parent, DataNode child, ISerializationContext? context = null);
|
||||
|
||||
public TNode PushComposition<TType, TNode>(TNode[] parents, TNode child, ISerializationContext? context = null) where TNode : DataNode
|
||||
{
|
||||
@@ -428,6 +429,12 @@ namespace Robust.Shared.Serialization.Manager
|
||||
return (TNode)PushComposition(typeof(TType), parents, child, context);
|
||||
}
|
||||
|
||||
public TNode PushComposition<TType, TNode>(TNode parent, TNode child, ISerializationContext? context = null)
|
||||
where TNode : DataNode
|
||||
{
|
||||
return (TNode) PushComposition(typeof(TType), parent, child, context);
|
||||
}
|
||||
|
||||
TNode PushInheritance<TType, TNode>(ITypeInheritanceHandler<TType, TNode> inheritanceHandler, TNode parent, TNode child,
|
||||
ISerializationContext? context = null) where TNode : DataNode;
|
||||
|
||||
@@ -441,6 +448,12 @@ namespace Robust.Shared.Serialization.Manager
|
||||
return (TNode) PushComposition(type, parents, child, context);
|
||||
}
|
||||
|
||||
public TNode PushCompositionWithGenericNode<TNode>(Type type, TNode parent, TNode child, ISerializationContext? context = null)
|
||||
where TNode : DataNode
|
||||
{
|
||||
return (TNode) PushComposition(type, parent, child, context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple <see cref="MappingDataNode"/> inheritance pusher clones data and overrides a parent's values with
|
||||
/// the child's.
|
||||
|
||||
@@ -24,27 +24,23 @@ public partial class SerializationManager
|
||||
|
||||
private readonly ConcurrentDictionary<(Type value, Type node), PushCompositionDelegate> _compositionPushers = new();
|
||||
|
||||
public DataNode PushComposition(Type type, DataNode[] parents, DataNode child, ISerializationContext? context = null)
|
||||
public DataNode PushComposition(
|
||||
Type type,
|
||||
DataNode[] parents,
|
||||
DataNode child,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
// TODO SERIALIZATION
|
||||
// Add variant that doesn't require a parent array.
|
||||
|
||||
// TODO SERIALIZATION
|
||||
// Change inheritance pushing so that it modifies the passed in child. This avoids re-creating the child
|
||||
// multiple times when there are multiple children.
|
||||
// multiple times when there are multiple parents.
|
||||
//
|
||||
// I.e., change the PushCompositionDelegate signature to not have a return value, and also add an override
|
||||
// of this method that modified the given child.
|
||||
// of this method that modifies the given child.
|
||||
|
||||
if (parents.Length == 0)
|
||||
return child.Copy();
|
||||
|
||||
DebugTools.Assert(parents.All(x => x.GetType() == child.GetType()));
|
||||
|
||||
|
||||
// the child.Clone() statement to the beginning here, then make the delegate modify the clone.
|
||||
// Currently pusing more than one parent requires multiple unnecessary clones.
|
||||
|
||||
var pusher = GetOrCreatePushCompositionDelegate(type, child);
|
||||
|
||||
var node = child;
|
||||
@@ -61,6 +57,22 @@ public partial class SerializationManager
|
||||
return node;
|
||||
}
|
||||
|
||||
public DataNode PushComposition(
|
||||
Type type,
|
||||
DataNode parent,
|
||||
DataNode child,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
DebugTools.AssertEqual(parent.GetType(), child.GetType());
|
||||
var pusher = GetOrCreatePushCompositionDelegate(type, child);
|
||||
|
||||
var newNode = pusher(type, parent, child, context);
|
||||
|
||||
// Currently delegate pusher should be returning a new instance, and not modifying the passed in child.
|
||||
DebugTools.Assert(!ReferenceEquals(newNode, child));
|
||||
return newNode;
|
||||
}
|
||||
|
||||
private PushCompositionDelegate GetOrCreatePushCompositionDelegate(Type type, DataNode node)
|
||||
{
|
||||
return _compositionPushers.GetOrAdd((type, node.GetType()), static (tuple, vfArgument) =>
|
||||
@@ -185,7 +197,7 @@ public partial class SerializationManager
|
||||
{
|
||||
if (field.InheritanceBehavior == InheritanceBehavior.Always)
|
||||
{
|
||||
newMapping[key] = PushComposition(field.FieldType, new[] { parentValue }, childValue, context);
|
||||
newMapping[key] = PushComposition(field.FieldType, parentValue, childValue, context);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -207,7 +207,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
|
||||
{
|
||||
newCompReg[idx] = serializationManager.PushCompositionWithGenericNode(
|
||||
reg.Type,
|
||||
new[] { parent[mapping] },
|
||||
parent[mapping],
|
||||
newCompReg[idx],
|
||||
context);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Robust.UnitTesting.Shared.Resources
|
||||
_testDir = Directory.CreateDirectory(_testDirPath);
|
||||
var subDir = Path.Combine(_testDirPath, "writable");
|
||||
|
||||
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir));
|
||||
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir), hideRootDir: false);
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
|
||||
@@ -21,6 +21,8 @@ proto:
|
||||
---
|
||||
entity:
|
||||
uid: int()
|
||||
paused: bool(required=False)
|
||||
mapInit: bool(required=False)
|
||||
components: list(comp())
|
||||
missingComponents: list(str(), required=False)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user