// Copyright Epic Games, Inc. All Rights Reserved. #include "UGSTab.h" #include "UGSLog.h" #include "ChangeInfo.h" #include "CreateClientTask.h" #include "FindStreamsTask.h" #include "UGSTabManager.h" #include "Utility.h" #include "BuildStep.h" #include "DetectProjectSettingsTask.h" #include "FindStreamsTask.h" #include "PerforceMonitor.h" #include "EventMonitor.h" #include "Widgets/SModalTaskWindow.h" #include "Widgets/SLogWidget.h" #include "HAL/PlatformApplicationMisc.h" #include "Framework/Application/SlateApplication.h" #include "Internationalization/Regex.h" #define LOCTEXT_NAMESPACE "UGSTab" UGSTab::UGSTab() : TabArgs(nullptr, FTabId()), TabWidget(SNew(SDockTab)), EmptyTabView(SNew(SEmptyTab, this)), GameSyncTabView(SNew(SGameSyncTab, this)), bHasQueuedMessages(false), bNeedUpdateGameTabBuildList(false) { Initialize(nullptr); } UGSTab::~UGSTab() { if (Workspace) { Workspace->CancelUpdate(); } } void UGSTab::Initialize(TSharedPtr InUserSettings) { UserSettings = InUserSettings; TabWidget->SetContent(EmptyTabView); TabWidget->SetLabel(FText(LOCTEXT("TabName", "Select a Project"))); PerforceClient = MakeShared(TEXT(""), TEXT(""), TEXT("")); } void UGSTab::SetTabManager(UGSTabManager* InTabManager) { TabManager = InTabManager; } const TSharedRef UGSTab::GetTabWidget() { return TabWidget; } void UGSTab::SetTabArgs(FSpawnTabArgs InTabArgs) { TabArgs = InTabArgs; } FSpawnTabArgs UGSTab::GetTabArgs() const { return TabArgs; } namespace { // TODO super hacky... fix up later #if PLATFORM_WINDOWS const TCHAR* HostPlatform = TEXT("Win64"); #elif PLATFORM_MAC const TCHAR* HostPlatform = TEXT("Mac"); #else const TCHAR* HostPlatform = TEXT("Linux"); #endif class FLineWriter : public UGSCore::FLineBasedTextWriter { public: virtual void FlushLine(const FString& Line) override { UE_LOG(LogSlateUGS, Log, TEXT("%s"), *Line); } }; FString GetEditorExePath(UGSCore::EBuildConfig Config, TSharedPtr DetectSettings) { #if PLATFORM_WINDOWS FString ExeFileName = TEXT("UnrealEditor.exe"); #else FString ExeFileName = TEXT("UnrealEditor"); #endif if (Config != UGSCore::EBuildConfig::DebugGame && Config != UGSCore::EBuildConfig::Development) { #if PLATFORM_WINDOWS ExeFileName = FString::Printf(TEXT("UnrealEditor-%s-%s.exe"), HostPlatform, *UGSCore::ToString(Config)); #else ExeFileName = FString::Printf(TEXT("UnrealEditor-%s-%s"), HostPlatform, *UGSCore::ToString(Config)); #endif } return DetectSettings->BranchDirectoryName / "Engine" / "Binaries" / HostPlatform / ExeFileName; } static FString FormatUserName(FString UserName) { TStringBuilder<256> NormalUserName; for (int Index = 0; Index < UserName.Len(); Index++) { if (Index == 0 || UserName[Index - 1] == '.') { NormalUserName.AppendChar(FChar::ToUpper(UserName[Index])); } else if (UserName[Index] == '.') { NormalUserName.AppendChar(' '); } else { NormalUserName.AppendChar(FChar::ToLower(UserName[Index])); } } return NormalUserName.ToString(); } } bool UGSTab::IsProjectLoaded() const { return !ProjectFileName.IsEmpty(); } bool UGSTab::OnWorkspaceChosen(const FString& Project) { bool bIsDataValid = FPaths::FileExists(Project); // Todo: Check that the project file is also associated with a workspace if (bIsDataValid) { ProjectFileName = Project; if (SetupWorkspace()) { GameSyncTabView->SetStreamPathText(FText::FromString(DetectSettings->StreamName)); GameSyncTabView->SetChangelistText(WorkspaceSettings->CurrentChangeNumber); GameSyncTabView->SetProjectPathText(FText::FromString(ProjectFileName)); TabWidget->SetContent(GameSyncTabView); TabWidget->SetLabel(FText::FromString(DetectSettings->StreamName)); return true; } } return false; } void UGSTab::RefreshBuildList() { Log->Logf(TEXT("Refreshing build list...")); PerforceMonitor->Refresh(); } void UGSTab::CancelSync() { if (Workspace->IsBusy()) { Log->Logf(TEXT("Canceling sync/build...")); Workspace->CancelUpdate(); } } TMap UGSTab::GetWorkspaceVariables() const { UGSCore::EBuildConfig EditorBuildConfig = GetEditorBuildConfig(); TMap Variables; Variables.Add("BranchDir", DetectSettings->BranchDirectoryName); Variables.Add("ProjectDir", FPaths::GetPath(DetectSettings->NewSelectedFileName)); Variables.Add("ProjectFile", DetectSettings->NewSelectedFileName); // Todo: These might not be called "UE4*" anymore Variables.Add("UE4EditorExe", GetEditorExePath(EditorBuildConfig, DetectSettings)); Variables.Add("UE4EditorCmdExe", GetEditorExePath(EditorBuildConfig, DetectSettings).Replace(TEXT(".exe"), TEXT("-Cmd.exe"))); Variables.Add("UE4EditorConfig", UGSCore::ToString(EditorBuildConfig)); Variables.Add("UE4EditorDebugArg", (EditorBuildConfig == UGSCore::EBuildConfig::Debug || EditorBuildConfig == UGSCore::EBuildConfig::DebugGame)? " -debug" : ""); return Variables; } UGSCore::EBuildConfig UGSTab::GetEditorBuildConfig() const { return ShouldSyncPrecompiledEditor() ? UGSCore::EBuildConfig::Development : UserSettings->CompiledEditorBuildConfig; } bool UGSTab::ShouldSyncPrecompiledEditor() const { return UserSettings->bSyncPrecompiledEditor && PerforceMonitor->HasZippedBinaries(); } TArray UGSTab::GetAllStreamNames() const { TArray Result; const TSharedRef TaskResult = ExecuteModalTask( FSlateApplication::Get().GetActiveModalWindow(), MakeShared(PerforceClient.ToSharedRef(), MakeShared(), Result, TEXT("//*/*")), LOCTEXT("FindingStreamsTitle", "Finding Streams"), LOCTEXT("FindingStreamsCaption", "Finding streams, please wait...")); if (TaskResult->Failed()) { FMessageDialog::Open(EAppMsgType::Ok, TaskResult->GetMessage()); return TArray(); } return Result; } // Honestly ... seems ... super hacky/hardcoded. With out all these you assert when trying to merge build targets sooo a bit odd // TODO Need to do each of these ... per ... platform?? TMap UGSTab::GetDefaultBuildStepObjects(const FString& EditorTargetName) { TArray DefaultBuildSteps; DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0x01F66060, 0x73FA4CC8, 0x9CB3E217, 0xFBBA954E), 0, TEXT("Compile UnrealHeaderTool"), TEXT("Compiling UnrealHeaderTool..."), 1, TEXT("UnrealHeaderTool"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); FString ActualEditorTargetName = (EditorTargetName.Len() > 0) ? EditorTargetName : "UnrealEditor"; DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0xF097FF61, 0xC9164058, 0x839135B4, 0x6C3173D5), 1, FString::Printf(TEXT("Compile %s"), *ActualEditorTargetName), FString::Printf(TEXT("Compiling %s..."), *ActualEditorTargetName), 10, ActualEditorTargetName, HostPlatform, UGSCore::ToString(UserSettings->CompiledEditorBuildConfig), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0xC6E633A1, 0x956F4AD3, 0xBC956D06, 0xD131E7B4), 2, TEXT("Compile ShaderCompileWorker"), TEXT("Compiling ShaderCompileWorker..."), 1, TEXT("ShaderCompileWorker"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0x24FFD88C, 0x79014899, 0x9696AE10, 0x66B4B6E8), 3, TEXT("Compile UnrealLightmass"), TEXT("Compiling UnrealLightmass..."), 1, TEXT("UnrealLightmass"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0xFFF20379, 0x06BF4205, 0x8A3EC534, 0x27736688), 4, TEXT("Compile CrashReportClient"), TEXT("Compiling CrashReportClient..."), 1, TEXT("CrashReportClient"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0x89FE8A79, 0xD2594C7B, 0xBFB468F7, 0x218B91C2), 5, TEXT("Compile UnrealInsights"), TEXT("Compiling UnrealInsights..."), 1, TEXT("UnrealInsights"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0x46312669, 0x5069428D, 0x8D72C241, 0x6C5A322E), 6, TEXT("Launch UnrealInsights"), TEXT("Running UnrealInsights..."), 1, TEXT("UnrealInsights"), HostPlatform, TEXT("Shipping"), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0xBB48CA5B, 0x56824432, 0x824DC451, 0x336A6523), 7, TEXT("Compile Zen Dashboard"), TEXT("Compile ZenDashboard Step..."), 1, TEXT("ZenDashboard"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0x586CC0D3, 0x39144DF9, 0xACB62C02, 0xCD9D4FC6), 8, TEXT("Launch Zen Dashboard"), TEXT("Running Zen Dashboard..."), 1, TEXT("ZenDashboard"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0x91C2A429, 0xC39149B4, 0x92A54E6B, 0xE71E0F00), 9, TEXT("Compile SwitchboardListener"), TEXT("Compiling SwitchboardListener..."), 1, TEXT("SwitchboardListener"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0x5036C75B, 0x8DF04329, 0x82A1869D, 0xD2D48605), 10, TEXT("Compile UnrealMultiUserServer"), TEXT("Compiling UnrealMultiUserServer..."), 1, TEXT("UnrealMultiUserServer"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0x274B89C3, 0x9DC64465, 0xA50840AB, 0xC4593CC2), 11, TEXT("Compile UnrealMultiUserSlateServer"), TEXT("Compiling UnrealMultiUserSlateServer..."), 1, TEXT("UnrealMultiUserSlateServer"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); DefaultBuildSteps.Add(UGSCore::FBuildStep(FGuid(0xF7F0C4C7, 0x55514485, 0xA961F3E4, 0xFD9D138C), 12, TEXT("Compile InterchangeWorker"), TEXT("Compiling InterchangeWorker..."), 1, TEXT("InterchangeWorker"), HostPlatform, TEXT("Development"), TEXT(""), !ShouldSyncPrecompiledEditor())); TMap DefaultBuildStepObjects; for (const UGSCore::FBuildStep& DefaultBuildStep : DefaultBuildSteps) { DefaultBuildStepObjects.Add(DefaultBuildStep.UniqueId, DefaultBuildStep.ToConfigObject()); } return DefaultBuildStepObjects; } void UGSTab::OnSyncChangelist(int Changelist) { // Options on what to do with workspace when updating it UGSCore::EWorkspaceUpdateOptions Options = UGSCore::EWorkspaceUpdateOptions::Sync | UGSCore::EWorkspaceUpdateOptions::GenerateProjectFiles; if (UserSettings->bAutoResolveConflicts) { Options |= UGSCore::EWorkspaceUpdateOptions::AutoResolveChanges; } if (UserSettings->bUseIncrementalBuilds) { Options |= UGSCore::EWorkspaceUpdateOptions::UseIncrementalBuilds; } if (UserSettings->bBuildAfterSync) { Options |= UGSCore::EWorkspaceUpdateOptions::Build; } if (UserSettings->bBuildAfterSync && UserSettings->bRunAfterSync) { Options |= UGSCore::EWorkspaceUpdateOptions::RunAfterSync; } if (UserSettings->bOpenSolutionAfterSync) { Options |= UGSCore::EWorkspaceUpdateOptions::OpenSolutionAfterSync; } TSharedRef Context = MakeShared( Changelist, Options, CombinedSyncFilter, GetDefaultBuildStepObjects(DetectSettings->NewProjectEditorTarget), ProjectSettings->BuildSteps, TSet(), GetWorkspaceVariables()); if (UserSettings->bSyncPrecompiledEditor) { FString ArchivePath; if (PerforceMonitor->TryGetArchivePathForChangeNumber(Changelist, ArchivePath)) { Context->Options |= UGSCore::EWorkspaceUpdateOptions::SyncArchives; Context->ArchiveTypeToDepotPath.Add(TPair("Editor", ArchivePath)); } } // Update the workspace with the Context! Workspace->Update(Context); } void UGSTab::OnBuildWorkspace() { UGSCore::EWorkspaceUpdateOptions Options = UGSCore::EWorkspaceUpdateOptions::Build; TSharedRef Context = MakeShared( -1, Options, CombinedSyncFilter, GetDefaultBuildStepObjects(DetectSettings->NewProjectEditorTarget), ProjectSettings->BuildSteps, TSet(), GetWorkspaceVariables()); // Update the workspace with the Context! Workspace->Update(Context); } void UGSTab::OnViewInSwarmClicked(int Changelist) const { FString SwarmURL = FString::Printf(TEXT("https://p4-swarm.epicgames.net/changes/%i"), Changelist); Log->Logf(TEXT("Opening Swarm to %s"), *SwarmURL); FPlatformProcess::LaunchURL(*SwarmURL, nullptr, nullptr); } void UGSTab::OnCopyChangeListClicked(int Changelist) const { Log->Logf(TEXT("Copying CL %i to clipboard"), Changelist); FPlatformApplicationMisc::ClipboardCopy(*FString::FromInt(Changelist)); } void UGSTab::OnMoreInfoClicked(int Changelist) const { Log->Logf(TEXT("Opening CL %i in p4v"), Changelist); PerforceClient->OpenP4VC(FString::Printf(TEXT("change %i"), Changelist)); } void UGSTab::OnOpenPerforceClicked() const { Log->Logf(TEXT("Opening %s in p4v"), *ProjectFileName); PerforceClient->OpenP4V(); } void UGSTab::OnSyncLatest() { int ChangeNumber = -1; FEvent* AbortEvent = FPlatformProcess::GetSynchEventFromPool(true); if (PerforceClient->LatestChangeList(ChangeNumber, AbortEvent, *Log.Get())) { OnSyncChangelist(ChangeNumber); } FPlatformProcess::ReturnSynchEventToPool(AbortEvent); } void UGSTab::OnSyncFilterWindowSaved( const TArray& SyncViewCurrent, const TArray& SyncExcludedCategoriesCurrent, const TArray& SyncViewAll, const TArray& SyncExcludedCategoriesAll) { WorkspaceSettings->SyncView = SyncViewCurrent; WorkspaceSettings->SyncExcludedCategories = SyncExcludedCategoriesCurrent; UserSettings->SyncView = SyncViewAll; UserSettings->SyncExcludedCategories = SyncExcludedCategoriesAll; UserSettings->Save(); } void UGSTab::OnOpenExplorer() { Log->Logf(TEXT("Opening %s in the explorer"), *FPaths::GetPath(ProjectFileName)); FPlatformProcess::ExploreFolder(*FPaths::GetPath(ProjectFileName)); } void UGSTab::OnOpenEditor() { UGSCore::EBuildConfig EditorBuildConfig = GetEditorBuildConfig(); FString EditorPath = GetEditorExePath(EditorBuildConfig, DetectSettings); Log->Logf(TEXT("Running Editor %s %s"), *EditorPath, *ProjectFileName); EditorProcessHandle = FPlatformProcess::CreateProc(*EditorPath, *ProjectFileName, true, false, false, nullptr, 0, nullptr, nullptr, nullptr); } void UGSTab::OnCreateWorkspace(const FString& WorkspaceName, const FString& Stream, const FString& RootDirectory) const { const TSharedRef CreateWorkspaceLog = MakeShared(); FEvent* AbortEvent = FPlatformProcess::GetSynchEventFromPool(true); TSharedPtr PerforceInfo; PerforceClient->Info(PerforceInfo, AbortEvent, *CreateWorkspaceLog); TMap Tags; Tags.Add(TEXT("client"), WorkspaceName); Tags.Add(TEXT("Owner"), PerforceInfo->UserName); Tags.Add(TEXT("Host"), PerforceInfo->HostName); Tags.Add(TEXT("Root"), RootDirectory); const UGSCore::FPerforceClientRecord ClientRecord(Tags); const TSharedRef TaskResult = ExecuteModalTask( FSlateApplication::Get().GetActiveModalWindow(), MakeShared(PerforceClient.ToSharedRef(), MakeShared(), ClientRecord, Stream), LOCTEXT("CreatingProjectTitle", "Creating Project"), LOCTEXT("CreatingProjectCaption", "Creating project, please wait...")); } void UGSTab::QueueMessageForMainThread(TFunction Function) { FScopeLock Lock(&CriticalSection); MessageQueue.Add(Function); bHasQueuedMessages = true; } void UGSTab::Tick() { if (bHasQueuedMessages) { bHasQueuedMessages = false; FScopeLock Lock(&CriticalSection); for (const TFunction& MessageFunction : MessageQueue) { MessageFunction(); } MessageQueue.Empty(); } if (bNeedUpdateGameTabBuildList) { bNeedUpdateGameTabBuildList = false; UpdateGameTabBuildList(); } // if we have spawned an editor process, check if its closed to reap it if (EditorProcessHandle.IsValid()) { if (!FPlatformProcess::IsProcRunning(EditorProcessHandle)) { FPlatformProcess::CloseProc(EditorProcessHandle); EditorProcessHandle.Reset(); } } } namespace { bool ShouldShowChange(const UGSCore::FPerforceChangeSummary& Change, const TArray& ExcludeChanges) { for (const FString& ExcludeChange : ExcludeChanges) { FRegexPattern RegexPattern(*ExcludeChange, ERegexPatternFlags::CaseInsensitive); FRegexMatcher RegexMatcher(RegexPattern, Change.Description); if (RegexMatcher.FindNext()) { return false; } } if (Change.User == TEXT("buildmachine") && Change.Description.Contains(TEXT("lightmaps"))) { return false; } return true; } } bool UGSTab::ShouldIncludeInReviewedList(const TSet& PromotedChangeNumbers, int ChangeNumber) const { if (PromotedChangeNumbers.Contains(ChangeNumber)) { return true; } TSharedPtr Review; if (EventMonitor->TryGetSummaryForChange(ChangeNumber, Review)) { if (Review->LastStarReview.IsValid() && Review->LastStarReview->Type == UGSCore::EEventType::Starred) { return true; } if (Review->Verdict == UGSCore::EReviewVerdict::Good || Review->Verdict == UGSCore::EReviewVerdict::Mixed) { return true; } } return false; } void UGSTab::UpdateGameTabBuildList() { TArray> Changes = PerforceMonitor->GetChanges(); TArray> ChangeInfos; // do we need to store these for something? TSet PromotedChangeNumbers = PerforceMonitor->GetPromotedChangeNumbers(); TArray ExcludeChanges; TSharedPtr ProjectConfigFile = Workspace->GetProjectConfigFile(); if (ProjectConfigFile.IsValid()) { ProjectConfigFile->TryGetValues(TEXT("Options.ExcludeChanges"), ExcludeChanges); } bool bFirstChange = true; bool bOnlyShowReviewed = false; // bool bOnlyShowReviewed = OnlyShowReviewedCheckBox.Checked; for (int ChangeIdx = 0; ChangeIdx < Changes.Num(); ChangeIdx++) { const UGSCore::FPerforceChangeSummary& Change = Changes[ChangeIdx].Get(); if (ShouldShowChange(Change, ExcludeChanges) || PromotedChangeNumbers.Contains(Change.Number)) { if (!bOnlyShowReviewed || (!EventMonitor->IsUnderInvestigation(Change.Number) && (ShouldIncludeInReviewedList(PromotedChangeNumbers, Change.Number) || bFirstChange))) { bFirstChange = false; FDateTime DisplayTime = Change.Date; if (UserSettings->bShowLocalTimes) { DisplayTime = (DisplayTime - DetectSettings->ServerTimeZone).GetDate(); } TSharedPtr Review; UGSCore::EReviewVerdict Status = UGSCore::EReviewVerdict::Unknown; if (EventMonitor->TryGetSummaryForChange(Change.Number, Review) && Review->LastStarReview.IsValid()) { if (Review->LastStarReview->Type == UGSCore::EEventType::Starred) { Status = UGSCore::EReviewVerdict::Good; } else { Status = Review->Verdict; } } bool bHasZippedBinaries = false; FString Temp; if (PerforceMonitor->TryGetArchivePathForChangeNumber(Change.Number, Temp)) { bHasZippedBinaries = true; } UGSCore::FChangeType ChangeType; PerforceMonitor->TryGetChangeType(Change.Number, ChangeType); if (ChangeIdx == 0 || Changes[ChangeIdx - 1]->Date.GetDayOfYear() != Changes[ChangeIdx]->Date.GetDayOfYear()) { TSharedPtr HeaderRow = MakeShareable(new FChangeInfo); HeaderRow->Time = DisplayTime; HeaderRow->bHeaderRow = true; ChangeInfos.Add(HeaderRow); } bool bCurrentSyncedChange = Change.Number == WorkspaceSettings->CurrentChangeNumber; ChangeInfos.Add(MakeShareable(new FChangeInfo { DisplayTime, false, Status, Change.Number, bCurrentSyncedChange, ShouldSyncPrecompiledEditor(), bHasZippedBinaries, ChangeType, FText::FromString(FormatUserName(Change.User)), Change.Description })); } } } GameSyncTabView->AddHordeBuilds(ChangeInfos); } bool UGSTab::IsSyncing() const { return Workspace->IsBusy(); } FString UGSTab::GetSyncProgress() const { TTuple CurrentProgress = Workspace->GetCurrentProgress(); if (CurrentProgress.Value > 0.0f) { return FString::Printf(TEXT("%s %.2f%%"), *CurrentProgress.Key, CurrentProgress.Value * 100.0f); } return CurrentProgress.Key; } TArray UGSTab::GetSyncCategories(SyncCategoryType CategoryType) const { TArray SyncCategories; TMap Categories = Workspace->GetSyncCategories(); TArray SyncExcludedCategories; if (CategoryType == SyncCategoryType::CurrentWorkspace) { SyncExcludedCategories = WorkspaceSettings->SyncExcludedCategories; } else { SyncExcludedCategories = UserSettings->SyncExcludedCategories; } for (const FGuid& Guid : SyncExcludedCategories) { Categories[Guid].bEnable = false; } Categories.GenerateValueArray(SyncCategories); return SyncCategories; } TArray UGSTab::GetSyncViews(SyncCategoryType CategoryType) const { if (CategoryType == SyncCategoryType::CurrentWorkspace) { return WorkspaceSettings->SyncView; } return UserSettings->SyncView; } const TArray& UGSTab::GetCombinedSyncFilter() const { return CombinedSyncFilter; } UGSTabManager* UGSTab::GetTabManager() const { return TabManager; } TSharedPtr UGSTab::GetUserSettings() const { return UserSettings; } // This is getting called on a thread, lock our stuff up void UGSTab::OnWorkspaceSyncComplete(TSharedRef WorkspaceContext, UGSCore::EWorkspaceUpdateResult SyncResult, const FString& StatusMessage) { FScopeLock Lock(&CriticalSection); if (SyncResult == UGSCore::EWorkspaceUpdateResult::Success) { WorkspaceSettings->CurrentChangeNumber = Workspace->GetCurrentChangeNumber(); WorkspaceSettings->LastBuiltChangeNumber = Workspace->GetLastBuiltChangeNumber(); // Queue up setting the changelist text on the main thread QueueMessageForMainThread([this] { GameSyncTabView->SetChangelistText(WorkspaceSettings->CurrentChangeNumber); }); // TODO hacky, but allows us to highlight which CL we have synced to in the list bNeedUpdateGameTabBuildList = true; } WorkspaceSettings->LastSyncResult = SyncResult; WorkspaceSettings->LastSyncResultMessage = StatusMessage; WorkspaceSettings->LastSyncTime = WorkspaceContext->StartTime; // TODO check this is valid, may be off WorkspaceSettings->LastSyncDurationSeconds = (FDateTime::UtcNow() - WorkspaceContext->StartTime).GetSeconds(); UserSettings->Save(); } bool UGSTab::SetupWorkspace() { ProjectFileName = UGSCore::FUtility::GetPathWithCorrectCase(ProjectFileName); // TODO likely should also log this on an Empty tab... so we can show logging info when we are loading things DetectSettings = MakeShared(PerforceClient.ToSharedRef(), ProjectFileName, MakeShared()); TSharedRef Result = ExecuteModalTask( FSlateApplication::Get().GetActiveModalWindow(), DetectSettings.ToSharedRef(), LOCTEXT("OpeningProjectTitle", "Opening Project"), LOCTEXT("OpeningProjectCaption", "Opening project, please wait...")); if (Result->Failed()) { FMessageDialog::Open(EAppMsgType::Ok, Result->GetMessage()); return false; } PerforceClient = DetectSettings->PerforceClient; WorkspaceSettings = UserSettings->FindOrAddWorkspace(*DetectSettings->BranchClientPath); ProjectSettings = UserSettings->FindOrAddProject(*DetectSettings->NewSelectedClientFileName); // Check if the project we've got open in this workspace is the one we're actually synced to int CurrentChangeNumber = -1; if (WorkspaceSettings->CurrentProjectIdentifier == DetectSettings->NewSelectedProjectIdentifier) { CurrentChangeNumber = WorkspaceSettings->CurrentChangeNumber; } FString ClientKey = DetectSettings->BranchClientPath.Replace(*FString::Printf(TEXT("//%s/"), *PerforceClient->ClientName), TEXT("")); if (ClientKey.EndsWith(TEXT("/"))) { ClientKey = ClientKey.Left(ClientKey.Len() - 1); } FString DataFolder = FString(FPlatformProcess::UserSettingsDir()) / TEXT("UnrealGameSync"); FString ProjectLogBaseName = DataFolder / FString::Printf(TEXT("%s@%s"), *PerforceClient->ClientName, *ClientKey.Replace(TEXT("/"), TEXT("$"))); FString TelemetryProjectIdentifier = UGSCore::FPerforceUtils::GetClientOrDepotDirectoryName(*DetectSettings->NewSelectedProjectIdentifier); FString SelectedProjectIdentifier = DetectSettings->NewSelectedProjectIdentifier; FString LogFileName = DataFolder / FPaths::GetPath(ProjectFileName) + TEXT(".sync.log"); GameSyncTabView->SetSyncLogLocation(LogFileName); // Todo: if SetSyncLogLocation fails, then it failed to create a log file and may need to handle that GameSyncTabView->GetSyncLog()->AppendLine(TEXT("Creating log at: ") + LogFileName); Log = MakeShared(GameSyncTabView->GetSyncLog().ToSharedRef()); Workspace = MakeShared( PerforceClient.ToSharedRef(), DetectSettings->BranchDirectoryName, ProjectFileName, DetectSettings->BranchClientPath, DetectSettings->NewSelectedClientFileName, SelectedProjectIdentifier, CurrentChangeNumber, WorkspaceSettings->LastBuiltChangeNumber, TelemetryProjectIdentifier, Log.ToSharedRef()); Workspace->OnUpdateComplete = [this] (TSharedRef WorkspaceContext, UGSCore::EWorkspaceUpdateResult SyncResult, const FString& StatusMessage) { OnWorkspaceSyncComplete(WorkspaceContext, SyncResult, StatusMessage); }; CombinedSyncFilter = UGSCore::FUserSettings::GetCombinedSyncFilter( Workspace->GetSyncCategories(), UserSettings->SyncView, UserSettings->SyncExcludedCategories, WorkspaceSettings->SyncView, WorkspaceSettings->SyncExcludedCategories); // Setup our Perforce and Event monitoring threads FString BranchClientPath = DetectSettings->BranchDirectoryName; FString SelectedClientFileName = DetectSettings->NewSelectedClientFileName; // TODO create callback functions that will be queued for the main thread to generate and update the main table view PerforceMonitor = MakeShared(PerforceClient.ToSharedRef(), BranchClientPath, SelectedClientFileName, SelectedProjectIdentifier, ProjectLogBaseName + ".p4.log"); PerforceMonitor->OnUpdate = [this]{ QueueMessageForMainThread([this] { UpdateGameTabBuildList(); }); }; //PerforceMonitor->OnUpdateMetadata = [this]{ QueueMessageForMainThread([this] { UpdateGameTabBuildList(); }); }; //MessageQueue.Add([this]{ UpdateBuildMetadata(); }); }; PerforceMonitor->OnChangeTypeQueryFinished = [this]{ QueueMessageForMainThread([this] { UpdateGameTabBuildList(); }); }; PerforceMonitor->OnStreamChange = [this]{ printf("PerforceMonitor->OnStreamChange\n"); }; // MessageQueue.Add([this]{ Owner->StreamChanged(this); }); }; /* TODO figure out if this is even working, and if so how to correctly use this */ FString SqlConnectionString; EventMonitor = MakeShared(SqlConnectionString, UGSCore::FPerforceUtils::GetClientOrDepotDirectoryName(*SelectedProjectIdentifier), PerforceClient->UserName, ProjectLogBaseName + ".review.log"); EventMonitor->OnUpdatesReady = [this]{ printf("EventMonitor->OnUpdatesReady\n"); }; //MessageQueue.Add([this]{ UpdateReviews(); }); }; // Start the threads PerforceMonitor->Start(); EventMonitor->Start(); return true; } #undef LOCTEXT_NAMESPACE