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 fibersDestroy– Clears pending jobs and prepares for shutdownPush– 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:
- LIFO semantics – Jobs are processed in reverse order of submission, prioritizing newer work
- Reentrancy safety – The recursive mutex allows a running job to call
Pushwhile 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:
FiberPooldecides which job executes nextScriptMgrdecides 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:
- Initialization –
FiberPool::Init(4)creates four worker fibers, sufficient for most menu operations - Operation – Continuous
Pushcalls enqueue work whileScriptMgr::TickImpldrives fiber execution - Shutdown –
FiberPool::Destroy()clears the job stack; individualScriptobjects are cleaned up byScriptMgr::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
FiberPoolinstance with private construction std::stack<std::function<void()>>provides LIFO job storage, protected by astd::recursive_mutexfor reentrant safety- Windows fibers enable cooperative multitasking through
CreateFiberandScriptEntryloops - Deterministic execution occurs via
FiberPool::Tickdispatching jobs inside child fibers, allowing safeYieldcalls ScriptMgrintegration drives fiber scheduling throughTickImpl, coordinating pool fibers with the game's main loop- Thread-safe
Pushallows 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →