Move MoveBuffer to the physicsmapcomponent (#3199)

Co-authored-by: Kara <lunarautomaton6@gmail.com>
This commit is contained in:
metalgearsloth
2022-09-07 21:44:41 +10:00
committed by GitHub
parent 3f5982cac0
commit 071acbc818
4 changed files with 77 additions and 121 deletions

View File

@@ -180,7 +180,12 @@ namespace Robust.Shared.GameObjects
TryComp(MapManager.GetMapEntityId(oldMapId), out SharedPhysicsMapComponent? oldMap);
TryComp(MapManager.GetMapEntityId(newMapId), out SharedPhysicsMapComponent? newMap);
_broadphase.TryGetMoveBuffer(oldMapId, out var oldMoveBuffer);
Dictionary<FixtureProxy, Box2>? oldMoveBuffer = null;
if (oldMap != null)
{
oldMoveBuffer = oldMap.MoveBuffer;
}
var newBroadphase = _broadphase.GetBroadphase(xform, broadQuery, xformQuery);

View File

@@ -2,6 +2,9 @@ using Robust.Shared.GameObjects;
namespace Robust.Shared.Physics
{
/// <summary>
/// Stores the broadphase structure for the relevant grid / map.
/// </summary>
[RegisterComponent]
public sealed class BroadphaseComponent : Component
{

View File

@@ -44,6 +44,11 @@ namespace Robust.Shared.Physics.Dynamics
public bool AutoClearForces;
/// <summary>
/// Keep a buffer of everything that moved in a tick. This will be used to check for physics contacts.
/// </summary>
public readonly Dictionary<FixtureProxy, Box2> MoveBuffer = new();
/// <summary>
/// Change the global gravity vector.
/// </summary>
@@ -158,7 +163,7 @@ namespace Robust.Shared.Physics.Dynamics
// Box2D does this at the end of a step and also here when there's a fixture update.
// Given external stuff can move bodies we'll just do this here.
// Unfortunately this NEEDS to be predicted to make pushing remotely fucking good.
BroadphaseSystem.FindNewContacts(MapId);
BroadphaseSystem.FindNewContacts(this, MapId);
var invDt = frameTime > 0.0f ? 1.0f / frameTime : 0.0f;
var dtRatio = _invDt0 * frameTime;

View File

@@ -19,7 +19,6 @@ namespace Robust.Shared.Physics
public abstract class SharedBroadphaseSystem : EntitySystem
{
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
@@ -27,10 +26,6 @@ namespace Robust.Shared.Physics
private const int MinimumBroadphaseCapacity = 256;
// We queue updates rather than handle them immediately for multiple reasons
// A) Entity initializing may call several events which only need handling once so we'd need to add a bunch of code to account for what stage of initializing they're at
// B) It's faster for instances like MoveEvent and RotateEvent both being issued
/*
* Okay so Box2D has its own "MoveProxy" stuff so you can easily find new contacts when required.
* Our problem is that we have nested broadphases (rather than being on separate maps) which makes this
@@ -38,18 +33,6 @@ namespace Robust.Shared.Physics
* Hence we need to check which broadphases it does intersect and checkar for colliding bodies.
*/
/// <summary>
/// Keep a buffer of everything that moved in a tick. This will be used to check for physics contacts.
/// </summary>
private readonly Dictionary<MapId, Dictionary<FixtureProxy, Box2>> _moveBuffer = new();
// Caching for FindNewContacts
private Dictionary<FixtureProxy, HashSet<FixtureProxy>> _pairBuffer = new(64);
private Dictionary<FixtureProxy, Box2> _gridMoveBuffer = new(64);
private List<FixtureProxy> _queryBuffer = new(32);
private List<MapGrid> _gridsPool = new(8);
/// <summary>
/// How much to expand bounds by to check cross-broadphase collisions.
/// Ideally you want to set this to your largest body size.
@@ -81,42 +64,28 @@ namespace Robust.Shared.Physics
{
if (ev.Created)
OnMapCreated(ev);
else
OnMapDestroyed(ev);
});
}
public override void Shutdown()
{
base.Shutdown();
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.UnsubValueChanged(CVars.BroadphaseExpand, SetBroadphaseExpand);
}
private void SetBroadphaseExpand(float value) => _broadphaseExpand = value;
public override void Update(float frameTime)
{
base.Update(frameTime);
ProcessUpdates();
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
ProcessUpdates();
}
/// <summary>
/// Go through every deferred event and update the broadphase.
/// </summary>
public void ProcessUpdates()
{
// Unfortunately we can't re-use our broadphase transforms as controllers may update them.
_physicsManager.ClearTransforms();
}
#region Find Contacts
/// <summary>
/// Check the AABB for each moved broadphase fixture and add any colliding entities to the movebuffer in case.
/// </summary>
private void FindGridContacts(
SharedPhysicsMapComponent component,
MapId mapId,
HashSet<IMapGrid> movedGrids,
Dictionary<FixtureProxy, Box2> gridMoveBuffer,
EntityQuery<PhysicsComponent> bodyQuery,
EntityQuery<BroadphaseComponent> broadQuery)
{
@@ -127,7 +96,7 @@ namespace Robust.Shared.Physics
// This is so that if we're on a broadphase that's moving (e.g. a grid) we need to make sure anything
// we move over is getting checked for collisions, and putting it on the movebuffer is the easiest way to do so.
var moveBuffer = _moveBuffer[mapId];
var moveBuffer = component.MoveBuffer;
foreach (var grid in movedGrids)
{
@@ -139,7 +108,7 @@ namespace Robust.Shared.Physics
// TODO: Use the callback for this you ape.
// Easier to just not go over each proxy as we already unioned the fixture's worldaabb.
foreach (var other in mapBroadphase.Tree.QueryAabb(_queryBuffer, enlargedAABB))
foreach (var other in mapBroadphase.Tree.QueryAabb(enlargedAABB))
{
DebugTools.Assert(other.Fixture.Body != gridBody);
// 99% of the time it's just going to be the broadphase (for now the grid) itself.
@@ -149,32 +118,42 @@ namespace Robust.Shared.Physics
// To avoid updating during iteration.
// Don't need to transform as it's already in map terms.
_gridMoveBuffer[other] = other.AABB;
gridMoveBuffer[other] = other.AABB;
}
_queryBuffer.Clear();
}
foreach (var (proxy, worldAABB) in _gridMoveBuffer)
foreach (var (proxy, worldAABB) in gridMoveBuffer)
{
moveBuffer[proxy] = worldAABB;
}
}
[Obsolete("Use the overload with SharedPhysicsMapComponent")]
internal void FindNewContacts(MapId mapId)
{
if (!TryComp<SharedPhysicsMapComponent>(_mapManager.GetMapEntityId(mapId), out var physicsMap))
return;
FindNewContacts(physicsMap, mapId);
}
/// <summary>
/// Go through every single created, moved, or touched proxy on the map and try to find any new contacts that should be created.
/// </summary>
internal void FindNewContacts(MapId mapId)
internal void FindNewContacts(SharedPhysicsMapComponent component, MapId mapId)
{
var moveBuffer = _moveBuffer[mapId];
var moveBuffer = component.MoveBuffer;
var movedGrids = _mapManager.GetMovedGrids(mapId);
var pairBuffer = new Dictionary<FixtureProxy, HashSet<FixtureProxy>>();
var gridMoveBuffer = new Dictionary<FixtureProxy, Box2>();
var gridsPool = new List<MapGrid>();
var broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
var physicsQuery = GetEntityQuery<PhysicsComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Find any entities being driven over that might need to be considered
FindGridContacts(mapId, movedGrids, physicsQuery, broadphaseQuery);
FindGridContacts(component, mapId, movedGrids, gridMoveBuffer, physicsQuery, broadphaseQuery);
// There is some mariana trench levels of bullshit going on.
// We essentially need to re-create Box2D's FindNewContacts but in a way that allows us to check every
@@ -184,56 +163,41 @@ namespace Robust.Shared.Physics
// FindNewContacts is inherently going to be a lot slower than Box2D's normal version so we need
// to cache a bunch of stuff to make up for it.
var contactManager = Comp<SharedPhysicsMapComponent>(_mapManager.GetMapEntityIdOrThrow(mapId)).ContactManager;
var contactManager = component.ContactManager;
// Handle grids first as they're not stored on map broadphase at all.
HandleGridCollisions(mapId, contactManager, movedGrids, physicsQuery, xformQuery);
DebugTools.Assert(moveBuffer.Count > 0 || _pairBuffer.Count == 0);
DebugTools.Assert(moveBuffer.Count > 0 || pairBuffer.Count == 0);
// TODO: Could store fixtures by broadphase for more perf?
foreach (var (proxy, worldAABB) in moveBuffer)
{
var proxyBody = proxy.Fixture.Body;
if (proxyBody.Deleted)
{
// TODO: This happens in some grid deletion scenarios which does have an issue on github.
_logger.Error($"Deleted body {ToPrettyString(proxyBody.Owner)} made it to FindNewContacts; this should never happen!");
DebugTools.Assert(false);
continue;
}
_gridsPool.Clear();
DebugTools.Assert(!proxyBody.Deleted);
// Get every broadphase we may be intersecting.
// Also TODO: Don't put grids on movebuffer so you get peak shuttle driving performance.
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_broadphaseExpand), _gridsPool, xformQuery, physicsQuery))
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_broadphaseExpand), gridsPool, xformQuery, physicsQuery))
{
FindPairs(proxy, worldAABB, grid.GridEntityId, xformQuery, broadphaseQuery);
FindPairs(proxy, worldAABB, grid.GridEntityId, pairBuffer, xformQuery, broadphaseQuery);
}
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), xformQuery, broadphaseQuery);
gridsPool.Clear();
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), pairBuffer, xformQuery, broadphaseQuery);
}
foreach (var (proxyA, proxies) in _pairBuffer)
foreach (var (proxyA, proxies) in pairBuffer)
{
var proxyABody = proxyA.Fixture.Body;
// TODO Why are we checking deleted what
if (proxyABody.Deleted) continue;
foreach (var other in proxies)
{
var otherBody = other.Fixture.Body;
if (otherBody.Deleted) continue;
// Because we may be colliding with something asleep (due to the way grid movement works) need
// to make sure the contact doesn't fail.
// This is because we generate a contact across 2 different broadphases where both bodies aren't
// moving locally but are moving in world-terms.
if (proxyA.Fixture.Hard && other.Fixture.Hard &&
(_gridMoveBuffer.ContainsKey(proxyA) || _gridMoveBuffer.ContainsKey(other)))
(gridMoveBuffer.ContainsKey(proxyA) || gridMoveBuffer.ContainsKey(other)))
{
proxyABody.WakeBody();
otherBody.WakeBody();
@@ -243,10 +207,7 @@ namespace Robust.Shared.Physics
}
}
movedGrids.Clear();
_pairBuffer.Clear();
moveBuffer.Clear();
_gridMoveBuffer.Clear();
_mapManager.ClearMovedGrids(mapId);
}
@@ -257,6 +218,8 @@ namespace Robust.Shared.Physics
EntityQuery<PhysicsComponent> bodyQuery,
EntityQuery<TransformComponent> xformQuery)
{
var gridsPool = new List<MapGrid>();
foreach (var grid in movedGrids)
{
DebugTools.Assert(grid.ParentMapId == mapId);
@@ -272,9 +235,9 @@ namespace Robust.Shared.Physics
// (nothing in SS14 does this yet).
var transform = bodyQuery.GetComponent(grid.GridEntityId).GetTransform(xform);
_gridsPool.Clear();
gridsPool.Clear();
foreach (var colliding in _mapManager.FindGridsIntersecting(mapId, aabb, _gridsPool, xformQuery, bodyQuery, true))
foreach (var colliding in _mapManager.FindGridsIntersecting(mapId, aabb, gridsPool, xformQuery, bodyQuery, true))
{
if (grid == colliding) continue;
@@ -329,6 +292,7 @@ namespace Robust.Shared.Physics
FixtureProxy proxy,
Box2 worldAABB,
EntityUid broadphase,
Dictionary<FixtureProxy, HashSet<FixtureProxy>> pairBuffer,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<BroadphaseComponent> broadphaseQuery)
{
@@ -363,7 +327,7 @@ namespace Robust.Shared.Physics
var broadphaseComp = broadphaseQuery.GetComponent(broadphase);
foreach (var other in broadphaseComp.Tree.QueryAabb(_queryBuffer, aabb))
foreach (var other in broadphaseComp.Tree.QueryAabb(aabb))
{
DebugTools.Assert(other.Fixture.Body.CanCollide);
// Logger.DebugS("physics", $"Checking {proxy.Fixture.Body.Owner} against {other.Fixture.Body.Owner} at {aabb}");
@@ -377,22 +341,20 @@ namespace Robust.Shared.Physics
// Look it disgusts me but we can't do it Box2D's way because we're getting pairs
// with different broadphases so can't use Proxy sorting to skip duplicates.
// TODO: This needs to be better
if (_pairBuffer.TryGetValue(other, out var existing) &&
if (pairBuffer.TryGetValue(other, out var existing) &&
existing.Contains(proxy))
{
continue;
}
if (!_pairBuffer.TryGetValue(proxy, out var proxyExisting))
if (!pairBuffer.TryGetValue(proxy, out var proxyExisting))
{
proxyExisting = new HashSet<FixtureProxy>();
_pairBuffer[proxy] = proxyExisting;
pairBuffer[proxy] = proxyExisting;
}
proxyExisting.Add(other);
}
_queryBuffer.Clear();
}
/// <summary>
@@ -409,7 +371,12 @@ namespace Robust.Shared.Physics
var broadQuery = GetEntityQuery<BroadphaseComponent>();
var newBroadphase = GetBroadphase(xform, broadQuery, xformQuery);
TryGetMoveBuffer(oldMapId, out var oldMoveBuffer);
Dictionary<FixtureProxy, Box2>? oldMoveBuffer = null;
if (TryComp<SharedPhysicsMapComponent>(_mapManager.GetMapEntityId(oldMapId), out var physicsMap))
{
oldMoveBuffer = physicsMap.MoveBuffer;
}
RecursiveBroadphaseUpdate(xform, bodyQuery, fixturesQuery, xformQuery, broadQuery, newBroadphase, oldMoveBuffer);
}
@@ -481,7 +448,11 @@ namespace Robust.Shared.Physics
if (body.Broadphase == null)
return;
DestroyProxies(body, manager, _moveBuffer[Transform(body.Broadphase.Owner).MapID]);
if (TryComp<TransformComponent>(body.Owner, out var xform) &&
TryComp<SharedPhysicsMapComponent>(xform.MapUid, out var map))
{
DestroyProxies(body, manager, map.MoveBuffer);
}
}
private void DestroyProxies(PhysicsComponent body, FixturesComponent manager, Dictionary<FixtureProxy, Box2>? moveBuffer)
@@ -705,33 +676,17 @@ namespace Robust.Shared.Physics
private void AddToMoveBuffer(MapId mapId, FixtureProxy proxy, Box2 aabb)
{
if(mapId == MapId.Nullspace)
if (!TryComp<SharedPhysicsMapComponent>(_mapManager.GetMapEntityId(mapId), out var physicsMap))
return;
DebugTools.Assert(proxy.Fixture.Body.CanCollide);
_moveBuffer[mapId][proxy] = aabb;
}
// For use when recursively deleting an entity, to avoid may dictionary lookups
internal bool TryGetMoveBuffer(MapId mapId, [NotNullWhen(true)]out Dictionary<FixtureProxy, Box2>? buffer)
=> _moveBuffer.TryGetValue(mapId, out buffer);
internal void RemoveFromMoveBuffer(FixturesComponent manager, Dictionary<FixtureProxy, Box2> buffer)
{
foreach (var (_, fixture) in manager.Fixtures)
{
for (var i = 0; i < fixture.ProxyCount; i++)
{
buffer.Remove(fixture.Proxies[i]);
}
}
physicsMap.MoveBuffer[proxy] = aabb;
}
/// <summary>
/// Get broadphase proxies from the body's fixtures and add them to the relevant broadphase.
/// </summary>
/// <param name="useCache">Whether we should use cached broadphase data. This is only valid during the physics step.</param>
private void CreateProxies(PhysicsComponent body, FixturesComponent? manager = null, TransformComponent? xform = null)
{
if (!Resolve(body.Owner, ref manager, ref xform) ||
@@ -832,26 +787,20 @@ namespace Robust.Shared.Physics
DebugTools.Assert(Transform(broadphase.Owner).MapID == map);
var proxyCount = fixture.ProxyCount;
var moveBuffer = _moveBuffer[map];
TryComp<SharedPhysicsMapComponent>(_mapManager.GetMapEntityId(map), out var physicsMap);
var moveBuffer = physicsMap?.MoveBuffer;
for (var i = 0; i < proxyCount; i++)
{
var proxy = fixture.Proxies[i];
broadphase.Tree.RemoveProxy(proxy.ProxyId);
proxy.ProxyId = DynamicTree.Proxy.Free;
moveBuffer.Remove(proxy);
moveBuffer?.Remove(proxy);
}
fixture.ProxyCount = 0;
}
public override void Shutdown()
{
base.Shutdown();
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.UnsubValueChanged(CVars.BroadphaseExpand, SetBroadphaseExpand);
}
#region Broadphase management
private void OnMapCreated(MapChangedEvent e)
@@ -859,12 +808,6 @@ namespace Robust.Shared.Physics
if (e.Map == MapId.Nullspace) return;
EntityManager.EnsureComponent<BroadphaseComponent>(_mapManager.GetMapEntityId(e.Map));
_moveBuffer[e.Map] = new Dictionary<FixtureProxy, Box2>(64);
}
private void OnMapDestroyed(MapChangedEvent e)
{
_moveBuffer.Remove(e.Map);
}
private void OnGridAdd(GridAddEvent ev)