Files
UnrealEngine/Engine/Plugins/MetaHuman/MetaHumanSDK/Source/MetaHumanSDKEditor/Private/MetaHumanVersionService.cpp
2025-05-18 13:04:45 +08:00

250 lines
8.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetaHumanVersionService.h"
#include "Algo/RemoveIf.h"
#include "HttpManager.h"
#include "HttpModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "MetaHumanSDKSettings.h"
#include "Serialization/JsonSerializer.h"
DEFINE_LOG_CATEGORY_STATIC(LogMetaHumanVersionService, Log, All)
namespace UE::MetaHuman
{
namespace Private
{
class FMetaHumanVersionServiceClient
{
public:
const FString& UEVersionFromMhVersion(const FMetaHumanVersion& Version)
{
static const FString UnknownVersion = TEXT("Unknown Version");
AwaitRequest(VersionInfoRequest);
if (const FString *UeVersion = VersionMapping.Find(Version))
{
return *UeVersion;
}
return UnknownVersion;
}
TArray<TSharedRef<FReleaseNoteData>> GetReleaseNotesForVersionUpgrade(const FMetaHumanVersion& FromVersion, const FMetaHumanVersion& ToVersion)
{
AwaitRequest(ReleaseNotesRequest);
// Take a copy of the release notes
TArray<TSharedRef<FReleaseNoteData>> ToReturn = ReleaseNotes;
// Remove the ones that do not relate to the current upgrade (either before the current version of after the version we are upgrading to)
ToReturn.SetNum(Algo::RemoveIf(ToReturn, [&FromVersion, &ToVersion](const TSharedRef<FReleaseNoteData>& Item)
{
return Item->Version <= FromVersion || Item->Version > ToVersion;
}));
// Sort by release version number
ToReturn.Sort([](const TSharedRef<FReleaseNoteData>& A, const TSharedRef<FReleaseNoteData>& B) { return A->Version > B->Version; });
return ToReturn;
}
static TSharedPtr<FMetaHumanVersionServiceClient> Get()
{
if (!MetaHumanVersionServiceClientInst.IsValid())
{
MetaHumanVersionServiceClientInst = MakeShareable(new FMetaHumanVersionServiceClient);
}
return MetaHumanVersionServiceClientInst;
}
void OverrideServiceUrl(const FString &OverrideUrl)
{
FetchDataFromVersionService(OverrideUrl);
}
~FMetaHumanVersionServiceClient()
{
TerminateRequest(VersionInfoRequest);
TerminateRequest(ReleaseNotesRequest);
}
private:
FMetaHumanVersionServiceClient()
{
const UMetaHumanSDKSettings* Settings = GetDefault<UMetaHumanSDKSettings>();
FetchDataFromVersionService(Settings->VersionServiceBaseUrl);
}
void ParseVersionInfoFromJson(const FJsonValue* Data)
{
VersionMapping.Reset();
for (const TSharedPtr<FJsonValue>& VersionInfoEntry : Data->AsArray())
{
FString UEVersion = VersionInfoEntry->AsObject()->GetStringField(TEXT("ueVersion"));
const TArray<TSharedPtr<FJsonValue>>& MHCVersions = VersionInfoEntry->AsObject()->GetArrayField(TEXT("all"));
for (const TSharedPtr<FJsonValue>& Version : MHCVersions)
{
// Values are ordered from most recent UE to least recent. This will mean that newer entries
// get overwritten by older ones and so we always end up with the earliest UEVersion per
// MHC version which is what we want.
VersionMapping.Add(FMetaHumanVersion{Version->AsString()}, UEVersion);
}
}
}
void ParseReleaseNotesFromJson(const FJsonValue* Data)
{
ReleaseNotes.Reset();
for (const TTuple<FString, TSharedPtr<FJsonValue>>& ReleaseNoteEntry : Data->AsObject()->Values)
{
const FString& MHCVersion = ReleaseNoteEntry.Key;
const TSharedPtr<FJsonObject>& ReleaseNote = ReleaseNoteEntry.Value->AsObject();
ReleaseNotes.Add(MakeShared<FReleaseNoteData>(FReleaseNoteData{
FText::FromString(ReleaseNote->GetStringField(TEXT("title"))),
FMetaHumanVersion(MHCVersion),
FText::FromString(ReleaseNote->GetStringField(TEXT("description"))),
FText::FromString(ReleaseNote->GetStringField(TEXT("details"))),
}));
}
}
// Initiate an HTTP request and attach an on-completion callback to parse and store the results.
FHttpRequestPtr InitiateRequest(const FString& RequestUrl, const FString& RequestName, const TFunction<void(const FJsonValue*)>& OnComplete) const
{
FHttpRequestPtr HttpRequest = FHttpModule::Get().CreateRequest();
HttpRequest->OnProcessRequestComplete().BindLambda(
[this, OnComplete, RequestName](FHttpRequestPtr Unused, FHttpResponsePtr HttpResponse, bool bSucceeded)
{
if (!bSucceeded || !HttpResponse.IsValid())
{
UE_LOG(LogMetaHumanVersionService, Warning, TEXT("%s: No response"), *RequestName);
return;
}
FString ResponseStr = HttpResponse->GetContentAsString();
TSharedPtr<FJsonValue> Data;
if (!EHttpResponseCodes::IsOk(HttpResponse->GetResponseCode()) || !FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(ResponseStr), Data))
{
UE_LOG(LogMetaHumanVersionService, Warning, TEXT("%s"), *FString::Format(TEXT("{0}: Invalid response. code={1} response={2}"), {RequestName, HttpResponse->GetResponseCode(), ResponseStr}));
return;
}
OnComplete(Data.Get());
});
// TODO some more refined retry policy.
// TODO authentication (if required).
HttpRequest->SetURL(RequestUrl);
HttpRequest->SetVerb(TEXT("GET"));
// HttpRequest.SetHeader("Authorization", AuthorizationHeader);
HttpRequest->SetTimeout(10);
HttpRequest->ProcessRequest();
return HttpRequest;
}
// Perform a blocking wait for the request.
static void AwaitRequest(FHttpRequestPtr& Request)
{
// This loop is bounded by the timeout on the request.
while (Request.IsValid() && !IsFinished(Request->GetStatus()))
{
FPlatformProcess::Sleep(0.1);
FHttpModule::Get().GetHttpManager().Tick(0.1);
}
// Request is completed, now clean up.
TerminateRequest(Request);
}
// Clean up any resources associated with the request and cancel it if it is not yet completed
static void TerminateRequest(FHttpRequestPtr& Request)
{
if (Request.IsValid())
{
Request->OnProcessRequestComplete().Unbind();
if (!IsFinished(Request->GetStatus()))
{
Request->CancelRequest();
}
Request.Reset();
}
}
// These pointers are valid while the initial request is processing and invalid once the data has been retrieved (or if the request fails).
FHttpRequestPtr VersionInfoRequest;
FHttpRequestPtr ReleaseNotesRequest;
// ReleaseNotes are initialised with fall-back data.
TArray<TSharedRef<FReleaseNoteData>> ReleaseNotes = {
MakeShared<FReleaseNoteData>(FReleaseNoteData{
FText::FromString("None Available"),
FMetaHumanVersion(2, 0, 0),
FText::FromString("Failed to retrieve release notes."),
FText::FromString("Release notes.")
})
};
// VersionMappings are initialised with fall-back data.
TMap<FMetaHumanVersion, FString> VersionMapping = {
{FMetaHumanVersion(0, 5, 0), TEXT("4.27")},
{FMetaHumanVersion(0, 5, 1), TEXT("4.27")},
{FMetaHumanVersion(0, 5, 2), TEXT("4.27")},
{FMetaHumanVersion(0, 5, 3), TEXT("4.27")},
{FMetaHumanVersion(1, 0, 0), TEXT("5.0")},
{FMetaHumanVersion(1, 1, 0), TEXT("5.0")},
{FMetaHumanVersion(1, 2, 0), TEXT("5.0")},
{FMetaHumanVersion(1, 2, 1), TEXT("5.0")},
{FMetaHumanVersion(1, 2, 2), TEXT("5.0")},
{FMetaHumanVersion(1, 2, 3), TEXT("5.0")},
{FMetaHumanVersion(1, 3, 0), TEXT("5.0")},
{FMetaHumanVersion(1, 3, 1), TEXT("5.0")},
{FMetaHumanVersion(2, 0, 0), TEXT("5.2")}
};
// Url Handling
void FetchDataFromVersionService(const FString &VersionServiceUrl)
{
// Cancel any in-flight requests
TerminateRequest(VersionInfoRequest);
TerminateRequest(ReleaseNotesRequest);
// Fire off the requests for the live data. These will complete asynchronously. If these requests fail we fall back to bundled data.
VersionInfoRequest = InitiateRequest(FString::Format(TEXT("{0}/api/v1/versions"), {VersionServiceUrl}), TEXT("Fetch Version Info"), [this](const FJsonValue* Data)
{
ParseVersionInfoFromJson(Data);
});
ReleaseNotesRequest = InitiateRequest(FString::Format(TEXT("{0}/api/v1/release-notes"), {VersionServiceUrl}), TEXT("Fetch Release Notes"), [this](const FJsonValue* Data)
{
ParseReleaseNotesFromJson(Data);
});
}
// Singleton Implementation
static TSharedPtr<FMetaHumanVersionServiceClient> MetaHumanVersionServiceClientInst;
};
TSharedPtr<FMetaHumanVersionServiceClient> FMetaHumanVersionServiceClient::MetaHumanVersionServiceClientInst;
}
const FString& UEVersionFromMhVersion(const FMetaHumanVersion& Version)
{
return Private::FMetaHumanVersionServiceClient::Get()->UEVersionFromMhVersion(Version);
}
TArray<TSharedRef<FReleaseNoteData>> GetReleaseNotesForVersionUpgrade(const FMetaHumanVersion& FromVersion, const FMetaHumanVersion& ToVersion)
{
return Private::FMetaHumanVersionServiceClient::Get()->GetReleaseNotesForVersionUpgrade(FromVersion, ToVersion);
}
void SetServiceUrl(const FString &ServiceUrl)
{
Private::FMetaHumanVersionServiceClient::Get()->OverrideServiceUrl(ServiceUrl);
}
void Init()
{
Private::FMetaHumanVersionServiceClient::Get();
}
}