activity_sdk_actor_program.cpp 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #include "../../middleware/gameplay/external/actor_entity_registry.h"
  2. #include "../../state/activity_sdk/generated_world/runtime.h"
  3. #include "activity_sdk_device_internal.h"
  4. #include "activity_sdk_squad_runtime.h"
  5. namespace sunrise::server::activity::activity_sdk_devices {
  6. namespace {
  7. /** The named actor's parent comes from its exact package source relation. */
  8. [[nodiscard]] Status parent_definition(const state::activity_sdk::BoundView& view,
  9. std::uint32_t actorSlot,
  10. std::uint32_t& squadRow) noexcept {
  11. namespace sdk = state::activity_sdk;
  12. namespace auth = middleware::bap::activity_message::scriptable_auth;
  13. if (view.catalog == nullptr || actorSlot >= view.catalog->slots().size()) {
  14. return Status::invalidSlot;
  15. }
  16. const auto& actor = view.catalog->slots()[actorSlot];
  17. if (actor.objectIndex >= view.catalog->objects().size()
  18. || actor.slotType != auth::kType2SlotType
  19. || actor.componentClass != auth::kType2ComponentClass
  20. || actor.authSchema != auth::kType2Schema || actor.senseSchema != auth::kType2SenseSchema) {
  21. return Status::invalidSlot;
  22. }
  23. sdk::generated_world::GeneratedWorldView world{};
  24. if (sdk::generated_world::resolve(view, world) != sdk::generated_world::BindStatus::ready
  25. || world.snapshot() == nullptr) {
  26. return Status::targetUnavailable;
  27. }
  28. const state::gameplay::entity_identity::ActorSourceReference reference{
  29. view.catalog->objects()[actor.objectIndex].objectKey,
  30. static_cast<std::uint16_t>(actor.slotIndex),
  31. static_cast<std::uint8_t>(actor.slotType),
  32. true,
  33. true};
  34. state::gameplay::entity_identity::ActorSourceReference source{};
  35. if (!middleware::gameplay::external::resolve_combatant_squad_source(
  36. *world.snapshot(), reference, source)) {
  37. return Status::targetUnavailable;
  38. }
  39. const auto* scenario = sdk::bound_scenario(view);
  40. if (scenario == nullptr) {
  41. return Status::invalidView;
  42. }
  43. squadRow = sdk::format::kAbsentIndex;
  44. const auto slots = view.catalog->slots();
  45. const auto objects = view.catalog->objects();
  46. const auto squads = view.catalog->squads();
  47. for (const auto& squad : sdk::scenario_squads(*view.catalog, *scenario)) {
  48. if (squad.slotIndex >= slots.size() || squad.objectIndex >= objects.size()) {
  49. return Status::invalidSlot;
  50. }
  51. const auto& slot = slots[squad.slotIndex];
  52. if (objects[squad.objectIndex].objectKey == source.key && slot.slotIndex == source.index
  53. && slot.slotType == source.type) {
  54. if (squadRow != sdk::format::kAbsentIndex) {
  55. return Status::ambiguousTarget;
  56. }
  57. squadRow = static_cast<std::uint32_t>(&squad - squads.data());
  58. }
  59. }
  60. return squadRow == sdk::format::kAbsentIndex ? Status::targetUnavailable : Status::ready;
  61. }
  62. /** Transport preparation uses the unique SDK parent selected by the actor descriptor. */
  63. [[nodiscard]] Status parent(const state::activity_sdk::BoundView& view,
  64. std::uint32_t actorSlot,
  65. detail::PreparedDevice& output,
  66. std::uint32_t& squadRow) noexcept {
  67. const auto resolved = parent_definition(view, actorSlot, squadRow);
  68. return resolved == Status::ready
  69. ? detail::prepare_slot(view, view.catalog->squads()[squadRow].slotIndex, output)
  70. : resolved;
  71. }
  72. /** Creation reserves authored candidates only when the parent has no usable reservation. */
  73. [[nodiscard]] Status
  74. ensure_source_reserved(const state::activity_sdk::BoundView& view,
  75. const detail::PreparedDevice& source,
  76. std::uint32_t squadRow,
  77. const host::ScriptableOutputReservation& reservation) noexcept {
  78. namespace sdk = state::activity_sdk;
  79. namespace squad_auth = middleware::bap::activity_message::squad_auth;
  80. const auto sourceStatus = host::actor_program_source_status(view.binding, source.target);
  81. if (sourceStatus == host::ActorProgramSourceStatus::ready) {
  82. return Status::ready;
  83. }
  84. if (sourceStatus == host::ActorProgramSourceStatus::incompatible) {
  85. return Status::refused;
  86. }
  87. const auto members = sdk::squad_members(*view.catalog, view.catalog->squads()[squadRow]);
  88. std::array<std::int32_t, squad_auth::kMaximumRequestedCountLength> counts{};
  89. if (members.empty() || members.size() > counts.size()) {
  90. return Status::invalidValue;
  91. }
  92. bool positive = false;
  93. for (std::size_t index = 0; index < members.size(); ++index) {
  94. counts[index] = members[index].defaultCount;
  95. if (counts[index] < 0) {
  96. return Status::invalidValue;
  97. }
  98. positive = positive || counts[index] > 0;
  99. }
  100. if (!positive) {
  101. return Status::invalidValue;
  102. }
  103. const auto status = activity_sdk_squads::place_reserved(view,
  104. squadRow,
  105. std::span(counts).first(members.size()),
  106. squad_auth::Mode::reserve,
  107. reservation);
  108. return status == activity_sdk_squads::Status::queued ? Status::queued : Status::refused;
  109. }
  110. } // namespace
  111. /** Script decisions capture only the native parent row; its transport preparation stays private. */
  112. Status actor_program_source_squad(const state::activity_sdk::BoundView& view,
  113. std::uint32_t actorSlot,
  114. std::uint32_t& squadRow) noexcept {
  115. squadRow = state::activity_sdk::format::kAbsentIndex;
  116. return parent_definition(view, actorSlot, squadRow);
  117. }
  118. /** Population bookkeeping uses the exact SDK parent that the transported body prepared. */
  119. Status actor_program_parent_squad(const state::activity_sdk::BoundView& view,
  120. std::uint32_t actorSlot,
  121. const host::ScriptableTarget& expectedParent,
  122. std::uint32_t& squadRow) noexcept {
  123. squadRow = state::activity_sdk::format::kAbsentIndex;
  124. detail::PreparedDevice actor{}, source{};
  125. const auto prepared = detail::prepare_combatant(view, actorSlot, actor);
  126. if (prepared != Status::ready) {
  127. return prepared;
  128. }
  129. const auto resolved = parent(view, actorSlot, source, squadRow);
  130. if (resolved != Status::ready) {
  131. return resolved;
  132. }
  133. const auto& target = source.target;
  134. return target.objectTag == expectedParent.objectTag
  135. && target.registryKey == expectedParent.registryKey
  136. && target.slotIndex == expectedParent.slotIndex
  137. && target.slotType == expectedParent.slotType
  138. && target.authSchema == expectedParent.authSchema
  139. ? Status::ready
  140. : Status::targetUnavailable;
  141. }
  142. /** Revalidates the pinned actor and its authored parent before reserving a native program. */
  143. Status run_actor_program_reserved(const state::activity_sdk::BoundView& view,
  144. const state::activity::mission::TypedIntent& intent,
  145. const host::ScriptableOutputReservation& reservation) noexcept {
  146. const bool retire = intent.kind == state::activity::mission::IntentKind::retireActor;
  147. if ((!retire && intent.kind != state::activity::mission::IntentKind::runActorProgram)
  148. || (retire && intent.active) || intent.authByteCount != intent.authBody.size()) {
  149. return Status::invalidBody;
  150. }
  151. const auto validation = detail::validate_auth(view,
  152. intent.firstRow,
  153. intent.objectTag,
  154. intent.registryKey,
  155. intent.authSchema,
  156. intent.slotIndex,
  157. intent.slotType,
  158. intent.authBody,
  159. intent.authBitCount,
  160. intent.sdkBuildSha256);
  161. if (validation != Status::ready) {
  162. return validation;
  163. }
  164. detail::PreparedDevice actor{}, source{};
  165. const auto prepared = detail::prepare_combatant(view, intent.firstRow, actor);
  166. if (prepared != Status::ready) {
  167. return prepared;
  168. }
  169. if (intent.active) {
  170. std::uint32_t squadRow = state::activity_sdk::format::kAbsentIndex;
  171. const auto resolved = parent(view, intent.firstRow, source, squadRow);
  172. if (resolved != Status::ready) {
  173. return resolved;
  174. }
  175. if (actor.activityClientGeneration != source.activityClientGeneration) {
  176. return Status::staleActivityClient;
  177. }
  178. if (squadRow != intent.secondRow) {
  179. return Status::invalidSlot;
  180. }
  181. const auto reserved = ensure_source_reserved(view, source, squadRow, reservation);
  182. if (reserved != Status::ready) {
  183. return reserved;
  184. }
  185. }
  186. return host::request_type2_program(view.binding,
  187. actor.target,
  188. source.target,
  189. actor.target.stateLocalRoster ? &actor.generatedRosterGroup
  190. : nullptr,
  191. intent.authBody,
  192. intent.authBitCount,
  193. intent.active,
  194. actor.activityClientGeneration,
  195. &reservation,
  196. retire)
  197. ? Status::queued
  198. : Status::refused;
  199. }
  200. } // namespace sunrise::server::activity::activity_sdk_devices