Skip to content

Commit

Permalink
refactor(threading): Remove cached collision lists (endless-sky#10318)
Browse files Browse the repository at this point in the history
  • Loading branch information
tibetiroka authored Jul 16, 2024
1 parent 5d0d87e commit 4c75192
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 69 deletions.
13 changes: 4 additions & 9 deletions source/AsteroidField.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,8 @@ void AsteroidField::Draw(DrawList &draw, const Point &center, double zoom) const


// Check if the given projectile collides with any asteroids. This excludes minables.
const vector<Collision> &AsteroidField::CollideAsteroids(const Projectile &projectile) const
void AsteroidField::CollideAsteroids(const Projectile &projectile, vector<Collision> &result) const
{
result.clear();

// Check for collisions with ordinary asteroids, which are tiled.
// Rather than tiling the collision set, tile the projectile.
Point from = projectile.Position();
Expand All @@ -150,19 +148,16 @@ const vector<Collision> &AsteroidField::CollideAsteroids(const Projectile &proje
for(int x = 0; x < tileX; ++x)
{
Point offset = Point(x, y) * WRAP;
const vector<Collision> &newHits = asteroidCollisions.Line(from + offset, to + offset);
result.insert(result.end(), newHits.begin(), newHits.end());
asteroidCollisions.Line(from + offset, to + offset, result);
}

return result;
}



// Check if the given projectile collides with any minables.
const vector<Collision> &AsteroidField::CollideMinables(const Projectile &projectile) const
void AsteroidField::CollideMinables(const Projectile &projectile, vector<Collision> &result) const
{
return minableCollisions.Line(projectile);
minableCollisions.Line(projectile, result);
}


Expand Down
7 changes: 2 additions & 5 deletions source/AsteroidField.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ class AsteroidField {
void Draw(DrawList &draw, const Point &center, double zoom) const;

// Check if the given projectile collides with any asteroids. This excludes minables.
const std::vector<Collision> &CollideAsteroids(const Projectile &projectile) const;
void CollideAsteroids(const Projectile &projectile, std::vector<Collision> &result) const;
// Check if the given projectile collides with any minables.
const std::vector<Collision> &CollideMinables(const Projectile &projectile) const;
void CollideMinables(const Projectile &projectile, std::vector<Collision> &result) const;

// Get the list of minable asteroids.
const std::list<std::shared_ptr<Minable>> &Minables() const;
Expand Down Expand Up @@ -91,9 +91,6 @@ class AsteroidField {

CollisionSet asteroidCollisions;
CollisionSet minableCollisions;

// Vector for returning the result of CollideAsteroids.
mutable std::vector<Collision> result;
};


Expand Down
44 changes: 19 additions & 25 deletions source/CollisionSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ namespace {
constexpr int USED_MAX_VELOCITY = MAX_VELOCITY - 1;
// Warn the user only once about too-large projectile velocities.
bool warned = false;

thread_local vector<bool> seen;
}


Expand Down Expand Up @@ -129,33 +131,28 @@ void CollisionSet::Finish()
sorted[counts[index]++] = entry;
}
// Now, counts[index] is where a certain bin begins.

// Initialize 'seen' with 0
seen.clear();
seen.resize(all.size());
seenEpoch = 0;
}



// Get all possible collisions for the given projectile. Collisions are not necessarily
// sorted by distance.
const vector<Collision> &CollisionSet::Line(const Projectile &projectile) const
void CollisionSet::Line(const Projectile &projectile, vector<Collision> &result) const
{
// What objects the projectile hits depends on its government.
const Government *pGov = projectile.GetGovernment();

// Convert the projectile to a line represented by its start and end points.
Point from = projectile.Position();
Point to = from + projectile.Velocity();
return Line(from, to, pGov, projectile.Target());
Line(from, to, result, pGov, projectile.Target());
}



// Get all possible collisions along a line. Collisions are not necessarily sorted by
// distance.
const vector<Collision> &CollisionSet::Line(const Point &from, const Point &to,
void CollisionSet::Line(const Point &from, const Point &to, vector<Collision> &lineResult,
const Government *pGov, const Body *target) const
{
const int x = from.X();
Expand All @@ -169,8 +166,6 @@ const vector<Collision> &CollisionSet::Line(const Point &from, const Point &to,
const int endGX = endX >> SHIFT;
const int endGY = endY >> SHIFT;

lineResult.clear();

// Special case, very common: the projectile is contained in one grid cell.
// In this case, all the complicated code below can be skipped.
if(gx == endGX && gy == endGY)
Expand Down Expand Up @@ -200,7 +195,7 @@ const vector<Collision> &CollisionSet::Line(const Point &from, const Point &to,
lineResult.emplace_back(it->body, collisionType, range);
}

return lineResult;
return;
}

const Point pVelocity = (to - from);
Expand All @@ -214,7 +209,8 @@ const vector<Collision> &CollisionSet::Line(const Point &from, const Point &to,
}
Point newEnd = from + pVelocity.Unit() * USED_MAX_VELOCITY;

return Line(from, newEnd, pGov, target);
Line(from, newEnd, lineResult, pGov, target);
return;
}

// When stepping from one grid cell to the next, we'll go in this direction.
Expand All @@ -238,7 +234,8 @@ const vector<Collision> &CollisionSet::Line(const Point &from, const Point &to,
if(stepY > 0)
ry = fullScale - ry;

++seenEpoch;
seen.clear();
seen.resize(all.size());

while(true)
{
Expand All @@ -253,9 +250,9 @@ const vector<Collision> &CollisionSet::Line(const Point &from, const Point &to,
if(it->x != gx || it->y != gy)
continue;

if(seen[it->seenIndex] == seenEpoch)
if(seen[it->seenIndex])
continue;
seen[it->seenIndex] = seenEpoch;
seen[it->seenIndex] = true;

// Check if this projectile can hit this object. If either the
// projectile or the object has no government, it will always hit.
Expand Down Expand Up @@ -307,33 +304,31 @@ const vector<Collision> &CollisionSet::Line(const Point &from, const Point &to,
gy += stepY;
}
}

return lineResult;
}



// Get all objects within the given range of the given point.
const vector<Body *> &CollisionSet::Circle(const Point &center, double radius) const
void CollisionSet::Circle(const Point &center, double radius, vector<Body *> &result) const
{
return Ring(center, 0., radius);
Ring(center, 0., radius, result);
}



// Get all objects touching a ring with a given inner and outer range
// centered at the given point.
const vector<Body *> &CollisionSet::Ring(const Point &center, double inner, double outer) const
void CollisionSet::Ring(const Point &center, double inner, double outer, vector<Body *> &circleResult) const
{
// Calculate the range of (x, y) grid coordinates this ring covers.
const int minX = static_cast<int>(center.X() - outer) >> SHIFT;
const int minY = static_cast<int>(center.Y() - outer) >> SHIFT;
const int maxX = static_cast<int>(center.X() + outer) >> SHIFT;
const int maxY = static_cast<int>(center.Y() + outer) >> SHIFT;

++seenEpoch;
seen.clear();
seen.resize(all.size());

circleResult.clear();
for(int y = minY; y <= maxY; ++y)
{
const auto gy = y & WRAP_MASK;
Expand All @@ -351,9 +346,9 @@ const vector<Body *> &CollisionSet::Ring(const Point &center, double inner, doub
if(it->x != x || it->y != y)
continue;

if(seen[it->seenIndex] == seenEpoch)
if(seen[it->seenIndex])
continue;
seen[it->seenIndex] = seenEpoch;
seen[it->seenIndex] = true;

const Mask &mask = it->body->GetMask(step);
Point offset = center - it->body->Position();
Expand All @@ -364,7 +359,6 @@ const vector<Body *> &CollisionSet::Ring(const Point &center, double inner, doub
}
}
}
return circleResult;
}


Expand Down
17 changes: 4 additions & 13 deletions source/CollisionSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,18 @@ class CollisionSet {

// Get all possible collisions for the given projectile. Collisions are not necessarily
// sorted by distance.
const std::vector<Collision> &Line(const Projectile &projectile) const;
void Line(const Projectile &projectile, std::vector<Collision> &result) const;

// Get all possible collisions along a line. Collisions are not necessarily sorted by
// distance.
const std::vector<Collision> &Line(const Point &from, const Point &to,
void Line(const Point &from, const Point &to, std::vector<Collision> &result,
const Government *pGov = nullptr, const Body *target = nullptr) const;

// Get all objects within the given range of the given point.
const std::vector<Body *> &Circle(const Point &center, double radius) const;
void Circle(const Point &center, double radius, std::vector<Body *> &result) const;
// Get all objects touching a ring with a given inner and outer range
// centered at the given point.
const std::vector<Body *> &Ring(const Point &center, double inner, double outer) const;
void Ring(const Point &center, double inner, double outer, std::vector<Body *> &result) const;

// Get all objects within this collision set.
const std::vector<Body *> &All() const;
Expand Down Expand Up @@ -99,15 +99,6 @@ class CollisionSet {
std::vector<Entry> sorted;
// After Finish(), counts[index] is where a certain bin begins.
std::vector<unsigned> counts;

// Vector for returning the result of a circle query.
mutable std::vector<Body *> circleResult;
// Vector for returning the result of a line query.
mutable std::vector<Collision> lineResult;

// Keep track of which objects we've already considered
mutable std::vector<unsigned> seen;
mutable unsigned seenEpoch = 0;
};


Expand Down
43 changes: 26 additions & 17 deletions source/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2194,7 +2194,11 @@ void Engine::DoCollisions(Projectile &projectile)
// For weapons with a trigger radius, check if any detectable object will set it off.
double triggerRadius = weapon.TriggerRadius();
if(triggerRadius)
for(const Body *body : shipCollisions.Circle(projectile.Position(), triggerRadius))
{
vector<Body *> inRadius;
inRadius.reserve(min(static_cast<vector<Body *>::size_type>(triggerRadius), ships.size()));
shipCollisions.Circle(projectile.Position(), triggerRadius, inRadius);
for(const Body *body : inRadius)
{
const Ship *ship = reinterpret_cast<const Ship *>(body);
if(body == projectile.Target() || (gov->IsEnemy(body->GetGovernment())
Expand All @@ -2204,25 +2208,17 @@ void Engine::DoCollisions(Projectile &projectile)
break;
}
}
}

// If nothing triggered the projectile, check for collisions with ships and asteroids.
if(collisions.empty())
{
if(weapon.CanCollideShips())
{
const vector<Collision> &newShipHits = shipCollisions.Line(projectile);
collisions.insert(collisions.end(), newShipHits.begin(), newShipHits.end());
}
shipCollisions.Line(projectile, collisions);
if(weapon.CanCollideAsteroids())
{
const vector<Collision> &newAsteroidHits = asteroids.CollideAsteroids(projectile);
collisions.insert(collisions.end(), newAsteroidHits.begin(), newAsteroidHits.end());
}
asteroids.CollideAsteroids(projectile, collisions);
if(weapon.CanCollideMinables())
{
const vector<Collision> &newMinableHits = asteroids.CollideMinables(projectile);
collisions.insert(collisions.end(), newMinableHits.begin(), newMinableHits.end());
}
asteroids.CollideMinables(projectile, collisions);
}
}

Expand Down Expand Up @@ -2265,7 +2261,10 @@ void Engine::DoCollisions(Projectile &projectile)
// "safe" weapon.
Point hitPos = projectile.Position() + range * projectile.Velocity();
bool isSafe = weapon.IsSafe();
for(Body *body : shipCollisions.Circle(hitPos, blastRadius))
vector<Body *> blastCollisions;
blastCollisions.reserve(32);
shipCollisions.Circle(hitPos, blastRadius, blastCollisions);
for(Body *body : blastCollisions)
{
Ship *ship = reinterpret_cast<Ship *>(body);
bool targeted = (projectile.Target() == ship);
Expand Down Expand Up @@ -2327,8 +2326,15 @@ void Engine::DoWeather(Weather &weather)
// Get all ship bodies that are touching a ring defined by the hazard's min
// and max ranges at the hazard's origin. Any ship touching this ring takes
// hazard damage.
for(Body *body : (hazard->SystemWide() ? shipCollisions.All()
: shipCollisions.Ring(weather.Origin(), hazard->MinRange(), hazard->MaxRange())))
vector<Body *> affectedShips;
if(hazard->SystemWide())
affectedShips = shipCollisions.All();
else
{
affectedShips.reserve(ships.size());
shipCollisions.Ring(weather.Origin(), hazard->MinRange(), hazard->MaxRange(), affectedShips);
}
for(Body *body : affectedShips)
{
Ship *hit = reinterpret_cast<Ship *>(body);
hit->TakeDamage(visuals, damage.CalculateDamage(*hit), nullptr);
Expand All @@ -2343,7 +2349,10 @@ void Engine::DoCollection(Flotsam &flotsam)
{
// Check if any ship can pick up this flotsam. Cloaked ships without "cloaked pickup" cannot act.
Ship *collector = nullptr;
for(Body *body : shipCollisions.Circle(flotsam.Position(), 5.))
vector<Body *> pickupShips;
pickupShips.reserve(16);
shipCollisions.Circle(flotsam.Position(), 5., pickupShips);
for(Body *body : pickupShips)
{
Ship *ship = reinterpret_cast<Ship *>(body);
if(!ship->CannotAct(Ship::ActionType::PICKUP) && ship->CanPickUp(flotsam))
Expand Down

0 comments on commit 4c75192

Please sign in to comment.