载入中...
搜索中...
未找到
CardContainers.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <exception>
5#include <limits>
6#include <unordered_set>
7
8namespace eve::card {
9namespace {
10
11[[nodiscard]]
12[[nodiscard]] bool hasId(const std::vector<CardData*>& cards, const eve::container::MembershipId& id) {
13 return std::any_of(cards.begin(), cards.end(), [&](const CardData* card) {
14 return card != nullptr && const_cast<CardData*>(card)->identity()->id == id.value();
15 });
16}
17
18[[nodiscard]] bool sameLayout(const eve::container::ContainerSnapshot& lhs,
20 if (lhs.id != rhs.id || lhs.revision != rhs.revision || lhs.entries.size() != rhs.entries.size()) return false;
21 for (std::size_t index = 0; index < lhs.entries.size(); ++index) {
22 const auto& left = lhs.entries[index];
23 const auto& right = rhs.entries[index];
24 if (left.membership.object != right.membership.object || left.membership.slot != right.membership.slot ||
25 left.membership.generation != right.membership.generation || left.object.id != right.object.id ||
26 left.object.type != right.object.type || left.object.quantity != right.object.quantity)
27 return false;
28 }
29 return true;
30}
31
32} // namespace
33
35public:
36 PreparedState(CardContainerAdapter& owner, std::vector<CardData*> staged, eve::Revision revision)
37 : owner_(owner), staged_(std::move(staged)), revision_(revision) {}
38
39 void commit() noexcept override {
40 if (committed_) return;
41 auto* live = owner_.cards();
42 if (live == nullptr) std::terminate();
43 live->swap(staged_);
44 owner_.revision_ = revision_;
45 for (auto* card : *live) owner_.updateState(card);
46 committed_ = true;
47 }
48
49 void rollback() noexcept override {
50 if (!committed_) staged_.clear();
51 }
52
53private:
55 std::vector<CardData*> staged_;
56 eve::Revision revision_;
57 bool committed_ = false;
58};
59
61 Hand* hand, std::vector<CardData*>* discard,
62 eve::container::Capacity capacity, std::vector<std::string> acceptedKinds)
63 : descriptor_{std::move(id), capacity,
64 kind == CardContainerKind::Deck ? eve::container::Ordering::Stack
65 : eve::container::Ordering::Insertion,
66 eve::container::Filter{}},
67 kind_(kind),
68 deck_(deck),
69 hand_(hand),
70 discard_(discard) {
71 if (!acceptedKinds.empty()) {
72 descriptor_.filter = eve::container::Filter(
73 [acceptedKinds = std::move(acceptedKinds)](const eve::container::ContainerObject& object) {
74 if (std::find(acceptedKinds.begin(), acceptedKinds.end(), object.type) == acceptedKinds.end())
76 "card kind is not accepted by container"));
78 });
79 }
80 if (kind_ == CardContainerKind::Deck && deck_ == nullptr)
83 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card deck adapter has no Deck"));
84 });
85 if (kind_ == CardContainerKind::Hand && hand_ == nullptr)
88 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card hand adapter has no Hand"));
89 });
90 if (kind_ == CardContainerKind::Discard && discard_ == nullptr)
93 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card discard adapter has no storage"));
94 });
95}
96
97std::vector<CardData*>* CardContainerAdapter::cards() noexcept {
98 if (kind_ == CardContainerKind::Deck) return deck_ ? &deck_->membership()->cards : nullptr;
99 if (kind_ == CardContainerKind::Hand) return hand_ ? &hand_->membership()->cards : nullptr;
100 return discard_;
101}
102
103const std::vector<CardData*>* CardContainerAdapter::cards() const noexcept {
104 if (kind_ == CardContainerKind::Deck) return deck_ ? &deck_->membership()->cards : nullptr;
105 if (kind_ == CardContainerKind::Hand) return hand_ ? &hand_->membership()->cards : nullptr;
106 return discard_;
107}
108
109eve::container::MembershipId CardContainerAdapter::objectId(const CardData& card) {
110 return eve::container::MembershipId(const_cast<CardData&>(card).identity()->id);
111}
112
113eve::container::ContainerObject CardContainerAdapter::describe(CardData* card) {
115 if (card == nullptr) return object;
116 object.id = objectId(*card);
117 object.type = card->identity()->kind;
118 object.quantity = 1;
119 auto payload = std::make_shared<CardContainerObject>();
120 payload->card = ecs::handle_of(card);
121 object.payload = std::move(payload);
122 return object;
123}
124
125void CardContainerAdapter::updateState(CardData* card) const noexcept {
126 if (card == nullptr) return;
127 if (kind_ == CardContainerKind::Deck)
128 card->state()->phase = CardState::Deck;
129 else if (kind_ == CardContainerKind::Hand)
130 card->state()->phase = card->visual()->disabled ? CardState::Disabled : CardState::Hand;
131 else
132 card->state()->phase = CardState::Discarded;
133}
134
136 const auto* values = cards();
137 if (!descriptor_.id.isValid() || values == nullptr)
139 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card container adapter is not bound"));
140 if (!descriptor_.capacity.isUnlimited() && values->size() > descriptor_.capacity.value())
142 eve::DiagnosticCode::InvariantViolation, "card container exceeds configured capacity"));
143 eve::container::ContainerSnapshot snapshot{descriptor_.id, revision_, {}};
144 std::unordered_set<eve::container::MembershipId> ids;
145 snapshot.entries.reserve(values->size());
146 for (std::size_t i = 0; i < values->size(); ++i) {
147 if (i > static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max()))
149 eve::DiagnosticCode::InvariantViolation, "card container has too many indexed memberships"));
150 CardData* card = (*values)[i];
151 if (card == nullptr || card->identity()->id.empty())
153 eve::DiagnosticCode::InvariantViolation, "card container contains null or unidentified card"));
154 const auto id = objectId(*card);
155 if (!ids.insert(id).second)
157 eve::DiagnosticCode::Conflict, "card container contains duplicate card identity"));
158 snapshot.entries.push_back(
159 {{id, eve::container::SlotIndex(static_cast<std::int32_t>(i)), eve::Generation(1)}, describe(card)});
160 }
162}
163
165 const eve::container::ContainerObject& object, std::optional<eve::container::SlotIndex> destination,
166 std::optional<eve::container::MembershipId> ignoredObject) const {
167 const auto* values = cards();
168 if (values == nullptr || !descriptor_.id.isValid())
169 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card container adapter is not bound"));
170 if (!object.id.isValid() || !object.payload)
172 if (object.quantity != 1u)
173 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "a card container membership must have quantity one"));
174 const auto* payload = dynamic_cast<const CardContainerObject*>(object.payload.get());
175 auto* card = payload == nullptr ? nullptr : static_cast<CardData*>(ecs::try_get(payload->card));
176 if (payload == nullptr || card == nullptr || card->identity()->id != object.id.value() ||
177 card->identity()->kind != object.type)
179 if (hasId(*values, object.id) && (!ignoredObject || *ignoredObject != object.id))
180 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "destination already contains card"));
181 const std::size_t effectiveSize = values->size() - ((ignoredObject && hasId(*values, *ignoredObject)) ? 1u : 0u);
182 if (destination) {
183 if (!destination->isValid() || static_cast<std::size_t>(destination->value()) > effectiveSize)
184 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "destination card slot is out of range"));
185 }
186 auto accepted = descriptor_.filter.evaluate(object);
187 if (!accepted) return accepted;
188 if (!descriptor_.capacity.isUnlimited() && effectiveSize >= descriptor_.capacity.value())
189 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "card container capacity is full"));
191}
192
195 const auto* values = cards();
196 if (values == nullptr || !descriptor_.id.isValid())
198 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card container adapter is not bound"));
199 if (expected.id != descriptor_.id || candidate.id != descriptor_.id)
201 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card prepare targets another container"));
202 auto currentResult = snapshot();
203 if (!currentResult)
204 return eve::Result<std::unique_ptr<eve::container::IContainer::PreparedState>>::failure(currentResult.status());
205 if (!sameLayout(currentResult.value(), expected))
207 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "card prepare expected snapshot is stale"));
208 const auto nextRevision = expected.revision.incremented();
209 if (!nextRevision || candidate.revision != *nextRevision)
211 eve::DiagnosticCode::InvariantViolation, "card candidate revision is not the next revision"));
212 if (!descriptor_.capacity.isUnlimited() && candidate.entries.size() > descriptor_.capacity.value())
214 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "card candidate exceeds container capacity"));
215
216 std::vector<const eve::container::MembershipEntry*> ordered;
217 ordered.reserve(candidate.entries.size());
218 for (const auto& entry : candidate.entries) ordered.push_back(&entry);
219 std::sort(ordered.begin(), ordered.end(),
220 [](const auto* lhs, const auto* rhs) { return lhs->membership.slot < rhs->membership.slot; });
221 std::vector<CardData*> staged;
222 staged.reserve(ordered.size());
223 std::unordered_set<eve::container::MembershipId> ids;
224 for (std::size_t index = 0; index < ordered.size(); ++index) {
225 const auto* entry = ordered[index];
226 if (!entry->membership.object.isValid() || !entry->membership.slot.isValid() ||
227 entry->membership.slot.value() != static_cast<std::int32_t>(index) ||
228 entry->membership.generation.isZero() || entry->object.id != entry->membership.object ||
229 entry->object.quantity != 1u || !ids.insert(entry->membership.object).second)
232 "card candidate contains invalid membership"));
233 const auto* payload = dynamic_cast<const CardContainerObject*>(entry->object.payload.get());
234 auto* card = payload == nullptr ? nullptr : static_cast<CardData*>(ecs::try_get(payload->card));
235 if (payload == nullptr || card == nullptr || card->identity()->id != entry->membership.object.value() ||
236 entry->object.type != card->identity()->kind)
237 return eve::Result<std::unique_ptr<eve::container::IContainer::PreparedState>>::failure(
238 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "card candidate payload is stale"));
239 staged.push_back(card);
240 }
241 std::unique_ptr<eve::container::IContainer::PreparedState> prepared =
242 std::make_unique<PreparedState>(*this, std::move(staged), *nextRevision);
244}
245
246} // namespace eve::card
Value::Object payload
Container protocol adapters for card deck, hand and discard piles.
std::map< std::string, Var > values
std::uint32_t capacity
glm::uvec4 ids
float u
Definition Grass.cpp:233
HexVec3 left
HexVec3 right
TokenKind kind
std::string id
Definition PlayHost.cpp:108
Json object
uint32_t index
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
void commit() noexcept override
Publish the staged state; the operation is non-throwing.
void rollback() noexcept override
Abandon an unpublished stage; the operation is non-throwing.
PreparedState(CardContainerAdapter &owner, std::vector< CardData * > staged, eve::Revision revision)
Exposes one card collection through the generic atomic transfer API.
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.
CardContainerAdapter(eve::container::ContainerId id, CardContainerKind kind, Deck *deck=nullptr, Hand *hand=nullptr, std::vector< CardData * > *discard=nullptr, eve::container::Capacity capacity=eve::container::Capacity::unlimited(), std::vector< std::string > acceptedKinds={})
Bind a borrowed Deck, Hand or discard collection.
eve::Revision revision() const noexcept
Return the current adapter revision for optimistic requests.
eve::Result< eve::container::ContainerSnapshot > snapshot() const override
Snapshot.
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.
单张卡牌:ECS 实体,数据拆成 Identity / Stats / Visual / Layout / State。
Definition CardTypes.h:94
牌库(栈顶在末尾)。
Definition CardTypes.h:188
手牌:扇形布局 + 悬浮 + 拖拽 + 落区判定 + 渲染。
Definition CardTypes.h:273
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
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
Explicit slot number; it never implicitly converts to an integer.
Definition Container.h:117
constexpr std::optional< StrongUint64 > incremented() const noexcept
Returns the next value, or empty instead of unsigned wraparound.
CardContainerKind
CardContainerKind public API.
WidgetDesc card(std::vector< WidgetDesc > children, std::string id)
Bordered surface container with editor-friendly padding.
Definition Widget.cpp:502
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
Generation-qualified reference to one CardData object.
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
NodeType type
Definition Widget.h:16
std::string id
Definition Widget.h:17