Parallel Execution in BHL
May 21, 2026 · View on GitHub
BHL provides powerful parallel execution capabilities through the paral and paral_all blocks. These constructs allow you to run multiple code blocks concurrently within coroutines.
Basic Parallel Execution
The paral Block
The paral block executes its child blocks in parallel. As soon as any one branch finishes, the entire block completes and all remaining branches are stopped:
coro func test() {
paral {
{
yield wait(1000)
trace("1 second") // finishes first — block ends here
}
{
yield wait(5000)
trace("5 seconds") // never reached
}
}
trace("done") // prints after 1 second
}
The paral_all Block
The paral_all block executes all child blocks in parallel and waits for all of them to complete:
coro func test() {
paral_all {
{
// Block 1
yield suspend()
}
{
// Block 2
yield()
DoSomething()
}
}
}
Control Functions
BHL provides three main functions for controlling parallel execution flow: yield(), suspend(), and wait().
yield()
The yield() function pauses the current block's execution until the next frame or update cycle. It's commonly used for frame synchronization in game loops and animations:
coro func UpdateLoop() {
paral {
{
// This block will run once per frame
while(true) {
UpdatePosition()
yield() // Pause until next frame
}
}
{
// This block runs in parallel
while(true) {
UpdateAnimation()
yield() // Sync with frame rate
}
}
}
}
suspend()
The suspend() function completely suspends the current block's execution forever. This is useful for event-driven parallel tasks:
coro func ProcessData() {
paral {
{
// This block will suspend forever
yield suspend()
// Never will be here
ProcessData()
}
{
yield PrepareData()
}
}
}
wait()
The wait() function pauses execution for a specified duration in milliseconds. Perfect for timed sequences and delays:
coro func TimedSequence() {
paral {
{
// Wait for 2000 milliseconds (2 seconds)
yield wait(2000)
ShowMessage("2 seconds passed!")
}
{
// Wait for 500 milliseconds
yield wait(500)
PlaySound()
}
}
}
Combining Control Functions
You can combine these functions for complex timing and control:
coro func ComplexControl() {
paral {
{
while(true) {
yield wait(1000) // Wait one second
if(shouldSuspend) {
yield suspend() // Suspend if needed
}
UpdateState()
yield() // Sync with frame
}
}
}
}
Control Flow
Parallel Block Completion
Parallel blocks have specific completion behavior:
- For
paralblocks:- When any branch completes its execution, the entire parallel block completes
- Other branches are terminated at that point
coro func test() {
paral {
{
yield wait(1000)
trace("Branch 1 done") // This will print
}
{
yield wait(2000) // This branch won't complete
trace("Branch 2 done") // This will never print
}
}
trace("After paral") // Prints after 1 second
}
- For
paral_allblocks:- All branches must complete their execution before the block completes
- The block only finishes when every branch has finished
coro func test() {
paral_all {
{
yield wait(1000)
trace("Branch 1 done") // Prints after 1 second
}
{
yield wait(2000)
trace("Branch 2 done") // Prints after 2 seconds
}
}
trace("After paral_all") // Prints after 2 seconds
}
Nested Parallel Execution
Nested Parallel Blocks
You can nest parallel blocks within each other:
coro func test() {
paral {
{
paral {
yield suspend()
DoSomething()
}
}
{
yield suspend()
}
}
}
Function Calls with Parallel Blocks
Functions can contain parallel blocks and be called from other parallel blocks:
func foo() {
paral {
DoTask1()
}
}
func test() {
paral_all {
foo() // Nested parallel execution
}
}
Coroutines and Parallel Execution
Yielding in Parallel Blocks
Parallel blocks can contain yield statements:
coro func test() {
paral {
{
yield() // Yields execution
DoTask1()
}
{
yield suspend() // Suspends execution
DoTask2()
}
}
}
Automatic Sequence Wrapping
Single statements in parallel blocks are automatically wrapped in sequence blocks:
coro func test() {
paral {
yield suspend() // Automatically wrapped
{
yield()
DoSomething()
}
yield suspend() // Automatically wrapped
}
}
Notes
- Empty
paral/paral_allblocks are a compile error. - Branches share the enclosing function's local variables — writes in one branch are visible in another.
- Branches within a
paralblock execute in order each tick; completion order is therefore deterministic.