Compare commits

..

39 Commits

Author SHA1 Message Date
PJB3005
93c887eec5 Version: 261.2.3 2025-09-26 13:40:43 +02:00
PJB3005
45a2895e4b Validate that content assemblies have a limited list of names.
Also, only read assemblies once from disk

(cherry picked from commit 443a8dfca65be7d60c4bd46181b4c749b4756114)
2025-09-26 13:40:43 +02:00
PJB3005
7d6def6adf Version: 261.2.2 2025-09-19 09:17:27 +02:00
Skye
f733a9efa5 Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:27 +02:00
PJB3005
8a68dce987 Version: 261.2.1 2025-09-14 14:55:51 +02:00
PJB3005
b8d840437e Squashed commit of the following:
commit d4f265c314
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sun Sep 14 14:32:44 2025 +0200

    Fix incorrect path combine in DirLoader and WritableDirProvider

    This (and the other couple past commits) reported by Elelzedel.

commit 7654d38612
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 22:50:51 2025 +0200

    Move CEF cache out of data directory

    Don't want content messing with this...

commit cdcc255123
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:11:16 2025 +0200

    Make Robust.Client.WebView.Cef.Program internal.

commit 2f56a6a110
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:10:46 2025 +0200

    Update SpaceWizards.NFluidSynth to 0.2.2

commit 16fc48cef2
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:09:43 2025 +0200

    Hide IWritableDirProvider.RootDir on client

    This shouldn't be exposed.

(cherry picked from commit 2f07159336bc640e41fbbccfdec4133a68c13bdb)
(cherry picked from commit d6c3212c74373ed2420cc4be2cf10fcd899c2106)
(cherry picked from commit bfa70d7e2ca6758901b680547fcfa9b24e0610b7)
(cherry picked from commit 06e52f5d58efc1491915822c2650f922673c82c6)
2025-09-14 14:55:51 +02:00
metalgearsloth
2b8057acf0 Version: 261.2.0 2025-06-05 22:55:09 +10:00
Tayrtahn
bec3caa5da Fix error when using tpto on a grid (#5991)
* Fix error when using tpto on a grid

* Calculate map coords outside of loop
2025-06-05 22:45:30 +10:00
metalgearsloth
ea6126563b Add NearestChunkEnumerator (#5972)
Not super fast but want it for biome loading to prio chunks nearby first.
2025-06-05 22:36:22 +10:00
slarticodefast
00494ad9eb fix TryQueueDelete (#5996) 2025-06-05 22:34:50 +10:00
Tayrtahn
6672b7b1bd Correct misleading error message in ShareMapSystem.OnParentChange (#5992) 2025-06-05 22:29:47 +10:00
ruddygreat
8dc55e8748 fix the lifestage checks on predicted entity deletion (#5993)
Co-authored-by: Ruddygreat <ruddygreat1@gmail.com>
2025-06-05 22:29:22 +10:00
Tayrtahn
44ea2cd396 Implement IEquatable for ResolvedPathSpecifier and ResolvedCollectionSpecifier (#5980) 2025-06-01 18:10:55 +10:00
metalgearsloth
2c5604432b Update some GetComponentName usages (#5942)
Rider tells me to use generic and generic one seems better.
2025-06-01 17:54:43 +10:00
Tayrtahn
c696466522 Remove ITileDefinition.ID (#5982) 2025-06-01 17:51:59 +10:00
slarticodefast
01bb98e400 fix static grid center of mass (#5985) 2025-05-29 23:05:54 +10:00
metalgearsloth
af08e747de Defer grid state handling TileChangedEvent (#5981)
Rather than doing the old raise-event-per-tile we just raise it at the end.
2025-05-29 09:21:45 +10:00
metalgearsloth
8c35c2c380 Version: 261.1.0 2025-05-29 00:15:26 +10:00
Tayrtahn
6d46d3f4a5 Cleanup most warnings in unit tests (#5946)
* 2 warnings in JointDeletion_Test

* 1 warning in Collision_Test

* 2 warnings in Color_Test (deleted test of deprecated HCY color space)

* 1 warning in MapVelocity_Test

* 2 warnings in MapManager_Tests

* 2 warnings in MapPauseTests

* 1 warning in NetDisconnectMessageTest

* 1 warning in ContainerTests

* Suppress 1 warning in EntityEventBusTests.ComponentEvent

* 4 warnings in MapGridMap_Tests

* 1 warning in GridDeletion_Test

* Remove TryGetContainingContainer foolishness

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2025-05-29 00:12:58 +10:00
Tayrtahn
50e06e43fa Automatic Sawmill generation for UIControllers (#5967) 2025-05-29 00:06:55 +10:00
metalgearsloth
986b0f979d Fix physics forces not autoclearing (#5978)
* Fix physics forces not autoclearing

* Changes
2025-05-29 00:04:55 +10:00
metalgearsloth
a51d786dee Version: 261.0.0 2025-05-28 19:34:47 +10:00
metalgearsloth
5f5fed5d6c Update contact xform usage (#5977)
Forgot this one as well.
2025-05-28 19:30:50 +10:00
Tayrtahn
e475cc7898 Cleanup warning in SpriteBoundsOverlay (#5944)
* Cleanup warning in SpriteBoundsOverlay

* Make better use of the primary constructor
2025-05-28 19:28:53 +10:00
metalgearsloth
ee8ea4ec3b Purge PhysicsMapComponent (#5766)
* Replace PhysicsMapComponent

- Dumb idea
- Lots of book-keeping and perf overhead.
- Much saner this way.

* stuff

* More work

* Purge

* Fixes

* Eh?

* Fixes

* Also this

* weh

* Fixes

* ice-cream

* Fix

* Fix stacking / gravity

* Gravity query

* MoveBuffer optimisations

* Fixes for test

* World gravity

* Fix build

* Avoid some transform resolves for contactless ents

* Less getcomps

* Fix contact caching

* Possibly less copies

* reh

* bulldoze

* Test "fix"

* seikrets

* a

* I saw this but now I decideded against it

* true
2025-05-28 19:18:36 +10:00
slarticodefast
7482451ec4 optimize ToMapCoordinates (#5953) 2025-05-28 12:07:23 +10:00
metalgearsloth
dddf5cd2fb Add entities to SpawnEntitiesAttachedTo (#5971)
* Add entities to SpawnEntitiesAttachedTo

Need it for biome stuff.

* factorio
2025-05-27 19:45:48 +10:00
metalgearsloth
01979c451d Make RaiseMoveEvent internal (#5918)
I don't think content should really be calling this tbh.
2025-05-27 19:45:04 +10:00
slarticodefast
181a5ef0b4 fix GetMapLinearVelocity (#5950)
* fix GetMapLinearVelocity

* resolve and adjust other methods
2025-05-27 19:41:43 +10:00
Princess Cheeseballs
e7c7011cc0 Init Commit (#5909) 2025-05-27 19:34:32 +10:00
metalgearsloth
dc97615fd4 Add some Box2i methods (#5969)
Equivalent to Box2.
2025-05-27 19:26:50 +10:00
metalgearsloth
3b4944376b Fix FastNoiseLite fractal bounding (#5970)
This shouldn't be datafielded because it gets set by other datafields.
2025-05-27 19:14:49 +10:00
Tayrtahn
fa6bd8f7ba Cleanup TypeSerializer Logger warnings (#5966) 2025-05-24 20:23:28 +02:00
Whatstone
2398cbcf26 GameController: init LocMgr before init broadcast (#5965) 2025-05-24 19:02:26 +02:00
Tayrtahn
38ce48a83f Cleanup 3 warnings in SharedContainerSystem (#5949)
* Cleanup 3 warnings in SharedContainerSystem

* Don't call Transform twice
2025-05-24 19:00:57 +02:00
PJB3005
4e7de2f272 File dialog fixes and improvements
File dialog requests can now specify the share and access mode they want out of the opened file. This means read-only access is now possible.

While doing this I noticed that the SDL3 backend had a memory leak *and* didn't match the behavior of the other backends. Cleaned up the code to avoid that.

In-engine commands that *can* specify read-only access on file open now do.
2025-05-24 16:38:01 +02:00
Cami
b61075c660 Stopped recursive updates for controls that are not visible, as it vi… (#5960)
* Stopped recursive updates for controls that are not visible, as it violates framerate in large menus.

* Update Robust.Client/UserInterface/Control.cs

---------

Co-authored-by: Cam <Nop>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-05-23 17:42:47 +02:00
Tayrtahn
7b571dc80e Fix 2 instances of warning CS0162 (#5951) 2025-05-23 17:37:08 +02:00
TemporalOroboros
f1c76ca899 Remove unused obsolete TryGetContainingContainer override (#5660)
* Remove unused obsolete TryGetContainingContainer override

* poke tests

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-05-23 17:36:38 +02:00
89 changed files with 1148 additions and 1282 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,19 +54,88 @@ END TEMPLATE-->
*None yet*
## 260.2.5
## 261.2.3
## 260.2.4
## 261.2.2
## 260.2.3
## 261.2.1
## 260.2.2
## 261.2.0
### New features
* Implement IEquatable for ResolvedPathSpecifier & ResolvedCollectionSpecifier.
* Add NearestChunkEnumerator.
### Bugfixes
* Fix static entities not having the center of mass updated.
* Fix TryQueueDelete.
* Fix tpto potentially parenting grids to non-map entities.
### Other
* TileChangedEvent is now raised once in clientside grid state handling rather than per tile.
* Removed ITileDefinition.ID as it was redundant.
* Change the lifestage checks on predicted entity deletion to check for terminating.
### Internal
* Update some `GetComponentName<T>` uses to generic.
## 260.2.1
## 261.1.0
### New features
* Automatically create logger sawmills for `UIController`s similar to `EntitySystem`s.
### Bugfixes
* Fix physics forces not auto-clearing / respecting the cvar.
### Internal
* Cleanup more compiler warnings in unit tests.
## 261.0.0
### Breaking changes
* Remove unused TryGetContainingContainer override.
* Stop recursive FrameUpdates for controls that are not visible.
* Initialize LocMgr earlier in the callstack for GameController.
* Fix FastNoiseLise fractal bounding and remove its DataField property as it should be derived on other properties updating.
* Make RaiseMoveEvent internal.
* MovedGridsComponent and PhysicsMapComponent are now purged and properties on `SharedPhysicsSystem`. Additionally the TransformComponent for Awake entities is stored alongside the PhysicsComponent for them.
* TransformComponent is now stored on physics contacts.
* Gravity2DComponent and Gravity2DController were moved to SharedPhysicsSystem.
### New features
* `IFileDialogManager` now allows specifying `FileAccess` and `FileShare` modes.
* Add Intersects and Enlarged to Box2i in line with Box2.
* Make `KeyFrame`s on `AnimationTrackProperty` public settable.
* Add the spawned entities to a returned array from `SpawnEntitiesAttachedTo`.
### Bugfixes
* Fixed SDL3 file dialog implementation having a memory leak and not opening files read-write.
* Fix GetMapLinearVelocity.
### Other
* `uploadfile` and `loadprototype` commands now only open files with read access.
* Optimize `ToMapCoordinates`.
### Internal
* Cleanup on internals of `IFileDialogManager`, removing duplicate code.
* Fix Contacts not correctly being marked as `Touching` while contact is ongoing.
## 260.2.0

View File

@@ -13,7 +13,7 @@ namespace Robust.Client.Animations
/// </summary>
public abstract class AnimationTrackProperty : AnimationTrack
{
public List<KeyFrame> KeyFrames { get; protected set; } = new();
public List<KeyFrame> KeyFrames { get; set; } = new();
/// <summary>
/// How to interpolate values when between two keyframes.

View File

@@ -160,6 +160,7 @@ namespace Robust.Client
}
_serializationManager.Initialize();
_loc.Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
@@ -182,7 +183,6 @@ namespace Robust.Client
_serializer.Initialize();
_inputManager.Initialize();
_console.Initialize();
_loc.Initialize();
// Make sure this is done before we try to load prototypes,
// avoid any possibility of race conditions causing the check to not finish

View File

@@ -296,7 +296,7 @@ namespace Robust.Client.GameObjects
public override void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
{
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|| ent.Comp1.EntityDeleted
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
{
return;
@@ -322,7 +322,7 @@ namespace Robust.Client.GameObjects
{
if (IsQueuedForDeletion(ent.Owner)
|| !MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|| ent.Comp1.EntityDeleted
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
{
return;

View File

@@ -24,9 +24,7 @@ namespace Robust.Client.GameObjects
public sealed class SpriteBoundsSystem : EntitySystem
{
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly SpriteTreeSystem _spriteTree = default!;
private SpriteBoundsOverlay? _overlay;
@@ -42,7 +40,7 @@ namespace Robust.Client.GameObjects
if (_enabled)
{
DebugTools.AssertNull(_overlay);
_overlay = new SpriteBoundsOverlay(_spriteTree, _xformSystem);
_overlay = new SpriteBoundsOverlay(EntityManager);
_overlayManager.AddOverlay(_overlay);
}
else
@@ -57,18 +55,13 @@ namespace Robust.Client.GameObjects
private bool _enabled;
}
public sealed class SpriteBoundsOverlay : Overlay
public sealed class SpriteBoundsOverlay(IEntityManager entMan) : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly SharedTransformSystem _xformSystem;
private SpriteTreeSystem _renderTree;
public SpriteBoundsOverlay(SpriteTreeSystem renderTree, SharedTransformSystem xformSystem)
{
_renderTree = renderTree;
_xformSystem = xformSystem;
}
private readonly SharedTransformSystem _xformSystem = entMan.System<SharedTransformSystem>();
private readonly SpriteSystem _spriteSystem = entMan.System<SpriteSystem>();
private readonly SpriteTreeSystem _renderTree = entMan.System<SpriteTreeSystem>();
protected internal override void Draw(in OverlayDrawArgs args)
{
@@ -76,10 +69,11 @@ namespace Robust.Client.GameObjects
var currentMap = args.MapId;
var viewport = args.WorldBounds;
foreach (var (sprite, xform) in _renderTree.QueryAabb(currentMap, viewport))
foreach (var entry in _renderTree.QueryAabb(currentMap, viewport))
{
var (sprite, xform) = entry;
var (worldPos, worldRot) = _xformSystem.GetWorldPositionRotation(xform);
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
var bounds = _spriteSystem.CalculateBounds((entry.Uid, sprite), worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
// Get scaled down bounds used to indicate the "south" of a sprite.
var localBound = bounds.Box;

View File

@@ -467,7 +467,7 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.RunOnWindowThread(a);
}
public IFileDialogManager? FileDialogImpl => _windowing as IFileDialogManager;
public IFileDialogManagerImplementation? FileDialogImpl => _windowing as IFileDialogManagerImplementation;
private abstract class WindowReg
{

View File

@@ -307,7 +307,7 @@ namespace Robust.Client.Graphics.Clyde
action();
}
public IFileDialogManager? FileDialogImpl => null;
public IFileDialogManagerImplementation? FileDialogImpl => null;
private sealed class DummyCursor : ICursor
{

View File

@@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -12,31 +11,16 @@ namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl3WindowingImpl : IFileDialogManager
private sealed partial class Sdl3WindowingImpl : IFileDialogManagerImplementation
{
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
public async Task<string?> OpenFile(FileDialogFilters? filters)
{
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_OPENFILE, filters);
if (fileName == null)
return null;
return File.OpenRead(fileName);
return await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_OPENFILE, filters);
}
public async Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
public async Task<string?> SaveFile(FileDialogFilters? filters)
{
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_SAVEFILE, filters);
if (fileName == null)
return null;
try
{
return (File.Open(fileName, truncate ? FileMode.Truncate : FileMode.Open), true);
}
catch (FileNotFoundException)
{
return (File.Open(fileName, FileMode.Create), false);
}
return await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_SAVEFILE, filters);
}
private unsafe Task<string?> ShowFileDialogOfType(int type, FileDialogFilters? filters)
@@ -74,6 +58,8 @@ internal partial class Clyde
NativeMemory.Free(filter.name);
NativeMemory.Free(filter.pattern);
}
NativeMemory.Free(filtersAlloc);
}
return task;

View File

@@ -71,6 +71,6 @@ namespace Robust.Client.Graphics
void RunOnWindowThread(Action action);
IFileDialogManager? FileDialogImpl { get; }
IFileDialogManagerImplementation? FileDialogImpl { get; }
}
}

View File

@@ -47,7 +47,7 @@ namespace Robust.Client.Physics
* This will draw above every body involved in a particular island solve.
*/
public readonly Queue<(TimeSpan Time, List<PhysicsComponent> Bodies)> IslandSolve = new();
public readonly Queue<(TimeSpan Time, List<Entity<PhysicsComponent, TransformComponent>> Bodies)> IslandSolve = new();
public const float SolveDuration = 0.1f;
public override void Initialize()

View File

@@ -90,9 +90,10 @@ public sealed partial class PhysicsSystem
// existing contacts for predicted entities before performing any actual prediction.
var contacts = new List<Contact>();
var maps = new HashSet<EntityUid>();
var enumerator = AllEntityQuery<PredictedPhysicsComponent, PhysicsComponent, TransformComponent>();
_broadphase.FindNewContacts();
while (enumerator.MoveNext(out _, out var physics, out var xform))
{
DebugTools.Assert(physics.Predict);
@@ -100,10 +101,6 @@ public sealed partial class PhysicsSystem
if (xform.MapUid is not { } map)
continue;
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

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.Collections;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics;
@@ -23,21 +24,23 @@ namespace Robust.Client.Physics
SimulateWorld(frameTime, _gameTiming.InPrediction);
}
protected override void Cleanup(PhysicsMapComponent component, float frameTime)
protected override void Cleanup(float frameTime)
{
var toRemove = new List<Entity<PhysicsComponent>>();
var toRemove = new ValueList<Entity<PhysicsComponent, TransformComponent>>();
// Because we're not predicting 99% of bodies its sleep timer never gets incremented so we'll just do it ourselves.
// (and serializing it over the network isn't necessary?)
// This is a client-only problem.
// Also need to suss out having the client build the island anyway and just... not solving it?
foreach (var body in component.AwakeBodies)
foreach (var ent in AwakeBodies)
{
var body = ent.Comp1;
if (!body.SleepingAllowed || body.LinearVelocity.Length() > LinearToleranceSqr / 2f || body.AngularVelocity * body.AngularVelocity > AngularToleranceSqr / 2f) continue;
body.SleepTime += frameTime;
if (body.SleepTime > TimeToSleep)
{
toRemove.Add(new Entity<PhysicsComponent>(body.Owner, body));
toRemove.Add(ent);
}
}
@@ -46,37 +49,38 @@ namespace Robust.Client.Physics
SetAwake(body, false);
}
base.Cleanup(component, frameTime);
base.Cleanup(frameTime);
}
protected override void UpdateLerpData(PhysicsMapComponent component, List<PhysicsComponent> bodies, EntityQuery<TransformComponent> xformQuery)
protected override void UpdateLerpData(List<Entity<PhysicsComponent, TransformComponent>> bodies)
{
foreach (var body in bodies)
foreach (var bodyEnt in bodies)
{
var body = bodyEnt.Comp1;
var xform = bodyEnt.Comp2;
if (body.BodyType == BodyType.Static ||
component.LerpData.TryGetValue(body.Owner, out var lerpData) ||
!xformQuery.TryGetComponent(body.Owner, out var xform) ||
lerpData.ParentUid == xform.ParentUid)
LerpData.TryGetValue(bodyEnt, out var lerpData) ||
lerpData == xform.ParentUid)
{
continue;
}
component.LerpData[xform.Owner] = (xform.ParentUid, xform.LocalPosition, xform.LocalRotation);
LerpData[bodyEnt.Owner] = xform.ParentUid;
}
}
/// <summary>
/// Flush all of our lerping data.
/// </summary>
protected override void FinalStep(PhysicsMapComponent component)
protected override void FinalStep()
{
base.FinalStep(component);
var xformQuery = GetEntityQuery<TransformComponent>();
base.FinalStep();
foreach (var (uid, (parentUid, position, rotation)) in component.LerpData)
foreach (var (uid, parentUid) in LerpData)
{
if (!xformQuery.TryGetComponent(uid, out var xform) ||
!parentUid.IsValid())
// Can't just re-use xform from before as movement events may cause event subs to fire.
if (!XformQuery.TryGetComponent(uid, out var xform) || !parentUid.IsValid())
{
continue;
}
@@ -85,7 +89,7 @@ namespace Robust.Client.Physics
_transform.SetLocalPositionRotation(uid, xform.LocalPosition, xform.LocalRotation, xform);
}
component.LerpData.Clear();
LerpData.Clear();
}
}
}

View File

@@ -22,7 +22,7 @@ public sealed class LoadPrototypeCommand : IConsoleCommand
var dialogManager = IoCManager.Resolve<IFileDialogManager>();
var loadManager = IoCManager.Resolve<IGamePrototypeLoadManager>();
var stream = await dialogManager.OpenFile();
var stream = await dialogManager.OpenFile(access: FileAccess.Read);
if (stream is null)
return;

View File

@@ -1,3 +1,4 @@
using System.IO;
using Robust.Client.UserInterface;
using Robust.Shared;
using Robust.Shared.Configuration;
@@ -36,7 +37,7 @@ public sealed class UploadFileCommand : IConsoleCommand
var path = new ResPath(args[0]).ToRelativePath();
var filters = new FileDialogFilters(new FileDialogFilters.Group(path.Extension));
await using var file = await _dialog.OpenFile(filters);
await using var file = await _dialog.OpenFile(filters, FileAccess.Read);
if (file == null)
{

View File

@@ -994,6 +994,9 @@ namespace Robust.Client.UserInterface
internal int DoFrameUpdateRecursive(FrameEventArgs args)
{
if (!Visible)
return 0;
var total = 1;
FrameUpdate(args);

View File

@@ -1,6 +1,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Robust.Client.UserInterface.Controllers;
@@ -11,11 +14,14 @@ namespace Robust.Client.UserInterface.Controllers;
/// and <see cref="UISystemDependencyAttribute"/> to depend on <see cref="EntitySystem"/>s, which will be automatically
/// injected once they are created.
/// </summary>
public abstract partial class UIController
public abstract partial class UIController : IPostInjectInit
{
[Dependency] protected readonly IUserInterfaceManager UIManager = default!;
[Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
[Dependency] protected readonly IEntityManager EntityManager = default!;
[Dependency] protected readonly ILogManager LogManager = default!;
public ISawmill Log { get; protected set; } = default!;
public virtual void Initialize()
{
@@ -24,4 +30,39 @@ public abstract partial class UIController
public virtual void FrameUpdate(FrameEventArgs args)
{
}
protected virtual string SawmillName
{
get
{
var name = GetType().Name;
// Strip trailing "UIController"
if (name.EndsWith("UIController"))
name = name.Substring(0, name.Length - "UIController".Length);
// Convert CamelCase to snake_case
// Ignore if all uppercase, assume acronym (e.g. NPC or HTN)
if (name.All(char.IsUpper))
{
name = name.ToLower(CultureInfo.InvariantCulture);
}
else
{
name = string.Concat(name.Select(x => char.IsUpper(x) ? $"_{char.ToLower(x)}" : x.ToString()));
name = name.Trim('_');
}
return $"ui.{name}";
}
}
public void PostInject()
{
Log = LogManager.GetSawmill(SawmillName);
#if !DEBUG
Log.Level = LogLevel.Info;
#endif
}
}

View File

@@ -8,12 +8,19 @@ namespace Robust.Client.UserInterface
/// </summary>
internal sealed class DummyFileDialogManager : IFileDialogManager
{
public Task<Stream?> OpenFile(FileDialogFilters? filters = null)
public Task<Stream?> OpenFile(
FileDialogFilters? filters = null,
FileAccess access = FileAccess.ReadWrite,
FileShare? share = null)
{
return Task.FromResult<Stream?>(null);
}
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(
FileDialogFilters? filters = null,
bool truncate = true,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None)
{
return Task.FromResult<(Stream fileStream, bool alreadyExisted)?>(null);
}

View File

@@ -7,9 +7,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.Asynchronous;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
@@ -30,18 +28,28 @@ namespace Robust.Client.UserInterface
private bool _kDialogAvailable;
private bool _checkedKDialogAvailable;
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
public async Task<Stream?> OpenFile(
FileDialogFilters? filters = null,
FileAccess access = FileAccess.ReadWrite,
FileShare? share = null)
{
if ((access & FileAccess.ReadWrite) != access)
throw new ArgumentException("Invalid file access specified");
var realShare = share ?? (access == FileAccess.Read ? FileShare.Read : FileShare.None);
if ((realShare & (FileShare.ReadWrite | FileShare.Delete)) != realShare)
throw new ArgumentException("Invalid file share specified");
string? name;
if (_clyde.FileDialogImpl is { } clydeImpl)
return await clydeImpl.OpenFile(filters);
name = await clydeImpl.OpenFile(filters);
else
name = await GetOpenFileName(filters);
var name = await GetOpenFileName(filters);
if (name == null)
{
return null;
}
return File.Open(name, FileMode.Open);
return File.Open(name, FileMode.Open, access, realShare);
}
private async Task<string?> GetOpenFileName(FileDialogFilters? filters)
@@ -54,24 +62,34 @@ namespace Robust.Client.UserInterface
return await OpenFileNfd(filters);
}
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters, bool truncate = true)
public async Task<(Stream, bool)?> SaveFile(
FileDialogFilters? filters,
bool truncate = true,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None)
{
if (_clyde.FileDialogImpl is { } clydeImpl)
return await clydeImpl.SaveFile(filters);
if ((access & FileAccess.ReadWrite) != access)
throw new ArgumentException("Invalid file access specified");
if ((share & (FileShare.ReadWrite | FileShare.Delete)) != share)
throw new ArgumentException("Invalid file share specified");
string? name;
if (_clyde.FileDialogImpl is { } clydeImpl)
name = await clydeImpl.SaveFile(filters);
else
name = await GetSaveFileName(filters);
var name = await GetSaveFileName(filters);
if (name == null)
{
return null;
}
try
{
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open), true);
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open, access, share), true);
}
catch (FileNotFoundException)
{
return (File.Open(name, FileMode.Create), false);
return (File.Open(name, FileMode.Create, access, share), false);
}
}

View File

@@ -1,5 +1,6 @@
using System.IO;
using System.Threading.Tasks;
using Robust.Client.Graphics;
namespace Robust.Client.UserInterface
{
@@ -19,7 +20,17 @@ namespace Robust.Client.UserInterface
/// The file stream for the file the user opened.
/// <see langword="null" /> if the user cancelled the action.
/// </returns>
Task<Stream?> OpenFile(FileDialogFilters? filters = null);
/// <param name="filters">Filters for file types that the user can select.</param>
/// <param name="access">What access is desired from the file operation.</param>
/// <param name="share">
/// What sharing mode is desired from the file operation.
/// If null is provided and <paramref name="access"/> is <see cref="FileAccess.Read"/>,
/// <see cref="FileShare.Read"/> is selected, otherwise <see cref="FileShare.None"/>.
/// </param>
Task<Stream?> OpenFile(
FileDialogFilters? filters = null,
FileAccess access = FileAccess.ReadWrite,
FileShare? share = null);
/// <summary>
/// Open a file dialog used for saving a single file.
@@ -29,6 +40,21 @@ namespace Robust.Client.UserInterface
/// Null if the user cancelled the action.
/// </returns>
/// <param name="truncate">Should we truncate an existing file to 0-size then write or append.</param>
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true);
/// <param name="access">What access is desired from the file operation.</param>
/// <param name="share">Sharing mode for the opened file.</param>
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(
FileDialogFilters? filters = null,
bool truncate = true,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None);
}
/// <summary>
/// Internal implementation interface used to connect <see cref="FileDialogManager"/> and <see cref="IClydeInternal"/>.
/// </summary>
internal interface IFileDialogManagerImplementation
{
Task<string?> OpenFile(FileDialogFilters? filters);
Task<string?> SaveFile(FileDialogFilters? filters);
}
}

View File

@@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Controllers;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
namespace Robust.Server.Physics.Commands;
public sealed class BoxStackCommand : IConsoleCommand
{
[Dependency] private readonly IEntityManager _entManager = default!;
public string Command => "boxstack";
public string Description => string.Empty;
public string Help => "boxstack [mapid] [columns] [rows]";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 3 ||
!int.TryParse(args[0], out var mapInt) ||
!int.TryParse(args[1], out var columns) ||
!int.TryParse(args[2], out var rows))
{
return;
}
var mapId = new MapId(mapInt);
var fixtureSystem = _entManager.System<FixtureSystem>();
var physSystem = _entManager.System<SharedPhysicsSystem>();
physSystem.SetGravity(new Vector2(0f, -9.8f));
var groundUid = _entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = _entManager.AddComponent<PhysicsComponent>(groundUid);
var groundManager = _entManager.EnsureComponent<FixturesComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtureSystem.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 1, 1, true), manager: groundManager, body: ground);
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
fixtureSystem.CreateFixture(groundUid, "fix2", new Fixture(vertical, 1, 1, true), manager: groundManager, body: ground);
physSystem.WakeBody(groundUid, manager: groundManager, body: ground);
var xs = new[]
{
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
};
for (var j = 0; j < columns; j++)
{
for (var i = 0; i < rows; i++)
{
var x = 0.0f;
var boxUid = _entManager.SpawnEntity(null,
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
var box = _entManager.AddComponent<PhysicsComponent>(boxUid);
var manager = _entManager.EnsureComponent<FixturesComponent>(boxUid);
physSystem.SetBodyType(boxUid, BodyType.Dynamic, manager: manager, body: box);
var poly = new PolygonShape(0.001f);
poly.Set(new List<Vector2>()
{
new(0.5f, -0.5f),
new(0.5f, 0.5f),
new(-0.5f, 0.5f),
new(-0.5f, -0.5f),
});
fixtureSystem.CreateFixture(boxUid, "fix1", new Fixture(poly, 1, 1, true), manager: manager, body: box);
physSystem.WakeBody(boxUid, manager: manager, body: box);
}
}
}
}

View File

@@ -140,7 +140,7 @@ namespace Robust.Server.Placement
{
// Replace existing entities if relevant.
if (msg.Replacement && _prototype.Index<EntityPrototype>(entityTemplateName).Components.TryGetValue(
_factory.GetComponentName(typeof(PlacementReplacementComponent)), out var compRegistry))
_factory.GetComponentName<PlacementReplacementComponent>(), out var compRegistry))
{
var key = ((PlacementReplacementComponent)compRegistry.Component).Key;
var gridUid = _xformSystem.GetGrid(coordinates);

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -101,6 +102,7 @@ namespace Robust.Shared.Maths
/// <summary>
/// Returns the smallest rectangle that contains both of the rectangles.
/// </summary>
[Pure]
public readonly Box2i Union(in Box2i other)
{
var botLeft = Vector2i.ComponentMin(BottomLeft, other.BottomLeft);
@@ -207,6 +209,7 @@ namespace Robust.Shared.Maths
/// <summary>
/// Multiplies each side of the box by the scalar.
/// </summary>
[Pure]
public Box2i Scale(int scalar)
{
return new Box2i(
@@ -215,6 +218,21 @@ namespace Robust.Shared.Maths
Right * scalar,
Top * scalar);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool Intersects(in Box2i other)
{
return other.Bottom <= this.Top && other.Top >= this.Bottom && other.Right >= this.Left &&
other.Left <= this.Right;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public readonly Box2i Enlarged(int size)
{
return new(Left - size, Bottom - size, Right + size, Top + size);
}
}
/// <summary>

View File

@@ -13,7 +13,8 @@ namespace Robust.Shared.Audio;
/// <seealso cref="ResolvedPathSpecifier"/>
/// <seealso cref="ResolvedCollectionSpecifier"/>
[Serializable, NetSerializable]
public abstract partial class ResolvedSoundSpecifier {
public abstract partial class ResolvedSoundSpecifier
{
[Obsolete("String literals for sounds are deprecated, use a SoundSpecifier or ResolvedSoundSpecifier as appropriate instead")]
public static implicit operator ResolvedSoundSpecifier(string s) => new ResolvedPathSpecifier(s);
[Obsolete("String literals for sounds are deprecated, use a SoundSpecifier or ResolvedSoundSpecifier as appropriate instead")]
@@ -22,8 +23,10 @@ public abstract partial class ResolvedSoundSpecifier {
/// <summary>
/// Returns whether <c>s</c> is null, or if it contains an empty path/collection ID.
/// </summary>
public static bool IsNullOrEmpty(ResolvedSoundSpecifier? s) {
return s switch {
public static bool IsNullOrEmpty(ResolvedSoundSpecifier? s)
{
return s switch
{
null => true,
ResolvedPathSpecifier path => path.Path.ToString() == "",
ResolvedCollectionSpecifier collection => string.IsNullOrEmpty(collection.Collection),
@@ -37,7 +40,8 @@ public abstract partial class ResolvedSoundSpecifier {
/// </summary>
/// <seealso cref="ResolvedCollectionSpecifier"/>
[Serializable, NetSerializable]
public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier {
public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier, IEquatable<ResolvedPathSpecifier>
{
/// <summary>
/// The resource path of the sound.
/// </summary>
@@ -57,6 +61,21 @@ public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier {
public ResolvedPathSpecifier(string path) : this(new ResPath(path))
{
}
public bool Equals(ResolvedPathSpecifier? other)
{
return Path.Equals(other?.Path);
}
public override bool Equals(object? obj)
{
return Equals(obj as ResolvedPathSpecifier);
}
public override int GetHashCode()
{
return Path.GetHashCode();
}
}
/// <summary>
@@ -64,7 +83,9 @@ public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier {
/// </summary>
/// <seealso cref="ResolvedPathSpecifier"/>
[Serializable, NetSerializable]
public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier {
public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier, IEquatable<ResolvedCollectionSpecifier>
{
/// <summary>
/// The ID of the <see cref="SoundCollectionPrototype">sound collection</see> to look up.
/// </summary>
@@ -87,4 +108,19 @@ public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier
Collection = collection;
Index = index;
}
public bool Equals(ResolvedCollectionSpecifier? other)
{
return Collection.Equals(other?.Collection) && Index.Equals(other?.Index);
}
public override bool Equals(object? obj)
{
return Equals(obj as ResolvedCollectionSpecifier);
}
public override int GetHashCode()
{
return HashCode.Combine(Collection, Index);
}
}

View File

@@ -137,9 +137,10 @@ public sealed class TeleportToCommand : LocalizedEntityCommands
}
}
var targetMapCoords = _transform.ToMapCoordinates(targetCoords);
foreach (var victim in victims)
{
_transform.SetCoordinates(victim.Entity, targetCoords);
_transform.SetMapCoordinates(victim.Entity, targetMapCoords);
_transform.AttachToGridOrMap(victim.Entity, victim.Transform);
}
}

View File

@@ -279,16 +279,6 @@ namespace Robust.Shared.Containers
#region Container Helpers
[Obsolete("Use Entity<T> variant")]
public bool TryGetContainingContainer(
EntityUid uid,
[NotNullWhen(true)] out BaseContainer? container,
MetaDataComponent? meta = null,
TransformComponent? transform = null)
{
return TryGetContainingContainer((uid, transform, meta), out container);
}
public bool TryGetContainingContainer(
Entity<TransformComponent?, MetaDataComponent?> ent,
[NotNullWhen(true)] out BaseContainer? container)
@@ -606,7 +596,7 @@ namespace Robust.Shared.Containers
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
/// <param name="wasInContainer">Whether the entity was actually inside a container or not.</param>
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
public bool TryRemoveFromContainer(EntityUid entity, bool force, out bool wasInContainer)
public bool TryRemoveFromContainer(Entity<TransformComponent?, MetaDataComponent?> entity, bool force, out bool wasInContainer)
{
DebugTools.Assert(Exists(entity));
@@ -631,7 +621,7 @@ namespace Robust.Shared.Containers
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
public bool TryRemoveFromContainer(EntityUid entity, bool force = false)
public bool TryRemoveFromContainer(Entity<TransformComponent?, MetaDataComponent?> entity, bool force = false)
{
return TryRemoveFromContainer(entity, force, out _);
}
@@ -678,9 +668,8 @@ namespace Robust.Shared.Containers
{
// TODO make this check upwards for any container, and parent to that.
// Currently this just checks the direct parent, so entities will still teleport through containers.
if (!transform.Comp.ParentUid.IsValid()
|| !TryGetContainingContainer(transform.Comp.ParentUid, out var container)
|| !TryGetContainingContainer((transform.Comp.ParentUid, Transform(transform.Comp.ParentUid)), out var container)
|| !TryInsertIntoContainer(transform, container))
{
_transform.AttachToGridOrMap(transform, transform.Comp);
@@ -692,8 +681,9 @@ namespace Robust.Shared.Containers
if (Insert((transform.Owner, transform.Comp, null, null), container))
return true;
if (Transform(container.Owner).ParentUid.IsValid()
&& TryGetContainingContainer(container.Owner, out var newContainer))
var ownerXform = Transform(container.Owner);
if (ownerXform.ParentUid.IsValid()
&& TryGetContainingContainer((container.Owner, ownerXform), out var newContainer))
return TryInsertIntoContainer(transform, newContainer);
return false;

View File

@@ -556,9 +556,10 @@ public sealed class EntityDeserializer :
{
#if !EXCEPTION_TOLERANCE
throw;
#endif
#else
ToDelete.Add(entity);
_log.Error($"Encountered error while loading entity. Yaml uid: {data.YamlId}. Loaded loaded entity: {EntMan.ToPrettyString(entity)}. Error:\n{e}.");
#endif
}
}

View File

@@ -162,8 +162,8 @@ public sealed class EntitySerializer : ISerializationContext,
_log = _logMan.GetSawmill("entity_serializer");
SerializerProvider.RegisterSerializer(this);
_metaName = _factory.GetComponentName(typeof(MetaDataComponent));
_xformName = _factory.GetComponentName(typeof(TransformComponent));
_metaName = _factory.GetComponentName<MetaDataComponent>();
_xformName = _factory.GetComponentName<TransformComponent>();
_emptyMetaNode = _serialization.WriteValueAs<MappingDataNode>(typeof(MetaDataComponent), new MetaDataComponent(), alwaysWrite: true, context: this);
CurrentComponent = _xformName;

View File

@@ -633,7 +633,7 @@ namespace Robust.Shared.GameObjects
/// An invalid entity UID indicates that this entity has intentionally been removed from broadphases and should
/// not automatically be re-added by movement events.
/// </remarks>
internal record struct BroadphaseData(EntityUid Uid, EntityUid PhysicsMap, bool CanCollide, bool Static)
internal record struct BroadphaseData(EntityUid Uid, bool CanCollide, bool Static)
{
public bool IsValid() => Uid.IsValid();
public bool Valid => IsValid();

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
@@ -84,12 +85,17 @@ public partial class EntityManager
return ents;
}
public void SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames)
public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames)
{
var ents = new ValueList<EntityUid>();
foreach (var protoName in protoNames)
{
SpawnAttachedTo(protoName, coordinates);
var uid = SpawnAttachedTo(protoName, coordinates);
ents.Add(uid);
}
return ents.ToArray();
}
public virtual EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)

View File

@@ -498,7 +498,7 @@ namespace Robust.Shared.GameObjects
if (Deleted(uid.Value))
return false;
if (!QueuedDeletionsSet.Add(uid.Value))
if (QueuedDeletionsSet.Contains(uid.Value))
return false;
QueueDeleteEntity(uid);

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -22,7 +23,7 @@ public partial interface IEntityManager
EntityUid[] SpawnEntities(MapCoordinates coordinates, params string?[] protoNames);
EntityUid[] SpawnEntities(MapCoordinates coordinates, string? prototype, int count);
EntityUid[] SpawnEntities(MapCoordinates coordinates, List<string?> protoNames);
void SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params EntProtoId[] protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params string?[] protoNames);

View File

@@ -89,7 +89,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<PhysicsMapComponent> _physMapQuery;
private EntityQuery<TransformComponent> _xformQuery;
/// <summary>
@@ -119,7 +118,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
_gridQuery = GetEntityQuery<MapGridComponent>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_physMapQuery = GetEntityQuery<PhysicsMapComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
SubscribeLocalEvent<BroadphaseComponent, EntityTerminatingEvent>(OnBroadphaseTerminating);
@@ -153,15 +151,12 @@ public sealed partial class EntityLookupSystem : EntitySystem
private void OnBroadphaseTerminating(EntityUid uid, BroadphaseComponent component, ref EntityTerminatingEvent args)
{
var xform = _xformQuery.GetComponent(uid);
var map = xform.MapUid;
_physMapQuery.TryGetComponent(map, out var physMap);
RemoveChildrenFromTerminatingBroadphase(xform, component, physMap);
RemoveChildrenFromTerminatingBroadphase(xform, component);
RemComp(uid, component);
}
private void RemoveChildrenFromTerminatingBroadphase(TransformComponent xform,
BroadphaseComponent component,
PhysicsMapComponent? map)
BroadphaseComponent component)
{
foreach (var child in xform._children)
{
@@ -179,19 +174,15 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (childXform.Broadphase.Value.CanCollide && _fixturesQuery.TryGetComponent(child, out var fixtures))
{
if (map == null)
_physMapQuery.TryGetComponent(childXform.Broadphase.Value.PhysicsMap, out map);
DebugTools.Assert(map == null || childXform.Broadphase.Value.PhysicsMap == map.Owner);
var tree = childXform.Broadphase.Value.Static ? component.StaticTree : component.DynamicTree;
foreach (var fixture in fixtures.Fixtures.Values)
{
DestroyProxies(fixture, tree, map);
DestroyProxies(fixture, tree);
}
}
childXform.Broadphase = null;
RemoveChildrenFromTerminatingBroadphase(childXform, component, map);
RemoveChildrenFromTerminatingBroadphase(childXform, component);
}
}
@@ -227,26 +218,18 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (xform.MapUid == null)
return;
if (!_physMapQuery.TryGetComponent(xform.MapUid, out var physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
}
var ent = new Entity<TransformComponent, BroadphaseComponent>(broadphase, xform, broadphase);
var map = new Entity<PhysicsMapComponent>(xform.MapUid.Value, physMap);
var enumerator = xform.ChildEnumerator;
while (enumerator.MoveNext(out var child))
{
if (!_broadQuery.HasComp(child))
InitializeChild(child, ent, map);
InitializeChild(child, ent);
}
}
private void InitializeChild(
EntityUid child,
Entity<TransformComponent, BroadphaseComponent> broadphase,
Entity<PhysicsMapComponent> map)
Entity<TransformComponent, BroadphaseComponent> broadphase)
{
if (LifeStage(child) <= EntityLifeStage.PreInit)
return;
@@ -258,7 +241,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (!xform.Broadphase.Value.IsValid())
return; // Entity is intentionally not on a broadphase (deferred updating?).
_physMapQuery.TryGetComponent(xform.Broadphase.Value.PhysicsMap, out var oldPhysMap);
if (!_broadQuery.TryGetComponent(xform.Broadphase.Value.Uid, out var oldBroadphase))
{
DebugTools.Assert("Encountered deleted broadphase.");
@@ -275,16 +257,15 @@ public sealed partial class EntityLookupSystem : EntitySystem
}
else if (oldBroadphase != broadphase.Comp2)
{
RemoveFromEntityTree(xform.Broadphase.Value.Uid, oldBroadphase, ref oldPhysMap, child, xform);
RemoveFromEntityTree(xform.Broadphase.Value.Uid, oldBroadphase,child, xform);
}
}
DebugTools.Assert(xform.Broadphase is not {} x || x.Uid == broadphase.Owner && (!x.CanCollide || x.PhysicsMap == map.Owner));
DebugTools.Assert(xform.Broadphase is not {} x || x.Uid == broadphase.Owner && !x.CanCollide);
AddOrUpdateEntityTree(
broadphase.Owner,
broadphase.Comp2,
broadphase.Comp1,
map.Comp,
child,
xform);
}
@@ -315,9 +296,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (!TryGetCurrentBroadphase(xform, out var broadphase))
return;
if (!_physMapQuery.TryGetComponent(xform.MapUid, out var physMap))
throw new InvalidOperationException();
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
var mapTransform = new Transform(worldPos, worldRot);
@@ -326,10 +304,10 @@ public sealed partial class EntityLookupSystem : EntitySystem
var tree = body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree;
DebugTools.Assert(fixture.ProxyCount == 0);
AddOrMoveProxies(uid, fixtureId, fixture, body, tree, broadphaseTransform, mapTransform, physMap.MoveBuffer);
AddOrMoveProxies((uid, body, xform), fixtureId, fixture, tree, broadphaseTransform);
}
internal void DestroyProxies(EntityUid uid, string fixtureId, Fixture fixture, TransformComponent xform, BroadphaseComponent broadphase, PhysicsMapComponent? physicsMap)
internal void DestroyProxies(EntityUid uid, string fixtureId, Fixture fixture, TransformComponent xform, BroadphaseComponent broadphase)
{
DebugTools.AssertNotNull(xform.Broadphase);
DebugTools.Assert(xform.Broadphase!.Value.Uid == broadphase.Owner);
@@ -344,7 +322,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
}
var tree = xform.Broadphase.Value.Static ? broadphase.StaticTree : broadphase.DynamicTree;
DestroyProxies(fixture, tree, physicsMap);
DestroyProxies(fixture, tree);
}
#endregion
@@ -395,8 +373,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
var fixtures = Comp<FixturesComponent>(uid);
if (old.CanCollide)
{
_physMapQuery.TryGetComponent(old.PhysicsMap, out var physicsMap);
RemoveBroadTree(broadphase, fixtures, old.Static, physicsMap);
RemoveBroadTree(broadphase, fixtures, old.Static);
}
else
(old.Static ? broadphase.StaticSundriesTree : broadphase.SundriesTree).Remove(uid);
@@ -408,23 +385,23 @@ public sealed partial class EntityLookupSystem : EntitySystem
AddOrUpdateSundriesTree(old.Uid, broadphase, uid, xform, body.BodyType == BodyType.Static);
}
private void RemoveBroadTree(BroadphaseComponent lookup, FixturesComponent manager, bool staticBody, PhysicsMapComponent? map)
private void RemoveBroadTree(BroadphaseComponent lookup, FixturesComponent manager, bool staticBody)
{
var tree = staticBody ? lookup.StaticTree : lookup.DynamicTree;
foreach (var fixture in manager.Fixtures.Values)
{
DestroyProxies(fixture, tree, map);
DestroyProxies(fixture, tree);
}
}
internal void DestroyProxies(Fixture fixture, IBroadPhase tree, PhysicsMapComponent? map)
internal void DestroyProxies(Fixture fixture, IBroadPhase tree)
{
var buffer = map?.MoveBuffer;
var buffer = _physics.MoveBuffer;
for (var i = 0; i < fixture.ProxyCount; i++)
{
var proxy = fixture.Proxies[i];
tree.RemoveProxy(proxy.ProxyId);
buffer?.Remove(proxy);
buffer.Remove(proxy);
}
fixture.ProxyCount = 0;
@@ -438,10 +415,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (broadphaseXform.MapID == MapId.Nullspace)
return;
if (!_physMapQuery.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);
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, xform, body, fixtures);
}
private void AddOrUpdatePhysicsTree(
@@ -449,16 +423,15 @@ public sealed partial class EntityLookupSystem : EntitySystem
EntityUid broadUid,
BroadphaseComponent broadphase,
TransformComponent broadphaseXform,
PhysicsMapComponent physicsMap,
TransformComponent xform,
PhysicsComponent body,
FixturesComponent manager)
{
DebugTools.Assert(!_container.IsEntityOrParentInContainer(body.Owner, null, xform));
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadphase.Owner, physicsMap.Owner, body.CanCollide, body.BodyType == BodyType.Static));
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadphase.Owner, body.CanCollide, body.BodyType == BodyType.Static));
DebugTools.Assert(broadphase.Owner == broadUid);
xform.Broadphase ??= new(broadUid, physicsMap.Owner, body.CanCollide, body.BodyType == BodyType.Static);
xform.Broadphase ??= new(broadUid, body.CanCollide, body.BodyType == BodyType.Static);
var tree = body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree;
// TOOD optimize this. This function iterates UP through parents, while we are currently iterating down.
@@ -470,20 +443,19 @@ public sealed partial class EntityLookupSystem : EntitySystem
foreach (var (id, fixture) in manager.Fixtures)
{
AddOrMoveProxies(uid, id, fixture, body, tree, broadphaseTransform, mapTransform, physicsMap.MoveBuffer);
AddOrMoveProxies((uid, body, xform), id, fixture, tree, broadphaseTransform);
}
}
private void AddOrMoveProxies(
EntityUid uid,
Entity<PhysicsComponent, TransformComponent> ent,
string fixtureId,
Fixture fixture,
PhysicsComponent body,
IBroadPhase tree,
Transform broadphaseTransform,
Transform mapTransform,
Dictionary<FixtureProxy, Box2> moveBuffer)
Transform broadphaseTransform)
{
var moveBuffer = _physics.MoveBuffer;
// Moving
if (fixture.ProxyCount > 0)
{
@@ -493,7 +465,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
var proxy = fixture.Proxies[i];
tree.MoveProxy(proxy.ProxyId, bounds);
proxy.AABB = bounds;
moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
moveBuffer.Add(proxy);
}
return;
@@ -505,11 +477,11 @@ public sealed partial class EntityLookupSystem : EntitySystem
for (var i = 0; i < count; i++)
{
var bounds = fixture.Shape.ComputeAABB(broadphaseTransform, i);
var proxy = new FixtureProxy(uid, body, bounds, fixtureId, fixture, i);
var proxy = new FixtureProxy(ent.Owner, ent.Comp1, ent.Comp2, bounds, fixtureId, fixture, i);
proxy.ProxyId = tree.AddProxy(ref proxy);
proxy.AABB = bounds;
proxies[i] = proxy;
moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
moveBuffer.Add(proxy);
}
fixture.Proxies = proxies;
@@ -519,8 +491,8 @@ public sealed partial class EntityLookupSystem : EntitySystem
private void AddOrUpdateSundriesTree(EntityUid broadUid, BroadphaseComponent broadphase, EntityUid uid, TransformComponent xform, bool staticBody, Box2? aabb = null)
{
DebugTools.Assert(!_container.IsEntityOrParentInContainer(uid));
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadUid, default, false, staticBody));
xform.Broadphase ??= new(broadUid, default, false, staticBody);
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadUid, false, staticBody));
xform.Broadphase ??= new(broadUid, false, staticBody);
(staticBody ? broadphase.StaticSundriesTree : broadphase.SundriesTree).AddOrUpdate(uid, aabb);
}
@@ -538,8 +510,12 @@ public sealed partial class EntityLookupSystem : EntitySystem
{
if (args.Component.GridUid == args.Sender)
{
if (args.ParentChanged) // grid changed maps, need to update children and clear the move buffer.
// If grid changes map MoveBuffer will have incorrect worldpositions for all children.
if (args.ParentChanged)
{
OnGridChangedMap(args);
}
return;
}
DebugTools.Assert(!_gridQuery.HasComp(args.Sender));
@@ -563,84 +539,21 @@ public sealed partial class EntityLookupSystem : EntitySystem
return;
// We need to recursively update the cached data and remove children from the move buffer
DebugTools.Assert(_gridQuery.HasComp(args.Sender));
DebugTools.Assert(!newMap.IsValid() || _mapQuery.HasComp(newMap));
DebugTools.Assert(!oldMap.IsValid() || _mapQuery.HasComp(oldMap));
var oldBuffer = _physMapQuery.CompOrNull(oldMap)?.MoveBuffer;
var newBuffer = _physMapQuery.CompOrNull(newMap)?.MoveBuffer;
foreach (var child in args.Component._children)
{
RecursiveOnGridChangedMap(child, oldMap, newMap, oldBuffer, newBuffer);
}
}
private void RecursiveOnGridChangedMap(
EntityUid uid,
EntityUid oldMap,
EntityUid newMap,
Dictionary<FixtureProxy, Box2>? oldBuffer,
Dictionary<FixtureProxy, Box2>? newBuffer)
{
if (!_xformQuery.TryGetComponent(uid, out var xform))
return;
foreach (var child in xform._children)
{
RecursiveOnGridChangedMap(child, oldMap, newMap, oldBuffer, newBuffer);
}
if (xform.Broadphase == null || !xform.Broadphase.Value.CanCollide)
return;
DebugTools.Assert(_netMan.IsClient || !xform.Broadphase.Value.PhysicsMap.IsValid() || xform.Broadphase.Value.PhysicsMap == oldMap);
xform.Broadphase = xform.Broadphase.Value with { PhysicsMap = newMap };
if (!_fixturesQuery.TryGetComponent(uid, out var fixtures))
return;
if (oldBuffer != null)
{
foreach (var fix in fixtures.Fixtures.Values)
foreach (var prox in fix.Proxies)
{
oldBuffer.Remove(prox);
}
}
if (newBuffer == null)
return;
// TODO PERFORMANCE
// track world position while recursively iterating down through children.
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
var mapTransform = new Transform(worldPos, worldRot);
foreach (var fixture in fixtures.Fixtures.Values)
{
for (var i = 0; i < fixture.ProxyCount; i++)
{
var proxy = fixture.Proxies[i];
newBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
}
}
DebugTools.Assert(HasComp<MapGridComponent>(args.Sender));
DebugTools.Assert(!newMap.IsValid() || HasComp<MapComponent>(newMap));
DebugTools.Assert(!oldMap.IsValid() || HasComp<MapComponent>(oldMap));
}
private void UpdateParent(EntityUid uid, TransformComponent xform)
{
BroadphaseComponent? oldBroadphase = null;
PhysicsMapComponent? oldPhysMap = null;
if (xform.Broadphase != null)
{
if (!xform.Broadphase.Value.IsValid())
return; // Entity is intentionally not on a broadphase (deferred updating?).
_physMapQuery.TryGetComponent(xform.Broadphase.Value.PhysicsMap, out oldPhysMap);
if (!_broadQuery.TryGetComponent(xform.Broadphase.Value.Uid, out oldBroadphase))
{
DebugTools.Assert("Encountered deleted broadphase.");
// broadphase was probably deleted.
@@ -661,24 +574,18 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (oldBroadphase != null && oldBroadphase != newBroadphase)
{
RemoveFromEntityTree(oldBroadphase.Owner, oldBroadphase, ref oldPhysMap, uid, xform);
RemoveFromEntityTree(oldBroadphase.Owner, oldBroadphase, uid, xform);
}
if (newBroadphase == null)
return;
var newBroadphaseXform = _xformQuery.GetComponent(newBroadphase.Owner);
if (!_physMapQuery.TryGetComponent(newBroadphaseXform.MapUid, out var physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(newBroadphase.Owner)}");
}
AddOrUpdateEntityTree(
newBroadphase.Owner,
newBroadphase,
newBroadphaseXform,
physMap,
uid,
xform);
}
@@ -713,17 +620,11 @@ public sealed partial class EntityLookupSystem : EntitySystem
bool recursive = true)
{
var broadphaseXform = _xformQuery.GetComponent(broadphase.Owner);
if (!_physMapQuery.TryGetComponent(broadphaseXform.MapUid, out var physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
}
AddOrUpdateEntityTree(
broadUid,
broadphase,
broadphaseXform,
physMap,
uid,
xform,
recursive);
@@ -733,7 +634,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
EntityUid broadUid,
BroadphaseComponent broadphase,
TransformComponent broadphaseXform,
PhysicsMapComponent physicsMap,
EntityUid uid,
TransformComponent xform,
bool recursive = true)
@@ -758,7 +658,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
}
else
{
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, physicsMap, xform, body, _fixturesQuery.GetComponent(uid));
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, xform, body, _fixturesQuery.GetComponent(uid));
}
if (xform.ChildCount == 0 || !recursive)
@@ -771,7 +671,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
foreach (var child in xform._children)
{
var childXform = _xformQuery.GetComponent(child);
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, physicsMap, child, childXform, recursive);
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, child, childXform, recursive);
}
return;
}
@@ -782,7 +682,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
continue;
var childXform = _xformQuery.GetComponent(child);
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, physicsMap, child, childXform, recursive);
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, child, childXform, recursive);
}
}
@@ -794,16 +694,10 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (!TryGetCurrentBroadphase(xform, out var broadphase))
return;
DebugTools.Assert(!_gridQuery.HasComp(uid));
DebugTools.Assert(!_mapQuery.HasComp(uid));
PhysicsMapComponent? physMap = null;
if (xform.Broadphase!.Value.PhysicsMap is { Valid: true } map && !_physMapQuery.TryGetComponent(map, out physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
}
DebugTools.Assert(!HasComp<MapGridComponent>(uid));
DebugTools.Assert(!HasComp<MapComponent>(uid));
RemoveFromEntityTree(broadphase.Owner, broadphase, ref physMap, uid, xform);
RemoveFromEntityTree(broadphase.Owner, broadphase, uid, xform);
}
/// <summary>
@@ -812,7 +706,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
private void RemoveFromEntityTree(
EntityUid broadUid,
BroadphaseComponent broadphase,
ref PhysicsMapComponent? physicsMap,
EntityUid uid,
TransformComponent xform,
bool recursive = true)
@@ -833,16 +726,9 @@ public sealed partial class EntityLookupSystem : EntitySystem
broadUid = old.Uid;
}
if (old.PhysicsMap.IsValid() && physicsMap?.Owner != old.PhysicsMap)
{
if (!_physMapQuery.TryGetComponent(old.PhysicsMap, out physicsMap))
Log.Error($"Entity {ToPrettyString(uid)} has missing physics map?");
}
if (old.CanCollide)
{
DebugTools.Assert(old.PhysicsMap == (physicsMap?.Owner ?? default));
RemoveBroadTree(broadphase, _fixturesQuery.GetComponent(uid), old.Static, physicsMap);
RemoveBroadTree(broadphase, _fixturesQuery.GetComponent(uid), old.Static);
}
else if (old.Static)
broadphase.StaticSundriesTree.Remove(uid);
@@ -858,7 +744,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
RemoveFromEntityTree(
broadUid,
broadphase,
ref physicsMap,
child,
_xformQuery.GetComponent(child));
}

View File

@@ -166,10 +166,7 @@ public abstract partial class SharedMapSystem
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
}
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
{
movedGrids.MovedGrids.Add(uid);
}
_physics.MovedGrids.Add(uid);
}
private void OnGridMove(EntityUid uid, MapGridComponent component, ref MoveEvent args)
@@ -191,10 +188,7 @@ public abstract partial class SharedMapSystem
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
}
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
{
movedGrids.MovedGrids.Add(uid);
}
_physics.MovedGrids.Add(uid);
}
private void OnParentChange(EntityUid uid, MapGridComponent component, ref MoveEvent args)
@@ -219,23 +213,23 @@ public abstract partial class SharedMapSystem
if (xform.ParentUid != xform.MapUid && meta.EntityLifeStage < EntityLifeStage.Terminating && _netManager.IsServer)
{
Log.Error($"Grid {ToPrettyString(uid, meta)} is not parented to {ToPrettyString(xform._parent)} which is not a map. y'all need jesus. {Environment.StackTrace}");
Log.Error($"Grid {ToPrettyString(uid, meta)} is parented to {ToPrettyString(xform._parent)} which is not a map. y'all need jesus. {Environment.StackTrace}");
return;
}
// Make sure we cleanup old map for moved grid stuff.
var oldMap = _transform.ToMapCoordinates(args.OldPosition);
var oldMapUid = GetMapOrInvalid(oldMap.MapId);
if (component.MapProxy != DynamicTree.Proxy.Free && TryComp<MovedGridsComponent>(oldMapUid, out var oldMovedGrids))
if (component.MapProxy != DynamicTree.Proxy.Free)
{
oldMovedGrids.MovedGrids.Remove(uid);
_physics.MovedGrids.Remove(uid);
RemoveGrid(uid, component, oldMapUid);
}
DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free);
if (TryComp<MovedGridsComponent>(xform.MapUid, out var newMovedGrids))
if (xform.MapUid != null)
{
newMovedGrids.MovedGrids.Add(uid);
_physics.MovedGrids.Add(uid);
AddGrid(uid, component);
}
}
@@ -344,6 +338,8 @@ public abstract partial class SharedMapSystem
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(data.TileData.Any(x => !x.IsEmpty));
DebugTools.Assert(data.TileData.Length == component.ChunkSize * component.ChunkSize);
var changedEntry = new ValueList<TileChangedEntry>();
for (ushort x = 0; x < component.ChunkSize; x++)
{
for (ushort y = 0; y < component.ChunkSize; y++)
@@ -352,12 +348,15 @@ public abstract partial class SharedMapSystem
if (!chunk.TrySetTile(x, y, tile, out var oldTile, out _))
continue;
var gridIndices = chunk.ChunkTileToGridTile((x, y));
var newTileRef = new TileRef(uid, gridIndices, tile);
_mapInternal.RaiseOnTileChanged(gridEnt, newTileRef, oldTile, index);
var chunkIndex = new Vector2i(x, y);
var gridIndices = chunk.ChunkTileToGridTile(chunkIndex);
changedEntry.Add(new TileChangedEntry(tile, oldTile, chunk.Indices, gridIndices));
}
}
var ev = new TileChangedEvent(gridEnt, changedEntry.ToArray());
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Owner, ref ev, true);
DebugTools.Assert(chunk.Fixtures.SetEquals(data.Fixtures));
// These should never refer to the same object
@@ -519,9 +518,9 @@ public abstract partial class SharedMapSystem
component.MapProxy = proxy;
}
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
if (xform.MapUid != null)
{
movedGrids.MovedGrids.Add(uid);
_physics.MovedGrids.Add(uid);
}
}
@@ -573,9 +572,9 @@ public abstract partial class SharedMapSystem
grid.MapProxy = proxy;
}
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
if (xform.MapUid != null)
{
movedGrids.MovedGrids.Add(uid);
_physics.MovedGrids.Add(uid);
}
}
@@ -588,9 +587,9 @@ public abstract partial class SharedMapSystem
grid.MapProxy = DynamicTree.Proxy.Free;
if (TryComp<MovedGridsComponent>(mapUid, out var movedGrids))
if (mapUid.IsValid())
{
movedGrids.MovedGrids.Remove(uid);
_physics.MovedGrids.Remove(uid);
}
}

View File

@@ -116,9 +116,7 @@ public abstract partial class SharedMapSystem
private void OnComponentAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
// ordered startups when
EnsureComp<PhysicsMapComponent>(uid);
EnsureComp<GridTreeComponent>(uid);
EnsureComp<MovedGridsComponent>(uid);
}
internal void AssignMapId(Entity<MapComponent> map, MapId? id = null)

View File

@@ -217,9 +217,10 @@ public abstract partial class SharedTransformSystem
{
#if !EXCEPTION_TOLERANCE
throw new Exception("Transform is initialising before map ids have been assigned?");
#endif
#else
Log.Error($"Transform is initialising before map ids have been assigned?");
_map.AssignMapId((uid, mapComp));
#endif
}
xform.MapUid = uid;

View File

@@ -53,8 +53,14 @@ public abstract partial class SharedTransformSystem
return MapCoordinates.Nullspace;
}
var worldPos = Vector2.Transform(coordinates.Position, GetWorldMatrix(xform));
return new MapCoordinates(worldPos, xform.MapID);
Vector2 pos = xform._localRotation.RotateVec(coordinates.Position) + xform._localPosition;
while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid())
{
xform = XformQuery.GetComponent(xform.ParentUid);
pos = xform._localRotation.RotateVec(pos) + xform._localPosition;
}
return new MapCoordinates(pos, xform.MapID);
}
/// <summary>
@@ -67,6 +73,41 @@ public abstract partial class SharedTransformSystem
return ToMapCoordinates(eCoords);
}
/// <summary>
/// Converts entity-local coordinates into map terms.
/// The same as ToMapCoordinates(coordinates, logError).Position, but doesn't have to construct the MapCoordinates first.
/// </summary>
public Vector2 ToWorldPosition(EntityCoordinates coordinates, bool logError = true)
{
if (!TryComp(coordinates.EntityId, out TransformComponent? xform))
{
if (logError)
Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}. Trace: {Environment.StackTrace}");
return Vector2.Zero;
}
Vector2 pos = xform._localRotation.RotateVec(coordinates.Position) + xform._localPosition;
while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid())
{
xform = XformQuery.GetComponent(xform.ParentUid);
pos = xform._localRotation.RotateVec(pos) + xform._localPosition;
}
return pos;
}
/// <summary>
/// Converts entity-local coordinates into map terms.
/// The same as ToMapCoordinates(coordinates).Position, but doesn't have to construct the MapCoordinates first.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector2 ToWorldPosition(NetCoordinates coordinates)
{
var eCoords = GetCoordinates(coordinates);
return ToWorldPosition(eCoords);
}
/// <summary>
/// Creates EntityCoordinates given an entity and some MapCoordinates.
/// </summary>

View File

@@ -263,7 +263,7 @@ namespace Robust.Shared.GameObjects
return true;
}
public void RaiseMoveEvent(
internal void RaiseMoveEvent(
Entity<TransformComponent, MetaDataComponent> ent,
EntityUid oldParent,
Vector2 oldPosition,

View File

@@ -1,16 +0,0 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Map.Components;
/// <summary>
/// Stores what grids moved in a tick.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class MovedGridsComponent : Component
{
[ViewVariables]
public HashSet<EntityUid> MovedGrids = new();
}

View File

@@ -0,0 +1,64 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Shared.Collections;
using Robust.Shared.Maths;
namespace Robust.Shared.Map.Enumerators;
/// <summary>
/// Iterates chunk indices but prefers ones closer towards the center first.
/// </summary>
public record struct NearestChunkEnumerator
{
private readonly Vector2i _chunkLB;
private readonly Vector2i _chunkRT;
private ValueList<Vector2i> _chunks = new();
private int _n;
public NearestChunkEnumerator(Box2 localAABB, int chunkSize)
{
_chunkLB = (localAABB.BottomLeft / chunkSize).Floored();
_chunkRT = (localAABB.TopRight / chunkSize).Floored();
InitializeChunks(new Vector2i(chunkSize, chunkSize));
}
public NearestChunkEnumerator(Box2 localAABB, Vector2i chunkSize)
{
_chunkLB = (localAABB.BottomLeft / chunkSize).Floored();
_chunkRT = (localAABB.TopRight / chunkSize).Floored();
InitializeChunks(chunkSize);
}
private void InitializeChunks(Vector2i chunkSize)
{
var bl = (Vector2)_chunkLB * chunkSize;
var tr = (Vector2)_chunkRT * chunkSize;
var halfChunk = new Vector2(chunkSize.X / 2f, chunkSize.Y / 2f);
var center = (tr - bl) / 2 + bl;
for (var x = _chunkLB.X; x < _chunkRT.X; x++)
{
for (var y = _chunkLB.Y; y < _chunkRT.Y; y++)
{
_chunks.Add(new Vector2i(x, y) * chunkSize);
}
}
_chunks.Sort((c1, c2) => ((c1 + halfChunk) - center).LengthSquared().CompareTo(((c2 + halfChunk) - center).LengthSquared()));
}
public bool MoveNext([NotNullWhen(true)] out Vector2i? indices)
{
if (_n >= _chunks.Count)
{
indices = null;
return false;
}
indices = _chunks[_n++] ;
return true;
}
}

View File

@@ -20,11 +20,6 @@ namespace Robust.Shared.Map
/// </summary>
string Name { get; }
/// <summary>
/// Internal name of the definition.
/// </summary>
string ID { get; }
/// <summary>
/// The path of the sprite to draw.
/// </summary>

View File

@@ -37,7 +37,7 @@ using FNLfloat = System.Single;
//using FNLfloat = System.Double;
[DataDefinition, Serializable, NetSerializable]
public sealed partial class FastNoiseLite
public sealed partial class FastNoiseLite : ISerializationHooks
{
private const short INLINE = 256; // MethodImplOptions.AggressiveInlining;
private const short OPTIMISE = 512; // MethodImplOptions.AggressiveOptimization;
@@ -136,7 +136,6 @@ public sealed partial class FastNoiseLite
[DataField("pingPongStrength")]
private float mPingPongStrength = 2.0f;
[DataField("fractalBounding")]
private float mFractalBounding = 1 / 1.75f;
[DataField("cellularDistanceFunction")]
@@ -170,6 +169,11 @@ public sealed partial class FastNoiseLite
SetSeed(1337);
}
void ISerializationHooks.AfterDeserialization()
{
CalculateFractalBounding();
}
public int GetSeed() => mSeed;
/// <summary>

View File

@@ -1,18 +0,0 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class Gravity2DComponent : Component
{
/// <summary>
/// Applies side-view gravity to the map.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("gravity")]
public Vector2 Gravity;
}

View File

@@ -154,11 +154,6 @@ public sealed partial class PhysicsComponent : Component, IComponentDelta
// ReSharper disable once InconsistentNaming
internal float _inertia;
/// <summary>
/// Indicates whether this body ignores gravity
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] public bool IgnoreGravity;
/// <summary>
/// Inverse moment of inertia (1 / I).
/// </summary>
@@ -264,6 +259,9 @@ public sealed partial class PhysicsComponent : Component, IComponentDelta
[DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)]
public BodyStatus BodyStatus { get; set; }
[DataField, Access(typeof(SharedPhysicsSystem))]
public bool IgnoreGravity;
[ViewVariables, Access(typeof(SharedPhysicsSystem))]
public bool Predict;
}

View File

@@ -1,106 +0,0 @@
using System;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics.Controllers;
public sealed class Gravity2DController : VirtualController
{
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
private ISawmill _sawmill = default!;
public override void Initialize()
{
base.Initialize();
_sawmill = Logger.GetSawmill("physics");
SubscribeLocalEvent<Gravity2DComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<Gravity2DComponent, ComponentHandleState>(OnHandleState);
}
private void OnGetState(EntityUid uid, Gravity2DComponent component, ref ComponentGetState args)
{
args.State = new Gravity2DComponentState() { Gravity = component.Gravity };
}
private void OnHandleState(EntityUid uid, Gravity2DComponent component, ref ComponentHandleState args)
{
if (args.Current is not Gravity2DComponentState state)
return;
component.Gravity = state.Gravity;
}
public Vector2 GetGravity(EntityUid uid, Gravity2DComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return Vector2.Zero;
return component.Gravity;
}
public void SetGravity(EntityUid uid, Vector2 value)
{
if (!HasComp<MapComponent>(uid))
{
_sawmill.Error($"Tried to set 2D gravity for an entity that isn't a map?");
DebugTools.Assert(false);
return;
}
var gravity = EnsureComp<Gravity2DComponent>(uid);
if (gravity.Gravity.Equals(value))
return;
gravity.Gravity = value;
WakeBodiesRecursive(uid, GetEntityQuery<TransformComponent>(), GetEntityQuery<PhysicsComponent>());
Dirty(uid, gravity);
}
public void SetGravity(MapId mapId, Vector2 value)
{
var mapUid = _mapSystem.GetMap(mapId);
var gravity = EnsureComp<Gravity2DComponent>(mapUid);
if (gravity.Gravity.Equals(value))
return;
gravity.Gravity = value;
WakeBodiesRecursive(mapUid, GetEntityQuery<TransformComponent>(), GetEntityQuery<PhysicsComponent>());
Dirty(mapUid, gravity);
}
private void WakeBodiesRecursive(EntityUid uid, EntityQuery<TransformComponent> xformQuery, EntityQuery<PhysicsComponent> bodyQuery)
{
if (bodyQuery.TryGetComponent(uid, out var body) &&
body.BodyType == BodyType.Dynamic)
{
_physics.WakeBody(uid, body: body);
}
var xform = xformQuery.GetComponent(uid);
foreach (var child in xform._children)
{
WakeBodiesRecursive(child, xformQuery, bodyQuery);
}
}
[Serializable, NetSerializable]
private sealed class Gravity2DComponentState : ComponentState
{
public Vector2 Gravity;
}
}

View File

@@ -35,9 +35,6 @@ namespace Robust.Shared.Physics.Controllers
SubscribeLocalEvent<PhysicsUpdateBeforeSolveEvent>(OnBeforeSolve, updatesBefore, updatesAfter);
SubscribeLocalEvent<PhysicsUpdateAfterSolveEvent>(OnAfterSolve, updatesBefore, updatesAfter);
SubscribeLocalEvent<PhysicsUpdateBeforeMapSolveEvent>(OnBeforeMapSolve, updatesBefore, updatesAfter);
SubscribeLocalEvent<PhysicsUpdateAfterMapSolveEvent>(OnAfterMapSolve, updatesBefore, updatesAfter);
}
private void OnBeforeSolve(ref PhysicsUpdateBeforeSolveEvent ev)
@@ -62,16 +59,6 @@ namespace Robust.Shared.Physics.Controllers
AfterMonitor.Observe(Stopwatch.Elapsed.TotalSeconds);
}
private void OnBeforeMapSolve(ref PhysicsUpdateBeforeMapSolveEvent ev)
{
UpdateBeforeMapSolve(ev.Prediction, ev.MapComponent, ev.DeltaTime);
}
private void OnAfterMapSolve(ref PhysicsUpdateAfterMapSolveEvent ev)
{
UpdateAfterMapSolve(ev.Prediction, ev.MapComponent, ev.DeltaTime);
}
#endregion
/// <summary>
@@ -87,21 +74,5 @@ namespace Robust.Shared.Physics.Controllers
/// <param name="prediction"></param>
/// <param name="frameTime"></param>
public virtual void UpdateAfterSolve(bool prediction, float frameTime) {}
/// <summary>
/// Run before a particular map starts.
/// </summary>
/// <param name="prediction"></param>
/// <param name="mapComponent"></param>
/// <param name="frameTime"></param>
public virtual void UpdateBeforeMapSolve(bool prediction, PhysicsMapComponent mapComponent, float frameTime) {}
/// <summary>
/// Run after a particular map finishes.
/// </summary>
/// <param name="prediction"></param>
/// <param name="mapComponent"></param>
/// <param name="frameTime"></param>
public virtual void UpdateAfterMapSolve(bool prediction, PhysicsMapComponent mapComponent, float frameTime) {}
}
}

View File

@@ -80,6 +80,9 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
public PhysicsComponent? BodyA;
public PhysicsComponent? BodyB;
public TransformComponent? XformA;
public TransformComponent? XformB;
public Manifold Manifold;
internal ContactType Type;
@@ -253,6 +256,11 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
{
status = ContactStatus.EndTouching;
}
// Still touching
else
{
status = ContactStatus.Touching;
}
}
#if DEBUG
@@ -442,6 +450,17 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
throw new InvalidOperationException();
}
[Pure]
public TransformComponent OtherTransform(EntityUid uid)
{
if (uid == EntityA)
return XformB!;
else if (uid == EntityB)
return XformA!;
throw new InvalidOperationException();
}
}
[Flags]

View File

@@ -25,43 +25,45 @@ using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics.Dynamics
namespace Robust.Shared.Physics.Dynamics;
public sealed class FixtureProxy
{
public sealed class FixtureProxy
public EntityUid Entity;
public PhysicsComponent Body { get; internal set; }
public TransformComponent Xform { get; internal set; }
/// <summary>
/// Grid-based AABB of this proxy.
/// </summary>
[ViewVariables]
public Box2 AABB;
[ViewVariables]
public int ChildIndex;
public string FixtureId;
/// <summary>
/// Our parent fixture
/// </summary>
public Fixture Fixture;
/// <summary>
/// ID of this proxy in the broadphase dynamictree.
/// </summary>
[ViewVariables]
public DynamicTree.Proxy ProxyId = DynamicTree.Proxy.Free;
internal FixtureProxy(EntityUid uid, PhysicsComponent body, TransformComponent xform, Box2 aabb, string fixtureId, Fixture fixture, int childIndex)
{
public EntityUid Entity;
public PhysicsComponent Body;
/// <summary>
/// Grid-based AABB of this proxy.
/// </summary>
[ViewVariables]
public Box2 AABB;
[ViewVariables]
public int ChildIndex;
public string FixtureId;
/// <summary>
/// Our parent fixture
/// </summary>
public Fixture Fixture;
/// <summary>
/// ID of this proxy in the broadphase dynamictree.
/// </summary>
[ViewVariables]
public DynamicTree.Proxy ProxyId = DynamicTree.Proxy.Free;
public FixtureProxy(EntityUid uid, PhysicsComponent body, Box2 aabb, string fixtureId, Fixture fixture, int childIndex)
{
Entity = uid;
Body = body;
AABB = aabb;
FixtureId = fixtureId;
Fixture = fixture;
ChildIndex = childIndex;
}
Entity = uid;
Body = body;
Xform = xform;
AABB = aabb;
FixtureId = fixtureId;
Fixture = fixture;
ChildIndex = childIndex;
}
}

View File

@@ -1,90 +0,0 @@
/*
* Farseer Physics Engine:
* Copyright (c) 2012 Ian Qvist
*
* Original source Box2D:
* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
using PhysicsComponent = Robust.Shared.Physics.Components.PhysicsComponent;
namespace Robust.Shared.Physics.Dynamics;
[RegisterComponent, NetworkedComponent]
public sealed partial class PhysicsMapComponent : Component
{
public bool AutoClearForces;
/// <summary>
/// When substepping the client needs to know about the first position to use for lerping.
/// </summary>
public readonly Dictionary<EntityUid, (EntityUid ParentUid, Vector2 LocalPosition, Angle LocalRotation)>
LerpData = new();
/// <summary>
/// Keep a buffer of everything that moved in a tick. This will be used to check for physics contacts.
/// </summary>
[ViewVariables]
public readonly Dictionary<FixtureProxy, Box2> MoveBuffer = new();
/// <summary>
/// All awake bodies on this map.
/// </summary>
[ViewVariables]
public readonly HashSet<PhysicsComponent> AwakeBodies = new();
/// <summary>
/// Store last tick's invDT
/// </summary>
internal float _invDt0;
}
[ByRefEvent]
public readonly struct PhysicsUpdateBeforeMapSolveEvent
{
public readonly bool Prediction;
public readonly PhysicsMapComponent MapComponent;
public readonly float DeltaTime;
public PhysicsUpdateBeforeMapSolveEvent(bool prediction, PhysicsMapComponent mapComponent, float deltaTime)
{
Prediction = prediction;
MapComponent = mapComponent;
DeltaTime = deltaTime;
}
}
[ByRefEvent]
public readonly struct PhysicsUpdateAfterMapSolveEvent
{
public readonly bool Prediction;
public readonly PhysicsMapComponent MapComponent;
public readonly float DeltaTime;
public PhysicsUpdateAfterMapSolveEvent(bool prediction, PhysicsMapComponent mapComponent, float deltaTime)
{
Prediction = prediction;
MapComponent = mapComponent;
DeltaTime = deltaTime;
}
}

View File

@@ -6,9 +6,9 @@ namespace Robust.Shared.Physics
{
internal sealed class IslandSolveMessage : EntityEventArgs
{
public List<PhysicsComponent> Bodies { get; }
public List<Entity<PhysicsComponent, TransformComponent>> Bodies { get; }
public IslandSolveMessage(List<PhysicsComponent> bodies)
public IslandSolveMessage(List<Entity<PhysicsComponent, TransformComponent>> bodies)
{
Bodies = bodies;
}

View File

@@ -21,13 +21,10 @@ namespace Robust.Shared.Physics.Systems
/// </summary>
public sealed partial class FixtureSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
#pragma warning disable CS0414
[Dependency] private readonly IGameTiming _timing = default!;
#pragma warning restore CS0414
private EntityQuery<PhysicsMapComponent> _mapQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<FixturesComponent> _fixtureQuery;
@@ -38,7 +35,6 @@ 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>();
}
@@ -206,8 +202,7 @@ namespace Robust.Shared.Physics.Systems
if (_lookup.TryGetCurrentBroadphase(xform, out var broadphase))
{
DebugTools.Assert(xform.MapUid == Transform(broadphase.Owner).MapUid);
_mapQuery.TryGetComponent(xform.MapUid, out var physicsMap);
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
}
if (updates)

View File

@@ -34,10 +34,12 @@ namespace Robust.Shared.Physics.Systems
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<PhysicsMapComponent> _mapQuery;
private float _broadphaseExpand;
private readonly Dictionary<EntityUid, Matrix3x2> _broadMatrices = new();
private HashSet<FixtureProxy> _gridMoveBuffer = new();
/*
* 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
@@ -56,6 +58,8 @@ namespace Robust.Shared.Physics.Systems
_mapManager = _mapManager,
System = this,
BroadphaseExpand = _broadphaseExpand,
// TODO: EntityManager one isn't ready yet?
XformQuery = GetEntityQuery<TransformComponent>(),
};
_broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
@@ -63,7 +67,6 @@ namespace Robust.Shared.Physics.Systems
_gridQuery = GetEntityQuery<MapGridComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_mapQuery = GetEntityQuery<PhysicsMapComponent>();
UpdatesOutsidePrediction = true;
UpdatesAfter.Add(typeof(SharedTransformSystem));
@@ -98,87 +101,75 @@ namespace Robust.Shared.Physics.Systems
/// <summary>
/// Check the AABB for each moved broadphase fixture and add any colliding entities to the movebuffer in case.
/// </summary>
private void FindGridContacts(
PhysicsMapComponent component,
MapId mapId,
HashSet<EntityUid> movedGrids,
Dictionary<FixtureProxy, Box2> gridMoveBuffer)
private void FindGridContacts(HashSet<EntityUid> movedGrids)
{
// None moved this tick
if (movedGrids.Count == 0) return;
var mapBroadphase = _broadphaseQuery.GetComponent(_map.GetMapOrInvalid(mapId));
// 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 = component.MoveBuffer;
var moveBuffer = _physicsSystem.MoveBuffer;
foreach (var gridUid in movedGrids)
{
var grid = _gridQuery.GetComponent(gridUid);
var xform = _xformQuery.GetComponent(gridUid);
DebugTools.Assert(xform.MapID == mapId);
// Moved to nullspace?
if (!_broadphaseQuery.TryComp(xform.MapUid, out var mapBroadphase))
continue;
var worldAABB = _transform.GetWorldMatrix(xform).TransformBox(grid.LocalAABB);
var enlargedAABB = worldAABB.Enlarged(_broadphaseExpand);
var state = (moveBuffer, gridMoveBuffer);
var state = (moveBuffer, _gridMoveBuffer);
QueryMapBroadphase(mapBroadphase.DynamicTree, ref state, enlargedAABB);
QueryMapBroadphase(mapBroadphase.StaticTree, ref state, enlargedAABB);
}
foreach (var (proxy, worldAABB) in gridMoveBuffer)
foreach (var proxy in _gridMoveBuffer)
{
moveBuffer[proxy] = worldAABB;
moveBuffer.Add(proxy);
// If something is in our AABB then try grid traversal for it
_traversal.CheckTraverse((proxy.Entity, _xformQuery.GetComponent(proxy.Entity)));
}
}
private void QueryMapBroadphase(IBroadPhase broadPhase,
ref (Dictionary<FixtureProxy, Box2>, Dictionary<FixtureProxy, Box2>) state,
ref (HashSet<FixtureProxy>, HashSet<FixtureProxy>) state,
Box2 enlargedAABB)
{
// Easier to just not go over each proxy as we already unioned the fixture's worldaabb.
broadPhase.QueryAabb(ref state, static (ref (
Dictionary<FixtureProxy, Box2> moveBuffer,
Dictionary<FixtureProxy, Box2> gridMoveBuffer) tuple,
HashSet<FixtureProxy> moveBuffer,
HashSet<FixtureProxy> gridMoveBuffer) tuple,
in FixtureProxy value) =>
{
// 99% of the time it's just going to be the broadphase (for now the grid) itself.
// hence this body check makes this run significantly better.
// Also check if it's not already on the movebuffer.
if (tuple.moveBuffer.ContainsKey(value))
if (tuple.moveBuffer.Contains(value))
return true;
// To avoid updating during iteration.
// Don't need to transform as it's already in map terms.
tuple.gridMoveBuffer[value] = value.AABB;
tuple.gridMoveBuffer.Add(value);
return true;
}, enlargedAABB, true);
}
[Obsolete("Use the overload with SharedPhysicsMapComponent")]
internal void FindNewContacts(MapId mapId)
{
if (!TryComp<PhysicsMapComponent>(_map.GetMapOrInvalid(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(PhysicsMapComponent component, MapId mapId)
internal void FindNewContacts()
{
var moveBuffer = component.MoveBuffer;
var mapUid = _map.GetMapOrInvalid(mapId);
var movedGrids = Comp<MovedGridsComponent>(mapUid).MovedGrids;
var gridMoveBuffer = new Dictionary<FixtureProxy, Box2>();
var moveBuffer = _physicsSystem.MoveBuffer;
var movedGrids = _physicsSystem.MovedGrids;
_gridMoveBuffer.Clear();
// Find any entities being driven over that might need to be considered
FindGridContacts(component, mapId, movedGrids, gridMoveBuffer);
FindGridContacts(movedGrids);
// 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
@@ -190,18 +181,29 @@ namespace Robust.Shared.Physics.Systems
// to cache a bunch of stuff to make up for it.
// Handle grids first as they're not stored on map broadphase at all.
HandleGridCollisions(mapId, movedGrids);
HandleGridCollisions(movedGrids);
// EZ
if (moveBuffer.Count == 0)
return;
_contactJob.MapUid = _mapManager.GetMapEntityIdOrThrow(mapId);
_contactJob.MoveBuffer.Clear();
foreach (var (proxy, aabb) in moveBuffer)
foreach (var proxy in moveBuffer)
{
_contactJob.MoveBuffer.Add((proxy, aabb));
DebugTools.Assert(_xformQuery.GetComponent(proxy.Entity).Broadphase?.Uid != null);
_contactJob.MoveBuffer.Add(proxy);
}
_broadMatrices.Clear();
var broadQuery = AllEntityQuery<BroadphaseComponent>();
// Cache broadphase matrices up front.
// We'll defer the proxy world AABBs until we get contacts rather than doing it on every single move.
// This is because contacts are run in parallel so we can spread the work a bit more and also don't duplicate it per tick.
while (broadQuery.MoveNext(out var bUid, out _))
{
_broadMatrices[bUid] = _transform.GetWorldMatrix(bUid);
}
for (var i = _contactJob.ContactBuffer.Count; i < _contactJob.MoveBuffer.Count; i++)
@@ -220,7 +222,7 @@ namespace Robust.Shared.Physics.Systems
if (proxies.Count == 0)
continue;
var proxyA = _contactJob.MoveBuffer[i].Proxy;
var proxyA = _contactJob.MoveBuffer[i];
var proxyABody = proxyA.Body;
_fixturesQuery.TryGetComponent(proxyA.Entity, out var manager);
@@ -234,7 +236,7 @@ namespace Robust.Shared.Physics.Systems
// 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.Contains(proxyA) || _gridMoveBuffer.Contains(other)))
{
_physicsSystem.WakeBody(proxyA.Entity, force: true, manager: manager, body: proxyABody);
_physicsSystem.WakeBody(other.Entity, force: true, body: otherBody);
@@ -248,15 +250,15 @@ namespace Robust.Shared.Physics.Systems
movedGrids.Clear();
}
private void HandleGridCollisions(
MapId mapId,
HashSet<EntityUid> movedGrids)
private void HandleGridCollisions(HashSet<EntityUid> movedGrids)
{
foreach (var gridUid in movedGrids)
{
var grid = _gridQuery.GetComponent(gridUid);
var xform = _xformQuery.GetComponent(gridUid);
DebugTools.Assert(xform.MapID == mapId);
if (xform.MapID == MapId.Nullspace)
continue;
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(xform);
@@ -269,7 +271,7 @@ namespace Robust.Shared.Physics.Systems
var transform = _physicsSystem.GetPhysicsTransform(gridUid);
var state = (
new Entity<FixturesComponent, MapGridComponent, PhysicsComponent>(gridUid, fixture, grid, physics),
new Entity<FixturesComponent, MapGridComponent, PhysicsComponent, TransformComponent>(gridUid, fixture, grid, physics, xform),
transform,
worldMatrix,
invWorldMatrix,
@@ -280,9 +282,9 @@ namespace Robust.Shared.Physics.Systems
_physicsQuery,
_xformQuery);
_mapManager.FindGridsIntersecting(mapId, aabb, ref state,
_mapManager.FindGridsIntersecting(xform.MapID, aabb, ref state,
static (EntityUid uid, MapGridComponent component,
ref (Entity<FixturesComponent, MapGridComponent, PhysicsComponent> grid,
ref (Entity<FixturesComponent, MapGridComponent, PhysicsComponent, TransformComponent> grid,
Transform transform,
Matrix3x2 worldMatrix,
Matrix3x2 invWorldMatrix,
@@ -299,7 +301,7 @@ namespace Robust.Shared.Physics.Systems
return true;
}
var (_, _, otherGridMatrix, otherGridInvMatrix) = tuple.xformSystem.GetWorldPositionRotationMatrixWithInv(collidingXform, tuple.xformQuery);
var (_, _, otherGridMatrix, otherGridInvMatrix) = tuple.xformSystem.GetWorldPositionRotationMatrixWithInv(collidingXform);
var otherGridBounds = otherGridMatrix.TransformBox(component.LocalAABB);
var otherTransform = tuple._physicsSystem.GetPhysicsTransform(uid);
@@ -340,7 +342,10 @@ namespace Robust.Shared.Physics.Systems
var otherAABB = otherFixture.Shape.ComputeAABB(otherTransform, j);
if (!fixAABB.Intersects(otherAABB)) continue;
tuple._physicsSystem.AddPair(tuple.grid.Owner, uid,
tuple._physicsSystem.AddPair(
(tuple.grid.Owner, tuple.grid.Comp3, tuple.grid.Comp4),
(uid, physicsB, collidingXform),
ourId, otherId,
fixture, i,
otherFixture, j,
@@ -451,29 +456,24 @@ namespace Robust.Shared.Physics.Systems
_physicsSystem.SetAwake(entity!, true);
var matrix = _transform.GetWorldMatrix(broadphase);
foreach (var fixture in entity.Comp2.Fixtures.Values)
{
TouchProxies(entity.Comp3.MapUid.Value, matrix, fixture);
TouchProxies(fixture);
}
}
internal void TouchProxies(EntityUid mapId, Matrix3x2 broadphaseMatrix, Fixture fixture)
internal void TouchProxies(Fixture fixture)
{
foreach (var proxy in fixture.Proxies)
{
AddToMoveBuffer(mapId, proxy, broadphaseMatrix.TransformBox(proxy.AABB));
AddToMoveBuffer(proxy);
}
}
private void AddToMoveBuffer(EntityUid mapId, FixtureProxy proxy, Box2 aabb)
private void AddToMoveBuffer(FixtureProxy proxy)
{
if (!_mapQuery.TryGetComponent(mapId, out var physicsMap))
return;
DebugTools.Assert(proxy.Body.CanCollide);
physicsMap.MoveBuffer[proxy] = aabb;
_physicsSystem.MoveBuffer.Add(proxy);
}
public void Refilter(EntityUid uid, Fixture fixture, TransformComponent? xform = null)
@@ -492,8 +492,7 @@ namespace Robust.Shared.Physics.Systems
if (!_xformQuery.TryGetComponent(xform.Broadphase?.Uid, out var broadphase))
return;
var matrix = _transform.GetWorldMatrix(broadphase);
TouchProxies(xform.MapUid.Value, matrix, fixture);
TouchProxies(fixture);
}
internal void GetBroadphases(MapId mapId, Box2 aabb, BroadphaseCallback callback)
@@ -560,30 +559,35 @@ namespace Robust.Shared.Physics.Systems
private record struct BroadphaseContactJob() : IParallelRobustJob
{
public SharedBroadphaseSystem System = default!;
public SharedTransformSystem TransformSys = default!;
public IMapManager _mapManager = default!;
public float BroadphaseExpand;
public EntityUid MapUid;
public EntityQuery<TransformComponent> XformQuery;
public List<List<FixtureProxy>> ContactBuffer = new();
public List<(FixtureProxy Proxy, Box2 WorldAABB)> MoveBuffer = new();
public List<FixtureProxy> MoveBuffer = new();
public int BatchSize => 8;
public void Execute(int index)
{
var (proxy, worldAABB) = MoveBuffer[index];
var proxy = MoveBuffer[index];
var broadphaseUid = XformQuery.GetComponent(proxy.Entity).Broadphase?.Uid;
var worldAABB = System._broadMatrices[broadphaseUid!.Value].TransformBox(proxy.AABB);
var buffer = ContactBuffer[index];
buffer.Clear();
var mapUid = XformQuery.GetComponent(proxy.Entity).MapUid ?? EntityUid.Invalid;
var proxyBody = proxy.Body;
DebugTools.Assert(!proxyBody.Deleted);
var state = (System, proxy, worldAABB, buffer);
// Get every broadphase we may be intersecting.
_mapManager.FindGridsIntersecting(MapUid, worldAABB.Enlarged(BroadphaseExpand), ref state,
_mapManager.FindGridsIntersecting(mapUid, worldAABB.Enlarged(BroadphaseExpand), ref state,
static (EntityUid uid, MapGridComponent _, ref (
SharedBroadphaseSystem system,
FixtureProxy proxy,
@@ -599,7 +603,7 @@ namespace Robust.Shared.Physics.Systems
// Struct ref moment, I have no idea what's fastest.
buffer = state.buffer;
System.FindPairs(proxy, worldAABB, MapUid, buffer);
System.FindPairs(proxy, worldAABB, mapUid, buffer);
}
}
}

View File

@@ -23,10 +23,8 @@
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared.Collections;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
@@ -314,13 +312,6 @@ public partial class SharedPhysicsSystem
body._inertia += data.I;
}
// Update this after re-calculating mass as content may want to use the sum of fixture masses instead.
if (((int) body.BodyType & (int) (BodyType.Kinematic | BodyType.Static)) != 0)
{
body._localCenter = Vector2.Zero;
return;
}
if (body._mass > 0.0f)
{
body._invMass = 1.0f / body._mass;
@@ -350,18 +341,23 @@ public partial class SharedPhysicsSystem
var oldCenter = body._localCenter;
body._localCenter = localCenter;
// Update center of mass velocity.
var comVelocityDiff = Vector2Helpers.Cross(body.AngularVelocity, localCenter - oldCenter);
if (((int) body.BodyType & (int) (BodyType.Kinematic | BodyType.Static)) == 0)
{
// Update center of mass velocity.
var comVelocityDiff = Vector2Helpers.Cross(body.AngularVelocity, localCenter - oldCenter);
if (comVelocityDiff != Vector2.Zero)
{
body.LinearVelocity += comVelocityDiff;
DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity));
}
if (comVelocityDiff != Vector2.Zero)
{
body.LinearVelocity += comVelocityDiff;
DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity));
}
}
if (body._mass == oldMass && body._inertia == oldInertia && oldCenter == localCenter)
return;
// we always do the full mass and COM calculation and raise this, even for static bodies as content may need the info
// examples are stations anchored with the station anchor, shuttles landed on planets, or grids getting an atmosphere above a certain mass threshold
var ev = new MassDataChangedEvent((uid, body, manager), oldMass, oldInertia, oldCenter);
RaiseLocalEvent(uid, ref ev);
}
@@ -488,14 +484,17 @@ public partial class SharedPhysicsSystem
body.Awake = true;
}
UpdateMapAwakeState(uid, body);
if (body.Awake)
AddAwakeBody((uid, body, Transform(uid)));
else
RemoveSleepBody((uid, body, Transform(uid)));
}
public void TrySetBodyType(EntityUid uid, BodyType value, FixturesComponent? manager = null, PhysicsComponent? body = null, TransformComponent? xform = null)
{
if (_fixturesQuery.Resolve(uid, ref manager, false) &&
PhysicsQuery.Resolve(uid, ref body, false) &&
_xformQuery.Resolve(uid, ref xform, false))
XformQuery.Resolve(uid, ref xform, false))
{
SetBodyType(uid, value, manager, body, xform);
}
@@ -511,7 +510,6 @@ public partial class SharedPhysicsSystem
var oldType = body.BodyType;
body.BodyType = value;
ResetMassData(uid, manager, body);
body.Force = Vector2.Zero;
body.Torque = 0f;
@@ -705,7 +703,7 @@ public partial class SharedPhysicsSystem
public Transform GetRelativePhysicsTransform(Transform worldTransform, Entity<TransformComponent?> relative)
{
if (!_xformQuery.Resolve(relative.Owner, ref relative.Comp))
if (!XformQuery.Resolve(relative.Owner, ref relative.Comp))
return Physics.Transform.Empty;
var (_, broadphaseRot, _, broadphaseInv) = _transform.GetWorldPositionRotationMatrixWithInv(relative.Comp);
@@ -721,8 +719,8 @@ public partial class SharedPhysicsSystem
Entity<TransformComponent?> entity,
Entity<TransformComponent?> relative)
{
if (!_xformQuery.Resolve(entity.Owner, ref entity.Comp) ||
!_xformQuery.Resolve(relative.Owner, ref relative.Comp))
if (!XformQuery.Resolve(entity.Owner, ref entity.Comp) ||
!XformQuery.Resolve(relative.Owner, ref relative.Comp))
{
return Physics.Transform.Empty;
}
@@ -738,7 +736,7 @@ public partial class SharedPhysicsSystem
/// </summary>
public Transform GetLocalPhysicsTransform(EntityUid uid, TransformComponent? xform = null)
{
if (!_xformQuery.Resolve(uid, ref xform) || xform.Broadphase == null)
if (!XformQuery.Resolve(uid, ref xform) || xform.Broadphase == null)
return Physics.Transform.Empty;
var broadphase = xform.Broadphase.Value.Uid;
@@ -753,7 +751,7 @@ public partial class SharedPhysicsSystem
public Transform GetPhysicsTransform(EntityUid uid, TransformComponent? xform = null)
{
if (!_xformQuery.Resolve(uid, ref xform))
if (!XformQuery.Resolve(uid, ref xform))
return Physics.Transform.Empty;
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);

View File

@@ -123,24 +123,26 @@ public abstract partial class SharedPhysicsSystem
DebugTools.Assert(obj.Flags is ContactFlags.None or ContactFlags.Deleted);
SetContact(obj,
false,
EntityUid.Invalid, EntityUid.Invalid,
new Entity<PhysicsComponent?, TransformComponent?>(EntityUid.Invalid, null, null),
new Entity<PhysicsComponent?, TransformComponent?>(EntityUid.Invalid, null, null),
string.Empty, string.Empty,
null, 0,
null, 0,
null, null);
null, 0);
return true;
}
}
private static void SetContact(Contact contact,
bool enabled,
EntityUid uidA, EntityUid uidB,
Entity<PhysicsComponent?, TransformComponent?> entA,
Entity<PhysicsComponent?, TransformComponent?> entB,
string fixtureAId, string fixtureBId,
Fixture? fixtureA, int indexA,
Fixture? fixtureB, int indexB,
PhysicsComponent? bodyA,
PhysicsComponent? bodyB)
Fixture? fixtureB, int indexB)
{
var uidA = entA.Owner;
var uidB = entB.Owner;
contact.Enabled = enabled;
contact.IsTouching = false;
DebugTools.Assert(contact.Flags is ContactFlags.None or ContactFlags.PreInit or ContactFlags.Deleted);
@@ -155,8 +157,11 @@ public abstract partial class SharedPhysicsSystem
contact.FixtureA = fixtureA;
contact.FixtureB = fixtureB;
contact.BodyA = bodyA;
contact.BodyB = bodyB;
contact.BodyA = entA.Comp1;
contact.BodyB = entB.Comp1;
contact.XformA = entA.Comp2;
contact.XformB = entB.Comp2;
contact.ChildIndexA = indexA;
contact.ChildIndexB = indexB;
@@ -213,11 +218,10 @@ public abstract partial class SharedPhysicsSystem
}
private Contact CreateContact(
EntityUid uidA, EntityUid uidB,
Entity<PhysicsComponent?, TransformComponent?> entA, Entity<PhysicsComponent?, TransformComponent?> entB,
string fixtureAId, string fixtureBId,
Fixture fixtureA, int indexA,
Fixture fixtureB, int indexB,
PhysicsComponent bodyA, PhysicsComponent bodyB)
Fixture fixtureB, int indexB)
{
var type1 = fixtureA.Shape.ShapeType;
var type2 = fixtureB.Shape.ShapeType;
@@ -233,11 +237,11 @@ public abstract partial class SharedPhysicsSystem
// Edge+Polygon is non-symmetrical due to the way Erin handles collision type registration.
if ((type1 >= type2 || (type1 == ShapeType.Edge && type2 == ShapeType.Polygon)) && !(type2 == ShapeType.Edge && type1 == ShapeType.Polygon))
{
SetContact(contact, true, uidA, uidB, fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB, bodyA, bodyB);
SetContact(contact, true, entA, entB, fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB);
}
else
{
SetContact(contact, true, uidB, uidA, fixtureBId, fixtureAId, fixtureB, indexB, fixtureA, indexA, bodyB, bodyA);
SetContact(contact, true, entB, entA, fixtureBId, fixtureAId, fixtureB, indexB, fixtureA, indexA);
}
contact.Type = _registers[(int)type1, (int)type2];
@@ -249,7 +253,7 @@ public abstract partial class SharedPhysicsSystem
/// Try to create a contact between these 2 fixtures.
/// </summary>
internal void AddPair(
EntityUid uidA, EntityUid uidB,
Entity<PhysicsComponent, TransformComponent> entA, Entity<PhysicsComponent, TransformComponent> entB,
string fixtureAId, string fixtureBId,
Fixture fixtureA, int indexA,
Fixture fixtureB, int indexB,
@@ -264,16 +268,15 @@ public abstract partial class SharedPhysicsSystem
return;
DebugTools.Assert(!fixtureB.Contacts.ContainsKey(fixtureA));
var xformA = _xformQuery.GetComponent(uidA);
var xformB = _xformQuery.GetComponent(uidB);
var xformA = entA.Comp2;
var xformB = entB.Comp2;
// Does a joint override collision? Is at least one body dynamic?
if (!ShouldCollide(uidA, uidB, bodyA, bodyB, fixtureA, fixtureB, xformA, xformB))
if (!ShouldCollide(entA.Owner, entB.Owner, bodyA, bodyB, fixtureA, fixtureB, xformA, xformB))
return;
// Call the factory.
var contact = CreateContact(uidA, uidB, fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB, bodyA, bodyB);
var contact = CreateContact((entA.Owner, entA.Comp1, entA.Comp2), (entB.Owner, entB.Comp1, entB.Comp2), fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB);
contact.Flags = flags;
// Contact creation may swap fixtures.
@@ -300,7 +303,7 @@ public abstract partial class SharedPhysicsSystem
// Checking only bodyA should be okay because if bodyA is the other ent (i.e. dynamic / kinematic) then it should already be awake.
if (bodyA.BodyType == BodyType.Static && !bodyB.Awake)
{
WakeBody(uidB, body: bodyB);
WakeBody(entB.Owner, body: bodyB);
}
}
@@ -309,7 +312,8 @@ public abstract partial class SharedPhysicsSystem
/// </summary>
internal void AddPair(string fixtureAId, string fixtureBId, in FixtureProxy proxyA, in FixtureProxy proxyB)
{
AddPair(proxyA.Entity, proxyB.Entity,
AddPair((proxyA.Entity, proxyA.Body, proxyA.Xform),
(proxyB.Entity, proxyB.Body, proxyB.Xform),
fixtureAId, fixtureBId,
proxyA.Fixture, proxyA.ChildIndex,
proxyB.Fixture, proxyB.ChildIndex,
@@ -429,8 +433,8 @@ public abstract partial class SharedPhysicsSystem
continue;
}
var xformA = _xformQuery.GetComponent(uidA);
var xformB = _xformQuery.GetComponent(uidB);
var xformA = contact.XformA!;
var xformB = contact.XformB!;
if (xformA.MapID == MapId.Nullspace || xformB.MapID == MapId.Nullspace)
{
@@ -526,8 +530,8 @@ public abstract partial class SharedPhysicsSystem
// Instead of transforming both boxes (which enlarges both aabbs), maybe just transform one box.
// I.e. use (matrixA * invMatrixB).TransformBox(). Or (invMatrixB * matrixA), whichever is correct.
// Alternatively, maybe just directly construct the relative transform matrix?
var proxyAWorldAABB = _transform.GetWorldMatrix(_xformQuery.GetComponent(broadphaseA.Value), _xformQuery).TransformBox(proxyA.AABB);
var proxyBWorldAABB = _transform.GetWorldMatrix(_xformQuery.GetComponent(broadphaseB.Value), _xformQuery).TransformBox(proxyB.AABB);
var proxyAWorldAABB = _transform.GetWorldMatrix(XformQuery.GetComponent(broadphaseA.Value)).TransformBox(proxyA.AABB);
var proxyBWorldAABB = _transform.GetWorldMatrix(XformQuery.GetComponent(broadphaseB.Value)).TransformBox(proxyB.AABB);
overlap = proxyAWorldAABB.Intersects(proxyBWorldAABB);
}
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Numerics;
using System.Threading.Tasks;
using Microsoft.Extensions.ObjectPool;
using Robust.Shared.Collections;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
@@ -125,8 +126,8 @@ public abstract partial class SharedPhysicsSystem
*/
private const int MaxIslands = 256;
private readonly ObjectPool<List<PhysicsComponent>> _islandBodyPool =
new DefaultObjectPool<List<PhysicsComponent>>(new ListPolicy<PhysicsComponent>(), MaxIslands);
private readonly ObjectPool<List<Entity<PhysicsComponent, TransformComponent>>> _islandBodyPool =
new DefaultObjectPool<List<Entity<PhysicsComponent, TransformComponent>>>(new ListPolicy<Entity<PhysicsComponent, TransformComponent>>(), MaxIslands);
private readonly ObjectPool<List<Contact>> _islandContactPool =
new DefaultObjectPool<List<Contact>>(new ListPolicy<Contact>(), MaxIslands);
@@ -140,11 +141,16 @@ public abstract partial class SharedPhysicsSystem
internal record struct IslandData(
int Index,
bool LoneIsland,
List<PhysicsComponent> Bodies,
List<Entity<PhysicsComponent, TransformComponent>> Bodies,
List<Contact> Contacts,
List<(Joint Original, Joint Joint)> Joints,
List<(Joint Joint, float Error)> BrokenJoints)
{
/// <summary>
/// MapUid for the island.
/// </summary>
public EntityUid MapUid;
/// <summary>
/// Island index to be used for bodies to identify which island they're in.
/// </summary>
@@ -161,7 +167,7 @@ public abstract partial class SharedPhysicsSystem
/// </summary>
public int Offset = 0;
public readonly List<PhysicsComponent> Bodies = Bodies;
public readonly List<Entity<PhysicsComponent, TransformComponent>> Bodies = Bodies;
public readonly List<Contact> Contacts = Contacts;
public readonly List<(Joint Original, Joint Joint)> Joints = Joints;
public bool PositionSolved = false;
@@ -169,9 +175,9 @@ public abstract partial class SharedPhysicsSystem
}
// Caching for island generation.
private readonly HashSet<PhysicsComponent> _islandSet = new(64);
private readonly Stack<PhysicsComponent> _bodyStack = new(64);
private readonly List<PhysicsComponent> _awakeBodyList = new(256);
private readonly HashSet<Entity<PhysicsComponent, TransformComponent>> _islandSet = new(64);
private readonly Stack<Entity<PhysicsComponent, TransformComponent>> _bodyStack = new(64);
private readonly List<Entity<PhysicsComponent, TransformComponent>> _awakeBodyList = new(256);
// Config
private bool _warmStarting;
@@ -260,47 +266,50 @@ public abstract partial class SharedPhysicsSystem
/// <summary>
/// Where the magic happens.
/// </summary>
public void Step(EntityUid uid, PhysicsMapComponent component, float frameTime, bool prediction)
public void Step(float frameTime, bool prediction)
{
var invDt = frameTime > 0.0f ? 1.0f / frameTime : 0.0f;
var dtRatio = component._invDt0 * frameTime;
var dtRatio = _invDt0 * frameTime;
// Integrate velocities, solve velocity constraints, and do integration.
Solve(uid, component, frameTime, dtRatio, invDt, prediction);
Solve(frameTime, dtRatio, invDt, prediction);
// TODO: SolveTOI
var updateAfterSolve = new PhysicsUpdateAfterMapSolveEvent(prediction, component, frameTime);
RaiseLocalEvent(ref updateAfterSolve);
// Box2d recommends clearing (if you are) during fixed updates rather than variable if you are using it
if (!prediction && component.AutoClearForces)
ClearForces(component);
if (_autoClearForces)
ClearForces();
component._invDt0 = invDt;
_invDt0 = invDt;
}
private void ClearForces(PhysicsMapComponent component)
private void ClearForces()
{
foreach (var body in component.AwakeBodies)
foreach (var ent in AwakeBodies)
{
// TODO: Netsync
body.Force = Vector2.Zero;
body.Torque = 0.0f;
var uid = ent.Owner;
var body = ent.Comp1;
if (body.Force != Vector2.Zero)
{
body.Force = Vector2.Zero;
DirtyField(uid, body, nameof(PhysicsComponent.Force));
}
if (body.Torque != 0f)
{
body.Torque = 0f;
DirtyField(uid, body, nameof(PhysicsComponent.Torque));
}
}
}
private void Solve(EntityUid uid, PhysicsMapComponent component, float frameTime, float dtRatio, float invDt, bool prediction)
private void Solve(float frameTime, float dtRatio, float invDt, bool prediction)
{
// Build and simulated islands from awake bodies.
_bodyStack.EnsureCapacity(component.AwakeBodies.Count);
_islandSet.EnsureCapacity(component.AwakeBodies.Count);
_awakeBodyList.AddRange(component.AwakeBodies);
var bodyQuery = GetEntityQuery<PhysicsComponent>();
var metaQuery = GetEntityQuery<MetaDataComponent>();
var jointQuery = GetEntityQuery<JointComponent>();
var jointRelayQuery = GetEntityQuery<JointRelayTargetComponent>();
_bodyStack.EnsureCapacity(AwakeBodies.Count);
_islandSet.EnsureCapacity(AwakeBodies.Count);
_awakeBodyList.AddRange(AwakeBodies);
var islandIndex = 0;
var loneIsland = new IslandData(
@@ -315,18 +324,28 @@ public abstract partial class SharedPhysicsSystem
var islandJoints = new List<(Joint Original, Joint Joint)>();
// Build the relevant islands / graphs for all bodies.
foreach (var seed in _awakeBodyList)
foreach (var ent in _awakeBodyList)
{
var xform = ent.Comp2;
var seed = ent.Comp1;
// I tried not running prediction for non-contacted entities but unfortunately it looked like shit
// when contact broke so if you want to try that then GOOD LUCK.
if (seed.Island) continue;
var seedUid = seed.Owner;
var mapUid = xform.MapUid;
if (!metaQuery.TryGetComponent(seedUid, out var metadata))
// TODO: Handle this on client.
if (mapUid == null)
{
continue;
}
if (!EntityManager.MetaQuery.TryGetComponent(seedUid, out var metadata))
{
Log.Error($"Found deleted entity {ToPrettyString(seedUid)} on map!");
RemoveSleepBody(seedUid, seed, component);
RemoveSleepBody(ent);
continue;
}
@@ -342,16 +361,18 @@ public abstract partial class SharedPhysicsSystem
var bodies = _islandBodyPool.Get();
var contacts = _islandContactPool.Get();
var joints = _islandJointPool.Get();
_bodyStack.Push(seed);
_bodyStack.Push(ent);
seed.Island = true;
while (_bodyStack.TryPop(out var body))
while (_bodyStack.TryPop(out var bodyEnt))
{
var bodyUid = body.Owner;
var bodyUid = bodyEnt.Owner;
var body = bodyEnt.Comp1;
bodies.Add(body);
_islandSet.Add(body);
bodies.Add(bodyEnt);
_islandSet.Add(bodyEnt);
// Static bodies don't propagate islands
if (body.BodyType == BodyType.Static) continue;
@@ -377,24 +398,24 @@ public abstract partial class SharedPhysicsSystem
contacts.Add(contact);
contact.Flags |= ContactFlags.Island;
var bodyA = contact.BodyA!;
var bodyB = contact.BodyB!;
var other = bodyA == body ? bodyB : bodyA;
var other = contact.OtherBody(bodyUid);
// Was the other body already added to this island?
if (other.Island) continue;
_bodyStack.Push(other);
var otherEnt = contact.OtherEnt(bodyUid);
var otherXform = contact.OtherTransform(bodyUid);
// TODO: Store this transform on the component directly.
_bodyStack.Push(new Entity<PhysicsComponent, TransformComponent>(otherEnt, other, otherXform));
other.Island = true;
}
// Handle joints
if (jointRelayQuery.TryGetComponent(bodyUid, out var relayComp))
if (RelayTargetQuery.TryGetComponent(bodyUid, out var relayComp))
{
foreach (var relay in relayComp.Relayed)
{
if (!jointQuery.TryGetComponent(relay, out var jointComp))
if (!JointQuery.TryGetComponent(relay, out var jointComp))
continue;
foreach (var joint in jointComp.GetJoints.Values)
@@ -406,14 +427,14 @@ public abstract partial class SharedPhysicsSystem
var uidB = joint.BodyBUid;
DebugTools.AssertNotEqual(uidA, uidB);
if (jointQuery.TryGetComponent(uidA, out var jointCompA) &&
if (JointQuery.TryGetComponent(uidA, out var jointCompA) &&
jointCompA.Relay != null)
{
DebugTools.AssertNotEqual(uidB, jointCompA.Relay.Value);
uidA = jointCompA.Relay.Value;
}
if (jointQuery.TryGetComponent(uidB, out var jointCompB) &&
if (JointQuery.TryGetComponent(uidB, out var jointCompB) &&
jointCompB.Relay != null)
{
DebugTools.AssertNotEqual(uidA, jointCompB.Relay.Value);
@@ -427,7 +448,7 @@ public abstract partial class SharedPhysicsSystem
}
}
if (jointQuery.TryGetComponent(bodyUid, out var jointComponent) &&
if (JointQuery.TryGetComponent(bodyUid, out var jointComponent) &&
jointComponent.Relay == null)
{
foreach (var joint in jointComponent.Joints.Values)
@@ -438,13 +459,13 @@ public abstract partial class SharedPhysicsSystem
var uidA = joint.BodyAUid;
var uidB = joint.BodyBUid;
if (jointQuery.TryGetComponent(uidA, out var jointCompA) &&
if (JointQuery.TryGetComponent(uidA, out var jointCompA) &&
jointCompA.Relay != null)
{
uidA = jointCompA.Relay.Value;
}
if (jointQuery.TryGetComponent(uidB, out var jointCompB) &&
if (JointQuery.TryGetComponent(uidB, out var jointCompB) &&
jointCompB.Relay != null)
{
uidB = jointCompB.Relay.Value;
@@ -458,8 +479,9 @@ public abstract partial class SharedPhysicsSystem
foreach (var (original, joint) in islandJoints)
{
var bodyA = bodyQuery.GetComponent(joint.BodyAUid);
var bodyB = bodyQuery.GetComponent(joint.BodyBUid);
// TODO: Same here store physicscomp + transform on the joint, the savings are worth it.
var bodyA = PhysicsQuery.GetComponent(joint.BodyAUid);
var bodyB = PhysicsQuery.GetComponent(joint.BodyBUid);
if (!bodyA.CanCollide || !bodyB.CanCollide)
continue;
@@ -468,13 +490,13 @@ public abstract partial class SharedPhysicsSystem
if (!bodyA.Island)
{
_bodyStack.Push(bodyA);
_bodyStack.Push(new Entity<PhysicsComponent, TransformComponent>(joint.BodyAUid, bodyA, XformQuery.GetComponent(joint.BodyAUid)));
bodyA.Island = true;
}
if (!bodyB.Island)
{
_bodyStack.Push(bodyB);
_bodyStack.Push(new Entity<PhysicsComponent, TransformComponent>(joint.BodyBUid, bodyB, XformQuery.GetComponent(joint.BodyBUid)));
bodyB.Island = true;
}
}
@@ -487,13 +509,17 @@ public abstract partial class SharedPhysicsSystem
// Bodies not touching anything, hence we can just add it to the lone island.
if (contacts.Count == 0 && joints.Count == 0)
{
DebugTools.Assert(bodies.Count == 1 && bodies[0].BodyType != BodyType.Static);
DebugTools.Assert(bodies.Count == 1 && bodies[0].Comp1.BodyType != BodyType.Static);
loneIsland.MapUid = mapUid.Value;
loneIsland.Bodies.Add(bodies[0]);
idx = loneIsland.Index;
}
else
{
var data = new IslandData(islandIndex++, false, bodies, contacts, joints, new List<(Joint Joint, float Error)>());
var data = new IslandData(islandIndex++, false, bodies, contacts, joints, new List<(Joint Joint, float Error)>())
{
MapUid = mapUid.Value
};
islands.Add(data);
idx = data.Index;
}
@@ -501,7 +527,7 @@ public abstract partial class SharedPhysicsSystem
// Allow static bodies to be re-used in other islands
for (var i = 0; i < bodies.Count; i++)
{
var body = bodies[i];
var body = bodies[i].Comp1;
// Static bodies can participate in other islands
if (body.BodyType == BodyType.Static)
@@ -523,20 +549,21 @@ public abstract partial class SharedPhysicsSystem
ReturnIsland(loneIsland);
}
SolveIslands(uid, component, islands, frameTime, dtRatio, invDt, prediction);
SolveIslands(islands, frameTime, dtRatio, invDt, prediction);
foreach (var island in islands)
{
ReturnIsland(island);
}
Cleanup(component, frameTime);
Cleanup(frameTime);
}
private void ReturnIsland(IslandData island)
{
foreach (var body in island.Bodies)
foreach (var bodyEnt in island.Bodies)
{
var body = bodyEnt.Comp1;
DebugTools.Assert(body.IslandIndex.ContainsKey(island.Index));
body.IslandIndex.Remove(island.Index);
}
@@ -559,10 +586,12 @@ public abstract partial class SharedPhysicsSystem
island.BrokenJoints.Clear();
}
protected virtual void Cleanup(PhysicsMapComponent component, float frameTime)
protected virtual void Cleanup(float frameTime)
{
foreach (var body in _islandSet)
foreach (var bodyEnt in _islandSet)
{
var body = bodyEnt.Comp1;
if (!body.Island || body.Deleted)
{
continue;
@@ -574,15 +603,14 @@ public abstract partial class SharedPhysicsSystem
// So Box2D would update broadphase here buutttt we'll just wait until MoveEvent queue is used.
}
_islandSet.Clear();
_islandSet.Clear();
_awakeBodyList.Clear();
}
private void SolveIslands(EntityUid uid, PhysicsMapComponent component, List<IslandData> islands, float frameTime, float dtRatio, float invDt, bool prediction)
private void SolveIslands(List<IslandData> islands, float frameTime, float dtRatio, float invDt, bool prediction)
{
var iBegin = 0;
var gravity = _gravity.GetGravity(uid);
var data = new SolverData(
frameTime,
dtRatio,
@@ -609,13 +637,12 @@ public abstract partial class SharedPhysicsSystem
var totalBodies = 0;
var actualIslands = islands.ToArray();
var xformQuery = GetEntityQuery<TransformComponent>();
for (var i = 0; i < islands.Count; i++)
{
ref var island = ref actualIslands[i];
island.Offset = totalBodies;
UpdateLerpData(component, island.Bodies, xformQuery);
UpdateLerpData(island.Bodies);
#if DEBUG
RaiseLocalEvent(new IslandSolveMessage(island.Bodies));
@@ -648,14 +675,14 @@ public abstract partial class SharedPhysicsSystem
if (!InternalParallel(island))
break;
SolveIsland(ref island, in data, options, gravity, prediction, solvedPositions, solvedAngles, linearVelocities, angularVelocities, sleepStatus);
SolveIsland(ref island, in data, options, prediction, solvedPositions, solvedAngles, linearVelocities, angularVelocities, sleepStatus);
iBegin++;
}
Parallel.For(iBegin, actualIslands.Length, options, i =>
{
ref var island = ref actualIslands[i];
SolveIsland(ref island, in data, null, gravity, prediction, solvedPositions, solvedAngles, linearVelocities, angularVelocities, sleepStatus);
SolveIsland(ref island, in data, null, prediction, solvedPositions, solvedAngles, linearVelocities, angularVelocities, sleepStatus);
});
// Update data sequentially
@@ -663,7 +690,7 @@ public abstract partial class SharedPhysicsSystem
{
var island = actualIslands[i];
UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities, xformQuery);
UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities);
SleepBodies(in island, sleepStatus);
}
@@ -679,7 +706,7 @@ public abstract partial class SharedPhysicsSystem
/// If this is the first time a body has been updated this tick update its position for lerping.
/// Due to substepping we have to check it every time.
/// </summary>
protected virtual void UpdateLerpData(PhysicsMapComponent component, List<PhysicsComponent> bodies, EntityQuery<TransformComponent> xformQuery)
protected virtual void UpdateLerpData(List<Entity<PhysicsComponent, TransformComponent>> bodies)
{
}
@@ -702,7 +729,6 @@ public abstract partial class SharedPhysicsSystem
ref IslandData island,
in SolverData data,
ParallelOptions? options,
Vector2 gravity,
bool prediction,
Vector2[] solvedPositions,
float[] solvedAngles,
@@ -714,13 +740,16 @@ public abstract partial class SharedPhysicsSystem
var positions = ArrayPool<Vector2>.Shared.Rent(bodyCount);
var angles = ArrayPool<float>.Shared.Rent(bodyCount);
var offset = island.Offset;
var xformQuery = GetEntityQuery<TransformComponent>();
var gravity = Gravity;
for (var i = 0; i < island.Bodies.Count; i++)
{
var body = island.Bodies[i];
var bodyEnt = island.Bodies[i];
var body = bodyEnt.Comp1;
var xform = bodyEnt.Comp2;
var (worldPos, worldRot) =
_transform.GetWorldPositionRotation(xformQuery.GetComponent(body.Owner), xformQuery);
_transform.GetWorldPositionRotation(xform);
var transform = new Transform(worldPos, worldRot);
var position = Physics.Transform.Mul(transform, body.LocalCenter);
@@ -737,9 +766,13 @@ public abstract partial class SharedPhysicsSystem
if (body.BodyType == BodyType.Dynamic)
{
if (body.IgnoreGravity)
{
linearVelocity += body.Force * data.FrameTime * body.InvMass;
}
else
{
linearVelocity += (gravity + body.Force * body.InvMass) * data.FrameTime;
}
angularVelocity += body.InvI * body.Torque * data.FrameTime;
@@ -768,7 +801,6 @@ public abstract partial class SharedPhysicsSystem
}
var jointCount = island.Joints.Count;
var bodyQuery = GetEntityQuery<PhysicsComponent>();
if (jointCount > 0)
{
@@ -777,8 +809,8 @@ public abstract partial class SharedPhysicsSystem
var joint = island.Joints[i].Joint;
if (!joint.Enabled) continue;
var bodyA = bodyQuery.GetComponent(joint.BodyAUid);
var bodyB = bodyQuery.GetComponent(joint.BodyBUid);
var bodyA = PhysicsQuery.GetComponent(joint.BodyAUid);
var bodyB = PhysicsQuery.GetComponent(joint.BodyBUid);
joint.InitVelocityConstraints(in data, in island, bodyA, bodyB, positions, angles, linearVelocities, angularVelocities);
}
}
@@ -880,12 +912,12 @@ public abstract partial class SharedPhysicsSystem
var start = i * FinaliseBodies;
var end = Math.Min(bodyCount, start + FinaliseBodies);
FinalisePositions(start, end, offset, bodies, xformQuery, positions, angles, solvedPositions, solvedAngles);
FinalisePositions(start, end, offset, bodies, positions, angles, solvedPositions, solvedAngles);
});
}
else
{
FinalisePositions(0, bodyCount, offset, bodies,xformQuery, positions, angles, solvedPositions, solvedAngles);
FinalisePositions(0, bodyCount, offset, bodies, positions, angles, solvedPositions, solvedAngles);
}
// Check sleep status for all of the bodies
@@ -899,7 +931,7 @@ public abstract partial class SharedPhysicsSystem
{
for (var i = 0; i < bodyCount; i++)
{
var body = island.Bodies[i];
var body = island.Bodies[i].Comp1;
if (body.BodyType == BodyType.Static) continue;
@@ -930,7 +962,7 @@ public abstract partial class SharedPhysicsSystem
for (var i = 0; i < bodyCount; i++)
{
var body = island.Bodies[i];
var body = island.Bodies[i].Comp1;
if (body.BodyType == BodyType.Static) continue;
@@ -965,18 +997,18 @@ public abstract partial class SharedPhysicsSystem
ArrayPool<ContactPositionConstraint>.Shared.Return(positionConstraints);
}
private void FinalisePositions(int start, int end, int offset, List<PhysicsComponent> bodies, EntityQuery<TransformComponent> xformQuery, Vector2[] positions, float[] angles, Vector2[] solvedPositions, float[] solvedAngles)
private void FinalisePositions(int start, int end, int offset, List<Entity<PhysicsComponent, TransformComponent>> bodies, Vector2[] positions, float[] angles, Vector2[] solvedPositions, float[] solvedAngles)
{
for (var i = start; i < end; i++)
{
var body = bodies[i];
var ent = bodies[i];
var body = ent.Comp1;
if (body.BodyType == BodyType.Static)
continue;
var xform = xformQuery.GetComponent(body.Owner);
var parentXform = xformQuery.GetComponent(xform.ParentUid);
var (_, parentRot, parentInvMatrix) = parentXform.GetWorldPositionRotationInvMatrix(xformQuery);
var xform = ent.Comp2;
var (_, parentRot, parentInvMatrix) = _transform.GetWorldPositionRotationInvMatrix(xform.ParentUid);
var worldRot = (float) (parentRot + xform._localRotation);
var angle = angles[i];
@@ -999,8 +1031,7 @@ public abstract partial class SharedPhysicsSystem
Vector2[] positions,
float[] angles,
Vector2[] linearVelocities,
float[] angularVelocities,
EntityQuery<TransformComponent> xformQuery)
float[] angularVelocities)
{
foreach (var (joint, error) in island.BrokenJoints)
{
@@ -1015,16 +1046,17 @@ public abstract partial class SharedPhysicsSystem
for (var i = 0; i < island.Bodies.Count; i++)
{
var body = island.Bodies[i];
var bodyEnt = island.Bodies[i];
var body = bodyEnt.Comp1;
// So technically we don't /need/ to skip static bodies here but it saves us having to check for deferred updates so we'll do it anyway.
// Plus calcing worldpos can be costly so we skip that too which is nice.
if (body.BodyType == BodyType.Static) continue;
var uid = body.Owner;
var uid = bodyEnt.Owner;
var position = positions[offset + i];
var angle = angles[offset + i];
var xform = xformQuery.GetComponent(uid);
var xform = bodyEnt.Comp2;
var linVelocity = linearVelocities[offset + i];
var physicsDirtied = false;
@@ -1052,7 +1084,9 @@ public abstract partial class SharedPhysicsSystem
}
if (physicsDirtied)
{
Dirty(uid, body);
}
}
}

View File

@@ -9,45 +9,36 @@ public partial class SharedPhysicsSystem
{
#region AddRemove
internal void AddAwakeBody(EntityUid uid, PhysicsComponent body, PhysicsMapComponent? map = null)
internal void AddAwakeBody(Entity<PhysicsComponent, TransformComponent> ent)
{
if (map == null)
return;
var body = ent.Comp1;
if (!body.CanCollide)
{
Log.Error($"Tried to add non-colliding {ToPrettyString(uid)} as an awake body to map!");
Log.Error($"Tried to add non-colliding {ToPrettyString(ent)} as an awake body to map!");
DebugTools.Assert(false);
return;
}
if (body.BodyType == BodyType.Static)
{
Log.Error($"Tried to add static body {ToPrettyString(uid)} as an awake body to map!");
Log.Error($"Tried to add static body {ToPrettyString(ent)} as an awake body to map!");
DebugTools.Assert(false);
return;
}
DebugTools.Assert(body.Awake);
map.AwakeBodies.Add(body);
DebugTools.Assert(!AwakeBodies.Contains(ent));
AwakeBodies.Add(ent);
}
internal void AddAwakeBody(EntityUid uid, PhysicsComponent body, EntityUid mapUid, PhysicsMapComponent? map = null)
internal void RemoveSleepBody(Entity<PhysicsComponent, TransformComponent> ent)
{
PhysMapQuery.Resolve(mapUid, ref map, false);
AddAwakeBody(uid, body, map);
DebugTools.Assert(!ent.Comp1.Awake);
DebugTools.Assert(AwakeBodies.Contains(ent));
AwakeBodies.Remove(ent);
}
internal void RemoveSleepBody(EntityUid uid, PhysicsComponent body, PhysicsMapComponent? map = null)
{
map?.AwakeBodies.Remove(body);
}
internal void RemoveSleepBody(EntityUid uid, PhysicsComponent body, EntityUid mapUid, PhysicsMapComponent? map = null)
{
PhysMapQuery.Resolve(mapUid, ref map, false);
RemoveSleepBody(uid, body, map);
}
#endregion
}

View File

@@ -27,10 +27,9 @@ public abstract partial class SharedPhysicsSystem
shape.Radius = radius;
if (body.CanCollide &&
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase) &&
TryComp<PhysicsMapComponent>(xform.MapUid, out var physicsMap))
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase))
{
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
_lookup.CreateProxies(uid, fixtureId, fixture, xform, body);
}
@@ -58,10 +57,9 @@ public abstract partial class SharedPhysicsSystem
shape.Radius = radius;
if (body.CanCollide &&
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase) &&
TryComp<PhysicsMapComponent>(xform.MapUid, out var physicsMap))
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase))
{
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
_lookup.CreateProxies(uid, fixtureId, fixture, xform, body);
}
@@ -84,10 +82,9 @@ public abstract partial class SharedPhysicsSystem
circle.Position = position;
if (body.CanCollide &&
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase) &&
TryComp<PhysicsMapComponent>(xform.MapUid, out var physicsMap))
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase))
{
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
_lookup.CreateProxies(uid, fixtureId, fixture, xform, body);
}
@@ -120,10 +117,9 @@ public abstract partial class SharedPhysicsSystem
edge.Vertex3 = vertex3;
if (body.CanCollide &&
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase) &&
TryComp<PhysicsMapComponent>(xform.MapUid, out var physicsMap))
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase))
{
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
_lookup.CreateProxies(uid, fixtureId, fixture, xform, body);
}
@@ -150,10 +146,9 @@ public abstract partial class SharedPhysicsSystem
poly.Set(vertices, vertices.Length);
if (body.CanCollide &&
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase) &&
TryComp<PhysicsMapComponent>(xform.MapUid, out var physicsMap))
TryComp<BroadphaseComponent>(xform.Broadphase?.Uid, out var broadphase))
{
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase);
_lookup.CreateProxies(uid, fixtureId, fixture, xform, body);
}

View File

@@ -27,7 +27,7 @@ public abstract partial class SharedPhysicsSystem
if (!PhysicsQuery.Resolve(uid, ref component))
return Vector2.Zero;
if (!_xformQuery.Resolve(uid, ref xform))
if (!XformQuery.Resolve(uid, ref xform))
return Vector2.Zero;
var velocity = component.LinearVelocity;
@@ -56,7 +56,7 @@ public abstract partial class SharedPhysicsSystem
{
// Could make this a method with the below one but ehh
// then you get a method bigger than this block with a billion out args and who wants that.
var xform = _xformQuery.GetComponent(parent);
var xform = XformQuery.GetComponent(parent);
if (PhysicsQuery.TryGetComponent(parent, out var body))
{
@@ -86,9 +86,11 @@ public abstract partial class SharedPhysicsSystem
PhysicsComponent? component = null,
TransformComponent? xform = null)
{
if (!_xformQuery.Resolve(uid, ref xform))
if (!XformQuery.Resolve(uid, ref xform))
return Vector2.Zero;
PhysicsQuery.Resolve(uid, ref component, false);
var parent = xform.ParentUid;
var localPos = xform.LocalPosition;
@@ -97,7 +99,7 @@ public abstract partial class SharedPhysicsSystem
while (parent != xform.MapUid && parent.IsValid())
{
xform = _xformQuery.GetComponent(parent);
xform = XformQuery.GetComponent(parent);
if (PhysicsQuery.TryGetComponent(parent, out var body))
{
@@ -131,20 +133,19 @@ public abstract partial class SharedPhysicsSystem
PhysicsComponent? component = null,
TransformComponent? xform = null)
{
if (!PhysicsQuery.Resolve(uid, ref component))
return 0;
if (!_xformQuery.Resolve(uid, ref xform))
if (!XformQuery.Resolve(uid, ref xform))
return 0f;
var angularVelocity = component.AngularVelocity;
PhysicsQuery.Resolve(uid, ref component, false);
var angularVelocity = component?.AngularVelocity ?? 0;
while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid())
{
if (PhysicsQuery.TryGetComponent(xform.ParentUid, out var body))
angularVelocity += body.AngularVelocity;
xform = _xformQuery.GetComponent(xform.ParentUid);
xform = XformQuery.GetComponent(xform.ParentUid);
}
return angularVelocity;
@@ -160,23 +161,22 @@ public abstract partial class SharedPhysicsSystem
PhysicsComponent? component = null,
TransformComponent? xform = null)
{
if (!PhysicsQuery.Resolve(uid, ref component))
if (!XformQuery.Resolve(uid, ref xform))
return (Vector2.Zero, 0);
if (!_xformQuery.Resolve(uid, ref xform))
return (Vector2.Zero, 0);
PhysicsQuery.Resolve(uid, ref component, false);
var parent = xform.ParentUid;
var localPos = xform.LocalPosition;
var linearVelocity = component.LinearVelocity;
var angularVelocity = component.AngularVelocity;
var linearVelocity = component?.LinearVelocity ?? Vector2.Zero;
var angularVelocity = component?.AngularVelocity ?? 0;
Vector2 linearVelocityAngularContribution = Vector2.Zero;
while (parent != xform.MapUid && parent.IsValid())
{
xform = _xformQuery.GetComponent(parent);
xform = XformQuery.GetComponent(parent);
if (PhysicsQuery.TryGetComponent(parent, out var body))
{
@@ -233,7 +233,7 @@ public abstract partial class SharedPhysicsSystem
}
var parent = oldParent;
TransformComponent? parentXform = _xformQuery.GetComponent(parent);
TransformComponent? parentXform = XformQuery.GetComponent(parent);
var localPos = Vector2.Transform(_transform.GetWorldPosition(xform), _transform.GetInvWorldMatrix(parentXform));
var oldLinear = physics.LinearVelocity;
@@ -258,7 +258,7 @@ public abstract partial class SharedPhysicsSystem
localPos = parentXform.LocalPosition + parentXform.LocalRotation.RotateVec(localPos);
parent = parentXform.ParentUid;
} while (parent.IsValid() && _xformQuery.TryGetComponent(parent, out parentXform));
} while (parent.IsValid() && XformQuery.TryGetComponent(parent, out parentXform));
oldLinear += linearAngularContribution;

View File

@@ -0,0 +1,63 @@
using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics.Systems;
public partial class SharedPhysicsSystem
{
[ViewVariables]
public Vector2 Gravity { get; private set; }
public void SetGravity(Vector2 value)
{
if (Gravity.Equals(value))
return;
Gravity = value;
// TODO: Network + datafield
}
// Box2D has a bunch of methods that work on worlds but in our case separate EntityManager instances are
// separate worlds so we can just treat the physics system as the world.
private bool _autoClearForces;
/// <summary>
/// When substepping the client needs to know about the first position to use for lerping.
/// </summary>
protected readonly Dictionary<EntityUid, EntityUid>
LerpData = new();
// TODO:
// - Add test that movebuffer removes entities moved to nullspace.
// Previously we stored the WorldAABB of the proxy being moved and tracked state.
// The issue is that if something moves multiple times in a tick it can add up, plus it's also done on hotpaths such as physics.
// As such we defer it until we actually try and get contacts, then we can run them in parallel.
/// <summary>
/// Keep a buffer of everything that moved in a tick. This will be used to check for physics contacts.
/// </summary>
[ViewVariables]
internal readonly HashSet<FixtureProxy> MoveBuffer = new();
/// <summary>
/// Track moved grids to know if we need to run checks for them driving over entities.
/// </summary>
[ViewVariables]
internal readonly HashSet<EntityUid> MovedGrids = new();
/// <summary>
/// All awake bodies in the game.
/// </summary>
[ViewVariables]
public readonly HashSet<Entity<PhysicsComponent, TransformComponent>> AwakeBodies = new();
/// <summary>
/// Store last tick's invDT
/// </summary>
private float _invDt0;
}

View File

@@ -4,13 +4,10 @@ using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Controllers;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Events;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
@@ -42,11 +39,9 @@ namespace Robust.Shared.Physics.Systems
Buckets = Histogram.ExponentialBuckets(0.000_001, 1.5, 25)
});
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IManifoldManager _manifoldManager = default!;
[Dependency] private readonly IParallelManager _parallel = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IDependencyCollection _deps = default!;
[Dependency] private readonly Gravity2DController _gravity = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
@@ -65,13 +60,14 @@ namespace Robust.Shared.Physics.Systems
public bool MetricsEnabled { get; protected set; }
private EntityQuery<FixturesComponent> _fixturesQuery;
protected EntityQuery<PhysicsComponent> PhysicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<CollideOnAnchorComponent> _anchorQuery;
protected EntityQuery<PhysicsMapComponent> PhysMapQuery;
private EntityQuery<FixturesComponent> _fixturesQuery;
private EntityQuery<JointComponent> JointQuery;
private EntityQuery<JointRelayTargetComponent> RelayTargetQuery;
private EntityQuery<MapGridComponent> _gridQuery;
protected EntityQuery<MapComponent> MapQuery;
protected EntityQuery<PhysicsComponent> PhysicsQuery;
protected EntityQuery<TransformComponent> XformQuery;
private ComponentRegistration _physicsReg = default!;
private byte _angularVelocityIndex;
@@ -106,18 +102,18 @@ namespace Robust.Shared.Physics.Systems
_angularVelocityIndex = 10;
_fixturesQuery = GetEntityQuery<FixturesComponent>();
PhysicsQuery = GetEntityQuery<PhysicsComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_anchorQuery = GetEntityQuery<CollideOnAnchorComponent>();
PhysMapQuery = GetEntityQuery<PhysicsMapComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_fixturesQuery = GetEntityQuery<FixturesComponent>();
JointQuery = GetEntityQuery<JointComponent>();
RelayTargetQuery = GetEntityQuery<JointRelayTargetComponent>();
MapQuery = GetEntityQuery<MapComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
PhysicsQuery = GetEntityQuery<PhysicsComponent>();
XformQuery = GetEntityQuery<TransformComponent>();
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
SubscribeLocalEvent<CollisionChangeEvent>(OnCollisionChange);
SubscribeLocalEvent<PhysicsComponent, EntGotRemovedFromContainerMessage>(HandleContainerRemoved);
SubscribeLocalEvent<PhysicsMapComponent, ComponentInit>(HandlePhysicsMapInit);
SubscribeLocalEvent<PhysicsComponent, ComponentInit>(OnPhysicsInit);
SubscribeLocalEvent<PhysicsComponent, ComponentShutdown>(OnPhysicsShutdown);
SubscribeLocalEvent<PhysicsComponent, ComponentGetState>(OnPhysicsGetState);
@@ -125,7 +121,7 @@ namespace Robust.Shared.Physics.Systems
InitializeIsland();
InitializeContacts();
Subs.CVar(_cfg, CVars.AutoClearForces, OnAutoClearChange);
Subs.CVar(_cfg, CVars.AutoClearForces, OnAutoClearChange, true);
Subs.CVar(_cfg, CVars.NetTickrate, UpdateSubsteps, true);
Subs.CVar(_cfg, CVars.TargetMinimumTickrate, UpdateSubsteps, true);
}
@@ -153,20 +149,9 @@ namespace Robust.Shared.Physics.Systems
}
}
private void HandlePhysicsMapInit(EntityUid uid, PhysicsMapComponent component, ComponentInit args)
{
_deps.InjectDependencies(component);
component.AutoClearForces = _cfg.GetCVar(CVars.AutoClearForces);
}
private void OnAutoClearChange(bool value)
{
var enumerator = AllEntityQuery<PhysicsMapComponent>();
while (enumerator.MoveNext(out var comp))
{
comp.AutoClearForces = value;
}
_autoClearForces = value;
}
private void UpdateSubsteps(int obj)
@@ -199,7 +184,7 @@ namespace Robust.Shared.Physics.Systems
if (oldMap != xform.MapUid)
{
// This will also handle broadphase updating & joint clearing.
HandleMapChange(uid, xform, body, oldMap, xform.MapUid);
HandleMapChange(uid, xform, body);
return;
}
@@ -210,11 +195,9 @@ namespace Robust.Shared.Physics.Systems
/// <summary>
/// Recursively add/remove from awake bodies, clear joints, remove from move buffer, and update broadphase.
/// </summary>
private void HandleMapChange(EntityUid uid, TransformComponent xform, PhysicsComponent? body, EntityUid? oldMapId, EntityUid? newMapId)
private void HandleMapChange(EntityUid uid, TransformComponent xform, PhysicsComponent? body)
{
PhysMapQuery.TryGetComponent(oldMapId, out var oldMap);
PhysMapQuery.TryGetComponent(newMapId, out var newMap);
RecursiveMapUpdate(uid, xform, body, newMap, oldMap);
RecursiveMapUpdate(uid, xform, body);
}
/// <summary>
@@ -223,33 +206,17 @@ namespace Robust.Shared.Physics.Systems
private void RecursiveMapUpdate(
EntityUid uid,
TransformComponent xform,
PhysicsComponent? body,
PhysicsMapComponent? newMap,
PhysicsMapComponent? oldMap)
PhysicsComponent? body)
{
DebugTools.Assert(!Deleted(uid));
// This entity may not have a body, but some of its children might:
if (body != null)
{
if (body.Awake)
{
RemoveSleepBody(uid, body, oldMap);
AddAwakeBody(uid, body, newMap);
DebugTools.Assert(body.Awake);
}
else
DebugTools.Assert(oldMap?.AwakeBodies.Contains(body) != true);
}
_joints.ClearJoints(uid);
foreach (var child in xform._children)
{
if (_xformQuery.TryGetComponent(child, out var childXform))
if (XformQuery.TryGetComponent(child, out var childXform))
{
PhysicsQuery.TryGetComponent(child, out var childBody);
RecursiveMapUpdate(child, childXform, childBody, newMap, oldMap);
RecursiveMapUpdate(child, childXform, childBody);
}
}
}
@@ -276,17 +243,6 @@ namespace Robust.Shared.Physics.Systems
ShutdownContacts();
}
private void UpdateMapAwakeState(EntityUid uid, PhysicsComponent body)
{
if (Transform(uid).MapUid is not {} map)
return;
if (body.Awake)
AddAwakeBody(uid, body, map);
else
RemoveSleepBody(uid, body, map);
}
private void HandleContainerRemoved(EntityUid uid, PhysicsComponent physics, EntGotRemovedFromContainerMessage message)
{
// If entity being deleted then the parent change will already be handled elsewhere and we don't want to re-add it to the map.
@@ -314,18 +270,11 @@ namespace Robust.Shared.Physics.Systems
var updateBeforeSolve = new PhysicsUpdateBeforeSolveEvent(prediction, frameTime);
RaiseLocalEvent(ref updateBeforeSolve);
var contactEnumerator = AllEntityQuery<PhysicsMapComponent, TransformComponent>();
// Find new contacts and (TODO: temporary) update any per-map virtual controllers
while (contactEnumerator.MoveNext(out var comp, out var xform))
{
// 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.
_broadphase.FindNewContacts(comp, xform.MapID);
var updateMapBeforeSolve = new PhysicsUpdateBeforeMapSolveEvent(prediction, comp, frameTime);
RaiseLocalEvent(ref updateMapBeforeSolve);
}
// 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.
_broadphase.FindNewContacts();
// 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,
@@ -350,12 +299,8 @@ namespace Robust.Shared.Physics.Systems
// But that might be unnecessarily expensive for what are hopefully only infrequent mispredicts.
CollideContacts();
var enumerator = AllEntityQuery<PhysicsMapComponent>();
while (enumerator.MoveNext(out var uid, out var comp))
{
Step(uid, comp, frameTime, prediction);
}
Step(frameTime, prediction);
var updateAfterSolve = new PhysicsUpdateAfterSolveEvent(prediction, frameTime);
RaiseLocalEvent(ref updateAfterSolve);
@@ -363,12 +308,7 @@ namespace Robust.Shared.Physics.Systems
// On last substep (or main step where no substeps occured) we'll update all of the lerp data.
if (i == _substeps - 1)
{
enumerator = AllEntityQuery<PhysicsMapComponent>();
while (enumerator.MoveNext(out var comp))
{
FinalStep(comp);
}
FinalStep();
}
EffectiveCurTime = EffectiveCurTime.Value + TimeSpan.FromSeconds(frameTime);
@@ -377,7 +317,7 @@ namespace Robust.Shared.Physics.Systems
EffectiveCurTime = null;
}
protected virtual void FinalStep(PhysicsMapComponent component)
protected virtual void FinalStep()
{
}

View File

@@ -170,9 +170,9 @@ namespace Robust.Shared.Prototypes
[Obsolete("Pass in IComponentFactory")]
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component)
where T : IComponent
where T : IComponent, new()
{
var compName = IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T));
var compName = IoCManager.Resolve<IComponentFactory>().GetComponentName<T>();
return TryGetComponent(compName, out component);
}
@@ -182,9 +182,9 @@ namespace Robust.Shared.Prototypes
return TryGetComponent(compName, out component);
}
public bool TryGetComponent<T>(string name, [NotNullWhen(true)] out T? component) where T : IComponent
public bool TryGetComponent<T>(string name, [NotNullWhen(true)] out T? component) where T : IComponent, new()
{
DebugTools.AssertEqual(IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T)), name);
DebugTools.AssertEqual(IoCManager.Resolve<IComponentFactory>().GetComponentName<T>(), name);
if (!Components.TryGetValue(name, out var componentUnCast))
{

View File

@@ -1,100 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using NetSerializer;
namespace Robust.Shared.Serialization;
/// <summary>
/// Custom serializer implementation for <see cref="BitArray"/>.
/// </summary>
/// <remarks>
/// <para>
/// This type is necessary as, since .NET 10, the internal layout of <see cref="BitArray"/> was changed.
/// The type now (internally) implements <see cref="ISerializable"/> for backwards compatibility with existing
/// <c>BinaryFormatter</c> code, but NetSerializer does not support <see cref="ISerializable"/>.
/// </para>
/// <para>
/// This code is designed to be backportable &amp; network compatible with the previous behavior on .NET 9.
/// </para>
/// </remarks>
internal sealed class NetBitArraySerializer : IDynamicTypeSerializer
{
// NOTE: MUST be a IDynamicTypeSerializer for compatibility!
// Can be changed in the future.
// For reference, the layout of BitArray before .NET 10 was:
// private int[] m_array;
// private int m_length;
// private int _version;
// NetSerializer serialized these in the following order (sorted by name):
// _version, m_array, m_length
public bool Handles(Type type)
{
return type == typeof(BitArray);
}
public IEnumerable<Type> GetSubtypes(Type type)
{
return [typeof(int[]), typeof(int)];
}
public void GenerateWriterMethod(Serializer serializer, Type type, ILGenerator il)
{
var method = typeof(NetBitArraySerializer).GetMethod("Write", BindingFlags.Static | BindingFlags.NonPublic)!;
// arg0: Serializer, arg1: Stream, arg2: value
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.EmitCall(OpCodes.Call, method, null);
il.Emit(OpCodes.Ret);
}
public void GenerateReaderMethod(Serializer serializer, Type type, ILGenerator il)
{
var method = typeof(NetBitArraySerializer).GetMethod("Read", BindingFlags.Static | BindingFlags.NonPublic)!;
// arg0: Serializer, arg1: stream, arg2: out value
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.EmitCall(OpCodes.Call, method, null);
il.Emit(OpCodes.Ret);
}
[UsedImplicitly]
private static void Write(Serializer serializer, Stream stream, BitArray value)
{
var intCount = (31 + value.Length) >> 5;
var ints = new int[intCount];
value.CopyTo(ints, 0);
serializer.SerializeDirect(stream, 0); // _version
serializer.SerializeDirect(stream, ints); // m_array
serializer.SerializeDirect(stream, value.Length); // m_length
}
[UsedImplicitly]
private static void Read(Serializer serializer, Stream stream, out BitArray value)
{
serializer.DeserializeDirect<int>(stream, out _); // _version
serializer.DeserializeDirect<int[]>(stream, out var array); // m_array
serializer.DeserializeDirect<int>(stream, out var length); // m_length
value = new BitArray(array)
{
Length = length
};
}
}

View File

@@ -91,7 +91,6 @@ namespace Robust.Shared.Serialization
MappedStringSerializer.TypeSerializer,
new Vector2Serializer(),
new Matrix3x2Serializer(),
new NetBitArraySerializer()
}
};
_serializer = new Serializer(types, settings);

View File

@@ -45,8 +45,11 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null,
ISerializationManager.InstantiationDelegate<PrototypeFlags<T>>? instanceProvider = null)
{
if(instanceProvider != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(PrototypeFlags<T>)}. Ignoring...");
if (instanceProvider != null)
{
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
sawmill.Warning($"Provided value to a Read-call for a {nameof(PrototypeFlags<T>)}. Ignoring...");
}
var flags = new List<string>(node.Sequence.Count);
@@ -78,8 +81,11 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null,
ISerializationManager.InstantiationDelegate<PrototypeFlags<T>>? instanceProvider = null)
{
if(instanceProvider != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(PrototypeFlags<T>)}. Ignoring...");
if (instanceProvider != null)
{
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
sawmill.Warning($"Provided value to a Read-call for a {nameof(PrototypeFlags<T>)}. Ignoring...");
}
return new PrototypeFlags<T>(node.Value);
}

View File

@@ -153,7 +153,8 @@ public sealed class DictionarySerializer<TKey, TValue> :
{
if (instanceProvider != null)
{
Logger.Warning(
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
sawmill.Warning(
$"Provided value to a Read-call for a {nameof(FrozenDictionary<TKey, TValue>)}. Ignoring...");
}
@@ -180,7 +181,8 @@ public sealed class DictionarySerializer<TKey, TValue> :
{
if (instanceProvider != null)
{
Logger.Warning(
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
sawmill.Warning(
$"Provided value to a Read-call for a {nameof(IReadOnlyDictionary<TKey, TValue>)}. Ignoring...");
}

View File

@@ -45,7 +45,10 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate<FrozenSet<T>>? instanceProvider = null)
{
if (instanceProvider != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(FrozenSet<T>)}. Ignoring...");
{
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
sawmill.Warning($"Provided value to a Read-call for a {nameof(FrozenSet<T>)}. Ignoring...");
}
var array = new T[node.Sequence.Count];
var i = 0;
@@ -65,7 +68,10 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
ISerializationManager.InstantiationDelegate<ImmutableHashSet<T>>? instanceProvider)
{
if (instanceProvider != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(ImmutableHashSet<T>)}. Ignoring...");
{
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
sawmill.Warning($"Provided value to a Read-call for a {nameof(ImmutableHashSet<T>)}. Ignoring...");
}
var set = ImmutableHashSet.CreateBuilder<T>();
foreach (var dataNode in node.Sequence)

View File

@@ -127,8 +127,11 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
SerializationHookContext hookCtx, ISerializationContext? context,
ISerializationManager.InstantiationDelegate<IReadOnlyList<T>>? instanceProvider)
{
if(instanceProvider != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(IReadOnlySet<T>)}. Ignoring...");
if (instanceProvider != null)
{
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
sawmill.Warning($"Provided value to a Read-call for a {nameof(IReadOnlySet<T>)}. Ignoring...");
}
var list = new List<T>();
@@ -146,8 +149,11 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
SerializationHookContext hookCtx, ISerializationContext? context,
ISerializationManager.InstantiationDelegate<IReadOnlyCollection<T>>? instanceProvider)
{
if(instanceProvider != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(IReadOnlyCollection<T>)}. Ignoring...");
if (instanceProvider != null)
{
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
sawmill.Warning($"Provided value to a Read-call for a {nameof(IReadOnlyCollection<T>)}. Ignoring...");
}
var list = new List<T>();
@@ -165,8 +171,11 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
SerializationHookContext hookCtx, ISerializationContext? context,
ISerializationManager.InstantiationDelegate<ImmutableList<T>>? instanceProvider)
{
if(instanceProvider != null)
Logger.Warning($"Provided value to a Read-call for a {nameof(ImmutableList<T>)}. Ignoring...");
if (instanceProvider != null)
{
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("szr");
sawmill.Warning($"Provided value to a Read-call for a {nameof(ImmutableList<T>)}. Ignoring...");
}
var list = ImmutableList.CreateBuilder<T>();

View File

@@ -53,12 +53,10 @@ namespace Robust.UnitTesting
typeof(MetaDataComponent),
typeof(TransformComponent),
typeof(PhysicsComponent),
typeof(PhysicsMapComponent),
typeof(BroadphaseComponent),
typeof(FixturesComponent),
typeof(JointComponent),
typeof(GridTreeComponent),
typeof(MovedGridsComponent),
typeof(JointRelayTargetComponent),
typeof(OccluderComponent),
typeof(OccluderTreeComponent),
@@ -66,7 +64,6 @@ namespace Robust.UnitTesting
typeof(LightTreeComponent),
typeof(CollisionWakeComponent),
typeof(CollideOnAnchorComponent),
typeof(Gravity2DComponent),
typeof(ActorComponent)
};
@@ -125,7 +122,6 @@ namespace Robust.UnitTesting
systems.LoadExtraSystemType<SharedGridTraversalSystem>();
systems.LoadExtraSystemType<FixtureSystem>();
systems.LoadExtraSystemType<Gravity2DController>();
systems.LoadExtraSystemType<CollisionWakeSystem>();
if (Project == UnitTestProject.Client)

View File

@@ -299,16 +299,13 @@ namespace Robust.UnitTesting.Server
compFactory.RegisterClass<JointComponent>();
compFactory.RegisterClass<EyeComponent>();
compFactory.RegisterClass<GridTreeComponent>();
compFactory.RegisterClass<MovedGridsComponent>();
compFactory.RegisterClass<JointRelayTargetComponent>();
compFactory.RegisterClass<BroadphaseComponent>();
compFactory.RegisterClass<ContainerManagerComponent>();
compFactory.RegisterClass<PhysicsMapComponent>();
compFactory.RegisterClass<FixturesComponent>();
compFactory.RegisterClass<CollisionWakeComponent>();
compFactory.RegisterClass<OccluderComponent>();
compFactory.RegisterClass<OccluderTreeComponent>();
compFactory.RegisterClass<Gravity2DComponent>();
compFactory.RegisterClass<CollideOnAnchorComponent>();
compFactory.RegisterClass<ActorComponent>();
@@ -322,7 +319,6 @@ namespace Robust.UnitTesting.Server
var entitySystemMan = container.Resolve<IEntitySystemManager>();
entitySystemMan.LoadExtraSystemType<PhysicsSystem>();
entitySystemMan.LoadExtraSystemType<Gravity2DController>();
entitySystemMan.LoadExtraSystemType<SharedGridTraversalSystem>();
entitySystemMan.LoadExtraSystemType<ContainerSystem>();
entitySystemMan.LoadExtraSystemType<JointSystem>();

View File

@@ -214,9 +214,7 @@ entities:
- type: Transform
- type: Map
mapInitialized: True
- type: PhysicsMap
- type: GridTree
- type: MovedGrids
- type: Broadphase
- type: OccluderTree
- type: EntitySaveTest
@@ -271,9 +269,7 @@ entities:
- type: Transform
- type: Map
mapPaused: True
- type: PhysicsMap
- type: GridTree
- type: MovedGrids
- type: Broadphase
- type: OccluderTree
- type: EntitySaveTest
@@ -296,9 +292,7 @@ entities:
- type: Transform
- type: Map
mapInitialized: True
- type: PhysicsMap
- type: GridTree
- type: MovedGrids
- type: Broadphase
- type: OccluderTree
- type: EntitySaveTest

View File

@@ -33,8 +33,8 @@ public sealed partial class DeferredEntityDeletionTest : RobustIntegrationTest
var server = StartServer(options);
await server.WaitIdleAsync();
EntityUid uid1 = default, uid2 = default, uid3 = default;
DeferredDeletionTestComponent comp1 = default!, comp2 = default!, comp3 = default!;
EntityUid uid1 = default, uid2 = default, uid3 = default, uid4 = default;
DeferredDeletionTestComponent comp1 = default!, comp2 = default!, comp3 = default!, comp4 = default!;
IEntityManager entMan = default!;
await server.WaitAssertion(() =>
@@ -46,14 +46,17 @@ public sealed partial class DeferredEntityDeletionTest : RobustIntegrationTest
uid1 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
uid2 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
uid3 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
uid4 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
comp1 = entMan.AddComponent<DeferredDeletionTestComponent>(uid1);
comp2 = entMan.AddComponent<DeferredDeletionTestComponent>(uid2);
comp3 =entMan.AddComponent<DeferredDeletionTestComponent>(uid3);
comp3 = entMan.AddComponent<DeferredDeletionTestComponent>(uid3);
comp4 = entMan.AddComponent<DeferredDeletionTestComponent>(uid4);
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid1);
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid2);
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid3);
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid4);
});
await server.WaitRunTicks(1);
@@ -76,17 +79,23 @@ public sealed partial class DeferredEntityDeletionTest : RobustIntegrationTest
var ev = new DeferredDeletionTestEvent();
entMan.EventBus.RaiseLocalEvent(uid2, ev);
entMan.EventBus.RaiseLocalEvent(uid3, ev);
entMan.EventBus.RaiseLocalEvent(uid4, ev);
entMan.DeleteEntity(uid2);
entMan.QueueDeleteEntity(uid3);
entMan.TryQueueDeleteEntity(uid4);
Assert.That(entMan.Deleted(uid2));
Assert.That(!entMan.Deleted(uid3));
Assert.That(!entMan.Deleted(uid4));
Assert.That(comp2.LifeStage == ComponentLifeStage.Deleted);
Assert.That(comp3.LifeStage == ComponentLifeStage.Stopped);
Assert.That(comp4.LifeStage == ComponentLifeStage.Stopped);
});
await server.WaitRunTicks(1);
Assert.That(comp3.LifeStage == ComponentLifeStage.Deleted);
Assert.That(comp4.LifeStage == ComponentLifeStage.Deleted);
Assert.That(entMan.Deleted(uid3));
Assert.That(entMan.Deleted(uid4));
await server.WaitIdleAsync();
}

View File

@@ -133,7 +133,9 @@ namespace Robust.UnitTesting.Shared.GameObjects
var entManMock = new Mock<IEntityManager>();
#pragma warning disable CS0618 // Type or member is obsolete
compInstance.Owner = entUid;
#pragma warning restore CS0618 // Type or member is obsolete
var compRegistration = new ComponentRegistration(
"MetaData",

View File

@@ -248,12 +248,15 @@ namespace Robust.UnitTesting.Shared.Map
var grid = mapManager.CreateGridEntity(mapId);
var gridEnt = grid.Owner;
var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(grid, entPos));
var newXform = entityManager.GetComponent<TransformComponent>(newEnt);
Assert.That(xformSys.ToMapCoordinates(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates), Is.EqualTo(new MapCoordinates(entPos, mapId)));
Assert.That(xformSys.ToMapCoordinates(newXform.Coordinates), Is.EqualTo(new MapCoordinates(entPos, mapId)));
Assert.That(xformSys.ToMapCoordinates(newXform.Coordinates).Position, Is.EqualTo(xformSys.ToWorldPosition(newXform.Coordinates)));
xformSys.SetLocalPosition(gridEnt, entityManager.GetComponent<TransformComponent>(gridEnt).LocalPosition + gridPos);
Assert.That(xformSys.ToMapCoordinates(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates), Is.EqualTo(new MapCoordinates(entPos + gridPos, mapId)));
Assert.That(xformSys.ToMapCoordinates(newXform.Coordinates), Is.EqualTo(new MapCoordinates(entPos + gridPos, mapId)));
Assert.That(xformSys.ToMapCoordinates(newXform.Coordinates).Position, Is.EqualTo(xformSys.ToWorldPosition(newXform.Coordinates)));
}
[Test]

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Robust.Shared.GameObjects;
@@ -21,12 +22,16 @@ public sealed class MapGridMap_Tests
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
var mapId = sim.CreateMap().MapId;
Assert.That(!mapManager.FindGridsIntersecting(mapId, Box2.UnitCentered).Any());
List<Entity<MapGridComponent>> grids = [];
mapManager.FindGridsIntersecting(mapId, Box2.UnitCentered, ref grids);
Assert.That(grids, Is.Empty);
entManager.AddComponent<MapGridComponent>(mapManager.GetMapEntityId(mapId));
Assert.That(mapManager.FindGridsIntersecting(mapId, Box2.UnitCentered).Count() == 1);
entManager.AddComponent<MapGridComponent>(mapSystem.GetMapOrInvalid(mapId));
mapManager.FindGridsIntersecting(mapId, Box2.UnitCentered, ref grids);
Assert.That(grids, Has.Count.EqualTo(1));
}
/// <summary>
@@ -46,7 +51,7 @@ public sealed class MapGridMap_Tests
Assert.DoesNotThrow(() =>
{
entManager.AddComponent<MapGridComponent>(mapManager.GetMapEntityId(mapId));
entManager.AddComponent<MapGridComponent>(mapSystem.GetMapOrInvalid(mapId));
entManager.TickUpdate(0.016f, false);
});

View File

@@ -27,12 +27,14 @@ namespace Robust.UnitTesting.Shared.Map
{
var sim = SimulationFactory();
var mapMan = sim.Resolve<IMapManager>();
var entMan = sim.Resolve<IEntityManager>();
var mapSys = entMan.System<SharedMapSystem>();
var mapID = sim.CreateMap().MapId;
mapMan.Restart();
Assert.That(mapMan.MapExists(mapID), Is.False);
Assert.That(mapSys.MapExists(mapID), Is.False);
}
/// <summary>
@@ -62,7 +64,7 @@ namespace Robust.UnitTesting.Shared.Map
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var oldEntity = entMan.CreateEntityUninitialized(null, MapCoordinates.Nullspace);
entMan.InitializeComponents(oldEntity);
entMan.InitializeEntity(oldEntity);
entMan.FlushEntities();
Assert.That(entMan.Deleted(oldEntity), Is.True);
}

View File

@@ -150,19 +150,17 @@ internal sealed class MapPauseTests
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
// arrange
var map1 = sim.CreateMap().Uid;
entMan.System<SharedMapSystem>().SetPaused(map1, true);
var newEnt = entMan.SpawnEntity(null, new EntityCoordinates(map1, default));
var xform = entMan.GetComponent<TransformComponent>(newEnt);
var map2 = sim.CreateMap().Uid;
entMan.System<SharedMapSystem>().SetPaused(map2, false);
// Act
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetParent(xform.Owner, map2);
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetParent(newEnt, map2);
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
Assert.That(metaData.EntityPaused, Is.False);
@@ -177,19 +175,17 @@ internal sealed class MapPauseTests
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
// arrange
var map1 = sim.CreateMap().Uid;
entMan.System<SharedMapSystem>().SetPaused(map1, false);
var newEnt = entMan.SpawnEntity(null, new EntityCoordinates(map1, default));
var xform = entMan.GetComponent<TransformComponent>(newEnt);
var map2 = sim.CreateMap().Uid;
entMan.System<SharedMapSystem>().SetPaused(map2, true);
// Act
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetParent(xform.Owner, map2);
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetParent(newEnt, map2);
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
Assert.That(metaData.EntityPaused, Is.True);

View File

@@ -396,17 +396,6 @@ namespace Robust.UnitTesting.Shared.Maths
Assert.That(MathHelper.CloseToPercent(color, controlColor));
}
[Test]
public void ToFromHcy([ValueSource(nameof(FourFloatsSource))] (float, float, float, float) floats)
{
var (rf, gf, bf, af) = floats;
var controlColor = new Color(rf, gf, bf, af);
var color = Color.FromHcy(Color.ToHcy(controlColor));
Assert.That(MathHelper.CloseToPercent(color, controlColor));
}
static IEnumerable<float> InterpolationValues => new float[]
{
0f,

View File

@@ -41,7 +41,7 @@ internal sealed class NetDisconnectMessageTest
};
var encoded = value.Encode();
TestContext.Write($"Encoded: {encoded}\n");
TestContext.Out.Write($"Encoded: {encoded}\n");
var decodedAgain = NetDisconnectMessage.Decode(encoded);
Assert.Multiple(() =>

View File

@@ -110,7 +110,7 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest
var cPlayerXform = cEntMan.GetComponent<TransformComponent>(cEntMan.GetEntity(playerNet));
// Client initially has correct transform data.
var broadphase = new BroadphaseData(grid1, map1, true, false);
var broadphase = new BroadphaseData(grid1, true, false);
var grid1Net = sEntMan.GetNetEntity(grid1);
Assert.That(cPlayerXform.GridUid, Is.EqualTo(cEntMan.GetEntity(grid1Net)));
@@ -119,7 +119,6 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest
Assert.That(sPlayerXform.MapUid, Is.EqualTo(map1));
Assert.That(cPlayerXform.Broadphase?.Uid, Is.EqualTo(cEntMan.GetEntity(sEntMan.GetNetEntity(broadphase.Uid))));
Assert.That(cPlayerXform.Broadphase?.PhysicsMap, Is.EqualTo(cEntMan.GetEntity(sEntMan.GetNetEntity(broadphase.PhysicsMap))));
Assert.That(cPlayerXform.Broadphase?.Static, Is.EqualTo(broadphase.Static));
Assert.That(cPlayerXform.Broadphase?.CanCollide, Is.EqualTo(broadphase.CanCollide));
Assert.That(sPlayerXform.Broadphase, Is.EqualTo(broadphase));
@@ -151,14 +150,13 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest
}
// Player & server xforms should match.
broadphase = new BroadphaseData(grid2, map2, true, false);
broadphase = new BroadphaseData(grid2, true, false);
Assert.That(cEntMan.GetNetEntity(cPlayerXform.GridUid), Is.EqualTo(grid2Net));
Assert.That(sPlayerXform.GridUid, Is.EqualTo(grid2));
Assert.That(cEntMan.GetNetEntity(cPlayerXform.MapUid), Is.EqualTo(map2Net));
Assert.That(sPlayerXform.MapUid, Is.EqualTo(map2));
Assert.That(cPlayerXform.Broadphase?.Uid, Is.EqualTo(cEntMan.GetEntity(sEntMan.GetNetEntity(broadphase.Uid))));
Assert.That(cPlayerXform.Broadphase?.PhysicsMap, Is.EqualTo(cEntMan.GetEntity(sEntMan.GetNetEntity(broadphase.PhysicsMap))));
Assert.That(cPlayerXform.Broadphase?.Static, Is.EqualTo(broadphase.Static));
Assert.That(cPlayerXform.Broadphase?.CanCollide, Is.EqualTo(broadphase.CanCollide));
Assert.That(sPlayerXform.Broadphase, Is.EqualTo(broadphase));

View File

@@ -47,9 +47,7 @@ public sealed class Broadphase_Test
Assert.That(!dynamicBody.Awake);
// Clear move buffer
entManager.System<SharedBroadphaseSystem>().FindNewContacts(
entManager.GetComponent<PhysicsMapComponent>(mapEnt),
entManager.GetComponent<MapComponent>(mapEnt).MapId);
entManager.System<SharedBroadphaseSystem>().FindNewContacts();
var staticEnt = entManager.SpawnAtPosition(null, new EntityCoordinates(mapEnt, Vector2.Zero));
var staticBody = entManager.AddComponent<PhysicsComponent>(staticEnt);
@@ -61,9 +59,7 @@ public sealed class Broadphase_Test
Assert.That(!staticBody.Awake);
Assert.That(staticBody.ContactCount, Is.EqualTo(0));
entManager.System<SharedBroadphaseSystem>().FindNewContacts(
entManager.GetComponent<PhysicsMapComponent>(mapEnt),
entManager.GetComponent<MapComponent>(mapEnt).MapId);
entManager.System<SharedBroadphaseSystem>().FindNewContacts();
Assert.That(staticBody.ContactCount, Is.EqualTo(1));
@@ -288,22 +284,18 @@ public sealed class Broadphase_Test
Assert.That(childBody.CanCollide);
// Initially on mapA
var AssertMap = (EntityUid map, EntityUid otherMap) =>
var AssertMap = (EntityUid map, EntityUid otherMap, Vector2 pos) =>
{
var broadphase = entManager.GetComponent<BroadphaseComponent>(map);
var physMap = entManager.GetComponent<PhysicsMapComponent>(map);
Assert.That(parentXform.ParentUid == map);
Assert.That(parentXform.MapUid == map);
Assert.That(childXform.MapUid == map);
Assert.That(lookup.FindBroadphase(parent), Is.EqualTo(broadphase));
Assert.That(lookup.FindBroadphase(child), Is.EqualTo(broadphase));
Assert.That(parentXform.Broadphase == new BroadphaseData(map, default, false, false));
Assert.That(childXform.Broadphase == new BroadphaseData(map, map, true, true));
Assert.That(physMap.MoveBuffer.ContainsKey(childFixtures.Fixtures.First().Value.Proxies.First()));
var otherPhysMap = entManager.GetComponent<PhysicsMapComponent>(otherMap);
Assert.That(otherPhysMap.MoveBuffer.Count == 0);
Assert.That(parentXform.Broadphase == new BroadphaseData(map, false, false));
Assert.That(childXform.Broadphase == new BroadphaseData(map, true, true));
};
AssertMap(mapA, mapB);
AssertMap(mapA, mapB, new Vector2(200, 200));
// we are now going to test several broadphase updates where we relocate the parent entity such that it moves:
// - map to map with a map change
@@ -315,15 +307,14 @@ public sealed class Broadphase_Test
// - grid to map without a map change
// Move to map B (map to map with a map change)
xforms.SetCoordinates(parent, new EntityCoordinates(mapB, new Vector2(200, 200)));
AssertMap(mapB, mapA);
xforms.SetCoordinates(parent, new EntityCoordinates(mapB, new Vector2(100, 100)));
AssertMap(mapB, mapA, new Vector2(100, 100));
// Move to gridA on mapA (map to grid with a map change)
xforms.SetCoordinates(parent, new EntityCoordinates(gridA, default));
var AssertGrid = (EntityUid grid, EntityUid map, EntityUid otherMap) =>
var AssertGrid = (EntityUid grid, EntityUid map, EntityUid otherMap, Vector2 pos) =>
{
var broadphase = entManager.GetComponent<BroadphaseComponent>(grid);
var physMap = entManager.GetComponent<PhysicsMapComponent>(map);
var gridXform = entManager.GetComponent<TransformComponent>(grid);
Assert.That(gridXform.ParentUid == map);
Assert.That(gridXform.MapUid == map);
@@ -332,42 +323,40 @@ public sealed class Broadphase_Test
Assert.That(childXform.MapUid == map);
Assert.That(lookup.FindBroadphase(parent), Is.EqualTo(broadphase));
Assert.That(lookup.FindBroadphase(child), Is.EqualTo(broadphase));
Assert.That(parentXform.Broadphase == new BroadphaseData(grid, default, false, false));
Assert.That(childXform.Broadphase == new BroadphaseData(grid, map, true, true));
Assert.That(physMap.MoveBuffer.ContainsKey(childFixtures.Fixtures.First().Value.Proxies.First()));
var otherPhysMap = entManager.GetComponent<PhysicsMapComponent>(otherMap);
Assert.That(otherPhysMap.MoveBuffer.Count == 0);
Assert.That(parentXform.Broadphase == new BroadphaseData(grid, false, false));
Assert.That(childXform.Broadphase == new BroadphaseData(grid, true, true));
};
AssertGrid(gridA, mapA, mapB);
AssertGrid(gridA, mapA, mapB, Vector2.Zero);
// Move to gridB on mapB (grid to grid with a map change)
xforms.SetCoordinates(parent, new EntityCoordinates(gridB, default));
AssertGrid(gridB, mapB, mapA);
AssertGrid(gridB, mapB, mapA, Vector2.Zero);
// move to mapA (grid to map with a map change)
xforms.SetCoordinates(parent, new EntityCoordinates(mapA, new Vector2(200, 200)));
AssertMap(mapA, mapB);
AssertMap(mapA, mapB, new Vector2(200, 200));
// move to gridA on mapA (map to grid without a map change)
xforms.SetCoordinates(parent, new EntityCoordinates(gridA, default));
AssertGrid(gridA, mapA, mapB);
AssertGrid(gridA, mapA, mapB, Vector2.Zero);
// move to gridC on mapA (grid to grid without a map change)
xforms.SetCoordinates(parent, new EntityCoordinates(gridC, default));
AssertGrid(gridC, mapA, mapB);
AssertGrid(gridC, mapA, mapB, new Vector2(10, 10));
// move to gridC on mapA (grid to map without a map change)
xforms.SetCoordinates(parent, new EntityCoordinates(mapA, new Vector2(200, 200)));
AssertMap(mapA, mapB);
xforms.SetCoordinates(parent, new EntityCoordinates(mapA, new Vector2(50, 50)));
AssertMap(mapA, mapB, new Vector2(50, 50));
// Finally, we check if the broadphase updates if the whole grid moves, instead of just the entity
// first, move it to a grid:
xforms.SetCoordinates(parent, new EntityCoordinates(gridC, default));
AssertGrid(gridC, mapA, mapB);
AssertGrid(gridC, mapA, mapB, new Vector2(10, 10));
// then move the grid to a new map:
xforms.SetCoordinates(gridC, new EntityCoordinates(mapB, new Vector2(200,200)));
AssertGrid(gridC, mapB, mapA);
// Asserting child pos NOT gridC pos.
AssertGrid(gridC, mapB, mapA, new Vector2(10, 10));
}
/// <summary>

View File

@@ -122,10 +122,10 @@ public sealed class Collision_Test
{
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var fixtures = entManager.System<FixtureSystem>();
var physics = entManager.System<SharedPhysicsSystem>();
var xformSystem = entManager.System<SharedTransformSystem>();
var mapSystem = entManager.System<SharedMapSystem>();
var mapId = sim.CreateMap().MapId;
var mapId2 = sim.CreateMap().MapId;
@@ -151,7 +151,7 @@ public sealed class Collision_Test
Assert.That(body1.ContactCount == 1 && body2.ContactCount == 1);
// Reparent body2 and assert the contact is destroyed
xformSystem.SetParent(ent2, mapManager.GetMapEntityId(mapId2));
xformSystem.SetParent(ent2, mapSystem.GetMapOrInvalid(mapId2));
physics.Update(0.01f);
Assert.That(body1.ContactCount == 0 && body2.ContactCount == 0);

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Numerics;
using System.Threading.Tasks;
using NUnit.Framework;
@@ -52,12 +53,11 @@ public sealed class GridDeletion_Test : RobustIntegrationTest
Assert.That(physics.LinearVelocity.Length, NUnit.Framework.Is.GreaterThan(0f));
entManager.DeleteEntity(grid);
List<Entity<MapGridComponent>> grids = [];
// So if gridtree is fucky then this SHOULD throw.
foreach (var _ in mapManager.FindGridsIntersecting(mapId,
mapManager.FindGridsIntersecting(mapId,
new Box2(new Vector2(float.MinValue, float.MinValue),
new Vector2(float.MaxValue, float.MaxValue))))
{
}
new Vector2(float.MaxValue, float.MaxValue)), ref grids);
});
}
}

View File

@@ -87,18 +87,6 @@ public sealed class GridReparentVelocity_Test
return obj;
}
[TearDown]
public void Teardown()
{
_entManager.DeleteEntity(_gridUid);
_gridUid = default!;
_entManager.DeleteEntity(_objUid);
_objUid = default!;
_mapSystem.DeleteMap(_mapId);
_mapId = default!;
_entManager.DeleteEntity(_mapUid);
}
// Moves an object off of a moving grid, checks for conservation of linear velocity.
[Test]
public void TestLinearVelocityOnlyMoveOffGrid()

View File

@@ -35,11 +35,12 @@ public sealed class JointDeletion_Test : RobustIntegrationTest
EntityUid ent2 = default!;
PhysicsComponent body1;
PhysicsComponent body2 = default!;
EntityUid mapEnt = default!;
MapId mapId = default!;
await server.WaitPost(() =>
{
entManager.System<SharedMapSystem>().CreateMap(out mapId);
mapEnt = entManager.System<SharedMapSystem>().CreateMap(out mapId);
ent1 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId));
ent2 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.One, mapId));
@@ -67,11 +68,11 @@ public sealed class JointDeletion_Test : RobustIntegrationTest
await server.WaitAssertion(() =>
{
Assert.That(joint.Enabled);
physicsSystem.SetAwake(ent2, body2, false);
physicsSystem.SetAwake((ent2, body2), false);
Assert.That(!body2.Awake);
entManager.DeleteEntity(ent2);
broadphase.FindNewContacts(mapId);
broadphase.FindNewContacts();
});
}
}

View File

@@ -39,7 +39,7 @@ public sealed class Joints_Test
var containerSys = entManager.System<SharedContainerSystem>();
var container = containerSys.EnsureContainer<Container>(uidC, "weh");
var joint = jointSystem.CreateDistanceJoint(uidA, uidB);
jointSystem.CreateDistanceJoint(uidA, uidB);
jointSystem.Update(0.016f);
containerSys.Insert(uidA, container);
@@ -73,7 +73,6 @@ public sealed class Joints_Test
var map = server.CreateMap();
var mapId = map.MapId;
var physicsMapComp = entManager.GetComponent<PhysicsMapComponent>(map.Uid);
var ent1 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId));
var ent2 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId));
@@ -95,7 +94,7 @@ public sealed class Joints_Test
Assert.That(entManager.HasComponent<JointComponent>(ent1), Is.EqualTo(true));
// We should have a contact in both situations.
broadphaseSystem.FindNewContacts(physicsMapComp, mapId);
broadphaseSystem.FindNewContacts();
Assert.That(body1.Contacts, Has.Count.EqualTo(1));
// Alright now try the other way
@@ -105,7 +104,7 @@ public sealed class Joints_Test
jointSystem.Update(0.016f);
Assert.That(entManager.HasComponent<JointComponent>(ent1));
broadphaseSystem.FindNewContacts(physicsMapComp, mapId);
broadphaseSystem.FindNewContacts();
Assert.That(body1.Contacts, Has.Count.EqualTo(1));
mapSystem.DeleteMap(mapId);

View File

@@ -162,7 +162,7 @@ namespace Robust.UnitTesting.Shared.Physics
Assert.That(velocities.Item2, Is.Approximately(angularVelocity, 1e-6));
// but not if we update the local position:
xformSystem.SetWorldPosition(xform2!, Vector2.Zero);
xformSystem.SetWorldPosition((dummy2, xform2!), Vector2.Zero);
linearVelocity = physicsSys.GetMapLinearVelocity(dummy2, body2);
angularVelocity = physicsSys.GetMapAngularVelocity(dummy2, body2);
velocities = physicsSys.GetMapVelocities(dummy2, body2);

View File

@@ -1,71 +0,0 @@
using System.Numerics;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
namespace Robust.UnitTesting.Shared.Physics;
[TestFixture, TestOf(typeof(PhysicsMapComponent))]
public sealed class PhysicsMap_Test
{
/// <summary>
/// If a body has a child does its child's physicsmap get updated.
/// </summary>
[Test]
public void RecursiveMapChange()
{
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var system = entManager.EntitySysManager;
var physSystem = system.GetEntitySystem<SharedPhysicsSystem>();
var fixtureSystem = system.GetEntitySystem<FixtureSystem>();
var xformSystem = system.GetEntitySystem<SharedTransformSystem>();
var map = sim.CreateMap();
var map2 = sim.CreateMap();
var mapId = map.MapId;
var mapId2 = map2.MapId;
var mapUid = map.Uid;
var mapUid2 = map2.Uid;
var physicsMap = entManager.GetComponent<PhysicsMapComponent>(mapUid);
var physicsMap2 = entManager.GetComponent<PhysicsMapComponent>(mapUid2);
var parent = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId));
var parentXform = entManager.GetComponent<TransformComponent>(parent);
var parentBody = entManager.AddComponent<PhysicsComponent>(parent);
physSystem.SetBodyType(parent, BodyType.Dynamic);
physSystem.SetSleepingAllowed(parent, parentBody, false);
fixtureSystem.CreateFixture(parent, "fix1", new Fixture(new PhysShapeCircle(0.5f), 0, 0, false), body: parentBody);
physSystem.WakeBody(parent);
Assert.That(physicsMap.AwakeBodies, Does.Contain(parentBody));
var child = entManager.SpawnEntity(null, new EntityCoordinates(parent, Vector2.Zero));
var childBody = entManager.AddComponent<PhysicsComponent>(child);
physSystem.SetBodyType(child, BodyType.Dynamic);
physSystem.SetSleepingAllowed(child, childBody, false);
fixtureSystem.CreateFixture(child, "fix1", new Fixture(new PhysShapeCircle(0.5f), 0, 0, false), body: childBody);
physSystem.WakeBody(child, body: childBody);
Assert.That(physicsMap.AwakeBodies, Does.Contain(childBody));
xformSystem.SetParent(parent, parentXform, mapUid2);
Assert.That(physicsMap.AwakeBodies, Is.Empty);
Assert.That(physicsMap2.AwakeBodies, Has.Count.EqualTo(2));
xformSystem.SetParent(parent, parentXform, mapUid);
Assert.That(physicsMap.AwakeBodies, Has.Count.EqualTo(2));
Assert.That(physicsMap2.AwakeBodies, Is.Empty);
}
}

View File

@@ -34,7 +34,7 @@ public sealed class RecursiveUpdateTest
var broadphase = entManager.GetComponent<BroadphaseComponent>(guid);
var coords = new EntityCoordinates(guid, new Vector2(0.5f, 0.5f));
var broadData = new BroadphaseData(guid, EntityUid.Invalid, false, false);
var broadData = new BroadphaseData(guid, false, false);
var container = entManager.SpawnEntity(null, coords);
var containerXform = entManager.GetComponent<TransformComponent>(container);
@@ -171,7 +171,7 @@ public sealed class RecursiveUpdateTest
var mapBroadphase = entManager.GetComponent<BroadphaseComponent>(map);
var coords = new EntityCoordinates(map, new Vector2(0.5f, 0.5f));
var mapBroadData = new BroadphaseData(map, EntityUid.Invalid, false, false);
var mapBroadData = new BroadphaseData(map, false, false);
// Set up parent & child
var parent = entManager.SpawnEntity(null, coords);
@@ -220,7 +220,7 @@ public sealed class RecursiveUpdateTest
var guid = grid.Owner;
mapSystem.SetTile(grid, Vector2i.Zero, new Tile(1));
var gridBroadphase = entManager.GetComponent<BroadphaseComponent>(guid);
var gridBroadData = new BroadphaseData(guid, EntityUid.Invalid, false, false);
var gridBroadData = new BroadphaseData(guid, false, false);
var gridCoords = new EntityCoordinates(map, new Vector2(-100f, -100f));
transforms.SetCoordinates(guid, gridCoords);

View File

@@ -52,13 +52,10 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
await server.WaitIdleAsync();
var entityManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
var fixtureSystem = entitySystemManager.GetEntitySystem<FixtureSystem>();
var physSystem = entitySystemManager.GetEntitySystem<SharedPhysicsSystem>();
var gravSystem = entitySystemManager.GetEntitySystem<Gravity2DController>();
var transformSystem = entitySystemManager.GetEntitySystem<SharedTransformSystem>();
MapId mapId;
const int columnCount = 1;
const int rowCount = 15;
@@ -67,8 +64,8 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
await server.WaitPost(() =>
{
var mapUid = entityManager.System<SharedMapSystem>().CreateMap(out mapId);
gravSystem.SetGravity(mapUid, new Vector2(0f, -9.8f));
var mapUid = entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
physSystem.SetGravity(new Vector2(0f, -9.8f));
var groundUid = entityManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = entityManager.AddComponent<PhysicsComponent>(groundUid);
@@ -162,7 +159,6 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
var fixtureSystem = entitySystemManager.GetEntitySystem<FixtureSystem>();
var physSystem = entitySystemManager.GetEntitySystem<SharedPhysicsSystem>();
var gravSystem = entitySystemManager.GetEntitySystem<Gravity2DController>();
var transformSystem = entitySystemManager.GetEntitySystem<SharedTransformSystem>();
MapId mapId;
@@ -174,7 +170,7 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
await server.WaitPost(() =>
{
var mapUid = entityManager.System<SharedMapSystem>().CreateMap(out mapId);
gravSystem.SetGravity(mapUid, new Vector2(0f, -9.8f));
physSystem.SetGravity(new Vector2(0f, -9.8f));
var groundUid = entityManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = entityManager.AddComponent<PhysicsComponent>(groundUid);