diff --git a/geometry/rect2d.hpp b/geometry/rect2d.hpp index 70f68d42a1..da423056cc 100644 --- a/geometry/rect2d.hpp +++ b/geometry/rect2d.hpp @@ -340,6 +340,7 @@ namespace m2 typedef Rect RectF; typedef Rect RectD; typedef Rect RectU; + typedef Rect RectU32; typedef Rect RectI; template diff --git a/indexer/indexer.pro b/indexer/indexer.pro index ff4c4f19e3..f13cdea12a 100644 --- a/indexer/indexer.pro +++ b/indexer/indexer.pro @@ -36,6 +36,7 @@ SOURCES += \ feature_loader_base.cpp \ feature_loader.cpp \ search_delimiters.cpp \ + mwm_set.cpp \ HEADERS += \ feature.hpp \ @@ -80,3 +81,6 @@ HEADERS += \ search_trie.hpp \ search_string_utils.hpp \ search_delimiters.hpp \ + mwm_set.hpp \ + + diff --git a/indexer/indexer_tests/indexer_tests.pro b/indexer/indexer_tests/indexer_tests.pro index 32daf6ec1d..a215d31f1b 100644 --- a/indexer/indexer_tests/indexer_tests.pro +++ b/indexer/indexer_tests/indexer_tests.pro @@ -33,3 +33,5 @@ SOURCES += \ scales_test.cpp \ test_polylines.cpp \ geometry_serialization_test.cpp \ + mwm_set_test.cpp + diff --git a/indexer/indexer_tests/mwm_set_test.cpp b/indexer/indexer_tests/mwm_set_test.cpp new file mode 100644 index 0000000000..68beabd2ea --- /dev/null +++ b/indexer/indexer_tests/mwm_set_test.cpp @@ -0,0 +1,79 @@ +#include "../../testing/testing.hpp" +#include "../mwm_set.hpp" +#include "../../coding/file_container.hpp" + +namespace +{ +void SetMwmInfoForTest(string const & path, MwmInfo & info) +{ + int n = path[0] - '0'; + info.m_maxScale = n; +} +FilesContainerR * CreateFileContainerForTest(string const &) +{ + return new FilesContainerR("minsk-pass.mwm"); +} +} // unnamed namespace + + +UNIT_TEST(MwmSetSmokeTest) +{ + MwmSet mwmSet(&SetMwmInfoForTest, &CreateFileContainerForTest); + vector info; + + mwmSet.Add("0"); + mwmSet.Add("1"); + mwmSet.Add("2"); + mwmSet.Remove("1"); + mwmSet.GetMwmInfo(info); + TEST_EQUAL(info.size(), 3, ()); + TEST(info[0].isValid(), ()); + TEST_EQUAL(info[0].m_maxScale, 0, ()); + TEST(!info[1].isValid(), ()); + TEST(info[2].isValid(), ()); + TEST_EQUAL(info[2].m_maxScale, 2, ()); + { + MwmSet::MwmLock lock0(mwmSet, 0); + MwmSet::MwmLock lock1(mwmSet, 1); + TEST(lock0.GetFileContainer() != NULL, ()); + TEST(lock1.GetFileContainer() == NULL, ()); + } + + mwmSet.Add("3"); + mwmSet.GetMwmInfo(info); + TEST_EQUAL(info.size(), 3, ()); + TEST(info[0].isValid(), ()); + TEST_EQUAL(info[0].m_maxScale, 0, ()); + TEST(info[1].isValid(), ()); + TEST_EQUAL(info[1].m_maxScale, 3, ()); + TEST(info[2].isValid(), ()); + TEST_EQUAL(info[2].m_maxScale, 2, ()); + + { + MwmSet::MwmLock lock(mwmSet, 1); + TEST(lock.GetFileContainer() != NULL, ()); + mwmSet.Remove("3"); + mwmSet.Add("4"); + } + mwmSet.GetMwmInfo(info); + TEST_EQUAL(info.size(), 4, ()); + TEST(info[0].isValid(), ()); + TEST_EQUAL(info[0].m_maxScale, 0, ()); + TEST(!info[1].isValid(), ()); + TEST(info[2].isValid(), ()); + TEST_EQUAL(info[2].m_maxScale, 2, ()); + TEST(info[3].isValid(), ()); + TEST_EQUAL(info[3].m_maxScale, 3, ()); + + mwmSet.Add("5"); + mwmSet.GetMwmInfo(info); + TEST_EQUAL(info.size(), 4, ()); + TEST(info[0].isValid(), ()); + TEST_EQUAL(info[0].m_maxScale, 0, ()); + TEST(info[1].isValid(), ()); + TEST_EQUAL(info[1].m_maxScale, 5, ()); + TEST(info[2].isValid(), ()); + TEST_EQUAL(info[2].m_maxScale, 2, ()); + TEST(info[3].isValid(), ()); + TEST_EQUAL(info[3].m_maxScale, 3, ()); +} diff --git a/indexer/mwm_set.cpp b/indexer/mwm_set.cpp new file mode 100644 index 0000000000..ada1201264 --- /dev/null +++ b/indexer/mwm_set.cpp @@ -0,0 +1,223 @@ +#include "mwm_set.hpp" +#include "../coding/file_container.hpp" +#include "../base/logging.hpp" +#include "../std/algorithm.hpp" + +namespace +{ +struct MwmIdIsEqualTo +{ + MwmSet::MwmId m_id; + explicit MwmIdIsEqualTo(MwmSet::MwmId id) : m_id(id) {} + bool operator() (pair const & p) const + { + return p.first == m_id; + } +}; +} // unnamed namespace + +MwmSet::MwmLock::MwmLock(MwmSet & mwmSet, MwmId mwmId) + : m_mwmSet(mwmSet), m_id(mwmId), m_pFileContainer(mwmSet.LockContainer(mwmId)) +{ + LOG(LINFO, ("MwmLock::MwmLock()", m_id)); +} + +MwmSet::MwmLock::~MwmLock() +{ + LOG(LINFO, ("MwmLock::~MwmLock()", m_id)); + if (m_pFileContainer) + m_mwmSet.UnlockContainer(m_id, m_pFileContainer); +} + +FilesContainerR * MwmSet::MwmLock::GetFileContainer() const +{ + return m_pFileContainer; +} + + +MwmSet::MwmSet(function const & fnGetMwmInfo, + function const & fnCreateContainer, + size_t cacheSize) + : m_cacheSize(cacheSize), m_fnGetMwmInfo(fnGetMwmInfo), m_fnCreateContainer(fnCreateContainer) +{ + LOG(LINFO, ("MwmSet::MwmSet()")); +} + +MwmSet::~MwmSet() +{ + threads::MutexGuard mutexGuard(m_lock); + UNUSED_VALUE(mutexGuard); + + LOG(LINFO, ("MwmSet::~MwmSet()")); + + ClearCacheImpl(m_cache.begin(), m_cache.end()); + + for (size_t i = 0; i < m_info.size(); ++i) + { + if (m_info[i].m_status == MwmInfo::STATUS_ACTIVE) + { + ASSERT_EQUAL(m_info[i].m_lockCount, 0, (i, m_name[i])); + ASSERT_NOT_EQUAL(m_name[i], string(), (i)); + } + } +} + +inline void MwmSet::UpdateMwmInfo(MwmInfo & info) +{ + if (info.m_status == MwmInfo::STATUS_TO_REMOVE && info.m_lockCount == 0) + info.m_status = MwmInfo::STATUS_REMOVED; +} + +MwmSet::MwmId MwmSet::GetFreeId() +{ + MwmId const size = static_cast(m_info.size()); + for (MwmId i = 0; i < size; ++i) + { + if (m_info[i].m_status == MwmInfo::STATUS_REMOVED) + return i; + } + m_info.push_back(MwmInfo()); + m_info.back().m_status = MwmInfo::STATUS_REMOVED; + m_info.back().m_lockCount = 0; + m_name.push_back(string()); + return size; +} + +MwmSet::MwmId MwmSet::GetIdByName(string const & name) +{ + MwmId const size = static_cast(m_info.size()); + for (MwmId i = 0; i < size; ++i) + { + UpdateMwmInfo(m_info[i]); + if (m_info[i].m_status == MwmInfo::STATUS_ACTIVE && m_name[i] == name) + return i; + } + return INVALID_MWM_ID; +} + +bool MwmSet::Add(string const & fileName) +{ + threads::MutexGuard mutexGuard(m_lock); + UNUSED_VALUE(mutexGuard); + + LOG(LINFO, ("MwmSet::Add()", fileName)); + + if (GetIdByName(fileName) != INVALID_MWM_ID) + return false; + + MwmId const id = GetFreeId(); + m_name[id] = fileName; + memset(&m_info[id], 0, sizeof(MwmInfo)); + m_fnGetMwmInfo(fileName, m_info[id]); + m_info[id].m_lockCount = 0; + m_info[id].m_status = MwmInfo::STATUS_ACTIVE; + return true; +} + +void MwmSet::Remove(string const & fileName) +{ + threads::MutexGuard mutexGuard(m_lock); + UNUSED_VALUE(mutexGuard); + + LOG(LINFO, ("MwmSet::Remove()", fileName)); + + MwmId const id = GetIdByName(fileName); + if (id != INVALID_MWM_ID) + { + if (m_info[id].m_lockCount == 0) + m_info[id].m_status = MwmInfo::STATUS_REMOVED; + else + m_info[id].m_status = MwmInfo::STATUS_TO_REMOVE; + m_name[id].clear(); + + // Update the cache. + ClearCacheImpl(remove_if(m_cache.begin(), m_cache.end(), MwmIdIsEqualTo(id)), m_cache.end()); + } +} + +void MwmSet::GetMwmInfo(vector & info) +{ + threads::MutexGuard mutexGuard(m_lock); + UNUSED_VALUE(mutexGuard); + + for (vector::iterator it = m_info.begin(); it != m_info.end(); ++it) + UpdateMwmInfo(*it); + + info = m_info; +} + +FilesContainerR * MwmSet::LockContainer(MwmId id) +{ + threads::MutexGuard mutexGuard(m_lock); + UNUSED_VALUE(mutexGuard); + + LOG(LINFO, ("MwmSet::LockContainer()", id)); + + ASSERT_LESS(id, m_info.size(), ()); + if (id >= m_info.size()) + return NULL; + + UpdateMwmInfo(m_info[id]); + if (m_info[id].m_status != MwmInfo::STATUS_ACTIVE) + return NULL; + + ++m_info[id].m_lockCount; + + // Search in cache. + for (CacheType::iterator it = m_cache.begin(); it != m_cache.end(); ++it) + { + if (it->first == id) + { + FilesContainerR * result = it->second; + m_cache.erase(it); + return result; + } + } + return m_fnCreateContainer(m_name[id]); +} + +void MwmSet::UnlockContainer(MwmId id, FilesContainerR * pContainer) +{ + threads::MutexGuard mutexGuard(m_lock); + UNUSED_VALUE(mutexGuard); + + LOG(LINFO, ("MwmSet::UnlockContainer()", id)); + + ASSERT(pContainer, (id)); + ASSERT_LESS(id, m_info.size(), ()); + if (id >= m_info.size() || !pContainer) + return; + + ASSERT_GREATER(m_info[id].m_lockCount, 0, ()); + if (m_info[id].m_lockCount > 0) + --m_info[id].m_lockCount; + UpdateMwmInfo(m_info[id]); + + if (m_info[id].m_status == MwmInfo::STATUS_ACTIVE) + { + m_cache.push_back(make_pair(id, pContainer)); + if (m_cache.size() > m_cacheSize) + { + ASSERT_EQUAL(m_cache.size(), m_cacheSize + 1, ()); + delete m_cache.front().second; + m_cache.pop_front(); + } + } + else + delete pContainer; +} + +void MwmSet::ClearCache() +{ + threads::MutexGuard mutexGuard(m_lock); + UNUSED_VALUE(mutexGuard); + + ClearCacheImpl(m_cache.begin(), m_cache.end()); +} + +void MwmSet::ClearCacheImpl(CacheType::iterator beg, CacheType::iterator end) +{ + for (CacheType::iterator it = beg; it != end; ++it) + delete it->second; + m_cache.erase(beg, end); +} diff --git a/indexer/mwm_set.hpp b/indexer/mwm_set.hpp new file mode 100644 index 0000000000..f592cfd7e3 --- /dev/null +++ b/indexer/mwm_set.hpp @@ -0,0 +1,94 @@ +#include "../geometry/rect2d.hpp" +#include "../base/mutex.hpp" +#include "../std/deque.hpp" +#include "../std/function.hpp" +#include "../std/string.hpp" +#include "../std/utility.hpp" +#include "../std/vector.hpp" + +class FilesContainerR; + +// Information about stored mwm. +struct MwmInfo +{ + m2::RectU32 m_limitRect; // Limit rect of mwm. + uint8_t m_minScale; // Min zoom level of mwm. + uint8_t m_maxScale; // Max zoom level of mwm. + + // Does this MwmInfo represent a valid Mwm? + bool isValid() const { return m_status == STATUS_ACTIVE; } +private: + friend class MwmSet; + + enum Status { STATUS_ACTIVE = 0, STATUS_TO_REMOVE = 1, STATUS_REMOVED = 2 }; + uint8_t m_lockCount; // Number of locks. + uint8_t m_status; // +}; + +class MwmSet +{ +public: + + typedef size_t MwmId; + + explicit MwmSet(function const & fnGetMwmInfo, + function const & fnCreateContainer, + size_t cacheSize = 5); + ~MwmSet(); + + // Mwm lock, which is used to lock mwm when its FileContainer is used. + class MwmLock + { + public: + MwmLock(MwmSet & mwmSet, MwmId mwmId); + ~MwmLock(); + + FilesContainerR * GetFileContainer() const; + private: + MwmSet & m_mwmSet; + MwmId m_id; + FilesContainerR * m_pFileContainer; + }; + + // Add new mwm. Returns false, if mwm with given fileName already exists. + bool Add(string const & fileName); + + // Remove mwm. + void Remove(string const & fileName); + + // Get ids of all mwms. Some of them may be marked to remove. + void GetMwmInfo(vector & info); + + // Clear caches. + void ClearCache(); + +private: + friend class MwmLock; + + static const MwmId INVALID_MWM_ID = static_cast(-1); + + typedef deque > CacheType; + + // Update given MwmInfo. + inline static void UpdateMwmInfo(MwmInfo & info); + + FilesContainerR * LockContainer(MwmId id); + void UnlockContainer(MwmId id, FilesContainerR * pContainer); + + // Find first removed mwm or add a new one. + MwmId GetFreeId(); + + // Find mwm with a given name. + MwmId GetIdByName(string const & name); + + // Do the cleaning for [beg, end) without acquiring the mutex. + void ClearCacheImpl(CacheType::iterator beg, CacheType::iterator end); + + vector m_info; + vector m_name; + CacheType m_cache; + size_t m_cacheSize; + function m_fnGetMwmInfo; + function const & m_fnCreateContainer; + threads::Mutex m_lock; +};