Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CollisionPredictionTest #5493

Merged
merged 3 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ END TEMPLATE-->

### Bugfixes

*None yet*
* Fixed `RaisePredictiveEvent()` not properly re-raising events during prediction for event handlers that did not take an `EntitySessionEventArgs` argument.

### Other

Expand Down
6 changes: 2 additions & 4 deletions Robust.Client/GameStates/ClientGameStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public sealed class ClientGameStateManager : IClientGameStateManager
private uint _nextInputCmdSeq = 1;
private readonly Queue<FullInputCmdMessage> _pendingInputs = new();

private readonly Queue<(uint sequence, GameTick sourceTick, EntityEventArgs msg, object sessionMsg)>
private readonly Queue<(uint sequence, GameTick sourceTick, object msg, object sessionMsg)>
_pendingSystemMessages
= new();

Expand Down Expand Up @@ -504,9 +504,7 @@ public void PredictTicks(GameTick predictionTarget)

while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= _timing.CurTick)
{
var msg = pendingMessagesEnumerator.Current.msg;

_entities.EventBus.RaiseEvent(EventSource.Local, msg);
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.msg);
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
}
Expand Down
2 changes: 1 addition & 1 deletion Robust.Client/Physics/PhysicsSystem.Predict.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ internal void ResetContacts()
var maps = new HashSet<EntityUid>();

var enumerator = AllEntityQuery<PredictedPhysicsComponent, PhysicsComponent, TransformComponent>();
while (enumerator.MoveNext(out var _, out var physics, out var xform))
while (enumerator.MoveNext(out _, out var physics, out var xform))
{
DebugTools.Assert(physics.Predict);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -899,11 +899,11 @@ public void SetMapCoordinates(Entity<TransformComponent> entity, MapCoordinates
_mapManager.TryFindGridAt(mapUid, coordinates.Position, out var targetGrid, out _))
{
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
SetCoordinates(entity, new EntityCoordinates(targetGrid, Vector2.Transform(coordinates.Position, invWorldMatrix)));
SetCoordinates((entity.Owner, entity.Comp, MetaData(entity.Owner)), new EntityCoordinates(targetGrid, Vector2.Transform(coordinates.Position, invWorldMatrix)));
}
else
{
SetCoordinates(entity, new EntityCoordinates(mapUid, coordinates.Position));
SetCoordinates((entity.Owner, entity.Comp, MetaData(entity.Owner)), new EntityCoordinates(mapUid, coordinates.Position));
}
}

Expand Down
26 changes: 17 additions & 9 deletions Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Numerics;
using System.Threading.Tasks;
Expand All @@ -8,7 +7,6 @@
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
Expand Down Expand Up @@ -413,24 +411,34 @@ private void QueryBroadphase(IBroadPhase broadPhase, (List<FixtureProxy>, Fixtur
}, aabb, true);
}

[Obsolete("Use Entity<T> variant")]
public void RegenerateContacts(EntityUid uid, PhysicsComponent body, FixturesComponent? fixtures = null, TransformComponent? xform = null)
{
_physicsSystem.DestroyContacts(body);
if (!Resolve(uid, ref xform, ref fixtures))
RegenerateContacts((uid, body, fixtures, xform));
}

public void RegenerateContacts(Entity<PhysicsComponent?, FixturesComponent?, TransformComponent?> entity)
{
if (!Resolve(entity.Owner, ref entity.Comp1))
return;

if (xform.MapUid == null)
_physicsSystem.DestroyContacts(entity.Comp1);

if (!Resolve(entity.Owner, ref entity.Comp2 , ref entity.Comp3))
return;

if (!_xformQuery.TryGetComponent(xform.Broadphase?.Uid, out var broadphase))
if (entity.Comp3.MapUid == null)
return;

if (!_xformQuery.TryGetComponent(entity.Comp3.Broadphase?.Uid, out var broadphase))
return;

_physicsSystem.SetAwake((uid, body), true);
_physicsSystem.SetAwake(entity!, true);

var matrix = _transform.GetWorldMatrix(broadphase);
foreach (var fixture in fixtures.Fixtures.Values)
foreach (var fixture in entity.Comp2.Fixtures.Values)
{
TouchProxies(xform.MapUid.Value, matrix, fixture);
TouchProxies(entity.Comp3.MapUid.Value, matrix, fixture);
}
}

Expand Down
22 changes: 11 additions & 11 deletions Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,27 +202,27 @@ public IEnumerable<Entity<PhysicsComponent>> GetCollidingEntities(MapId mapId, i
return bodies;
}

public HashSet<EntityUid> GetContactingEntities(EntityUid uid, PhysicsComponent? body = null, bool approximate = false)
public void GetContactingEntities(Entity<PhysicsComponent?> ent, HashSet<EntityUid> contacting, bool approximate = false)
{
// HashSet to ensure that we only return each entity once, instead of once per colliding fixture.
var result = new HashSet<EntityUid>();
if (!Resolve(ent.Owner, ref ent.Comp))
return;

if (!Resolve(uid, ref body))
return result;

var node = body.Contacts.First;
var node = ent.Comp.Contacts.First;

while (node != null)
{
var contact = node.Value;
node = node.Next;

if (!approximate && !contact.IsTouching)
continue;

result.Add(uid == contact.EntityA ? contact.EntityB : contact.EntityA);
if (approximate || contact.IsTouching)
contacting.Add(ent.Owner == contact.EntityA ? contact.EntityB : contact.EntityA);
}
}

public HashSet<EntityUid> GetContactingEntities(EntityUid uid, PhysicsComponent? body = null, bool approximate = false)
{
var result = new HashSet<EntityUid>();
GetContactingEntities((uid, body), result, approximate);
return result;
}

Expand Down
22 changes: 22 additions & 0 deletions Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,28 @@ protected void SimulateWorld(float deltaTime, bool prediction)
RaiseLocalEvent(ref updateMapBeforeSolve);
}

// TODO PHYSICS Fix Collision Mispredicts
// If a physics update induces a position update that brings fixtures into contact, the collision starts in the NEXT tick,
// as positions are updated after CollideContacts() gets called.
//
// If a player input induces a position update that brings fixtures into contact, the collision happens on the SAME tick,
// as inputs are handled before system updates.
//
// When applying a server's game state with new positions, the client won't know what caused the positions to update,
// and thus can't know whether the collision already occurred (i.e., whether its effects are already contained within the
// game state currently being applied), or whether it should start on the next tick and needs to predict the start of
// the collision.
//
// Currently the client assumes that any position updates happened due to physics steps. I.e., positions are reset, then
// contacts are reset via ResetContacts(), then positions are updated using the new game state. Alternatively, we could
// call ResetContacts() AFTER applying the server state, which would correspond to assuming that the collisions have
// already "started" in the state, and we don't want to re-raise the events.
//
// Currently there is no way to avoid mispredicts from happening. E.g., a simple collision-counter component will always
// either mispredict on physics induced position changes, or on player/input induced updates. The easiest way I can think
// of to fix this would be to always call `CollideContacts` again at the very end of a physics update.
// But that might be unnecessarily expensive for what are hopefully only infrequent mispredicts.

CollideContacts();
var enumerator = AllEntityQuery<PhysicsMapComponent>();

Expand Down
Loading
Loading