Abort formation-walking on any message from UnitMotion.

Units in formation can occasionally request many short paths (and thus
introduce crippling lag) if their offset is obstructed.
This particularly happen when the formation is idle, since the offset
then always remains obstructed.

To prevent this, it is OK to immediately stop pathing on any motion
message (obstructed, failure, success). This does not break formation
movement since messages are only sent when the formation controller is
not moving (this finishes what was started in 0535eb9b92).

Ideally, this hack could be removed if the short-pathfinder was quick
enough / units were better at aborting.

Fixes concern raised by Freagarach on a7da40ac2f.

Refs #5624 in that the max-short-path range is the source of the lag.

Reviewed By: Angen
Differential Revision: https://code.wildfiregames.com/D2871
This was SVN commit r23867.
This commit is contained in:
wraitii 2020-07-22 09:31:08 +00:00
parent 2ef3fd7a77
commit f489ab3a16
2 changed files with 40 additions and 26 deletions

View file

@ -1384,17 +1384,12 @@ UnitAI.prototype.UnitFsmSpec = {
// Occurs when the unit has reached its destination and the controller
// is done moving. The controller is notified.
"MovementUpdate": function(msg) {
// We're supposed to be walking in formation,
// but the controller has no position -> abort.
let cmpControllerPosition = Engine.QueryInterface(this.formationController, IID_Position);
if (!cmpControllerPosition || !cmpControllerPosition.IsInWorld())
{
this.FinishOrder();
return;
}
if (!msg.likelyFailure && !msg.likelySuccess)
return;
// When walking in formation, we'll only get notified in case of failure
// if the formation controller has stopped walking.
// Formations can start lagging a lot if many entities request short path
// so prefer to finish order early than retry pathing.
// (see https://code.wildfiregames.com/rP23806)
// (if the message is likelyFailure of likelySuccess, we also want to stop).
this.FinishOrder();
},
},

View file

@ -483,10 +483,10 @@ private:
return m_MoveRequest.m_Type == MoveRequest::OFFSET;
}
bool IsFormationControllerNotMoving() const
bool IsFormationControllerMoving() const
{
CmpPtr<ICmpUnitMotion> cmpControllerMotion(GetSimContext(), m_MoveRequest.m_Entity);
return cmpControllerMotion && !cmpControllerMotion->IsMoveRequested();
return cmpControllerMotion && cmpControllerMotion->IsMoveRequested();
}
entity_id_t GetGroup() const
@ -501,6 +501,12 @@ private:
*/
void MoveFailed()
{
// Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time
// if our current offset is unreachable, but we don't want to end up stuck.
// (If the formation controller has stopped moving however, we can safely message).
if (IsFormationMember() && IsFormationControllerMoving())
return;
CMessageMotionUpdate msg(CMessageMotionUpdate::LIKELY_FAILURE);
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
@ -512,10 +518,33 @@ private:
*/
void MoveSucceeded()
{
// Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time
// if our current offset is unreachable, but we don't want to end up stuck.
// (If the formation controller has stopped moving however, we can safely message).
if (IsFormationMember() && IsFormationControllerMoving())
return;
CMessageMotionUpdate msg(CMessageMotionUpdate::LIKELY_SUCCESS);
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
/**
* Warns other components that our current movement was obstructed (i.e. we failed to move this turn).
* This should only be called before the actual movement in a given turn, or units might both move and try to do things
* on the same turn, leading to gliding units.
*/
void MoveObstructed()
{
// Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time
// if our current offset is unreachable, but we don't want to end up stuck.
// (If the formation controller has stopped moving however, we can safely message).
if (IsFormationMember() && IsFormationControllerMoving())
return;
CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED);
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
/**
* Increment the number of failed path computations and notify other components if required.
*/
@ -761,17 +790,10 @@ void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
}
if (m_FailedPathComputations >= 1)
{
// Inform other components - we might be ordered to stop, and computeGoal will then fail and return early.
CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED);
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
MoveObstructed();
// Don't notify if we are a formation member - we can occasionally be stuck for a long time
// if our current offset is unreachable.
// Unless the formationcontroller has reached final destination and we are stuck
if (!IsFormationMember() || IsFormationControllerNotMoving())
IncrementFailedPathComputationAndMaybeNotify();
IncrementFailedPathComputationAndMaybeNotify();
// If there's no waypoints then we couldn't get near the target
// If we're globally following a long path, try to remove the next waypoint,
@ -1013,11 +1035,8 @@ bool CCmpUnitMotion::HandleObstructedMove()
return false;
if (m_FailedPathComputations >= 1)
{
// Inform other components - we might be ordered to stop, and computeGoal will then fail and return early.
CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED);
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
MoveObstructed();
CFixedVector2D pos = cmpPosition->GetPosition2D();