Skip to content

Commit

Permalink
Major rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
Haldoors committed Aug 2, 2018
1 parent bce3152 commit f5083f7
Show file tree
Hide file tree
Showing 16 changed files with 4,241 additions and 1,724 deletions.
25 changes: 25 additions & 0 deletions CMakeLists.txt
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)
49 changes: 13 additions & 36 deletions README.md
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.
17 changes: 17 additions & 0 deletions demo/CMakeLists.txt
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)
143 changes: 143 additions & 0 deletions demo/basic_demo.cpp
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
}
Loading

0 comments on commit f5083f7

Please sign in to comment.