载入中...
搜索中...
未找到
HouseGenTarget.cpp
浏览该文件的文档.
2
4
5#include <algorithm>
6#include <cmath>
7#include <set>
8#include <utility>
9
10namespace eve::housegen_editing {
11namespace {
12const EditorValue* field(const EditorValue& value, const char* key) {
13 const auto* object = value.getIf<EditorValue::Object>();
14 if (!object) return nullptr;
15 const auto it = object->find(key);
16 return it == object->end() ? nullptr : &it->second;
17}
18std::string directionName(housegen::SocketDirection d) {
19 switch (d) {
20 case housegen::SocketDirection::North: return "north";
21 case housegen::SocketDirection::East: return "east";
22 case housegen::SocketDirection::South: return "south";
23 case housegen::SocketDirection::West: return "west";
24 case housegen::SocketDirection::Up: return "up";
25 case housegen::SocketDirection::Down: return "down";
26 }
27 return "north";
28}
29bool direction(const std::string& s, housegen::SocketDirection& d) {
30 if (s == "north")
32 else if (s == "east")
34 else if (s == "south")
36 else if (s == "west")
38 else if (s == "up")
40 else if (s == "down")
42 else
43 return false;
44 return true;
45}
46EditorValue strings(const std::vector<std::string>& values) {
48 for (const auto& v : values) out.push_back(v);
49 return out;
50}
51EditorValue integers(const std::vector<int>& values) {
53 for (int v : values) out.push_back(int64_t{v});
54 return out;
55}
56bool readStrings(const EditorValue* value, std::vector<std::string>& out) {
57 const auto* array = value ? value->getIf<EditorValue::Array>() : nullptr;
58 if (!array) return false;
59 out.clear();
60 for (const auto& v : *array) {
61 const auto* s = v.getIf<std::string>();
62 if (!s) return false;
63 out.push_back(*s);
64 }
65 return true;
66}
67bool readIntegers(const EditorValue* value, std::vector<int>& out) {
68 const auto* array = value ? value->getIf<EditorValue::Array>() : nullptr;
69 if (!array) return false;
70 out.clear();
71 for (const auto& v : *array) {
72 const auto* n = v.getIf<int64_t>();
73 if (!n) return false;
74 out.push_back(static_cast<int>(*n));
75 }
76 return true;
77}
78EditorValue materialValue(const housegen::HouseMaterialOverride& m) {
80 {"hasBaseColor", m.hasBaseColor},
81 {"baseColor", EditorValue::Array{m.baseColorR, m.baseColorG, m.baseColorB, m.baseColorA}},
82 {"baseColorTexture", m.baseColorTexture},
83 {"normalTexture", m.normalTexture},
84 {"heightTexture", m.heightTexture},
85 {"hasMetallic", m.hasMetallic},
86 {"metallic", m.metallic},
87 {"hasRoughness", m.hasRoughness},
88 {"roughness", m.roughness},
89 {"parallaxScale", m.parallaxScale},
90 {"parallaxMinLayers", m.parallaxMinLayers},
91 {"parallaxMaxLayers", m.parallaxMaxLayers},
92 {"cellBombScale", m.cellBombScale},
93 {"cellBombStrength", m.cellBombStrength},
94 {"cellBombRotation", m.cellBombRotation}};
95}
96EditorValue componentValue(const HouseKitComponentValue& value) {
97 const auto& c = value.component;
98 EditorValue::Array sockets;
99 for (const auto& s : c.sockets)
100 sockets.push_back(EditorValue::Object{
101 {"direction", directionName(s.direction)}, {"type", s.type}, {"accepts", strings(s.accepts)}});
102 return EditorValue::Object{{"editorId", value.id.value()},
103 {"id", c.id},
104 {"model", c.modelPath},
105 {"category", c.category},
106 {"width", int64_t{c.width}},
107 {"depth", int64_t{c.depth}},
108 {"height", int64_t{c.height}},
109 {"weight", int64_t{c.weight}},
110 {"rotations", integers(c.rotations)},
111 {"tags", strings(c.tags)},
112 {"sockets", std::move(sockets)},
113 {"material", materialValue(c.material)}};
114}
115bool number(const EditorValue* value, float& out) {
116 const auto* n = value ? value->getIf<double>() : nullptr;
117 if (!n || !std::isfinite(*n)) return false;
118 out = static_cast<float>(*n);
119 return true;
120}
121Result<HouseKitComponentValue> parseComponent(const EditorValue& value) {
122 HouseKitComponentValue out;
123 const auto *editorId = field(value, "editorId"), *id = field(value, "id"), *model = field(value, "model"),
124 *category = field(value, "category");
125 const auto* editorIds = editorId ? editorId->getIf<std::string>() : nullptr;
126 const auto* ids = id ? id->getIf<std::string>() : nullptr;
127 const auto* models = model ? model->getIf<std::string>() : nullptr;
128 const auto* categories = category ? category->getIf<std::string>() : nullptr;
129 if (!editorIds || editorIds->empty() || !ids || !models || !categories)
130 return eve::editing::failed<HouseKitComponentValue>(EditorStatus::Rejected, RuleId("editor.housegen.component"),
131 "House component identity is invalid");
132 out.id = ObjectId(*editorIds);
133 out.component.id = *ids;
134 out.component.modelPath = *models;
135 out.component.category = *categories;
136 int* values[] = {&out.component.width, &out.component.depth, &out.component.height, &out.component.weight};
137 const char* fields[] = {"width", "depth", "height", "weight"};
138 for (int i = 0; i < 4; ++i) {
139 const auto* v = field(value, fields[i]);
140 const auto* n = v ? v->getIf<int64_t>() : nullptr;
141 if (!n)
142 return eve::editing::failed<HouseKitComponentValue>(
143 EditorStatus::Rejected, RuleId("editor.housegen.dimension"), "House component dimension is invalid");
144 *values[i] = static_cast<int>(*n);
145 }
146 if (!readIntegers(field(value, "rotations"), out.component.rotations) ||
147 !readStrings(field(value, "tags"), out.component.tags))
148 return eve::editing::failed<HouseKitComponentValue>(EditorStatus::Rejected, RuleId("editor.housegen.arrays"),
149 "House component rotations or tags are invalid");
150 const auto* socketValues = field(value, "sockets");
151 const auto* socketArray = socketValues ? socketValues->getIf<EditorValue::Array>() : nullptr;
152 if (!socketArray)
153 return eve::editing::failed<HouseKitComponentValue>(EditorStatus::Rejected, RuleId("editor.housegen.sockets"),
154 "House sockets are invalid");
155 for (const auto& socketValue : *socketArray) {
156 housegen::HouseSocket socket;
157 const auto * d = field(socketValue, "direction"), *t = field(socketValue, "type");
158 const auto* ds = d ? d->getIf<std::string>() : nullptr;
159 const auto* ts = t ? t->getIf<std::string>() : nullptr;
160 if (!ds || !ts || !direction(*ds, socket.direction) ||
161 !readStrings(field(socketValue, "accepts"), socket.accepts))
162 return eve::editing::failed<HouseKitComponentValue>(
163 EditorStatus::Rejected, RuleId("editor.housegen.socket"), "House socket is invalid");
164 socket.type = *ts;
165 out.component.sockets.push_back(std::move(socket));
166 }
167 const auto* material = field(value, "material");
168 if (!material)
169 return eve::editing::failed<HouseKitComponentValue>(EditorStatus::Rejected, RuleId("editor.housegen.material"),
170 "House material is missing");
171 auto& m = out.component.material;
172 const auto *hbc = field(*material, "hasBaseColor"), *hm = field(*material, "hasMetallic"),
173 *hr = field(*material, "hasRoughness");
174 const auto* hbcb = hbc ? hbc->getIf<bool>() : nullptr;
175 const auto* hmb = hm ? hm->getIf<bool>() : nullptr;
176 const auto* hrb = hr ? hr->getIf<bool>() : nullptr;
177 const auto* colorValue = field(*material, "baseColor");
178 const auto* color = colorValue ? colorValue->getIf<EditorValue::Array>() : nullptr;
179 if (!hbcb || !hmb || !hrb || !color || color->size() != 4)
180 return eve::editing::failed<HouseKitComponentValue>(EditorStatus::Rejected, RuleId("editor.housegen.material"),
181 "House material flags or color are invalid");
182 m.hasBaseColor = *hbcb;
183 m.hasMetallic = *hmb;
184 m.hasRoughness = *hrb;
185 float* colors[] = {&m.baseColorR, &m.baseColorG, &m.baseColorB, &m.baseColorA};
186 for (int i = 0; i < 4; ++i)
187 if (!number(&(*color)[i], *colors[i]))
188 return eve::editing::failed<HouseKitComponentValue>(EditorStatus::Rejected, RuleId("editor.housegen.color"),
189 "House material color is invalid");
190 std::string* paths[] = {&m.baseColorTexture, &m.normalTexture, &m.heightTexture};
191 const char* pathFields[] = {"baseColorTexture", "normalTexture", "heightTexture"};
192 for (int i = 0; i < 3; ++i) {
193 const auto* p = field(*material, pathFields[i]);
194 const auto* s = p ? p->getIf<std::string>() : nullptr;
195 if (!s)
196 return eve::editing::failed<HouseKitComponentValue>(
197 EditorStatus::Rejected, RuleId("editor.housegen.texture"), "House material texture is invalid");
198 *paths[i] = *s;
199 }
200 float* scalars[] = {&m.metallic, &m.roughness, &m.parallaxScale, &m.parallaxMinLayers,
201 &m.parallaxMaxLayers, &m.cellBombScale, &m.cellBombStrength, &m.cellBombRotation};
202 const char* scalarFields[] = {"metallic", "roughness", "parallaxScale", "parallaxMinLayers",
203 "parallaxMaxLayers", "cellBombScale", "cellBombStrength", "cellBombRotation"};
204 for (int i = 0; i < 8; ++i)
205 if (!number(field(*material, scalarFields[i]), *scalars[i]))
206 return eve::editing::failed<HouseKitComponentValue>(
207 EditorStatus::Rejected, RuleId("editor.housegen.material-number"), "House material number is invalid");
208 return eve::editing::applied<HouseKitComponentValue>(std::move(out));
209}
210EditorValue requestValue(const housegen::HouseRequest& r) {
211 return EditorValue::Object{{"seed", int64_t{r.seed}},
212 {"width", int64_t{r.width}},
213 {"depth", int64_t{r.depth}},
214 {"floors", int64_t{r.floors}},
215 {"moduleSize", r.moduleSize},
216 {"floorHeight", r.floorHeight},
217 {"style", r.style},
218 {"footprint", r.footprint},
219 {"roof", r.roof},
220 {"entrance", r.entrance},
221 {"requiredRooms", strings(r.requiredRooms)},
222 {"maxAttempts", int64_t{r.maxAttempts}}};
223}
224Result<housegen::HouseRequest> parseRequest(const EditorValue& value) {
225 housegen::HouseRequest r;
226 uint32_t* unused = nullptr;
227 (void)unused;
228 const auto* seed = field(value, "seed");
229 const auto* seedn = seed ? seed->getIf<int64_t>() : nullptr;
230 if (!seedn || *seedn < 0 || *seedn > UINT32_MAX)
231 return eve::editing::failed<housegen::HouseRequest>(EditorStatus::Rejected, RuleId("editor.housegen.seed"),
232 "House seed is invalid");
233 r.seed = static_cast<uint32_t>(*seedn);
234 int* ints[] = {&r.width, &r.depth, &r.floors, &r.maxAttempts};
235 const char* intFields[] = {"width", "depth", "floors", "maxAttempts"};
236 for (int i = 0; i < 4; ++i) {
237 const auto* v = field(value, intFields[i]);
238 const auto* n = v ? v->getIf<int64_t>() : nullptr;
239 if (!n)
240 return eve::editing::failed<housegen::HouseRequest>(
241 EditorStatus::Rejected, RuleId("editor.housegen.request-number"), "House request integer is invalid");
242 *ints[i] = static_cast<int>(*n);
243 }
244 if (!number(field(value, "moduleSize"), r.moduleSize) || !number(field(value, "floorHeight"), r.floorHeight))
245 return eve::editing::failed<housegen::HouseRequest>(
246 EditorStatus::Rejected, RuleId("editor.housegen.request-number"), "House request scale is invalid");
247 std::string* values[] = {&r.style, &r.footprint, &r.roof, &r.entrance};
248 const char* fields[] = {"style", "footprint", "roof", "entrance"};
249 for (int i = 0; i < 4; ++i) {
250 const auto* v = field(value, fields[i]);
251 const auto* s = v ? v->getIf<std::string>() : nullptr;
252 if (!s)
253 return eve::editing::failed<housegen::HouseRequest>(
254 EditorStatus::Rejected, RuleId("editor.housegen.request-string"), "House request string is invalid");
255 *values[i] = *s;
256 }
257 if (!readStrings(field(value, "requiredRooms"), r.requiredRooms))
258 return eve::editing::failed<housegen::HouseRequest>(EditorStatus::Rejected, RuleId("editor.housegen.rooms"),
259 "Required rooms are invalid");
260 return eve::editing::applied<housegen::HouseRequest>(std::move(r));
261}
263 double maximum = 1e9) {
266 d.displayNameKey = "editor.housegen." + path;
267 d.category = "component";
268 d.type = type;
269 d.defaultValue = std::move(defaultValue);
270 d.flags = PropertyFlag::EditorOnly;
271 d.numeric.minimum = minimum;
272 d.numeric.maximum = maximum;
273 return d;
274}
275bool hasErrors(const std::vector<EditorDiagnostic>& values) {
276 return std::any_of(values.begin(), values.end(),
277 [](const auto& d) { return d.severity() == DiagnosticSeverity::Error; });
278}
279} // namespace
280
281HouseGenDocumentTarget::HouseGenDocumentTarget(std::string id) : id_(std::move(id)) {}
283 return {TargetId(id_),
284 "housegen-asset",
286 false,
287 {CapabilityId("eve.editor.target.housegen-properties")}};
288}
290 return c == CapabilityId("eve.editor.target.housegen-properties") ? static_cast<IPropertyProvider*>(this) : nullptr;
291}
292bool HouseGenDocumentTarget::matches(const SelectionSnapshot& s) const {
293 if (s.items.empty()) return false;
294 for (const auto& item : s.items)
295 if (item.target != TargetId(id_) || std::none_of(components_.begin(), components_.end(), [&](const auto& c) {
296 return c.id.value() == item.item.value();
297 }))
298 return false;
299 return true;
300}
309 s.typeId = "housegen.component";
310 s.properties = {prop("component.model", PropertyType::AssetRef, ""),
311 prop("component.category", PropertyType::Enum, "wall"),
312 prop("component.width", PropertyType::Int, int64_t{1}, 1, 128),
313 prop("component.depth", PropertyType::Int, int64_t{1}, 1, 128),
314 prop("component.height", PropertyType::Int, int64_t{1}, 1, 128),
315 prop("component.weight", PropertyType::Int, int64_t{1}, 1, 100000),
316 prop("material.metallic", PropertyType::Float, 0.0, 0, 1),
317 prop("material.roughness", PropertyType::Float, 1.0, 0, 1)};
318 s.properties[0].assetTypeFilters = {"model3d"};
319 s.properties[1].enumItems = {"foundation", "floor", "wall", "door", "roof"};
320 return s;
321}
323 if (!matches(s) || !schema(s).find(path)) return {};
324 std::optional<EditorValue> common;
325 for (const auto& item : s.items) {
326 const auto& c = std::find_if(components_.begin(), components_.end(), [&](const auto& v) {
327 return v.id.value() == item.item.value();
328 })->component;
330 if (path == PropertyPath("component.model"))
331 value = c.modelPath;
332 else if (path == PropertyPath("component.category"))
333 value = c.category;
334 else if (path == PropertyPath("component.width"))
335 value = int64_t{c.width};
336 else if (path == PropertyPath("component.depth"))
337 value = int64_t{c.depth};
338 else if (path == PropertyPath("component.height"))
339 value = int64_t{c.height};
340 else if (path == PropertyPath("component.weight"))
341 value = int64_t{c.weight};
342 else if (path == PropertyPath("material.metallic"))
343 value = static_cast<double>(c.material.metallic);
344 else
345 value = static_cast<double>(c.material.roughness);
346 if (!common)
347 common = value;
348 else if (*common != value)
349 return {PropertyReadState::Mixed, {}, {}};
350 }
351 return {PropertyReadState::Value, *common, {}};
352}
353EditorValue HouseGenDocumentTarget::contentValue() const {
355 for (const auto& c : components_) components.push_back(componentValue(c));
356 return EditorValue::Object{{"components", std::move(components)}, {"request", requestValue(request_)}};
357}
358Result<DomainOperation> HouseGenDocumentTarget::replacement(EditorValue content,
359 std::string property) const {
361 op.type = "housegen.document.replace.v1";
362 op.inverseType = op.type;
363 op.target = TargetId(id_);
364 op.payload = std::move(content);
365 op.inverse = contentValue();
366 op.hasInverse = true;
367 if (!property.empty()) op.affectedProperties.push_back(property);
368 op.mergeKey = "housegen:" + id_ + ":" + (property.empty() ? "structure" : property);
369 return eve::editing::applied<DomainOperation>(std::move(op));
370}
372 const EditorValue& value, PropertySetMode mode) const {
373 if (mode == PropertySetMode::Reset) return makeReset(s, path);
374 auto d = schema(s).find(path);
375 if (!matches(s) || !d || mode != PropertySetMode::Absolute || !validatePropertyValue(*d, value).ok())
376 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.housegen.set"),
377 "House component property is invalid");
378 auto candidate = *this;
379 for (const auto& item : s.items) {
380 auto& c = std::find_if(candidate.components_.begin(), candidate.components_.end(), [&](const auto& v) {
381 return v.id.value() == item.item.value();
382 })->component;
383 if (path == PropertyPath("component.model"))
384 c.modelPath = *value.getIf<std::string>();
385 else if (path == PropertyPath("component.category"))
386 c.category = *value.getIf<std::string>();
387 else if (path == PropertyPath("component.width"))
388 c.width = static_cast<int>(*value.getIf<int64_t>());
389 else if (path == PropertyPath("component.depth"))
390 c.depth = static_cast<int>(*value.getIf<int64_t>());
391 else if (path == PropertyPath("component.height"))
392 c.height = static_cast<int>(*value.getIf<int64_t>());
393 else if (path == PropertyPath("component.weight"))
394 c.weight = static_cast<int>(*value.getIf<int64_t>());
395 else if (path == PropertyPath("material.metallic")) {
396 c.material.hasMetallic = true;
397 c.material.metallic = static_cast<float>(*value.getIf<double>());
398 } else {
399 c.material.hasRoughness = true;
400 c.material.roughness = static_cast<float>(*value.getIf<double>());
401 }
402 }
403 if (hasErrors(candidate.validate()))
404 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.housegen.invalid"),
405 "House component edit invalidates the kit");
406 return replacement(candidate.contentValue(), path.value());
407}
409 const PropertyPath& path) const {
410 auto d = schema(s).find(path);
411 if (!d)
412 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.housegen.property"),
413 "Unknown house component property");
414 return makeSet(s, path, d->defaultValue, PropertySetMode::Absolute);
415}
417 const HouseKitComponentValue& component) const {
418 if (component.id.empty() || std::any_of(components_.begin(), components_.end(), [&](const auto& c) {
419 return c.id == component.id || c.component.id == component.component.id;
420 }))
421 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.housegen.component-id"),
422 "House editor and runtime component IDs must be unique");
423 auto candidate = *this;
424 candidate.components_.push_back(component);
426 if (!library.registerComponent(component.component).ok())
427 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.housegen.component"),
428 "House runtime rejected the component");
429 return replacement(candidate.contentValue());
430}
432 if (std::none_of(components_.begin(), components_.end(), [&](const auto& c) { return c.id == id; }))
433 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.housegen.component"),
434 "House component does not exist");
435 auto candidate = *this;
436 std::erase_if(candidate.components_, [&](const auto& c) { return c.id == id; });
437 return replacement(candidate.contentValue());
438}
440 auto candidate = *this;
441 candidate.request_ = request;
442 if (hasErrors(candidate.validate()))
443 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.housegen.request"),
444 "House generation request is invalid");
445 return replacement(candidate.contentValue(), "request");
446}
447std::vector<EditorDiagnostic> HouseGenDocumentTarget::validate() const {
448 std::vector<EditorDiagnostic> out;
449 if (components_.size() > 2048)
451 RuleId("editor.housegen.component-budget"), DiagnosticSeverity::Error,
452 "House kit exceeds 2048 components"));
454 std::set<std::string> editorIds;
455 std::set<std::string> categories;
456 for (const auto& c : components_) {
457 if (c.id.empty() || !editorIds.insert(c.id.value()).second)
459 RuleId("editor.housegen.editor-id"), DiagnosticSeverity::Error,
460 "House editor component IDs must be unique"));
461 auto registered = library.registerComponent(c.component);
462 if (!registered.ok())
464 RuleId("editor.housegen.component"), DiagnosticSeverity::Error,
465 "Invalid or duplicate runtime component: " + c.component.id));
466 categories.insert(c.component.category);
467 }
468 for (const char* required : {"foundation", "floor", "wall", "door", "roof"})
469 if (!categories.contains(required))
471 RuleId("editor.housegen.category"), DiagnosticSeverity::Warning,
472 "House kit is not generation-ready; missing category: " + std::string(required)));
473 const bool footprint = request_.footprint == "auto" || request_.footprint == "rectangle" ||
474 request_.footprint == "l_shape" || request_.footprint == "t_shape";
475 const bool roof =
476 request_.roof == "auto" || request_.roof == "gable" || request_.roof == "flat" || request_.roof == "shed";
477 const bool entrance = request_.entrance == "auto" || request_.entrance == "north" || request_.entrance == "east" ||
478 request_.entrance == "south" || request_.entrance == "west";
479 if (request_.width < 2 || request_.width > 256 || request_.depth < 2 || request_.depth > 256 ||
480 request_.floors < 1 || request_.floors > 64 || !std::isfinite(request_.moduleSize) ||
481 request_.moduleSize <= 0 || request_.moduleSize > 10000 || !std::isfinite(request_.floorHeight) ||
482 request_.floorHeight <= 0 || request_.floorHeight > 10000 || request_.maxAttempts < 1 ||
483 request_.maxAttempts > 10000 || !footprint || !roof || !entrance)
485 RuleId("editor.housegen.request-range"), DiagnosticSeverity::Error,
486 "House generation request exceeds supported values or budgets"));
487 if (request_.requiredRooms.size() > 256)
489 RuleId("editor.housegen.room-budget"), DiagnosticSeverity::Error,
490 "House request exceeds 256 required rooms"));
491 return out;
492}
494 if (op.target != TargetId(id_) || op.type != "housegen.document.replace.v1")
495 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.housegen.operation"),
496 "House operation mismatch");
497 const auto *componentsValue = field(op.payload, "components"), *requestValueField = field(op.payload, "request");
498 const auto* components = componentsValue ? componentsValue->getIf<EditorValue::Array>() : nullptr;
499 if (!components || !requestValueField || components->size() > 2048 ||
500 !op.payload.isWithinLimits(10, 200000, 16 * 1024 * 1024))
501 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.housegen.payload"),
502 "House document payload exceeds limits");
503 HouseGenDocumentTarget candidate(id_);
504 for (const auto& value : *components) {
505 auto parsed = parseComponent(value);
506 if (!parsed.ok())
507 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.housegen.component"),
508 "House document component cannot be parsed");
509 candidate.components_.push_back(parsed.value());
510 }
511 auto request = parseRequest(*requestValueField);
512 if (!request.ok())
513 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.housegen.request"),
514 "House document request cannot be parsed");
515 candidate.request_ = request.value();
516 if (hasErrors(candidate.validate()))
517 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.housegen.invalid"),
518 "House document validation failed");
519 components_ = std::move(candidate.components_);
520 request_ = std::move(candidate.request_);
521 bumpRevision();
522 widenDirty(0, 0);
523 return eve::editing::applied<void>();
524}
525std::unique_ptr<IDomainOperationTarget> HouseGenDocumentTarget::cloneDomainState() const {
526 return std::make_unique<HouseGenDocumentTarget>(*this);
527}
529 std::unique_ptr<IDomainOperationTarget> candidate) {
530 auto* typed = dynamic_cast<HouseGenDocumentTarget*>(candidate.get());
531 if (!typed || typed->id_ != id_)
532 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.housegen.candidate"),
533 "House candidate mismatch");
534 *this = *typed;
535 return eve::editing::applied<void>();
536}
538 return EditorValue::Object{{"schemaVersion", int64_t{1}}, {"content", contentValue()}};
539}
541 const auto *versionValue = field(snapshot, "schemaVersion"), *content = field(snapshot, "content");
542 const auto* version = versionValue ? versionValue->getIf<int64_t>() : nullptr;
543 if (!version || *version != 1 || !content)
544 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.housegen.snapshot"),
545 "Unsupported house snapshot");
547 op.target = TargetId(id_);
548 op.type = "housegen.document.replace.v1";
549 op.payload = *content;
550 auto result = applyDomainOperation(op);
551 if (result.ok()) clearDirtyRegion();
552 return result;
553}
554
555} // namespace eve::housegen_editing
LogicalId target
double value
const std::string & s
glm::vec4 p[6]
std::map< std::string, Var > values
float maximum[3]
float minimum[3]
const GltfImportRequest & request
std::uint32_t key
glm::uvec4 ids
glm::vec3 n
Definition Grass.cpp:63
double r
float v
std::int32_t c
bool required
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
std::string id
Definition PlayHost.cpp:108
std::uint32_t seed
Definition PointSet.cpp:807
float begin
float d
float t
glm::mat4 model
Material * material
std::vector< float > colors
RoadLaneDirection direction
double number
const char * category
std::map< std::string, std::string > paths
float m[16]
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
bool ok() const noexcept
Whether this result represents a non-failure outcome.
Definition Result.h:480
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
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
bool empty() const noexcept
True when no compatibility spelling has been assigned.
Definition Identity.h:374
void bumpRevision()
Advance the revision without touching the dirty region.
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
void widenDirty()
Mark an unknown area dirty without advancing the revision.
virtual void clearDirtyRegion()=0
Clears dirty region.
UI-independent property access for an immutable selection snapshot.
Dotted property path (layout.size) compared and persisted as text.
Definition EditingIds.h:78
Deterministic owning value tree shared by authoring hosts.
std::vector< Value > Array
bool isWithinLimits(size_t maxDepth, size_t maxElements, size_t maxStringBytes) const
Validate nesting, element count and approximate string byte limits.
std::map< std::string, Value > Object
Type type() const
Return the active value type.
const T * getIf() const
Return an immediately borrowed typed value.
房屋组件注册表(按 id 索引,支持分类/风格查询)。
eve::Result< void > registerComponent(const HouseComponent &component)
注册单个组件(id 重复会报错)。
Revisioned component-kit and deterministic generation-request asset.
Result< DomainOperation > makeCreateComponent(const HouseKitComponentValue &component) const
Add one component with stable editor and unique runtime IDs.
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
const housegen::HouseRequest & request() const
Request.
Result< DomainOperation > makeDeleteComponent(const ObjectId &id) const
Remove one component.
Result< DomainOperation > makeSetRequest(const housegen::HouseRequest &request) const
Replace the deterministic generation request.
Result< DomainOperation > makeSet(const SelectionSnapshot &selection, const PropertyPath &path, const EditorValue &value, PropertySetMode mode) const override
Make set.
EditorValue snapshotValue() const
Snapshot value.
TargetDescriptor describe() const override
Describe.
eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &selection) const override
Current revision.
PropertySchema schema(const SelectionSnapshot &selection) const override
Schema.
Result< DomainOperation > makeReset(const SelectionSnapshot &selection, const PropertyPath &path) const override
Make reset.
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
const std::vector< HouseKitComponentValue > & components() const
Components.
PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const override
Reads .
std::vector< EditorDiagnostic > validate() const
Validate kit fields, generator-required categories and request budgets.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
Result< void > loadSnapshot(const EditorValue &snapshot)
Loads snapshot.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
const char * defaultValue
EVENGINE_API_PLATFORM bool hasErrors(const std::vector< DnutDiagnostic > &diagnostics)
Return whether any diagnostic is an error.
PropertySetMode
PropertySetMode public API.
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
editing::PropertyPath PropertyPath
editing::ObjectId ObjectId
editing::PropertyDescriptor PropertyDescriptor
editing::TargetId TargetId
editing::Value EditorValue
editing::PropertyType PropertyType
editing::DomainOperation DomainOperation
editing::CapabilityId CapabilityId
editing::RuleId RuleId
SocketDirection
组件连接点方向(上下 + 四向)。
const EditorValue * field(const EditorValue &value, const char *name)
std::vector< int64_t > ints(const RuntimeTensor &v)
Ints.
DomainOperation public API.
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
PropertyDescriptorLookup find(const PropertyPath &path) const
Finds find.
SelectionSnapshot public API.
Stable discovery metadata exposed by every editable target.
一次房屋生成请求(参数集)。
std::vector< std::string > requiredRooms
int maxAttempts
生成重试上限。
std::string footprint
auto | rectangle | l_shape | t_shape | polygon。
std::string roof
auto | gable | flat | shed。
std::string entrance
auto | north | east | south | west。
int width
占地尺寸(格)与层数。
float moduleSize
模块尺寸 / 层高(世界单位)。
Authored house component with stable editor identity.
glm::vec4 color