载入中...
搜索中...
未找到
ContainerAdapters.cpp
浏览该文件的文档.
2
4
5#include <algorithm>
6#include <exception>
7#include <limits>
8#include <string_view>
9#include <unordered_set>
10#include <utility>
11#include <vector>
12
13namespace eve::building {
14namespace {
15
16[[nodiscard]] std::string quote(std::string_view value) {
17 std::string result;
18 result.reserve(value.size() + 2);
19 result.push_back('"');
20 for (const unsigned char byte : value) {
21 switch (byte) {
22 case '"': result += "\\\""; break;
23 case '\\': result += "\\\\"; break;
24 case '\n': result += "\\n"; break;
25 case '\r': result += "\\r"; break;
26 case '\t': result += "\\t"; break;
27 default:
28 if (byte < 0x20u) {
29 result += "\\u00";
30 constexpr char digits[] = "0123456789abcdef";
31 result.push_back(digits[(byte >> 4u) & 0x0fu]);
32 result.push_back(digits[byte & 0x0fu]);
33 } else {
34 result.push_back(static_cast<char>(byte));
35 }
36 }
37 }
38 result.push_back('"');
39 return result;
40}
41
42[[nodiscard]] const GarrisonMember* payloadMember(const eve::container::ContainerObject& object,
43 GarrisonMember& fallback) {
44 const auto* payload = dynamic_cast<const BuildingGarrisonContainerObject*>(object.payload.get());
45 if (payload != nullptr) {
46 if (payload->member.id != object.id.value() || payload->member.type != object.type ||
47 payload->member.tags != object.tags)
48 return nullptr;
49 return &payload->member;
50 }
51 if (!object.id.isValid() || object.type.empty()) return nullptr;
52 fallback.id = object.id.value();
53 fallback.type = object.type;
54 fallback.tags = object.tags;
55 return &fallback;
56}
57
58[[nodiscard]] bool sameLayout(const eve::container::ContainerSnapshot& lhs,
60 if (lhs.id != rhs.id || lhs.revision != rhs.revision || lhs.entries.size() != rhs.entries.size()) return false;
61 for (const auto& left : lhs.entries) {
62 const auto right = std::find_if(rhs.entries.begin(), rhs.entries.end(), [&](const auto& entry) {
63 return entry.membership.object == left.membership.object;
64 });
65 if (right == rhs.entries.end() || left.membership.slot != right->membership.slot ||
66 left.membership.generation != right->membership.generation || left.object.id != right->object.id ||
67 left.object.type != right->object.type || left.object.quantity != right->object.quantity ||
68 left.object.tags != right->object.tags)
69 return false;
70 }
71 return true;
72}
73
74[[nodiscard]] game_event::GameEvent makeEvent(const eve::container::ContainerId& container,
75 const eve::container::MembershipId& object,
78 std::string_view reason = {}) {
80 if (!schema) std::terminate();
81
82 game_event::GameEvent event;
83 event.eventId = eve::container::deterministicContainerEventId(container, object, kind, serial);
85 event.source = container.value();
86 event.subject = object.value();
87 event.schemaId = *schema;
88 event.schemaVersion = eve::SchemaVersion(1);
89 event.tick = tick;
90 event.payload = "{\"containerId\":" + quote(container.value()) + ",\"objectId\":" + quote(object.value()) +
91 ",\"slot\":" + std::to_string(slot.isValid() ? slot.value() : -1) + ",\"revision\":\"" +
92 std::to_string(revision.value()) + "\"";
93 if (!reason.empty()) event.payload += ",\"reason\":" + quote(reason);
94 event.payload += '}';
95 return event;
96}
97
98void notify(const eve::container::GameEventSink& sink, const eve::container::ContainerId& container,
101 std::string_view reason, bool& callbackFailed) noexcept {
102 if (!sink) return;
103 try {
104 const auto event = makeEvent(container, object, kind, slot, revision, serial, tick, reason);
105 sink(event);
106 } catch (...) {
107 callbackFailed = true;
108 }
109}
110
111[[nodiscard]] eve::Status appliedStatus(bool callbackFailed) {
112 if (!callbackFailed) return eve::Status::success(eve::StatusCode::Applied);
113 try {
114 std::vector<eve::Diagnostic> diagnostics;
115 diagnostics.emplace_back(eve::Diagnostic::warning(
116 eve::DiagnosticCode::CallbackFailure, "container membership committed but an event observer threw"));
117 return eve::Status(eve::StatusCode::Applied, std::move(diagnostics));
118 } catch (...) {
120 }
121}
122
123} // namespace
124
126public:
127 PreparedState(BuildingGarrisonContainerAdapter& owner, std::vector<GarrisonMember> staged,
128 eve::Revision expectedRevision, std::size_t expectedSize, eve::Revision revision)
129 : owner_(owner),
130 staged_(std::move(staged)),
131 expectedRevision_(expectedRevision),
132 expectedSize_(expectedSize),
133 revision_(revision) {}
134
135 void commit() noexcept override {
136 if (committed_) return;
137 auto* building = owner_.building();
138 if (building == nullptr || building->garrisonRevision != expectedRevision_ ||
139 building->garrison.size() != expectedSize_)
140 std::terminate();
141 building->garrison.swap(staged_);
142 building->garrisonRevision = revision_;
143 committed_ = true;
144 }
145
146 void rollback() noexcept override {
147 if (!committed_) staged_.clear();
148 }
149
150private:
152 std::vector<GarrisonMember> staged_;
153 eve::Revision expectedRevision_;
154 std::size_t expectedSize_ = 0;
155 eve::Revision revision_;
156 bool committed_ = false;
157};
158
160 PlacementWorld* world, int instanceId,
163 : descriptor_{std::move(id), capacity, eve::container::Ordering::Insertion, std::move(accepted)},
164 world_(world),
165 instanceId_(instanceId) {}
166
167const PlacedBuilding* BuildingGarrisonContainerAdapter::building() const noexcept {
168 if (world_ == nullptr || instanceId_ <= 0) return nullptr;
169 const auto& values = world_->buildings();
170 const auto it = values.find(instanceId_);
171 return it == values.end() ? nullptr : &it->second;
172}
173
174PlacedBuilding* BuildingGarrisonContainerAdapter::building() noexcept {
175 if (world_ == nullptr || instanceId_ <= 0) return nullptr;
176 auto& values = world_->buildings();
177 const auto it = values.find(instanceId_);
178 return it == values.end() ? nullptr : &it->second;
179}
180
182 const auto* value = building();
183 return value == nullptr ? eve::Revision::zero() : value->garrisonRevision;
184}
185
186std::uint64_t BuildingGarrisonContainerAdapter::nextEventSerial() noexcept {
187 if (eventSerial_ != std::numeric_limits<std::uint64_t>::max()) ++eventSerial_;
188 return eventSerial_;
189}
190
191eve::container::ContainerObject BuildingGarrisonContainerAdapter::describe(const GarrisonMember& member) {
194 object.type = member.type;
195 object.tags = member.tags;
196 object.quantity = 1;
197 auto payload = std::make_shared<BuildingGarrisonContainerObject>();
198 payload->member = member;
199 object.payload = std::move(payload);
200 return object;
201}
202
204 const auto* value = building();
205 if (!descriptor_.id.isValid() || value == nullptr)
207 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "building garrison adapter is stale or unbound"));
208 if (!descriptor_.capacity.isUnlimited() && value->garrison.size() > descriptor_.capacity.value())
210 eve::DiagnosticCode::InvariantViolation, "building garrison exceeds configured capacity"));
211 eve::container::ContainerSnapshot result{descriptor_.id, value->garrisonRevision, {}};
212 result.entries.reserve(value->garrison.size());
213 std::unordered_set<eve::container::MembershipId> identities;
214 for (std::size_t index = 0; index < value->garrison.size(); ++index) {
215 if (index > static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max()))
217 eve::DiagnosticCode::InvariantViolation, "building garrison has too many indexed memberships"));
218 const auto& member = value->garrison[index];
219 if (!eve::container::MembershipId::from(member.id) || member.type.empty() ||
220 !identities.insert(eve::container::MembershipId(member.id)).second)
222 eve::DiagnosticCode::Conflict, "building garrison contains invalid or duplicate member"));
223 const auto id = eve::container::MembershipId(member.id);
224 result.entries.push_back(
225 {{id, eve::container::SlotIndex(static_cast<std::int32_t>(index)), eve::Generation(1)}, describe(member)});
226 }
228}
229
231 const eve::container::ContainerObject& object, std::optional<eve::container::SlotIndex> destination,
232 std::optional<eve::container::MembershipId> ignoredObject) const {
233 const auto* value = building();
234 if (!descriptor_.id.isValid() || value == nullptr)
236 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "building garrison adapter is stale or unbound"));
237 if (!object.id.isValid() || object.type.empty() || object.quantity != 1u)
239 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "building garrison object facts are invalid"));
240 GarrisonMember fallback;
241 if (payloadMember(object, fallback) == nullptr)
243 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "building garrison payload is stale"));
244 for (const auto& member : value->garrison) {
245 if (member.id == object.id.value() && (!ignoredObject || *ignoredObject != object.id))
247 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "building garrison already contains member"));
248 }
249 const std::size_t effectiveSize =
250 value->garrison.size() -
251 ((ignoredObject && std::any_of(value->garrison.begin(), value->garrison.end(),
252 [&](const auto& member) { return member.id == ignoredObject->value(); }))
253 ? 1u
254 : 0u);
255 if (destination && (!destination->isValid() || static_cast<std::size_t>(destination->value()) > effectiveSize))
257 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "building garrison slot is out of range"));
258 auto accepted = descriptor_.filter.evaluate(object);
259 if (!accepted) return accepted;
260 if (!descriptor_.capacity.isUnlimited() && effectiveSize >= descriptor_.capacity.value())
262 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "building garrison capacity is full"));
264}
265
268 const auto* value = building();
269 if (!descriptor_.id.isValid() || value == nullptr)
271 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "building garrison adapter is stale or unbound"));
272 if (expected.id != descriptor_.id || candidate.id != descriptor_.id)
274 eve::DiagnosticCode::InvalidArgument, "building garrison prepare targets another container"));
275 auto current = snapshot();
276 if (!current)
278 if (!sameLayout(current.value(), expected))
280 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "building garrison expected snapshot is stale"));
281 const auto next = expected.revision.incremented();
282 if (!next || candidate.revision != *next)
284 eve::DiagnosticCode::InvariantViolation, "building garrison candidate revision is not the next revision"));
285 if (!descriptor_.capacity.isUnlimited() && candidate.entries.size() > descriptor_.capacity.value())
287 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "building garrison candidate exceeds capacity"));
288
289 std::vector<GarrisonMember> staged;
290 staged.reserve(candidate.entries.size());
291 std::unordered_set<eve::container::MembershipId> identities;
292 for (std::size_t index = 0; index < candidate.entries.size(); ++index) {
293 const auto& entry = candidate.entries[index];
294 if (!entry.membership.object.isValid() || !entry.membership.slot.isValid() ||
295 entry.membership.slot.value() != static_cast<std::int32_t>(index) || entry.membership.generation.isZero() ||
296 entry.object.id != entry.membership.object || entry.object.quantity != 1u ||
297 !identities.insert(entry.membership.object).second)
300 "building garrison candidate contains invalid membership"));
301 GarrisonMember fallback;
302 const auto* member = payloadMember(entry.object, fallback);
303 if (member == nullptr)
306 "building garrison candidate payload is stale"));
307 staged.push_back(*member);
308 }
309 std::unique_ptr<eve::container::IContainer::PreparedState> prepared =
310 std::make_unique<PreparedState>(*this, std::move(staged), expected.revision, expected.entries.size(), *next);
312}
313
315 std::string type,
316 std::vector<std::string> tags,
319 const auto serial = nextEventSerial();
320 const eve::container::MembershipId objectId(std::move(memberId));
321 auto current = snapshot();
322 if (!current) {
323 const auto status = current.status();
324 bool ignored = false;
325 const auto* diagnostic = status.primaryDiagnostic();
326 notify(sink, descriptor_.id, objectId, eve::container::ContainerEventKind::Rejected,
328 diagnostic == nullptr ? "stale_container" : diagnostic->message(), ignored);
330 }
332 object.id = objectId;
333 object.type = type.empty() ? "building.garrison.member" : std::move(type);
334 object.tags = std::move(tags);
335 object.quantity = 1;
336 auto accepted = validateInsert(object, std::nullopt, std::nullopt);
337 if (!accepted) {
338 const auto status = accepted.status();
339 bool ignored = false;
340 const auto* diagnostic = status.primaryDiagnostic();
341 notify(sink, descriptor_.id, objectId, eve::container::ContainerEventKind::Rejected,
343 diagnostic == nullptr ? "rejected" : diagnostic->message(), ignored);
345 }
346 auto candidate = current.value();
347 const auto next = candidate.revision.incremented();
348 if (!next) {
349 const auto status = eve::Status::failure(
350 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "building garrison revision exhausted"));
351 bool ignored = false;
352 notify(sink, descriptor_.id, objectId, eve::container::ContainerEventKind::Rejected,
353 eve::container::SlotIndex::invalid(), candidate.revision, serial, tick, "revision_exhausted", ignored);
355 }
356 candidate.revision = *next;
357 candidate.entries.push_back(
358 {{objectId, eve::container::SlotIndex(static_cast<std::int32_t>(current.value().entries.size())),
359 eve::Generation(1)},
360 std::move(object)});
361 auto prepared = prepare(current.value(), candidate);
362 if (!prepared) {
363 const auto status = prepared.status();
364 bool ignored = false;
365 const auto* diagnostic = status.primaryDiagnostic();
366 notify(sink, descriptor_.id, objectId, eve::container::ContainerEventKind::Rejected,
368 diagnostic == nullptr ? "prepare_failed" : diagnostic->message(), ignored);
370 }
371 auto staged = std::move(prepared).takeValue();
372 staged->commit();
373 bool callbackFailed = false;
374 const auto slot = candidate.entries.back().membership.slot;
375 notify(sink, descriptor_.id, objectId, eve::container::ContainerEventKind::Accepted, slot, *next, serial, tick, {},
376 callbackFailed);
377 notify(sink, descriptor_.id, objectId, eve::container::ContainerEventKind::Enter, slot, *next, serial, tick, {},
378 callbackFailed);
379 return eve::Result<eve::container::MembershipId>::success(objectId, appliedStatus(callbackFailed));
380}
381
385 const auto serial = nextEventSerial();
386 auto current = snapshot();
388 if (current) {
389 for (const auto& entry : current.value().entries) {
390 if (entry.membership.object == memberId) {
391 slot = entry.membership.slot;
392 break;
393 }
394 }
395 }
396 if (!current || !slot.isValid()) {
398 eve::DiagnosticCode::NotFound, "building garrison member was not found"))
399 : current.status();
400 bool ignored = false;
401 const auto* diagnostic = status.primaryDiagnostic();
402 notify(sink, descriptor_.id, memberId, eve::container::ContainerEventKind::Rejected, slot, revision(), serial,
403 tick, diagnostic == nullptr ? "rejected" : diagnostic->message(), ignored);
405 }
406 auto candidate = current.value();
407 candidate.entries.erase(std::remove_if(candidate.entries.begin(), candidate.entries.end(),
408 [&](const auto& entry) { return entry.membership.object == memberId; }),
409 candidate.entries.end());
410 for (std::size_t index = 0; index < candidate.entries.size(); ++index)
411 candidate.entries[index].membership.slot = eve::container::SlotIndex(static_cast<std::int32_t>(index));
412 const auto next = candidate.revision.incremented();
413 if (!next) {
414 const auto status = eve::Status::failure(
415 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "building garrison revision exhausted"));
416 bool ignored = false;
417 notify(sink, descriptor_.id, memberId, eve::container::ContainerEventKind::Rejected, slot,
418 current.value().revision, serial, tick, "revision_exhausted", ignored);
420 }
421 candidate.revision = *next;
422 auto prepared = prepare(current.value(), candidate);
423 if (!prepared) {
424 const auto status = prepared.status();
425 bool ignored = false;
426 const auto* diagnostic = status.primaryDiagnostic();
427 notify(sink, descriptor_.id, memberId, eve::container::ContainerEventKind::Rejected, slot,
428 current.value().revision, serial, tick, diagnostic == nullptr ? "prepare_failed" : diagnostic->message(),
429 ignored);
431 }
432 auto staged = std::move(prepared).takeValue();
433 staged->commit();
434 bool callbackFailed = false;
435 notify(sink, descriptor_.id, memberId, eve::container::ContainerEventKind::Accepted, slot, *next, serial, tick, {},
436 callbackFailed);
437 notify(sink, descriptor_.id, memberId, eve::container::ContainerEventKind::Exit, slot, *next, serial, tick, {},
438 callbackFailed);
439 return eve::Result<eve::container::MembershipId>::success(memberId, appliedStatus(callbackFailed));
440}
441
442} // namespace eve::building
double value
Value::Object payload
std::unordered_map< std::string, QuestRuntime > entries
std::map< std::string, Var > values
std::uint32_t capacity
wgpu::PopErrorScopeStatus status
float u
Definition Grass.cpp:233
HexVec3 left
HexVec3 right
TokenKind kind
char quote
World3D * world
std::uint64_t revision
std::string id
Definition PlayHost.cpp:108
double current
SimulationTick tick
Json object
uint64_t serial
Definition UISystem.cpp:68
float(ui::Theme::* member)[4]
uint32_t index
Building adapters for the common Container/Zone/Transfer protocol.
static Diagnostic warning(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct a warning diagnostic.
Definition Diagnostic.h:134
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
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
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
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
static Status failure(StatusCode code, Diagnostic diagnostic)
Construct a failed status with one diagnostic.
Definition Status.h:84
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
void rollback() noexcept override
Abandon an unpublished stage; the operation is non-throwing.
void commit() noexcept override
Publish the staged state; the operation is non-throwing.
PreparedState(BuildingGarrisonContainerAdapter &owner, std::vector< GarrisonMember > staged, eve::Revision expectedRevision, std::size_t expectedSize, eve::Revision revision)
Transactional adapter over one placed building's authoritative garrison.
eve::Result< eve::container::MembershipId > exit(eve::container::MembershipId memberId, eve::container::GameEventSink sink={}, eve::SimulationTick tick=eve::SimulationTick::zero())
Stage and publish removal of one member.
eve::Result< eve::container::ContainerSnapshot > snapshot() const override
Snapshot.
eve::Revision revision() const noexcept
Return the authoritative garrison revision, or zero when stale.
BuildingGarrisonContainerAdapter(eve::container::ContainerId id, PlacementWorld *world, int instanceId, eve::container::Capacity capacity=eve::container::Capacity::unlimited(), eve::container::AcceptedCondition accepted={})
Binds an adapter to a placed building.
eve::Result< std::unique_ptr< eve::container::IContainer::PreparedState > > prepare(const eve::container::ContainerSnapshot &expected, const eve::container::ContainerSnapshot &candidate) override
Prepare an exact replacement from expected to candidate.
eve::Result< void > validateInsert(const eve::container::ContainerObject &object, std::optional< eve::container::SlotIndex > destination, std::optional< eve::container::MembershipId > ignoredObject=std::nullopt) const override
Validate insert.
eve::Result< eve::container::MembershipId > enter(std::string memberId, std::string type="building.garrison.member", std::vector< std::string > tags={}, eve::container::GameEventSink sink={}, eve::SimulationTick tick=eve::SimulationTick::zero())
Stage and publish a new garrison member.
格子型建筑放置世界(脚本可直接操作)。
const std::unordered_map< int, PlacedBuilding > & buildings() const
Non-negative capacity measured in membership slots.
Definition Container.h:92
constexpr std::size_t value() const noexcept
Definition Container.h:99
constexpr bool isUnlimited() const noexcept
Definition Container.h:98
Strong identity of one runtime or persistent container.
Definition Container.h:44
bool isValid() const noexcept
Return whether this ID is usable by a transfer request.
Definition Container.h:58
const std::string & value() const noexcept
Return the owned stable spelling.
Definition Container.h:60
Extensible filter result used by container and zone acceptance rules.
Definition Container.h:139
Result< void > evaluate(const struct ContainerObject &object) const
Evaluate the filter; an empty filter accepts every object.
Definition Container.h:149
Detached state prepared for an atomic container replacement.
Definition Container.h:296
Strong identity of an object which can have one container membership.
Definition Container.h:68
static std::optional< MembershipId > from(std::string_view value)
Construct a valid membership ID, rejecting an empty value.
Definition Container.h:76
Explicit slot number; it never implicitly converts to an integer.
Definition Container.h:117
static constexpr SlotIndex invalid() noexcept
Invalid.
Definition Container.h:124
constexpr std::int32_t value() const noexcept
Value.
Definition Container.h:128
constexpr bool isValid() const noexcept
True when valid.
Definition Container.h:126
constexpr std::optional< StrongUint64 > incremented() const noexcept
Returns the next value, or empty instead of unsigned wraparound.
static constexpr StrongUint64 zero() noexcept
Returns the zero value for this strong type.
建筑放置模块入口:定义 / 放置世界 / 鬼影 / 变更事件的脚本绑定点。 设计文档:docs/dev/建筑放置系统设计.md
Definition Building.cpp:7
constexpr std::string_view containerEventSchemaName
Canonical schema name for container lifecycle envelopes.
Definition Container.h:232
ContainerEventKind
Lifecycle outcome represented by every container-domain event.
Definition Container.h:205
const char * containerEventTypeName(ContainerEventKind kind) noexcept
Stable event type spelling shared by all container adapters.
Definition Container.h:221
std::function< void(const game_event::GameEvent &)> GameEventSink
Callback receiving a canonical game_event envelope.
Definition Container.h:245
EventId deterministicContainerEventId(const ContainerId &container, const MembershipId &object, ContainerEventKind kind, std::uint64_t serial) noexcept
Derives a deterministic event identity for an adapter lifecycle event.
Definition Container.h:257
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
detail::StrongUint64< detail::GenerationTag > Generation
Registry/object replacement generation used to reject stale handles.
Definition Generation.h:13
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
One persistent member of a placed building's logical garrison.
已放置的建筑实例。
Generic object facts used by filters and transfer diagnostics.
Definition Container.h:168
Lossless snapshot of one container at one revision.
Definition Container.h:190
std::vector< MembershipEntry > entries
Definition Container.h:193