2021-09-28 13:33:00 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "SmartObjectCollection.h"
# include "SmartObjectTypes.h"
# include "SmartObjectSubsystem.h"
# include "SmartObjectComponent.h"
# include "Engine/World.h"
# include "VisualLogger/VisualLogger.h"
//----------------------------------------------------------------------//
// FSmartObjectCollectionEntry
//----------------------------------------------------------------------//
2022-01-19 14:16:16 -05:00
FSmartObjectCollectionEntry : : FSmartObjectCollectionEntry ( const FSmartObjectHandle & SmartObjectHandle , const USmartObjectComponent & SmartObjectComponent , const uint32 DefinitionIndex )
: Handle ( SmartObjectHandle )
2021-09-28 13:33:00 -04:00
, Path ( & SmartObjectComponent )
2021-11-02 11:12:43 -04:00
, Transform ( SmartObjectComponent . GetComponentTransform ( ) )
, Bounds ( SmartObjectComponent . GetSmartObjectBounds ( ) )
2021-11-26 15:48:13 -05:00
, DefinitionIdx ( DefinitionIndex )
2021-09-28 13:33:00 -04:00
{
}
USmartObjectComponent * FSmartObjectCollectionEntry : : GetComponent ( ) const
{
return CastChecked < USmartObjectComponent > ( Path . ResolveObject ( ) , ECastCheckedType : : NullAllowed ) ;
}
//----------------------------------------------------------------------//
// ASmartObjectCollection
//----------------------------------------------------------------------//
ASmartObjectCollection : : ASmartObjectCollection ( const FObjectInitializer & ObjectInitializer )
: Super ( ObjectInitializer )
{
2021-11-26 15:48:13 -05:00
# if WITH_EDITORONLY_DATA
bLockLocation = true ;
bActorLabelEditable = false ;
# endif
2021-09-28 13:33:00 -04:00
PrimaryActorTick . bCanEverTick = false ;
bNetLoadOnClient = false ;
SetCanBeDamaged ( false ) ;
}
void ASmartObjectCollection : : Destroyed ( )
{
// Handle editor delete.
UnregisterWithSubsystem ( ANSI_TO_TCHAR ( __FUNCTION__ ) ) ;
Super : : Destroyed ( ) ;
}
void ASmartObjectCollection : : EndPlay ( const EEndPlayReason : : Type EndPlayReason )
{
// Handle Level unload, PIE end, SIE end, game end.
UnregisterWithSubsystem ( ANSI_TO_TCHAR ( __FUNCTION__ ) ) ;
Super : : EndPlay ( EndPlayReason ) ;
}
void ASmartObjectCollection : : PostActorCreated ( )
{
// Register after being initially spawned.
Super : : PostActorCreated ( ) ;
RegisterWithSubsystem ( ANSI_TO_TCHAR ( __FUNCTION__ ) ) ;
}
void ASmartObjectCollection : : PreRegisterAllComponents ( )
{
Super : : PreRegisterAllComponents ( ) ;
// Handle UWorld::AddToWorld(), i.e. turning on level visibility
if ( const ULevel * Level = GetLevel ( ) )
{
// This function gets called in editor all the time, we're only interested the case where level is being added to world.
if ( Level - > bIsAssociatingLevel )
{
RegisterWithSubsystem ( ANSI_TO_TCHAR ( __FUNCTION__ ) ) ;
}
}
}
void ASmartObjectCollection : : PostUnregisterAllComponents ( )
{
// Handle UWorld::RemoveFromWorld(), i.e. turning off level visibility
if ( const ULevel * Level = GetLevel ( ) )
{
// This function gets called in editor all the time, we're only interested the case where level is being removed from world.
if ( Level - > bIsDisassociatingLevel )
{
UnregisterWithSubsystem ( ANSI_TO_TCHAR ( __FUNCTION__ ) ) ;
}
}
Super : : PostUnregisterAllComponents ( ) ;
}
2021-11-02 11:12:43 -04:00
bool ASmartObjectCollection : : RegisterWithSubsystem ( const FString & Context )
2021-09-28 13:33:00 -04:00
{
if ( bRegistered )
{
2021-12-10 18:31:54 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Error , TEXT ( " '%s' %s - Failed: already registered " ) , * GetFullName ( ) , * Context ) ;
2021-09-28 13:33:00 -04:00
return false ;
}
if ( HasAnyFlags ( RF_ClassDefaultObject ) )
{
2021-12-10 18:31:54 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Log , TEXT ( " '%s' %s - Failed: ignoring default object " ) , * GetFullName ( ) , * Context ) ;
2021-09-28 13:33:00 -04:00
return false ;
}
USmartObjectSubsystem * SmartObjectSubsystem = USmartObjectSubsystem : : GetCurrent ( GetWorld ( ) ) ;
if ( SmartObjectSubsystem = = nullptr )
{
2021-11-02 11:12:43 -04:00
// Collection might attempt to register before the subsystem is created. At its initialization the subsystem gathers
// all collections and registers them. For this reason we use a log instead of an error.
2021-12-10 18:31:54 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Log , TEXT ( " '%s' %s - Failed: unable to find smart object subsystem " ) , * GetFullName ( ) , * Context ) ;
2021-09-28 13:33:00 -04:00
return false ;
}
2022-01-18 16:25:49 -05:00
const ESmartObjectCollectionRegistrationResult Result = SmartObjectSubsystem - > RegisterCollection ( * this ) ;
UE_VLOG_UELOG ( this , LogSmartObject , Log , TEXT ( " '%s' %s - %s " ) , * GetFullName ( ) , * Context , * UEnum : : GetValueAsString ( Result ) ) ;
2021-09-28 13:33:00 -04:00
return true ;
}
2021-11-02 11:12:43 -04:00
bool ASmartObjectCollection : : UnregisterWithSubsystem ( const FString & Context )
2021-09-28 13:33:00 -04:00
{
if ( ! bRegistered )
{
2021-12-10 18:31:54 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Log , TEXT ( " '%s' %s - Failed: not registered " ) , * GetFullName ( ) , * Context ) ;
2021-09-28 13:33:00 -04:00
return false ;
}
USmartObjectSubsystem * SmartObjectSubsystem = USmartObjectSubsystem : : GetCurrent ( GetWorld ( ) ) ;
if ( SmartObjectSubsystem = = nullptr )
{
2021-12-10 18:31:54 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Log , TEXT ( " '%s' %s - Failed: unable to find smart object subsystem " ) , * GetFullName ( ) , * Context ) ;
2021-09-28 13:33:00 -04:00
return false ;
}
SmartObjectSubsystem - > UnregisterCollection ( * this ) ;
2021-12-10 18:31:54 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Log , TEXT ( " '%s' %s - Succeeded " ) , * GetFullName ( ) , * Context ) ;
2021-09-28 13:33:00 -04:00
return true ;
}
2021-11-02 11:12:43 -04:00
bool ASmartObjectCollection : : AddSmartObject ( USmartObjectComponent & SOComponent )
2021-09-28 13:33:00 -04:00
{
2021-11-02 11:12:43 -04:00
const UWorld * World = GetWorld ( ) ;
if ( World = = nullptr )
{
UE_VLOG_UELOG ( this , LogSmartObject , Error , TEXT ( " '%s' can't be registered to collection '%s': no associated world " ) ,
2021-12-10 18:31:54 -05:00
* GetNameSafe ( SOComponent . GetOwner ( ) ) , * GetFullName ( ) ) ;
2021-11-02 11:12:43 -04:00
return false ;
}
2021-09-28 13:33:00 -04:00
2021-11-02 11:12:43 -04:00
const FSoftObjectPath ObjectPath = & SOComponent ;
FString AssetPathString = ObjectPath . GetAssetPathString ( ) ;
2021-09-28 13:33:00 -04:00
2021-11-02 11:12:43 -04:00
// We are not using asset path for partitioned world since they are not stable between editor and runtime.
// SubPathString should be enough since all actors are part of the main level.
if ( World - > IsPartitionedWorld ( ) )
{
AssetPathString . Reset ( ) ;
}
# if WITH_EDITOR
else if ( World - > WorldType = = EWorldType : : PIE )
{
AssetPathString = UWorld : : RemovePIEPrefix ( ObjectPath . GetAssetPathString ( ) ) ;
}
# endif // WITH_EDITOR
// Compute hash manually from strings since GetTypeHash(FSoftObjectPath) relies on a FName which implements run-dependent hash computations.
2022-01-19 14:16:16 -05:00
FSmartObjectHandle Handle = FSmartObjectHandle ( HashCombine ( GetTypeHash ( AssetPathString ) , GetTypeHash ( ObjectPath . GetSubPathString ( ) ) ) ) ;
SOComponent . SetRegisteredHandle ( Handle ) ;
2021-11-02 11:12:43 -04:00
2022-01-19 14:16:16 -05:00
const FSmartObjectCollectionEntry * ExistingEntry = CollectionEntries . FindByPredicate ( [ Handle ] ( const FSmartObjectCollectionEntry & Entry )
2021-11-02 11:12:43 -04:00
{
2022-01-19 14:16:16 -05:00
return Entry . Handle = = Handle ;
2021-11-02 11:12:43 -04:00
} ) ;
if ( ExistingEntry ! = nullptr )
{
2022-01-19 14:16:16 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , VeryVerbose , TEXT ( " '%s[%s]' already registered to collection '%s' " ) ,
* GetNameSafe ( SOComponent . GetOwner ( ) ) , * LexToString ( Handle ) , * GetFullName ( ) ) ;
2021-11-02 11:12:43 -04:00
return false ;
}
2021-11-26 15:48:13 -05:00
const USmartObjectDefinition * Definition = SOComponent . GetDefinition ( ) ;
ensureMsgf ( Definition ! = nullptr , TEXT ( " Shouldn't reach this point with an invalid definition asset " ) ) ;
uint32 DefinitionIndex = Definitions . AddUnique ( Definition ) ;
2021-11-02 11:12:43 -04:00
2022-01-19 14:16:16 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Verbose , TEXT ( " Adding '%s[%s]' to collection '%s' " ) , * GetNameSafe ( SOComponent . GetOwner ( ) ) , * LexToString ( Handle ) , * GetFullName ( ) ) ;
CollectionEntries . Emplace ( Handle , SOComponent , DefinitionIndex ) ;
RegisteredIdToObjectMap . Add ( Handle , ObjectPath ) ;
2021-11-02 11:12:43 -04:00
return true ;
2021-09-28 13:33:00 -04:00
}
2021-11-02 11:12:43 -04:00
bool ASmartObjectCollection : : RemoveSmartObject ( USmartObjectComponent & SOComponent )
2021-09-28 13:33:00 -04:00
{
2022-01-19 14:16:16 -05:00
FSmartObjectHandle Handle = SOComponent . GetRegisteredHandle ( ) ;
if ( ! Handle . IsValid ( ) )
2021-09-28 13:33:00 -04:00
{
2021-11-02 11:12:43 -04:00
return false ;
2021-09-28 13:33:00 -04:00
}
2022-01-19 14:16:16 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Verbose , TEXT ( " Removing '%s[%s]' from collection '%s' " ) , * GetNameSafe ( SOComponent . GetOwner ( ) ) , * LexToString ( Handle ) , * GetFullName ( ) ) ;
2021-09-28 13:33:00 -04:00
const int32 Index = CollectionEntries . IndexOfByPredicate (
2022-01-19 14:16:16 -05:00
[ & Handle ] ( const FSmartObjectCollectionEntry & Entry )
2021-09-28 13:33:00 -04:00
{
2022-01-19 14:16:16 -05:00
return Entry . GetHandle ( ) = = Handle ;
2021-09-28 13:33:00 -04:00
} ) ;
if ( Index ! = INDEX_NONE )
{
CollectionEntries . RemoveAt ( Index ) ;
2022-01-19 14:16:16 -05:00
RegisteredIdToObjectMap . Remove ( Handle ) ;
2021-09-28 13:33:00 -04:00
}
2022-01-19 14:16:16 -05:00
SOComponent . SetRegisteredHandle ( FSmartObjectHandle : : Invalid ) ;
2021-11-02 11:12:43 -04:00
return Index ! = INDEX_NONE ;
2021-09-28 13:33:00 -04:00
}
2022-01-19 14:16:16 -05:00
USmartObjectComponent * ASmartObjectCollection : : GetSmartObjectComponent ( const FSmartObjectHandle & SmartObjectHandle ) const
2021-09-28 13:33:00 -04:00
{
2022-01-19 14:16:16 -05:00
const FSoftObjectPath * Path = RegisteredIdToObjectMap . Find ( SmartObjectHandle ) ;
2021-09-28 13:33:00 -04:00
return Path ! = nullptr ? CastChecked < USmartObjectComponent > ( Path - > ResolveObject ( ) , ECastCheckedType : : NullAllowed ) : nullptr ;
}
2021-11-26 15:48:13 -05:00
const USmartObjectDefinition * ASmartObjectCollection : : GetDefinitionForEntry ( const FSmartObjectCollectionEntry & Entry ) const
2021-09-28 13:33:00 -04:00
{
2021-11-26 15:48:13 -05:00
const bool bIsValidIndex = Definitions . IsValidIndex ( Entry . GetDefinitionIndex ( ) ) ;
2021-11-02 11:12:43 -04:00
if ( ! bIsValidIndex )
{
2021-12-10 18:31:54 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Error , TEXT ( " Using invalid index (%d) to retrieve definition from collection '%s' " ) , Entry . GetDefinitionIndex ( ) , * GetFullName ( ) ) ;
2021-11-02 11:12:43 -04:00
return nullptr ;
}
2021-11-26 15:48:13 -05:00
const USmartObjectDefinition * Definition = Definitions [ Entry . GetDefinitionIndex ( ) ] ;
ensureMsgf ( Definition ! = nullptr , TEXT ( " Collection is expected to contain only valid definition entries " ) ) ;
return Definition ;
2021-09-28 13:33:00 -04:00
}
void ASmartObjectCollection : : OnRegistered ( )
{
bRegistered = true ;
}
void ASmartObjectCollection : : OnUnregistered ( )
{
bRegistered = false ;
}
2021-11-26 15:48:13 -05:00
void ASmartObjectCollection : : ValidateDefinitions ( )
2021-11-02 11:12:43 -04:00
{
2021-11-26 15:48:13 -05:00
for ( const USmartObjectDefinition * Definition : Definitions )
2021-11-02 11:12:43 -04:00
{
2022-02-25 14:18:42 -05:00
UE_CVLOG_UELOG ( Definition = = nullptr , this , LogSmartObject , Warning
, TEXT ( " Null definition found at index (%d) in collection '%s'. Collection needs to be rebuilt and saved. " )
, Definitions . IndexOfByKey ( Definition )
, * GetFullName ( ) ) ;
if ( Definition ! = nullptr )
2021-11-02 11:12:43 -04:00
{
2021-11-26 15:48:13 -05:00
Definition - > Validate ( ) ;
2021-11-02 11:12:43 -04:00
}
}
}
2021-09-28 13:33:00 -04:00
# if WITH_EDITOR
void ASmartObjectCollection : : PostEditUndo ( )
{
Super : : PostEditUndo ( ) ;
if ( IsPendingKillPending ( ) )
{
UnregisterWithSubsystem ( ANSI_TO_TCHAR ( __FUNCTION__ ) ) ;
}
else
{
RegisterWithSubsystem ( ANSI_TO_TCHAR ( __FUNCTION__ ) ) ;
}
}
void ASmartObjectCollection : : PostEditChangeProperty ( FPropertyChangedEvent & PropertyChangedEvent )
{
Super : : PostEditChangeProperty ( PropertyChangedEvent ) ;
if ( PropertyChangedEvent . Property )
{
const FName PropName = PropertyChangedEvent . Property - > GetFName ( ) ;
if ( PropName = = GET_MEMBER_NAME_CHECKED ( ASmartObjectCollection , bBuildOnDemand ) )
{
if ( ! bBuildOnDemand )
{
RebuildCollection ( ) ;
}
}
}
}
void ASmartObjectCollection : : RebuildCollection ( )
{
if ( USmartObjectSubsystem * SmartObjectSubsystem = USmartObjectSubsystem : : GetCurrent ( GetWorld ( ) ) )
{
SmartObjectSubsystem - > RebuildCollection ( * this ) ;
2021-11-02 11:12:43 -04:00
// Dirty package since this is an explicit user action
MarkPackageDirty ( ) ;
2021-09-28 13:33:00 -04:00
}
}
2021-11-02 11:12:43 -04:00
void ASmartObjectCollection : : RebuildCollection ( const TConstArrayView < USmartObjectComponent * > Components )
2021-09-28 13:33:00 -04:00
{
2021-12-10 18:31:54 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Log , TEXT ( " Rebuilding collection '%s' from component list " ) , * GetFullName ( ) ) ;
2021-09-28 13:33:00 -04:00
2021-11-26 15:48:13 -05:00
ResetCollection ( Components . Num ( ) ) ;
2021-11-02 11:12:43 -04:00
2021-09-28 13:33:00 -04:00
for ( USmartObjectComponent * const Component : Components )
{
if ( Component ! = nullptr )
{
AddSmartObject ( * Component ) ;
}
}
2021-11-02 11:12:43 -04:00
CollectionEntries . Shrink ( ) ;
RegisteredIdToObjectMap . Shrink ( ) ;
2021-11-26 15:48:13 -05:00
Definitions . Shrink ( ) ;
2021-11-02 11:12:43 -04:00
}
2021-11-26 15:48:13 -05:00
void ASmartObjectCollection : : ResetCollection ( const int32 ExpectedNumElements )
2021-11-02 11:12:43 -04:00
{
2021-12-10 18:31:54 -05:00
UE_VLOG_UELOG ( this , LogSmartObject , Log , TEXT ( " Reseting collection '%s' " ) , * GetFullName ( ) ) ;
2021-11-02 11:12:43 -04:00
2021-11-26 15:48:13 -05:00
CollectionEntries . Reset ( ExpectedNumElements ) ;
RegisteredIdToObjectMap . Empty ( ExpectedNumElements ) ;
Definitions . Reset ( ) ;
2021-09-28 13:33:00 -04:00
}
# endif // WITH_EDITOR