Skip to content

Commit 72940d0

Browse files
committed
Add fast alternative for signals and observers (OBSERVABLE_FAST_LIST)
1 parent 0655170 commit 72940d0

20 files changed

Lines changed: 179 additions & 89 deletions

.github/workflows/build.yml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ jobs:
88
matrix:
99
os: [windows-latest, macos-latest, ubuntu-latest]
1010
build_type: [debug,release]
11-
benchmark: [false]
11+
benchmark: [tests]
12+
fast: [safe, fast]
1213
sanitizer: [""]
1314
include:
1415
- os: macos-latest
@@ -19,7 +20,12 @@ jobs:
1920
sanitizer: "-fsanitize=address"
2021
- os: ubuntu-latest
2122
build_type: release
22-
benchmark: true
23+
benchmark: benchmark
24+
fast: safe
25+
- os: ubuntu-latest
26+
build_type: release
27+
benchmark: benchmark
28+
fast: fast
2329
steps:
2430
- uses: actions/checkout@v4
2531
- uses: aseprite/get-ninja@main
@@ -28,15 +34,26 @@ jobs:
2834
- name: Generating Makefiles
2935
shell: bash
3036
run: |
37+
if [[ "${{ matrix.benchmark }}" == "benchmark" ]] ; then
38+
OBSERVABLE_BENCHMARKS=ON
39+
else
40+
OBSERVABLE_BENCHMARKS=OFF
41+
fi
42+
if [[ "${{ matrix.fast }}" == fast ]] ; then
43+
OBSERVABLE_FAST_LIST=ON
44+
else
45+
OBSERVABLE_FAST_LIST=OFF
46+
fi
3147
cmake . -G Ninja \
3248
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
3349
-DCMAKE_CXX_FLAGS=${{ matrix.sanitizer }} \
3450
-DCMAKE_EXE_LINKER_FLAGS=${{ matrix.sanitizer }} \
35-
-DOBSERVABLE_BENCHMARKS=${{ matrix.benchmark }}
51+
-DOBSERVABLE_FAST_LIST=$OBSERVABLE_FAST_LIST \
52+
-DOBSERVABLE_BENCHMARKS=$OBSERVABLE_BENCHMARKS
3653
- name: Compiling
3754
run: cmake --build .
3855
- name: Running Tests
3956
shell: bash
4057
run: |
4158
ctest --output-on-failure
42-
if [[ "${{ matrix.benchmark }}" == "true" ]] ; then ./benchmarks/obs_benchmarks ; fi
59+
if [[ "${{ matrix.benchmark }}" == "benchmark" ]] ; then ./benchmarks/obs_benchmarks ; fi

CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# Observable Library
2-
# Copyright (C) 2016-2025 David Capello
2+
# Copyright (C) 2016-present David Capello
33

44
cmake_minimum_required(VERSION 3.15)
55

66
project(observable CXX)
77
option(OBSERVABLE_TESTS "Compile observable tests" ON)
88
option(OBSERVABLE_BENCHMARKS "Compile observable benchmarks" OFF)
9+
option(OBSERVABLE_FAST_LIST "Use fast list (non-thread safe) instead of safe (thread-safe) one by default" OFF)
910

1011
set(CMAKE_CXX_STANDARD 11)
1112
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -17,6 +18,10 @@ endif()
1718
add_library(obs obs/connection.cpp)
1819
target_include_directories(obs PUBLIC .)
1920

21+
if(OBSERVABLE_FAST_LIST)
22+
target_compile_definitions(obs PUBLIC OBSERVABLE_FAST_LIST)
23+
endif()
24+
2025
if(OBSERVABLE_TESTS)
2126
enable_testing()
2227
add_subdirectory(tests)

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Observable Library
22
==================
33

4-
*Copyright (C) 2016-2025 David Capello*
4+
*Copyright (C) 2016-present David Capello*
55

66
[![build](https://github.com/dacap/observable/actions/workflows/build.yml/badge.svg)](https://github.com/dacap/observable/actions/workflows/build.yml)
77
[![MIT Licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
@@ -70,9 +70,21 @@ int main() {
7070
}
7171
```
7272

73-
Tested Compilers
73+
Safe vs Fast
7474
----------------
7575

76-
* Visual Studio 2015
77-
* Xcode 7.3.1 (`-std=c++11`)
78-
* GCC 4.8.4 (`-std=c++11`)
76+
There are two variants of signals and observers:
77+
78+
* `obs::safe_signal` vs `obs::fast_signal`
79+
* `obs::safe_observers` vs `obs::fast_observers`
80+
81+
The only difference between both is that the "fast" version uses
82+
`obs::fast_list` instead of `obs::safe_list`, where the former keeps
83+
track of slots with a `std::vector` and the later one is a thread-safe
84+
version list to connect/disconnect slots from different threads.
85+
86+
By default and for backward compatibility, `obs::signal` and
87+
`obs::observers` use the safe version, but you can enable the
88+
`OBSERVABLE_FAST_LIST` option to switch from `obs::safe_list` to
89+
`obs::fast_list` which is recommended for most cases (e.g. you don't
90+
need to do strange connections/disconnections as in [tests](tests)).

benchmarks/obs_benchmarks.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ static void BM_ObsSignal(benchmark::State& state) {
5454
BENCHMARK(BM_ObsSignal)->Range(1, 1024);
5555

5656
static void BM_ObsThreads(benchmark::State& state) {
57-
obs::signal<void()> sig;
57+
obs::safe_signal<void()> sig;
5858
for (auto _ : state) {
5959
state.PauseTiming();
6060
std::vector<std::thread> threads;

obs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#define OBS_H_INCLUDED
99
#pragma once
1010

11+
#include "obs/lists.h"
1112
#include "obs/observable.h"
1213
#include "obs/observers.h"
1314
#include "obs/signal.h"

obs/connection.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// Observable Library
2-
// Copyright (c) 2016 David Capello
2+
// Copyright (c) 2016-present David Capello
33
//
44
// This file is released under the terms of the MIT license.
55
// Read LICENSE.txt for more information.
66

77
#include "obs/connection.h"
88
#include "obs/signal.h"
99

10+
#include <cassert>
11+
1012
namespace obs {
1113

1214
void connection::disconnect() {

obs/fast_list.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Observable Library
2+
// Copyright (c) 2026-present David Capello
3+
//
4+
// This file is released under the terms of the MIT license.
5+
// Read LICENSE.txt for more information.
6+
7+
#ifndef OBS_FAST_LIST_H_INCLUDED
8+
#define OBS_FAST_LIST_H_INCLUDED
9+
#pragma once
10+
11+
#include <algorithm>
12+
#include <functional>
13+
#include <vector>
14+
15+
namespace obs {
16+
17+
template<typename T>
18+
class fast_list {
19+
std::vector<T*> m_list;
20+
21+
public:
22+
using iterator = typename std::vector<T*>::iterator;
23+
24+
fast_list() = default;
25+
~fast_list() = default;
26+
27+
bool empty() const { return m_list.empty(); }
28+
iterator begin() { return m_list.begin(); }
29+
iterator end() { return m_list.end(); }
30+
31+
void push_back(T* value) {
32+
m_list.push_back(value);
33+
}
34+
35+
void erase(T* value) {
36+
auto it = std::find(m_list.begin(), m_list.end(), value);
37+
if (it != m_list.end())
38+
m_list.erase(it);
39+
}
40+
};
41+
42+
} // namespace obs
43+
44+
#endif

obs/lists.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Observable Library
2+
// Copyright (c) 2026-present David Capello
3+
//
4+
// This file is released under the terms of the MIT license.
5+
// Read LICENSE.txt for more information.
6+
7+
#ifndef OBS_LISTS_H_INCLUDED
8+
#define OBS_LISTS_H_INCLUDED
9+
#pragma once
10+
11+
#include "obs/fast_list.h"
12+
#include "obs/safe_list.h"
13+
14+
namespace obs {
15+
16+
#ifdef OBSERVABLE_FAST_LIST
17+
template<typename T>
18+
using default_list = fast_list<T>;
19+
#else
20+
template<typename T>
21+
using default_list = safe_list<T>;
22+
#endif
23+
24+
} // namespace obs
25+
26+
#endif

obs/observable.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Observable Library
2-
// Copyright (c) 2016 David Capello
2+
// Copyright (c) 2016-present David Capello
33
//
44
// This file is released under the terms of the MIT license.
55
// Read LICENSE.txt for more information.
@@ -12,7 +12,7 @@
1212

1313
namespace obs {
1414

15-
template<typename Observer>
15+
template<typename Observer, typename List = observers<Observer, default_list>>
1616
class observable {
1717
public:
1818

@@ -34,9 +34,15 @@ class observable {
3434
}
3535

3636
private:
37-
observers<Observer> m_observers;
37+
List m_observers;
3838
};
3939

40+
template<typename Observer>
41+
using fast_observable = observable<Observer, fast_observers<Observer>>;
42+
43+
template<typename Observer>
44+
using safe_observable = observable<Observer, safe_observers<Observer>>;
45+
4046
} // namespace obs
4147

4248
#endif

obs/observers.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
#define OBS_OBSERVERS_H_INCLUDED
99
#pragma once
1010

11-
#include "obs/safe_list.h"
11+
#include "obs/lists.h"
1212

1313
namespace obs {
1414

15-
template<typename T>
15+
template<typename T, template<typename> class List = default_list>
1616
class observers {
1717
public:
1818
using observer_type = T;
19-
using list_type = safe_list<observer_type>;
19+
using list_type = List<observer_type>;
2020
using iterator = typename list_type::iterator;
2121

2222
bool empty() const { return m_observers.empty(); }
@@ -42,6 +42,12 @@ class observers {
4242
list_type m_observers;
4343
};
4444

45+
template<typename T>
46+
using fast_observers = observers<T, fast_list>;
47+
48+
template<typename T>
49+
using safe_observers = observers<T, safe_list>;
50+
4551
} // namespace obs
4652

4753
#endif

0 commit comments

Comments
 (0)