载入中...
搜索中...
未找到
PrefabActionCapabilities.cpp
浏览该文件的文档.
2
7#include "common/Capability.h"
8#include "common/ECS.h"
10#include "graphics/Graphics.h"
12#include "model3d/Model3D.h"
14
15#include <cmath>
16#include <cstddef>
17#include <map>
18#include <memory>
19#include <optional>
20#include <string>
21#include <utility>
22#include <vector>
23
24namespace eve::sceneloader {
25namespace {
26
27constexpr double kDegreesToRadians = 0.017453292519943295;
28using ActiveKey = std::pair<action::ActionExecutionId, std::string>;
29
30using PrefabLease = action::PrefabInstanceHandle;
31
32struct WorldTransform {
33 float x = 0.f;
34 float y = 0.f;
35 float z = 0.f;
36 float yaw = 0.f;
37 float pitch = 0.f;
38 float roll = 0.f;
39 float sx = 1.f;
40 float sy = 1.f;
41 float sz = 1.f;
42};
43
44class RenderablePrefabPool {
45public:
46 ~RenderablePrefabPool() {
47 for (auto& slot : slots_)
48 for (const auto entity : slot.entities)
49 if (auto* live = ecs::try_get(entity)) ecs::DestroyEntity(live);
50 }
51
52 [[nodiscard]] Result<PrefabLease> spawn(const std::string& uri, const WorldTransform& transform,
53 bool visible = true) {
54 for (std::uint32_t index = 0; index < slots_.size(); ++index) {
55 auto& slot = slots_[index];
56 if (slot.occupied || slot.retired || slot.uri != uri || !allEntitiesAlive(slot)) continue;
57 slot.occupied = true;
58 slot.independent = false;
59 setSlotVisible(slot, visible);
60 apply(slot, transform);
61 return Result<PrefabLease>::success(PrefabLease(index, slot.generation));
62 }
63
64 auto* graphics = ModuleManager::getInstance<graphics::Graphics>("Graphics");
65 auto* models = ModuleManager::getInstance<model3d::Model3D>("Model3D");
66 if (!graphics || !models)
68 DiagnosticCode::NotFound, "Prefab spawn requires Graphics and Model3D modules", "uri"));
69 try {
70 auto* model = models->newModelDataFromFile(uri);
71 auto renderables = model3d::buildRenderables(*graphics, model);
72 if (renderables.empty())
74 Diagnostic::error(DiagnosticCode::Failed, "Prefab model contains no renderable meshes", "uri"));
75 Slot slot;
76 slot.uri = uri;
77 slot.occupied = true;
78 slot.entities.reserve(renderables.size());
79 for (auto* renderable : renderables) slot.entities.push_back(ecs::handle_of(renderable));
80 slots_.push_back(std::move(slot));
81 const auto index = static_cast<std::uint32_t>(slots_.size() - 1u);
82 apply(slots_.back(), transform);
83 setSlotVisible(slots_.back(), visible);
84 return Result<PrefabLease>::success(PrefabLease(index, slots_.back().generation));
85 } catch (const std::exception& error) {
87 }
88 }
89
90 [[nodiscard]] Result<void> update(PrefabLease lease, const WorldTransform& transform) {
91 auto slot = resolve(lease);
92 if (!slot) return Result<void>::failure(slot.status());
93 apply(*slot.value(), transform);
95 }
96
97 [[nodiscard]] Result<void> recycle(PrefabLease lease) {
98 auto slot = resolve(lease);
99 if (!slot) return Result<void>::failure(slot.status());
100 auto* value = slot.value();
101 setSlotVisible(*value, false);
102 value->occupied = false;
103 value->independent = false;
104 const auto next = PrefabLease::nextGeneration(value->generation);
105 if (next)
106 value->generation = *next;
107 else
108 value->retired = true;
110 }
111
112 [[nodiscard]] Result<void> setVisible(PrefabLease lease, bool visible) {
113 auto slot = resolve(lease);
114 if (!slot) return Result<void>::failure(slot.status());
115 setSlotVisible(*slot.value(), visible);
117 }
118
119 [[nodiscard]] Result<void> makeIndependent(PrefabLease lease) {
120 auto slot = resolve(lease);
121 if (!slot) return Result<void>::failure(slot.status());
122 slot.value()->independent = true;
124 }
125
126 [[nodiscard]] std::vector<action::PrefabInstanceInfo> independentInstances() const {
127 std::vector<action::PrefabInstanceInfo> result;
128 for (std::uint32_t index = 0; index < slots_.size(); ++index) {
129 const auto& slot = slots_[index];
130 if (!slot.occupied || !slot.independent || !allEntitiesAlive(slot)) continue;
131 result.push_back({PrefabLease(index, slot.generation), slot.uri});
132 }
133 return result;
134 }
135
136 [[nodiscard]] Result<void> recycleIndependent(PrefabLease lease) {
137 auto slot = resolve(lease);
138 if (!slot) return Result<void>::failure(slot.status());
139 if (!slot.value()->independent)
141 DiagnosticCode::NotFound, "Prefab instance is still owned by an action block", "instance"));
142 return recycle(lease);
143 }
144
145private:
146 struct Slot {
147 std::string uri;
148 std::vector<ecs::EntityHandle> entities;
149 std::uint32_t generation = 1;
150 bool occupied = false;
151 bool independent = false;
152 bool retired = false;
153 };
154
155 [[nodiscard]] Result<Slot*> resolve(PrefabLease lease) {
156 if (!lease.isValid() || lease.index() >= slots_.size())
158 Diagnostic::error(DiagnosticCode::NotFound, "Prefab instance handle is invalid", "instance"));
159 auto& slot = slots_[lease.index()];
160 if (!slot.occupied || slot.generation != lease.generation() || !allEntitiesAlive(slot))
162 Diagnostic::error(DiagnosticCode::NotFound, "Prefab instance handle is missing or stale", "instance"));
163 return Result<Slot*>::success(&slot);
164 }
165
166 static bool allEntitiesAlive(const Slot& slot) {
167 if (slot.entities.empty()) return false;
168 for (const auto entity : slot.entities)
169 if (!ecs::try_get(entity)) return false;
170 return true;
171 }
172
173 static void setSlotVisible(Slot& slot, bool visible) {
174 for (const auto entity : slot.entities)
175 if (auto* renderable = dynamic_cast<graphics::Renderable3D*>(ecs::try_get(entity)))
176 renderable->setVisible(visible);
177 }
178
179 static void apply(Slot& slot, const WorldTransform& transform) {
180 for (const auto entity : slot.entities) {
181 auto* renderable = dynamic_cast<graphics::Renderable3D*>(ecs::try_get(entity));
182 if (!renderable) continue;
183 renderable->setPosition(transform.x, transform.y, transform.z);
184 renderable->setRotation(transform.yaw, transform.pitch, transform.roll);
185 renderable->setScale(transform.sx, transform.sy, transform.sz);
186 }
187 }
188
189 std::vector<Slot> slots_;
190};
191
192WorldTransform previewWorldTransform(const action::ActionSpatialBinding& spatial) {
193 return {
194 static_cast<float>(spatial.positionOffset.x),
195 static_cast<float>(spatial.positionOffset.y),
196 static_cast<float>(spatial.positionOffset.z),
197 static_cast<float>(spatial.rotationOffsetDegrees.y * kDegreesToRadians),
198 static_cast<float>(spatial.rotationOffsetDegrees.x * kDegreesToRadians),
199 static_cast<float>(spatial.rotationOffsetDegrees.z * kDegreesToRadians),
200 static_cast<float>(spatial.scale.x),
201 static_cast<float>(spatial.scale.y),
202 static_cast<float>(spatial.scale.z),
203 };
204}
205
206class PrefabActionPreviewSink final : public action::IActionPreviewSink {
207public:
208 ~PrefabActionPreviewSink() override {
209 discardPrepared();
210 recycleAll(current_);
211 }
212
213 Result<void> prepare(const action::ActionPreviewFrame& frame) override {
214 discardPrepared();
215 for (const auto& block : frame.activeBlocks) {
216 if (block.type.format() != "gameplay:prefab-spawn") continue;
218 if (!binding) return Result<void>::failure(binding.status());
219 if (binding.value().lifecycle == action::PrefabSpawnLifecycle::CustomDuration &&
220 block.localTime >= binding.value().customDuration)
221 continue;
222 auto lease = pool_.spawn(binding.value().uri, previewWorldTransform(binding.value().spatial), false);
223 if (!lease) {
224 discardPrepared();
225 return Result<void>::failure(lease.status());
226 }
227 staged_.push_back(lease.value());
228 }
230 }
231
232 void present(const action::ActionPreviewFrame&) noexcept override {
233 recycleAll(current_);
234 current_ = std::move(staged_);
235 staged_.clear();
236 for (const auto lease : current_) {
237 auto visible = pool_.setVisible(lease, true);
238 visible.ignore();
239 }
240 }
241
242 void discardPrepared() noexcept override { recycleAll(staged_); }
243
244private:
245 void recycleAll(std::vector<PrefabLease>& leases) noexcept {
246 for (const auto lease : leases) {
247 auto recycled = pool_.recycle(lease);
248 recycled.ignore();
249 }
250 leases.clear();
251 }
252
253 RenderablePrefabPool pool_;
254 std::vector<PrefabLease> current_;
255 std::vector<PrefabLease> staged_;
256};
257
258class PrefabActionProvider;
259
260class PrefabActionHandler final : public action::IActionNotifyHandler {
261public:
262 explicit PrefabActionHandler(PrefabActionProvider& provider) : provider_(provider) {}
263
264 Result<void> handle(const action::ActionTimelineEvent& event,
265 const action::ActionNotifyContext& context) override {
266 const ActiveKey key{context.executionId, event.itemId.format()};
267 if (event.kind == action::ActionTimelineEventKind::StateExit) return exit(key, context);
270 "Prefab spawn requires state enter or exit", "event.kind"));
271 if (active_.contains(key))
273 Diagnostic::error(DiagnosticCode::Conflict, "Prefab spawn state is already active", "itemId"));
275 if (!binding) return Result<void>::failure(binding.status());
276 auto pose = resolvePose(binding.value().spatial, context);
277 if (!pose) return Result<void>::failure(pose.status());
278 auto* instances = pool();
279 if (!instances)
281 DiagnosticCode::NotFound, "SceneLoader prefab instance service is unavailable", "sceneloader"));
282 auto lease = instances->spawn(binding.value().uri, worldTransform(binding.value().spatial, pose.value()));
283 if (!lease) return Result<void>::failure(lease.status());
284 auto deadline = context.time.tryAdd(binding.value().customDuration);
285 if (!deadline) {
286 auto recycled = instances->recycle(lease.value());
287 recycled.ignore();
288 return Result<void>::failure(deadline.status());
289 }
290 active_.emplace(key, Active{lease.value(), std::move(binding).takeValue(),
291 std::move(pose).takeValue(), deadline.value()});
293 }
294
295 Result<void> update(const action::ActionActiveBlock& block,
296 const action::ActionNotifyContext& context) override {
297 const auto found = active_.find({context.executionId, block.itemId.format()});
298 if (found == active_.end())
300 Diagnostic::error(DiagnosticCode::NotFound, "Active prefab state has no instance", "itemId"));
301 auto& active = found->second;
303 if (active.binding.lifecycle == action::PrefabSpawnLifecycle::CustomDuration &&
304 context.time >= active.deadline) {
305 auto* instances = pool();
306 if (!instances)
308 DiagnosticCode::NotFound, "SceneLoader prefab instance service is unavailable", "sceneloader"));
309 auto recycled = instances->recycle(active.lease);
310 if (!recycled) return recycled;
311 active.recycled = true;
312 return recycled;
313 }
315 auto pose = resolvePose(active.binding.spatial, context);
316 if (!pose) return Result<void>::failure(pose.status());
318 active.pose.positionX = pose.value().positionX;
319 active.pose.positionY = pose.value().positionY;
320 active.pose.positionZ = pose.value().positionZ;
321 } else {
322 active.pose = std::move(pose).takeValue();
323 }
324 }
325 auto* instances = pool();
326 if (!instances)
328 DiagnosticCode::NotFound, "SceneLoader prefab instance service is unavailable", "sceneloader"));
329 return instances->update(active.lease, worldTransform(active.binding.spatial, active.pose));
330 }
331
332 Result<void> sample(const action::ActionActiveBlock& block,
333 const action::ActionNotifyContext&) const override {
335 if (!binding) return Result<void>::failure(binding.status());
337 }
338
339 Result<void> advance(const action::ActionNotifyContext& context) override {
341 for (std::size_t index = 0; index < timed_.size();) {
342 if (timed_[index].executionId != context.executionId || context.time < timed_[index].deadline) {
343 ++index;
344 continue;
345 }
346 auto* instances = pool();
347 if (!instances)
349 DiagnosticCode::NotFound, "SceneLoader prefab instance service is unavailable", "sceneloader"));
350 auto recycled = instances->recycle(timed_[index].lease);
351 if (!recycled) return recycled;
352 timed_.erase(timed_.begin() + static_cast<std::ptrdiff_t>(index));
353 outcome = StatusCode::Applied;
354 }
355 return Result<void>::success(Status::success(outcome));
356 }
357
358private:
359 struct Active {
360 PrefabLease lease;
361 action::ActionPrefabSpawnBinding binding;
362 EntitySpatialPose pose;
363 Duration deadline;
364 bool recycled = false;
365 };
366 struct Timed {
368 PrefabLease lease;
369 Duration deadline;
370 };
371
372 Result<void> exit(const ActiveKey& key, const action::ActionNotifyContext& context) {
373 const auto found = active_.find(key);
374 if (found == active_.end()) return Result<void>::success(Status::success(StatusCode::NoOp));
375 if (found->second.recycled) {
376 active_.erase(found);
378 }
379 if (found->second.binding.lifecycle == action::PrefabSpawnLifecycle::CustomDuration) {
380 timed_.push_back({context.executionId, found->second.lease, found->second.deadline});
381 active_.erase(found);
383 }
384 auto* instances = pool();
385 if (!instances)
387 DiagnosticCode::NotFound, "SceneLoader prefab instance service is unavailable", "sceneloader"));
388 auto result = found->second.binding.lifecycle == action::PrefabSpawnLifecycle::RecycleOnBlockExit
389 ? instances->recycle(found->second.lease)
390 : instances->makeIndependent(found->second.lease);
391 if (!result) return result;
392 active_.erase(found);
393 return result;
394 }
395
396 static Result<EntitySpatialPose> resolvePose(const action::ActionSpatialBinding& spatial,
397 const action::ActionNotifyContext& context) {
398 std::optional<ecs::EntityHandle> handle;
399 OptionalRef<const IAttachmentPointSource> attachments;
401 handle = context.source;
402 attachments = context.sourceAttachment;
403 } else {
404 if (spatial.targetIndex >= context.targets.size())
406 Diagnostic::error(DiagnosticCode::NotFound, "Prefab target index is unavailable", "targetIndex"));
408 if (spatial.targetIndex < context.targetAttachments.size())
409 attachments = context.targetAttachments[spatial.targetIndex];
410 }
411 EntitySpatialPose pose;
412 if (handle) {
414 if (!root) return root;
415 pose = std::move(root).takeValue();
416 }
417 if (spatial.bone.empty()) return Result<EntitySpatialPose>::success(std::move(pose));
418 if (!attachments)
420 Diagnostic::error(DiagnosticCode::Unsupported, "Prefab bone requires an attachment source", "bone"));
421 auto point = attachments->get().sampleAttachmentPoint(
422 spatial.bone, {static_cast<float>(spatial.positionOffset.x),
423 static_cast<float>(spatial.positionOffset.y),
424 static_cast<float>(spatial.positionOffset.z)});
425 if (!point) return Result<EntitySpatialPose>::failure(point.status());
426 pose.positionX = point.value().x;
427 pose.positionY = point.value().y;
428 pose.positionZ = point.value().z;
429 return Result<EntitySpatialPose>::success(std::move(pose));
430 }
431
432 static WorldTransform worldTransform(const action::ActionSpatialBinding& spatial,
433 const EntitySpatialPose& pose) {
434 const bool bone = !spatial.bone.empty();
435 return {
436 static_cast<float>(pose.positionX + (bone ? 0.0 : spatial.positionOffset.x)),
437 static_cast<float>(pose.positionY + (bone ? 0.0 : spatial.positionOffset.y)),
438 static_cast<float>(pose.positionZ + (bone ? 0.0 : spatial.positionOffset.z)),
439 static_cast<float>((pose.rotationYDegrees + spatial.rotationOffsetDegrees.y) * kDegreesToRadians),
440 static_cast<float>((pose.rotationXDegrees + spatial.rotationOffsetDegrees.x) * kDegreesToRadians),
441 static_cast<float>((pose.rotationZDegrees + spatial.rotationOffsetDegrees.z) * kDegreesToRadians),
442 static_cast<float>(pose.scaleX * spatial.scale.x),
443 static_cast<float>(pose.scaleY * spatial.scale.y),
444 static_cast<float>(pose.scaleZ * spatial.scale.z),
445 };
446 }
447
448 [[nodiscard]] RenderablePrefabPool* pool() const;
449
450 PrefabActionProvider& provider_;
451 std::map<ActiveKey, Active> active_;
452 std::vector<Timed> timed_;
453};
454
455class PrefabActionProvider final : public action::IActionNotifyProvider,
456 public action::IActionPrefabInstances,
457 public action::IActionPreviewSinkProvider {
458public:
459 void start() {
460 if (!pool_) pool_ = std::make_unique<RenderablePrefabPool>();
461 }
462
463 void shutdown() { pool_.reset(); }
464
465 Result<void> install(action::ActionNotifyRegistry& registry) override {
466 if (!pool_)
468 DiagnosticCode::NotFound, "SceneLoader prefab instance service is unavailable", "sceneloader"));
469 return registry.registerHandler("gameplay:prefab-spawn", std::make_shared<PrefabActionHandler>(*this));
470 }
471
472 std::vector<action::PrefabInstanceInfo> independentInstances() const override {
473 return pool_ ? pool_->independentInstances() : std::vector<action::PrefabInstanceInfo>{};
474 }
475
476 Result<void> recycleIndependent(action::PrefabInstanceHandle handle) override {
477 if (!pool_)
479 DiagnosticCode::NotFound, "SceneLoader prefab instance service is unavailable", "sceneloader"));
480 return pool_->recycleIndependent(handle);
481 }
482
483 Result<std::unique_ptr<action::IActionPreviewSink>> createActionPreviewSink() override {
484 if (!pool_)
485 return Result<std::unique_ptr<action::IActionPreviewSink>>::failure(Diagnostic::error(
486 DiagnosticCode::NotFound, "SceneLoader prefab preview service is unavailable", "sceneloader"));
487 return Result<std::unique_ptr<action::IActionPreviewSink>>::success(
488 std::make_unique<PrefabActionPreviewSink>());
489 }
490
491 [[nodiscard]] RenderablePrefabPool* pool() const noexcept { return pool_.get(); }
492
493private:
494 std::unique_ptr<RenderablePrefabPool> pool_;
495};
496
497PrefabActionProvider& provider() {
498 static PrefabActionProvider value;
499 return value;
500}
501
502RenderablePrefabPool* PrefabActionHandler::pool() const { return provider_.pool(); }
503
504} // namespace
505
507 auto& value = provider();
508 value.start();
509 cap::provide<action::IActionPrefabInstances>(&value);
510 cap::addListener<action::IActionPreviewSinkProvider>(&value);
511 cap::addListener<action::IActionNotifyProvider>(&value);
512}
513
515 auto& value = provider();
516 cap::removeListener<action::IActionNotifyProvider>(&value);
517 cap::removeListener<action::IActionPreviewSinkProvider>(&value);
518 cap::revoke<action::IActionPrefabInstances>(&value);
519 value.shutdown();
520}
521
522} // namespace eve::sceneloader
double value
Extensible notify descriptors, validation and runtime routing.
Typed payload contract for prefab-spawn action states.
External ownership boundary for independent prefab spawns.
Presentation-neutral action preview frame contracts.
Duration start
bool & active
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
int root
Definition AnimSmr.cpp:119
eve::action::ActionExecutionId executionId
eve::EntitySpatialPose pose
eve::action::ActionSpatialBinding spatial
std::vector< BuildingInstanceSnapshot > instances
Optional ECS-entity to world-pose capability.
bool retired
std::uint32_t key
std::uint32_t bone
std::string error
Definition Package.cpp:60
eve::action::ActionVfxBinding binding
std::string uri
std::vector< ecs::EntityHandle > entities
Duration deadline
std::uint32_t generation
PrefabLease lease
PrimitiveHandle handle
glm::mat4 model
bool found
bool occupied
bool visible
uint32_t index
const VegetationPresetContext & context
glm::vec3 point
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 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
RuntimeHandle< PrefabInstanceHandleTag > PrefabInstanceHandle
Process-local, generation-qualified identity of one spawned prefab instance.
eve::detail::StrongUint64< ActionExecutionIdTag > ActionExecutionId
Definition Action.h:157
@ CustomDuration
The action block recycles the instance after customDuration.
@ RecycleOnBlockExit
The action block recycles the instance on Exit or interruption.
@ WorldTransformAtStart
Capture the complete world transform once on Enter.
@ FollowPositionOnly
Re-evaluate position only and retain the spawn rotation.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
std::vector< Renderable3D * > buildRenderables(IResourceFactory &gfx, ModelData *model, const ModelRenderOptions &options)
double sample(const Heightmap &map, double u, double v)
Sample.
void shutdownPrefabActionCapabilities()
Release all provider-owned prefab instances during SceneLoader shutdown.
void registerPrefabActionCapabilities()
Register the SceneLoader-backed prefab action provider.
Result< EntitySpatialPose > resolveEntitySpatialPose(ecs::EntityHandle entity, std::string_view bone)
Resolve through registered providers in deterministic registration order.
StatusCode
Stable outcome category for an operation.
Definition Status.h:27
static Result< ActionPrefabSpawnBinding > fromPayload(const Value::Object &payload)
Decode a prefab-spawn payload without mutating runtime state.
ActionSpatialVector3 rotationOffsetDegrees
ActionSpatialBinding spatial