载入中...
搜索中...
未找到
ReflectedPropertyModel.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cstdlib>
5#include <utility>
6
7namespace eve::property_access {
8namespace {
9
10using eve::Value;
11
12PropertyKind propertyKind(const ReflectedMember &member) {
13 const std::string editor = member.attrString("editor");
14 if (editor == "combo" && !member.attrOptions("options").empty()) return PropertyKind::Enum;
15 if (editor == "color") return PropertyKind::Color;
16 if (editor == "vec2") return PropertyKind::Vec2;
17 if (editor == "vec3") return PropertyKind::Vec3;
18 if (editor == "vec4") return PropertyKind::Vec4;
19 switch (member.value.kind) {
20 case ReflectedValueKind::Bool: return PropertyKind::Bool;
21 case ReflectedValueKind::Integer: return PropertyKind::Integer;
22 case ReflectedValueKind::Float: return PropertyKind::Number;
23 case ReflectedValueKind::String: return PropertyKind::String;
24 case ReflectedValueKind::Array: return PropertyKind::Array;
25 case ReflectedValueKind::Table: return PropertyKind::Map;
26 case ReflectedValueKind::Instance: return PropertyKind::ObjectRef;
27 case ReflectedValueKind::None:
28 case ReflectedValueKind::Other: return PropertyKind::ReadOnlyText;
29 }
30 return PropertyKind::Auto;
31}
32
33bool editableKind(PropertyKind kind) {
34 switch (kind) {
49 return true;
53 return false;
54 }
55 return false;
56}
57
58bool scalar(ReflectedValueKind kind) {
59 return kind == ReflectedValueKind::Bool || kind == ReflectedValueKind::Integer ||
60 kind == ReflectedValueKind::Float || kind == ReflectedValueKind::String;
61}
62
63std::string ownerClass(const Runtime &runtime, const std::string &className,
64 const std::string &memberName) {
65 // Prefer the most-base class that still declares the member so inherited
66 // fields group under their defining class even when the leaf also lists them.
67 std::string owner;
68 std::string current = className;
69 while (!current.empty()) {
70 const ReflectedClass *reflected = runtime.reflectedClass(current);
71 if (!reflected) break;
72 const auto found = std::find_if(reflected->members.begin(), reflected->members.end(),
73 [&memberName](const ReflectedMember &member) {
74 return member.name == memberName;
75 });
76 if (found != reflected->members.end()) owner = current;
77 current = reflected->base;
78 }
79 return owner.empty() ? className : owner;
80}
81
82PropertyFlag propertyFlags(const ReflectedMember &member, PropertyKind kind) {
84 if (!editableKind(kind) || member.attrBool("read_only") || member.attrBool("readonly"))
85 flags = flags | PropertyFlag::ReadOnly;
86 if (member.attrBool("advanced")) flags = flags | PropertyFlag::Advanced;
87 if (member.attrBool("editor_only")) flags = flags | PropertyFlag::EditorOnly;
88 if (member.attrBool("transient")) flags = flags | PropertyFlag::Transient;
89 if (member.attrBool("dangerous")) flags = flags | PropertyFlag::Dangerous;
90 return flags;
91}
92
93ReflectedValue toReflectedValue(const Value &value) {
94 ReflectedValue result;
95 if (const auto *boolean = value.getIf<bool>()) {
96 result.kind = ReflectedValueKind::Bool;
97 result.boolean = *boolean;
98 } else if (const auto *integer = value.getIf<std::int64_t>()) {
99 result.kind = ReflectedValueKind::Integer;
100 result.integer = *integer;
101 } else if (const auto *number = value.getIf<double>()) {
102 result.kind = ReflectedValueKind::Float;
103 result.floating = *number;
104 } else if (const auto *text = value.getIf<std::string>()) {
105 result.kind = ReflectedValueKind::String;
106 result.text = *text;
107 }
108 return result;
109}
110
111Value scalarValue(const ReflectedValue &value) {
112 switch (value.kind) {
113 case ReflectedValueKind::Bool: return Value(value.boolean);
114 case ReflectedValueKind::Integer: return Value(value.integer);
115 case ReflectedValueKind::Float: return Value(value.floating);
116 case ReflectedValueKind::String: return Value(value.text);
117 default: return {};
118 }
119}
120
121const char *scriptValidationCode(const std::string &sharedCode) {
122 if (sharedCode == "property_access.property.read-only") return "property_access.script.read-only";
123 if (sharedCode == "property_access.property.type") return "property_access.script.type";
124 if (sharedCode == "property_access.property.choice") return "property_access.script.choice";
125 if (sharedCode == "property_access.property.finite") return "property_access.script.finite";
126 if (sharedCode == "property_access.property.minimum") return "property_access.script.minimum";
127 if (sharedCode == "property_access.property.maximum") return "property_access.script.maximum";
128 if (sharedCode == "property_access.property.arity") return "property_access.script.arity";
129 return "property_access.script.validation";
130}
131
132WriteResult validationFailure(const WriteResult &validation) {
133 return WriteResult::reject(scriptValidationCode(validation.code), validation.message);
134}
135
136} // namespace
137
139 struct Entry {
140 std::uint64_t id = 0;
142 };
143 std::uint64_t nextId = 1;
144 std::vector<Entry> entries;
145};
146
148 : runtime_(&runtime), instance_(std::move(instance)), observers_(std::make_shared<ObserverState>()) {
150 refresh();
151}
152
154
156 schema_ = {};
157 cachedValues_.clear();
158 if (!runtime_ || instance_.getType() != ssq::Type::INSTANCE) return;
159 schema_.typeId = runtime_->classNameOf(instance_);
160 for (const ReflectedMember &member : runtime_->reflectInstance(instance_)) {
161 if (member.method) continue;
163 descriptor.path = member.name;
164 descriptor.displayName = member.attrString("label", member.name);
165 descriptor.description = member.attrString("tooltip", member.attrString("description"));
166 descriptor.category = member.attrString("category", ownerClass(*runtime_, schema_.typeId, member.name));
167 descriptor.kind = propertyKind(member);
168 descriptor.flags = propertyFlags(member, descriptor.kind);
169 descriptor.defaultValue = convertValue(member.name, member.value);
170 descriptor.numeric.minimum =
171 member.findAttribute("min") ? std::optional<double>(member.attrFloat("min")) : std::nullopt;
172 descriptor.numeric.maximum =
173 member.findAttribute("max") ? std::optional<double>(member.attrFloat("max")) : std::nullopt;
174 descriptor.numeric.step =
175 member.findAttribute("step") ? std::optional<double>(member.attrFloat("step")) : std::nullopt;
176 descriptor.numeric.units = member.attrString("units");
177 descriptor.numeric.precision = static_cast<int>(member.attrFloat("precision", 3.f));
178 descriptor.choices = member.attrOptions("options");
179 descriptor.presenterHint = member.attrString("editor");
180 schema_.properties.push_back(std::move(descriptor));
181 }
182 ++revision_;
183}
184
185Value ReflectedPropertyModel::convertValue(const std::string &path, const ReflectedValue &value) const {
186 switch (value.kind) {
187 case ReflectedValueKind::Bool: return Value(value.boolean);
188 case ReflectedValueKind::Integer: return Value(value.integer);
189 case ReflectedValueKind::Float: return Value(value.floating);
190 case ReflectedValueKind::String: return Value(value.text);
191 case ReflectedValueKind::Array: {
192 Value::Array result;
193 const std::size_t count = runtime_->arraySize(instance_, path);
194 result.reserve(count);
195 for (std::size_t index = 0; index < count; ++index) {
196 const ReflectedValue entry = runtime_->arrayGet(instance_, path, index);
197 if (scalar(entry.kind))
198 result.push_back(scalarValue(entry));
199 else
200 result.emplace_back();
201 }
202 return Value(std::move(result));
203 }
204 case ReflectedValueKind::Table: {
205 Value::Object result;
206 for (const std::string &key : runtime_->tableKeys(instance_, path)) {
207 const ReflectedValue entry = runtime_->tableGet(instance_, path, key);
208 if (scalar(entry.kind))
209 result.emplace(key, scalarValue(entry));
210 else
211 result.emplace(key, Value());
212 }
213 return Value(std::move(result));
214 }
215 case ReflectedValueKind::Instance: {
216 const ssq::Object nested = runtime_->readObjectProperty(instance_, path);
217 return Value(runtime_->classNameOf(nested));
218 }
219 case ReflectedValueKind::None:
220 case ReflectedValueKind::Other: return {};
221 }
222 return {};
223}
224
225std::optional<Value> ReflectedPropertyModel::read(const std::string &path) const {
226 if (!runtime_ || !schema_.find(path)) return std::nullopt;
227 return convertValue(path, runtime_->readProperty(instance_, path));
228}
229
231 auto descriptor = schema_.find(path);
232 if (!descriptor) return WriteResult::reject("property_access.script.missing", "Property is not reflected");
233 const WriteResult validation = validatePropertyValue(descriptor->get(), value);
234 if (!validation.accepted) return validationFailure(validation);
235
236 const PropertyKind kind = descriptor->get().kind;
239 const auto *items = value.getIf<Value::Array>();
240 if (!items)
241 return WriteResult::reject("property_access.script.type", "Unsupported property value type");
242 std::size_t current = runtime_->arraySize(instance_, path);
243 while (current > items->size()) {
244 if (!runtime_->arrayRemove(instance_, path, current - 1))
245 return WriteResult::reject("property_access.script.write", "Runtime rejected the property write");
246 --current;
247 }
248 for (std::size_t index = 0; index < items->size(); ++index) {
249 const ReflectedValue reflected = toReflectedValue((*items)[index]);
250 if (reflected.empty())
251 return WriteResult::reject("property_access.script.type", "Unsupported property value type");
252 if (index < current) {
253 if (!runtime_->arraySet(instance_, path, index, reflected))
254 return WriteResult::reject("property_access.script.write",
255 "Runtime rejected the property write");
256 } else if (!runtime_->arrayAppend(instance_, path, reflected)) {
257 return WriteResult::reject("property_access.script.write", "Runtime rejected the property write");
258 }
259 }
260 } else if (kind == PropertyKind::Map || kind == PropertyKind::Struct) {
261 const auto *fields = value.getIf<Value::Object>();
262 if (!fields)
263 return WriteResult::reject("property_access.script.type", "Unsupported property value type");
264 const std::vector<std::string> existing = runtime_->tableKeys(instance_, path);
265 for (const std::string &key : existing) {
266 if (fields->find(key) != fields->end()) continue;
267 if (!runtime_->tableRemove(instance_, path, key))
268 return WriteResult::reject("property_access.script.write", "Runtime rejected the property write");
269 }
270 for (const auto &[key, entry] : *fields) {
271 const ReflectedValue reflected = toReflectedValue(entry);
272 if (reflected.empty())
273 return WriteResult::reject("property_access.script.type", "Unsupported property value type");
274 if (!runtime_->tableSet(instance_, path, key, reflected))
275 return WriteResult::reject("property_access.script.write", "Runtime rejected the property write");
276 }
277 } else {
278 ReflectedValue reflected = toReflectedValue(value);
279 if (reflected.empty())
280 return WriteResult::reject("property_access.script.type", "Unsupported property value type");
281 if (!runtime_->writeProperty(instance_, path, reflected))
282 return WriteResult::reject("property_access.script.write", "Runtime rejected the property write");
283 }
284
285 const Value applied = convertValue(path, runtime_->readProperty(instance_, path));
286 const auto found = cachedValues_.find(path);
287 if (found == cachedValues_.end() || found->second != applied) emit(path, applied);
288 return WriteResult::success();
289}
290
292 const std::uint64_t id = observers_->nextId++;
293 observers_->entries.push_back({id, std::move(callback)});
294 std::weak_ptr<ObserverState> weak = observers_;
295 return Subscription([weak, id]() {
296 if (const auto state = weak.lock())
297 std::erase_if(state->entries, [id](const ObserverState::Entry &entry) { return entry.id == id; });
298 });
299}
300
302 for (const PropertyDescriptor &descriptor : schema_.properties) {
303 const std::optional<Value> current = read(descriptor.path);
304 if (!current) continue;
305 const auto found = cachedValues_.find(descriptor.path);
306 if (found == cachedValues_.end() || found->second != *current) emit(descriptor.path, *current);
307 }
308}
309
310void ReflectedPropertyModel::emit(const std::string &path, const Value &value) {
311 cachedValues_[path] = value;
312 const PropertyChange change{path, value, ++revision_};
313 const auto snapshot = observers_->entries;
314 for (const ObserverState::Entry &entry : snapshot)
315 if (entry.callback) entry.callback(change);
316}
317
318} // namespace eve::property_access
double value
std::string descriptor
std::uint32_t key
std::string text
TokenKind kind
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
std::string id
Definition PlayHost.cpp:108
double number
bool found
double current
bool boolean
std::uint32_t count
float(ui::Theme::* member)[4]
uint32_t index
ViewPreparation callback
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:189
bool arrayRemove(const ssq::Object &instance, const std::string &name, size_t index) const
Removes one element of an array member (shifts the rest).
Definition Runtime.cpp:887
std::vector< ReflectedMember > reflectInstance(const ssq::Object &instance) const
Inspects a live instance: own + inherited members with current values.
Definition Runtime.cpp:669
ReflectedValue tableGet(const ssq::Object &instance, const std::string &name, const std::string &key) const
Value of a table member key; empty when missing.
Definition Runtime.cpp:936
bool writeProperty(const ssq::Object &instance, const std::string &name, const ReflectedValue &value) const
Writes one property of a live instance.
Definition Runtime.cpp:732
ssq::Object readObjectProperty(const ssq::Object &instance, const std::string &name) const
Reads a live object-valued property (nested instance).
Definition Runtime.cpp:791
bool tableRemove(const ssq::Object &instance, const std::string &name, const std::string &key) const
Removes a key from a table member.
Definition Runtime.cpp:995
bool tableSet(const ssq::Object &instance, const std::string &name, const std::string &key, const ReflectedValue &value) const
Sets a key of a table member (creates the key when missing).
Definition Runtime.cpp:957
bool arrayAppend(const ssq::Object &instance, const std::string &name, const ReflectedValue &value) const
Appends an element to an array member.
Definition Runtime.cpp:870
std::string classNameOf(const ssq::Object &instance) const
Name of the script class of a live instance ("" when unknown).
Definition Runtime.cpp:1163
ReflectedValue readProperty(const ssq::Object &instance, const std::string &name) const
Reads one property of a live instance.
Definition Runtime.cpp:716
size_t arraySize(const ssq::Object &instance, const std::string &name) const
Element count of an array member (0 when missing/not an array).
Definition Runtime.cpp:810
bool arraySet(const ssq::Object &instance, const std::string &name, size_t index, const ReflectedValue &value) const
Replaces one element of an array member.
Definition Runtime.cpp:846
ReflectedValue arrayGet(const ssq::Object &instance, const std::string &name, size_t index) const
Element value of an array member; empty when out of range.
Definition Runtime.cpp:826
std::vector< std::string > tableKeys(const ssq::Object &instance, const std::string &name) const
String keys of a table member.
Definition Runtime.cpp:910
Move-only RAII token that owns one observer registration.
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
std::function< void(const PropertyChange &)> ChangeCallback
~ReflectedPropertyModel() override
Reflected property model.
ReflectedPropertyModel(Runtime &runtime, ssq::Object instance)
Bind one live script instance to the shared property-access contract.
void rebuildSchema()
Rebuild reflection metadata after a script reload.
void refresh()
Pull live values and notify observers for changed properties.
Subscription subscribe(ChangeCallback callback) override
Subscribe.
std::optional< eve::Value > read(const std::string &path) const override
Reads .
WriteResult write(const std::string &path, const eve::Value &value) override
Writes .
eve::Subscription Subscription
Compatibility alias for the common observer lifetime token.
PropertyFlag
Cross-host property visibility and editing flags.
PropertyKind
Semantic property kind used by renderer-independent presenters.
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
enum EVENGINE_API_FOUNDATION ReflectedValueKind
Runtime value kind read from a live script instance slot.
Definition Runtime.h:114
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:152
Typed snapshot of one live instance property.
Definition Runtime.h:132
bool empty() const noexcept
True when the slot exists and is non-null.
Definition Runtime.h:140
ReflectedValueKind kind
Definition Runtime.h:133
Immutable notification emitted after a model property changes.
One stable property description consumed by any UI shell.
eve::OptionalRef< const PropertyDescriptor > find(const std::string &path) const
Find a property by stable path, or an empty immediate borrow.
std::vector< PropertyDescriptor > properties
Result of a model write, including a stable diagnostic code.
static WriteResult success()
Success.
static WriteResult reject(std::string code, std::string message)
Reject.