actor_command_policy_lane.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. #include <Windows.h>
  2. #include <algorithm>
  3. #include <array>
  4. #include <cstddef>
  5. #include <cstdint>
  6. #include <limits>
  7. #include "../../middleware/gameplay/external/composite_entity_codec.h"
  8. #include "../../middleware/gameplay/external/simulation_event_runtime_codec.h"
  9. #include "../../state/activity_sdk/runtime.h"
  10. #include "actor_command_policy.h"
  11. #include "actor_command_policy_internal.h"
  12. #include "actor_command_policy_session.h"
  13. #include "gameplay_log.h"
  14. namespace sunrise::server::gameplay::actor_command_policy {
  15. namespace external = middleware::gameplay::external;
  16. namespace format = state::activity_sdk::format;
  17. namespace wire = middleware::bap::activity_message::wire_schema;
  18. namespace {
  19. volatile LONG g_entityRecordDiagnostics{};
  20. /** Decoded layout addressed by the type-1 baseline schema fields. */
  21. struct SquadClientRefLayout final {
  22. std::uint32_t registryKey{};
  23. std::int8_t slotType{};
  24. std::byte alignment{};
  25. std::int16_t slotIndex{};
  26. };
  27. /** @return A decoded integral value as a signed scalar. */
  28. [[nodiscard]] bool integral_value(const wire::RuntimeDecodedValue& value,
  29. std::int64_t& output) noexcept {
  30. if (!value.present) {
  31. return false;
  32. }
  33. if (value.kind == wire::ValueKind::signedInteger) {
  34. output = value.signedValue;
  35. return true;
  36. }
  37. if (value.kind == wire::ValueKind::unsignedInteger
  38. && value.unsignedValue
  39. <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)())) {
  40. output = static_cast<std::int64_t>(value.unsignedValue);
  41. return true;
  42. }
  43. return false;
  44. }
  45. /** Decodes the type-1 baseline ClientRef triple. */
  46. [[nodiscard]] bool decode_squad_identity(const state::activity_sdk::Snapshot& catalog,
  47. const external::TypePayload& payload,
  48. SquadEntityRow& output) noexcept {
  49. std::array<wire::RuntimeDecodedValue, wire::kRuntimeValueCapacity> values{};
  50. wire::RuntimeDecodeResult result{};
  51. if (!external::decode_composite_entity_payload(catalog,
  52. external::EntityType::squad,
  53. external::TypePayloadPart::baseline,
  54. payload,
  55. values,
  56. result)) {
  57. return false;
  58. }
  59. bool key = false;
  60. bool type = false;
  61. bool index = false;
  62. for (std::size_t position = 0; position < result.valueCount; ++position) {
  63. const wire::RuntimeDecodedValue& value = values[position];
  64. if (value.fieldRow >= catalog->runtime_fields().size()) {
  65. return false;
  66. }
  67. const std::uint64_t offset = catalog->runtime_fields()[value.fieldRow].structOffset;
  68. std::int64_t decoded = 0;
  69. if (value.role != wire::ValueRole::scalar || !integral_value(value, decoded)) {
  70. continue;
  71. }
  72. if (offset == offsetof(SquadClientRefLayout, registryKey) && decoded > 0
  73. && decoded <= (std::numeric_limits<std::uint32_t>::max)()) {
  74. output.registryKey = static_cast<std::uint32_t>(decoded);
  75. key = true;
  76. } else if (offset == offsetof(SquadClientRefLayout, slotType) && decoded >= 0
  77. && decoded <= (std::numeric_limits<std::uint8_t>::max)()) {
  78. output.slotType = static_cast<std::uint8_t>(decoded);
  79. type = true;
  80. } else if (offset == offsetof(SquadClientRefLayout, slotIndex) && decoded >= 0
  81. && decoded <= (std::numeric_limits<std::uint32_t>::max)()) {
  82. output.slotIndex = static_cast<std::uint32_t>(decoded);
  83. index = true;
  84. }
  85. }
  86. return key && type && index;
  87. }
  88. /** Decodes the latest actor-token list from one type-1 update. */
  89. [[nodiscard]] bool decode_squad_actors(const state::activity_sdk::Snapshot& catalog,
  90. const external::TypePayload& payload,
  91. SquadEntityRow& output) noexcept {
  92. std::array<wire::RuntimeDecodedValue, wire::kRuntimeValueCapacity> values{};
  93. wire::RuntimeDecodeResult result{};
  94. if (!external::decode_composite_entity_payload(catalog,
  95. external::EntityType::squad,
  96. external::TypePayloadPart::update,
  97. payload,
  98. values,
  99. result)) {
  100. return false;
  101. }
  102. output.actorCount = 0;
  103. for (std::size_t position = 0; position < result.valueCount; ++position) {
  104. const wire::RuntimeDecodedValue& slot = values[position];
  105. if (!slot.present || slot.role != wire::ValueRole::entityReferenceSlot
  106. || slot.unsignedValue > external::kMaximumEntitySlot) {
  107. continue;
  108. }
  109. const auto incarnation =
  110. std::find_if(values.begin() + position + 1,
  111. values.begin() + result.valueCount,
  112. [&slot](const wire::RuntimeDecodedValue& value) {
  113. return value.present && value.fieldRow == slot.fieldRow
  114. && value.occurrence == slot.occurrence
  115. && value.role == wire::ValueRole::entityReferenceIncarnation;
  116. });
  117. if (incarnation == values.begin() + result.valueCount
  118. || incarnation->unsignedValue > external::kMaximumEntityIncarnation
  119. || output.actorCount == output.actors.size()) {
  120. return false;
  121. }
  122. output.actors[output.actorCount++] = {
  123. static_cast<std::uint16_t>(slot.unsignedValue),
  124. static_cast<std::uint8_t>(incarnation->unsignedValue)};
  125. }
  126. return true;
  127. }
  128. /** @return True when the active policy names this exact live squad. */
  129. [[nodiscard]] bool selected_squad(const SessionRow& session, const SquadEntityRow& squad) noexcept {
  130. return std::any_of(session.selectedSquads.begin(),
  131. session.selectedSquads.begin() + session.selectedSquadCount,
  132. [&squad](const SelectedSquad& selected) {
  133. return selected.registryKey == squad.registryKey
  134. && selected.slotType == squad.slotType
  135. && selected.slotIndex == squad.slotIndex;
  136. });
  137. }
  138. /** Applies one type-1 record and queues policy for its current exact members. */
  139. [[nodiscard]] bool accept_squad_record(SessionRow& session,
  140. const external::EntityRecord& record) noexcept {
  141. auto row =
  142. std::find_if(session.squads.begin(), session.squads.end(), [&record](const auto& value) {
  143. return value.occupied
  144. && (((record.flags & external::entityCreate) != 0
  145. && value.token.slot == record.token.slot)
  146. || same_token(value.token, record.token));
  147. });
  148. if ((record.flags & external::entityRemove) != 0) {
  149. if (row != session.squads.end()) {
  150. *row = {};
  151. }
  152. return true;
  153. }
  154. SquadEntityRow candidate =
  155. row == session.squads.end() || (record.flags & external::entityCreate) != 0
  156. ? SquadEntityRow{}
  157. : *row;
  158. candidate.token = record.token;
  159. candidate.occupied = true;
  160. if ((record.flags & external::entityCreate) != 0
  161. && !decode_squad_identity(session.catalog, record.baseline, candidate)) {
  162. return false;
  163. }
  164. if ((record.flags & external::entityUpdate) != 0
  165. && !decode_squad_actors(session.catalog, record.update, candidate)) {
  166. return false;
  167. }
  168. if (row == session.squads.end()) {
  169. row = std::find_if(session.squads.begin(), session.squads.end(), [](const auto& value) {
  170. return !value.occupied;
  171. });
  172. if (row == session.squads.end()) {
  173. return false;
  174. }
  175. }
  176. const SquadEntityRow prior = *row;
  177. *row = candidate;
  178. const bool selected = session.policyActive && selected_squad(session, candidate);
  179. report(core::log::Level::debug,
  180. "ev=actor_policy stage=squad_record key=0x%08X type=%u index=%u actors=%u "
  181. "selected=%u flags=0x%X",
  182. candidate.registryKey,
  183. static_cast<unsigned>(candidate.slotType),
  184. static_cast<unsigned>(candidate.slotIndex),
  185. static_cast<unsigned>(candidate.actorCount),
  186. selected ? 1U : 0U,
  187. static_cast<unsigned>(record.flags));
  188. if (!selected) {
  189. return true;
  190. }
  191. std::array<external::EntityToken, kSquadActorCapacity> queued{};
  192. std::size_t queuedCount = 0;
  193. for (std::size_t index = 0; index < candidate.actorCount; ++index) {
  194. std::uint32_t actorClass = format::kAbsentIndex;
  195. if (!selected_entity_class(session, candidate.actors[index], actorClass)) {
  196. continue;
  197. }
  198. if (!queue_command(session,
  199. actorClass,
  200. candidate.actors[index],
  201. session.policyValue,
  202. OutputPurpose::policyCommand)) {
  203. for (OutputRow& output : session.outputs) {
  204. if (output.purpose == OutputPurpose::policyCommand
  205. && std::any_of(
  206. queued.begin(), queued.begin() + queuedCount, [&output](const auto& token) {
  207. return same_token(output.target, token);
  208. })) {
  209. output = {};
  210. }
  211. }
  212. *row = prior;
  213. return false;
  214. }
  215. queued[queuedCount++] = candidate.actors[index];
  216. }
  217. return true;
  218. }
  219. } // namespace
  220. /** Optional policy projection retains only its supported entity metadata. */
  221. static bool accept_entity_record(std::uint64_t groupSessionId,
  222. const external::EntityRecord& record) noexcept {
  223. if (groupSessionId == 0) {
  224. return false;
  225. }
  226. if (InterlockedIncrement(&g_entityRecordDiagnostics) <= 128) {
  227. report(core::log::Level::debug,
  228. "ev=actor_policy stage=entity_record type=%u flags=0x%X slot=%u incarnation=%u "
  229. "baseline=%u update=%u",
  230. static_cast<unsigned>(record.type),
  231. static_cast<unsigned>(record.flags),
  232. static_cast<unsigned>(record.token.slot),
  233. static_cast<unsigned>(record.token.incarnation),
  234. static_cast<unsigned>(record.baseline.byteCount),
  235. static_cast<unsigned>(record.update.byteCount));
  236. }
  237. external::ActorEntityCatalog published{};
  238. if (!external::published_actor_entity_catalog(published)) {
  239. return false;
  240. }
  241. AcquireSRWLockExclusive(&g_lock);
  242. SessionRow* const session = find_or_create_session(groupSessionId);
  243. if (session == nullptr) {
  244. ReleaseSRWLockExclusive(&g_lock);
  245. return false;
  246. }
  247. if (session->catalog == nullptr
  248. || (!session->policyActive && session->catalog != published.owner)) {
  249. session->catalog = published.owner;
  250. }
  251. // A live policy owns its catalog. Records from another one name different class rows.
  252. if (session->policyActive && session->catalog != published.owner) {
  253. ReleaseSRWLockExclusive(&g_lock);
  254. return false;
  255. }
  256. if (record.type == external::EntityType::squad) {
  257. const bool accepted = accept_squad_record(*session, record);
  258. ReleaseSRWLockExclusive(&g_lock);
  259. return accepted;
  260. }
  261. if (record.type != external::EntityType::sobject) {
  262. ReleaseSRWLockExclusive(&g_lock);
  263. return true;
  264. }
  265. external::ActorEntityCatalog catalog{session->catalog, session->catalog->actor_classes()};
  266. if (record.token.slot >= session->actors.slots.size()) {
  267. ReleaseSRWLockExclusive(&g_lock);
  268. return false;
  269. }
  270. const bool catalogChange = session->actors.catalog != catalog.owner
  271. || session->actors.classData != catalog.classes.data()
  272. || session->actors.classCount != catalog.classes.size();
  273. if (catalogChange
  274. && std::any_of(session->actors.slots.begin(),
  275. session->actors.slots.end(),
  276. [](const auto& slot) { return slot.occupied; })) {
  277. ReleaseSRWLockExclusive(&g_lock);
  278. return false;
  279. }
  280. const state::activity_sdk::Snapshot priorCatalog = session->actors.catalog;
  281. const format::ActorClass* const priorClassData = session->actors.classData;
  282. const std::size_t priorClassCount = session->actors.classCount;
  283. const external::ActorEntitySlot priorSlot = session->actors.slots[record.token.slot];
  284. const external::ActorEntityApplyResult result =
  285. external::apply_actor_entity_record(session->actors, catalog, record);
  286. bool policyCommandQueued = false;
  287. bool accepted = result != external::ActorEntityApplyResult::invalid
  288. && result != external::ActorEntityApplyResult::staleToken;
  289. if (result == external::ActorEntityApplyResult::actorRemoved) {
  290. remove_target_state(*session, record.token);
  291. } else if (result == external::ActorEntityApplyResult::actorCreated && session->policyActive) {
  292. std::uint32_t actorClassIndex = format::kAbsentIndex;
  293. if (selected_entity_class(*session, record.token, actorClassIndex)) {
  294. policyCommandQueued = queue_command(*session,
  295. actorClassIndex,
  296. record.token,
  297. session->policyValue,
  298. OutputPurpose::policyCommand);
  299. accepted = policyCommandQueued;
  300. }
  301. }
  302. // The projection is all-or-nothing, so a refused command restores the whole slot.
  303. if (!accepted) {
  304. session->actors.catalog = priorCatalog;
  305. session->actors.classData = priorClassData;
  306. session->actors.classCount = priorClassCount;
  307. session->actors.slots[record.token.slot] = priorSlot;
  308. }
  309. ReleaseSRWLockExclusive(&g_lock);
  310. if (policyCommandQueued) {
  311. report(core::log::Level::info,
  312. "ev=actor_policy stage=command result=queued group=0x%016llX slot=%u incarnation=%u",
  313. static_cast<unsigned long long>(groupSessionId),
  314. static_cast<unsigned>(record.token.slot),
  315. static_cast<unsigned>(record.token.incarnation));
  316. }
  317. return accepted;
  318. }
  319. /** Policy projection visits every record after transport acceptance. */
  320. bool accept_entity_batch(std::uint64_t groupSessionId,
  321. const external::EntityBatch& batch) noexcept {
  322. if (groupSessionId == 0) return false;
  323. bool accepted = true;
  324. for (std::size_t index = 0; index < external::entity_record_count(batch); ++index)
  325. accepted = accept_entity_record(groupSessionId, external::entity_record_at(batch, index))
  326. && accepted;
  327. return accepted;
  328. }
  329. /**
  330. * Retains all new damage replays transactionally for one exact group session.
  331. * @param groupSessionId Group session the lane arrived on.
  332. * @param batch Decoded lane-0 events.
  333. * @return True when every damage event is retained and its restore command queued.
  334. */
  335. bool accept_lane0(std::uint64_t groupSessionId,
  336. const external::SimulationEventBatch& batch) noexcept {
  337. if (groupSessionId == 0 || batch.count > batch.records.size()) {
  338. return false;
  339. }
  340. AcquireSRWLockExclusive(&g_lock);
  341. SessionRow* const session = find_session(groupSessionId);
  342. if (session == nullptr || !session->policyActive) {
  343. ReleaseSRWLockExclusive(&g_lock);
  344. return true;
  345. }
  346. if (session->catalog != state::activity_sdk::snapshot()) {
  347. ReleaseSRWLockExclusive(&g_lock);
  348. return false;
  349. }
  350. external::ActorCommandCatalog catalog{};
  351. if (!external::published_actor_command_catalog(session->catalog, catalog)) {
  352. ReleaseSRWLockExclusive(&g_lock);
  353. return false;
  354. }
  355. bool accepted = true;
  356. std::array<std::uint32_t, kReplayCapacity> createdReplays{};
  357. std::size_t createdReplayCount = 0;
  358. for (std::size_t index = 0; index < batch.count; ++index) {
  359. external::DecodedRuntimeEvent event{};
  360. if (!internal::decode_event(catalog, batch, batch.records[index], event)) {
  361. accepted = false;
  362. break;
  363. }
  364. if (event.identity.eventIndex != session->damageEventIndex) {
  365. continue;
  366. }
  367. external::EntityToken target{};
  368. std::uint32_t actorClassIndex = format::kAbsentIndex;
  369. if (!internal::damage_target(event, target)
  370. || !selected_entity_class(*session, target, actorClassIndex)) {
  371. continue;
  372. }
  373. // One replay per target. A second hit on the same actor restores the same faction.
  374. const auto replay = std::find_if(
  375. session->replays.begin(), session->replays.end(), [&target](const auto& row) {
  376. return row.occupied && same_token(row.target, target);
  377. });
  378. if (replay != session->replays.end()) {
  379. continue;
  380. }
  381. const auto emptyReplay = std::find_if(session->replays.begin(),
  382. session->replays.end(),
  383. [](const auto& row) { return !row.occupied; });
  384. if (emptyReplay == session->replays.end()
  385. || !external::retain_runtime_event(emptyReplay->transaction, event)) {
  386. accepted = false;
  387. break;
  388. }
  389. emptyReplay->target = target;
  390. emptyReplay->occupied = true;
  391. const std::uint32_t replayIndex =
  392. static_cast<std::uint32_t>(emptyReplay - session->replays.begin());
  393. createdReplays[createdReplayCount++] = replayIndex;
  394. const std::int32_t defaultFaction = catalog.profiles[actorClassIndex].defaultFaction;
  395. if (!queue_command(*session,
  396. actorClassIndex,
  397. target,
  398. defaultFaction,
  399. OutputPurpose::restoreCommand,
  400. replayIndex)) {
  401. *emptyReplay = {};
  402. accepted = false;
  403. break;
  404. }
  405. }
  406. // The lane is accepted whole, so a refused event drops every replay this call created.
  407. if (!accepted) {
  408. for (OutputRow& row : session->outputs) {
  409. if (row.purpose != OutputPurpose::restoreCommand) {
  410. continue;
  411. }
  412. if (std::find(createdReplays.begin(),
  413. createdReplays.begin() + createdReplayCount,
  414. row.replayIndex)
  415. != createdReplays.begin() + createdReplayCount) {
  416. row = {};
  417. }
  418. }
  419. for (std::size_t index = 0; index < createdReplayCount; ++index) {
  420. session->replays[createdReplays[index]] = {};
  421. }
  422. }
  423. ReleaseSRWLockExclusive(&g_lock);
  424. return accepted;
  425. }
  426. /**
  427. * Stages queued output rows in one batch-owned arena and binds them to one transmission.
  428. * @param groupSessionId Group session the packet carries.
  429. * @param transmissionId Identity the outcome callback names later.
  430. * @param writer Packet writer positioned at the lane.
  431. * @return True when the lane is written, empty or not.
  432. */
  433. bool write_lane0(std::uint64_t groupSessionId,
  434. std::uint64_t transmissionId,
  435. middleware::encoding::bits::Writer& writer) noexcept {
  436. if (groupSessionId == 0 || transmissionId == 0) {
  437. return false;
  438. }
  439. AcquireSRWLockExclusive(&g_lock);
  440. SessionRow* const session = find_session(groupSessionId);
  441. if (session == nullptr || !session->policyActive) {
  442. ReleaseSRWLockExclusive(&g_lock);
  443. external::ActorCommandCatalog published{};
  444. if (!external::published_actor_command_catalog(published)) {
  445. return false;
  446. }
  447. const external::RuntimeEventPayloadCodecContext context{&published};
  448. const external::SimulationEventPayloadCodec codec =
  449. external::make_runtime_event_payload_codec(context);
  450. return external::write_simulation_event_lane(writer, codec, {});
  451. }
  452. if (session->catalog != state::activity_sdk::snapshot()) {
  453. ReleaseSRWLockExclusive(&g_lock);
  454. return false;
  455. }
  456. external::ActorCommandCatalog catalog{};
  457. if (!external::published_actor_command_catalog(session->catalog, catalog)) {
  458. ReleaseSRWLockExclusive(&g_lock);
  459. return false;
  460. }
  461. external::SimulationEventBatch batch{};
  462. std::array<OutputRow*, external::kSimulationEventCapacity> selected{};
  463. for (OutputRow& row : session->outputs) {
  464. if (row.state != OutputState::queued) {
  465. continue;
  466. }
  467. if (batch.count == batch.records.size()) {
  468. break;
  469. }
  470. external::SimulationEventRecord& record = batch.records[batch.count];
  471. record.eventType = static_cast<std::uint8_t>(row.draft.identity.eventType);
  472. record.primaryPresent = row.draft.primaryPresent;
  473. if ((record.primaryPresent
  474. && !external::append_runtime_event_body(batch, row.draft.primary, record.primary))
  475. || !external::append_runtime_event_body(batch, row.draft.secondary, record.secondary)) {
  476. ReleaseSRWLockExclusive(&g_lock);
  477. return false;
  478. }
  479. selected[batch.count] = &row;
  480. ++batch.count;
  481. }
  482. const external::RuntimeEventPayloadCodecContext context{&catalog};
  483. const external::SimulationEventPayloadCodec codec =
  484. external::make_runtime_event_payload_codec(context);
  485. if (!external::write_simulation_event_lane(writer, codec, batch)) {
  486. ReleaseSRWLockExclusive(&g_lock);
  487. return false;
  488. }
  489. // The rows only leave the ledger once the packet owns them.
  490. for (std::size_t index = 0; index < batch.count; ++index) {
  491. selected[index]->state = OutputState::inFlight;
  492. selected[index]->transmissionId = transmissionId;
  493. }
  494. ReleaseSRWLockExclusive(&g_lock);
  495. return true;
  496. }
  497. /**
  498. * Commits or retries the exact lane contribution named by one ACK outcome.
  499. * @param groupSessionId Group session the packet carried.
  500. * @param transmissionId Identity write_lane0 bound to the rows.
  501. * @param outcome Whether the peer acknowledged that packet.
  502. */
  503. void lane0_outcome(std::uint64_t groupSessionId,
  504. std::uint64_t transmissionId,
  505. middleware::gameplay::peer::AckOutcome outcome) noexcept {
  506. AcquireSRWLockExclusive(&g_lock);
  507. SessionRow* const session = find_session(groupSessionId);
  508. if (session == nullptr) {
  509. ReleaseSRWLockExclusive(&g_lock);
  510. return;
  511. }
  512. const bool committed = outcome == middleware::gameplay::peer::AckOutcome::received;
  513. for (OutputRow& row : session->outputs) {
  514. if (row.state != OutputState::inFlight || row.transmissionId != transmissionId) {
  515. continue;
  516. }
  517. if (committed) {
  518. // The restore barrier starts only once the client holds the command.
  519. if (row.purpose == OutputPurpose::restoreCommand
  520. && row.replayIndex < session->replays.size()
  521. && session->replays[row.replayIndex].occupied) {
  522. if (!external::mark_restore_queued(session->replays[row.replayIndex].transaction,
  523. g_serviceFrame)) {
  524. session->replays[row.replayIndex] = {};
  525. }
  526. }
  527. row = {};
  528. } else if (++row.attempts < kMaximumAttempts) {
  529. row.state = OutputState::queued;
  530. row.transmissionId = 0;
  531. } else {
  532. if (row.replayIndex < session->replays.size()) {
  533. session->replays[row.replayIndex] = {};
  534. }
  535. row = {};
  536. }
  537. }
  538. ReleaseSRWLockExclusive(&g_lock);
  539. }
  540. } // namespace sunrise::server::gameplay::actor_command_policy