UE控制台命令及帮助处理相关

发布时间 2024-01-06 17:53:15作者: tsecer

文档

对于一个(大型)软件来说,文档始终是一个重要功能。例如,vim内置的在线文档就十分轻便基于文本格式,使用普通编辑器查看也不影响阅读),这样让vim的使用非常丝滑。

UE作为一款游戏软件,同样内置了控制台命令,通过这些命令可以交互式执行控制台命令,查看/控制进程状态。更妙的是在输入的时候,控制台会实时根据输入内容提示可能得命令,并且在命令的后面还会有命令对应的帮助文档。

by typing “help” in UE editor Cmd input box, and press Enter, a local web page will be open with all console variables and commands.

IConsoleObject

控制台对象主要分为两大类,一个是命令,一个是变量。

  • IConsoleCommand

在IConsoleCommand的直接派生类FConsoleCommandBase中就包含了

class FConsoleCommandBase : public IConsoleCommand
{
public:
	/**
	 * Constructor
	 * @param InHelp must not be 0, must not be empty
	 */
	FConsoleCommandBase(const TCHAR* InHelp, EConsoleVariableFlags InFlags)
		: Help(InHelp), Flags(InFlags)
	{
		check(InHelp);
		//check(*Help != 0); for now disabled as there callstack when we crash early during engine init

		ApplyPreviewIfScalability();
	}
	
	// not using TCHAR* to allow chars support reloading of modules (otherwise we would keep a pointer into the module)
	FString Help;
};

Help字符串,所以帮助算是一个比较基础的功能。

  • IConsoleVariable

在FConsoleVariableBase类中直接包含了Help字段

class FConsoleVariableBase : public IConsoleVariable
{
///....
	// not using TCHAR* to allow chars support reloading of modules (otherwise we would keep a pointer into the module)
	FString Help;
///....
};

通过一些编程技巧,可以方便的注册一个控制台变量。

static TAutoConsoleVariable<int32> CVarBadDriverWarningIsFatal(
	TEXT("r.BadDriverWarningIsFatal"),
	0,
	TEXT("If non-zero, trigger a fatal error when warning of bad drivers.\n")
	TEXT("For the fatal error to occur, r.WarnOfBadDrivers must be non-zero.\n")
	TEXT(" 0: off (default)\n")
	TEXT(" 1: a fatal error occurs after the out of date driver message is dismissed\n"),
	ECVF_RenderThreadSafe);

还可以注册变量修改时的回调,从而执行特定动作。

/**
 * Interface for console variables
 */
class IConsoleVariable : public IConsoleObject
{
///...
	/**
	 * Allows to specify a callback function that is called when the console variable value changes.
 	 * Is even called if the value is the same as the value before. Will always be on the game thread.
	 * This can be dangerous (instead try to use RegisterConsoleVariableSink())
	 * - Setting other console variables in the delegate can cause infinite loops
	 * - Setting many console variables could result in wasteful cycles (e.g. if multiple console variables require to reattach all objects it would happen for each one)
	 * - The call can be at any time during initialization.
	 * As this cannot be specified during constructions you are not called on creation.
	 * We also don't call for the SetOnChangedCallback() call as this is up to the caller.
	 **/
	virtual void SetOnChangedCallback(const FConsoleVariableDelegate& Callback) = 0;
///...
};
  • 一些约定

一些常见命名约定

///@file: UE5\Engine\Source\Runtime\Core\Private\HAL\ConsoleManager.cpp
// Naming conventions:
//
// Console variable should start with (suggestion):
//
// r.      Renderer / 3D Engine / graphical feature
// RHI.    Low level RHI (rendering platform) specific
// a.	   Animation
// s. 	   Sound / Music
// n.      Network
// ai.     Artificial intelligence
// i.      Input e.g. mouse/keyboard
// p.      Physics
// t.      Timer
// log.	   Logging system
// con.	   Console (in game  or editor) 
// g.      Game specific
// Compat.
// FX.     Particle effects
// sg.     scalability group (used by scalability system, ini load/save or using SCALABILITY console command)

注册

除了代码外,配置文件也可以添加命令帮助。

;UE5\Engine\Config\BaseInput.ini
[/Script/EngineSettings.ConsoleSettings]
MaxScrollbackSize=1024
+AutoCompleteMapPaths=Content/Maps
+ManualAutoCompleteList=(Command="Exit",Desc="Exits the game")
+ManualAutoCompleteList=(Command="DebugCreatePlayer 1",Desc=)
+ManualAutoCompleteList=(Command="ToggleDrawEvents",Desc="Toggles annotations for shader debugging with Pix, Razor or similar GPU capture tools")
+ManualAutoCompleteList=(Command="Shot",Desc="Make a screenshot")
;.....
+ManualAutoCompleteList=(Command="MemReport",Desc="Outputs memory stats to a profile file. -Full gives more data, -Log outputs to the log")

帮助提示

  • 结构
/**
 * A basic command line console that accepts most commands.
 */
UCLASS(Within=GameViewportClient, config=Input, transient)
class ENGINE_API UConsole
	: public UObject
	, public FOutputDevice
{
///...
	/** Full list of auto-complete commands and info */
	TArray<FAutoCompleteCommand> AutoCompleteList;
///...
};
  • 生成

void UConsole::BuildRuntimeAutoCompleteList(bool bForce)
{
	LLM_SCOPE(ELLMTag::EngineMisc);

#if ALLOW_CONSOLE
	if (!bForce)
	{
		// unless forced delay updating until needed
		bIsRuntimeAutoCompleteUpToDate = false;
		return;
	}
	///...
	
	// copy the manual list first
	AutoCompleteList.Reset();
	AutoCompleteList.AddDefaulted(ConsoleSettings->ManualAutoCompleteList.Num());
	for (int32 Idx = 0; Idx < ConsoleSettings->ManualAutoCompleteList.Num(); Idx++)
	{
		AutoCompleteList[Idx] = ConsoleSettings->ManualAutoCompleteList[Idx];
		AutoCompleteList[Idx].Color = ConsoleSettings->AutoCompleteCommandColor;
	}

	// systems that have registered to want to introduce entries
	RegisterConsoleAutoCompleteEntries.Broadcast(AutoCompleteList);

	// console variables
	{
		IConsoleManager::Get().ForEachConsoleObjectThatStartsWith(
			FConsoleObjectVisitor::CreateStatic(
				&FConsoleVariableAutoCompleteVisitor::OnConsoleVariable,
				&AutoCompleteList));
	}
	///...
	// clear the existing tree
	//@todo - probably only need to rebuild the tree + partial command list on level load
	for (int32 Idx = 0; Idx < AutoCompleteTree.ChildNodes.Num(); Idx++)
	{
		FAutoCompleteNode* Node = AutoCompleteTree.ChildNodes[Idx];
		delete Node;
	}

	AutoCompleteTree.ChildNodes.Reset();

	// copy the manual list first
	AutoCompleteList.Reset();
	AutoCompleteList.AddDefaulted(ConsoleSettings->ManualAutoCompleteList.Num());
	for (int32 Idx = 0; Idx < ConsoleSettings->ManualAutoCompleteList.Num(); Idx++)
	{
		AutoCompleteList[Idx] = ConsoleSettings->ManualAutoCompleteList[Idx];
		AutoCompleteList[Idx].Color = ConsoleSettings->AutoCompleteCommandColor;
	}
	///...
	
		for (const FString& MapName : Packages)
		{
			int32 NewIdx = 0;
			// put _P maps at the front so that they match early, since those are generally the maps we want to actually open
			if (MapName.EndsWith(TEXT("_P")))
			{
				AutoCompleteList.InsertDefaulted(0, 3);
			}
			else
			{
				NewIdx = AutoCompleteList.AddDefaulted(3);
			}

			AutoCompleteList[NewIdx].Command = FString::Printf(TEXT("open %s"), *MapName);
			AutoCompleteList[NewIdx].Color = ConsoleSettings->AutoCompleteCommandColor;
			AutoCompleteList[NewIdx + 1].Command = FString::Printf(TEXT("travel %s"), *MapName);
			AutoCompleteList[NewIdx + 1].Color = ConsoleSettings->AutoCompleteCommandColor;
			AutoCompleteList[NewIdx + 2].Command = FString::Printf(TEXT("servertravel %s"), *MapName);
			AutoCompleteList[NewIdx + 2].Color = ConsoleSettings->AutoCompleteCommandColor;
		}
	}
  • 匹配
  1. 代码
void UConsole::UpdateCompleteIndices()
{
	if (!bIsRuntimeAutoCompleteUpToDate)
	{
		BuildRuntimeAutoCompleteList(true);
	}

	AutoComplete.Empty();
	AutoCompleteIndex = 0;
	AutoCompleteCursor = 0;

	if (CVarConsoleLegacySearch.GetValueOnAnyThread())
	{
		// use the old autocomplete behaviour
		FAutoCompleteNode* Node = &AutoCompleteTree;
		FString LowerTypedStr = TypedStr.ToLower();
		int32 EndIdx = -1;
		for (int32 Idx = 0; Idx < TypedStr.Len(); Idx++)
		{
			int32 Char = LowerTypedStr[Idx];
			bool bFoundMatch = false;
			int32 BranchCnt = 0;
			for (int32 CharIdx = 0; CharIdx < Node->ChildNodes.Num(); CharIdx++)
			{
				BranchCnt += Node->ChildNodes[CharIdx]->ChildNodes.Num();
				if (Node->ChildNodes[CharIdx]->IndexChar == Char)
				{
					bFoundMatch = true;
					Node = Node->ChildNodes[CharIdx];
					break;
				}
			}
			if (!bFoundMatch)
			{
				if (!bAutoCompleteLocked && BranchCnt > 0)
				{
					// we're off the grid!
					return;
				}
				else
				{
					if (Idx < TypedStr.Len())
					{
						// if the first non-matching character is a space we might be adding parameters, stay on the last node we found so users can see the parameter info
						if (TypedStr[Idx] == TCHAR(' '))
						{
							EndIdx = Idx;
							break;
						}
						// there is more text behind the auto completed text, we don't need auto completion
						return;
					}
					else
					{
						break;
					}
				}
			}
		}
		if (Node != &AutoCompleteTree)
		{
			const TArray<int32>& Leaf = Node->AutoCompleteListIndices;

			for (uint32 i = 0, Num = (uint32)Leaf.Num(); i < Num; ++i)
			{
				// if we're adding parameters we want to make sure that we only display exact matches
				// ie Typing "Foo 5" should still show info for "Foo" but not for "FooBar"
				if (EndIdx < 0 || AutoCompleteList[Leaf[i]].Command.Len() == EndIdx)
				{
					AutoComplete.Add(AutoCompleteList[Leaf[i]]);
				}
			}
			AutoComplete.Sort();
		}
	}
	else if (!TypedStr.IsEmpty())
	{
		// search for any substring, not just the prefix
		static FCheatTextFilter Filter(FCheatTextFilter::FItemToStringArray::CreateStatic(&CommandToStringArray));
		Filter.SetRawFilterText(FText::FromString(TypedStr));

		for (const FAutoCompleteCommand& Command : AutoCompleteList)
		{
			if (Filter.PassesFilter(Command))
			{
				AutoComplete.Add(Command);
			}
		}

		AutoComplete.Sort();
	}	
}
  1. 调用链
>	UnrealEditor-Engine.dll!UConsole::UpdateCompleteIndices() 行 551	C++
 	UnrealEditor-Engine.dll!UConsole::AppendInputText(const FString & Text) 行 798	C++
 	UnrealEditor-Engine.dll!UConsole::InputChar_Typing(FInputDeviceId DeviceId, const FString & Unicode) 行 819	C++
 	UnrealEditor-Engine.dll!UGameViewportClient::InputChar(FViewport * InViewport, int ControllerId, wchar_t Character) 行 749	C++
 	UnrealEditor-Engine.dll!FSceneViewport::OnKeyChar(const FGeometry & InGeometry, const FCharacterEvent & InCharacterEvent) 行 1147	C++
 	UnrealEditor-Slate.dll!SViewport::OnKeyChar(const FGeometry & MyGeometry, const FCharacterEvent & CharacterEvent) 行 302	C++
 	UnrealEditor-Slate.dll!FSlateApplication::ProcessKeyCharEvent::__l3::<lambda>(const FArrangedWidget & SomeWidgetGettingEvent, const FCharacterEvent & Event) 行 4511	C++
 	UnrealEditor-Slate.dll!FEventRouter::Route<FReply,FEventRouter::FBubblePolicy,FCharacterEvent,FReply <lambda>(const FArrangedWidget &, const FCharacterEvent &)>(FSlateApplication * ThisApplication, FEventRouter::FBubblePolicy RoutingPolicy, FCharacterEvent EventCopy, const FSlateApplication::ProcessKeyCharEvent::__l3::FReply <lambda>(const FArrangedWidget &, const FCharacterEvent &) & Lambda, ESlateDebuggingInputEvent DebuggingInputEvent) 行 425	C++
 	[内联框架] UnrealEditor-Slate.dll!FEventRouter::RouteAlongFocusPath(FSlateApplication *) 行 394	C++
 	UnrealEditor-Slate.dll!FSlateApplication::ProcessKeyCharEvent(const FCharacterEvent & InCharacterEvent) 行 4505	C++
 	UnrealEditor-Slate.dll!FSlateApplication::OnKeyChar(const wchar_t Character, const bool IsRepeat) 行 4478	C++
 	UnrealEditor-ApplicationCore.dll!FWindowsApplication::ProcessDeferredMessage(const FDeferredWindowsMessage & DeferredMessage) 行 1974	C++
 	UnrealEditor-ApplicationCore.dll!FWindowsApplication::DeferMessage(TSharedPtr<FWindowsWindow,1> & NativeWindow, HWND__ * InHWnd, unsigned int InMessage, unsigned __int64 InWParam, __int64 InLParam, int MouseX, int MouseY, unsigned int RawInputFlags) 行 2726	C++
 	UnrealEditor-ApplicationCore.dll!FWindowsApplication::ProcessMessage(HWND__ * hwnd, unsigned int msg, unsigned __int64 wParam, __int64 lParam) 行 1895	C++
 	[内联框架] UnrealEditor-ApplicationCore.dll!WindowsApplication_WndProc(HWND__ *) 行 919	C++
 	UnrealEditor-ApplicationCore.dll!FWindowsApplication::AppWndProc(HWND__ * hwnd, unsigned int msg, unsigned __int64 wParam, __int64 lParam) 行 925	C++