Skip to content

valeksan/CoreTemplateStd

Repository files navigation

CoreTemplateStd

A modern, header-only C++17 library for running registered tasks in separate threads, with grouping, cooperative cancellation, stop timeouts, and a small callback/event API.

Build Status License Language

Русская версия

Features

  • Header-only core: copy core.h or link the exported CMake interface target.
  • No required Qt dependency in core: public API uses standard C++ types.
  • Type-safe registration: register free functions, lambdas, functors, non-const member functions, and const member functions.
  • Task grouping: only one task per group runs at a time, while tasks from different groups can run concurrently.
  • Cooperative cancellation: tasks can check stopTaskFlag() and exit gracefully.
  • Event callbacks: observe started, finished, cancelled-before-start, stop-requested, and stop-timeout events.
  • Configurable logging: redirect core debug and warning messages with a standard C++ callback.
  • Optional Qt adapter: build a separate QObject/signal bridge without adding Qt to core.h.
  • C++17 payloads: task arguments and results are exposed as std::vector<std::any> and std::any.

Getting Started

CoreTemplateStd requires a C++17 compiler. No Qt package is required to build or consume the core target.

Copy core.h into your project, or use CMake from this repository:

add_subdirectory(CoreTemplateStd)
target_link_libraries(your_target PRIVATE CoreTemplateStd::CoreTemplateStd)

Use directly from GitHub with FetchContent:

include(FetchContent)

FetchContent_Declare(
    CoreTemplateStd
    GIT_REPOSITORY https://github.com/valeksan/CoreTemplateStd.git
    GIT_TAG v0.4.0
)

FetchContent_MakeAvailable(CoreTemplateStd)

target_link_libraries(your_target PRIVATE CoreTemplateStd::CoreTemplateStd)

Install and consume via find_package:

cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/your/prefix
cmake --build build
cmake --install build
find_package(CoreTemplateStd REQUIRED)
target_link_libraries(your_target PRIVATE CoreTemplateStd::CoreTemplateStd)

Installed package consumers can include the header explicitly:

#include <CoreTemplateStd/core.h>

The old CMake package name remains available as a compatibility layer:

find_package(CoreTemplate REQUIRED)
target_link_libraries(your_target PRIVATE CoreTemplate::CoreTemplate)

Optional Qt Adapter

The core target stays Qt-free. If a Qt application wants signal/slot integration, enable the separate adapter target from the source tree:

set(CORETEMPLATE_BUILD_QT_ADAPTER ON)
add_subdirectory(CoreTemplateStd)

target_link_libraries(your_qt_target PRIVATE
    CoreTemplateStd::QtAdapter
)

Installed packages expose the adapter as an optional component:

find_package(CoreTemplateStd REQUIRED COMPONENTS QtAdapter)
target_link_libraries(your_qt_target PRIVATE CoreTemplateStd::QtAdapter)

CoreQtAdapter owns a std-only Core, exposes it through core(), and re-emits core callbacks as Qt signals with QVariantList/QVariant payloads. The adapter supports common scalar and string payload conversions; unsupported std::any payloads become invalid QVariant values. It uses Core::setWakeCallback with queued Qt delivery and one-shot timers, so the GUI example does not need a polling timer.

The Qt Widgets GUI example is also optional:

cmake -S . -B build/qt_gui \
  -DCORETEMPLATE_BUILD_EXAMPLE=OFF \
  -DCORETEMPLATE_BUILD_QT_GUI_EXAMPLE=ON
cmake --build build/qt_gui --target ExampleQtGuiApp

Migrating From Qt CoreTemplate

CoreTemplateStd is a breaking std-only branch of the original Qt-oriented API:

  • Qt signals such as finishedTask are replaced by callback setters such as onFinished.
  • QVariant and QVariantList payloads are replaced by std::any and std::vector<std::any>.
  • Qt event-loop delivery is replaced by explicit processEvents() calls from the managing thread.
  • QObject ownership is removed; construct Core directly without a parent object.
  • Force termination no longer kills worker threads in the std::thread backend; non-cooperative tasks receive stop requests and timeout events.
  • The old CMake names remain as compatibility aliases, but new code should prefer CoreTemplateStd and CoreTemplateStd::CoreTemplateStd.

Quick Start

#include <CoreTemplateStd/core.h>

#include <any>
#include <chrono>
#include <iostream>
#include <thread>

int main()
{
    Core core;
    bool finished = false;

    core.registerTask(1, [](int a, int b) -> int {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        return a + b;
    });

    core.onFinished([&](const FinishedEvent& event) {
        std::cout << "Result: " << std::any_cast<int>(event.result) << '\n';
        finished = true;
    });

    core.addTask(1, 10, 20);

    while (!finished) {
        core.processEvents();
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
}

See example/console_main.cpp for a minimal runnable example.

Public API

The complete API is defined in core.h. The primary methods are:

  • registerTask: registers a function, lambda, functor, or member function by task type.
  • registerTaskWithContext: registers a task that receives an explicit TaskContext as its first argument.
  • addTask: queues a registered task with arguments.
  • unregisterTask: removes a registered task type.
  • onStarted, onFinished, onCancelledBeforeStart, onStopRequested, onStopTimedOut: set one callback per event kind.
  • setLogHandler, clearLogHandler: configure or reset the global std-only core log sink.
  • setWakeCallback, clearWakeCallback: configure or reset an owner-loop wake hook for adapters and integrations.
  • processEvents: delivers queued owner-thread events and should be called by the managing thread.
  • cancelTaskById, cancelTaskByType, cancelTaskByGroup, cancelTasks, cancelAllTasks, cancelTasksByGroup: request cooperative cancellation.
  • stopTaskById, stopTaskByType, stopTaskByGroup, stopTasks, stopAllTasks, stopTasksByGroup: compatibility names for the cancellation API.
  • isTaskRegistered, groupByTask, isIdle, isTaskAddedByType, isTaskAddedByGroup: query task state.
  • stopTaskFlag: returns the thread-local stop flag for the currently running task.
  • requestStopAll: requests cooperative cancellation for active work and removes queued work.
  • waitForIdle(timeout): processes owner events while waiting for active workers; it does not cancel them.
  • shutdown(timeout): calls requestStopAll, waits up to timeout, and returns the queued-cancellation count and IDs of workers still running.

Threading Model

Core is designed for one managing thread.

  • Create and use a Core instance from one thread.
  • Call public methods such as registerTask, addTask, cancellation, and query methods from that same managing thread.
  • Registered task functions run in worker threads managed by the library.
  • Worker completion is queued back to the managing side; call processEvents() regularly to deliver callbacks and start queued follow-up tasks.
  • Code running inside a task should not directly call public Core methods. Use your application-level message passing to communicate back to the managing thread.

Cooperative Cancellation

Cancellation is cooperative. A long-running task should periodically check:

if (auto* stop = core.stopTaskFlag(); stop != nullptr && stop->load()) {
    return;
}

The portable std::thread backend has no safe standard way to kill a running thread. A non-cooperative task receives a stop request and then a stop-timeout event, but keeps running until its callable returns. A timeout is a diagnostic, not successful termination.

Task implementations are responsible for checking the stop flag, putting time limits on blocking operations, and returning before Core is destroyed. The destructor joins active workers to protect Core state, so violating this contract with an infinite non-cooperative task can block shutdown. Code that must be forcibly stopped should run in a separate process.

For new code, prefer an explicit context rather than capturing Core solely to read its thread-local flag:

core.registerTaskWithContext(1, [](TaskContext context, int value) -> int {
    while (value-- > 0) {
        if (context.stopRequested()) {
            return -1;
        }
        // Process one short unit of work.
    }
    return 0;
});

TaskContext only reports a request; it does not preempt the callable. Before destroying a Core, call shutdown(timeout) and keep objects captured by running tasks alive until its result is idle (or until a later waitForIdle succeeds).

Grouping Example

#include <CoreTemplateStd/core.h>

#include <any>
#include <chrono>
#include <iostream>
#include <thread>

int main()
{
    Core core;
    int finishedCount = 0;

    auto work = [](int value) -> int {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        return value * 10;
    };

    core.registerTask(1, work, 1);
    core.registerTask(2, work, 1);
    core.registerTask(3, work, 2);

    core.onFinished([&](const FinishedEvent& event) {
        std::cout << "Task " << event.type
                  << " result " << std::any_cast<int>(event.result) << '\n';
        ++finishedCount;
    });

    core.addTask(1, 10);
    core.addTask(2, 20);
    core.addTask(3, 30);

    while (finishedCount < 3) {
        core.processEvents();
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
}

Tasks 1 and 2 share group 1, so they run sequentially. Task 3 belongs to group 2, so it can run concurrently with group 1.

Tests

cmake -S . -B build/std_only_check -DCORETEMPLATE_BUILD_TESTS=ON -DCORETEMPLATE_BUILD_EXAMPLE=ON
cmake --build build/std_only_check
ctest --test-dir build/std_only_check --output-on-failure
./build/std_only_check/example/ExampleConsoleApp

Important Notes

  • The core is header-only and implemented in core.h.
  • Core debug and warning messages go to std::clog and std::cerr by default, or to the handler configured with Core::setLogHandler.
  • TaskArgs is std::vector<std::any>.
  • TaskResult is std::any; a void task produces an empty std::any.
  • std::any_cast<T> is the caller's responsibility when reading callback payloads.
  • The current backend uses std::thread; non-cooperative tasks cannot be forcibly killed by standard C++.

Support The Project

If you find this library helpful and wish to support its development, feel free to use the Sponsor button. Any support is voluntary and entirely optional. The library remains free and open-source.

About

C++ std-only & header-only library for registering, queuing, and executing tasks in groups across separate threads.

Topics

Resources

Contributing

Stars

Watchers

Forks

Sponsor this project

Contributors

Languages