mission_script_lua_population_api.cpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #include <algorithm>
  2. #include <array>
  3. #include <cstdint>
  4. #include "mission_script_lua_internal.h"
  5. #include "mission_script_lua_resolve.h"
  6. #include "mission_script_lua_types.h"
  7. namespace sunrise::server::activity::mission::lua_vm::detail {
  8. namespace {
  9. /** One optional mission cohort holds at most 64 distinct authored squads. */
  10. constexpr std::size_t kCohortCapacity = 64;
  11. constexpr char kCohortMetatable[] = "sunrise.mission.cohort";
  12. /** The handle stores SDK rows, while native Mission State owns all population facts. */
  13. struct CohortHandle final {
  14. std::array<std::uint32_t, kCohortCapacity> rows{};
  15. std::uint8_t count{};
  16. };
  17. /** Aggregates only the current attempt's exact transported squad generations. */
  18. int cohort_index(lua_State* state) {
  19. const auto* handle =
  20. static_cast<const CohortHandle*>(luaL_checkudata(state, 1, kCohortMetatable));
  21. const auto* impl = impl_from_state(state);
  22. std::int64_t alive = 0;
  23. bool known = true;
  24. bool full = true;
  25. for (std::size_t index = 0; index < handle->count; ++index) {
  26. SquadDefinition squad{};
  27. if (!current_squad(state, SquadHandle{handle->rows[index]}, squad)) {
  28. return luaL_error(state, "cohort contains a stale squad");
  29. }
  30. const auto pending = [&squad, impl](const auto& intent) noexcept {
  31. const bool scene = intent.kind == IntentKind::activateAuthoredScene && intent.active
  32. && intent.burstRowCount <= intent.burstRows.size()
  33. && std::find(intent.burstRows.begin(),
  34. intent.burstRows.begin() + intent.burstRowCount,
  35. squad.nativeRow)
  36. != intent.burstRows.begin() + intent.burstRowCount;
  37. const bool source =
  38. scene
  39. || (intent.kind == IntentKind::placeSquad
  40. ? intent.firstRow == squad.nativeRow
  41. : intent.kind == IntentKind::runActorProgram && intent.active
  42. && intent.secondRow == squad.nativeRow);
  43. return source && intent.attemptGeneration == impl->attempt.generation;
  44. };
  45. const auto& candidate = active_frame(state).candidate.intents;
  46. const auto outbox = std::span(impl->outbox).subspan(impl->outboxRead);
  47. if (std::any_of(candidate.begin(), candidate.end(), pending)
  48. || std::any_of(outbox.begin(), outbox.end(), pending)) {
  49. known = full = false;
  50. continue;
  51. }
  52. const auto found =
  53. std::find_if(impl->squadPopulations.begin(),
  54. impl->squadPopulations.end(),
  55. [&squad, impl](const auto& population) noexcept {
  56. return population.squadRow == squad.nativeRow
  57. && population.attemptGeneration == impl->attempt.generation;
  58. });
  59. if (found == impl->squadPopulations.end() || !found->generationKnown
  60. || !found->aliveKnown) {
  61. known = full = false;
  62. continue;
  63. }
  64. alive += found->alive;
  65. // Members ever created also prove a full spawn, so an early death cannot hide one.
  66. full = full && found->expectedAlive > 0
  67. && (found->maximumObservedAlive >= found->expectedAlive
  68. || found->maximumObservedCreated >= found->expectedAlive);
  69. }
  70. const std::string_view key = lua_string_view(state, 2);
  71. if (key == "alive_count") {
  72. if (known) {
  73. lua_pushinteger(state, alive);
  74. } else {
  75. lua_pushnil(state);
  76. }
  77. } else if (key == "observed_full") {
  78. lua_pushboolean(state, known && full);
  79. } else if (key == "cleared") {
  80. lua_pushboolean(state, known && full && alive == 0);
  81. } else if (key == "size") {
  82. lua_pushinteger(state, handle->count);
  83. } else {
  84. lua_pushnil(state);
  85. }
  86. return 1;
  87. }
  88. } // namespace
  89. /** Resolves a nonempty group of SDK squads without exposing count bookkeeping to Lua. */
  90. int context_cohort(lua_State* state) {
  91. static_cast<void>(luaL_checkudata(state, 1, kContextMetatable));
  92. // Only these named arguments belong to this API.
  93. static constexpr std::array<std::string_view, 1> kDeclared{"squads"};
  94. refuse_unknown_arguments(state, kDeclared);
  95. lua_getfield(state, 2, "squads");
  96. luaL_checktype(state, -1, LUA_TTABLE);
  97. const int list = lua_gettop(state);
  98. const std::size_t count = lua_rawlen(state, list);
  99. if (count == 0 || count > kCohortCapacity) {
  100. return luaL_error(state, "cohort needs one to 64 squads");
  101. }
  102. std::size_t entries = 0;
  103. lua_pushnil(state);
  104. while (lua_next(state, list) != 0) {
  105. if (!lua_isinteger(state, -2) || lua_tointeger(state, -2) < 1
  106. || static_cast<std::uint64_t>(lua_tointeger(state, -2)) > count) {
  107. return luaL_error(state, "cohort squads must be a dense list");
  108. }
  109. ++entries;
  110. lua_pop(state, 1);
  111. }
  112. if (entries != count) {
  113. return luaL_error(state, "cohort squads must be a dense list");
  114. }
  115. CohortHandle cohort{};
  116. for (std::size_t index = 0; index < count; ++index) {
  117. lua_rawgeti(state, list, static_cast<lua_Integer>(index + 1));
  118. const auto* handle =
  119. static_cast<const SquadHandle*>(luaL_testudata(state, -1, kSquadMetatable));
  120. SquadDefinition squad{};
  121. if (!(handle != nullptr ? current_squad(state, *handle, squad)
  122. : resolve_squad(state, -1, squad))) {
  123. return luaL_error(state, "cohort requires an SDK squad declaration");
  124. }
  125. lua_pop(state, 1);
  126. const auto previous = std::span(cohort.rows).first(index);
  127. if (std::find(previous.begin(), previous.end(), squad.localRow) != previous.end()) {
  128. return luaL_error(state, "cohort repeats a squad");
  129. }
  130. cohort.rows[index] = squad.localRow;
  131. }
  132. cohort.count = static_cast<std::uint8_t>(count);
  133. push_handle(state, kCohortMetatable, cohort);
  134. return 1;
  135. }
  136. void register_population_metatables(lua_State* state) {
  137. register_metatable(state, kCohortMetatable, &cohort_index);
  138. }
  139. } // namespace sunrise::server::activity::mission::lua_vm::detail