Files
UnrealEngineUWP/Engine/Source/Developer/SourceControl/Private/SourceControlFileStatusMonitor.cpp
patrick laflamme c710b14d86 Optimized the source control changelist to more efficienty update the list of source controlled files to monitor.
- Added function to the source control file status monitor to easily set the exact list of file to monitor efficiently
Fixed the source control changelist not monitoring all the files in view.

#rb Aditya.Ravichandran
#preflight 6362b9bf1a32af63a8f5b719

[CL 22937889 by patrick laflamme in ue5-main branch]
2022-11-02 23:06:17 -04:00

365 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SourceControlFileStatusMonitor.h"
#include "Framework/Application/SlateApplication.h"
#include "HAL/PlatformTime.h"
#include "ISourceControlModule.h"
#include "ISourceControlProvider.h"
#include "Math/NumericLimits.h"
#include "SourceControlOperations.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"
FSourceControlFileStatusMonitor::FSourceControlFileStatusMonitor()
: ProbationPeriodPolicy(FTimespan::FromSeconds(1))
, RefreshPeriodPolicy(FTimespan::FromMinutes(5))
{
TickerHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FSourceControlFileStatusMonitor::Tick));
SetSuspendMonitoringPolicy([]()
{
// By default, suspend monitoring if the user didn't interact for the last 5 minutes.
return FPlatformTime::Seconds() - FSlateApplication::Get().GetLastUserInteractionTime() > FTimespan::FromMinutes(5).GetTotalSeconds();
});
}
FSourceControlFileStatusMonitor::~FSourceControlFileStatusMonitor()
{
FTSTicker::GetCoreTicker().RemoveTicker(TickerHandle);
}
TSharedPtr<FSourceControlFileStatusMonitor::FSourceControlFileStatus> FSourceControlFileStatusMonitor::FindFileStatus(const FString& AbsPathname) const
{
if (const TSharedPtr<FSourceControlFileStatus>* FileStatus = MonitoredFiles.Find(AbsPathname))
{
return *FileStatus;
}
return nullptr;
}
void FSourceControlFileStatusMonitor::OnSourceControlProviderChanged(ISourceControlProvider& OldProvider, ISourceControlProvider& NewProvider)
{
ensure(IsInGameThread()); // Concurrency issues if invoked from a background thread.
// Start new with the new provider.
RequestedStatusFiles.Reset();
for (const TPair<FString, TSharedPtr<FSourceControlFileStatus>>& Pair : MonitoredFiles)
{
if (Pair.Value->FileState)
{
for (TPair<uintptr_t, FOnSourceControlFileStatus>& OwnerDelegatePair : Pair.Value->OwnerDelegateMap)
{
OwnerDelegatePair.Value.ExecuteIfBound(Pair.Key, nullptr); // Passing 'nullptr' state means that the state is now unknown.
}
Pair.Value->LastStatusCheckTimestampSecs = 0.0;
Pair.Value->FileState.Reset();
}
}
NewAddedFileCount = MonitoredFiles.Num();
LastAddedFileTimeSecs = FPlatformTime::Seconds();
OldestFileStatusTimeSecs = 0.0;
}
void FSourceControlFileStatusMonitor::StartMonitoringFile(uintptr_t OwnerId, const FString& AbsPathname, FOnSourceControlFileStatus OnSourceControlFileStatus)
{
ensure(IsInGameThread()); // Concurrency issues if invoked from a background thread.
// If the file is already monitored.
if (TSharedPtr<FSourceControlFileStatus> FileStatus = FindFileStatus(AbsPathname))
{
// Another 'client' is looking at this file (if the client already exit, override the callback with the new one).
FOnSourceControlFileStatus& OnSourceControlFileStatusDelegate = FileStatus->OwnerDelegateMap.FindOrAdd(OwnerId);
OnSourceControlFileStatusDelegate = OnSourceControlFileStatus;
// The monitor already knows the status.
if (FileStatus->FileState)
{
OnSourceControlFileStatusDelegate.ExecuteIfBound(AbsPathname, FileStatus->FileState.Get());
}
}
else
{
MonitoredFiles.Emplace(AbsPathname, MakeShared<FSourceControlFileStatus>(OwnerId, MoveTemp(OnSourceControlFileStatus)));
LastAddedFileTimeSecs = FPlatformTime::Seconds();
++NewAddedFileCount;
}
if (!SourceControlProviderChangedDelegateHandle.IsValid())
{
SourceControlProviderChangedDelegateHandle = ISourceControlModule::Get().RegisterProviderChanged(
FSourceControlProviderChanged::FDelegate::CreateSP(this, &FSourceControlFileStatusMonitor::OnSourceControlProviderChanged));
}
}
void FSourceControlFileStatusMonitor::StartMonitoringFiles(uintptr_t OwnerId, const TArray<FString>& AbsPathnames, FOnSourceControlFileStatus OnSourceControlledFileStatus)
{
for (const FString& AbsPathname : AbsPathnames)
{
StartMonitoringFile(OwnerId, AbsPathname, OnSourceControlledFileStatus);
}
}
void FSourceControlFileStatusMonitor::StartMonitoringFiles(uintptr_t OwnerId, const TSet<FString>& AbsPathnames, FOnSourceControlFileStatus OnSourceControlledFileStatus)
{
for (const FString& AbsPathname : AbsPathnames)
{
StartMonitoringFile(OwnerId, AbsPathname, OnSourceControlledFileStatus);
}
}
void FSourceControlFileStatusMonitor::StopMonitoringFile(uintptr_t OwnerId, const FString& AbsPathname)
{
ensure(IsInGameThread()); // Concurrency issues if the callback in invoked from a background thread.
if (TSharedPtr<FSourceControlFileStatus> FileStatus = FindFileStatus(AbsPathname))
{
if (FileStatus->OwnerDelegateMap.Remove(OwnerId) > 0 && FileStatus->OwnerDelegateMap.IsEmpty())
{
MonitoredFiles.Remove(AbsPathname);
}
}
}
void FSourceControlFileStatusMonitor::StopMonitoringFiles(uintptr_t OwnerId, const TArray<FString>& AbsPathnames)
{
for (const FString& AbsPathname : AbsPathnames)
{
StopMonitoringFile(OwnerId, AbsPathname);
}
}
void FSourceControlFileStatusMonitor::StopMonitoringFiles(uintptr_t OwnerId, const TSet<FString>& AbsPathnames)
{
for (const FString& AbsPathname : AbsPathnames)
{
StopMonitoringFile(OwnerId, AbsPathname);
}
}
void FSourceControlFileStatusMonitor::StopMonitoringFiles(uintptr_t OwnerId)
{
ensure(IsInGameThread()); // Concurrency issues if the callback in invoked from a background thread.
for (auto It = MonitoredFiles.CreateIterator(); It; ++It)
{
if (It->Value->OwnerDelegateMap.Remove(OwnerId) > 0 && It->Value->OwnerDelegateMap.IsEmpty())
{
It.RemoveCurrent();
}
}
}
void FSourceControlFileStatusMonitor::SetMonitoringFiles(uintptr_t OwnerId, TSet<FString>&& AbsPathnames, FOnSourceControlFileStatus OnSourceControlledFileStatus)
{
for (auto It = MonitoredFiles.CreateIterator(); It; ++It)
{
const FString& MonitoredAbsPathname = It->Key;
TSharedPtr<FSourceControlFileStatus>& MonitoredFileInfo = It->Value;
FOnSourceControlFileStatus* OwnerCallbackDelegate = MonitoredFileInfo->OwnerDelegateMap.Find(OwnerId);
// If the caller wants to monitor that file.
if (AbsPathnames.Contains(MonitoredAbsPathname))
{
// If the caller was already monitoring the file.
if (OwnerCallbackDelegate)
{
// Replace the delegate with the new one.
*OwnerCallbackDelegate = OnSourceControlledFileStatus;
}
else // The file was monitored, but not by this caller. Add this caller.
{
MonitoredFileInfo->OwnerDelegateMap.Add(OwnerId, OnSourceControlledFileStatus);
// If the status is already known.
if (MonitoredFileInfo->FileState)
{
OnSourceControlledFileStatus.ExecuteIfBound(MonitoredAbsPathname, MonitoredFileInfo->FileState.Get());
}
}
// That file was resolved, remove it from the set.
AbsPathnames.Remove(MonitoredAbsPathname);
}
else if (OwnerCallbackDelegate) // The caller used to monitor this file but doesn't want to monitor it anymore
{
if (MonitoredFileInfo->OwnerDelegateMap.Num() > 1)
{
// Just remove the caller, leaving the others.
MonitoredFileInfo->OwnerDelegateMap.Remove(OwnerId);
}
else
{
// Remove the caller and the files since nobody else monitor the file.
It.RemoveCurrent();
}
}
}
// What remains in the set are the files that weren't monitored yet, start monitoring them.
for (const FString& AbsPathname : AbsPathnames)
{
StartMonitoringFile(OwnerId, AbsPathname, OnSourceControlledFileStatus);
}
}
TSet<FString> FSourceControlFileStatusMonitor::GetMonitoredFiles(uintptr_t OwnerId)
{
TSet<FString> Pathnames;
for (const TPair<FString, TSharedPtr<FSourceControlFileStatus>>& Pair : MonitoredFiles)
{
if (!Pair.Value->OwnerDelegateMap.Contains(OwnerId))
{
Pathnames.Add(Pair.Key);
}
}
return Pathnames;
}
TOptional<FTimespan> FSourceControlFileStatusMonitor::GetStatusAge(const FString& AbsPathname) const
{
TOptional<FTimespan> Age;
if (TSharedPtr<FSourceControlFileStatus> FileStatus = FindFileStatus(AbsPathname))
{
Age.Emplace(FTimespan::FromSeconds(FileStatus->FileState ? FPlatformTime::Seconds() - FileStatus->LastStatusCheckTimestampSecs : 0.0));
}
return Age;
}
bool FSourceControlFileStatusMonitor::Tick(float DeltaTime)
{
ensure(IsInGameThread()); // Concurrency issues if the callback in invoked from a background thread.
// Nothing to check or a request is already in-flight.
if (!ISourceControlModule::Get().IsEnabled() || MonitoredFiles.IsEmpty() || HasOngoingRequest())
{
return true;
}
// Check if the monitor is suspended, not allowed to send requests.
if (SuspendMonitoringPolicy && SuspendMonitoringPolicy())
{
return true;
}
double NowSecs = FPlatformTime::Seconds();
// Throttle the source control status check when no new files need to be checked. Don't overload the source control server with too many requests.
if (NewAddedFileCount == 0 && NowSecs - OldestFileStatusTimeSecs < RefreshPeriodPolicy.GetTotalSeconds())
{
return true; // Nothing to do this time around.
}
// Throttle the checks when new files are added. Batch new files edited close in time to be more efficient.
if (NowSecs - LastAddedFileTimeSecs < ProbationPeriodPolicy.GetTotalSeconds() && NewAddedFileCount < MaxFileNumPerRequestPolicy)
{
return true; // Delay the request, give chance to capture more files.
}
TArray<const TPair<FString, TSharedPtr<FSourceControlFileStatus>>*> NewFiles;
NewFiles.Reserve(NewAddedFileCount);
TArray<const TPair<FString, TSharedPtr<FSourceControlFileStatus>>*> RefreshedFiles;
RefreshedFiles.Reserve(MonitoredFiles.Num());
// List all the files that are new and those that weren't updated recently.
for (const TPair<FString, TSharedPtr<FSourceControlFileStatus>>& Pair : MonitoredFiles)
{
if (!Pair.Value->FileState.IsValid())
{
NewFiles.Add(&Pair);
}
else if (NowSecs - Pair.Value->LastStatusCheckTimestampSecs >= RefreshPeriodPolicy.GetTotalSeconds())
{
RefreshedFiles.Add(&Pair);
}
}
// Too many status to query/refresh?
if (NewFiles.Num() < MaxFileNumPerRequestPolicy && NewFiles.Num() + RefreshedFiles.Num() > MaxFileNumPerRequestPolicy)
{
// Get the status of all newly added files and refresh the ones that were less recently updated.
RefreshedFiles.Sort([](const TPair<FString, TSharedPtr<FSourceControlFileStatus>>& Lhs, const TPair<FString, TSharedPtr<FSourceControlFileStatus>>& Rhs)
{
// Sort ascending as we are going to use Last()/Pop() later, so the oldest must be at the end.
return Lhs.Value->LastStatusCheckTimestampSecs > Rhs.Value->LastStatusCheckTimestampSecs;
});
}
RequestedStatusFiles.Reserve(MaxFileNumPerRequestPolicy);
while (RequestedStatusFiles.Num() < MaxFileNumPerRequestPolicy)
{
if (NewFiles.Num() > 0)
{
RequestedStatusFiles.Emplace(NewFiles.Last()->Key);
NewFiles.Pop(/*bAllowShrinking*/false);
}
else if (RefreshedFiles.Num())
{
RequestedStatusFiles.Emplace(RefreshedFiles.Last()->Key);
RefreshedFiles.Pop(/*bAllowShrinking*/false);
}
else
{
break; // All files to query/refresh were added.
}
}
// The remaining number of new files.
NewAddedFileCount = NewFiles.Num();
if (RequestedStatusFiles.Num())
{
LastSourceControlCheckSecs = NowSecs;
TSharedRef<FUpdateStatus> UpdateStatusRequest = ISourceControlOperation::Create<FUpdateStatus>();
UpdateStatusRequest->SetForceUpdate(true);
ISourceControlModule::Get().GetProvider().Execute(UpdateStatusRequest, RequestedStatusFiles, EConcurrency::Asynchronous,
FSourceControlOperationComplete::CreateSP(this, &FSourceControlFileStatusMonitor::OnSourceControlStatusUpdate));
}
return true;
}
void FSourceControlFileStatusMonitor::OnSourceControlStatusUpdate(const TSharedRef<ISourceControlOperation>& InOperation, ECommandResult::Type InResult)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSourceControlFileStatusMonitor::OnSourceControlStatusUpdate);
ensure(IsInGameThread()); // Concurrency issues if the callback in invoked from a background thread.
double NowSecs = FPlatformTime::Seconds();
ON_SCOPE_EXIT
{
RequestedStatusFiles.Reset();
OldestFileStatusTimeSecs = NowSecs;
for (const TPair<FString, TSharedPtr<FSourceControlFileStatus>>& Pair : MonitoredFiles)
{
OldestFileStatusTimeSecs = FMath::Min(OldestFileStatusTimeSecs, Pair.Value->LastStatusCheckTimestampSecs);
}
};
if (InResult != ECommandResult::Succeeded)
{
return;
}
for (const FString& AbsPathname : RequestedStatusFiles)
{
// NOTE: The file and its status can be removed while exeucting OnFileStatusUpdateDelegate. Keep the shared pointer to avoid early destruction.
if (TSharedPtr<FSourceControlFileStatus> FileStatus = FindFileStatus(AbsPathname))
{
if (TSharedPtr<ISourceControlState> FileState = ISourceControlModule::Get().GetProvider().GetState(AbsPathname, EStateCacheUsage::Use))
{
FileStatus->FileState = MoveTemp(FileState);
FileStatus->LastStatusCheckTimestampSecs = NowSecs;
for (TPair<uintptr_t, FOnSourceControlFileStatus>& OwnerDelegatePair : FileStatus->OwnerDelegateMap)
{
OwnerDelegatePair.Value.ExecuteIfBound(AbsPathname, FileStatus->FileState.Get());
}
}
}
}
}