From fb61ac89e62a7e78cf112d1a021c62d369500790 Mon Sep 17 00:00:00 2001 From: Daria Volvenkova Date: Tue, 29 Nov 2016 17:02:29 +0300 Subject: [PATCH] Logic of traffic manager's states. --- android/jni/com/mapswithme/maps/Framework.cpp | 16 +- android/jni/com/mapswithme/maps/Framework.hpp | 6 + .../jni/com/mapswithme/maps/TrafficState.cpp | 27 ++ .../com/mapswithme/maps/LocationState.java | 24 +- .../src/com/mapswithme/maps/TrafficState.java | 60 +++++ map/framework.cpp | 29 +- map/framework.hpp | 5 +- map/traffic_manager.cpp | 253 ++++++++++++++++-- map/traffic_manager.hpp | 59 +++- traffic/traffic_info.cpp | 54 +++- traffic/traffic_info.hpp | 14 +- traffic/traffic_tests/traffic_info_test.cpp | 6 +- 12 files changed, 488 insertions(+), 65 deletions(-) create mode 100644 android/jni/com/mapswithme/maps/TrafficState.cpp create mode 100644 android/src/com/mapswithme/maps/TrafficState.java 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 85446f0d11..c4f12f51e2 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..0f65b1b129 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -1,5 +1,7 @@ #include "map/traffic_manager.hpp" +#include "platform/platform.hpp" + #include "routing/routing_helpers.hpp" #include "drape_frontend/drape_engine.hpp" @@ -10,14 +12,18 @@ 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::TrafficManager(GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes) - : m_isEnabled(false) - , m_getMwmsByRectFn(getMwmsByRectFn) + : m_getMwmsByRectFn(getMwmsByRectFn) + , m_state(TrafficState::Disabled) + , m_notifyStateChanged(false) , m_maxCacheSizeBytes(maxCacheSizeBytes) - , m_currentCacheSizeBytes(0) , m_isRunning(true) , m_thread(&TrafficManager::ThreadRoutine, this) { @@ -34,11 +40,41 @@ TrafficManager::~TrafficManager() 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_requestedMwmsLock); + 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); + } + + NotifyStateChanged(); +} + +void TrafficManager::Clear() +{ + m_mwmCache.clear(); + m_activeMwms.clear(); + m_requestedMwms.clear(); } void TrafficManager::SetDrapeEngine(ref_ptr engine) @@ -46,19 +82,38 @@ 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_requestedMwmsLock); + 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}; - if (!m_isEnabled) + if (!IsEnabled() || IsInvalidState()) return; if (df::GetZoomLevel(screen.GetScale()) < df::kRoadClass0ZoomLevel) @@ -74,18 +129,20 @@ void TrafficManager::UpdateViewport(ScreenBase const & screen) for (auto const & mwm : mwms) { if (mwm.IsAlive()) - m_activeMwms.push_back(mwm); + m_activeMwms.insert(mwm); } RequestTrafficData(); } + + NotifyStateChanged(); } void TrafficManager::UpdateMyPosition(MyPosition const & myPosition) { - m_currentPosition = myPosition; + m_currentPosition = {myPosition, true}; - if (!m_isEnabled) + if (!IsEnabled() || IsInvalidState()) return; // 1. Determine mwm's nearby "my position". @@ -102,12 +159,19 @@ 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); + } + + NotifyStateChanged(); } mwms.clear(); } @@ -116,12 +180,20 @@ 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(); }); + + bool const timeout = !m_condition.wait_for(lock, kUpdateInterval, [this] + { + return !m_isRunning || !m_requestedMwms.empty(); + }); + if (!m_isRunning) return false; - if (timeout) + + if (timeout && IsEnabled() && !IsInvalidState()) { - mwms = m_activeMwms; + mwms.reserve(m_activeMwms.size()); + for (auto const & mwmId : m_activeMwms) + mwms.push_back(mwmId); } else { @@ -141,7 +213,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 +228,7 @@ void TrafficManager::RequestTrafficData() if (passedSeconds >= kUpdateInterval) { needRequesting = true; + it->second.m_isWaitingForResponse = true; it->second.m_lastRequestTime = currentTime; } } @@ -163,6 +236,8 @@ void TrafficManager::RequestTrafficData() if (needRequesting) RequestTrafficData(mwmId); } + + UpdateState(); } void TrafficManager::RequestTrafficData(MwmSet::MwmId const & mwmId) @@ -171,16 +246,57 @@ 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_requestedMwmsLock); + 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) + { + if (!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_requestedMwmsLock); + + 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 +305,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 +333,94 @@ 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 expiredMwm = 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; + expiredMwm |= it->second.m_lastAvailability == traffic::TrafficInfo::Availability::ExpiredMwm; + 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 (expiredMwm) + 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; + m_notifyStateChanged = true; +} + +void TrafficManager::NotifyStateChanged() +{ + if (m_notifyStateChanged) + { + m_notifyStateChanged = false; + + GetPlatform().RunOnGuiThread([this]() + { + if (m_onStateChangedFn != nullptr) + m_onStateChangedFn(m_state); + }); + } +} diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index c8ccc17ace..87da904521 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,57 +53,85 @@ 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 RequestTrafficData(); void RequestTrafficData(MwmSet::MwmId const & mwmId); + void OnTrafficDataResponse(traffic::TrafficInfo const & info); + void OnTrafficRequestFailed(traffic::TrafficInfo const & info); + + void Clear(); void CheckCacheSize(); - bool m_isEnabled; + void UpdateState(); + void ChangeState(TrafficState newState); + void NotifyStateChanged(); + + bool IsInvalidState() const; + bool IsEnabled() const; GetMwmsByRectFn m_getMwmsByRectFn; ref_ptr m_drapeEngine; + int64_t m_currentDataVersion = 0; - MyPosition m_currentPosition; - ScreenBase m_currentModelView; + pair m_currentPosition = {MyPosition(), false}; + pair m_currentModelView = {ScreenBase(), false}; + + atomic m_state; + atomic m_notifyStateChanged; + TrafficStateChangedFn m_onStateChangedFn; struct CacheEntry { CacheEntry() = default; CacheEntry(time_point const & requestTime) : m_lastRequestTime(requestTime) {} + bool m_isLoaded = false; + size_t m_dataSize = 0; + time_point m_lastSeenTime; time_point m_lastRequestTime; - size_t m_dataSize = 0; + time_point m_lastResponseTime; + + uint32_t m_retriesCount = 0; + bool m_isWaitingForResponse = false; + + traffic::TrafficInfo::Availability m_lastAvailability = + traffic::TrafficInfo::Availability::Unknown; }; 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; diff --git a/traffic/traffic_info.cpp b/traffic/traffic_info.cpp index ff62864e68..a7e56bceab 100644 --- a/traffic/traffic_info.cpp +++ b/traffic/traffic_info.cpp @@ -22,18 +22,25 @@ namespace traffic { namespace { -bool ReadRemoteFile(string const & url, vector & result) +bool ReadRemoteFile(string const & url, string & result, 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())); + LOG(LINFO, ("Couldn't run traffic request", url)); + errorCode = -1; 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(); + result = request.ServerResponse(); + + if (errorCode != 200) + { + LOG(LINFO, ("Traffic request", url, "failed. HTTP Error:", errorCode)); + return false; + } + return true; } @@ -60,7 +67,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 const & currentDataVersion) + : m_mwmId(mwmId) + , m_currentDataVersion(currentDataVersion) +{} bool TrafficInfo::ReceiveTrafficData() { @@ -73,9 +83,32 @@ bool TrafficInfo::ReceiveTrafficData() if (url.empty()) return false; - vector contents; - if (!ReadRemoteFile(url, contents)) + string result; + int errorCode; + if (!ReadRemoteFile(url, result, errorCode)) + { + if (errorCode == 404) + { + int64_t version = atoi(result.c_str()); + + if (version > info->GetVersion() && version <= m_currentDataVersion) + m_availabilityStatus = Availability::ExpiredMwm; + else if (version > m_currentDataVersion) + m_availabilityStatus = Availability::ExpiredApp; + else + m_availabilityStatus = Availability::NoData; + } + else + { + m_availabilityStatus = Availability::Unknown; + } return false; + } + + vector contents; + contents.resize(result.size()); + for (size_t i = 0; i < result.size(); ++i) + contents[i] = static_cast(result[i]); Coloring coloring; try @@ -89,6 +122,7 @@ bool TrafficInfo::ReceiveTrafficData() return false; } m_coloring.swap(coloring); + m_availabilityStatus = Availability::IsAvailable; return true; } diff --git a/traffic/traffic_info.hpp b/traffic/traffic_info.hpp index b1b5a419c3..92de4f6b2a 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, + ExpiredMwm, + 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 const & 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_availabilityStatus; } 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_availabilityStatus = 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(), ()); } }