Pre-reqs for multithreaded physics (#1784)

* Tanks off guard

* Medic

* Diamondback

* Gaming

* Gamingo

* Finalise

* Doh

* Bullet licence

* Marginal cleanup

* Physics licences

* Apply reviews
This commit is contained in:
metalgearsloth
2021-06-17 16:01:41 +10:00
committed by GitHub
parent ce2a4282f3
commit f4d427f5c5
11 changed files with 449 additions and 89 deletions

View File

@@ -23,6 +23,48 @@
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- name: Box2D
license:
MIT License
Copyright (c) 2019 Erin Catto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- name: Bullet Physics SDK
license:
The files in this repository are licensed under the zlib license, except for the files under 'Extras' and examples/ThirdPartyLibs.
Bullet Continuous Collision Detection and Physics Library
http://bulletphysics.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.
- name: Castle Core
license: |
Copyright 2004-2016 Castle Project - http://www.castleproject.org/
@@ -317,6 +359,43 @@
See the License for the specific language governing permissions and
limitations under the License.
- name: Farseer Physics Engine
license:
Microsoft Permissive License (Ms-PL)
This license governs use of the accompanying software.
If you use the software, you accept this license.
If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
derivative works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
your patent license from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
including a complete copy of this license with your distribution. If you distribute any portion of the software in
compiled or object code form, you may only do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
To the extent permitted under your local laws, the contributors exclude the implied warranties of
merchantability, fitness for a particular purpose and non-infringement.
- name: Mono.Cecil
license: |
Copyright (c) 2008 - 2015 Jb Evain

View File

@@ -62,8 +62,9 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Store the body's index within the island so we can lookup its data.
/// Key is Island's ID and value is our index.
/// </summary>
public int IslandIndex { get; set; }
public Dictionary<int, int> IslandIndex { get; set; } = new();
// TODO: Actually implement after the initial pr dummy
/// <summary>

View File

@@ -108,6 +108,8 @@ namespace Robust.Shared.GameObjects
SubscribeLocalEvent<EntRemovedFromContainerMessage>(HandleContainerRemoved);
BuildControllers();
Logger.DebugS("physics", $"Found {_controllers.Count} physics controllers.");
IoCManager.Resolve<IIslandManager>().Initialize();
}

View File

@@ -121,8 +121,8 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
velocityConstraint.Friction = contact.Friction;
velocityConstraint.Restitution = contact.Restitution;
velocityConstraint.TangentSpeed = contact.TangentSpeed;
velocityConstraint.IndexA = bodyA.IslandIndex;
velocityConstraint.IndexB = bodyB.IslandIndex;
velocityConstraint.IndexA = bodyA.IslandIndex[data.IslandIndex];
velocityConstraint.IndexB = bodyB.IslandIndex[data.IslandIndex];
(velocityConstraint.InvMassA, velocityConstraint.InvMassB) = GetInvMass(bodyA, bodyB);
velocityConstraint.InvIA = bodyA.InvI;
@@ -137,8 +137,8 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
}
var positionConstraint = _positionConstraints[i];
positionConstraint.IndexA = bodyA.IslandIndex;
positionConstraint.IndexB = bodyB.IslandIndex;
positionConstraint.IndexA = bodyA.IslandIndex[data.IslandIndex];
positionConstraint.IndexB = bodyB.IslandIndex[data.IslandIndex];
(positionConstraint.InvMassA, positionConstraint.InvMassB) = GetInvMass(bodyA, bodyB);
// TODO: Dis
// positionConstraint.LocalCenterA = bodyA._sweep.LocalCenter;

View File

@@ -334,8 +334,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
internal override void InitVelocityConstraints(SolverData data)
{
_indexA = BodyA.IslandIndex;
_indexB = BodyB.IslandIndex;
_indexA = BodyA.IslandIndex[data.IslandIndex];
_indexB = BodyB.IslandIndex[data.IslandIndex];
_localCenterA = Vector2.Zero; //BodyA->m_sweep.localCenter;
_localCenterB = Vector2.Zero; //BodyB->m_sweep.localCenter;
_invMassA = BodyA.InvMass;

View File

@@ -159,8 +159,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
internal override void InitVelocityConstraints(SolverData data)
{
_indexA = BodyA.IslandIndex;
_indexB = BodyB.IslandIndex;
_indexA = BodyA.IslandIndex[data.IslandIndex];
_indexB = BodyB.IslandIndex[data.IslandIndex];
_localCenterA = Vector2.Zero; // BodyA._sweep.LocalCenter;
_localCenterB = Vector2.Zero; //BodyB._sweep.LocalCenter;
_invMassA = BodyA.InvMass;

View File

@@ -138,7 +138,7 @@ constraint structures. The body velocities/positions are held in compact, tempor
arrays to increase the number of cache hits. Linear and angular velocity are
stored in a single array since multiple arrays lead to multiple misses.
*/
internal sealed class PhysicsIsland
public sealed class PhysicsIsland
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
#if DEBUG
@@ -148,6 +148,10 @@ stored in a single array since multiple arrays lead to multiple misses.
private ContactSolver _contactSolver = default!;
internal int ID { get; set; } = -1;
internal bool LoneIsland { get; set; }
private float _angTolSqr;
private float _linTolSqr;
private bool _warmStarting;
@@ -169,8 +173,14 @@ stored in a single array since multiple arrays lead to multiple misses.
private Vector2[] _positions = Array.Empty<Vector2>();
private float[] _angles = Array.Empty<float>();
private bool _positionSolved = false;
internal SolverData SolverData = new();
private const int BodyIncrease = 8;
private const int ContactIncrease = 4;
private const int JointIncrease = 4;
/// <summary>
/// How many bodies we can fit in the island before needing to re-size.
/// </summary>
@@ -250,9 +260,28 @@ stored in a single array since multiple arrays lead to multiple misses.
_configManager.OnValueChanged(CVars.TimeToSleep, value => _timeToSleep = value);
}
public void Append(List<IPhysBody> bodies, List<Contact> contacts, List<Joint> joints)
{
Resize(BodyCount + bodies.Count, ContactCount + contacts.Count, JointCount + joints.Count);
foreach (var body in bodies)
{
Add(body);
}
foreach (var contact in contacts)
{
Add(contact);
}
foreach (var joint in joints)
{
Add(joint);
}
}
public void Add(IPhysBody body)
{
body.IslandIndex = BodyCount;
body.IslandIndex[ID] = BodyCount;
Bodies[BodyCount++] = body;
}
@@ -268,6 +297,7 @@ stored in a single array since multiple arrays lead to multiple misses.
public void Clear()
{
ID = -1;
BodyCount = 0;
ContactCount = 0;
JointCount = 0;
@@ -277,48 +307,39 @@ stored in a single array since multiple arrays lead to multiple misses.
* Look there's a whole lot of stuff going on around here but all you need to know is it's trying to avoid
* allocations where possible so it does a whole lot of passing data around and using arrays.
*/
public void Reset(int bodyCapacity, int contactCapacity, int jointCapacity)
public void Resize(int bodyCount, int contactCount, int jointCount)
{
BodyCapacity = bodyCapacity;
BodyCount = 0;
BodyCapacity = Math.Max(bodyCount, Bodies.Length);
ContactCapacity = Math.Max(contactCount, _contacts.Length);
JointCapacity = Math.Max(jointCount, _joints.Length);
ContactCapacity = contactCapacity;
ContactCount = 0;
JointCapacity = jointCapacity;
JointCount = 0;
if (Bodies.Length < bodyCapacity)
if (Bodies.Length < BodyCapacity)
{
Array.Resize(ref Bodies, bodyCapacity);
Array.Resize(ref _linearVelocities, bodyCapacity);
Array.Resize(ref _angularVelocities, bodyCapacity);
Array.Resize(ref _positions, bodyCapacity);
Array.Resize(ref _angles, bodyCapacity);
BodyCapacity = BodyIncrease * (int) MathF.Ceiling(BodyCapacity / (float) BodyIncrease);
Array.Resize(ref Bodies, BodyCapacity);
Array.Resize(ref _linearVelocities, BodyCapacity);
Array.Resize(ref _angularVelocities, BodyCapacity);
Array.Resize(ref _positions, BodyCapacity);
Array.Resize(ref _angles, BodyCapacity);
}
if (_contacts.Length < contactCapacity)
if (_contacts.Length < ContactCapacity)
{
Array.Resize(ref _contacts, contactCapacity * 2);
ContactCapacity = ContactIncrease * (int) MathF.Ceiling(ContactCapacity / (float) ContactIncrease);
Array.Resize(ref _contacts, ContactCapacity * 2);
}
if (_joints.Length < jointCapacity)
if (_joints.Length < JointCapacity)
{
Array.Resize(ref _joints, jointCapacity * 2);
JointCapacity = JointIncrease * (int) MathF.Ceiling(JointCapacity / (float) JointIncrease);
Array.Resize(ref _joints, JointCapacity * 2);
}
}
/// <summary>
/// Go through all the bodies in this island and solve.
/// </summary>
/// <param name="gravity"></param>
/// <param name="frameTime"></param>
/// <param name="dtRatio"></param>
/// <param name="invDt"></param>
/// <param name="prediction"></param>
/// <param name="deferredUpdates">Add any transform updates to a deferred list</param>
public void Solve(Vector2 gravity, float frameTime, float dtRatio, float invDt, bool prediction, List<(ITransformComponent, IPhysBody)> deferredUpdates)
public void Solve(Vector2 gravity, float frameTime, float dtRatio, float invDt, bool prediction)
{
#if DEBUG
_debugBodies.Clear();
@@ -366,6 +387,7 @@ stored in a single array since multiple arrays lead to multiple misses.
SolverData.FrameTime = frameTime;
SolverData.DtRatio = dtRatio;
SolverData.InvDt = invDt;
SolverData.IslandIndex = ID;
SolverData.LinearVelocities = _linearVelocities;
SolverData.AngularVelocities = _angularVelocities;
@@ -443,7 +465,7 @@ stored in a single array since multiple arrays lead to multiple misses.
_angles[i] = angle;
}
var positionSolved = false;
_positionSolved = false;
for (var i = 0; i < _positionIterations; i++)
{
@@ -464,11 +486,14 @@ stored in a single array since multiple arrays lead to multiple misses.
if (contactsOkay && jointsOkay)
{
positionSolved = true;
_positionSolved = true;
break;
}
}
}
internal void UpdateBodies(List<(ITransformComponent, IPhysBody)> deferredUpdates)
{
// Update data on bodies by copying the buffers back
for (var i = 0; i < BodyCount; i++)
{
@@ -517,39 +542,72 @@ stored in a single array since multiple arrays lead to multiple misses.
body.AngularVelocity = _angularVelocities[i];
}
}
}
// Sleep bodies if needed. Prediction won't accumulate sleep-time for bodies.
if (!prediction && _sleepAllowed)
internal void SleepBodies(bool prediction, float frameTime)
{
if (LoneIsland)
{
var minSleepTime = float.MaxValue;
for (var i = 0; i < BodyCount; i++)
{
var body = Bodies[i];
if (body.BodyType == BodyType.Static)
continue;
if (!body.SleepingAllowed ||
body.AngularVelocity * body.AngularVelocity > _angTolSqr ||
Vector2.Dot(body.LinearVelocity, body.LinearVelocity) > _linTolSqr)
{
body.SleepTime = 0.0f;
minSleepTime = 0.0f;
}
else
{
body.SleepTime += frameTime;
minSleepTime = MathF.Min(minSleepTime, body.SleepTime);
}
}
if (minSleepTime >= _timeToSleep && positionSolved)
if (!prediction && _sleepAllowed)
{
for (var i = 0; i < BodyCount; i++)
{
var body = Bodies[i];
body.Awake = false;
if (body.BodyType == BodyType.Static) continue;
if (!body.SleepingAllowed ||
body.AngularVelocity * body.AngularVelocity > _angTolSqr ||
Vector2.Dot(body.LinearVelocity, body.LinearVelocity) > _linTolSqr)
{
body.SleepTime = 0.0f;
}
else
{
body.SleepTime += frameTime;
}
if (body.SleepTime >= _timeToSleep && _positionSolved)
{
body.Awake = false;
}
}
}
}
else
{
// Sleep bodies if needed. Prediction won't accumulate sleep-time for bodies.
if (!prediction && _sleepAllowed)
{
var minSleepTime = float.MaxValue;
for (var i = 0; i < BodyCount; i++)
{
var body = Bodies[i];
if (body.BodyType == BodyType.Static) continue;
if (!body.SleepingAllowed ||
body.AngularVelocity * body.AngularVelocity > _angTolSqr ||
Vector2.Dot(body.LinearVelocity, body.LinearVelocity) > _linTolSqr)
{
body.SleepTime = 0.0f;
minSleepTime = 0.0f;
}
else
{
body.SleepTime += frameTime;
minSleepTime = MathF.Min(minSleepTime, body.SleepTime);
}
}
if (minSleepTime >= _timeToSleep && _positionSolved)
{
for (var i = 0; i < BodyCount; i++)
{
var body = Bodies[i];
body.Awake = false;
}
}
}
}
@@ -561,6 +619,8 @@ stored in a single array since multiple arrays lead to multiple misses.
/// </summary>
internal sealed class SolverData
{
public int IslandIndex { get; set; } = -1;
public float FrameTime { get; set; }
public float DtRatio { get; set; }
public float InvDt { get; set; }

View File

@@ -40,6 +40,7 @@ namespace Robust.Shared.Physics.Dynamics
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IIslandManager _islandManager = default!;
private SharedPhysicsSystem _physicsSystem = default!;
@@ -104,7 +105,9 @@ namespace Robust.Shared.Physics.Dynamics
private Queue<CollisionChangeMessage> _queuedCollisionMessages = new();
private PhysicsIsland _island = default!;
private List<IPhysBody> _islandBodies = new(64);
private List<Contact> _islandContacts = new(32);
private List<Joint> _islandJoints = new(8);
/// <summary>
/// To build islands we do a depth-first search of all colliding bodies and group them together.
@@ -130,8 +133,6 @@ namespace Robust.Shared.Physics.Dynamics
IoCManager.InjectDependencies(this);
ContactManager.Initialize();
ContactManager.MapId = MapId;
_island = new PhysicsIsland();
_island.Initialize();
_autoClearForces = _configManager.GetCVar(CVars.AutoClearForces);
_configManager.OnValueChanged(CVars.AutoClearForces, value => _autoClearForces = value);
@@ -447,8 +448,7 @@ namespace Robust.Shared.Physics.Dynamics
private void Solve(float frameTime, float dtRatio, float invDt, bool prediction)
{
// Re-size island for worst-case -> TODO Probably smaller than this given everything's awake at the start?
_island.Reset(Bodies.Count, ContactManager.ContactCount, Joints.Count);
_islandManager.InitializePools();
DebugTools.Assert(_islandSet.Count == 0);
@@ -463,7 +463,7 @@ namespace Robust.Shared.Physics.Dynamics
}
// Build and simulated islands from awake bodies.
// Ideally you don't need a stack size for all bodies but we'll optimise it later.
// Ideally you don't need a stack size for all bodies but we'll TODO: optimise it later.
var stackSize = Bodies.Count;
if (stackSize > _stack.Length)
{
@@ -486,17 +486,19 @@ namespace Robust.Shared.Physics.Dynamics
seed.BodyType == BodyType.Static) continue;
// Start of a new island
_island.Clear();
_islandBodies.Clear();
_islandContacts.Clear();
_islandJoints.Clear();
var stackCount = 0;
_stack[stackCount++] = seed;
_islandSet.Add(seed);
// TODO: Probably don't need _islandSet anymore.
seed.Island = true;
while (stackCount > 0)
{
var body = _stack[--stackCount];
_island.Add(body);
_islandBodies.Add(body);
_islandSet.Add(body);
// Static bodies don't propagate islands
@@ -518,7 +520,7 @@ namespace Robust.Shared.Physics.Dynamics
// Skip sensors.
if (contact.FixtureA?.Hard != true || contact.FixtureB?.Hard != true) continue;
_island.Add(contact);
_islandContacts.Add(contact);
contact.IslandFlag = true;
var other = contactEdge.Other!;
@@ -529,9 +531,6 @@ namespace Robust.Shared.Physics.Dynamics
DebugTools.Assert(stackCount < stackSize);
_stack[stackCount++] = other;
if (!_islandSet.Contains(body))
_islandSet.Add(body);
other.Island = true;
}
@@ -547,7 +546,7 @@ namespace Robust.Shared.Physics.Dynamics
// Don't simulate joints connected to inactive bodies.
if (!other.CanCollide) continue;
_island.Add(je.Joint);
_islandJoints.Add(je.Joint);
je.Joint.IslandFlag = true;
if (other.Island) continue;
@@ -555,19 +554,18 @@ namespace Robust.Shared.Physics.Dynamics
DebugTools.Assert(stackCount < stackSize);
_stack[stackCount++] = other;
if (!_islandSet.Contains(body))
_islandSet.Add(body);
other.Island = true;
}
}
_island.Solve(Gravity, frameTime, dtRatio, invDt, prediction, _deferredUpdates);
_islandManager
.AllocateIsland(_islandBodies.Count, _islandContacts.Count, _islandJoints.Count)
.Append(_islandBodies, _islandContacts, _islandJoints);
// Post-solve cleanup for island
for (var i = 0; i < _island.BodyCount; i++)
// Allow static bodies to be re-used in other islands
for (var i = 0; i < _islandBodies.Count; i++)
{
var body = _island.Bodies[i];
var body = _islandBodies[i];
// Static bodies can participate in other islands
if (body.BodyType == BodyType.Static)
@@ -577,6 +575,8 @@ namespace Robust.Shared.Physics.Dynamics
}
}
SolveIslands(frameTime, dtRatio, invDt, prediction);
foreach (var body in _islandSet)
{
if (!body.Island || body.Deleted)
@@ -584,6 +584,7 @@ namespace Robust.Shared.Physics.Dynamics
continue;
}
body.IslandIndex.Clear();
body.Island = false;
DebugTools.Assert(body.BodyType != BodyType.Static);
@@ -596,6 +597,32 @@ namespace Robust.Shared.Physics.Dynamics
ContactManager.PostSolve();
}
private void SolveIslands(float frameTime, float dtRatio, float invDt, bool prediction)
{
var islands = _islandManager.GetActive;
// Islands are already pre-sorted
var iBegin = 0;
while (iBegin < islands.Count)
{
var island = islands[iBegin];
island.Solve(Gravity, frameTime, dtRatio, invDt, prediction);
iBegin++;
// TODO: Submit rest in parallel if applicable
}
// TODO: parallel dispatch here
// Update bodies sequentially to avoid race conditions. May be able to do this parallel someday
// but easier to just do this for now.
foreach (var island in islands)
{
island.UpdateBodies(_deferredUpdates);
island.SleepBodies(prediction, frameTime);
}
}
private void ClearForces()
{
foreach (var body in AwakeBodies)

View File

@@ -15,7 +15,7 @@ namespace Robust.Shared.Physics
{
bool IgnoreGravity { get; set; }
int IslandIndex { get; set; }
Dictionary<int, int> IslandIndex { get; set; }
/// <summary>
/// Has this body already been added to a physics island

View File

@@ -0,0 +1,190 @@
/*
Bullet Continuous Collision Detection and Physics Library
Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/
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 Robust.Shared.Physics.Dynamics;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics
{
internal interface IIslandManager
{
void Initialize();
void InitializePools();
PhysicsIsland AllocateIsland(int numBodies, int numContacts, int numJoints);
IReadOnlyList<PhysicsIsland> GetActive { get; }
}
/// <summary>
/// Contains physics islands for use by PhysicsMaps.
/// Key difference from bullet3 is we won't merge small islands together due to the way box2d sleeping works.
/// </summary>
internal sealed class IslandManager : IIslandManager
{
private static readonly IslandBodyCapacitySort CapacitySort = new();
private static readonly IslandBodyCountSort CountSort = new();
/// <summary>
/// The island all non-contact non-joint bodies are added to to batch together. This needs its own custom sleeping
/// given we cant wait for every body to be ready to sleep.
/// </summary>
private PhysicsIsland _loneIsland = new() {LoneIsland = true, ID = 0};
/// <summary>
/// Contains islands currently in use.
/// </summary>
private List<PhysicsIsland> _activeIslands = new();
private List<PhysicsIsland> _freeIslands = new();
/// <summary>
/// Contains every single PhysicsIsland.
/// </summary>
private List<PhysicsIsland> _allocatedIslands = new();
public IReadOnlyList<PhysicsIsland> GetActive
{
get
{
if (_loneIsland.BodyCount > 0)
{
_activeIslands.Add(_loneIsland);
}
// Look this is kinda stinky but it's only called at the appropriate place for now
_activeIslands.Sort(CountSort);
return _activeIslands;
}
}
public void Initialize()
{
_loneIsland.Initialize();
// Set an initial size so we don't spam a bunch of array resizes at the start
_loneIsland.Resize(64, 32, 8);
}
public void InitializePools()
{
_loneIsland.Clear();
_activeIslands.Clear();
_freeIslands.Clear();
// Check whether allocated islands are sorted
var lastCapacity = 0;
var isSorted = true;
foreach (var island in _allocatedIslands)
{
var capacity = island.BodyCapacity;
if (capacity > lastCapacity)
{
isSorted = false;
break;
}
lastCapacity = capacity;
}
if (!isSorted)
{
_allocatedIslands.Sort(CapacitySort);
}
// TODO: Look at removing islands occasionally just to avoid memory bloat over time.
// e.g. every 30 seconds go through every island that hasn't been used in 30 seconds and remove it.
// Free up islands
foreach (var island in _allocatedIslands)
{
island.Clear();
_freeIslands.Add(island);
}
}
public PhysicsIsland AllocateIsland(int bodyCount, int contactCount, int jointCount)
{
// If only 1 body then that means no contacts or joints. This also means that we can add it to the loneisland
if (bodyCount == 1)
{
// Island manages re-sizes internally so don't need to worry about this being hammered.
_loneIsland.Resize(_loneIsland.BodyCount + 1, 0, 0);
return _loneIsland;
}
PhysicsIsland? island = null;
// Because bullet3 combines islands it's more relevant for them to allocate by bodies but at least for now
// we don't combine.
if (_freeIslands.Count > 0)
{
// Try to use an existing island; with the smallest size that can hold us if possible.
var iFound = _freeIslands.Count;
for (var i = _freeIslands.Count - 1; i >= 0; i--)
{
if (_freeIslands[i].BodyCapacity >= bodyCount)
{
iFound = i;
island = _freeIslands[i];
DebugTools.Assert(island.BodyCount == 0 && island.ContactCount == 0 && island.JointCount == 0);
break;
}
}
if (island != null)
{
var iDest = iFound;
var iSrc = iDest + 1;
while (iSrc < _freeIslands.Count)
{
_freeIslands[iDest++] = _freeIslands[iSrc++];
}
_freeIslands.RemoveAt(_freeIslands.Count - 1);
}
}
if (island == null)
{
island = new PhysicsIsland();
island.Initialize();
_allocatedIslands.Add(island);
}
island.Resize(bodyCount, contactCount, jointCount);
// 0 ID taken up by LoneIsland
island.ID = _activeIslands.Count + 1;
_activeIslands.Add(island);
return island;
}
}
internal sealed class IslandBodyCapacitySort : Comparer<PhysicsIsland>
{
public override int Compare(PhysicsIsland? x, PhysicsIsland? y)
{
if (x == null || y == null) return 0;
return x.BodyCapacity > y.BodyCapacity ? 1 : 0;
}
}
internal sealed class IslandBodyCountSort : Comparer<PhysicsIsland>
{
public override int Compare(PhysicsIsland? x, PhysicsIsland? y)
{
if (x == null || y == null) return 0;
return x.BodyCount > y.BodyCount ? 1 : 0;
}
}
}

View File

@@ -46,6 +46,7 @@ namespace Robust.Shared
IoCManager.Register<IComponentDependencyManager, ComponentDependencyManager>();
IoCManager.Register<ISandboxHelper, SandboxHelper>();
IoCManager.Register<ICollisionManager, CollisionManager>();
IoCManager.Register<IIslandManager, IslandManager>();
}
}
}