| 579 | // ============================================================================= |
| 580 | |
| 581 | TSharedPtr<FJsonObject> FURLabRpcDispatcher::HandleHello(const TSharedPtr<FJsonObject>& Req) |
| 582 | { |
| 583 | // Editor-time clients connect before PIE to call import_xml / spawn_*; |
| 584 | // BuildHandshakePayload sets manager_present=false so the bridge |
| 585 | // skips PIE-only follow-ups (set_mode auto-promote, streaming SUBs). |
| 586 | AAMjManager* Mgr = OwnerMgr.Get(); |
| 587 | |
| 588 | // ActiveSessionId is a non-atomic FString — guard the write so a |
| 589 | // concurrent ValidateSession on another transport thread reads a |
| 590 | // stable value. Hello is the only writer. |
| 591 | FString NewSessionId = FGuid::NewGuid().ToString( |
| 592 | EGuidFormats::DigitsWithHyphens) |
| 593 | .ToLower(); |
| 594 | { |
| 595 | FScopeLock Lock(&DispatchMutex); |
| 596 | ActiveSessionId = NewSessionId; |
| 597 | } |
| 598 | |
| 599 | // Reset to msgpack default on each handshake; per-session opt-in via encoding=json. |
| 600 | bUseJsonEncoding.store(false, std::memory_order_release); |
| 601 | FString Encoding; |
| 602 | if (Req.IsValid() && Req->TryGetStringField(TEXT("encoding"), Encoding)) |
| 603 | { |
| 604 | const bool bJson = Encoding.Equals(TEXT("json"), ESearchCase::IgnoreCase); |
| 605 | bUseJsonEncoding.store(bJson, std::memory_order_release); |
| 606 | } |
| 607 | |
| 608 | // Observation level handshake. Atomic store — read on the physics |
| 609 | // thread without locking. |
| 610 | FString ObsLevel; |
| 611 | if (Req.IsValid() && Req->TryGetStringField(TEXT("observations"), ObsLevel)) |
| 612 | { |
| 613 | EObservationLevel Lvl = EObservationLevel::Standard; |
| 614 | if (ObsLevel.Equals(TEXT("minimal"), ESearchCase::IgnoreCase)) |
| 615 | Lvl = EObservationLevel::Minimal; |
| 616 | else if (ObsLevel.Equals(TEXT("full"), ESearchCase::IgnoreCase)) |
| 617 | Lvl = EObservationLevel::Full; |
| 618 | ActiveObservationLevel.store(Lvl, std::memory_order_release); |
| 619 | } |
| 620 | StepCounter.store(0, std::memory_order_relaxed); |
| 621 | |
| 622 | bool bIncludeAssets = false; |
| 623 | if (Req.IsValid()) |
| 624 | Req->TryGetBoolField(TEXT("include_assets"), bIncludeAssets); |
| 625 | |
| 626 | return BuildHandshakePayload(Mgr, NewSessionId, URLabVersion, bIncludeAssets); |
| 627 | } |
| 628 | |
| 629 | TSharedPtr<FJsonObject> FURLabRpcDispatcher::BuildHandshakePayload(AAMjManager* Manager, |
| 630 | const FString& SessionId, |