Page MenuHomePhorge

No OneTemporary

Size
7 KB
Referenced Files
None
Subscribers
None
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7872d72..dc966a5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,33 +1,34 @@
cmake_minimum_required(VERSION 3.16)
project(lager-qt-demo)
include(FeatureSummary)
set(QT_MAJOR_VERSION 6)
set(KF_MAJOR_VERSION 6)
set(QT_MIN_VERSION 6.5.0)
set(KF_MIN_VERSION 6.0.0)
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMSetupVersion)
include(ECMGenerateHeaders)
include(ECMPoQmTools)
include(ECMAddTests)
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
include(ECMQmlModule)
kde_enable_exceptions()
find_package(Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Widgets Test)
qt6_policy(SET QTP0001 NEW)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Kirigami)
find_package(Threads REQUIRED)
+find_package(Lager REQUIRED)
add_subdirectory(src)
diff --git a/doc.org b/doc.org
index 1c25e6b..8cafd12 100644
--- a/doc.org
+++ b/doc.org
@@ -1,58 +1,87 @@
+#+PROPERTY: header-args :exports code
* What is lager?
- library to help with *value-oriented design*
- store application state as a *value* type
+- state transformations are described in pure functions (*reducers*), src_c++{auto update(Model, Action) -> Model;}
+- actions are also value types
* What is a value type?
- copyable
- equality comparable
#+BEGIN_SRC c++
T a = ...;
T b = a;
REQUIRE(a == b);
#+END_SRC
- identity does not depend on a reference
#+BEGIN_SRC c++
// not a value type
struct Person
{
std::string name;
std::vector<std::weak_ptr<Person>> contacts;
};
// value type
struct Person
{
using IdType = std::string;
IdType id;
std::string name;
std::vector<IdType> contacts;
};
#+END_SRC
* Basic example of a value-oriented program
#+INCLUDE: "src/basic-example/logic.hpp" src c++
#+INCLUDE: "src/basic-example/logic.cpp" src c++
#+INCLUDE: "src/basic-example/main.cpp" src c++
+The basic example just covered doesn't even make use of lager (!!!)
* Benefits of using lager?
- Testability
- libkazv: [[https://lily-is.land/kazv/libkazv][76.9%]] test coverage
- libQuotient: [[https://sonarcloud.io/component_measures?metric=coverage&selected=quotient-im_libQuotient%3AQuotient&id=quotient-im_libQuotient][41.6%]] (raw), 46.9% (adjusted)
#+INCLUDE: "src/basic-example/test.cpp" src c++
-
+- Offload work into worker threads
+#+BEGIN_SRC c++
+ QtConcurrent::run([model]() { // capture model by value
+ save(model);
+ });
+#+END_SRC
+ - Performance of copying? Structural sharing, immer containers
+- Undo/redo for free
+ - History is a list of models
+ - Undo/redo = changing the current index
+ - Normal actions = push a new model onto the list
+* How to do IO?
+** Reducers revisited
+- src_c++{auto update(Model, Action) -> Model;}
+- But also src_c++{auto update(Model, Action) -> std::pair<Model, Effect>;}
+- What is an effect? A (maybe non-pure) function that takes a *context* as an argument.
+- A *store* contains the model and the context. It also has an event loop. Reducers will be run in the event loop.
+- The store can be constructed with *dependencies*, which can be accessed through the context.
+- The context can also be used to dispatch actions.
+#+INCLUDE: "src/effects-example/main.cpp" src c++
+* How can lager help you integrate value-oriented design with a Qt application?
+** Missing part: view
+- In the example, src_c++{show()} is the view.
+- We want to show the state as a src_c++{QWidget} or src_c++{QQuickItem}.
+- Signal/slot connections to update display values.
+- Cursors
* Appendix
** Calculating libQuotient coverage adjusted
libQuotient includes the generated csapi folder into coverage, while libkazv does not. Here is the code that calculates the coverage of libQuotient excluding the csapi folder.
#+NAME: libquotient-coverage
#+BEGIN_SRC perl
my $csapiCoverage = 0.169;
my $csapiUncovered = 1_668;
my $totalLines = 11_229;
my $totalUncovered = 6_561;
my $csapiLines = $csapiUncovered / (1-$csapiCoverage);
my $remainingLines = $totalLines - $csapiLines;
my $remainingUncovered = $totalUncovered - $csapiUncovered;
my $remainingCoverage = 1 - $remainingUncovered / $remainingLines;
$remainingCoverage;
#+END_SRC
#+RESULTS: libquotient-coverage
: 0.46940827964562
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8092618..82b00c5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1 +1,2 @@
add_subdirectory(basic-example)
+add_subdirectory(effects-example)
diff --git a/src/effects-example/CMakeLists.txt b/src/effects-example/CMakeLists.txt
new file mode 100644
index 0000000..b4dcd48
--- /dev/null
+++ b/src/effects-example/CMakeLists.txt
@@ -0,0 +1,7 @@
+
+add_executable(effects-example main.cpp)
+target_link_libraries(effects-example PRIVATE
+ Qt6::Core
+ Qt6::Concurrent
+ lager
+)
diff --git a/src/effects-example/main.cpp b/src/effects-example/main.cpp
new file mode 100644
index 0000000..a7f4aea
--- /dev/null
+++ b/src/effects-example/main.cpp
@@ -0,0 +1,92 @@
+
+#include <iostream>
+#include <variant>
+#include <boost/asio.hpp>
+#include <lager/event_loop/qt.hpp>
+#include <lager/store.hpp>
+#include <QObject>
+#include <QCoreApplication>
+#include <QTimer>
+
+struct Model
+{
+ int value{0};
+};
+
+struct IncrementAction {};
+struct IncrementTwiceAction {};
+
+using Action = std::variant<IncrementAction, IncrementTwiceAction>;
+
+struct IOHandler
+{
+ template<class T>
+ void show(T x)
+ {
+ std::cout << "-->" << x << std::endl;
+ }
+};
+
+using DepsT = lager::deps<IOHandler &>;
+
+std::pair<Model, lager::effect<Action, DepsT>> update(Model m, IncrementAction)
+{
+ return {Model{m.value + 1}, [m](const auto &ctx) {
+ auto &h = ctx.template get<IOHandler &>();
+ h.show(m.value);
+ h.show("increment action");
+ }};
+}
+
+std::pair<Model, lager::effect<Action, DepsT>> update(Model m, IncrementTwiceAction)
+{
+ return {m, [](const auto &ctx) {
+ ctx.dispatch(IncrementAction{});
+ ctx.dispatch(IncrementAction{});
+ }};
+}
+
+std::pair<Model, lager::effect<Action, DepsT>> update(Model m, Action a)
+{
+ return std::visit([&](const auto &action) {
+ return update(m, action);
+ }, a);
+}
+
+void repl(QObject *obj, lager::context<Action> ctx)
+{
+ std::cout << "?" << std::endl;
+ std::string a;
+ std::cin >> a;
+ if (a == "1") {
+ ctx.dispatch(IncrementAction{});
+ } else if (a == "2") {
+ ctx.dispatch(IncrementTwiceAction{});
+ } else {
+ std::cout << "unrecognized action" << std::endl;
+ }
+
+ QTimer::singleShot(100, obj, [=]() {
+ repl(obj, ctx);
+ });
+}
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+ QObject mainObject;
+ IOHandler handler;
+
+ auto store = lager::make_store<Action>(
+ Model{},
+ lager::with_qt_event_loop{mainObject},
+ lager::with_deps(std::ref(handler)));
+ auto context = static_cast<lager::context<Action>>(store);
+
+ QTimer::singleShot(0, &mainObject,
+ [obj=&mainObject, context]() {
+ repl(obj, context);
+ });
+
+ return app.exec();
+}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 24, 11:33 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39506
Default Alt Text
(7 KB)

Event Timeline