When crushables and crates change their Location/TopLeft, their crushability is cached, but when their CenterPosition is changed, their cached crushability is not refreshed. Since their CrushableBy functions depends on IsAtGroundLevel, which depends on the CenterPosition, this means that when the crushability is cached it will depend on the current height of the object. If the height of the object changes, the cache is not refreshed and now contains out of date information. The Locomotor cache and the HPF both cache this same information, but at different times. HPF caches immediately, but Locomotor caches on demand which means there can be a delay. This means they can have inconsistent, differing views of the crushability information. This eventually surfaces in a "The abstract path should never be searched for an unreachable point." error from HPF when it detects the inconsistency. The bug is that Locomotor was caching information without refreshing it when required. Fixing this to refresh the cache when the CenterPosition changes is likely to have negative performance impacts. As would removing crushability from the cache. These would both be fixes that address the underlying bug. The high impacts of a proper fix lead us to a workaround instead. If we set the CenterPosition before setting the Location, then when the Location is set and the caches are refreshed, the new CenterPosition is available when caching the crushability information. This means logic depending on IsAtGroundLevel will get the new information and cache a more up-to-date view of things. This means when changing both the CenterPosition and Location together we now cache correct information. However calls that set only the CenterPosition and not the Location can still result in a bad cache state. Although this is imperfect it is an improvement over current affairs, and has less impact.
149 lines
4.0 KiB
C#
149 lines
4.0 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
|
|
* This file is part of OpenRA, which is free software. It is made
|
|
* available to you under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, either version 3 of
|
|
* the License, or (at your option) any later version. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using OpenRA.Activities;
|
|
using OpenRA.Mods.Common.Traits;
|
|
using OpenRA.Traits;
|
|
|
|
namespace OpenRA.Mods.Common.Activities
|
|
{
|
|
public class UnloadCargo : Activity
|
|
{
|
|
readonly Actor self;
|
|
readonly Cargo cargo;
|
|
readonly INotifyUnload[] notifiers;
|
|
readonly bool unloadAll;
|
|
readonly Aircraft aircraft;
|
|
readonly Mobile mobile;
|
|
readonly bool assignTargetOnFirstRun;
|
|
readonly WDist unloadRange;
|
|
|
|
Target destination;
|
|
bool takeOffAfterUnload;
|
|
|
|
public UnloadCargo(Actor self, WDist unloadRange, bool unloadAll = true)
|
|
: this(self, Target.Invalid, unloadRange, unloadAll)
|
|
{
|
|
assignTargetOnFirstRun = true;
|
|
}
|
|
|
|
public UnloadCargo(Actor self, in Target destination, WDist unloadRange, bool unloadAll = true)
|
|
{
|
|
this.self = self;
|
|
cargo = self.Trait<Cargo>();
|
|
notifiers = self.TraitsImplementing<INotifyUnload>().ToArray();
|
|
this.unloadAll = unloadAll;
|
|
aircraft = self.TraitOrDefault<Aircraft>();
|
|
mobile = self.TraitOrDefault<Mobile>();
|
|
this.destination = destination;
|
|
this.unloadRange = unloadRange;
|
|
}
|
|
|
|
public (CPos Cell, SubCell SubCell)? ChooseExitSubCell(Actor passenger)
|
|
{
|
|
var pos = passenger.Trait<IPositionable>();
|
|
|
|
return cargo.CurrentAdjacentCells
|
|
.Shuffle(self.World.SharedRandom)
|
|
.Select(c => (c, pos.GetAvailableSubCell(c)))
|
|
.Cast<(CPos, SubCell SubCell)?>()
|
|
.FirstOrDefault(s => s.Value.SubCell != SubCell.Invalid);
|
|
}
|
|
|
|
IEnumerable<CPos> BlockedExitCells(Actor passenger)
|
|
{
|
|
var pos = passenger.Trait<IPositionable>();
|
|
|
|
// Find the cells that are blocked by transient actors
|
|
return cargo.CurrentAdjacentCells
|
|
.Where(c => pos.CanEnterCell(c, null, BlockedByActor.All) != pos.CanEnterCell(c, null, BlockedByActor.None));
|
|
}
|
|
|
|
protected override void OnFirstRun(Actor self)
|
|
{
|
|
if (assignTargetOnFirstRun)
|
|
destination = Target.FromCell(self.World, self.Location);
|
|
|
|
// Move to the target destination
|
|
if (aircraft != null)
|
|
{
|
|
// Queue the activity even if already landed in case self.Location != destination
|
|
QueueChild(new Land(self, destination, unloadRange));
|
|
takeOffAfterUnload = !aircraft.AtLandAltitude;
|
|
}
|
|
else if (mobile != null)
|
|
{
|
|
var cell = self.World.Map.Clamp(this.self.World.Map.CellContaining(destination.CenterPosition));
|
|
QueueChild(new Move(self, cell, unloadRange));
|
|
}
|
|
|
|
QueueChild(new Wait(cargo.Info.BeforeUnloadDelay));
|
|
}
|
|
|
|
public override bool Tick(Actor self)
|
|
{
|
|
if (IsCanceling || cargo.IsEmpty())
|
|
return true;
|
|
|
|
if (cargo.CanUnload())
|
|
{
|
|
foreach (var inu in notifiers)
|
|
inu.Unloading(self);
|
|
|
|
var actor = cargo.Peek();
|
|
var spawn = self.CenterPosition;
|
|
|
|
var exitSubCell = ChooseExitSubCell(actor);
|
|
if (exitSubCell == null)
|
|
{
|
|
self.NotifyBlocker(BlockedExitCells(actor));
|
|
QueueChild(new Wait(10));
|
|
return false;
|
|
}
|
|
|
|
cargo.Unload(self);
|
|
self.World.AddFrameEndTask(w =>
|
|
{
|
|
if (actor.Disposed)
|
|
return;
|
|
|
|
var move = actor.Trait<IMove>();
|
|
var pos = actor.Trait<IPositionable>();
|
|
|
|
// HACK: Call SetCenterPosition before SetPosition
|
|
// So when SetPosition calls ActorMap.CellUpdated
|
|
// the listeners see the new CenterPosition.
|
|
pos.SetCenterPosition(actor, spawn);
|
|
pos.SetPosition(actor, exitSubCell.Value.Cell, exitSubCell.Value.SubCell);
|
|
|
|
actor.CancelActivity();
|
|
w.Add(actor);
|
|
});
|
|
}
|
|
|
|
if (!unloadAll || !cargo.CanUnload())
|
|
{
|
|
if (cargo.Info.AfterUnloadDelay > 0)
|
|
QueueChild(new Wait(cargo.Info.AfterUnloadDelay, false));
|
|
|
|
if (takeOffAfterUnload)
|
|
QueueChild(new TakeOff(self));
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|