Files
comaps/map/traffic_manager.cpp
mvglasow 24d65bd37f WIP: [traffic] Implement basic TraFF parsing, currently from hardcoded path
Not feature complete, produces incorrect results for some test cases
Some parts of the implementation are not very elegant yet
Inefficient as the whole set of messages is parsed on update
Lots of verbose debug logging
Lots of dead code from old traffic module (#ifdef traffic_dead_code)

Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00

969 lines
28 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "map/traffic_manager.hpp"
#include "routing/routing_helpers.hpp"
#include "drape_frontend/drape_engine.hpp"
#include "drape_frontend/visual_params.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "indexer/scales.hpp"
#include "geometry/mercator.hpp"
#include "openlr/decoded_path.hpp"
#include "openlr/openlr_decoder.hpp"
#include "openlr/openlr_model.hpp"
#include "platform/platform.hpp"
#include "traffxml/traff_model_xml.hpp"
using namespace std::chrono;
namespace
{
auto constexpr kUpdateInterval = minutes(1);
auto constexpr kOutdatedDataTimeout = minutes(5) + kUpdateInterval;
auto constexpr kNetworkErrorTimeout = minutes(20);
auto constexpr kMaxRetriesCount = 5;
// Number of identical data sources to create for the OpenLR decoder, one source per worker thread.
// TODO how to determine the best number of worker threads?
auto constexpr kNumDecoderThreads = 1;
} // namespace
TrafficManager::CacheEntry::CacheEntry()
: m_isLoaded(false)
, m_dataSize(0)
, m_retriesCount(0)
, m_isWaitingForResponse(false)
, m_lastAvailability(traffic::TrafficInfo::Availability::Unknown)
{}
TrafficManager::CacheEntry::CacheEntry(time_point<steady_clock> const & requestTime)
: m_isLoaded(false)
, m_dataSize(0)
, m_lastActiveTime(requestTime)
, m_lastRequestTime(requestTime)
, m_retriesCount(0)
, m_isWaitingForResponse(true)
, m_lastAvailability(traffic::TrafficInfo::Availability::Unknown)
{}
TrafficManager::TrafficManager(const CountryParentNameGetterFn &countryParentNameGetter,
GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes,
traffic::TrafficObserver & observer)
: m_countryParentNameGetterFn(countryParentNameGetter)
, m_getMwmsByRectFn(getMwmsByRectFn)
, m_observer(observer)
, m_currentDataVersion(0)
, m_state(TrafficState::Disabled)
// TODO no longer needed
#ifdef traffic_dead_code
, m_maxCacheSizeBytes(maxCacheSizeBytes)
#endif
, m_isRunning(true)
, m_isPaused(false)
, m_thread(&TrafficManager::ThreadRoutine, this)
{
CHECK(m_getMwmsByRectFn != nullptr, ());
}
TrafficManager::~TrafficManager()
{
#ifdef DEBUG
{
std::lock_guard<std::mutex> lock(m_mutex);
ASSERT(!m_isRunning, ());
}
#endif
}
void TrafficManager::Teardown()
{
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_isRunning)
return;
m_isRunning = false;
}
m_condition.notify_one();
m_thread.join();
}
TrafficManager::TrafficState TrafficManager::GetState() const
{
return m_state;
}
void TrafficManager::SetStateListener(TrafficStateChangedFn const & onStateChangedFn)
{
m_onStateChangedFn = onStateChangedFn;
}
void TrafficManager::SetEnabled(bool enabled)
{
{
std::lock_guard<std::mutex> lock(m_mutex);
if (enabled == IsEnabled())
return;
Clear();
ChangeState(enabled ? TrafficState::Enabled : TrafficState::Disabled);
}
m_drapeEngine.SafeCall(&df::DrapeEngine::EnableTraffic, enabled);
if (enabled)
Invalidate();
else
m_observer.OnTrafficInfoClear();
}
void TrafficManager::Clear()
{
// TODO no longer needed
#ifdef traffic_dead_code
m_currentCacheSizeBytes = 0;
#endif
m_mwmCache.clear();
m_lastDrapeMwmsByRect.clear();
m_lastRoutingMwmsByRect.clear();
m_activeDrapeMwms.clear();
m_activeRoutingMwms.clear();
m_requestedMwms.clear();
m_trafficETags.clear();
}
void TrafficManager::SetDrapeEngine(ref_ptr<df::DrapeEngine> engine)
{
m_drapeEngine.Set(engine);
}
void TrafficManager::SetCurrentDataVersion(int64_t dataVersion)
{
m_currentDataVersion = dataVersion;
}
void TrafficManager::OnMwmDeregistered(platform::LocalCountryFile const & countryFile)
{
if (!IsEnabled())
return;
{
std::lock_guard<std::mutex> lock(m_mutex);
MwmSet::MwmId mwmId;
for (auto const & cacheEntry : m_mwmCache)
{
if (cacheEntry.first.IsDeregistered(countryFile))
{
mwmId = cacheEntry.first;
break;
}
}
ClearCache(mwmId);
}
}
void TrafficManager::OnDestroySurface()
{
Pause();
}
void TrafficManager::OnRecoverSurface()
{
Resume();
}
void TrafficManager::Invalidate()
{
if (!IsEnabled())
return;
m_lastDrapeMwmsByRect.clear();
m_lastRoutingMwmsByRect.clear();
if (m_currentModelView.second)
UpdateViewport(m_currentModelView.first);
if (m_currentPosition.second)
UpdateMyPosition(m_currentPosition.first);
}
void TrafficManager::UpdateActiveMwms(m2::RectD const & rect,
std::vector<MwmSet::MwmId> & lastMwmsByRect,
std::set<MwmSet::MwmId> & activeMwms)
{
auto mwms = m_getMwmsByRectFn(rect);
if (lastMwmsByRect == mwms)
return;
lastMwmsByRect = mwms;
{
std::lock_guard<std::mutex> lock(m_mutex);
m_activeMwmsChanged = true;
activeMwms.clear();
for (auto const & mwm : mwms)
{
if (mwm.IsAlive())
activeMwms.insert(mwm);
}
RequestTrafficData();
}
}
void TrafficManager::UpdateMyPosition(MyPosition const & myPosition)
{
// Side of square around |myPosition|. Every mwm which is covered by the square
// will get traffic info.
double const kSquareSideM = 5000.0;
m_currentPosition = {myPosition, true /* initialized */};
if (!IsEnabled() || IsInvalidState() || m_isPaused)
return;
m2::RectD const rect =
mercator::RectByCenterXYAndSizeInMeters(myPosition.m_position, kSquareSideM / 2.0);
// Request traffic.
UpdateActiveMwms(rect, m_lastRoutingMwmsByRect, m_activeRoutingMwms);
// @TODO Do all routing stuff.
}
void TrafficManager::UpdateViewport(ScreenBase const & screen)
{
m_currentModelView = {screen, true /* initialized */};
if (!IsEnabled() || IsInvalidState() || m_isPaused)
return;
if (df::GetZoomLevel(screen.GetScale()) < df::kRoadClass0ZoomLevel)
return;
// Request traffic.
UpdateActiveMwms(screen.ClipRect(), m_lastDrapeMwmsByRect, m_activeDrapeMwms);
}
// TODO make this work with multiple sources (e.g. Android)
bool TrafficManager::Subscribe(std::set<MwmSet::MwmId> & mwms)
{
// TODO what if were subscribed already?
// TODO
LOG(LINFO, ("Would subscribe to", mwms));
m_subscriptionId = "placeholder_subscription_id";
m_isPollNeeded = true; // would be false if we got a feed here
return true;
}
// TODO make this work with multiple sources (e.g. Android)
bool TrafficManager::ChangeSubscription(std::set<MwmSet::MwmId> & mwms)
{
// TODO what if were not subscribed yet?
// TODO
LOG(LINFO, ("Would change subscription", m_subscriptionId, "to", mwms));
m_isPollNeeded = true; // would be false if we got a feed here
return true;
}
bool TrafficManager::SetSubscriptionArea()
{
std::set<MwmSet::MwmId> activeMwms;
if (!IsSubscribed())
{
{
std::lock_guard<std::mutex> lock(m_mutex);
m_activeMwmsChanged = false;
UniteActiveMwms(activeMwms);
}
if (!Subscribe(activeMwms))
return false;
}
else if (m_activeMwmsChanged)
{
{
std::lock_guard<std::mutex> lock(m_mutex);
m_activeMwmsChanged = false;
UniteActiveMwms(activeMwms);
}
if (!ChangeSubscription(activeMwms))
return false;
}
return true;
}
// TODO make this work with multiple sources (e.g. Android)
void TrafficManager::Unsubscribe()
{
if (!IsSubscribed())
return;
// TODO
LOG(LINFO, ("Would unsubscribe from", m_subscriptionId));
m_subscriptionId.clear();
}
bool TrafficManager::IsSubscribed()
{
return !m_subscriptionId.empty();
}
// TODO make this work with multiple sources (e.g. Android)
// TODO deal with subscriptions rejected by the server (delete, resubscribe)
bool TrafficManager::Poll()
{
// TODO
//std::string path("/home/michael/src/organicmaps/data/test_data/traff/PL-A18-Krzyzowa-Lipiany.xml");
std::string path("/home/michael/src/organicmaps/data/test_data/traff/PL-A18-Krzyzowa-Lipiany-bidir.xml");
//std::string path("/home/michael/src/organicmaps/data/test_data/traff/LT-A1-Vezaiciai-Endriejavas.xml");
pugi::xml_document document;
auto const load_result = document.load_file(path.data());
if (!load_result)
{
LOG(LERROR, ("Can't load file", path, ":", load_result.description()));
return false;
}
std::setlocale(LC_ALL, "en_US.UTF-8");
traffxml::TraffFeed feed;
if (traffxml::ParseTraff(document, feed))
{
{
std::lock_guard<std::mutex> lock(m_mutex);
m_feeds.push_back(feed);
}
return true;
}
else
{
LOG(LWARNING, ("An error occurred parsing the TraFF feed"));
return false;
}
}
void TrafficManager::Push(traffxml::TraffFeed feed)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_feeds.push_back(feed);
}
void TrafficManager::UpdateMessageCache(std::map<std::string, traffxml::TraffMessage> & cache)
{
traffxml::TraffFeed feed;
// Thread-safe iteration over m_feeds, releasing the mutex during the loop
while (true)
{
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_feeds.empty())
{
feed = m_feeds.front();
m_feeds.erase(m_feeds.begin());
}
else
break;
}
for (auto message : feed)
{
LOG(LINFO, (" message:", message));
auto it = cache.find(message.m_id);
bool process = (it == cache.end());
if (!process)
process = (timegm(&(it->second.m_updateTime)) < timegm(&(message.m_updateTime)));
if (process)
cache.insert_or_assign(message.m_id, message);
}
}
}
void TrafficManager::InitializeDataSources(std::vector<FrozenDataSource> & dataSources)
{
/*
* TODO can we include all available MWMs in the list (including non-active ones)?
* Then we could initialize the decoder once and for all.
*/
ForEachActiveMwm([this, &dataSources](MwmSet::MwmId const & mwmId) {
ASSERT(mwmId.IsAlive(), ());
// TODO do we need .SyncWithDisk() for the file?
for (size_t i = 0; i < dataSources.size(); i++)
dataSources[i].RegisterMap(mwmId.GetInfo()->GetLocalFile());
});
}
/*
* TODO the OpenLR decoder is designed to handle multiple segments (i.e. locations).
* Decoding message by message kind of defeats the purpose.
* But after decoding the location, we need to examine the map features we got in order to
* determine the speed groups, thus we may need to decode one by one (TBD).
* If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path
* structures to accept a TraFF message ID (string) rather than an integer.
*/
void TrafficManager::DecodeMessage(openlr::OpenLRDecoder & decoder,
traffxml::TraffMessage & message, std::map<std::string,
traffic::TrafficInfo::Coloring> & trafficCache)
{
if (message.m_location)
{
// Decode events into consolidated traffic impact
std::optional<traffxml::TrafficImpact> impact = message.GetTrafficImpact();
LOG(LINFO, (" Impact: ", impact));
// Skip further processing if there is no impact
if (!impact)
return;
// Convert the location to a format understood by the OpenLR decoder.
std::vector<openlr::LinearSegment> segments
= message.m_location.value().ToOpenLrSegments(message.m_id);
for (auto segment : segments)
{
LOG(LINFO, (" Segment:", segment.m_segmentId));
for (int i = 0; i < segment.m_locationReference.m_points.size(); i++)
{
LOG(LINFO, (" ", i, ":", segment.m_locationReference.m_points[i].m_latLon));
if (i < segment.m_locationReference.m_points.size() - 1)
{
LOG(LINFO, (" FRC:", segment.m_locationReference.m_points[i].m_functionalRoadClass));
LOG(LINFO, (" DNP:", segment.m_locationReference.m_points[i].m_distanceToNextPoint));
}
}
}
// Decode the location into a path on the map.
// One path per segment
std::vector<openlr::DecodedPath> paths(segments.size());
decoder.DecodeV3(segments, kNumDecoderThreads, paths);
for (size_t i = 0; i < paths.size(); i++)
{
LOG(LINFO, (" Path", i));
LOG(LINFO, (" Partner segment ID:", paths[i].m_segmentId));
LOG(LINFO, (" Edges:", paths[i].m_path.size()));
for (size_t j = 0; j < paths[i].m_path.size(); j++)
{
LOG(LINFO, (" ", paths[i].m_path[j]));
}
}
// TODO store maxspeed in edges
// store decoded paths and speed groups in trafficCache
if (impact)
{
/*
* TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else)
* If no maxspeed or delay is set, just give out speed groups.
* Else, examine segments, length, normal travel time, travel time considering impact, and
* determine the closest matching speed group.
*/
for (size_t i = 0; i < paths.size(); i++)
for (size_t j = 0; j < paths[i].m_path.size(); j++)
{
std::string countryName = paths[i].m_path[j].GetFeatureId().m_mwmId.GetInfo()->GetCountryName();
auto fid = paths[i].m_path[j].GetFeatureId().m_index;
auto segment = paths[i].m_path[j].GetSegId();
uint8_t direction = paths[i].m_path[j].IsForward() ?
traffic::TrafficInfo::RoadSegmentId::kForwardDirection :
traffic::TrafficInfo::RoadSegmentId::kReverseDirection;
// TODO process all TrafficImpact fields and determine the speed group based on that
trafficCache[countryName][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = impact.value().m_speedGroup;
}
}
}
}
void TrafficManager::ThreadRoutine()
{
std::vector<MwmSet::MwmId> mwms;
while (WaitForRequest(mwms))
{
// TODO clean out expired messages
// poll is always needed, unless a new subscription or a subscription change returns a feed
m_isPollNeeded = true;
if (!SetSubscriptionArea())
{
LOG(LWARNING, ("SetSubscriptionArea failed."));
if (!IsSubscribed())
// do not skip out of the loop, we may need to process pushed feeds
LOG(LWARNING, ("No subscription, no traffic data will be retrieved."));
}
// fetch traffic data if subscribed, unless this has already happened in the previous step
if (m_isPollNeeded && IsSubscribed())
{
if (!Poll())
{
LOG(LWARNING, ("Poll failed."));
// TODO set failed status somewhere and retry
}
}
LOG(LINFO, (m_feeds.size(), "feed(s) in queue"));
/*
* TODO call on a temp struct, then unite with m_messageCache, processing only messages with changes
* (adding segments for new messages, removing segments for deleted messages, replacing segments
* for updated messages) and leaving all other segments untouched
*/
UpdateMessageCache(m_messageCache);
LOG(LINFO, (m_messageCache.size(), "message(s) in cache"));
// initialize the decoder
/*
* Access to `DataSource` is not thread-safe. The main app, which works with
* `EditableDataSource` (as the map can be edited), wraps map operations into a
* `FeaturesLoaderGuard`. The OpenLR decoder expects one `FrozenDataSource` (a read-only
* subclass) per worker thread which works as long as the map is not modified.
* Edits are not relevant to the OpenLR decoder. However, if the edits modify MWM files (rather
* than being stored separately), this might confuse the `FrozenDataSource`. In this case, we
* would need to rewrite the OpenLR decoder to work with a `FeaturesLoaderGuard` (which is
* probably the more elegant way to do this anyway).
*/
std::vector<FrozenDataSource> dataSources(kNumDecoderThreads);
// TODO test with data source from framework
InitializeDataSources(dataSources);
openlr::OpenLRDecoder decoder(dataSources, m_countryParentNameGetterFn);
/*
* Map between country names and their colorings.
* TODO use MwmId as map keys:
* As long as we dont/cant use the frameworks `DataSource` instance for the OpenLR decoder,
* `MwmId` instances from the decoder will not match those from the framework because of the
* way the identity operator is currently implemented (comparing `MwmInfo` instances rather than
* their contents). The ultimate goal is to do matching based on `MwmId`s, but that requires
* either running the OpenLR decoder off the shared `DataSource` or changing the way `MwmInfo`
* comparison works, eitehr of which may come with regressions and needs to be tested.
*/
std::map<std::string, traffic::TrafficInfo::Coloring> allMwmColoring;
for (auto [id, message] : m_messageCache)
{
LOG(LINFO, (" ", id, ":", message));
DecodeMessage(decoder, message, allMwmColoring);
}
// set new coloring for MWMs
OnTrafficDataUpdate(allMwmColoring);
// TODO no longer needed
#ifdef traffic_dead_code
for (auto const & mwm : mwms)
{
if (!mwm.IsAlive())
continue;
traffic::TrafficInfo info(mwm, m_currentDataVersion);
std::string tag;
{
std::lock_guard<std::mutex> lock(m_mutex);
tag = m_trafficETags[mwm];
}
if (info.ReceiveTrafficData(tag))
{
OnTrafficDataResponse(std::move(info));
}
else
{
LOG(LWARNING, ("Traffic request failed. Mwm =", mwm));
OnTrafficRequestFailed(std::move(info));
}
{
std::lock_guard<std::mutex> lock(m_mutex);
m_trafficETags[mwm] = tag;
}
}
#endif
mwms.clear();
}
// Calling Unsubscribe() form the worker thread on exit makes thread synchronization easier
Unsubscribe();
}
bool TrafficManager::WaitForRequest(std::vector<MwmSet::MwmId> & mwms)
{
std::unique_lock<std::mutex> lock(m_mutex);
bool const timeout = !m_condition.wait_for(lock, kUpdateInterval, [this]
{
return !m_isRunning || !m_requestedMwms.empty();
});
if (!m_isRunning)
return false;
if (timeout)
RequestTrafficData();
if (!m_requestedMwms.empty())
mwms.swap(m_requestedMwms);
return true;
}
void TrafficManager::RequestTrafficData(MwmSet::MwmId const & mwmId, bool force)
{
bool needRequesting = false;
auto const currentTime = steady_clock::now();
auto const it = m_mwmCache.find(mwmId);
if (it == m_mwmCache.end())
{
needRequesting = true;
m_mwmCache.insert(std::make_pair(mwmId, CacheEntry(currentTime)));
}
else
{
auto const passedSeconds = currentTime - it->second.m_lastRequestTime;
if (passedSeconds >= kUpdateInterval || force)
{
needRequesting = true;
it->second.m_isWaitingForResponse = true;
it->second.m_lastRequestTime = currentTime;
}
if (!force)
it->second.m_lastActiveTime = currentTime;
}
if (needRequesting)
{
m_requestedMwms.push_back(mwmId);
m_condition.notify_one();
}
}
void TrafficManager::RequestTrafficData()
{
if ((m_activeDrapeMwms.empty() && m_activeRoutingMwms.empty()) || !IsEnabled() ||
IsInvalidState() || m_isPaused)
{
return;
}
ForEachActiveMwm([this](MwmSet::MwmId const & mwmId) {
ASSERT(mwmId.IsAlive(), ());
RequestTrafficData(mwmId, false /* force */);
});
UpdateState();
}
// TODO no longer needed
#ifdef traffic_dead_code
void TrafficManager::OnTrafficRequestFailed(traffic::TrafficInfo && info)
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_mwmCache.find(info.GetMwmId());
if (it == m_mwmCache.end())
return;
it->second.m_isWaitingForResponse = false;
it->second.m_lastAvailability = info.GetAvailability();
if (info.GetAvailability() == traffic::TrafficInfo::Availability::Unknown &&
!it->second.m_isLoaded)
{
if (m_activeDrapeMwms.find(info.GetMwmId()) != m_activeDrapeMwms.cend() ||
m_activeRoutingMwms.find(info.GetMwmId()) != m_activeRoutingMwms.cend())
{
if (it->second.m_retriesCount < kMaxRetriesCount)
RequestTrafficData(info.GetMwmId(), true /* force */);
++it->second.m_retriesCount;
}
else
{
it->second.m_retriesCount = 0;
}
}
UpdateState();
}
#endif
void TrafficManager::OnTrafficDataUpdate(std::map<std::string, traffic::TrafficInfo::Coloring> & trafficCache)
{
/*
* Much of this code is copied and pasted together from old MWM code, with some minor adaptations:
*
* ForEachActiveMwm and the assertion (not the rest of the body) is from RequestTrafficData().
* trafficCache lookup is original code.
* TrafficInfo construction is taken fron TheadRoutine(), with modifications (different constructor).
* Updating m_mwmCache is from RequestTrafficData(MwmSet::MwmId const &, bool), with modifications.
* The remainder of the loop is from OnTrafficDataResponse(traffic::TrafficInfo &&), with minor modifications
*/
ForEachActiveMwm([this, trafficCache](MwmSet::MwmId const & mwmId) {
ASSERT(mwmId.IsAlive(), ());
auto tcit = trafficCache.find(mwmId.GetInfo()->GetCountryName());
if (tcit != trafficCache.end())
{
std::lock_guard<std::mutex> lock(m_mutex);
traffic::TrafficInfo::Coloring coloring = tcit->second;
LOG(LINFO, ("Setting new coloring for", mwmId, "with", coloring.size(), "entries"));
traffic::TrafficInfo info(mwmId, std::move(coloring));
m_mwmCache.try_emplace(mwmId, CacheEntry(steady_clock::now()));
auto it = m_mwmCache.find(mwmId);
if (it != m_mwmCache.end())
{
it->second.m_isLoaded = true;
it->second.m_lastResponseTime = steady_clock::now();
it->second.m_isWaitingForResponse = false;
it->second.m_lastAvailability = info.GetAvailability();
UpdateState();
if (!info.GetColoring().empty())
{
m_drapeEngine.SafeCall(&df::DrapeEngine::UpdateTraffic,
static_cast<traffic::TrafficInfo const &>(info));
// Update traffic colors for routing.
m_observer.OnTrafficInfoAdded(std::move(info));
}
}
}
});
}
// TODO no longer needed
#ifdef traffic_dead_code
void TrafficManager::OnTrafficDataResponse(traffic::TrafficInfo && info)
{
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_mwmCache.find(info.GetMwmId());
if (it == m_mwmCache.end())
return;
it->second.m_isLoaded = true;
it->second.m_lastResponseTime = steady_clock::now();
it->second.m_isWaitingForResponse = false;
it->second.m_lastAvailability = info.GetAvailability();
if (!info.GetColoring().empty())
{
// Update cache.
size_t constexpr kElementSize = sizeof(traffic::TrafficInfo::RoadSegmentId) + sizeof(traffic::SpeedGroup);
size_t const dataSize = info.GetColoring().size() * kElementSize;
m_currentCacheSizeBytes += (dataSize - it->second.m_dataSize);
it->second.m_dataSize = dataSize;
ShrinkCacheToAllowableSize();
}
UpdateState();
}
if (!info.GetColoring().empty())
{
m_drapeEngine.SafeCall(&df::DrapeEngine::UpdateTraffic,
static_cast<traffic::TrafficInfo const &>(info));
// Update traffic colors for routing.
m_observer.OnTrafficInfoAdded(std::move(info));
}
}
#endif
void TrafficManager::UniteActiveMwms(std::set<MwmSet::MwmId> & activeMwms) const
{
activeMwms.insert(m_activeDrapeMwms.cbegin(), m_activeDrapeMwms.cend());
activeMwms.insert(m_activeRoutingMwms.cbegin(), m_activeRoutingMwms.cend());
}
// TODO no longer needed
#ifdef traffic_dead_code
void TrafficManager::ShrinkCacheToAllowableSize()
{
// Calculating number of different active mwms.
std::set<MwmSet::MwmId> activeMwms;
UniteActiveMwms(activeMwms);
size_t const numActiveMwms = activeMwms.size();
if (m_currentCacheSizeBytes > m_maxCacheSizeBytes && m_mwmCache.size() > numActiveMwms)
{
std::multimap<time_point<steady_clock>, MwmSet::MwmId> seenTimings;
for (auto const & mwmInfo : m_mwmCache)
seenTimings.insert(std::make_pair(mwmInfo.second.m_lastActiveTime, mwmInfo.first));
auto itSeen = seenTimings.begin();
while (m_currentCacheSizeBytes > m_maxCacheSizeBytes && m_mwmCache.size() > numActiveMwms)
{
ClearCache(itSeen->second);
++itSeen;
}
}
}
#endif
void TrafficManager::ClearCache(MwmSet::MwmId const & mwmId)
{
auto const it = m_mwmCache.find(mwmId);
if (it == m_mwmCache.end())
return;
if (it->second.m_isLoaded)
{
// TODO no longer needed
#ifdef traffic_dead_code
ASSERT_GREATER_OR_EQUAL(m_currentCacheSizeBytes, it->second.m_dataSize, ());
m_currentCacheSizeBytes -= it->second.m_dataSize;
#endif
m_drapeEngine.SafeCall(&df::DrapeEngine::ClearTrafficCache, mwmId);
GetPlatform().RunTask(Platform::Thread::Gui, [this, mwmId]()
{
m_observer.OnTrafficInfoRemoved(mwmId);
});
}
m_mwmCache.erase(it);
m_trafficETags.erase(mwmId);
m_activeDrapeMwms.erase(mwmId);
m_activeRoutingMwms.erase(mwmId);
m_lastDrapeMwmsByRect.clear();
m_lastRoutingMwmsByRect.clear();
}
bool TrafficManager::IsEnabled() const
{
return m_state != TrafficState::Disabled;
}
bool TrafficManager::IsInvalidState() const
{
return m_state == TrafficState::NetworkError;
}
void TrafficManager::UpdateState()
{
if (!IsEnabled() || IsInvalidState())
return;
auto const currentTime = steady_clock::now();
auto maxPassedTime = steady_clock::duration::zero();
bool waiting = false;
bool networkError = false;
bool expiredApp = false;
bool expiredData = false;
bool noData = false;
for (MwmSet::MwmId const & mwmId : m_activeDrapeMwms)
{
auto it = m_mwmCache.find(mwmId);
ASSERT(it != m_mwmCache.end(), ());
if (it->second.m_isWaitingForResponse)
{
waiting = true;
}
else
{
expiredApp |= it->second.m_lastAvailability == traffic::TrafficInfo::Availability::ExpiredApp;
expiredData |= it->second.m_lastAvailability == traffic::TrafficInfo::Availability::ExpiredData;
noData |= it->second.m_lastAvailability == traffic::TrafficInfo::Availability::NoData;
if (it->second.m_isLoaded)
{
auto const timeSinceLastResponse = currentTime - it->second.m_lastResponseTime;
if (timeSinceLastResponse > maxPassedTime)
maxPassedTime = timeSinceLastResponse;
}
else if (it->second.m_retriesCount >= kMaxRetriesCount)
{
networkError = true;
}
}
}
if (networkError || maxPassedTime >= kNetworkErrorTimeout)
ChangeState(TrafficState::NetworkError);
else if (waiting)
ChangeState(TrafficState::WaitingData);
else if (expiredApp)
ChangeState(TrafficState::ExpiredApp);
else if (expiredData)
ChangeState(TrafficState::ExpiredData);
else if (noData)
ChangeState(TrafficState::NoData);
else if (maxPassedTime >= kOutdatedDataTimeout)
ChangeState(TrafficState::Outdated);
else
ChangeState(TrafficState::Enabled);
}
void TrafficManager::ChangeState(TrafficState newState)
{
if (m_state == newState)
return;
m_state = newState;
GetPlatform().RunTask(Platform::Thread::Gui, [this, newState]()
{
if (m_onStateChangedFn != nullptr)
m_onStateChangedFn(newState);
});
}
void TrafficManager::OnEnterForeground()
{
Resume();
}
void TrafficManager::OnEnterBackground()
{
Pause();
}
void TrafficManager::Pause()
{
m_isPaused = true;
}
void TrafficManager::Resume()
{
if (!m_isPaused)
return;
m_isPaused = false;
Invalidate();
}
void TrafficManager::SetSimplifiedColorScheme(bool simplified)
{
m_hasSimplifiedColorScheme = simplified;
m_drapeEngine.SafeCall(&df::DrapeEngine::SetSimplifiedTrafficColors, simplified);
}
std::string DebugPrint(TrafficManager::TrafficState state)
{
switch (state)
{
case TrafficManager::TrafficState::Disabled:
return "Disabled";
case TrafficManager::TrafficState::Enabled:
return "Enabled";
case TrafficManager::TrafficState::WaitingData:
return "WaitingData";
case TrafficManager::TrafficState::Outdated:
return "Outdated";
case TrafficManager::TrafficState::NoData:
return "NoData";
case TrafficManager::TrafficState::NetworkError:
return "NetworkError";
case TrafficManager::TrafficState::ExpiredData:
return "ExpiredData";
case TrafficManager::TrafficState::ExpiredApp:
return "ExpiredApp";
default:
ASSERT(false, ("Unknown state"));
}
return "Unknown";
}