mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-29 22:30:04 +00:00
524 lines
15 KiB
C++
524 lines
15 KiB
C++
#include <scwx/qt/manager/marker_manager.hpp>
|
|
#include <scwx/qt/types/marker_types.hpp>
|
|
#include <scwx/qt/util/color.hpp>
|
|
#include <scwx/qt/util/texture_atlas.hpp>
|
|
#include <scwx/qt/main/application.hpp>
|
|
#include <scwx/qt/manager/resource_manager.hpp>
|
|
#include <scwx/util/json.hpp>
|
|
#include <scwx/util/logger.hpp>
|
|
|
|
#include <filesystem>
|
|
#include <shared_mutex>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
|
|
#include <QStandardPaths>
|
|
#include <boost/json.hpp>
|
|
#include <boost/asio/post.hpp>
|
|
#include <boost/asio/thread_pool.hpp>
|
|
|
|
namespace scwx
|
|
{
|
|
namespace qt
|
|
{
|
|
namespace manager
|
|
{
|
|
|
|
static const std::string logPrefix_ = "scwx::qt::manager::marker_manager";
|
|
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
|
|
|
static const std::string kNameName_ = "name";
|
|
static const std::string kLatitudeName_ = "latitude";
|
|
static const std::string kLongitudeName_ = "longitude";
|
|
static const std::string kIconName_ = "icon";
|
|
static const std::string kIconColorName_ = "icon-color";
|
|
|
|
static const std::string defaultIconName = "images/location-marker";
|
|
|
|
class MarkerManager::Impl
|
|
{
|
|
public:
|
|
class MarkerRecord;
|
|
|
|
explicit Impl(MarkerManager* self) : self_ {self} {}
|
|
~Impl() { threadPool_.join(); }
|
|
|
|
std::string markerSettingsPath_ {""};
|
|
std::vector<std::shared_ptr<MarkerRecord>> markerRecords_ {};
|
|
std::unordered_map<types::MarkerId, size_t> idToIndex_ {};
|
|
std::unordered_map<std::string, types::MarkerIconInfo> markerIcons_ {};
|
|
|
|
MarkerManager* self_;
|
|
|
|
boost::asio::thread_pool threadPool_ {1u};
|
|
std::shared_mutex markerRecordLock_ {};
|
|
std::shared_mutex markerIconsLock_ {};
|
|
|
|
void InitializeMarkerSettings();
|
|
void ReadMarkerSettings();
|
|
void WriteMarkerSettings();
|
|
std::shared_ptr<MarkerRecord> GetMarkerByName(const std::string& name);
|
|
|
|
bool markerFileRead_ {false};
|
|
|
|
void InitalizeIds();
|
|
types::MarkerId NewId();
|
|
types::MarkerId lastId_ {0};
|
|
};
|
|
|
|
class MarkerManager::Impl::MarkerRecord
|
|
{
|
|
public:
|
|
MarkerRecord(types::MarkerInfo info) : markerInfo_ {std::move(info)} {}
|
|
|
|
const types::MarkerInfo& toMarkerInfo() { return markerInfo_; }
|
|
|
|
types::MarkerInfo markerInfo_;
|
|
|
|
friend void tag_invoke(boost::json::value_from_tag,
|
|
boost::json::value& jv,
|
|
const std::shared_ptr<MarkerRecord>& record)
|
|
{
|
|
jv = {{kNameName_, record->markerInfo_.name},
|
|
{kLatitudeName_, record->markerInfo_.latitude},
|
|
{kLongitudeName_, record->markerInfo_.longitude},
|
|
{kIconName_, record->markerInfo_.iconName},
|
|
{kIconColorName_,
|
|
util::color::ToArgbString(record->markerInfo_.iconColor)}};
|
|
}
|
|
|
|
friend MarkerRecord tag_invoke(boost::json::value_to_tag<MarkerRecord>,
|
|
const boost::json::value& jv)
|
|
{
|
|
static const boost::gil::rgba8_pixel_t defaultIconColor =
|
|
util::color::ToRgba8PixelT("#ffff0000");
|
|
|
|
const boost::json::object& jo = jv.as_object();
|
|
|
|
std::string iconName = defaultIconName;
|
|
boost::gil::rgba8_pixel_t iconColor = defaultIconColor;
|
|
|
|
if (jo.contains(kIconName_) && jo.at(kIconName_).is_string())
|
|
{
|
|
iconName = boost::json::value_to<std::string>(jv.at(kIconName_));
|
|
}
|
|
|
|
if (jo.contains(kIconColorName_) && jo.at(kIconName_).is_string())
|
|
{
|
|
try
|
|
{
|
|
iconColor = util::color::ToRgba8PixelT(
|
|
boost::json::value_to<std::string>(jv.at(kIconColorName_)));
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
logger_->warn(
|
|
"Could not parse color value in location-markers.json with the "
|
|
"following exception: {}",
|
|
ex.what());
|
|
}
|
|
}
|
|
|
|
return {types::MarkerInfo(
|
|
boost::json::value_to<std::string>(jv.at(kNameName_)),
|
|
boost::json::value_to<double>(jv.at(kLatitudeName_)),
|
|
boost::json::value_to<double>(jv.at(kLongitudeName_)),
|
|
iconName,
|
|
iconColor)};
|
|
}
|
|
};
|
|
|
|
void MarkerManager::Impl::InitalizeIds()
|
|
{
|
|
lastId_ = 0;
|
|
}
|
|
|
|
types::MarkerId MarkerManager::Impl::NewId()
|
|
{
|
|
return ++lastId_;
|
|
}
|
|
|
|
void MarkerManager::Impl::InitializeMarkerSettings()
|
|
{
|
|
std::string appDataPath {
|
|
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
|
|
.toStdString()};
|
|
|
|
if (!std::filesystem::exists(appDataPath))
|
|
{
|
|
if (!std::filesystem::create_directories(appDataPath))
|
|
{
|
|
logger_->error("Unable to create application data directory: \"{}\"",
|
|
appDataPath);
|
|
}
|
|
}
|
|
|
|
markerSettingsPath_ = appDataPath + "/location-markers.json";
|
|
}
|
|
|
|
void MarkerManager::Impl::ReadMarkerSettings()
|
|
{
|
|
logger_->info("Reading location marker settings");
|
|
InitalizeIds();
|
|
|
|
boost::json::value markerJson = nullptr;
|
|
{
|
|
const std::unique_lock lock(markerRecordLock_);
|
|
|
|
// Determine if marker settings exists
|
|
if (std::filesystem::exists(markerSettingsPath_))
|
|
{
|
|
markerJson = scwx::util::json::ReadJsonFile(markerSettingsPath_);
|
|
}
|
|
|
|
if (markerJson != nullptr && markerJson.is_array())
|
|
{
|
|
// For each marker entry
|
|
auto& markerArray = markerJson.as_array();
|
|
markerRecords_.reserve(markerArray.size());
|
|
idToIndex_.reserve(markerArray.size());
|
|
for (auto& markerEntry : markerArray)
|
|
{
|
|
try
|
|
{
|
|
auto record = boost::json::value_to<MarkerRecord>(markerEntry);
|
|
|
|
const types::MarkerId id = NewId();
|
|
const size_t index = markerRecords_.size();
|
|
record.markerInfo_.id = id;
|
|
markerRecords_.emplace_back(
|
|
std::make_shared<MarkerRecord>(record.markerInfo_));
|
|
idToIndex_.emplace(id, index);
|
|
|
|
self_->add_icon(record.markerInfo_.iconName, true);
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
logger_->warn("Invalid location marker entry: {}", ex.what());
|
|
}
|
|
}
|
|
|
|
ResourceManager::BuildAtlas();
|
|
|
|
logger_->debug("{} location marker entries", markerRecords_.size());
|
|
}
|
|
}
|
|
|
|
markerFileRead_ = true;
|
|
Q_EMIT self_->MarkersUpdated();
|
|
}
|
|
|
|
void MarkerManager::Impl::WriteMarkerSettings()
|
|
{
|
|
if (!markerFileRead_)
|
|
{
|
|
return;
|
|
}
|
|
logger_->info("Saving location marker settings");
|
|
|
|
const std::shared_lock lock(markerRecordLock_);
|
|
auto markerJson = boost::json::value_from(markerRecords_);
|
|
scwx::util::json::WriteJsonFile(markerSettingsPath_, markerJson);
|
|
}
|
|
|
|
std::shared_ptr<MarkerManager::Impl::MarkerRecord>
|
|
MarkerManager::Impl::GetMarkerByName(const std::string& name)
|
|
{
|
|
for (auto& markerRecord : markerRecords_)
|
|
{
|
|
if (markerRecord->markerInfo_.name == name)
|
|
{
|
|
return markerRecord;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
MarkerManager::MarkerManager() : p(std::make_unique<Impl>(this))
|
|
{
|
|
static const std::vector<types::MarkerIconInfo> defaultMarkerIcons_ {
|
|
types::MarkerIconInfo(types::ImageTexture::LocationMarker, -1, -1),
|
|
types::MarkerIconInfo(types::ImageTexture::LocationPin, 6, 16),
|
|
types::MarkerIconInfo(types::ImageTexture::LocationCrosshair, -1, -1),
|
|
types::MarkerIconInfo(types::ImageTexture::LocationStar, -1, -1),
|
|
types::MarkerIconInfo(types::ImageTexture::LocationBriefcase, -1, -1),
|
|
types::MarkerIconInfo(
|
|
types::ImageTexture::LocationBuildingColumns, -1, -1),
|
|
types::MarkerIconInfo(types::ImageTexture::LocationBuilding, -1, -1),
|
|
types::MarkerIconInfo(types::ImageTexture::LocationCaravan, -1, -1),
|
|
types::MarkerIconInfo(types::ImageTexture::LocationHouse, -1, -1),
|
|
types::MarkerIconInfo(types::ImageTexture::LocationTent, -1, -1),
|
|
};
|
|
|
|
p->InitializeMarkerSettings();
|
|
|
|
boost::asio::post(p->threadPool_,
|
|
[this]()
|
|
{
|
|
try
|
|
{
|
|
// Read Marker settings on startup
|
|
main::Application::WaitForInitialization();
|
|
{
|
|
const std::unique_lock lock(p->markerIconsLock_);
|
|
p->markerIcons_.reserve(
|
|
defaultMarkerIcons_.size());
|
|
for (auto& icon : defaultMarkerIcons_)
|
|
{
|
|
p->markerIcons_.emplace(icon.name, icon);
|
|
}
|
|
}
|
|
p->ReadMarkerSettings();
|
|
|
|
Q_EMIT IconsReady();
|
|
Q_EMIT MarkersInitialized(p->markerRecords_.size());
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
logger_->error(ex.what());
|
|
}
|
|
});
|
|
}
|
|
|
|
MarkerManager::~MarkerManager()
|
|
{
|
|
p->WriteMarkerSettings();
|
|
}
|
|
|
|
size_t MarkerManager::marker_count()
|
|
{
|
|
return p->markerRecords_.size();
|
|
}
|
|
|
|
std::optional<types::MarkerInfo> MarkerManager::get_marker(types::MarkerId id)
|
|
{
|
|
const std::shared_lock lock(p->markerRecordLock_);
|
|
if (!p->idToIndex_.contains(id))
|
|
{
|
|
return {};
|
|
}
|
|
size_t index = p->idToIndex_[id];
|
|
if (index >= p->markerRecords_.size())
|
|
{
|
|
logger_->warn("id in idToIndex_ but out of range!");
|
|
return {};
|
|
}
|
|
std::shared_ptr<MarkerManager::Impl::MarkerRecord>& markerRecord =
|
|
p->markerRecords_[index];
|
|
return markerRecord->toMarkerInfo();
|
|
}
|
|
|
|
std::optional<size_t> MarkerManager::get_index(types::MarkerId id)
|
|
{
|
|
const std::shared_lock lock(p->markerRecordLock_);
|
|
if (!p->idToIndex_.contains(id))
|
|
{
|
|
return {};
|
|
}
|
|
return p->idToIndex_[id];
|
|
}
|
|
|
|
void MarkerManager::set_marker(types::MarkerId id,
|
|
const types::MarkerInfo& marker)
|
|
{
|
|
{
|
|
const std::unique_lock lock(p->markerRecordLock_);
|
|
if (!p->idToIndex_.contains(id))
|
|
{
|
|
return;
|
|
}
|
|
size_t index = p->idToIndex_[id];
|
|
if (index >= p->markerRecords_.size())
|
|
{
|
|
logger_->warn("id in idToIndex_ but out of range!");
|
|
return;
|
|
}
|
|
const std::shared_ptr<MarkerManager::Impl::MarkerRecord>& markerRecord =
|
|
p->markerRecords_[index];
|
|
markerRecord->markerInfo_ = marker;
|
|
markerRecord->markerInfo_.id = id;
|
|
|
|
add_icon(marker.iconName);
|
|
}
|
|
Q_EMIT MarkerChanged(id);
|
|
Q_EMIT MarkersUpdated();
|
|
}
|
|
|
|
types::MarkerId MarkerManager::add_marker(const types::MarkerInfo& marker)
|
|
{
|
|
types::MarkerId id;
|
|
{
|
|
const std::unique_lock lock(p->markerRecordLock_);
|
|
id = p->NewId();
|
|
size_t index = p->markerRecords_.size();
|
|
p->idToIndex_.emplace(id, index);
|
|
p->markerRecords_.emplace_back(
|
|
std::make_shared<Impl::MarkerRecord>(marker));
|
|
p->markerRecords_[index]->markerInfo_.id = id;
|
|
|
|
add_icon(marker.iconName);
|
|
}
|
|
Q_EMIT MarkerAdded(id);
|
|
Q_EMIT MarkersUpdated();
|
|
return id;
|
|
}
|
|
|
|
void MarkerManager::remove_marker(types::MarkerId id)
|
|
{
|
|
{
|
|
const std::unique_lock lock(p->markerRecordLock_);
|
|
if (!p->idToIndex_.contains(id))
|
|
{
|
|
return;
|
|
}
|
|
size_t index = p->idToIndex_[id];
|
|
if (index >= p->markerRecords_.size())
|
|
{
|
|
logger_->warn("id in idToIndex_ but out of range!");
|
|
return;
|
|
}
|
|
|
|
p->markerRecords_.erase(std::next(p->markerRecords_.begin(), index));
|
|
p->idToIndex_.erase(id);
|
|
|
|
for (auto& pair : p->idToIndex_)
|
|
{
|
|
if (pair.second > index)
|
|
{
|
|
pair.second -= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Q_EMIT MarkerRemoved(id);
|
|
Q_EMIT MarkersUpdated();
|
|
}
|
|
|
|
void MarkerManager::move_marker(size_t from, size_t to)
|
|
{
|
|
{
|
|
const std::unique_lock lock(p->markerRecordLock_);
|
|
if (from >= p->markerRecords_.size() || to >= p->markerRecords_.size())
|
|
{
|
|
return;
|
|
}
|
|
std::shared_ptr<MarkerManager::Impl::MarkerRecord>& markerRecord =
|
|
p->markerRecords_[from];
|
|
|
|
if (from == to) {}
|
|
else if (from < to)
|
|
{
|
|
for (size_t i = from; i < to; i++)
|
|
{
|
|
p->markerRecords_[i] = p->markerRecords_[i + 1];
|
|
}
|
|
p->markerRecords_[to] = markerRecord;
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = from; i > to; i--)
|
|
{
|
|
p->markerRecords_[i] = p->markerRecords_[i - 1];
|
|
}
|
|
p->markerRecords_[to] = markerRecord;
|
|
}
|
|
}
|
|
Q_EMIT MarkersUpdated();
|
|
}
|
|
|
|
void MarkerManager::for_each(std::function<MarkerForEachFunc> func)
|
|
{
|
|
const std::shared_lock lock(p->markerRecordLock_);
|
|
for (auto marker : p->markerRecords_)
|
|
{
|
|
func(marker->markerInfo_);
|
|
}
|
|
}
|
|
|
|
void MarkerManager::add_icon(const std::string& name, bool startup)
|
|
{
|
|
{
|
|
const std::unique_lock lock(p->markerIconsLock_);
|
|
if (p->markerIcons_.contains(name))
|
|
{
|
|
return;
|
|
}
|
|
const std::shared_ptr<boost::gil::rgba8_image_t> image =
|
|
ResourceManager::LoadImageResource(name);
|
|
|
|
if (image)
|
|
{
|
|
auto icon = types::MarkerIconInfo(name, -1, -1, image);
|
|
p->markerIcons_.emplace(name, icon);
|
|
}
|
|
else
|
|
{
|
|
// defaultIconName should always be in markerIcons, so at is fine
|
|
auto icon = p->markerIcons_.at(defaultIconName);
|
|
p->markerIcons_.emplace(name, icon);
|
|
}
|
|
}
|
|
|
|
if (!startup)
|
|
{
|
|
ResourceManager::BuildAtlas();
|
|
Q_EMIT IconAdded(name);
|
|
}
|
|
}
|
|
|
|
std::optional<types::MarkerIconInfo>
|
|
MarkerManager::get_icon(const std::string& name)
|
|
{
|
|
const std::shared_lock lock(p->markerIconsLock_);
|
|
auto it = p->markerIcons_.find(name);
|
|
if (it != p->markerIcons_.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
const std::unordered_map<std::string, types::MarkerIconInfo>
|
|
MarkerManager::get_icons()
|
|
{
|
|
const std::shared_lock lock(p->markerIconsLock_);
|
|
return p->markerIcons_;
|
|
}
|
|
|
|
// Only use for testing
|
|
void MarkerManager::set_marker_settings_path(const std::string& path)
|
|
{
|
|
p->markerSettingsPath_ = path;
|
|
}
|
|
|
|
std::shared_ptr<MarkerManager> MarkerManager::Instance()
|
|
{
|
|
static std::weak_ptr<MarkerManager> markerManagerReference_ {};
|
|
static std::mutex instanceMutex_ {};
|
|
|
|
const std::unique_lock lock(instanceMutex_);
|
|
|
|
std::shared_ptr<MarkerManager> markerManager =
|
|
markerManagerReference_.lock();
|
|
|
|
if (markerManager == nullptr)
|
|
{
|
|
markerManager = std::make_shared<MarkerManager>();
|
|
markerManagerReference_ = markerManager;
|
|
}
|
|
|
|
return markerManager;
|
|
}
|
|
|
|
const std::string& MarkerManager::getDefaultIconName()
|
|
{
|
|
return defaultIconName;
|
|
}
|
|
|
|
} // namespace manager
|
|
} // namespace qt
|
|
} // namespace scwx
|