FiberPool Architecture for Concurrent Script Execution in YimMenuV2

The FiberPool in YimMenuV2 implements a lightweight, fiber-based scheduler using Windows fibers to enable cooperative multitasking of scripts within the game's single-threaded main loop.

The YimMenuV2 mod menu utilizes a sophisticated FiberPool for concurrent script execution to manage multiple scripting tasks without blocking the main thread. This architecture leverages Windows fibers to create a cooperative multitasking environment where scripts can yield execution and resume deterministically. By examining the source files in src/core/backend/, we can see how this deterministic scheduler operates alongside the game's engine.

Singleton-Based Pool Design

At the core of the system lies the FiberPool class, implemented as a Meyers singleton to guarantee global accessibility and single instance semantics. The constructor is private, and copy/move operations are explicitly deleted, forcing access through the static GetInstance() method.

Public initialization and management occur through three static methods defined in FiberPool.hpp:

  • Init – Allocates and initializes the requested number of worker fibers
  • Destroy – Clears pending jobs and prepares for shutdown
  • Push – Thread-safe interface for queuing new script jobs

This design ensures that the pool remains accessible from any context while maintaining strict control over its lifecycle.

Job Storage and Synchronization

The pool maintains pending work using a std::stack<std::function<void()>> named m_Jobs, protected by a std::recursive_mutex called m_Mutex. This combination provides two critical characteristics:

  1. LIFO semantics – Jobs are processed in reverse order of submission, prioritizing newer work
  2. Reentrancy safety – The recursive mutex allows a running job to call Push while the lock is already held, preventing deadlocks when scripts schedule additional work

Any thread can safely enqueue callbacks via FiberPool::Push, making the system suitable for asynchronous event handling from the game's input hooks or network callbacks.

Fiber Lifecycle and ScriptEntry

When FiberPool::Init(num_fibers) executes, it creates the specified number of Script objects via ScriptMgr::AddScript. Each Script instance constructs a Windows fiber using the CreateFiber API, with FiberPool::ScriptEntry serving as the entry point.

The ScriptEntry function implements an infinite loop that drives execution:

while (g_Running) {
    FiberPool::GetInstance().Tick(); // Execute one queued job if available
    ScriptMgr::Yield();               // Return control to the main fiber
}

Each fiber continuously pulls jobs from the pool and then yields back to the main thread, creating the illusion of parallelism within the single-threaded game engine.

Job Dispatch and Execution

The FiberPool::Tick() method handles the actual dispatch of work. It locks the recursive mutex, checks m_Jobs for pending tasks, pops the top entry, unlocks the mutex, and executes the callback via std::invoke.

Crucially, the job runs inside the currently active child fiber. This means any ScriptMgr::Yield() call within the job immediately transfers execution back to the main fiber without blocking other pool fibers. This cooperative yielding prevents script logic from freezing the game while maintaining responsiveness.

Integration with ScriptMgr

The ScriptMgr class coordinates the overall fiber ecosystem. Its TickImpl method iterates over all active Script objects and invokes their Tick methods, which switch execution to the child fiber if the script is not complete.

This architecture creates a clear separation of concerns:

  • FiberPool decides which job executes next
  • ScriptMgr decides when each fiber gets CPU time (once per frame)

The coordination ensures deterministic scheduling where every fiber receives execution time each frame while the pool manages work distribution.

Thread Safety and Reentrancy Considerations

The choice of std::recursive_mutex over a standard mutex addresses a specific reentrancy requirement. Because ScriptEntry executes within a fiber that holds the pool's context, a job that internally calls FiberPool::Push() would deadlock with a non-recursive lock. The recursive implementation allows nested locking scenarios common in callback chains.

Additionally, the stack-based m_Jobs container provides natural prioritization for newer tasks, though the implementation could use std::queue if FIFO ordering were required for specific use cases.

Lifecycle Management

The pool follows a strict three-phase lifecycle:

  1. Initialization – FiberPool::Init(4) creates four worker fibers, sufficient for most menu operations
  2. Operation – Continuous Push calls enqueue work while ScriptMgr::TickImpl drives fiber execution
  3. Shutdown – FiberPool::Destroy() clears the job stack; individual Script objects are cleaned up by ScriptMgr::Destroy()

Code Examples

Initialize the pool during menu startup:

void OnMenuInit()
{
    // Create 4 worker fibers
    YimMenu::FiberPool::Init(4);
}

Queue a simple job that can safely yield:

void ScheduleHelloWorld()
{
    YimMenu::FiberPool::Push([]()
    {
        LOG("Hello from Fiber %d!", GetCurrentFiberId());
        // Work here...
        YimMenu::ScriptMgr::Yield(); // Yield without blocking
    });
}

Clean up during shutdown:

void OnMenuShutdown()
{
    YimMenu::FiberPool::Destroy();
}

Summary

  • Meyers singleton pattern ensures a single globally accessible FiberPool instance with private construction
  • std::stack<std::function<void()>> provides LIFO job storage, protected by a std::recursive_mutex for reentrant safety
  • Windows fibers enable cooperative multitasking through CreateFiber and ScriptEntry loops
  • Deterministic execution occurs via FiberPool::Tick dispatching jobs inside child fibers, allowing safe Yield calls
  • ScriptMgr integration drives fiber scheduling through TickImpl, coordinating pool fibers with the game's main loop
  • Thread-safe Push allows any thread to enqueue work while the fiber loop handles execution

Frequently Asked Questions

What is the FiberPool in YimMenuV2?

The FiberPool is a lightweight concurrency scheduler in YimMenuV2 that manages multiple script execution contexts using Windows fibers. It allows the menu to run numerous scripting tasks concurrently within Grand Theft Auto V's single-threaded environment by implementing cooperative multitasking where scripts yield control back to the main loop.

How does the FiberPool handle thread safety?

The pool uses a std::recursive_mutex to protect its internal std::stack of jobs. This recursive locking mechanism is essential because jobs running inside fibers may call FiberPool::Push to schedule additional work, which would deadlock with a standard mutex. The design allows safe job submission from any thread while the fiber loop executes them.

Why does FiberPool use a stack instead of a queue for jobs?

The stack implementation provides LIFO (Last-In-First-Out) semantics, which prioritizes newer jobs over older ones. This ordering is beneficial for UI responsiveness and recursive operations where the most recent task is typically the highest priority. However, the underlying container could be changed to std::queue if FIFO ordering were required for specific applications.

How does FiberPool integrate with the game's main loop?

The pool integrates through ScriptMgr::TickImpl, which iterates over all active Script objects each frame and switches to their associated fibers. Each fiber runs FiberPool::ScriptEntry, which executes one job from the pool before yielding back to the main fiber. This coordination ensures that script execution happens deterministically within the game's frameupdate cycle without blocking the render thread.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →