diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index 196c69a0d1..6a25337529 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -84,6 +84,8 @@ Framework::Framework() { ASSERT_EQUAL ( g_framework, 0, () ); g_framework = this; + + m_work.GetTrafficManager().SetStateListener(bind(&Framework::TrafficStateChanged, this, _1)); } void Framework::OnLocationError(int errorCode) @@ -127,6 +129,12 @@ void Framework::MyPositionModeChanged(location::EMyPositionMode mode, bool routi m_myPositionModeSignal(mode, routingActive); } +void Framework::TrafficStateChanged(TrafficManager::TrafficState state) +{ + if (m_onTrafficStateChangedFn) + m_onTrafficStateChangedFn(state); +} + void Framework::SetMyPositionMode(location::EMyPositionMode mode) { OnMyPositionModeChanged(mode); @@ -186,6 +194,7 @@ void Framework::DetachSurface(bool destroyContext) LOG(LINFO, ("Destroy context.")); m_isContextDestroyed = true; m_work.EnterBackground(); + m_work.OnDestroyGLContext(); } m_work.SetRenderingDisabled(destroyContext); @@ -215,7 +224,7 @@ bool Framework::AttachSurface(JNIEnv * env, jobject jSurface) if (m_isContextDestroyed) { LOG(LINFO, ("Recover GL resources, viewport size:", factory->GetWidth(), factory->GetHeight())); - m_work.UpdateDrapeEngine(factory->GetWidth(), factory->GetHeight()); + m_work.OnRecoverGLContext(factory->GetWidth(), factory->GetHeight()); m_isContextDestroyed = false; m_work.EnterForeground(); @@ -412,6 +421,11 @@ void Framework::ShowTrack(int category, int track) NativeFramework()->ShowTrack(*nTrack); } +void Framework::SetTrafficStateListener(TrafficManager::TrafficStateChangedFn const & fn) +{ + m_onTrafficStateChangedFn = fn; +} + void Framework::SetMyPositionModeListener(location::TMyPositionModeChanged const & fn) { m_myPositionModeSignal = fn; diff --git a/android/jni/com/mapswithme/maps/Framework.hpp b/android/jni/com/mapswithme/maps/Framework.hpp index 88d7bd25f7..50f0e15dd0 100644 --- a/android/jni/com/mapswithme/maps/Framework.hpp +++ b/android/jni/com/mapswithme/maps/Framework.hpp @@ -49,6 +49,8 @@ namespace android map m_guiPositions; + void TrafficStateChanged(TrafficManager::TrafficState state); + void MyPositionModeChanged(location::EMyPositionMode mode, bool routingActive); void SetMyPositionMode(location::EMyPositionMode mode); @@ -56,6 +58,8 @@ namespace android location::EMyPositionMode m_currentMode; bool m_isCurrentModeInitialized; + TrafficManager::TrafficStateChangedFn m_onTrafficStateChangedFn; + bool m_isChoosePositionMode; place_page::Info m_info; @@ -141,6 +145,8 @@ namespace android void OnMyPositionModeChanged(location::EMyPositionMode mode); void SwitchMyPositionNextMode(); + void SetTrafficStateListener(TrafficManager::TrafficStateChangedFn const & fn); + void Save3dMode(bool allow3d, bool allow3dBuildings); void Set3dMode(bool allow3d, bool allow3dBuildings); void Get3dMode(bool & allow3d, bool & allow3dBuildings); diff --git a/android/jni/com/mapswithme/maps/TrafficState.cpp b/android/jni/com/mapswithme/maps/TrafficState.cpp new file mode 100644 index 0000000000..8ef60e2eea --- /dev/null +++ b/android/jni/com/mapswithme/maps/TrafficState.cpp @@ -0,0 +1,27 @@ +#include "Framework.hpp" + +#include "../core/jni_helper.hpp" + +#include "../platform/Platform.hpp" + +extern "C" +{ +static void TrafficStateChanged(TrafficManager::TrafficState state, shared_ptr const & listener) +{ + JNIEnv * env = jni::GetEnv(); + static jmethodID const = jni::GetMethodID(env, *listener.get(), "onTrafficStateChanged", "(I)V"); + env->CallVoidMethod(*listener, jmethodID, static_cast(state)); +} + +JNIEXPORT void JNICALL +Java_com_mapswithme_maps_TrafficState_nativeSetListener(JNIEnv * env, jclass clazz, jobject listener) +{ + g_framework->SetTrafficStateListener(bind(&TrafficStateChanged, _1, jni::make_global_ref(listener))); +} + +JNIEXPORT void JNICALL +Java_com_mapswithme_maps_TrafficState_nativeRemoveListener(JNIEnv * env, jclass clazz) +{ + g_framework->SetTrafficStateListener(TrafficManager::TrafficStateChangedFn()); +} +} // extern "C" diff --git a/android/src/com/mapswithme/maps/LocationState.java b/android/src/com/mapswithme/maps/LocationState.java index 637b8e0154..5a817b86c8 100644 --- a/android/src/com/mapswithme/maps/LocationState.java +++ b/android/src/com/mapswithme/maps/LocationState.java @@ -46,23 +46,23 @@ public final class LocationState { switch (mode) { - case PENDING_POSITION: - return "PENDING_POSITION"; + case PENDING_POSITION: + return "PENDING_POSITION"; - case NOT_FOLLOW_NO_POSITION: - return "NOT_FOLLOW_NO_POSITION"; + case NOT_FOLLOW_NO_POSITION: + return "NOT_FOLLOW_NO_POSITION"; - case NOT_FOLLOW: - return "NOT_FOLLOW"; + case NOT_FOLLOW: + return "NOT_FOLLOW"; - case FOLLOW: - return "FOLLOW"; + case FOLLOW: + return "FOLLOW"; - case FOLLOW_AND_ROTATE: - return "FOLLOW_AND_ROTATE"; + case FOLLOW_AND_ROTATE: + return "FOLLOW_AND_ROTATE"; - default: - return "Unknown: " + mode; + default: + return "Unknown: " + mode; } } } diff --git a/android/src/com/mapswithme/maps/TrafficState.java b/android/src/com/mapswithme/maps/TrafficState.java new file mode 100644 index 0000000000..171d4351d6 --- /dev/null +++ b/android/src/com/mapswithme/maps/TrafficState.java @@ -0,0 +1,60 @@ +package com.mapswithme.maps; + +public final class TrafficState +{ + public interface StateChangeListener + { + // This method is called from JNI layer. + @SuppressWarnings("unused") + void onTrafficStateChanged(int newMode); + } + + // These values should correspond to + // TrafficManager::TrafficState enum (from map/traffic_manager.hpp) + public static final int DISABLED = 0; + public static final int ENABLED = 1; + public static final int WAITING_DATA = 2; + public static final int OUTDATED = 3; + public static final int NO_DATA = 4; + public static final int NETWORK_ERROR = 5; + public static final int EXPIRED_DATA = 6; + public static final int EXPIRED_APP = 7; + + public static native void nativeSetListener(StateChangeListener listener); + public static native void nativeRemoveListener(); + + private TrafficState() {} + + public static String nameOf(int state) + { + switch (state) + { + case DISABLED: + return "DISABLED"; + + case ENABLED: + return "ENABLED"; + + case WAITING_DATA: + return "WAITING_DATA"; + + case OUTDATED: + return "OUTDATED"; + + case NO_DATA: + return "NO_DATA"; + + case NETWORK_ERROR: + return "NETWORK_ERROR"; + + case EXPIRED_DATA: + return "EXPIRED_DATA"; + + case EXPIRED_APP: + return "EXPIRED_APP"; + + default: + return "Unknown: " + state; + } + } +} diff --git a/map/framework.cpp b/map/framework.cpp index d441e3a876..9fcc720e9c 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -127,7 +127,7 @@ char const kTrafficEnabledKey[] = "TrafficEnabled"; double const kDistEqualQueryMeters = 100.0; -size_t constexpr kMaxTrafficCacheSizeBytes = 256 /* Mb */ * 1024 * 1024; +size_t constexpr kMaxTrafficCacheSizeBytes = 128 /* Mb */ * 1024 * 1024; // Must correspond SearchMarkType. vector kSearchMarks = @@ -237,6 +237,11 @@ void Framework::SetMyPositionModeListener(TMyPositionModeChanged && fn) m_myPositionListener = move(fn); } +TrafficManager & Framework::GetTrafficManager() +{ + return m_trafficManager; +} + bool Framework::IsTrackingReporterEnabled() const { if (m_currentRouterType != routing::RouterType::Vehicle) @@ -318,7 +323,10 @@ void Framework::Migrate(bool keepDownloaded) // If we do not suspend drape, it tries to access framework fields (i.e. m_infoGetter) which are null // while migration is performed. if (m_drapeEngine && m_isRenderingEnabled) + { m_drapeEngine->SetRenderingDisabled(true); + OnDestroyGLContext(); + } m_selectedFeature = FeatureID(); m_searchEngine.reset(); m_infoGetter.reset(); @@ -330,11 +338,13 @@ void Framework::Migrate(bool keepDownloaded) InitCountryInfoGetter(); InitSearchEngine(); RegisterAllMaps(); + + m_trafficManager.SetCurrentDataVersion(GetStorage().GetCurrentDataVersion()); if (m_drapeEngine && m_isRenderingEnabled) { m_drapeEngine->SetRenderingEnabled(); - UpdateDrapeEngine(m_currentModelView.PixelRectIn3d().SizeX(), - m_currentModelView.PixelRectIn3d().SizeY()); + OnRecoverGLContext(m_currentModelView.PixelRectIn3d().SizeX(), + m_currentModelView.PixelRectIn3d().SizeY()); } InvalidateRect(MercatorBounds::FullRect()); } @@ -443,6 +453,8 @@ Framework::Framework() m_model.GetIndex().AddObserver(editor); LOG(LINFO, ("Editor initialized")); + + m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion()); } Framework::~Framework() @@ -1711,7 +1723,7 @@ void Framework::CreateDrapeEngine(ref_ptr contextFactory, m_trafficManager.SetDrapeEngine(make_ref(m_drapeEngine)); } -void Framework::UpdateDrapeEngine(int width, int height) +void Framework::OnRecoverGLContext(int width, int height) { if (m_drapeEngine) { @@ -1722,9 +1734,14 @@ void Framework::UpdateDrapeEngine(int width, int height) UpdatePlacePageInfoForCurrentSelection(); m_drapeApi.Invalidate(); - - m_trafficManager.OnRecover(); } + + m_trafficManager.OnRecoverGLContext(); +} + +void Framework::OnDestroyGLContext() +{ + m_trafficManager.OnDestroyGLContext(); } ref_ptr Framework::GetDrapeEngine() diff --git a/map/framework.hpp b/map/framework.hpp index e2b965113b..34ad4aa94f 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -448,7 +448,8 @@ public: void SetRenderingEnabled(ref_ptr contextFactory = nullptr); void SetRenderingDisabled(bool destroyContext); - void UpdateDrapeEngine(int width, int height); + void OnRecoverGLContext(int width, int height); + void OnDestroyGLContext(); void SetFontScaleFactor(double scaleFactor); @@ -780,6 +781,8 @@ public: void AllowAutoZoom(bool allowAutoZoom); void SaveAutoZoom(bool allowAutoZoom); + TrafficManager & GetTrafficManager(); + bool LoadTrafficEnabled(); void SaveTrafficEnabled(bool trafficEnabled); diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index cf5acfc085..be2c9b4e3e 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -8,16 +8,40 @@ #include "indexer/ftypes_matcher.hpp" #include "indexer/scales.hpp" +#include "platform/platform.hpp" + namespace { -auto const kUpdateInterval = minutes(1); -} // namespace +auto constexpr kUpdateInterval = minutes(1); +auto constexpr kOutdatedDataTimeout = minutes(5) + kUpdateInterval; +auto constexpr kNetworkErrorTimeout = minutes(20); + +auto constexpr kMaxRetriesCount = 5; +} // 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 const & requestTime) + : m_isLoaded(false) + , m_dataSize(0) + , m_lastRequestTime(requestTime) + , m_retriesCount(0) + , m_isWaitingForResponse(false) + , m_lastAvailability(traffic::TrafficInfo::Availability::Unknown) +{} TrafficManager::TrafficManager(GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes) - : m_isEnabled(false) - , m_getMwmsByRectFn(getMwmsByRectFn) + : m_getMwmsByRectFn(getMwmsByRectFn) + , m_currentDataVersion(0) + , m_state(TrafficState::Disabled) , m_maxCacheSizeBytes(maxCacheSizeBytes) - , m_currentCacheSizeBytes(0) , m_isRunning(true) , m_thread(&TrafficManager::ThreadRoutine, this) { @@ -27,18 +51,54 @@ TrafficManager::TrafficManager(GetMwmsByRectFn const & getMwmsByRectFn, size_t m TrafficManager::~TrafficManager() { { - lock_guard lock(m_requestedMwmsLock); + lock_guard lock(m_mutex); m_isRunning = false; } m_condition.notify_one(); m_thread.join(); } +void TrafficManager::SetStateListener(TrafficStateChangedFn const & onStateChangedFn) +{ + GetPlatform().RunOnGuiThread([this, onStateChangedFn]() + { + m_onStateChangedFn = onStateChangedFn; + }); +} + void TrafficManager::SetEnabled(bool enabled) { - m_isEnabled = enabled; + { + lock_guard lock(m_mutex); + if (enabled == IsEnabled()) + { + LOG(LWARNING, ("Invalid attempt to", enabled ? "enable" : "disable", + "traffic manager, it's already", enabled ? "enabled" : "disabled", + ", doing nothing.")); + return; + } + + Clear(); + ChangeState(enabled ? TrafficState::Enabled : TrafficState::Disabled); + } + if (m_drapeEngine != nullptr) m_drapeEngine->EnableTraffic(enabled); + + if (enabled) + { + if (m_currentModelView.second) + UpdateViewport(m_currentModelView.first); + if (m_currentPosition.second) + UpdateMyPosition(m_currentPosition.first); + } +} + +void TrafficManager::Clear() +{ + m_mwmCache.clear(); + m_activeMwms.clear(); + m_requestedMwms.clear(); } void TrafficManager::SetDrapeEngine(ref_ptr engine) @@ -46,19 +106,36 @@ void TrafficManager::SetDrapeEngine(ref_ptr engine) m_drapeEngine = engine; } -void TrafficManager::OnRecover() +void TrafficManager::SetCurrentDataVersion(int64_t dataVersion) { - m_mwmCache.clear(); + m_currentDataVersion = dataVersion; +} - UpdateViewport(m_currentModelView); - UpdateMyPosition(m_currentPosition); +void TrafficManager::OnDestroyGLContext() +{ + if (!IsEnabled()) + return; + + lock_guard lock(m_mutex); + Clear(); +} + +void TrafficManager::OnRecoverGLContext() +{ + if (!IsEnabled()) + return; + + if (m_currentModelView.second) + UpdateViewport(m_currentModelView.first); + if (m_currentPosition.second) + UpdateMyPosition(m_currentPosition.first); } void TrafficManager::UpdateViewport(ScreenBase const & screen) { - m_currentModelView = screen; + m_currentModelView = {screen, true /* initialized */}; - if (!m_isEnabled) + if (!IsEnabled() || IsInvalidState()) return; if (df::GetZoomLevel(screen.GetScale()) < df::kRoadClass0ZoomLevel) @@ -68,13 +145,13 @@ void TrafficManager::UpdateViewport(ScreenBase const & screen) auto mwms = m_getMwmsByRectFn(screen.ClipRect()); { - lock_guard lock(m_requestedMwmsLock); + lock_guard lock(m_mutex); m_activeMwms.clear(); for (auto const & mwm : mwms) { if (mwm.IsAlive()) - m_activeMwms.push_back(mwm); + m_activeMwms.insert(mwm); } RequestTrafficData(); @@ -83,9 +160,9 @@ void TrafficManager::UpdateViewport(ScreenBase const & screen) void TrafficManager::UpdateMyPosition(MyPosition const & myPosition) { - m_currentPosition = myPosition; + m_currentPosition = {myPosition, true /* initialized */}; - if (!m_isEnabled) + if (!IsEnabled() || IsInvalidState()) return; // 1. Determine mwm's nearby "my position". @@ -102,12 +179,17 @@ void TrafficManager::ThreadRoutine() { for (auto const & mwm : mwms) { - traffic::TrafficInfo info(mwm); + traffic::TrafficInfo info(mwm, m_currentDataVersion); if (info.ReceiveTrafficData()) + { OnTrafficDataResponse(info); + } else + { LOG(LWARNING, ("Traffic request failed. Mwm =", mwm)); + OnTrafficRequestFailed(info); + } } mwms.clear(); } @@ -115,13 +197,24 @@ void TrafficManager::ThreadRoutine() bool TrafficManager::WaitForRequest(vector & mwms) { - unique_lock lock(m_requestedMwmsLock); - bool const timeout = !m_condition.wait_for(lock, kUpdateInterval, [this] { return !m_isRunning || !m_requestedMwms.empty(); }); + unique_lock 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) { - mwms = m_activeMwms; + if (!IsEnabled() || IsInvalidState()) + return true; + + mwms.reserve(m_activeMwms.size()); + for (auto const & mwmId : m_activeMwms) + mwms.push_back(mwmId); } else { @@ -141,7 +234,7 @@ void TrafficManager::RequestTrafficData() ASSERT(mwmId.IsAlive(), ()); bool needRequesting = false; - auto currentTime = steady_clock::now(); + auto const currentTime = steady_clock::now(); auto it = m_mwmCache.find(mwmId); if (it == m_mwmCache.end()) @@ -156,6 +249,7 @@ void TrafficManager::RequestTrafficData() if (passedSeconds >= kUpdateInterval) { needRequesting = true; + it->second.m_isWaitingForResponse = true; it->second.m_lastRequestTime = currentTime; } } @@ -163,6 +257,8 @@ void TrafficManager::RequestTrafficData() if (needRequesting) RequestTrafficData(mwmId); } + + UpdateState(); } void TrafficManager::RequestTrafficData(MwmSet::MwmId const & mwmId) @@ -171,16 +267,55 @@ void TrafficManager::RequestTrafficData(MwmSet::MwmId const & mwmId) m_condition.notify_one(); } -void TrafficManager::OnTrafficDataResponse(traffic::TrafficInfo const & info) +void TrafficManager::OnTrafficRequestFailed(traffic::TrafficInfo const & info) { + lock_guard 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_activeMwms.find(info.GetMwmId()) != m_activeMwms.end()) + { + if (it->second.m_retriesCount < kMaxRetriesCount) + { + it->second.m_lastRequestTime = steady_clock::now(); + it->second.m_isWaitingForResponse = true; + RequestTrafficData(info.GetMwmId()); + } + ++it->second.m_retriesCount; + } + else + { + it->second.m_retriesCount = 0; + } + } + + UpdateState(); +} + +void TrafficManager::OnTrafficDataResponse(traffic::TrafficInfo const & info) +{ + lock_guard 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(); + // Update cache. size_t constexpr kElementSize = sizeof(traffic::TrafficInfo::RoadSegmentId) + sizeof(traffic::SpeedGroup); size_t const dataSize = info.GetColoring().size() * kElementSize; - it->second.m_isLoaded = true; m_currentCacheSizeBytes += (dataSize - it->second.m_dataSize); it->second.m_dataSize = dataSize; CheckCacheSize(); @@ -189,6 +324,8 @@ void TrafficManager::OnTrafficDataResponse(traffic::TrafficInfo const & info) df::TrafficSegmentsColoring coloring; coloring[info.GetMwmId()] = info.GetColoring(); m_drapeEngine->UpdateTraffic(coloring); + + UpdateState(); } void TrafficManager::CheckCacheSize() @@ -215,3 +352,85 @@ void TrafficManager::CheckCacheSize() } } } + +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 (auto const & mwmId : m_activeMwms) + { + 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().RunOnGuiThread([this, newState]() + { + if (m_onStateChangedFn != nullptr) + m_onStateChangedFn(newState); + }); +} diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index c8ccc17ace..865fd565e1 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -13,6 +13,7 @@ #include "indexer/index.hpp" #include "indexer/mwm_set.hpp" +#include "std/atomic.hpp" #include "std/chrono.hpp" #include "std/map.hpp" #include "std/mutex.hpp" @@ -28,6 +29,18 @@ class DrapeEngine; class TrafficManager final { public: + enum class TrafficState + { + Disabled, + Enabled, + WaitingData, + Outdated, + NoData, + NetworkError, + ExpiredData, + ExpiredApp + }; + struct MyPosition { m2::PointD m_position = m2::PointD(0.0, 0.0); @@ -40,59 +53,86 @@ public: {} }; + using TrafficStateChangedFn = function; using GetMwmsByRectFn = function(m2::RectD const &)>; TrafficManager(GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes); ~TrafficManager(); + void SetStateListener(TrafficStateChangedFn const & onStateChangedFn); + void SetDrapeEngine(ref_ptr engine); + void SetCurrentDataVersion(int64_t dataVersion); + void SetEnabled(bool enabled); void UpdateViewport(ScreenBase const & screen); void UpdateMyPosition(MyPosition const & myPosition); - void OnRecover(); - - void SetDrapeEngine(ref_ptr engine); + void OnDestroyGLContext(); + void OnRecoverGLContext(); private: void ThreadRoutine(); bool WaitForRequest(vector & mwms); + + void OnTrafficDataResponse(traffic::TrafficInfo const & info); + void OnTrafficRequestFailed(traffic::TrafficInfo const & info); + +private: + // This is a group of methods that haven't their own synchronization inside. void RequestTrafficData(); void RequestTrafficData(MwmSet::MwmId const & mwmId); - void OnTrafficDataResponse(traffic::TrafficInfo const & info); + + void Clear(); void CheckCacheSize(); - bool m_isEnabled; + void UpdateState(); + void ChangeState(TrafficState newState); + + bool IsInvalidState() const; + bool IsEnabled() const; GetMwmsByRectFn m_getMwmsByRectFn; ref_ptr m_drapeEngine; + atomic m_currentDataVersion; - MyPosition m_currentPosition; - ScreenBase m_currentModelView; + // These fields have a flag of their initialization. + pair m_currentPosition = {MyPosition(), false}; + pair m_currentModelView = {ScreenBase(), false}; + + atomic m_state; + TrafficStateChangedFn m_onStateChangedFn; struct CacheEntry { - CacheEntry() = default; + CacheEntry(); + CacheEntry(time_point const & requestTime); + + bool m_isLoaded; + size_t m_dataSize; - CacheEntry(time_point const & requestTime) : m_lastRequestTime(requestTime) {} - bool m_isLoaded = false; time_point m_lastSeenTime; time_point m_lastRequestTime; - size_t m_dataSize = 0; + time_point m_lastResponseTime; + + int m_retriesCount; + bool m_isWaitingForResponse; + + traffic::TrafficInfo::Availability m_lastAvailability; }; size_t m_maxCacheSizeBytes; - size_t m_currentCacheSizeBytes; + size_t m_currentCacheSizeBytes = 0; map m_mwmCache; bool m_isRunning; condition_variable m_condition; - vector m_activeMwms; + set m_activeMwms; vector m_requestedMwms; - mutex m_requestedMwmsLock; + mutex m_mutex; thread m_thread; }; diff --git a/traffic/traffic_info.cpp b/traffic/traffic_info.cpp index ff62864e68..e02c00e900 100644 --- a/traffic/traffic_info.cpp +++ b/traffic/traffic_info.cpp @@ -4,12 +4,14 @@ #include "coding/bit_streams.hpp" #include "coding/reader.hpp" +#include "coding/url_encode.hpp" #include "coding/varint.hpp" #include "coding/write_to_sink.hpp" #include "coding/writer.hpp" #include "base/assert.hpp" #include "base/logging.hpp" +#include "base/string_utils.hpp" #include "std/algorithm.hpp" #include "std/string.hpp" @@ -22,18 +24,28 @@ namespace traffic { namespace { -bool ReadRemoteFile(string const & url, vector & result) +bool ReadRemoteFile(string const & url, vector & contents, int & errorCode) { platform::HttpClient request(url); - if (!request.RunHttpRequest() || request.ErrorCode() != 200) + if (!request.RunHttpRequest()) { - LOG(LINFO, ("Traffic request", url, "failed. HTTP Error:", request.ErrorCode())); + errorCode = request.ErrorCode(); + LOG(LINFO, ("Couldn't run traffic request", url, ". Error:", errorCode)); return false; } - string const & response = request.ServerResponse(); - result.resize(response.size()); - for (size_t i = 0; i < response.size(); ++i) - result[i] = static_cast(response[i]); + + errorCode = request.ErrorCode(); + + string const & result = request.ServerResponse(); + contents.resize(result.size()); + memcpy(contents.data(), result.data(), result.size()); + + if (errorCode != 200) + { + LOG(LINFO, ("Traffic request", url, "failed. HTTP Error:", errorCode)); + return false; + } + return true; } @@ -46,7 +58,7 @@ string MakeRemoteURL(string const & name, uint64_t version) ss << TRAFFIC_DATA_BASE_URL; if (version != 0) ss << version << "/"; - ss << name << TRAFFIC_FILE_EXTENSION; + ss << UrlEncode(name) << TRAFFIC_FILE_EXTENSION; return ss.str(); } } // namespace @@ -60,7 +72,10 @@ TrafficInfo::RoadSegmentId::RoadSegmentId(uint32_t fid, uint16_t idx, uint8_t di } // TrafficInfo -------------------------------------------------------------------------------- -TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId) : m_mwmId(mwmId) {} +TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion) + : m_mwmId(mwmId) + , m_currentDataVersion(currentDataVersion) +{} bool TrafficInfo::ReceiveTrafficData() { @@ -74,8 +89,29 @@ bool TrafficInfo::ReceiveTrafficData() return false; vector contents; - if (!ReadRemoteFile(url, contents)) + int errorCode; + if (!ReadRemoteFile(url, contents, errorCode)) + { + if (errorCode == 404) + { + string const result(reinterpret_cast(contents.data()), contents.size()); + + int64_t version = 0; + strings::to_int64(result.c_str(), version); + + if (version > info->GetVersion() && version <= m_currentDataVersion) + m_availability = Availability::ExpiredData; + else if (version > m_currentDataVersion) + m_availability = Availability::ExpiredApp; + else + m_availability = Availability::NoData; + } + else + { + m_availability = Availability::Unknown; + } return false; + } Coloring coloring; try @@ -84,11 +120,13 @@ bool TrafficInfo::ReceiveTrafficData() } catch (Reader::Exception const & e) { - LOG(LINFO, ("Could not read traffic data received from server. MWM:", info->GetCountryName(), - "Version:", info->GetVersion())); + m_availability = Availability::NoData; + LOG(LWARNING, ("Could not read traffic data received from server. MWM:", info->GetCountryName(), + "Version:", info->GetVersion())); return false; } m_coloring.swap(coloring); + m_availability = Availability::IsAvailable; return true; } diff --git a/traffic/traffic_info.hpp b/traffic/traffic_info.hpp index b1b5a419c3..a07e4d8cc4 100644 --- a/traffic/traffic_info.hpp +++ b/traffic/traffic_info.hpp @@ -16,6 +16,15 @@ namespace traffic class TrafficInfo { public: + enum class Availability + { + IsAvailable, + NoData, + ExpiredData, + ExpiredApp, + Unknown + }; + struct RoadSegmentId { // m_dir can be kForwardDirection or kReverseDirection. @@ -56,7 +65,7 @@ public: TrafficInfo() = default; - TrafficInfo(MwmSet::MwmId const & mwmId); + TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion); // Fetches the latest traffic data from the server and updates the coloring. // Construct the url by passing an MwmId. @@ -68,6 +77,7 @@ public: MwmSet::MwmId const & GetMwmId() const { return m_mwmId; } Coloring const & GetColoring() const { return m_coloring; } + Availability GetAvailability() const { return m_availability; } static void SerializeTrafficData(Coloring const & coloring, vector & result); @@ -77,5 +87,7 @@ private: // The mapping from feature segments to speed groups (see speed_groups.hpp). Coloring m_coloring; MwmSet::MwmId m_mwmId; + Availability m_availability = Availability::Unknown; + int64_t m_currentDataVersion = 0; }; } // namespace traffic diff --git a/traffic/traffic_tests/traffic_info_test.cpp b/traffic/traffic_tests/traffic_info_test.cpp index 84d1bcf0cd..5b19f599ec 100644 --- a/traffic/traffic_tests/traffic_info_test.cpp +++ b/traffic/traffic_tests/traffic_info_test.cpp @@ -50,21 +50,21 @@ UNIT_TEST(TrafficInfo_RemoteFile) TestMwmSet mwmSet; auto const & r = mwmSet.Register(platform::LocalCountryFile::MakeForTesting("traffic_data_test")); - TrafficInfo trafficInfo(r.first); + TrafficInfo trafficInfo(r.first, r.first.GetInfo()->GetVersion()); TEST(trafficInfo.ReceiveTrafficData(), ()); } { TestMwmSet mwmSet; auto const & r = mwmSet.Register(platform::LocalCountryFile::MakeForTesting("traffic_data_test2")); - TrafficInfo trafficInfo(r.first); + TrafficInfo trafficInfo(r.first, r.first.GetInfo()->GetVersion()); TEST(!trafficInfo.ReceiveTrafficData(), ()); } { TestMwmSet mwmSet; auto const & r = mwmSet.Register(platform::LocalCountryFile::MakeForTesting("traffic_data_test", 101010)); - TrafficInfo trafficInfo(r.first); + TrafficInfo trafficInfo(r.first, r.first.GetInfo()->GetVersion()); TEST(trafficInfo.ReceiveTrafficData(), ()); } }