ue-mcp 1.0.37 → 1.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +1 -1
- package/dist/pie/schema.d.ts +10 -10
- package/dist/tool-counts.json +6 -6
- package/dist/tools/animation.js +3 -0
- package/dist/tools/animation.js.map +1 -1
- package/dist/tools/gameplay.js +21 -0
- package/dist/tools/gameplay.js.map +1 -1
- package/dist/ue-mcp.default.yml +72 -0
- package/package.json +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerUtils.h +13 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +2 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +1 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +127 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +11 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +13 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_PIEObserve.cpp +495 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_PIERecord.cpp +0 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +236 -92
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/MCPObservationProfile.h +59 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/PIEInputRecorder.cpp +0 -7
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/PIEInputReplayer.cpp +0 -8
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/PIEObserver.cpp +379 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/PIEObserver.h +109 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/PIESequenceFormat.h +5 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +5 -1
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
#include "PIEObserver.h"
|
|
2
|
+
#include "MCPObservationProfile.h"
|
|
3
|
+
#include "PIESequenceFormat.h"
|
|
4
|
+
#include "UE_MCP_BridgeModule.h"
|
|
5
|
+
#include "Editor.h"
|
|
6
|
+
#include "Engine/World.h"
|
|
7
|
+
#include "HAL/FileManager.h"
|
|
8
|
+
#include "Misc/FileHelper.h"
|
|
9
|
+
#include "Misc/Paths.h"
|
|
10
|
+
|
|
11
|
+
namespace UEMCPPIE
|
|
12
|
+
{
|
|
13
|
+
namespace
|
|
14
|
+
{
|
|
15
|
+
void SampleActors(UWorld* World,
|
|
16
|
+
const TArray<FString>& Ids,
|
|
17
|
+
TMap<FString, TWeakObjectPtr<AActor>>& Cache,
|
|
18
|
+
FTrackedActorRow& OutRow)
|
|
19
|
+
{
|
|
20
|
+
for (const FString& Id : Ids)
|
|
21
|
+
{
|
|
22
|
+
FActorState AS;
|
|
23
|
+
AActor* A = nullptr;
|
|
24
|
+
if (TWeakObjectPtr<AActor>* Cached = Cache.Find(Id))
|
|
25
|
+
{
|
|
26
|
+
A = Cached->Get();
|
|
27
|
+
}
|
|
28
|
+
if (!A)
|
|
29
|
+
{
|
|
30
|
+
A = FindActorById(World, Id);
|
|
31
|
+
if (A) Cache.Add(Id, A);
|
|
32
|
+
}
|
|
33
|
+
if (A)
|
|
34
|
+
{
|
|
35
|
+
AS.Location = A->GetActorLocation();
|
|
36
|
+
AS.Rotation = A->GetActorRotation();
|
|
37
|
+
AS.Velocity = A->GetVelocity();
|
|
38
|
+
AS.bResolved = true;
|
|
39
|
+
}
|
|
40
|
+
OutRow.Actors.Add(Id, AS);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
FPIEObserver& FPIEObserver::Get()
|
|
46
|
+
{
|
|
47
|
+
static FPIEObserver Instance;
|
|
48
|
+
return Instance;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void FPIEObserver::Init()
|
|
52
|
+
{
|
|
53
|
+
if (BeginPIEHandle.IsValid()) return;
|
|
54
|
+
BeginPIEHandle = FEditorDelegates::BeginPIE.AddLambda([this](bool bSim)
|
|
55
|
+
{
|
|
56
|
+
this->OnBeginPIE(bSim);
|
|
57
|
+
});
|
|
58
|
+
EndPIEHandle = FEditorDelegates::EndPIE.AddLambda([this](bool bSim)
|
|
59
|
+
{
|
|
60
|
+
this->OnEndPIE(bSim);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void FPIEObserver::Shutdown()
|
|
65
|
+
{
|
|
66
|
+
if (BeginPIEHandle.IsValid()) FEditorDelegates::BeginPIE.Remove(BeginPIEHandle);
|
|
67
|
+
if (EndPIEHandle.IsValid()) FEditorDelegates::EndPIE.Remove(EndPIEHandle);
|
|
68
|
+
BeginPIEHandle.Reset();
|
|
69
|
+
EndPIEHandle.Reset();
|
|
70
|
+
if (bEndFrameBound && OnEndFrameHandle.IsValid())
|
|
71
|
+
{
|
|
72
|
+
FCoreDelegates::OnEndFrame.Remove(OnEndFrameHandle);
|
|
73
|
+
}
|
|
74
|
+
OnEndFrameHandle.Reset();
|
|
75
|
+
bEndFrameBound = false;
|
|
76
|
+
State = EObserverState::Idle;
|
|
77
|
+
bArmed = false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
bool FPIEObserver::Arm(const FObserverArmConfig& Cfg, FString& OutError, FString& OutMessage)
|
|
81
|
+
{
|
|
82
|
+
if (State == EObserverState::Observing || State == EObserverState::WaitingForPawn)
|
|
83
|
+
{
|
|
84
|
+
OutError = TEXT("Observation already in flight; pie_observe_stop first.");
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
UMCPObservationProfile* Profile = LoadObject<UMCPObservationProfile>(
|
|
89
|
+
nullptr, *Cfg.ProfilePath);
|
|
90
|
+
if (!Profile)
|
|
91
|
+
{
|
|
92
|
+
OutError = FString::Printf(TEXT("Profile not found: %s"), *Cfg.ProfilePath);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Pending = Cfg;
|
|
97
|
+
|
|
98
|
+
TrackedValuePaths.Reset();
|
|
99
|
+
TrackedThresholds.Reset();
|
|
100
|
+
for (const FMCPTrackedValueEntry& E : Profile->TrackedValues)
|
|
101
|
+
{
|
|
102
|
+
TrackedValuePaths.Add(E.Path);
|
|
103
|
+
if (E.DriftThreshold > 0.f)
|
|
104
|
+
{
|
|
105
|
+
TrackedThresholds.Add(E.Path, E.DriftThreshold);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
TrackedActorIds.Reset();
|
|
110
|
+
for (const FMCPTrackedActorEntry& E : Profile->TrackedActors)
|
|
111
|
+
{
|
|
112
|
+
TrackedActorIds.Add(E.ActorId);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
bCapturePawnState = Profile->bCapturePawnState;
|
|
116
|
+
bCaptureMontage = Profile->bCaptureMontage;
|
|
117
|
+
ThrPosCm = Profile->PositionThresholdCm;
|
|
118
|
+
ThrRotDeg = Profile->RotationThresholdDeg;
|
|
119
|
+
ThrVelCms = Profile->VelocityThresholdCms;
|
|
120
|
+
ThrTrackedDefault = Profile->TrackedValueDefaultThreshold;
|
|
121
|
+
CurrentProfilePath = Cfg.ProfilePath;
|
|
122
|
+
|
|
123
|
+
CurrentRunId = FString::Printf(TEXT("obs_%s"), *FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S")));
|
|
124
|
+
const FString Root = Cfg.OutputDir.IsEmpty()
|
|
125
|
+
? (FPaths::ProjectSavedDir() / TEXT("MCPObservations"))
|
|
126
|
+
: Cfg.OutputDir;
|
|
127
|
+
CurrentOutputDir = Root / CurrentRunId;
|
|
128
|
+
|
|
129
|
+
ActorRows.Reset();
|
|
130
|
+
ActorCache.Reset();
|
|
131
|
+
CSVBody.Reset();
|
|
132
|
+
FramesSampled = 0;
|
|
133
|
+
|
|
134
|
+
bArmed = true;
|
|
135
|
+
State = EObserverState::Armed;
|
|
136
|
+
OutMessage = FString::Printf(TEXT("Armed: profile=%s run=%s values=%d actors=%d"),
|
|
137
|
+
*FPaths::GetBaseFilename(Cfg.ProfilePath),
|
|
138
|
+
*CurrentRunId,
|
|
139
|
+
TrackedValuePaths.Num(),
|
|
140
|
+
TrackedActorIds.Num());
|
|
141
|
+
|
|
142
|
+
if (GEditor && GEditor->PlayWorld)
|
|
143
|
+
{
|
|
144
|
+
OnBeginPIE(false);
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
bool FPIEObserver::Disarm(FString& OutError)
|
|
150
|
+
{
|
|
151
|
+
if (State == EObserverState::Observing || State == EObserverState::WaitingForPawn)
|
|
152
|
+
{
|
|
153
|
+
OutError = TEXT("Observation is in flight; pie_observe_stop to finalize.");
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
bArmed = false;
|
|
157
|
+
State = EObserverState::Idle;
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
void FPIEObserver::OnBeginPIE(bool /*bIsSimulating*/)
|
|
162
|
+
{
|
|
163
|
+
if (!bArmed) return;
|
|
164
|
+
bArmed = false;
|
|
165
|
+
|
|
166
|
+
FPIEFrameSampler::FConfig SC;
|
|
167
|
+
SC.AxisThreshold = 0.15f;
|
|
168
|
+
SC.bCapturePawnState = bCapturePawnState;
|
|
169
|
+
SC.bCaptureMontage = bCaptureMontage;
|
|
170
|
+
SC.TrackedValuePaths = TrackedValuePaths;
|
|
171
|
+
SC.ClientIndex = Pending.ClientId;
|
|
172
|
+
Sampler.Reset();
|
|
173
|
+
Sampler.SetConfig(SC);
|
|
174
|
+
|
|
175
|
+
State = EObserverState::WaitingForPawn;
|
|
176
|
+
AttachTime = 0.0;
|
|
177
|
+
StartedAt = ISOTimestampNow();
|
|
178
|
+
|
|
179
|
+
if (!bEndFrameBound)
|
|
180
|
+
{
|
|
181
|
+
OnEndFrameHandle = FCoreDelegates::OnEndFrame.AddLambda([this]()
|
|
182
|
+
{
|
|
183
|
+
this->OnEndFrame();
|
|
184
|
+
});
|
|
185
|
+
bEndFrameBound = true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[PIE-OBS] Armed -> BeginPIE: profile=%s run=%s"),
|
|
189
|
+
*FPaths::GetBaseFilename(CurrentProfilePath), *CurrentRunId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
void FPIEObserver::OnEndFrame()
|
|
193
|
+
{
|
|
194
|
+
if (State == EObserverState::Idle || State == EObserverState::Completed) return;
|
|
195
|
+
UWorld* PIEWorld = nullptr;
|
|
196
|
+
if (GEditor) PIEWorld = GEditor->PlayWorld;
|
|
197
|
+
if (!PIEWorld) return;
|
|
198
|
+
|
|
199
|
+
if (State == EObserverState::WaitingForPawn)
|
|
200
|
+
{
|
|
201
|
+
if (Sampler.AttachToPIE(PIEWorld))
|
|
202
|
+
{
|
|
203
|
+
AttachTime = PIEWorld->GetTimeSeconds();
|
|
204
|
+
|
|
205
|
+
CSVHdr = FCSVHeader();
|
|
206
|
+
CSVHdr.RecordingId = CurrentRunId;
|
|
207
|
+
CSVHdr.SampleHz = Pending.SampleHz > 0 ? Pending.SampleHz : 60;
|
|
208
|
+
CSVHdr.Actions = Sampler.GetActions();
|
|
209
|
+
CSVHdr.TrackedValues = Sampler.GetTrackedValues();
|
|
210
|
+
CSVHeaderStr = BuildCSVHeader(CSVHdr);
|
|
211
|
+
CSVBody.Reset();
|
|
212
|
+
|
|
213
|
+
State = EObserverState::Observing;
|
|
214
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[PIE-OBS] Pawn attached, observing"));
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (State == EObserverState::Observing)
|
|
220
|
+
{
|
|
221
|
+
const double GameTime = PIEWorld->GetTimeSeconds();
|
|
222
|
+
const double Dt = PIEWorld->GetDeltaSeconds();
|
|
223
|
+
const uint64 FrameNum = static_cast<uint64>(FramesSampled);
|
|
224
|
+
FCSVRow Row = Sampler.SampleFrame(PIEWorld, FrameNum, GameTime, Dt);
|
|
225
|
+
|
|
226
|
+
if (TrackedActorIds.Num() > 0)
|
|
227
|
+
{
|
|
228
|
+
FTrackedActorRow AR;
|
|
229
|
+
AR.Frame = FrameNum;
|
|
230
|
+
AR.Time = GameTime;
|
|
231
|
+
SampleActors(PIEWorld, TrackedActorIds, ActorCache, AR);
|
|
232
|
+
ActorRows.Add(MoveTemp(AR));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
AppendCSVRow(CSVBody, Row, CSVHdr);
|
|
236
|
+
FramesSampled++;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
void FPIEObserver::OnEndPIE(bool /*bIsSimulating*/)
|
|
241
|
+
{
|
|
242
|
+
if (State == EObserverState::Idle) return;
|
|
243
|
+
FinaliseCurrent();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
FObserverFinishResult FPIEObserver::FinaliseCurrent()
|
|
247
|
+
{
|
|
248
|
+
FObserverFinishResult R;
|
|
249
|
+
if (State == EObserverState::Idle)
|
|
250
|
+
{
|
|
251
|
+
R.Error = TEXT("Not observing");
|
|
252
|
+
return R;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
R.RunId = CurrentRunId;
|
|
256
|
+
R.OutputDir = CurrentOutputDir;
|
|
257
|
+
R.FramesSampled = FramesSampled;
|
|
258
|
+
|
|
259
|
+
if (FramesSampled == 0)
|
|
260
|
+
{
|
|
261
|
+
R.bSuccess = true;
|
|
262
|
+
State = EObserverState::Idle;
|
|
263
|
+
return R;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
IFileManager::Get().MakeDirectory(*CurrentOutputDir, true);
|
|
267
|
+
|
|
268
|
+
// Write observation CSV
|
|
269
|
+
{
|
|
270
|
+
const FString FullCSV = CSVHeaderStr + CSVBody;
|
|
271
|
+
FString Err;
|
|
272
|
+
if (SaveCSV(CurrentOutputDir / TEXT("observation.csv"), FullCSV, Err))
|
|
273
|
+
{
|
|
274
|
+
R.CSVPath = CurrentOutputDir / TEXT("observation.csv");
|
|
275
|
+
}
|
|
276
|
+
else
|
|
277
|
+
{
|
|
278
|
+
UE_LOG(LogMCPBridge, Warning, TEXT("[PIE-OBS] CSV write failed: %s"), *Err);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Write tracked actors
|
|
283
|
+
if (ActorRows.Num() > 0)
|
|
284
|
+
{
|
|
285
|
+
FString Err;
|
|
286
|
+
if (SaveTrackedActorsJSONL(CurrentOutputDir / TEXT("tracked.jsonl"), ActorRows, Err))
|
|
287
|
+
{
|
|
288
|
+
R.TrackedActorsPath = CurrentOutputDir / TEXT("tracked.jsonl");
|
|
289
|
+
}
|
|
290
|
+
else
|
|
291
|
+
{
|
|
292
|
+
UE_LOG(LogMCPBridge, Warning, TEXT("[PIE-OBS] tracked.jsonl write failed: %s"), *Err);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Write manifest
|
|
297
|
+
{
|
|
298
|
+
TSharedRef<FJsonObject> M = MakeShared<FJsonObject>();
|
|
299
|
+
M->SetNumberField(TEXT("version"), kFormatVersion);
|
|
300
|
+
M->SetStringField(TEXT("type"), TEXT("observation"));
|
|
301
|
+
M->SetStringField(TEXT("run_id"), CurrentRunId);
|
|
302
|
+
M->SetStringField(TEXT("profile"), CurrentProfilePath);
|
|
303
|
+
M->SetStringField(TEXT("started_at"), StartedAt);
|
|
304
|
+
M->SetStringField(TEXT("ended_at"), ISOTimestampNow());
|
|
305
|
+
M->SetNumberField(TEXT("frames_sampled"), FramesSampled);
|
|
306
|
+
M->SetNumberField(TEXT("sample_hz"), CSVHdr.SampleHz);
|
|
307
|
+
|
|
308
|
+
TArray<TSharedPtr<FJsonValue>> Vals;
|
|
309
|
+
for (const FString& P : TrackedValuePaths)
|
|
310
|
+
{
|
|
311
|
+
Vals.Add(MakeShared<FJsonValueString>(P));
|
|
312
|
+
}
|
|
313
|
+
M->SetArrayField(TEXT("tracked_values"), Vals);
|
|
314
|
+
|
|
315
|
+
TArray<TSharedPtr<FJsonValue>> Acts;
|
|
316
|
+
for (const FString& A : TrackedActorIds)
|
|
317
|
+
{
|
|
318
|
+
Acts.Add(MakeShared<FJsonValueString>(A));
|
|
319
|
+
}
|
|
320
|
+
M->SetArrayField(TEXT("tracked_actors"), Acts);
|
|
321
|
+
|
|
322
|
+
TSharedRef<FJsonObject> Thr = MakeShared<FJsonObject>();
|
|
323
|
+
Thr->SetNumberField(TEXT("position_cm"), ThrPosCm);
|
|
324
|
+
Thr->SetNumberField(TEXT("rotation_deg"), ThrRotDeg);
|
|
325
|
+
Thr->SetNumberField(TEXT("velocity_cms"), ThrVelCms);
|
|
326
|
+
Thr->SetNumberField(TEXT("tracked_default"), ThrTrackedDefault);
|
|
327
|
+
if (TrackedThresholds.Num() > 0)
|
|
328
|
+
{
|
|
329
|
+
TSharedRef<FJsonObject> PerPath = MakeShared<FJsonObject>();
|
|
330
|
+
for (const TPair<FString, float>& KV : TrackedThresholds)
|
|
331
|
+
{
|
|
332
|
+
PerPath->SetNumberField(KV.Key, KV.Value);
|
|
333
|
+
}
|
|
334
|
+
Thr->SetObjectField(TEXT("tracked"), PerPath);
|
|
335
|
+
}
|
|
336
|
+
M->SetObjectField(TEXT("thresholds"), Thr);
|
|
337
|
+
|
|
338
|
+
FString JsonStr;
|
|
339
|
+
TSharedRef<TJsonWriter<>> W = TJsonWriterFactory<>::Create(&JsonStr);
|
|
340
|
+
FJsonSerializer::Serialize(M, W);
|
|
341
|
+
FFileHelper::SaveStringToFile(JsonStr, *(CurrentOutputDir / TEXT("manifest.json")),
|
|
342
|
+
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
R.bSuccess = true;
|
|
346
|
+
R.DurationSeconds = 0.0;
|
|
347
|
+
|
|
348
|
+
if (bEndFrameBound && OnEndFrameHandle.IsValid())
|
|
349
|
+
{
|
|
350
|
+
FCoreDelegates::OnEndFrame.Remove(OnEndFrameHandle);
|
|
351
|
+
OnEndFrameHandle.Reset();
|
|
352
|
+
bEndFrameBound = false;
|
|
353
|
+
}
|
|
354
|
+
State = EObserverState::Idle;
|
|
355
|
+
|
|
356
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[PIE-OBS] Finalized: %d frames -> %s"),
|
|
357
|
+
FramesSampled, *CurrentOutputDir);
|
|
358
|
+
return R;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
FObserverFinishResult FPIEObserver::ForceStop()
|
|
362
|
+
{
|
|
363
|
+
return FinaliseCurrent();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
FObserverStatus FPIEObserver::GetStatus() const
|
|
367
|
+
{
|
|
368
|
+
FObserverStatus S;
|
|
369
|
+
S.State = State;
|
|
370
|
+
S.RunId = CurrentRunId;
|
|
371
|
+
S.ProfilePath = CurrentProfilePath;
|
|
372
|
+
S.FramesSampled = FramesSampled;
|
|
373
|
+
if (GEditor && GEditor->PlayWorld && AttachTime > 0.0)
|
|
374
|
+
{
|
|
375
|
+
S.ElapsedSeconds = GEditor->PlayWorld->GetTimeSeconds() - AttachTime;
|
|
376
|
+
}
|
|
377
|
+
return S;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "CoreMinimal.h"
|
|
4
|
+
#include "PIEFrameSampler.h"
|
|
5
|
+
#include "PIESequenceFormat.h"
|
|
6
|
+
#include "UObject/WeakObjectPtrTemplates.h"
|
|
7
|
+
|
|
8
|
+
class UMCPObservationProfile;
|
|
9
|
+
class UWorld;
|
|
10
|
+
class AActor;
|
|
11
|
+
|
|
12
|
+
namespace UEMCPPIE
|
|
13
|
+
{
|
|
14
|
+
enum class EObserverState : uint8
|
|
15
|
+
{
|
|
16
|
+
Idle,
|
|
17
|
+
Armed,
|
|
18
|
+
WaitingForPawn,
|
|
19
|
+
Observing,
|
|
20
|
+
Completed
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
struct FObserverArmConfig
|
|
24
|
+
{
|
|
25
|
+
FString ProfilePath;
|
|
26
|
+
FString OutputDir;
|
|
27
|
+
int32 SampleHz = 60;
|
|
28
|
+
int32 PinFPS = -1;
|
|
29
|
+
int32 ClientId = 0;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
struct FObserverStatus
|
|
33
|
+
{
|
|
34
|
+
EObserverState State = EObserverState::Idle;
|
|
35
|
+
FString RunId;
|
|
36
|
+
FString ProfilePath;
|
|
37
|
+
int32 FramesSampled = 0;
|
|
38
|
+
double ElapsedSeconds = 0.0;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
struct FObserverFinishResult
|
|
42
|
+
{
|
|
43
|
+
bool bSuccess = false;
|
|
44
|
+
FString Error;
|
|
45
|
+
FString RunId;
|
|
46
|
+
FString OutputDir;
|
|
47
|
+
FString CSVPath;
|
|
48
|
+
FString TrackedActorsPath;
|
|
49
|
+
int32 FramesSampled = 0;
|
|
50
|
+
double DurationSeconds = 0.0;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
class FPIEObserver
|
|
54
|
+
{
|
|
55
|
+
public:
|
|
56
|
+
static FPIEObserver& Get();
|
|
57
|
+
|
|
58
|
+
void Init();
|
|
59
|
+
void Shutdown();
|
|
60
|
+
|
|
61
|
+
bool Arm(const FObserverArmConfig& Cfg, FString& OutError, FString& OutMessage);
|
|
62
|
+
bool Disarm(FString& OutError);
|
|
63
|
+
FObserverFinishResult ForceStop();
|
|
64
|
+
FObserverStatus GetStatus() const;
|
|
65
|
+
bool IsActive() const { return State != EObserverState::Idle && State != EObserverState::Completed; }
|
|
66
|
+
|
|
67
|
+
private:
|
|
68
|
+
void OnBeginPIE(bool bIsSimulating);
|
|
69
|
+
void OnEndPIE(bool bIsSimulating);
|
|
70
|
+
void OnEndFrame();
|
|
71
|
+
FObserverFinishResult FinaliseCurrent();
|
|
72
|
+
|
|
73
|
+
FObserverArmConfig Pending;
|
|
74
|
+
EObserverState State = EObserverState::Idle;
|
|
75
|
+
bool bArmed = false;
|
|
76
|
+
|
|
77
|
+
FString CurrentRunId;
|
|
78
|
+
FString CurrentOutputDir;
|
|
79
|
+
FString CurrentProfilePath;
|
|
80
|
+
|
|
81
|
+
FPIEFrameSampler Sampler;
|
|
82
|
+
FCSVHeader CSVHdr;
|
|
83
|
+
FString CSVHeaderStr;
|
|
84
|
+
FString CSVBody;
|
|
85
|
+
|
|
86
|
+
TArray<FString> TrackedActorIds;
|
|
87
|
+
TArray<FTrackedActorRow> ActorRows;
|
|
88
|
+
TMap<FString, TWeakObjectPtr<AActor>> ActorCache;
|
|
89
|
+
|
|
90
|
+
double AttachTime = 0.0;
|
|
91
|
+
int32 FramesSampled = 0;
|
|
92
|
+
FString StartedAt;
|
|
93
|
+
|
|
94
|
+
// Profile config extracted at arm time
|
|
95
|
+
TArray<FString> TrackedValuePaths;
|
|
96
|
+
bool bCapturePawnState = true;
|
|
97
|
+
bool bCaptureMontage = true;
|
|
98
|
+
float ThrPosCm = 5.f;
|
|
99
|
+
float ThrRotDeg = 2.f;
|
|
100
|
+
float ThrVelCms = 25.f;
|
|
101
|
+
float ThrTrackedDefault = 0.f;
|
|
102
|
+
TMap<FString, float> TrackedThresholds;
|
|
103
|
+
|
|
104
|
+
FDelegateHandle BeginPIEHandle;
|
|
105
|
+
FDelegateHandle EndPIEHandle;
|
|
106
|
+
FDelegateHandle OnEndFrameHandle;
|
|
107
|
+
bool bEndFrameBound = false;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -247,4 +247,9 @@ namespace UEMCPPIE
|
|
|
247
247
|
// recorder (initial resolve), the replayer (re-resolve in the new world),
|
|
248
248
|
// and pie_snapshot.
|
|
249
249
|
AActor* FindActorById(UWorld* World, const FString& Id);
|
|
250
|
+
|
|
251
|
+
inline FString ISOTimestampNow()
|
|
252
|
+
{
|
|
253
|
+
return FDateTime::Now().ToString(TEXT("%Y-%m-%dT%H:%M:%S"));
|
|
254
|
+
}
|
|
250
255
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include "PIE/PIEInputInjector.h"
|
|
6
6
|
#include "PIE/PIEInputRecorder.h"
|
|
7
7
|
#include "PIE/PIEInputReplayer.h"
|
|
8
|
+
#include "PIE/PIEObserver.h"
|
|
8
9
|
#include "Editor.h"
|
|
9
10
|
#include "Editor/EditorEngine.h"
|
|
10
11
|
#include "Misc/ConfigCacheIni.h"
|
|
@@ -24,6 +25,7 @@ void FUE_MCP_BridgeModule::StartupModule()
|
|
|
24
25
|
UEMCPPIE::FPIEInputInjector::Init();
|
|
25
26
|
UEMCPPIE::FPIEInputRecorder::Get().Init();
|
|
26
27
|
UEMCPPIE::FPIEInputReplayer::Get().Init();
|
|
28
|
+
UEMCPPIE::FPIEObserver::Get().Init();
|
|
27
29
|
// Clear any leftover injections from a previous PIE session so a fresh
|
|
28
30
|
// EndPIE-BeginPIE pair starts with no ghost holds in the queue.
|
|
29
31
|
FEditorDelegates::EndPIE.AddLambda([](bool /*bIsSimulating*/)
|
|
@@ -100,7 +102,8 @@ void FUE_MCP_BridgeModule::StartupModule()
|
|
|
100
102
|
Suppress.BindLambda([]() -> bool
|
|
101
103
|
{
|
|
102
104
|
return UEMCPPIE::FPIEInputRecorder::Get().IsActive()
|
|
103
|
-
|| UEMCPPIE::FPIEInputReplayer::Get().IsActive()
|
|
105
|
+
|| UEMCPPIE::FPIEInputReplayer::Get().IsActive()
|
|
106
|
+
|| UEMCPPIE::FPIEObserver::Get().IsActive();
|
|
104
107
|
});
|
|
105
108
|
GEditor->ShouldDisableCPUThrottlingDelegates.Add(Suppress);
|
|
106
109
|
|
|
@@ -113,6 +116,7 @@ void FUE_MCP_BridgeModule::ShutdownModule()
|
|
|
113
116
|
{
|
|
114
117
|
// Stop bridge server
|
|
115
118
|
FDialogHandlers::RemoveDialogHook();
|
|
119
|
+
UEMCPPIE::FPIEObserver::Get().Shutdown();
|
|
116
120
|
UEMCPPIE::FPIEInputReplayer::Get().Shutdown();
|
|
117
121
|
UEMCPPIE::FPIEInputRecorder::Get().Shutdown();
|
|
118
122
|
UEMCPPIE::FPIEInputInjector::Shutdown();
|