demo.ClientRecordAsyncEndOfFrame

demo.ClientRecordAsyncEndOfFrame

#Overview

name: demo.ClientRecordAsyncEndOfFrame

This variable is created as a Console Variable (cvar).

It is referenced in 7 C++ source files.

#Summary

#Usage in the C++ source code

The purpose of demo.ClientRecordAsyncEndOfFrame is to control whether the TickFlush function should be called on a separate thread in parallel with Slate during client-side replay recording in Unreal Engine.

This setting variable is primarily used in the replay system of Unreal Engine, specifically in the DemoNetDriver and ReplayHelper modules. It affects how the engine handles the recording of client-side replays.

The value of this variable is set through a console variable (CVar) named CVarDemoClientRecordAsyncEndOfFrame. It is initialized with a default value of 0, meaning the async recording is disabled by default.

The main variable that interacts with demo.ClientRecordAsyncEndOfFrame is CVarDemoClientRecordAsyncEndOfFrame, which is the actual console variable implementation.

Developers must be aware that enabling this variable (setting it to a non-zero value) will cause TickFlush to be called on a separate thread, which can improve performance but may also introduce thread-safety concerns.

Best practices when using this variable include:

  1. Only enable it if you’re sure your code can handle async recording safely.
  2. Test thoroughly with this setting enabled to ensure no thread-related issues arise.
  3. Be cautious when modifying channel-related code, as this setting affects how channels are created and managed.

Regarding the associated variable CVarDemoClientRecordAsyncEndOfFrame:

The purpose of CVarDemoClientRecordAsyncEndOfFrame is to provide a runtime-configurable way to enable or disable asynchronous end-of-frame tasks for client-side replay recording.

This variable is used in the DemoNetDriver and ReplayHelper subsystems of Unreal Engine.

The value is set through the console, and it can be changed at runtime.

It interacts closely with the Engine’s ShouldDoAsyncEndOfFrameTasks() function and the World’s IsRecordingClientReplay() function to determine if async recording should be performed.

Developers should be aware that changing this value at runtime can have immediate effects on how replay recording is performed.

Best practices include:

  1. Use GetValueOnAnyThread() when accessing this variable to ensure thread-safety.
  2. Consider the performance implications of enabling or disabling this feature, especially on lower-end hardware.
  3. Ensure that any code that might be affected by async recording is properly synchronized if this feature is enabled.

#References in C++ code

#Callsites

This variable is referenced in the following C++ source code:

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp:59

Scope: file

Source code excerpt:

static TAutoConsoleVariable<int32> CVarDemoLoadCheckpointGarbageCollect( TEXT( "demo.LoadCheckpointGarbageCollect" ), 1, TEXT("If nonzero, CollectGarbage will be called during LoadCheckpoint after the old actors and connection are cleaned up." ) );
TAutoConsoleVariable<float> CVarCheckpointSaveMaxMSPerFrameOverride( TEXT( "demo.CheckpointSaveMaxMSPerFrameOverride" ), -1.0f, TEXT( "If >= 0, this value will override the CheckpointSaveMaxMSPerFrame member variable, which is the maximum time allowed each frame to spend on saving a checkpoint. If 0, it will save the checkpoint in a single frame, regardless of how long it takes." ) );
TAutoConsoleVariable<int32> CVarDemoClientRecordAsyncEndOfFrame( TEXT( "demo.ClientRecordAsyncEndOfFrame" ), 0, TEXT( "If true, TickFlush will be called on a thread in parallel with Slate." ) );
static TAutoConsoleVariable<int32> CVarForceDisableAsyncPackageMapLoading( TEXT( "demo.ForceDisableAsyncPackageMapLoading" ), 0, TEXT( "If true, async package map loading of network assets will be disabled." ) );
TAutoConsoleVariable<int32> CVarDemoUseNetRelevancy( TEXT( "demo.UseNetRelevancy" ), 0, TEXT( "If 1, will enable relevancy checks and distance culling, using all connected clients as reference." ) );
static TAutoConsoleVariable<float> CVarDemoCullDistanceOverride( TEXT( "demo.CullDistanceOverride" ), 0.0f, TEXT( "If > 0, will represent distance from any viewer where actors will stop being recorded." ) );
static TAutoConsoleVariable<float> CVarDemoRecordHzWhenNotRelevant( TEXT( "demo.RecordHzWhenNotRelevant" ), 2.0f, TEXT( "Record at this frequency when actor is not relevant." ) );
static TAutoConsoleVariable<int32> CVarLoopDemo(TEXT("demo.Loop"), 0, TEXT("<1> : play replay from beginning once it reaches the end / <0> : stop replay at the end"));
static TAutoConsoleVariable<int32> CVarDemoFastForwardIgnoreRPCs( TEXT( "demo.FastForwardIgnoreRPCs" ), 1, TEXT( "If true, RPCs will be discarded during playback fast forward." ) );

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Classes/Engine/NetConnection.h:1671

Scope: file

Source code excerpt:

	bool bIgnoreReservedChannels;

	/** This is only used in UChannel::SendBunch. It's a member so that we can preserve the allocation between calls, as an optimization, and in a thread-safe way to be compatible with demo.ClientRecordAsyncEndOfFrame */
	TArray<FOutBunch*> OutgoingBunches;

	/** Per packet bookkeeping of written channelIds */
	FWrittenChannelsRecord ChannelRecord;

	/** Sequence data used to implement reliability */

#Associated Variable and Callsites

This variable is associated with another variable named CVarDemoClientRecordAsyncEndOfFrame. They share the same value. See the following C++ source code.

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp:59

Scope: file

Source code excerpt:

static TAutoConsoleVariable<int32> CVarDemoLoadCheckpointGarbageCollect( TEXT( "demo.LoadCheckpointGarbageCollect" ), 1, TEXT("If nonzero, CollectGarbage will be called during LoadCheckpoint after the old actors and connection are cleaned up." ) );
TAutoConsoleVariable<float> CVarCheckpointSaveMaxMSPerFrameOverride( TEXT( "demo.CheckpointSaveMaxMSPerFrameOverride" ), -1.0f, TEXT( "If >= 0, this value will override the CheckpointSaveMaxMSPerFrame member variable, which is the maximum time allowed each frame to spend on saving a checkpoint. If 0, it will save the checkpoint in a single frame, regardless of how long it takes." ) );
TAutoConsoleVariable<int32> CVarDemoClientRecordAsyncEndOfFrame( TEXT( "demo.ClientRecordAsyncEndOfFrame" ), 0, TEXT( "If true, TickFlush will be called on a thread in parallel with Slate." ) );
static TAutoConsoleVariable<int32> CVarForceDisableAsyncPackageMapLoading( TEXT( "demo.ForceDisableAsyncPackageMapLoading" ), 0, TEXT( "If true, async package map loading of network assets will be disabled." ) );
TAutoConsoleVariable<int32> CVarDemoUseNetRelevancy( TEXT( "demo.UseNetRelevancy" ), 0, TEXT( "If 1, will enable relevancy checks and distance culling, using all connected clients as reference." ) );
static TAutoConsoleVariable<float> CVarDemoCullDistanceOverride( TEXT( "demo.CullDistanceOverride" ), 0.0f, TEXT( "If > 0, will represent distance from any viewer where actors will stop being recorded." ) );
static TAutoConsoleVariable<float> CVarDemoRecordHzWhenNotRelevant( TEXT( "demo.RecordHzWhenNotRelevant" ), 2.0f, TEXT( "Record at this frequency when actor is not relevant." ) );
static TAutoConsoleVariable<int32> CVarLoopDemo(TEXT("demo.Loop"), 0, TEXT("<1> : play replay from beginning once it reaches the end / <0> : stop replay at the end"));
static TAutoConsoleVariable<int32> CVarDemoFastForwardIgnoreRPCs( TEXT( "demo.FastForwardIgnoreRPCs" ), 1, TEXT( "If true, RPCs will be discarded during playback fast forward." ) );

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp:1282

Scope (from outer to inner):

file
function     bool UDemoNetDriver::ShouldTickFlushAsyncEndOfFrame

Source code excerpt:

bool UDemoNetDriver::ShouldTickFlushAsyncEndOfFrame() const
{
	return GEngine && GEngine->ShouldDoAsyncEndOfFrameTasks() && CVarDemoClientRecordAsyncEndOfFrame.GetValueOnAnyThread() != 0 && World && World->IsRecordingClientReplay();
}

void UDemoNetDriver::TickFlush(float DeltaSeconds)
{
	if (!ShouldTickFlushAsyncEndOfFrame())
	{

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp:5381

Scope (from outer to inner):

file
function     UChannel* UDemoNetDriver::InternalCreateChannelByName

Source code excerpt:

UChannel* UDemoNetDriver::InternalCreateChannelByName(const FName& ChName)
{
	// In case of recording off the game thread with CVarDemoClientRecordAsyncEndOfFrame,
	// we need to clear the async flag on the channel so that it will get cleaned up by GC.
	// This should be safe since channel objects don't interact with async loading, and
	// async recording happens in a very controlled manner.
	UChannel* NewChannel = Super::InternalCreateChannelByName(ChName);
	if (NewChannel)
	{

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/ReplayHelper.cpp:27

Scope: file

Source code excerpt:

extern TAutoConsoleVariable<float> CVarCheckpointSaveMaxMSPerFrameOverride;
extern TAutoConsoleVariable<int32> CVarDemoUseNetRelevancy;
extern TAutoConsoleVariable<int32> CVarDemoClientRecordAsyncEndOfFrame;
extern TAutoConsoleVariable<float> CVarDemoRecordHz;
extern TAutoConsoleVariable<float> CVarDemoMinRecordHz;

CSV_DECLARE_CATEGORY_EXTERN(Demo);

FReplayHelper::FReplayHelper()

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/ReplayHelper.cpp:232

Scope (from outer to inner):

file
function     void FReplayHelper::WriteNetworkDemoHeader

Source code excerpt:

				DemoHeader.HeaderFlags |= EReplayHeaderFlags::ClientRecorded;

				if (CVarDemoClientRecordAsyncEndOfFrame.GetValueOnAnyThread() > 0)
				{
					DemoHeader.HeaderFlags |= EReplayHeaderFlags::AsyncRecorded;
				}
			}
		}