载入中...
搜索中...
未找到
EffectContainer.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <iomanip>
6#include <limits>
7#include <sstream>
8#include <utility>
9
10namespace eve::effects {
11namespace {
12
13eve::Result<std::string> rejected(std::string message) {
16}
17
18eve::Result<std::string> conflict(std::string message) {
21}
22
23eve::Result<void> notFound(std::string message) {
26}
27
30 eve::DiagnosticCode::InvalidArgument, "effect update delta must be finite and non-negative"));
31}
32
33double normalisedRemaining(double duration) { return duration > 0.0 ? duration : -1.0; }
34
35eve::Result<double> mergeDuration(const EffectInstance& current, const EffectDefinition& definition) {
36 switch (definition.policy.duration) {
38 case DurationPolicy::Replace: return eve::Result<double>::success(normalisedRemaining(definition.duration));
40 if (definition.duration <= 0.0 || current.remaining < 0.0)
41 return eve::Result<double>::success(current.remaining);
42 if (current.remaining > std::numeric_limits<double>::max() - definition.duration)
44 "effect duration extension overflows"));
45 return eve::Result<double>::success(current.remaining + definition.duration);
46 }
48 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "unknown effect duration policy"));
49}
50
51eve::Result<double> mergeMagnitude(const EffectInstance& current, const EffectDefinition& definition) {
52 switch (definition.policy.magnitude) {
54 case MagnitudePolicy::Replace: return eve::Result<double>::success(definition.magnitude);
56 const double value = current.magnitude + definition.magnitude;
57 if (!std::isfinite(value))
59 "effect magnitude addition overflows"));
61 }
63 return eve::Result<double>::success(std::max(current.magnitude, definition.magnitude));
64 }
66 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "unknown effect magnitude policy"));
67}
68
69bool stackWouldOverflow(const EffectInstance& current, const EffectDefinition& definition) {
70 if (definition.policy.maxStacks == 0) return false;
71 switch (definition.policy.stackCount) {
72 case StackCountPolicy::Keep: return current.stackCount > definition.policy.maxStacks;
74 return definition.stackCount > definition.policy.maxStacks - current.stackCount;
75 case StackCountPolicy::Set: return definition.stackCount > definition.policy.maxStacks;
76 }
77 return true;
78}
79
80eve::Result<std::uint32_t> mergeStackCount(const EffectInstance& current, const EffectDefinition& definition) {
81 std::uint32_t value = current.stackCount;
82 switch (definition.policy.stackCount) {
83 case StackCountPolicy::Keep: break;
85 if (definition.stackCount > std::numeric_limits<std::uint32_t>::max() - current.stackCount)
87 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "effect stack count overflows"));
88 value = current.stackCount + definition.stackCount;
89 break;
90 case StackCountPolicy::Set: value = definition.stackCount; break;
91 }
92 if (definition.policy.maxStacks != 0 && value > definition.policy.maxStacks) {
93 if (definition.policy.overflow == OverflowPolicy::Clamp)
94 value = definition.policy.maxStacks;
95 else
97 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "effect stack count exceeds its maximum"));
98 }
100}
101
102} // namespace
103
105 if (entropy) effectIdGenerator_.emplace(std::move(entropy), std::move(clock));
106}
107
109 : nextId_(other.nextId_),
110 nextSequence_(other.nextSequence_),
111 effectIdGenerator_(other.effectIdGenerator_),
112 generation_(other.generation_),
113 lastTick_(other.lastTick_),
114 hasLastTick_(other.hasLastTick_),
115 events_(other.events_) {
116 for (const auto& effect : other.effects_)
117 effects_.push_back(effect ? std::make_unique<EffectInstance>(*effect) : nullptr);
118}
119
121 if (this == &other) return *this;
122
123 EffectContainer replacement(other);
124 using std::swap;
125 swap(effects_, replacement.effects_);
126 swap(events_, replacement.events_);
127 swap(nextId_, replacement.nextId_);
128 swap(nextSequence_, replacement.nextSequence_);
129 swap(generation_, replacement.generation_);
130 swap(lastTick_, replacement.lastTick_);
131 swap(hasLastTick_, replacement.hasLastTick_);
132 swap(effectIdGenerator_, replacement.effectIdGenerator_);
133 return *this;
134}
135
137
139
141 if (this == &snapshotValue) {
142 if (generation_ == std::numeric_limits<std::uint64_t>::max())
144 "effect container generation overflow"));
145 ++generation_;
147 }
148 if (generation_ == std::numeric_limits<std::uint64_t>::max())
150 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "effect container generation overflow"));
151
152 EffectContainer candidate(snapshotValue);
153 candidate.generation_ = generation_ + 1;
154 *this = std::move(candidate);
156}
157
158std::string EffectContainer::effectiveKey(const EffectDefinition& definition) const {
159 return definition.stackKey.empty() ? definition.id : definition.stackKey;
160}
161
162void EffectContainer::emit(EffectEventKind kind, const EffectInstance& effect, const std::string& reason) {
163 EffectEvent event;
164 event.sequence = nextSequence_++;
165 event.kind = kind;
166 event.effectId = effect.id;
167 event.subject = effect.subject;
168 event.type = effect.type;
169 event.source = effect.source;
170 event.reason = reason;
171 event.effectIdentity = effect.identity;
172 event.tick = lastTick_;
173 events_.push_back(std::move(event));
174}
175
176EffectContainer::Store::iterator EffectContainer::findIterator(const std::string& id) {
177 return std::find_if(effects_.begin(), effects_.end(), [&id](const auto& effect) { return effect->id == id; });
178}
179
180EffectContainer::Store::const_iterator EffectContainer::findIterator(const std::string& id) const {
181 return std::find_if(effects_.begin(), effects_.end(), [&id](const auto& effect) { return effect->id == id; });
182}
183
184EffectContainer::Store::iterator EffectContainer::findIterator(eve::EffectId id) {
185 return std::find_if(effects_.begin(), effects_.end(),
186 [id](const auto& effect) { return !id.isNil() && effect->identity == id; });
187}
188
189EffectContainer::Store::const_iterator EffectContainer::findIterator(eve::EffectId id) const {
190 return std::find_if(effects_.begin(), effects_.end(),
191 [id](const auto& effect) { return !id.isNil() && effect->identity == id; });
192}
193
195 const std::string& source) {
196 auto validation = definition.validate();
197 if (!validation) return eve::Result<std::string>::failure(validation.status());
198 if (subject.empty()) return rejected("effect subject must not be empty");
199
200 // Resolve the new instance's initial stack before any Replace/overflow
201 // mutation. A rejected application must leave both instances and events
202 // unchanged.
203 std::uint32_t initialStacks = definition.stackCount;
204 if (definition.policy.maxStacks != 0 && initialStacks > definition.policy.maxStacks) {
205 if (definition.policy.overflow == OverflowPolicy::Reject)
206 return conflict("effect stack count exceeds its maximum");
207 initialStacks = definition.policy.maxStacks;
208 }
209
210 const std::string key = effectiveKey(definition);
211 auto matches = [&subject, &key](const auto& effect) {
212 return effect->subject == subject && effect->stackKey == key;
213 };
214 auto existing = std::find_if(effects_.begin(), effects_.end(), matches);
215
216 if (existing != effects_.end() && definition.policy.stackMode == StackMode::Reuse) {
217 EffectInstance& current = **existing;
218 auto duration = mergeDuration(current, definition);
220 auto magnitude = mergeMagnitude(current, definition);
221 if (!magnitude) return eve::Result<std::string>::failure(magnitude.status());
222 if (stackWouldOverflow(current, definition) && definition.policy.overflow == OverflowPolicy::ReplaceOldest) {
223 emit(EffectEventKind::Removed, current, "overflow");
224 effects_.erase(existing);
225 existing = effects_.end();
226 } else {
227 auto stacks = mergeStackCount(current, definition);
228 if (!stacks) return eve::Result<std::string>::failure(stacks.status());
229 current.priority = definition.priority;
230 current.duration = definition.duration;
231 current.period = definition.period;
232 current.periodElapsed = 0.0;
233 current.remaining = std::move(duration).takeValue();
234 current.magnitude = std::move(magnitude).takeValue();
235 current.stackCount = std::move(stacks).takeValue();
238 }
239 }
240
241 if (existing != effects_.end() && definition.policy.stackMode == StackMode::Accumulate) {
242 EffectInstance& current = **existing;
243 auto duration = mergeDuration(current, definition);
245 auto magnitude = mergeMagnitude(current, definition);
246 if (!magnitude) return eve::Result<std::string>::failure(magnitude.status());
247 if (stackWouldOverflow(current, definition) && definition.policy.overflow == OverflowPolicy::ReplaceOldest) {
248 emit(EffectEventKind::Removed, current, "overflow");
249 effects_.erase(existing);
250 existing = effects_.end();
251 } else {
252 auto stacks = mergeStackCount(current, definition);
253 if (!stacks) {
254 if (stacks.code() == eve::StatusCode::Conflict)
255 return conflict("effect stack count exceeds its maximum");
256 return eve::Result<std::string>::failure(stacks.status());
257 }
258 current.priority = definition.priority;
259 current.duration = definition.duration;
260 current.period = definition.period;
261 current.periodElapsed = 0.0;
262 current.remaining = std::move(duration).takeValue();
263 current.magnitude = std::move(magnitude).takeValue();
264 current.stackCount = std::move(stacks).takeValue();
267 }
268 }
269
270 if (definition.policy.stackMode == StackMode::Replace) {
271 for (auto it = effects_.begin(); it != effects_.end();) {
272 if (matches(*it)) {
273 emit(EffectEventKind::Removed, **it, "replaced");
274 it = effects_.erase(it);
275 } else {
276 ++it;
277 }
278 }
279 }
280
281 auto effect = std::make_unique<EffectInstance>();
282 if (effectIdGenerator_) {
283 const auto generated = effectIdGenerator_->generate();
284 if (!generated)
286 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "canonical effect identity generation failed"));
287 effect->identity = eve::EffectId::fromUuid(*generated);
288 if (find(effect->identity) != nullptr)
290 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "canonical effect identity already exists"));
291 effect->id = effect->identity.format();
292 } else {
293 std::ostringstream id;
294 id << "effect-" << std::setw(16) << std::setfill('0') << nextId_++;
295 effect->id = id.str();
296 }
297 effect->subject = subject;
298 effect->type = definition.id;
299 effect->source = source;
300 effect->stackKey = key;
301 effect->priority = definition.priority;
302 effect->duration = std::max(0.0, definition.duration);
303 effect->remaining = normalisedRemaining(definition.duration);
304 effect->period = definition.period;
305 effect->periodElapsed = 0.0;
306 effect->magnitude = definition.magnitude;
307 effect->stackCount = initialStacks;
308 effect->payload = definition.payload;
309 effect->tags = definition.tags;
310 std::sort(effect->tags.begin(), effect->tags.end());
311 effect->tags.erase(std::unique(effect->tags.begin(), effect->tags.end()), effect->tags.end());
312
313 const std::string result = effect->id;
314 effects_.push_back(std::move(effect));
315 emit(EffectEventKind::Applied, *effects_.back());
317}
318
320 const std::string& subject, const std::string& source) {
321 if (!effectIdGenerator_)
323 eve::DiagnosticCode::Unsupported, "canonical effect identity requires an injected UUID entropy source"));
324 auto result = apply(definition, subject, source);
325 if (!result) return eve::Result<eve::EffectId>::failure(result.status());
326 const auto id = eve::EffectId::parse(std::move(result).takeValue());
327 if (!id || id->isNil())
329 eve::DiagnosticCode::Failed, "effect application did not produce a canonical identity"));
331}
332
334 if (id.empty())
336 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "effect handle id must not be empty", "id"));
337 if (find(id) == nullptr)
339 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "effect instance is not active", "id"));
342}
343
345 if (!handle.isValid())
347 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "effect handle is invalid", "handle"));
348 if (handle.containerGeneration != generation_)
350 eve::DiagnosticCode::StaleHandle, "effect handle belongs to an older container generation", "handle"));
351 const auto* instance = find(handle.instanceId);
352 if (instance == nullptr)
354 eve::DiagnosticCode::NotFound, "effect instance is not active", "handle.instanceId"));
356}
357
358eve::Result<std::string> EffectContainer::apply(const std::string& subject, const std::string& type,
359 const std::string& source, int priority, double duration,
360 const std::string& stackKey, StackPolicy policy) {
361 EffectDefinition definition;
362 definition.id = type;
363 definition.stackKey = stackKey;
364 definition.priority = priority;
365 definition.duration = duration;
366 switch (policy) {
367 case StackPolicy::Replace: definition.policy.stackMode = StackMode::Replace; break;
368 case StackPolicy::Stack: definition.policy.stackMode = StackMode::NewInstance; break;
369 case StackPolicy::Refresh: definition.policy.stackMode = StackMode::Reuse; break;
370 }
371 auto result = apply(definition, subject, source);
372 return result;
373}
374
375eve::Result<void> EffectContainer::remove(const std::string& id, const std::string& reason) {
376 const auto it = findIterator(id);
377 if (it == effects_.end()) return notFound("effect instance is not active");
378 emit(EffectEventKind::Removed, **it, reason);
379 effects_.erase(it);
381}
382
384 const auto it = findIterator(id);
385 if (it == effects_.end()) return notFound("effect instance is not active");
386 emit(EffectEventKind::Removed, **it, reason);
387 effects_.erase(it);
389}
390
392 const std::string& categoryTag, int strength,
393 std::size_t maxCount, const std::string& reason) {
394 if (subject.empty())
396 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "dispel subject must not be empty",
397 "subject"));
398 if (categoryTag.empty())
400 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "dispel category tag must not be empty",
401 "categoryTag"));
402 if (reason.empty())
404 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "dispel reason must not be empty",
405 "reason"));
406 if (maxCount == 0)
409
410 struct Candidate {
411 std::string id;
412 int priority = 0;
413 std::size_t order = 0;
414 };
415 std::vector<Candidate> matches;
416 matches.reserve(effects_.size());
417 std::size_t order = 0;
418 for (const auto& instance : effects_) {
419 if (instance->subject == subject && instance->priority <= strength && instance->hasTag(categoryTag) &&
420 !instance->hasTag("effect:undispellable"))
421 matches.push_back({instance->id, instance->priority, order});
422 ++order;
423 }
424 std::stable_sort(matches.begin(), matches.end(), [](const Candidate& left, const Candidate& right) {
425 if (left.priority != right.priority) return left.priority > right.priority;
426 return left.order < right.order;
427 });
428 if (matches.size() > maxCount) matches.resize(maxCount);
429 if (matches.empty())
432
433 EffectContainer candidate = snapshot();
434 std::vector<std::string> removed;
435 removed.reserve(matches.size());
436 for (const auto& match : matches) {
437 auto removal = candidate.remove(match.id, reason);
438 if (!removal) return eve::Result<std::vector<std::string>>::failure(removal.status());
439 removed.push_back(match.id);
440 }
441 auto committed = restore(candidate);
442 if (!committed) return eve::Result<std::vector<std::string>>::failure(committed.status());
445}
446
447eve::Result<EffectUpdateSummary> EffectContainer::advanceInstances(double dtSeconds, eve::SimulationTick tick) {
448 if (!std::isfinite(dtSeconds) || dtSeconds < 0.0) return invalidDelta();
449 EffectUpdateSummary summary;
450 summary.elapsedSeconds = dtSeconds;
451 if (dtSeconds == 0.0)
453
454 for (auto it = effects_.begin(); it != effects_.end();) {
455 EffectInstance& effect = **it;
456 const double activeDelta = effect.remaining < 0.0 ? dtSeconds : std::min(dtSeconds, effect.remaining);
457 if (effect.period > 0.0 && activeDelta > 0.0) {
458 if (!std::isfinite(effect.periodElapsed) ||
459 effect.periodElapsed > std::numeric_limits<double>::max() - activeDelta)
461 eve::DiagnosticCode::InvalidArgument, "effect periodic accumulator overflow"));
462 effect.periodElapsed += activeDelta;
463 while (effect.periodElapsed >= effect.period) {
464 effect.periodElapsed -= effect.period;
465 EffectPeriodicTick periodic;
466 periodic.effectId = effect.id;
467 periodic.subject = effect.subject;
468 periodic.source = effect.source;
469 periodic.magnitude = effect.magnitude;
470 periodic.stackCount = effect.stackCount;
471 periodic.tags = effect.tags;
472 periodic.effectIdentity = effect.identity;
473 periodic.tick = tick;
474 summary.periodicTicks.push_back(std::move(periodic));
475 emit(EffectEventKind::Periodic, effect, "periodic");
476 }
477 }
478 if (effect.remaining < 0.0) {
479 ++it;
480 continue;
481 }
482 effect.remaining = std::max(0.0, effect.remaining - dtSeconds);
483 if (effect.remaining == 0.0) {
484 emit(EffectEventKind::Expired, effect);
485 ++summary.expired;
486 it = effects_.erase(it);
487 } else {
488 ++it;
489 }
490 }
492}
493
494eve::Result<EffectUpdateSummary> EffectContainer::update(double dtSeconds) {
495 auto duration = eve::Duration::fromSeconds(dtSeconds);
497
498 auto nextTick = hasLastTick_ ? lastTick_.incremented() : std::optional<eve::SimulationTick>(eve::SimulationTick(1));
499 if (!nextTick)
501 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "effect simulation tick overflow"));
502
503 return advance({*nextTick, std::move(duration).takeValue()});
504}
505
507 if (step.delta.nanoseconds() < 0)
509 eve::DiagnosticCode::InvalidArgument, "effect simulation delta must be non-negative"));
510 if (hasLastTick_ && step.tick <= lastTick_)
512 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "effect simulation tick must advance monotonically"));
513
514 // Stage the lifecycle mutation so a failure does not publish partial state.
515 auto candidate = clone();
516 candidate.lastTick_ = step.tick;
517 candidate.hasLastTick_ = true;
518 auto result = candidate.advanceInstances(step.delta.seconds(), step.tick);
519 if (!result) return eve::Result<EffectUpdateSummary>::failure(result.status());
520 auto status = result.status();
521 EffectUpdateSummary summary = std::move(result).takeValue();
522 summary.tick = step.tick;
523 candidate.lastTick_ = step.tick;
524 candidate.hasLastTick_ = true;
525 *this = std::move(candidate);
526 return eve::Result<EffectUpdateSummary>::success(std::move(summary), std::move(status));
527}
528
529void EffectContainer::clear() {
530 effects_.clear();
531 events_.clear();
532 nextId_ = nextSequence_ = 1;
533 lastTick_ = eve::SimulationTick::zero();
534 hasLastTick_ = false;
535 if (generation_ != std::numeric_limits<std::uint64_t>::max()) ++generation_;
536}
537
538EffectInstance* EffectContainer::find(const std::string& id) {
539 const auto it = findIterator(id);
540 return it == effects_.end() ? nullptr : it->get();
541}
542
543const EffectInstance* EffectContainer::find(const std::string& id) const {
544 const auto it = findIterator(id);
545 return it == effects_.end() ? nullptr : it->get();
546}
547
548EffectInstance* EffectContainer::find(eve::EffectId id) {
549 const auto it = findIterator(id);
550 return it == effects_.end() ? nullptr : it->get();
551}
552
553const EffectInstance* EffectContainer::find(eve::EffectId id) const {
554 const auto it = findIterator(id);
555 return it == effects_.end() ? nullptr : it->get();
556}
557
558int EffectContainer::effectCount() const { return static_cast<int>(effects_.size()); }
559
560EffectInstance* EffectContainer::effectAt(int index) {
561 return index < 0 || static_cast<std::size_t>(index) >= effects_.size()
562 ? nullptr
563 : effects_[static_cast<std::size_t>(index)].get();
564}
565
566const EffectInstance* EffectContainer::effectAt(int index) const {
567 return index < 0 || static_cast<std::size_t>(index) >= effects_.size()
568 ? nullptr
569 : effects_[static_cast<std::size_t>(index)].get();
570}
571
572int EffectContainer::subjectCount(const std::string& subject) const {
573 return static_cast<int>(std::count_if(effects_.begin(), effects_.end(),
574 [&subject](const auto& effect) { return effect->subject == subject; }));
575}
576
577EffectInstance* EffectContainer::subjectAt(const std::string& subject, int index) {
578 if (index < 0) return nullptr;
579 for (auto& effect : effects_)
580 if (effect->subject == subject && index-- == 0) return effect.get();
581 return nullptr;
582}
583
584const EffectInstance* EffectContainer::subjectAt(const std::string& subject, int index) const {
585 if (index < 0) return nullptr;
586 for (const auto& effect : effects_)
587 if (effect->subject == subject && index-- == 0) return effect.get();
588 return nullptr;
589}
590
591int EffectContainer::taggedCount(const std::string& subject, const std::string& tag) const {
592 return static_cast<int>(std::count_if(effects_.begin(), effects_.end(), [&subject, &tag](const auto& effect) {
593 return effect->subject == subject && effect->hasTag(tag);
594 }));
595}
596
597EffectInstance* EffectContainer::taggedAt(const std::string& subject, const std::string& tag, int index) {
598 if (index < 0) return nullptr;
599 for (auto& effect : effects_)
600 if (effect->subject == subject && effect->hasTag(tag) && index-- == 0) return effect.get();
601 return nullptr;
602}
603
604const EffectInstance* EffectContainer::taggedAt(const std::string& subject, const std::string& tag, int index) const {
605 if (index < 0) return nullptr;
606 for (const auto& effect : effects_)
607 if (effect->subject == subject && effect->hasTag(tag) && index-- == 0) return effect.get();
608 return nullptr;
609}
610
611int EffectContainer::eventCount() const { return static_cast<int>(events_.size()); }
612
613EffectEvent* EffectContainer::eventAt(int index) {
614 return index < 0 || static_cast<std::size_t>(index) >= events_.size() ? nullptr
615 : &events_[static_cast<std::size_t>(index)];
616}
617
618const EffectEvent* EffectContainer::eventAt(int index) const {
619 return index < 0 || static_cast<std::size_t>(index) >= events_.size() ? nullptr
620 : &events_[static_cast<std::size_t>(index)];
621}
622
623void EffectContainer::clearEvents() { events_.clear(); }
624
625eve::Result<EffectUpdateSummary> EffectExecutor::advance(EffectContainer& container, double dtSeconds) const {
626 return container.update(dtSeconds);
627}
628
629eve::Result<EffectUpdateSummary> EffectExecutor::advance(EffectContainer& container,
630 const eve::SimulationStep& step) const {
631 return container.advance(step);
632}
633
634} // namespace eve::effects
double value
float duration
int subject
Definition AnimSmr.cpp:163
int priority
Owning effect storage and lifecycle-only executor.
std::string message
std::uint32_t key
wgpu::PopErrorScopeStatus status
HexVec3 left
HexVec3 right
TokenKind kind
std::vector< std::int32_t > order
const std::string * tag
std::unique_ptr< ParticleEffect > effect
std::string id
Definition PlayHost.cpp:108
PrimitiveHandle handle
int removed
double current
SimulationTick tick
float step
Definition TreeMesh.cpp:314
uint32_t index
const UnitySourceAsset & source
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 Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
Definition Time.cpp:16
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 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
Id128 public API.
Definition Identity.h:112
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
static constexpr Id128 fromUuid(const Id128< OtherTag > &value) noexcept
Re-tags an existing UUID at an explicit domain boundary.
Definition Identity.h:136
static constexpr StrongUint64 zero() noexcept
Returns the zero value for this strong type.
Owns active effect instances and their deterministic lifecycle events.
eve::Result< std::vector< std::string > > dispel(const std::string &subject, const std::string &categoryTag, int strength, std::size_t maxCount, const std::string &reason="dispelled")
Atomically remove matching dispellable effects in deterministic priority order.
EffectContainer snapshot() const
Capture an independent lifecycle snapshot for staging/restore.
eve::Result< const EffectInstance * > resolve(EffectHandle handle) const
Resolve a generation-qualified handle for this call.
eve::Result< eve::EffectId > applyCanonical(const EffectDefinition &definition, const std::string &subject, const std::string &source={})
Applies a definition and returns its UUID-backed effect identity.
eve::Result< void > remove(const std::string &id, const std::string &reason="removed")
Removes one active instance and emits a Removed event.
EffectInstance * find(const std::string &id)
Returns an active instance by stable id, or nullptr when absent. @ownership Borrowed from this contai...
EffectContainer clone() const
Create an independent deep copy suitable for ECS staging.
EffectContainer & operator=(const EffectContainer &other)
Deep-copy assign a container, including instances, events and counters.
eve::Result< EffectUpdateSummary > update(double dtSeconds)
Advances finite instances through the lifecycle executor.
eve::Result< EffectHandle > handleFor(const std::string &id) const
Return a generation-qualified handle for a live instance.
eve::Result< EffectUpdateSummary > advance(const eve::SimulationStep &step)
Advances lifecycle state using one scheduler-owned simulation step.
eve::Result< std::string > apply(const EffectDefinition &definition, const std::string &subject, const std::string &source={})
Applies a definition and returns the stable instance identifier.
eve::Result< void > restore(const EffectContainer &snapshot)
Restore a previously captured snapshot as one observable commit.
EffectContainer()=default
Construct an empty effect container.
StackPolicy
Compatibility policy used by the original effects facade.
Definition EffectTypes.h:28
EffectEventKind
Lifecycle event kind emitted by an effect container.
std::function< std::chrono::system_clock::time_point()> UuidClock
Clock callback used by the injectable UUIDv7 generator.
Definition Identity.h:469
std::function< bool(std::span< std::uint8_t > bytes)> UuidEntropySource
Entropy callback used by UUIDv7 generation.
Definition Identity.h:466
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Immutable-by-convention template used to create effect instances.
Deterministically sequenced effect lifecycle event.
Generation-qualified local reference to a canonical effect instance.
One owned runtime effect fact.
Summary returned by the lifecycle executor after one simulation step.
eve::SimulationTick tick
Scheduler tick that produced this summary.