载入中...
搜索中...
未找到
VersionedRegistry.h
浏览该文件的文档.
1#pragma once
2
14#include "common/BorrowedRef.h"
16#include "common/Export.h"
17#include "common/Generation.h"
19#include "common/Subscription.h"
20
21#include <algorithm>
22#include <cstddef>
23#include <exception>
24#include <functional>
25#include <map>
26#include <memory>
27#include <optional>
28#include <ostream>
29#include <string>
30#include <type_traits>
31#include <utility>
32#include <variant>
33#include <vector>
34
35namespace eve {
36namespace detail {
37
46
49
52
60public:
62 [[nodiscard]] eve::Result<void> accept(EventSequence sequence, Generation generation, bool remove, bool tombstone);
63
65 [[nodiscard]] eve::Result<void> finish(EventSequence nextEventSequence) const;
66
67private:
68 EventSequence previous_{};
69};
70
71} // namespace detail
72
73
75enum class RegistryOperation : std::uint8_t {
76 Insert,
77 Replace,
78 Remove,
79};
80
91 switch (operation) {
92 case RegistryOperation::Insert: return "insert";
93 case RegistryOperation::Replace: return "replace";
94 case RegistryOperation::Remove: return "remove";
95 }
96 return "unknown";
97}
98
100inline std::ostream& operator<<(std::ostream& stream, RegistryOperation operation) {
102 return stream << registryOperationName(operation);
103}
104
132template <class Key, class Value, class EventData = std::monostate, class Compare = std::less<Key>>
135 static_assert(std::is_copy_constructible_v<Key>, "VersionedRegistry keys must be copyable");
136 static_assert(std::is_copy_constructible_v<Value>, "VersionedRegistry values must be copyable");
137 static_assert(std::is_copy_constructible_v<EventData>, "VersionedRegistry event data must be copyable");
138
139public:
141 struct Handle {
142 Key key;
144
146 [[nodiscard]] bool isValid() const noexcept { return !generation.isZero(); }
147 };
148
150 struct Event {
153 Key key;
155 bool tombstone = false;
156 std::string label;
157 EventData data{};
158
160 [[nodiscard]] bool isTombstone() const noexcept { return tombstone; }
161 };
162
164 struct Entry {
166 std::optional<Value> value;
167 };
168
175 struct State {
176 std::map<Key, Entry, Compare> entries;
177 std::vector<Event> events;
179
181 void swap(State& other) noexcept {
182 static_assert(std::is_nothrow_swappable_v<Compare>,
183 "VersionedRegistry comparator must be nothrow-swappable");
184 entries.swap(other.entries);
185 events.swap(other.events);
186 const auto sequence = nextEventSequence;
187 nextEventSequence = other.nextEventSequence;
188 other.nextEventSequence = sequence;
189 }
190 };
191
193 using GenerationProjector = std::function<void(Value&, Generation)>;
194 using ChangeCallback = std::function<void(const Event&)>;
195
202 explicit VersionedRegistry(GenerationProjector generationProjector = {})
203 : generationProjector_(std::move(generationProjector)), observers_() {}
204
210
219 [[nodiscard]] Result<Handle> insert(Key key, Value value, EventData data = {}, std::string label = {}) {
220 try {
221 return mutate(RegistryOperation::Insert, std::move(key), std::optional<Value>(std::move(value)),
222 std::move(data), std::move(label));
223 } catch (const std::exception&) {
225 Diagnostic::error(DiagnosticCode::Failed, "registry mutation argument preparation failed"));
226 } catch (...) {
228 Diagnostic::error(DiagnosticCode::Failed, "registry mutation argument preparation failed"));
229 }
230 }
231
240 [[nodiscard]] Result<Handle> replace(Key key, Value value, EventData data = {}, std::string label = {}) {
241 try {
242 return mutate(RegistryOperation::Replace, std::move(key), std::optional<Value>(std::move(value)),
243 std::move(data), std::move(label));
244 } catch (const std::exception&) {
246 Diagnostic::error(DiagnosticCode::Failed, "registry mutation argument preparation failed"));
247 } catch (...) {
249 Diagnostic::error(DiagnosticCode::Failed, "registry mutation argument preparation failed"));
250 }
251 }
252
260 [[nodiscard]] Result<Handle> remove(Key key, EventData data = {}, std::string label = {}) {
261 try {
262 return mutate(RegistryOperation::Remove, std::move(key), std::nullopt, std::move(data), std::move(label));
263 } catch (const std::exception&) {
265 Diagnostic::error(DiagnosticCode::Failed, "registry mutation argument preparation failed"));
266 } catch (...) {
268 Diagnostic::error(DiagnosticCode::Failed, "registry mutation argument preparation failed"));
269 }
270 }
271
277 [[nodiscard]] ResultRef<const Value> resolve(const Key& key) const {
278 const auto it = state_.entries.find(key);
279 if (it == state_.entries.end() || !it->second.value.has_value())
281 Diagnostic::error(DiagnosticCode::NotFound, "registry key is not live"));
282 return ResultRef<const Value>::success(std::cref(*it->second.value));
283 }
284
290 [[nodiscard]] ResultRef<const Value> resolve(const Handle& handle) const {
291 const auto it = state_.entries.find(handle.key);
292 if (it == state_.entries.end())
294 Diagnostic::error(DiagnosticCode::NotFound, "registry key is not present"));
295 if (handle.generation.isZero() || it->second.generation != handle.generation || !it->second.value.has_value())
297 Diagnostic::error(DiagnosticCode::StaleHandle, "registry handle is stale"));
298 return ResultRef<const Value>::success(std::cref(*it->second.value));
299 }
300
302 [[nodiscard]] Result<Handle> handle(const Key& key) const {
303 const auto it = state_.entries.find(key);
304 if (it == state_.entries.end() || !it->second.value.has_value())
305 return Result<Handle>::failure(Diagnostic::error(DiagnosticCode::NotFound, "registry key is not live"));
306 return Result<Handle>::success(Handle{key, it->second.generation});
307 }
308
313 [[nodiscard]] Result<Generation> generationOf(const Key& key) const {
314 const auto it = state_.entries.find(key);
315 if (it == state_.entries.end())
317 Diagnostic::error(DiagnosticCode::NotFound, "registry key is not present"));
318 return Result<Generation>::success(it->second.generation);
319 }
320
322 [[nodiscard]] bool contains(const Key& key) const noexcept {
323 const auto it = state_.entries.find(key);
324 return it != state_.entries.end() && it->second.value.has_value();
325 }
326
328 [[nodiscard]] bool isTombstone(const Key& key) const noexcept {
329 const auto it = state_.entries.find(key);
330 return it != state_.entries.end() && !it->second.value.has_value();
331 }
332
334 [[nodiscard]] bool isStale(const Handle& handle) const noexcept {
335 const auto it = state_.entries.find(handle.key);
336 return it == state_.entries.end() || handle.generation.isZero() || it->second.generation != handle.generation ||
337 !it->second.value.has_value();
338 }
339
341 [[nodiscard]] std::size_t size() const noexcept {
342 std::size_t count = 0;
343 for (const auto& [key, entry] : state_.entries)
344 if (entry.value.has_value()) ++count;
345 return count;
346 }
347
349 [[nodiscard]] std::size_t tombstoneCount() const noexcept {
350 std::size_t count = 0;
351 for (const auto& [key, entry] : state_.entries)
352 if (!entry.value.has_value()) ++count;
353 return count;
354 }
355
368 [[nodiscard]] const Value* at(std::size_t index) const noexcept {
369 for (const auto& [key, entry] : state_.entries) {
370 if (!entry.value.has_value()) continue;
371 if (index-- == 0) return &*entry.value;
372 }
373 return nullptr;
374 }
375
385 [[nodiscard]] const Key* keyAt(std::size_t index) const noexcept {
386 for (const auto& [key, entry] : state_.entries) {
387 if (!entry.value.has_value()) continue;
388 if (index-- == 0) return &key;
389 }
390 return nullptr;
391 }
392
402 [[nodiscard]] const Event* eventAt(std::size_t index) const noexcept {
403 return index < state_.events.size() ? &state_.events[index] : nullptr;
404 }
405
407 [[nodiscard]] std::size_t eventCount() const noexcept { return state_.events.size(); }
408
410 void clearEvents() noexcept { state_.events.clear(); }
411
413 [[nodiscard]] State snapshotState() const { return state_; }
414
423 [[nodiscard]] Result<void> restoreState(State candidate) {
424 auto sequenceOk = detail::validateRegistryNextSequence(candidate.nextEventSequence);
425 if (!sequenceOk) return sequenceOk;
426
427 try {
428 for (auto& [key, entry] : candidate.entries) {
429 if (entry.generation.isZero())
431 Diagnostic::error(DiagnosticCode::ParseError, "registry entry generation must be positive"));
432 if (entry.value.has_value()) generationProject(*entry.value, entry.generation);
433 }
434 } catch (const std::exception&) {
436 Diagnostic::error(DiagnosticCode::Failed, "registry generation projection failed"));
437 } catch (...) {
439 Diagnostic::error(DiagnosticCode::Failed, "registry generation projection failed"));
440 }
442 for (const auto& event : candidate.events) {
443 auto accepted = validator.accept(event.sequence, event.generation,
444 event.operation == RegistryOperation::Remove, event.tombstone);
445 if (!accepted) return accepted;
446 }
447 auto boundary = validator.finish(candidate.nextEventSequence);
448 if (!boundary) return boundary;
449
450 state_.swap(candidate);
451 return Result<void>::success();
452 }
453
461 [[nodiscard("retain Subscription or explicitly dispose it")]] Subscription subscribe(ChangeCallback callback) {
462 return observers_.subscribe(std::move(callback));
463 }
464
465private:
466 void generationProject(Value& value, Generation generation) const {
467 if (generationProjector_) generationProjector_(value, generation);
468 }
469
470 [[nodiscard]] Result<Handle> mutate(RegistryOperation operation, Key key, std::optional<Value> value,
471 EventData data, std::string label) {
472 try {
473 // Every potentially-throwing operation happens on a detached
474 // candidate. The live state is touched only by the nothrow swap
475 // below, so Value/EventData/projector failures cannot partially
476 // publish a registry mutation.
477 State candidate = state_;
478 auto it = candidate.entries.find(key);
479 const bool live = it != candidate.entries.end() && it->second.value.has_value();
482 Diagnostic::error(DiagnosticCode::AlreadyExists, "registry key already exists"));
484 return Result<Handle>::failure(Diagnostic::error(DiagnosticCode::NotFound, "registry key is not live"));
485 if (operation == RegistryOperation::Remove && !live)
486 return Result<Handle>::failure(Diagnostic::error(DiagnosticCode::NotFound, "registry key is not live"));
487 if (operation != RegistryOperation::Remove && !value.has_value())
489 Diagnostic::error(DiagnosticCode::InvariantViolation, "live registry mutation has no value"));
490
491 const auto current = it == candidate.entries.end() ? nullptr : &it->second;
492 auto next =
493 detail::nextRegistryGeneration(current ? current->generation : Generation{}, current != nullptr);
494 if (!next.ok()) return Result<Handle>::failure(next.status());
495 const auto nextSequence = detail::nextRegistryEventSequence(candidate.nextEventSequence);
496 if (!nextSequence.ok()) return Result<Handle>::failure(nextSequence.status());
497
498 if (operation != RegistryOperation::Remove) generationProject(*value, next.value());
499
500 candidate.events.reserve(candidate.events.size() + 1);
501 Event event{candidate.nextEventSequence,
502 operation,
503 key,
504 next.value(),
506 std::move(label),
507 std::move(data)};
508
509 if (operation == RegistryOperation::Insert && it == candidate.entries.end()) {
510 auto [inserted, wasInserted] =
511 candidate.entries.try_emplace(std::move(key), Entry{next.value(), std::move(value)});
512 (void)wasInserted;
513 it = inserted;
514 } else if (operation == RegistryOperation::Remove) {
515 it->second.generation = next.value();
516 it->second.value.reset();
517 } else {
518 it->second.generation = next.value();
519 it->second.value = std::move(value);
520 }
521
522 candidate.events.push_back(std::move(event));
523 candidate.nextEventSequence = nextSequence.value();
524 // Keep the notification independent of state_.events. Observer
525 // dispatch is reentrant; a nested mutation is allowed to swap or
526 // reallocate the canonical event vector while callbacks run.
527 const Event notification = candidate.events.back();
528 const Handle handle{notification.key, notification.generation};
530 Status callbackWarningStatus(
532 "registry mutation committed; observer callback failed")});
533 state_.swap(candidate);
534
535 // State is already committed. A user callback is outside the
536 // transaction boundary: never roll back an event that another
537 // callback may already have observed. Surface the exception as a
538 // successful Applied result carrying a warning instead.
539 try {
540 observers_.notify(notification);
541 } catch (const std::exception&) {
542 return Result<Handle>::success(std::move(handle), std::move(callbackWarningStatus));
543 } catch (...) {
544 return Result<Handle>::success(std::move(handle), std::move(callbackWarningStatus));
545 }
546 return Result<Handle>::success(std::move(handle), std::move(appliedStatus));
547 } catch (const std::exception&) {
549 Diagnostic::error(DiagnosticCode::Failed, "registry mutation preparation failed"));
550 } catch (...) {
552 Diagnostic::error(DiagnosticCode::Failed, "registry mutation preparation failed"));
553 }
554 }
555
556 State state_;
557 GenerationProjector generationProjector_;
558 Observer<Event> observers_;
559};
560
561} // namespace eve
ActionParameterOperation operation
double value
Vocabulary aliases for nullable and checked borrowed references.
std::string label
Stream-local event ordering value.
#define EVENGINE_API
宿主(eve / libmain)导出宏;插件从进程导入同一批符号。
Definition Export.h:31
Runtime replacement generation for stale-handle rejection.
std::uint32_t key
std::unique_ptr< gpgpu::Sequence > sequence
Definition OnnxGpgpu.cpp:43
std::uint32_t generation
PrimitiveHandle handle
int detail
Persistent data-contract version identity.
double current
std::uint32_t count
CommandLogBoundary boundary
Anchor rule, see above.
uint32_t index
ViewPreparation callback
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
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
Move-only RAII token that owns one observer registration.
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
A registry whose live values are qualified by a monotonically increasing runtime generation.
const Key * keyAt(std::size_t index) const noexcept
Returns the key for a live enumeration index, or nullptr.
ResultRef< const Value > resolve(const Key &key) const
Resolves a live key to a borrowed value.
std::size_t size() const noexcept
Number of live values, excluding tombstones.
bool isTombstone(const Key &key) const noexcept
Returns whether a key is retained as a removal tombstone.
VersionedRegistry & operator=(VersionedRegistry &&)=delete
bool isStale(const Handle &handle) const noexcept
Returns true when a handle cannot resolve to its exact live incarnation.
Result< Handle > replace(Key key, Value value, EventData data={}, std::string label={})
Replaces an existing live value and invalidates its old handle.
VersionedRegistry(const VersionedRegistry &)=delete
void clearEvents() noexcept
Clears retained events without changing entries or sequence monotonicity.
Result< Handle > handle(const Key &key) const
Returns the current live handle for a key.
Result< void > restoreState(State candidate)
Transactionally replaces all registry state from an owning image.
const Value * at(std::size_t index) const noexcept
Returns a live value by deterministic key order.
Subscription subscribe(ChangeCallback callback)
Subscribes to successful insert, replace and remove events.
Result< Generation > generationOf(const Key &key) const
Returns the latest generation for a live key or tombstone.
bool contains(const Key &key) const noexcept
Returns whether a key currently has a live value.
VersionedRegistry & operator=(const VersionedRegistry &)=delete
~VersionedRegistry()=default
VersionedRegistry(VersionedRegistry &&)=delete
Result< Handle > insert(Key key, Value value, EventData data={}, std::string label={})
Inserts a new live value, or revives a tombstoned key.
Result< Handle > remove(Key key, EventData data={}, std::string label={})
Removes a live value while retaining its generation tombstone.
std::function< void(const Event &)> ChangeCallback
std::size_t tombstoneCount() const noexcept
Number of retained tombstone slots.
const Event * eventAt(std::size_t index) const noexcept
Returns a retained event by insertion sequence order, or nullptr.
State snapshotState() const
Takes an owning copy of live entries, tombstones and event history.
VersionedRegistry(GenerationProjector generationProjector={})
Constructs an empty registry.
std::size_t eventCount() const noexcept
Number of retained mutation events.
ResultRef< const Value > resolve(const Handle &handle) const
Resolves a generation-qualified handle.
std::function< void(Value &, Generation)> GenerationProjector
Callback used to keep a legacy value field as a read-only projection of generation.
Incremental validator for the retained event log of a registry image.
eve::Result< void > accept(EventSequence sequence, Generation generation, bool remove, bool tombstone)
Accept one event header; the first failure describes the whole log.
eve::Result< void > finish(EventSequence nextEventSequence) const
Validate the boundary between the retained log and the next sequence.
constexpr bool isZero() const noexcept
Returns whether this value is zero.
eve::Result< EventSequence > nextRegistryEventSequence(EventSequence current)
Next event sequence, or a Failed diagnostic when it is exhausted.
eve::Result< void > validateRegistryNextSequence(EventSequence nextEventSequence)
Validates the next-event-sequence floor of a restored registry image.
eve::Result< Generation > nextRegistryGeneration(Generation current, bool present)
Next generation of one slot; present false means the slot is new.
eve::StatusCode Status
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
State
Lifecycle state of a generic transaction plan.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
std::ostream & operator<<(std::ostream &stream, Severity severity)
Writes a severity for diagnostics and test output.
Definition Diagnostic.h:39
detail::StrongUint64< detail::GenerationTag > Generation
Registry/object replacement generation used to reject stale handles.
Definition Generation.h:13
RegistryOperation
Canonical mutation kind emitted by a VersionedRegistry.
const char * registryOperationName(RegistryOperation operation) noexcept
Stable lowercase name for a registry mutation kind.
detail::StrongUint64< detail::EventSequenceTag > EventSequence
Stream-local event ordering value; it is not a global event identity.
One stored key slot; absent value means the slot is a tombstone.
One canonical mutation event retained by the registry.
bool isTombstone() const noexcept
Whether this event advanced a key to a removed tombstone.
Generation-qualified identity of one live incarnation or tombstone.
bool isValid() const noexcept
Returns false for the zero-generation sentinel.
Owning state image used for transactional snapshot/restore.
std::map< Key, Entry, Compare > entries
void swap(State &other) noexcept
Atomically exchanges complete registry state without allocation.