Files
UnrealEngineUWP/Engine/Source/Runtime/Engine/Private/RepLayout.cpp
John Pollard c61ba91c6d More package map cleanup
[CL 2096430 by John Pollard in Main branch]
2014-06-05 19:49:52 -04:00

2583 lines
79 KiB
C++

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
RepLayout.cpp: Unreal replication layout implementation.
=============================================================================*/
#include "EnginePrivate.h"
#include "Net/RepLayout.h"
#include "Net/DataReplication.h"
#include "Net/NetworkProfiler.h"
#include "Engine/ActorChannel.h"
static TAutoConsoleVariable<int32> CVarAllowPropertySkipping( TEXT( "net.AllowPropertySkipping" ), 1, TEXT( "Allow skipping of properties that haven't changed for other clients" ) );
static TAutoConsoleVariable<int32> CVarDoPropertyChecksum( TEXT( "net.DoPropertyChecksum" ), 0, TEXT( "" ) );
FAutoConsoleVariable CVarDoReplicationContextString( TEXT( "net.ContextDebug" ), 0, TEXT( "" ) );
#define ENABLE_PROPERTY_CHECKSUMS
//#define SANITY_CHECK_MERGES
#define USE_CUSTOM_COMPARE
//#define ENABLE_SUPER_CHECKSUMS
#ifdef USE_CUSTOM_COMPARE
static FORCEINLINE bool CompareBool( const FRepLayoutCmd & Cmd, const void * A, const void * B )
{
return Cmd.Property->Identical( A, B );
}
static FORCEINLINE bool CompareObject( const FRepLayoutCmd & Cmd, const void * A, const void * B )
{
return Cmd.Property->Identical( A, B );
}
template< typename T >
bool CompareValue( const T * A, const T * B )
{
return *A == *B;
}
template< typename T >
bool CompareValue( const void * A, const void * B )
{
return CompareValue( (T*)A, (T*)B);
}
static FORCEINLINE bool PropertiesAreIdenticalNative( const FRepLayoutCmd & Cmd, const void * A, const void * B )
{
switch ( Cmd.Type )
{
case REPCMD_PropertyBool: return CompareBool( Cmd, A, B );
case REPCMD_PropertyByte: return CompareValue<uint8>( A, B );
case REPCMD_PropertyFloat: return CompareValue<float>( A, B );
case REPCMD_PropertyInt: return CompareValue<int32>( A, B );
case REPCMD_PropertyName: return CompareValue<FName>( A, B );
case REPCMD_PropertyObject: return CompareObject( Cmd, A, B );
case REPCMD_PropertyUInt32: return CompareValue<uint32>( A, B );
case REPCMD_PropertyUInt64: return CompareValue<uint64>( A, B );
case REPCMD_PropertyVector: return CompareValue<FVector>( A, B );
case REPCMD_PropertyVector100: return CompareValue<FVector_NetQuantize100>( A, B );
case REPCMD_PropertyVectorQ: return CompareValue<FVector_NetQuantize>( A, B );
case REPCMD_PropertyVectorNormal: return CompareValue<FVector_NetQuantizeNormal>( A, B );
case REPCMD_PropertyVector10: return CompareValue<FVector_NetQuantize10>( A, B );
case REPCMD_PropertyPlane: return CompareValue<FPlane>( A, B );
case REPCMD_PropertyRotator: return CompareValue<FRotator>( A, B );
case REPCMD_PropertyNetId: return CompareValue<FUniqueNetIdRepl>( A, B );
case REPCMD_RepMovement: return CompareValue<FRepMovement>( A, B );
case REPCMD_PropertyString: return CompareValue<FString>( A, B );
case REPCMD_Property: return Cmd.Property->Identical( A, B );
default:
UE_LOG( LogNet, Fatal, TEXT( "PropertiesAreIdentical: Unsupported type! %i (%s)" ), Cmd.Type, *Cmd.Property->GetName() );
}
return false;
}
static FORCEINLINE bool PropertiesAreIdentical( const FRepLayoutCmd & Cmd, const void * A, const void * B )
{
const bool bIsIdentical = PropertiesAreIdenticalNative( Cmd, A, B );
#if 0
// Sanity check result
if ( bIsIdentical != Cmd.Property->Identical( A, B ) )
{
UE_LOG( LogNet, Fatal, TEXT( "PropertiesAreIdentical: Result mismatch! (%s)" ), *Cmd.Property->GetFullName() );
}
#endif
return bIsIdentical;
}
#else
static FORCEINLINE bool PropertiesAreIdentical( const FRepLayoutCmd & Cmd, const void * A, const void * B )
{
return Cmd.Property->Identical( A, B );
}
#endif
static FORCEINLINE void StoreProperty( const FRepLayoutCmd & Cmd, void * A, const void * B )
{
Cmd.Property->CopySingleValue( A, B );
}
static FORCEINLINE void SerializeGenericChecksum( FArchive & Ar )
{
uint32 Checksum = 0xABADF00D;
Ar << Checksum;
check( Checksum == 0xABADF00D );
}
static void SerializeReadWritePropertyChecksum( const FRepLayoutCmd & Cmd, const int32 CurCmdIndex, const uint8 * Data, FArchive & Ar, const bool bDiscard )
{
if ( bDiscard )
{
uint32 MarkerChecksum = 0;
uint32 PropertyChecksum = 0;
Ar << MarkerChecksum;
Ar << PropertyChecksum;
return;
}
// Serialize various attributes that will mostly ensure we are working on the same property
const uint32 NameHash = GetTypeHash( Cmd.Property->GetName() );
uint32 MarkerChecksum = 0;
// Evolve the checksum over several values that will uniquely identity where we are and should be
MarkerChecksum = FCrc::MemCrc_DEPRECATED( &NameHash, sizeof( NameHash ), MarkerChecksum );
MarkerChecksum = FCrc::MemCrc_DEPRECATED( &Cmd.Offset, sizeof( Cmd.Offset ), MarkerChecksum );
MarkerChecksum = FCrc::MemCrc_DEPRECATED( &CurCmdIndex, sizeof( CurCmdIndex ), MarkerChecksum );
const uint32 OriginalMarkerChecksum = MarkerChecksum;
Ar << MarkerChecksum;
if ( MarkerChecksum != OriginalMarkerChecksum )
{
// This is fatal, as it means we are out of sync to the point we can't recover
UE_LOG( LogNet, Fatal, TEXT( "SerializeReadWritePropertyChecksum: Property checksum marker failed! [%s]" ), *Cmd.Property->GetFullName() );
}
if ( Cmd.Property->IsA( UObjectPropertyBase::StaticClass() ) )
{
// Can't handle checksums for objects right now
// Need to resolve how to handle unmapped objects
return;
}
// Now generate a checksum that guarantee that this property is in the exact state as the server
// This will require NetSerializeItem to be deterministic, in and out
// i.e, not only does NetSerializeItem need to write the same blob on the same input data, but
// it also needs to write the same blob it just read as well.
FBitWriter Writer( 0, true );
Cmd.Property->NetSerializeItem( Writer, NULL, const_cast< uint8 * >( Data ) );
if ( Ar.IsSaving() )
{
// If this is the server, do a read, and then another write so that we do exactly what the client will do, which will better ensure determinism
// We do this to force InitializeValue, DestroyValue etc to work on a single item
const int32 OriginalDim = Cmd.Property->ArrayDim;
Cmd.Property->ArrayDim = 1;
TArray< uint8 > TempPropMemory;
TempPropMemory.AddZeroed( Cmd.Property->ElementSize + 4 );
uint32 * Guard = (uint32*)&TempPropMemory[TempPropMemory.Num() - 4];
const uint32 TAG_VALUE = 0xABADF00D;
*Guard = TAG_VALUE;
Cmd.Property->InitializeValue( TempPropMemory.GetTypedData() );
check( *Guard == TAG_VALUE );
// Read it back in and then write it out to produce what the client will produce
FBitReader Reader( Writer.GetData(), Writer.GetNumBits() );
Cmd.Property->NetSerializeItem( Reader, NULL, TempPropMemory.GetTypedData() );
check( Reader.AtEnd() && !Reader.IsError() );
check( *Guard == TAG_VALUE );
// Write it back out for a final time
Writer.Reset();
Cmd.Property->NetSerializeItem( Writer, NULL, TempPropMemory.GetTypedData() );
check( *Guard == TAG_VALUE );
// Destroy temp memory
Cmd.Property->DestroyValue( TempPropMemory.GetTypedData() );
// Restore the static array size
Cmd.Property->ArrayDim = OriginalDim;
check( *Guard == TAG_VALUE );
}
uint32 PropertyChecksum = FCrc::MemCrc_DEPRECATED( Writer.GetData(), Writer.GetNumBytes() );
const uint32 OriginalPropertyChecksum = PropertyChecksum;
Ar << PropertyChecksum;
if ( PropertyChecksum != OriginalPropertyChecksum )
{
// This is a warning, because for some reason, float rounding issues in the quantization functions cause this to return false positives
UE_LOG( LogNet, Warning, TEXT( "Property checksum failed! [%s]" ), *Cmd.Property->GetFullName() );
}
}
uint16 FRepLayout::CompareProperties_r(
const int32 CmdStart,
const int32 CmdEnd,
const uint8 * RESTRICT CompareData,
const uint8 * RESTRICT Data,
TArray< uint16 > & Changed,
uint16 Handle
) const
{
for ( int32 CmdIndex = CmdStart; CmdIndex < CmdEnd; CmdIndex++ )
{
const FRepLayoutCmd & Cmd = Cmds[CmdIndex];
check( Cmd.Type != REPCMD_Return );
Handle++;
if ( Cmd.Type == REPCMD_DynamicArray )
{
// Once we hit an array, start using a stack based approach
CompareProperties_Array_r( CompareData ? CompareData + Cmd.Offset : NULL, (const uint8*)Data + Cmd.Offset, Changed, CmdIndex, Handle );
CmdIndex = Cmd.EndCmd - 1; // The -1 to handle the ++ in the for loop
continue;
}
if ( CompareData == NULL || !PropertiesAreIdentical( Cmd, (const void*)( CompareData + Cmd.Offset ), (const void*)( Data + Cmd.Offset ) ) )
{
Changed.Add( Handle );
}
}
return Handle;
}
void FRepLayout::CompareProperties_Array_r(
const uint8 * RESTRICT CompareData,
const uint8 * RESTRICT Data,
TArray< uint16 > & Changed,
const uint16 CmdIndex,
const uint16 Handle
) const
{
const FRepLayoutCmd & Cmd = Cmds[CmdIndex];
FScriptArray * CompareArray = (FScriptArray *)CompareData;
FScriptArray * Array = (FScriptArray *)Data;
const uint16 ArrayNum = Array->Num();
const uint16 CompareArrayNum = CompareArray ? CompareArray->Num() : 0;
TArray< uint16 > ChangedLocal;
uint16 LocalHandle = 0;
Data = (uint8*)Array->GetData();
CompareData = CompareData ? (uint8*)CompareArray->GetData() : (uint8*)NULL;
for ( int32 i = 0; i < ArrayNum; i++ )
{
const int32 ElementOffset = i * Cmd.ElementSize;
const uint8 * LocalCompareData = ( i < CompareArrayNum ) ? ( CompareData + ElementOffset ) : NULL;
LocalHandle = CompareProperties_r( CmdIndex + 1, Cmd.EndCmd - 1, LocalCompareData, Data + ElementOffset, ChangedLocal, LocalHandle );
}
if ( ChangedLocal.Num() )
{
Changed.Add( Handle );
Changed.Add( ChangedLocal.Num() ); // This is so we can jump over the array if we need to
Changed.Append( ChangedLocal );
Changed.Add( 0 );
}
else if ( ArrayNum != CompareArrayNum )
{
// If nothing below us changed, we either shrunk, or we grew and our inner was an array that didn't have any elements
check( ArrayNum < CompareArrayNum || Cmds[CmdIndex + 1].Type == REPCMD_DynamicArray );
// Array got smaller, send the array handle to force array size change
Changed.Add( Handle );
Changed.Add( 0 );
Changed.Add( 0 );
}
}
bool FRepLayout::CompareProperties(
FRepState * RESTRICT RepState,
const uint8 * RESTRICT CompareData,
const uint8 * RESTRICT Data,
TArray< FRepChangedParent > & OutChangedParents,
const TArray< uint16 > & PropertyList ) const
{
bool PropertyChanged = false;
const uint16 * RESTRICT FirstProp = PropertyList.GetTypedData();
const uint16 * RESTRICT LastProp = FirstProp + PropertyList.Num();
for ( const uint16 * RESTRICT pLifeProp = FirstProp; pLifeProp < LastProp; ++pLifeProp )
{
//#if USE_NETWORK_PROFILER
//const uint32 PropertyStartTime = GNetworkProfiler.IsTrackingEnabled() ? FPlatformTime::Cycles() : 0;
//#endif
const FRepParentCmd & ParentCmd = Parents[*pLifeProp];
// We store changed properties on each parent, so we can build a final sorted change list later
TArray< uint16 > & Changed = OutChangedParents[*pLifeProp].Changed;
check( Changed.Num() == 0 );
// Loop over the block of child properties that are children of this parent
for ( int32 i = ParentCmd.CmdStart; i < ParentCmd.CmdEnd; i++ )
{
const FRepLayoutCmd & Cmd = Cmds[i];
check( Cmd.Type != REPCMD_Return ); // REPCMD_Return's are markers we shouldn't hit
if ( Cmd.Type == REPCMD_DynamicArray )
{
// Once we hit an array, start using a recursive based approach
CompareProperties_Array_r( CompareData + Cmd.Offset, Data + Cmd.Offset, Changed, i, Cmd.RelativeHandle );
i = Cmd.EndCmd - 1; // Jump past properties under array, we've already checked them (the -1 because of the ++ in the for loop)
continue;
}
if ( !PropertiesAreIdentical( Cmd, (void*)( CompareData + Cmd.Offset ), (const void*)( Data + Cmd.Offset ) ) )
{
// Add this properties handle to the change list
Changed.Add( Cmd.RelativeHandle );
}
}
if ( Changed.Num() > 0 )
{
// Something changed on this parent property
PropertyChanged = true;
}
//NETWORK_PROFILER( GNetworkProfiler.TrackReplicateProperty( ParentCmd.Property, true, false, FPlatformTime::Cycles() - PropertyStartTime, ParentCmd.Property->ElementSize * 8, 0 ) );
}
return PropertyChanged;
}
void FRepLayout::LogChangeListMismatches(
const uint8 * Data,
UActorChannel * OwningChannel,
const TArray< uint16 > & PropertyList,
FRepState * RepState1,
FRepState * RepState2,
const TArray< FRepChangedParent > & ChangedParents1,
const TArray< FRepChangedParent > & ChangedParents2 ) const
{
FRepChangedPropertyTracker * ChangeTracker = RepState1->RepChangedPropertyTracker.Get();
UObject * Object = (UObject*)Data;
const UNetDriver * NetDriver = OwningChannel->Connection->Driver;
UE_LOG( LogNet, Warning, TEXT( "LogChangeListMismatches: %s" ), *Object->GetName() );
UE_LOG( LogNet, Warning, TEXT( " %i, %i, %i, %i" ), NetDriver->ReplicationFrame, ChangeTracker->LastReplicationFrame, ChangeTracker->LastReplicationGroupFrame, RepState1->LastReplicationFrame );
for ( int32 i = 0; i < PropertyList.Num(); i++ )
{
const int32 Index = PropertyList[i];
const int32 Changed1 = ChangedParents1[Index].Changed.Num();
const int32 Changed2 = ChangedParents2[Index].Changed.Num();
if ( Changed1 || Changed2 )
{
FString Changed1Str = Changed1 ? TEXT( "TRUE" ) : TEXT( "FALSE" );
FString Changed2Str = Changed2 ? TEXT( "TRUE" ) : TEXT( "FALSE" );
FString ExtraStr = Changed1 != Changed2 ? TEXT( "<--- MISMATCH!" ) : TEXT( "<--- SAME" );
UE_LOG( LogNet, Warning, TEXT( " Property changed: %s (%s / %s) %s" ), *Parents[Index].Property->GetName(), *Changed1Str, *Changed2Str, *ExtraStr );
FString StringValue1;
FString StringValue2;
FString StringValue3;
Parents[Index].Property->ExportText_InContainer( Parents[Index].ArrayIndex, StringValue1, Data, NULL, NULL, PPF_DebugDump );
Parents[Index].Property->ExportText_InContainer( Parents[Index].ArrayIndex, StringValue2, RepState1->StaticBuffer.GetTypedData(), NULL, NULL, PPF_DebugDump );
Parents[Index].Property->ExportText_InContainer( Parents[Index].ArrayIndex, StringValue3, RepState2->StaticBuffer.GetTypedData(), NULL, NULL, PPF_DebugDump );
UE_LOG( LogNet, Warning, TEXT( " Values: %s, %s, %s" ), *StringValue1, *StringValue2, *StringValue3 );
}
}
}
static bool ChangedParentsHasChanged( const TArray< uint16 > & PropertyList, const TArray< FRepChangedParent > & ChangedParents )
{
for ( int32 i = 0; i < PropertyList.Num(); i++ )
{
if ( ChangedParents[PropertyList[i]].Changed.Num() )
{
return true;
}
}
return false;
}
void FRepLayout::SanityCheckShadowStateAgainstChangeList(
FRepState * RepState,
const uint8 * Data,
UActorChannel * OwningChannel,
const TArray< uint16 > & PropertyList,
FRepState * OtherRepState,
const TArray< FRepChangedParent > & OtherChangedParents ) const
{
const uint8 * CompareData = RepState->StaticBuffer.GetTypedData();
UObject * Object = (UObject*)Data;
TArray< FRepChangedParent > LocalChangedParents;
LocalChangedParents.SetNum( OtherChangedParents.Num() );
// Do an actual check to see which properties are different from our internal shadow state, and make sure the passed in change list matches
const bool Result = CompareProperties( RepState, CompareData, Data, LocalChangedParents, PropertyList );
if ( Result != ChangedParentsHasChanged( PropertyList, OtherChangedParents ) )
{
LogChangeListMismatches( Data, OwningChannel, PropertyList, RepState, OtherRepState, LocalChangedParents, OtherChangedParents );
UE_LOG( LogNet, Fatal, TEXT( "ReplicateProperties: Compare result mismatch: %s" ), *Object->GetName() );
}
for ( int32 i = 0; i < PropertyList.Num(); i++ )
{
const int32 Index = PropertyList[i];
if ( OtherChangedParents[Index].Changed.Num() != LocalChangedParents[Index].Changed.Num() )
{
LogChangeListMismatches( Data, OwningChannel, PropertyList, RepState, OtherRepState, LocalChangedParents, OtherChangedParents );
UE_LOG( LogNet, Fatal, TEXT( "ReplicateProperties: Compare count mismatch: %s" ), *Object->GetName() );
}
for ( int32 j = 0; j < OtherChangedParents[Index].Changed.Num(); j++ )
{
if ( OtherChangedParents[Index].Changed[j] != LocalChangedParents[Index].Changed[j] )
{
LogChangeListMismatches( Data, OwningChannel, PropertyList, RepState, OtherRepState, LocalChangedParents, OtherChangedParents );
UE_LOG( LogNet, Fatal, TEXT( "ReplicateProperties: Compare changelist value mismatch: %s" ), *Object->GetName() );
}
}
}
}
bool FRepLayout::ReplicateProperties(
FRepState * RESTRICT RepState,
const uint8 * RESTRICT Data,
UClass * ObjectClass,
UActorChannel * OwningChannel,
FOutBunch & Writer,
const FReplicationFlags & RepFlags,
int32 & LastIndex,
bool & bContentBlockWritten ) const
{
SCOPE_CYCLE_COUNTER( STAT_NetReplicateDynamicPropTime );
check( ObjectClass == Owner );
UObject * Object = (UObject*)Data;
const UNetDriver * NetDriver = OwningChannel->Connection->Driver;
FRepChangedPropertyTracker * ChangeTracker = RepState->RepChangedPropertyTracker.Get();
const uint8 * CompareData = RepState->StaticBuffer.GetTypedData();
// Rebuild conditional properties if needed
if ( RepState->RepFlags.Value != RepFlags.Value || RepState->ActiveStatusChanged != ChangeTracker->ActiveStatusChanged )
{
RebuildConditionalProperties( RepState, *ChangeTracker, RepFlags );
RepState->RepFlags.Value = RepFlags.Value;
RepState->ActiveStatusChanged = ChangeTracker->ActiveStatusChanged;
}
bool PropertyChanged = false;
#ifdef ENABLE_SUPER_CHECKSUMS
const bool bIsAllAcked = AllAcked( RepState );
if ( bIsAllAcked || !RepState->OpenAckedCalled )
#endif
{
const int32 AllowSkipping = CVarAllowPropertySkipping.GetValueOnGameThread();
const bool bCanSkip = AllowSkipping > 0 &&
RepState->LastReplicationFrame != 0 &&
ChangeTracker->LastReplicationFrame == NetDriver->ReplicationFrame &&
ChangeTracker->LastReplicationGroupFrame == RepState->LastReplicationFrame;
if ( bCanSkip )
{
INC_DWORD_STAT_BY( STAT_NetSkippedDynamicProps, UnconditionalLifetime.Num() );
if ( AllowSkipping == 2 )
{
// Sanity check results
check( ChangeTracker->UnconditionalPropChanged == ChangedParentsHasChanged( UnconditionalLifetime, ChangeTracker->Parents ) );
SanityCheckShadowStateAgainstChangeList( RepState, Data, OwningChannel, UnconditionalLifetime, ChangeTracker->LastRepState, ChangeTracker->Parents );
}
}
else
{
// FRepState group changed, force this group to compare again this frame
// This happens either once a frame, which is normal, or multiple times a frame
// when multiple connections of the same actor aren't updated at the same time
ChangeTracker->LastReplicationFrame = NetDriver->ReplicationFrame;
ChangeTracker->LastReplicationGroupFrame = RepState->LastReplicationFrame;
ChangeTracker->LastRepState = RepState;
// Reset changed list if anything changed last time
if ( ChangeTracker->UnconditionalPropChanged )
{
for ( int32 i = UnconditionalLifetime.Num() - 1; i >= 0; i-- )
{
ChangeTracker->Parents[UnconditionalLifetime[i]].Changed.Empty();
}
}
// Loop over all unconditional lifetime properties
ChangeTracker->UnconditionalPropChanged = CompareProperties( RepState, CompareData, Data, ChangeTracker->Parents, UnconditionalLifetime );
}
// Remember the last frame this FRepState was replicated, so we can note above when the FRepState replication group changes
RepState->LastReplicationFrame = NetDriver->ReplicationFrame;
if ( ChangeTracker->UnconditionalPropChanged )
{
PropertyChanged = true;
}
// Loop over all the conditional properties
if ( CompareProperties( RepState, CompareData, Data, ChangeTracker->Parents, RepState->ConditionalLifetime ) )
{
PropertyChanged = true;
}
}
#ifdef ENABLE_SUPER_CHECKSUMS
else
{
// If we didn't compare this frame, make sure to reset out replication frame
// This is to force a compare next time it comes up
RepState->LastReplicationFrame = 0;
}
#endif
// PreOpenAckHistory are all the properties sent before we got our first open ack
const bool bFlushPreOpenAckHistory = RepState->OpenAckedCalled && RepState->PreOpenAckHistory.Num() > 0;
if ( PropertyChanged || RepState->NumNaks > 0 || bFlushPreOpenAckHistory )
{
// Use the first inactive history item to build this change list on
check( RepState->HistoryEnd - RepState->HistoryStart < FRepState::MAX_CHANGE_HISTORY );
const int32 HistoryIndex = RepState->HistoryEnd % FRepState::MAX_CHANGE_HISTORY;
FRepChangedHistory & NewHistoryItem = RepState->ChangeHistory[ HistoryIndex ];
RepState->HistoryEnd++;
TArray<uint16> & Changed = NewHistoryItem.Changed;
check( Changed.Num() == 0 ); // Make sure this history item is actually inactive
if ( PropertyChanged )
{
// Initialize the history item change list with the parent change lists
// We do it in the order of the parents so that the final change list will be fully sorted
for ( int32 i = 0; i < Parents.Num(); i++ )
{
if ( ChangeTracker->Parents[i].Changed.Num() > 0 )
{
Changed.Append( ChangeTracker->Parents[i].Changed );
if ( Parents[i].Flags & PARENT_IsConditional )
{
// Reset properties that don't share information across connections
ChangeTracker->Parents[i].Changed.Empty();
}
}
}
Changed.Add( 0 );
#ifdef SANITY_CHECK_MERGES
SanityCheckChangeList( Data, Changed );
#endif
}
// Update the history, and merge in any nak'd change lists
UpdateChangelistHistory( RepState, ObjectClass, Data, OwningChannel->Connection->OutAckPacketId, &Changed );
// Merge in the PreOpenAckHistory (unreliable properties sent before the bunch was initially acked)
if ( bFlushPreOpenAckHistory )
{
for ( int32 i = 0; i < RepState->PreOpenAckHistory.Num(); i++ )
{
TArray< uint16 > Temp = Changed;
Changed.Empty();
MergeDirtyList( (void*)Data, Temp, RepState->PreOpenAckHistory[i].Changed, Changed );
}
RepState->PreOpenAckHistory.Empty();
}
// At this point we should have a non empty change list
check( Changed.Num() > 0 );
#ifdef SANITY_CHECK_MERGES
SanityCheckChangeList( Data, Changed );
#endif
// For RepLayout properties, we hijack the first non custom property, and use that to identify these properties
WritePropertyHeader( (UObject*)Data, ObjectClass, OwningChannel, Parents[FirstNonCustomParent].Property, Writer, 0, LastIndex, bContentBlockWritten );
// Send the final merged change list
SendProperties( RepState, RepFlags, Data, ObjectClass, OwningChannel, Writer, Changed, LastIndex, bContentBlockWritten );
#ifdef ENABLE_SUPER_CHECKSUMS
Writer.WriteBit( bIsAllAcked ? 1 : 0 );
if ( bIsAllAcked )
{
ValidateWithChecksum( RepState->StaticBuffer.GetTypedData(), Writer, false );
}
#endif
return true;
}
// Nothing changed and there are no nak's, so just do normal housekeeping and remove acked history items
UpdateChangelistHistory( RepState, ObjectClass, Data, OwningChannel->Connection->OutAckPacketId, NULL );
return false;
}
void FRepLayout::UpdateChangelistHistory( FRepState * RepState, UClass * ObjectClass, const uint8 * RESTRICT Data, const int32 AckPacketId, TArray< uint16 > * OutMerged ) const
{
check( RepState->HistoryEnd >= RepState->HistoryStart );
const int32 HistoryCount = RepState->HistoryEnd - RepState->HistoryStart;
const bool DumpHistory = HistoryCount == FRepState::MAX_CHANGE_HISTORY;
// If our buffer is currently full, forcibly send the entire history
if ( DumpHistory )
{
UE_LOG( LogNet, Warning, TEXT( "FRepLayout::UpdateChangelistHistory: History overflow, forcing history dump %s" ), *ObjectClass->GetName() );
}
for ( int32 i = RepState->HistoryStart; i < RepState->HistoryEnd; i++ )
{
const int32 HistoryIndex = i % FRepState::MAX_CHANGE_HISTORY;
FRepChangedHistory & HistoryItem = RepState->ChangeHistory[ HistoryIndex ];
if ( HistoryItem.OutPacketIdRange.First == INDEX_NONE )
{
continue; // Hasn't been initialized in PostReplicate yet
}
check( HistoryItem.Changed.Num() > 0 ); // All active history items should contain a change list
if ( AckPacketId >= HistoryItem.OutPacketIdRange.Last || HistoryItem.Resend || DumpHistory )
{
if ( HistoryItem.Resend || DumpHistory )
{
// Merge in nak'd change lists
check( OutMerged != NULL );
TArray< uint16 > Temp = *OutMerged;
OutMerged->Empty();
MergeDirtyList( (void*)Data, Temp, HistoryItem.Changed, *OutMerged );
HistoryItem.Changed.Empty();
#ifdef SANITY_CHECK_MERGES
SanityCheckChangeList( Data, *OutMerged );
#endif
if ( HistoryItem.Resend )
{
HistoryItem.Resend = false;
RepState->NumNaks--;
}
}
HistoryItem.Changed.Empty();
HistoryItem.OutPacketIdRange = FPacketIdRange();
RepState->HistoryStart++;
}
}
// Remove any tiling in the history markers to keep them from wrapping over time
const int32 NewHistoryCount = RepState->HistoryEnd - RepState->HistoryStart;
RepState->HistoryStart = RepState->HistoryStart % FRepState::MAX_CHANGE_HISTORY;
RepState->HistoryEnd = RepState->HistoryStart + NewHistoryCount;
check( RepState->NumNaks == 0 ); // Make sure we processed all the naks properly
}
void FRepLayout::OpenAcked( FRepState * RepState ) const
{
check( RepState != NULL );
RepState->OpenAckedCalled = true;
}
void FRepLayout::PostReplicate( FRepState * RepState, FPacketIdRange & PacketRange, bool bReliable ) const
{
for ( int32 i = RepState->HistoryStart; i < RepState->HistoryEnd; i++ )
{
const int32 HistoryIndex = i % FRepState::MAX_CHANGE_HISTORY;
FRepChangedHistory & HistoryItem = RepState->ChangeHistory[ HistoryIndex ];
if ( HistoryItem.OutPacketIdRange.First == INDEX_NONE )
{
check( HistoryItem.Changed.Num() > 0 );
check( !HistoryItem.Resend );
HistoryItem.OutPacketIdRange = PacketRange;
if ( !bReliable && !RepState->OpenAckedCalled )
{
RepState->PreOpenAckHistory.Add( HistoryItem );
}
}
}
}
void FRepLayout::ReceivedNak( FRepState * RepState, int32 NakPacketId ) const
{
if ( RepState == NULL )
{
return; // I'm not 100% certain why this happens, the only think I can think of is this is a bNetTemporary?
}
for ( int32 i = RepState->HistoryStart; i < RepState->HistoryEnd; i++ )
{
const int32 HistoryIndex = i % FRepState::MAX_CHANGE_HISTORY;
FRepChangedHistory & HistoryItem = RepState->ChangeHistory[ HistoryIndex ];
if ( !HistoryItem.Resend && HistoryItem.OutPacketIdRange.InRange( NakPacketId ) )
{
check( HistoryItem.Changed.Num() > 0 );
HistoryItem.Resend = true;
RepState->NumNaks++;
}
}
}
bool FRepLayout::AllAcked( FRepState * RepState ) const
{
if ( RepState->HistoryStart != RepState->HistoryEnd )
{
// We have change lists that haven't been acked
return false;
}
if ( RepState->NumNaks > 0 )
{
return false;
}
if ( !RepState->OpenAckedCalled )
{
return false;
}
if ( RepState->PreOpenAckHistory.Num() > 0 )
{
return false;
}
return true;
}
bool FRepLayout::ReadyForDormancy( FRepState * RepState ) const
{
if ( RepState == NULL )
{
return false;
}
return AllAcked( RepState );
}
static FORCEINLINE void WritePropertyHandle( FNetBitWriter & Writer, uint16 Handle, bool bDoChecksum )
{
uint32 LocalHandle = Handle;
Writer.SerializeIntPacked( LocalHandle );
#ifdef ENABLE_PROPERTY_CHECKSUMS
if ( bDoChecksum )
{
SerializeGenericChecksum( Writer );
}
#endif
}
static bool ShouldSendProperty( FRepWriterState & WriterState, const uint16 Handle )
{
if ( Handle == WriterState.Changed[WriterState.CurrentChanged] )
{
// Write out the handle
WritePropertyHandle( WriterState.Writer, Handle, WriterState.bDoChecksum );
// Advance to the next expected handle
WriterState.CurrentChanged++;
return true;
}
return false;
}
void FRepLayout::SendProperties_DynamicArray_r(
FRepState * RESTRICT RepState,
const FReplicationFlags & RepFlags,
FRepWriterState & WriterState,
const int32 CmdIndex,
const uint8 * RESTRICT StoredData,
const uint8 * RESTRICT Data,
uint16 Handle ) const
{
const FRepLayoutCmd & Cmd = Cmds[ CmdIndex ];
FScriptArray * Array = (FScriptArray *)Data;
FScriptArray * StoredArray = (FScriptArray *)StoredData;
// Write array num
uint16 ArrayNum = Array->Num();
WriterState.Writer << ArrayNum;
// Make the shadow state match the actual state at the time of send
FScriptArrayHelper StoredArrayHelper( (UArrayProperty *)Cmd.Property, StoredData );
StoredArrayHelper.Resize( ArrayNum );
// Read the jump offset
// We won't need to actually jump over anything because we expect the change list to be pruned once we get here
// But we can use it to verify we read the correct amount.
const int32 ArrayChangedCount = WriterState.Changed[WriterState.CurrentChanged++];
const int32 OldChangedIndex = WriterState.CurrentChanged;
Data = (uint8*)Array->GetData();
StoredData = (uint8*)StoredArray->GetData();
uint16 LocalHandle = 0;
for ( int32 i = 0; i < Array->Num(); i++ )
{
const int32 ElementOffset = i * Cmd.ElementSize;
LocalHandle = SendProperties_r( RepState, RepFlags, WriterState, CmdIndex + 1, Cmd.EndCmd - 1, StoredData + ElementOffset, Data + ElementOffset, LocalHandle );
}
check( WriterState.CurrentChanged - OldChangedIndex == ArrayChangedCount ); // Make sure we read correct amount
check( WriterState.Changed[WriterState.CurrentChanged] == 0 ); // Make sure we are at the end
WriterState.CurrentChanged++;
WritePropertyHandle( WriterState.Writer, 0, WriterState.bDoChecksum ); // Signify end of dynamic array
}
uint16 FRepLayout::SendProperties_r(
FRepState * RESTRICT RepState,
const FReplicationFlags & RepFlags,
FRepWriterState & WriterState,
const int32 CmdStart,
const int32 CmdEnd,
const uint8 * RESTRICT StoredData,
const uint8 * RESTRICT Data,
uint16 Handle ) const
{
for ( int32 CmdIndex = CmdStart; CmdIndex < CmdEnd; CmdIndex++ )
{
const FRepLayoutCmd & Cmd = Cmds[ CmdIndex ];
check( Cmd.Type != REPCMD_Return );
Handle++;
if ( Cmd.Type == REPCMD_DynamicArray )
{
if ( ShouldSendProperty( WriterState, Handle ) )
{
SendProperties_DynamicArray_r( RepState, RepFlags, WriterState, CmdIndex, StoredData + Cmd.Offset, Data + Cmd.Offset, Handle );
}
CmdIndex = Cmd.EndCmd - 1; // Jump past children of this array (-1 for the ++ in the for loop)
continue;
}
if ( ShouldSendProperty( WriterState, Handle ) )
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (CVarDoReplicationContextString->GetInt() > 0)
{
WriterState.Writer.PackageMap->SetDebugContextString( FString::Printf(TEXT("%s - %s"), *Owner->GetPathName(), *Cmd.Property->GetPathName() ) );
}
#endif
const int32 NumStartBits = WriterState.Writer.GetNumBits();
// This property changed, so send it
Cmd.Property->NetSerializeItem( WriterState.Writer, WriterState.Writer.PackageMap, (void*)( Data + Cmd.Offset ) );
const int32 NumEndBits = WriterState.Writer.GetNumBits();
const FRepParentCmd & ParentCmd = Parents[Cmd.ParentIndex];
NETWORK_PROFILER( GNetworkProfiler.TrackReplicateProperty( ParentCmd.Property, false, false, 0, 0, NumEndBits - NumStartBits ) );
// Make the shadow state match the actual state at the time of send
StoreProperty( Cmd, (void*)( StoredData + Cmd.Offset ), (const void*)( Data + Cmd.Offset ) );
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (CVarDoReplicationContextString->GetInt() > 0)
{
WriterState.Writer.PackageMap->ClearDebugContextString();
}
#endif
#ifdef ENABLE_PROPERTY_CHECKSUMS
if ( WriterState.bDoChecksum )
{
SerializeReadWritePropertyChecksum( Cmd, CmdIndex, Data + Cmd.Offset, WriterState.Writer, false );
}
#endif
}
}
return Handle;
}
void FRepLayout::WritePropertyHeader(
UObject * Object,
UClass * ObjectClass,
UActorChannel * OwningChannel,
UProperty * Property,
FOutBunch & Bunch,
int32 ArrayIndex,
int32 & LastArrayIndex,
bool & bContentBlockWritten ) const
{
UNetConnection * Connection = OwningChannel->Connection;
// Get class network info cache.
FClassNetCache * ClassCache = Connection->Driver->NetCache->GetClassNetCache( ObjectClass );
check( ClassCache );
if ( !bContentBlockWritten )
{
OwningChannel->BeginContentBlock( Object, Bunch );
bContentBlockWritten = true;
}
// Get the network friend property index to replicate
// Swap Role/RemoteRole while we're at it
FFieldNetCache * FieldCache
= Property->GetFName() == NAME_Role
? ClassCache->GetFromField( Connection->Driver->RemoteRoleProperty )
: Property->GetFName() == NAME_RemoteRole
? ClassCache->GetFromField( Connection->Driver->RoleProperty )
: ClassCache->GetFromField( Property );
checkSlow( FieldCache );
// Send property name and optional array index.
check( FieldCache->FieldNetIndex <= ClassCache->GetMaxIndex() );
Bunch.WriteIntWrapped( FieldCache->FieldNetIndex, ClassCache->GetMaxIndex() + 1 );
NET_CHECKSUM( Bunch );
if ( Property->ArrayDim != 1 )
{
// Serialize index as delta from previous index to increase chance we'll only use 1 byte
uint32 idx = static_cast<uint32>( ArrayIndex - LastArrayIndex );
Bunch.SerializeIntPacked(idx);
LastArrayIndex = ArrayIndex;
}
}
void FRepLayout::SendProperties(
FRepState * RESTRICT RepState,
const FReplicationFlags & RepFlags,
const uint8 * RESTRICT Data,
UClass * ObjectClass,
UActorChannel * OwningChannel,
FOutBunch & Writer,
TArray< uint16 > & Changed,
int32 & LastIndex,
bool & bContentBlockWritten ) const
{
#ifdef ENABLE_PROPERTY_CHECKSUMS
const bool bDoChecksum = CVarDoPropertyChecksum.GetValueOnGameThread() == 1;
#else
const bool bDoChecksum = false;
#endif
FRepWriterState WriterState( Writer, Changed, bDoChecksum );
#ifdef ENABLE_PROPERTY_CHECKSUMS
Writer.WriteBit( bDoChecksum ? 1 : 0 );
#endif
SendProperties_r( RepState, RepFlags, WriterState, 0, Cmds.Num() - 1, RepState->StaticBuffer.GetTypedData(), Data, 0 );
WritePropertyHandle( Writer, 0, bDoChecksum );
}
static void ReadNextHandle( FRepReaderState & ReaderState )
{
ReaderState.Bunch.SerializeIntPacked( ReaderState.WaitingHandle );
#ifdef ENABLE_PROPERTY_CHECKSUMS
if ( ReaderState.bDoChecksum )
{
SerializeGenericChecksum( ReaderState.Bunch );
}
#endif
}
static bool ShouldReadProperty( FRepReaderState & ReaderState )
{
ReaderState.CurrentHandle++;
if ( ReaderState.CurrentHandle == ReaderState.WaitingHandle )
{
check( ReaderState.WaitingHandle != 0 );
return true;
}
return false;
}
bool FRepLayout::ReadProperty(
FRepReaderState & ReaderState,
FUnmappedGuidMgr * UnmappedGuids,
const int32 AbsOffset,
const FRepLayoutCmd & Cmd,
const int32 CmdIndex,
uint8 * RESTRICT StoredData,
uint8 * RESTRICT Data,
const bool bDiscard ) const
{
if ( !ShouldReadProperty( ReaderState ) )
{
// This property didn't change, nothing to read
return true;
}
if ( bDiscard )
{
FMemMark Mark( FMemStack::Get() );
uint8 * DiscardBuffer = NewZeroed<uint8>( FMemStack::Get(), Cmd.ElementSize );
const int32 OldArrayDim = Cmd.Property->ArrayDim;
Cmd.Property->ArrayDim = 1; // Force this to 1, so that InitializeValue/DestroyValue work on a single item
Cmd.Property->InitializeValue( DiscardBuffer );
Cmd.Property->NetSerializeItem( ReaderState.Bunch, ReaderState.Bunch.PackageMap, DiscardBuffer );
Cmd.Property->DestroyValue( DiscardBuffer );
Cmd.Property->ArrayDim = OldArrayDim;
Mark.Pop();
// Read the next property handle
ReadNextHandle( ReaderState );
return true;
}
const FRepParentCmd & Parent = Parents[Cmd.ParentIndex];
// This swaps Role/RemoteRole as we write it
const FRepLayoutCmd & SwappedCmd = Parent.RoleSwapIndex != -1 ? Cmds[Parents[Parent.RoleSwapIndex].CmdStart] : Cmd;
ReaderState.Bunch.PackageMap->ResetLoadedUnmappedObject();
if ( Parent.Property->HasAnyPropertyFlags( CPF_RepNotify ) )
{
// Copy current value over so we can check to see if it changed
StoreProperty( Cmd, StoredData + Cmd.Offset, Data + SwappedCmd.Offset );
// Read the property
Cmd.Property->NetSerializeItem( ReaderState.Bunch, ReaderState.Bunch.PackageMap, Data + SwappedCmd.Offset );
// Check to see if this property changed
if ( !PropertiesAreIdentical( Cmd, StoredData + Cmd.Offset, Data + SwappedCmd.Offset ) )
{
ReaderState.RepState->RepNotifies.AddUnique( Parent.Property );
}
}
else
{
Cmd.Property->NetSerializeItem( ReaderState.Bunch, ReaderState.Bunch.PackageMap, Data + SwappedCmd.Offset );
}
if ( ReaderState.Bunch.PackageMap->GetLoadedUnmappedObject() )
{
// If we have an unmapped guid, we need to remember it, so we can fix up this object pointer when it finally arrives at a other time
const int32 LocalAbsOffset = AbsOffset + SwappedCmd.Offset;
UnmappedGuids->Map.Add( LocalAbsOffset, FUnmappedGuidMgrElement( ReaderState.Bunch.PackageMap->GetLastUnmappedNetGUID(), Cmd.ParentIndex, CmdIndex ) );
UE_LOG( LogNet, Verbose, TEXT( "ADDED unmapped property: Offset: %i, Guid: %s, Name: %s"), LocalAbsOffset, *ReaderState.Bunch.PackageMap->GetLastUnmappedNetGUID().ToString(), *Cmd.Property->GetName() );
ReaderState.bHasUnmapped = true;
}
#ifdef ENABLE_PROPERTY_CHECKSUMS
if ( ReaderState.bDoChecksum )
{
SerializeReadWritePropertyChecksum( Cmd, CmdIndex, Data + SwappedCmd.Offset, ReaderState.Bunch, false );
}
#endif
// Store the property we just received FIXME: Can't do this because of rep notifies. This needs to be done for super checksums though, FIXME!!!
//StoreProperty( Cmd, StoredData + Cmd.Offset, Data + SwappedCmd.Offset );
// Read the next property handle
ReadNextHandle( ReaderState );
return true;
}
bool FRepLayout::ReceiveProperties_AnyArray_r(
FRepReaderState & ReaderState,
FUnmappedGuidMgr * UnmappedGuids,
const int32 AbsOffset,
const int32 ArrayNum,
const int32 ElementSize,
const int32 CmdIndex,
uint8 * RESTRICT StoredData,
uint8 * RESTRICT Data,
const bool bDiscard ) const
{
const int32 CmdStart = CmdIndex + 1;
const int32 CmdEnd = Cmds[CmdIndex].EndCmd - 1;
for ( int32 i = 0; i < ArrayNum; i++ )
{
const int32 ElementOffset = i * ElementSize;
if ( !ReceiveProperties_r( ReaderState, UnmappedGuids, AbsOffset + ElementOffset, CmdStart, CmdEnd, StoredData + ElementOffset, Data + ElementOffset, bDiscard ) )
{
return false;
}
}
return true;
}
bool FRepLayout::ReceiveProperties_DynamicArray_r(
FRepReaderState & ReaderState,
FUnmappedGuidMgr * UnmappedGuids,
const int32 AbsOffset,
const FRepLayoutCmd & Cmd,
const int32 CmdIndex,
uint8 * RESTRICT StoredData,
uint8 * RESTRICT Data,
const bool bDiscard ) const
{
if ( !ShouldReadProperty( ReaderState ) )
{
// This array didn't change, nothing to read
return true;
}
// Read array size
uint16 ArrayNum = 0;
ReaderState.Bunch << ArrayNum;
// Read the next property handle
ReadNextHandle( ReaderState );
if ( !bDiscard )
{
FScriptArray * Array = (FScriptArray *)Data;
FScriptArray * StoredArray = (FScriptArray *)StoredData;
// Since we don't know yet if something under us could be unmapped, go ahead and allocate an array container now
FUnmappedGuidMgrElement * NewArrayElement = UnmappedGuids->Map.Find( AbsOffset );
if ( NewArrayElement == NULL )
{
NewArrayElement = &UnmappedGuids->Map.FindOrAdd( AbsOffset );
NewArrayElement->Array = new FUnmappedGuidMgr;
NewArrayElement->ParentIndex = Cmd.ParentIndex;
NewArrayElement->CmdIndex = CmdIndex;
}
check( NewArrayElement != NULL );
check( NewArrayElement->ParentIndex == Cmd.ParentIndex );
check( NewArrayElement->CmdIndex == CmdIndex );
UnmappedGuids = NewArrayElement->Array;
const FRepParentCmd & Parent = Parents[Cmd.ParentIndex];
if ( Array->Num() != ArrayNum && Parent.Property->HasAnyPropertyFlags( CPF_RepNotify ) )
{
ReaderState.RepState->RepNotifies.AddUnique( Parent.Property );
}
// Set the array to the correct size (if we aren't discarding)
FScriptArrayHelper ArrayHelper( (UArrayProperty *)Cmd.Property, Data );
ArrayHelper.Resize( ArrayNum );
FScriptArrayHelper StoredArrayHelper( (UArrayProperty *)Cmd.Property, StoredData );
StoredArrayHelper.Resize( ArrayNum );
Data = (uint8*)Array->GetData();
StoredData = (uint8*)StoredArray->GetData();
}
const uint16 OldHandle = ReaderState.CurrentHandle;
// Array children handles are always relative to their immediate parent
ReaderState.CurrentHandle = 0;
// Read any changed properties into the array
if ( !ReceiveProperties_AnyArray_r( ReaderState, UnmappedGuids, 0, ArrayNum, Cmd.ElementSize, CmdIndex, StoredData, Data, bDiscard ) )
{
return false;
}
ReaderState.CurrentHandle = OldHandle;
// We should be waiting on the NULL terminator handle at this point
check( ReaderState.WaitingHandle == 0 );
ReadNextHandle( ReaderState );
return true;
}
bool FRepLayout::ReceiveProperties_r(
FRepReaderState & ReaderState,
FUnmappedGuidMgr * UnmappedGuids,
const int32 AbsOffset,
const int32 CmdStart,
const int32 CmdEnd,
uint8 * RESTRICT StoredData,
uint8 * RESTRICT Data,
const bool bDiscard ) const
{
for ( int32 CmdIndex = CmdStart; CmdIndex < CmdEnd; CmdIndex++ )
{
const FRepLayoutCmd & Cmd = Cmds[ CmdIndex ];
check( Cmd.Type != REPCMD_Return );
if ( Cmd.Type == REPCMD_DynamicArray )
{
if ( !ReceiveProperties_DynamicArray_r( ReaderState, UnmappedGuids, AbsOffset + Cmd.Offset, Cmd, CmdIndex, StoredData + Cmd.Offset, Data + Cmd.Offset, bDiscard ) )
{
return false;
}
CmdIndex = Cmd.EndCmd - 1; // Jump past children of this array ( -1 for the ++ in the for loop)
continue;
}
if ( !ReadProperty( ReaderState, UnmappedGuids, AbsOffset, Cmd, CmdIndex, StoredData, Data, bDiscard ) )
{
return false;
}
}
return true;
}
bool FRepLayout::ReceiveProperties( UClass * InObjectClass, FRepState * RESTRICT RepState, void * RESTRICT Data, FNetBitReader & InBunch, bool bDiscard, bool & bOutHasUnmapped ) const
{
check( InObjectClass == Owner );
#ifdef ENABLE_PROPERTY_CHECKSUMS
const bool bDoChecksum = InBunch.ReadBit() ? true : false;
#else
const bool bDoChecksum = false;
#endif
bOutHasUnmapped = false;
FRepReaderState ReaderState( InBunch, RepState, bDoChecksum );
// Read first handle
ReadNextHandle( ReaderState );
// Read all properties
if ( !ReceiveProperties_r( ReaderState, &RepState->UnmappedGuids, 0, 0, Cmds.Num() - 1, RepState->StaticBuffer.GetTypedData(), (uint8*)Data, bDiscard ) )
{
return false;
}
// Make sure we're waiting on the last NULL terminator
if ( ReaderState.WaitingHandle != 0 )
{
UE_LOG( LogNet, Warning, TEXT( "Read out of sync." ) );
return false;
}
#ifdef ENABLE_SUPER_CHECKSUMS
if ( InBunch.ReadBit() == 1 )
{
ValidateWithChecksum( RepState->StaticBuffer.GetTypedData(), InBunch, bDiscard );
}
#endif
bOutHasUnmapped = ReaderState.bHasUnmapped;
return true;
}
FUnmappedGuidMgrElement::~FUnmappedGuidMgrElement()
{
if ( Array != NULL )
{
delete Array;
Array = NULL;
}
}
void FRepLayout::UpdateUnmappedObjects_r(
FRepState * RepState,
FUnmappedGuidMgr * UnmappedGuids,
UObject * OriginalObject,
UPackageMap * PackageMap,
uint8 * RESTRICT Data,
const int32 MaxAbsOffset,
bool & bOutSomeObjectsWereMapped,
bool & bOutHasMoreUnmapped ) const
{
bool bHasUnmapped = false;
for ( auto It = UnmappedGuids->Map.CreateIterator(); It; ++It )
{
const int32 AbsOffset = It.Key();
if ( AbsOffset >= MaxAbsOffset )
{
// Array must have shrunk, we can remove this item
UE_LOG( LogNet, VeryVerbose, TEXT( "REMOVED unmapped property: AbsOffset >= MaxAbsOffset. Offset: %i" ), AbsOffset );
It.RemoveCurrent();
continue;
}
const FRepLayoutCmd & Cmd = Cmds[It.Value().CmdIndex];
const FRepParentCmd & Parent = Parents[It.Value().ParentIndex];
if ( It.Value().Array != NULL )
{
check( Cmd.Type == REPCMD_DynamicArray );
FScriptArray * Array = (FScriptArray *)( Data + AbsOffset );
UpdateUnmappedObjects_r( RepState, It.Value().Array, OriginalObject, PackageMap, (uint8*)Array->GetData(), Array->Num() * Cmd.ElementSize, bOutSomeObjectsWereMapped, bOutHasMoreUnmapped );
continue;
}
check( Cmd.Type == REPCMD_PropertyObject );
UObject * Object = PackageMap->GetObjectFromNetGUID( It.Value().Guid );
if ( Object != NULL )
{
UE_LOG( LogNet, VeryVerbose, TEXT( "REMOVED unmapped property: Offset: %i, Guid: %s, PropName: %s, ObjName: %s" ), AbsOffset, *It.Value().Guid.ToString(), *Cmd.Property->GetName(), *Object->GetName() );
UObjectPropertyBase * ObjProperty = CastChecked< UObjectPropertyBase>( Cmd.Property );
UObject * OldObject = ObjProperty->GetObjectPropertyValue( Data + AbsOffset );
if ( OldObject != Object )
{
if ( !bOutSomeObjectsWereMapped )
{
// Call PreNetReceive if we are going to change a value (some game code will need to think this is an actual replicated value)
OriginalObject->PreNetReceive();
bOutSomeObjectsWereMapped = true;
}
ObjProperty->SetObjectPropertyValue( Data + AbsOffset, Object );
// If this properties needs an OnRep, queue that up to be handled later
if ( Parent.Property->HasAnyPropertyFlags( CPF_RepNotify ) )
{
RepState->RepNotifies.AddUnique( Parent.Property );
}
}
It.RemoveCurrent();
continue;
}
UE_LOG( LogNet, VeryVerbose, TEXT( "UnmappedGuids: Offset: %i, Guid: %s" ), AbsOffset, *It.Value().Guid.ToString() );
bOutHasMoreUnmapped = true;
}
}
void FRepLayout::UpdateUnmappedObjects( FRepState * RepState, UPackageMap * PackageMap, UObject * OriginalObject, bool & bOutSomeObjectsWereMapped, bool & bOutHasMoreUnmapped ) const
{
bOutSomeObjectsWereMapped = false;
bOutHasMoreUnmapped = false;
UpdateUnmappedObjects_r( RepState, &RepState->UnmappedGuids, OriginalObject, PackageMap, (uint8*)OriginalObject, RepState->StaticBuffer.Num(), bOutSomeObjectsWereMapped, bOutHasMoreUnmapped );
}
void FRepLayout::CallRepNotifies( FRepState * RepState, UObject * Object ) const
{
if ( RepState->RepNotifies.Num() == 0 )
{
return;
}
for ( int32 i = 0; i < RepState->RepNotifies.Num(); i++ )
{
UProperty * RepProperty = RepState->RepNotifies[i];
UFunction * RepNotifyFunc = Object->FindFunctionChecked( RepProperty->RepNotifyFunc );
check( RepNotifyFunc->NumParms <= 1 ); // 2 parms not supported yet
if ( RepNotifyFunc->NumParms == 0 )
{
Object->ProcessEvent( RepNotifyFunc, NULL );
}
else if (RepNotifyFunc->NumParms == 1 )
{
Object->ProcessEvent( RepNotifyFunc, RepProperty->ContainerPtrToValuePtr<uint8>( RepState->StaticBuffer.GetTypedData() ) );
}
// Store the property we just received
//StoreProperty( Cmd, StoredData + Cmd.Offset, Data + SwappedCmd.Offset );
}
RepState->RepNotifies.Empty();
}
void FRepLayout::ValidateWithChecksum_DynamicArray_r( const FRepLayoutCmd & Cmd, const int32 CmdIndex, const uint8 * RESTRICT Data, FArchive & Ar, const bool bDiscard ) const
{
if ( bDiscard )
{
uint16 ArrayNum = 0;
uint16 ElementSize = 0;
Ar << ArrayNum;
Ar << ElementSize;
for ( int32 i = 0; i < ArrayNum; i++ )
{
ValidateWithChecksum_r( CmdIndex + 1, Cmd.EndCmd - 1, NULL, Ar, bDiscard );
}
return;
}
FScriptArray * Array = (FScriptArray *)Data;
uint16 ArrayNum = Array->Num();
uint16 ElementSize = Cmd.ElementSize;
Ar << ArrayNum;
Ar << ElementSize;
if ( ArrayNum != Array->Num() )
{
UE_LOG( LogNet, Fatal, TEXT( "ValidateWithChecksum_AnyArray_r: Array sizes different! %s %i / %i" ), *Cmd.Property->GetFullName(), ArrayNum, Array->Num() );
}
if ( ElementSize != Cmd.ElementSize )
{
UE_LOG( LogNet, Fatal, TEXT( "ValidateWithChecksum_AnyArray_r: Array element sizes different! %s %i / %i" ), *Cmd.Property->GetFullName(), ElementSize, Cmd.ElementSize );
}
uint8 * LocalData = (uint8*)Array->GetData();
for ( int32 i = 0; i < ArrayNum; i++ )
{
ValidateWithChecksum_r( CmdIndex + 1, Cmd.EndCmd - 1, LocalData + i * ElementSize, Ar, bDiscard );
}
}
void FRepLayout::ValidateWithChecksum_r(
const int32 CmdStart,
const int32 CmdEnd,
const uint8 * RESTRICT Data,
FArchive & Ar,
const bool bDiscard ) const
{
for ( int32 CmdIndex = CmdStart; CmdIndex < CmdEnd; CmdIndex++ )
{
const FRepLayoutCmd & Cmd = Cmds[ CmdIndex ];
check( Cmd.Type != REPCMD_Return );
if ( Cmd.Type == REPCMD_DynamicArray )
{
ValidateWithChecksum_DynamicArray_r( Cmd, CmdIndex, Data + Cmd.Offset, Ar, bDiscard );
CmdIndex = Cmd.EndCmd - 1; // Jump past children of this array (-1 for ++ in for loop)
continue;
}
SerializeReadWritePropertyChecksum( Cmd, CmdIndex - 1, Data + Cmd.Offset, Ar, bDiscard );
}
}
void FRepLayout::ValidateWithChecksum( const void * RESTRICT Data, FArchive & Ar, const bool bDiscard ) const
{
ValidateWithChecksum_r( 0, Cmds.Num() - 1, (const uint8*)Data, Ar, bDiscard );
}
uint32 FRepLayout::GenerateChecksum( const FRepState * RepState ) const
{
FBitWriter Writer( 1024, true );
ValidateWithChecksum_r( 0, Cmds.Num() - 1, (const uint8*)RepState->StaticBuffer.GetTypedData(), Writer, false );
return FCrc::MemCrc32( Writer.GetData(), Writer.GetNumBytes(), 0 );
}
void FRepLayout::MergeDirtyList_AnyArray_r( FMergeDirtyListState & MergeState, const FRepLayoutCmd & Cmd, const int32 ArrayNum, const int32 ElementSize, const int32 CmdIndex, const uint8 * RESTRICT Data ) const
{
for ( int32 i = 0; i < ArrayNum; i++ )
{
MergeDirtyList_r( MergeState, CmdIndex + 1, Cmd.EndCmd - 1, Data + i * ElementSize );
}
}
void FRepLayout::MergeDirtyList_DynamicArray_r( FMergeDirtyListState & MergeState, const FRepLayoutCmd & Cmd, const int32 CmdIndex, const uint8 * RESTRICT Data ) const
{
// At least one of the list should be valid to be here
check( MergeState.DirtyValid1 || MergeState.DirtyValid2 );
MergeState.Handle++;
const bool Dirty1Matches = MergeState.DirtyValid1 && MergeState.DirtyList1[MergeState.DirtyListIndex1] == MergeState.Handle;
const bool Dirty2Matches = MergeState.DirtyValid2 && MergeState.DirtyList2[MergeState.DirtyListIndex2] == MergeState.Handle;
if ( !Dirty1Matches && !Dirty2Matches )
{
// Neither match, we're done with this branch
return;
}
// This will be a new merged dirty entry
MergeState.MergedDirtyList.Add( MergeState.Handle );
const int32 RememberIndex = MergeState.MergedDirtyList.AddUninitialized();
const int32 OldMergedListSize = MergeState.MergedDirtyList.Num();
// Advance the matching dirty lists
if ( Dirty1Matches )
{
MergeState.DirtyListIndex1++;
}
if ( Dirty2Matches )
{
MergeState.DirtyListIndex2++;
}
// Remember valid dirty lists
const bool OldDirtyValid1 = MergeState.DirtyValid1;
const bool OldDirtyValid2 = MergeState.DirtyValid2;
// Update which lists are still valid from this point
MergeState.DirtyValid1 = Dirty1Matches;
MergeState.DirtyValid2 = Dirty2Matches;
const int32 JumpToIndex1 = Dirty1Matches ? MergeState.DirtyList1[MergeState.DirtyListIndex1++] : -1;
const int32 JumpToIndex2 = Dirty2Matches ? MergeState.DirtyList2[MergeState.DirtyListIndex2++] : -1;
const int32 OldDirtyListIndex1 = MergeState.DirtyListIndex1;
const int32 OldDirtyListIndex2 = MergeState.DirtyListIndex2;
FScriptArray * Array = (FScriptArray *)Data;
const int32 OldHandle = MergeState.Handle;
MergeState.Handle = 0;
MergeDirtyList_AnyArray_r( MergeState, Cmd, Array->Num(), Cmd.ElementSize, CmdIndex, (uint8*)Array->GetData() );
MergeState.Handle = OldHandle;
if ( Dirty1Matches )
{
check( MergeState.DirtyListIndex1 - OldDirtyListIndex1 <= JumpToIndex1 );
MergeState.DirtyListIndex1 = OldDirtyListIndex1 + JumpToIndex1;
check( MergeState.DirtyList1[MergeState.DirtyListIndex1] == 0 );
MergeState.DirtyListIndex1++;
}
if ( Dirty2Matches )
{
check( MergeState.DirtyListIndex2 - OldDirtyListIndex2 <= JumpToIndex2 );
MergeState.DirtyListIndex2 = OldDirtyListIndex2 + JumpToIndex2;
check( MergeState.DirtyList2[MergeState.DirtyListIndex2] == 0 );
MergeState.DirtyListIndex2++;
}
MergeState.DirtyValid1 = OldDirtyValid1;
MergeState.DirtyValid2 = OldDirtyValid2;
// Patch in the jump offset
MergeState.MergedDirtyList[RememberIndex] = MergeState.MergedDirtyList.Num() - OldMergedListSize;
// Add the array terminator
MergeState.MergedDirtyList.Add( 0 );
}
void FRepLayout::MergeDirtyList_r( FMergeDirtyListState & MergeState, const int32 CmdStart, const int32 CmdEnd, const uint8 * RESTRICT Data ) const
{
for ( int32 CmdIndex = CmdStart; CmdIndex < CmdEnd; CmdIndex++ )
{
const FRepLayoutCmd & Cmd = Cmds[ CmdIndex ];
check( Cmd.Type != REPCMD_Return );
if ( Cmd.Type == REPCMD_DynamicArray )
{
MergeDirtyList_DynamicArray_r( MergeState, Cmd, CmdIndex, Data + Cmd.Offset );
CmdIndex = Cmd.EndCmd - 1; // Jump past children of this array (-1 for ++ in for loop)
continue;
}
MergeState.Handle++;
const bool Dirty1Matches = MergeState.DirtyValid1 && MergeState.DirtyList1[MergeState.DirtyListIndex1] == MergeState.Handle;
const bool Dirty2Matches = MergeState.DirtyValid2 && MergeState.DirtyList2[MergeState.DirtyListIndex2] == MergeState.Handle;
if ( Dirty1Matches || Dirty2Matches )
{
// This will be a new merged dirty entry
MergeState.MergedDirtyList.Add( MergeState.Handle );
// Advance matching dirty indices
if ( Dirty1Matches )
{
MergeState.DirtyListIndex1++;
}
if ( Dirty2Matches )
{
MergeState.DirtyListIndex2++;
}
}
}
}
void FRepLayout::MergeDirtyList( const void * RESTRICT Data, const TArray< uint16 > & Dirty1, const TArray< uint16 > & Dirty2, TArray< uint16 > & MergedDirty ) const
{
check( Dirty1.Num() > 0 || Dirty2.Num() > 0 );
FMergeDirtyListState MergeState( Dirty1, Dirty2, MergedDirty );
MergedDirty.Empty();
// Even though one of these can be empty, we need to send the single one through, so we can prune it to the current shape of the tree
MergeState.DirtyValid1 = Dirty1.Num() > 0;
MergeState.DirtyValid2 = Dirty2.Num() > 0;
MergeDirtyList_r( MergeState, 0, Cmds.Num() - 1, (const uint8*)Data );
MergeState.MergedDirtyList.Add( 0 );
}
void FRepLayout::SanityCheckChangeList_DynamicArray_r(
const int32 CmdIndex,
const uint8 * RESTRICT Data,
TArray< uint16 > & Changed,
int32 & ChangedIndex ) const
{
const FRepLayoutCmd & Cmd = Cmds[ CmdIndex ];
FScriptArray * Array = (FScriptArray *)Data;
// Read the jump offset
// We won't need to actually jump over anything because we expect the change list to be pruned once we get here
// But we can use it to verify we read the correct amount.
const int32 ArrayChangedCount = Changed[ChangedIndex++];
const int32 OldChangedIndex = ChangedIndex;
Data = (uint8*)Array->GetData();
uint16 LocalHandle = 0;
for ( int32 i = 0; i < Array->Num(); i++ )
{
LocalHandle = SanityCheckChangeList_r( CmdIndex + 1, Cmd.EndCmd - 1, Data + i * Cmd.ElementSize, Changed, ChangedIndex, LocalHandle );
}
check( ChangedIndex - OldChangedIndex == ArrayChangedCount ); // Make sure we read correct amount
check( Changed[ChangedIndex] == 0 ); // Make sure we are at the end
ChangedIndex++;
}
uint16 FRepLayout::SanityCheckChangeList_r(
const int32 CmdStart,
const int32 CmdEnd,
const uint8 * RESTRICT Data,
TArray< uint16 > & Changed,
int32 & ChangedIndex,
uint16 Handle
) const
{
for ( int32 CmdIndex = CmdStart; CmdIndex < CmdEnd; CmdIndex++ )
{
const FRepLayoutCmd & Cmd = Cmds[ CmdIndex ];
check( Cmd.Type != REPCMD_Return );
Handle++;
if ( Cmd.Type == REPCMD_DynamicArray )
{
if ( Handle == Changed[ChangedIndex] )
{
const int32 LastChangedArrayHandle = Changed[ChangedIndex];
ChangedIndex++;
SanityCheckChangeList_DynamicArray_r( CmdIndex, Data + Cmd.Offset, Changed, ChangedIndex );
check( Changed[ChangedIndex] == 0 || Changed[ChangedIndex] > LastChangedArrayHandle );
}
CmdIndex = Cmd.EndCmd - 1; // Jump past children of this array (the -1 because of the ++ in the for loop)
continue;
}
if ( Handle == Changed[ChangedIndex] )
{
const int32 LastChangedArrayHandle = Changed[ChangedIndex];
ChangedIndex++;
check( Changed[ChangedIndex] == 0 || Changed[ChangedIndex] > LastChangedArrayHandle );
}
}
return Handle;
}
void FRepLayout::SanityCheckChangeList( const uint8 * RESTRICT Data, TArray< uint16 > & Changed ) const
{
int32 ChangedIndex = 0;
SanityCheckChangeList_r( 0, Cmds.Num() - 1, Data, Changed, ChangedIndex, 0 );
check( Changed[ChangedIndex] == 0 );
}
void FRepLayout::DiffProperties_DynamicArray_r(
FRepState * RepState,
const int32 CmdIndex,
const uint8 * RESTRICT StoredData,
const uint8 * RESTRICT Data,
const bool bSync,
bool & bOutDifferent ) const
{
const FRepLayoutCmd & Cmd = Cmds[ CmdIndex ];
FScriptArray * Array = (FScriptArray *)Data;
FScriptArray * StoredArray = (FScriptArray *)StoredData;
if ( Array->Num() != StoredArray->Num() )
{
bOutDifferent = true;
if ( !bSync )
{
UE_LOG( LogNet, Warning, TEXT( "DiffProperties_DynamicArray_r: Array sizes different: %s %i / %i" ), *Cmd.Property->GetFullName(), Array->Num(), StoredArray->Num() );
return;
}
if ( !( Parents[Cmd.ParentIndex].Flags & PARENT_IsLifetime ) )
{
// Currently, only lifetime properties init from their defaults
return;
}
// Make the shadow state match the actual state
FScriptArrayHelper StoredArrayHelper( (UArrayProperty *)Cmd.Property, StoredData );
StoredArrayHelper.Resize( Array->Num() );
}
Data = (uint8*)Array->GetData();
StoredData = (uint8*)StoredArray->GetData();
for ( int32 i = 0; i < Array->Num(); i++ )
{
const int32 ElementOffset = i * Cmd.ElementSize;
DiffProperties_r( RepState, CmdIndex + 1, Cmd.EndCmd - 1, StoredData + ElementOffset, Data + ElementOffset, bSync, bOutDifferent );
}
}
void FRepLayout::DiffProperties_r(
FRepState * RepState,
const int32 CmdStart,
const int32 CmdEnd,
const uint8 * RESTRICT StoredData,
const uint8 * RESTRICT Data,
const bool bSync,
bool & bOutDifferent ) const
{
for ( int32 CmdIndex = CmdStart; CmdIndex < CmdEnd; CmdIndex++ )
{
const FRepLayoutCmd & Cmd = Cmds[ CmdIndex ];
check( Cmd.Type != REPCMD_Return );
if ( Cmd.Type == REPCMD_DynamicArray )
{
DiffProperties_DynamicArray_r( RepState, CmdIndex, StoredData + Cmd.Offset, Data + Cmd.Offset, bSync, bOutDifferent );
CmdIndex = Cmd.EndCmd - 1; // Jump past children of this array (-1 for the ++ in the for loop)
continue;
}
const FRepParentCmd & Parent = Parents[Cmd.ParentIndex];
const FRepLayoutCmd & SwappedCmd = Cmd;//Parent.RoleSwapIndex != -1 ? Cmds[Parents[Parent.RoleSwapIndex].CmdStart] : Cmd;
// Make the shadow state match the actual state at the time of send
if ( !PropertiesAreIdentical( Cmd, (const void*)( Data + SwappedCmd.Offset ), (const void*)( StoredData + Cmd.Offset ) ) )
{
bOutDifferent = true;
if ( !bSync )
{
UE_LOG( LogNet, Warning, TEXT( "DiffProperties_r: Property different: %s" ), *Cmd.Property->GetFullName() );
continue;
}
if ( !( Parents[Cmd.ParentIndex].Flags & PARENT_IsLifetime ) )
{
// Currently, only lifetime properties init from their defaults
continue;
}
StoreProperty( Cmd, (void*)( Data + SwappedCmd.Offset ), (const void*)( StoredData + Cmd.Offset ) );
if ( Parent.Property->HasAnyPropertyFlags( CPF_RepNotify ) )
{
RepState->RepNotifies.AddUnique( Parent.Property );
}
}
}
}
bool FRepLayout::DiffProperties( FRepState * RepState, const void * RESTRICT Data, const bool bSync ) const
{
bool bDifferent = false;
DiffProperties_r( RepState, 0, Cmds.Num() - 1, RepState->StaticBuffer.GetTypedData(), (uint8*)Data, bSync, bDifferent );
return bDifferent;
}
void FRepLayout::AddPropertyCmd( UProperty * Property, int32 Offset, int32 RelativeHandle, int32 ParentIndex )
{
const int32 Index = Cmds.AddZeroed();
FRepLayoutCmd & Cmd = Cmds[Index];
Cmd.Property = Property;
Cmd.Type = REPCMD_Property; // Initially set to generic type
Cmd.Offset = Offset;
Cmd.ElementSize = Property->ElementSize;
Cmd.RelativeHandle = RelativeHandle;
Cmd.ParentIndex = ParentIndex;
// Try to special case to custom types we know about
if ( Property->IsA( UStructProperty::StaticClass() ) )
{
UStructProperty * StructProp = Cast< UStructProperty >( Property );
UScriptStruct * Struct = StructProp->Struct;
if ( Struct->GetFName() == NAME_Vector )
{
Cmd.Type = REPCMD_PropertyVector;
}
else if ( Struct->GetFName() == NAME_Rotator )
{
Cmd.Type = REPCMD_PropertyRotator;
}
else if ( Struct->GetFName() == NAME_Plane )
{
Cmd.Type = REPCMD_PropertyPlane;
}
else if ( Struct->GetName() == TEXT( "Vector_NetQuantize100" ) )
{
Cmd.Type = REPCMD_PropertyVector100;
}
else if ( Struct->GetName() == TEXT( "Vector_NetQuantize10" ) )
{
Cmd.Type = REPCMD_PropertyVector10;
}
else if ( Struct->GetName() == TEXT( "Vector_NetQuantizeNormal" ) )
{
Cmd.Type = REPCMD_PropertyVectorNormal;
}
else if ( Struct->GetName() == TEXT( "Vector_NetQuantize" ) )
{
Cmd.Type = REPCMD_PropertyVectorQ;
}
else if ( Struct->GetName() == TEXT( "UniqueNetIdRepl" ) )
{
Cmd.Type = REPCMD_PropertyNetId;
}
else if ( Struct->GetName() == TEXT( "RepMovement" ) )
{
Cmd.Type = REPCMD_RepMovement;
}
else
{
UE_LOG( LogNet, Verbose, TEXT( "AddPropertyCmd: Falling back to default type for property [%s]" ), *Cmd.Property->GetFullName() );
}
}
else if ( Property->IsA( UBoolProperty::StaticClass() ) )
{
Cmd.Type = REPCMD_PropertyBool;
}
else if ( Property->IsA( UFloatProperty::StaticClass() ) )
{
Cmd.Type = REPCMD_PropertyFloat;
}
else if ( Property->IsA( UIntProperty::StaticClass() ) )
{
Cmd.Type = REPCMD_PropertyInt;
}
else if ( Property->IsA( UByteProperty::StaticClass() ) )
{
Cmd.Type = REPCMD_PropertyByte;
}
else if ( Property->IsA( UObjectPropertyBase::StaticClass() ) )
{
Cmd.Type = REPCMD_PropertyObject;
}
else if ( Property->IsA( UNameProperty::StaticClass() ) )
{
Cmd.Type = REPCMD_PropertyName;
}
else if ( Property->IsA( UUInt32Property::StaticClass() ) )
{
Cmd.Type = REPCMD_PropertyUInt32;
}
else if ( Property->IsA( UUInt64Property::StaticClass() ) )
{
Cmd.Type = REPCMD_PropertyUInt64;
}
else if ( Property->IsA( UStrProperty::StaticClass() ) )
{
Cmd.Type = REPCMD_PropertyString;
}
else
{
UE_LOG( LogNet, Verbose, TEXT( "AddPropertyCmd: Falling back to default type for property [%s]" ), *Cmd.Property->GetFullName() );
}
}
void FRepLayout::AddArrayCmd( UArrayProperty * Property, int32 Offset, int32 RelativeHandle, int32 ParentIndex )
{
const int32 Index = Cmds.AddZeroed();
FRepLayoutCmd & Cmd = Cmds[Index];
Cmd.Type = REPCMD_DynamicArray;
Cmd.Property = Property;
Cmd.Offset = Offset;
Cmd.ElementSize = Property->Inner->ElementSize;
Cmd.RelativeHandle = RelativeHandle;
Cmd.ParentIndex = ParentIndex;
}
void FRepLayout::AddReturnCmd()
{
const int32 Index = Cmds.AddZeroed();
FRepLayoutCmd & Cmd = Cmds[Index];
Cmd.Type = REPCMD_Return;
}
int32 FRepLayout::InitFromProperty_r( UProperty * Property, int32 Offset, int32 RelativeHandle, int32 ParentIndex )
{
UArrayProperty * ArrayProp = Cast< UArrayProperty >( Property );
if ( ArrayProp != NULL )
{
const int32 CmdStart = Cmds.Num();
RelativeHandle++;
AddArrayCmd( ArrayProp, Offset + ArrayProp->GetOffset_ForGC(), RelativeHandle, ParentIndex );
InitFromProperty_r( ArrayProp->Inner, 0, 0, ParentIndex );
AddReturnCmd();
Cmds[CmdStart].EndCmd = Cmds.Num(); // Patch in the offset to jump over our array inner elements
return RelativeHandle;
}
UStructProperty * StructProp = Cast< UStructProperty >( Property );
if ( StructProp != NULL )
{
UScriptStruct * Struct = StructProp->Struct;
if ( Struct->StructFlags & STRUCT_NetDeltaSerializeNative )
{
// Custom delta serializers handles outside of FRepLayout
return RelativeHandle;
}
if ( Struct->StructFlags & STRUCT_NetSerializeNative )
{
RelativeHandle++;
AddPropertyCmd( Property, Offset + Property->GetOffset_ForGC(), RelativeHandle, ParentIndex );
return RelativeHandle;
}
TArray< UProperty * > NetProperties; // Track properties so me can ensure they are sorted by offsets at the end
for ( TFieldIterator<UProperty> It( Struct ); It; ++It )
{
if ( ( It->PropertyFlags & CPF_RepSkip ) )
{
continue;
}
NetProperties.Add( *It );
}
// Sort NetProperties by memory offset
struct FCompareUFieldOffsets
{
FORCEINLINE bool operator()( UProperty & A, UProperty & B ) const
{
return A.GetOffset_ForGC() < B.GetOffset_ForGC();
}
};
Sort( NetProperties.GetTypedData(), NetProperties.Num(), FCompareUFieldOffsets() );
for ( int32 i = 0; i < NetProperties.Num(); i++ )
{
for ( int32 j = 0; j < NetProperties[i]->ArrayDim; j++ )
{
RelativeHandle = InitFromProperty_r( NetProperties[i], Offset + StructProp->GetOffset_ForGC() + j * NetProperties[i]->ElementSize, RelativeHandle, ParentIndex );
}
}
return RelativeHandle;
}
// Add actual property
RelativeHandle++;
AddPropertyCmd( Property, Offset + Property->GetOffset_ForGC(), RelativeHandle, ParentIndex );
return RelativeHandle;
}
uint16 FRepLayout::AddParentProperty( UProperty * Property, int32 ArrayIndex )
{
return Parents.Add( FRepParentCmd( Property, ArrayIndex ) );
}
static bool IsCustomDeltaProperty( UProperty * Property )
{
UStructProperty * StructProperty = Cast< UStructProperty >( Property );
if ( StructProperty != NULL && StructProperty->Struct->StructFlags & STRUCT_NetDeltaSerializeNative )
{
return true;
}
return false;
}
void FRepLayout::InitFromObjectClass( UClass * InObjectClass )
{
RoleIndex = -1;
RemoteRoleIndex = -1;
FirstNonCustomParent = -1;
int32 RelativeHandle = 0;
int32 LastOffset = -1;
Parents.Empty();
for ( int32 i = 0; i < InObjectClass->ClassReps.Num(); i++ )
{
UProperty * Property = InObjectClass->ClassReps[i].Property;
const int32 ArrayIdx = InObjectClass->ClassReps[i].Index;
check( Property->PropertyFlags & CPF_Net );
const int32 ParentHandle = AddParentProperty( Property, ArrayIdx );
check( ParentHandle == i );
check( Parents[i].Property->RepIndex + Parents[i].ArrayIndex == i );
Parents[ParentHandle].CmdStart = Cmds.Num();
RelativeHandle = InitFromProperty_r( Property, Property->ElementSize * ArrayIdx, RelativeHandle, ParentHandle );
Parents[ParentHandle].CmdEnd = Cmds.Num();
Parents[ParentHandle].Flags |= PARENT_IsConditional;
if ( Parents[i].CmdEnd > Parents[i].CmdStart )
{
check( Cmds[Parents[i].CmdStart].Offset >= LastOffset ); // >= since bool's can be combined
LastOffset = Cmds[Parents[i].CmdStart].Offset;
}
// Setup flags
if ( IsCustomDeltaProperty( Property ) )
{
Parents[ParentHandle].Flags |= PARENT_IsCustomDelta;
}
if ( Property->GetPropertyFlags() & CPF_Config )
{
Parents[ParentHandle].Flags |= PARENT_IsConfig;
}
// Hijack the first non custom property for identifying this as a rep layout block
if ( FirstNonCustomParent == -1 && Property->ArrayDim == 1 && ( Parents[ParentHandle].Flags & PARENT_IsCustomDelta ) == 0 )
{
FirstNonCustomParent = ParentHandle;
}
// Find Role/RemoteRole property indexes so we can swap them on the client
if ( Property->GetFName() == NAME_Role )
{
check( RoleIndex == -1 );
check( Parents[ParentHandle].CmdEnd == Parents[ParentHandle].CmdStart + 1 );
RoleIndex = ParentHandle;
}
if ( Property->GetFName() == NAME_RemoteRole )
{
check( RemoteRoleIndex == -1 );
check( Parents[ParentHandle].CmdEnd == Parents[ParentHandle].CmdStart + 1 );
RemoteRoleIndex = ParentHandle;
}
}
// Make sure it either found both, or didn't find either
check( ( RoleIndex == -1 ) == ( RemoteRoleIndex == -1 ) );
// This is so the receiving side can swap these as it receives them
if ( RoleIndex != -1 )
{
Parents[RoleIndex].RoleSwapIndex = RemoteRoleIndex;
Parents[RemoteRoleIndex].RoleSwapIndex = RoleIndex;
}
AddReturnCmd();
// Initialize lifetime props
TArray< FLifetimeProperty > LifetimeProps; // Properties that replicate for the lifetime of the channel
UObject * Object = InObjectClass->GetDefaultObject();
Object->GetLifetimeReplicatedProps( LifetimeProps );
if ( LifetimeProps.Num() > 0 )
{
UnconditionalLifetime.Empty();
ConditionalLifetime.Empty();
// Fix Lifetime props to have the proper index to the parent
for ( int32 i = 0; i < LifetimeProps.Num(); i++ )
{
// Store the condition on the parent in case we need it
Parents[LifetimeProps[i].RepIndex].Condition = LifetimeProps[i].Condition;
if ( Parents[LifetimeProps[i].RepIndex].Flags & PARENT_IsCustomDelta )
{
continue; // We don't handle custom properties in the FRepLayout class
}
Parents[LifetimeProps[i].RepIndex].Flags |= PARENT_IsLifetime;
if ( LifetimeProps[i].RepIndex == RemoteRoleIndex )
{
// We handle remote role specially, since it can change between connections when downgraded
// So we force it on the conditional list
check( LifetimeProps[i].Condition == COND_None );
LifetimeProps[i].Condition = COND_Custom;
ConditionalLifetime.AddUnique( LifetimeProps[i] );
continue;
}
if ( LifetimeProps[i].Condition == COND_None )
{
// These properties are simple, and can benefit from property compare sharing
UnconditionalLifetime.AddUnique( LifetimeProps[i].RepIndex );
Parents[LifetimeProps[i].RepIndex].Flags &= ~PARENT_IsConditional;
continue;
}
// The rest are conditional
// These properties are eventually conditionally copied to the RepState
ConditionalLifetime.AddUnique( LifetimeProps[i] );
}
}
Owner = InObjectClass;
}
void FRepLayout::InitFromFunction( UFunction * InFunction )
{
int32 RelativeHandle = 0;
for ( TFieldIterator<UProperty> It( InFunction ); It && ( It->PropertyFlags & ( CPF_Parm | CPF_ReturnParm ) ) == CPF_Parm; ++It )
{
for ( int32 ArrayIdx = 0; ArrayIdx < It->ArrayDim; ++ArrayIdx )
{
const int32 ParentHandle = AddParentProperty( *It, ArrayIdx );
Parents[ParentHandle].CmdStart = Cmds.Num();
RelativeHandle = InitFromProperty_r( *It, It->ElementSize * ArrayIdx, RelativeHandle, ParentHandle );
Parents[ParentHandle].CmdEnd = Cmds.Num();
}
}
Owner = InFunction;
}
void FRepLayout::InitFromStruct( UStruct * InStruct )
{
int32 RelativeHandle = 0;
for ( TFieldIterator<UProperty> It( InStruct ); It; ++It )
{
if ( It->PropertyFlags & CPF_RepSkip )
{
continue;
}
for ( int32 ArrayIdx = 0; ArrayIdx < It->ArrayDim; ++ArrayIdx )
{
const int32 ParentHandle = AddParentProperty( *It, ArrayIdx );
Parents[ParentHandle].CmdStart = Cmds.Num();
RelativeHandle = InitFromProperty_r( *It, It->ElementSize * ArrayIdx, RelativeHandle, ParentHandle );
Parents[ParentHandle].CmdEnd = Cmds.Num();
}
}
Owner = InStruct;
}
void FRepLayout::SerializeProperties_DynamicArray_r(
FArchive & Ar,
UPackageMap * Map,
const int32 CmdIndex,
uint8 * Data,
bool & bHasUnmapped ) const
{
const FRepLayoutCmd & Cmd = Cmds[ CmdIndex ];
FScriptArray * Array = (FScriptArray *)Data;
// Read array num
uint16 ArrayNum = Array->Num();
Ar << ArrayNum;
const int MAX_ARRAY_SIZE = 2048;
if ( ArrayNum > MAX_ARRAY_SIZE )
{
UE_LOG( LogNetTraffic, Error, TEXT( "SerializeProperties_DynamicArray_r: ArrayNum > MAX_ARRAY_SIZE (%s)" ), *Cmd.Property->GetName() );
Ar.SetError();
return;
}
const int MAX_ARRAY_MEMORY = 1024 * 64;
if ( (int32)ArrayNum * Cmd.ElementSize > MAX_ARRAY_MEMORY )
{
UE_LOG( LogNetTraffic, Error, TEXT( "SerializeProperties_DynamicArray_r: ArrayNum * Cmd.ElementSize > MAX_ARRAY_MEMORY (%s)" ), *Cmd.Property->GetName() );
Ar.SetError();
return;
}
if ( Ar.IsLoading() && ArrayNum != Array->Num() )
{
// If we are reading, size the array to the incoming value
FScriptArrayHelper ArrayHelper( (UArrayProperty *)Cmd.Property, Data );
ArrayHelper.Resize( ArrayNum );
}
Data = (uint8*)Array->GetData();
for ( int32 i = 0; i < Array->Num() && !Ar.IsError(); i++ )
{
SerializeProperties_r( Ar, Map, CmdIndex + 1, Cmd.EndCmd - 1, Data + i * Cmd.ElementSize, bHasUnmapped );
}
}
void FRepLayout::SerializeProperties_r(
FArchive & Ar,
UPackageMap * Map,
const int32 CmdStart,
const int32 CmdEnd,
void * Data,
bool & bHasUnmapped ) const
{
for ( int32 CmdIndex = CmdStart; CmdIndex < CmdEnd && !Ar.IsError(); CmdIndex++ )
{
const FRepLayoutCmd & Cmd = Cmds[CmdIndex];
check( Cmd.Type != REPCMD_Return );
if ( Cmd.Type == REPCMD_DynamicArray )
{
SerializeProperties_DynamicArray_r( Ar, Map, CmdIndex, (uint8*)Data + Cmd.Offset, bHasUnmapped );
CmdIndex = Cmd.EndCmd - 1; // The -1 to handle the ++ in the for loop
continue;
}
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (CVarDoReplicationContextString->GetInt() > 0)
{
Map->SetDebugContextString( FString::Printf(TEXT("%s - %s"), *Owner->GetPathName(), *Cmd.Property->GetPathName() ) );
}
#endif
if ( !Cmd.Property->NetSerializeItem( Ar, Map, (void*)( (uint8*)Data + Cmd.Offset ) ) )
{
bHasUnmapped = true;
}
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (CVarDoReplicationContextString->GetInt() > 0)
{
Map->ClearDebugContextString();
}
#endif
}
}
void FRepLayout::SendPropertiesForRPC( UObject * Object, UFunction * Function, UActorChannel * Channel, FNetBitWriter & Writer, void * Data ) const
{
check( Function == Owner );
for ( int32 i = 0; i < Parents.Num(); i++ )
{
/*
if ( !Writer.PackageMap->SupportsObject( Parents[i].Property ) )
{
continue;
}
*/
bool Send = true;
if ( !Cast<UBoolProperty>( Parents[i].Property ) )
{
// check for a complete match, including arrays
// (we're comparing against zero data here, since
// that's the default.)
Send = false;
if ( !Parents[i].Property->Identical_InContainer( Data, NULL, Parents[i].ArrayIndex ) )
{
Send = true;
}
Writer.WriteBit( Send ? 1 : 0 );
}
if ( Send )
{
bool bHasUnmapped = false;
SerializeProperties_r( Writer, Writer.PackageMap, Parents[i].CmdStart, Parents[i].CmdEnd, Data, bHasUnmapped );
if ( bHasUnmapped )
{
// RPC function is sending an unmapped object...
UE_LOG( LogNetTraffic, Log, TEXT( "Actor[%d] %s RPC %s parameter %s was sent while unmapped! This call may not be correctly handled on the receiving end." ),
Channel->ChIndex, *Object->GetName(), *Function->GetName(), *Parents[i].Property->GetName() );
}
}
}
}
void FRepLayout::ReceivePropertiesForRPC( UObject * Object, UFunction * Function, UActorChannel * Channel, FNetBitReader & Reader, void * Data ) const
{
check( Function == Owner );
for ( int32 i = 0; i < Parents.Num(); i++ )
{
/*
if ( !Reader.PackageMap->SupportsObject( Parents[i].Property ) )
{
continue;
}
*/
if ( Cast<UBoolProperty>( Parents[i].Property ) || Reader.ReadBit() )
{
bool bHasUnmapped = false;
SerializeProperties_r( Reader, Reader.PackageMap, Parents[i].CmdStart, Parents[i].CmdEnd, Data, bHasUnmapped );
if ( Reader.IsError() )
{
return;
}
if ( bHasUnmapped )
{
UE_LOG( LogNetTraffic, Log, TEXT( "Unable to resolve RPC parameter. Object[%d] %s. Function %s. Parameter %s." ),
Channel->ChIndex, *Object->GetName(), *Function->GetName(), *Parents[i].Property->GetName() );
}
}
}
}
void FRepLayout::SerializePropertiesForStruct( UStruct * Struct, FArchive & Ar, UPackageMap * Map, void * Data, bool & bHasUnmapped ) const
{
check( Struct == Owner );
for ( int32 i = 0; i < Parents.Num(); i++ )
{
/*
if ( Map != NULL && !Map->SupportsObject( Parents[i].Property ) )
{
continue;
}
*/
SerializeProperties_r( Ar, Map, Parents[i].CmdStart, Parents[i].CmdEnd, Data, bHasUnmapped );
if ( Ar.IsError() )
{
return;
}
}
}
void FRepLayout::RebuildConditionalProperties( FRepState * RESTRICT RepState, const FRepChangedPropertyTracker & ChangedTracker, const FReplicationFlags & RepFlags ) const
{
SCOPE_CYCLE_COUNTER( STAT_NetRebuildConditionalTime );
//UE_LOG( LogNet, Warning, TEXT( "Rebuilding custom properties [%s]" ), *Owner->GetName() );
// Setup condition map
bool ConditionMap[COND_Max];
const bool bIsInitial = RepFlags.bNetInitial ? true : false;
const bool bIsOwner = RepFlags.bNetOwner ? true : false;
const bool bIsSimulated = RepFlags.bNetSimulated ? true : false;
const bool bIsPhysics = RepFlags.bRepPhysics ? true : false;
ConditionMap[COND_None] = true;
ConditionMap[COND_InitialOnly] = bIsInitial;
ConditionMap[COND_OwnerOnly] = bIsOwner;
ConditionMap[COND_SkipOwner] = !bIsOwner;
ConditionMap[COND_SimulatedOnly] = bIsSimulated;
ConditionMap[COND_AutonomousOnly] = !bIsSimulated;
ConditionMap[COND_SimulatedOrPhysics] = bIsSimulated || bIsPhysics;
ConditionMap[COND_InitialOrOwner] = bIsInitial || bIsOwner;
ConditionMap[COND_Custom] = true;
RepState->ConditionalLifetime.Empty();
for ( int32 i = 0; i < ConditionalLifetime.Num(); i++ )
{
if ( !ConditionMap[ConditionalLifetime[i].Condition] )
{
continue;
}
if ( !ChangedTracker.Parents[ConditionalLifetime[i].RepIndex].Active )
{
continue;
}
RepState->ConditionalLifetime.Add( ConditionalLifetime[i].RepIndex );
}
RepState->RepFlags = RepFlags;
}
void FRepLayout::InitChangedTracker( FRepChangedPropertyTracker * ChangedTracker ) const
{
ChangedTracker->Parents.SetNum( Parents.Num() );
for ( int32 i = 0; i < Parents.Num(); i++ )
{
ChangedTracker->Parents[i].IsConditional = ( Parents[i].Flags & PARENT_IsConditional ) ? 1 : 0;
}
}
void FRepLayout::InitRepState(
FRepState * RepState,
UClass * InObjectClass,
uint8 * Src,
TSharedPtr< FRepChangedPropertyTracker > & InRepChangedPropertyTracker ) const
{
RepState->StaticBuffer.Empty();
RepState->StaticBuffer.AddZeroed( InObjectClass->GetDefaultsCount() );
// Construct the properties
ConstructProperties( RepState );
// Init the properties
InitProperties( RepState, Src );
RepState->RepChangedPropertyTracker = InRepChangedPropertyTracker;
check( RepState->RepChangedPropertyTracker->Parents.Num() == Parents.Num() );
// Start out the conditional props based on a default RepFlags struct
// It will rebuild if it ever changes
RebuildConditionalProperties( RepState, *InRepChangedPropertyTracker.Get(), FReplicationFlags() );
}
void FRepLayout::ConstructProperties( FRepState * RepState ) const
{
uint8 * StoredData = RepState->StaticBuffer.GetTypedData();
// Construct all items
for ( int32 i = 0; i < Parents.Num(); i++ )
{
// Only construct the 0th element of static arrays (InitializeValue will handle the elements)
if ( Parents[i].ArrayIndex == 0 )
{
PTRINT Offset = Parents[i].Property->ContainerPtrToValuePtr<uint8>( StoredData ) - StoredData;
check( Offset >= 0 && Offset < RepState->StaticBuffer.Num() );
Parents[i].Property->InitializeValue( StoredData + Offset );
}
}
}
void FRepLayout::InitProperties( FRepState * RepState, uint8 * Src ) const
{
uint8 * StoredData = RepState->StaticBuffer.GetTypedData();
// Init all items
for ( int32 i = 0; i < Parents.Num(); i++ )
{
// Only copy the 0th element of static arrays (CopyCompleteValue will handle the elements)
if ( Parents[i].ArrayIndex == 0 )
{
PTRINT Offset = Parents[i].Property->ContainerPtrToValuePtr<uint8>( StoredData ) - StoredData;
check( Offset >= 0 && Offset < RepState->StaticBuffer.Num() );
Parents[i].Property->CopyCompleteValue( StoredData + Offset, Src + Offset );
}
}
}
void FRepLayout::DestructProperties( FRepState * RepState ) const
{
uint8 * StoredData = RepState->StaticBuffer.GetTypedData();
// Destruct all items
for ( int32 i = 0; i < Parents.Num(); i++ )
{
// Only copy the 0th element of static arrays (DestroyValue will handle the elements)
if ( Parents[i].ArrayIndex == 0 )
{
PTRINT Offset = Parents[i].Property->ContainerPtrToValuePtr<uint8>( StoredData ) - StoredData;
check( Offset >= 0 && Offset < RepState->StaticBuffer.Num() );
Parents[i].Property->DestroyValue( StoredData + Offset );
}
}
}
void FRepLayout::GetLifetimeCustomDeltaProperties( TArray< int32 > & OutCustom )
{
OutCustom.Empty();
for ( int32 i = 0; i < Parents.Num(); i++ )
{
if ( Parents[i].Flags & PARENT_IsCustomDelta )
{
check( Parents[i].Property->RepIndex + Parents[i].ArrayIndex == i );
OutCustom.AddUnique( i );
}
}
}
FRepState::~FRepState()
{
if (RepLayout.IsValid() && StaticBuffer.Num() > 0)
{
RepLayout->DestructProperties( this );
}
}