Files
comaps/map/traffic_manager.hpp
mvglasow d03b47bee0 [traffic] Refactoring
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00

782 lines
27 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.

#pragma once
#include "traffic/traffic_info.hpp"
#include "drape_frontend/drape_engine_safe_ptr.hpp"
#include "drape_frontend/traffic_generator.hpp"
#include "drape/pointers.hpp"
#include "indexer/mwm_set.hpp"
#include "routing/routing_session.hpp"
#include "storage/country_info_getter.hpp"
#include "traffxml/traff_decoder.hpp"
#include "traffxml/traff_model.hpp"
#include "traffxml/traff_source.hpp"
#include "traffxml/traff_storage.hpp"
#include "geometry/point2d.hpp"
#include "geometry/polyline2d.hpp"
#include "geometry/screenbase.hpp"
#include "base/thread.hpp"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <map>
#include <mutex>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
class TrafficManager final : public traffxml::TraffSourceManager
{
public:
using CountryInfoGetterFn = std::function<storage::CountryInfoGetter const &()>;
using CountryParentNameGetterFn = std::function<std::string(std::string const &)>;
/**
* @brief Global state of traffic information.
*/
// TODO apart from `Disabled` and `Enabled`, all states are obsolete
enum class TrafficState
{
/** Traffic is disabled, no traffic data will be retrieved or considered for routing. */
Disabled,
/** Traffic is enabled and working normally (the first request may not have been scheduled yet). */
Enabled,
/** At least one request is currently pending. */
WaitingData,
/** At least one MWM has stale traffic data. */
Outdated,
/** Traffic data for at least one MWM was invalid or not found on the server. */
NoData,
/** At least one request failed or timed out. */
NetworkError,
/** Traffic data could not be retrieved because the map data is outdated. */
ExpiredData,
/** Traffic data could not be retrieved because the app version is outdated. */
ExpiredApp
};
/**
* @brief The mode for the traffic manager.
*
* Future versions may introduce further test modes. Therefore, always use `TrafficManager::IsTestMode()`
* to verify if the traffic manager is running in test mode.
*/
enum class Mode
{
/**
* Traffic manager mode for normal operation.
*
* This is the default mode unless something else is explicitly set.
*/
Normal,
/**
* Test mode.
*
* This mode will prevent the traffic manager from automatically subscribing to sources and
* polling them. It will still receive and process push feeds.
*
* Future versions may introduce further behavior changes, and/or introduce more test modes.
*/
Test
};
struct MyPosition
{
m2::PointD m_position = m2::PointD(0.0, 0.0);
bool m_knownPosition = false;
MyPosition() = default;
MyPosition(m2::PointD const & position)
: m_position(position),
m_knownPosition(true)
{}
};
using TrafficStateChangedFn = std::function<void(TrafficState)>;
using GetMwmsByRectFn = std::function<std::vector<MwmSet::MwmId>(m2::RectD const &)>;
TrafficManager(DataSource & dataSource,
CountryInfoGetterFn countryInfoGetter,
CountryParentNameGetterFn const & countryParentNameGetter,
GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes,
routing::RoutingSession & routingSession);
~TrafficManager();
void Teardown();
/**
* @brief Returns a copy of the cache of all currently active TraFF messages.
*
* For testing purposes.
*
* Keys are message IDs, values are messages.
*
* This method is safe to call from any thread.
*/
std::map<std::string, traffxml::TraffMessage> GetMessageCache();
TrafficState GetState() const;
void SetStateListener(TrafficStateChangedFn const & onStateChangedFn);
void SetDrapeEngine(ref_ptr<df::DrapeEngine> engine);
/**
* @brief Sets the version of the MWM used locally.
*/
void SetCurrentDataVersion(int64_t dataVersion);
/**
* @brief Enables or disables the traffic manager.
*
* This sets the internal state and notifies the drape engine.
*
* Upon creation, the traffic manager is disabled and will not poll any sources or process any
* feeds until enabled. Feeds received through `Push()` will be added to the queue before the
* traffic manager is started, but will not be processed any further until the traffic manager is
* started.
*
* MWMs must be loaded before first enabling the traffic manager.
*
* Calling this function with `enabled` identical to the current state is a no-op.
*
* @todo Currently, all MWMs must be loaded before calling `SetEnabled()`, as MWMs loaded after
* that will not get picked up. We need to extend `TrafficManager` to react to MWMs being added
* (and removed) note that this affects the `DataSource`, not the set of active MWMs.
*
* @todo State/pause/resume logic is not fully implemented ATM and needs to be revisited.
*
* @param enabled True to enable, false to disable
*/
void SetEnabled(bool enabled);
/**
* @brief Whether the traffic manager is enabled.
*
* @return True if enabled, false if not
*/
bool IsEnabled() const;
/**
* @brief Starts the traffic manager.
*
*/
void Start();
void UpdateViewport(ScreenBase const & screen);
void UpdateMyPosition(MyPosition const & myPosition);
/**
* @brief Recalculates the TraFF subscription area.
*
* The subscription area needs to be recalculated when the traffic manager goes from disabled to
* enabled, or when it is resumed after being paused, as the subscription area is not updated
* while the traffic manager is disabled or paused.
*
* If the subscription are has changed, this triggers a change of the active TraFF subscription.
*
* No traffic data is discarded, but sources will be polled for an update, which may turn out
* larger than usual if the traffic manager was in disabled/paused state for an extended period of
* time or the subscription area has changed.
*
* @todo Routing is currently not considered (active MWMs are based on viewport and current
* position).
*/
void RecalculateSubscription();
/**
* @brief Invalidates traffic information for the specified MWM.
*
* Invalidation of traffic data is always per MWM and affects locations which refer to any version
* of this MWM, or whose enclosing rectangle overlaps with that of the MWM. The decoded segments
* for these locations are discarded and decoded again, ensuring they are based on the new MWM.
* The TraFF messages themselves remain unchanged.
*
* @param mwmId The newly addded MWM.
*/
void Invalidate(MwmSet::MwmId const & mwmId);
void OnDestroySurface();
void OnRecoverSurface();
void OnMwmDeregistered(platform::LocalCountryFile const & countryFile);
void OnEnterForeground();
void OnEnterBackground();
void SetSimplifiedColorScheme(bool simplified);
bool HasSimplifiedColorScheme() const { return m_hasSimplifiedColorScheme; }
/**
* @brief Whether the traffic manager is operating in test mode.
*/
bool IsTestMode() { return m_mode != Mode::Normal; }
/**
* @brief Switches the traffic manager into test mode.
*
* The mode can only be set before the traffic manager is first enabled. After that, this method
* will log a warning but otherwise do nothing.
*
* In test mode, the traffic manager will not subscribe to sources or poll them automatically.
* Expired messages will not get purged automatically, but `PurgeExpiredMessages()` can be called
* to purge expired messages once. The traffic manager will still receive and process push feeds.
*
* Future versions may introduce further behavior changes.
*/
void SetTestMode();
/**
* @brief Processes a traffic feed.
*
* The feed may be a result of a pull operation, or received through a push operation.
* (Push operations are not supported by all sources.)
*
* This method is safe to call from any thread.
*
* @param feed The traffic feed.
*/
virtual void ReceiveFeed(traffxml::TraffFeed feed) override;
/**
* @brief Registers a `TraffSource`.
* @param source The source.
*/
virtual void RegisterSource(std::unique_ptr<traffxml::TraffSource> source) override;
/**
* @brief Retrieves all currently active MWMs.
*
* This method retrieves all MWMs in the viewport, within a certain distance of the current
* position (if there is a valid position) or part of the route (if any), and stores them in
* `activeMwms`.
*
* This method locks `m_mutex` and is therefore safe to call from any thread. Callers which
* already hold `m_mutex` can use the private `UniteActiveMwms()` method instead.
*
* @param activeMwms Retrieves the list of active MWMs.
*/
virtual void GetActiveMwms(std::set<MwmSet::MwmId> & activeMwms) override;
/**
* @brief Purges expired messages from the cache.
*
* This method is safe to call from any thread, except for the traffic worker thread.
*/
void PurgeExpiredMessages();
/**
* @brief Clears the entire traffic cache.
*
* This is currently called when the traffic manager is enabled or disabled.
*
* The old MWM traffic architecture was somewhat liberal in clearing its cache and re-fetching
* traffic data. This was possible because data was pre-processed and required no processing
* beyond deserialization, whereas TraFF data is more expensive to recreate. Also, the old
* architecture lacked any explicit notion of expiration; the app decided that data was to be
* considered stale after a certain period of time. TraFF, in contrast, has an explicit expiration
* time for each message, which can be anywhere from a few minutes to several weeks or months.
* Messages that have expired get deleted individually.
* For this reason, the TraFF message cache should not be cleared out under normal conditions
* (the main exception being tests).
*
* @todo Currently not implemented for TraFF; implement it for test purposes but do not call when
* the enabled state changes.
*/
void Clear();
private:
// TODO no longer needed
#ifdef traffic_dead_code
/**
* @brief Holds information about pending or previous traffic requests pertaining to an MWM.
*/
struct CacheEntry
{
CacheEntry();
explicit CacheEntry(std::chrono::time_point<std::chrono::steady_clock> const & requestTime);
/**
* @brief Whether we have traffic data for this MWM.
*/
bool m_isLoaded;
/**
* @brief The amount of memory occupied by the coloring for this MWM.
*/
size_t m_dataSize;
/**
* @brief When the last update request occurred, not including forced updates.
*
* This timestamp is the basis for eliminating the oldest entries from the cache.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastActiveTime;
/**
* @brief When the last update request occurred, including forced updates.
*
* This timestamp is the basis for determining whether an update is needed.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastRequestTime;
/**
* @brief When the last response was received.
*
* This timestamp is the basis for determining whether a network request timed out, or if data is outdated.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastResponseTime;
/**
* @brief The number of failed traffic requests for this MWM.
*
* Reset when the MWM becomes inactive.
*/
int m_retriesCount;
/**
* @brief Whether a request is currently pending for this MWM.
*
* Set to `true` when a request is scheduled, reverted to `false` when a response is received or the request fails.
*/
bool m_isWaitingForResponse;
traffic::TrafficInfo::Availability m_lastAvailability;
};
#endif
/**
* @brief Ensures every TraFF source has a subscription covering all currently active MWMs.
*
* This method cycles through all TraFF sources in `m_trafficSources` and calls
* `SubscribeOrChangeSubscription()` on each of them.
*/
void SubscribeOrChangeSubscription();
/**
* @brief Unsubscribes from all traffic services we are subscribed to.
*
* This method cycles through all TraFF sources in `m_trafficSources` and calls `Unsubscribe()`
* on each of them.
*/
void Unsubscribe();
/**
* @brief Restores the message cache from file storage.
*
* @note The caller must lock `m_mutex` prior to calling this function, as it makes unprotected
* changes to shared data structures.
*
* @note The return value indicates whether actions related to a traffic update should be taken,
* such as notifying the routing and drape engine. It is true if at least one message with a
* decoded location was read, and no messages without decoded locations. If messages without a
* decoded location were read, the return value is false, as the location decoding will trigger
* updates by itself. If errors occurred and no messages are read, the return value is also false.
*
* @return True if a traffic update needs to be sent, false if not
*/
bool RestoreCache();
/**
* @brief Polls all traffic services for updates.
*
* This method cycles through all TraFF sources in `m_trafficSources` and calls `IsPollNeeded()`
* on each of them. If this method returns true, it then calls `Poll()` on the source.
*/
void Poll();
/**
* @brief Purges expired messages from the cache.
*
* This is the internal conterpart of `PurgeExpiredMessages()`. It is safe to call from any
* thread. Unlike `PurgeExpiredMessages()`, it does not wake the worker thread, making it suitable
* for use on the worker thread.
*
* @return true if messages were purged, false if not
*/
bool PurgeExpiredMessagesImpl();
/**
* @brief Consolidates the feed queue.
*
* If multiple feeds in the queue have the same message ID, only the message with the newest
* update time is kept (if two messages have the same ID and update time, the one in the feed
* with the higher index is kept); other messages with the same ID are discarded. Empty feeds
* are discarded.
*/
void ConsolidateFeedQueue();
/**
* @brief Removes the first message from the first feed and decodes it.
*/
void DecodeFirstMessage();
/**
* @brief Event loop for the traffic worker thread.
*
* This method runs an event loop, which blocks until woken up or a timeout equivalent to the
* update interval elapses. It cycles through the list of MWMs for which updates have been
* scheduled, triggering a network request for each and processing the result.
*/
void ThreadRoutine();
/**
* @brief Blocks until a request for traffic data is received or a timeout expires.
*
* This method acts as the loop condition for `ThreadRoutine()`. It blocks until woken up or the
* update interval expires. In the latter case, it calls `RequestTrafficData()` to insert all
* currently active MWMs into the list of MWMs to update; otherwise, it leaves the list as it is.
* In either case, it populates `mwms` with the list and returns.
*
* @param mwms Receives a list of MWMs for which to update traffic data.
* @return `true` during normal operation, `false` during teardown (signaling the event loop to exit).
*/
// TODO mwms argument is no longer needed
bool WaitForRequest();
/**
* @brief Processes new traffic data.
*
* The new per-MWM colorings (preprocessed traffic information) are taken from `m_allMmColoring`.
* `m_allMwmColoring` is rebuilt from per-message colorings in `m_messageCache` as needed.
*
* This method is normally called from the traffic worker thread. Test tools may also call it from
* other threads.
*/
void OnTrafficDataUpdate();
// TODO no longer needed
#ifdef traffic_dead_code
void OnTrafficDataResponse(traffic::TrafficInfo && info);
/**
* @brief Processes a failed traffic request.
*
* This method gets called when a traffic request has failed.
*
* It updates the `m_isWaitingForResponse` and `m_lastAvailability` of `info.
*
* If the MWM is no longer active, this method returns immediately after that.
*
* If the retry limit has not been reached, the MWM is re-inserted into the list by calling
* `RequestTrafficSubscription(MwmSet::MwmId, bool)` with `force` set to true. Otherwise, the retry count
* is reset and the state updated accordingly.
*
* @param info
*/
void OnTrafficRequestFailed(traffic::TrafficInfo && info);
#endif
/**
* @brief Updates `activeMwms` and requests traffic data.
*
* The old and new list of active MWMs may refer either to those used by the rendering engine
* (`m_lastDrapeMwmsByRect`/`m_activeDrapeMwms`) or to those around the current position.
* (`m_lastPositionMwmsByRect`/`m_activePositionMwms`).
*
* The method first determines the list of MWMs overlapping with `rect`. If it is identical to
* `lastMwmsByRect`, the method returns immediately. Otherwise, it stores the new set in
* `lastMwmsByRect` and populates `activeMwms` with the elements.
*
* This method locks `m_mutex` while populating `activeMwms`. There is no need for the caller to
* do that.
*
* @param rect Rectangle covering the new active MWM set.
* @param lastMwmsByRect Set of active MWMs, see description.
* @param activeMwms Vector of active MWMs, see description.
*/
void UpdateActiveMwms(m2::RectD const & rect, std::vector<MwmSet::MwmId> & lastMwmsByRect,
std::set<MwmSet::MwmId> & activeMwms);
// This is a group of methods that haven't their own synchronization inside.
// TODO no longer needed
#ifdef traffic_dead_code
/**
* @brief Requests a refresh of traffic subscriptions to match all currently active MWMs.
*
* The actual call to the TraFF sources is performed asynchronously on a separate thread.
*
* The method does nothing if the `TrafficManager` instance is disabled, paused, in an invalid
* state (`NetworkError`) or if neither the rendering engine nor the routing engine have any
* active MWMs.
*
* This method is unsynchronized; the caller must lock `m_mutex` prior to calling it.
*/
void RequestTrafficSubscription();
/**
* @brief Removes traffic data for one specific MWM from the cache.
*
* This would be used when an MWM file gets deregistered and its traffic data is no longer needed.
* With the old MWM traffic architecture (pre-processed sets of segments), this method was also
* used to shrink the cache to stay below a certain size (no longer possible with TraFF, due to
* the data structures being more complex, and also due to re-fetching data being expensive in
* terms of computing time).
*
* @param mwmId The mwmId for which to remove traffic data.
*/
void ClearCache(MwmSet::MwmId const & mwmId);
void ShrinkCacheToAllowableSize();
/**
* @brief Updates the state of the traffic manager based on the state of all MWMs used by the renderer.
*
* This method cycles through the state of all MWMs used by the renderer (MWMs used by the
* routing engine but not by the rendering engine are not considered), examines their traffic
* state and sets the global state accordingly.
*
* For a description of states, see `TrafficState`. The order of states is as follows, the first
* state whose conditions are fulfilled becomes the new state: `TrafficState::NetworkError`,
* `TrafficState::WaitingData`, `TrafficState::ExpiredApp`, `TrafficState::ExpiredData`,
* `TrafficState::NoData`, `TrafficState::Outdated`, `TrafficState::Enabled`.
*/
void UpdateState();
#endif
void ChangeState(TrafficState newState);
bool IsInvalidState() const;
void OnChangeRoutingSessionState(routing::SessionState previous, routing::SessionState current);
/**
* @brief Retrieves all currently active MWMs.
*
* This method retrieves all MWMs in the viewport, within a certain distance of the current
* position (if there is a valid position) or part of the route (if any), and stores them in
* `activeMwms`.
*
* The caller must hold `m_mutex` prior to calling this method. `GetActiveMwms()` is available
* as a convenience wrapper which locks `m_mutex`, calls this method and releases it.
*
* @param activeMwms Retrieves the list of active MWMs.
*/
void UniteActiveMwms(std::set<MwmSet::MwmId> & activeMwms) const;
void Pause();
void Resume();
template <class F>
void ForEachMwm(F && f) const
{
std::vector<std::shared_ptr<MwmInfo>> allMwmInfo;
m_dataSource.GetMwmsInfo(allMwmInfo);
std::for_each(allMwmInfo.begin(), allMwmInfo.end(), std::forward<F>(f));
}
template <class F>
void ForEachActiveMwm(F && f) const
{
std::set<MwmSet::MwmId> activeMwms;
UniteActiveMwms(activeMwms);
std::for_each(activeMwms.begin(), activeMwms.end(), std::forward<F>(f));
}
DataSource & m_dataSource;
CountryInfoGetterFn m_countryInfoGetterFn;
CountryParentNameGetterFn m_countryParentNameGetterFn;
GetMwmsByRectFn m_getMwmsByRectFn;
/*
* Originally this was m_observer, of type traffic::TrafficObserver. Since routing::RoutingSession
* inherits from that class, and an interface to the routing session is needed in order to
* determine the MWMs for which we need traffic information, the type was changed and the member
* renamed to reflect that.
*/
routing::RoutingSession & m_routingSession;
df::DrapeEngineSafePtr m_drapeEngine;
std::atomic<int64_t> m_currentDataVersion;
// These fields have a flag of their initialization.
std::pair<MyPosition, bool> m_currentPosition = {MyPosition(), false};
std::pair<ScreenBase, bool> m_currentModelView = {ScreenBase(), false};
/**
* The mode in which the traffic manager is running.
*/
Mode m_mode = Mode::Normal;
/**
* Whether the traffic manager accepts mode changes.
*
* Mode cannot be set after the traffic manager has been enabled for the first time.
*/
bool m_canSetMode = true;
std::atomic<TrafficState> m_state;
TrafficStateChangedFn m_onStateChangedFn;
bool m_hasSimplifiedColorScheme = true;
// TODO no longer needed
#ifdef traffic_dead_code
size_t m_maxCacheSizeBytes;
size_t m_currentCacheSizeBytes = 0;
std::map<MwmSet::MwmId, CacheEntry> m_mwmCache;
#endif
/**
* @brief The TraFF sources from which we get traffic information.
*
* Threads must lock `m_trafficSourceMutex` prior to accessing this member.
*/
std::vector<std::unique_ptr<traffxml::TraffSource>> m_trafficSources;
bool m_isRunning;
std::condition_variable m_condition;
/*
* To determine for which MWMs we need traffic data, we need to keep track of 3 groups of MWMs:
* those used by the renderer (i.e. in or just around the viewport), those within a certain area
* around the current position, and those used by the routing engine (only if currently routing).
*
* Routing MWMs are stored as a set.
*
* The other groups are stored twice: as a set and as a vector. The set always holds the MWMs which
* were last seen in use. Both get updated together when active MWMs are added or removed.
* However, the vector is used as a reference to detect changes. Clear() clears the vector but not
* the set, invalidating the set without destroying its contents.
*
* Methods which use only the set:
*
* * RequestTrafficSubscription(), exits if empty, otherwise cycles through the set.
* * UniteActiveMwms(), build the list of active MWMs (used by RequestTrafficSubscription()).
*
* Methods which use both, but in a different way:
*
* * UpdateActiveMwms(), uses the vector to detect changes (not for routing MWMs). If so, it
* updates both vector and set, but adds MWMs to the set only if they are alive.
*/
std::vector<MwmSet::MwmId> m_lastDrapeMwmsByRect;
std::set<MwmSet::MwmId> m_activeDrapeMwms;
std::vector<MwmSet::MwmId> m_lastPositionMwmsByRect;
std::set<MwmSet::MwmId> m_activePositionMwms;
std::set<MwmSet::MwmId> m_activeRoutingMwms;
/**
* @brief Whether active MWMs have changed since the last request.
*/
bool m_activeMwmsChanged = false;
// TODO no longer needed
#ifdef traffic_dead_code
// The ETag or entity tag is part of HTTP, the protocol for the World Wide Web.
// It is one of several mechanisms that HTTP provides for web cache validation,
// which allows a client to make conditional requests.
std::map<MwmSet::MwmId, std::string> m_trafficETags;
#endif
std::atomic<bool> m_isPaused;
// TODO no longer needed
#ifdef traffic_dead_code
/**
* @brief MWMs for which to retrieve traffic data.
*/
std::vector<MwmSet::MwmId> m_requestedMwms;
#endif
/**
* @brief Mutex for access to shared members.
*
* Threads which access shared members (see documentation) must lock this mutex while doing so.
*
* @note To access `m_trafficSource`, lock `m_trafficSourceMutex`, not this mutex.
*/
std::mutex m_mutex;
/**
* @brief Mutex for access to `m_trafficSources`.
*
* Threads which access `m_trafficSources` must lock this mutex while doing so.
*/
std::mutex m_trafficSourceMutex;
/**
* @brief Worker thread which fetches traffic updates.
*/
threads::SimpleThread m_thread;
/**
* @brief When the last response was received.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastResponseTime;
/**
* @brief When the last update notification to the Drape engine was posted.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastDrapeUpdate;
/**
* @brief When the last update notification to the traffic observer was posted.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastObserverUpdate;
/**
* @brief When the cache file was last updated.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastStorageUpdate;
/**
* @brief Whether a poll operation is needed.
*
* Used in the worker thread to indicate we need to poll all sources. The poll operation may still
* be inhibited for individual sources.
*/
bool m_isPollNeeded;
/**
* @brief Queue of feeds waiting to be processed.
*
* Threads must lock `m_mutex` before accessing `m_feedQueue`, as some platforms may receive feeds
* on multiple threads.
*/
std::vector<traffxml::TraffFeed> m_feedQueue;
/**
* @brief Cache of all currently active TraFF messages.
*
* Keys are message IDs, values are messages.
*
* Threads must lock `m_mutex` before accessing `m_messageCache`, as access can happen from
* multiple threads (messages are added by the worker thread, `Clear()` can be called from the UI
* thread).
*/
std::map<std::string, traffxml::TraffMessage> m_messageCache;
/**
* @brief The storage instance.
*
* Used to persist the TraFF message cache between sessions.
*/
std::unique_ptr<traffxml::LocalStorage> m_storage;
/**
* @brief The TraFF decoder instance.
*
* Used to decode TraFF locations into road segments on the map.
*/
std::unique_ptr<traffxml::DefaultTraffDecoder> m_traffDecoder;
/**
* @brief Map between MWM IDs and their colorings.
*
* Threads must lock `m_mutex` before accessing `m_allMwmColoring`, as access can happen from
* multiple threads (messages are added by the worker thread, `Clear()` can be called from the UI
* thread).
*/
std::map<MwmSet::MwmId, traffic::TrafficInfo::Coloring> m_allMwmColoring;
};
extern std::string DebugPrint(TrafficManager::TrafficState state);