1.26.2.1
Gameface
Preloading resources

Overview

Every HTML page depends on a horde of resources, such as text files (html/js/css/etc.), images and fonts. Unsurprisingly, most of the time spent during every page initialization (and in some rare cases, its continued execution) goes to resource loading and preprocessing. Most resources require some type of preprocessing before they are ready to use - HTML/CSS/JavaScript require parsing, images require decoding, etc. Every resource needs to be read from the hard drive as well. Performing the aforementioned operations before your View starts initializing is referred to as resource preloading. Preloaded resources can give you a huge performance boost in page initialization. You can even achieve single frame initialization if you manage to preload all resources for a given page.

Most resource loading optimizations consist of finding a convenient time to load the resource in advance, for example during a "Loading" screen. There are two main ways to do that with Cohtml views:

  • You can prompt Cohtml to preload and cache some types of resources to avoid processing them each time. For example, using the PreloadAndCacheStylesheet API you can ensure that all of your CSS resources are already parsed and waiting when your page starts loading. Note that Font and CSS resources are cached system-wide, meaning that all Views which share the resource will benefit.
  • You can preload resources in the memory yourself, so you can skip fetching them from the hard drive when they are requested later on. For example, using a simple hash table you can cache the content of your JavaScript text files, ensuring that they are instantly available to Cohtml when requested.

Cohtml resource preloading APIs

You can find example implementations of all APIs listed below in the Gameface Instaload sample

Preloading fonts

You can register fonts in advance via the cohtml::System::RegisterFont API. Calling this API will schedule a stream resource request for the font as a work job. When the work is executed your OnResourceStreamRequest callback will be invoked. Usually, you would respond to this request with an ISyncStreamReader implementation that will read the font from the disk. To preload the font and avoid disk reads at runtime you can read and buffer the whole file inside your ISyncStreamReader implementation and later respond to read requests from that buffer. Fonts can be heavy memory-wise and Cohtml does cache font information for already displayed characters, so if you can't spare the extra memory overhead you can use this only during initialization then release the buffer and continue reading from disk.

This API does not guarantee that the font will be loaded by the time your page first initializes, as explained in Common pitfalls.

Warning
Make sure you are preloading the exact same fonts that you will be using, meaning that the weight, style, and name should be the same. This is especially important when using cohtml::FontDescription with default cohtml::Fonts::FS_Auto / cohtml::Fonts::FW_Auto, because they will auto-resolve values from the provided font resource.

Preloading CSS files

You can preload CSS files via the cohtml::System::PreloadAndCacheStylesheet API. Passing a CSS file to this API will trigger a corresponding OnResourceRequest callback to load the CSS file, parse it, then cache it inside Cohtml. Once loaded, the stylesheet will stay cached in Cohtml's memory, making all future initialization of the same page or other pages that use it faster.

You can use RemoveStylesheetCacheEntry or ClearStylesheetCache to clear one or all pre-loaded stylesheets respectively after you are done using them.

This API does not guarantee that the CSS will be loaded by the time your page first initializes, as explained in Common pitfalls.

Warning
This API does not cover inline CSS inside your HTML code. In those cases your CSS will not be preloaded, even if you preload the HTML page itself, leading to worse performance.

Preloading HTML

You can preload HTML via the cohtml::System::PreloadAndCacheHTML API.

Passing an HTML file to this API will trigger a corresponding OnResourceRequest callback to load the html body, asynchronously parse it, then cache it Cohtml. The parsed HTML will stay cached in Cohtml's memory and will be used by all views if it's requested.

RemoveHTMLCacheEntry and ClearHTMLCache can be used to clear cache entries from the HTML cache. Note that removing an HTML from cache that is currently in use by a view won't free the occupied memory immediately. It will be freed after all Views finish DOM building, even if the HTML is not loaded yet.

This API does not guarantee that the HTML will be loaded by the time your page first initializes, as explained in Common pitfalls.

Warning
This API will not pre-parse any inline CSS inside your HTML page. Using inline css will lead to slower View initialization times, so it's best to move your CSS code to a separate file and preload it via the PreloadAndCacheStylesheet API.

Preloading Images

Preloading image resources is a fairly complicated process, which rightfully deserves its own page.

Common pitfalls

Due to Cohtml's concurrent resource parsing, there is no way to guarantee that your resource will actually be parsed by the time the page starts loading. This is true for HTML, CSS, and Font preloading APIs. Consider the following snippet, which uses CSS as an example:

System->PreloadAndCacheStylesheet("SomeFolder/common.css"); // stylesheet will start pre-loading here
View->LoadURL("my_url.html"); // let's assume this page uses common.css.
// ...
// later on, during your engine's frame execution
View->Advance(time);

In the example above, when your code reaches the View->Advance(time) line, Cohtml might still be loading common.css on another thread. In those types of cases Cohtml will build your page without the resource, then update it on a later Advance call, after the resource is ready. In a similar way, a View won't start building the DOM until the HTML is fully parsed.

Preloading resources in the memory

When no API is provided for a given resource type, you can still preload the resource contents in the memory. In straightforward implementation of this can work as follows:

  • At some point in time before the HTML page is loaded, read the contents of the resource files from the disk and store them in memory. You can find an example implementation of this in the ResourceHandler::AddPreloadedResource(path) function of the instaload sample.
        // Store file contents in memory
        for (auto& it : std::filesystem::recursive_directory_iterator(m_ResourcesRoot))
        {
            auto extention = it.path().extension();
            // JS files are currently not preloadable by Cohtml, so they can be
            // preloaded in memory only.
            if (extention == ".js")
            {
                auto path = it.path().generic_string();
                // Assume ReadFile is a function that fetches the contents of a file in memory
                auto contents = ReadFile(path);
                m_PreloadedResources.Emplace(path, contents);
            }
        }
    
  • Modify your cohtml::IAsyncResourceHandler::OnResourceRequest implementation to check if the contents of the requested file are already loaded in memory. If they are, return them to Cohtml immediately, then signal that the request is finished.
      void OnResourceRequest(const cohtml::IAsyncResourceRequest* request,
                          cohtml::IAsyncResourceResponse* response)
    
          std::string path = GetPathFromRequest(request->GetURL());
          // Check if resource is preloaded
          auto findIt = m_PreloadedResources.find(request->path);
          if (findIt != m_PreloadedResources.end())
          {
              // Assume the function below passes the contents of the file
              // to Cohtml via the IAsyncResourceResponse API.
              PassFileContentsToCohtml(findIt-second, response);
              response->Finish(cohtml::IAsyncResourceResponse::Success);
          }
          else
          {
              // You can read the file from disk here and respond immediately,
              // or start some process that will eventually provide the file later on
          }