activity_sdk_scene_spawn.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. #include "activity_sdk_scene_spawn.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <cstdio>
  5. #include <string_view>
  6. #include "../../core/logging/log.h"
  7. #include "../../state/activity_sdk/generated_world/runtime.h"
  8. #include "activity_sdk_device_internal.h"
  9. #include "activity_sdk_mission_internal.h"
  10. #include "activity_sdk_squad_runtime.h"
  11. namespace sunrise::server::activity::activity_sdk_mission {
  12. namespace {
  13. namespace sdk = state::activity_sdk;
  14. namespace devices = activity_sdk_devices;
  15. namespace squad_auth = middleware::bap::activity_message::squad_auth;
  16. using Counts = std::array<std::int32_t, squad_auth::kMaximumRequestedCountLength>;
  17. /** Package defaults must supply a positive bounded reservation for a missing source. */
  18. [[nodiscard]] bool default_counts(const sdk::Catalog& catalog,
  19. std::uint32_t squadRow,
  20. Counts& output,
  21. std::size_t& count) noexcept {
  22. output = {};
  23. count = 0;
  24. if (squadRow >= catalog.squads().size()) {
  25. return false;
  26. }
  27. const auto members = sdk::squad_members(catalog, catalog.squads()[squadRow]);
  28. if (members.empty() || members.size() > output.size()) {
  29. return false;
  30. }
  31. bool positive = false;
  32. for (const auto& member : members) {
  33. if (member.defaultCount < 0) {
  34. return false;
  35. }
  36. output[count++] = member.defaultCount;
  37. positive = positive || member.defaultCount > 0;
  38. }
  39. return positive;
  40. }
  41. /** The physical ClientRef stays the same when its publication route is rebuilt. */
  42. [[nodiscard]] bool same_source(const host::ScriptableTarget& left,
  43. const host::ScriptableTarget& right) noexcept {
  44. return left.objectTag == right.objectTag && left.registryKey == right.registryKey
  45. && left.slotIndex == right.slotIndex && left.slotType == right.slotType
  46. && left.authSchema == right.authSchema;
  47. }
  48. /** Static scene choices need package rows but no active Host reservation. */
  49. [[nodiscard]] SceneStatus collect(const sdk::BoundView& view,
  50. std::uint32_t occurrenceRow,
  51. std::uint32_t sceneSlotRow,
  52. SceneSpawnPlan& output) noexcept {
  53. output = {};
  54. if (view.catalog == nullptr || occurrenceRow >= view.catalog->occurrences().size()) {
  55. return SceneStatus::invalidView;
  56. }
  57. if (view.catalog->occurrences()[occurrenceRow].scenarioIndex != view.scenarioRow) {
  58. return SceneStatus::wrongScenario;
  59. }
  60. if (sceneSlotRow < view.catalog->slots().size()
  61. && sdk::slot_authored_scene_squad_edges(*view.catalog, view.catalog->slots()[sceneSlotRow])
  62. .empty()) {
  63. const state::build_data::scriptables::Snapshot empty{};
  64. return collect_scene_spawn_plan(*view.catalog, empty, occurrenceRow, sceneSlotRow, output);
  65. }
  66. sdk::generated_world::GeneratedWorldView world{};
  67. if (sdk::generated_world::resolve(view, world) != sdk::generated_world::BindStatus::ready
  68. || world.snapshot() == nullptr) {
  69. return SceneStatus::targetUnavailable;
  70. }
  71. return collect_scene_spawn_plan(
  72. *view.catalog, *world.snapshot(), occurrenceRow, sceneSlotRow, output);
  73. }
  74. /** Every target and source policy is checked before a cast can queue its first preparation. */
  75. [[nodiscard]] SceneStatus
  76. prepare_pair(const sdk::BoundView& view, std::uint32_t sceneState, SceneSpawnPair& pair) noexcept {
  77. devices::detail::PreparedDevice actor{}, source{};
  78. if (devices::detail::prepare_combatant(view, pair.actorSlotRow, actor) != devices::Status::ready
  79. || devices::detail::prepare_slot(
  80. view, view.catalog->squads()[pair.squadRow].slotIndex, source)
  81. != devices::Status::ready) {
  82. return SceneStatus::targetUnavailable;
  83. }
  84. if (!actor.target.stateLocalRoster || actor.stateRow != sceneState
  85. || (source.target.stateLocalRoster && source.stateRow != sceneState)) {
  86. return SceneStatus::wrongState;
  87. }
  88. pair.actorTarget = actor.target;
  89. pair.sourceTarget = source.target;
  90. const auto actorStatus = host::actor_squad_binding_status(view.binding, pair.actorTarget);
  91. const auto sourceStatus = host::actor_program_source_status(view.binding, pair.sourceTarget);
  92. if (actorStatus == host::ActorSquadBindingStatus::incompatible
  93. || sourceStatus == host::ActorProgramSourceStatus::incompatible) {
  94. return SceneStatus::refused;
  95. }
  96. pair.actorReady = actorStatus == host::ActorSquadBindingStatus::ready;
  97. pair.sourceReady = sourceStatus == host::ActorProgramSourceStatus::ready;
  98. if (!pair.sourceReady) {
  99. Counts counts{};
  100. std::size_t count = 0;
  101. if (!default_counts(*view.catalog, pair.squadRow, counts, count)
  102. || activity_sdk_squads::availability(
  103. view, pair.squadRow, std::span(counts).first(count), squad_auth::Mode::reserve)
  104. != activity_sdk_squads::Status::ready) {
  105. return SceneStatus::refused;
  106. }
  107. }
  108. return SceneStatus::ready;
  109. }
  110. } // namespace
  111. /** Sends the cast's squads when the wire can carry them all, otherwise none. */
  112. bool scene_dependencies(
  113. const SceneSpawnPlan& plan,
  114. middleware::bap::activity_message::sensor_auth_update::AuthoredSceneDependencies&
  115. output) noexcept {
  116. output = {};
  117. if (plan.count > output.references.size()) {
  118. return false;
  119. }
  120. for (std::size_t index = 0; index < plan.count; ++index) {
  121. const host::ScriptableTarget& source = plan.pairs[index].sourceTarget;
  122. output.references[output.count++] = {source.registryKey,
  123. static_cast<std::int8_t>(source.slotType),
  124. static_cast<std::int16_t>(source.slotIndex)};
  125. }
  126. return true;
  127. }
  128. /** Logged once per activation; an availability check resolves the same cast every frame. */
  129. void log_omitted_scene_dependencies(const sdk::Catalog& catalog,
  130. std::uint32_t sceneSlotRow,
  131. std::size_t castCount) noexcept {
  132. std::array<char, core::log::kLineCapacity> line{};
  133. const std::string_view scene = sceneSlotRow < catalog.slots().size()
  134. ? catalog.string(catalog.slots()[sceneSlotRow].id)
  135. : std::string_view{};
  136. const int written = std::snprintf(
  137. line.data(),
  138. line.size(),
  139. "ev=scene_dependencies result=omitted scene=%.*s cast=%zu capacity=%zu",
  140. static_cast<int>(scene.size()),
  141. scene.data(),
  142. castCount,
  143. middleware::bap::activity_message::sensor_auth_update::AuthoredSceneDependencies{}
  144. .references.size());
  145. if (written > 0) {
  146. core::log::write(
  147. core::log::Channel::server,
  148. core::log::Level::warn,
  149. {line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1)});
  150. }
  151. }
  152. /** Returns immutable scene cast identities without consulting live output state. */
  153. SceneStatus resolve_scene_spawn_plan(const sdk::BoundView& view,
  154. std::uint32_t occurrenceRow,
  155. std::uint32_t sceneSlotRow,
  156. SceneSpawnPlan& output) noexcept {
  157. return collect(view, occurrenceRow, sceneSlotRow, output);
  158. }
  159. /**
  160. * Captures the exact parent rows without touching Host state.
  161. * @param view Pinned SDK and generated world.
  162. * @param occurrenceRow Selected scene occurrence.
  163. * @param sceneSlotRow Selected Type 43 slot.
  164. * @param output Receives source squad rows; cleared on failure.
  165. * @param count Receives the written row count; zero on failure.
  166. * @return Ready for a complete bounded cast, including an empty cast.
  167. */
  168. SceneStatus scene_spawn_sources(const sdk::BoundView& view,
  169. std::uint32_t occurrenceRow,
  170. std::uint32_t sceneSlotRow,
  171. std::span<std::uint32_t> output,
  172. std::size_t& count) noexcept {
  173. std::fill(output.begin(), output.end(), sdk::format::kAbsentIndex);
  174. count = 0;
  175. SceneSpawnPlan plan{};
  176. const auto status = collect(view, occurrenceRow, sceneSlotRow, plan);
  177. if (status != SceneStatus::ready) {
  178. return status;
  179. }
  180. if (plan.count > output.size()) {
  181. return SceneStatus::refused;
  182. }
  183. for (std::size_t index = 0; index < plan.count; ++index) {
  184. output[count++] = plan.pairs[index].squadRow;
  185. }
  186. return SceneStatus::ready;
  187. }
  188. /**
  189. * Resolves every cast member and refuses incompatible live ownership before any output.
  190. * @param view Current SDK and activity binding.
  191. * @param occurrenceRow Selected scene occurrence.
  192. * @param sceneSlotRow Selected Type 43 slot.
  193. * @param output Receives the complete current plan; cleared on failure.
  194. * @return Ready only when every source can be reused or prepared.
  195. */
  196. SceneStatus query_scene_spawn_plan(const sdk::BoundView& view,
  197. std::uint32_t occurrenceRow,
  198. std::uint32_t sceneSlotRow,
  199. SceneSpawnPlan& output) noexcept {
  200. output = {};
  201. detail::PreparedScene scene{};
  202. const auto available = detail::prepare_scene(view, occurrenceRow, sceneSlotRow, scene);
  203. if (available != SceneStatus::ready) {
  204. return available;
  205. }
  206. SceneSpawnPlan candidate{};
  207. const auto collected = collect(view, occurrenceRow, sceneSlotRow, candidate);
  208. if (collected != SceneStatus::ready) {
  209. return collected;
  210. }
  211. for (std::size_t index = 0; index < candidate.count; ++index) {
  212. const auto status = prepare_pair(view, scene.stateRow, candidate.pairs[index]);
  213. if (status != SceneStatus::ready) {
  214. return status;
  215. }
  216. }
  217. output = candidate;
  218. return SceneStatus::ready;
  219. }
  220. /**
  221. * Reuses retained preparation and queues only the next missing world change.
  222. * @param view Current SDK and activity binding.
  223. * @param occurrenceRow Selected scene occurrence.
  224. * @param sceneSlotRow Selected Type 43 slot.
  225. * @param reservation Exact output revision owned by the durable scene intent.
  226. * @return Queued for one preparation or the final scene activation.
  227. */
  228. SceneStatus activate_authored_scene_spawn_reserved(
  229. const sdk::BoundView& view,
  230. std::uint32_t occurrenceRow,
  231. std::uint32_t sceneSlotRow,
  232. const host::ScriptableOutputReservation& reservation) noexcept {
  233. SceneSpawnPlan plan{};
  234. const auto status = query_scene_spawn_plan(view, occurrenceRow, sceneSlotRow, plan);
  235. if (status != SceneStatus::ready) {
  236. return status;
  237. }
  238. for (std::size_t index = 0; index < plan.count; ++index) {
  239. const auto& pair = plan.pairs[index];
  240. if (!pair.sourceReady) {
  241. Counts counts{};
  242. std::size_t count = 0;
  243. if (!default_counts(*view.catalog, pair.squadRow, counts, count)) {
  244. return SceneStatus::refused;
  245. }
  246. return activity_sdk_squads::place_reserved(view,
  247. pair.squadRow,
  248. std::span(counts).first(count),
  249. squad_auth::Mode::reserve,
  250. reservation)
  251. == activity_sdk_squads::Status::queued
  252. ? SceneStatus::queued
  253. : SceneStatus::refused;
  254. }
  255. if (!pair.actorReady) {
  256. return devices::bind_combatant_to_squad_reserved(view, pair.actorSlotRow, reservation)
  257. == devices::Status::queued
  258. ? SceneStatus::queued
  259. : SceneStatus::refused;
  260. }
  261. }
  262. return activate_authored_scene_reserved(view, occurrenceRow, sceneSlotRow, reservation);
  263. }
  264. /**
  265. * A transported preparation must belong to this scene's exact static cast.
  266. * @param view Current SDK and activity binding.
  267. * @param occurrenceRow Selected scene occurrence.
  268. * @param sceneSlotRow Selected Type 43 slot.
  269. * @param staged Exact transported Host output.
  270. * @param squadRow Receives the preparation's source squad row; absent on failure.
  271. * @return Ready only for an owned source reservation or actor binding.
  272. */
  273. SceneStatus validate_scene_preparation_output(const sdk::BoundView& view,
  274. std::uint32_t occurrenceRow,
  275. std::uint32_t sceneSlotRow,
  276. const host::PendingScriptableOverride& staged,
  277. std::uint32_t& squadRow) noexcept {
  278. squadRow = sdk::format::kAbsentIndex;
  279. if (staged.kind != host::ScriptableOverrideKind::squad
  280. && staged.kind != host::ScriptableOverrideKind::combatantBinding) {
  281. return SceneStatus::refused;
  282. }
  283. SceneSpawnPlan plan{};
  284. const auto status = collect(view, occurrenceRow, sceneSlotRow, plan);
  285. if (status != SceneStatus::ready) {
  286. return status;
  287. }
  288. for (std::size_t index = 0; index < plan.count; ++index) {
  289. const auto& pair = plan.pairs[index];
  290. const auto& target = staged.kind == host::ScriptableOverrideKind::squad ? pair.sourceTarget
  291. : pair.actorTarget;
  292. if (same_source(staged.target, target)) {
  293. squadRow = pair.squadRow;
  294. return SceneStatus::ready;
  295. }
  296. }
  297. return SceneStatus::targetUnavailable;
  298. }
  299. } // namespace sunrise::server::activity::activity_sdk_mission