spectator/id.h (177 lines of code) (raw):
#pragma once
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <memory>
#include <ostream>
#include <string>
namespace spectator {
class Tags {
using table_t = absl::flat_hash_map<std::string, std::string>;
table_t entries_;
public:
Tags() = default;
Tags(std::initializer_list<std::pair<absl::string_view, absl::string_view>> vs) {
for (auto& pair : vs) {
add(pair.first, pair.second);
}
}
template <typename Cont>
static Tags from(Cont&& cont) {
Tags tags;
tags.entries_.reserve(cont.size());
for (auto&& kv : cont) {
tags.add(kv.first, kv.second);
}
return tags;
}
void add(absl::string_view k, absl::string_view v) {
entries_[k] = std::string(v);
}
[[nodiscard]] size_t hash() const {
using hs = std::hash<std::string>;
size_t h = 0;
for (const auto& entry : entries_) {
h += (hs()(entry.first) << 1U) ^ hs()(entry.second);
}
return h;
}
void move_all(Tags&& source) {
entries_.insert(std::make_move_iterator(source.begin()),
std::make_move_iterator(source.end()));
}
bool operator==(const Tags& that) const { return that.entries_ == entries_; }
[[nodiscard]] bool has(absl::string_view key) const {
return entries_.find(key) != entries_.end();
}
[[nodiscard]] std::string at(absl::string_view key) const {
auto entry = entries_.find(key);
if (entry != entries_.end()) {
return entry->second;
}
return {};
}
[[nodiscard]] size_t size() const { return entries_.size(); }
[[nodiscard]] table_t::const_iterator begin() const {
return entries_.begin();
}
[[nodiscard]] table_t::const_iterator end() const { return entries_.end(); }
};
inline std::ostream& operator<<(std::ostream& os, const Tags& tags) {
os << fmt::format("{}", tags);
return os;
}
class Id {
public:
Id(absl::string_view name, Tags tags) noexcept
: name_(name), tags_(std::move(tags)), hash_(0u) {}
static std::shared_ptr<Id> of(absl::string_view name, Tags tags = {}) {
return std::make_shared<Id>(name, std::move(tags));
}
bool operator==(const Id& rhs) const noexcept {
return name_ == rhs.name_ && tags_ == rhs.tags_;
}
const std::string& Name() const noexcept { return name_; }
const Tags& GetTags() const noexcept { return tags_; }
std::unique_ptr<Id> WithTag(const std::string& key,
const std::string& value) const {
// Create a copy
Tags tags{GetTags()};
tags.add(key, value);
return std::make_unique<Id>(Name(), tags);
}
std::unique_ptr<Id> WithTags(Tags&& extra_tags) const {
Tags tags{GetTags()};
tags.move_all(std::move(extra_tags));
return std::make_unique<Id>(Name(), tags);
}
std::unique_ptr<Id> WithTags(const Tags& extra_tags) const {
Tags tags{GetTags()};
for (const auto& t : extra_tags) {
tags.add(t.first, t.second);
}
return std::make_unique<Id>(Name(), tags);
}
std::unique_ptr<Id> WithStat(const std::string& stat) const {
return WithTag("statistic", stat);
};
static std::shared_ptr<Id> WithDefaultStat(std::shared_ptr<Id> baseId,
const std::string& stat) {
if (baseId->GetTags().has("statistic")) {
return baseId;
} else {
return baseId->WithStat(stat);
}
}
friend std::ostream& operator<<(std::ostream& os, const Id& id) {
os << fmt::format("{}", id);
return os;
}
friend struct std::hash<Id>;
friend struct std::hash<std::shared_ptr<Id>>;
private:
std::string name_;
Tags tags_;
mutable size_t hash_;
size_t Hash() const noexcept {
if (hash_ == 0) {
// compute hash code, and reuse it
hash_ = tags_.hash() ^ std::hash<std::string>()(name_);
}
return hash_;
}
};
using IdPtr = std::shared_ptr<Id>;
} // namespace spectator
namespace std {
template <>
struct hash<spectator::Id> {
size_t operator()(const spectator::Id& id) const { return id.Hash(); }
};
template <>
struct hash<spectator::Tags> {
size_t operator()(const spectator::Tags& tags) const { return tags.hash(); }
};
template <>
struct hash<shared_ptr<spectator::Id>> {
size_t operator()(const shared_ptr<spectator::Id>& id) const {
return id->Hash();
}
};
template <>
struct equal_to<shared_ptr<spectator::Id>> {
bool operator()(const shared_ptr<spectator::Id>& lhs,
const shared_ptr<spectator::Id>& rhs) const {
return *lhs == *rhs;
}
};
} // namespace std
template <> struct fmt::formatter<spectator::Tags>: formatter<std::string_view> {
auto format(const spectator::Tags& tags, format_context& ctx) const -> format_context::iterator {
std::string s;
auto size = tags.size();
if (size > 0) {
// sort keys, to ensure stable output
std::vector<std::string> keys;
for (const auto& pair : tags) {
keys.push_back(pair.first);
}
std::sort(keys.begin(), keys.end());
s = "[";
for (const auto &key : keys) {
if (size > 1) {
s += key + "=" + tags.at(key) + ", ";
} else {
s += key + "=" + tags.at(key) + "]";
}
size -= 1;
}
} else {
s = "[]";
}
return fmt::formatter<std::string_view>::format(s, ctx);
}
};
template <> struct fmt::formatter<spectator::Id>: formatter<std::string_view> {
static auto format(const spectator::Id& id, format_context& ctx) -> format_context::iterator {
return fmt::format_to(ctx.out(), "Id(name={}, tags={})", id.Name(), id.GetTags());
}
};