3rdParty: Add FastSignals library.

This commit is contained in:
Joao Matos
2025-06-14 10:53:06 +01:00
committed by tritao
parent 6683fdb122
commit 3a3e8e5e05
43 changed files with 17763 additions and 0 deletions

102
src/3rdParty/FastSignals/.clang-format vendored Normal file
View File

@@ -0,0 +1,102 @@
---
Language: Cpp
# BasedOnStyle: WebKit
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: false
AlignOperands: false
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 0
CommentPragmas: '^ IWYU pragma:'
BreakBeforeInheritanceComma: true
FixNamespaceComments: true
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
# FixNamespaceComments: false
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeCategories:
- Regex: '^"(stdafx|PrecompiledHeader)'
Priority: -2
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 4
- Regex: '^(<|"(gtest|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 2
IncludeIsMainRegex: '$'
IndentCaseLabels: false
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: 'BEGIN_MESSAGE_MAP_CUSTOM$|BEGIN_MSG_MAP$|BEGIN_SINK_MAP$|BEGIN_MESSAGE_MAP$|BOOST_FIXTURE_TEST_SUITE$|BOOST_AUTO_TEST_SUITE$|BEGIN_DLGRESIZE_MAP$|BEGIN_MSG_MAP_EX$|BEGIN_DDX_MAP$|BEGIN_COM_MAP$|BEGIN_CONNECTION_POINT_MAP$|WTL_BEGIN_LAYOUT_MAP$|WTL_BEGIN_LAYOUT_CONTAINER$'
MacroBlockEnd: 'END_MESSAGE_MAP_CUSTOM$|END_MSG_MAP$|END_SINK_MAP$|END_MESSAGE_MAP$|BOOST_AUTO_TEST_SUITE_END$|END_DLGRESIZE_MAP$|END_DDX_MAP$|END_COM_MAP$|END_CONNECTION_POINT_MAP$|WTL_END_LAYOUT_MAP$|WTL_END_LAYOUT_CONTAINER$'
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 10000000
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 4
UseTab: Always
IndentPPDirectives: AfterHash
...

367
src/3rdParty/FastSignals/.gitignore vendored Normal file
View File

@@ -0,0 +1,367 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# ------------
# Custom Rules
build/

27
src/3rdParty/FastSignals/.travis.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
dist: trusty
sudo: required
language: cpp
os:
- linux
compiler:
- gcc
# TODO: - clang
env:
- TARGET_CPU=amd64 BUILD_CONFIGURATION=Release CI_NAME=TRAVIS
# TODO: - TARGET_CPU=x86 BUILD_CONFIGURATION=Release
before_install:
# build environment setup script
- source build/travis-install-env-$TRAVIS_OS_NAME.sh
script:
# Build project
- mkdir -p build && cd build
- cmake .. -DCMAKE_BUILD_TYPE=$BUILD_CONFIGURATION -DTARGET_CPU=$TARGET_CPU -DBUILD_TESTING=ON
- cmake --build .
# Run tests
- tests/libfastsignals_unit_tests/libfastsignals_unit_tests

25
src/3rdParty/FastSignals/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
project(FastSignals)
# Use `-DBUILD_TESTING=OFF` to disable testing/benchmarking.
enable_testing()
include(cmake/functions.cmake)
add_subdirectory(libfastsignals)
if(BUILD_TESTING)
# add_subdirectory(tests/benchmark)
# add_subdirectory(tests/libfastsignals_bench)
#add_subdirectory(tests/libfastsignals_stress_tests)
add_subdirectory(tests/libfastsignals_unit_tests)
endif()
# Trace variables, both environment and internal.
execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "environment")
get_cmake_property(_variableNames VARIABLES)
list (SORT _variableNames)
foreach (_variableName ${_variableNames})
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()

View File

@@ -0,0 +1,57 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2035
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfastsignals", "libfastsignals\libfastsignals.vcxproj", "{32BD918F-EDBC-4057-A033-10DC361DA4A0}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfastsignals_unit_tests", "tests\libfastsignals_unit_tests\libfastsignals_unit_tests.vcxproj", "{BAC23A51-8DC1-4589-940F-9923D8E12718}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{6BBDE9AD-AF40-4DDF-8DA6-BEAD0A204033}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfastsignals_stress_tests", "tests\libfastsignals_stress_tests\libfastsignals_stress_tests.vcxproj", "{751DC150-1907-4D9F-8566-AA4E24FDFA64}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x64.ActiveCfg = Debug|x64
{32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x64.Build.0 = Debug|x64
{32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x86.ActiveCfg = Debug|Win32
{32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x86.Build.0 = Debug|Win32
{32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x64.ActiveCfg = Release|x64
{32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x64.Build.0 = Release|x64
{32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x86.ActiveCfg = Release|Win32
{32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x86.Build.0 = Release|Win32
{BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x64.ActiveCfg = Debug|x64
{BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x64.Build.0 = Debug|x64
{BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x86.ActiveCfg = Debug|Win32
{BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x86.Build.0 = Debug|Win32
{BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x64.ActiveCfg = Release|x64
{BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x64.Build.0 = Release|x64
{BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x86.ActiveCfg = Release|Win32
{BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x86.Build.0 = Release|Win32
{751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x64.ActiveCfg = Debug|x64
{751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x64.Build.0 = Debug|x64
{751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x86.ActiveCfg = Debug|Win32
{751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x86.Build.0 = Debug|Win32
{751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x64.ActiveCfg = Release|x64
{751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x64.Build.0 = Release|x64
{751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x86.ActiveCfg = Release|Win32
{751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BAC23A51-8DC1-4589-940F-9923D8E12718} = {6BBDE9AD-AF40-4DDF-8DA6-BEAD0A204033}
{751DC150-1907-4D9F-8566-AA4E24FDFA64} = {6BBDE9AD-AF40-4DDF-8DA6-BEAD0A204033}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {12A1931D-508E-41C2-BAC6-B68CC62A710E}
EndGlobalSection
EndGlobal

21
src/3rdParty/FastSignals/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 iSpring Solutions Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17
src/3rdParty/FastSignals/README.md vendored Normal file
View File

@@ -0,0 +1,17 @@
# FastSignals
Yet another C++ signals and slots library
* Works as drop-in replacement for Boost.Signals2 with the same API
* Has better performance and more compact binary code
* Thread-safe in most operations, including concurrent connects/disconnects/emits
* Implemented with compact, pure C++17 code
[![Build Status](https://travis-ci.org/ispringteam/FastSignals.svg?branch=master)](https://travis-ci.org/ispringteam/FastSignals)
[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)
## Documentation
* [Why FastSignals?](docs/why-fastsignals.md)
* [Simple Examples](docs/simple-examples.md)
* [Migration from Boost.Signals2](docs/migration-from-boost-signals2.md)

View File

@@ -0,0 +1,22 @@
# install GCC
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update -qq
sudo apt-get install -qq g++-8
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 90
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 90
sudo update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-8 90
g++ --version
# install cmake
CMAKE_VERSION=3.12.1
CMAKE_VERSION_DIR=v3.12
CMAKE_OS=Linux-x86_64
CMAKE_TAR=cmake-$CMAKE_VERSION-$CMAKE_OS.tar.gz
CMAKE_URL=http://www.cmake.org/files/$CMAKE_VERSION_DIR/$CMAKE_TAR
CMAKE_DIR=$(pwd)/cmake-$CMAKE_VERSION
wget --quiet $CMAKE_URL
mkdir -p $CMAKE_DIR
tar --strip-components=1 -xzf $CMAKE_TAR -C $CMAKE_DIR
export PATH=$CMAKE_DIR/bin:$PATH
cmake --version

View File

@@ -0,0 +1,39 @@
# В текущей версии CMake не может включить режим C++17 в некоторых компиляторах.
# Функция использует обходной манёвр.
function(custom_enable_cxx17 TARGET)
# Включаем C++17 везде, где CMake может.
target_compile_features(${TARGET} PUBLIC cxx_std_17)
# Включаем режим C++latest в Visual Studio
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set_target_properties(${TARGET} PROPERTIES COMPILE_FLAGS "/std:c++latest")
# Включаем компоновку с libc++, libc++experimental и pthread для Clang
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set_target_properties(${TARGET} PROPERTIES COMPILE_FLAGS "-stdlib=libc++ -pthread")
target_link_libraries(${TARGET} c++experimental pthread)
endif()
endfunction(custom_enable_cxx17)
# Функция добавляет цель-библиотеку.
function(custom_add_library_from_dir TARGET)
# Собираем файлы с текущего каталога
file(GLOB TARGET_SRC "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
add_library(${TARGET} ${TARGET_SRC})
endfunction()
# Функция добавляет цель исполняемого файла.
function(custom_add_executable_from_dir TARGET)
# Собираем файлы с текущего каталога
file(GLOB TARGET_SRC "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
add_executable(${TARGET} ${TARGET_SRC})
endfunction()
# Функция добавляет цель исполняемого файла, содержащего тесты библиотеки.
function(custom_add_test_from_dir TARGET LIBRARY)
custom_add_executable_from_dir(${TARGET})
# Добавляем путь к заголовку фреймворка Catch
target_include_directories(${TARGET} PRIVATE "${CMAKE_SOURCE_DIR}/libs/catch")
# Добавляем компоновку с проверяемой библиотекой
target_link_libraries(${TARGET} ${LIBRARY})
# Регистрируем исполняемый файл в CMake как набор тестов.
add_test(${TARGET} ${TARGET})
endfunction()

View File

@@ -0,0 +1,94 @@
# Function bind_weak
## Usage
* Use `is::signals::bind_weak` instead of `std::bind` to ensure that nothing happens if method called when binded object already destroyed
* Pass pointer to T class method as first argument, `shared_ptr<T>` or `weak_ptr<T>` as second argument
* Example: `bind_weak(&Document::save(), document, std::placeholders::_1)`, where `document` is a `weak_ptr<Document>` or `shared_ptr<Document>`
## Weak this idiom
The `is::signals::bind_weak(...)` function implements "weak this" idiom. This idiom helps to avoid dangling pointers and memory access wiolations in asynchronous and/or multithreaded programs.
In the following example, we use weak this idiom to avoid using dangling pointer wehn calling `print()` method of the `Enityt`:
```cpp
struct Entity : std::enable_shared_from_this<Entity>
{
int value = 42;
void print()
{
std::cout << "print called, num = " << value << std::endl;
}
std::function<void()> print_later()
{
// ! weak this idiom here !
auto weak_this = weak_from_this();
return [weak_this] {
if (auto shared_this = weak_this.lock())
{
shared_this->print();
}
};
}
};
int main()
{
auto entity = std::make_shared<Entity>();
auto print = entity->print_later();
// Prints OK.
print();
// Prints nothing - last shared_ptr to the Entity destroyed, so `weak_this.lock()` will return nullptr.
entity = nullptr;
print();
}
```
## Using bind_weak to avoid signal receiver lifetime issues
In the following example, `Entity::print()` method connected to the signal. Signal emmited once before and once after the `Entity` instance destroyed. However, no memory access violation happens: once `Entity` destoryed, no slot will be called because `bind_weak` doesn't call binded method if it cannot lock `std::weak_ptr` to binded object. The second `event()` expression just does nothing.
```cpp
#include "fastsignals/signal.h"
#include "fastsignals/bind_weak.h"
#include <iostream>
using VoidSignal = is::signals::signal<void()>;
using VoidSlot = VoidSignal::slot_type;
struct Entity : std::enable_shared_from_this<Entity>
{
int value = 42;
VoidSlot get_print_slot()
{
// Here is::signals::bind_weak() used instead of std::bind.
return is::signals::bind_weak(&Entity::print, weak_from_this());
}
void print()
{
std::cout << "print called, num = " << value << std::endl;
}
};
int main()
{
VoidSignal event;
auto entity = std::make_shared<Entity>();
event.connect(entity->get_print_slot());
// Here slot called - it prints `slot called, num = 42`
event();
entity = nullptr;
// Here nothing happens - no exception, no slot call.
event();
}
```

View File

@@ -0,0 +1,163 @@
# Migration from Boost.Signals2
This guide helps to migrate large codebase from Boost.Signals2 to `FastSignals` signals/slots library. It helps to solve known migration issues in the right way.
During migrations, you will probably face with following things:
* You code uses `boost::signals2::` namespace and `<boost/signals2.hpp>` header directly
* You code uses third-party headers included implicitly by the `<boost/signals2.hpp>` header
## Reasons migrate from Boost.Signals2 to FastSignals
FastSignals API mostly compatible with Boost.Signals2 - there are differences, and all differences has their reasons explained below.
Comparing to Boost.Signals2, FastSignals has following pros:
* FastSignals is not header-only - so binary code will be more compact
* FastSignals implemented using C++17 with variadic templates, `constexpr if` and other modern metaprogramming techniques - so it compiles faster and, again, binary code will be more compact
* FastSignals probably will faster than Boost.Signals2 for your codebase because with FastSignals you don't pay for things that you don't use, with one exception: you always pay for the multithreading support
## Step 1: Create header with aliases
## Step 2: Rebuild and fix compile errors
### 2.1 Add missing includes
Boost.Signals2 is header-only library. It includes a lot of STL/Boost stuff while FastSignals does not:
```cpp
#include <boost/signals2.hpp>
// Also includes std::map, boost::variant, boost::optional, etc.
// Compiled OK even without `#include <map>`!
std::map CreateMyMap();
```
With FastSignals, you must include headers like `<map>` manually. The following table shows which files should be included explicitly if you see compile erros after migration.
| Class | Header |
|--------------------|:--------------------------------------:|
| std::map | `#include <map>` |
| boost::variant | `#include <boost/variant/variant.hpp>` |
| boost::optional | `#include <boost/optional/optional.hpp>` |
| boost::scoped_ptr | `#include <boost/scoped_ptr.hpp>` |
| boost::noncopyable | `#include <boost/noncopyable.hpp>` |
| boost::bind | `#include <boost/bind.hpp>` |
| boost::function | `#include <boost/function.hpp>` |
If you just want to compile you code, you can add following includes in you `signals.h` header:
```cpp
// WARNING: [libfastsignals] we do not recommend to include following extra headers.
#include <map>
#include <boost/variant/variant.hpp>
#include <boost/optional/optional.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/noncopyable.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
```
### 2.2 Remove redundant returns for void signals
With Boost.Signals2, following code compiled without any warning:
```cpp
boost::signals2::signal<void()> event;
event.connect([] {
return true;
});
```
With FastSignals, slot cannot return non-void value when for `signal<void(...)>`. You must fix your code: just remove returns from your slots or add lambdas to wrap slot and ignore it result.
### 2.3 Replace track() and track_foreign() with bind_weak_ptr()
Boost.Signals2 [can track connected objects lifetype](https://www.boost.org/doc/libs/1_55_0/doc/html/signals2/tutorial.html#idp204830936) using `track(...)` and `track_foreign(...)` methods. In the following example `Entity` created with `make_shared`, and `Entity::get_print_slot()` creates slot function which tracks weak pointer to Entity:
```cpp
#include <boost/signals2.hpp>
#include <iostream>
#include <memory>
using VoidSignal = boost::signals2::signal<void()>;
using VoidSlot = VoidSignal::slot_type;
struct Entity : std::enable_shared_from_this<Entity>
{
int value = 42;
VoidSlot get_print_slot()
{
// Here track() tracks object itself.
return VoidSlot(std::bind(&Entity::print, this)).track_foreign(shared_from_this());
}
void print()
{
std::cout << "print called, num = " << value << std::endl;
}
};
int main()
{
VoidSignal event;
auto entity = std::make_shared<Entity>();
event.connect(entity->get_print_slot());
// Here slot called - it prints `print called, num = 42`
event();
entity = nullptr;
// This call does nothing.
event();
}
```
FastSignals uses another approach: `bind_weak` function:
```cpp
#include "fastsignals/bind_weak.h"
#include <iostream>
using VoidSignal = is::signals::signal<void()>;
using VoidSlot = VoidSignal::slot_type;
struct Entity : std::enable_shared_from_this<Entity>
{
int value = 42;
VoidSlot get_print_slot()
{
// Here is::signals::bind_weak() used instead of std::bind.
return is::signals::bind_weak(&Entity::print, weak_from_this());
}
void print()
{
std::cout << "print called, num = " << value << std::endl;
}
};
int main()
{
VoidSignal event;
auto entity = std::make_shared<Entity>();
event.connect(entity->get_print_slot());
// Here slot called - it prints `slot called, num = 42`
event();
entity = nullptr;
// Here nothing happens - no exception, no slot call.
event();
}
```
### FastSignals Differences in Result Combiners
## Step 3: Run Tests
Run all automated tests that you have (unit tests, integration tests, system tests, stress tests, benchmarks, UI tests).
Probably you will see no errors. If you see any, please report an issue.

View File

@@ -0,0 +1,55 @@
# Simple Examples
>If you are not familar with Boost.Signals2, please read [Boost.Signals2: Connections](https://theboostcpplibraries.com/boost.signals2-connections)
## Example with signal&lt;&gt; and connection
```cpp
// Creates signal and connects 1 slot, calls 2 times, disconnects, calls again.
// Outputs:
// 13
// 17
#include "libfastsignals/signal.h"
using namespace is::signals;
int main()
{
signal<void(int)> valueChanged;
connection conn;
conn = valueChanged.connect([](int value) {
cout << value << endl;
});
valueChanged(13);
valueChanged(17);
conn.disconnect();
valueChanged(42);
}
```
## Example with scoped_connection
```cpp
// Creates signal and connects 1 slot, calls 2 times, calls again after scoped_connection destroyed.
// - note: scoped_connection closes connection in destructor
// Outputs:
// 13
// 17
#include "libfastsignals/signal.h"
using namespace is::signals;
int main()
{
signal<void(int)> valueChanged;
{
scoped_connection conn;
conn = valueChanged.connect([](int value) {
cout << value << endl;
});
valueChanged(13);
valueChanged(17);
}
valueChanged(42);
}
```

View File

@@ -0,0 +1,40 @@
# Why FastSignals?
FastSignals is a C++17 signals/slots implementation which API is compatible with Boost.Signals2.
FastSignals pros:
* Faster than Boost.Signals2
* Has more compact binary code
* Has the same API as Boost.Signals2
FastSignals cons:
* Supports only C++17 compatible compilers: Visual Studio 2017, modern Clang, modern GCC
* Lacks a few rarely used features presented in Boost.Signals2
* No access to connection from slot with `signal::connect_extended` method
* No connected object tracking with `slot::track` method
* Use [bind_weak](bind_weak.md) instead
* No temporary signal blocking with `shared_connection_block` class
* Cannot disconnect equivalent slots since no `disconnect(slot)` function overload
* Any other API difference is a bug - please report it!
See also [Migration from Boost.Signals2](migration-from-boost-signals2.md).
## Benchmark results
Directory `tests/libfastsignals_bench` contains simple benchmark with compares two signal/slot implementations:
* Boost.Signals2
* libfastsignals
Benchmark compairs performance when signal emitted frequently with 0, 1 and 8 active connections. In these cases libfastsignals is 3-6 times faster.
```
*** Results:
measure emit_boost emit_fastsignals
emit_boost/0 1.00 3.00
emit_boost/1 1.00 5.76
emit_boost/8 1.00 3.70
***
```

View File

@@ -0,0 +1,5 @@
file(GLOB LIBFASTSIGNALS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h")
add_library(libfastsignals ${LIBFASTSIGNALS_SRC})
custom_enable_cxx17(libfastsignals)
target_include_directories(libfastsignals INTERFACE "${CMAKE_SOURCE_DIR}")

View File

@@ -0,0 +1,75 @@
#pragma once
namespace is::signals
{
namespace detail
{
template <class ReturnType, class ClassType, bool AddConst, class... Args>
struct weak_binder
{
using ConstMethodType = ReturnType (ClassType::*)(Args... args) const;
using NonConstMethodType = ReturnType (ClassType::*)(Args... args);
using MethodType = std::conditional_t<AddConst, ConstMethodType, NonConstMethodType>;
using WeakPtrType = std::weak_ptr<ClassType>;
weak_binder(MethodType pMethod, WeakPtrType&& pObject)
: m_pMethod(pMethod)
, m_pObject(pObject)
{
}
ReturnType operator()(Args... args) const
{
if (auto pThis = m_pObject.lock())
{
return (pThis.get()->*m_pMethod)(std::forward<Args>(args)...);
}
return ReturnType();
}
MethodType m_pMethod;
WeakPtrType m_pObject;
};
} // namespace detail
/// Weak this binding of non-const methods.
template <typename ReturnType, typename ClassType, typename... Params, typename... Args>
decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args), std::shared_ptr<ClassType> const& pThis, Args... args)
{
using weak_binder_alias = detail::weak_binder<ReturnType, ClassType, false, Params...>;
weak_binder_alias invoker(memberFn, std::weak_ptr<ClassType>(pThis));
return std::bind(invoker, args...);
}
/// Weak this binding of const methods.
template <typename ReturnType, typename ClassType, typename... Params, typename... Args>
decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args) const, std::shared_ptr<ClassType> const& pThis, Args... args)
{
using weak_binder_alias = detail::weak_binder<ReturnType, ClassType, true, Params...>;
weak_binder_alias invoker(memberFn, std::weak_ptr<ClassType>(pThis));
return std::bind(invoker, args...);
}
/// Weak this binding of non-const methods.
template <typename ReturnType, typename ClassType, typename... Params, typename... Args>
decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args), std::weak_ptr<ClassType> pThis, Args... args)
{
using weak_binder_alias = detail::weak_binder<ReturnType, ClassType, false, Params...>;
weak_binder_alias invoker(memberFn, std::move(pThis));
return std::bind(invoker, args...);
}
/// Weak this binding of const methods.
template <typename ReturnType, typename ClassType, typename... Params, typename... Args>
decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args) const, std::weak_ptr<ClassType> pThis, Args... args)
{
using weak_binder_alias = detail::weak_binder<ReturnType, ClassType, true, Params...>;
weak_binder_alias invoker(memberFn, std::move(pThis));
return std::bind(invoker, args...);
}
} // namespace is::signals

View File

@@ -0,0 +1,40 @@
#pragma once
#include <optional>
namespace is::signals
{
/**
* This results combiner reduces results collection into last value of this collection.
* In other words, it keeps only result of the last slot call.
*/
template <class T>
class optional_last_value
{
public:
using result_type = std::optional<T>;
template <class TRef>
void operator()(TRef&& value)
{
m_result = std::forward<TRef>(value);
}
result_type get_value() const
{
return m_result;
}
private:
result_type m_result = {};
};
template <>
class optional_last_value<void>
{
public:
using result_type = void;
};
} // namespace is::signals

View File

@@ -0,0 +1,114 @@
#pragma once
#include "signal_impl.h"
namespace is::signals
{
// Connection keeps link between signal and slot and can disconnect them.
// Disconnect operation is thread-safe: any thread can disconnect while
// slots called on other thread.
// This class itself is not thread-safe: you can't use the same connection
// object from different threads at the same time.
class connection
{
public:
connection() noexcept;
explicit connection(detail::signal_impl_weak_ptr storage, uint64_t id) noexcept;
connection(const connection& other) noexcept;
connection& operator=(const connection& other) noexcept;
connection(connection&& other) noexcept;
connection& operator=(connection&& other) noexcept;
bool connected() const noexcept;
void disconnect() noexcept;
protected:
detail::signal_impl_weak_ptr m_storage;
uint64_t m_id = 0;
};
// Connection class that supports blocking callback execution
class advanced_connection : public connection
{
public:
struct advanced_connection_impl
{
void block() noexcept;
void unblock() noexcept;
bool is_blocked() const noexcept;
private:
std::atomic<int> m_blockCounter = ATOMIC_VAR_INIT(0);
};
using impl_ptr = std::shared_ptr<advanced_connection_impl>;
advanced_connection() noexcept;
explicit advanced_connection(connection&& conn, impl_ptr&& impl) noexcept;
advanced_connection(const advanced_connection&) noexcept;
advanced_connection& operator=(const advanced_connection&) noexcept;
advanced_connection(advanced_connection&& other) noexcept;
advanced_connection& operator=(advanced_connection&& other) noexcept;
protected:
impl_ptr m_impl;
};
// Blocks advanced connection, so its callback will not be executed
class shared_connection_block
{
public:
shared_connection_block(const advanced_connection& connection = advanced_connection(), bool initially_blocked = true) noexcept;
shared_connection_block(const shared_connection_block& other) noexcept;
shared_connection_block(shared_connection_block&& other) noexcept;
shared_connection_block& operator=(const shared_connection_block& other) noexcept;
shared_connection_block& operator=(shared_connection_block&& other) noexcept;
~shared_connection_block();
void block() noexcept;
void unblock() noexcept;
bool blocking() const noexcept;
private:
void increment_if_blocked() const noexcept;
std::weak_ptr<advanced_connection::advanced_connection_impl> m_connection;
std::atomic<bool> m_blocked = ATOMIC_VAR_INIT(false);
};
// Scoped connection keeps link between signal and slot and disconnects them in destructor.
// Scoped connection is movable, but not copyable.
class scoped_connection : public connection
{
public:
scoped_connection() noexcept;
scoped_connection(const connection& conn) noexcept;
scoped_connection(connection&& conn) noexcept;
scoped_connection(const advanced_connection& conn) = delete;
scoped_connection(advanced_connection&& conn) noexcept = delete;
scoped_connection(const scoped_connection&) = delete;
scoped_connection& operator=(const scoped_connection&) = delete;
scoped_connection(scoped_connection&& other) noexcept;
scoped_connection& operator=(scoped_connection&& other) noexcept;
~scoped_connection();
connection release() noexcept;
};
// scoped connection for advanced connections
class advanced_scoped_connection : public advanced_connection
{
public:
advanced_scoped_connection() noexcept;
advanced_scoped_connection(const advanced_connection& conn) noexcept;
advanced_scoped_connection(advanced_connection&& conn) noexcept;
advanced_scoped_connection(const advanced_scoped_connection&) = delete;
advanced_scoped_connection& operator=(const advanced_scoped_connection&) = delete;
advanced_scoped_connection(advanced_scoped_connection&& other) noexcept;
advanced_scoped_connection& operator=(advanced_scoped_connection&& other) noexcept;
~advanced_scoped_connection();
advanced_connection release() noexcept;
};
} // namespace is::signals

View File

@@ -0,0 +1,54 @@
#pragma once
#include "function_detail.h"
namespace is::signals
{
// Derive your class from not_directly_callable to prevent function from wrapping it using its template constructor
// Useful if your class provides custom operator for casting to function
struct not_directly_callable
{
};
template <class Fn, class Function, class Return, class... Arguments>
using enable_if_callable_t = typename std::enable_if_t<
!std::is_same_v<std::decay_t<Fn>, Function> && !std::is_base_of_v<not_directly_callable, std::decay_t<Fn>> && std::is_same_v<std::invoke_result_t<Fn, Arguments...>, Return>>;
template <class Signature>
class function;
// Compact function class - causes minimal code bloat when compiled.
// Replaces std::function in this library.
template <class Return, class... Arguments>
class function<Return(Arguments...)>
{
public:
function() = default;
function(const function& other) = default;
function(function&& other) noexcept = default;
function& operator=(const function& other) = default;
function& operator=(function&& other) noexcept = default;
template <class Fn, typename = enable_if_callable_t<Fn, function<Return(Arguments...)>, Return, Arguments...>>
function(Fn&& function) noexcept(detail::is_noexcept_packed_function_init<Fn, Return, Arguments...>)
{
m_packed.init<Fn, Return, Arguments...>(std::forward<Fn>(function));
}
Return operator()(Arguments&&... args) const
{
auto& proxy = m_packed.get<Return(Arguments...)>();
return proxy(std::forward<Arguments>(args)...);
}
detail::packed_function release() noexcept
{
return std::move(m_packed);
}
private:
detail::packed_function m_packed;
};
} // namespace is::signals

View File

@@ -0,0 +1,163 @@
#pragma once
#include <cassert>
#include <cstdint>
#include <type_traits>
#include <utility>
namespace is::signals::detail
{
/// Buffer for callable object in-place construction,
/// helps to implement Small Buffer Optimization.
static constexpr size_t inplace_buffer_size = (sizeof(int) == sizeof(void*) ? 8 : 6) * sizeof(void*);
/// Structure that has size enough to keep type "T" including vtable.
template <class T>
struct type_container
{
T data;
};
/// Type that has enough space to keep type "T" (including vtable) with suitable alignment.
using function_buffer_t = std::aligned_storage_t<inplace_buffer_size>;
/// Constantly is true if callable fits function buffer, false otherwise.
template <class T>
inline constexpr bool fits_inplace_buffer = (sizeof(type_container<T>) <= inplace_buffer_size);
// clang-format off
/// Constantly is true if callable fits function buffer and can be safely moved, false otherwise
template <class T>
inline constexpr bool can_use_inplace_buffer =
fits_inplace_buffer<T> &&
std::is_nothrow_move_constructible_v<T>;
// clang format on
/// Type that is suitable to keep copy of callable object.
/// - equal to pointer-to-function if Callable is pointer-to-function
/// - otherwise removes const/volatile and references to allow copying callable.
template <class Callable>
using callable_copy_t = std::conditional_t<std::is_function_v<std::remove_reference_t<Callable>>,
Callable,
std::remove_cv_t<std::remove_reference_t<Callable>>>;
class base_function_proxy
{
public:
virtual ~base_function_proxy() = default;
virtual base_function_proxy* clone(void* buffer) const = 0;
virtual base_function_proxy* move(void* buffer) noexcept = 0;
};
template <class Signature>
class function_proxy;
template <class Return, class... Arguments>
class function_proxy<Return(Arguments...)> : public base_function_proxy
{
public:
virtual Return operator()(Arguments&&...) = 0;
};
template <class Callable, class Return, class... Arguments>
class function_proxy_impl final : public function_proxy<Return(Arguments...)>
{
public:
// If you see this error, probably your function returns value and you're trying to
// connect it to `signal<void(...)>`. Just remove return value from callback.
static_assert(std::is_same_v<std::invoke_result_t<Callable, Arguments...>, Return>,
"cannot construct function<> class from callable object with different return type");
template <class FunctionObject>
explicit function_proxy_impl(FunctionObject&& function)
: m_callable(std::forward<FunctionObject>(function))
{
}
Return operator()(Arguments&&... args) final
{
return m_callable(std::forward<Arguments>(args)...);
}
base_function_proxy* clone(void* buffer) const final
{
if constexpr (can_use_inplace_buffer<function_proxy_impl>)
{
return new (buffer) function_proxy_impl(*this);
}
else
{
(void)buffer;
return new function_proxy_impl(*this);
}
}
base_function_proxy* move(void* buffer) noexcept final
{
if constexpr (can_use_inplace_buffer<function_proxy_impl>)
{
base_function_proxy* moved = new (buffer) function_proxy_impl(std::move(*this));
this->~function_proxy_impl();
return moved;
}
else
{
(void)buffer;
return this;
}
}
private:
callable_copy_t<Callable> m_callable;
};
template <class Fn, class Return, class... Arguments>
inline constexpr bool is_noexcept_packed_function_init = can_use_inplace_buffer<function_proxy_impl<Fn, Return, Arguments...>>;
class packed_function final
{
public:
packed_function() = default;
packed_function(packed_function&& other) noexcept;
packed_function(const packed_function& other);
packed_function& operator=(packed_function&& other) noexcept;
packed_function& operator=(const packed_function& other);
~packed_function() noexcept;
// Initializes packed function.
// Cannot be called without reset().
template <class Callable, class Return, class... Arguments>
void init(Callable&& function) noexcept(is_noexcept_packed_function_init<Callable, Return, Arguments...>)
{
using proxy_t = function_proxy_impl<Callable, Return, Arguments...>;
assert(m_proxy == nullptr);
if constexpr (can_use_inplace_buffer<proxy_t>)
{
m_proxy = new (&m_buffer) proxy_t{ std::forward<Callable>(function) };
}
else
{
m_proxy = new proxy_t{ std::forward<Callable>(function) };
}
}
template <class Signature>
function_proxy<Signature>& get() const
{
return static_cast<function_proxy<Signature>&>(unwrap());
}
void reset() noexcept;
private:
base_function_proxy* move_proxy_from(packed_function&& other) noexcept;
base_function_proxy* clone_proxy_from(const packed_function &other);
base_function_proxy& unwrap() const;
bool is_buffer_allocated() const noexcept;
function_buffer_t m_buffer[1] = {};
base_function_proxy* m_proxy = nullptr;
};
} // namespace is::signals::detail

View File

@@ -0,0 +1,31 @@
#pragma once
#if defined(_MSC_VER)
# if defined(__clang__)
# if defined(_DEBUG) && defined(_WIN64)
# pragma comment(lib, "libfastsignalsd-llvm-x64.lib")
# elif defined(_DEBUG)
# pragma comment(lib, "libfastsignalsd-llvm-x32.lib")
# elif defined(_WIN64)
# pragma comment(lib, "libfastsignals-llvm-x64.lib")
# else
# pragma comment(lib, "libfastsignals-llvm-x32.lib")
# endif
# elif _MSC_VER <= 1900
# error this library needs Visual Studio 2017 and higher
# elif _MSC_VER < 2000
# if defined(_DEBUG) && defined(_WIN64)
# pragma comment(lib, "libfastsignalsd-v141-x64.lib")
# elif defined(_DEBUG)
# pragma comment(lib, "libfastsignalsd-v141-x32.lib")
# elif defined(_WIN64)
# pragma comment(lib, "libfastsignals-v141-x64.lib")
# else
# pragma comment(lib, "libfastsignals-v141-x32.lib")
# endif
# else
# error unknown Visual Studio version, auto-linking setup failed
# endif
#endif

View File

@@ -0,0 +1,153 @@
#pragma once
#include "combiners.h"
#include "connection.h"
#include "function.h"
#include "signal_impl.h"
#include "type_traits.h"
#include <type_traits>
#if defined(_MSC_VER)
# include "msvc_autolink.h"
#endif
namespace is::signals
{
template <class Signature, template <class T> class Combiner = optional_last_value>
class signal;
struct advanced_tag
{
};
/// Signal allows to fire events to many subscribers (slots).
/// In other words, it implements one-to-many relation between event and listeners.
/// Signal implements observable object from Observable pattern.
template <class Return, class... Arguments, template <class T> class Combiner>
class signal<Return(Arguments...), Combiner> : private not_directly_callable
{
public:
using signature_type = Return(signal_arg_t<Arguments>...);
using slot_type = function<signature_type>;
using combiner_type = Combiner<Return>;
using result_type = typename combiner_type::result_type;
signal()
: m_slots(std::make_shared<detail::signal_impl>())
{
}
/// No copy construction
signal(const signal&) = delete;
/// Moves signal from other. Any operations on other except destruction, move, and swap are invalid
signal(signal&& other) = default;
/// No copy assignment
signal& operator=(const signal&) = delete;
/// Moves signal from other. Any operations on other except destruction, move, and swap are invalid
signal& operator=(signal&& other) = default;
/**
* connect(slot) method subscribes slot to signal emission event.
* Each time you call signal as functor, all slots are also called with given arguments.
* @returns connection - object which manages signal-slot connection lifetime
*/
connection connect(slot_type slot)
{
const uint64_t id = m_slots->add(slot.release());
return connection(m_slots, id);
}
/**
* connect(slot, advanced_tag) method subscribes slot to signal emission event with the ability to temporarily block slot execution
* Each time you call signal as functor, all non-blocked slots are also called with given arguments.
* You can temporarily block slot execution using shared_connection_block
* @returns advanced_connection - object which manages signal-slot connection lifetime
*/
advanced_connection connect(slot_type slot, advanced_tag)
{
static_assert(std::is_void_v<Return>, "Advanced connect can only be used with slots returning void (implementation limitation)");
auto conn_impl = std::make_shared<advanced_connection::advanced_connection_impl>();
slot_type slot_impl = [this, slot, weak_conn_impl = std::weak_ptr(conn_impl)](signal_arg_t<Arguments>... args) {
auto conn_impl = weak_conn_impl.lock();
if (!conn_impl || !conn_impl->is_blocked())
{
slot(args...);
}
};
auto conn = connect(std::move(slot_impl));
return advanced_connection(std::move(conn), std::move(conn_impl));
}
/**
* disconnect_all_slots() method disconnects all slots from signal emission event.
*/
void disconnect_all_slots() noexcept
{
m_slots->remove_all();
}
/**
* num_slots() method returns number of slots attached to this singal
*/
[[nodiscard]] std::size_t num_slots() const noexcept
{
return m_slots->count();
}
/**
* empty() method returns true if signal has any slots attached
*/
[[nodiscard]] bool empty() const noexcept
{
return m_slots->count() == 0;
}
/**
* operator(args...) calls all slots connected to this signal.
* Logically, it fires signal emission event.
*/
result_type operator()(signal_arg_t<Arguments>... args) const
{
return detail::signal_impl_ptr(m_slots)->invoke<combiner_type, result_type, signature_type, signal_arg_t<Arguments>...>(args...);
}
void swap(signal& other) noexcept
{
m_slots.swap(other.m_slots);
}
/**
* Allows using signals as slots for another signal
*/
operator slot_type() const noexcept
{
return [weakSlots = detail::signal_impl_weak_ptr(m_slots)](signal_arg_t<Arguments>... args) {
if (auto slots = weakSlots.lock())
{
return slots->invoke<combiner_type, result_type, signature_type, signal_arg_t<Arguments>...>(args...);
}
};
}
private:
detail::signal_impl_ptr m_slots;
};
} // namespace is::signals
namespace std
{
// free swap function, findable by ADL
template <class Signature, template <class T> class Combiner>
void swap(
::is::signals::signal<Signature, Combiner>& sig1,
::is::signals::signal<Signature, Combiner>& sig2)
{
sig1.swap(sig2);
}
} // namespace std

View File

@@ -0,0 +1,59 @@
#pragma once
#include "function_detail.h"
#include "spin_mutex.h"
#include <memory>
#include <vector>
namespace is::signals::detail
{
class signal_impl
{
public:
uint64_t add(packed_function fn);
void remove(uint64_t id) noexcept;
void remove_all() noexcept;
size_t count() const noexcept;
template <class Combiner, class Result, class Signature, class... Args>
Result invoke(Args... args) const
{
packed_function slot;
size_t slotIndex = 0;
uint64_t slotId = 1;
if constexpr (std::is_same_v<Result, void>)
{
while (get_next_slot(slot, slotIndex, slotId))
{
slot.get<Signature>()(std::forward<Args>(args)...);
}
}
else
{
Combiner combiner;
while (get_next_slot(slot, slotIndex, slotId))
{
combiner(slot.get<Signature>()(std::forward<Args>(args)...));
}
return combiner.get_value();
}
}
private:
bool get_next_slot(packed_function& slot, size_t& expectedIndex, uint64_t& nextId) const;
mutable spin_mutex m_mutex;
std::vector<packed_function> m_functions;
std::vector<uint64_t> m_ids;
uint64_t m_nextId = 1;
};
using signal_impl_ptr = std::shared_ptr<signal_impl>;
using signal_impl_weak_ptr = std::weak_ptr<signal_impl>;
} // namespace is::signals::detail

View File

@@ -0,0 +1,38 @@
#pragma once
#include <atomic>
namespace is::signals::detail
{
class spin_mutex
{
public:
spin_mutex() = default;
spin_mutex(const spin_mutex&) = delete;
spin_mutex& operator=(const spin_mutex&) = delete;
spin_mutex(spin_mutex&&) = delete;
spin_mutex& operator=(spin_mutex&&) = delete;
inline bool try_lock() noexcept
{
return !m_busy.test_and_set(std::memory_order_acquire);
}
inline void lock() noexcept
{
while (!try_lock())
{
/* do nothing */;
}
}
inline void unlock() noexcept
{
m_busy.clear(std::memory_order_release);
}
private:
std::atomic_flag m_busy = ATOMIC_FLAG_INIT;
};
} // namespace is::signals::detail

View File

@@ -0,0 +1,24 @@
#pragma once
namespace is::signals
{
namespace detail
{
template <typename T>
struct signal_arg
{
using type = const T&;
};
template <typename U>
struct signal_arg<U&>
{
using type = U&;
};
} // namespace detail
template <typename T>
using signal_arg_t = typename detail::signal_arg<T>::type;
} // namespace is::signals

View File

@@ -0,0 +1,200 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{32BD918F-EDBC-4057-A033-10DC361DA4A0}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>FastSignals</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\libfastsignals_build_options.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\libfastsignals_build_options.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\libfastsignals_build_options.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\libfastsignals_build_options.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
<TargetName>$(ProjectName)$(DebugSuffixOpt)-$(PlatformToolset)$(PlatformSuffix)</TargetName>
<RunCodeAnalysis>true</RunCodeAnalysis>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(SolutionDir)3rdparty;$(IncludePath)</IncludePath>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
<TargetName>$(ProjectName)$(DebugSuffixOpt)-$(PlatformToolset)$(PlatformSuffix)</TargetName>
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis>true</RunCodeAnalysis>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
<TargetName>$(ProjectName)$(DebugSuffixOpt)-$(PlatformToolset)$(PlatformSuffix)</TargetName>
<RunCodeAnalysis>true</RunCodeAnalysis>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>$(SolutionDir)3rdparty;$(IncludePath)</IncludePath>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
<TargetName>$(ProjectName)$(DebugSuffixOpt)-$(PlatformToolset)$(PlatformSuffix)</TargetName>
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis>true</RunCodeAnalysis>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<EnablePREfast>true</EnablePREfast>
<DisableSpecificWarnings>26495;26439;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<EnablePREfast>true</EnablePREfast>
<DisableSpecificWarnings>26495;26439;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<EnablePREfast>true</EnablePREfast>
<DisableSpecificWarnings>26495;26439;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<EnablePREfast>true</EnablePREfast>
<DisableSpecificWarnings>26495;26439;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="include/combiners.h" />
<ClInclude Include="include/connection.h" />
<ClInclude Include="include/function.h" />
<ClInclude Include="include/function_detail.h" />
<ClInclude Include="include/signal.h" />
<ClInclude Include="include/signal_impl.h" />
<ClInclude Include="include/spin_mutex.h" />
<ClInclude Include="include/type_traits.h" />
<ClInclude Include="include\bind_weak.h" />
<ClInclude Include="include\msvc_autolink.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\connection.cpp" />
<ClCompile Include="src\function_detail.cpp" />
<ClCompile Include="src\signal_impl.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClInclude Include="include/combiners.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include/connection.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include/function.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include/function_detail.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include/signal.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include/signal_impl.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include/spin_mutex.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include/type_traits.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include\msvc_autolink.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include\bind_weak.h">
<Filter>include</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="include">
<UniqueIdentifier>{474d3307-8dbe-47d6-a12f-35f944912d9d}</UniqueIdentifier>
</Filter>
<Filter Include="src">
<UniqueIdentifier>{ac074187-2f8f-44b9-a170-24568deb06e6}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\function_detail.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="src\signal_impl.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="src\connection.cpp">
<Filter>src</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,251 @@
#include "../include/connection.h"
namespace is::signals
{
namespace
{
auto get_advanced_connection_impl(const advanced_connection& connection) noexcept
{
struct advanced_connection_impl_getter : private advanced_connection
{
advanced_connection_impl_getter(const advanced_connection& connection) noexcept
: advanced_connection(connection)
{
}
using advanced_connection::m_impl;
};
return advanced_connection_impl_getter(connection).m_impl;
}
} // namespace
connection::connection(connection&& other) noexcept
: m_storage(other.m_storage)
, m_id(other.m_id)
{
other.m_storage.reset();
other.m_id = 0;
}
connection::connection(detail::signal_impl_weak_ptr storage, uint64_t id) noexcept
: m_storage(std::move(storage))
, m_id(id)
{
}
connection::connection() noexcept = default;
connection::connection(const connection& other) noexcept = default;
connection& connection::operator=(connection&& other) noexcept
{
m_storage = other.m_storage;
m_id = other.m_id;
other.m_storage.reset();
other.m_id = 0;
return *this;
}
connection& connection::operator=(const connection& other) noexcept = default;
bool connection::connected() const noexcept
{
return (m_id != 0);
}
void connection::disconnect() noexcept
{
if (auto storage = m_storage.lock())
{
storage->remove(m_id);
m_storage.reset();
}
m_id = 0;
}
scoped_connection::scoped_connection(connection&& conn) noexcept
: connection(std::move(conn))
{
}
scoped_connection::scoped_connection(const connection& conn) noexcept
: connection(conn)
{
}
scoped_connection::scoped_connection() noexcept = default;
scoped_connection::scoped_connection(scoped_connection&& other) noexcept = default;
scoped_connection& scoped_connection::operator=(scoped_connection&& other) noexcept
{
disconnect();
static_cast<connection&>(*this) = std::move(other);
return *this;
}
scoped_connection::~scoped_connection()
{
disconnect();
}
connection scoped_connection::release() noexcept
{
connection conn = std::move(static_cast<connection&>(*this));
return conn;
}
bool advanced_connection::advanced_connection_impl::is_blocked() const noexcept
{
return m_blockCounter.load(std::memory_order_acquire) != 0;
}
void advanced_connection::advanced_connection_impl::block() noexcept
{
++m_blockCounter;
}
void advanced_connection::advanced_connection_impl::unblock() noexcept
{
--m_blockCounter;
}
advanced_connection::advanced_connection() noexcept = default;
advanced_connection::advanced_connection(connection&& conn, impl_ptr&& impl) noexcept
: connection(std::move(conn))
, m_impl(std::move(impl))
{
}
advanced_connection::advanced_connection(const advanced_connection&) noexcept = default;
advanced_connection::advanced_connection(advanced_connection&& other) noexcept = default;
advanced_connection& advanced_connection::operator=(const advanced_connection&) noexcept = default;
advanced_connection& advanced_connection::operator=(advanced_connection&& other) noexcept = default;
shared_connection_block::shared_connection_block(const advanced_connection& connection, bool initially_blocked) noexcept
: m_connection(get_advanced_connection_impl(connection))
{
if (initially_blocked)
{
block();
}
}
shared_connection_block::shared_connection_block(const shared_connection_block& other) noexcept
: m_connection(other.m_connection)
, m_blocked(other.m_blocked.load(std::memory_order_acquire))
{
increment_if_blocked();
}
shared_connection_block::shared_connection_block(shared_connection_block&& other) noexcept
: m_connection(other.m_connection)
, m_blocked(other.m_blocked.load(std::memory_order_acquire))
{
other.m_connection.reset();
other.m_blocked.store(false, std::memory_order_release);
}
shared_connection_block& shared_connection_block::operator=(const shared_connection_block& other) noexcept
{
if (&other != this)
{
unblock();
m_connection = other.m_connection;
m_blocked = other.m_blocked.load(std::memory_order_acquire);
increment_if_blocked();
}
return *this;
}
shared_connection_block& shared_connection_block::operator=(shared_connection_block&& other) noexcept
{
if (&other != this)
{
unblock();
m_connection = other.m_connection;
m_blocked = other.m_blocked.load(std::memory_order_acquire);
other.m_connection.reset();
other.m_blocked.store(false, std::memory_order_release);
}
return *this;
}
shared_connection_block::~shared_connection_block()
{
unblock();
}
void shared_connection_block::block() noexcept
{
bool blocked = false;
if (m_blocked.compare_exchange_strong(blocked, true, std::memory_order_acq_rel, std::memory_order_relaxed))
{
if (auto connection = m_connection.lock())
{
connection->block();
}
}
}
void shared_connection_block::unblock() noexcept
{
bool blocked = true;
if (m_blocked.compare_exchange_strong(blocked, false, std::memory_order_acq_rel, std::memory_order_relaxed))
{
if (auto connection = m_connection.lock())
{
connection->unblock();
}
}
}
bool shared_connection_block::blocking() const noexcept
{
return m_blocked;
}
void shared_connection_block::increment_if_blocked() const noexcept
{
if (m_blocked)
{
if (auto connection = m_connection.lock())
{
connection->block();
}
}
}
advanced_scoped_connection::advanced_scoped_connection() noexcept = default;
advanced_scoped_connection::advanced_scoped_connection(const advanced_connection& conn) noexcept
: advanced_connection(conn)
{
}
advanced_scoped_connection::advanced_scoped_connection(advanced_connection&& conn) noexcept
: advanced_connection(std::move(conn))
{
}
advanced_scoped_connection::advanced_scoped_connection(advanced_scoped_connection&& other) noexcept = default;
advanced_scoped_connection& advanced_scoped_connection::operator=(advanced_scoped_connection&& other) noexcept = default;
advanced_scoped_connection::~advanced_scoped_connection()
{
disconnect();
}
advanced_connection advanced_scoped_connection::release() noexcept
{
advanced_connection conn = std::move(static_cast<advanced_connection&>(*this));
return conn;
}
} // namespace is::signals

View File

@@ -0,0 +1,96 @@
#include "../include/function_detail.h"
#include <cstddef>
#include <functional>
namespace is::signals::detail
{
packed_function::packed_function(packed_function&& other) noexcept
: m_proxy(move_proxy_from(std::move(other)))
{
}
packed_function::packed_function(const packed_function& other)
: m_proxy(clone_proxy_from(other))
{
}
packed_function& packed_function::operator=(packed_function&& other) noexcept
{
assert(this != &other);
reset();
m_proxy = move_proxy_from(std::move(other));
return *this;
}
base_function_proxy* packed_function::move_proxy_from(packed_function&& other) noexcept
{
auto proxy = other.m_proxy ? other.m_proxy->move(&m_buffer) : nullptr;
other.m_proxy = nullptr;
return proxy;
}
base_function_proxy* packed_function::clone_proxy_from(const packed_function& other)
{
return other.m_proxy ? other.m_proxy->clone(&m_buffer) : nullptr;
}
packed_function& packed_function::operator=(const packed_function& other)
{
if (this != &other)
{
if (other.is_buffer_allocated() && is_buffer_allocated())
{
// "This" and "other" are using SBO. Safe assignment must use copy+move
*this = packed_function(other);
}
else
{
// Buffer is used either by "this" or by "other" or not used at all.
// If this uses buffer then other's proxy is null or allocated on heap, so clone won't overwrite buffer
// If this uses heap or null then other's proxy can safely use buffer because reset() won't access buffer
auto newProxy = clone_proxy_from(other);
reset();
m_proxy = newProxy;
}
}
return *this;
}
packed_function::~packed_function() noexcept
{
reset();
}
void packed_function::reset() noexcept
{
if (m_proxy != nullptr)
{
if (is_buffer_allocated())
{
m_proxy->~base_function_proxy();
}
else
{
delete m_proxy;
}
m_proxy = nullptr;
}
}
base_function_proxy& packed_function::unwrap() const
{
if (m_proxy == nullptr)
{
throw std::bad_function_call();
}
return *m_proxy;
}
bool packed_function::is_buffer_allocated() const noexcept
{
return std::less_equal<const void*>()(&m_buffer[0], m_proxy)
&& std::less<const void*>()(m_proxy, &m_buffer[1]);
}
} // namespace is::signals::detail

View File

@@ -0,0 +1,84 @@
#include "../include/signal_impl.h"
#include <algorithm>
#include <mutex>
namespace is::signals::detail
{
uint64_t signal_impl::add(packed_function fn)
{
std::lock_guard lock(m_mutex);
m_functions.emplace_back(std::move(fn));
try
{
m_ids.emplace_back(m_nextId);
}
catch (const std::bad_alloc& /*e*/)
{
// Remove function since we failed to add its id
m_functions.pop_back();
throw;
}
return m_nextId++;
}
void signal_impl::remove(uint64_t id) noexcept
{
std::lock_guard lock(m_mutex);
// We use binary search because ids array is always sorted.
auto it = std::lower_bound(m_ids.begin(), m_ids.end(), id);
if (it != m_ids.end() && *it == id)
{
size_t i = std::distance(m_ids.begin(), it);
m_ids.erase(m_ids.begin() + i);
m_functions.erase(m_functions.begin() + i);
}
}
void signal_impl::remove_all() noexcept
{
std::lock_guard lock(m_mutex);
m_functions.clear();
m_ids.clear();
}
bool signal_impl::get_next_slot(packed_function& slot, size_t& expectedIndex, uint64_t& nextId) const
{
// Slots always arranged by ID, so we can use a simple algorithm which avoids races:
// - on each step find first slot with ID >= slotId
// - after each call increment slotId
std::lock_guard lock(m_mutex);
// Avoid binary search if next slot wasn't moved between mutex locks.
if (expectedIndex >= m_ids.size() || m_ids[expectedIndex] != nextId)
{
auto it = (nextId < m_nextId)
? std::lower_bound(m_ids.cbegin(), m_ids.cend(), nextId)
: m_ids.end();
if (it == m_ids.end())
{
return false;
}
expectedIndex = std::distance(m_ids.cbegin(), it);
}
slot.reset();
slot = m_functions[expectedIndex];
nextId = (expectedIndex + 1 < m_ids.size()) ? m_ids[expectedIndex + 1] : m_ids[expectedIndex] + 1;
++expectedIndex;
return true;
}
size_t signal_impl::count() const noexcept
{
std::lock_guard lock(m_mutex);
return m_functions.size();
}
} // namespace is::signals::detail

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros">
<!--debug suffix-->
<DebugSuffixOpt Condition="$(Configuration.StartsWith('Debug'))">d</DebugSuffixOpt>
<DebugSuffixOpt Condition="$(Configuration.StartsWith('Release'))">
</DebugSuffixOpt>
<PlatformSuffix Condition="'$(Platform)'=='Win32'">-x32</PlatformSuffix>
<PlatformSuffix Condition="'$(Platform)'=='x64'">-x64</PlatformSuffix>
</PropertyGroup>
<PropertyGroup />
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<WholeProgramOptimization>false</WholeProgramOptimization>
</ClCompile>
<Link>
<LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<WarningLevel>Level4</WarningLevel>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup />
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
custom_add_test_from_dir(libfastsignals_stress_tests libfastsignals)

View File

@@ -0,0 +1,180 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{751DC150-1907-4D9F-8566-AA4E24FDFA64}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>libfastsignalsstresstests</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\libfastsignals_build_options.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\libfastsignals_build_options.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\libfastsignals_build_options.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\libfastsignals_build_options.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>$(SolutionDir);$(SolutionDir)tests;$(IncludePath)</IncludePath>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(SolutionDir);$(SolutionDir)tests;$(IncludePath)</IncludePath>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(SolutionDir);$(SolutionDir)tests;$(IncludePath)</IncludePath>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>$(SolutionDir);$(SolutionDir)tests;$(IncludePath)</IncludePath>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="signal_stress_tests.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\libfastsignals\libfastsignals.vcxproj">
<Project>{32bd918f-edbc-4057-a033-10dc361da4a0}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="signal_stress_tests.cpp" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"

View File

@@ -0,0 +1,123 @@
#include "catch2/catch.hpp"
#include "libfastsignals/include/signal.h"
#include <array>
#include <mutex>
#include <random>
#include <vector>
using namespace is::signals;
namespace
{
using string_signal = signal<void(std::string)>;
using string_slot = string_signal::slot_type;
using void_signal = signal<void()>;
using void_slot = void_signal::slot_type;
class named_entity
{
public:
std::string name() const
{
std::lock_guard lock(m_nameMutex);
return m_name;
}
void fire_changed(std::string value)
{
bool fire = false;
{
std::lock_guard lock(m_nameMutex);
if (m_name != value)
{
m_name = std::move(value);
fire = true;
}
}
if (fire)
{
m_nameChanged(value);
}
}
connection on_name_changed(string_slot slot)
{
return m_nameChanged.connect(std::move(slot));
}
private:
mutable std::mutex m_nameMutex;
std::string m_name;
signal<void(std::string)> m_nameChanged;
};
unsigned get_next_seed()
{
static std::minstd_rand seedEngine(777);
return seedEngine();
}
size_t get_random_index(size_t size)
{
thread_local std::minstd_rand disconnectRandomEngine{ get_next_seed() };
std::uniform_int_distribution<size_t> disconnectIndexDistribution{ 0, size - 1 };
return disconnectIndexDistribution(disconnectRandomEngine);
}
} // namespace
TEST_CASE("Can work in a few threads", "[signal]")
{
constexpr unsigned fireThreadCount = 8;
constexpr unsigned signalsCount = 7;
constexpr unsigned fireCountPerThread = 100'000;
constexpr unsigned connectCallsCount = 80'000;
constexpr unsigned totalRunCount = 10;
for (unsigned i = 0; i < totalRunCount; ++i)
{
std::array<void_signal, signalsCount> signals;
std::mutex connectionsMutex;
std::vector<connection> connections;
connections.reserve(connectCallsCount);
std::vector<std::thread> threads;
auto slot = [&] {
std::lock_guard lock(connectionsMutex);
if (!connections.empty())
{
const size_t index = get_random_index(connections.size());
connections.at(index).disconnect();
}
};
threads.emplace_back([&] {
for (unsigned cci = 0; cci < connectCallsCount; ++cci)
{
const size_t index = get_random_index(signalsCount);
connection conn = signals.at(index).connect(slot);
{
std::lock_guard lock(connectionsMutex);
connections.emplace_back(conn);
}
}
});
for (unsigned fti = 0; fti < fireThreadCount; ++fti)
{
threads.emplace_back([&] {
for (unsigned fi = 0; fi < fireCountPerThread; ++fi)
{
const size_t index = get_random_index(signalsCount);
signals.at(index)();
}
});
}
for (auto& thread : threads)
{
thread.join();
}
}
}

View File

@@ -0,0 +1,4 @@
custom_add_test_from_dir(libfastsignals_unit_tests libfastsignals)
custom_enable_cxx17(libfastsignals_unit_tests)
target_include_directories(libfastsignals_unit_tests PRIVATE "${CMAKE_SOURCE_DIR}/tests")

View File

@@ -0,0 +1,618 @@
#include "catch2/catch.hpp"
#include "libfastsignals/include/function.h"
#include <array>
using namespace is::signals;
namespace
{
int Abs(int x)
{
return x >= 0 ? x : -x;
}
int Sum(int a, int b)
{
return a + b;
}
void InplaceAbs(int& x)
{
x = Abs(x);
}
std::string GetStringHello()
{
return "hello";
}
class AbsFunctor
{
public:
int operator()(int x) const
{
return Abs(x);
}
};
class SumFunctor
{
public:
int operator()(int a, int b) const
{
return Sum(a, b);
}
};
class InplaceAbsFunctor
{
public:
void operator()(int& x) /* non-const */
{
if (m_calledOnce)
{
abort();
}
m_calledOnce = true;
InplaceAbs(x);
}
private:
bool m_calledOnce = false;
};
class GetStringFunctor
{
public:
explicit GetStringFunctor(const std::string& value)
: m_value(value)
{
}
std::string operator()() /* non-const */
{
if (m_calledOnce)
{
abort();
}
m_calledOnce = true;
return m_value;
}
private:
bool m_calledOnce = false;
std::string m_value;
};
} // namespace
TEST_CASE("Can use free function with 1 argument", "[function]")
{
function<int(int)> fn = Abs;
REQUIRE(fn(10) == 10);
REQUIRE(fn(-10) == 10);
REQUIRE(fn(0) == 0);
}
TEST_CASE("Can use free function with 2 arguments", "[function]")
{
function<int(int, int)> fn = Sum;
REQUIRE(fn(10, 5) == 15);
REQUIRE(fn(-10, 0) == -10);
}
TEST_CASE("Can use free function without arguments", "[function]")
{
function<std::string()> fn = GetStringHello;
REQUIRE(fn() == "hello");
}
TEST_CASE("Can use free function without return value", "[function]")
{
function<void(int&)> fn = InplaceAbs;
int a = -10;
fn(a);
REQUIRE(a == 10);
}
TEST_CASE("Can use lambda with 1 argument", "[function]")
{
function<int(int)> fn = [](int value) {
return Abs(value);
};
REQUIRE(fn(10) == 10);
REQUIRE(fn(-10) == 10);
REQUIRE(fn(0) == 0);
}
TEST_CASE("Can use lambda with 2 arguments", "[function]")
{
function<int(int, int)> fn = [](auto&& a, auto&& b) {
return Sum(a, b);
};
REQUIRE(fn(10, 5) == 15);
REQUIRE(fn(-10, 0) == -10);
}
TEST_CASE("Can use lambda without arguments", "[function]")
{
function<std::string()> fn = [] {
return GetStringHello();
};
REQUIRE(fn() == "hello");
}
TEST_CASE("Can use lambda without return value", "[function]")
{
bool calledOnce = false;
function<void(int&)> fn = [calledOnce](auto& value) mutable {
if (calledOnce)
{
abort();
}
calledOnce = true;
InplaceAbs(value);
};
int a = -10;
fn(a);
REQUIRE(a == 10);
}
TEST_CASE("Can use functor with 1 argument", "[function]")
{
function<int(int)> fn = AbsFunctor();
REQUIRE(fn(10) == 10);
REQUIRE(fn(-10) == 10);
REQUIRE(fn(0) == 0);
}
TEST_CASE("Can use functor with 2 arguments", "[function]")
{
function<int(int, int)> fn = SumFunctor();
REQUIRE(fn(10, 5) == 15);
REQUIRE(fn(-10, 0) == -10);
}
TEST_CASE("Can use functor without arguments", "[function]")
{
function<std::string()> fn = GetStringFunctor("hello");
REQUIRE(fn() == "hello");
}
TEST_CASE("Can use functor without return value", "[function]")
{
function<void(int&)> fn = InplaceAbsFunctor();
int a = -10;
fn(a);
REQUIRE(a == 10);
}
TEST_CASE("Can construct function with cons std::function<>&", "[function]")
{
using BoolCallback = std::function<void(bool succeed)>;
bool value = false;
const BoolCallback& cb = [&value](bool succeed) {
value = succeed;
};
function<void(bool)> fn = cb;
fn(true);
REQUIRE(value == true);
fn(false);
REQUIRE(value == false);
fn(true);
REQUIRE(value == true);
}
TEST_CASE("Can copy function", "[function]")
{
unsigned calledCount = 0;
bool value = false;
function<void(bool)> callback = [&](bool gotValue) {
++calledCount;
value = gotValue;
};
auto callback2 = callback;
REQUIRE(calledCount == 0);
CHECK(!value);
callback(true);
REQUIRE(calledCount == 1);
CHECK(value);
callback2(false);
REQUIRE(calledCount == 2);
CHECK(!value);
}
TEST_CASE("Can move function", "[function]")
{
bool called = false;
function<void()> callback = [&] {
called = true;
};
auto callback2(std::move(callback));
REQUIRE_THROWS(callback());
REQUIRE(!called);
callback2();
REQUIRE(called);
}
TEST_CASE("Works when copying self", "[function]")
{
bool called = false;
function<void()> callback = [&] {
called = true;
};
callback = callback;
callback();
REQUIRE(called);
}
TEST_CASE("Can release packed function", "[function]")
{
function<int()> iota = [v = 0]() mutable {
return v++;
};
REQUIRE(iota() == 0);
auto packedFn = std::move(iota).release();
REQUIRE_THROWS_AS(iota(), std::bad_function_call);
auto&& proxy = packedFn.get<int()>();
REQUIRE(proxy() == 1);
REQUIRE(proxy() == 2);
}
TEST_CASE("Function copy has its own packed function", "[function]")
{
function<int()> iota = [v = 0]() mutable {
return v++;
};
REQUIRE(iota() == 0);
auto iotaCopy(iota);
REQUIRE(iota() == 1);
REQUIRE(iota() == 2);
REQUIRE(iotaCopy() == 1);
REQUIRE(iotaCopy() == 2);
}
TEST_CASE("can work with callables that have vtable", "[function]")
{
class Base
{
};
class Interface : public Base
{
public:
virtual ~Interface() = default;
virtual void operator()() const = 0;
};
class Class : public Interface
{
public:
Class(bool* destructorCalled)
: m_destructorCalled(destructorCalled)
{
}
~Class()
{
*m_destructorCalled = true;
}
void operator()() const override
{
}
bool* m_destructorCalled = nullptr;
};
bool destructorCalled = false;
{
function<void()> f = Class(&destructorCalled);
f();
auto packed = f.release();
destructorCalled = false;
}
CHECK(destructorCalled);
}
TEST_CASE("can work with callables with virtual inheritance", "[function]")
{
struct A
{
void operator()() const
{
m_called = true;
}
~A()
{
*m_destructorCalled = true;
}
mutable bool m_called = false;
bool* m_destructorCalled = nullptr;
};
struct B : public virtual A
{
};
struct C : public virtual A
{
};
struct D : virtual public B
, virtual public C
{
D(bool* destructorCalled)
{
m_destructorCalled = destructorCalled;
}
using A::operator();
};
bool destructorCalled = false;
{
function<void()> f = D(&destructorCalled);
f();
auto packed = f.release();
destructorCalled = false;
}
CHECK(destructorCalled);
}
TEST_CASE("uses copy constructor if callable's move constructor throws", "[function]")
{
struct Callable
{
Callable() = default;
Callable(Callable&&)
{
throw std::runtime_error("throw");
}
Callable(const Callable& other) = default;
void operator()() const
{
}
};
Callable c;
function<void()> f(c);
auto f2 = std::move(f);
f2();
CHECK_THROWS(f());
}
TEST_CASE("uses move constructor if it is noexcept", "[function]")
{
struct Callable
{
Callable() = default;
Callable(Callable&& other) noexcept = default;
Callable(const Callable&)
{
throw std::runtime_error("throw");
}
void operator()() const
{
}
};
Callable c;
function<void()> f(std::move(c));
auto f2 = std::move(f);
f2();
CHECK_THROWS(f());
}
TEST_CASE("can copy and move empty function", "[function]")
{
function<void()> f;
auto f2 = f;
auto f3 = std::move(f);
}
TEST_CASE("properly copies callable on assignment", "[function]")
{
struct Callable
{
Callable(int& aliveCounter)
: m_aliveCounter(&aliveCounter)
{
++*m_aliveCounter;
}
Callable(const Callable& other)
: m_aliveCounter(other.m_aliveCounter)
{
if (m_aliveCounter)
{
++*m_aliveCounter;
}
}
Callable(Callable&& other) noexcept
: m_aliveCounter(other.m_aliveCounter)
{
other.m_aliveCounter = nullptr;
}
~Callable()
{
if (m_aliveCounter)
{
--*m_aliveCounter;
}
}
void operator()() const
{
}
int* m_aliveCounter = nullptr;
};
int aliveCounter1 = 0;
int aliveCounter2 = 0;
function<void()> f = Callable(aliveCounter1);
function<void()> f2 = Callable(aliveCounter2);
CHECK(aliveCounter1 == 1);
CHECK(aliveCounter2 == 1);
f = f2;
CHECK(aliveCounter1 == 0);
CHECK(aliveCounter2 == 2);
f = function<void()>();
f2 = function<void()>();
CHECK(aliveCounter1 == 0);
CHECK(aliveCounter2 == 0);
}
TEST_CASE("copy assignment operator provides strong exception safety", "[function]")
{
struct State
{
int callCount = 0;
bool throwOnCopy = false;
};
struct Callable
{
Callable(State& state)
: state(&state)
{
}
void operator()()
{
++state->callCount;
}
Callable(const Callable& other)
: state(other.state)
{
if (state->throwOnCopy)
{
throw std::runtime_error("throw on request");
}
}
State* state = nullptr;
};
static_assert(!detail::can_use_inplace_buffer<Callable>);
State srcState;
State dstState;
function<void()> srcFn(Callable{ srcState });
function<void()> dstFn(Callable{ dstState });
srcFn();
dstFn();
REQUIRE(srcState.callCount == 1);
REQUIRE(dstState.callCount == 1);
srcState.throwOnCopy = true;
REQUIRE_THROWS_AS(dstFn = srcFn, std::runtime_error);
// srcFn and dstFn must not be emptied even if assignment throws
REQUIRE_NOTHROW(srcFn());
REQUIRE_NOTHROW(dstFn());
// srcFn and dstFn must keep their state
REQUIRE(srcState.callCount == 2);
REQUIRE(dstState.callCount == 2);
// The next copy will succeed
srcState.throwOnCopy = false;
REQUIRE_NOTHROW(dstFn = srcFn);
// Both functions are usable
REQUIRE_NOTHROW(srcFn());
REQUIRE_NOTHROW(dstFn());
// After assignment, dstFn and srcFn refer the same state - srcState
REQUIRE(srcState.callCount == 4);
REQUIRE(dstState.callCount == 2);
}
TEST_CASE("assignment of variously allocated functions", "[function]")
{
int heapCalls = 0;
auto onHeap = [&heapCalls, largeVar = std::array<std::string, 1000>()]() mutable {
std::fill(largeVar.begin(), largeVar.end(), "large string to be allocated on heap instead of stack");
++heapCalls;
};
int stackCalls = 0;
auto onStack = [&stackCalls] {
++stackCalls;
};
static_assert(detail::can_use_inplace_buffer<detail::function_proxy_impl<decltype(onStack), void>>);
static_assert(!detail::can_use_inplace_buffer<detail::function_proxy_impl<decltype(onHeap), void>>);
using Fn = function<void()>;
{
Fn heap(onHeap);
Fn stack(onStack);
heap = stack;
heap();
REQUIRE(stackCalls == 1);
REQUIRE(heapCalls == 0);
}
{
Fn heap(onHeap);
Fn stack(onStack);
stack = heap;
stack();
REQUIRE(stackCalls == 1);
REQUIRE(heapCalls == 1);
}
{
Fn heap(onHeap);
Fn heap1(onHeap);
heap = heap1;
heap();
REQUIRE(stackCalls == 1);
REQUIRE(heapCalls == 2);
}
{
Fn stack(onStack);
Fn stack1(onStack);
stack = stack1;
stack();
REQUIRE(stackCalls == 2);
REQUIRE(heapCalls == 2);
}
{
Fn heap(onHeap);
Fn empty;
heap = empty;
REQUIRE_THROWS(heap());
REQUIRE(stackCalls == 2);
REQUIRE(heapCalls == 2);
}
{
Fn stack(onStack);
Fn empty;
stack = empty;
REQUIRE_THROWS(stack());
REQUIRE(stackCalls == 2);
REQUIRE(heapCalls == 2);
}
{
Fn empty;
Fn heap(onHeap);
empty = heap;
empty();
REQUIRE(stackCalls == 2);
REQUIRE(heapCalls == 3);
}
{
Fn empty;
Fn stack(onStack);
empty = stack;
empty();
REQUIRE(stackCalls == 3);
REQUIRE(heapCalls == 3);
}
{
Fn empty;
Fn empty1;
empty = empty1;
REQUIRE_THROWS(empty());
REQUIRE(stackCalls == 3);
REQUIRE(heapCalls == 3);
}
}

View File

@@ -0,0 +1,132 @@
#include "catch2/catch.hpp"
#include "libfastsignals/include/bind_weak.h"
using namespace is::signals;
namespace
{
class Testbed
{
public:
Testbed(unsigned& counter)
: m_pCounter(&counter)
{
}
void IncrementNonConst()
{
++(*m_pCounter);
}
void IncrementsConst() const
{
++(*m_pCounter);
}
int ReflectInt(int value) const
{
return value;
}
private:
unsigned* m_pCounter = nullptr;
};
} // namespace
TEST_CASE("can bind const methods", "[bind_weak]")
{
unsigned counter = 0;
auto pSharedBed = std::make_shared<Testbed>(counter);
auto boundFn = bind_weak(&Testbed::IncrementNonConst, pSharedBed);
REQUIRE(counter == 0u);
boundFn();
REQUIRE(counter == 1u);
boundFn();
REQUIRE(counter == 2u);
pSharedBed = nullptr;
boundFn();
REQUIRE(counter == 2u);
boundFn();
REQUIRE(counter == 2u);
}
TEST_CASE("can bind non const methods", "[bind_weak]")
{
unsigned counter = 0;
auto pSharedBed = std::make_shared<Testbed>(counter);
auto boundFn = bind_weak(&Testbed::IncrementsConst, pSharedBed);
REQUIRE(counter == 0u);
boundFn();
REQUIRE(counter == 1u);
boundFn();
REQUIRE(counter == 2u);
pSharedBed = nullptr;
boundFn();
REQUIRE(counter == 2u);
boundFn();
REQUIRE(counter == 2u);
}
TEST_CASE("can bind method with argument value", "[bind_weak]")
{
unsigned counter = 0;
auto pSharedBed = std::make_shared<Testbed>(counter);
auto boundFn = bind_weak(&Testbed::ReflectInt, pSharedBed, 42);
REQUIRE(boundFn() == 42);
REQUIRE(boundFn() == 42);
pSharedBed = nullptr;
REQUIRE(boundFn() == 0);
REQUIRE(boundFn() == 0);
}
TEST_CASE("copies value when bind method with argument const reference value", "[bind_weak]")
{
unsigned counter = 0;
auto pSharedBed = std::make_shared<Testbed>(counter);
auto makeBoundFn = [&]() {
int value = 15;
const int& valueRef = value;
auto result = bind_weak(&Testbed::ReflectInt, pSharedBed, valueRef);
value = 25;
return result;
};
auto boundFn = makeBoundFn();
REQUIRE(boundFn(42) == 15);
REQUIRE(boundFn(42) == 15);
pSharedBed = nullptr;
REQUIRE(boundFn(42) == 0);
REQUIRE(boundFn(42) == 0);
}
TEST_CASE("copies value when bind method with argument reference value", "[bind_weak]")
{
unsigned counter = 0;
auto pSharedBed = std::make_shared<Testbed>(counter);
auto makeBoundFn = [&]() {
int value = 15;
int& valueRef = value;
auto result = bind_weak(&Testbed::ReflectInt, pSharedBed, valueRef);
valueRef = 25;
return result;
};
auto boundFn = makeBoundFn();
REQUIRE(boundFn(42) == 15);
REQUIRE(boundFn(42) == 15);
pSharedBed = nullptr;
REQUIRE(boundFn(42) == 0);
REQUIRE(boundFn(42) == 0);
}
TEST_CASE("can bind method with placeholder", "[bind_weak]")
{
unsigned counter = 0;
auto pSharedBed = std::make_shared<Testbed>(counter);
auto boundFn = bind_weak(&Testbed::ReflectInt, pSharedBed, std::placeholders::_1);
REQUIRE(boundFn(42) == 42);
REQUIRE(boundFn(42) == 42);
pSharedBed = nullptr;
REQUIRE(boundFn(42) == 0);
REQUIRE(boundFn(42) == 0);
}

View File

@@ -0,0 +1,190 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{BAC23A51-8DC1-4589-940F-9923D8E12718}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>libfastsignals_unit_tests</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\libfastsignals_build_options.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\libfastsignals_build_options.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\libfastsignals_build_options.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\libfastsignals_build_options.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(SolutionDir);$(SolutionDir)tests;$(IncludePath)</IncludePath>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(SolutionDir);$(SolutionDir)tests;$(IncludePath)</IncludePath>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>$(SolutionDir);$(SolutionDir)tests;$(IncludePath)</IncludePath>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>$(SolutionDir);$(SolutionDir)tests;$(IncludePath)</IncludePath>
<OutDir>$(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<PostBuildEvent>
<Command>$(TargetPath)</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<PostBuildEvent>
<Command>$(TargetPath)</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<PostBuildEvent>
<Command>$(TargetPath)</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<PostBuildEvent>
<Command>$(TargetPath)</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="bind_weak_tests.cpp" />
<ClCompile Include="function_tests.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="signal_tests.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\libfastsignals\libfastsignals.vcxproj">
<Project>{32bd918f-edbc-4057-a033-10dc361da4a0}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="signal_tests.cpp" />
<ClCompile Include="function_tests.cpp" />
<ClCompile Include="bind_weak_tests.cpp" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"

View File

@@ -0,0 +1,707 @@
#include "catch2/catch.hpp"
#include "libfastsignals/include/signal.h"
#include <string>
using namespace is::signals;
using namespace std::literals;
namespace
{
template <class T>
class any_of_combiner
{
public:
static_assert(std::is_same_v<T, bool>);
using result_type = bool;
template <class TRef>
void operator()(TRef&& value)
{
m_result = m_result || bool(value);
}
result_type get_value() const
{
return m_result;
}
private:
result_type m_result = {};
};
} // namespace
TEST_CASE("Can connect a few slots and emit", "[signal]")
{
signal<void(int)> valueChanged;
int value1 = 0;
int value2 = 0;
valueChanged.connect([&value1](int value) {
value1 = value;
});
valueChanged.connect([&value2](int value) {
value2 = value;
});
REQUIRE(value1 == 0);
REQUIRE(value2 == 0);
valueChanged(10);
REQUIRE(value1 == 10);
REQUIRE(value2 == 10);
}
TEST_CASE("Can safely pass rvalues", "[signal]")
{
const std::string expected = "If the type T is a reference type, provides the member typedef type which is the type referred to by T. Otherwise type is T.";
std::string passedValue = expected;
signal<void(std::string)> valueChanged;
std::string value1;
std::string value2;
valueChanged.connect([&value1](std::string value) {
value1 = value;
});
valueChanged.connect([&value2](std::string value) {
value2 = value;
});
valueChanged(std::move(passedValue));
REQUIRE(value1 == expected);
REQUIRE(value2 == expected);
}
TEST_CASE("Can pass mutable ref", "[signal]")
{
const std::string expected = "If the type T is a reference type, provides the member typedef type which is the type referred to by T. Otherwise type is T.";
signal<void(std::string&)> valueChanged;
std::string passedValue;
valueChanged.connect([expected](std::string& value) {
value = expected;
});
valueChanged(passedValue);
REQUIRE(passedValue == expected);
}
TEST_CASE("Can disconnect slot with explicit call", "[signal]")
{
signal<void(int)> valueChanged;
int value1 = 0;
int value2 = 0;
int value3 = 0;
auto conn1 = valueChanged.connect([&value1](int value) {
value1 = value;
});
auto conn2 = valueChanged.connect([&value2](int value) {
value2 = value;
});
valueChanged.connect([&value3](int value) {
value3 = value;
});
REQUIRE(value1 == 0);
REQUIRE(value2 == 0);
REQUIRE(value3 == 0);
valueChanged(10);
REQUIRE(value1 == 10);
REQUIRE(value2 == 10);
REQUIRE(value3 == 10);
conn2.disconnect();
valueChanged(-99);
REQUIRE(value1 == -99);
REQUIRE(value2 == 10);
REQUIRE(value3 == -99);
conn1.disconnect();
valueChanged(17);
REQUIRE(value1 == -99);
REQUIRE(value2 == 10);
REQUIRE(value3 == 17);
}
TEST_CASE("Can disconnect slot with scoped_connection", "[signal]")
{
signal<void(int)> valueChanged;
int value1 = 0;
int value2 = 0;
int value3 = 0;
{
scoped_connection conn1 = valueChanged.connect([&value1](int value) {
value1 = value;
});
{
scoped_connection conn2 = valueChanged.connect([&value2](int value) {
value2 = value;
});
valueChanged.connect([&value3](int value) {
value3 = value;
});
REQUIRE(value1 == 0);
REQUIRE(value2 == 0);
REQUIRE(value3 == 0);
valueChanged(10);
REQUIRE(value1 == 10);
REQUIRE(value2 == 10);
REQUIRE(value3 == 10);
}
// conn2 disconnected.
valueChanged(-99);
REQUIRE(value1 == -99);
REQUIRE(value2 == 10);
REQUIRE(value3 == -99);
}
// conn1 disconnected.
valueChanged(17);
REQUIRE(value1 == -99);
REQUIRE(value2 == 10);
REQUIRE(value3 == 17);
}
TEST_CASE("Can disconnect all", "[signal]")
{
signal<void(int)> valueChanged;
int value1 = 0;
int value2 = 0;
int value3 = 0;
valueChanged.connect([&value1](int value) {
value1 = value;
});
valueChanged.connect([&value2](int value) {
value2 = value;
});
valueChanged.connect([&value3](int value) {
value3 = value;
});
REQUIRE(value1 == 0);
REQUIRE(value2 == 0);
REQUIRE(value3 == 0);
valueChanged(63);
REQUIRE(value1 == 63);
REQUIRE(value2 == 63);
REQUIRE(value3 == 63);
valueChanged.disconnect_all_slots();
valueChanged(101);
REQUIRE(value1 == 63);
REQUIRE(value2 == 63);
REQUIRE(value3 == 63);
}
TEST_CASE("Can disconnect inside slot", "[signal]")
{
signal<void(int)> valueChanged;
int value1 = 0;
int value2 = 0;
int value3 = 0;
connection conn2;
valueChanged.connect([&value1](int value) {
value1 = value;
});
conn2 = valueChanged.connect([&](int value) {
value2 = value;
conn2.disconnect();
});
valueChanged.connect([&value3](int value) {
value3 = value;
});
REQUIRE(value1 == 0);
REQUIRE(value2 == 0);
REQUIRE(value3 == 0);
valueChanged(63);
REQUIRE(value1 == 63);
REQUIRE(value2 == 63);
REQUIRE(value3 == 63);
valueChanged(101);
REQUIRE(value1 == 101);
REQUIRE(value2 == 63); // disconnected in slot.
REQUIRE(value3 == 101);
}
TEST_CASE("Disconnects OK if signal dead first", "[signal]")
{
connection conn2;
{
scoped_connection conn1;
{
signal<void(int)> valueChanged;
conn2 = valueChanged.connect([](int) {
});
// Just unused.
valueChanged.connect([](int) {
});
conn1 = valueChanged.connect([](int) {
});
}
REQUIRE(conn2.connected());
REQUIRE(conn1.connected());
conn2.disconnect();
REQUIRE(!conn2.connected());
REQUIRE(conn1.connected());
}
conn2.disconnect();
}
TEST_CASE("Returns last called slot result with default combiner", "[signal]")
{
connection conn2;
{
scoped_connection conn1;
{
signal<int(int)> absSignal;
conn2 = absSignal.connect([](int value) {
return value * value;
});
conn1 = absSignal.connect([](int value) {
return abs(value);
});
absSignal(-1);
REQUIRE(absSignal(45) == 45);
REQUIRE(absSignal(-1) == 1);
REQUIRE(absSignal(-177) == 177);
REQUIRE(absSignal(0) == 0);
}
REQUIRE(conn2.connected());
conn2.disconnect();
REQUIRE(!conn2.connected());
}
conn2.disconnect();
REQUIRE(!conn2.connected());
}
TEST_CASE("Works with custom any_of combiner", "[signal]")
{
using cancellable_signal = signal<bool(std::string), any_of_combiner>;
cancellable_signal startRequested;
auto conn1 = startRequested.connect([](std::string op) {
return op == "1";
});
auto conn2 = startRequested.connect([](std::string op) {
return op == "1" || op == "2";
});
REQUIRE(startRequested("0") == false);
REQUIRE(startRequested("1") == true);
REQUIRE(startRequested("2") == true);
REQUIRE(startRequested("3") == false);
conn1.disconnect();
conn2.disconnect();
REQUIRE(startRequested("0") == false);
REQUIRE(startRequested("1") == false);
REQUIRE(startRequested("2") == false);
REQUIRE(startRequested("3") == false);
}
TEST_CASE("Can release scoped connection", "[signal]")
{
int value2 = 0;
int value3 = 0;
signal<void(int)> valueChanged;
connection conn1;
{
scoped_connection conn2;
scoped_connection conn3;
conn2 = valueChanged.connect([&value2](int x) {
value2 = x;
});
conn3 = valueChanged.connect([&value3](int x) {
value3 = x;
});
valueChanged(42);
REQUIRE(value2 == 42);
REQUIRE(value3 == 42);
REQUIRE(conn2.connected());
REQUIRE(conn3.connected());
REQUIRE(!conn1.connected());
conn1 = conn3.release();
REQUIRE(conn2.connected());
REQUIRE(!conn3.connected());
REQUIRE(conn1.connected());
valueChanged(144);
REQUIRE(value2 == 144);
REQUIRE(value3 == 144);
}
// conn2 disconnected, conn1 connected.
valueChanged(17);
REQUIRE(value2 == 144);
REQUIRE(value3 == 17);
REQUIRE(conn1.connected());
conn1.disconnect();
valueChanged(90);
REQUIRE(value2 == 144);
REQUIRE(value3 == 17);
}
TEST_CASE("Can use signal with more than one argument", "[signal]")
{
signal<void(int, std::string, std::vector<std::string>)> event;
int value1 = 0;
std::string value2;
std::vector<std::string> value3;
event.connect([&](int v1, const std::string& v2, const std::vector<std::string>& v3) {
value1 = v1;
value2 = v2;
value3 = v3;
});
event(9815, "using namespace std::literals!"s, std::vector{ "std::vector"s, "using namespace std::literals"s });
REQUIRE(value1 == 9815);
REQUIRE(value2 == "using namespace std::literals!"s);
REQUIRE(value3 == std::vector{ "std::vector"s, "using namespace std::literals"s });
}
TEST_CASE("Can blocks slots using shared_connection_block", "[signal]")
{
bool callbackShouldBeCalled = true;
bool callbackCalled = false;
const int value = 123;
signal<void(int)> event;
auto conn = event.connect([&](int gotValue) {
CHECK(gotValue == value);
callbackCalled = true;
if (!callbackShouldBeCalled)
{
FAIL("callback is blocked and should not be called");
}
},
advanced_tag{});
event(value);
REQUIRE(callbackCalled);
shared_connection_block block(conn);
callbackShouldBeCalled = false;
callbackCalled = false;
event(value);
REQUIRE(!callbackCalled);
block.unblock();
callbackShouldBeCalled = true;
event(value);
REQUIRE(callbackCalled);
}
TEST_CASE("Other slots are unaffected by the block", "[signal]")
{
bool callback1Called = false;
bool callback2Called = false;
const int value = 123;
signal<void(int)> event;
auto conn1 = event.connect([&](int gotValue) {
CHECK(gotValue == value);
callback1Called = true;
},
advanced_tag{});
auto conn2 = event.connect([&](int) {
callback2Called = true;
FAIL("callback is blocked and should not be called");
},
advanced_tag{});
shared_connection_block block(conn2);
event(value);
REQUIRE(callback1Called);
REQUIRE(!callback2Called);
}
TEST_CASE("Multiple blocks block until last one is unblocked", "[signal]")
{
bool callbackShouldBeCalled = false;
bool callbackCalled = false;
const int value = 123;
signal<void(int)> event;
auto conn = event.connect([&](int gotValue) {
CHECK(gotValue == value);
callbackCalled = true;
if (!callbackShouldBeCalled)
{
FAIL("callback is blocked and should not be called");
}
},
advanced_tag{});
shared_connection_block block1(conn);
shared_connection_block block2(conn);
event(value);
REQUIRE(!callbackCalled);
block1.unblock();
event(value);
REQUIRE(!callbackCalled);
block1.block();
block2.unblock();
event(value);
REQUIRE(!callbackCalled);
block1.unblock();
callbackShouldBeCalled = true;
event(value);
REQUIRE(callbackCalled);
}
TEST_CASE("Can copy and move shared_connection_block objects", "[signal]")
{
bool callbackShouldBeCalled = false;
bool callbackCalled = false;
const int value = 123;
signal<void(int)> event;
auto conn = event.connect([&](int gotValue) {
CHECK(gotValue == value);
callbackCalled = true;
if (!callbackShouldBeCalled)
{
FAIL("callback is blocked and should not be called");
}
},
advanced_tag{});
shared_connection_block block1(conn);
shared_connection_block block2(block1);
event(value);
REQUIRE(block1.blocking());
REQUIRE(block2.blocking());
REQUIRE(!callbackCalled);
shared_connection_block block3(std::move(block2));
event(value);
REQUIRE(block1.blocking());
REQUIRE(!block2.blocking());
REQUIRE(block3.blocking());
REQUIRE(!callbackCalled);
block2 = block3;
event(value);
REQUIRE(block1.blocking());
REQUIRE(block2.blocking());
REQUIRE(block3.blocking());
REQUIRE(!callbackCalled);
block3 = std::move(block2);
event(value);
REQUIRE(block1.blocking());
REQUIRE(!block2.blocking());
REQUIRE(block3.blocking());
REQUIRE(!callbackCalled);
block3 = shared_connection_block(conn, false);
event(value);
REQUIRE(block1.blocking());
REQUIRE(!block2.blocking());
REQUIRE(!block3.blocking());
REQUIRE(!callbackCalled);
block1.unblock();
callbackShouldBeCalled = true;
event(value);
REQUIRE(!block1.blocking());
REQUIRE(!block2.blocking());
REQUIRE(!block3.blocking());
REQUIRE(callbackCalled);
}
TEST_CASE("Unblocks when shared_connection_block goes out of scope")
{
bool callbackCalled = false;
const int value = 123;
signal<void(int)> event;
auto conn = event.connect([&](int gotValue) {
CHECK(gotValue == value);
callbackCalled = true;
},
advanced_tag{});
callbackCalled = false;
event(value);
CHECK(callbackCalled);
{
callbackCalled = false;
shared_connection_block block(conn);
event(value);
CHECK(!callbackCalled);
{
callbackCalled = false;
shared_connection_block block2(conn);
event(value);
CHECK(!callbackCalled);
}
}
callbackCalled = false;
event(value);
CHECK(callbackCalled);
}
TEST_CASE("Can disconnect advanced slot using advanced_scoped_connection", "[signal]")
{
signal<void(int)> valueChanged;
int value1 = 0;
int value2 = 0;
int value3 = 0;
{
advanced_scoped_connection conn1 = valueChanged.connect([&value1](int value) {
value1 = value;
},
advanced_tag{});
{
advanced_scoped_connection conn2 = valueChanged.connect([&value2](int value) {
value2 = value;
},
advanced_tag{});
valueChanged.connect([&value3](int value) {
value3 = value;
});
REQUIRE(value1 == 0);
REQUIRE(value2 == 0);
REQUIRE(value3 == 0);
valueChanged(10);
REQUIRE(value1 == 10);
REQUIRE(value2 == 10);
REQUIRE(value3 == 10);
}
// conn2 disconnected.
valueChanged(-99);
REQUIRE(value1 == -99);
REQUIRE(value2 == 10);
REQUIRE(value3 == -99);
}
// conn1 disconnected.
valueChanged(17);
REQUIRE(value1 == -99);
REQUIRE(value2 == 10);
REQUIRE(value3 == 17);
}
TEST_CASE("Can move signal", "[signal]")
{
signal<void()> src;
int srcFireCount = 0;
auto srcConn = src.connect([&srcFireCount] {
++srcFireCount;
});
src();
REQUIRE(srcFireCount == 1);
auto dst = std::move(src);
int dstFireCount = 0;
auto dstConn = dst.connect([&dstFireCount] {
++dstFireCount;
});
dst();
REQUIRE(srcFireCount == 2);
REQUIRE(dstFireCount == 1);
srcConn.disconnect();
dstConn.disconnect();
dst();
REQUIRE(srcFireCount == 2);
REQUIRE(dstFireCount == 1);
}
TEST_CASE("Can swap signals", "[signal]")
{
signal<void()> s1;
signal<void()> s2;
int s1FireCount = 0;
int s2FireCount = 0;
s1.connect([&s1FireCount] {
++s1FireCount;
});
s2.connect([&s2FireCount] {
++s2FireCount;
});
std::swap(s1, s2);
s1();
REQUIRE(s1FireCount == 0);
REQUIRE(s2FireCount == 1);
s2();
REQUIRE(s1FireCount == 1);
REQUIRE(s2FireCount == 1);
}
TEST_CASE("Signal can be destroyed inside its slot and will call the rest of its slots", "[signal]")
{
std::optional<signal<void()>> s;
s.emplace();
s->connect([&] {
s.reset();
});
bool called = false;
s->connect([&] {
called = true;
});
(*s)();
CHECK(called);
}
TEST_CASE("Signal can be used as a slot for another signal", "[signal]")
{
signal<void()> s1;
bool called = false;
s1.connect([&] {
called = true;
});
signal<void()> s2;
s2.connect(s1);
s2();
CHECK(called);
}
// memory leak fix
TEST_CASE("Releases lambda and its captured const data", "[signal]")
{
struct Captured
{
Captured(bool& released)
: m_released(released)
{
}
~Captured()
{
m_released = true;
}
private:
bool& m_released;
};
bool released = false;
{
const auto captured = std::make_shared<Captured>(released);
signal<void()> changeSignal;
changeSignal.connect([captured]{});
}
CHECK(released);
}