Compare commits

..

12 Commits

Author SHA1 Message Date
metalgearsloth
6e25ead588 Version: 233.0.2 2024-08-31 18:39:01 +10:00
metalgearsloth
cfae6e1f95 Don't rely on client for grid fixture rebuilds (#5348)
* Don't rely on client for grid fixture rebuilds

Server is already networking fixture data and this has a chance to go bad.
Easier to just stop this entirely and remove the fixture references to just network the relevant ones for each chunk. Performance impact should pretty much be non-existent and it should be less buggy.

* a

* weh notes

* fix aabb update

* Fix AABB gen

* weh

* More networking
2024-08-31 18:37:43 +10:00
metalgearsloth
da56851846 Version: 233.0.1 2024-08-31 18:22:39 +10:00
metalgearsloth
69ed2c3c33 Fix IsHardCollidable (#5416) 2024-08-31 17:49:59 +10:00
metalgearsloth
c558a0327b Version: 233.0.0 2024-08-31 14:37:23 +10:00
metalgearsloth
3bb7df3254 Relative lookup fix (#5415)
* Relative lookup fix

Some of the transforms weren't being transformed, added another test.

* test

* better test

* Reduce any-entities-intersecting tests
2024-08-31 14:35:17 +10:00
metalgearsloth
ab6bd19817 Fix mouse hover not updating for new controls (#5313)
* Fix mouse hover not updating for new controls

It only ever updated on movement previously. The issue is for example if a new window shows up where the mouse is (or any control) it doesn't handle it. Just checking it every frame AFAIK shouldn't be that expensive. Worst case we just have some flag to check it that gets set on <mouse movement OR controls changed>.

* review
2024-08-31 11:45:59 +10:00
Leon Friedrich
73ef69aa94 Try and ensure that parents are always initialized before children (#5343)
* Try and ensure that parents are always initialized before children

* release notes
2024-08-31 11:05:56 +10:00
nikthechampiongr
656835e7fa Change EntityRenamedEvents arguments and make it broadcast (#5413) 2024-08-31 11:04:44 +10:00
Pieter-Jan Briers
26c87b5858 Make tests run parallelizable (#5412)
Hope this won't cause issues.

Massively improves test speed.
2024-08-31 11:04:21 +10:00
Pieter-Jan Briers
be36001ab8 Add thread check assert to core entity mutation commands. (#5411)
* Add thread check assert to core entity mutation commands.

Creation of entities and components is now checked to happen from the main thread.

This is already catching multiple buggy integration tests, these will be addressed in separate commits.

* Fix broken tests directly mutating entities from wrong thread.
2024-08-31 11:04:02 +10:00
Pieter-Jan Briers
2002402af8 Version script now supports dash versions 2024-08-29 12:52:52 +02:00
31 changed files with 454 additions and 306 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,39 @@ END TEMPLATE-->
*None yet*
## 233.0.2
### Bugfixes
* Fix exceptions in client game state handling for grids. Now they will rely upon the networked fixture data and not try to rebuild in the grid state handler.
## 233.0.1
### Bugfixes
* Fix IsHardCollidable component to EntityUid references.
## 233.0.0
### Breaking changes
* Made EntityRenamed a broadcast event & added additional args.
* Made test runs parallelizable.
* Added a debug assert that other threads aren't touching entities.
### Bugfixes
* Fix some entitylookup method transformations and add more tests.
* Fix mousehover not updating if new controls showed up under the mouse.
### Internal
* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere.
* Engine version script now supports dashes.
## 232.0.0
### Breaking changes

View File

@@ -82,6 +82,7 @@ namespace Robust.Client.GameObjects
var viewport = args.WorldBounds;
var worldHandle = args.WorldHandle;
var fixturesQuery = _entityManager.GetEntityQuery<FixturesComponent>();
_grids.Clear();
_mapManager.FindGridsIntersecting(currentMap, viewport, ref _grids);
foreach (var grid in _grids)
@@ -89,13 +90,15 @@ namespace Robust.Client.GameObjects
var worldMatrix = _transformSystem.GetWorldMatrix(grid);
worldHandle.SetTransform(worldMatrix);
var transform = new Transform(Vector2.Zero, Angle.Zero);
var fixtures = fixturesQuery.Comp(grid.Owner);
var chunkEnumerator = _mapSystem.GetMapChunks(grid.Owner, grid.Comp, viewport);
while (chunkEnumerator.MoveNext(out var chunk))
{
foreach (var fixture in chunk.Fixtures.Values)
foreach (var id in chunk.Fixtures)
{
var fixture = fixtures.Fixtures[id];
var poly = (PolygonShape) fixture.Shape;
var verts = new Vector2[poly.VertexCount];

View File

@@ -960,7 +960,7 @@ namespace Robust.Client.GameStates
// Initialize and start the newly created entities.
if (_toCreate.Count > 0)
InitializeAndStart(_toCreate);
InitializeAndStart(_toCreate, metas, xforms);
_prof.WriteValue("State Size", ProfData.Int32(curSpan.Length));
_prof.WriteValue("Entered PVS", ProfData.Int32(enteringPvs));
@@ -1188,7 +1188,10 @@ namespace Robust.Client.GameStates
}
}
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
private void InitializeAndStart(
Dictionary<NetEntity, EntityState> toCreate,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms)
{
_toStart.Clear();
@@ -1197,22 +1200,8 @@ namespace Robust.Client.GameStates
EntityUid entity = default;
foreach (var netEntity in toCreate.Keys)
{
try
{
(entity, var meta) = _entityManager.GetEntityData(netEntity);
_entities.InitializeEntity(entity, meta);
_toStart.Add((entity, netEntity));
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
_toCreate.Remove(netEntity);
_brokenEnts.Add(entity);
#if !EXCEPTION_TOLERANCE
throw;
#endif
}
(entity, var meta) = _entityManager.GetEntityData(netEntity);
InitializeRecursive(entity, meta, metas, xforms);
}
}
@@ -1244,6 +1233,44 @@ namespace Robust.Client.GameStates
_brokenEnts.Clear();
}
private void InitializeRecursive(
EntityUid entity,
MetaDataComponent meta,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms)
{
var xform = xforms.GetComponent(entity);
if (xform.ParentUid is {Valid: true} parent)
{
var parentMeta = metas.GetComponent(parent);
if (parentMeta.EntityLifeStage < EntityLifeStage.Initialized)
InitializeRecursive(parent, parentMeta, metas, xforms);
}
if (meta.EntityLifeStage >= EntityLifeStage.Initialized)
{
// Was probably already initialized because one of its children appeared earlier in the list.
DebugTools.AssertEqual(_toStart.Count(x => x.Item1 == entity), 1);
return;
}
try
{
_entities.InitializeEntity(entity, meta);
_toStart.Add((entity, meta.NetEntity));
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Init: nent={meta.NetEntity}, ent={_entities.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
_toCreate.Remove(meta.NetEntity);
_brokenEnts.Add(entity);
#if !EXCEPTION_TOLERANCE
throw;
#endif
}
}
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
{

View File

@@ -148,7 +148,7 @@ internal partial class UserInterfaceManager
var newHovered = MouseGetControl(mouseMoveEventArgs.Position);
SetHovered(newHovered);
var target = ControlFocused ?? newHovered;
var target = ControlFocused ?? CurrentlyHovered;
if (target != null)
{
var pos = mouseMoveEventArgs.Position.Position;
@@ -164,7 +164,7 @@ internal partial class UserInterfaceManager
public void UpdateHovered()
{
var ctrl = MouseGetControl(_inputManager.MouseScreenPosition);
var ctrl = MouseGetControl(_inputManager.MouseScreenPosition);
SetHovered(ctrl);
}

View File

@@ -215,6 +215,9 @@ namespace Robust.Client.UserInterface
{
using (_prof.Group("Update"))
{
// Update hovered. Can't rely upon mouse movement due to New controls potentially coming up.
UpdateHovered();
foreach (var root in _roots)
{
CheckRootUIScaleUpdate(root);

View File

@@ -81,6 +81,7 @@ namespace Robust.Server.GameObjects
InitializeEntity(entity, meta);
}
[Obsolete("Use StartEntity")]
void IServerEntityManagerInternal.FinishEntityStartup(EntityUid entity)
{
StartEntity(entity);

View File

@@ -110,6 +110,7 @@ namespace Robust.Shared.GameObjects
return dict.Count;
}
[Obsolete("Use InitializeEntity")]
public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = null)
{
DebugTools.AssertOwner(uid, metadata);
@@ -145,6 +146,7 @@ namespace Robust.Shared.GameObjects
SetLifeStage(metadata, EntityLifeStage.Initialized);
}
[Obsolete("Use StartEntity")]
public void StartComponents(EntityUid uid)
{
// Startup() can modify _components
@@ -342,6 +344,8 @@ namespace Robust.Shared.GameObjects
private void AddComponentInternal<T>(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent metadata) where T : IComponent
{
ThreadCheck();
// We can't use typeof(T) here in case T is just Component
DebugTools.Assert(component is MetaDataComponent ||
(metadata ?? MetaQuery.GetComponent(uid)).EntityLifeStage < EntityLifeStage.Terminating,
@@ -602,6 +606,8 @@ namespace Robust.Shared.GameObjects
bool terminating,
MetaDataComponent? meta)
{
ThreadCheck();
if (component.Deleted)
{
_sawmill.Warning($"Deleting an already deleted component. Entity: {ToPrettyString(uid)}, Component: {_componentFactory.GetComponentName(component.GetType())}.");

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -117,6 +118,10 @@ namespace Robust.Shared.GameObjects
public bool Initialized { get; protected set; }
#if DEBUG
private int _mainThreadId;
#endif
/// <summary>
/// Constructs a new instance of <see cref="EntityManager"/>.
/// </summary>
@@ -138,6 +143,10 @@ namespace Robust.Shared.GameObjects
_sawmill = LogManager.GetSawmill("entity");
_resolveSawmill = LogManager.GetSawmill("resolve");
#if DEBUG
_mainThreadId = Environment.CurrentManagedThreadId;
#endif
Initialized = true;
}
@@ -511,6 +520,8 @@ namespace Robust.Shared.GameObjects
if (!Started)
return;
ThreadCheck();
if (meta.EntityLifeStage >= EntityLifeStage.Deleted)
return;
@@ -752,6 +763,8 @@ namespace Robust.Shared.GameObjects
/// </summary>
private EntityUid AllocEntity(out MetaDataComponent metadata)
{
ThreadCheck();
var uid = GenerateEntityUid();
#if DEBUG
@@ -864,15 +877,35 @@ namespace Robust.Shared.GameObjects
public void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null)
{
// Ideally, entities only ever get initialized once their parent has already been initialized.
// Note that this doesn't guarantee that an uninitialized entity will never have initialized children.
// In particular, for the client this might happen when applying a new game state that re-parents an
// existing entity to a newly created entity. The new entity only gets initialiuzed & started at the end,
// after the old/existing entity was already moved to the new parent.
DebugTools.Assert(TransformQuery.GetComponent(entity).ParentUid is not { Valid: true } parent
|| MetaQuery.GetComponent(parent).EntityLifeStage >= EntityLifeStage.Initialized);
DebugTools.AssertOwner(entity, meta);
meta ??= GetComponent<MetaDataComponent>(entity);
#pragma warning disable CS0618 // Type or member is obsolete
InitializeComponents(entity, meta);
#pragma warning restore CS0618 // Type or member is obsolete
EntityInitialized?.Invoke((entity, meta));
}
public void StartEntity(EntityUid entity)
{
// Ideally, entities only ever get initialized once their parent has already been initialized.
// Note that this doesn't guarantee that an uninitialized entity will never have initialized children.
// In particular, for the client this might happen when applying a new game state that re-parents an
// existing entity to a newly created entity. The new entity only gets initialiuzed & started at the end,
// after the old/existing entity was already moved to the new parent.
DebugTools.Assert(TransformQuery.GetComponent(entity).ParentUid is not { Valid: true } parent
|| MetaQuery.GetComponent(parent).EntityLifeStage >= EntityLifeStage.Initialized);
#pragma warning disable CS0618 // Type or member is obsolete
StartComponents(entity);
#pragma warning restore CS0618 // Type or member is obsolete
}
public void RunMapInit(EntityUid entity, MetaDataComponent meta)
@@ -953,6 +986,16 @@ namespace Robust.Shared.GameObjects
/// Generates a unique network id and increments <see cref="NextNetworkId"/>
/// </summary>
protected virtual NetEntity GenerateNetEntity() => new(NextNetworkId++);
[Conditional("DEBUG")]
protected void ThreadCheck()
{
#if DEBUG
DebugTools.Assert(
Environment.CurrentManagedThreadId == _mainThreadId,
"Environment.CurrentManagedThreadId == _mainThreadId");
#endif
}
}
public enum EntityMessageType : byte

View File

@@ -2,6 +2,7 @@ namespace Robust.Shared.GameObjects;
/// <summary>
/// Raised directed on an entity when its name is changed.
/// Contains the EntityUid as systems may need to subscribe to it without targeting a specific component.
/// </summary>
[ByRefEvent]
public readonly record struct EntityRenamedEvent(string NewName);
public readonly record struct EntityRenamedEvent(EntityUid Uid, string OldName, string NewName);

View File

@@ -22,11 +22,13 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Calls Initialize() on all registered components of the entity.
/// </summary>
[Obsolete("Use InitializeEntity")]
void InitializeComponents(EntityUid uid, MetaDataComponent? meta = null);
/// <summary>
/// Calls Startup() on all registered components of the entity.
/// </summary>
[Obsolete("Use StartEntity")]
void StartComponents(EntityUid uid);
/// <summary>

View File

@@ -252,7 +252,7 @@ public sealed partial class EntityLookupSystem
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
if (state.Lookup.AnyEntitiesIntersecting(uid, state.Shape, localAabb, state.Transform, state.Flags, ignored: state.Ignored))
if (state.Lookup.AnyEntitiesIntersecting(uid, state.Shape, localAabb, localTransform, state.Flags, ignored: state.Ignored))
{
state.Found = true;
return false;
@@ -266,7 +266,7 @@ public sealed partial class EntityLookupSystem
var mapUid = _map.GetMapOrInvalid(mapId);
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
state.Found = AnyEntitiesIntersecting(mapUid, shape, localAabb, shapeTransform, flags, ignored);
state.Found = AnyEntitiesIntersecting(mapUid, shape, localAabb, localTransform, flags, ignored);
}
return state.Found;
@@ -454,26 +454,8 @@ public sealed partial class EntityLookupSystem
{
if (mapId == MapId.Nullspace) return false;
// Don't need to check contained entities as they have the same bounds as the parent.
var found = false;
var state = (this, worldAABB, flags, found);
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid uid, MapGridComponent _, ref (EntityLookupSystem lookup, Box2 worldAABB, LookupFlags flags, bool found) tuple) =>
{
if (!tuple.lookup.AnyLocalEntitiesIntersecting(uid, tuple.worldAABB, tuple.flags))
return true;
tuple.found = true;
return false;
}, approx: true, includeMap: false);
if (state.found)
return true;
var mapUid = _map.GetMapOrInvalid(mapId);
return AnyLocalEntitiesIntersecting(mapUid, worldAABB, flags);
var shape = new Polygon(worldAABB);
return AnyEntitiesIntersecting(mapId, shape, Physics.Transform.Empty, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
@@ -487,23 +469,8 @@ public sealed partial class EntityLookupSystem
{
if (mapId == MapId.Nullspace) return;
// Get grid entities
var state = (this, intersecting, worldAABB, _transform, flags);
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid gridUid, MapGridComponent _, ref (
EntityLookupSystem lookup, HashSet<EntityUid> intersecting,
Box2 worldAABB, SharedTransformSystem xformSystem, LookupFlags flags) tuple) =>
{
var localAABB = tuple.xformSystem.GetInvWorldMatrix(gridUid).TransformBox(tuple.worldAABB);
tuple.lookup.AddLocalEntitiesIntersecting(gridUid, tuple.intersecting, localAABB, tuple.flags);
return true;
}, approx: true, includeMap: false);
// Get map entities
var mapUid = _map.GetMapOrInvalid(mapId);
AddLocalEntitiesIntersecting(mapUid, intersecting, worldAABB, flags);
AddContained(intersecting, flags);
var shape = new Polygon(worldAABB);
AddEntitiesIntersecting(mapId, intersecting, shape, Physics.Transform.Empty, flags);
}
#endregion
@@ -513,73 +480,15 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
// Don't need to check contained entities as they have the same bounds as the parent.
var worldAABB = worldBounds.CalcBoundingBox();
const bool found = false;
var state = (this, worldBounds, flags, found);
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid uid, MapGridComponent grid, ref (EntityLookupSystem lookup, Box2Rotated worldBounds, LookupFlags flags, bool found) tuple) =>
{
if (tuple.lookup.AnyEntitiesIntersecting(uid, tuple.worldBounds, tuple.flags))
{
tuple.found = true;
return false;
}
return true;
}, approx: true, includeMap: false);
if (state.found)
return true;
var mapUid = _map.GetMapOrInvalid(mapId);
return AnyEntitiesIntersecting(mapUid, worldBounds, flags);
var shape = new Polygon(worldBounds);
return AnyEntitiesIntersecting(mapId, shape, Physics.Transform.Empty, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
var intersecting = new HashSet<EntityUid>();
if (mapId == MapId.Nullspace)
return intersecting;
var mapUid = _map.GetMapOrInvalid(mapId);
// Get grid entities
var shape = new Polygon(worldBounds);
var transform = Physics.Transform.Empty;
var state = (this, _physics, intersecting, transform, shape, flags);
_mapManager.FindGridsIntersecting(mapUid, shape, transform, ref state,
static (
EntityUid uid,
MapGridComponent grid,
ref (EntityLookupSystem lookup,
SharedPhysicsSystem _physics,
HashSet<EntityUid> intersecting,
Transform transform,
Polygon shape, LookupFlags flags) state) =>
{
var localTransform = state._physics.GetRelativePhysicsTransform(state.transform, uid);
var localAabb = state.shape.ComputeAABB(localTransform, 0);
state.lookup.AddEntitiesIntersecting(uid,
state.intersecting,
state.shape,
localAabb,
state.transform,
state.flags);
return true;
});
// Get map entities
var localTransform = _physics.GetRelativePhysicsTransform(transform, mapUid);
var localAabb = shape.ComputeAABB(localTransform, 0);
AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, transform, flags);
AddContained(intersecting, flags);
AddEntitiesIntersecting(mapId, intersecting, shape, Physics.Transform.Empty, flags);
return intersecting;
}
@@ -599,47 +508,8 @@ public sealed partial class EntityLookupSystem
if (mapPos.MapId == MapId.Nullspace)
return false;
var rangeVec = new Vector2(range, range);
var worldAABB = new Box2(mapPos.Position - rangeVec, mapPos.Position + rangeVec);
var circle = new PhysShapeCircle(range, mapPos.Position);
const bool found = false;
var transform = Physics.Transform.Empty;
var state = (this, _physics, transform, circle, flags, found, uid);
_mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB, ref state, static (
EntityUid gridUid,
MapGridComponent _, ref (
EntityLookupSystem lookup,
SharedPhysicsSystem physics,
Transform worldTransform,
PhysShapeCircle circle,
LookupFlags flags,
bool found,
EntityUid ignored) tuple) =>
{
var localTransform = tuple.physics.GetRelativePhysicsTransform(tuple.worldTransform, gridUid);
var localAabb = tuple.circle.ComputeAABB(localTransform, 0);
if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.circle, localAabb, localTransform, tuple.flags, tuple.ignored))
{
tuple.found = true;
return false;
}
return true;
}, approx: true, includeMap: false);
if (state.found)
{
return true;
}
var mapUid = _map.GetMapOrInvalid(mapPos.MapId);
var localTransform = _physics.GetRelativePhysicsTransform(transform, uid);
var localAabb = circle.ComputeAABB(localTransform, 0);
return AnyEntitiesIntersecting(mapUid, circle, localAabb, localTransform, flags, uid);
var shape = new PhysShapeCircle(range, mapPos.Position);
return AnyEntitiesIntersecting(mapPos.MapId, shape, Physics.Transform.Empty, flags, uid);
}
public HashSet<EntityUid> GetEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)

View File

@@ -542,7 +542,7 @@ public sealed partial class EntityLookupSystem
{
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, state.Transform, state.Flags, state.Query);
state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, localTransform, state.Flags, state.Query);
return true;
}, approx: true, includeMap: false);
@@ -550,7 +550,7 @@ public sealed partial class EntityLookupSystem
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, shapeTransform, flags, query);
AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, localTransform, flags, query);
AddContained(intersecting, flags, query);
}
@@ -586,7 +586,7 @@ public sealed partial class EntityLookupSystem
{
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, state.Transform, state.Flags, state.Query);
state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, localTransform, state.Flags, state.Query);
return true;
}, approx: true, includeMap: false);
@@ -595,7 +595,7 @@ public sealed partial class EntityLookupSystem
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
AddEntitiesIntersecting(mapUid, entities, shape, localAabb, shapeTransform, flags, query);
AddEntitiesIntersecting(mapUid, entities, shape, localAabb, localTransform, flags, query);
AddContained(entities, flags, query);
}
}
@@ -678,8 +678,8 @@ public sealed partial class EntityLookupSystem
public void GetEntitiesInRange<T>(MapId mapId, Vector2 worldPos, float range, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
var shape = new PhysShapeCircle(range);
var transform = new Transform(worldPos, 0f);
var shape = new PhysShapeCircle(range, worldPos);
var transform = Physics.Transform.Empty;
GetEntitiesInRange(mapId, shape, transform, entities, flags);
}

View File

@@ -47,12 +47,14 @@ public abstract class MetaDataSystem : EntitySystem
if (!_metaQuery.Resolve(uid, ref metadata) || value.Equals(metadata.EntityName))
return;
var oldName = metadata.EntityName;
metadata._entityName = value;
if (raiseEvents)
{
var ev = new EntityRenamedEvent(value);
RaiseLocalEvent(uid, ref ev);
var ev = new EntityRenamedEvent(uid, oldName, value);
RaiseLocalEvent(uid, ref ev, true);
}
Dirty(uid, metadata, metadata);

View File

@@ -94,9 +94,9 @@ namespace Robust.Shared.GameObjects
{
UpdateFixture(uid, chunk, rectangles, body, manager, xform);
foreach (var (id, fixture) in chunk.Fixtures)
foreach (var id in chunk.Fixtures)
{
fixtures[id] = fixture;
fixtures[id] = manager.Fixtures[id];
}
}
@@ -157,8 +157,9 @@ namespace Robust.Shared.GameObjects
// Check if we even need to issue an eventbus event
var updated = false;
foreach (var (oldId, oldFixture) in chunk.Fixtures)
foreach (var oldId in chunk.Fixtures)
{
var oldFixture = manager.Fixtures[oldId];
var existing = false;
// Handle deleted / updated fixtures
@@ -196,16 +197,16 @@ namespace Robust.Shared.GameObjects
// Anything remaining is a new fixture (or at least, may have not serialized onto the chunk yet).
foreach (var (id, fixture) in newFixtures.Span)
{
chunk.Fixtures.Add(id);
var existingFixture = _fixtures.GetFixtureOrNull(uid, id, manager: manager);
// Check if it's the same (otherwise remove anyway).
if (existingFixture?.Shape is PolygonShape poly &&
poly.EqualsApprox((PolygonShape) fixture.Shape))
{
chunk.Fixtures.Add(id, existingFixture);
continue;
}
chunk.Fixtures.Add(id, fixture);
_fixtures.CreateFixture(uid, id, fixture, false, manager, body, xform);
}

View File

@@ -12,6 +12,7 @@ using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -245,6 +246,7 @@ public abstract partial class SharedMapSystem
private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref ComponentHandleState args)
{
HashSet<MapChunk> modifiedChunks;
switch (args.Current)
{
case MapGridComponentDeltaState delta:
@@ -257,9 +259,9 @@ public abstract partial class SharedMapSystem
if (delta.ChunkData == null)
return;
foreach (var chunkData in delta.ChunkData)
foreach (var (index, chunkData) in delta.ChunkData)
{
ApplyChunkData(uid, component, chunkData, modifiedChunks);
ApplyChunkData(uid, component, index, chunkData, modifiedChunks);
}
component.LastTileModifiedTick = delta.LastTileModifiedTick;
@@ -277,12 +279,12 @@ public abstract partial class SharedMapSystem
foreach (var index in component.Chunks.Keys)
{
if (!state.FullGridData.ContainsKey(index))
ApplyChunkData(uid, component, ChunkDatum.CreateDeleted(index), modifiedChunks);
ApplyChunkData(uid, component, index, ChunkDatum.Empty, modifiedChunks);
}
foreach (var (index, tiles) in state.FullGridData)
foreach (var (index, data) in state.FullGridData)
{
ApplyChunkData(uid, component, ChunkDatum.CreateModified(index, tiles), modifiedChunks);
ApplyChunkData(uid, component, index, new(data), modifiedChunks);
}
break;
@@ -291,12 +293,8 @@ public abstract partial class SharedMapSystem
return;
}
var count = component.Chunks.Count;
RegenerateCollision(uid, component, modifiedChunks);
// Regeneration can remove chunks in general, but it shouldn't do that here as the state handling
// should already have removed all the chunks.
DebugTools.AssertEqual(component.Chunks.Count, count);
RegenerateAabb(component);
OnGridBoundsChange(uid, component);
#if DEBUG
foreach (var chunk in component.Chunks.Values)
@@ -307,7 +305,11 @@ public abstract partial class SharedMapSystem
#endif
}
private void ApplyChunkData(EntityUid uid, MapGridComponent component, ChunkDatum data,
private void ApplyChunkData(
EntityUid uid,
MapGridComponent component,
Vector2i index,
ChunkDatum data,
HashSet<MapChunk> modifiedChunks)
{
bool shapeChanged = false;
@@ -315,7 +317,7 @@ public abstract partial class SharedMapSystem
if (data.IsDeleted())
{
if (!component.Chunks.TryGetValue(data.Index, out var deletedChunk))
if (!component.Chunks.TryGetValue(index, out var deletedChunk))
return;
// Deleted chunks still need to raise tile-changed events.
@@ -329,18 +331,18 @@ public abstract partial class SharedMapSystem
var gridIndices = deletedChunk.ChunkTileToGridTile((x, y));
var newTileRef = new TileRef(uid, gridIndices, Tile.Empty);
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, data.Index);
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
}
}
component.Chunks.Remove(data.Index);
component.Chunks.Remove(index);
// TODO is this required?
modifiedChunks.Add(deletedChunk);
return;
}
var chunk = GetOrAddChunk(uid, component, data.Index);
var chunk = GetOrAddChunk(uid, component, index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(data.TileData.Any(x => !x.IsEmpty));
DebugTools.Assert(data.TileData.Length == component.ChunkSize * component.ChunkSize);
@@ -355,13 +357,24 @@ public abstract partial class SharedMapSystem
shapeChanged |= tileShapeChanged;
var gridIndices = chunk.ChunkTileToGridTile((x, y));
var newTileRef = new TileRef(uid, gridIndices, tile);
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, data.Index);
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
}
}
if (data.Fixtures != null && !chunk.Fixtures.SetEquals(data.Fixtures))
{
chunk.Fixtures.Clear();
if (data.Fixtures != null)
chunk.Fixtures.UnionWith(data.Fixtures);
}
chunk.CachedBounds = data.CachedBounds!.Value;
chunk.SuppressCollisionRegeneration = false;
if (shapeChanged)
{
modifiedChunks.Add(chunk);
}
}
private void OnGridGetState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
@@ -372,7 +385,7 @@ public abstract partial class SharedMapSystem
return;
}
List<ChunkDatum>? chunkData;
Dictionary<Vector2i, ChunkDatum>? chunkData;
var fromTick = args.FromTick;
if (component.LastTileModifiedTick < fromTick)
@@ -381,7 +394,7 @@ public abstract partial class SharedMapSystem
}
else
{
chunkData = new List<ChunkDatum>();
chunkData = new Dictionary<Vector2i, ChunkDatum>();
foreach (var (tick, indices) in component.ChunkDeletionHistory)
{
@@ -391,7 +404,7 @@ public abstract partial class SharedMapSystem
// Chunk may have been re-added sometime after it was deleted, but before deletion history was culled.
if (!component.Chunks.TryGetValue(indices, out var chunk))
{
chunkData.Add(ChunkDatum.CreateDeleted(indices));
chunkData.Add(indices, ChunkDatum.Empty);
continue;
}
@@ -416,7 +429,7 @@ public abstract partial class SharedMapSystem
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
}
}
chunkData.Add(ChunkDatum.CreateModified(index, tileBuffer));
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, chunk.Fixtures, chunk.CachedBounds));
}
}
@@ -427,12 +440,12 @@ public abstract partial class SharedMapSystem
return;
HashSet<Vector2> keys = new();
foreach (var chunk in chunkData)
foreach (var (index, chunk) in chunkData)
{
if (chunk.TileData == null)
if (chunk.IsDeleted())
continue;
DebugTools.Assert(keys.Add(chunk.Index), "Duplicate chunk");
DebugTools.Assert(keys.Add(index), "Duplicate chunk");
DebugTools.Assert(chunk.TileData.Any(x => !x.IsEmpty), "Empty non-deleted chunk");
}
#endif
@@ -440,7 +453,7 @@ public abstract partial class SharedMapSystem
private void GetFullState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
{
var chunkData = new Dictionary<Vector2i, Tile[]>();
var chunkData = new Dictionary<Vector2i, ChunkDatum>();
foreach (var (index, chunk) in GetMapChunks(uid, component))
{
@@ -453,7 +466,7 @@ public abstract partial class SharedMapSystem
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
}
}
chunkData.Add(index, tileBuffer);
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, chunk.Fixtures, chunk.CachedBounds));
}
args.State = new MapGridComponentState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
@@ -461,7 +474,7 @@ public abstract partial class SharedMapSystem
#if DEBUG
foreach (var chunk in chunkData.Values)
{
DebugTools.Assert(chunk.Any(x => !x.IsEmpty));
DebugTools.Assert(chunk.TileData!.Any(x => !x.IsEmpty));
}
#endif
}
@@ -495,7 +508,7 @@ public abstract partial class SharedMapSystem
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
{
var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, component));
var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), component));
DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free);
component.MapProxy = proxy;
}
@@ -549,7 +562,7 @@ public abstract partial class SharedMapSystem
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
{
var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, grid));
var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), grid));
DebugTools.Assert(grid.MapProxy == DynamicTree.Proxy.Free);
grid.MapProxy = proxy;
}
@@ -629,9 +642,9 @@ public abstract partial class SharedMapSystem
PhysicsComponent? body = null;
TransformComponent? xform = null;
foreach (var (id, fixture) in mapChunk.Fixtures)
foreach (var id in mapChunk.Fixtures)
{
_fixtures.DestroyFixture(uid, id, fixture, false, manager: manager, body: body, xform: xform);
_fixtures.DestroyFixture(uid, id, false, manager: manager, body: body, xform: xform);
}
RemoveChunk(uid, grid, mapChunk.Indices);
@@ -639,6 +652,20 @@ public abstract partial class SharedMapSystem
}
}
RegenerateAabb(grid);
// May have been deleted from the bulk update above!
if (Deleted(uid))
return;
_physics.WakeBody(uid);
OnGridBoundsChange(uid, grid);
var ev = new RegenerateGridBoundsEvent(uid, chunkRectangles, removedChunks);
RaiseLocalEvent(ref ev);
}
private void RegenerateAabb(MapGridComponent grid)
{
grid.LocalAABB = new Box2();
foreach (var chunk in grid.Chunks.Values)
@@ -659,15 +686,6 @@ public abstract partial class SharedMapSystem
grid.LocalAABB = grid.LocalAABB.Union(gridBounds);
}
}
// May have been deleted from the bulk update above!
if (Deleted(uid))
return;
_physics.WakeBody(uid);
OnGridBoundsChange(uid, grid);
var ev = new RegenerateGridBoundsEvent(uid, chunkRectangles, removedChunks);
RaiseLocalEvent(ref ev);
}
/// <summary>

View File

@@ -5,6 +5,7 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
@@ -23,6 +24,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
private EntityQuery<FixturesComponent> _fixturesQuery;
private EntityQuery<MapComponent> _mapQuery;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
@@ -34,6 +36,7 @@ namespace Robust.Shared.GameObjects
{
base.Initialize();
_fixturesQuery = GetEntityQuery<FixturesComponent>();
_mapQuery = GetEntityQuery<MapComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_metaQuery = GetEntityQuery<MetaDataComponent>();

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -9,33 +10,49 @@ namespace Robust.Shared.GameStates
[Serializable, NetSerializable]
public readonly struct ChunkDatum
{
public readonly Vector2i Index;
public static readonly ChunkDatum Empty = new ChunkDatum();
public readonly HashSet<string>? Fixtures;
// Definitely wasteful to send EVERY tile.
// Optimize away future coder.
// Also it's stored row-major.
public readonly Tile[]? TileData;
public readonly Box2i? CachedBounds;
[MemberNotNullWhen(false, nameof(TileData))]
public bool IsDeleted()
{
return TileData == null;
}
private ChunkDatum(Vector2i index, Tile[] tileData)
internal ChunkDatum(ChunkDatum data)
{
if (data.TileData != null)
{
TileData = new Tile[data.TileData.Length];
data.TileData.CopyTo(TileData, 0);
}
if (data.Fixtures != null)
{
Fixtures = new HashSet<string>(data.Fixtures);
}
CachedBounds = data.CachedBounds;
}
private ChunkDatum(Tile[] tileData, HashSet<string> fixtures, Box2i cachedBounds)
{
Index = index;
TileData = tileData;
Fixtures = fixtures;
CachedBounds = cachedBounds;
}
public static ChunkDatum CreateModified(Vector2i index, Tile[] tileData)
public static ChunkDatum CreateModified(Tile[] tileData, HashSet<string> fixtures, Box2i cachedBounds)
{
return new ChunkDatum(index, tileData);
}
public static ChunkDatum CreateDeleted(Vector2i index)
{
return new ChunkDatum(index, null!);
return new ChunkDatum(tileData, fixtures, cachedBounds);
}
}
}

View File

@@ -9,5 +9,5 @@ namespace Robust.Shared.Map.Components;
public sealed partial class GridTreeComponent : Component
{
[ViewVariables]
public readonly B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> Tree = new();
public readonly B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree = new();
}

View File

@@ -335,7 +335,7 @@ namespace Robust.Shared.Map.Components
/// Serialized state of a <see cref="MapGridComponentState"/>.
/// </summary>
[Serializable, NetSerializable]
internal sealed class MapGridComponentState(ushort chunkSize, Dictionary<Vector2i, Tile[]> fullGridData, GameTick lastTileModifiedTick) : ComponentState
internal sealed class MapGridComponentState(ushort chunkSize, Dictionary<Vector2i, ChunkDatum> fullGridData, GameTick lastTileModifiedTick) : ComponentState
{
/// <summary>
/// The size of the chunks in the map grid.
@@ -345,7 +345,7 @@ namespace Robust.Shared.Map.Components
/// <summary>
/// Networked chunk data containing the full grid state.
/// </summary>
public Dictionary<Vector2i, Tile[]> FullGridData = fullGridData;
public Dictionary<Vector2i, ChunkDatum> FullGridData = fullGridData;
/// <summary>
/// Last game tick that the tile on the grid was modified.
@@ -357,7 +357,7 @@ namespace Robust.Shared.Map.Components
/// Serialized state of a <see cref="MapGridComponentState"/>.
/// </summary>
[Serializable, NetSerializable]
internal sealed class MapGridComponentDeltaState(ushort chunkSize, List<ChunkDatum>? chunkData, GameTick lastTileModifiedTick)
internal sealed class MapGridComponentDeltaState(ushort chunkSize, Dictionary<Vector2i, ChunkDatum>? chunkData, GameTick lastTileModifiedTick)
: ComponentState, IComponentDeltaState<MapGridComponentState>
{
/// <summary>
@@ -368,7 +368,7 @@ namespace Robust.Shared.Map.Components
/// <summary>
/// Networked chunk data.
/// </summary>
public readonly List<ChunkDatum>? ChunkData = chunkData;
public readonly Dictionary<Vector2i, ChunkDatum>? ChunkData = chunkData;
/// <summary>
/// Last game tick that the tile on the grid was modified.
@@ -382,12 +382,12 @@ namespace Robust.Shared.Map.Components
if (ChunkData == null)
return;
foreach (var data in ChunkData)
foreach (var (index, data) in ChunkData)
{
if (data.IsDeleted())
state.FullGridData!.Remove(data.Index);
state.FullGridData.Remove(index);
else
state.FullGridData![data.Index] = data.TileData;
state.FullGridData[index] = new(data);
}
state.LastTileModifiedTick = LastTileModifiedTick;
@@ -395,12 +395,11 @@ namespace Robust.Shared.Map.Components
public MapGridComponentState CreateNewFullState(MapGridComponentState state)
{
var fullGridData = new Dictionary<Vector2i, Tile[]>(state.FullGridData.Count);
var fullGridData = new Dictionary<Vector2i, ChunkDatum>(state.FullGridData.Count);
foreach (var (key, value) in state.FullGridData)
{
var arr = fullGridData[key] = new Tile[value.Length];
Array.Copy(value, arr, value.Length);
fullGridData[key] = new(value);
}
var newState = new MapGridComponentState(ChunkSize, fullGridData, LastTileModifiedTick);

View File

@@ -39,12 +39,11 @@ namespace Robust.Shared.Map
/// <summary>
/// Chunk-local AABB of this chunk.
/// </summary>
[ViewVariables]
public Box2i CachedBounds { get; set; }
/// <summary>
/// Physics fixtures that make up this grid chunk.
/// </summary>
public Dictionary<string, Fixture> Fixtures { get; } = new();
[ViewVariables]
internal HashSet<string> Fixtures = new();
/// <summary>
/// The last game simulation tick that a tile on this chunk was modified.

View File

@@ -18,14 +18,16 @@ internal partial class MapManager
ChunkEnumerator enumerator,
IPhysShape shape,
Transform shapeTransform,
EntityUid gridUid)
Entity<FixturesComponent> grid)
{
var gridTransform = _physics.GetPhysicsTransform(gridUid);
var gridTransform = _physics.GetPhysicsTransform(grid);
while (enumerator.MoveNext(out var chunk))
{
foreach (var fixture in chunk.Fixtures.Values)
foreach (var id in chunk.Fixtures)
{
var fixture = grid.Comp.Fixtures[id];
for (var j = 0; j < fixture.Shape.ChildCount; j++)
{
if (_manifolds.TestOverlap(shape, 0, fixture.Shape, j, shapeTransform, gridTransform))
@@ -169,7 +171,7 @@ internal partial class MapManager
if (!overlappingChunks.MoveNext(out _))
return true;
}
else if (!state.MapManager.IsIntersecting(overlappingChunks, state.Shape, state.Transform, data.Uid))
else if (!state.MapManager.IsIntersecting(overlappingChunks, state.Shape, state.Transform, (data.Uid, data.Fixtures)))
{
return true;
}
@@ -345,7 +347,7 @@ internal partial class MapManager
Box2 WorldAABB,
IPhysShape Shape,
Transform Transform,
B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> Tree,
B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree,
SharedMapSystem MapSystem,
MapManager MapManager,
SharedTransformSystem TransformSystem,
@@ -357,7 +359,7 @@ internal partial class MapManager
Box2 WorldAABB,
IPhysShape Shape,
Transform Transform,
B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> Tree,
B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree,
SharedMapSystem MapSystem,
MapManager MapManager,
SharedTransformSystem TransformSystem,

View File

@@ -249,24 +249,36 @@ namespace Robust.Shared.Physics.Systems
// TODO: Need to handle grids colliding with non-grid entities with the same layer
// (nothing in SS14 does this yet).
var fixture = _fixturesQuery.Comp(gridUid);
var physics = _physicsQuery.Comp(gridUid);
var transform = _physicsSystem.GetPhysicsTransform(gridUid);
var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _map, _physicsSystem, _transform, _physicsQuery, _xformQuery);
var state = (
new Entity<FixturesComponent, MapGridComponent, PhysicsComponent>(gridUid, fixture, grid, physics),
transform,
worldMatrix,
invWorldMatrix,
_map,
_physicsSystem,
_transform,
_fixturesQuery,
_physicsQuery,
_xformQuery);
_mapManager.FindGridsIntersecting(mapId, aabb, ref state,
static (EntityUid uid, MapGridComponent component,
ref (EntityUid gridUid,
MapGridComponent grid,
ref (Entity<FixturesComponent, MapGridComponent, PhysicsComponent> grid,
Transform transform,
Matrix3x2 worldMatrix,
Matrix3x2 invWorldMatrix,
SharedMapSystem _map,
SharedPhysicsSystem _physicsSystem,
SharedTransformSystem xformSystem,
EntityQuery<FixturesComponent> fixturesQuery,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> xformQuery) tuple) =>
{
if (tuple.gridUid == uid ||
if (tuple.grid.Owner == uid ||
!tuple.xformQuery.TryGetComponent(uid, out var collidingXform))
{
return true;
@@ -277,38 +289,43 @@ namespace Robust.Shared.Physics.Systems
var otherTransform = tuple._physicsSystem.GetPhysicsTransform(uid);
// Get Grid2 AABB in grid1 ref
var aabb1 = tuple.grid.LocalAABB.Intersect(tuple.invWorldMatrix.TransformBox(otherGridBounds));
var aabb1 = tuple.grid.Comp2.LocalAABB.Intersect(tuple.invWorldMatrix.TransformBox(otherGridBounds));
// TODO: AddPair has a nasty check in there that's O(n) but that's also a general physics problem.
var ourChunks = tuple._map.GetLocalMapChunks(tuple.gridUid, tuple.grid, aabb1);
var physicsA = tuple.physicsQuery.GetComponent(tuple.gridUid);
var ourChunks = tuple._map.GetLocalMapChunks(tuple.grid.Owner, tuple.grid, aabb1);
var physicsA = tuple.grid.Comp3;
var physicsB = tuple.physicsQuery.GetComponent(uid);
var fixturesB = tuple.fixturesQuery.Comp(uid);
// Only care about chunks on other grid overlapping us.
while (ourChunks.MoveNext(out var ourChunk))
{
var ourChunkWorld =
tuple.worldMatrix.TransformBox(
ourChunk.CachedBounds.Translated(ourChunk.Indices * tuple.grid.ChunkSize));
ourChunk.CachedBounds.Translated(ourChunk.Indices * tuple.grid.Comp2.ChunkSize));
var ourChunkOtherRef = otherGridInvMatrix.TransformBox(ourChunkWorld);
var collidingChunks = tuple._map.GetLocalMapChunks(uid, component, ourChunkOtherRef);
while (collidingChunks.MoveNext(out var collidingChunk))
{
foreach (var (ourId, fixture) in ourChunk.Fixtures)
foreach (var ourId in ourChunk.Fixtures)
{
var fixture = tuple.grid.Comp1.Fixtures[ourId];
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
var fixAABB = fixture.Shape.ComputeAABB(tuple.transform, i);
foreach (var (otherId, otherFixture) in collidingChunk.Fixtures)
foreach (var otherId in collidingChunk.Fixtures)
{
var otherFixture = fixturesB.Fixtures[otherId];
for (var j = 0; j < otherFixture.Shape.ChildCount; j++)
{
var otherAABB = otherFixture.Shape.ComputeAABB(otherTransform, j);
if (!fixAABB.Intersects(otherAABB)) continue;
tuple._physicsSystem.AddPair(tuple.gridUid, uid,
tuple._physicsSystem.AddPair(tuple.grid.Owner, uid,
ourId, otherId,
fixture, i,
otherFixture, j,

View File

@@ -81,7 +81,7 @@ public abstract partial class SharedPhysicsSystem
if (!_fixturesQuery.Resolve(bodyA, ref bodyA.Comp1, false) ||
!_fixturesQuery.Resolve(bodyB, ref bodyB.Comp1, false) ||
!PhysicsQuery.Resolve(bodyA, ref bodyA.Comp2, false) ||
!PhysicsQuery.Resolve(bodyA, ref bodyB.Comp2, false))
!PhysicsQuery.Resolve(bodyB, ref bodyB.Comp2, false))
{
return false;
}
@@ -103,7 +103,7 @@ public abstract partial class SharedPhysicsSystem
if (!_fixturesQuery.Resolve(bodyA, ref bodyA.Comp1, false) ||
!_fixturesQuery.Resolve(bodyB, ref bodyB.Comp1, false) ||
!PhysicsQuery.Resolve(bodyA, ref bodyA.Comp2, false) ||
!PhysicsQuery.Resolve(bodyA, ref bodyB.Comp2, false))
!PhysicsQuery.Resolve(bodyB, ref bodyB.Comp2, false))
{
return false;
}

View File

@@ -1,4 +1,7 @@
using System.Runtime.CompilerServices;
using NUnit.Framework;
// So it can use RobustServerSimulation.
[assembly: InternalsVisibleTo("Robust.Benchmarks")]
[assembly: Parallelizable(ParallelScope.Fixtures)]

View File

@@ -18,6 +18,12 @@ namespace Robust.UnitTesting.Shared
{
private static readonly MapId MapId = new MapId(1);
private static readonly TestCaseData[] IntersectingCases = new[]
{
// Big offset
new TestCaseData(true, new MapCoordinates(new Vector2(10.5f, 10.5f), MapId), new MapCoordinates(new Vector2(10.5f, 10.5f), MapId), 0.25f, true),
};
private static readonly TestCaseData[] InRangeCases = new[]
{
new TestCaseData(true, new MapCoordinates(Vector2.One, MapId), new MapCoordinates(Vector2.Zero, MapId), 0.5f, false),
@@ -207,6 +213,32 @@ namespace Robust.UnitTesting.Shared
mapManager.DeleteMap(spawnPos.MapId);
}
[Test, TestCaseSource(nameof(IntersectingCases))]
public void TestGridIntersecting(bool physics, MapCoordinates spawnPos, MapCoordinates queryPos, float range, bool result)
{
var sim = RobustServerSimulation.NewSimulation();
var server = sim.InitializeInstance();
var lookup = server.Resolve<IEntitySystemManager>().GetEntitySystem<EntityLookupSystem>();
var entManager = server.Resolve<IEntityManager>();
var mapManager = server.Resolve<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
mapSystem.CreateMap(spawnPos.MapId);
var grid = SetupGrid(spawnPos.MapId, mapSystem, entManager, mapManager);
if (physics)
GetPhysicsEntity(entManager, spawnPos);
else
entManager.Spawn(null, spawnPos);
_ = entManager.SpawnEntity(null, spawnPos);
var bounds = new Box2Rotated(Box2.CenteredAround(queryPos.Position, new Vector2(range, range)));
Assert.That(lookup.GetEntitiesIntersecting(queryPos.MapId, bounds).Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
}
[Test, TestCaseSource(nameof(InRangeCases))]
public void TestGridInRange(bool physics, MapCoordinates spawnPos, MapCoordinates queryPos, float range, bool result)
{

View File

@@ -38,7 +38,11 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true));
// Set up map.
var map = server.System<SharedMapSystem>().CreateMap();
EntityUid map = default;
server.Post(() =>
{
map = server.System<SharedMapSystem>().CreateMap();
});
await RunTicks();
@@ -157,7 +161,11 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true));
// Set up map.
var map = server.System<SharedMapSystem>().CreateMap();
EntityUid map = default;
server.Post(() =>
{
map = server.System<SharedMapSystem>().CreateMap();
});
await RunTicks();

View File

@@ -37,7 +37,10 @@ public sealed partial class NoSharedReferencesTest : RobustIntegrationTest
client.Post(() => netMan.ClientConnect(null!, 0, null!));
// Set up map.
var map = server.System<SharedMapSystem>().CreateMap();
server.Post(() =>
{
server.System<SharedMapSystem>().CreateMap();
});
await RunTicks();

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
@@ -8,62 +9,92 @@ using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
namespace Robust.UnitTesting.Shared.Map
namespace Robust.UnitTesting.Shared.Map;
/// <summary>
/// Tests whether grid fixtures are being generated correctly.
/// </summary>
[Parallelizable(ParallelScope.All)]
[TestFixture]
public sealed class GridFixtures_Tests : RobustIntegrationTest
{
/// <summary>
/// Tests whether grid fixtures are being generated correctly.
/// Tests that grid fixtures match what's expected.
/// </summary>
[Parallelizable(ParallelScope.All)]
[TestFixture]
public sealed class GridFixtures_Tests : RobustIntegrationTest
[Test]
public void TestGridFixtureDeletion()
{
[Test]
public async Task TestGridFixtures()
var server = RobustServerSimulation.NewSimulation().InitializeInstance();
var map = server.CreateMap();
var entManager = server.Resolve<IEntityManager>();
var grid = server.Resolve<IMapManager>().CreateGridEntity(map.MapId);
var mapSystem = entManager.System<SharedMapSystem>();
var fixtures = entManager.GetComponent<FixturesComponent>(grid);
mapSystem.SetTiles(grid, new List<(Vector2i GridIndices, Tile Tile)>()
{
var server = StartServer();
await server.WaitIdleAsync();
(Vector2i.Zero, new Tile(1)),
(Vector2i.Right, new Tile(1)),
(Vector2i.Right * 2, new Tile(1)),
(Vector2i.Up, new Tile(1)),
});
var entManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var physSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedPhysicsSystem>();
Assert.That(fixtures.FixtureCount, Is.EqualTo(2));
Assert.That(grid.Comp.LocalAABB.Equals(new Box2(0f, 0f, 3f, 2f)));
await server.WaitAssertion(() =>
{
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
var grid = mapManager.CreateGridEntity(mapId);
mapSystem.SetTile(grid, Vector2i.Up, Tile.Empty);
// Should be nothing if grid empty
Assert.That(entManager.TryGetComponent(grid, out PhysicsComponent? gridBody));
Assert.That(entManager.TryGetComponent(grid, out FixturesComponent? manager));
Assert.That(manager!.FixtureCount, Is.EqualTo(0));
Assert.That(gridBody!.BodyType, Is.EqualTo(BodyType.Static));
Assert.That(fixtures.FixtureCount, Is.EqualTo(1));
Assert.That(grid.Comp.LocalAABB.Equals(new Box2(0f, 0f, 3f, 1f)));
}
// 1 fixture if we only ever update the 1 chunk
grid.Comp.SetTile(Vector2i.Zero, new Tile(1));
[Test]
public async Task TestGridFixtures()
{
var server = StartServer();
await server.WaitIdleAsync();
Assert.That(manager.FixtureCount, Is.EqualTo(1));
// Also should only be a single tile.
var bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
// Poly probably has Box2D's radius added to it so won't be a unit square
Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 1.0f, 0.1f));
var entManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var physSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedPhysicsSystem>();
// Now do 2 tiles (same chunk)
grid.Comp.SetTile(new Vector2i(0, 1), new Tile(1));
await server.WaitAssertion(() =>
{
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
var grid = mapManager.CreateGridEntity(mapId);
Assert.That(manager.FixtureCount, Is.EqualTo(1));
bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
// Should be nothing if grid empty
Assert.That(entManager.TryGetComponent(grid, out PhysicsComponent? gridBody));
Assert.That(entManager.TryGetComponent(grid, out FixturesComponent? manager));
Assert.That(manager!.FixtureCount, Is.EqualTo(0));
Assert.That(gridBody!.BodyType, Is.EqualTo(BodyType.Static));
// Even if we add a new tile old fixture should stay the same if they don't connect.
Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 2.0f, 0.1f));
// 1 fixture if we only ever update the 1 chunk
grid.Comp.SetTile(Vector2i.Zero, new Tile(1));
// If we add a new chunk should be 2 now
grid.Comp.SetTile(new Vector2i(0, -1), new Tile(1));
Assert.That(manager.FixtureCount, Is.EqualTo(2));
Assert.That(manager.FixtureCount, Is.EqualTo(1));
// Also should only be a single tile.
var bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
// Poly probably has Box2D's radius added to it so won't be a unit square
Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 1.0f, 0.1f));
physSystem.SetLinearVelocity(grid, Vector2.One, manager: manager, body: gridBody);
Assert.That(gridBody.LinearVelocity.Length, Is.EqualTo(0f));
});
}
// Now do 2 tiles (same chunk)
grid.Comp.SetTile(new Vector2i(0, 1), new Tile(1));
Assert.That(manager.FixtureCount, Is.EqualTo(1));
bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
// Even if we add a new tile old fixture should stay the same if they don't connect.
Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 2.0f, 0.1f));
// If we add a new chunk should be 2 now
grid.Comp.SetTile(new Vector2i(0, -1), new Tile(1));
Assert.That(manager.FixtureCount, Is.EqualTo(2));
physSystem.SetLinearVelocity(grid, Vector2.One, manager: manager, body: gridBody);
Assert.That(gridBody.LinearVelocity.Length, Is.EqualTo(0f));
});
}
}

View File

@@ -38,6 +38,30 @@ namespace Robust.UnitTesting.Shared.Physics;
[TestFixture]
public sealed class Collision_Test
{
[Test]
public void TestHardCollidable()
{
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var fixtures = entManager.System<FixtureSystem>();
var physics = entManager.System<SharedPhysicsSystem>();
var map = sim.CreateMap();
var bodyAUid = entManager.SpawnAttachedTo(null, new EntityCoordinates(map.Uid, Vector2.Zero));
var bodyBUid = entManager.SpawnAttachedTo(null, new EntityCoordinates(map.Uid, Vector2.Zero));
var bodyA = entManager.AddComponent<PhysicsComponent>(bodyAUid);
var bodyB = entManager.AddComponent<PhysicsComponent>(bodyBUid);
Assert.That(!physics.IsHardCollidable(bodyAUid, bodyBUid));
fixtures.CreateFixture(bodyAUid, "fix1", new Fixture(new PhysShapeCircle(0.5f), 1, 1, true));
fixtures.CreateFixture(bodyBUid, "fix1", new Fixture(new PhysShapeCircle(0.5f), 1, 1, true));
Assert.That(physics.IsHardCollidable(bodyAUid, bodyBUid));
}
[Test]
public void TestCollision()
{

View File

@@ -28,7 +28,7 @@ def main():
def verify_version(version: str):
parts = version.split(".")
parts = version.split("-")[0].split(".")
if len(parts) != 3:
print("Version must be split into three parts with '.'")
sys.exit(1)