bap_connection_publication.cpp 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. #include "bap_connection_publication.h"
  2. #include <Windows.h>
  3. namespace sunrise::server::bap::encrypted {
  4. namespace {
  5. /** Measured delay before the second Family-4 snapshot. */
  6. constexpr std::uint64_t kFamily4RepushDelayMs = 400;
  7. /** The banner pair lands the same unsolicited way and hits the same record-state race. */
  8. constexpr std::uint64_t kBannerRepushDelayMs = 400;
  9. /**
  10. * Delay before the ability-icon re-derivation owed by a subclass selection.
  11. * The Client content-extraction pump that rebuilds the invalidated ability buckets runs on the
  12. * next few RunCallbacks pumps, well under this window.
  13. */
  14. constexpr std::uint64_t kAbilityRefreshDelayMs = 500;
  15. /**
  16. * How long the roster keeps its faster cadence after a load starts.
  17. * The slice-set load step costs 9.2 to 14.1 s, so this covers it.
  18. */
  19. constexpr std::uint64_t kTransitionWindowMs = 15'000;
  20. } // namespace
  21. /** Captures the connection fields one service outcome carries. */
  22. ConnectionFields connection_fields(const ServiceOutcome& outcome) noexcept {
  23. ConnectionFields fields{};
  24. const auto* plan = transaction_if<activity_message::ActivityPlan>(outcome);
  25. if (plan == nullptr) {
  26. return fields;
  27. }
  28. if (plan->delivery == activity_message::Delivery::joinNotifications) {
  29. fields.joinMemberKey = plan->entitySlotMutation.memberKey;
  30. fields.joinCharacterSoid = plan->joinCharacterSoid;
  31. fields.joinsActivity = true;
  32. }
  33. // The initial load is a transition too, and its token does not arrive for several seconds.
  34. fields.opensTransitionWindow =
  35. plan->delivery == activity_message::Delivery::joinNotifications || plan->transitionStarted;
  36. if (plan->mutationDomain == activity_message::MutationDomain::patchEpoch) {
  37. fields.patchEpoch = plan->patchEpoch;
  38. fields.retainsPatchEpoch = true;
  39. }
  40. return fields;
  41. }
  42. /** Publishes the captured connection fields after a successful commit. */
  43. void publish_connection_fields(Session& session,
  44. const transactions::Publication& publication,
  45. const ConnectionFields& fields) noexcept {
  46. if (publication.hasActivitySessionBinding) {
  47. // Only the first binding decides the link's kind. A link that allocated its own session
  48. // also joins later, and that join must not reclassify it.
  49. if (session.activitySessionId == 0 && publication.activitySessionFromJoin) {
  50. session.activityJoinedForeignSession = true;
  51. }
  52. session.activitySessionId = publication.activitySessionId;
  53. }
  54. if (fields.joinMemberKey != 0) {
  55. session.activityMemberKey = fields.joinMemberKey;
  56. }
  57. if (fields.joinCharacterSoid != 0) {
  58. session.activityCharacterSoid = fields.joinCharacterSoid;
  59. }
  60. if (fields.retainsPatchEpoch) {
  61. session.activityPatchEpoch = fields.patchEpoch;
  62. session.activityPatchEpochSeen = true;
  63. }
  64. if (fields.opensTransitionWindow) {
  65. session.activityTransitionUntilTick = GetTickCount64() + kTransitionWindowMs;
  66. }
  67. // A join resets the client's roster container, so the warm-up is re-armed. Its unconditional
  68. // state-byte moves make the client deactivate and rebuild every roster-owned object, and the
  69. // player object binds to the published membership only on that rebuild.
  70. if (fields.joinsActivity) {
  71. session.activityRosterSends = 0;
  72. session.activityRosterGroups = 0;
  73. }
  74. }
  75. /** Arms the owed Family-4 and banner re-pushes when the queuez publication asks for them. */
  76. void arm_repushes(Session& session, const queuez::StagedPublication& queuezPublication) noexcept {
  77. const std::uint64_t now = GetTickCount64();
  78. if (queuezPublication.armsAbilityRefresh) {
  79. session.abilityRefreshDueTick = now + kAbilityRefreshDelayMs;
  80. session.abilityRefreshArmed = true;
  81. }
  82. if (queuezPublication.armsFamily4Repush && queuezPublication.family4RepushRoot != 0) {
  83. session.family4RepushDueTick = now + kFamily4RepushDelayMs;
  84. session.family4RepushRoot = queuezPublication.family4RepushRoot;
  85. session.family4RepushArmed = true;
  86. }
  87. // Armed on its own signal, not on family four's. Family zero re-subscribes on every record
  88. // cycle, and each subscribe needs a delayed copy because the immediate answer arrives too soon.
  89. if (queuezPublication.armsBannerRepush && queuezPublication.bannerRepushRoot != 0) {
  90. session.bannerRepushDueTick = now + kBannerRepushDelayMs;
  91. session.bannerRepushRoot = queuezPublication.bannerRepushRoot;
  92. session.bannerRepushArmed = true;
  93. }
  94. }
  95. } // namespace sunrise::server::bap::encrypted