mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-20 05:13:58 +00:00
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>
969 lines
28 KiB
C++
969 lines
28 KiB
C++
#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 we’re 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 we’re 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 don’t/can‘t use the framework’s `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";
|
||
}
|