Activity Framework is a C++23 toolkit for composing reflective, asynchronous work: a lock-free task queue, work-stealing thread pool, pipelines, coroutine helpers, and a Qt-like signal/slot system built on top of a compile-time reflection layer.
- Compile-time reflection (members, overloads, enums) that powers dynamic invoke by name and metadata-driven serialization.
- Thread-aware meta-object system with Qt-style signals/slots, queued/direct delivery, and safe cross-thread invocation.
- Activity/ActivityProxy wrappers with affinity-aware scheduling, result fetchers, and a lock-free circular queue underneath a platform-specific thread pool.
- Pipeline orchestrators (auto, concurrent, manual, stepped, key-activity) with progress signals and cached results.
- Serialization with endian conversion, property versioning, and deep support for STL containers, pointers, enums, and inheritance.
- Coroutine helpers (manual/eager tasks, generators, activity and signal awaitables) that bridge to the Activity Framework's threading model.
- Small-object optimized variant type used to move results across activities, pipelines, and coroutines.
- Windows (MSVC 17.5+)
- Linux (GCC 13+ or recent Clang)
- Android (arm64-v8a and x86_64 via CMake presets; validated in CI with NDK 27.0.12077973)
Depends only on the C++ standard library; GoogleTest is bundled for optional unit tests.
- CMake 3.20+
- C++23 toolchain (MSVC 17.5+, GCC 13+, or Clang with coroutines enabled)
- Ninja (optional, used by Android presets)
- Android: set
ANDROID_NDK_HOME(r26d/26.2+ works; CI uses 27.0.12077973) - Tests: build GoogleTest once under
Test/ThirdParty/googletest(its output tree is used by the test CMakeLists)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config ReleaseOn Windows (multi-config):
cmake -S . -B build-windows -G "Visual Studio 17 2022" -A x64
cmake --build build-windows --config Release -- /mOutputs are placed under build/ActivityFramework/output (or the VS configuration directory).
ACTIVITYFRAMEWORK_BUILD_TESTS(default:ON): build the unit tests. Android arm64 presets turn this off because GoogleTest binaries are not produced there; enable it after providing GoogleTest outputs if you need on-device tests.
cmake --build build --config Release --target ActivityFrameworkTest
ctest --test-dir build -C Release --output-on-failureExecutables live under build/Test/ActivityFrameworkTest (or build-windows/Test/Release/ActivityFrameworkTest.exe).
Use the supplied presets in CMakePresets.json:
cmake --preset android-arm64-release
cmake --build --preset android-arm64-release
cmake --preset android-x86_64-debug # runs with tests if GoogleTest is built for x86_64
cmake --build --preset android-x86_64-debugArtifacts land in build/android-*/<config>/ActivityFramework/output/.
Register metadata once, then query or invoke by name. Private members require ENABLE_REFLEX on the class.
#include "Components/TA_MetaReflex.h"
class MetaTest {
ENABLE_REFLEX
public:
int sub(int a, int b) { return a - b; }
static std::string getStr(const std::string &s) { return "123" + s; }
};
DEFINE_TYPE_INFO(MetaTest){
AUTO_META_FIELDS(
REGISTER_FIELD(sub),
REGISTER_FIELD(getStr)
)
};
MetaTest test;
std::string_view name = CoreAsync::Reflex::TA_TypeInfo<MetaTest>::findName(&MetaTest::sub); // "sub"
auto res = CoreAsync::Reflex::TA_TypeInfo<MetaTest>::invoke(META_STRING("getStr"), "abc");TA_TypeInfo can also map enums, static data members, overloads (REGISTER_METHOD_OVERLOAD_GENERIC), and base-class members.
Properties opt-in via metadata; versions gate reads/writes automatically and endian conversion is handled for primitive types.
class Data {
ENABLE_REFLEX
public:
int value{42};
std::vector<int> nums{1,2,3};
};
DEFINE_TYPE_INFO(Data){
AUTO_META_FIELDS(
REGISTER_FIELD(value, TA_PROPERTY(2)), // serialized when version >= 2
REGISTER_FIELD(nums, TA_DEFAULT_PROPERTY)
)
};
Data in, out;
CoreAsync::TA_Serializer writer("data.afw", 2);
writer << in;
CoreAsync::TA_Serializer<CoreAsync::BufferReader> reader("data.afw", 2);
reader >> out;Supported: STL containers/adaptors, arrays, enums, pointers (to serializable types), inheritance hierarchies, and custom types with metadata.
TA_MetaObject provides thread-aware connections and an invokeMethod helper to run member functions or callables (optionally queued on the framework thread pool).
class Sender : public CoreAsync::TA_MetaObject {
public:
TA_Signals: void fired(int) {}
};
class Receiver : public CoreAsync::TA_MetaObject {
public:
void onFired(int v) { std::printf("%d\n", v); }
};
DEFINE_TYPE_INFO(Sender){AUTO_META_FIELDS(REGISTER_FIELD(fired))};
DEFINE_TYPE_INFO(Receiver){AUTO_META_FIELDS(REGISTER_FIELD(onFired))};
Sender s; Receiver r;
CoreAsync::ITA_Connection::connect(&s, &Sender::fired, &r, &Receiver::onFired); // auto/queued based on threads
CoreAsync::TA_MetaObject::invokeMethod(META_STRING("fired"), &s, 5)(); // returns an activity fetcherConnections can be direct, queued, or auto; lambdas are supported; TA_SignalAwaitable lets coroutines wait a signal emission.
Activities wrap callables or reflected method names. They carry thread affinity, an ID, and a stolenEnabled flag for work stealing. TA_ThreadPool (and the singleton TA_ThreadHolder) schedule them on a lock-free queue with platform-specific threads.
CoreAsync::TA_ThreadHolder::create(); // ensure global pool exists
auto activity = CoreAsync::TA_ActivityCreator::create(&MetaTest::sub, &test, 7, 2);
auto fetcher = CoreAsync::TA_ThreadHolder::get().postActivity(activity, true);
int result = fetcher().get<int>();Results travel as TA_DefaultVariant (small-object optimized, smart pointer backed for larger types). TA_ActivityFetcherAwaitable and TA_ActivityExecutingAwaitable bridge activities to coroutines.
Create pipelines through ITA_PipelineCreator:
- Auto chain: run activities in order.
- Concurrent: run all activities in parallel.
- Manual chain: advance one activity per
execute()call. - Manual steps: run a fixed step count per call.
- Manual key-activity: repeat a marked key activity until skipped.
APIs: add/remove/clear, execute(Async|Sync), result(index, out), reset, setSteps, setKeyActivityIndex, skipKeyActivity. Signals: pipelineStateChanged, pipelineReady, and activityCompleted (index + TA_Variant result).
TA_ManualCoroutineTask (lazy/eager) and TA_CoroutineGenerator wrap standard coroutines with blocking get()/value() helpers. Awaitables include waiting on signals, executing activities (sync/async), and fetching results, letting coroutine code integrate with the ActivityFramework scheduler.
- Sol Zhu — breakersol@outlook.com
See CONTRIBUTING.md for how to propose changes or improvements.
Licensed under the Apache-2.0 License. See LICENSE for details.