activity_sdk_native_pack_pipeline.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  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. }
  202. } // namespace
  203. /** Returns the stable log token for each pipeline result. */
  204. const char* status_name(Status value) noexcept {
  205. switch (value) {
  206. case Status::ready:
  207. return "ready";
  208. case Status::cancelled:
  209. return "cancelled";
  210. case Status::invalidInput:
  211. return "invalid_input";
  212. case Status::activityEnrichment:
  213. return "activity_enrichment";
  214. case Status::topologyEnrichment:
  215. return "topology_enrichment";
  216. case Status::squadFacts:
  217. return "squad_facts";
  218. case Status::actorRsat:
  219. return "actor_rsat";
  220. case Status::squadLink:
  221. return "squad_link";
  222. case Status::authoredSceneFacts:
  223. return "authored_scene_facts";
  224. case Status::authoredSceneLinks:
  225. return "authored_scene_links";
  226. case Status::dialogueCues:
  227. return "dialogue_cues";
  228. case Status::authoredText:
  229. return "authored_text";
  230. case Status::behaviors:
  231. return "behaviors";
  232. case Status::policyInputs:
  233. return "policy_inputs";
  234. case Status::policy:
  235. return "policy";
  236. case Status::composition:
  237. return "composition";
  238. case Status::identity:
  239. return "identity";
  240. case Status::luaBuild:
  241. return "lua_build";
  242. case Status::publication:
  243. return "publication";
  244. case Status::luaPublication:
  245. return "lua_publication";
  246. case Status::reload:
  247. return "reload";
  248. }
  249. return "invalid_input";
  250. }
  251. /** Builds and publishes one canonical native SDK generation without a runtime reload. */
  252. Status stage(const wchar_t* sdkDirectory,
  253. const wchar_t* packPath,
  254. const reader::Source& source,
  255. const pack::Digest& sourceFingerprint,
  256. const activity_inventory::Snapshot& activities,
  257. topology_inventory::Snapshot& topology,
  258. std::span<const lua::ScenarioWorldSource> scenarioWorldSources,
  259. const external_placements::Index& externalPlacements,
  260. bool luaDeclarations,
  261. CancelProbe cancel,
  262. void* cancelContext,
  263. ProgressProbe progress,
  264. void* progressContext,
  265. Result& output) noexcept {
  266. output = {};
  267. if (sdkDirectory == nullptr || sdkDirectory[0] == L'\0' || packPath == nullptr
  268. || packPath[0] == L'\0' || source.directory.empty() || source.keys == nullptr
  269. || !state::activity_sdk::identity::valid(sourceFingerprint) || !topology.ready) {
  270. return Status::invalidInput;
  271. }
  272. if (cancelled(cancel, cancelContext)) {
  273. return Status::cancelled;
  274. }
  275. try {
  276. PackageContext packageContext{
  277. &source,
  278. std::unique_ptr<reader::Scratch>(new (std::nothrow) reader::Scratch()),
  279. cancel,
  280. cancelContext};
  281. if (packageContext.scratch == nullptr || cancelled(cancel, cancelContext)) {
  282. return cancelled(cancel, cancelContext) ? Status::cancelled
  283. : Status::activityEnrichment;
  284. }
  285. report(progress, progressContext, Phase::activityMetadata);
  286. activity_enrichment::Snapshot activityNames{};
  287. if (!activity_enrichment::build(source, *packageContext.scratch, activities, activityNames)
  288. || !activity_enrichment::apply(activityNames, topology)) {
  289. return Status::activityEnrichment;
  290. }
  291. report(progress, progressContext, Phase::worldTopology);
  292. topology_enrichment::Snapshot topologyDetails{};
  293. if (!topology_enrichment::build_generated(topology, topologyDetails)) {
  294. return Status::topologyEnrichment;
  295. }
  296. if (cancelled(cancel, cancelContext)) {
  297. return Status::cancelled;
  298. }
  299. report(progress, progressContext, Phase::squadFacts);
  300. squads::Facts squadFacts{};
  301. if (!squads::collect_facts(topology, &read_tag, &packageContext, squadFacts)
  302. || !squads::append_external_placements(topology, externalPlacements, squadFacts)) {
  303. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::squadFacts;
  304. }
  305. report(progress, progressContext, Phase::actorDefinitions);
  306. actor_rsat::Snapshot actorRows{};
  307. std::vector<std::uint32_t> installedRsats{};
  308. reader::ScanResult rsatScan{};
  309. if (!reader::scan_class(source.directory,
  310. actor_rsat::kActorRsatClass,
  311. &collect_tag,
  312. &installedRsats,
  313. rsatScan)) {
  314. return Status::actorRsat;
  315. }
  316. std::sort(installedRsats.begin(), installedRsats.end());
  317. installedRsats.erase(std::unique(installedRsats.begin(), installedRsats.end()),
  318. installedRsats.end());
  319. if (!actor_rsat::build_with_rsats(source,
  320. squadFacts.actorDefinitionTags,
  321. installedRsats,
  322. cancel,
  323. cancelContext,
  324. actorRows)) {
  325. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::actorRsat;
  326. }
  327. report(progress, progressContext, Phase::squadLinks);
  328. std::vector<squads::SlotSchemaFact> slotSchemas{};
  329. if (!build_slot_schemas(topology, topologyDetails, slotSchemas)) {
  330. return Status::squadLink;
  331. }
  332. squadFacts.slotSchemas = std::move(slotSchemas);
  333. squads::Snapshot squadRows{};
  334. if (!squads::link(
  335. topology, squadFacts, &resolve_actor, &actorRows.actorClasses, squadRows)) {
  336. return Status::squadLink;
  337. }
  338. if (!actor_ability_inventory::build(
  339. topology, squadFacts, squadRows, &read_tag, &packageContext, actorRows)) {
  340. return Status::actorRsat;
  341. }
  342. report(progress, progressContext, Phase::authoredSceneFacts);
  343. authored_scene::Facts sceneFacts{};
  344. if (!authored_scene::derive_facts(topology, squadFacts, sceneFacts)) {
  345. return Status::authoredSceneFacts;
  346. }
  347. report(progress, progressContext, Phase::authoredSceneLinks);
  348. authored_scene::Snapshot sceneRows{};
  349. if (!authored_scene::build(topology, sceneFacts, &read_tag, &packageContext, sceneRows)) {
  350. return cancelled(cancel, cancelContext) ? Status::cancelled
  351. : Status::authoredSceneLinks;
  352. }
  353. if (!attach_unresourced_scenes(topology, sceneRows, topologyDetails)) {
  354. return Status::authoredSceneLinks;
  355. }
  356. report(progress, progressContext, Phase::dialogueCues);
  357. if (!attach_dialogue_cue_counts(
  358. topology, squadFacts, packageContext, topologyDetails, sceneRows)) {
  359. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::dialogueCues;
  360. }
  361. report(progress, progressContext, Phase::authoredText);
  362. if (!attach_combat_objective_groups(topology, squadFacts, packageContext, sceneRows)
  363. || !attach_authored_text(topology, squadFacts, packageContext, sceneRows)) {
  364. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::authoredText;
  365. }
  366. if (cancelled(cancel, cancelContext)) {
  367. return Status::cancelled;
  368. }
  369. report(progress, progressContext, Phase::behaviors);
  370. behaviors::Snapshot behaviorRows{};
  371. if (!behaviors::build(
  372. source, squadFacts.actorDefinitionTags, cancel, cancelContext, behaviorRows)) {
  373. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::behaviors;
  374. }
  375. report(progress, progressContext, Phase::actionPolicies);
  376. policy_adapter::Snapshot policyInputs{};
  377. if (!policy_adapter::build(activities, topology, topologyDetails, policyInputs)) {
  378. return Status::policyInputs;
  379. }
  380. policy::Snapshot policyRows{};
  381. if (!policy::build(policyInputs.view(), policyRows)) {
  382. return Status::policy;
  383. }
  384. if (cancelled(cancel, cancelContext)) {
  385. return Status::cancelled;
  386. }
  387. report(progress, progressContext, Phase::packTables);
  388. const composer::Inputs inputs{&activities,
  389. &activityNames,
  390. &topology,
  391. &topologyDetails,
  392. &policyRows,
  393. &actorRows,
  394. &squadFacts,
  395. &squadRows,
  396. &sceneRows,
  397. &behaviorRows};
  398. composer::Storage storage{};
  399. if (!composer::compose_generated(inputs, storage)) {
  400. return Status::composition;
  401. }
  402. pack::PreparedImage image{};
  403. if (pack::prepare(storage.tables(), image) != pack::Status::ready) {
  404. return Status::composition;
  405. }
  406. const pack::Digest payload = image.payload_sha256();
  407. const std::uint64_t fileBytes = image.file_size();
  408. state::activity_sdk::identity::Expected identity{};
  409. if (!state::activity_sdk::identity::derive(sourceFingerprint, payload, identity)) {
  410. return Status::identity;
  411. }
  412. if (cancelled(cancel, cancelContext)) {
  413. return Status::cancelled;
  414. }
  415. lua::Bundle luaBundle{};
  416. if (luaDeclarations) {
  417. report(progress, progressContext, Phase::luaDeclarations);
  418. if (lua::build(lua_source(identity, storage, scenarioWorldSources), luaBundle)
  419. != lua::Status::ready) {
  420. return Status::luaBuild;
  421. }
  422. }
  423. report(progress, progressContext, Phase::outputFiles);
  424. pack::Digest written{};
  425. if (pack::publish(packPath, pack_identity(identity), std::move(image), written)
  426. != pack::Status::ready) {
  427. return Status::publication;
  428. }
  429. lua::Result luaResult{};
  430. // The commit renames a lua directory whether or not it holds declarations.
  431. if (!luaDeclarations) {
  432. if (!ensure_lua_directory(sdkDirectory)) {
  433. return Status::luaPublication;
  434. }
  435. } else {
  436. if (lua::publish_bundle(sdkDirectory, luaBundle, luaResult) != lua::Status::ready) {
  437. return Status::luaPublication;
  438. }
  439. }
  440. output.identity = identity;
  441. output.payloadSha256 = payload;
  442. output.fileBytes = fileBytes;
  443. output.luaBytes = luaResult.byteCount;
  444. output.luaFiles = luaResult.fileCount;
  445. return Status::ready;
  446. } catch (...) {
  447. output = {};
  448. return cancelled(cancel, cancelContext) ? Status::cancelled : Status::invalidInput;
  449. }
  450. }
  451. /** Builds, publishes, and reloads one canonical native SDK generation. */
  452. Status publish(void* module,
  453. const wchar_t* sdkDirectory,
  454. const wchar_t* packPath,
  455. const reader::Source& source,
  456. const pack::Digest& sourceFingerprint,
  457. const activity_inventory::Snapshot& activities,
  458. topology_inventory::Snapshot& topology,
  459. const external_placements::Index& externalPlacements,
  460. bool luaDeclarations,
  461. CancelProbe cancel,
  462. void* cancelContext,
  463. Result& output) noexcept {
  464. output = {};
  465. if (module == nullptr) {
  466. return Status::invalidInput;
  467. }
  468. const Status staged = stage(sdkDirectory,
  469. packPath,
  470. source,
  471. sourceFingerprint,
  472. activities,
  473. topology,
  474. {},
  475. externalPlacements,
  476. luaDeclarations,
  477. cancel,
  478. cancelContext,
  479. nullptr,
  480. nullptr,
  481. output);
  482. if (staged != Status::ready) {
  483. return staged;
  484. }
  485. if (!state::activity_sdk::reload(module, output.identity)) {
  486. output = {};
  487. return Status::reload;
  488. }
  489. // A reload replaces the catalog the wire descriptors borrow, so they are rebuilt here.
  490. return Status::ready;
  491. }
  492. } // namespace sunrise::client::content::activity::sdk_generation::native_pack_pipeline