| 3010 | // ============================================================================= |
| 3011 | |
| 3012 | TSharedPtr<FJsonObject> FURLabRpcDispatcher::HandleRecording(const FString& Op, const TSharedPtr<FJsonObject>& Req) |
| 3013 | { |
| 3014 | AAMjManager* Mgr = OwnerMgr.Get(); |
| 3015 | if (!Mgr) |
| 3016 | return MakeError(TEXT("not_ready"), TEXT("Manager missing")); |
| 3017 | // Use the game-thread-cached pointer; TActorIterator from this worker |
| 3018 | // thread would assert IsInGameThread() and crash. |
| 3019 | AMjReplayManager* RM = CachedReplayManager.Get(); |
| 3020 | if (!RM) |
| 3021 | return MakeError(TEXT("not_ready"), TEXT("AMjReplayManager not present in scene")); |
| 3022 | |
| 3023 | if (Op.Equals(TEXT("recording_start"))) |
| 3024 | { |
| 3025 | if (RM->bIsRecording) |
| 3026 | return MakeError(TEXT("recording_already_active"), TEXT("Recording already active")); |
| 3027 | double MaxDur = 0.0; |
| 3028 | if (Req->TryGetNumberField(TEXT("max_duration_s"), MaxDur)) |
| 3029 | RM->MaxRecordDuration = (float)MaxDur; |
| 3030 | else |
| 3031 | RM->MaxRecordDuration = FLT_MAX; |
| 3032 | RM->StartRecording(); |
| 3033 | TSharedPtr<FJsonObject> Reply = MakeShared<FJsonObject>(); |
| 3034 | Reply->SetStringField(TEXT("op"), TEXT("recording_start_ok")); |
| 3035 | Reply->SetStringField(TEXT("name"), AMjReplayManager::LiveSessionName); |
| 3036 | Reply->SetNumberField(TEXT("max_duration_s"), RM->MaxRecordDuration); |
| 3037 | return Reply; |
| 3038 | } |
| 3039 | if (Op.Equals(TEXT("recording_stop"))) |
| 3040 | { |
| 3041 | if (!RM->bIsRecording) |
| 3042 | return MakeError(TEXT("recording_not_active"), TEXT("Recording is not active")); |
| 3043 | RM->StopRecording(); |
| 3044 | TSharedPtr<FJsonObject> Reply = MakeShared<FJsonObject>(); |
| 3045 | Reply->SetStringField(TEXT("op"), TEXT("recording_stop_ok")); |
| 3046 | Reply->SetStringField(TEXT("name"), AMjReplayManager::LiveSessionName); |
| 3047 | // Populate the summary fields the Python client maps to |
| 3048 | // RecordingSummary. Recording has been stopped (StopRecording |
| 3049 | // above set bIsRecording=false) so the OnPostStep hook isn't |
| 3050 | // mutating Frames in parallel. |
| 3051 | Reply->SetNumberField(TEXT("frame_count"), static_cast<double>(RM->GetLiveFrameCount())); |
| 3052 | Reply->SetNumberField(TEXT("sim_duration_s"), RM->GetLiveSimDurationS()); |
| 3053 | return Reply; |
| 3054 | } |
| 3055 | if (Op.Equals(TEXT("recording_save"))) |
| 3056 | { |
| 3057 | FString Path; |
| 3058 | Req->TryGetStringField(TEXT("path"), Path); |
| 3059 | FString FileName = Path.IsEmpty() ? TEXT("recording.json") : FPaths::GetCleanFilename(Path); |
| 3060 | if (Path.IsEmpty()) |
| 3061 | Path = FileName; |
| 3062 | bool bOk = RM->SaveRecordingToFile(FileName); |
| 3063 | // ResolveReplayPath now matches the manager's |
| 3064 | // ProjectSavedDir/URLab/Replays/ output dir, so the absolute_path |
| 3065 | // returned to the client points at the file the manager actually wrote. |
| 3066 | FString Abs = ResolveReplayPath(Path); |
| 3067 | TSharedPtr<FJsonObject> Reply = MakeShared<FJsonObject>(); |
| 3068 | Reply->SetStringField(TEXT("op"), bOk ? TEXT("recording_save_ok") : TEXT("error")); |
| 3069 | Reply->SetStringField(TEXT("absolute_path"), Abs); |
nothing calls this directly
no test coverage detected