Preloaded Resources
Every HTML page depends on 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 storage 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.
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
PreloadAndCacheStylesheetAPI 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 storage 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.
Preloading fonts
Section titled “Preloading fonts”While standard CSS @font-face is recommended for general use, you can explicitly preload fonts via the System::RegisterFont API.
When executed successfully before a page loads, this API provides major performance and utility benefits:
- Support for Font Collections: It supports loading all fonts from a
.ttcor.otccollection at once, provided you do not supply a specific font description. If a description is provided, it behaves like@font-faceand only loads the first font in the collection. - First-Frame Readiness: By parsing the font description in advance, it ensures the font is ready to render on the very first frame without falling back to a default font, provided the preload completes before the page initializes.
- Eliminating IO operation spikes: By buffering the font entirely into memory, you can completely eliminate synchronous disk reads when new glyphs are rendered, preventing frame time spikes during gameplay.
This API does not guarantee that the font will be loaded by the time your page first initializes, as explained in Common pitfalls.
To preload custom fonts, you must explicitly register them via System.RegisterFont. Place your font files in the StreamingAssets/Cohtml/UIResources folder (or your custom designated UI folder).
The recommended way to preload fonts is by subscribing to the OnNativeSystemCreated event of your CohtmlUISystem component. You must subscribe to this event during Unity’s Awake phase to ensure you do not miss the initialization trigger. Alternatively, you can register the font at runtime after the page has loaded, and Prysm will load and replace it dynamically.
You can see a complete implementation of font registration in the Unity3D Preloading Example.
Preloading CSS
Section titled “Preloading CSS”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.
You can see a complete implementation of CSS caching in the Unity3D Preloading Example.
This API does not guarantee that the CSS will be loaded by the time your page first initializes, as explained in Common pitfalls.
Preloading HTML
Section titled “Preloading HTML”You can preload HTML via the cohtml::System::PreloadAndCacheHTML API.
Passing a URL to this API will trigger a corresponding OnResourceRequest callback to load a valid HTML document, asynchronously parse it, and then cache it inside Cohtml. The parsed HTML will stay cached in Cohtml’s memory and will be used by all views if it’s requested.
The cache uses a URL as an identifier for preloaded HTML documents and ignores query parameters. When preloading a URL with query parameters, the HTML document from the response will be cached under a URL without the query parameters. When a URL with query parameters is requested from the cache, the cache will serve the cached HTML document that matches the URL without the query parameters.
RemoveHTMLCacheEntry and ClearHTMLCache can be used to clear cache entries from the HTML cache. Note that removing a HTML from a 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.
You can see a complete implementation of HTML caching in the Unity3D Preloading Example.
Most commonly preloaded HTML content will be used for accelerated loading of pages in a View using the View::LoadURL API. The HTML cache is also accessible via XmlHTTPRequest (XHR) in JavaScript and is effective for loading partial HTML content on the page. This can be achieved by sending an XHR with the preloaded URL and using its unmodified response for inserting content with DOM APIs such as innerHTML or insertAdjacentHTML. Modifying the response will invalidate the cache and will trigger HTML parsing.
This API does not guarantee that the HTML will be loaded by the time of your first usage, as explained in Common pitfalls.
Preloading JavaScript
Section titled “Preloading JavaScript”You can preload JavaScript by compiling it ahead of time and providing the result of the compilation when responding to the resource request. This significantly reduces the runtime overhead associated with initial JavaScript loading and enables faster UI load times. Responding to requests for JS resources with optimized data makes the first compile and run of said JavaScript roughly three times faster. The optimized data can easily be serialized and deserialized, allowing you to implement a disk cache for your JS files.
To achieve this, use the ScriptCompiler’s Compile API to generate an optimized data buffer corresponding to your .js files in advance. You can even do this before/during packaging. Later on, when a .js file is requested - use the optimized data buffer to respond to the request using ReceiveOptimizedData.
Implementing a Custom Resource Handler
Section titled “Implementing a Custom Resource Handler”To serve precompiled JavaScript in Unity, you must intercept the resource request using a custom resource handler and manually manage the unmanaged memory buffers for the raw file bytes. Inherit from DefaultResourceHandler and override OnResourceRequest. When the target JS file is requested, copy its raw bytes into the response’s unmanaged memory space and supply the compiled buffer using response.ReceiveOptimizedData().
Compiling the Data Buffer
Section titled “Compiling the Data Buffer”To compile the script, read the raw file bytes, copy them into unmanaged memory using Marshal.AllocHGlobal, and pass them to the ScriptCompiler. Ensure the Cohtml system is fully initialized before attempting to create the compiler and loading your view.
Memory Management
Section titled “Memory Management”It is mandatory to release the memory of the loaded marshaled data on application quit or when it is no longer needed to prevent severe memory leaks. You must explicitly call Release() on the original DataBuffer containing the raw unmanaged bytes, and the OptimizedDataBuffer generated by the compiler. Calling Release() will trigger your custom destruction callback (DestroyBufferCallback), which safely invokes Marshal.FreeHGlobal to clean up the allocated pointer.
You can see the full implementation of the custom handler, compilation logic, and memory management in the Unity3D Preloading Example.
Preloading Images
Section titled “Preloading Images”Preloading image resources is a fairly complicated process, which rightfully deserves its own page.
Common pitfalls
Section titled “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:
m_System.SystemNative.PreloadAndCacheStylesheet("SomeFolder/common.css"); // stylesheet will start pre-loading hereviewComponent = AddComponent<CohtmlView>().Page = "coui://UIResources/index.html"; // let's assume this page uses common.css// later on, during Unity's Update() loop, the System and View automatically advancesIn the example above, when your code reaches the View’s Advance call, 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.
If fonts, CSS, JS and HTML resources are not preloaded, they will be requested on page load and can slow down the FinishLoad event. That is why you should preload them first and as early as possible. One good practice, for example, would be preloading such files before doing any image or raw file preloading.
Unity3D Preloading Example
Section titled “Unity3D Preloading Example”The following snippet demonstrates a comprehensive approach to preloading resources in Unity. It includes using the OnNativeSystemCreated event for font registration, HTML/CSS caching, and JavaScript preloading using a custom resource handler and unmanaged memory.
public class DataBufferResourceHandler : DefaultResourceHandler{ public DataBuffer OptimizedDataBuffer; public byte[] RawJSBytes;
public override void OnResourceRequest(IResourceRequest request, IResourceResponse response) { // Intercept the request for the specific JS file. if (!request.GetURL().Contains("scripts.js") || RawJSBytes == null) { base.OnResourceRequest(request, response); return; }
// Copy the raw JS file int rawJSFileSize = RawJSBytes.Length; IntPtr data = response.GetSpace((ulong)rawJSFileSize); Marshal.Copy(RawJSBytes, 0, data, rawJSFileSize);
// Serve the optimized data buffer if (OptimizedDataBuffer != null) response.ReceiveOptimizedData(OptimizedDataBuffer);
response.Finish(ResourceResponse.Status.Success); }}
public class PreloadedResources : MonoBehaviour{ private CohtmlUISystem System; private DataBuffer m_DataBuffer; // Track raw buffer for cleanup
// Construct a safe, cross-platform path to your resources private static string ResourcesUrl = $"{DefaultResourceHandler.CouiProtocol}{DefaultResourceHandler.UIResourcesHost}"; private string ResourcesPath = $"{Application.streamingAssetsPath}/{DefaultResourceHandler.Cohtml}{DefaultResourceHandler.UIResourcesHost}";
private void Start() { // 1. Create the System and attach the DataBufferResourceHandler System = CohtmlUISystem.GetUISystem(true); System.Settings.OnResourceHandlerAssign.AddListener(() => System.Settings.ResourceHandler = new DataBufferResourceHandler());
// 2. Subscribe to the event to preload the resource the exact moment the native system is ready System.OnNativeSystemCreated += _ => Preload(); }
private void Preload() { // Preload HTML/CSS files System.SystemNative.PreloadAndCacheHTML($"{ResourcesUrl}/index.html"); System.SystemNative.PreloadAndCacheStylesheet($"{ResourcesUrl}/style.css");
// Preload Fonts System.SystemNative.RegisterFont($"{ResourcesUrl}/font.ttf");
// Preload JS DataBufferResourceHandler dataBufferHandler = System.Settings.ResourceHandler as DataBufferResourceHandler; dataBufferHandler.RawJSBytes = File.ReadAllBytes($"{ResourcesPath}/scripts.js"); int rawJSFileSize = dataBufferHandler.RawJSBytes.Length; IntPtr jsBufferPtr = Marshal.AllocHGlobal(rawJSFileSize); Marshal.Copy(dataBufferHandler.RawJSBytes, 0, jsBufferPtr, rawJSFileSize);
// Create the DataBuffer and bind the destruction callback m_DataBuffer = DataBuffer.CreateDataBuffer(jsBufferPtr, (uint)rawJSFileSize, userData: IntPtr.Zero, callback: (data, userdata) => { if (data != IntPtr.Zero) Marshal.FreeHGlobal(data); });
using (ScriptCompiler scriptCompiler = cohtml.Library.Instance.CreateScriptCompiler()) { dataBufferHandler.OptimizedDataBuffer = scriptCompiler.Compile(m_DataBuffer, ScriptCompiler.ScriptType.ST_Classic); }
gameObject.AddComponent<CohtmlView>().Page = $"{ResourcesUrl}/index.html"; }
private void OnDestroy() { // Unregister the font System.SystemNative.UnregisterFont($"{ResourcesUrl}/font.ttf");
// Clear HTML/CSS cache System.SystemNative.ClearHTMLCache(); System.SystemNative.RemoveHTMLCacheEntry($"{ResourcesUrl}/index.html");
System.SystemNative.ClearStylesheetCache(); System.SystemNative.RemoveStylesheetCacheEntry($"{ResourcesUrl}/style.css");
// Release JavaScript DataBuffers m_DataBuffer?.Release();
if (System != null && System.Settings.ResourceHandler is DataBufferResourceHandler handler) { handler.OptimizedDataBuffer?.Release(); } }}© 2026 Coherent Labs. All rights reserved.