Skip to content

Commit ff52e1d

Browse files
lumia431cursoragent
andcommitted
feat: Add compound assignment and increment/decrement operators for reactive variables (#18)
- Add type-safe operator overloads (++, --, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=) - Restrict operators to VarExpr types only using C++20 concepts - Maintain reactive notifications and batch operation compatibility - Add comprehensive unit tests (10 test cases, 100% pass rate) - Preserve all existing functionality (75/75 tests pass) Enables natural C++ syntax: auto v = var(1); v++; ++v; v += 1; Files modified: - include/reaction/core/concept.h: Added operator type safety concepts - include/reaction/core/react.h: Implemented operator overloads - test/unit/test_operators.cpp: Added comprehensive unit tests Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 11d6930 commit ff52e1d

31 files changed

+1867
-357
lines changed

CMakeLists.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,14 @@ install(
105105
EXPORT ${PROJECT_NAME}Targets
106106
NAMESPACE ${PROJECT_NAME}::
107107
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
108-
)
108+
)
109+
110+
if(NOT TARGET uninstall)
111+
configure_file(
112+
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
113+
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
114+
IMMEDIATE @ONLY)
115+
116+
add_custom_target(uninstall
117+
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
118+
endif()

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ After installation, you can include and link against reaction in your own CMake-
102102
find_package(reaction REQUIRED)
103103
```
104104

105+
### Uninstall
106+
107+
To uninstall the framework:
108+
109+
```bash
110+
cmake --build build/ --target uninstall
111+
```
112+
105113
If you want to run example or test units:
106114

107115
```bash

README.zh-CN.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ cmake --install build/ --prefix /your/install/path
102102
find_package(reaction REQUIRED)
103103
```
104104

105+
### 卸载
106+
107+
要卸载框架:
108+
109+
```bash
110+
cmake --build build/ --target uninstall
111+
```
112+
105113
如需运行示例或测试单元:
106114

107115
```bash

cmake/cmake_uninstall.cmake.in

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
2+
message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
3+
endif()
4+
5+
file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
6+
string(REGEX REPLACE "\n" ";" files "${files}")
7+
foreach(file ${files})
8+
message(STATUS "Uninstalling: $ENV{DESTDIR}${file}")
9+
if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
10+
exec_program(
11+
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
12+
OUTPUT_VARIABLE rm_out
13+
RETURN_VALUE rm_retval
14+
)
15+
if(NOT "${rm_retval}" STREQUAL 0)
16+
message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
17+
endif()
18+
else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
19+
message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
20+
endif()
21+
endforeach()

example/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ file(GLOB EXAMPLE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
22
foreach(example_file ${EXAMPLE_SOURCES})
33
get_filename_component(example_name ${example_file} NAME_WE)
44
add_executable(${example_name} ${example_file})
5-
target_link_libraries(${example_name} PRIVATE ${PROJECT_NAME})
5+
target_link_libraries(${example_name} PRIVATE ${PROJECT_NAME} -pthread)
66
endforeach()

example/multi_thread_example.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (c) 2025 Lummy
3+
*
4+
* This software is released under the MIT License.
5+
* See the LICENSE file in the project root for full details.
6+
*/
7+
8+
#include <cmath>
9+
#include <iomanip>
10+
#include <iostream>
11+
#include <random>
12+
#include <reaction.h>
13+
#include <thread>
14+
#include <vector>
15+
16+
using namespace std;
17+
using namespace reaction;
18+
19+
constexpr int N = 100;
20+
21+
thread_local std::mt19937 gen(std::random_device{}());
22+
thread_local std::uniform_int_distribution<> dist(0, N - 1);
23+
24+
vector<Var<int>> g_v(N);
25+
vector<Calc<int>> g_c(N);
26+
vector<Action<>> g_a(N);
27+
28+
void test1() {
29+
auto rd1 = dist(gen);
30+
auto rd2 = dist(gen);
31+
auto rd3 = dist(gen);
32+
33+
g_c[rd1].reset([&]() {
34+
return g_v[rd2]() + g_v[rd3]();
35+
});
36+
}
37+
38+
void test2() {
39+
auto rd1 = dist(gen);
40+
auto rd2 = dist(gen);
41+
auto rd3 = dist(gen);
42+
43+
g_a[rd1].reset([&]() {
44+
cout << g_c[rd2]() + g_c[rd3]() << '\n';
45+
});
46+
}
47+
48+
int main() {
49+
for (int i = 0; i < N; ++i) {
50+
g_v[i] = var(i);
51+
g_c[i] = calc([]() { return 1; });
52+
g_a[i] = action([]() {});
53+
}
54+
55+
vector<thread> v_t1, v_t2;
56+
v_t1.reserve(N);
57+
v_t2.reserve(N);
58+
59+
for (int i = 0; i < N; ++i) {
60+
v_t1.emplace_back(test1);
61+
v_t2.emplace_back(test2);
62+
}
63+
64+
for (auto &t : v_t1)
65+
t.join();
66+
for (auto &t : v_t2)
67+
t.join();
68+
69+
cout << "Done\n";
70+
return 0;
71+
}

include/reaction.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* This is the primary header file that provides access to all Reaction framework
1515
* functionality. Include this file to get complete access to reactive programming
1616
* features including:
17-
*
17+
*
1818
* - Reactive variables, calculations, and expressions
1919
* - Observer pattern implementation
2020
* - Batch operations for performance optimization
@@ -29,14 +29,14 @@
2929

3030
// Core reactive node and resource management
3131
#include "reaction/core/observer_node.h"
32-
#include "reaction/core/resource.h"
3332
#include "reaction/core/react.h"
33+
#include "reaction/core/resource.h"
3434

3535
// === Graph Management ===
3636

3737
// Observer and dependency graph management
38-
#include "reaction/graph/observer_graph.h"
3938
#include "reaction/graph/field_graph.h"
39+
#include "reaction/graph/observer_graph.h"
4040

4141
// === Expression System ===
4242

@@ -65,8 +65,8 @@
6565
#include "reaction/core/exception.h"
6666

6767
// Support utilities
68-
#include "reaction/core/id_generator.h"
6968
#include "reaction/concurrency/global_state.h"
69+
#include "reaction/core/id_generator.h"
7070
#include "reaction/core/raii_guards.h"
7171

7272
// === Factory Functions ===

include/reaction/concurrency/global_state.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ inline bool isBatchFunctionActive() noexcept {
6868

6969
/**
7070
* @brief Reset all global state to default values.
71-
*
71+
*
7272
* Useful for cleanup or testing scenarios.
7373
*/
7474
inline void resetGlobalState() noexcept {

include/reaction/concurrency/thread_safety.h

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
#pragma once
99

1010
#include <atomic>
11-
#include <shared_mutex>
11+
#include <memory>
1212
#include <mutex>
13+
#include <shared_mutex>
1314
#include <thread>
1415
#include <unordered_map>
15-
#include <memory>
1616

1717
// Compile-time configuration for forcing thread safety
1818
#ifndef REACTION_FORCE_THREAD_SAFETY
@@ -22,9 +22,9 @@
2222
// Thread safety mode detection
2323
#ifndef REACTION_THREAD_SAFETY_MODE
2424
#if REACTION_FORCE_THREAD_SAFETY
25-
#define REACTION_THREAD_SAFETY_MODE 1 // Always thread-safe
25+
#define REACTION_THREAD_SAFETY_MODE 1 // Always thread-safe
2626
#else
27-
#define REACTION_THREAD_SAFETY_MODE 0 // Auto-detect
27+
#define REACTION_THREAD_SAFETY_MODE 0 // Auto-detect
2828
#endif
2929
#endif
3030

@@ -48,7 +48,7 @@ class ThreadSafetyManager {
4848
* @brief Get the singleton instance.
4949
* @return ThreadSafetyManager& Reference to the singleton instance.
5050
*/
51-
static ThreadSafetyManager& getInstance() noexcept {
51+
static ThreadSafetyManager &getInstance() noexcept {
5252
static ThreadSafetyManager instance;
5353
return instance;
5454
}
@@ -148,12 +148,12 @@ class ThreadSafetyManager {
148148
private:
149149
ThreadSafetyManager() = default;
150150
~ThreadSafetyManager() = default;
151-
ThreadSafetyManager(const ThreadSafetyManager&) = delete;
152-
ThreadSafetyManager& operator=(const ThreadSafetyManager&) = delete;
151+
ThreadSafetyManager(const ThreadSafetyManager &) = delete;
152+
ThreadSafetyManager &operator=(const ThreadSafetyManager &) = delete;
153153

154154
std::atomic<bool> m_threadSafetyEnabled{REACTION_THREAD_SAFETY_MODE};
155155
std::atomic<std::thread::id> m_firstThreadId{};
156-
std::atomic<uint32_t> m_safetyVersion{1}; // Version counter for cache invalidation
156+
std::atomic<uint32_t> m_safetyVersion{1}; // Version counter for cache invalidation
157157
};
158158

159159
/**
@@ -177,7 +177,7 @@ class ThreadRegistrationGuard {
177177
*
178178
* @tparam MutexType The underlying mutex type (std::mutex, std::shared_mutex, etc.)
179179
*/
180-
template<typename MutexType>
180+
template <typename MutexType>
181181
class ConditionalMutexWrapper {
182182
public:
183183
/// @brief Acquire lock if thread safety is enabled.
@@ -203,23 +203,23 @@ class ConditionalMutexWrapper {
203203
}
204204

205205
// Shared mutex operations (SFINAE-enabled for types that support them)
206-
template<typename T = MutexType>
206+
template <typename T = MutexType>
207207
auto lock_shared() noexcept(noexcept(std::declval<T>().lock_shared()))
208208
-> decltype(std::declval<T>().lock_shared(), void()) {
209209
if (ThreadSafetyManager::getInstance().isThreadSafetyEnabled()) [[likely]] {
210210
m_mutex.lock_shared();
211211
}
212212
}
213213

214-
template<typename T = MutexType>
214+
template <typename T = MutexType>
215215
auto unlock_shared() noexcept(noexcept(std::declval<T>().unlock_shared()))
216216
-> decltype(std::declval<T>().unlock_shared(), void()) {
217217
if (ThreadSafetyManager::getInstance().isThreadSafetyEnabled()) [[likely]] {
218218
m_mutex.unlock_shared();
219219
}
220220
}
221221

222-
template<typename T = MutexType>
222+
template <typename T = MutexType>
223223
auto try_lock_shared() noexcept(noexcept(std::declval<T>().try_lock_shared()))
224224
-> decltype(std::declval<T>().try_lock_shared()) {
225225
if (ThreadSafetyManager::getInstance().isThreadSafetyEnabled()) [[likely]] {
@@ -251,26 +251,26 @@ using ConditionalMutex = ConditionalMutexWrapper<std::mutex>;
251251
* @param LockMethod The locking method name
252252
* @param UnlockMethod The unlocking method name
253253
*/
254-
#define REACTION_DEFINE_LOCK_GUARD(GuardName, LockMethod, UnlockMethod) \
255-
template<typename Mutex> \
256-
class GuardName { \
257-
public: \
258-
explicit GuardName(Mutex& mutex) noexcept(noexcept(mutex.LockMethod())) \
259-
: m_mutex(mutex) { \
260-
m_mutex.LockMethod(); \
261-
} \
262-
\
263-
~GuardName() noexcept(noexcept(m_mutex.UnlockMethod())) { \
264-
m_mutex.UnlockMethod(); \
265-
} \
266-
\
267-
GuardName(const GuardName&) = delete; \
268-
GuardName& operator=(const GuardName&) = delete; \
269-
GuardName(GuardName&&) = delete; \
270-
GuardName& operator=(GuardName&&) = delete; \
271-
\
272-
private: \
273-
Mutex& m_mutex; \
254+
#define REACTION_DEFINE_LOCK_GUARD(GuardName, LockMethod, UnlockMethod) \
255+
template <typename Mutex> \
256+
class GuardName { \
257+
public: \
258+
explicit GuardName(Mutex &mutex) noexcept(noexcept(mutex.LockMethod())) \
259+
: m_mutex(mutex) { \
260+
m_mutex.LockMethod(); \
261+
} \
262+
\
263+
~GuardName() noexcept(noexcept(m_mutex.UnlockMethod())) { \
264+
m_mutex.UnlockMethod(); \
265+
} \
266+
\
267+
GuardName(const GuardName &) = delete; \
268+
GuardName &operator=(const GuardName &) = delete; \
269+
GuardName(GuardName &&) = delete; \
270+
GuardName &operator=(GuardName &&) = delete; \
271+
\
272+
private: \
273+
Mutex &m_mutex; \
274274
}
275275

276276
// Generate optimized lock guards
@@ -280,21 +280,21 @@ REACTION_DEFINE_LOCK_GUARD(ConditionalSharedLockGuard, lock_shared, unlock_share
280280
/**
281281
* @brief RAII shared lock guard for conditional shared mutex.
282282
*/
283-
template<typename Mutex>
283+
template <typename Mutex>
284284
using ConditionalSharedLock = ConditionalSharedLockGuard<Mutex>;
285285

286286
/**
287287
* @brief RAII unique lock guard for conditional mutex.
288288
*/
289-
template<typename Mutex>
289+
template <typename Mutex>
290290
using ConditionalUniqueLock = ConditionalLockGuard<Mutex>;
291291

292292
// Helper macros for thread registration with better performance
293-
#define REACTION_REGISTER_THREAD() \
294-
do { \
293+
#define REACTION_REGISTER_THREAD() \
294+
do { \
295295
static thread_local reaction::ThreadRegistrationGuard thread_guard_##__COUNTER__; \
296-
(void)thread_guard_##__COUNTER__; \
297-
} while(0)
296+
(void)thread_guard_##__COUNTER__; \
297+
} while (0)
298298

299299
// Cleanup macro to avoid pollution
300300
#undef REACTION_DEFINE_LOCK_GUARD

0 commit comments

Comments
 (0)