Improve AdventureBot planning and travel performance
This commit is contained in:
1 parent
69b5a1c4a1
commit
d1ef007cef
18 files changed
+906
-27
No files matched your search
@@ -574,6 +574,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
// Reuse all start-point observations only WITHIN this immutable planning
|
||||
// call, including its two search tiers. Never cache live Player paths globally.
|
||||
std::map<ClientlessGraphProbeCache::Key,ClientlessPathDiagnostics> startProbes;
|
||||
std::map<std::pair<ClientlessGraphProbeCache::Key, bool>, float> flightEstimates;
|
||||
auto analyzeStart=[&](Position const& target,ClientlessPathDiagnostics& result)
|
||||
{
|
||||
auto key=ClientlessGraphProbeCache::MakeKey(player->GetMapId(),player->GetInstanceId(),player->GetPositionX(),player->GetPositionY(),player->GetPositionZ(),target.GetPositionX(),target.GetPositionY(),target.GetPositionZ());
|
||||
@@ -621,12 +622,26 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
{ error="destination location denied by area catalog"; return false; }
|
||||
}
|
||||
bool const flightEnabled = sClientlessFlightMgr->Enabled(player);
|
||||
constexpr std::uint64_t MaxFlightEstimatesPerPlan = 16;
|
||||
auto flightCost = [&](ClientlessTravelNode const& from, uint32 map, float x, float y, float z, bool access = false)
|
||||
{
|
||||
if (!flightEnabled || from.MapId != map ||
|
||||
(blockedBridgeKeys && blockedBridgeKeys->count("flight_origin:"+from.Key)))
|
||||
return std::numeric_limits<float>::infinity();
|
||||
Position a,b; a.Relocate(from.X,from.Y,from.Z,from.O); b.Relocate(x,y,z);
|
||||
auto const estimateKey = std::make_pair(
|
||||
ClientlessGraphProbeCache::MakeKey(map, player->GetInstanceId(),
|
||||
from.X, from.Y, from.Z, x, y, z), access);
|
||||
auto cachedEstimate = flightEstimates.find(estimateKey);
|
||||
if (cachedEstimate != flightEstimates.end())
|
||||
return cachedEstimate->second;
|
||||
// Flight links are optional accelerators around the persistent graph.
|
||||
// A pathological graph must not monopolize the world thread while
|
||||
// constructing an implicit complete air graph. Cached estimates remain
|
||||
// usable after the cap; unexamined links simply stay unavailable in
|
||||
// this plan and ordinary graph travel can still win.
|
||||
if (trace.FlightCalls >= MaxFlightEstimatesPerPlan)
|
||||
return std::numeric_limits<float>::infinity();
|
||||
auto begin=std::chrono::steady_clock::now();++trace.FlightCalls;
|
||||
float result=sClientlessFlightMgr->Estimate(player,map,a,b,access);
|
||||
if(access && std::isfinite(result))
|
||||
@@ -637,6 +652,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
40+distance*0.4f:180+distance*1.5f;
|
||||
}
|
||||
trace.FlightUs+=std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now()-begin).count();
|
||||
flightEstimates.emplace(estimateKey, result);
|
||||
return result;
|
||||
};
|
||||
if (startRadius <= 0.0f)
|
||||
@@ -649,6 +665,47 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
bridgeRadius = MaxVirtualNodeBridge;
|
||||
bridgeRadius = std::min(bridgeRadius, MaxVirtualNodeBridge);
|
||||
|
||||
// The overwhelmingly common free-flight case is a same-map Location. Do
|
||||
// not construct a macro graph first: Estimate already enforces licenses,
|
||||
// mount flags, area policy, distance and the complete catalog segment.
|
||||
// Native departure/landing validation still happens in FlightMgr::Start.
|
||||
if (locationGoal && locationGoal->MapId == player->GetMapId())
|
||||
{
|
||||
ClientlessTravelNode departureNode;
|
||||
departureNode.Key = "flight_departure";
|
||||
departureNode.MapId = player->GetMapId();
|
||||
departureNode.X = player->GetPositionX();
|
||||
departureNode.Y = player->GetPositionY();
|
||||
departureNode.Z = player->GetPositionZ();
|
||||
float const directFlight = flightCost(departureNode, locationGoal->MapId,
|
||||
locationGoal->X, locationGoal->Y, locationGoal->Z);
|
||||
if (std::isfinite(directFlight))
|
||||
{
|
||||
ClientlessTravelRouteStep departure;
|
||||
departure.NodeKey = "flight_departure";
|
||||
departure.NodeType = "ground";
|
||||
departure.EdgeType = "start";
|
||||
departure.MapId = player->GetMapId();
|
||||
departure.X = player->GetPositionX();
|
||||
departure.Y = player->GetPositionY();
|
||||
departure.Z = player->GetPositionZ();
|
||||
route.push_back(std::move(departure));
|
||||
|
||||
ClientlessTravelRouteStep flight;
|
||||
flight.NodeKey = "flight_location";
|
||||
flight.NodeType = "location";
|
||||
flight.EdgeType = "free_flight";
|
||||
flight.MapId = locationGoal->MapId;
|
||||
flight.X = locationGoal->X;
|
||||
flight.Y = locationGoal->Y;
|
||||
flight.Z = locationGoal->Z;
|
||||
route.push_back(std::move(flight));
|
||||
complete = true;
|
||||
reachedNode = "flight_location";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
struct State
|
||||
{
|
||||
std::string Node;
|
||||
@@ -1005,6 +1062,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
? std::min(300.0f, locationGoal->AnchorRadius) : 0.0f;
|
||||
std::vector<ExitCandidate> rawExitCandidates;
|
||||
std::unordered_map<std::string, std::vector<ExitCandidate>> exitsByFrom;
|
||||
std::size_t flightExitProbes = 0;
|
||||
if (locationGoal || flightEnabled)
|
||||
{
|
||||
auto const& targetNode = destinationItr->second;
|
||||
@@ -1046,6 +1104,12 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
float travelled = 0.0f;
|
||||
ExitCandidate nearest;
|
||||
nearest.DistanceSq = std::numeric_limits<float>::infinity();
|
||||
struct RankedFlightExit
|
||||
{
|
||||
float LowerBound = 0.0f;
|
||||
ExitCandidate Candidate;
|
||||
};
|
||||
std::vector<RankedFlightExit> rankedFlightExits;
|
||||
for (uint32 i = 1; i + 1 < ordered.size(); ++i)
|
||||
{
|
||||
float dx = ordered[i].X - ordered[i - 1].X;
|
||||
@@ -1078,18 +1142,59 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
blockedBridgeKeys->count("ground_exit_edge:" + identity);
|
||||
candidate.LocalCost = locationGoal && !groundExitExhausted && dsq <= locationAccessRadius*locationAccessRadius
|
||||
? std::sqrt(dsq) : std::numeric_limits<float>::infinity();
|
||||
|
||||
// A recorded edge can contain hundreds of samples. Calling
|
||||
// FlightMgr::Estimate for every sample made the synchronous
|
||||
// world-thread planner quadratic in road geometry (13k+
|
||||
// area checks for one route in production). Keep an exact
|
||||
// ground candidate, but validate only the geometrically best
|
||||
// flight exits below. Estimate still performs all catalog and
|
||||
// native flight-permission checks for those candidates.
|
||||
if (std::isfinite(candidate.LocalCost) &&
|
||||
(!std::isfinite(nearest.DistanceSq) ||
|
||||
candidate.RoadCost + candidate.LocalCost < nearest.RoadCost + nearest.LocalCost))
|
||||
nearest = candidate;
|
||||
|
||||
if (flightEnabled && !(blockedBridgeKeys && blockedBridgeKeys->count("flight_exit:" + exitKey)))
|
||||
{
|
||||
float const direct = std::sqrt(dsq);
|
||||
// Forty plus 0.4 * distance is the cheapest possible
|
||||
// free-flight cost. It is an admissible ordering key;
|
||||
// rejected area segments simply advance to the next item.
|
||||
rankedFlightExits.push_back({ candidate.RoadCost + 40.0f + direct * 0.4f,
|
||||
std::move(candidate) });
|
||||
}
|
||||
}
|
||||
std::sort(rankedFlightExits.begin(), rankedFlightExits.end(),
|
||||
[](RankedFlightExit const& left, RankedFlightExit const& right)
|
||||
{ return left.LowerBound < right.LowerBound; });
|
||||
constexpr std::size_t MaxFlightExitProbesPerEdge = 2;
|
||||
for (std::size_t i = 0; i < std::min(MaxFlightExitProbesPerEdge, rankedFlightExits.size()); ++i)
|
||||
{
|
||||
constexpr std::size_t MaxFlightExitProbesPerPlan = 8;
|
||||
if (flightExitProbes >= MaxFlightExitProbesPerPlan)
|
||||
break;
|
||||
++flightExitProbes;
|
||||
RankedFlightExit const& ranked = rankedFlightExits[i];
|
||||
ExitCandidate candidate = ranked.Candidate;
|
||||
ClientlessTravelNode exitNode;
|
||||
exitNode.MapId=point.MapId;exitNode.X=point.X;exitNode.Y=point.Y;exitNode.Z=point.Z;
|
||||
float const air = blockedBridgeKeys && blockedBridgeKeys->count("flight_exit:"+exitKey)
|
||||
? std::numeric_limits<float>::infinity() : flightCost(exitNode,goalMap,goalX,goalY,goalZ);
|
||||
if (air < candidate.LocalCost) { candidate.LocalCost=air;candidate.Flight=true; }
|
||||
if (!std::isfinite(candidate.LocalCost)) continue;
|
||||
// Never replace a node goal with an unvalidated ground exit.
|
||||
if (!locationGoal && !candidate.Flight) continue;
|
||||
exitNode.MapId = candidate.Point.MapId;
|
||||
exitNode.X = candidate.Point.X;
|
||||
exitNode.Y = candidate.Point.Y;
|
||||
exitNode.Z = candidate.Point.Z;
|
||||
float const air = flightCost(exitNode, goalMap, goalX, goalY, goalZ);
|
||||
if (!std::isfinite(air))
|
||||
continue;
|
||||
candidate.LocalCost = air;
|
||||
candidate.Flight = true;
|
||||
if (std::isfinite(nearest.DistanceSq) &&
|
||||
candidate.RoadCost+candidate.LocalCost >= nearest.RoadCost+nearest.LocalCost) continue;
|
||||
candidate.RoadCost + candidate.LocalCost >= nearest.RoadCost + nearest.LocalCost)
|
||||
continue;
|
||||
nearest = std::move(candidate);
|
||||
}
|
||||
// Never replace a node goal with an unvalidated ground exit.
|
||||
if (!locationGoal && std::isfinite(nearest.DistanceSq) && !nearest.Flight)
|
||||
nearest.DistanceSq = std::numeric_limits<float>::infinity();
|
||||
if (std::isfinite(nearest.DistanceSq))
|
||||
rawExitCandidates.push_back(std::move(nearest));
|
||||
}
|
||||
@@ -1115,6 +1220,29 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
float bestGoalCost = std::numeric_limits<float>::infinity();
|
||||
std::string bestGoalState;
|
||||
bool bestFinalFlight = false;
|
||||
// Free flight is a macro connector, not a reason to turn every graph map
|
||||
// into a complete directed graph. A flyer only needs direct links to the
|
||||
// requested destination and to nodes that enter a map-changing or physical
|
||||
// transport edge (portal, ship, zeppelin or tram). Taxi nodes are excluded:
|
||||
// a free flyer can already reach their same-map destination directly, and
|
||||
// adding every taxi source recreates an almost-complete graph. Ordinary
|
||||
// same-map ground
|
||||
// nodes remain reachable through their recorded graph, while a location
|
||||
// goal is evaluated directly from every expanded node below.
|
||||
std::unordered_set<std::string> flightConnectorNodes;
|
||||
flightConnectorNodes.insert(destinationNode);
|
||||
for (auto const& edgeGroup : _edges)
|
||||
for (ClientlessTravelEdge const& edge : edgeGroup.second)
|
||||
{
|
||||
auto fromItr = _nodes.find(edge.FromNode);
|
||||
auto toItr = _nodes.find(edge.ToNode);
|
||||
bool const changesMap = fromItr != _nodes.end() && toItr != _nodes.end() &&
|
||||
fromItr->second.MapId != toItr->second.MapId;
|
||||
bool const physicalTransport = edge.Type == "portal" || edge.Type == "ship" ||
|
||||
edge.Type == "zeppelin" || edge.Type == "tram" || edge.Type == "transport";
|
||||
if (changesMap || physicalTransport)
|
||||
flightConnectorNodes.insert(edge.FromNode);
|
||||
}
|
||||
// C8.20: a virtual entry is represented in the queue by its FAR
|
||||
// endpoint. Check exits BEFORE that endpoint independently: otherwise
|
||||
// an exit halfway along the road would pay for the entire remaining edge.
|
||||
@@ -1259,6 +1387,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
|
||||
// Persistent TravelGraph edges always reset the bridge guard.
|
||||
auto edgeItr = _edges.find(current.Value.Node);
|
||||
bool hasPersistentContinuation = false;
|
||||
if (edgeItr != _edges.end())
|
||||
{
|
||||
for (ClientlessTravelEdge const& edge : edgeItr->second)
|
||||
@@ -1268,6 +1397,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
continue;
|
||||
if (blockedEdgeIds && edge.DatabaseId && blockedEdgeIds->count(edge.DatabaseId))
|
||||
continue;
|
||||
hasPersistentContinuation = true;
|
||||
|
||||
State next{ edge.ToNode, false };
|
||||
std::string const nextKey = stateKey(next);
|
||||
@@ -1294,9 +1424,12 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
if (flightEnabled && !portalArrival)
|
||||
{
|
||||
auto const& origin=_nodes.at(current.Value.Node);
|
||||
for (auto const& pair:_nodes)
|
||||
for (std::string const& targetKey : flightConnectorNodes)
|
||||
{
|
||||
auto const& target=pair.second;
|
||||
auto targetItr = _nodes.find(targetKey);
|
||||
if (targetItr == _nodes.end())
|
||||
continue;
|
||||
auto const& target=targetItr->second;
|
||||
if (target.Key==origin.Key || target.MapId!=origin.MapId) continue;
|
||||
State next{target.Key,false};std::string key=stateKey(next);
|
||||
auto old=distance.find(key);
|
||||
@@ -1335,7 +1468,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
|
||||
from.Key.rfind("portal_at_", 0) == 0 &&
|
||||
from.Key.size() >= 7 &&
|
||||
from.Key.compare(from.Key.size() - 7, 7, "_source") == 0;
|
||||
if (!futurePortalSource && persistentHops[currentKey] > 0 &&
|
||||
if (!futurePortalSource && !hasPersistentContinuation && persistentHops[currentKey] > 0 &&
|
||||
!(blockedBridgeKeys &&
|
||||
blockedBridgeKeys->count(MakeLocalBridgeKey(from.Key, "virtual_ground_entry"))))
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user