载入中...
搜索中...
未找到
ArtifactProvider.cpp
浏览该文件的文档.
2
3#include "common/Capability.h"
4#include "physics/Body3D.h"
5#include "physics/Shape3D.h"
6#include "physics/World3D.h"
7
8#include <algorithm>
9#include <charconv>
10#include <cmath>
11#include <cstdint>
12#include <exception>
13#include <limits>
14#include <memory>
15#include <string>
16#include <type_traits>
17#include <unordered_set>
18#include <utility>
19
20namespace eve::physics {
21
32 std::unique_ptr<World3D> ownedWorld;
33 World3D* world = nullptr;
34 std::weak_ptr<const void> worldLifetime;
35 std::unique_ptr<Body3D> body;
36 std::unique_ptr<Shape3D> shape;
37 };
38
39 std::vector<RuntimeCollider> records;
40 std::size_t pendingStages = 0;
41};
42
43namespace {
44
45const eve::Value* member(const eve::Value::Object& object, const char* name) noexcept {
46 const auto found = object.find(name);
47 return found == object.end() ? nullptr : &found->second;
48}
49
50bool readString(const eve::Value::Object& object, const char* name, std::string& output) {
51 const auto* value = member(object, name);
52 const auto* text = value ? value->getIf<std::string>() : nullptr;
53 if (!text || text->empty()) return false;
54 output = *text;
55 return true;
56}
57
58bool readU64(const eve::Value::Object& object, const char* name, std::uint64_t& output) {
59 std::string text;
60 if (!readString(object, name, text)) return false;
61 const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), output);
62 return error == std::errc{} && end == text.data() + text.size();
63}
64
65bool readFloat(const eve::Value::Object& object, const char* name, float& output) {
66 const auto* value = member(object, name);
67 if (!value) return false;
68 if (const auto* number = value->getIf<double>()) {
69 if (!std::isfinite(*number)) return false;
70 output = static_cast<float>(*number);
71 return std::isfinite(output);
72 }
73 if (const auto* integer = value->getIf<std::int64_t>()) {
74 output = static_cast<float>(*integer);
75 return std::isfinite(output);
76 }
77 return false;
78}
79
80bool hasSupportedVersion(const eve::Value::Object& object) noexcept {
81 const auto* encoded = member(object, "version");
82 const auto* version = encoded ? encoded->getIf<std::int64_t>() : nullptr;
83 return version && *version == 1;
84}
85
86bool validBounds(const eve::artifact::Bounds& bounds) noexcept {
87 return bounds.valid && std::isfinite(bounds.minX) && std::isfinite(bounds.minY) && std::isfinite(bounds.minZ) &&
88 std::isfinite(bounds.maxX) && std::isfinite(bounds.maxY) && std::isfinite(bounds.maxZ) &&
89 bounds.minX <= bounds.maxX && bounds.minY <= bounds.maxY && bounds.minZ <= bounds.maxZ;
90}
91
93 const auto* encoded = member(object, "bounds");
94 const auto* bounds = encoded ? encoded->getIf<eve::Value::Object>() : nullptr;
95 if (!bounds || !readFloat(*bounds, "minX", output.minX) || !readFloat(*bounds, "minY", output.minY) ||
96 !readFloat(*bounds, "minZ", output.minZ) || !readFloat(*bounds, "maxX", output.maxX) ||
97 !readFloat(*bounds, "maxY", output.maxY) || !readFloat(*bounds, "maxZ", output.maxZ))
98 return false;
99 output.valid = true;
100 return validBounds(output);
101}
102
103template <class T>
104eve::Value arrayValue(const std::vector<T>& values) {
105 eve::Value::Array encoded;
106 encoded.reserve(values.size());
107 for (const auto value : values) {
108 if constexpr (std::is_same_v<T, float>)
109 encoded.emplace_back(value);
110 else
111 encoded.emplace_back(std::int64_t(value));
112 }
113 return eve::Value(std::move(encoded));
114}
115
116template <class T>
117bool readArray(const eve::Value* encoded, std::vector<T>& output) {
118 const auto* array = encoded ? encoded->getIf<eve::Value::Array>() : nullptr;
119 if (!array) return false;
120 output.clear();
121 output.reserve(array->size());
122 for (const eve::Value& value : *array) {
123 if constexpr (std::is_same_v<T, float>) {
124 if (const auto* number = value.getIf<double>()) {
125 if (!std::isfinite(*number)) return false;
126 const float converted = static_cast<float>(*number);
127 if (!std::isfinite(converted)) return false;
128 output.push_back(converted);
129 } else if (const auto* integer = value.getIf<std::int64_t>()) {
130 const float converted = static_cast<float>(*integer);
131 if (!std::isfinite(converted)) return false;
132 output.push_back(converted);
133 } else {
134 return false;
135 }
136 } else {
137 const auto* integer = value.getIf<std::int64_t>();
138 if (!integer || *integer < 0 || static_cast<std::uint64_t>(*integer) > std::numeric_limits<T>::max())
139 return false;
140 output.push_back(static_cast<T>(*integer));
141 }
142 }
143 return true;
144}
145
148 value.emplace("minX", eve::Value(bounds.minX));
149 value.emplace("minY", eve::Value(bounds.minY));
150 value.emplace("minZ", eve::Value(bounds.minZ));
151 value.emplace("maxX", eve::Value(bounds.maxX));
152 value.emplace("maxY", eve::Value(bounds.maxY));
153 value.emplace("maxZ", eve::Value(bounds.maxZ));
154 return eve::Value(std::move(value));
155}
156
157bool validGeometry(const std::vector<float>& vertices, const std::vector<std::uint32_t>& indices) {
158 if (vertices.empty() || vertices.size() % 3u != 0u || indices.empty() || indices.size() % 3u != 0u ||
159 vertices.size() / 3u > static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max()))
160 return false;
161 for (const float value : vertices)
162 if (!std::isfinite(value)) return false;
163 for (const auto index : indices)
164 if (index >= vertices.size() / 3u ||
165 index > static_cast<std::uint32_t>(std::numeric_limits<std::int32_t>::max()))
166 return false;
167 return true;
168}
169
170std::vector<std::int32_t> signedIndices(const std::vector<std::uint32_t>& indices) {
171 std::vector<std::int32_t> result;
172 result.reserve(indices.size());
173 for (const auto index : indices) result.push_back(static_cast<std::int32_t>(index));
174 return result;
175}
176
178 std::string message) {
181}
182
183} // namespace
184
187public:
189 std::unique_ptr<World3D> world, std::unique_ptr<Body3D> body, std::unique_ptr<Shape3D> shape)
190 : owner_(&owner),
191 ownerLifetime_(owner.providerLifetime_),
192 collider_(std::move(collider)),
193 world_(std::move(world)),
194 body_(std::move(body)),
195 shape_(std::move(shape)) {}
196
198
199 void commit() noexcept override {
200 if (!owner_) return;
201 if (ownerLifetime_.expired()) {
202 owner_ = nullptr;
203 collider_ = {};
204 return;
205 }
206 owner_->commit(std::move(collider_), std::move(world_), std::move(body_), std::move(shape_));
207 owner_ = nullptr;
208 }
209
210 void rollback() noexcept override {
211 if (!owner_) return;
212 if (ownerLifetime_.expired()) {
213 owner_ = nullptr;
214 collider_ = {};
215 return;
216 }
217 owner_->release(collider_, world_, body_, shape_);
218 owner_ = nullptr;
219 collider_ = {};
220 }
221
222private:
223 PhysicsArtifactProvider* owner_ = nullptr;
224 std::weak_ptr<const void> ownerLifetime_;
225 PhysicsArtifactCollider collider_;
226 std::unique_ptr<World3D> world_;
227 std::unique_ptr<Body3D> body_;
228 std::unique_ptr<Shape3D> shape_;
229};
230
232
234 providerLifetime_.reset();
235 clear();
236}
237
239 const eve::artifact::PublicationView& publication) {
240 if (failPrepare_)
241 return prepareFailure(eve::DiagnosticCode::Failed, "physics artifact prepare failure was injected");
242 if (publication.schemaVersion != 1 || publication.id.isNil() || publication.buildKey.empty())
243 return prepareFailure(
245 "physics publication requires schema version 1, identity and build key");
246 if (find(publication.id) != nullptr)
247 return prepareFailure(eve::DiagnosticCode::Conflict, "physics artifact identity is already published");
248
249 const eve::artifact::PartView* colliderPart = nullptr;
250 for (const auto& part : publication.parts) {
251 if (part.kind == eve::artifact::PartKind::Collider && (part.role == "collider" || colliderPart == nullptr))
252 colliderPart = &part;
253 }
254 if (!colliderPart)
255 return prepareFailure(eve::DiagnosticCode::PreconditionViolation,
256 "physics publication requires a collider part");
257 if (colliderPart->schemaVersion != 1)
258 return prepareFailure(eve::DiagnosticCode::UnknownVersion, "physics collider schema version is unsupported");
259
261 collider.id = publication.id;
262 collider.buildKey = std::string(publication.buildKey);
263 collider.shape = "triangle_mesh";
264 collider.backend = "box3d";
265 collider.bounds = colliderPart->bounds;
266 try {
267 collider.vertices.assign(colliderPart->positions.begin(), colliderPart->positions.end());
268 collider.indices.assign(colliderPart->indices.begin(), colliderPart->indices.end());
269 if (!validBounds(collider.bounds) || !validGeometry(collider.vertices, collider.indices))
270 return prepareFailure(eve::DiagnosticCode::PreconditionViolation,
271 "physics publication requires finite valid triangle geometry");
272 if (nextIndex_ == PhysicsArtifactHandle::invalidIndex)
273 return prepareFailure(eve::DiagnosticCode::Failed, "physics artifact handle index exhausted");
274
275 // Allocate the artifact handle during prepare. A rolled-back stage
276 // burns this slot intentionally; reusing it would make a stale handle
277 // resolve to a different collider.
278 collider.handle = PhysicsArtifactHandle(nextIndex_, 1u);
279 ++nextIndex_;
280 state_->records.reserve(state_->records.size() + state_->pendingStages + 1u);
281
282 std::unique_ptr<World3D> world;
283 World3D* targetWorld = nullptr;
284 if (boundWorldHandle_.isValid()) {
285 if (!boundWorld_ || boundWorldLifetime_.expired() || !boundWorld_->isValid() ||
286 boundWorld_->runtimeHandle() != boundWorldHandle_)
287 return prepareFailure(eve::DiagnosticCode::Conflict,
288 "physics artifact target world binding is stale");
289 targetWorld = boundWorld_;
290 collider.world = boundWorldHandle_;
291 } else {
292 world = std::make_unique<World3D>(0.f, 0.f, 0.f, true);
293 targetWorld = world.get();
294 collider.world = world->runtimeHandle();
295 }
296 auto body = std::unique_ptr<Body3D>(targetWorld->newBody("static", 0.f, 0.f, 0.f));
297 if (!body)
298 return prepareFailure(eve::DiagnosticCode::Failed, "Box3D did not create the generated collider body");
299 auto mesh = signedIndices(collider.indices);
300 auto shape = std::unique_ptr<Shape3D>(body->newTriangleMeshShape(collider.vertices, mesh));
301 if (!body || !shape)
302 return prepareFailure(eve::DiagnosticCode::Failed, "Box3D did not create the generated collider");
303 // Shared-world resources are fully allocated during prepare, but stay
304 // disabled until the transaction's noexcept visibility boundary.
305 if (!world) body->setActive(false);
306 auto stage = std::make_unique<PhysicsArtifactStage>(*this, std::move(collider), std::move(world),
307 std::move(body), std::move(shape));
308 ++state_->pendingStages;
310 } catch (const std::exception& error) {
311 return prepareFailure(eve::DiagnosticCode::Failed,
312 std::string("Box3D collider construction failed: ") + error.what());
313 } catch (...) {
314 return prepareFailure(eve::DiagnosticCode::Failed, "Box3D collider construction failed");
315 }
316}
317
318void PhysicsArtifactProvider::commit(PhysicsArtifactCollider collider, std::unique_ptr<World3D> world,
319 std::unique_ptr<Body3D> body, std::unique_ptr<Shape3D> shape) noexcept {
320 State::RuntimeCollider runtime;
321 if (state_->pendingStages > 0) --state_->pendingStages;
322 if (collider.world == boundWorldHandle_) {
323 // prepare performed every potentially failing allocation. Enabling an
324 // already-created body is the only shared-world visibility mutation.
325 if (!boundWorld_ || boundWorldLifetime_.expired() || !boundWorld_->isValid() ||
326 boundWorld_->runtimeHandle() != boundWorldHandle_ || !body || !shape)
327 std::terminate();
328 body->setActive(true);
329 runtime.world = boundWorld_;
330 runtime.worldLifetime = boundWorldLifetime_;
331 } else {
332 runtime.world = world.get();
333 runtime.worldLifetime = world->lifetimeToken();
334 runtime.ownedWorld = std::move(world);
335 }
336 runtime.descriptor = std::move(collider);
337 runtime.body = std::move(body);
338 runtime.shape = std::move(shape);
339 state_->records.push_back(std::move(runtime));
340}
341
342void PhysicsArtifactProvider::release(PhysicsArtifactCollider& collider, std::unique_ptr<World3D>& world,
343 std::unique_ptr<Body3D>& body, std::unique_ptr<Shape3D>& shape) noexcept {
344 // Explicitly tear down the backend before wrapper ownership is released.
345 // This also makes rollback behavior independent of Box3D's wrapper order.
346 if (state_->pendingStages > 0) --state_->pendingStages;
347 if (shape) shape->destroy();
348 shape.reset();
349 if (body) body->destroy();
350 body.reset();
351 if (world) world->destroy();
352 world.reset();
353 collider = {};
354}
355
357 const auto found = std::find_if(state_->records.begin(), state_->records.end(),
358 [id](const auto& record) { return record.descriptor.id == id; });
359 return found == state_->records.end() ? nullptr : &found->descriptor;
360}
361
363 if (handle.isInvalid()) return nullptr;
364 const auto found = std::find_if(state_->records.begin(), state_->records.end(),
365 [handle](const auto& record) { return record.descriptor.handle == handle; });
366 return found == state_->records.end() ? nullptr : &found->descriptor;
367}
368
370 if (handle.isInvalid()) return false;
371 const auto found = std::find_if(state_->records.begin(), state_->records.end(),
372 [handle](const auto& record) { return record.descriptor.handle == handle; });
373 if (found == state_->records.end()) return false;
374 const auto& runtime = *found;
375 return !runtime.worldLifetime.expired() && runtime.world && runtime.body && runtime.shape &&
376 runtime.world->isValid() && runtime.body->isValid() && runtime.shape->isValid();
377}
378
380 float dy, float dz) const noexcept {
382 const auto found = std::find_if(state_->records.begin(), state_->records.end(),
383 [id](const auto& record) { return record.descriptor.id == id; });
384 if (found == state_->records.end()) return result;
385 const auto& runtime = *found;
386 if (runtime.worldLifetime.expired() || !runtime.world || !runtime.body || !runtime.shape ||
387 !runtime.world->isValid() || !runtime.body->isValid() ||
388 !runtime.shape->isValid())
389 return result;
390 if (!std::isfinite(ox) || !std::isfinite(oy) || !std::isfinite(oz) || !std::isfinite(dx) || !std::isfinite(dy) ||
391 !std::isfinite(dz))
392 return result;
393 const float ex = ox + dx;
394 const float ey = oy + dy;
395 const float ez = oz + dz;
396 if (!std::isfinite(ex) || !std::isfinite(ey) || !std::isfinite(ez)) return result;
397 try {
398 const int bodyId = runtime.world->rayCast(ox, oy, oz, ex, ey, ez);
399 if (bodyId < 0 || runtime.world->getRayResultCount() <= 0) return result;
400 result.hit = true;
401 result.fraction = runtime.world->getRayResultFraction(0);
402 result.x = runtime.world->getRayResultX(0);
403 result.y = runtime.world->getRayResultY(0);
404 result.z = runtime.world->getRayResultZ(0);
405 result.normalX = runtime.world->getRayResultNormalX(0);
406 result.normalY = runtime.world->getRayResultNormalY(0);
407 result.normalZ = runtime.world->getRayResultNormalZ(0);
408 } catch (...) {
409 // The public query is a noexcept probe. Invalid input or a backend
410 // query failure is represented as a miss; the collider remains intact.
411 return PhysicsArtifactRayHit{};
412 }
413 return result;
414}
415
416std::size_t PhysicsArtifactProvider::size() const noexcept { return state_->records.size(); }
417
419 const auto* collider = find(id);
420 return collider ? collider->backend : std::string{};
421}
422
424 const auto found = std::find_if(state_->records.begin(), state_->records.end(),
425 [id](const auto& record) { return record.descriptor.id == id; });
426 if (found == state_->records.end()) return false;
427 const auto& runtime = *found;
428 return !runtime.worldLifetime.expired() && runtime.world && runtime.body && runtime.shape &&
429 runtime.world->isValid() && runtime.body->isValid() &&
430 runtime.shape->isValid() && runtime.shape->getKind() == "triangleMesh";
431}
432
434 if (!world.isValid() || world.runtimeHandle().isInvalid())
437 "physics artifact target world must be live", "physics.artifact.world"));
438 if (!state_->records.empty() || state_->pendingStages != 0)
441 "clear committed and prepared colliders before rebinding the target world",
442 "physics.artifact.world"));
443 if (boundWorldHandle_ == world.runtimeHandle() && !boundWorldLifetime_.expired())
445 if (boundWorldHandle_.isValid() && !boundWorldLifetime_.expired())
448 "physics artifact provider already has another live target world",
449 "physics.artifact.world"));
450 boundWorld_ = &world;
451 boundWorldHandle_ = world.runtimeHandle();
452 boundWorldLifetime_ = world.lifetimeToken();
454}
455
457 if (!state_->records.empty() || state_->pendingStages != 0)
460 "clear committed and prepared colliders before unbinding the target world",
461 "physics.artifact.world"));
462 if (world.isInvalid() || world != boundWorldHandle_)
465 "physics artifact target world binding is stale or does not match",
466 "physics.artifact.world"));
467 boundWorld_ = nullptr;
468 boundWorldHandle_ = PhysicsWorldHandle::invalid();
469 boundWorldLifetime_.reset();
471}
472
474 if (!boundWorld_ || boundWorldLifetime_.expired() || !boundWorld_->isValid() ||
475 boundWorld_->runtimeHandle() != boundWorldHandle_)
477 return boundWorldHandle_;
478}
479
481 const auto* collider = find(id);
482 const auto liveWorld = boundWorld();
483 return collider && liveWorld.isValid() && collider->world == liveWorld && isBox3DBacked(id);
484}
485
486bool PhysicsArtifactProvider::emptyState() const noexcept { return state_->records.empty(); }
487
489 state_->records.clear();
490 // Do not reset nextIndex_: artifact handles are process-local and must not
491 // become valid again after destruction. Exhausted slots are retired.
492 failPrepare_ = false;
493}
494
496 try {
497 eve::Value::Array colliders;
498 colliders.reserve(state_->records.size());
499 for (const auto& runtime : state_->records) {
500 const auto& collider = runtime.descriptor;
502 item.emplace("artifactId", eve::Value(collider.id.format()));
503 item.emplace("buildKey", eve::Value(collider.buildKey));
504 // Kept for diagnostics and compatibility. restoreState deliberately
505 // regenerates this process-local handle rather than reviving it.
506 item.emplace("handle", eve::Value(std::to_string(collider.handle.packed())));
507 item.emplace("shape", eve::Value(collider.shape));
508 item.emplace("backend", eve::Value(collider.backend));
509 item.emplace("bounds", boundsValue(collider.bounds));
510 item.emplace("vertices", arrayValue(collider.vertices));
511 item.emplace("indices", arrayValue(collider.indices));
512 colliders.emplace_back(std::move(item));
513 }
515 state.emplace("provider", eve::Value("physics.box3d-collider"));
516 state.emplace("version", eve::Value(std::int64_t(1)));
517 state.emplace("colliders", eve::Value(std::move(colliders)));
519 } catch (...) {
521 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "physics artifact snapshot allocation failed"));
522 }
523}
524
526 if (state_->pendingStages != 0)
527 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "cannot restore physics artifacts while a publication is staged"));
528 const auto* object = state.getIf<eve::Value::Object>();
529 const auto* provider = object ? member(*object, "provider") : nullptr;
530 const auto* providerName = provider ? provider->getIf<std::string>() : nullptr;
531 const auto* encodedColliders = object ? member(*object, "colliders") : nullptr;
532 const auto* colliders = encodedColliders ? encodedColliders->getIf<eve::Value::Array>() : nullptr;
533 if (!object || !providerName || *providerName != "physics.box3d-collider" || !hasSupportedVersion(*object) ||
534 !colliders)
535 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "invalid physics artifact provider state"));
536 if (boundWorld().isValid() && !colliders->empty())
537 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Unsupported, "restore into a shared physics world requires explicit republishing"));
538
539 std::vector<State::RuntimeCollider> candidate;
540 std::unordered_set<eve::PersistentId> identities;
541 std::uint32_t next = nextIndex_;
542 try {
543 candidate.reserve(colliders->size());
544 for (const eve::Value& encoded : *colliders) {
545 const auto* item = encoded.getIf<eve::Value::Object>();
546 std::string idText;
547 std::string buildKey;
548 std::string shape;
549 std::string backend;
550 std::uint64_t packed = 0;
552 if (!item || !readString(*item, "artifactId", idText) || !readString(*item, "buildKey", buildKey) ||
553 !readString(*item, "shape", shape) || !readString(*item, "backend", backend) ||
554 !readU64(*item, "handle", packed) || !readBounds(*item, collider.bounds) ||
555 !readArray(member(*item, "vertices"), collider.vertices) ||
556 !readArray(member(*item, "indices"), collider.indices))
557 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "invalid physics artifact collider state"));
558 const auto parsedId = eve::PersistentId::parse(idText);
559 const auto persistedHandle = PhysicsArtifactHandle::fromPacked(packed);
560 if (!parsedId || parsedId->isNil() || persistedHandle.isInvalid() || backend != "box3d" ||
561 shape != "triangle_mesh" || !identities.emplace(*parsedId).second ||
562 !validGeometry(collider.vertices, collider.indices))
563 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Unsupported, "physics artifact state has unsupported backend or geometry"));
564
566 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Failed, "physics artifact handle index exhausted during restore"));
567 collider.id = *parsedId;
568 collider.buildKey = std::move(buildKey);
569 collider.shape = std::move(shape);
570 collider.backend = std::move(backend);
571 // Runtime handles are deliberately regenerated. This preserves
572 // stale detection across clear/restore and keeps process-local
573 // coordinates out of the persistent contract.
574 collider.handle = PhysicsArtifactHandle(next++, 1u);
575
576 auto world = std::make_unique<World3D>(0.f, 0.f, 0.f, true);
577 collider.world = world->runtimeHandle();
578 auto body = std::unique_ptr<Body3D>(world->newBody("static", 0.f, 0.f, 0.f));
579 if (!body)
580 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Failed, "Box3D did not recreate the restored collider body"));
581 const auto mesh = signedIndices(collider.indices);
582 auto shapeObject = std::unique_ptr<Shape3D>(body->newTriangleMeshShape(collider.vertices, mesh));
583 if (!body || !shapeObject)
584 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Failed, "Box3D did not recreate a restored collider"));
586 runtime.descriptor = std::move(collider);
587 runtime.world = world.get();
588 runtime.worldLifetime = world->lifetimeToken();
589 runtime.ownedWorld = std::move(world);
590 runtime.body = std::move(body);
591 runtime.shape = std::move(shapeObject);
592 candidate.push_back(std::move(runtime));
593 }
594 } catch (const std::exception& error) {
595 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Failed, std::string("Box3D restore failed: ") + error.what()));
596 } catch (...) {
598 }
599
600 state_->records.swap(candidate);
601 nextIndex_ = next;
603}
604
606 static PhysicsArtifactProvider provider;
607 return provider;
608}
609
611 eve::cap::provide<eve::artifact::IPhysicsArtifactAdapter>(&physicsArtifactProvider());
612}
613
614} // namespace eve::physics
double value
std::string output
std::map< std::string, Var > values
std::string message
DiagnosticCode code
ShaderImageInput shape
float u
Definition Grass.cpp:233
std::vector< std::uint32_t > indices
std::string text
std::string name
std::string error
Definition Package.cpp:60
World3D * world
std::vector< Point > vertices
PrimitiveHandle handle
Mesh * mesh
double number
bool found
float dz
float dy
float dx
float size
Definition TreeMesh.cpp:156
float(ui::Theme::* member)[4]
std::string body
uint32_t index
double oy
double ox
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 constexpr RuntimeHandle fromPacked(std::uint64_t value) noexcept
Reconstructs a handle from an explicit packed boundary value.
constexpr bool isValid() const noexcept
Returns whether both coordinates are usable handle values.
static constexpr index_type invalidIndex
Reserved index value shared by all invalid handles.
static constexpr RuntimeHandle invalid() noexcept
Returns the canonical invalid handle.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
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
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
Definition Value.cpp:124
A prepared consumer mutation with a non-throwing visibility boundary.
Id128 public API.
Definition Identity.h:112
constexpr bool isNil() const noexcept
Returns whether this value is the all-zero nil ID.
Definition Identity.h:176
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
Real physics-module provider for generated collider publication.
PhysicsArtifactRayHit rayCast(eve::PersistentId id, float ox, float oy, float oz, float dx, float dy, float dz) const noexcept
Ray query one collider through its live Box3D world.
bool emptyState() const noexcept override
Return whether the physics registry has no committed colliders.
eve::Result< void > unbindWorld(PhysicsWorldHandle world)
Remove an empty provider's matching shared-world binding.
eve::Result< void > restoreState(const eve::Value &state) override
Replace colliders from validated backend-neutral CPU geometry.
~PhysicsArtifactProvider() override
Release all staged/committed Box3D resources.
eve::Result< std::unique_ptr< eve::artifact::PreparedPublication > > prepare(const eve::artifact::PublicationView &publication) override
Stage and copy a collider leaf from a generated publication.
const PhysicsArtifactCollider * find(eve::PersistentId id) const noexcept
Find a committed collider by persistent identity.
eve::Result< eve::Value > snapshotState() const override
Export collider identity, build keys, handles and CPU geometry.
bool isSharedWorldBacked(eve::PersistentId id) const noexcept
Return whether the collider resolves to the currently live shared world binding.
void clear() noexcept
Remove all colliders while retiring their runtime handle slots; the world binding is retained.
eve::Result< void > bindWorld(World3D &world)
Bind future collider publications to a caller-owned shared world.
std::string backendName(eve::PersistentId id) const
Return the backend family for a committed collider, or empty.
PhysicsWorldHandle boundWorld() const noexcept
Return the live shared-world binding, or invalid for compatibility-private mode/stale binding.
std::size_t size() const noexcept
Return the number of committed physics colliders.
bool isHandleLive(PhysicsArtifactHandle handle) const noexcept
Return whether a generation-qualified artifact handle is live.
PhysicsArtifactProvider()
Construct an empty provider; all methods use the owner thread.
bool isBox3DBacked(eve::PersistentId id) const noexcept
Return whether a collider currently owns a valid Box3D shape.
Staged, owning Box3D construction for one provider mutation.
void commit() noexcept override
Make the prepared mutation observable; the operation cannot fail.
PhysicsArtifactStage(PhysicsArtifactProvider &owner, PhysicsArtifactCollider collider, std::unique_ptr< World3D > world, std::unique_ptr< Body3D > body, std::unique_ptr< Shape3D > shape)
void rollback() noexcept override
Discard the prepared mutation; idempotent and non-throwing.
Box3D rigid-body world. Script coordinates are meters (Box3D native), unlike 2D World which uses pixe...
Definition World3D.h:63
PhysicsWorldHandle runtimeHandle() const noexcept
Process-local identity used by PhysicsLink; invalid after destruction.
Definition World3D.h:141
std::weak_ptr< const void > lifetimeToken() const noexcept
Return a weak lifetime token for owner-thread runtime integrations.
Definition World3D.h:150
bool isValid() const
True while the underlying Box3D world is alive.
Definition World3D.cpp:430
void destroy()
Destroys the world and invalidates all wrappers.
Definition World3D.cpp:432
Body3D * newBody(const std::string &bodyType, float x, float y, float z)
Creates a body in meter-space units.
Definition World3D.cpp:902
Result< float > readFloat(const Accessor &accessor, std::uint32_t element, std::uint32_t component)
Read one bounded finite FLOAT component.
bool hasSupportedVersion(const eve::Value::Object &object) noexcept
Optional physics backend for vehicle mobility and body attach.
Definition Climbing.h:36
PhysicsArtifactProvider & physicsArtifactProvider() noexcept
Return the process-owned physics artifact provider singleton.
void registerPhysicsArtifactProvider()
Register the physics provider in the common capability registry.
eve::RuntimeHandle< PhysicsArtifactHandleTag > PhysicsArtifactHandle
Generation-qualified handle for a physics artifact collider.
eve::Value boundsValue(const eve::artifact::Bounds &bounds)
bool validBounds(const eve::artifact::Bounds &bounds) noexcept
bool readBounds(const eve::Value::Object &object, eve::artifact::Bounds &bounds)
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
Physics-owned CPU collider provider for generated artifacts.
Backend-neutral axis-aligned bounds copied from a generated part.
Borrowed immutable view of one generated leaf.
std::span< const std::uint32_t > indices
std::span< const float > positions
Borrowed immutable view of one complete generated publication.
std::span< const PartView > parts
Queryable CPU collider resource owned by the physics module.
PhysicsWorldHandle world
Shared or compatibility-world identity that owns the live collider.
std::string backend
Backend family that owns the live shape, normally box3d.
std::vector< std::uint32_t > indices
Owning runtime state for one published collider.
Backend-neutral ray-query result used for parity tests.
glm::vec4 bounds