{"id":27880588,"url":"https://github.com/lumia431/reaction","last_synced_at":"2025-05-05T04:03:17.112Z","repository":{"id":281166157,"uuid":"944416086","full_name":"lumia431/reaction","owner":"lumia431","description":"A lightweight, header-only reactive programming framework leveraging modern C++20 features for building efficient dataflow applications.","archived":false,"fork":false,"pushed_at":"2025-04-29T14:42:29.000Z","size":562,"stargazers_count":285,"open_issues_count":0,"forks_count":12,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-29T15:26:22.018Z","etag":null,"topics":["modern-cpp","mvvm-framework","reactive-framework","template-meta-programming","ui-dataflow"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lumia431.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-03-07T10:01:39.000Z","updated_at":"2025-04-29T14:12:40.000Z","dependencies_parsed_at":"2025-03-07T11:22:42.597Z","dependency_job_id":"6157618e-c6cd-468e-b145-349d4610bc6d","html_url":"https://github.com/lumia431/reaction","commit_stats":null,"previous_names":["lumia431/reaction"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lumia431%2Freaction","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lumia431%2Freaction/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lumia431%2Freaction/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lumia431%2Freaction/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lumia431","download_url":"https://codeload.github.com/lumia431/reaction/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252436292,"owners_count":21747470,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["modern-cpp","mvvm-framework","reactive-framework","template-meta-programming","ui-dataflow"],"created_at":"2025-05-05T04:02:26.253Z","updated_at":"2025-05-05T04:03:17.024Z","avatar_url":"https://github.com/lumia431.png","language":"C++","readme":"# Reaction: Modern C++ Reactive Programming Framework\n\n[![C++20](https://img.shields.io/badge/C++-20-blue.svg)](https://en.cppreference.com/w/cpp/20)\n[![Header-only](https://img.shields.io/badge/Header--only-Yes-green.svg)](https://en.wikipedia.org/wiki/Header-only)\n[![CMake](https://img.shields.io/badge/CMake-3.15+-blueviolet.svg)](https://cmake.org)\n[![ReactiveX](https://img.shields.io/badge/Reactive-Programming-ff69b4.svg)](https://reactivex.io)\n[![TMP](https://img.shields.io/badge/Template-Metaprogramming-orange.svg)](https://en.cppreference.com/w/cpp/language/templates)\n[![MVVM](https://img.shields.io/badge/Pattern-MVVM%2FMVC-9cf.svg)](https://en.wikipedia.org/wiki/Model–view–viewmodel)\n\nReaction is a blazing-fast, modern C++20 header-only reactive framework that brings React/Vue-style dataflow to native C++ – perfect for UI, game logic, and more.\n\n### 🎯 **Focused on UI Dataflow Management**\n\n- **Pure Data-Driven Updates** – Optimized for **one-way binding** (Model → View)\n- **No Event Emitters** – Changes propagate **only through data dependencies**, avoiding callback hell\n- **Predictable Updates** – Strict **top-down dataflow** like React/Vue, but with zero runtime overhead\n\n#### **Ideal For:**\n✅ **MVVM/MVC UI Architectures**\n✅ **Game Object Properties**\n✅ **Form Validation Chains**\n✅ **Animation State Machines**\n\n### 🚀 Performance Optimized\n\n- **Zero-cost abstractions** through template metaprogramming\n- Minimal runtime overhead with **smart change propagation**\n- Propagation efficiency **at the level of millions per second**\n\n### 🔗 Intelligent Dependency Management\n\n- Automatic **DAG detection** and cycle prevention\n- Fine-grained **change propagation control**\n- Configurable **caching strategies**\n\n### 🛡️ Safety Guarantees\n\n- Compile-time **type checking** with C++20 concepts\n- Safe **value semantics** throughout the framework\n- Framework manages object lifetime internally\n\n### 🧩 Extensible Design\n\n| Feature          | Options                          |\n|------------------|----------------------------------|\n| Trigger Policy   | ValueChange, Threshold, Timer, Custom |\n| Invalidation     | Direct, KeepCalculate, LastValue |\n\n### 📦 Requirements\n\n- **Compiler**: C++20 compatible (GCC 10+, Clang 12+, MSVC 19.30+)\n- **Build System**: CMake 3.15+\n\n## 🛠 Installation\n\nTo build and install the `reaction` reactive framework, follow the steps below:\n\n```bash\ngit clone https://github.com/lumia431/reaction.git \u0026\u0026 cd reaction\ncmake -B build\ncmake --build build/\ncmake --install build/ --prefix /your/install/path\n```\n\nAfter installation, you can include and link against reaction in your own CMake-based project:\n\n```cmake\nfind_package(reaction REQUIRED)\ntarget_link_libraries(your_target PRIVATE reaction)\n```\n\n### 🚀 Quick Start\n\n```cpp\n#include \u003creaction/reaction.h\u003e\n#include \u003ciostream\u003e\n#include \u003ciomanip\u003e\n#include \u003ccmath\u003e\n\nint main() {\n    using namespace reaction;\n\n    // 1. Reactive variables for stock prices\n    auto buyPrice = var(100.0);      // Price at which stock was bought\n    auto currentPrice = var(105.0);  // Current market price\n\n    // 2. Use 'calc' to compute profit or loss amount\n    auto profit = calc([\u0026]() {\n        return currentPrice() - buyPrice();\n    });\n\n    // 3. Use 'expr' to compute percentage gain/loss\n    auto profitPercent = expr(std::abs(currentPrice - buyPrice) / buyPrice * 100);\n\n    // 4. Use 'action' to print the log whenever values change\n    auto logger = action([\u0026]() {\n        std::cout \u003c\u003c std::fixed \u003c\u003c std::setprecision(2);\n        std::cout \u003c\u003c \"[Stock Update] Current Price: $\" \u003c\u003c currentPrice()\n                  \u003c\u003c \", Profit: $\" \u003c\u003c profit()\n                  \u003c\u003c \" (\" \u003c\u003c profitPercent() \u003c\u003c \"%)\" \u003c\u003c std::endl;\n    });\n\n    // Simulate price changes\n    currentPrice.value(110.0).value(95.0);  // Stock price increases\n    *buyPrice = 90.0;                       // Buy price adjusted\n\n    return 0;\n}\n```\n\n### 📖 Basic Usage\n\n#### 1. Reactive Variables: `var`\n\nDefine reactive state variables with `var\u003cT\u003e`.\n\n```cpp\nauto a = reaction::var(1);         // int variable\nauto b = reaction::var(3.14);      // double variable\n```\n\n- Method-style get value:\n\n```cpp\nauto val = a.get();\n```\n\n- brief way:\n\n```cpp\nauto val = a();\n```\n\n- Method-style assignment:\n\n```cpp\na.value(2);\n```\n\n- Pointer-style assignment:\n\n```cpp\n*a = 2;\n```\n\n#### 2. Derived Computation: calc\n\nUse **calc** to create reactive computations based on one or more var instances.\n\n- Lambda Capture Style:\n\n```cpp\nauto a = reaction::var(1);\nauto b = reaction::var(3.14);\nauto sum = reaction::calc([=]() {\n    return a() + b();  // Retrieve current values using a() and b()\n});\n```\n\n- Parameter Binding Style (High Performance):\n\n```cpp\nauto ds = reaction::calc([](auto aa, auto bb) {\n    return std::to_string(aa) + std::to_string(bb);\n}, a, b);  // Dependencies: a and b\n```\n\n#### 3. Declarative Expression: expr\n\nexpr provides a clean and concise syntax to declare reactive expressions. The result automatically updates when any dependent variable changes.\n\n```cpp\nauto a = reaction::var(1);\nauto b = reaction::var(2);\nauto result = reaction::expr(a + b * 3);  // result updates automatically when 'a' or 'b' change\n```\n\n#### 4. Reactive Side Effects: action\n\nRegister actions to perform side effects whenever the observed variables change.\n\n```cpp\nint val = 10;\nauto a = reaction::var(1);\nauto dds = reaction::action([\u0026val]() {\n    val = a();\n});\n```\n\nOfcourse, to get high performance can use Parameter Binding Style.\n\n```cpp\nint val = 10;\nauto a = reaction::var(1);\nauto dds = reaction::action([\u0026val](auto aa) {\n    val = aa;\n}, a);\n```\n\n#### 5. Reactive Struct Fields: `Field`\n\nFor complex types with reactive fields allow you to define struct-like variables whose members are individually reactive.\n\nHere's an example of a `PersonField` class:\n\n```cpp\nclass PersonField : public reaction::FieldBase {\npublic:\n    PersonField(std::string name, int age):\n        m_name(reaction::field(this, name)),\n        m_age(reaction::field(this, age)){}\n\n    std::string getName() const { return m_name.get(); }\n    void setName(const std::string \u0026name) { *m_name = name; }\n    int getAge() const { return m_age.get(); }\n    void setAge(int age) { *m_age = age; }\n\nprivate:\n    reaction::Field\u003cstd::string\u003e m_name;\n    reaction::Field\u003cint\u003e m_age;\n};\n\nauto p = reaction::var(PersonField{\"Jack\", 18});\nauto action = reaction::action(\n    []() {\n        std::cout \u003c\u003c \"Action Trigger , name = \" \u003c\u003c p().getName() \u003c\u003c \" age = \" \u003c\u003c p().getAge() \u003c\u003c '\\n';\n    });\n\np-\u003esetName(\"Jackson\"); // Action Trigger\np-\u003esetAge(28);         // Action Trigger\n```\n\n#### 6. Copy and move semantics support\n\n```cpp\nauto a = reaction::var(1);\nauto b = reaction::var(3.14);\nauto ds = reaction::calc([]() { return a() + b(); });\nauto ds_copy = ds;\nauto ds_move = std::move(ds);\nEXPECT_FALSE(static_cast\u003cbool\u003e(ds));\n```\n\n#### 7. Resetting Nodes and Dependencies\n\nThe reaction framework allows you to **reset a computation node** by replacing its computation function.\nThis mechanism is useful when the result needs to be recalculated using a different logic or different dependencies after the node has been initially created.\n\n``Note:`` **The return value type cannot be changed**\n\nBelow is an example that demonstrates the reset functionality:\n\n```cpp\nTEST(TestReset, ReactionTest) {\n    auto a = reaction::var(1);\n    auto b = reaction::var(std::string{\"2\"});\n    auto ds = reaction::calc([]() { return std::to_string(a()); });\n    auto ret = ds.set([=]() { return b() + \"set\"; });\n    EXPECT_EQ(ret, reaction::ReactionError::NoErr);\n\n    ret = ds.set([=]() { return a(); });\n    EXPECT_EQ(ret, reaction::ReactionError::ReturnTypeErr);\n\n    ret = ds.set([=]() { return ds(); });\n    EXPECT_EQ(ret, reaction::ReactionError::CycleDepErr);\n}\n```\n\n#### 8. Trigger Mode\n\nThe `reaction` framework supports various triggering mode to control when reactive computations are re-evaluated. This example demonstrates three mode:\n\n1. **Value Change Trigger:** The reactive computation is triggered only when the underlying value actually changes.\n2. **Threshold Trigger:** The reactive computation is triggered when the value crosses a specified threshold.\n3. **Always Trigger:** (Not explicitly shown in this example) Always triggers regardless of whether the value has changed.\n\nThe trigger Mode can be specified by the type parameter\n\n```cpp\nusing namespace reaction;\nauto stockPrice = var(100.0);\nauto profit = expr\u003cChangedTrigger\u003e(stockPrice() - 100.0);\nauto assignAction = action([=]() {  // defalut AlwaysTrigger\n    std::cout \u003c\u003c \"Checky assign, price = \" \u003c\u003c stockPrice() \u003c\u003c'\\n';\n});\nauto sellAction = action\u003cThresholdTrigger\u003e([=]() {\n    std::cout \u003c\u003c \"It's time to sell, profit = \" \u003c\u003c profit() \u003c\u003c'\\n';\n});\nsellAction.setThreshold([=]() {\n    return profit() \u003e 5.0;\n});\n*stockPrice = 100.0; // assignAction trigger\n*stockPrice = 101.0; // assignAction, profit trigger\n*stockPrice = 106.0; // all trigger\n\n```\n\nYou can even define a trigger mode yourself in your code, just include the **checkTrigger** method:\n\n```cpp\nstruct MyTrigger {\n    bool checkTrigger() {\n        // do something\n        return true;\n    }\n};\nauto a = var(1);\nauto b = expr\u003cMyTrigger\u003e(a + 1);\n```\n\n#### 9. Invalid Strategies\n\nIn the `reaction` framework, all data sources **obtained by users are actually in the form of weak references**, and their actual memory is managed **in the observer map**.\nUsers can manually call the **close** method, so that all dependent data sources will also be closed.\n\n```cpp\nauto a = reaction::var(1);\nauto b = reaction::var(2);\nauto dsA = reaction::calc([=]() { return a(); });\nauto dsB = reaction::calc([=]() { return dsA() + b(); });\ndsA.close(); //dsB will automatically close, cause dsB dependents dsA.\nEXPECT_FALSE(static_cast\u003cbool\u003e(dsA));\nEXPECT_FALSE(static_cast\u003cbool\u003e(dsB));\n```\n\nHowever, for scenarios where the lifecycle of a weak reference acquired by user ends, the `reaction` framework makes several strategy for different scenarios.\n\n- **DirectCloseStrategy:**\n  The node is immediately closed (made invalid) when any of its dependencies become invalid.\n\n- **KeepCalcStrategy:**\n  The node continues to recalculate, its dependencies work normally.\n\n- **LastValStrategy:**\n  The node retains the last valid, its dependencies use the value to calculate.\n\nBelow is a concise example that illustrates all three strategies:\n\n```cpp\n{\n    auto a = var(1);\n    auto b = calc([]() { return a(); });\n    {\n        auto temp = calc([]() { return a(); }); // default is DirectCloseStrategy\n        b.set([]() { return temp(); });\n    }\n    // temp lifecycle ends, b will end too.\n    EXPECT_FALSE(static_cast\u003cbool\u003e(b));\n}\n{\n    auto a = var(1);\n    auto b = calc([]() { return a(); });\n    {\n        auto temp = calc\u003cAlwaysTrigger, KeepCalcStrategy\u003e([]() { return a(); }); // default is DirectFailureStrategy\n        b.set([]() { return temp(); });\n    }\n    // temp lifecycle ends, b not be influenced.\n    EXPECT_TRUE(static_cast\u003cbool\u003e(b));\n    EXPECT_EQ(b.get(), 1);\n    a.value(2);\n    EXPECT_EQ(b.get(), 2);\n}\n{\n    auto a = var(1);\n    auto b = calc([]() { return a(); });\n    {\n        auto temp = calc\u003cAlwaysTrigger, LastValStrategy\u003e([]() { return a(); }); // default is DirectFailureStrategy\n        b.set([]() { return temp(); });\n    }\n    // temp lifecycle ends, b use its last val to calculate.\n    EXPECT_TRUE(static_cast\u003cbool\u003e(b));\n    EXPECT_EQ(b.get(), 1);\n    a.value(2);\n    EXPECT_EQ(b.get(), 1);\n}\n```\n\nLikewise, you can define a strategy yourself in your code, just include the **handleInvalid** method:\n\n```cpp\nstruct MyStrategy {\n    void handleInvalid() {\n        std::cout \u003c\u003c \"Invalid\" \u003c\u003c std::endl;\n    }\n};\nauto a = var(1);\nauto b = expr\u003cAlwaysTrigger, MyStrategy\u003e(a + 1);\n```\n\n## **Contributions Welcome!**\n\nWe welcome all forms of contributions to make **Reaction** even better:\n\n### **How to Contribute**\n1. **Report Issues**\n   🐛 Found a bug? [Open an Issue](https://github.com/lumia431/reaction/issues) with detailed reproduction steps.\n\n2. **Suggest Features**\n   💡 Have an idea? Propose new features through GitHub Discussions.\n\n3. **Submit Pull Requests**\n   👩💻 Follow our workflow:\n   ```bash\n   git clone https://github.com/lumia431/reaction.git\n   cd reaction\n   # Create a feature branch (feat/xxx or fix/xxx)\n   # Submit PR against `dev` branch","funding_links":[],"categories":["Miscellaneous","Recently Updated"],"sub_categories":["[May 03, 2025](/content/2025/05/03/README.md)"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flumia431%2Freaction","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flumia431%2Freaction","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flumia431%2Freaction/lists"}