载入中...
搜索中...
未找到
SimulationBackend.cpp
浏览该文件的文档.
2
3#include "common/Capability.h"
4#include "common/Exception.h"
5
6#include <Box2D/Box2D.h>
7
8#include <algorithm>
9#include <cmath>
10#include <limits>
11#include <memory>
12#include <string>
13#include <unordered_set>
14#include <utility>
15#include <vector>
16
17namespace eve::physics {
18namespace {
19
20constexpr int kMinimumIterations = 1;
21constexpr int kMaximumIterations = 1024;
22
23eve::Result<void> validateStep(const eve::SimulationStep& step, const SimulationSettings& settings,
24 const SimulationObservation& observation) {
25 const double seconds = step.delta.seconds();
26 if (!std::isfinite(seconds) || seconds < 0.0) {
28 eve::DiagnosticCode::InvalidArgument, "Physics simulation step duration must be finite and non-negative",
29 "physics.simulationStep.delta"));
30 }
31 if (seconds > 0.05) {
33 eve::DiagnosticCode::InvalidArgument, "Physics simulation step duration must be in [0, 0.05] seconds",
34 "physics.simulationStep.delta"));
35 }
36 if (!(step.tick > observation.lastTick)) {
39 "Physics simulation tick must increase strictly for every backend step", "physics.simulationStep.tick"));
40 }
41 if (settings.velocityIterations < kMinimumIterations || settings.velocityIterations > kMaximumIterations ||
42 settings.positionIterations < kMinimumIterations || settings.positionIterations > kMaximumIterations ||
43 settings.subStepCount < kMinimumIterations || settings.subStepCount > kMaximumIterations) {
45 eve::DiagnosticCode::InvalidArgument, "Physics simulation iteration counts must be in [1, 1024]",
46 "physics.simulationStep.settings"));
47 }
49}
50
51eve::Result<eve::Duration> accumulatedDuration(const eve::Duration& current, const eve::Duration& delta) {
52 auto total = current.tryAdd(delta);
53 if (!total) {
54 const eve::Status status = total.status();
56 }
57 return eve::Result<eve::Duration>::success(std::move(total).takeValue());
58}
59
60class CallbackSimulationBackend final : public ISimulationBackend {
61public:
62 CallbackSimulationBackend(void* context, SimulationStepCallback callback, SimulationBackendKind kind,
63 SimulationDeterminism determinism)
64 : context_(context), callback_(callback), kind_(kind), determinism_(determinism) {}
65
66 [[nodiscard]] eve::Result<void> step(const eve::SimulationStep& step, const SimulationSettings& settings) override {
67 auto valid = detail::validateSimulationStep(step, settings, observation_);
68 if (!valid) return valid;
69
70 auto next = detail::advanceSimulationObservation(observation_, step);
71 if (!next) return eve::Result<void>::failure(next.status());
72
73 if (callback_) callback_(context_, step, settings);
74
75 observation_ = std::move(next).takeValue();
77 }
78
79 [[nodiscard]] SimulationObservation observation() const noexcept override { return observation_; }
80
81 [[nodiscard]] SimulationBackendKind kind() const noexcept override { return kind_; }
82
83 [[nodiscard]] SimulationDeterminism determinism() const noexcept override { return determinism_; }
84
85 [[nodiscard]] eve::Result<void> restoreObservation(const SimulationObservation& observation) override {
86 auto valid = detail::validateSimulationObservation(observation, "physics.simulationBackend.restoreObservation");
87 if (!valid) return valid;
88 observation_ = observation;
90 }
91
92private:
93 void* context_ = nullptr; // borrowed by the owner that created this backend
94 SimulationStepCallback callback_ = nullptr;
97 SimulationObservation observation_;
98};
99
100void stepBox2D(void* context, const eve::SimulationStep& step, const SimulationSettings& settings) noexcept {
101 auto* world = static_cast<b2World*>(context);
102 world->Step(static_cast<float>(step.delta.seconds()), settings.velocityIterations, settings.positionIterations);
103}
104
105eve::Diagnostic fallbackDiagnostic(SimulationBackendDomain domain, const char* reason) {
107 "Physics accelerator capability is unavailable; CPU backend selected",
108 "physics.simulationBackend",
110 {"selected", "cpu"},
111 {"domain", std::string(simulationBackendDomainName(domain))},
112 {"fallback", "structured-capability-fallback"},
113 {"reason", reason}});
114}
115
116eve::Result<SimulationBackendSelection> cpuFallback(std::unique_ptr<ISimulationBackend> cpuBackend,
118 const char* reason,
119 std::vector<eve::Diagnostic> diagnostics = {}) {
120 diagnostics.insert(diagnostics.begin(), fallbackDiagnostic(domain, reason));
122 {std::move(cpuBackend), SimulationBackendKind::Gpu, SimulationBackendKind::Cpu, true},
123 eve::Status(eve::StatusCode::Applied, std::move(diagnostics)));
124}
125
126std::unordered_set<IAcceleratorBackendProvider*>& registeredAcceleratorProviders() {
127 static std::unordered_set<IAcceleratorBackendProvider*> providers;
128 return providers;
129}
130
131eve::Diagnostic attemptedProviderWarning(const eve::Diagnostic& diagnostic, std::size_t attempt) {
132 auto details = diagnostic.details();
133 details.emplace_back("providerAttempt", std::to_string(attempt));
134 return eve::Diagnostic::warning(diagnostic.code(), diagnostic.message(), diagnostic.path(), std::move(details),
135 diagnostic.source());
136}
137
138} // namespace
139
141 IAcceleratorBackendProvider& provider, int priority) {
142 auto& providers = registeredAcceleratorProviders();
143 if (providers.contains(&provider)) {
145 eve::DiagnosticCode::Conflict, "Physics accelerator provider is already registered",
146 "physics.simulationBackend.provider.registration"));
147 }
148 providers.insert(&provider);
149 eve::cap::addListener<IAcceleratorBackendProvider>(&provider, priority);
151 registration.provider_ = &provider;
153}
154
156
157AcceleratorBackendProviderRegistration::AcceleratorBackendProviderRegistration(
159 : provider_(std::exchange(other.provider_, nullptr)) {}
160
163 if (this == &other) return *this;
164 reset();
165 provider_ = std::exchange(other.provider_, nullptr);
166 return *this;
167}
168
170 if (!provider_) return;
171 eve::cap::removeListener<IAcceleratorBackendProvider>(provider_);
172 registeredAcceleratorProviders().erase(provider_);
173 provider_ = nullptr;
174}
175
176namespace detail {
177
178eve::Result<void> validateSimulationStep(const eve::SimulationStep& step, const SimulationSettings& settings,
179 const SimulationObservation& observation) {
180 return validateStep(step, settings, observation);
181}
182
183eve::Result<SimulationObservation> advanceSimulationObservation(const SimulationObservation& current,
184 const eve::SimulationStep& step) {
185 if (current.stepCount == std::numeric_limits<std::uint64_t>::max()) {
187 eve::DiagnosticCode::InvariantViolation, "Physics simulation observation step count cannot overflow",
188 "physics.simulationBackend.stepCount"));
189 }
190 auto total = accumulatedDuration(current.simulatedDuration, step.delta);
191 if (!total) return eve::Result<SimulationObservation>::failure(total.status());
192
194 next.simulatedDuration = std::move(total).takeValue();
195 next.lastTick = step.tick;
196 next.simulatedSeconds = next.simulatedDuration.seconds();
197 next.lastDeltaSeconds = static_cast<float>(step.delta.seconds());
198 ++next.stepCount;
199 if (!std::isfinite(next.simulatedSeconds) || !std::isfinite(next.lastDeltaSeconds)) {
202 "Physics simulation observation exceeded representable floating-point range",
203 "physics.simulationBackend.observation"));
204 }
205 return eve::Result<SimulationObservation>::success(std::move(next));
206}
207
208eve::Result<void> validateSimulationObservation(const SimulationObservation& observation, const char* path) {
209 if (!std::isfinite(observation.simulatedSeconds) || observation.simulatedSeconds < 0.0 ||
210 !std::isfinite(observation.lastDeltaSeconds) || observation.lastDeltaSeconds < 0.f ||
211 observation.simulatedDuration.nanoseconds() < 0 ||
212 (observation.stepCount == 0 && !observation.lastTick.isZero()) ||
213 (observation.stepCount == 0 && observation.lastDeltaSeconds != 0.f)) {
215 eve::DiagnosticCode::InvalidArgument, "Simulation observation contains invalid progress values", path));
216 }
217 const double durationSeconds = observation.simulatedDuration.seconds();
218 if (!std::isfinite(durationSeconds) || std::fabs(durationSeconds - observation.simulatedSeconds) > 1e-6) {
220 eve::DiagnosticCode::Conflict, "Simulation observation seconds disagree with exact duration", path));
221 }
223}
224
225std::unique_ptr<ISimulationBackend> makeBox2DSimulationBackend(b2World* world) {
226 if (!world) throw eve::Exception("Physics: cannot create a simulation backend for a null world");
227 return makeCallbackSimulationBackend(world, &stepBox2D, SimulationBackendKind::Cpu,
229}
230
231std::unique_ptr<ISimulationBackend> makeCallbackSimulationBackend(void* context, SimulationStepCallback callback,
233 SimulationDeterminism determinism) {
234 if (!callback) throw eve::Exception("Physics: callback simulation backend requires a callback");
235 return std::make_unique<CallbackSimulationBackend>(context, callback, kind, determinism);
236}
237
238std::unique_ptr<ISimulationBackend> makeMockAcceleratorBackend() {
239 return std::make_unique<CallbackSimulationBackend>(nullptr, nullptr, SimulationBackendKind::MockAccelerator,
241}
242
244 std::unique_ptr<ISimulationBackend> cpuBackend,
245 void* state, bool preferAccelerator) {
246 if (!cpuBackend) {
248 eve::DiagnosticCode::InvalidArgument, "Physics backend selection requires a CPU fallback backend",
249 "physics.simulationBackend.cpu"));
250 }
251
252 if (!preferAccelerator) {
254 {std::move(cpuBackend), SimulationBackendKind::Cpu, SimulationBackendKind::Cpu, false},
256 }
257
258 std::vector<IAcceleratorBackendProvider*> providers;
259 const auto listenerCount = eve::cap::listenerCount<IAcceleratorBackendProvider>();
260 providers.reserve(listenerCount + 1);
261 for (std::size_t index = 0; index < listenerCount; ++index) {
262 if (auto* provider = eve::cap::listenerAt<IAcceleratorBackendProvider>(index)) providers.push_back(provider);
263 }
264 if (auto* legacy = eve::cap::query<IAcceleratorBackendProvider>();
265 legacy && std::find(providers.begin(), providers.end(), legacy) == providers.end()) {
266 providers.push_back(legacy);
267 }
268 if (providers.empty()) return cpuFallback(std::move(cpuBackend), domain, "capability-absent");
269
270 std::vector<eve::Diagnostic> failedDiagnostics;
271 bool supported = false;
272 std::size_t attempt = 0;
273 for (auto* provider : providers) {
274 if (!provider->supports(domain)) continue;
275 supported = true;
276 ++attempt;
277 try {
278 auto candidate = provider->create(domain, state);
279 if (!candidate) {
280 const auto& diagnostics = candidate.diagnostics();
281 for (const auto& diagnostic : diagnostics)
282 failedDiagnostics.push_back(attemptedProviderWarning(diagnostic, attempt));
283 continue;
284 }
285 auto successDiagnostics = candidate.diagnostics();
286 for (auto& diagnostic : successDiagnostics)
287 diagnostic.addDetail("providerAttempt", std::to_string(attempt));
288 auto backend = std::move(candidate).takeValue();
289 if (!backend) {
290 failedDiagnostics.push_back(eve::Diagnostic::error(
291 eve::DiagnosticCode::InvariantViolation, "Physics accelerator provider returned a null backend",
292 "physics.simulationBackend.provider.create"));
293 continue;
294 }
295
297 selection.backend = std::move(backend);
299 selection.actualKind = selection.backend->kind();
300 selection.usedFallback = false;
301 failedDiagnostics.insert(failedDiagnostics.end(), successDiagnostics.begin(), successDiagnostics.end());
302 return eve::Result<SimulationBackendSelection>::success(std::move(selection),
304 std::move(failedDiagnostics)));
305 } catch (const std::exception& error) {
306 failedDiagnostics.push_back(eve::Diagnostic::warning(
307 eve::DiagnosticCode::CallbackFailure, error.what(), "physics.simulationBackend.provider.create",
308 {{"providerAttempt", std::to_string(attempt)}}));
309 } catch (...) {
310 failedDiagnostics.push_back(eve::Diagnostic::warning(
312 "Physics accelerator provider threw a non-standard exception",
313 "physics.simulationBackend.provider.create", {{"providerAttempt", std::to_string(attempt)}}));
314 }
315 }
316
317 return cpuFallback(std::move(cpuBackend), domain,
318 supported ? "all-supporting-providers-failed" : "provider-does-not-support-domain",
319 std::move(failedDiagnostics));
320}
321
322} // namespace detail
323} // namespace eve::physics
int priority
wgpu::PopErrorScopeStatus status
TokenKind kind
bool valid
std::string error
Definition Package.cpp:60
World3D * world
std::string path
Definition PlayHost.cpp:110
int detail
double current
Backend-neutral, observable fixed-step contract for physics domains.
TerrainThermalSettings settings
float step
Definition TreeMesh.cpp:314
uint32_t index
const VegetationPresetContext & context
ViewPreparation callback
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
DiagnosticCode code() const noexcept
Stable machine-readable code.
Definition Diagnostic.h:143
const std::string & path() const noexcept
Optional field, property, URI, or logical path.
Definition Diagnostic.h:149
void addDetail(std::string key, std::string value)
Add structured context without changing the stable code.
Definition Diagnostic.h:156
static Diagnostic warning(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct a warning diagnostic.
Definition Diagnostic.h:134
const std::string & message() const noexcept
Human-readable explanation.
Definition Diagnostic.h:147
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
const DiagnosticDetails & details() const noexcept
Structured context in insertion order.
Definition Diagnostic.h:151
const std::string & source() const noexcept
Optional producer or subsystem that emitted the diagnostic.
Definition Diagnostic.h:153
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
double seconds() const noexcept
Return this duration as seconds for legacy/presentation APIs.
Definition Time.cpp:28
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
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
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
constexpr bool isZero() const noexcept
Returns whether this value is zero.
Move-only lifetime token for one independently registered accelerator provider.
static eve::Result< AcceleratorBackendProviderRegistration > registerProvider(IAcceleratorBackendProvider &provider, int priority=0)
Registers an externally owned provider for multi-provider selection.
AcceleratorBackendProviderRegistration & operator=(const AcceleratorBackendProviderRegistration &)=delete
void reset() noexcept
Removes this provider immediately; safe to call more than once.
~AcceleratorBackendProviderRegistration()
Removes this registration without affecting other providers.
Optional provider that creates an accelerator backend for domain state.
static constexpr const char * capabilityName
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
Optional physics backend for vehicle mobility and body attach.
Definition Climbing.h:36
constexpr std::string_view simulationBackendDomainName(SimulationBackendDomain domain) noexcept
Returns the stable diagnostic spelling for an accelerated simulation domain.
void(*)(void *context, const eve::SimulationStep &step, const SimulationSettings &settings) noexcept SimulationStepCallback
Callback used by a domain-owned CPU or test backend adapter.
SimulationBackendKind
Kind of implementation that owns a simulation step.
SimulationBackendDomain
Domain state consumed by an accelerator provider.
SimulationDeterminism
Determinism guarantee made by a simulation backend.
@ ToleranceBounded
Results are equivalent within a documented numeric tolerance.
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Result of choosing an optional accelerator or CPU alternate path.
bool usedFallback
Whether CPU was selected after an accelerator request.
SimulationBackendKind requestedKind
Provider family requested by the owner.
SimulationBackendKind actualKind
Provider family actually selected.
std::unique_ptr< ISimulationBackend > backend
Owning selected backend; it borrows the supplied domain state.
Observable backend progress shared by CPU and accelerator providers.
eve::Duration simulatedDuration
Accumulated logical simulation time, never wall-clock time.
float lastDeltaSeconds
Duration consumed by the most recently completed step.
double simulatedSeconds
Accumulated logical simulation time in seconds.
eve::SimulationTick lastTick
Tick of the most recently completed step.
std::uint64_t stepCount
Number of completed steps since backend creation.
Validated solver policy for one simulation step.