# FiberPool Architecture for Concurrent Script Execution in YimMenuV2

> Explore the FiberPool architecture in YimMenuV2, utilizing Windows fibers for efficient, concurrent script execution within the game's single-threaded loop. Optimize your scripting.

- Repository: [YimMenu/YimMenuV2](https://github.com/YimMenu/YimMenuV2)
- Tags: architecture
- Published: 2026-07-16

---

**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`](https://github.com/YimMenu/YimMenuV2/blob/main/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:

```cpp
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:

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

```

Queue a simple job that can safely yield:

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

```

Clean up during shutdown:

```cpp
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.