co-maps/libs/indexer/mwm_set.cpp
2025-11-22 13:58:55 +01:00

446 lines
12 KiB
C++

#include "indexer/mwm_set.hpp"
#include "indexer/features_offsets_table.hpp"
#include "indexer/metadata_serdes.hpp" // needed for MwmValue dtor
#include "indexer/scales.hpp"
#include "platform/local_country_file_utils.hpp"
#include "base/assert.hpp"
#include "base/exception.hpp"
#include "base/logging.hpp"
#include "base/stl_helpers.hpp"
#include <algorithm>
#include <exception>
#include <sstream>
using namespace std;
using platform::CountryFile;
using platform::LocalCountryFile;
MwmInfo::MwmInfo() : m_minScale(0), m_maxScale(0), m_status(STATUS_DEREGISTERED), m_numRefs(0) {}
MwmInfo::MwmTypeT MwmInfo::GetType() const
{
if (m_minScale > 0)
return COUNTRY;
if (m_maxScale == scales::GetUpperWorldScale())
return WORLD;
ASSERT_EQUAL(m_maxScale, scales::GetUpperScale(), ());
return COASTS;
}
bool MwmSet::MwmId::IsDeregistered(platform::LocalCountryFile const & deregisteredCountryFile) const
{
return m_info && m_info->GetStatus() == MwmInfo::STATUS_DEREGISTERED &&
m_info->GetLocalFile() == deregisteredCountryFile;
}
string DebugPrint(MwmSet::MwmId const & id)
{
ostringstream ss;
if (id.m_info.get())
ss << "MwmId [" << id.m_info->GetCountryName() << ", " << id.m_info->GetVersion() << "]";
else
ss << "MwmId [invalid]";
return ss.str();
}
MwmSet::MwmHandle::MwmHandle() : m_mwmSet(nullptr), m_value(nullptr) {}
MwmSet::MwmHandle::MwmHandle(MwmSet & mwmSet, MwmId const & mwmId, unique_ptr<MwmValue> && value)
: m_mwmId(mwmId)
, m_mwmSet(&mwmSet)
, m_value(std::move(value))
{}
MwmSet::MwmHandle::MwmHandle(MwmHandle && handle)
: m_mwmId(std::move(handle.m_mwmId))
, m_mwmSet(handle.m_mwmSet)
, m_value(std::move(handle.m_value))
{
handle.m_mwmSet = nullptr;
handle.m_mwmId.Reset();
handle.m_value = nullptr;
}
MwmSet::MwmHandle::~MwmHandle()
{
if (m_mwmSet && m_value)
m_mwmSet->UnlockValue(m_mwmId, std::move(m_value));
}
shared_ptr<MwmInfo> const & MwmSet::MwmHandle::GetInfo() const
{
ASSERT(IsAlive(), ("MwmHandle is not active."));
return m_mwmId.GetInfo();
}
MwmSet::MwmHandle & MwmSet::MwmHandle::operator=(MwmHandle && handle)
{
swap(m_mwmSet, handle.m_mwmSet);
swap(m_mwmId, handle.m_mwmId);
swap(m_value, handle.m_value);
return *this;
}
MwmSet::MwmId MwmSet::GetMwmIdByCountryFileImpl(CountryFile const & countryFile) const
{
string const & name = countryFile.GetName();
ASSERT(!name.empty(), ());
auto const it = m_info.find(name);
if (it == m_info.cend() || it->second.empty())
return MwmId();
return MwmId(it->second.back());
}
pair<MwmSet::MwmId, MwmSet::RegResult> MwmSet::Register(LocalCountryFile const & localFile)
{
pair<MwmSet::MwmId, MwmSet::RegResult> result;
auto registerFile = [&](EventList & events)
{
CountryFile const & countryFile = localFile.GetCountryFile();
MwmId const id = GetMwmIdByCountryFileImpl(countryFile);
if (!id.IsAlive())
{
result = RegisterImpl(localFile, events);
return;
}
shared_ptr<MwmInfo> info = id.GetInfo();
// Deregister old mwm for the country.
if (info->GetVersion() < localFile.GetVersion())
{
DeregisterImpl(id, events);
result = RegisterImpl(localFile, events);
return;
}
string const name = countryFile.GetName();
// Update the status of the mwm with the same version.
if (info->GetVersion() == localFile.GetVersion())
{
LOG(LINFO, ("Updating already registered mwm:", name));
SetStatus(*info, MwmInfo::STATUS_REGISTERED, events);
info->m_file = localFile;
result = make_pair(id, RegResult::VersionAlreadyExists);
return;
}
LOG(LWARNING, ("Trying to add too old (", localFile.GetVersion(), ") mwm (", name,
"), current version:", info->GetVersion()));
result = make_pair(MwmId(), RegResult::VersionTooOld);
return;
};
WithEventLog(registerFile);
return result;
}
pair<MwmSet::MwmId, MwmSet::RegResult> MwmSet::RegisterImpl(LocalCountryFile const & localFile, EventList & events)
{
// This function can throw an exception for a bad mwm file.
shared_ptr<MwmInfo> info(CreateInfo(localFile));
if (!info)
return make_pair(MwmId(), RegResult::UnsupportedFileFormat);
info->m_file = localFile;
SetStatus(*info, MwmInfo::STATUS_REGISTERED, events);
m_info[localFile.GetCountryName()].push_back(info);
return make_pair(MwmId(info), RegResult::Success);
}
bool MwmSet::DeregisterImpl(MwmId const & id, EventList & events)
{
if (!id.IsAlive())
return false;
shared_ptr<MwmInfo> const & info = id.GetInfo();
if (info->m_numRefs == 0)
{
SetStatus(*info, MwmInfo::STATUS_DEREGISTERED, events);
vector<shared_ptr<MwmInfo>> & infos = m_info[info->GetCountryName()];
infos.erase(remove(infos.begin(), infos.end(), info), infos.end());
for (auto it = m_cache.begin(); it != m_cache.end(); ++it)
{
if (it->first == id)
{
m_cache.erase(it);
break;
}
}
return true;
}
SetStatus(*info, MwmInfo::STATUS_MARKED_TO_DEREGISTER, events);
return false;
}
bool MwmSet::Deregister(CountryFile const & countryFile)
{
bool deregistered = false;
WithEventLog([&](EventList & events) { deregistered = DeregisterImpl(countryFile, events); });
return deregistered;
}
bool MwmSet::DeregisterImpl(CountryFile const & countryFile, EventList & events)
{
MwmId const id = GetMwmIdByCountryFileImpl(countryFile);
if (!id.IsAlive())
return false;
bool const deregistered = DeregisterImpl(id, events);
ClearCache(id);
return deregistered;
}
bool MwmSet::IsLoaded(CountryFile const & countryFile) const
{
lock_guard<mutex> lock(m_lock);
MwmId const id = GetMwmIdByCountryFileImpl(countryFile);
return id.IsAlive() && id.GetInfo()->IsRegistered();
}
void MwmSet::GetMwmsInfo(vector<shared_ptr<MwmInfo>> & info) const
{
lock_guard<mutex> lock(m_lock);
info.clear();
info.reserve(m_info.size());
for (auto const & p : m_info)
if (!p.second.empty())
info.push_back(p.second.back());
}
void MwmSet::SetStatus(MwmInfo & info, MwmInfo::Status status, EventList & events)
{
MwmInfo::Status oldStatus = info.SetStatus(status);
if (oldStatus == status)
return;
switch (status)
{
case MwmInfo::STATUS_REGISTERED: events.Add(Event(Event::TYPE_REGISTERED, info.GetLocalFile())); break;
case MwmInfo::STATUS_MARKED_TO_DEREGISTER: break;
case MwmInfo::STATUS_DEREGISTERED: events.Add(Event(Event::TYPE_DEREGISTERED, info.GetLocalFile())); break;
}
}
void MwmSet::ProcessEventList(EventList & events)
{
for (auto const & event : events.Get())
{
switch (event.m_type)
{
case Event::TYPE_REGISTERED: m_observers.ForEach(&Observer::OnMapRegistered, event.m_file); break;
case Event::TYPE_DEREGISTERED: m_observers.ForEach(&Observer::OnMapDeregistered, event.m_file); break;
}
}
}
unique_ptr<MwmValue> MwmSet::LockValue(MwmId const & id)
{
unique_ptr<MwmValue> result;
WithEventLog([&](EventList & events) { result = LockValueImpl(id, events); });
return result;
}
unique_ptr<MwmValue> MwmSet::LockValueImpl(MwmId const & id, EventList & events)
{
if (!id.IsAlive())
return nullptr;
shared_ptr<MwmInfo> info = id.GetInfo();
// It's better to return valid "value pointer" even for "out-of-date" files,
// because they can be locked for a long time by other algos.
// if (!info->IsUpToDate())
// return TMwmValuePtr();
++info->m_numRefs;
// Search in cache.
for (auto it = m_cache.begin(); it != m_cache.end(); ++it)
{
if (it->first == id)
{
unique_ptr<MwmValue> result = std::move(it->second);
m_cache.erase(it);
return result;
}
}
try
{
return CreateValue(*info);
}
catch (Reader::TooManyFilesException const & ex)
{
LOG(LERROR, ("Too many open files, can't open:", info->GetCountryName()));
--info->m_numRefs;
return nullptr;
}
catch (exception const & ex)
{
LOG(LERROR, ("Can't create MWMValue for", info->GetCountryName(), "Reason", ex.what()));
--info->m_numRefs;
DeregisterImpl(id, events);
return nullptr;
}
}
void MwmSet::UnlockValue(MwmId const & id, unique_ptr<MwmValue> p)
{
WithEventLog([&](EventList & events) { UnlockValueImpl(id, std::move(p), events); });
}
void MwmSet::UnlockValueImpl(MwmId const & id, unique_ptr<MwmValue> p, EventList & events)
{
ASSERT(id.IsAlive(), (id));
ASSERT(p.get() != nullptr, ());
if (!id.IsAlive() || !p)
return;
shared_ptr<MwmInfo> const & info = id.GetInfo();
ASSERT_GREATER(info->m_numRefs, 0, ());
--info->m_numRefs;
if (info->m_numRefs == 0 && info->GetStatus() == MwmInfo::STATUS_MARKED_TO_DEREGISTER)
VERIFY(DeregisterImpl(id, events), ());
if (info->IsUpToDate())
{
/// @todo Probably, it's better to store only "unique by id" free caches here.
/// But it's no obvious if we have many threads working with the single mwm.
m_cache.push_back(make_pair(id, std::move(p)));
if (m_cache.size() > m_cacheSize)
{
LOG(LDEBUG, ("MwmValue max cache size reached! Added", id, "removed", m_cache.front().first));
ASSERT_EQUAL(m_cache.size(), m_cacheSize + 1, ());
m_cache.pop_front();
}
}
}
void MwmSet::Clear()
{
lock_guard<mutex> lock(m_lock);
ClearCacheImpl(m_cache.begin(), m_cache.end());
m_info.clear();
}
void MwmSet::ClearCache()
{
lock_guard<mutex> lock(m_lock);
ClearCacheImpl(m_cache.begin(), m_cache.end());
}
MwmSet::MwmId MwmSet::GetMwmIdByCountryFile(CountryFile const & countryFile) const
{
lock_guard<mutex> lock(m_lock);
return GetMwmIdByCountryFileImpl(countryFile);
}
MwmSet::MwmHandle MwmSet::GetMwmHandleByCountryFile(CountryFile const & countryFile)
{
MwmSet::MwmHandle handle;
WithEventLog([&](EventList & events)
{ handle = GetMwmHandleByIdImpl(GetMwmIdByCountryFileImpl(countryFile), events); });
return handle;
}
MwmSet::MwmHandle MwmSet::GetMwmHandleById(MwmId const & id)
{
MwmSet::MwmHandle handle;
WithEventLog([&](EventList & events) { handle = GetMwmHandleByIdImpl(id, events); });
return handle;
}
MwmSet::MwmHandle MwmSet::GetMwmHandleByIdImpl(MwmId const & id, EventList & events)
{
unique_ptr<MwmValue> value;
if (id.IsAlive())
value = LockValueImpl(id, events);
return MwmHandle(*this, id, std::move(value));
}
void MwmSet::ClearCacheImpl(Cache::iterator beg, Cache::iterator end)
{
m_cache.erase(beg, end);
}
void MwmSet::ClearCache(MwmId const & id)
{
auto sameId = [&id](pair<MwmSet::MwmId, unique_ptr<MwmValue>> const & p) { return (p.first == id); };
ClearCacheImpl(base::RemoveIfKeepValid(m_cache.begin(), m_cache.end(), sameId), m_cache.end());
}
// MwmValue ----------------------------------------------------------------------------------------
MwmValue::MwmValue(LocalCountryFile const & localFile)
: m_cont(platform::GetCountryReader(localFile, MapFileType::Map))
, m_file(localFile)
{
m_version = version::MwmVersion::Read(m_cont);
if (m_version.GetFormat() < version::Format::v11)
MYTHROW(CorruptedMwmFile, (m_cont.GetFileName()));
m_header.Load(m_cont);
if (m_cont.IsExist(REGION_INFO_FILE_TAG))
{
ReaderSource<FilesContainerR::TReader> src(m_cont.GetReader(REGION_INFO_FILE_TAG));
m_regionData.Deserialize(src);
}
}
MwmValue::~MwmValue() {}
void MwmValue::SetTable(MwmInfoEx & info)
{
m_ftTable = info.m_ftTable.lock();
if (!m_ftTable)
{
m_ftTable = feature::FeaturesOffsetsTable::Load(m_cont, FEATURE_OFFSETS_FILE_TAG);
info.m_ftTable = m_ftTable;
}
m_relTable = info.m_relTable.lock();
if (!m_relTable && m_cont.IsExist(RELATION_OFFSETS_FILE_TAG))
{
m_relTable = feature::FeaturesOffsetsTable::Load(m_cont, RELATION_OFFSETS_FILE_TAG);
info.m_relTable = m_relTable;
}
}
string DebugPrint(MwmSet::RegResult result)
{
switch (result)
{
case MwmSet::RegResult::Success: return "Success";
case MwmSet::RegResult::VersionAlreadyExists: return "VersionAlreadyExists";
case MwmSet::RegResult::VersionTooOld: return "VersionTooOld";
case MwmSet::RegResult::BadFile: return "BadFile";
case MwmSet::RegResult::UnsupportedFileFormat: return "UnsupportedFileFormat";
}
UNREACHABLE();
}
string DebugPrint(MwmSet::Event::Type type)
{
switch (type)
{
case MwmSet::Event::TYPE_REGISTERED: return "Registered";
case MwmSet::Event::TYPE_DEREGISTERED: return "Deregistered";
}
return "Undefined";
}
string DebugPrint(MwmSet::Event const & event)
{
ostringstream os;
os << "MwmSet::Event [" << DebugPrint(event.m_type) << ", " << DebugPrint(event.m_file) << "]";
return os.str();
}