Background threading - lmparppei/BeatPlugins GitHub Wiki

While running a resident plugin (ie. something with either onTextChange, onSelectionChange, onOutlineChange or htmlWindow), you might want to run more demanding processes in the background. Using background threads requires some minor knowledge on how threading works on macOS.

You can use Beat.dispatch() to run a background worker, and then Beat.dispatch_sync() to return its results to main thread.

  • Beat.async(function) — run a function in a background thread
  • Beat.sync(function) — run a function in the main thread

Note: These methods used to be called Beat.dispatch() and Beat.dispatch_sync(). The old names still work, but will be undocumented.

WARNING: NEVER call anything UI-related from a background thread. Everything that has to do with the actual editor or UI is synchronous. Be sure to fetch any required synchronous data (such as selected range, scenes, lines etc.) before entering a background thread. Never access anything synchronous while processing something in the background, or you might risk crashing the app.

Be careful, dread lightly and test your code thoroughly.

Background Threading Example

This illustrates the basic idea. First, fetch and store the data you are planning to use (scenes, lines etc.). Then perform the heavy duty in a background thread with Beat.dispatch(function), and when done, return the results into main (synchronous) thread using Beat.dispatch_sync(function).

Anything related to editor content (selection ranges, substrings of the text) is considered synchronous, so be careful to actually fetch the data first. Using main thread from a background task can sometimes even cause the app to crash.

// Load any required data and make copies of the arrays
// to make sure they won't be mutated while processing them
const scenes = [...Beat.scenes()]
const lines = [...Beat.lines()]

// Dispatch to a background thread
Beat.dispatch(function () {
    // Do something with the data
    for (let line of lines) {
        // ...
    }

    // Return results to the main thread (UI)
    Beat.dispatch_sync(function () {
        htmlWindow.setHTML("Results: ... ")
    })
})

Multithreading madness

Note that if you are dealing with live content, such as Line objects that are being manipulated while you are iterating through them, you can run into weird race conditions. The plugin API is in direct contact with Objective C and Swift, and unlike in your usual JavaScript code, you will need take memory access into consideration.

If you run into crashes when doing stuff in the background, you are probably trying to access data while it's being mutated. Beat has some basic safeguards for that, but it's very easy to go around them. Always gather any data you need in the main thread and move into a background thread only after that.