Threads

How parallel script execution works in TapForge

spawnThread starts a function on a separate thread. The calling script continues immediately — spawnThread doesn't wait for the function to finish. Each spawned thread runs in its own independent JavaScript environment.

What threads share — and what they don't

Each thread gets its own isolated JS context. Regular variables from the main script (let, var, const) are not visible in a spawned thread. Top-level function declarations are the exception — they are text-copied into the thread's environment before it starts, so helper functions you define at the top of your script work inside threads.

The only thread-safe shared state is setGlobalVar and getGlobalVar. These are backed by a synchronized map shared across all threads and the main script. Use them to pass signals and values between threads.

// Top-level helpers are available in threads
function formatCount(n) { return n + " items"; }

spawnThread(function() {
  print(formatCount(10));     // works — top-level function was copied in
  print(typeof someMainVar);  // "undefined" — regular vars are not shared
});

Thread lifecycle and stopping

isStopped() works inside a spawned thread exactly like in the main script — it returns true when the user presses Stop, when any script calls exit(), or when the main script finishes. Always check it in loops so threads don't outlive the macro run.

When the main script finishes, all spawned threads are interrupted and TapForge waits up to three seconds per thread for them to exit. A thread that calls wait() or checks isStopped() regularly will respond within a fraction of a second. A thread stuck in a tight loop without any check may be abandoned after the timeout.

spawnThread(function() {
  while (!isStopped()) {  // exits cleanly when the macro stops
    var count = getGlobalVar("item_count") || 0;
    if (count > 50) {
      setGlobalVar("bag_full", true);
    }
    wait(500);
  }
});

Errors in spawned threads

If a spawned thread throws an error, it is logged but does not stop the macro or notify the main script — the main script keeps running. If a thread calls exit(), it stops the entire runner, including the main script and all other threads.

Performance: what actually runs in parallel

Physical inputs — tapAt, swipe, and the actual tapping part of findAndTap — must route through Android's main thread and execute sequentially. If multiple threads try to tap at the exact same time, they form a queue and execute one after the other. Spawning more threads doesn't increase how many taps per second the device can perform.

However, visual processing is heavily parallelized. Functions like getPosition, findColor, readColor, ocr, and the visual search phase of findAndTap immediately offload their heavy processing (like OpenCV template matching) to a background thread pool. If you spawn multiple threads that all call visual functions simultaneously, they will process their screenshots at the exact same time across different CPU cores.

The practical use case for threads is parallel logic, not parallel input: a background thread monitors the screen for an event (a popup, a resource icon appearing, a danger indicator) while the main script runs its normal loop, and signals the main script via setGlobalVar when something is detected.

Example: background popup monitor

// Background thread watches for an error popup
spawnThread(function() {
  while (!isStopped()) {
    if (getPosition("error_popup")) {
      setGlobalVar("popup", true);
    }
    wait(800);
  }
});

// Main loop: farm normally, handle popup when signalled
while (!isStopped()) {
  if (getGlobalVar("popup")) {
    findAndTap("dismiss_button");
    setGlobalVar("popup", false);
    wait(500);
    continue;
  }
  findAndTap("collect_resource");
  wait(2000);
}