Multithreading

There are some utilities for using AngelScript in the multithreading environment.

Note

AngelScript does not support multithreading on all platforms.

inline bool asbind20::has_threads(const char *options = asGetLibraryOptions())

Check if asGetLibraryOptions() doesn’t return "AS_NO_THREADS"

Initializing and Cleaning the Multithreading Environment

According to the official document, AngelScript needs some additional efforts to work in the multithreading environment.

The following functions are provided by the external header concurrent/threading.hpp.

inline void asbind20::concurrent::auto_thread_cleanup() noexcept

Mark this thread needs to clean up AngelScript data before terminating.

It’s safe to call this function for the second time in the same thread.

Note

Remember to call this function in any thread other than main thread to prevent memory leak.

inline void asbind20::concurrent::prepare_multithread(asIThreadManager *external_mgr = nullptr)

Call this function in the main thread to prepare for multithreading.

Warning

Call this function before any script engine is created, and make sure global variables don’t contain any script engine that may be released after asUnprepareMultithread being called.

Example code:

#include <thread>
#include <asbind20/asbind.hpp>
#include <asbind20/concurrent/threading.hpp>

int main()
{
    if(!asbind20::has_threads())
    {
        std::cerr << "AS_NO_THREADS" << std::endl;
        return 1;
    }

    using namespace asbind20;
    concurrent::prepare_multithread();

    auto engine = make_script_engine();

    auto* m = engine->GetModule(
        "script_multithreading", asGM_ALWAYS_CREATE
    );
    m->AddScriptSection(
        "script_multithreading",
        "int fn(int arg) { return arg * 2; }"
    );
    m->Build();
    auto* f = m->GetFunctionByName("fn");

    std::condition_variable cv;
    std::mutex mx;
    int result = -1;

    auto helper = [&, f](int arg)
    {
        concurrent::auto_thread_cleanup();

        {
            using namespace std::chrono_literals;

            request_context ctx(engine);
            std::this_thread::sleep_for(3ms); // Emulates running a complex script
            auto r = script_invoke<int>(ctx, f, arg);
            std::unique_lock lock(mx);
            result = r.value();
        }

        cv.notify_all();
    };

    std::thread thr(helper, 10);
    thr.detach();
    {
        std::unique_lock lock(mx);
        std::cv_status st = cv.wait(lock);
    }
    assert(result == 20);

    return 0;
}

Locks

The AngelScript library provides two locks.

constexpr detail::as_shared_lock_t asbind20::as_shared_lock = {}

Wrapper for asAcquireSharedLock() and asReleaseSharedLock()

constexpr detail::as_exclusive_lock_t asbind20::as_exclusive_lock = {}

Wrapper for asAcquireExclusiveLock() and asReleaseExclusiveLock()

The weak reference flag (asILockableSharedBool*) is also lockable.

class lockable_shared_bool

Helper for asILockableSharedBool*

This class can be helpful for implementing weak reference support.

Public Functions

inline void lock()

Lock the flag.

inline void unlock() noexcept

Unlock the flag.

Example code:

{
    std::lock_guard guard(asbind20::as_exclusive_lock);
    // Do something...
}

Atomic Reference Counting

class atomic_counter

Atomic counter for multithreading.

This wraps the asAtomicInc and asAtomicDec.

Note

Its initial value will be 1.

Increment and decrement operators

Even the prefix increment / decrement will return int value directly, which is similar to how the std::atomic<T> does.

inline int operator++() noexcept
inline int operator--() noexcept

Public Types

using value_type = int

Public Functions

inline atomic_counter() noexcept

Construct a new atomic counter and set the counter value to 1.

atomic_counter(const atomic_counter&) = default
inline atomic_counter(int val) noexcept
~atomic_counter() = default
atomic_counter &operator=(const atomic_counter&) noexcept = default
inline atomic_counter &operator=(int val) noexcept
bool operator==(const atomic_counter&) const = default
inline int inc() noexcept
inline int dec() noexcept
inline operator int() const noexcept
template<typename Destroyer, typename ...Args>
inline decltype(auto) dec_and_try_destroy(Destroyer &&d, Args&&... args)

Decrease reference count. It will call the destroyer if the count reaches 0.

template<typename T>
inline void dec_and_try_delete(T *ptr) noexcept

Decrease reference count. It will delete the pointer if the count reaches 0.

For garbage collected types, please read the official document about thread safety and GC.