forked from vdksoft/signals
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
4,241 additions
and
1,724 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
cmake_minimum_required(VERSION 3.8) | ||
|
||
project(signals CXX) | ||
|
||
enable_testing() | ||
|
||
set(CMAKE_CXX_STANDARD 17) | ||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
set(CMAKE_CXX_EXTENSIONS OFF) | ||
|
||
add_library(signals "") | ||
target_sources(signals PRIVATE ${CMAKE_SOURCE_DIR}/src/signals.h | ||
${CMAKE_SOURCE_DIR}/src/signals.cpp) | ||
target_include_directories(signals PUBLIC ${CMAKE_SOURCE_DIR}/src) | ||
target_compile_options(signals PUBLIC | ||
$<$<CXX_COMPILER_ID:GNU>:-Wall> | ||
$<$<CXX_COMPILER_ID:Clang>:-Wall> | ||
$<$<CXX_COMPILER_ID:AppleClang>:-Wall> | ||
$<$<CXX_COMPILER_ID:MSVC>:/W4>) | ||
|
||
find_package (Threads) | ||
target_link_libraries (signals PUBLIC ${CMAKE_THREAD_LIBS_INIT}) | ||
|
||
add_subdirectory(tests) | ||
add_subdirectory(demo) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,25 @@ | ||
C++ Signals & Slots (vdk-signals) | ||
================================= | ||
# C++ signals and slots | ||
|
||
## Introduction | ||
**vdk-signals** is a type-safe and thread-safe signals-slots system for standard C++ designed with performance and simplicity in mind. It follows the main philosophy of the C++ language avoiding unnecessary overheads and superfluous functionality that can slow down your program. | ||
|
||
Signals and slots is a mechanism used for communication between different parts of some application. The key concept is allowing objects of any kind to communicate with one another while keeping them as much decoupled as possible. Signals are emitted by an object when its internal state has changed in some way that might be interesting to other objects. Slots are called when signals connected to them are emitted. Slots can be used to receive signals, but they are also normal C++ functions and can be called directly. | ||
High performance is achieved through the use of modern C++ features and atomic variables. Specialized synchronization mechanism used internally makes signal emissions lock-free and ensures the fastest possible execution. The library supports synchronous and asynchronous slot invocations with automatic detection of target threads, as well as automatic object lifetime tracking. | ||
|
||
The signals and slots mechanism is type safe. The signal type must match the signature of the receiving slot and the compiler helps to detect type mismatches generating compile-time errors. Each signal can be connected to any number of slots and each slot can be connected to any number of signals. It is even possible to connect a signal directly to another signal. | ||
`vdk-signals` has no external dependencies and relies on standard C++17 only. It is organized as the amalgamation and can be easily integrated into any existing project. | ||
|
||
The signals and slots mechanism is especially convenient for Graphical User Interface programming, but can also be used pretty much everywhere. It enables a powerful and flexible component programming mechanism that makes code easy to design and reuse. | ||
## Usage: | ||
|
||
## Overview | ||
**Note!** `vdk-signals` requires a compiler that supports **C++17** standard. | ||
|
||
**vdk-signals** is a one-header library, which implements a type-safe, thread-safe signals-slots system in C++. It is written entirely in pure standard C++ with high performance and efficiency in mind. **vdk-signals** relies on C++ Standard Library only, and does not require any pre-compilation steps, special tools, third party libraries or non-standard extensions. Everything you need is in one header, just put it in your project, #include and use. The interface of the library is designed to be intuitive, very simple, and with clean syntax. | ||
The library is not meant to be built and linked as a standalone package. Instead, it is organized as "the amalgamation" and contains everything you need in just two files. This allows you to easily integrate `vdk-signals` into any target project. Just copy `signals.h` and `signals.cpp` into your project and compile them together with your other source files. | ||
|
||
**In order to use this library, compiler must be compatible with at least C++11 Standard.** | ||
Please note, that in order to provide maximum flexibility and independence from target project's structure, `signals.cpp` includes `signals.h` as a standard header (`#include<signals.h>`), so make sure that `signals.h` resides in a folder that is searched for header files by the compiler. | ||
|
||
High speed is achieved through the use of modern C++ features and atomic variables to ensure thread-safety and fastest possible execution. Specialized synchronization mechanism makes signal emission lock-free and wait-free, avoiding any undesirable consequences introduced by locks. | ||
GoogleTest is required to build and run tests. CMake files are provided to simplify the process. If you already have a copy of GoogleTest framework, just run CMake and set `GTEST_SOURCE_DIR` cache variable to point to the directory that contains your GoogleTest sources. If you don't have GoogleTest sources, CMake will download and compile them automatically. | ||
|
||
The core of this library is **vdk::signal** template class. This class exposes all public methods and encapsulates a set of internal classes. An instance of **vdk::signal** contains a list of connections (connected slots). When a particular event occurs the signal is emitted and all connected slots are executed one after another. | ||
`demo` directory contains code examples that can serve as a tutorial for learning how to use `vdk::signals`. | ||
|
||
Slots are normal C++ functions, which are called in response to a particular signal emission. Any callable target can be connected to some signal as a slot. It may be a free (global) function, static member function of some class (static method), member function of some class (method) with any qualifiers (like const, volatile), function object (functor), lambda or even another signal. | ||
## [API documentation](docs/signals.md) | ||
|
||
**vdk-signals** library supports auto-disconnection feature. A signal object can track connected slots through std::shared_ptr / std::weak_ptr and automatically disconnect destroyed slots, once such slots are detected. This tracking mechanism makes it possible to avoid needless and intrusive inheritance for tracked objects. | ||
## License: | ||
|
||
Special care has been taken regarding memory usage efficiency to ensure complete absence of memory leaks or memory fragmentation. You can choose whether you want to allocate all memory for the signal object as one memory block up-front, or allocate it as separate parts later when your program runs. | ||
|
||
In some cases, when user is absolutely sure that the library will be used in single-threaded environment only, one could activate special library mode to enable signals optimized for single-threaded usage. For that purpose, **"VDK_SIGNALS_LITE"** macro should be defined before including the library header. This macro switches the library mode making it single-threaded, so that user can avoid any unnecessary overhead introduced by thread-safety and benefit from keeping every bit of performance. This single-threaded mode is also optimized for memory usage efficiency. Don’t pay for what you don’t use! | ||
|
||
This library enables any object in your program work as an independent software component that can communicate with other unrelated components through signals and slots. This approach allows true decoupling, makes your design clearer, and increases reusability of your code. | ||
|
||
## Features | ||
|
||
1. Single-header implementation. Does not require any special compilation. Just put it in the project, #include and use; | ||
2. Signal class is solid and opaque. There is no mess with helper classes or functions. Signal can be treated as built-in type; | ||
3. Simple to use, with intuitive and clean syntax, but without any limitations of necessary functionality in the same time; | ||
4. Thread-safe signals with excellent concurrency capabilities. C++11 atomics ensure fast work even in high-load situations; | ||
5. Type-safe signals with compile-time checks. The compiler detects type mismatches generating compile-time errors; | ||
6. Efficient memory usage. Memory can be allocated up-front in single memory block. Eliminates memory fragmentation; | ||
7. Library relies on standard modern C++ only, without any non-standard extensions, meta-object compilers or other tools; | ||
8. Signals support auto-disconnection feature. Expired / destroyed slots are detected and disconnected automatically; | ||
9. Signals are able to connect any type of callable targets, including methods with qualifiers, lambdas and other signals; | ||
10. Special single-threaded mode is available. Applications that work in single thread does not lose any bit of performance; | ||
11. Exception safety. No thrown exception can leave a signal object in undetermined state. All exceptions are documented. | ||
|
||
|
||
|
||
### **Full documentation, tutorial, performance comparison, and API reference can be found in the library package.** | ||
This software is licensed under the Apache License version 2.0. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
cmake_minimum_required(VERSION 3.8) | ||
|
||
project(signals-demo CXX) | ||
|
||
set(CMAKE_CXX_STANDARD 17) | ||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
set(CMAKE_CXX_EXTENSIONS OFF) | ||
|
||
add_executable(signals-demo "") | ||
target_sources(signals-demo | ||
PRIVATE | ||
"demo.h" | ||
"demo.cpp" | ||
"basic_demo.cpp" | ||
"track_objects.cpp" | ||
"cross_thread_calls.cpp") | ||
target_link_libraries(signals-demo signals) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/*=================================================================== | ||
* Copyright (c) Vadim Karkhin. All rights reserved. | ||
* Use, modification, and distribution is subject to license terms. | ||
* You are welcome to contact the author at: [email protected] | ||
===================================================================*/ | ||
|
||
#include "demo.h" | ||
|
||
// All operations shown in this demo apply to both: 'normal' | ||
// (thread-safe) and 'lite' (single-threaded) versions of signals. | ||
// Therefore, vdk::lite::signal can be used instead of vdk::signal. | ||
|
||
using vdk::signal; | ||
|
||
namespace | ||
{ | ||
void function(int arg) | ||
{ | ||
std::cout << "function(" << arg << ")" << std::endl; | ||
} | ||
|
||
struct functor | ||
{ | ||
explicit functor(int data) noexcept | ||
: data_{ data } | ||
{} | ||
void operator()(int arg) | ||
{ | ||
std::cout << "functor(" << arg << ")" << std::endl; | ||
} | ||
bool operator==(const functor & other) const noexcept | ||
{ | ||
return data_ == other.data_; | ||
} | ||
int data_; | ||
}; | ||
|
||
class demo_class | ||
{ | ||
public: | ||
|
||
demo_class() = default; | ||
|
||
void method(int arg) | ||
{ | ||
std::cout << "demo_class::method(" << arg << ")" << std::endl; | ||
} | ||
}; | ||
|
||
} // namespace | ||
|
||
void signals_basic_demo() | ||
{ | ||
// Create a signal with no connected slots | ||
signal<void(int)> sig; | ||
|
||
// Connect the signal to a function | ||
sig.connect(function); | ||
// Connect the signal to a function object | ||
sig.connect(functor{ 5 }); | ||
|
||
// Create an object to call methods on | ||
demo_class object; | ||
// Connect the signal to a method | ||
sig.connect(&object, &demo_class::method); | ||
|
||
auto lambda = [](int arg) | ||
{ | ||
std::cout << "I am lambda(" << arg << ")" << std::endl; | ||
}; | ||
|
||
// Connect the signal to the lambda | ||
// 'connect()' method returns connection 'id' to disconnect it later | ||
auto id = sig.connect(lambda); | ||
|
||
// Emit the signal | ||
sig.emit(5); | ||
// Emit the signal using another syntax | ||
sig(10); | ||
|
||
std::cout << "-------------------------------" << std::endl; | ||
|
||
// Disconnect the function | ||
// 'disconnect()' returns 'true' if the given slot has been | ||
// disconnected successfully, 'false' otherwise | ||
if (sig.disconnect(function)) | ||
std::cout << "function has been disconnected" << std::endl; | ||
|
||
// Disconnect the function object | ||
if (sig.disconnect(functor{ 5 })) | ||
std::cout << "functor has been disconnected" << std::endl; | ||
|
||
// Disconnect the method | ||
if (sig.disconnect(&object, &demo_class::method)) | ||
std::cout << "method has been disconnected" << std::endl; | ||
|
||
// Lambdas are not equality comparable, so we have to use ids | ||
// returned from 'connect()' method to disconnect them | ||
// Note! If a callable target provides accessible equality | ||
// comparison operator it is much more convenient to disconnect | ||
// that target using the syntax shown above than using ids | ||
if (sig.disconnect(id)) | ||
std::cout << "lambda has been disconnected" << std::endl; | ||
|
||
std::cout << "-------------------------------" << std::endl; | ||
|
||
// signal connects and disconnects equal slots one by one | ||
sig.connect(function); | ||
sig.connect(function); | ||
sig.connect(function); | ||
|
||
// Now the signal has 3 identical connections to 'function' | ||
sig.emit(15); | ||
// Disconnect one of them | ||
sig.disconnect(function); | ||
// Now only 2 connections to 'function' remain in the signal | ||
sig(20); | ||
|
||
// We can disconnect all remaining connections at once | ||
sig.disconnect(); | ||
// Now the signal has no connected slots again | ||
sig.emit(100); // nothing happens | ||
|
||
std::cout << "-------------------------------" << std::endl; | ||
|
||
sig.connect(function); | ||
// signals can be temporarily blocked | ||
// 'block()' returns whether the signal was blocked before the call | ||
if (!sig.block()) | ||
std::cout << "signal was not blocked" << std::endl; | ||
// We can check whether the signal is blocked now | ||
if (sig.blocked()) | ||
std::cout << "signal is blocked" << std::endl; | ||
sig.emit(110); // nothing happens; the signal is blocked | ||
|
||
// Unblock the signal | ||
if (sig.block(false)) | ||
std::cout << "signal was blocked" << std::endl; | ||
// Again, we can check whether the signal is blocked now | ||
if (!sig.blocked()) | ||
std::cout << "signal is not blocked" << std::endl; | ||
sig.emit(110); // the signal is working again | ||
} |
Oops, something went wrong.