Compare commits

...

22 Commits

Author SHA1 Message Date
metalgearsloth
b146b1b82c Version: 254.0.0 2025-04-18 19:06:45 +10:00
Leon Friedrich
2f8f4f2f7a Make MappingDataNode use string keys (#5783)
* string keys

* obsoletions

* Fix ValueTupleSerializer

* Release notes

* fix release note conflict

* cleanup

* Fix yaml validator & tests

* cleanup release notes

* a

* enumerator allocations

* Also sequence enumerator alloc
2025-04-18 19:01:34 +10:00
Tayrtahn
7365a59bd9 Cleanup warnings in CollisionWake_Test (#5847) 2025-04-18 12:05:48 +10:00
Tayrtahn
37560f663b Add GetContainingContainers method to SharedContainerSystem (#5803)
* Add SharedContainerSystem.EnumerateContainingContainers

* More obvious name
2025-04-17 21:51:23 +10:00
TemporalOroboros
bd489e9218 A compilation of simple one-line fixes (#5661)
* Fix warnings in SharedJointSystem

* Fix reference to EC TransformComponent method
Replaces call to TransformComponent.GetMapUid with SharedTransformSystem.GetMap

* Fix obsolete calls in SharedPhysicsSystem.Contacts.cs
Fixes a couple calls to obsolete varients of SetAwake and an obsolete call to RegenerateContacts by converting them to their Entity<T> varients

* Fix obsolete call in SharedPhysicsSystem.Components.cs
Adds one set of parenthesis to convert a 'uid, comp, comp, comp' call to an 'Entity<T, T, T> call.

* Removes unused local var
Removes an unused list of broadphases that was being allocated in TryCollideRect

* One-line fixes in SharedPhysicsSystem.Islands.cs
Fixes all of the easy warnings regarding physics island processing, the rest require more complicated changes than a simple argument rearrangement

* Fix obsolete method call in SharedMapSystem

* Fix a few obsolete ToMap calls in EntityLookup.Queries

* Fix calls to obsolete EntityCoordinate methods in SharedMapSystem.Grids

* Fix calls to obsolete EntityCoordinate methods in SharedLookupSystem.ComponentQueries

* Fix a few obsolete method calls in entity spawning

* Fix obsolete method calls in MapLoaderSystem

* Fix obsolete method call in GridFixtureSystem

* Fix obsolete IsMapInitialized call in SaveMap command

* Fix obsolete MapPosition reference in Client.EyeSystem

* Fix obsolete EntitySystem.Get<TSystem> references in DebugLightTreeSystem

* Fix obsolete EntitySystem.Get<TSystem> reference in DebugEntityLookup command

* Fix obsolete method calls in SpriteBoundsOverlay
Slightly more complicated than the rest, but it's really just changing an unused dependency over to use SharedTransformSystem

* Remove unused IClyde references from controls
LineEdit and TextEdit never use their IClyde dependencies and it generates a warning so yeet

* Remove use of EntitySystem.Get from lightbb command

* Fix DebugDrawingSystem
Removes a bunch of unused private IEntityManager vars
Also removes an obsolete use of TransformComponent.GetWorldPositionRotation

* Removes duplicate position set when splitting grids
There's nothing saying why this is this way and the blame looks like it was an oversight when replacing a bit where they set position and then rotation
Please, oh Chesterton's Fence, spare me your wrath

* Fix obsolete method use in PlacementMode

* Fix obsolete method use in Placement Modes

* Removes unused local var in gamestate management

* Fix unreachable code warnings in gamestate management
Use #else sections to make sure they don't complain about being on the wrong side of a throw

* Fix obsolete ToMap use in EyeManager

* Make InputManager use a sawmill to log

* Fix obsolete ContainerManagerComponent method calls in ContainerSystem

* Make ClientPrototypeManager use a sawmill for logging

* poke tests

* Use LocalizedEntityCommands for SpriteBoundsOverlay toggle

* Use LocalizedEntityCommands for system toggles

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2025-04-17 11:57:03 +10:00
metalgearsloth
33166e8866 Component lifecycle generics (#5844)
Non-breaking minor.
2025-04-16 21:46:14 +10:00
Leon Friedrich
ed16032280 Disable tile edges (#5842) 2025-04-16 21:25:31 +10:00
metalgearsloth
cf785c886b Mark GetCollidingEntities as obsolete (#5831)
Bad methods, entitylookup is better (it does everything for area queries) and anyone who wants contacts should be querying those directly.
2025-04-14 18:50:01 +10:00
metalgearsloth
6f28c396cf Version: 253.0.0 2025-04-14 14:12:32 +10:00
metalgearsloth
b631f408f2 ItemList optimisation (#5796)
- VV AddComponent window no longer takes 300ms every time you press a key.
- Significantly optimise ItemList internally.
2025-04-14 13:55:48 +10:00
Leon Friedrich
34f4cf9452 More map validator fixes (#5820) 2025-04-14 13:55:02 +10:00
B_Kirill
6a4e4cf3b4 Cleanup warnings: Commands (#5825) 2025-04-14 13:54:42 +10:00
metalgearsloth
8aefa5c53e Make TestPoint use generics (#5828) 2025-04-14 01:49:07 +10:00
DrSmugleaf
2cfc981aa3 Fix popup text overflowing the sides of the screen (#5788)
* Fix popup text overflowing the sides of the screen

* Fix not escaping text
2025-04-13 12:59:23 +02:00
Leon Friedrich
4987c324d9 Fix bad debug assert (#5826) 2025-04-13 20:50:46 +10:00
DrSmugleaf
5450ddd0ba Fix SharedJointSystem.CreateDistanceJoint taking in an int for minimumDistance instead of a float (#5824) 2025-04-13 16:22:22 +10:00
Tayrtahn
378a10678c Improve location reporting for non-writeable DataFields (#5715)
* Better location reporting for readonly DataField errors

* Better location reporting for readonly DataField property errors

* Use SyntaxKind instead of string for TryGetModifierLocation
2025-04-13 16:22:05 +10:00
Leon Friedrich
2e0735b92f Fix NRE in screen-space overlays (#5823)
* Fix pre-init screen-space overlays

* Debug asserts
2025-04-13 16:21:02 +10:00
Leon Friedrich
5756d15333 Make BoundUserInterfaceMessageAttempt broadcast again (#5821) 2025-04-12 18:22:13 +10:00
Leon Friedrich
b6f74b8dea Fix logMissing in EntitySystem.Resolve() (#5822) 2025-04-12 17:40:08 +10:00
Leon Friedrich
3800c5707e Add new SerializationManager.PushComposition overload (#5785) 2025-04-12 12:58:40 +10:00
Leon Friedrich
8f49785b4e Fix RemCompDeferred not always setting lifestage (#5786)
* Fix RemCompDeferred not setting lifestage

* spaelling
2025-04-12 12:57:11 +10:00
76 changed files with 891 additions and 449 deletions

View File

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

View File

@@ -54,6 +54,53 @@ END TEMPLATE-->
*None yet*
## 254.0.0
### Breaking changes
* Yaml mappings/dictionaries now only support string keys instead of generic nodes
* Several MappingDataNode method arguments or return values now use strings instead of a DataNode object
* The MappingDataNode class has various helper methods that still accept a ValueDataNode, but these methods are marked as obsolete and may be removed in the future.
* yaml validators should use `MappingDataNode.GetKeyNode()` when validating mapping keys, so that errors can print node start & end information
* ValueTuple yaml serialization has changed
* Previously they would get serialized into a single mapping with one entry (i.e., `{foo : bar }`
* Now they serialize into a sequence (i.e., `[foo, bar]`
* The ValueTuple serializer will still try to read mappings, but due to the MappingDataNode this may fail if the previously serialized "key" can't be read as a simple string
### New features
* Add cvar to disable tile edges.
* Add GetContainingContainers method to ContainerSystem to recursively get containers upwards on an entity.
### Internal
* Make component lifecycle methods use generics.
## 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

View File

@@ -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")
);
}
}

View File

@@ -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)

View File

@@ -1,17 +1,19 @@
#if DEBUG
using Robust.Client.Debugging;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
public sealed class DebugAnchoredCommand : LocalizedCommands
public sealed class DebugAnchoredCommand : LocalizedEntityCommands
{
[Dependency] private readonly DebugAnchoringSystem _system = default!;
public override string Command => "showanchored";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugAnchoringSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}
}

View File

@@ -1,17 +1,19 @@
#if DEBUG
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
internal sealed class LightDebugCommand : LocalizedCommands
internal sealed class LightDebugCommand : LocalizedEntityCommands
{
[Dependency] private readonly DebugLightTreeSystem _system = default!;
public override string Command => "lightbb";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}
}

View File

@@ -1,19 +1,18 @@
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
public sealed class ShowPlayerVelocityCommand : LocalizedCommands
public sealed class ShowPlayerVelocityCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
[Dependency] private readonly ShowPlayerVelocityDebugSystem _system = default!;
public override string Command => "showplayervelocity";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_entitySystems.GetEntitySystem<ShowPlayerVelocityDebugSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}
}

View File

@@ -10,19 +10,21 @@ using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
public sealed class ShowSpriteBBCommand : LocalizedCommands
public sealed class ShowSpriteBBCommand : LocalizedEntityCommands
{
[Dependency] private readonly SpriteBoundsSystem _system = default!;
public override string Command => "showspritebb";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<SpriteBoundsSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}
public sealed class SpriteBoundsSystem : EntitySystem
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly SpriteTreeSystem _spriteTree = default!;
@@ -40,7 +42,7 @@ namespace Robust.Client.GameObjects
if (_enabled)
{
DebugTools.AssertNull(_overlay);
_overlay = new SpriteBoundsOverlay(_spriteTree, _entityManager);
_overlay = new SpriteBoundsOverlay(_spriteTree, _xformSystem);
_overlayManager.AddOverlay(_overlay);
}
else
@@ -59,13 +61,13 @@ namespace Robust.Client.GameObjects
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly IEntityManager _entityManager;
private readonly SharedTransformSystem _xformSystem;
private SpriteTreeSystem _renderTree;
public SpriteBoundsOverlay(SpriteTreeSystem renderTree, IEntityManager entityManager)
public SpriteBoundsOverlay(SpriteTreeSystem renderTree, SharedTransformSystem xformSystem)
{
_renderTree = renderTree;
_entityManager = entityManager;
_xformSystem = xformSystem;
}
protected internal override void Draw(in OverlayDrawArgs args)
@@ -76,7 +78,7 @@ namespace Robust.Client.GameObjects
foreach (var (sprite, xform) in _renderTree.QueryAabb(currentMap, viewport))
{
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var (worldPos, worldRot) = _xformSystem.GetWorldPositionRotation(xform);
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
// Get scaled down bounds used to indicate the "south" of a sprite.

View File

@@ -303,7 +303,7 @@ namespace Robust.Client.GameObjects
while (parent.IsValid() && (!spriteOccluded || !lightOccluded))
{
var parentXform = TransformQuery.GetComponent(parent);
if (TryComp<ContainerManagerComponent>(parent, out var manager) && manager.TryGetContainer(child, out var container))
if (TryComp<ContainerManagerComponent>(parent, out var manager) && TryGetContainingContainer(parent, child, out var container, manager))
{
spriteOccluded = spriteOccluded || !container.ShowContents;
lightOccluded = lightOccluded || container.OccludesLight;
@@ -344,7 +344,7 @@ namespace Robust.Client.GameObjects
var childLightOccluded = lightOccluded;
// We already know either sprite or light is not occluding so need to check container.
if (manager.TryGetContainer(child, out var container))
if (TryGetContainingContainer(entity, child, out var container, manager))
{
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
childLightOccluded = childLightOccluded || container.OccludesLight;

View File

@@ -2,24 +2,24 @@ using System.Collections.Generic;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.Containers;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Utility;
using Color = Robust.Shared.Maths.Color;
namespace Robust.Client.GameObjects;
public sealed class DebugEntityLookupCommand : LocalizedCommands
public sealed class DebugEntityLookupCommand : LocalizedEntityCommands
{
[Dependency] private readonly DebugEntityLookupSystem _system = default!;
public override string Command => "togglelookup";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugEntityLookupSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}

View File

@@ -26,10 +26,10 @@ namespace Robust.Client.GameObjects
if (_enabled)
{
_lightOverlay = new DebugLightOverlay(
EntitySystem.Get<EntityLookupSystem>(),
EntityManager.System<EntityLookupSystem>(),
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IMapManager>(),
Get<LightTreeSystem>());
EntityManager.System<LightTreeSystem>());
overlayManager.AddOverlay(_lightOverlay);
}

View File

@@ -62,7 +62,7 @@ public sealed class EyeSystem : SharedEyeSystem
eyeComponent.Target = null;
}
eyeComponent.Eye.Position = xform.MapPosition;
eyeComponent.Eye.Position = TransformSystem.GetMapCoordinates(xform);
}
}
}

View File

@@ -123,7 +123,8 @@ namespace Robust.Client.Graphics
/// <inheritdoc />
public ScreenCoordinates CoordinatesToScreen(EntityCoordinates point)
{
return MapToScreen(point.ToMap(_entityManager, _entityManager.System<SharedTransformSystem>()));
var transformSystem = _entityManager.System<SharedTransformSystem>();
return MapToScreen(transformSystem.ToMapCoordinates(point));
}
public ScreenCoordinates MapToScreen(MapCoordinates point)

View File

@@ -30,6 +30,23 @@ namespace Robust.Client.Graphics.Clyde
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
private List<Entity<MapGridComponent>> _grids = new();
private bool _drawTileEdges;
private void RenderTileEdgesChanges(bool value)
{
_drawTileEdges = value;
if (!value)
return;
// Dirty all Edges
foreach (var gridData in _mapChunkData.Values)
{
foreach (var chunk in gridData.Values)
{
chunk.EdgeDirty = true;
}
}
}
private void _drawGrids(Viewport viewport, Box2 worldAABB, Box2Rotated worldBounds, IEye eye)
{
@@ -81,6 +98,9 @@ namespace Robust.Client.Graphics.Clyde
_updateChunkMesh(mapGrid, chunk, datum);
if (!_drawTileEdges)
continue;
// Dirty edge tiles for next step.
datum.EdgeDirty = true;
@@ -99,17 +119,16 @@ namespace Robust.Client.Graphics.Clyde
}
}
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
// Handle edge sprites.
while (enumerator.MoveNext(out var chunk))
if (_drawTileEdges)
{
var datum = data[chunk.Indices];
if (!datum.EdgeDirty)
continue;
_updateChunkEdges(mapGrid, chunk, datum);
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
while (enumerator.MoveNext(out var chunk))
{
var datum = data[chunk.Indices];
if (datum.EdgeDirty)
_updateChunkEdges(mapGrid, chunk, datum);
}
}
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
@@ -129,7 +148,7 @@ namespace Robust.Client.Graphics.Clyde
CheckGlError();
}
if (datum.EdgeCount > 0)
if (_drawTileEdges && datum.EdgeCount > 0)
{
BindVertexArray(datum.EdgeVAO);
CheckGlError();
@@ -138,7 +157,6 @@ namespace Robust.Client.Graphics.Clyde
GL.DrawElements(GetQuadGLPrimitiveType(), datum.EdgeCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
CheckGlError();
}
}
requiresFlush = false;
@@ -274,7 +292,8 @@ namespace Robust.Client.Graphics.Clyde
var gridX = x + chunkOriginScaled.X;
var gridY = y + chunkOriginScaled.Y;
var tile = chunk.GetTile(x, y);
var tileDef = _tileDefinitionManager[tile.TypeId];
if (!_tileDefinitionManager.TryGetDefinition(tile.TypeId, out var tileDef))
continue;
// Edge render
for (var nx = -1; nx <= 1; nx++)
@@ -288,7 +307,8 @@ namespace Robust.Client.Graphics.Clyde
if (!maps.TryGetTile(grid.Comp, neighborIndices, out var neighborTile))
continue;
var neighborDef = _tileDefinitionManager[neighborTile.TypeId];
if (!_tileDefinitionManager.TryGetDefinition(neighborTile.TypeId, out var neighborDef))
continue;
// If it's the same tile then no edge to be drawn.
if (tile.TypeId == neighborTile.TypeId || neighborDef.EdgeSprites.Count == 0)

View File

@@ -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)
{

View File

@@ -121,6 +121,7 @@ namespace Robust.Client.Graphics.Clyde
_cfg.OnValueChanged(CVars.LightSoftShadows, SoftShadowsChanged, true);
_cfg.OnValueChanged(CVars.MaxLightCount, MaxLightsChanged, true);
_cfg.OnValueChanged(CVars.MaxOccluderCount, MaxOccludersChanged, true);
_cfg.OnValueChanged(CVars.RenderTileEdges, RenderTileEdgesChanges, true);
// I can't be bothered to tear down and set these threads up in a cvar change handler.
// Windows and Linux can be trusted to not explode with threaded windowing,

View File

@@ -48,6 +48,7 @@ namespace Robust.Client.Input
[Dependency] private readonly IUserInterfaceManagerInternal _uiMgr = default!;
[Dependency] private readonly IConsoleHost _console = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
private ISawmill _logger = default!;
private bool _currentlyFindingViewport;
@@ -114,6 +115,8 @@ namespace Robust.Client.Input
/// <inheritdoc />
public void Initialize()
{
_logger = Logger.GetSawmill("input");
NetworkBindMap = new BoundKeyMap(_reflectionManager);
NetworkBindMap.PopulateKeyFunctionsMap();
@@ -130,7 +133,7 @@ namespace Robust.Client.Input
}
catch (Exception e)
{
Logger.ErrorS("input", "Failed to load user keybindings: " + e);
_logger.Error("Failed to load user keybindings: " + e);
}
}
@@ -531,8 +534,7 @@ namespace Robust.Client.Input
if (reg.Type != KeyBindingType.Command && !NetworkBindMap.FunctionExists(reg.Function.FunctionName))
{
Logger.DebugS("input", "Key function in {0} does not exist: '{1}'.", file,
reg.Function);
_logger.Debug("Key function in {0} does not exist: '{1}'.", file, reg.Function);
invalid = true;
}

View File

@@ -30,11 +30,13 @@ namespace Robust.Client.Placement.Modes
return;
}
var mapId = MouseCoords.GetMapId(pManager.EntityManager);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var mapId = transformSys.GetMapId(MouseCoords);
var snapToEntities = EntitySystem.Get<EntityLookupSystem>().GetEntitiesInRange(MouseCoords, SnapToRange)
var snapToEntities = pManager.EntityManager.System<EntityLookupSystem>()
.GetEntitiesInRange(MouseCoords, SnapToRange)
.Where(entity => pManager.EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent<TransformComponent>(entity).MapID == mapId)
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager, pManager.EntityManager.System<SharedTransformSystem>())).LengthSquared())
.OrderBy(entity => (transformSys.GetWorldPosition(entity) - transformSys.ToMapCoordinates(MouseCoords).Position).LengthSquared())
.ToList();
if (snapToEntities.Count == 0)
@@ -51,10 +53,11 @@ namespace Robust.Client.Placement.Modes
var closestBounds = component.BaseRSI.Size;
var closestPos = transformSys.GetWorldPosition(closestTransform);
var closestRect =
Box2.FromDimensions(
closestTransform.WorldPosition.X - closestBounds.X / 2f,
closestTransform.WorldPosition.Y - closestBounds.Y / 2f,
closestPos.X - closestBounds.X / 2f,
closestPos.Y - closestBounds.Y / 2f,
closestBounds.X, closestBounds.Y);
var sides = new[]

View File

@@ -21,7 +21,8 @@ namespace Robust.Client.Placement.Modes
{
MouseCoords = ScreenToCursorGrid(mouseScreen);
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var gridIdOpt = transformSys.GetGrid(MouseCoords);
SnapSize = 1f;
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{

View File

@@ -16,8 +16,10 @@ namespace Robust.Client.Placement.Modes
{
MouseCoords = ScreenToCursorGrid(mouseScreen);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var tileSize = 1f;
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
var gridIdOpt = transformSys.GetGrid(MouseCoords);
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{

View File

@@ -16,9 +16,11 @@ namespace Robust.Client.Placement.Modes
{
MouseCoords = ScreenToCursorGrid(mouseScreen);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var tileSize = 1f;
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
var gridIdOpt = transformSys.GetGrid(MouseCoords);
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);

View File

@@ -121,8 +121,8 @@ namespace Robust.Client.Placement
{
if (!coordinate.IsValid(pManager.EntityManager))
return; // Just some paranoia just in case
var worldPos = coordinate.ToMapPos(pManager.EntityManager, transformSys);
var worldRot = pManager.EntityManager.GetComponent<TransformComponent>(coordinate.EntityId).WorldRotation + dirAng;
var worldPos = transformSys.ToMapCoordinates(coordinate).Position;
var worldRot = transformSys.GetWorldRotation(coordinate.EntityId) + dirAng;
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
var rot = args.Viewport.Eye?.Rotation ?? default;
@@ -230,7 +230,7 @@ namespace Robust.Client.Placement
var range = pManager.CurrentPermission!.Range;
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
if (range > 0 && !pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates.InRange(pManager.EntityManager, transformSys, coordinates, range))
if (range > 0 && !transformSys.InRange(pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates, coordinates, range))
return false;
return true;
}
@@ -239,7 +239,7 @@ namespace Robust.Client.Placement
{
var bounds = pManager.ColliderAABB;
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var mapCoords = coordinates.ToMap(pManager.EntityManager, transformSys);
var mapCoords = transformSys.ToMapCoordinates(coordinates);
var (x, y) = mapCoords.Position;
var collisionBox = Box2.FromDimensions(
@@ -248,7 +248,9 @@ namespace Robust.Client.Placement
bounds.Width,
bounds.Height);
return EntitySystem.Get<SharedPhysicsSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
return pManager.EntityManager
.System<SharedPhysicsSystem>()
.TryCollideRect(collisionBox, mapCoords.MapId);
}
protected Vector2 ScreenToWorld(Vector2 point)
@@ -265,10 +267,8 @@ namespace Robust.Client.Placement
{
var mapCoords = pManager.EyeManager.PixelToMap(coords.Position);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid))
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var gridUid, out _))
{
return transformSys.ToCoordinates(mapCoords);
}

View File

@@ -2,9 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using Robust.Client.Timing;
using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
@@ -56,7 +54,7 @@ namespace Robust.Client.Prototypes
using var _ = _timing.StartStateApplicationArea();
ReloadPrototypes([file]);
Logger.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms");
Sawmill.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms");
#endif
}
}

View File

@@ -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;
}
}

View File

@@ -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
{

View File

@@ -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)

View File

@@ -268,7 +268,6 @@ namespace Robust.Server.Physics
newGrids[i] = newGridUid;
// Keep same origin / velocity etc; this makes updating a lot faster and easier.
_xformSystem.SetWorldPosition(newGridXform, gridPos);
_xformSystem.SetWorldPositionRotation(newGridUid, gridPos, gridRot, newGridXform);
var splitBody = _bodyQuery.GetComponent(newGridUid);
_physics.SetLinearVelocity(newGridUid, mapBody.LinearVelocity, body: splitBody);

View File

@@ -968,6 +968,13 @@ namespace Robust.Shared
public static readonly CVarDef<string> RenderFOVColor =
CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Whether to render tile edges, which is where some tiles can partially overlap other adjacent tiles on a grid.
/// E.g., snow tiles partly extending beyond their own tile to blend together with different adjacent tiles types.
/// </summary>
public static readonly CVarDef<bool> RenderTileEdges =
CVarDef.Create("render.tile_edges", true, CVar.CLIENTONLY);
/*
* CONTROLS
*/

View File

@@ -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.");
}
}

View File

@@ -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}.");

View File

@@ -530,6 +530,39 @@ namespace Robust.Shared.Containers
return false;
}
/// <summary>
/// Returns the full chain of containers containing the entity passed in, from innermost to outermost.
/// </summary>
/// <remarks>
/// The resulting collection includes the container directly containing the entity (if any),
/// the container containing that container, and so on until reaching the outermost container.
/// </remarks>
public IEnumerable<BaseContainer> GetContainingContainers(Entity<TransformComponent?> ent)
{
if (!ent.Owner.IsValid())
yield break;
if (!Resolve(ent, ref ent.Comp))
yield break;
var child = ent.Owner;
var parent = ent.Comp.ParentUid;
while (parent.IsValid())
{
if (((MetaQuery.GetComponent(child).Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer) &&
_managerQuery.TryGetComponent(parent, out var conManager) &&
TryGetContainingContainer(parent, child, out var parentContainer, conManager))
{
yield return parentContainer;
}
var parentXform = TransformQuery.GetComponent(parent);
child = parent;
parent = parentXform.ParentUid;
}
}
/// <summary>
/// Gets the top-most container in the hierarchy for this entity, if it exists.
/// </summary>

View File

@@ -475,7 +475,7 @@ public sealed class EntityDeserializer :
foreach (var (key, value) in tileMap.Children)
{
var yamlTileId = ((ValueDataNode) key).AsInt();
var yamlTileId = int.Parse(key, CultureInfo.InvariantCulture);
var tileName = ((ValueDataNode) value).Value;
if (migrations.TryGetValue(tileName, out var @new))
tileName = @new;

View File

@@ -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);

View File

@@ -69,7 +69,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
var tileDefinitionManager = dependencies.Resolve<ITileDefinitionManager>();
node.TryGetValue(new ValueDataNode("version"), out var versionNode);
node.TryGetValue("version", out var versionNode);
var version = ((ValueDataNode?) versionNode)?.AsInt() ?? 1;
for (ushort y = 0; y < chunk.ChunkSize; y++)

View File

@@ -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)

View File

@@ -14,8 +14,9 @@ public partial class EntityManager
/// Increases the life stage from <see cref="ComponentLifeStage.PreAdd" /> to <see cref="ComponentLifeStage.Added" />,
/// after raising a <see cref="ComponentAdd"/> event.
/// </summary>
internal void LifeAddToEntity(EntityUid uid, IComponent component, CompIdx idx)
internal void LifeAddToEntity<T>(EntityUid uid, T component, CompIdx idx) where T : IComponent
{
DebugTools.Assert(!_deleteSet.Contains(component));
DebugTools.Assert(component.LifeStage == ComponentLifeStage.PreAdd);
#pragma warning disable CS0618 // Type or member is obsolete
@@ -32,8 +33,9 @@ public partial class EntityManager
/// Increases the life stage from <see cref="ComponentLifeStage.Added" /> to <see cref="ComponentLifeStage.Initialized" />,
/// calling <see cref="Initialize" />.
/// </summary>
internal void LifeInitialize(EntityUid uid, IComponent component, CompIdx idx)
internal void LifeInitialize<T>(EntityUid uid, T component, CompIdx idx) where T : IComponent
{
DebugTools.Assert(!_deleteSet.Contains(component));
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Added);
component.LifeStage = ComponentLifeStage.Initializing;
@@ -45,8 +47,9 @@ public partial class EntityManager
/// Increases the life stage from <see cref="ComponentLifeStage.Initialized" /> to
/// <see cref="ComponentLifeStage.Running" />, calling <see cref="Startup" />.
/// </summary>
internal void LifeStartup(EntityUid uid, IComponent component, CompIdx idx)
internal void LifeStartup<T>(EntityUid uid, T component, CompIdx idx) where T : IComponent
{
DebugTools.Assert(!_deleteSet.Contains(component));
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Initialized);
component.LifeStage = ComponentLifeStage.Starting;
@@ -61,7 +64,7 @@ public partial class EntityManager
/// <remarks>
/// Components are allowed to remove themselves in their own Startup function.
/// </remarks>
internal void LifeShutdown(EntityUid uid, IComponent component, CompIdx idx)
internal void LifeShutdown<T>(EntityUid uid, T component, CompIdx idx) where T : IComponent
{
DebugTools.Assert(component.LifeStage is >= ComponentLifeStage.Initializing and < ComponentLifeStage.Stopping);
@@ -81,7 +84,7 @@ public partial class EntityManager
/// Increases the life stage from <see cref="ComponentLifeStage.Stopped" /> to <see cref="ComponentLifeStage.Deleted" />,
/// calling <see cref="Component.OnRemove" />.
/// </summary>
internal void LifeRemoveFromEntity(EntityUid uid, IComponent component, CompIdx idx)
internal void LifeRemoveFromEntity<T>(EntityUid uid, T component, CompIdx idx) where T : IComponent
{
// can be called at any time after PreAdd, including inside other life stage events.
DebugTools.Assert(component.LifeStage != ComponentLifeStage.PreAdd);

View File

@@ -79,7 +79,7 @@ public partial class EntityManager
throw new InvalidOperationException($"Tried to spawn entity {protoName} on invalid coordinates {coordinates}.");
var entity = CreateEntityUninitialized(protoName, coordinates, overrides, rotation);
InitializeAndStartEntity(entity, coordinates.GetMapId(this));
InitializeAndStartEntity(entity, _xforms.GetMapId(coordinates));
return entity;
}
@@ -100,7 +100,7 @@ public partial class EntityManager
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid SpawnAtPosition(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> Spawn(protoName, coordinates.ToMap(this, _xforms), overrides);
=> Spawn(protoName, _xforms.ToMapCoordinates(coordinates), overrides);
public bool TrySpawnNextTo(
string? protoName,

View File

@@ -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>

View File

@@ -429,7 +429,7 @@ public sealed partial class EntityLookupSystem
float arcWidth,
LookupFlags flags = DefaultFlags)
{
var position = coordinates.ToMap(EntityManager, _transform);
var position = _transform.ToMapCoordinates(coordinates);
return GetEntitiesInArc(position, range, direction, arcWidth, flags);
}
@@ -628,7 +628,7 @@ public sealed partial class EntityLookupSystem
if (!coordinates.IsValid(EntityManager))
return false;
var mapPos = coordinates.ToMap(EntityManager, _transform);
var mapPos = _transform.ToMapCoordinates(coordinates);
return AnyEntitiesIntersecting(mapPos, flags);
}
@@ -637,13 +637,13 @@ public sealed partial class EntityLookupSystem
if (!coordinates.IsValid(EntityManager))
return false;
var mapPos = coordinates.ToMap(EntityManager, _transform);
var mapPos = _transform.ToMapCoordinates(coordinates);
return AnyEntitiesInRange(mapPos, range, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(EntityCoordinates coordinates, LookupFlags flags = DefaultFlags)
{
var mapPos = coordinates.ToMap(EntityManager, _transform);
var mapPos = _transform.ToMapCoordinates(coordinates);
return GetEntitiesIntersecting(mapPos, flags);
}
@@ -656,7 +656,7 @@ public sealed partial class EntityLookupSystem
public void GetEntitiesInRange(EntityCoordinates coordinates, float range, HashSet<EntityUid> entities, LookupFlags flags = DefaultFlags)
{
var mapPos = coordinates.ToMap(EntityManager, _transform);
var mapPos = _transform.ToMapCoordinates(coordinates);
if (mapPos.MapId == MapId.Nullspace)
return;

View File

@@ -621,7 +621,7 @@ public sealed partial class EntityLookupSystem
public void GetEntitiesInRange<T>(EntityCoordinates coordinates, float range, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
var mapPos = coordinates.ToMap(EntityManager, _transform);
var mapPos = _transform.ToMapCoordinates(coordinates);
GetEntitiesInRange(mapPos, range, entities, flags);
}

View File

@@ -9,6 +9,7 @@ namespace Robust.Shared.GameObjects;
public abstract class SharedEyeSystem : EntitySystem
{
[Dependency] private readonly SharedViewSubscriberSystem _views = default!;
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
public override void Initialize()
{

View File

@@ -23,7 +23,7 @@ public abstract partial class SharedMapSystem
}
// Check if mappos intersects a grid.
var mapPos = coordinates.ToMap(EntityManager, _transform);
var mapPos = _transform.ToMapCoordinates(coordinates);
if (_mapInternal.TryFindGridAt(mapPos, out var gridUid, out gridComponent))
{

View File

@@ -1222,7 +1222,7 @@ public abstract partial class SharedMapSystem
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.GetMapId(EntityManager));
DebugTools.Assert(mapId == _transform.GetMapId(coords));
#endif
return SnapGridLocalCellFor(uid, grid, LocalToGrid(uid, grid, coords));
@@ -1432,7 +1432,7 @@ public abstract partial class SharedMapSystem
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.GetMapId(EntityManager));
DebugTools.Assert(mapId == _transform.GetMapId(coords));
#endif
var local = LocalToGrid(uid, grid, coords);
@@ -1454,7 +1454,7 @@ public abstract partial class SharedMapSystem
{
return position.EntityId == uid
? position.Position
: WorldToLocal(uid, grid, position.ToMapPos(EntityManager, _transform));
: WorldToLocal(uid, grid, _transform.ToMapCoordinates(position).Position);
}
public bool CollidesWithGrid(EntityUid uid, MapGridComponent grid, Vector2i indices)

View File

@@ -121,6 +121,11 @@ 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;

View File

@@ -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);
}
}

View File

@@ -29,11 +29,9 @@ public sealed class FixtureSerializer : ITypeSerializer<Dictionary<string, Fixtu
foreach (var subNode in node)
{
var key = (ValueDataNode)subNode.Key;
if (!keys.Add(key.Value))
if (!keys.Add(subNode.Key))
{
seq.Add(new ErrorNode(subNode.Key, $"Found duplicate fixture ID {key.Value}"));
seq.Add(new ErrorNode(new ValueDataNode(subNode.Key), $"Found duplicate fixture ID {subNode.Key}"));
continue;
}
@@ -50,10 +48,8 @@ public sealed class FixtureSerializer : ITypeSerializer<Dictionary<string, Fixtu
foreach (var subNode in node)
{
var key = (ValueDataNode)subNode.Key;
var fixture = serializationManager.Read<Fixture>(subNode.Value, hookCtx, context, notNullableOverride: true);
value.Add(key.Value, fixture);
value.Add(subNode.Key, fixture);
}
return value;

View File

@@ -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)
{

View File

@@ -19,11 +19,11 @@ 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;
private EntityQuery<JointRelayTargetComponent> _relayQuery;
private EntityQuery<TransformComponent> _xformQuery;
// To avoid issues with component states we'll queue up all dirty joints and check it every tick to see if
// we can delete the component.
@@ -37,7 +37,6 @@ public abstract partial class SharedJointSystem : EntitySystem
_jointsQuery = GetEntityQuery<JointComponent>();
_relayQuery = GetEntityQuery<JointRelayTargetComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
UpdatesOutsidePrediction = true;
@@ -237,7 +236,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 +246,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);
@@ -347,7 +346,7 @@ public abstract partial class SharedJointSystem : EntitySystem
if (!Resolve(uid, ref xform))
return Vector2.Zero;
return Physics.Transform.MulT(new Quaternion2D((float) xform.WorldRotation.Theta), worldVector);
return Physics.Transform.MulT(new Quaternion2D((float) _transform.GetWorldRotation(xform).Theta), worldVector);
}
#endregion

View File

@@ -535,7 +535,7 @@ public partial class SharedPhysicsSystem
DirtyFields(uid, body, null, nameof(PhysicsComponent.Force), nameof(PhysicsComponent.Torque));
}
_broadphase.RegenerateContacts(uid, body, manager, xform);
_broadphase.RegenerateContacts((uid, body, manager, xform));
if (body.Initialized)
{

View File

@@ -661,8 +661,8 @@ public abstract partial class SharedPhysicsSystem
var aUid = contact.EntityA;
var bUid = contact.EntityB;
SetAwake(aUid, bodyA, true);
SetAwake(bUid, bodyB, true);
SetAwake((aUid, bodyA), true);
SetAwake((bUid, bodyB), true);
}
ArrayPool<bool>.Shared.Return(wake);
@@ -788,7 +788,7 @@ public abstract partial class SharedPhysicsSystem
if (!PhysicsQuery.Resolve(entity.Owner, ref entity.Comp))
return;
_broadphase.RegenerateContacts(entity.Owner, entity.Comp);
_broadphase.RegenerateContacts(entity);
}
/// <summary>

View File

@@ -1045,7 +1045,10 @@ public abstract partial class SharedPhysicsSystem
// May reparent object and change body's velocity.
if (!float.IsNaN(position.X) && !float.IsNaN(position.Y))
{
_transform.SetLocalPositionRotation(xform, xform.LocalPosition + position, xform.LocalRotation + angle);
_transform.SetLocalPositionRotation(uid,
xform.LocalPosition + position,
xform.LocalRotation + angle,
xform);
}
if (physicsDirtied)

View File

@@ -137,6 +137,7 @@ namespace Robust.Shared.Physics.Systems
/// <summary>
/// Get all entities colliding with a certain body.
/// </summary>
[Obsolete("Use EntityLookupSystem")]
public IEnumerable<PhysicsComponent> GetCollidingEntities(MapId mapId, in Box2 worldAABB)
{
if (mapId == MapId.Nullspace) return Array.Empty<PhysicsComponent>();
@@ -169,6 +170,7 @@ namespace Robust.Shared.Physics.Systems
/// <summary>
/// Get all entities colliding with a certain body.
/// </summary>
[Obsolete("Use EntityLookupSystem")]
public IEnumerable<Entity<PhysicsComponent>> GetCollidingEntities(MapId mapId, in Box2Rotated worldBounds)
{
if (mapId == MapId.Nullspace)

View File

@@ -45,7 +45,7 @@ public abstract partial class SharedPhysicsSystem
if (!coordinates.IsValid(EntityManager))
return Vector2.Zero;
var mapUid = coordinates.GetMapUid(EntityManager);
var mapUid = _transform.GetMap(coordinates);
var parent = coordinates.EntityId;
var localPos = coordinates.Position;

View File

@@ -342,8 +342,7 @@ public partial class PrototypeManager
{
if (abstractNode is not ValueDataNode abstractValueNode)
{
mapping.Remove(abstractNode);
mapping.Add("abstract", "true");
mapping["abstract"] = new ValueDataNode("true");
return;
}

View File

@@ -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))

View File

@@ -112,7 +112,7 @@ public sealed class ReplayData
ClientSideRecording = clientSideRecording;
YamlData = yamlData;
if (YamlData.TryGet(new ValueDataNode(ReplayConstants.MetaKeyRecordedBy), out ValueDataNode? node)
if (YamlData.TryGet(ReplayConstants.MetaKeyRecordedBy, out ValueDataNode? node)
&& Guid.TryParse(node.Value, out var guid))
{
Recorder = new NetUserId(guid);

View File

@@ -266,27 +266,21 @@ namespace Robust.Shared.Serialization.Manager.Definition
foreach (var (key, val) in mapping.Children)
{
if (key is not ValueDataNode valueDataNode)
if (!TryGetIndex(key, out var idx))
{
validatedMapping.Add(new ErrorNode(key, "Key not ValueDataNode."), new InconclusiveNode(val));
continue;
}
if (!TryGetIndex(valueDataNode.Value, out var idx))
{
if (TryGetIncludeMappingPair(includeValidations, valueDataNode.Value, out var validatedNotFoundPair))
if (TryGetIncludeMappingPair(includeValidations, key, out var validatedNotFoundPair))
{
validatedMapping.Add(validatedNotFoundPair.Key, validatedNotFoundPair.Value);
continue;
}
var error = new FieldNotFoundErrorNode(valueDataNode, typeof(T));
var error = new FieldNotFoundErrorNode(mapping.GetKeyNode(key), typeof(T));
validatedMapping.Add(error, new InconclusiveNode(val));
continue;
}
var keyValidated = serialization.ValidateNode<string>(key, context);
var keyValidated = serialization.ValidateNode<string>(mapping.GetKeyNode(key), context);
ValidationNode valNode;
if (IsNull(val))
@@ -295,7 +289,7 @@ namespace Robust.Shared.Serialization.Manager.Definition
{
var error = new ErrorNode(
val,
$"Field \"{valueDataNode.Value}\" had null value despite not being annotated as nullable.");
$"Field \"{key}\" had null value despite not being annotated as nullable.");
validatedMapping.Add(keyValidated, error);
continue;
@@ -309,7 +303,7 @@ namespace Robust.Shared.Serialization.Manager.Definition
}
//include node errors override successful nodes on the main datadef
if (valNode is not ErrorNode && TryGetIncludeMappingPair(includeValidations, valueDataNode.Value, out var validatedPair))
if (valNode is not ErrorNode && TryGetIncludeMappingPair(includeValidations, key, out var validatedPair))
{
if (validatedPair.Value is ErrorNode)
{

View File

@@ -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.

View File

@@ -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) =>
@@ -178,14 +190,15 @@ public partial class SerializationManager
{
// tag is set on data definition creation
if(!processedTags.Add(dfa.Tag!)) continue; //tag was already processed, probably because we are using the same tag in an include
var key = new ValueDataNode(dfa.Tag);
var key = dfa.Tag!;
if (parent.TryGetValue(key, out var parentValue))
{
if (newMapping.TryGetValue(key, out var childValue))
{
if (field.InheritanceBehavior == InheritanceBehavior.Always)
{
newMapping[key] = PushComposition(field.FieldType, new[] { parentValue }, childValue, context);
newMapping[key] = PushComposition(field.FieldType, parentValue, childValue, context);
}
}
else

View File

@@ -1,3 +1,4 @@
using System;
using System.IO;
using Robust.Shared.Serialization.Manager;
using YamlDotNet.Core;
@@ -28,6 +29,7 @@ namespace Robust.Shared.Serialization.Markdown
/// </summary>
public abstract DataNode? Except(DataNode node);
[Obsolete("Use SerializationManager.PushComposition()")]
public abstract DataNode PushInheritance(DataNode parent);
public T CopyCast<T>() where T : DataNode
@@ -60,6 +62,7 @@ namespace Robust.Shared.Serialization.Markdown
public abstract T? Except(T node);
[Obsolete("Use SerializationManager.PushComposition()")]
public abstract T PushInheritance(T node);
public override DataNode? Except(DataNode node)
@@ -67,6 +70,7 @@ namespace Robust.Shared.Serialization.Markdown
return node is not T tNode ? throw new InvalidNodeTypeException() : Except(tNode);
}
[Obsolete("Use SerializationManager.PushComposition()")]
public override DataNode PushInheritance(DataNode parent)
{
return parent is not T tNode ? throw new InvalidNodeTypeException() : PushInheritance(tNode);

View File

@@ -25,10 +25,7 @@ public static class DataNodeHelpers
foreach (var (k, v) in node)
{
foreach (var child in GetAllNodes(k))
{
yield return child;
}
yield return node.GetKeyNode(k);
foreach (var child in GetAllNodes(v))
{

View File

@@ -85,6 +85,16 @@ public static class DataNodeParser
return node;
}
private static string ParseKey(Parser parser)
{
var ev = parser.Consume<Scalar>();
if (!ev.Anchor.IsEmpty)
throw new NotSupportedException();
return ev.Value;
}
private static SequenceDataNode ParseSequence(Parser parser, DocumentState state)
{
var ev = parser.Consume<SequenceStart>();
@@ -124,12 +134,11 @@ public static class DataNodeParser
MappingEnd mapEnd;
while (!parser.TryConsume(out mapEnd))
{
var key = Parse(parser, state);
var key = ParseKey(parser);
var value = Parse(parser, state);
node.Add(key, value);
unresolvedAlias |= key is DataNodeAlias;
unresolvedAlias |= value is DataNodeAlias;
}
@@ -173,17 +182,16 @@ public static class DataNodeParser
private static void ResolveMappingAliases(MappingDataNode mapping, DocumentState state)
{
var swaps = new ValueList<(DataNode key, DataNode value)>();
var swaps = new ValueList<(string key, DataNode value)>();
foreach (var (key, value) in mapping)
{
if (key is not DataNodeAlias && value is not DataNodeAlias)
if (value is not DataNodeAlias valueAlias)
return;
var newKey = key is DataNodeAlias keyAlias ? ResolveAlias(keyAlias, state) : key;
var newValue = value is DataNodeAlias valueAlias ? ResolveAlias(valueAlias, state) : value;
var newValue = ResolveAlias(valueAlias, state);
swaps.Add((newKey, newValue));
swaps.Add((key, newValue));
mapping.Remove(key);
}
@@ -242,6 +250,7 @@ public static class DataNodeParser
throw new NotSupportedException();
}
[Obsolete("Use SerializationManager.PushComposition()")]
public override DataNode PushInheritance(DataNode parent)
{
throw new NotSupportedException();

View File

@@ -5,25 +5,30 @@ using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Utility;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.Serialization.Markdown.Mapping
{
public sealed class MappingDataNode : DataNode<MappingDataNode>, IDictionary<DataNode, DataNode>
public sealed class MappingDataNode : DataNode<MappingDataNode>, IDictionary<string, DataNode>
{
// To fetch nodes by key name with YAML, we NEED a YamlScalarNode.
// We use a thread local one to avoid allocating one every fetch, since we just replace the inner value.
// Obviously thread local to avoid threading issues.
private static readonly ThreadLocal<ValueDataNode> FetchNode =
new(() => new ValueDataNode(""));
private readonly Dictionary<string, DataNode> _children;
private readonly List<KeyValuePair<string,DataNode>> _list;
private readonly Dictionary<DataNode, DataNode> _children;
private readonly List<KeyValuePair<DataNode,DataNode>> _list;
/// <summary>
/// ValueDataNodes associated with each key. This is used for yaml validation / error reporting.
/// I.e., if a key is meant to be an EntityPrototype ID, we want to print an error that points to the
/// corresponding yaml lines.
/// </summary>
private IReadOnlyDictionary<string, ValueDataNode>? _keyNodes;
// TODO avoid populating this unless we are running the yaml linter?
public IReadOnlyDictionary<DataNode, DataNode> Children => _children;
public override bool IsEmpty => _children.Count == 0;
public int Count => _children.Count;
public bool IsReadOnly => false;
public IReadOnlyDictionary<string, DataNode> Children => _children;
public MappingDataNode() : base(NodeMark.Invalid, NodeMark.Invalid)
{
@@ -41,113 +46,106 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
{
_children = new(mapping.Children.Count);
_list = new(mapping.Children.Count);
foreach (var (key, val) in mapping.Children)
var keyNodes = new Dictionary<string, ValueDataNode>(mapping.Children.Count);
foreach (var (keyNode, val) in mapping.Children)
{
Add(key.ToDataNode(), val.ToDataNode());
if (keyNode is not YamlScalarNode scalarNode)
throw new NotSupportedException("Mapping data nodes must have a scalar keys");
var valueNode = new ValueDataNode(scalarNode);
Add(valueNode.Value, val.ToDataNode());
keyNodes.Add(valueNode.Value, valueNode);
}
_keyNodes = keyNodes;
Tag = mapping.Tag.IsEmpty ? null : mapping.Tag.Value;
}
public MappingDataNode(Dictionary<DataNode, DataNode> nodes) : base(NodeMark.Invalid, NodeMark.Invalid)
public MappingDataNode(Dictionary<string, DataNode> nodes) : base(NodeMark.Invalid, NodeMark.Invalid)
{
_children = new(nodes.Count);
_list = new(nodes.Count);
foreach (var (key, val) in nodes)
{
Add(key, val);
}
_children = new(nodes);
_list = new(_children);
}
public KeyValuePair<DataNode, DataNode> this[int key] => _list[key];
public DataNode this[string index]
{
get => Get(index);
set => Add(index, value);
}
public KeyValuePair<string, DataNode> this[int key] => _list[key];
public MappingDataNode Add(string key, DataNode node)
{
Add(new ValueDataNode(key), node);
return this;
}
private static ValueDataNode GetFetchNode(string key)
{
var node = FetchNode.Value!;
node.Value = key;
return node;
}
public MappingDataNode Add(DataNode key, DataNode node)
{
_children.Add(key, node);
_list.Add(new(key, node));
return this;
}
public DataNode this[DataNode key]
public DataNode this[string key]
{
get => _children[key];
set
{
if (_children.TryAdd(key, value))
{
_list.Add(new( key, value));
_list.Add(new(key, value));
return;
}
var i = _list.IndexOf(new(key, _children[key]));
_list[i] = new(key, value);
var index = IndexOf(key);
if (index == -1)
throw new Exception("Key exists in Children, but not list?");
_list[index] = new(key, value);
_children[key] = value;
}
}
void IDictionary<DataNode, DataNode>.Add(DataNode key, DataNode value) => Add(key, value);
public int IndexOf(string key)
{
// TODO MappingDataNode
// Consider having a Dictionary<string,int> for faster lookups?
// IndexOf() gets called in Remove(), which itself gets called frequently (e.g., per serialized component,
// per entity, when loading a map.
//
// Then again, if most mappings only contain 1-4 entries, this list search is comparable in speed, reduces
// allocations, and makes adding/inserting entries faster.
public bool ContainsKey(DataNode key) => _children.ContainsKey(key);
for (var index = 0; index < _list.Count; index++)
{
if (_list[index].Key == key)
return index;
}
bool IDictionary<DataNode, DataNode>.Remove(DataNode key)
=> ((IDictionary<DataNode, DataNode>)this).Remove(key);
return -1;
}
public bool TryGetValue(DataNode key, [NotNullWhen(true)] out DataNode? value) => TryGet(key, out value);
void IDictionary<string, DataNode>.Add(string key, DataNode value) => Add(key, value);
public ICollection<DataNode> Keys => _list.Select(x => x.Key).ToArray();
public bool ContainsKey(string key) => _children.ContainsKey(key);
bool IDictionary<string, DataNode>.Remove(string key)
=> ((IDictionary<string, DataNode>)this).Remove(key);
public bool TryGetValue(string key, [NotNullWhen(true)] out DataNode? value)
=> TryGet(key, out value);
// TODO consider changing these to unsorted collections
// I.e., just redirect to _children.Keys to avoid hidden linq & allocations.
public ICollection<string> Keys => _list.Select(x => x.Key).ToArray();
public ICollection<DataNode> Values => _list.Select(x => x.Value).ToArray();
public DataNode Get(DataNode key)
public DataNode Get(string key)
{
return _children[key];
}
public T Get<T>(DataNode key) where T : DataNode
public T Get<T>(string key) where T : DataNode
{
return (T) Get(key);
}
public DataNode Get(string key)
public bool TryGet(string key, [NotNullWhen(true)] out DataNode? node)
{
return Get(GetFetchNode(key));
return _children.TryGetValue(key, out node);
}
public T Get<T>(string key) where T : DataNode
{
return Get<T>(GetFetchNode(key));
}
public bool TryGet(DataNode key, [NotNullWhen(true)] out DataNode? node)
{
if (_children.TryGetValue(key, out node))
{
return true;
}
node = null;
return false;
}
public bool TryGet<T>(DataNode key, [NotNullWhen(true)] out T? node) where T : DataNode
public bool TryGet<T>(string key, [NotNullWhen(true)] out T? node) where T : DataNode
{
node = null;
if (!TryGet(key, out var rawNode) || rawNode is not T castNode)
@@ -156,41 +154,27 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return true;
}
public bool TryGet(string key, [NotNullWhen(true)] out DataNode? node)
{
return TryGet(GetFetchNode(key), out node);
}
public bool TryGet<T>(string key, [NotNullWhen(true)] out T? node) where T : DataNode
{
return TryGet(GetFetchNode(key), out node);
}
public bool Has(DataNode key)
public bool Has(string key)
{
return _children.ContainsKey(key);
}
public bool Has(string key)
public bool Remove(string key)
{
return Has(GetFetchNode(key));
if (!_children.Remove(key))
return false;
var index = IndexOf(key);
if (index == -1)
throw new Exception("Key exists in Children, but not list?");
_list.RemoveAt(index);
return true;
}
public MappingDataNode Remove(DataNode key)
public T Cast<T>(string key) where T : DataNode
{
if (_children.Remove(key, out var val))
_list.Remove(new(key, val));
return this;
}
public MappingDataNode Remove(string key)
{
return Remove(GetFetchNode(key));
}
public T Cast<T>(string index) where T : DataNode
{
return (T) this[index];
return (T) this[key];
}
public YamlMappingNode ToYaml()
@@ -199,7 +183,23 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
foreach (var (key, val) in _list)
{
mapping.Add(key.ToYamlNode(), val.ToYamlNode());
YamlScalarNode yamlKeyNode;
if (_keyNodes != null && _keyNodes.TryGetValue(key, out var keyNode))
{
yamlKeyNode = (YamlScalarNode)keyNode;
}
else
{
// This is matches the ValueDataNode -> YamlScalarNode cast operator
yamlKeyNode = new(key)
{
Style = ValueDataNode.IsNullLiteral(key) || string.IsNullOrWhiteSpace(key)
? ScalarStyle.DoubleQuoted
: ScalarStyle.Any
};
}
mapping.Add(yamlKeyNode, val.ToYamlNode());
}
mapping.Tag = Tag;
@@ -207,6 +207,11 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return mapping;
}
public ValueDataNode GetKeyNode(string key)
{
return _keyNodes?.GetValueOrDefault(key) ?? new ValueDataNode(key);
}
public MappingDataNode Merge(MappingDataNode otherMapping)
{
var newMapping = Copy();
@@ -227,12 +232,12 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
if (!skipDuplicates || !Has(key))
{
// Intentionally raises an ArgumentException
Add(key.Copy(), val.Copy());
Add(key, val.Copy());
}
}
}
public void InsertAt(int index, DataNode key, DataNode value)
public void InsertAt(int index, string key, DataNode value)
{
if (index > _list.Count || index < 0)
throw new ArgumentOutOfRangeException();
@@ -243,20 +248,6 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
_list.Insert(index, new(key, value));
}
public void InsertAt(int index, string key, DataNode value)
{
if (index > _list.Count || index < 0)
throw new ArgumentOutOfRangeException();
var k = new ValueDataNode(key);
if (!_children.TryAdd(k, value))
throw new InvalidOperationException($"Already contains key {key}");
_list.Insert(index, new(k, value));
}
public override bool IsEmpty => _children.Count == 0;
public override MappingDataNode Copy()
{
var newMapping = new MappingDataNode(_children.Count)
@@ -268,9 +259,10 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
foreach (var (key, val) in _list)
{
newMapping.Add(key.Copy(), val.Copy());
newMapping.Add(key, val.Copy());
}
newMapping._keyNodes = _keyNodes;
return newMapping;
}
@@ -308,17 +300,13 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
foreach (var (key, val) in _list)
{
var other = node._list.FirstOrNull(p => p.Key.Equals(key));
if (other == null)
if (!node._children.TryGetValue(key, out var otherVal))
{
mappingNode.Add(key.Copy(), val.Copy());
mappingNode.Add(key, val.Copy());
}
else
else if (val.Except(otherVal) is { } newValue)
{
// We recursively call except on the values and keep only the differences.
var newValue = val.Except(other.Value.Value);
if (newValue == null) continue;
mappingNode.Add(key.Copy(), newValue);
mappingNode.Add(key, newValue);
}
}
@@ -336,18 +324,8 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
foreach (var (key, val) in _list)
{
var other = node._list.FirstOrNull(p => p.Key.Equals(key));
if (other == null)
{
mappingNode.Add(key.Copy(), val.Copy());
}
else
{
// We only keep the entry if the values are not equal
if (!val.Equals(other.Value.Value))
mappingNode.Add(key.Copy(), val.Copy());
}
if (!node._children.TryGetValue(key, out var otherVal) || !val.Equals(otherVal))
mappingNode.Add(key, val.Copy());
}
return mappingNode._children.Count == 0 ? null : mappingNode;
@@ -384,34 +362,24 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
if (_children.Count != other._children.Count)
return false;
if (Tag != other.Tag)
return false;
foreach (var (key, otherValue) in other)
{
if (!_children.TryGetValue(key, out var ownValue) ||
!otherValue.Equals(ownValue))
if (!_children.TryGetValue(key, out var ownValue)
|| !otherValue.Equals(ownValue))
{
return false;
}
}
return Tag == other.Tag;
return true;
}
public override MappingDataNode PushInheritance(MappingDataNode node)
{
var newNode = Copy();
foreach (var (key, val) in node)
{
if(_children.ContainsKey(key))
continue;
newNode.Remove(key);
newNode.Add(key.Copy(), val.Copy());
}
return newNode;
}
public IEnumerator<KeyValuePair<DataNode, DataNode>> GetEnumerator() => _list.GetEnumerator();
public List<KeyValuePair<string, DataNode>>.Enumerator GetEnumerator() => _list.GetEnumerator();
IEnumerator<KeyValuePair<string, DataNode>> IEnumerable<KeyValuePair<string, DataNode>>.GetEnumerator() =>
_list.GetEnumerator();
public override int GetHashCode()
{
@@ -430,7 +398,7 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return GetEnumerator();
}
public void Add(KeyValuePair<DataNode, DataNode> item) => Add(item.Key, item.Value);
public void Add(KeyValuePair<string, DataNode> item) => Add(item.Key, item.Value);
public void Clear()
{
@@ -438,18 +406,31 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
_list.Clear();
}
public bool Contains(KeyValuePair<DataNode, DataNode> item) => _children.ContainsKey(item.Key);
public bool Contains(KeyValuePair<string, DataNode> item) => _children.ContainsKey(item.Key);
public void CopyTo(KeyValuePair<DataNode, DataNode>[] array, int arrayIndex)
[Obsolete("Use SerializationManager.PushComposition()")]
public override MappingDataNode PushInheritance(MappingDataNode node)
{
var newNode = Copy();
foreach (var (key, val) in node)
{
if (_children.ContainsKey(key))
continue;
newNode.Remove(key);
newNode.Add(key, val.Copy());
}
return newNode;
}
public void CopyTo(KeyValuePair<string, DataNode>[] array, int arrayIndex)
=> _list.CopyTo(array, arrayIndex);
public bool Remove(KeyValuePair<DataNode, DataNode> item)
=> ((IDictionary<DataNode, DataNode>)this).Remove(item.Key);
public bool Remove(KeyValuePair<string, DataNode> item)
=> ((IDictionary<string, DataNode>) this).Remove(item.Key);
public int Count => _children.Count;
public bool IsReadOnly => false;
public bool TryAdd(DataNode key, DataNode value)
public bool TryAdd(string key, DataNode value)
{
if (!_children.TryAdd(key, value))
return false;
@@ -458,7 +439,7 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return true;
}
public bool TryAddCopy(DataNode key, DataNode value)
public bool TryAddCopy(string key, DataNode value)
{
ref var entry = ref CollectionsMarshal.GetValueRefOrAddDefault(_children, key, out var exists);
if (exists)
@@ -468,5 +449,52 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
_list.Add(new(key, entry));
return true;
}
// These methods are probably fine to keep around as helper methods, but are currently marked as obsolete
// so that people don't uneccesarily allocate a ValueDataNode. I.e., to prevent people from using code like
// mapping.TryGet(new ValueDataNode("key"), ...)
#region ValueDataNode Helpers
[Obsolete("Use string keys instead of ValueDataNode")]
public bool TryGet(ValueDataNode key, [NotNullWhen(true)] out DataNode? value)
=> TryGet(key.Value, out value);
[Obsolete("Use string keys instead of ValueDataNode")]
public DataNode this[ValueDataNode key]
{
get => this[key.Value];
set => this[key.Value] = value;
}
[Obsolete("Use string keys instead of ValueDataNode")]
public bool TryGetValue(ValueDataNode key, [NotNullWhen(true)] out DataNode? value)
=> TryGet(key.Value, out value);
[Obsolete("Use string keys instead of ValueDataNode")]
public bool TryGet<T>(ValueDataNode key, [NotNullWhen(true)] out T? node) where T : DataNode
=> TryGet(key.Value, out node);
[Obsolete("Use string keys instead of ValueDataNode")]
public bool Has(ValueDataNode key) => Has(key.Value);
[Obsolete("Use string keys instead of ValueDataNode")]
public T Cast<T>(ValueDataNode key) where T : DataNode => Cast<T>(key.Value);
[Obsolete("Use string keys instead of ValueDataNode")]
public void Add(KeyValuePair<ValueDataNode, DataNode> item) => Add(item.Key, item.Value);
[Obsolete("Use string keys instead of ValueDataNode")]
public MappingDataNode Add(ValueDataNode key, DataNode node) => Add(key.Value, node);
[Obsolete("Use string keys instead of ValueDataNode")]
public void InsertAt(int index, ValueDataNode key, DataNode value) => InsertAt(index, key.Value, value);
[Obsolete("Use string keys instead of ValueDataNode")]
public bool Contains(KeyValuePair<ValueDataNode, DataNode> item) => _children.ContainsKey(item.Key.Value);
[Obsolete("Use string keys instead of ValueDataNode")]
public bool Remove(ValueDataNode key) => Remove(key.Value);
#endregion
}
}

View File

@@ -6,21 +6,15 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
{
public static class MappingDataNodeExtensions
{
public static MappingDataNode Add(this MappingDataNode mapping, string key, DataNode node)
{
mapping.Add(new ValueDataNode(key), node);
return mapping;
}
public static MappingDataNode Add(this MappingDataNode mapping, string key, string value)
{
mapping.Add(new ValueDataNode(key), new ValueDataNode(value));
mapping.Add(key, new ValueDataNode(value));
return mapping;
}
public static MappingDataNode Add(this MappingDataNode mapping, string key, List<string> sequence)
{
mapping.Add(new ValueDataNode(key), new SequenceDataNode(sequence));
mapping.Add(key, new SequenceDataNode(sequence));
return mapping;
}
}

View File

@@ -133,7 +133,8 @@ namespace Robust.Shared.Serialization.Markdown.Sequence
return newSequence;
}
public IEnumerator<DataNode> GetEnumerator() => _nodes.GetEnumerator();
public List<DataNode>.Enumerator GetEnumerator() => _nodes.GetEnumerator();
IEnumerator<DataNode> IEnumerable<DataNode>.GetEnumerator() => _nodes.GetEnumerator();
public override int GetHashCode()
{
@@ -192,6 +193,7 @@ namespace Robust.Shared.Serialization.Markdown.Sequence
return true;
}
[Obsolete("Use SerializationManager.PushComposition()")]
public override SequenceDataNode PushInheritance(SequenceDataNode node)
{
var newNode = Copy();

View File

@@ -58,7 +58,7 @@ namespace Robust.Shared.Serialization.Markdown.Value
public override bool IsEmpty => string.IsNullOrWhiteSpace(Value);
private static bool IsNullLiteral(string? value) => value != null && value.Trim().ToLower() is "null" ;
public static bool IsNullLiteral(string? value) => value != null && value.Trim().ToLower() is "null" ;
public override ValueDataNode Copy()
{
@@ -76,6 +76,7 @@ namespace Robust.Shared.Serialization.Markdown.Value
return node.Value == Value ? null : Copy();
}
[Obsolete("Use SerializationManager.PushComposition()")]
public override ValueDataNode PushInheritance(ValueDataNode node)
{
return Copy();

View File

@@ -207,7 +207,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
{
newCompReg[idx] = serializationManager.PushCompositionWithGenericNode(
reg.Type,
new[] { parent[mapping] },
parent[mapping],
newCompReg[idx],
context);

View File

@@ -11,30 +11,25 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
/// <summary>
/// A custom type serializer for reading a set of types that inherit from some base type.
/// A custom type serializer for reading a set of types that inherit from some base type.
/// </summary>
public sealed class AbstractDictionarySerializer<TValue> : ITypeSerializer<Dictionary<Type, TValue>, MappingDataNode>
public sealed class AbstractDictionarySerializer<TValue> : ITypeSerializer<Dictionary<Type, TValue>, MappingDataNode>
where TValue : notnull
{
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
{
var mapping = new Dictionary<ValidationNode, ValidationNode>();
foreach (var (keyNode, valueNode) in node.Children)
foreach (var (key, valueNode) in node.Children)
{
if (keyNode is not ValueDataNode key)
{
mapping.Add(new ErrorNode(keyNode, $"Expected {nameof(ValueDataNode)} but was {keyNode.GetType()}"), new ValidatedValueNode(valueNode));
continue;
}
var type = serializationManager.ReflectionManager.YamlTypeTagLookup(typeof(TValue), key.Value);
var type = serializationManager.ReflectionManager.YamlTypeTagLookup(typeof(TValue), key);
if (type == null)
{
mapping.Add(new ErrorNode(keyNode, $"Could not resolve type: {key.Value}"), new ValidatedValueNode(valueNode));
mapping.Add(new ErrorNode(node.GetKeyNode(key), $"Could not resolve type: {key}"), new ValidatedValueNode(valueNode));
continue;
}
mapping.Add(new ValidatedValueNode(key), serializationManager.ValidateNode(type, valueNode, context));
mapping.Add(new ValidatedValueNode(node.GetKeyNode(key)), serializationManager.ValidateNode(type, valueNode, context));
}
return new ValidatedMappingNode(mapping);
@@ -44,10 +39,9 @@ public sealed class AbstractDictionarySerializer<TValue> : ITypeSerializer<Dicti
SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate<Dictionary<Type, TValue>>? instanceProvider = null)
{
var dict = instanceProvider != null ? instanceProvider() : new Dictionary<Type, TValue>();
foreach (var (keyNode, valueNode) in node.Children)
foreach (var (key, valueNode) in node.Children)
{
var key = (ValueDataNode) keyNode;
var type = serializationManager.ReflectionManager.YamlTypeTagLookup(typeof(TValue), key.Value)!;
var type = serializationManager.ReflectionManager.YamlTypeTagLookup(typeof(TValue), key)!;
var value = (TValue) serializationManager.Read(type, valueNode, hookCtx, context, notNullableOverride:true)!;
dict.Add(type, value);
}
@@ -62,8 +56,14 @@ public sealed class AbstractDictionarySerializer<TValue> : ITypeSerializer<Dicti
foreach (var (key, val) in value)
{
// TODO SERIALIZATION
// Add some way to directly return a string w/o allocating a ValueDataNode
var keyNode = serializationManager.WriteValue(key.Name, alwaysWrite, context, notNullableOverride: true);
if (keyNode is not ValueDataNode valueNode)
throw new NotSupportedException();
mappingNode.Add(
serializationManager.WriteValue(key.Name, alwaysWrite, context, notNullableOverride:true),
valueNode.Value,
serializationManager.WriteValue(key, val, alwaysWrite, context));
}

View File

@@ -32,13 +32,8 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
foreach (var (key, val) in node.Children)
{
if (key is not ValueDataNode value)
{
mapping.Add(new ErrorNode(key, $"Cannot cast node {key} to ValueDataNode."), serializationManager.ValidateNode<TValue>(val, context));
continue;
}
mapping.Add(PrototypeSerializer.Validate(serializationManager, value, dependencies, context), serializationManager.ValidateNode<TValue>(val, context));
var keyNode = new ValueDataNode(key);
mapping.Add(PrototypeSerializer.Validate(serializationManager, keyNode, dependencies, context), serializationManager.ValidateNode<TValue>(val, context));
}
return new ValidatedMappingNode(mapping);

View File

@@ -30,8 +30,9 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
{
var mapping = new Dictionary<ValidationNode, ValidationNode>();
foreach (var (key, val) in node.Children)
foreach (var (k, val) in node.Children)
{
var key = node.GetKeyNode(k);
if (val is not ValueDataNode value)
{
mapping.Add(new ErrorNode(val, $"Cannot cast node {val} to ValueDataNode."), serializationManager.ValidateNode<TValue>(key, context));

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
@@ -8,6 +9,7 @@ using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
@@ -59,7 +61,7 @@ public sealed class DictionarySerializer<TKey, TValue> :
var mapping = new Dictionary<ValidationNode, ValidationNode>();
foreach (var (key, val) in node.Children)
{
mapping.Add(serializationManager.ValidateNode<TKey>(key, context),
mapping.Add(serializationManager.ValidateNode<TKey>(node.GetKeyNode(key), context),
serializationManager.ValidateNode<TValue>(val, context));
}
@@ -79,8 +81,14 @@ public sealed class DictionarySerializer<TKey, TValue> :
var mappingNode = new MappingDataNode();
foreach (var (key, val) in value)
{
// TODO SERIALIZATION
// Add some way to directly return a string w/o allocating a ValueDataNode
var keyNode = serializationManager.WriteValue(key, alwaysWrite, context);
if (keyNode is not ValueDataNode valueNode)
throw new NotSupportedException("Yaml mapping keys must serialize to a ValueDataNode (i.e. a string)");
mappingNode.Add(
serializationManager.WriteValue(key, alwaysWrite, context),
valueNode.Value,
serializationManager.WriteValue(val, alwaysWrite, context));
}
@@ -128,9 +136,11 @@ public sealed class DictionarySerializer<TKey, TValue> :
{
var dict = instanceProvider != null ? instanceProvider() : new Dictionary<TKey, TValue>();
var keyNode = new ValueDataNode();
foreach (var (key, value) in node.Children)
{
dict.Add(serializationManager.Read<TKey>(key, hookCtx, context),
keyNode.Value = key;
dict.Add(serializationManager.Read<TKey>(keyNode, hookCtx, context),
serializationManager.Read<TValue>(value, hookCtx, context));
}
@@ -149,9 +159,11 @@ public sealed class DictionarySerializer<TKey, TValue> :
var array = new KeyValuePair<TKey, TValue>[node.Children.Count];
int i = 0;
var keyNode = new ValueDataNode();
foreach (var (key, value) in node.Children)
{
var k = serializationManager.Read<TKey>(key, hookCtx, context);
keyNode.Value = key;
var k = serializationManager.Read<TKey>(keyNode, hookCtx, context);
var v = serializationManager.Read<TValue>(value, hookCtx, context);
array[i++] = new(k,v);
}
@@ -174,9 +186,11 @@ public sealed class DictionarySerializer<TKey, TValue> :
var dict = new Dictionary<TKey, TValue>();
var keyNode = new ValueDataNode();
foreach (var (key, value) in node.Children)
{
dict.Add(serializationManager.Read<TKey>(key, hookCtx, context),
keyNode.Value = key;
dict.Add(serializationManager.Read<TKey>(keyNode, hookCtx, context),
serializationManager.Read<TValue>(value, hookCtx, context));
}
@@ -190,10 +204,12 @@ public sealed class DictionarySerializer<TKey, TValue> :
ISerializationManager.InstantiationDelegate<SortedDictionary<TKey, TValue>>? instanceProvider)
{
var dict = instanceProvider != null ? instanceProvider() : new SortedDictionary<TKey, TValue>();
var keyNode = new ValueDataNode();
foreach (var (key, value) in node.Children)
{
dict.Add(serializationManager.Read<TKey>(key, hookCtx, context),
keyNode.Value = key;
dict.Add(serializationManager.Read<TKey>(keyNode, hookCtx, context),
serializationManager.Read<TValue>(value, hookCtx, context));
}

View File

@@ -6,40 +6,67 @@ using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
{
[TypeSerializer]
public sealed class ValueTupleSerializer<T1, T2> : ITypeSerializer<ValueTuple<T1, T2>, MappingDataNode>, ITypeCopyCreator<ValueTuple<T1, T2>>
public sealed class ValueTupleSerializer<T1, T2> :
ITypeReader<ValueTuple<T1, T2>, MappingDataNode>,
ITypeSerializer<ValueTuple<T1, T2>, SequenceDataNode>,
ITypeCopyCreator<ValueTuple<T1, T2>>
{
public (T1, T2) Read(ISerializationManager serializationManager, MappingDataNode node,
public (T1, T2) Read(
ISerializationManager serializationManager,
MappingDataNode node,
IDependencyCollection dependencies,
SerializationHookContext hookCtx,
ISerializationContext? context = null, ISerializationManager.InstantiationDelegate<(T1, T2)>? val = null)
ISerializationContext? context = null,
ISerializationManager.InstantiationDelegate<(T1, T2)>? instanceProvider = null)
{
if (node.Children.Count != 1)
throw new InvalidMappingException("Less than or more than 1 mappings provided to ValueTupleSerializer");
var entry = node.Children.First();
var v1 = serializationManager.Read<T1>(entry.Key, hookCtx, context);
var v1 = serializationManager.Read<T1>(node.GetKeyNode(entry.Key), hookCtx, context);
var v2 = serializationManager.Read<T2>(entry.Value, hookCtx, context);
return (v1, v2);
}
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
public (T1, T2) Read(
ISerializationManager serializationManager,
SequenceDataNode node,
IDependencyCollection dependencies,
SerializationHookContext hookCtx,
ISerializationContext? context = null,
ISerializationManager.InstantiationDelegate<(T1, T2)>? val = null)
{
if (node.Count != 2)
throw new InvalidMappingException("Sequence must contain exactly 2 elements.");
var v1 = serializationManager.Read<T1>(node[0], hookCtx, context);
var v2 = serializationManager.Read<T2>(node[1], hookCtx, context);
return (v1, v2);
}
public ValidationNode Validate(
ISerializationManager serializationManager,
MappingDataNode node,
IDependencyCollection dependencies,
ISerializationContext? context = null)
{
if (node.Children.Count != 1) return new ErrorNode(node, "More or less than 1 Mapping for ValueTuple found.");
if (node.Children.Count != 1)
return new ErrorNode(node, "More or less than 1 Mapping for ValueTuple found.");
var entry = node.Children.First();
var dict = new Dictionary<ValidationNode, ValidationNode>
{
{
serializationManager.ValidateNode<T1>(entry.Key, context),
serializationManager.ValidateNode<T1>(node.GetKeyNode(entry.Key), context),
serializationManager.ValidateNode<T2>(entry.Value, context)
}
};
@@ -47,21 +74,44 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
return new ValidatedMappingNode(dict);
}
public DataNode Write(ISerializationManager serializationManager, (T1, T2) value,
IDependencyCollection dependencies, bool alwaysWrite = false,
public ValidationNode Validate(
ISerializationManager serializationManager,
SequenceDataNode node,
IDependencyCollection dependencies,
ISerializationContext? context = null)
{
var mapping = new MappingDataNode();
if (node.Count != 2)
throw new InvalidMappingException("Sequence must contain exactly 2 elements.");
mapping.Add(
serializationManager.WriteValue<T1>(value.Item1, alwaysWrite, context),
serializationManager.WriteValue<T2>(value.Item2, alwaysWrite, context));
var seq = new List<ValidationNode>
{
serializationManager.ValidateNode<T1>(node[0], context),
serializationManager.ValidateNode<T2>(node[1], context)
};
return mapping;
return new ValidatedSequenceNode(seq);
}
public (T1, T2) CreateCopy(ISerializationManager serializationManager, (T1, T2) source,
IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null)
public DataNode Write(
ISerializationManager serializationManager,
(T1, T2) value,
IDependencyCollection dependencies,
bool alwaysWrite = false,
ISerializationContext? context = null)
{
return new SequenceDataNode(new List<DataNode>
{
serializationManager.WriteValue(value.Item1, alwaysWrite, context),
serializationManager.WriteValue(value.Item2, alwaysWrite, context)
});
}
public (T1, T2) CreateCopy(
ISerializationManager serializationManager,
(T1, T2) source,
IDependencyCollection dependencies,
SerializationHookContext hookCtx,
ISerializationContext? context = null)
{
return (serializationManager.CreateCopy(source.Item1, hookCtx, context),
serializationManager.CreateCopy(source.Item2, hookCtx, context));

View File

@@ -51,24 +51,29 @@ namespace Robust.UnitTesting.Shared.Physics
TransformComponent xform = default!;
PhysicsComponent entityTwoPhysics = default!;
EntityUid? entityOne = null;
EntityUid? entityTwo = null;
await server.WaitPost(() =>
{
mapSystem.CreateMap(out mapId);
grid = mapManager.CreateGridEntity(mapId);
mapSystem.SetTile(grid, Vector2i.Zero, new Tile(1));
var entityOne = entManager.SpawnEntity("CollisionWakeTestItem", new MapCoordinates(Vector2.One * 2f, mapId));
entityOnePhysics = entManager.GetComponent<PhysicsComponent>(entityOne);
xform = entManager.GetComponent<TransformComponent>(entityOne);
entityOne = entManager.SpawnEntity("CollisionWakeTestItem", new MapCoordinates(Vector2.One * 2f, mapId));
entityOnePhysics = entManager.GetComponent<PhysicsComponent>(entityOne.Value);
xform = entManager.GetComponent<TransformComponent>(entityOne.Value);
mapSystem.TryGetMap(mapId, out var mapUid);
Assert.That(xform.ParentUid == mapUid);
var entityTwo = entManager.SpawnEntity("CollisionWakeTestItem", new EntityCoordinates(grid, new Vector2(0.5f, 0.5f)));
entityTwoPhysics = entManager.GetComponent<PhysicsComponent>(entityTwo);
Assert.That(entManager.GetComponent<TransformComponent>(entityTwo).ParentUid == grid.Owner);
entityTwo = entManager.SpawnEntity("CollisionWakeTestItem", new EntityCoordinates(grid, new Vector2(0.5f, 0.5f)));
entityTwoPhysics = entManager.GetComponent<PhysicsComponent>(entityTwo.Value);
Assert.That(entManager.GetComponent<TransformComponent>(entityTwo.Value).ParentUid == grid.Owner);
});
Assert.That(entityOne, Is.Not.Null);
Assert.That(entityTwo, Is.Not.Null);
// Item 1 Should still be collidable
await server.WaitRunTicks(1);
@@ -77,8 +82,8 @@ namespace Robust.UnitTesting.Shared.Physics
Assert.That(entityOnePhysics.Awake, Is.EqualTo(false));
Assert.That(entityOnePhysics.CanCollide, Is.EqualTo(true));
xform.LocalPosition = new Vector2(0.5f, 0.5f);
xform.AttachParent(grid);
transformSystem.SetLocalPosition(entityOne.Value, new Vector2(0.5f, 0.5f), xform);
transformSystem.SetParent(entityOne.Value, xform, grid);
// Entity 2 should immediately not be collidable on spawn
Assert.That(entityTwoPhysics.Awake, Is.EqualTo(false));
@@ -92,8 +97,8 @@ namespace Robust.UnitTesting.Shared.Physics
Assert.That(entityOnePhysics.Awake, Is.EqualTo(false));
Assert.That(entityOnePhysics.CanCollide, Is.EqualTo(false));
xform.LocalPosition = Vector2.One * 2f;
xform.AttachParent(mapManager.GetMapEntityId(mapId));
transformSystem.SetLocalPosition(entityOne.Value, Vector2.One * 2f);
transformSystem.SetParent(entityOne.Value, xform, mapSystem.GetMapOrInvalid(mapId));
});
// Juussttt in case we'll re-parent it to the map and check its collision is back on.

View File

@@ -69,10 +69,10 @@ public sealed partial class ManagerTests : SerializationTest
}, //ISelfSerialize
new object[]
{
new MappingDataNode(new Dictionary<DataNode, DataNode>
new MappingDataNode(new Dictionary<string, DataNode>
{
{ new ValueDataNode("one"), new ValueDataNode("valueOne") },
{ new ValueDataNode("two"), new SequenceDataNode("2", "3") },
{ "one", new ValueDataNode("valueOne") },
{ "two", new SequenceDataNode("2", "3") },
}){Tag = $"!type:{nameof(DataDefClass)}"},
() => (IDataDefBaseInterface)new DataDefClass
{
@@ -112,10 +112,10 @@ public sealed partial class ManagerTests : SerializationTest
}, //array
new object[]
{
new MappingDataNode(new Dictionary<DataNode, DataNode>
new MappingDataNode(new Dictionary<string, DataNode>
{
{ new ValueDataNode("one"), new ValueDataNode("valueOne") },
{ new ValueDataNode("two"), new SequenceDataNode("2", "3") },
{ "one", new ValueDataNode("valueOne") },
{ "two", new SequenceDataNode("2", "3") },
}),
() => new DataDefClass
{
@@ -204,10 +204,10 @@ public sealed partial class ManagerTests : SerializationTest
{
new object[]
{
new MappingDataNode(new Dictionary<DataNode, DataNode>()
new MappingDataNode(new Dictionary<string, DataNode>()
{
{ new ValueDataNode("one"), new ValueDataNode("valueOne") },
{ new ValueDataNode("two"), new SequenceDataNode("2", "3") },
{ "one", new ValueDataNode("valueOne") },
{ "two", new SequenceDataNode("2", "3") },
}),
() => new DataDefStruct
{

View File

@@ -77,7 +77,7 @@ public sealed partial class ReadValueProviderTests : SerializationTest
public void DataDefinitionMappingBaseTest()
{
var data = "someData";
var mapping = new MappingDataNode(new Dictionary<DataNode, DataNode>{{ new ValueDataNode("data"), new ValueDataNode(data) }})
var mapping = new MappingDataNode(new Dictionary<string, DataNode>{{ "data", new ValueDataNode(data) }})
{
Tag = $"!type:{nameof(DataDefinitionValueProviderTestDummy)}"
};

View File

@@ -21,6 +21,8 @@ proto:
---
entity:
uid: int()
paused: bool(required=False)
mapInit: bool(required=False)
components: list(comp())
missingComponents: list(str(), required=False)