载入中...
搜索中...
未找到
GameplayTag.cpp
浏览该文件的文档.
1#include "tags/GameplayTag.h"
2
3#include <algorithm>
4#include <cctype>
5#include <iomanip>
6#include <sstream>
7#include <utility>
8
9namespace eve::tags {
10namespace {
11
12std::string idText(GameplayTagId id) {
13 std::ostringstream stream;
14 stream << std::hex << std::setfill('0') << std::setw(16) << id.value();
15 return stream.str();
16}
17
18std::string lower(std::string_view value) {
19 std::string result(value);
20 std::transform(result.begin(), result.end(), result.begin(),
21 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
22 return result;
23}
24
25const Value* member(const Value& value, std::string_view key) { return value.find(std::string(key)); }
26
27} // namespace
28
29bool isValidGameplayTagName(std::string_view name) noexcept {
30 if (name.empty() || name.front() == '.' || name.back() == '.') return false;
31 bool segmentHasCharacter = false;
32 for (const unsigned char c : name) {
33 if (c == '.') {
34 if (!segmentHasCharacter) return false;
35 segmentHasCharacter = false;
36 continue;
37 }
38 if (!(std::isalnum(c) || c == '_' || c == '-')) return false;
39 segmentHasCharacter = true;
40 }
41 return segmentHasCharacter;
42}
43
44GameplayTagId gameplayTagId(std::string_view name) noexcept {
45 std::uint64_t hash = 14695981039346656037ull;
46 for (const unsigned char byte : name) {
47 hash ^= byte;
48 hash *= 1099511628211ull;
49 }
50 return GameplayTagId(hash);
51}
52
53bool gameplayTagMatches(std::string_view candidate, std::string_view query, GameplayTagMatch match) noexcept {
54 if (candidate == query) return true;
55 return match == GameplayTagMatch::IncludeDescendants && candidate.size() > query.size() &&
56 candidate.starts_with(query) && candidate[query.size()] == '.';
57}
58
62 Diagnostic::error(DiagnosticCode::InvalidArgument, "Invalid gameplay-tag name", "name"));
64 if (const auto existing = definitionsByName_.find(name); existing != definitionsByName_.end()) {
65 if (existing->second.description != description)
67 DiagnosticCode::AlreadyExists, "Gameplay tag is already registered with different metadata", name));
69 }
70 if (const auto collision = nameById_.find(id); collision != nameById_.end() && collision->second != name)
72 Diagnostic::error(DiagnosticCode::Conflict, "Gameplay-tag hash collision", name));
73 definitionsByName_.emplace(name, GameplayTagDefinition{id, name, std::move(description)});
74 nameById_.emplace(id, std::move(name));
76}
77
79 const auto found = definitionsByName_.find(name);
80 if (found == definitionsByName_.end())
82 Diagnostic::error(DiagnosticCode::NotFound, "Gameplay tag is not registered", std::string(name)));
84}
85
86bool GameplayTagRegistry::contains(std::string_view name) const { return definitionsByName_.contains(name); }
87
88std::vector<GameplayTagDefinition> GameplayTagRegistry::definitions() const {
89 std::vector<GameplayTagDefinition> result;
90 result.reserve(definitionsByName_.size());
91 for (const auto& [name, definition] : definitionsByName_) result.push_back(definition);
92 return result;
93}
94
95std::vector<GameplayTagDefinition> GameplayTagRegistry::search(std::string_view text, std::string_view root) const {
96 const std::string needle = lower(text);
97 std::vector<GameplayTagDefinition> result;
98 for (const auto& [name, definition] : definitionsByName_) {
100 if (!needle.empty() && lower(name + " " + definition.description).find(needle) == std::string::npos) continue;
101 result.push_back(definition);
102 }
103 return result;
104}
105
107 Value::Array tags;
108 tags.reserve(definitionsByName_.size());
109 for (const auto& [name, definition] : definitionsByName_) {
110 tags.emplace_back(
111 Value::Object{{"description", definition.description}, {"id", idText(definition.id)}, {"name", name}});
112 }
114 Value(Value::Object{{"schema", "eve.gameplay-tags"}, {"tags", std::move(tags)}, {"version", 1}}));
115}
116
118 if (!value.isObject())
120 Diagnostic::error(DiagnosticCode::InvalidArgument, "Gameplay-tag registry must be an object", "$"));
121 const Value* schema = member(value, "schema");
122 const Value* version = member(value, "version");
123 const Value* tags = member(value, "tags");
124 if (!schema || !schema->isString() || schema->asString() != "eve.gameplay-tags")
126 Diagnostic::error(DiagnosticCode::InvalidArgument, "Unsupported gameplay-tag schema", "schema"));
127 if (!version || !version->isInt64() || version->asInt() != 1)
129 Diagnostic::error(DiagnosticCode::InvalidArgument, "Unsupported gameplay-tag schema version", "version"));
130 if (!tags || !tags->isArray())
132 Diagnostic::error(DiagnosticCode::InvalidArgument, "Gameplay tags must be an array", "tags"));
133
134 GameplayTagRegistry candidate;
135 for (std::size_t i = 0; i < tags->arraySize(); ++i) {
136 const auto& entry = tags->at(i);
137 const std::string path = "tags[" + std::to_string(i) + "]";
138 if (!entry.isObject())
140 Diagnostic::error(DiagnosticCode::InvalidArgument, "Gameplay-tag entry must be an object", path));
141 const Value* name = member(entry, "name");
142 const Value* description = member(entry, "description");
143 const Value* id = member(entry, "id");
144 if (!name || !name->isString())
146 DiagnosticCode::InvalidArgument, "Gameplay-tag name must be a string", path + ".name"));
147 if (!description || !description->isString())
149 DiagnosticCode::InvalidArgument, "Gameplay-tag description must be a string", path + ".description"));
150 if (!id || !id->isString() || id->asString() != idText(gameplayTagId(name->asString())))
152 DiagnosticCode::InvalidArgument, "Gameplay-tag id does not match its canonical name", path + ".id"));
153 auto registered = candidate.registerTag(name->asString(), description->asString());
154 if (!registered) return Result<GameplayTagRegistry>::failure(registered.status());
155 }
156 return Result<GameplayTagRegistry>::success(std::move(candidate));
157}
158
159} // namespace eve::tags
double value
int root
Definition AnimSmr.cpp:119
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
Stable hierarchical gameplay-tag definitions and registry.
std::uint32_t key
std::int32_t c
std::string text
std::string description
std::string name
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
bool found
float(ui::Theme::* member)[4]
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
const std::string & asString() const
Return the string payload; the caller must have checked the kind.
Definition Value.cpp:87
bool isInt64() const noexcept
Return true when this value is an Int64.
Definition Value.h:91
std::map< std::string, Value > Object
Definition Value.h:34
bool isArray() const noexcept
Return true when this value is an array.
Definition Value.h:97
bool isString() const noexcept
Return true when this value is a string.
Definition Value.h:95
std::int64_t asInt() const
Return the Int64 payload; the caller must have checked the kind.
Definition Value.cpp:69
std::vector< Value > Array
Definition Value.h:33
std::size_t arraySize() const
Return the number of array elements.
Definition Value.cpp:100
Value & at(std::size_t index)
Return a bounds-checked array element.
Definition Value.cpp:106
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
Definition Value.cpp:124
Canonical owner of versioned gameplay-tag definitions.
Definition GameplayTag.h:49
Result< GameplayTagDefinition > find(std::string_view name) const
Find an exact definition and return an owning copy.
std::vector< GameplayTagDefinition > search(std::string_view text, std::string_view root={}) const
Return definitions matching a root and case-insensitive text filter.
Result< GameplayTagId > registerTag(std::string name, std::string description={})
Register a definition, returning NoOp for an identical definition.
Result< Value > toValue() const
Encode schema eve.gameplay-tags version 1. Unknown fields are ignored on load.
std::vector< GameplayTagDefinition > definitions() const
Return all definitions in lexical name order.
static Result< GameplayTagRegistry > fromValue(const Value &value)
Transactionally decode schema eve.gameplay-tags version 1.
bool contains(std::string_view name) const
Return whether an exact definition exists.
bool gameplayTagMatches(std::string_view candidate, std::string_view query, GameplayTagMatch match) noexcept
Tests exact or dot-boundary descendant membership.
bool isValidGameplayTagName(std::string_view name) noexcept
Returns whether a name is a canonical dot-separated gameplay tag.
GameplayTagMatch
Selects exact or hierarchical descendant matching.
Definition GameplayTag.h:23
eve::detail::StrongUint64< GameplayTagIdTag > GameplayTagId
Stable FNV-1a identifier derived from a canonical gameplay-tag name.
Definition GameplayTag.h:20
GameplayTagId gameplayTagId(std::string_view name) noexcept
Computes the stable 64-bit FNV-1a identifier of a canonical name.
An owning gameplay-tag definition shared by runtime and editor tools.
Definition GameplayTag.h:26