载入中...
搜索中...
未找到
SmartObject.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <set>
6
7namespace eve::npc_ai {
8namespace {
9bool finitePosition(const std::array<double, 3>& position) {
10 return std::all_of(position.begin(), position.end(), [](double value) { return std::isfinite(value); });
11}
12} // namespace
13
15 if (definition.logicalId.empty() || definition.slots.empty())
17 "smart object logical id and slots are required",
18 {}, {}, "npc_ai.smart_object"));
19 if (objectsByLogicalId_.contains(definition.logicalId))
21 DiagnosticCode::AlreadyExists, "smart object logical id already exists", {}, {}, "npc_ai.smart_object"));
22 std::set<std::string> slotIds;
23 for (auto& slot : definition.slots) {
24 if (slot.id.empty() || slot.activity.empty() || !finitePosition(slot.position) ||
25 !slotIds.insert(slot.id).second)
28 "smart object slots require unique ids, activities and finite positions", {}, {},
29 "npc_ai.smart_object"));
30 std::sort(slot.tags.begin(), slot.tags.end());
31 if (std::adjacent_find(slot.tags.begin(), slot.tags.end()) != slot.tags.end())
33 "smart object slot tags must be unique", {}, {},
34 "npc_ai.smart_object"));
35 }
36 std::uint32_t index;
37 if (freeObjects_.empty()) {
38 index = static_cast<std::uint32_t>(objects_.size());
39 objects_.push_back({});
40 } else {
41 index = freeObjects_.back();
42 freeObjects_.pop_back();
43 }
45 const std::string logicalId = definition.logicalId;
46 objects_[index].value = Object{std::move(definition), {}};
47 objectsByLogicalId_.emplace(logicalId, handle);
49}
50
52 if (!handle.isValid() || handle.index() >= objects_.size())
54 DiagnosticCode::StaleHandle, "smart object handle is stale", {}, {}, "npc_ai.smart_object"));
55 auto& slot = objects_[handle.index()];
56 if (!slot.value || slot.generation != handle.generation())
57 return Result<std::reference_wrapper<Object>>::failure(Diagnostic::error(
58 DiagnosticCode::StaleHandle, "smart object handle is stale", {}, {}, "npc_ai.smart_object"));
59 return Result<std::reference_wrapper<Object>>::success(std::ref(*slot.value));
60}
61
62Result<std::reference_wrapper<const SmartObjectWorld::Object>> SmartObjectWorld::resolveObject(
64 if (!handle.isValid() || handle.index() >= objects_.size())
65 return Result<std::reference_wrapper<const Object>>::failure(Diagnostic::error(
66 DiagnosticCode::StaleHandle, "smart object handle is stale", {}, {}, "npc_ai.smart_object"));
67 const auto& slot = objects_[handle.index()];
68 if (!slot.value || slot.generation != handle.generation())
69 return Result<std::reference_wrapper<const Object>>::failure(Diagnostic::error(
70 DiagnosticCode::StaleHandle, "smart object handle is stale", {}, {}, "npc_ai.smart_object"));
71 return Result<std::reference_wrapper<const Object>>::success(std::cref(*slot.value));
72}
73
74Result<std::reference_wrapper<SmartObjectWorld::Claim>> SmartObjectWorld::resolveClaim(SmartObjectClaimHandle handle) {
75 if (!handle.isValid() || handle.index() >= claims_.size())
76 return Result<std::reference_wrapper<Claim>>::failure(Diagnostic::error(
77 DiagnosticCode::StaleHandle, "smart object claim is stale", {}, {}, "npc_ai.smart_object"));
78 auto& slot = claims_[handle.index()];
79 if (!slot.value || slot.generation != handle.generation())
80 return Result<std::reference_wrapper<Claim>>::failure(Diagnostic::error(
81 DiagnosticCode::StaleHandle, "smart object claim is stale", {}, {}, "npc_ai.smart_object"));
82 return Result<std::reference_wrapper<Claim>>::success(std::ref(*slot.value));
83}
84
85Result<std::reference_wrapper<const SmartObjectWorld::Claim>> SmartObjectWorld::resolveClaim(
87 if (!handle.isValid() || handle.index() >= claims_.size())
88 return Result<std::reference_wrapper<const Claim>>::failure(Diagnostic::error(
89 DiagnosticCode::StaleHandle, "smart object claim is stale", {}, {}, "npc_ai.smart_object"));
90 const auto& slot = claims_[handle.index()];
91 if (!slot.value || slot.generation != handle.generation())
92 return Result<std::reference_wrapper<const Claim>>::failure(Diagnostic::error(
93 DiagnosticCode::StaleHandle, "smart object claim is stale", {}, {}, "npc_ai.smart_object"));
94 return Result<std::reference_wrapper<const Claim>>::success(std::cref(*slot.value));
95}
96
98 auto resolved = resolveObject(object);
99 if (!resolved.ok()) return Result<void>::failure(resolved.status());
100 auto& value = resolved.value().get();
101 if (!value.claimsBySlot.empty() && policy == SmartObjectRemoval::RejectClaimed)
102 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "smart object still has live claims",
103 {}, {}, "npc_ai.smart_object"));
104 std::vector<SmartObjectClaimHandle> claims;
105 for (const auto& [slot, claim] : value.claimsBySlot) {
106 (void)slot;
107 claims.push_back(claim);
108 }
109 for (const auto claim : claims) releaseClaimUnchecked(claim);
110 objectsByLogicalId_.erase(value.definition.logicalId);
111 auto& slot = objects_[object.index()];
112 slot.value.reset();
113 const auto next = SmartObjectHandle::nextGeneration(slot.generation);
114 if (next) {
115 slot.generation = *next;
116 freeObjects_.push_back(object.index());
117 }
119}
120
122 if (queryValue.activity.empty() || !finitePosition(queryValue.origin) || !std::isfinite(queryValue.maxDistance) ||
123 queryValue.maxDistance < 0.0)
125 DiagnosticCode::InvalidArgument, "smart object query is invalid", {}, {}, "npc_ai.smart_object"));
126 std::vector<SmartObjectCandidate> result;
127 for (std::uint32_t index = 0; index < objects_.size(); ++index) {
128 const auto& objectSlot = objects_[index];
129 if (!objectSlot.value) continue;
130 for (const auto& slot : objectSlot.value->definition.slots) {
131 if (slot.activity != queryValue.activity || objectSlot.value->claimsBySlot.contains(slot.id)) continue;
132 const bool tagsMatch = std::all_of(
133 queryValue.requiredTags.begin(), queryValue.requiredTags.end(),
134 [&](const auto& tag) { return std::binary_search(slot.tags.begin(), slot.tags.end(), tag); });
135 if (!tagsMatch) continue;
136 const double dx = slot.position[0] - queryValue.origin[0];
137 const double dy = slot.position[1] - queryValue.origin[1];
138 const double dz = slot.position[2] - queryValue.origin[2];
139 const double distance = std::sqrt(dx * dx + dy * dy + dz * dz);
140 if (distance <= queryValue.maxDistance)
141 result.push_back({SmartObjectHandle(index, objectSlot.generation),
142 objectSlot.value->definition.logicalId, slot.id, slot.position, distance});
143 }
144 }
145 std::sort(result.begin(), result.end(), [](const auto& a, const auto& b) {
146 return std::tuple{a.distance, a.logicalId, a.slotId} < std::tuple{b.distance, b.logicalId, b.slotId};
147 });
148 return Result<std::vector<SmartObjectCandidate>>::success(std::move(result));
149}
150
152 std::string_view slotId, std::uint64_t currentTick,
153 std::uint64_t leaseTicks) {
154 if (!agent.isValid() || slotId.empty() || leaseTicks == 0 || currentTick > UINT64_MAX - leaseTicks)
156 DiagnosticCode::InvalidArgument, "smart object claim is invalid", {}, {}, "npc_ai.smart_object"));
157 auto resolved = resolveObject(object);
158 if (!resolved.ok()) return Result<SmartObjectClaimHandle>::failure(resolved.status());
159 auto& value = resolved.value().get();
160 const auto definition = std::find_if(value.definition.slots.begin(), value.definition.slots.end(),
161 [&](const auto& slot) { return slot.id == slotId; });
162 if (definition == value.definition.slots.end())
164 DiagnosticCode::NotFound, "smart object slot was not found", {}, {}, "npc_ai.smart_object"));
165 if (value.claimsBySlot.contains(std::string(slotId)))
167 DiagnosticCode::Conflict, "smart object slot is already claimed", {}, {}, "npc_ai.smart_object"));
168 std::uint32_t index;
169 if (freeClaims_.empty()) {
170 index = static_cast<std::uint32_t>(claims_.size());
171 claims_.push_back({});
172 } else {
173 index = freeClaims_.back();
174 freeClaims_.pop_back();
175 }
177 claims_[index].value = Claim{object, std::string(slotId), agent, currentTick + leaseTicks};
178 value.claimsBySlot.emplace(slotId, handle);
180}
181
182Result<void> SmartObjectWorld::renew(SmartObjectClaimHandle claimHandle, AgentHandle agent, std::uint64_t currentTick,
183 std::uint64_t leaseTicks) {
184 if (leaseTicks == 0 || currentTick > UINT64_MAX - leaseTicks)
186 DiagnosticCode::InvalidArgument, "smart object lease duration is invalid", {}, {}, "npc_ai.smart_object"));
187 auto resolved = resolveClaim(claimHandle);
188 if (!resolved.ok()) return Result<void>::failure(resolved.status());
189 auto& claim = resolved.value().get();
190 if (claim.agent != agent)
191 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "smart object claim owner mismatch",
192 {}, {}, "npc_ai.smart_object"));
193 if (claim.expiresAtTick <= currentTick)
195 "smart object claim already expired", {}, {},
196 "npc_ai.smart_object"));
197 claim.expiresAtTick = currentTick + leaseTicks;
199}
200
201Result<void> SmartObjectWorld::release(SmartObjectClaimHandle claimHandle, AgentHandle agent) {
202 auto resolved = resolveClaim(claimHandle);
203 if (!resolved.ok()) return Result<void>::failure(resolved.status());
204 if (resolved.value().get().agent != agent)
205 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "smart object claim owner mismatch",
206 {}, {}, "npc_ai.smart_object"));
207 releaseClaimUnchecked(claimHandle);
209}
210
211void SmartObjectWorld::releaseClaimUnchecked(SmartObjectClaimHandle handle) noexcept {
212 if (!handle.isValid() || handle.index() >= claims_.size()) return;
213 auto& slot = claims_[handle.index()];
214 if (!slot.value || slot.generation != handle.generation()) return;
215 auto object = resolveObject(slot.value->object);
216 if (object.ok()) object.value().get().claimsBySlot.erase(slot.value->slotId);
217 slot.value.reset();
218 const auto next = SmartObjectClaimHandle::nextGeneration(slot.generation);
219 if (next) {
220 slot.generation = *next;
221 freeClaims_.push_back(handle.index());
222 }
223}
224
225Result<std::uint32_t> SmartObjectWorld::releaseAgentClaims(AgentHandle agent) {
226 if (!agent.isValid())
228 DiagnosticCode::InvalidArgument, "agent handle is invalid", {}, {}, "npc_ai.smart_object"));
229 std::vector<SmartObjectClaimHandle> owned;
230 for (std::uint32_t index = 0; index < claims_.size(); ++index)
231 if (claims_[index].value && claims_[index].value->agent == agent)
232 owned.emplace_back(index, claims_[index].generation);
233 for (const auto claim : owned) releaseClaimUnchecked(claim);
234 return Result<std::uint32_t>::success(static_cast<std::uint32_t>(owned.size()));
235}
236
237Result<SmartObjectExpirationReport> SmartObjectWorld::expire(std::uint64_t currentTick) {
238 std::vector<SmartObjectClaimHandle> expired;
239 for (std::uint32_t index = 0; index < claims_.size(); ++index)
240 if (claims_[index].value && claims_[index].value->expiresAtTick <= currentTick)
241 expired.emplace_back(index, claims_[index].generation);
242 for (const auto claim : expired) releaseClaimUnchecked(claim);
243 return Result<SmartObjectExpirationReport>::success({static_cast<std::uint32_t>(expired.size())});
244}
245
246Result<SmartObjectClaimSnapshot> SmartObjectWorld::snapshot(SmartObjectClaimHandle claimHandle) const {
247 auto resolved = resolveClaim(claimHandle);
248 if (!resolved.ok()) return Result<SmartObjectClaimSnapshot>::failure(resolved.status());
249 const auto& claim = resolved.value().get();
251 {claimHandle, claim.object, claim.slotId, claim.agent, claim.expiresAtTick});
252}
253
254} // namespace eve::npc_ai
double value
ActiveSource owned
std::array< float, 3 > position
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
const std::string * tag
std::uint32_t generation
PrimitiveHandle handle
float dz
float dy
float dx
Json object
uint32_t index
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
constexpr bool isValid() const noexcept
Returns whether both coordinates are usable handle values.
constexpr std::optional< RuntimeHandle > nextGeneration() const noexcept
Returns this slot at its next generation.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Result< SmartObjectClaimHandle > claim(AgentHandle agent, SmartObjectHandle object, std::string_view slotId, std::uint64_t currentTick, std::uint64_t leaseTicks)
Atomically leases one free slot to an agent for a bounded tick duration.
Result< void > removeObject(SmartObjectHandle object, SmartObjectRemoval policy)
Removes an object using the explicit live-claim policy.
Result< std::vector< SmartObjectCandidate > > query(const SmartObjectQuery &query) const
Returns unclaimed matching slots in deterministic distance/name order.
Result< SmartObjectHandle > registerObject(SmartObjectDefinition definition)
Validates and atomically registers one uniquely named object.
RuntimeHandle< SmartObjectHandleTag > SmartObjectHandle
Definition SmartObject.h:24
SmartObjectRemoval
SmartObjectRemoval public API.
Definition SmartObject.h:28
RuntimeHandle< SmartObjectClaimHandleTag > SmartObjectClaimHandle
Definition SmartObject.h:25
SmartObjectDefinition public API.
Definition SmartObject.h:39
SmartObjectQuery public API.
Definition SmartObject.h:45
std::array< double, 3 > origin
Definition SmartObject.h:48
std::vector< std::string > requiredTags
Definition SmartObject.h:47