You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Please note that file comments had no purpose in nearly all cases and just added visual clutter. The two files that had meaningful file comments had their comments moved into the corresponding classes. There are still hundreds of file comments left in other files that will be removed over time. Also cleaned up some random stuff along the way: - relative paths to public headers within the same module are no longer necessary (automatically discovered by UBT now) - header guards are deprecated, use #pragma once instead (all compilers support it now) - space between multiple template brackets is no longer required (all compilers support >> now) - NULL to nullptr, OVERRIDE to override - spelling errors, whitespace, line breaks [CL 2104067 by Max Preussner in Main branch]
598 lines
16 KiB
C++
598 lines
16 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "TaskBrowserPrivatePCH.h"
|
|
#include "TaskDatabase.h"
|
|
#include "TaskDatabaseThread.h"
|
|
|
|
|
|
#if WITH_TESTTRACK
|
|
#include "TestTrackTaskDatabaseProvider.h"
|
|
#endif // WITH_TESTTRACK
|
|
|
|
DEFINE_LOG_CATEGORY(LogTaskDatabaseDefs);
|
|
|
|
|
|
namespace TaskDatabaseSystem
|
|
{
|
|
|
|
/** Global threaded task database singleton object */
|
|
FTaskDatabaseThreadRunnable* GTaskDatabaseThreadRunnable = NULL;
|
|
|
|
/** The actual task database thread object */
|
|
FRunnableThread* GTaskDatabaseThread = NULL;
|
|
|
|
/** True if a request has been sent and we're waiting for a response */
|
|
bool GIsWaitingForResponse = false;
|
|
|
|
/** True if we're currently connected to the database (as far as we know) */
|
|
bool GIsConnected = false;
|
|
|
|
/** Array of main-thread 'listeners' that registered to hear about completed requests */
|
|
TArray< FTaskDatabaseListener* > GListeners;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Initialize the task database system. This must be called before anything else.
|
|
*
|
|
* @return True if successful
|
|
*/
|
|
bool Init()
|
|
{
|
|
TDLOG( TEXT( "Initializing Task Database" ) );
|
|
|
|
if( GTaskDatabaseThreadRunnable != NULL )
|
|
{
|
|
// Already initialized?
|
|
return false;
|
|
}
|
|
|
|
|
|
// We're not connected yet!
|
|
GIsConnected = false;
|
|
|
|
|
|
|
|
// Create and initialize the task database provider
|
|
FTaskDatabaseProviderInterface* TaskDatabaseProvider = NULL;
|
|
|
|
#if WITH_TESTTRACK
|
|
TaskDatabaseProvider = FTestTrackProvider::CreateTestTrackProvider();
|
|
#endif // WITH_TESTTRACK
|
|
|
|
if( TaskDatabaseProvider == NULL )
|
|
{
|
|
// Failed to initialized task database provider
|
|
return false;
|
|
}
|
|
|
|
// Allocate threaded task database
|
|
GTaskDatabaseThreadRunnable =
|
|
new FTaskDatabaseThreadRunnable( TaskDatabaseProvider );
|
|
check( GTaskDatabaseThreadRunnable != NULL );
|
|
|
|
|
|
// No requests have been issued yet
|
|
GIsWaitingForResponse = false;
|
|
|
|
|
|
|
|
// Select thread priority
|
|
// @todo: Consider using TPri_Low here since task database ops shouldn't require much CPU
|
|
EThreadPriority TaskDatabaseThreadPriority = TPri_Normal;
|
|
|
|
|
|
TDLOG( TEXT( "Starting Task Database Thread" ) );
|
|
|
|
// Launch the task database thread!
|
|
GTaskDatabaseThread = FRunnableThread::Create(
|
|
GTaskDatabaseThreadRunnable, // Runnable module
|
|
TEXT("TaskDatabaseThread"), // Thread name
|
|
0, // Stack size
|
|
TaskDatabaseThreadPriority ); // Thread priority
|
|
|
|
if( GTaskDatabaseThread == NULL )
|
|
{
|
|
// Failed to launch the thread
|
|
delete GTaskDatabaseThreadRunnable;
|
|
GTaskDatabaseThreadRunnable = NULL;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// Suspend the task worker thread until there is actually work to do
|
|
GTaskDatabaseThreadRunnable->bThreadSafeShouldSuspend = true;
|
|
// GTaskDatabaseThread->Suspend( true );
|
|
|
|
TDLOG( TEXT( "Task Database initialized" ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/** Process any responses that are outstanding and fire off callback events */
|
|
void ProcessCompletedRequests()
|
|
{
|
|
if( GIsWaitingForResponse )
|
|
{
|
|
FTaskDatabaseResponse* ResponseToBroadcast = NULL;
|
|
|
|
{
|
|
FScopeLock ScopeLock( >askDatabaseThreadRunnable->CriticalSection );
|
|
|
|
// Any requests pending?
|
|
if( GTaskDatabaseThreadRunnable->ThreadSafeResponse != NULL )
|
|
{
|
|
// There should never be another request already enqueued. We delete and clear the
|
|
// request data as soon as the response is generated.
|
|
check( GTaskDatabaseThreadRunnable->ThreadSafeCurrentRequest == NULL );
|
|
|
|
// OK, we've received a response to our last request!
|
|
ResponseToBroadcast = GTaskDatabaseThreadRunnable->ThreadSafeResponse;
|
|
|
|
// Tell the task database thread it can start working on other tasks now
|
|
GTaskDatabaseThreadRunnable->ThreadSafeResponse = NULL;
|
|
|
|
// No longer waiting for anything
|
|
GIsWaitingForResponse = false;
|
|
}
|
|
}
|
|
|
|
if( ResponseToBroadcast != NULL )
|
|
{
|
|
// We completed our task, so go back to sleep
|
|
GTaskDatabaseThreadRunnable->bThreadSafeShouldSuspend = true;
|
|
// GTaskDatabaseThread->Suspend( true );
|
|
|
|
TDLOG( TEXT( "Broadcasting response for completed request (Type: %i)" ), ( int32 )ResponseToBroadcast->RequestType );
|
|
|
|
|
|
// Peek to see if this is a connection event, and update our status if so
|
|
if( ResponseToBroadcast->RequestType == ETaskDatabaseRequestType::ConnectToDatabase )
|
|
{
|
|
// We assume we're disconnected if a 'connect to database' request fails
|
|
GIsConnected = ResponseToBroadcast->bSucceeded;
|
|
}
|
|
else if( ResponseToBroadcast->RequestType == ETaskDatabaseRequestType::DisconnectFromDatabase )
|
|
{
|
|
// We assume we're no longer connected after completing a disconnect request
|
|
GIsConnected = false;
|
|
}
|
|
|
|
if( ResponseToBroadcast->bFailedBecauseOfDisconnection )
|
|
{
|
|
// We were disconnected unexpectedly!
|
|
GIsConnected = false;
|
|
}
|
|
|
|
// Broadcast callbacks for this response
|
|
for( TArray< FTaskDatabaseListener* >::TIterator ListenerIter( GListeners ); ListenerIter; ++ListenerIter )
|
|
{
|
|
FTaskDatabaseListener* CurListener = *ListenerIter;
|
|
check( CurListener != NULL );
|
|
|
|
CurListener->OnTaskDatabaseRequestCompleted( ResponseToBroadcast );
|
|
}
|
|
|
|
// We're done with the response data now
|
|
delete ResponseToBroadcast;
|
|
ResponseToBroadcast = NULL;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Clean up the task database system. Call this after before exiting the app. */
|
|
void Destroy()
|
|
{
|
|
TDLOG( TEXT( "Destroying Task Database" ) );
|
|
|
|
// Clear listener array
|
|
GListeners.Empty();
|
|
|
|
|
|
|
|
if( IsInitialized() )
|
|
{
|
|
TDLOG( TEXT( "Destroying: Waiting for current task to complete" ) );
|
|
|
|
// We're probably trying to shutdown the editor, so never wait an innappropriate amount of time
|
|
const float MaxTimeToWaitForThread = 5.0f;
|
|
const double TimeStartedWaiting = FPlatformTime::Seconds();
|
|
|
|
|
|
// Wait for the current task to complete
|
|
GTaskDatabaseThreadRunnable->bThreadSafeShouldSuspend = false;
|
|
while( GIsWaitingForResponse && ( FPlatformTime::Seconds() - TimeStartedWaiting ) < MaxTimeToWaitForThread )
|
|
{
|
|
ProcessCompletedRequests();
|
|
FPlatformProcess::Sleep( 0.01f );
|
|
}
|
|
|
|
// Are we connected? If so, we'll log out before shutting down
|
|
if( IsConnected() )
|
|
{
|
|
TDLOG( TEXT( "Destroying: Logging out of database" ) );
|
|
|
|
// Initiate a log out before exiting
|
|
DisconnectFromDatabase_Async();
|
|
|
|
TDLOG( TEXT( "Destroying: Waiting for log out to complete" ) );
|
|
|
|
// Wait for the current task to complete
|
|
while( GIsWaitingForResponse && ( FPlatformTime::Seconds() - TimeStartedWaiting ) < MaxTimeToWaitForThread )
|
|
{
|
|
ProcessCompletedRequests();
|
|
FPlatformProcess::Sleep( 0.01f );
|
|
}
|
|
|
|
TDLOG( TEXT( "Destroying: Ready to destroy" ) );
|
|
}
|
|
}
|
|
|
|
|
|
// Stop the task database
|
|
if( GTaskDatabaseThreadRunnable != NULL )
|
|
{
|
|
GTaskDatabaseThreadRunnable->bThreadSafeShouldSuspend = false;
|
|
GTaskDatabaseThreadRunnable->Stop();
|
|
}
|
|
|
|
// Kill the task database thread
|
|
if( GTaskDatabaseThread != NULL )
|
|
{
|
|
// GTaskDatabaseThread->Suspend( false );
|
|
|
|
TDLOG( TEXT( "Waiting for Task Database thread to complete" ) );
|
|
|
|
// NOTE: We shouldn't really have to wait long here since we already waited earlier
|
|
GTaskDatabaseThread->WaitForCompletion();
|
|
|
|
delete GTaskDatabaseThread;
|
|
|
|
GTaskDatabaseThread = NULL;
|
|
}
|
|
|
|
// Destroy the task database object
|
|
if( GTaskDatabaseThreadRunnable != NULL )
|
|
{
|
|
delete GTaskDatabaseThreadRunnable;
|
|
GTaskDatabaseThreadRunnable = NULL;
|
|
}
|
|
|
|
|
|
GIsConnected = false;
|
|
|
|
|
|
TDLOG( TEXT( "Task Database destroyed" ) );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Returns true if the task database system was initialized successfully
|
|
*
|
|
* @return True if initialized
|
|
*/
|
|
bool IsInitialized()
|
|
{
|
|
return ( GTaskDatabaseThreadRunnable != NULL );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Returns true if we're currently waiting for a response from the server
|
|
*
|
|
* @return True if a request is in progress
|
|
*/
|
|
bool IsRequestInProgress()
|
|
{
|
|
return GIsWaitingForResponse;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Returns true if we're currently connected to a database
|
|
*
|
|
* @return True if currently connected
|
|
*/
|
|
bool IsConnected()
|
|
{
|
|
return GIsConnected;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Kicks off an asynchronous request using the task database thread
|
|
*
|
|
* @param Request The request to start. Assumes ownership of allocated memory.
|
|
*
|
|
* @return True if successful
|
|
*/
|
|
bool StartAsyncRequest( FTaskDatabaseRequest* Request )
|
|
{
|
|
check( Request != NULL );
|
|
|
|
TDLOG( TEXT( "Starting async request (Type: %i)" ), ( int32 )Request->RequestType );
|
|
|
|
// First, process any outstanding request so that we're not holding up the queue
|
|
// @todo: Should we do this here? It's a bit risky because the frontend UI may not be expecting to
|
|
// receive callback events while in a 'start request' stack frame. Maybe just leave this to Update()
|
|
ProcessCompletedRequests();
|
|
|
|
if( GIsWaitingForResponse )
|
|
{
|
|
// Another request is already in progress. Can't start this request yet.
|
|
delete Request;
|
|
Request = NULL;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
{
|
|
FScopeLock ScopeLock( >askDatabaseThreadRunnable->CriticalSection );
|
|
|
|
// Current request should always be NULL when GIsWaitingForResponse is false
|
|
check( GTaskDatabaseThreadRunnable->ThreadSafeCurrentRequest == NULL );
|
|
|
|
// Start the request
|
|
GTaskDatabaseThreadRunnable->ThreadSafeCurrentRequest = Request;
|
|
}
|
|
|
|
// We're waiting for a response now
|
|
GIsWaitingForResponse = true;
|
|
|
|
// Wake the thread up to start working
|
|
GTaskDatabaseThreadRunnable->bThreadSafeShouldSuspend = false;
|
|
// GTaskDatabaseThread->Suspend( false );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/** Updates the task database and checks for any completed requests, firing callbacks if needed. This
|
|
should usually be called with every engine Tick. */
|
|
void Update()
|
|
{
|
|
// Process and requests that may have completed and fire callbacks
|
|
ProcessCompletedRequests();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Adds the specified object as a 'listener' for completed task database requests. Remember to remove
|
|
* the object using UnregisterListener() before it's deleted!
|
|
*
|
|
* @param Listener The object to register. Should not be NULL.
|
|
*/
|
|
void RegisterListener( FTaskDatabaseListener* Listener )
|
|
{
|
|
check( Listener != NULL );
|
|
|
|
TDLOG( TEXT( "Listener registered (Pointer: %i)" ), ( int32 )Listener );
|
|
|
|
GListeners.AddUnique( Listener );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Removes the specified object from the list of 'listeners'
|
|
*
|
|
* @param Listener The object to unregister. Should have been previously registered.
|
|
*/
|
|
void UnregisterListener( FTaskDatabaseListener* Listener )
|
|
{
|
|
check( Listener != NULL )
|
|
|
|
TDLOG( TEXT( "Listener unregistered (Pointer: %i)" ), ( int32 )Listener );
|
|
|
|
GListeners.RemoveSwap( Listener );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Initiates asynchronous query for available task databases
|
|
*
|
|
* @param InServerName Server host name
|
|
* @param InServerPort Server port
|
|
* @param InLoginUserName User name
|
|
* @param InLoginPassword Password
|
|
*
|
|
* @return True if async requested was kicked off successfully
|
|
*/
|
|
bool QueryAvailableDatabases_Async( const FString& InServerName, const uint32 InServerPort, const FString& InLoginUserName, const FString& InLoginPassword )
|
|
{
|
|
if( IsConnected() )
|
|
{
|
|
TDLOG( TEXT( "Async request failed because we're already connected to a server" ) );
|
|
return false;
|
|
}
|
|
|
|
// Build a server URL string (e.g. "http://myservername:80")
|
|
FString ServerURL =
|
|
FString( TEXT( "http://" ) ) +
|
|
InServerName +
|
|
FString( TEXT( ":" ) ) +
|
|
FString( FString::FromInt( InServerPort ) );
|
|
|
|
FTaskDatabaseRequest_QueryAvailableDatabases* Request = new FTaskDatabaseRequest_QueryAvailableDatabases();
|
|
Request->RequestType = ETaskDatabaseRequestType::QueryAvailableDatabases;
|
|
Request->ServerURL = ServerURL;
|
|
Request->LoginUserName = InLoginUserName;
|
|
Request->LoginPassword = InLoginPassword;
|
|
|
|
return StartAsyncRequest( Request );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Asynchronously connects to the task database and attempts to login to a specific database
|
|
*
|
|
* @param InServerName Server host name
|
|
* @param InServerPort Server port
|
|
* @param InLoginUserName User name
|
|
* @param InLoginPassword Password
|
|
* @param InDatabaeName The database to login to
|
|
*
|
|
* @return True if async requested was kicked off successfully
|
|
*/
|
|
bool ConnectToDatabase_Async( const FString& InServerName, const uint32 InServerPort, const FString& InLoginUserName, const FString& InLoginPassword, const FString& InDatabaseName )
|
|
{
|
|
if( IsConnected() )
|
|
{
|
|
TDLOG( TEXT( "Async request failed because we're already connected to a server" ) );
|
|
return false;
|
|
}
|
|
|
|
// Build a server URL string (e.g. "http://myservername:80")
|
|
FString ServerURL =
|
|
FString( TEXT( "http://" ) ) +
|
|
InServerName +
|
|
FString( TEXT( ":" ) ) +
|
|
FString( FString::FromInt( InServerPort ) );
|
|
|
|
FTaskDatabaseRequest_ConnectToDatabase* Request = new FTaskDatabaseRequest_ConnectToDatabase();
|
|
Request->RequestType = ETaskDatabaseRequestType::ConnectToDatabase;
|
|
Request->ServerURL = ServerURL;
|
|
Request->LoginUserName = InLoginUserName;
|
|
Request->LoginPassword = InLoginPassword;
|
|
Request->DatabaseName = InDatabaseName;
|
|
|
|
return StartAsyncRequest( Request );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Disconnects from the database asynchronously
|
|
*
|
|
* @return True if async requested was kicked off successfully
|
|
*/
|
|
bool DisconnectFromDatabase_Async()
|
|
{
|
|
if( !IsConnected() )
|
|
{
|
|
TDLOG( TEXT( "Async request failed because we'not connected to a server" ) );
|
|
return false;
|
|
}
|
|
|
|
FTaskDatabaseRequest* Request = new FTaskDatabaseRequest();
|
|
Request->RequestType = ETaskDatabaseRequestType::DisconnectFromDatabase;
|
|
|
|
return StartAsyncRequest( Request );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Asynchronously queries a list of available database filters
|
|
*
|
|
* @return True if async requested was kicked off successfully
|
|
*/
|
|
bool QueryFilters_Async()
|
|
{
|
|
if( !IsConnected() )
|
|
{
|
|
TDLOG( TEXT( "Async request failed because we'not connected to a server" ) );
|
|
return false;
|
|
}
|
|
|
|
FTaskDatabaseRequest* Request = new FTaskDatabaseRequest();
|
|
Request->RequestType = ETaskDatabaseRequestType::QueryFilters;
|
|
|
|
return StartAsyncRequest( Request );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Asynchronously queries a list of task entries for the specified filter name
|
|
*
|
|
* @param InFilterName Name of the filter (can be empty string)
|
|
*
|
|
* @return True if async requested was kicked off successfully
|
|
*/
|
|
bool QueryTasks_Async( const FString& InFilterName )
|
|
{
|
|
if( !IsConnected() )
|
|
{
|
|
TDLOG( TEXT( "Async request failed because we'not connected to a server" ) );
|
|
return false;
|
|
}
|
|
|
|
FTaskDatabaseRequest_QueryTasks* Request = new FTaskDatabaseRequest_QueryTasks();
|
|
Request->RequestType = ETaskDatabaseRequestType::QueryTasks;
|
|
Request->FilterName = InFilterName;
|
|
|
|
return StartAsyncRequest( Request );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Asynchronously queries details about a single specific task entry
|
|
*
|
|
* @param InTaskNumber The database task number for the task entry
|
|
*
|
|
* @return True if async requested was kicked off successfully
|
|
*/
|
|
bool QueryTaskDetails_Async( const uint32 InTaskNumber )
|
|
{
|
|
if( !IsConnected() )
|
|
{
|
|
TDLOG( TEXT( "Async request failed because we'not connected to a server" ) );
|
|
return false;
|
|
}
|
|
|
|
FTaskDatabaseRequest_QueryTaskDetails* Request = new FTaskDatabaseRequest_QueryTaskDetails();
|
|
Request->RequestType = ETaskDatabaseRequestType::QueryTaskDetails;
|
|
Request->TaskNumber = InTaskNumber;
|
|
|
|
return StartAsyncRequest( Request );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Attempts to asynchronously mark the specified task as completed
|
|
*
|
|
* @param InTaskNumber The database task number to mark complete
|
|
* @param InResolutionData Information about how the task should be resolved
|
|
*
|
|
* @return True if async requested was kicked off successfully
|
|
*/
|
|
bool MarkTaskComplete_Async( const uint32 InTaskNumber, const FTaskResolutionData& InResolutionData )
|
|
{
|
|
if( !IsConnected() )
|
|
{
|
|
TDLOG( TEXT( "Async request failed because we'not connected to a server" ) );
|
|
return false;
|
|
}
|
|
|
|
FTaskDatabaseRequest_MarkTaskComplete* Request = new FTaskDatabaseRequest_MarkTaskComplete();
|
|
Request->RequestType = ETaskDatabaseRequestType::MarkTaskComplete;
|
|
Request->TaskNumber = InTaskNumber;
|
|
Request->ResolutionData = InResolutionData;
|
|
|
|
return StartAsyncRequest( Request );
|
|
}
|
|
|
|
}
|
|
|
|
|