载入中...
搜索中...
未找到
ObjectRegistry.cpp
浏览该文件的文档.
1#include "ui/ObjectRegistry.h"
2
3#include "common/Capability.h"
4#include "common/Module.h"
5#include "common/Runtime.h"
7
8#include <algorithm>
9#include <exception>
10#include <squirrel.h>
11#include <utility>
12
13namespace eve::ui {
14namespace {
15
16void abandonSquirrelObject(ssq::Object& object) noexcept {
17 // Null the HSQOBJECT so ssq::Object::reset() will not call sq_release on a
18 // VM that has already been destroyed. The VM owns the remaining cells.
19 sq_resetobject(&object.getRaw());
20}
21
22class RegistryRootReleaser final : public eve::script::ISquirrelRootReleaser {
23public:
24 void releaseSquirrelRoots() noexcept override {
25 try {
27 } catch (...) {
28 }
29 }
30};
31
32RegistryRootReleaser& registryRootReleaser() {
33 static RegistryRootReleaser releaser;
34 return releaser;
35}
36
37void ensureRegistryRootReleaser() {
38 auto& releaser = registryRootReleaser();
39 eve::cap::removeListener<eve::script::ISquirrelRootReleaser>(&releaser);
40 eve::cap::addListener<eve::script::ISquirrelRootReleaser>(&releaser);
41}
42
43} // namespace
44
46 static ObjectRegistry registry;
47 ensureRegistryRootReleaser();
48 return registry;
49}
50
53 clearAll();
54 return;
55 }
56 for (auto& target : slots_) {
57 if (target.entry) abandonSquirrelObject(target.entry->object);
58 target.entry.reset();
59 }
60 slots_.clear();
61 freeSlots_.clear();
62 byClass_.clear();
63}
64
65ObjectRegistry::Slot* ObjectRegistry::slot(ObjectHandle handle) noexcept {
66 if (!handle.isValid() || handle.index() >= slots_.size()) return nullptr;
67 return &slots_[handle.index()];
68}
69
70const ObjectRegistry::Slot* ObjectRegistry::slot(ObjectHandle handle) const noexcept {
71 if (!handle.isValid() || handle.index() >= slots_.size()) return nullptr;
72 return &slots_[handle.index()];
73}
74
76 if (className.empty()) {
78 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "UI object class name must not be empty"));
79 }
80
82 if (!runtime) {
84 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "UI object registry requires an active Runtime"));
85 }
86
87 try {
88 return registerObject(className, runtime->createInstance(className));
89 } catch (const std::exception& error) {
91 eve::DiagnosticCode::Failed, std::string("UI object construction failed: ") + error.what()));
92 } catch (...) {
94 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "UI object construction failed"));
95 }
96}
97
98eve::Result<ObjectHandle> ObjectRegistry::registerObject(const std::string& className, const ssq::Object& object,
99 const std::string& label) {
100 std::uint32_t allocatedIndex = ObjectHandle::invalidIndex;
101 bool appendedSlot = false;
102 try {
103 if (object.getType() != ssq::Type::INSTANCE) {
105 eve::DiagnosticCode::InvalidArgument, "UI object registry accepts only script instances"));
106 }
107
108 std::string group = className;
109 if (group.empty()) {
110 Runtime* runtime = ModuleManager::runtime();
111 if (!runtime) {
113 eve::DiagnosticCode::Failed, "UI object registry requires an active Runtime to derive the class"));
114 }
115 group = runtime->classNameOf(object);
116 if (group.empty()) {
118 eve::DiagnosticCode::InvalidArgument, "UI object registry could not derive a script class name"));
119 }
120 }
121
122 if (!freeSlots_.empty()) {
123 allocatedIndex = freeSlots_.back();
124 } else {
125 if (slots_.size() >= ObjectHandle::invalidIndex) {
127 eve::DiagnosticCode::Failed, "UI object registry exhausted its slot index space"));
128 }
129 allocatedIndex = static_cast<std::uint32_t>(slots_.size());
130 slots_.emplace_back();
131 appendedSlot = true;
132 }
133
134 Slot& target = slots_[allocatedIndex];
135 if (target.retired || target.entry.has_value()) {
136 if (appendedSlot) slots_.pop_back();
138 eve::DiagnosticCode::InvariantViolation, "UI object registry selected an occupied or retired slot"));
139 }
140
141 const ObjectHandle handle(allocatedIndex, target.generation);
143 if (!ownedObject) return eve::Result<ObjectHandle>::failure(ownedObject.status());
144 ObjectEntry candidate;
145 candidate.handle = handle;
146 candidate.className = group;
147 candidate.object = std::move(ownedObject).takeValue().release();
148 candidate.label = label.empty() ? group + " #" + std::to_string(count(group) + 1) : label;
149 target.entry.emplace(std::move(candidate));
150
151 bool insertedClass = false;
152 try {
153 auto [classIt, inserted] = byClass_.try_emplace(group);
154 insertedClass = inserted;
155 classIt->second.push_back(handle);
156 } catch (...) {
157 if (insertedClass) byClass_.erase(group);
158 target.entry.reset();
159 throw;
160 }
161
162 // The free-list entry is consumed only after all potentially-throwing
163 // registration work has completed.
164 if (!freeSlots_.empty() && freeSlots_.back() == allocatedIndex) freeSlots_.pop_back();
166 } catch (const std::exception& error) {
167 if (appendedSlot && !slots_.empty() && allocatedIndex + 1 == slots_.size() &&
168 !slots_.back().entry.has_value()) {
169 slots_.pop_back();
170 }
172 eve::DiagnosticCode::Failed, std::string("UI object registration failed: ") + error.what()));
173 } catch (...) {
174 if (appendedSlot && !slots_.empty() && allocatedIndex + 1 == slots_.size() &&
175 !slots_.back().entry.has_value()) {
176 slots_.pop_back();
177 }
179 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "UI object registration failed"));
180 }
181}
182
184 if (!handle.isValid()) {
186 "cannot unregister an invalid UI object handle"));
187 }
188
189 Slot* target = slot(handle);
190 if (!target) {
192 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "UI object handle index is outside the registry"));
193 }
194 if (!target->entry.has_value() || target->generation != handle.generation()) {
196 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "UI object handle is stale"));
197 }
198
199 auto classIt = byClass_.find(target->entry->className);
200 if (classIt == byClass_.end()) {
202 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "UI object class index is missing"));
203 }
204 auto& classEntries = classIt->second;
205 const auto handleIt = std::find(classEntries.begin(), classEntries.end(), handle);
206 if (handleIt == classEntries.end()) {
208 "UI object class index does not contain its handle"));
209 }
210
211 const auto next = ObjectHandle::nextGeneration(target->generation);
212 if (next) {
213 try {
214 // Reserve the reusable-slot bookkeeping before making the live
215 // entry unreachable. The remaining operations are noexcept.
216 freeSlots_.push_back(handle.index());
217 } catch (const std::exception& error) {
219 eve::DiagnosticCode::Failed, std::string("UI object slot release failed: ") + error.what()));
220 } catch (...) {
222 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "UI object slot release failed"));
223 }
224 }
225
226 classEntries.erase(handleIt);
227 if (classEntries.empty()) byClass_.erase(classIt);
228 target->entry.reset();
229 if (next) {
230 target->generation = *next;
231 } else {
232 target->retired = true;
233 }
235}
236
237void ObjectRegistry::clear(const std::string& className) {
238 const auto found = byClass_.find(className);
239 if (found == byClass_.end()) return;
240
241 const std::vector<ObjectHandle> handles = found->second;
242 for (const ObjectHandle handle : handles) {
243 unregister(handle).ignore("clear UI object class");
244 }
245}
246
248 std::vector<ObjectHandle> handles;
249 for (const auto& [className, classHandles] : byClass_) {
250 (void)className;
251 handles.insert(handles.end(), classHandles.begin(), classHandles.end());
252 }
253 for (const ObjectHandle handle : handles) {
254 unregister(handle).ignore("clear all UI objects");
255 }
256}
257
258std::vector<std::string> ObjectRegistry::classNames() const {
259 std::vector<std::string> names;
260 names.reserve(byClass_.size());
261 for (const auto& pair : byClass_) names.push_back(pair.first);
262 return names;
263}
264
265std::vector<ObjectEntry> ObjectRegistry::entries(const std::string& className) const {
266 const auto found = byClass_.find(className);
267 if (found == byClass_.end()) return {};
268
269 std::vector<ObjectEntry> result;
270 result.reserve(found->second.size());
271 for (const ObjectHandle handle : found->second) {
272 if (const ObjectEntry* value = entry(handle)) result.push_back(*value);
273 }
274 return result;
275}
276
278 const Slot* target = slot(handle);
279 if (!target || !target->entry.has_value() || target->generation != handle.generation()) return nullptr;
280 return &*target->entry;
281}
282
284 if (!handle.isValid()) return false;
285 const Slot* target = slot(handle);
286 return !target || !target->entry.has_value() || target->generation != handle.generation();
287}
288
289size_t ObjectRegistry::count(const std::string& className) const {
290 const auto found = byClass_.find(className);
291 return found == byClass_.end() ? 0 : found->second.size();
292}
293
294} // namespace eve::ui
LogicalId target
double value
building::EdgeCurveGroup group
std::string label
std::string error
Definition Package.cpp:60
PrimitiveHandle handle
bool found
std::uint32_t count
Explicit Value/Owned/Borrowed semantics at the Squirrel boundary.
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 Runtime * runtime()
Active runtime associated with the last expose() call, or nullptr. @ownership Borrowed; the embedding...
Definition Module.cpp:292
void ignore(std::string_view reason={}) const noexcept
Explicitly discard this result after documenting the reason.
Definition Result.h:537
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
constexpr std::optional< RuntimeHandle > nextGeneration() const noexcept
Returns this slot at its next generation.
static constexpr index_type invalidIndex
Reserved index value shared by all invalid handles.
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:189
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Releases C++-owned Squirrel roots before Runtime destroys its VM.
Reflectable script-object registry backing the database panel.
eve::Result< ObjectHandle > create(const std::string &className)
Creates a class instance through the active Runtime and registers it.
const ObjectEntry * entry(ObjectHandle handle) const noexcept
Looks up a live entry without transferring ownership.
void clearAll()
Removes every entry.
size_t count(const std::string &className) const
Number of entries of a class.
static ObjectRegistry & instance()
Returns the process-local singleton owned by the UI module.
std::vector< std::string > classNames() const
Class names with at least one entry, sorted.
void clear(const std::string &className)
Removes every entry of a class.
eve::Result< void > unregister(ObjectHandle handle)
Removes a live entry and advances its slot generation.
std::vector< ObjectEntry > entries(const std::string &className) const
Entries of a class, in registration order.
eve::Result< ObjectHandle > registerObject(const std::string &className, const ssq::Object &object, const std::string &label={})
Registers an existing live script instance.
~ObjectRegistry()
Releases remaining roots, or abandons them if the Runtime VM is gone.
bool isStale(ObjectHandle handle) const noexcept
Reports whether a valid handle is stale in this registry.
eve::Result< OwnedSquirrelObject > ownSquirrelObject(BorrowedSquirrelObject borrowed)
Promotes a borrowed VM object to an explicit rooted owner.
BorrowedSquirrelObject borrowSquirrelObject(const ssq::Object &object) noexcept
Creates a one-call Borrowed view without retaining a VM reference.
eve::RuntimeHandle< UiObjectHandleTag > ObjectHandle
Generation-qualified identity for one live UI database object.
One registered live script instance.
ssq::Object object
Rooted script instance owned by the registry while live.
std::string className
Reflected script class used to group this entry.
std::string label
Optional display label shown by the database panel.
ObjectHandle handle
Generation-qualified handle for the live registry slot.