载入中...
搜索中...
未找到
ProjectileRuntime.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
4
5#include <algorithm>
6#include <cmath>
7#include <limits>
8#include <utility>
9
10namespace eve::weapon {
11namespace {
12
13constexpr std::uint32_t kDefaultCapacity = 128;
14constexpr std::uint32_t kMaximumCapacity = 1024 * 1024;
15constexpr double kPi = 3.14159265358979323846;
16
17bool finite(const ProjectilePoint& value) {
18 return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z);
19}
20
21bool finite(const ProjectileVector& value) {
22 return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z);
23}
24
25double length(const ProjectileVector& value) {
26 return std::sqrt(value.x * value.x + value.y * value.y + value.z * value.z);
27}
28
29ProjectileVector normalized(const ProjectileVector& value) {
30 const double magnitude = length(value);
31 return {value.x / magnitude, value.y / magnitude, value.z / magnitude};
32}
33
34ProjectileVector directionTo(const ProjectilePoint& from, const ProjectilePoint& to) {
35 return {to.x - from.x, to.y - from.y, to.z - from.z};
36}
37
38ProjectileVector steer(ProjectileVector velocity, ProjectileVector desired, double maxRadians) {
39 const double speed = length(velocity);
40 if (speed <= 0.0) return velocity;
41 ProjectileVector current = normalized(velocity);
42 desired = normalized(desired);
43 const double dot = std::clamp(current.x * desired.x + current.y * desired.y + current.z * desired.z, -1.0, 1.0);
44 const double angle = std::acos(dot);
45 if (angle <= maxRadians || angle <= 1e-12) return {desired.x * speed, desired.y * speed, desired.z * speed};
46 const double alpha = maxRadians / angle;
47 ProjectileVector blended{current.x + (desired.x - current.x) * alpha, current.y + (desired.y - current.y) * alpha,
48 current.z + (desired.z - current.z) * alpha};
49 blended = normalized(blended);
50 return {blended.x * speed, blended.y * speed, blended.z * speed};
51}
52
53} // namespace
54
56 if (!id.isValid()) return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile id must not be empty", "id"));
57 if (!std::isfinite(speed) || speed <= 0.0)
58 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile speed must be finite and positive", "speed"));
59 if (!std::isfinite(gravity) || gravity < 0.0)
60 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile gravity must be finite and non-negative", "gravity"));
61 if (!std::isfinite(maxTurnRateDegrees) || maxTurnRateDegrees < 0.0)
62 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile turn rate must be finite and non-negative", "maxTurnRateDegrees"));
63 if (lifetime <= Duration::zero())
64 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile lifetime must be positive", "lifetime"));
66 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Homing projectile turn rate must be positive", "maxTurnRateDegrees"));
67 return Result<void>::success();
68}
69
70ProjectileRuntime::ProjectileRuntime() : slots_(kDefaultCapacity) {}
71
73 if (activeCount_ != 0)
74 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "Projectile pool cannot be resized while active", {}));
75 if (capacity == 0 || capacity > kMaximumCapacity)
76 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile pool capacity must be in 1..1048576", "capacity"));
77 slots_.assign(capacity, Slot{});
78 return Result<void>::success();
79}
80
81Result<void> ProjectileRuntime::validateSpawn(const ProjectileDefinition& definition,
83 auto valid = definition.validate();
84 if (!valid) return valid;
85 if (!finite(request.position) || !finite(request.direction))
86 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile transform must contain finite values", "request"));
87 if (length(request.direction) <= 1e-12)
88 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile direction must be non-zero", "direction"));
89 if (definition.mode == ProjectileMode::Homing && !request.target)
90 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Homing projectile requires a target", "target"));
91 return Result<void>::success();
92}
93
96 auto valid = validateSpawn(definition, request);
97 if (!valid) return Result<ProjectileHandle>::failure(valid.status());
98 auto free = std::find_if(slots_.begin(), slots_.end(), [](const Slot& slot) { return !slot.state.has_value(); });
99 if (free == slots_.end())
101 Diagnostic::error(DiagnosticCode::Conflict, "Projectile pool is exhausted", {}));
102 const std::size_t index = static_cast<std::size_t>(std::distance(slots_.begin(), free));
103 ++free->generation;
104 if (free->generation == 0) ++free->generation;
105 const ProjectileVector direction = normalized(request.direction);
107 state.handle = {static_cast<std::uint32_t>(index), free->generation};
108 state.definitionId = definition.id;
109 state.mode = definition.mode;
110 state.position = request.position;
111 state.velocity = {direction.x * definition.speed, direction.y * definition.speed, direction.z * definition.speed};
112 state.gravity = definition.gravity;
113 state.maxTurnRateDegrees = definition.maxTurnRateDegrees;
114 state.lifetime = definition.lifetime;
115 state.target = request.target;
116 free->state = state;
117 ++activeCount_;
119}
120
122 if (delta < Duration::zero())
124 Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile delta must be non-negative", "delta"));
126
127 std::vector<Slot> staged = slots_;
129 const double seconds = delta.seconds();
130 for (Slot& slot : staged) {
131 if (!slot.state) continue;
132 ProjectileState& state = *slot.state;
133 auto nextAge = state.age.tryAdd(delta);
134 if (!nextAge) return Result<ProjectileUpdate>::failure(nextAge.status());
135 state.age = std::move(nextAge).takeValue();
136 if (state.age >= state.lifetime) {
137 update.released.push_back(state.handle);
138 slot.state.reset();
139 continue;
140 }
141 if (state.mode == ProjectileMode::Homing) {
142 if (!targets)
144 DiagnosticCode::Unsupported, "Homing projectile target provider is unavailable", {}));
145 auto target = targets->position(*state.target);
146 if (!target) return Result<ProjectileUpdate>::failure(target.status());
147 ProjectileVector desired = directionTo(state.position, target.value());
148 if (length(desired) <= 1e-12)
150 Diagnostic::error(DiagnosticCode::Conflict, "Homing projectile overlaps its target", {}));
151 state.velocity = steer(state.velocity, desired, state.maxTurnRateDegrees * kPi / 180.0 * seconds);
152 }
153 if (state.mode == ProjectileMode::Ballistic) state.velocity.y -= state.gravity * seconds;
154 state.position.x += state.velocity.x * seconds;
155 state.position.y += state.velocity.y * seconds;
156 state.position.z += state.velocity.z * seconds;
157 update.advanced.push_back(state.handle);
158 }
159 slots_ = std::move(staged);
160 activeCount_ -= update.released.size();
161 return Result<ProjectileUpdate>::success(std::move(update));
162}
163
165 if (handle.slot >= slots_.size() || !slots_[handle.slot].state ||
166 slots_[handle.slot].generation != handle.generation)
167 return Result<void>::failure(Diagnostic::error(DiagnosticCode::StaleHandle, "Projectile handle is stale", {}));
168 slots_[handle.slot].state.reset();
169 --activeCount_;
170 return Result<void>::success();
171}
172
173std::optional<ProjectileState> ProjectileRuntime::find(ProjectileHandle handle) const {
174 if (handle.slot >= slots_.size() || !slots_[handle.slot].state ||
175 slots_[handle.slot].generation != handle.generation)
176 return std::nullopt;
177 return slots_[handle.slot].state;
178}
179
180std::vector<ProjectileState> ProjectileRuntime::states() const {
181 std::vector<ProjectileState> result;
182 result.reserve(activeCount_);
183 for (const Slot& slot : slots_)
184 if (slot.state) result.push_back(*slot.state);
185 return result;
186}
187
190 result.slots.reserve(slots_.size());
191 for (const Slot& slot : slots_) result.slots.push_back({slot.generation, slot.state});
192 return result;
193}
194
195Result<void> ProjectileRuntime::restore(const ProjectileRuntimeSnapshot& snapshot) {
196 if (snapshot.slots.empty() || snapshot.slots.size() > 1048576)
197 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile snapshot pool capacity is outside 1..1048576", {}));
198 std::vector<Slot> staged(snapshot.slots.size());
199 std::size_t active = 0;
200 for (std::size_t index = 0; index < snapshot.slots.size(); ++index) {
201 const auto& input = snapshot.slots[index];
202 staged[index].generation = input.generation;
203 if (!input.state) continue;
204 const auto& state = *input.state;
205 const auto finitePoint = [](const ProjectilePoint& point) {
206 return std::isfinite(point.x) && std::isfinite(point.y) && std::isfinite(point.z);
207 };
208 const auto finiteVector = [](const ProjectileVector& value) {
209 return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z);
210 };
211 if (state.handle.slot != index || state.handle.generation != input.generation ||
212 !state.definitionId.isValid() || !finitePoint(state.position) || !finiteVector(state.velocity) ||
213 !std::isfinite(state.gravity) || state.gravity < 0.0 ||
214 !std::isfinite(state.maxTurnRateDegrees) || state.maxTurnRateDegrees < 0.0 ||
215 state.age.nanoseconds() < 0 || state.lifetime.nanoseconds() <= 0 || state.age >= state.lifetime ||
216 (state.mode == ProjectileMode::Homing && !state.target))
217 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Projectile snapshot contains an invalid live slot", {}));
218 staged[index].state = state;
219 ++active;
220 }
221 slots_ = std::move(staged);
222 activeCount_ = active;
223 return Result<void>::success();
224}
225
226} // namespace eve::weapon
LogicalId target
double value
bool & active
std::string from
float length
Definition CaveMesh.cpp:94
Stable, structured diagnostics shared by engine modules.
EvpackChunkInput input
Definition Evpack.cpp:170
const GltfImportRequest & request
std::uint32_t capacity
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
bool valid
bool finite
PrimitiveHandle handle
Deterministic pooled projectile simulation.
RoadLaneDirection direction
double current
uint32_t index
glm::vec3 point
float angle
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
static constexpr Duration zero() noexcept
Return zero duration.
Definition Time.h:60
double seconds() const noexcept
Return this duration as seconds for legacy/presentation APIs.
Definition Time.cpp:28
constexpr bool isZero() const noexcept
Whether this duration is exactly zero.
Definition Time.h:75
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
Optional target-position boundary used only by homing projectiles.
virtual Result< ProjectilePoint > position(ecs::EntityHandle target) const =0
Resolve one generation-checked target to an owning simulation-space position.
Result< void > release(ProjectileHandle handle)
Explicitly release one live handle; stale handles are rejected.
ProjectileRuntimeSnapshot snapshot() const
Capture live trajectories plus empty-slot generations.
Result< void > configurePool(std::uint32_t capacity)
Resize the pool while empty; capacity must be in 1..1048576.
std::vector< ProjectileState > states() const
Return owning live states in stable slot order.
std::optional< ProjectileState > find(ProjectileHandle handle) const
Return an owning state copy, or empty for a stale/released handle.
ProjectileRuntime()
Projectile runtime.
Result< ProjectileUpdate > update(Duration delta, const IProjectileTargetProvider *targets=nullptr)
Atomically advance all live projectiles by supplied deterministic time.
Result< ProjectileHandle > spawn(const ProjectileDefinition &definition, const ProjectileSpawnRequest &request)
Spawn one projectile or return a structured capacity/validation failure.
std::size_t capacity() const noexcept
Current fixed pool capacity.
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
载具模块入口(eve.Vehicle):定义注册 / 实体工厂 / RTS 命令 / 帧调度。 设计文档:docs/dev/通用载具系统设计.md
Data-driven trajectory and lifetime definition.
Result< void > validate() const
Validate finite, non-negative trajectory parameters and mode-specific requirements.
Generation-qualified reference to one pooled projectile slot.
Owning point in projectile simulation space.
Complete deterministic projectile-pool state for rollback and save games.
std::vector< ProjectileSlotSnapshot > slots
Owning spawn request; homing requests require a generation-checked target.
Owning observable state for one live projectile.
Owning result of one atomic projectile simulation step.
Owning velocity or direction vector in projectile simulation space.