载入中...
搜索中...
未找到
ActionAttackVfxHandler.cpp
浏览该文件的文档.
3#include "common/Capability.h"
4#include "common/Module.h"
9
10#include <cstddef>
11#include <cstdint>
12#include <map>
13#include <memory>
14#include <string>
15#include <utility>
16#include <vector>
17
19namespace {
20
21eve::stylize::AttackVfxRuntime* moduleRuntime() {
22 auto* module = eve::ModuleManager::getInstance<StylizeAction>("StylizeAction");
23 return module ? &module->runtime() : nullptr;
24}
25
28 if (binding.recipeId.isValid()) {
29 if (!runtime.findRecipe(binding.recipeId))
31 eve::DiagnosticCode::NotFound, "AttackVfx recipeId is not registered", "recipeId"));
33 }
34
35 std::string json;
36 if (binding.uri.rfind("json:", 0) == 0) {
37 json = binding.uri.substr(5);
38 } else {
39 auto* fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>("Filesystem");
40 if (!fs)
42 eve::DiagnosticCode::NotFound, "Filesystem is required to load AttackVfx uri", "uri"));
43 json = fs->readText(binding.uri);
44 if (json.empty())
46 eve::DiagnosticCode::NotFound, "AttackVfx recipe uri could not be read", "uri"));
47 }
49 if (!parsed) return eve::Result<eve::LogicalId>::failure(parsed.status());
50 auto recipe = std::move(parsed).takeValue();
51 const eve::LogicalId id = recipe.id;
52 if (!runtime.findRecipe(id)) {
53 auto registered = runtime.registerRecipe(recipe);
54 if (!registered) return eve::Result<eve::LogicalId>::failure(registered.status());
55 }
57}
58
63 if (context.source) {
64 request.sourceId = context.source->id;
65 request.sourceGeneration = context.source->generation;
66 }
67 if (!context.targets.empty()) {
68 const auto index =
70 if (index < context.targets.size()) {
71 request.targetId = context.targets[index].id;
72 request.targetGeneration = context.targets[index].generation;
73 }
74 }
75 return request;
76}
77
78class ActionAttackVfxHandler final : public eve::action::IActionNotifyHandler {
79public:
82 auto* runtime = moduleRuntime();
83 if (!runtime)
85 eve::DiagnosticCode::NotFound, "StylizeAction module is unavailable", "stylizeAction"));
86
87 const ActiveKey key{context.executionId, event.itemId.format()};
89 const auto found = active_.find(key);
90 if (found == active_.end())
92 auto stopped = runtime->stop(found->second.handle, eve::stylize::AttackVfxStopMode::StopEmitting);
93 active_.erase(found);
94 if (!stopped) {
95 // Recipe may already have completed and invalidated the handle.
96 if (stopped.code() == eve::StatusCode::Rejected ||
97 stopped.code() == eve::StatusCode::NotFound)
99 return eve::Result<void>::failure(stopped.status());
100 }
102 }
103
104 const bool instant = event.kind == eve::action::ActionTimelineEventKind::Notify;
108 "AttackVfx state handler requires enter or exit boundary", "event.kind"));
109 if (!instant && active_.contains(key))
111 eve::DiagnosticCode::Conflict, "AttackVfx state is already active", "itemId"));
112
113 const auto shape =
116 if (!binding) return eve::Result<void>::failure(binding.status());
117
118 auto recipeId = ensureRecipe(*runtime, binding.value());
119 if (!recipeId) return eve::Result<void>::failure(recipeId.status());
120
121 auto request = makeRequest(binding.value(), context);
122 auto played = runtime->play(recipeId.value(), request);
123 if (!played) return eve::Result<void>::failure(played.status());
124
125 ActiveInstance owned;
126 owned.handle = std::move(played).takeValue();
127 owned.binding = std::move(binding).takeValue();
128 owned.startTime = context.time;
129 owned.executionId = context.executionId;
130 owned.nextCue = 0;
131 owned.lastAge = 0.0;
132 executionClocks_[context.executionId] = context.time.seconds();
133
134 if (instant) {
135 auto duration = eve::Duration::fromSeconds(owned.binding.lifetimeSeconds);
136 if (!duration) return eve::Result<void>::failure(duration.status());
137 auto end = context.time.tryAdd(duration.value());
138 if (!end) return eve::Result<void>::failure(end.status());
139 owned.endTime = std::move(end).takeValue();
140 transients_.push_back(std::move(owned));
141 } else {
142 active_.emplace(key, std::move(owned));
143 }
145 }
146
149 auto* runtime = moduleRuntime();
150 if (!runtime)
152 eve::DiagnosticCode::NotFound, "StylizeAction module is unavailable", "stylizeAction"));
153 const auto found = active_.find({context.executionId, block.itemId.format()});
154 if (found == active_.end())
156 eve::DiagnosticCode::NotFound, "Active AttackVfx state has no instance", "itemId"));
157 return deliverCues(*runtime, found->second, context.time);
158 }
159
161 auto* runtime = moduleRuntime();
162 if (!runtime)
164
165 bool changed = false;
166 const double now = context.time.seconds();
167
168 // Per-execution clocks: concurrent actions must not share one lastAdvance.
169 auto clockIt = executionClocks_.find(context.executionId);
170 if (clockIt == executionClocks_.end()) {
171 clockIt = executionClocks_.emplace(context.executionId, now).first;
172 }
173 double dt = now - clockIt->second;
174 if (context.scrubbing || dt < 0.0) dt = 0.0;
175 clockIt->second = now;
176 if (dt > 0.0) {
177 auto frame = runtime->advance(dt);
178 if (!frame) return eve::Result<void>::failure(frame.status());
179 changed = !frame.value().advanced.empty() || !frame.value().stopped.empty() ||
180 !frame.value().events.empty();
181 }
182
183 for (auto& active : active_) {
184 if (active.second.executionId != context.executionId) continue;
185 auto cues = deliverCues(*runtime, active.second, context.time);
186 if (!cues) return cues;
187 changed = changed || cues.code() == eve::StatusCode::Applied;
188 }
189
190 for (auto it = transients_.begin(); it != transients_.end();) {
191 if (it->executionId != context.executionId) {
192 ++it;
193 continue;
194 }
195 auto cues = deliverCues(*runtime, *it, context.time);
196 if (!cues) return cues;
197 if (context.time < it->endTime) {
198 ++it;
199 continue;
200 }
201 auto stopped = runtime->stop(it->handle, eve::stylize::AttackVfxStopMode::ClearImmediately);
202 if (!stopped) {
203 // Instant recipes often complete before lifetime ends; ignore stale stops.
204 if (stopped.code() != eve::StatusCode::Rejected &&
205 stopped.code() != eve::StatusCode::NotFound)
206 return eve::Result<void>::failure(stopped.status());
207 }
208 it = transients_.erase(it);
209 changed = true;
210 }
211
214 }
215
216private:
217 using ActiveKey = std::pair<eve::action::ActionExecutionId, std::string>;
218
219 struct ActiveInstance {
225 std::size_t nextCue = 0;
226 double lastAge = 0.0;
227 };
228
229 static eve::Result<void> deliverCues(eve::stylize::AttackVfxRuntime& runtime, ActiveInstance& active,
230 eve::Duration now) {
231 const double age =
232 static_cast<double>(now.nanoseconds() - active.startTime.nanoseconds()) / 1000000000.0;
233 bool changed = false;
234 while (active.nextCue < active.binding.cues.size()) {
235 const auto& cue = active.binding.cues[active.nextCue];
236 if (cue.offsetSeconds > age) break;
237 if (cue.offsetSeconds + 1e-9 >= active.lastAge) {
238 auto signaled = runtime.signal(active.handle, cue.cue);
239 if (!signaled) {
240 if (signaled.code() == eve::StatusCode::Rejected ||
241 signaled.code() == eve::StatusCode::NotFound) {
242 ++active.nextCue;
243 continue;
244 }
245 return eve::Result<void>::failure(signaled.status());
246 }
247 changed = true;
248 }
249 ++active.nextCue;
250 }
251 active.lastAge = age;
254 }
255
256 std::map<ActiveKey, ActiveInstance> active_;
257 std::vector<ActiveInstance> transients_;
258 std::map<eve::action::ActionExecutionId, double> executionClocks_;
259};
260
261class ActionAttackVfxProvider final : public eve::action::IActionNotifyProvider {
262public:
263 eve::Result<void> install(eve::action::ActionNotifyRegistry& registry) override {
264 auto handler = std::make_shared<ActionAttackVfxHandler>();
265 auto instant = registry.registerHandler("presentation:attack-vfx", handler);
266 if (!instant) return instant;
267 return registry.registerHandler("presentation:attack-vfx-state", std::move(handler));
268 }
269};
270
271ActionAttackVfxProvider& provider() {
272 static ActionAttackVfxProvider instance;
273 return instance;
274}
275
276bool gProviderRegistered = false;
277
278} // namespace
279
281 if (gProviderRegistered) return;
282 eve::cap::addListener<eve::action::IActionNotifyProvider>(&provider());
283 gProviderRegistered = true;
284}
285
287 if (!gProviderRegistered) return;
288 eve::cap::removeListener<eve::action::IActionNotifyProvider>(&provider());
289 gProviderRegistered = false;
290}
291
292} // namespace eve::stylize_action
Typed AttackVfx action-block payload contract.
std::size_t nextCue
double lastAge
Extensible notify descriptors, validation and runtime routing.
bool & active
float duration
Data-driven layered attack VFX recipe and element skin (Phase 1 schema).
Pooled AttackVfx orchestration with optional layer executors.
eve::action::ActionExecutionId executionId
ActiveSource owned
eve::Duration endTime
eve::Duration startTime
const GltfImportRequest & request
std::uint32_t key
ShaderImageInput shape
HSQOBJECT handler
eve::action::ActionVfxBinding binding
PrimitiveHandle handle
bool found
Action/camera adapter satellite for AttackVfx presentation blocks.
uint32_t index
const VegetationPresetContext & context
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
Signed, fixed-resolution simulation duration in nanoseconds.
Definition Time.h:43
constexpr std::int64_t nanoseconds() const noexcept
Return the exact signed nanosecond representation.
Definition Time.h:69
static Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
Definition Time.cpp:16
static constexpr Duration zero() noexcept
Return zero duration.
Definition Time.h:60
Human-readable, scoped identifier in the form namespace:name.
Definition Identity.h:411
const std::string & format() const noexcept
Returns the canonical namespace:name representation.
Definition Identity.h:436
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 Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Canonical notify descriptor owner and runtime handler router.
Result< void > registerHandler(std::string_view type, std::shared_ptr< IActionNotifyHandler > handler)
Register an owning handler for an existing descriptor.
Consumer-owned handler for one registered notify type.
Optional module provider that installs handlers into a new registry.
Deterministic pooled runtime for AttackVfxRecipe instances.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
StatusCode
Stable outcome category for an operation.
Definition Status.h:27
Owning per-step sample of one currently active timeline state.
Owning validated AttackVfx settings shared by runtime and editor preview.
static Result< ActionAttackVfxBinding > fromPayload(const Value::Object &payload, ActionAttackVfxShape shape)
Decode one dynamic AttackVfx payload transactionally.
Context linking a routed event to its action execution.
Owning event projection returned by the runtime sampler.
ActionTimelineEventKind kind
ActionSpatialBinding spatial
Generation-qualified reference to one pooled attack VFX instance.
static Result< AttackVfxRecipe > fromJson(std::string_view json)
Parse, validate, and return a complete recipe without mutating live state.
std::optional< LogicalId > skinOverride