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:

  1. For paral blocks:
    • 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
}
  1. For paral_all blocks:
    • 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_all blocks are a compile error.
  • Branches share the enclosing function's local variables — writes in one branch are visible in another.
  • Branches within a paral block execute in order each tick; completion order is therefore deterministic.