--- a/rts/Game/WebbarCloakEvents.h +++ b/rts/Game/WebbarCloakEvents.h @@ -9,7 +9,7 @@ // Read-only presentation history, outside all simulation and checksum state. // Called at the native cloak events, using BAR GL4's signed-frame convention. // Destruction retires the entry before a unit ID can be reused. Only the -// owner-filtered WBC3 publisher can expose this history to the browser. +// owner-filtered WBC publisher can expose this history to the browser. namespace webbarCloakEvents { inline std::unordered_map frames; @@ -18,6 +18,19 @@ struct Pending { Event event; int team; }; inline std::vector pending; inline uint32_t nextID = 0, dropped = 0; +inline std::unordered_map ownershipLifetimes; +inline uint32_t nextLifetime = 0; + +// Called only after the publisher's local-owner and record-limit filters. +// Hidden unit creation/transfers cannot advance this presentation counter. +inline uint32_t OwnershipLifetime(int unitID) +{ + const auto it = ownershipLifetimes.find(unitID); + if (it != ownershipLifetimes.end()) return it->second; + if (++nextLifetime == 0) ++nextLifetime; + ownershipLifetimes.emplace(unitID, nextLifetime); + return nextLifetime; +} inline void Record(int unitID, int frame, bool cloaked, int team, int localTeam) { @@ -30,12 +43,18 @@ pending.push_back({{nextID, uint32_t(unitID), uint32_t(frame), uint32_t(cloaked)}, team}); } -inline void Forget(int unitID) +inline void OwnershipChanged(int unitID) { - frames.erase(unitID); + ownershipLifetimes.erase(unitID); pending.erase(std::remove_if(pending.begin(), pending.end(), [unitID](const Pending& entry) { return entry.event.unit == uint32_t(unitID); }), pending.end()); +} + +inline void Forget(int unitID) +{ + OwnershipChanged(unitID); + frames.erase(unitID); } inline int Frame(int unitID) --- a/rts/Game/WebbarCloak.h +++ b/rts/Game/WebbarCloak.h @@ -12,8 +12,9 @@ uint32_t unit, flags; float idleCost, movingCost, speed, decloakDistance; int32_t transitionFrame; + uint32_t ownershipLifetime; }; - static_assert(sizeof(Record) == 28); + static_assert(sizeof(Record) == 32); std::vector records; uint32_t dropped = 0; for (const CUnit* unit: unitHandler.GetActiveUnits()) { @@ -33,7 +34,7 @@ (uint32_t(rule("areacloaked") == 1) << 6) | (uint32_t(unit->IsStunned()) << 7) | (uint32_t(unit->beingBuilt) << 8) | (uint32_t(unit->stealth) << 9) | (uint32_t(unit->sonarStealth) << 10); records.push_back({uint32_t(unit->id), flags, std::max(0.0f, def->cloakCost), std::max(0.0f, def->cloakCostMoving), - unit->speed.w, std::max(0.0f, unit->decloakDistance), webbarCloakEvents::Frame(unit->id)}); + unit->speed.w, std::max(0.0f, unit->decloakDistance), webbarCloakEvents::Frame(unit->id), webbarCloakEvents::OwnershipLifetime(unit->id)}); } std::vector events; for (const auto& entry: webbarCloakEvents::pending) { @@ -43,7 +44,7 @@ events.push_back(entry.event); } webbarCloakEvents::pending.clear(); - const uint32_t header[] = {0x33434257, uint32_t(gs->frameNum), uint32_t(records.size()), dropped, + const uint32_t header[] = {0x34434257, uint32_t(gs->frameNum), uint32_t(records.size()), dropped, uint32_t(events.size()), webbarCloakEvents::dropped}; webbarCloakEvents::dropped = 0; std::vector packet(sizeof(header) + records.size() * sizeof(Record) + events.size() * sizeof(webbarCloakEvents::Event)); --- a/rts/Sim/Units/Unit.cpp +++ b/rts/Sim/Units/Unit.cpp @@ -1548,6 +1548,14 @@ } const int oldteam = team; +#ifdef __EMSCRIPTEN__ + // Retire presentation ownership even if the unit leaves and returns before + // the next browser publication. Native simulation and cloak phase are intact. + if (oldteam != newteam) { + webbarCloakEvents::OwnershipChanged(id); + webbarWorkAudio::Forget(id); + } +#endif selectedUnitsHandler.RemoveUnit(this); SetGroup(nullptr);