activity_sdk_native_pack_pipeline.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. #include "activity_sdk_native_pack_pipeline.h"
  2. #include <Windows.h>
  3. #include <algorithm>
  4. #include <array>
  5. #include <cstdio>
  6. #include <cstring>
  7. #include <limits>
  8. #include <memory>
  9. #include <new>
  10. #include <span>
  11. #include <string>
  12. #include <unordered_map>
  13. #include <utility>
  14. #include <vector>
  15. #include "../../../state/activity_sdk/runtime.h"
  16. #include "activity_sdk_activity_enrichment_inventory.h"
  17. #include "activity_sdk_actor_ability_inventory.h"
  18. #include "activity_sdk_actor_rsat_inventory.h"
  19. #include "activity_sdk_authored_scene_inventory.h"
  20. #include "activity_sdk_behavior_inventory.h"
  21. #include "activity_sdk_lua_artifacts.h"
  22. #include "activity_sdk_native_pack_internal.h"
  23. #include "activity_sdk_pack_composer.h"
  24. #include "activity_sdk_policy_input_adapter.h"
  25. #include "activity_sdk_policy_inventory.h"
  26. #include "activity_sdk_squad_inventory.h"
  27. #include "activity_sdk_topology_enrichment.h"
  28. namespace sunrise::client::content::activity::sdk_generation::native_pack_pipeline {
  29. bool cancelled(CancelProbe probe, void* context) noexcept {
  30. return probe != nullptr && probe(context);
  31. }
  32. /** Adapts the checked package reader to the squad and authored-scene boundaries. */
  33. bool read_tag(void* opaque,
  34. std::uint32_t tag,
  35. std::vector<std::byte>& bytes,
  36. std::uint32_t& classId) noexcept {
  37. bytes.clear();
  38. if (opaque == nullptr) {
  39. return false;
  40. }
  41. auto& context = *static_cast<PackageContext*>(opaque);
  42. return context.source != nullptr && context.scratch != nullptr
  43. && !cancelled(context.cancel, context.cancelContext)
  44. && reader::read_tag(*context.source, *context.scratch, tag, bytes, classId);
  45. }
  46. bool read_localized_tag(void* opaque,
  47. std::uint32_t tag,
  48. std::uint32_t expectedClass,
  49. std::vector<std::byte>& bytes) noexcept {
  50. std::uint32_t classId = 0;
  51. return read_tag(opaque, tag, bytes, classId) && classId == expectedClass;
  52. }
  53. namespace {
  54. namespace format = state::activity_sdk::format;
  55. namespace activity_enrichment = activity_enrichment_inventory;
  56. namespace actor_rsat = actor_rsat_inventory;
  57. namespace authored_scene = authored_scene_inventory;
  58. namespace behaviors = behavior_inventory;
  59. namespace lua = lua_artifacts;
  60. namespace composer = pack_composer;
  61. namespace policy_adapter = policy_input_adapter;
  62. namespace policy = policy_inventory;
  63. namespace squads = squad_inventory;
  64. namespace topology_enrichment = sdk_generation::topology_enrichment;
  65. static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t));
  66. /** Appends one live tag to the caller's vector. @return False when the tag or sink is unusable. */
  67. [[nodiscard]] bool collect_tag(void* opaque, std::uint32_t tag) noexcept {
  68. if (opaque == nullptr || tag == 0 || tag == format::kAbsentIndex) {
  69. return false;
  70. }
  71. try {
  72. static_cast<std::vector<std::uint32_t>*>(opaque)->push_back(tag);
  73. return true;
  74. } catch (...) {
  75. return false;
  76. }
  77. }
  78. void report(ProgressProbe probe, void* context, Phase phase) noexcept {
  79. if (probe != nullptr) {
  80. probe(context, phase);
  81. }
  82. }
  83. /** Creates the empty lua child the publication commit renames when declarations are off. */
  84. [[nodiscard]] bool ensure_lua_directory(const wchar_t* sdkDirectory) noexcept {
  85. std::wstring path;
  86. try {
  87. path.assign(sdkDirectory);
  88. path.append(L"\\lua");
  89. } catch (...) {
  90. return false;
  91. }
  92. if (CreateDirectoryW(path.c_str(), nullptr) != FALSE) {
  93. return true;
  94. }
  95. if (GetLastError() != ERROR_ALREADY_EXISTS) {
  96. return false;
  97. }
  98. const DWORD attributes = GetFileAttributesW(path.c_str());
  99. return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
  100. }
  101. /** Builds one schema fact for every final catalog-global slot row. */
  102. [[nodiscard]] bool build_slot_schemas(const topology_inventory::Snapshot& topology,
  103. const topology_enrichment::Snapshot& enrichment,
  104. std::vector<squads::SlotSchemaFact>& output) {
  105. output.clear();
  106. if (topology.slots.size() != enrichment.slots.size()
  107. || topology.slots.size() > (std::numeric_limits<std::uint32_t>::max)()) {
  108. return false;
  109. }
  110. try {
  111. output.reserve(topology.slots.size());
  112. for (std::uint32_t index = 0; index < topology.slots.size(); ++index) {
  113. const topology_enrichment::Slot& slot = enrichment.slots[index];
  114. output.push_back({index,
  115. slot.componentClass,
  116. slot.senseSchema,
  117. slot.authSchema,
  118. (slot.flags & format::kSlotSchemaJoinExact) != 0});
  119. }
  120. return true;
  121. } catch (...) {
  122. output.clear();
  123. return false;
  124. }
  125. }
  126. /** Resolves one exact actor definition tag against the validated sorted actor section. */
  127. [[nodiscard]] bool
  128. resolve_actor(void* opaque, std::uint32_t definitionTag, std::uint32_t& output) noexcept {
  129. output = format::kAbsentIndex;
  130. if (opaque == nullptr) {
  131. return false;
  132. }
  133. const auto& actors = *static_cast<const std::vector<actor_rsat::ActorClass>*>(opaque);
  134. const auto found = std::lower_bound(actors.begin(),
  135. actors.end(),
  136. definitionTag,
  137. [](const actor_rsat::ActorClass& row, std::uint32_t tag) {
  138. return row.definitionTag < tag;
  139. });
  140. if (found == actors.end() || found->definitionTag != definitionTag
  141. || static_cast<std::size_t>(found - actors.begin()) >= format::kAbsentIndex) {
  142. return false;
  143. }
  144. output = static_cast<std::uint32_t>(found - actors.begin());
  145. return true;
  146. }
  147. /** Converts the runtime trust identity to the writer's header-only identity. */
  148. [[nodiscard]] pack::Identity
  149. pack_identity(const state::activity_sdk::identity::Expected& identity) noexcept {
  150. return {identity.sdkBuildSha256, identity.contentKeySha256, identity.logicalIrSha256};
  151. }
  152. /** Borrows all declaration-bearing final sections for one Lua artifact transaction. */
  153. [[nodiscard]] lua::Source
  154. lua_source(const state::activity_sdk::identity::Expected& identity,
  155. const composer::Storage& storage,
  156. std::span<const lua::ScenarioWorldSource> worldSources) noexcept {
  157. return {identity.sdkBuildSha256,
  158. identity.payloadSha256,
  159. identity.contentKeySha256,
  160. identity.logicalIrSha256,
  161. storage.strings,
  162. storage.activities,
  163. storage.scenarios,
  164. storage.bubbles,
  165. storage.states,
  166. storage.objects,
  167. storage.occurrences,
  168. storage.slots,
  169. storage.squads,
  170. storage.squadMembers,
  171. storage.squadAnchors,
  172. storage.authoredSceneResources,
  173. storage.authoredSceneSquadEdges,
  174. storage.taskTargets,
  175. storage.dialogueCueTexts,
  176. storage.directiveElements,
  177. storage.behaviorPrograms,
  178. storage.behaviorInputs,
  179. storage.behaviorChannelWrites,
  180. storage.behaviorOwners,
  181. storage.behaviorActivityBindings,
  182. storage.actorClasses,
  183. storage.actorMessageSchemas,
  184. storage.actorCommandDefinitions,
  185. storage.actorBehaviorProfiles,
  186. storage.simulationEventDefinitions,
  187. storage.runtimeSchemas,
  188. storage.runtimeFields,
  189. storage.runtimeTypeDefinitions,
  190. storage.sobjectRsats,
  191. storage.sobjectRsatDescriptors,
  192. storage.entityTypeDefinitions,
  193. storage.sobjectRsatFieldBindings,
  194. storage.actorStateNames,
  195. storage.actorSequenceTables,
  196. storage.actorSequenceEntries,
  197. storage.actorSequenceBindings,
  198. worldSources,
  199. storage.combatObjectiveGroups,
  200. storage.actorAbilities,
  201. storage.authoredSceneEventKeys};
  202. }
  203. } // namespace
  204. /** Returns the stable log token for each pipeline result. */
  205. const char* status_name(Status value) noexcept {
  206. switch (value) {
  207. case Status::ready:
  208. return "ready";
  209. case Status::cancelled:
  210. return "cancelled";
  211. case Status::invalidInput:
  212. return "invalid_input";
  213. case Status::activityEnrichment:
  214. return "activity_enrichment";
  215. case Status::topologyEnrichment:
  216. return "topology_enrichment";
  217. case Status::squadFacts:
  218. return "squad_facts";
  219. case Status::actorRsat:
  220. return "actor_rsat";
  221. case Status::squadLink:
  222. return "squad_link";
  223. case Status::authoredSceneFacts:
  224. return "authored_scene_facts";
  225. case Status::authoredSceneLinks:
  226. return "authored_scene_links";
  227. case Status::dialogueCues:
  228. return "dialogue_cues";
  229. case Status::authoredText:
  230. return "authored_text";
  231. case Status::behaviors:
  232. return "behaviors";
  233. case Status::policyInputs:
  234. return "policy_inputs";
  235. case Status::policy:
  236. return "policy";
  237. case Status::composition:
  238. return "composition";
  239. case Status::identity:
  240. return "identity";
  241. case Status::luaBuild:
  242. return "lua_build";
  243. case Status::publication:
  244. return "publication";
  245. case Status::luaPublication:
  246. return "lua_publication";
  247. case Status::reload:
  248. return "reload";
  249. }
  250. return "invalid_input";
  251. }
  252. /** Builds and publishes one canonical native SDK generation without a runtime reload. */
  253. Status stage(const wchar_t* sdkDirectory,
  254. const wchar_t* packPath,
  255. const reader::Source& source,
  256. const pack::Digest& sourceFingerprint,
  257. const activity_inventory::Snapshot& activities,
  258. topology_inventory::Snapshot& topology,
  259. std::span<const lua::ScenarioWorldSource> scenarioWorldSources,
  260. const external_placements::Index& externalPlacements,
  261. bool luaDeclarations,
  262. CancelProbe cancel,
  263. void* cancelContext,
  264. ProgressProbe progress,
  265. void* progressContext,
  266. Result& output) noexcept {
  267. output = {};
  268. if (sdkDirectory == nullptr || sdkDirectory[0] == L'\0' || packPath == nullptr
  269. || packPath[0] == L'\0' || source.directory.empty() || source.keys == nullptr
  270. || !state::activity_sdk::identity::valid(sourceFingerprint) || !topology.ready) {
  271. return Status::invalidInput;
  272. }
  273. if (cancelled(cancel, cancelContext)) {
  274. return Status::cancelled;
  275. }
  276. try {
  277. PackageContext packageContext{
  278. &source,
  279. std::unique_ptr<reader::Scratch>(new (std::nothrow) reader::Scratch()),
  280. cancel,
  281. cancelContext};
  282. if (packageContext.scratch == nullptr || cancelled(cancel, cancelContext)) {
  283. return cancelled(cancel, cancelContext) ? Status::cancelled
  284. : Status::activityEnrichment;
  285. }
  286. report(progress, progressContext, Phase::activityMetadata);
  287. activity_enrichment::Snapshot activityNames{};
  288. if (!activity_enrichment::build(source, *packageContext.scratch, activities, activityNames)
  289. || !activity_enrichment::apply(activityNames, topology)) {
  290. return Status::activityEnrichment;
  291. }
  292. report(progress, progressContext, Phase::worldTopology);
  293. topology_enrichment::Snapshot topologyDetails{};
  294. if (!topology_enrichment::build_generated(topology, topologyDetails)) {
  295. return Status::topologyEnrichment;
  296. }
  297. if (cancelled(cancel, cancelContext)) {
  298. return Status::cancelled;
  299. }
  300. report(progress, progressContext, Phase::squadFacts);
  301. squads::Facts squadFacts{};
  302. if (!squads::collect_facts(topology, &read_tag, &packageContext, squadFacts)
  303. || !squads::append_external_placements(topology, externalPlacements, squadFacts)) {
  304. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::squadFacts;
  305. }
  306. report(progress, progressContext, Phase::actorDefinitions);
  307. actor_rsat::Snapshot actorRows{};
  308. std::vector<std::uint32_t> installedRsats{};
  309. reader::ScanResult rsatScan{};
  310. if (!reader::scan_class(source.directory,
  311. actor_rsat::kActorRsatClass,
  312. &collect_tag,
  313. &installedRsats,
  314. rsatScan)) {
  315. return Status::actorRsat;
  316. }
  317. std::sort(installedRsats.begin(), installedRsats.end());
  318. installedRsats.erase(std::unique(installedRsats.begin(), installedRsats.end()),
  319. installedRsats.end());
  320. if (!actor_rsat::build_with_rsats(source,
  321. squadFacts.actorDefinitionTags,
  322. installedRsats,
  323. cancel,
  324. cancelContext,
  325. actorRows)) {
  326. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::actorRsat;
  327. }
  328. report(progress, progressContext, Phase::squadLinks);
  329. std::vector<squads::SlotSchemaFact> slotSchemas{};
  330. if (!build_slot_schemas(topology, topologyDetails, slotSchemas)) {
  331. return Status::squadLink;
  332. }
  333. squadFacts.slotSchemas = std::move(slotSchemas);
  334. squads::Snapshot squadRows{};
  335. if (!squads::link(
  336. topology, squadFacts, &resolve_actor, &actorRows.actorClasses, squadRows)) {
  337. return Status::squadLink;
  338. }
  339. if (!actor_ability_inventory::build(
  340. topology, squadFacts, squadRows, &read_tag, &packageContext, actorRows)) {
  341. return Status::actorRsat;
  342. }
  343. report(progress, progressContext, Phase::authoredSceneFacts);
  344. authored_scene::Facts sceneFacts{};
  345. if (!authored_scene::derive_facts(topology, squadFacts, sceneFacts)) {
  346. return Status::authoredSceneFacts;
  347. }
  348. report(progress, progressContext, Phase::authoredSceneLinks);
  349. authored_scene::Snapshot sceneRows{};
  350. if (!authored_scene::build(topology, sceneFacts, &read_tag, &packageContext, sceneRows)) {
  351. return cancelled(cancel, cancelContext) ? Status::cancelled
  352. : Status::authoredSceneLinks;
  353. }
  354. if (!attach_unresourced_scenes(topology, sceneRows, topologyDetails)) {
  355. return Status::authoredSceneLinks;
  356. }
  357. report(progress, progressContext, Phase::dialogueCues);
  358. if (!attach_dialogue_cue_counts(
  359. topology, squadFacts, packageContext, topologyDetails, sceneRows)) {
  360. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::dialogueCues;
  361. }
  362. report(progress, progressContext, Phase::authoredText);
  363. if (!attach_combat_objective_groups(topology, squadFacts, packageContext, sceneRows)
  364. || !attach_authored_text(topology, squadFacts, packageContext, sceneRows)) {
  365. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::authoredText;
  366. }
  367. if (cancelled(cancel, cancelContext)) {
  368. return Status::cancelled;
  369. }
  370. report(progress, progressContext, Phase::behaviors);
  371. behaviors::Snapshot behaviorRows{};
  372. if (!behaviors::build(
  373. source, squadFacts.actorDefinitionTags, cancel, cancelContext, behaviorRows)) {
  374. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::behaviors;
  375. }
  376. report(progress, progressContext, Phase::actionPolicies);
  377. policy_adapter::Snapshot policyInputs{};
  378. if (!policy_adapter::build(activities, topology, topologyDetails, policyInputs)) {
  379. return Status::policyInputs;
  380. }
  381. policy::Snapshot policyRows{};
  382. if (!policy::build(policyInputs.view(), policyRows)) {
  383. return Status::policy;
  384. }
  385. if (cancelled(cancel, cancelContext)) {
  386. return Status::cancelled;
  387. }
  388. report(progress, progressContext, Phase::packTables);
  389. const composer::Inputs inputs{&activities,
  390. &activityNames,
  391. &topology,
  392. &topologyDetails,
  393. &policyRows,
  394. &actorRows,
  395. &squadFacts,
  396. &squadRows,
  397. &sceneRows,
  398. &behaviorRows};
  399. composer::Storage storage{};
  400. if (!composer::compose_generated(inputs, storage)) {
  401. return Status::composition;
  402. }
  403. pack::PreparedImage image{};
  404. if (pack::prepare(storage.tables(), image) != pack::Status::ready) {
  405. return Status::composition;
  406. }
  407. const pack::Digest payload = image.payload_sha256();
  408. const std::uint64_t fileBytes = image.file_size();
  409. state::activity_sdk::identity::Expected identity{};
  410. if (!state::activity_sdk::identity::derive(sourceFingerprint, payload, identity)) {
  411. return Status::identity;
  412. }
  413. if (cancelled(cancel, cancelContext)) {
  414. return Status::cancelled;
  415. }
  416. lua::Bundle luaBundle{};
  417. if (luaDeclarations) {
  418. report(progress, progressContext, Phase::luaDeclarations);
  419. if (lua::build(lua_source(identity, storage, scenarioWorldSources), luaBundle)
  420. != lua::Status::ready) {
  421. return Status::luaBuild;
  422. }
  423. }
  424. report(progress, progressContext, Phase::outputFiles);
  425. pack::Digest written{};
  426. if (pack::publish(packPath, pack_identity(identity), std::move(image), written)
  427. != pack::Status::ready) {
  428. return Status::publication;
  429. }
  430. lua::Result luaResult{};
  431. // The commit renames a lua directory whether or not it holds declarations.
  432. if (!luaDeclarations) {
  433. if (!ensure_lua_directory(sdkDirectory)) {
  434. return Status::luaPublication;
  435. }
  436. } else {
  437. if (lua::publish_bundle(sdkDirectory, luaBundle, luaResult) != lua::Status::ready) {
  438. return Status::luaPublication;
  439. }
  440. }
  441. output.identity = identity;
  442. output.payloadSha256 = payload;
  443. output.fileBytes = fileBytes;
  444. output.luaBytes = luaResult.byteCount;
  445. output.luaFiles = luaResult.fileCount;
  446. return Status::ready;
  447. } catch (...) {
  448. output = {};
  449. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::invalidInput;
  450. }
  451. }
  452. /** Builds, publishes, and reloads one canonical native SDK generation. */
  453. Status publish(void* module,
  454. const wchar_t* sdkDirectory,
  455. const wchar_t* packPath,
  456. const reader::Source& source,
  457. const pack::Digest& sourceFingerprint,
  458. const activity_inventory::Snapshot& activities,
  459. topology_inventory::Snapshot& topology,
  460. const external_placements::Index& externalPlacements,
  461. bool luaDeclarations,
  462. CancelProbe cancel,
  463. void* cancelContext,
  464. Result& output) noexcept {
  465. output = {};
  466. if (module == nullptr) {
  467. return Status::invalidInput;
  468. }
  469. const Status staged = stage(sdkDirectory,
  470. packPath,
  471. source,
  472. sourceFingerprint,
  473. activities,
  474. topology,
  475. {},
  476. externalPlacements,
  477. luaDeclarations,
  478. cancel,
  479. cancelContext,
  480. nullptr,
  481. nullptr,
  482. output);
  483. if (staged != Status::ready) {
  484. return staged;
  485. }
  486. if (!state::activity_sdk::reload(module, output.identity)) {
  487. output = {};
  488. return Status::reload;
  489. }
  490. // A reload replaces the catalog the wire descriptors borrow, so they are rebuilt here.
  491. return Status::ready;
  492. }
  493. } // namespace sunrise::client::content::activity::sdk_generation::native_pack_pipeline