Commandlet

案例

头文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#pragma once

#include "CoreMinimal.h"
#include "Commandlets/Commandlet.h"
#include "Commandlets/ResavePackagesCommandlet.h"
#include "XXXCommandlet.generated.h"

DECLARE_LOG_CATEGORY_EXTERN(LogXXXCommandlet, All, All);

UCLASS()
class UXXXCommandlet : public UResavePackagesCommandlet
{
	GENERATED_BODY()

public:
	UXXXCommandlet();

	virtual int32 Main(const FString& InCommandline) override;
};

源文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include "XXXCommandlet.h"

DEFINE_LOG_CATEGORY(LogXXXCommandlet);

UXXXCommandlet::UXXXCommandlet()
{
}

int32 UXXXCommandlet::Main(const FString& InCommandline)
{
	// 可以解析参数
	TArray<FString> Tokens;
	TArray<FString> InSwitches;
	TMap<FString, FString> ParamsMap;
	ParseCommandLine(*InCommandline, Tokens, InSwitches, ParamsMap);

	// do some thing
	return 0;
}

启动/调试时在vs中增加参数:-skipcompile -run=XXX

这里的 XXX 换成你想要的名字,UResavePackagesCommandlet 也可以换成 UCommandlet

本地测试:

1
.\UnrealEditor.exe E:\Work\xxx.uproject -skipcompile -run=XXX

小技巧

参数

skipcompile参数作用

字面意思就是跳过编译,源码对应位置:

AllowCommandletRendering

commandlet 下默认没有渲染,需要加上 -AllowCommandletRendering 打开

tick

Commandlet 无法tick,但是看ue源码发现大世界有个这样的函数:

1
FWorldPartitionHelpers::FakeEngineTick

里面是调用了

1
CommandletHelpers::TickEngine

资源检索

很多需要用资源的地方比如 AssetRegistryModule.Get().GetDependencies 之类的,需要提前注册到 AssetRegistryModule 里:

1
2
3
4
UE_LOG(LogTemp, Display, TEXT("Commandlet Start Load All Assets!"));
FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked<FAssetRegistryModule>("AssetRegistry");
AssetRegistryModule.Get().SearchAllAssets(/*bSynchronousSearch =*/true);
UE_LOG(LogTemp, Display, TEXT("Commandlet Finish Load All Assets!"));

一般情况上面这样就够了,再严谨点可以用 FEvent 额外保护确保执行完成了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 准备 AsssetRegistry
FEvent* AssetRegistryLoadedEvent = FPlatformProcess::GetSynchEventFromPool(true);

// Ensure AssetRegistryModule is initialized
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();

// 初始化 AssetRegistry
AssetRegistry.SearchAllAssets(true);

// Wait until the AssetRegistry is fully loaded
AssetRegistry.OnFilesLoaded().AddLambda([AssetRegistryLoadedEvent]()
{
	AssetRegistryLoadedEvent->Trigger();
});

if (!AssetRegistry.IsLoadingAssets())
{
	AssetRegistryLoadedEvent->Trigger();
}

// Wait for the AssetRegistry to be fully loaded
AssetRegistryLoadedEvent->Wait();
FPlatformProcess::ReturnSynchEventToPool(AssetRegistryLoadedEvent);

http

因为这种网络相关,我们很多时候需要确保拿到数据再同步执行后续操作,或是直接执行回调的方式,此时也可以通过 FEvent 来保证,通过让 HttpManager 不断 tick 去确保拿到数据,示例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
FEvent* ReadyEvent = FPlatformProcess::GetSynchEventFromPool(true);
TSharedPtr<FJsonObject> ResponseJson;

// Create HTTP request
TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
Request->SetURL(Url);
Request->SetVerb(Verb);

// Set headers
for (const auto& Header : Headers)
{
	Request->SetHeader(Header.Key, Header.Value);
}

// Set content if provided
if (!Content.IsEmpty())
{
	Request->SetContentAsString(Content);
}

// Set completion callback
Request->OnProcessRequestComplete().BindLambda([ReadyEvent, &ResponseJson](FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSuccess)
{
	if (bSuccess && HttpResponse.IsValid())
	{
		ResponseJson = MakeShareable(new FJsonObject());
		TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(HttpResponse->GetContentAsString());
		FJsonSerializer::Deserialize(Reader, ResponseJson);
	}
	ReadyEvent->Trigger();
});

// Process the request
if (!Request->ProcessRequest())
{
	FPlatformProcess::ReturnSynchEventToPool(ReadyEvent);
	UE_LOG(LogTemp, Error, TEXT("Failed to start HTTP request: %s"), *Url);
	return false;
}

// Wait for the request to complete
while (!ReadyEvent->Wait(FTimespan::FromMilliseconds(100)))
{
	// Tick the HTTP manager to process the request
	FHttpModule::Get().GetHttpManager().Tick(0.1f);
}

ReadyEvent->Reset();
FPlatformProcess::ReturnSynchEventToPool(ReadyEvent);

贴图

我想在 Commandlet 下拿到贴图的 Resource Size,跟编辑器打开贴图的 Detail 信息里的 Resource Size 等同,把源码挖来应该是这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 尝试从PlatformData获取资源大小
FTexturePlatformData** PlatformDataPtr = Texture->GetRunningPlatformData();
if (PlatformDataPtr && PlatformDataPtr[0])
{
	FTexturePlatformData* PlatformData = PlatformDataPtr[0];
	// 获取当前LOD bias
	const FStreamableRenderResourceState SRRState = Texture->GetStreamableResourceState();
	const int32 ActualMipBias = SRRState.IsValid() ? (SRRState.ResidentFirstLODIdx() + SRRState.AssetLODBias) : Texture->GetCachedLODBias();
	
	// 使用GetPayloadSize获取资源大小
	ResourceSizeBytes = PlatformData->GetPayloadSize(ActualMipBias);
}
else
{
	// 如果无法从PlatformData获取,则使用GetResourceSizeBytes
	ResourceSizeBytes = Texture->GetResourceSizeBytes(EResourceSizeMode::Exclusive);
}

// 转换为KB
return FMath::DivideAndRoundNearest(ResourceSizeBytes, static_cast<int64>(1024));

但是发现 Commandlet 环境下会有问题,最后发现需要补上一些前置代码:

1
2
3
FTextureCompilingManager::Get().FinishCompilation({Texture});
Texture->SetForceMipLevelsToBeResident(30.0f);
Texture->WaitForStreaming();

完整代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 获取Texture的Resource Size(KB)
int64 GetTextureResourceSizeKB(UTexture* Texture)
{
	if (!Texture)
		return 0;

	FTextureCompilingManager::Get().FinishCompilation({Texture});
	Texture->SetForceMipLevelsToBeResident(30.0f);
	Texture->WaitForStreaming();
	
	int64 ResourceSizeBytes = 0;
	
	// 尝试从PlatformData获取资源大小
	FTexturePlatformData** PlatformDataPtr = Texture->GetRunningPlatformData();
	if (PlatformDataPtr && PlatformDataPtr[0])
	{
		FTexturePlatformData* PlatformData = PlatformDataPtr[0];
		// 获取当前LOD bias
		const FStreamableRenderResourceState SRRState = Texture->GetStreamableResourceState();
		const int32 ActualMipBias = SRRState.IsValid() ? (SRRState.ResidentFirstLODIdx() + SRRState.AssetLODBias) : Texture->GetCachedLODBias();
		
		// 使用GetPayloadSize获取资源大小
		ResourceSizeBytes = PlatformData->GetPayloadSize(ActualMipBias);
	}
	else
	{
		// 如果无法从PlatformData获取,则使用GetResourceSizeBytes
		ResourceSizeBytes = Texture->GetResourceSizeBytes(EResourceSizeMode::Exclusive);
	}
	
	// 转换为KB
	return FMath::DivideAndRoundNearest(ResourceSizeBytes, static_cast<int64>(1024));
}

参考

https://zhuanlan.zhihu.com/p/512610557

https://zhuanlan.zhihu.com/p/377903983

Licensed under CC BY-NC-SA 4.0