Responding to resource requests asynchronously

Overview

Cohtml sends resource requests immediately as resources are encountered while executing Cohtml work, for example during page parsing or script execution. These requests can occur from multiple threads executing Cohtml work simultaneously.

While your IAsyncResourceHandler can respond to resource requests immediately within the same call stack, doing so for large assets can stall the thread executing the work. To alleviate that, Gameface allows you to respond to requests from any thread at any point in time.

How to use

On a high level, you must:

  1. Implement IAsyncResourceHandler::OnResourceRequest() so that the call schedules the resource response work to happen on a different thread.

  2. Execute the work and respond to the resource by calling IAsyncResourceResponse::Finish().

To avoid doing unnecessary work, you can also implement IAsyncResourceHandler::OnAbortResourceRequest() so that you are notified when a resource won’t be used and can drop scheduled resource work for it.

Example

Here is a pseudo code implementation of IAsyncResourceHandler that reads local resources from disk asynchronously.

void MyResourceHandler::OnResourceRequest(const cohtml::IAsyncResourceRequest* request, cohtml::IAsyncResourceResponse* response)
{
    auto requestId = request->GetId();
	myEngine->ScheduleResourceWorkOnAnotherThread(requestId, [myEngine, request, response]() {
        // Get URL of resource and use it to read the file from disk
        auto path = request->GetURL();
        auto fileContents = myEngine->ReadFileFromDisk(path);
        // Fill the body of the response with the contents of the file.
        auto responseBody = response->GetSpace(fileContents.size());
        myEngine->FillResponseBody(responseBody, fileContents);
        // Respond to the request
        response->Finish(cohtml::IAsyncResourceResponse::Success);
    });
}

void MyResourceHandler::OnAbortResourceRequest(unsigned id)
{
    // cohtml::IAsyncResourceResponse::Finish() must be called exactly once, after which it becomes unsafe to call any methods for the response.
    // For that reason, we call finish here only if we successfully aborted the resource work.
    if (myEngine->TryAbortResourceWorkFromAnotherThread(id))
    {
        cohtml::IAsyncResourceResponse* response = myEngine->GetResourceResponseById(id);
        response->Finish(cohtml::IAsyncResourceResponse::Failure);
    }
}

// Before you begin uninitializing Cohtml, stop all in-flight resource requests
void Unitialize()
{
    myEngine->StopAndJoinResourceWorkerThreads();
    // Uninitialize rest of Cohtml
    cohtmlSystem->Destroy();
    // ...
}