Fix some physics bugs (#4746)

* Fix SetAwake()

* Remove bad debug assert

* EntityQuery & misc optimizations

* Remove bad(?) code, and add new test

* I love engine tests

* Add debug assert
This commit is contained in:
Leon Friedrich
2023-12-24 01:28:43 -05:00
committed by GitHub
parent 7e8a5e199f
commit 7b171b2212
14 changed files with 254 additions and 96 deletions

View File

@@ -99,8 +99,8 @@ public sealed partial class PhysicsSystem
if (xform.MapUid is not { } map)
continue;
if (maps.Add(map) && TryComp(map, out PhysicsMapComponent? physMap) &&
TryComp(map, out MapComponent? mapComp))
if (maps.Add(map) && PhysMapQuery.TryGetComponent(map, out var physMap) &&
MapQuery.TryGetComponent(map, out var mapComp))
_broadphase.FindNewContacts(physMap, mapComp.MapId);
contacts.AddRange(physics.Contacts);

View File

@@ -10,6 +10,7 @@ namespace Robust.Shared.GameObjects
public sealed class CollisionWakeSystem : EntitySystem
{
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
private EntityQuery<CollisionWakeComponent> _query;
public override void Initialize()
{
@@ -19,18 +20,17 @@ namespace Robust.Shared.GameObjects
SubscribeLocalEvent<CollisionWakeComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<CollisionWakeComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<CollisionWakeComponent, PhysicsWakeEvent>(OnWake);
SubscribeLocalEvent<CollisionWakeComponent, PhysicsSleepEvent>(OnSleep);
SubscribeLocalEvent<CollisionWakeComponent, JointAddedEvent>(OnJointAdd);
SubscribeLocalEvent<CollisionWakeComponent, JointRemovedEvent>(OnJointRemove);
SubscribeLocalEvent<CollisionWakeComponent, EntParentChangedMessage>(OnParentChange);
_query = GetEntityQuery<CollisionWakeComponent>();
}
public void SetEnabled(EntityUid uid, bool enabled, CollisionWakeComponent? component = null)
{
if (!Resolve(uid, ref component) || component.Enabled == enabled)
if (!_query.Resolve(uid, ref component) || component.Enabled == enabled)
return;
component.Enabled = enabled;
@@ -89,17 +89,13 @@ namespace Robust.Shared.GameObjects
_physics.SetCanCollide(uid, true);
}
private void OnWake(EntityUid uid, CollisionWakeComponent component, ref PhysicsWakeEvent args)
internal void UpdateCanCollide(Entity<PhysicsComponent> entity, bool checkTerminating = true, bool dirty = true)
{
UpdateCanCollide(uid, component, args.Body, checkTerminating: false);
if (_query.TryGetComponent(entity, out var wakeComp))
UpdateCanCollide(entity.Owner, wakeComp, entity.Comp, checkTerminating: checkTerminating, dirty: dirty);
}
private void OnSleep(EntityUid uid, CollisionWakeComponent component, ref PhysicsSleepEvent args)
{
UpdateCanCollide(uid, component, args.Body);
}
private void UpdateCanCollide(
internal void UpdateCanCollide(
EntityUid uid,
CollisionWakeComponent component,
PhysicsComponent? body = null,

View File

@@ -231,7 +231,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (!TryGetCurrentBroadphase(xform, out var broadphase))
return;
if (!TryComp(xform.MapUid, out PhysicsMapComponent? physMap))
if (!_mapQuery.TryGetComponent(xform.MapUid, out var physMap))
throw new InvalidOperationException();
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
@@ -304,14 +304,14 @@ public sealed partial class EntityLookupSystem : EntitySystem
xform.Broadphase = null;
if (!TryComp(old.Uid, out BroadphaseComponent? broadphase))
if (!_broadQuery.TryGetComponent(old.Uid, out var broadphase))
return; // broadphase probably got deleted.
// remove from the old broadphase
var fixtures = Comp<FixturesComponent>(uid);
if (old.CanCollide)
{
TryComp(old.PhysicsMap, out PhysicsMapComponent? physicsMap);
_mapQuery.TryGetComponent(old.PhysicsMap, out var physicsMap);
RemoveBroadTree(broadphase, fixtures, old.Static, physicsMap);
}
else
@@ -355,7 +355,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (broadphaseXform.MapID == MapId.Nullspace)
return;
if (!TryComp(broadphaseXform.MapUid, out PhysicsMapComponent? physMap))
if (!_mapQuery.TryGetComponent(broadphaseXform.MapUid, out var physMap))
throw new InvalidOperationException($"Physics Broadphase is missing physics map. {ToPrettyString(broadUid)}");
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, physMap, xform, body, fixtures, xformQuery);
@@ -488,9 +488,8 @@ public sealed partial class EntityLookupSystem : EntitySystem
DebugTools.Assert(!newMap.IsValid() || HasComp<MapComponent>(newMap));
DebugTools.Assert(!oldMap.IsValid() || HasComp<MapComponent>(oldMap));
var oldBuffer = CompOrNull<PhysicsMapComponent>(oldMap)?.MoveBuffer;
var newBuffer = CompOrNull<PhysicsMapComponent>(newMap)?.MoveBuffer;
var oldBuffer = _mapQuery.CompOrNull(oldMap)?.MoveBuffer;
var newBuffer = _mapQuery.CompOrNull(newMap)?.MoveBuffer;
foreach (var child in args.Component._children)
{
@@ -559,7 +558,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (!xform.Broadphase.Value.IsValid())
return; // Entity is intentionally not on a broadphase (deferred updating?).
TryComp(xform.Broadphase.Value.PhysicsMap, out oldPhysMap);
_mapQuery.TryGetComponent(xform.Broadphase.Value.PhysicsMap, out oldPhysMap);
if (!_broadQuery.TryGetComponent(xform.Broadphase.Value.Uid, out oldBroadphase))
{
@@ -580,18 +579,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
}
}
if (oldBroadphase != null && _xformQuery.GetComponent(oldParent).MapID == MapId.Nullspace)
{
oldBroadphase = null;
// Note that the parentXform.MapID != MapId.Nullspace is required because currently grids are not allowed to
// ever enter null-space. If they are in null-space, we assume that the grid is being deleted, as otherwise
// RemoveFromEntityTree() will explode. This may eventually have to change if we stop universally sending
// all grids to all players (i.e., out-of view grids will need to get sent to null-space)
//
// This also means the queries above can be reverted (check broadQuery, then xformQuery, as this will
// generally save a component lookup.
}
TryFindBroadphase(xform, out var newBroadphase);
if (oldBroadphase != null && oldBroadphase != newBroadphase)
@@ -603,7 +590,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
return;
var newBroadphaseXform = _xformQuery.GetComponent(newBroadphase.Owner);
if (!TryComp(newBroadphaseXform.MapUid, out PhysicsMapComponent? physMap))
if (!_mapQuery.TryGetComponent(newBroadphaseXform.MapUid, out var physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(newBroadphase.Owner)}");
@@ -648,7 +635,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
bool recursive = true)
{
var broadphaseXform = _xformQuery.GetComponent(broadphase.Owner);
if (!TryComp(broadphaseXform.MapUid, out PhysicsMapComponent? physMap))
if (!_mapQuery.TryGetComponent(broadphaseXform.MapUid, out var physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
@@ -730,7 +717,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
return;
PhysicsMapComponent? physMap = null;
if (xform.Broadphase!.Value.PhysicsMap is { Valid: true } map && !TryComp(map, out physMap))
if (xform.Broadphase!.Value.PhysicsMap is { Valid: true } map && !_mapQuery.TryGetComponent(map, out physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
@@ -768,7 +755,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (old.PhysicsMap.IsValid() && physicsMap?.Owner != old.PhysicsMap)
{
if (!TryComp(old.PhysicsMap, out physicsMap))
if (!_mapQuery.TryGetComponent(old.PhysicsMap, out physicsMap))
Log.Error($"Entity {ToPrettyString(uid)} has missing physics map?");
}
@@ -803,12 +790,12 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (xform.Broadphase is not { Valid: true } old)
return false;
if (!TryComp(old.Uid, out broadphase))
if (!_broadQuery.TryGetComponent(old.Uid, out broadphase))
{
// broadphase was probably deleted
DebugTools.Assert("Encountered deleted broadphase.");
if (TryComp(xform.Owner, out FixturesComponent? fixtures))
if (_fixturesQuery.TryGetComponent(xform.Owner, out FixturesComponent? fixtures))
{
foreach (var fixture in fixtures.Fixtures.Values)
{

View File

@@ -227,8 +227,6 @@ public abstract partial class SharedMapSystem
"Can't modify chunk size of an existing grid.");
component.ChunkSize = state.ChunkSize;
DebugTools.Assert(state.ChunkData != null || state.FullGridData != null);
if (state.ChunkData == null && state.FullGridData == null)
return;

View File

@@ -11,6 +11,7 @@ public record struct PhysicsSleepEvent(EntityUid Entity, PhysicsComponent Body)
{
/// <summary>
/// Marks the entity as still being awake and cancels sleeping.
/// This only works if <see cref="PhysicsComponent.CanCollide"/> is still enabled and the object is not a static object..
/// </summary>
public bool Cancelled;
};

View File

@@ -21,6 +21,9 @@ namespace Robust.Shared.Physics.Systems
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
private EntityQuery<PhysicsMapComponent> _mapQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<FixturesComponent> _fixtureQuery;
public override void Initialize()
{
@@ -29,6 +32,9 @@ namespace Robust.Shared.Physics.Systems
SubscribeLocalEvent<FixturesComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<FixturesComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<FixturesComponent, ComponentHandleState>(OnHandleState);
_mapQuery = GetEntityQuery<PhysicsMapComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_fixtureQuery = GetEntityQuery<FixturesComponent>();
}
private void OnShutdown(EntityUid uid, FixturesComponent component, ComponentShutdown args)
@@ -37,10 +43,8 @@ namespace Robust.Shared.Physics.Systems
// Yes it is actively making the game buggier but I would essentially double the size of this PR trying to fix it
// my best solution rn is move the broadphase property onto FixturesComponent and then refactor
// SharedBroadphaseSystem a LOT.
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? body))
{
if (!_physicsQuery.TryGetComponent(uid, out var body))
return;
}
// Can't just get physicscomp on shutdown as it may be touched completely independently.
_physics.DestroyContacts(body);
@@ -63,7 +67,7 @@ namespace Robust.Shared.Physics.Systems
PhysicsComponent? body = null,
TransformComponent? xform = null)
{
if (!Resolve(uid, ref body, ref manager))
if (!_physicsQuery.Resolve(uid, ref body) || !_fixtureQuery.Resolve(uid, ref manager))
return false;
if (manager.Fixtures.ContainsKey(id))
@@ -85,7 +89,7 @@ namespace Robust.Shared.Physics.Systems
{
DebugTools.Assert(MetaData(uid).EntityLifeStage < EntityLifeStage.Terminating);
if (!Resolve(uid, ref manager, ref body))
if (!_physicsQuery.Resolve(uid, ref body) || !_fixtureQuery.Resolve(uid, ref manager))
{
DebugTools.Assert(false);
return;
@@ -120,10 +124,8 @@ namespace Robust.Shared.Physics.Systems
/// </summary>
public Fixture? GetFixtureOrNull(EntityUid uid, string id, FixturesComponent? manager = null)
{
if (!Resolve(uid, ref manager, false))
{
if (!_fixtureQuery.Resolve(uid, ref manager))
return null;
}
return manager.Fixtures.TryGetValue(id, out var fixture) ? fixture : null;
}
@@ -142,11 +144,12 @@ namespace Robust.Shared.Physics.Systems
FixturesComponent? manager = null,
TransformComponent? xform = null)
{
if (!_fixtureQuery.Resolve(uid, ref manager))
return;
var fixture = GetFixtureOrNull(uid, id, manager);
if (fixture == null) return;
DestroyFixture(uid, id, fixture, updates, body, manager, xform);
if (fixture != null)
DestroyFixture(uid, id, fixture, updates, body, manager, xform);
}
/// <summary>
@@ -183,8 +186,8 @@ namespace Robust.Shared.Physics.Systems
if (_lookup.TryGetCurrentBroadphase(xform, out var broadphase))
{
var map = Transform(broadphase.Owner).MapUid;
TryComp<PhysicsMapComponent>(map, out var physicsMap);
DebugTools.Assert(xform.MapUid == Transform(broadphase.Owner).MapUid);
_mapQuery.TryGetComponent(xform.MapUid, out var physicsMap);
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
}
@@ -323,7 +326,7 @@ namespace Robust.Shared.Physics.Systems
/// </summary>
public void FixtureUpdate(EntityUid uid, bool dirty = true, bool resetMass = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body, ref manager))
if (!_physicsQuery.Resolve(uid, ref body) || !_fixtureQuery.Resolve(uid, ref manager))
return;
var mask = 0;
@@ -363,7 +366,7 @@ namespace Robust.Shared.Physics.Systems
public int GetFixtureCount(EntityUid uid, FixturesComponent? manager = null)
{
if (!Resolve(uid, ref manager, false))
if (!_fixtureQuery.Resolve(uid, ref manager))
{
return 0;
}

View File

@@ -33,6 +33,7 @@ namespace Robust.Shared.Physics.Systems
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<PhysicsMapComponent> _mapQuery;
/*
* Okay so Box2D has its own "MoveProxy" stuff so you can easily find new contacts when required.
@@ -61,6 +62,7 @@ namespace Robust.Shared.Physics.Systems
_gridQuery = GetEntityQuery<MapGridComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_mapQuery = GetEntityQuery<PhysicsMapComponent>();
UpdatesOutsidePrediction = true;
UpdatesAfter.Add(typeof(SharedTransformSystem));
@@ -436,30 +438,32 @@ namespace Robust.Shared.Physics.Systems
if (!Resolve(uid, ref xform, ref fixtures))
return;
if (!_lookup.TryGetCurrentBroadphase(xform, out var broadphase))
if (xform.MapUid == null)
return;
_physicsSystem.SetAwake(uid, body, true);
if (!_xformQuery.TryGetComponent(xform.Broadphase?.Uid, out var broadphase))
return;
_physicsSystem.SetAwake((uid, body), true);
var matrix = _transform.GetWorldMatrix(broadphase);
foreach (var fixture in fixtures.Fixtures.Values)
{
TouchProxies(xform.MapID, broadphase, fixture);
TouchProxies(xform.MapUid.Value, matrix, fixture);
}
}
private void TouchProxies(MapId mapId, BroadphaseComponent broadphase, Fixture fixture)
private void TouchProxies(EntityUid mapId, Matrix3 broadphaseMatrix, Fixture fixture)
{
var broadphasePos = Transform(broadphase.Owner).WorldMatrix;
foreach (var proxy in fixture.Proxies)
{
AddToMoveBuffer(mapId, proxy, broadphasePos.TransformBox(proxy.AABB));
AddToMoveBuffer(mapId, proxy, broadphaseMatrix.TransformBox(proxy.AABB));
}
}
private void AddToMoveBuffer(MapId mapId, FixtureProxy proxy, Box2 aabb)
private void AddToMoveBuffer(EntityUid mapId, FixtureProxy proxy, Box2 aabb)
{
if (!TryComp<PhysicsMapComponent>(_mapManager.GetMapEntityId(mapId), out var physicsMap))
if (!_mapQuery.TryGetComponent(mapId, out var physicsMap))
return;
DebugTools.Assert(proxy.Body.CanCollide);
@@ -477,10 +481,14 @@ namespace Robust.Shared.Physics.Systems
if (!Resolve(uid, ref xform))
return;
if (!_lookup.TryGetCurrentBroadphase(xform, out var broadphase))
if (xform.MapUid == null)
return;
TouchProxies(xform.MapID, broadphase, fixture);
if (!_xformQuery.TryGetComponent(xform.Broadphase?.Uid, out var broadphase))
return;
var matrix = _transform.GetWorldMatrix(broadphase);
TouchProxies(xform.MapUid.Value, matrix, fixture);
}
// TODO: The below is slow and should just query the map's broadphase directly. The problem is that

View File

@@ -202,7 +202,7 @@ public partial class SharedPhysicsSystem
/// <summary>
/// Completely resets a dynamic body.
/// </summary>
public void ResetDynamics(PhysicsComponent body)
public void ResetDynamics(PhysicsComponent body, bool dirty = true)
{
var updated = false;
@@ -230,7 +230,7 @@ public partial class SharedPhysicsSystem
updated = true;
}
if (updated)
if (updated && dirty)
Dirty(body);
}
@@ -380,12 +380,16 @@ public partial class SharedPhysicsSystem
public void SetAwake(Entity<PhysicsComponent> ent, bool value, bool updateSleepTime = true)
{
var uid = ent.Owner;
var body = ent.Comp;
if (body.Awake == value)
return;
var (uid, body) = ent;
var canWake = body.BodyType != BodyType.Static && body.CanCollide;
if (value && (body.BodyType == BodyType.Static || !body.CanCollide))
if (body.Awake == value)
{
DebugTools.Assert(!body.Awake || canWake);
return;
}
if (value && !canWake)
return;
body.Awake = value;
@@ -397,21 +401,30 @@ public partial class SharedPhysicsSystem
}
else
{
// TODO C# event?
var ev = new PhysicsSleepEvent(uid, body);
RaiseLocalEvent(uid, ref ev, true);
// Reset the sleep timer.
if (ev.Cancelled)
if (ev.Cancelled && canWake)
{
body.Awake = true;
// TODO C# event?
var wakeEv = new PhysicsWakeEvent(uid, body);
RaiseLocalEvent(uid, ref wakeEv, true);
if (updateSleepTime)
SetSleepTime(body, 0);
return;
}
ResetDynamics(body);
ResetDynamics(body, dirty: false);
}
// Update wake system after we are sure that the wake/sleep event wasn't cancelled.
_wakeSystem.UpdateCanCollide(ent, checkTerminating: false, dirty: false);
if (updateSleepTime)
SetSleepTime(body, 0);

View File

@@ -34,7 +34,7 @@ public partial class SharedPhysicsSystem
internal void AddAwakeBody(EntityUid uid, PhysicsComponent body, EntityUid mapUid, PhysicsMapComponent? map = null)
{
Resolve(mapUid, ref map, false);
PhysMapQuery.Resolve(mapUid, ref map, false);
AddAwakeBody(uid, body, map);
}
@@ -45,7 +45,7 @@ public partial class SharedPhysicsSystem
internal void RemoveSleepBody(EntityUid uid, PhysicsComponent body, EntityUid mapUid, PhysicsMapComponent? map = null)
{
Resolve(mapUid, ref map, false);
PhysMapQuery.Resolve(mapUid, ref map, false);
RemoveSleepBody(uid, body, map);
}

View File

@@ -57,6 +57,7 @@ namespace Robust.Shared.Physics.Systems
[Dependency] private readonly SharedGridTraversalSystem _traversal = default!;
[Dependency] private readonly SharedJointSystem _joints = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly CollisionWakeSystem _wakeSystem = default!;
private int _substeps;
@@ -65,6 +66,9 @@ namespace Robust.Shared.Physics.Systems
private EntityQuery<FixturesComponent> _fixturesQuery;
protected EntityQuery<PhysicsComponent> PhysicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<CollideOnAnchorComponent> _anchorQuery;
protected EntityQuery<PhysicsMapComponent> PhysMapQuery;
protected EntityQuery<MapComponent> MapQuery;
public override void Initialize()
{
@@ -73,6 +77,9 @@ namespace Robust.Shared.Physics.Systems
_fixturesQuery = GetEntityQuery<FixturesComponent>();
PhysicsQuery = GetEntityQuery<PhysicsComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_anchorQuery = GetEntityQuery<CollideOnAnchorComponent>();
PhysMapQuery = GetEntityQuery<PhysicsMapComponent>();
MapQuery = GetEntityQuery<MapComponent>();
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
SubscribeLocalEvent<CollisionChangeEvent>(OnCollisionChange);
@@ -180,8 +187,8 @@ namespace Robust.Shared.Physics.Systems
var xformQuery = GetEntityQuery<TransformComponent>();
var jointQuery = GetEntityQuery<JointComponent>();
TryComp(_mapManager.GetMapEntityId(oldMapId), out PhysicsMapComponent? oldMap);
TryComp(_mapManager.GetMapEntityId(newMapId), out PhysicsMapComponent? newMap);
PhysMapQuery.TryGetComponent(_mapManager.GetMapEntityId(oldMapId), out var oldMap);
PhysMapQuery.TryGetComponent(_mapManager.GetMapEntityId(newMapId), out var newMap);
RecursiveMapUpdate(uid, xform, body, newMap, oldMap, bodyQuery, xformQuery, jointQuery);
}
@@ -254,18 +261,13 @@ namespace Robust.Shared.Physics.Systems
private void UpdateMapAwakeState(EntityUid uid, PhysicsComponent body)
{
var mapId = EntityManager.GetComponent<TransformComponent>(uid).MapID;
if (mapId == MapId.Nullspace)
if (Transform(uid).MapUid is not {} map)
return;
var tempQualifier = _mapManager.GetMapEntityId(mapId);
if (body.Awake)
{
AddAwakeBody(uid, body, tempQualifier);
}
AddAwakeBody(uid, body, map);
else
{
RemoveSleepBody(uid, body, tempQualifier);
}
RemoveSleepBody(uid, body, map);
}
private void HandleContainerRemoved(EntityUid uid, PhysicsComponent physics, EntGotRemovedFromContainerMessage message)
@@ -274,7 +276,7 @@ namespace Robust.Shared.Physics.Systems
if (MetaData(uid).EntityLifeStage >= EntityLifeStage.Terminating) return;
// If this entity is only meant to collide when anchored, return early.
if (TryComp(uid, out CollideOnAnchorComponent? collideComp) && collideComp.Enable)
if (_anchorQuery.TryGetComponent(uid, out var collideComp) && collideComp.Enable)
return;
WakeBody(uid, body: physics);

View File

@@ -94,6 +94,7 @@ namespace Robust.UnitTesting
systems.LoadExtraSystemType<SharedGridTraversalSystem>();
systems.LoadExtraSystemType<FixtureSystem>();
systems.LoadExtraSystemType<Gravity2DController>();
systems.LoadExtraSystemType<CollisionWakeSystem>();
if (Project == UnitTestProject.Client)
{
@@ -224,6 +225,16 @@ namespace Robust.UnitTesting
compFactory.RegisterClass<Gravity2DComponent>();
}
if (!compFactory.AllRegisteredTypes.Contains(typeof(CollisionWakeComponent)))
{
compFactory.RegisterClass<CollisionWakeComponent>();
}
if (!compFactory.AllRegisteredTypes.Contains(typeof(CollideOnAnchorComponent)))
{
compFactory.RegisterClass<CollideOnAnchorComponent>();
}
if (!compFactory.AllRegisteredTypes.Contains(typeof(ActorComponent)))
{
compFactory.RegisterClass<ActorComponent>();

View File

@@ -284,6 +284,7 @@ namespace Robust.UnitTesting.Server
compFactory.RegisterClass<OccluderComponent>();
compFactory.RegisterClass<OccluderTreeComponent>();
compFactory.RegisterClass<Gravity2DComponent>();
compFactory.RegisterClass<CollideOnAnchorComponent>();
_regDelegate?.Invoke(compFactory);

View File

@@ -0,0 +1,142 @@
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using Robust.Client.GameStates;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Player;
namespace Robust.UnitTesting.Shared.Map;
public sealed class GridDeleteSingleTileRemoveTestTest : RobustIntegrationTest
{
/// <summary>
/// Spawns a simple 1-tile grid with an entity on it, and then sets the tile to "space".
/// This should delete the grid without deleting the entity.
/// This also checks the networking to players, as previously this caused clients to crash.
/// </summary>
[Test]
public async Task TestRemoveSingleTile()
{
var server = StartServer();
var client = StartClient();
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
var mapMan = server.ResolveDependency<IMapManager>();
var sEntMan = server.ResolveDependency<IEntityManager>();
var confMan = server.ResolveDependency<IConfigurationManager>();
var sPlayerMan = server.ResolveDependency<ISharedPlayerManager>();
var xforms = sEntMan.System<SharedTransformSystem>();
var stateMan = (ClientGameStateManager) client.ResolveDependency<IClientGameStateManager>();
var cEntMan = client.ResolveDependency<IEntityManager>();
var netMan = client.ResolveDependency<IClientNetManager>();
var cPlayerMan = client.ResolveDependency<ISharedPlayerManager>();
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
client.Post(() => netMan.ClientConnect(null!, 0, null!));
server.Post(() => confMan.SetCVar(CVars.NetPVS, true));
for (int i = 0; i < 10; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
// Ensure client & server ticks are synced.
// Client runs 1 tick ahead
{
var sTick = (int)server.Timing.CurTick.Value;
var cTick = (int)client.Timing.CurTick.Value;
var delta = cTick - sTick;
if (delta > 1)
await server.WaitRunTicks(delta - 1);
else if (delta < 1)
await client.WaitRunTicks(1 - delta);
sTick = (int)server.Timing.CurTick.Value;
cTick = (int)client.Timing.CurTick.Value;
delta = cTick - sTick;
Assert.That(delta, Is.EqualTo(1));
}
// Set up map, grid, entity, and player
Entity<MapGridComponent> grid = default;
EntityUid sEntity = default;
EntityUid sMap = default;
EntityUid sPlayer = default;
var sys = sEntMan.System<SharedMapSystem>();
await server.WaitPost(() =>
{
var mapId = mapMan.CreateMap();
sMap = mapMan.GetMapEntityId(mapId);
var comp = mapMan.CreateGridEntity(mapId);
grid = (comp.Owner, comp);
sys.SetTile(grid, grid, new Vector2i(0, 0), new Tile(1, (TileRenderFlag)1, 1));
var coords = new EntityCoordinates(grid, 0.5f, 0.5f);
sPlayer = sEntMan.SpawnEntity(null, coords);
sEntity = sEntMan.SpawnEntity(null, coords);
// Attach player.
var session = sPlayerMan.Sessions.First();
server.PlayerMan.SetAttachedEntity(session, sPlayer);
sPlayerMan.JoinGame(session);
});
for (int i = 0; i < 10; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
var nEntity = sEntMan.GetNetEntity(sEntity);
var nPlayer = sEntMan.GetNetEntity(sPlayer);
var nGrid = sEntMan.GetNetEntity(grid);
var nMap = sEntMan.GetNetEntity(sMap);
// Check player got properly attached, and has received the other entity.
Assert.That(cEntMan.TryGetEntity(nEntity, out var cEntity));
Assert.That(cEntMan.TryGetEntity(nPlayer, out var cPlayerUid));
Assert.That(cEntMan.TryGetEntity(nGrid, out var cGrid));
Assert.That(cEntMan.TryGetEntity(nMap, out var cMap));
Assert.That(cPlayerMan.LocalEntity, Is.EqualTo(cPlayerUid));
var sQuery = sEntMan.GetEntityQuery<TransformComponent>();
Assert.That(sQuery.GetComponent(sEntity).ParentUid, Is.EqualTo(grid.Owner));
Assert.That(sQuery.GetComponent(grid.Owner).ParentUid, Is.EqualTo(sMap));
var cQuery = cEntMan.GetEntityQuery<TransformComponent>();
Assert.That(cQuery.GetComponent(cEntity!.Value).ParentUid, Is.EqualTo(cGrid));
Assert.That(cQuery.GetComponent(cGrid!.Value).ParentUid, Is.EqualTo(cMap));
// Remove the tile.
await server.WaitPost(() =>
{
sys.SetTile(grid, grid, new Vector2i(0, 0), Tile.Empty);
});
for (int i = 0; i < 10; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
// Grid should no longer exist.
Assert.That(!sEntMan.EntityExists(grid));
Assert.That(!cEntMan.EntityExists(cGrid));
// Entity should now be parented to the map
Assert.That(sQuery.GetComponent(sEntity).ParentUid, Is.EqualTo(sMap));
Assert.That(cQuery.GetComponent(cEntity.Value).ParentUid, Is.EqualTo(cMap));
}
}

View File

@@ -21,10 +21,6 @@ public sealed class Joints_Test
public void JointsRelayTest()
{
var factory = RobustServerSimulation.NewSimulation();
factory.RegisterComponents(fac =>
{
fac.RegisterClass<CollideOnAnchorComponent>();
});
var sim = factory.InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();