Исходник ktsignal - библиотека для сигналов

kin4stat

😕
Автор темы
Модератор
1,935
2,327

ktsignal​

Небольшая библиотека сигналов для C++. Для компиляции потребуется C++17 (на MSVC можно скомпилировать на C++14)


Немного примеров кода​

Базовое использование:​

Пример ниже показывает базовое использование сигналов
C++:
void on_click(int value) { }
class A {
public:
    void on_class_click(int value) { }
};
int main() {
    // Сигнал с сигнатурой функции в шаблонном параметре
    ktsignal::ktsignal<void(int)> click{};
    // Подключение обычной функции
    click.connect(on_click);
 
    // Подключение member function
    A object;
    click.connect(&object, &A::on_class_click);
    // Подключение лямбда функции
    click.connect([](int){ });
    // Вызов коллбэков
    click.emit(1);
}
Scoped соединения автоматически отключаются в конце области видимости
C++:
void on_click(int value) { }

class A {
public:
    void on_class_click(int value) { }
};

int main() {
    ktsignal::ktsignal<void(int)> click{};
 
    {
        auto connection = click.scoped_connect(on_click);
        A object;
        auto method_connection = click.scoped_connect(&object, &A::on_class_click);

        click.scoped_connect([](int v){ std::cout << "This will never be printed" << std::endl; });

        // on_click and on_class_click are called
        click.emit(1);
    }
    // nothing is called
    click.emit(1);
}

Подключение / Отключение коллбэков​

C++:
auto connection = click.connect(on_click);

// Подключение лямбда функции
click.connect([](int){ });

// Вызов сигнала (on_click и lambda будут вызваны)
click.emit(1);

// Отключение on_click коллбэка от сигнала
connection.disconnect();

// Только лямбда будет вызвана
click.emit(1);

Несколько важных замечаний об объекте ktsignal_connection
  • ktsignal_connection можно создавать по умолчанию, перемещать, но не копировать.
  • Вы должны убедиться, что соединение не будет использоваться после уничтожения сигнала.

Итерация через слоты сигнала​

Вы можете итерироваться по сигналу через range-based for при этом получая значение возврата

C++:
int on_click(int value) { return 5; }
int on_click_second(int value) { return 1; }

int main() {
    ktsignal::ktsignal<int(int)> click{};

    click.connect(on_click);
    click.connect(on_click_second);

    // Будет выведено `emit_iterate returned 5 emit_iterate returned 1`
    for (auto returned : signal.emit_iterate(1)) {
        std::cout << "emit_iterate returned " << returned << " ";
    }
}

Также вы легко можете использовать функции из стандартной библиотеки C++

C++:
int on_click(int value) { return 5; }
int on_click_second(int value) { return 1; }

int main() {
    ktsignal::ktsignal<int(int)> click{};

    click.connect(on_click);
    click.connect(on_click_second);

    auto iterate = signal.emit_iterate(0);
    auto accumulated = std::accumulate(iterate.begin(), iterate.end(), 0);

    // Will display 6
    std::cout << "Accumulated: " << accumulated << std::endl;
}

Использование ktsignal в многопоточном коде​

Для многопоточного кода вы должны использовать ktsignal_threadsafe
C++:
void func_thread(int v) {
    std::cout << "before sleep" << std::endl;
    std::this_thread::sleep_for(1000ms);
    std::cout << "after sleep" << std::endl;
    return 2;
}

int main() {
    ktsignal::ktsignal_threadsafe<int(int)> signal{};

    signal.connect(func_thread);

    // Создание потока который сразу же вызовет emit
    std::thread([&signal]() {

        // Создание потока который вызовет emit спустя 100мс
        std::thread(
            [&signal]() {
                std::this_thread::sleep_for(100ms);
                signal.emit(1);
            }
        ).detach();

         signal.emit(2);
    }).join();

    std::this_thread::sleep_for(1.5s);
}
Код:
Вывод:
[func_thread] - before sleep
[func_thread] - before sleep
[func_thread] - after sleep
[func_thread] - after sleep

C++:
void func_thread(int v) {
    std::cout << "before sleep" << std::endl;
    std::this_thread::sleep_for(1000ms);
    std::cout << "after sleep" << std::endl;
    return 2;
}

int main() {
    ktsignal::ktsignal_threadsafe_emit<int(int)> signal{};

    signal.connect(func_thread);

    // Создание потока который сразу же вызовет emit
    std::thread([&signal]() {

        // Создание потока который вызовет emit спустя 100мс
        std::thread(
            [&signal]() {
                std::this_thread::sleep_for(100ms);
                signal.emit(1);
            }
        ).detach();

         signal.emit(2);
    }).join();

    std::this_thread::sleep_for(1.5s);
}
Код:
Вывод:
[func_thread] - before sleep
[func_thread] - after sleep
[func_thread] - before sleep
[func_thread] - after sleep


Download & Source:
 
Последнее редактирование:

SR_team

like pancake
BH Team
4,122
4,710
void func_thread(int v) { std::cout << "before sleep" << std::endl; std::this_thread::sleep_for(1000ms); std::cout << "after sleep" << std::endl; return 2; } int main() { ktsignal::ktsignal_threadsafe_emit<int(std::string&)> signal{}; signal.connect(func_thread); // Создание потока который сразу же вызовет emit std::thread([&signal]() { // Создание потока который вызовет emit спустя 100мс std::thread( [&signal]() { std::this_thread::sleep_for(100ms); signal.emit(1); } ).detach(); signal.emit(2); }).join(); std::this_thread::sleep_for(1.5s); }
как у тебя 1 и 2 в строку разворачивается? Где-то перегрузка опператора = или эти цифры что-то другое означают?
 
  • Нравится
Реакции: kin4stat

kin4stat

😕
Автор темы
Модератор
1,935
2,327
как у тебя 1 и 2 в строку разворачивается? Где-то перегрузка опператора = или эти цифры что-то другое означают?
Как ты лайкнул, я зашел в тему и увидел что проебался в примере.
Исправил до того как ты написал этот ответ.
 

SR_team

like pancake
BH Team
4,122
4,710
Как ты лайкнул, я зашел в тему и увидел что проебался в примере.
Исправил до того как ты написал этот ответ.
Еще у тебя сигнатура позволяет возвращать что-то из сигналов. Как это работает? emit возвращает список результатов?

UPD: В примере многопоточки сигнал что-то возвращает int(int)
 
  • Нравится
Реакции: kin4stat

kin4stat

😕
Автор темы
Модератор
1,935
2,327
Еще у тебя сигнатура позволяет возвращать что-то из сигналов. Как это работает? emit возвращает список результатов?
emit_iterate позволяет по ним итерироваться(оператор * у итератора делает вызов слота в сигнале).
Не стал делать вектор возвратных значений для emit чтобы не оверхеда. При желании есть emit_iterate который позволяет это сделать
 

kin4stat

😕
Автор темы
Модератор
1,935
2,327
Почему не оператор ()? Как это выглядит?
Оператор * перегружен для вызова функции, чтобы в range-based for можно было красиво получить возвратное значение. Да и в целом это позволяет много красивых вещей делать.
Ну например вот такое:
C++:
auto iterate = signal.emit_iterate("test");
std::vector<int> vec(iterate.begin(), iterate.end());
И в vec будут все значения которые вернулись после вызова коллбэков

(Только что залил обновление на гите, теперь на MSVC можно конструировать STL контейнеры через итераторы. Microsoft решили выебнуться проверкой на поля итератора)
Почему не оператор ()?

Мне кажется что вызов сигнала через () не очень явное действие, поэтому решил сделать на emit. В целом это на сам сигнал никак не влияет
Как это выглядит?
Примерно вот так:
1630692767218.png


UPD: В примере многопоточки сигнал что-то возвращает int(int)
UPD: сигнал может что-то возвращать, но при вызове через emit эти значения получить нельзя. Для этих целей стоит юзать emit_iterate и его методы begin и end возвращающие итераторы
 
Последнее редактирование:
  • Нравится
Реакции: SR_team