docs

Custom Threads

Start library threads through an engine thread API to set names, priorities, and affinity.

On this page

You can start Ultralight's background threads through an engine's thread API by implementing ThreadFactory. This lets you name threads, set priorities, configure CPU affinity, and track background workers in an engine profiler. The factory is available in every edition, but it's optional— most applications don't need one.

The Default Thread Factory

AppCore doesn't install a thread factory. Without one, App::Create() and Renderer::Create() start threads directly using _beginthreadex() on Windows or pthread_create() on POSIX.

Setting a custom factory on Platform before creating the App or Renderer overrides this default. For setup details, see Setting Up the Platform.

Threads the Factory Covers

The factory creates the threads that run page content. This includes script workers, the JavaScript compiler, garbage collection, networking, and resource loading.

📘 Threads Started Outside the Factory

The factory does not manage every thread in the process. Media playback, some background rendering tasks, and linked third-party libraries start their own threads directly.

Implementing CreateThread()

Subclass ThreadFactory and override CreateThread(). Each call has one job— start the requested thread and report its identifiers back to the library.

Starting the Thread

Start a thread that executes entry_point(entry_point_data).

Return true if the thread started successfully, or false if creation failed.

Using Thread Name and Type

The name parameter provides a label for the thread, which can be nullptr.

The type parameter indicates the thread's role, such as ThreadType::JavaScript, ThreadType::Network, or ThreadType::GarbageCollection. You can use this hint to set thread priority, assign CPU affinity, or tag threads in a profiler.

Reporting the ID and Handle

Before returning from CreateThread(), fill result with the thread's identifiers.

Reporting on Windows

Set result.id to the thread's numeric identifier— either the ID written by _beginthreadex(), or what GetCurrentThreadId() returns inside the thread.

Set result.handle to the HANDLE returned by _beginthreadex().

Reporting on POSIX

Set result.id to any unique integer you choose.

Set result.handle to the thread's pthread_t handle.

Registering the Factory

To register the factory, pass an instance to Platform::instance().set_thread_factory() before creating the Renderer:

C++
#include <Ultralight/Ultralight.h>

using namespace ultralight;

class MyThreadFactory : public ThreadFactory {
 public:
  bool CreateThread(const char* name, ThreadType type,
                    ThreadEntryPoint entry_point, void* entry_point_data,
                    CreateThreadResult& result) override {
    ///
    /// Start a thread that calls entry_point(entry_point_data), naming it after
    /// name and picking a priority from type.
    ///
    // Pseudo-code, spawn the thread through your engine's thread API here.

    ///
    /// Report the id and handle the library will use to refer to it later.
    ///
    result.id = 0;      // Pseudo-code, the id (what GetCurrentThreadId()
                        // reports on Windows).
    result.handle = 0;  // Pseudo-code, the OS handle (HANDLE / pthread_t).
    return true;
  }
};

MyThreadFactory my_thread_factory;

void InitThreads() {
  ///
  /// Install the factory before creating the Renderer.
  ///
  Platform::instance().set_thread_factory(&my_thread_factory);
}

For handler lifetime requirements and registration timing, see Setting Up the Platform.