Announcement

Collapse
No announcement yet.

New Core Feature: Async Framework (Master Branch and 4.8)

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • replied
    Originally posted by XADIFY View Post
    I'm a newbie to threads and such stuff ... I was profiling my 1st game and ended up here while trying to optimize my game performance. But i'm interested in this topic. Where should I look for basic info about threads, async event n stuff. I'm using pure BP for now but this info won't be useless for me later.
    Hi,

    this works for me:

    http://orfeasel.com/implementing-multithreading-in-ue4/

    Leave a comment:


  • replied
    I'm a newbie to threads and such stuff ... I was profiling my 1st game and ended up here while trying to optimize my game performance. But i'm interested in this topic. Where should I look for basic info about threads, async event n stuff. I'm using pure BP for now but this info won't be useless for me later.

    Leave a comment:


  • replied
    Originally posted by duke22 View Post
    Is there any update on how continuations are going?
    No progress yet, sorry. I have no ETA at the moment. I doubt that I'll get to this before December. Either way, 4.14 is already branched, and 4.15 is not going to be until February anyway.

    Leave a comment:


  • replied
    Is there any update on how continuations are going?

    Leave a comment:


  • replied
    Originally posted by duke22 View Post
    I don't suppose you could whip up an example of adapting this for use with IHttpRequest, could you? At the moment I have this horrible nonsense:
    Request is already Async. If you look into the source code in OSS implementation for example, you will see that you can bind OnProcessRequestComplete delegate.
    So no need for a while loop... and Async method.

    Leave a comment:


  • replied
    I don't suppose you could whip up an example of adapting this for use with IHttpRequest, could you? At the moment I have this horrible nonsense:

    Code:
    TFuture<FXmlFile*> FOsmClient::GetCapabilities()
    {
    	return Async<FXmlFile*>(EAsyncExecution::Thread, [&]() -> FXmlFile* {
    		TSharedRef<IHttpRequest> Request = HttpModule->CreateRequest();
    		Request->SetURL("http//api.openstreetmaps.org/api/capabilities");
    		Request->SetVerb("GET");
    		Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent");
    		Request->SetHeader(TEXT("Content-Type"), TEXT("application/xml"));
    		Request->ProcessRequest();
    
    		FHttpResponsePtr Response;
    		while (!(Response = Request->GetResponse()).IsValid()) { }
    
    		const FString Str = Response->GetContentAsString();
    		FXmlFile* Content = new FXmlFile(Str, EConstructMethod::ConstructFromBuffer);
    		return Content;
    	});
    }
    Last edited by duke22; 10-12-2016, 12:59 AM.

    Leave a comment:


  • replied
    Originally posted by muchcharles View Post
    Any way we could get an option in when using EAsyncExecution::TaskGraph to optionally specify a named thread?
    So, in 4.13 there will be the AsyncTask function, which does exactly what you want. That being said, I'm not too happy with the name (should probably be ExecuteOnThread), and there are some concerns about wrapping the task graph system in this way. You can start using it, but it needs a little more thought, and it may change in the near future.

    What we really want is an API for continuations similar to what is possible in .NET and some JavaScript frameworks (DoThis(..).ThenThat(..).AndThis(..).OrThat(..) - you get the idea). It is not yet clear how this would be implemented. The CompletionCallback on Async and the new AsyncThread is also a bit of a hack and not all that useful. I need to find some time to think about this some more and provide a cleaner and more useful API for chaining multiple tasks. Consider the current features as a stepping stone in that direction.

    Leave a comment:


  • replied
    Specify task graph thread?

    Any way we could get an option in when using EAsyncExecution::TaskGraph to optionally specify a named thread? That way we could send tasks that are only safe to run on the game thread, to the game thread.

    Just to enable things like this without as much boilerplate: https://answers.unrealengine.com/que...onentupda.html

    Leave a comment:


  • replied
    Originally posted by n00854180t View Post
    @HateDread - would you mind posting a complete example for future reference? Thanks bud Glad you got it all working!
    Sure!

    Note: I've changed the names of many of my classes to conceal the purpose of this code, and I may have accidently chosen some that are already in use. They're generic and stupid, so please pick better ones for your own code! Also, this example is a little too boilerplate - it's how I did it, not how you /could/ do it in a general sense, so don't feel the need to copy this approach directly (wrapper structs etc).

    -----------------

    I have a struct that wraps my callback (it has some data that the worker uses, for now let's assume that the in-data is a TArray of FStrings.

    Code:
    struct FAsyncDataRequest
    {
    	FAsyncDataRequest() {}
    
    // callback is set manually, not by constructor (long story)
    	FAsyncDataRequest(TArray<FString> a_meshIDs)
    		: m_meshIDs(a_meshIDs) {}
    
    	TArray<FString> m_meshIDs;
    	FOnAsyncDataLoaded m_callback;
    };
    I pass this in to a worker that inherits from FNonAbandonableTask.

    Code:
    class FAsyncDataWorker : public FNonAbandonableTask
    {
    public:
    	FOnAsyncDataLoaded OnLoadFinishedCallback; // This is the delegate called on the main thread when the data has been loaded.
    		
    public:
    	FAsyncDataWorker ();
    	~FAsyncDataWorker ();
    
    // The owner of this worker pulled the data from the request and passed it in directly
    	FAsyncDataWorker (TArray<FString> a_meshIDs, FString a_dataFilesDir); 
    
    	void DoWork();
    	TStatId GetStatId() const;
    
    private:
    
    // this is the function actually called by the taskgraph system to hop from the worker thread back to the main
    // the data result struct just holds the results of this worker's DoWork (in my case, a TArray of structs).
    	void OnLoadFinished(FAsyncDataResult a_dataLoadResult); 
    
    	TArray<FString> m_meshIDs;
    	FString m_dataFilesDir;
    };
    I also have a quick typedef, since you need to have an FAsyncTask template with your worker class that inherits from FNonAbandonableTask (it doesn't have to inherit, but it's easier using this stub I suppose).
    Code:
    typedef FAsyncTask<FAsyncDataWorker > FAsyncDataTask;

    This is how the actual work begins:
    Code:
    // You can pass arguments into this constructor that are given to the FAsyncDataWorker's constructor
    // since they're using a templated 'pass-through' constructor. Pretend that you're constructing the worker
    // directly
    FAsyncDataTask* asyncTask = new FAsyncDataTask(a_asyncDataRequest.m_meshIDs, m_baseDataPath);
    // I bind the callback that will be executed on the main thread after load
    asyncTask->GetTask().OnLoadFinishedCallback = a_asyncDataRequest.m_callback;
    asyncTask->StartBackgroundTask();
    This will start the FAsyncDataWorker:oWork() function, which essentially does this:

    Code:
    void FAsyncDataWorker:: DoWork()
    {
    
    	FAsyncDataResult loadResult;
    	MyDataLoaderClass dataLoader(m_dataFilesDir);
    // the data loader returns my TArray of structs by reference
    	dataLoader.LoadMyData(m_meshIDs, loadResult.TheTArrayOfStructs);
    
    	FGraphEventRef ref = FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
    		(
    			// works fine, but requires us to specify and create the delegate here, rather than
    			// elsewhere and passing it in.
    			FSimpleDelegateGraphTask::FDelegate::CreateRaw(this, &FAsyncDataWorker::OnLoadFinished, loadResult),
    			TStatId(),
    			nullptr,
    			ENamedThreads::GameThread
    		);
    }
    The delegate created above will be called back on the main thread, and will have the data load result as an argument. This 'OnLoadFinished' is essentially a function internal to our worker. All it does is the following:

    Code:
    void FAsyncDataWorker::OnLoadFinished(FAsyncDataResult a_loadResult)
    {
    		if (!OnLoadFinishedCallback.ExecuteIfBound(a_loadResult))
    	{
    		UE_LOG(LogTemp, Warning, TEXT("Failed to execute delegate when finished loading data in FAsyncDataWorker."));
    	}
    }
    Now my original delegate is execute on the main thread, bound to a class about which this worker does not know, and I get the data I've loaded onto the main thread!

    -----------------

    Remember, I had to change the names here due to it otherwise being obvious what I'm working on. Some of these names might be taken, so pick new ones intelligently I may have screwed them up a little, too, so let me know.

    Leave a comment:


  • replied
    @HateDread - would you mind posting a complete example for future reference? Thanks bud Glad you got it all working!

    Leave a comment:


  • replied
    Scrap that, I get it.

    From my worker's DoWork() function:
    Code:
    FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
    (
    	FSimpleDelegateGraphTask::FDelegate::CreateStatic(&TestFunction,
    m_loadedData),
    	TStatId(),
    	nullptr,
    	ENamedThreads::GameThread
    );
    Where TestFunction returns void and takes in a TArray<MyDataStruct> argument (which is m_loadedData in this case).

    What I can't figure out is how to pass in or bind a delegate from outside of here, i.e. binding/passing a delegate of my own choosing to this worker from outside of it, so that the worker doesn't need a pointer to the object and the function in question (which could be several steps removed by this point).

    I.e.

    MyCoreClass::StartLoading ---- (Delegate bound to MyCoreClass::OnDataLoaded) ----> MyDataLoader::LoadData -> MyWorkerInstance.BindDelegateSomehow(//use the passed-in delegate).
    Then when MyWorkerInstance (of MyWorker type) finishes its DoWork, it invokes the delegate and passes the TArray<MyDataStruct> back to MyCoreClass::OnDataLoaded. You can see why requiring that I have a pointer to a MyCoreClass instance and know of its functions at the point of the DoWork function is a bit of a problem - I don't want everything in this chain to have to know about everything else!

    EDIT:

    After digging, and even trying to create my own DelegateGraphTask class, it seems that the delegate used MUST be of the type given by DECLARE_DELEGATE(FMyDelegateName), yet it can take bindings from functions with one or more arguments? I thought you needed DECLARE_DELEGATE_OneParam(...) etc for that!

    Is there at least a way to get my own delegate type (i.e. a OneParam) in where this type is currently? A way to copy the delegate binding across to this accepted type, or to swap out the delegate? I'd really not want to pass a pointer to the original object all the way down the callstack so that the delegate can be bound at the point of task creation. And having my public delegate type be DECLARE_DELEGATE gives users of my code no real indication of the format needed for whatever functions they choose to bind.

    EDIT#2: I'm an idiot.

    The solution to the above was just to execute the passed-in/bound delegate inside the function that I'm binding to the above task. I.e. the OnDataLoaded calls my delegate and passes the worker's output through. All is well/solved
    Last edited by HateDread; 10-14-2015, 10:23 PM.

    Leave a comment:


  • replied
    Originally posted by gmpreussner View Post
    Search for "FSimpleDelegate::Create", for example, to see how we are passing delegates as a parameter to function calls. Another option would be to make the parameter a TFunction, which can accept function pointers and lambdas. It doesn't allow for payloads, but looks cleaner. It really depends on your use case.
    Originally posted by gmpreussner View Post
    We don't have continuations yet, if that's what you mean, but it's on the to-do list.

    In the meantime, you could pass a delegate as a parameter to the async function, which the async function will execute when it is complete. Make sure that your delegate handler is thread-safe.
    Awesome, thanks! I'm passing a TFunction in now (should've thought of that). What do you mean re. 'It doesn't allow for payloads'? Is it that it can only take static functions, and not those called as member functions on an object?

    Additionally, how can I make sure that my FAsyncTask is destroyed? I want to fire it off with the delegate/TFunction bound, let it do its work, then have that delegate/TFunction execute. After that, the task would ideally be cleaned up. I presumed that the task pool/queue would destroy tasks when they've been completed? This doesn't seem to be the case, and obviously calling delete this from inside DoWork is not possible.

    Actually, now that I think of it... how does one make sure that the delegate/TFunction is executed on the main thread? You say to make sure that the delegate handler is thread-safe, but I'm afraid I can't make the connection between that and this.

    Thanks again.

    Leave a comment:


  • replied
    Awesome, thanks again, that should be plenty for what I need!

    Leave a comment:


  • replied
    Search for "FSimpleDelegate::Create", for example, to see how we are passing delegates as a parameter to function calls. Another option would be to make the parameter a TFunction, which can accept function pointers and lambdas. It doesn't allow for payloads, but looks cleaner. It really depends on your use case.

    Leave a comment:


  • replied
    Originally posted by gmpreussner View Post
    We don't have continuations yet, if that's what you mean, but it's on the to-do list.

    In the meantime, you could pass a delegate as a parameter to the async function, which the async function will execute when it is complete. Make sure that your delegate handler is thread-safe.
    Is there somewhere the engine does this we could look at as an example? I get the concept, just haven't really done much with delegates yet.

    Thanks for the help

    Leave a comment:

Working...
X