cpp-common: add observable.hpp
authorSimon Marchi <simon.marchi@efficios.com>
Fri, 8 Dec 2023 19:11:21 +0000 (19:11 +0000)
committerSimon Marchi <simon.marchi@efficios.com>
Wed, 4 Sep 2024 19:05:14 +0000 (15:05 -0400)
Add the `Observable` class, which implements the observer pattern.  See
comments in the new file for details on usage.

Change-Id: I1f7a3faead9350f1f1217ffcf431fdcd3ddedab9
Signed-off-by: Simon Marchi <simon.marchi@efficios.com>
Reviewed-on: https://review.lttng.org/c/babeltrace/+/12254
Reviewed-by: Philippe Proulx <eeppeliteloop@gmail.com>
Tested-by: jenkins <jenkins@lttng.org>
src/Makefile.am
src/cpp-common/bt2c/observable.hpp [new file with mode: 0644]

index a1455b9397c6b5de228b6ae0218a33466b222d7d..a21b7189e1e58a8243bcaa5046a4bb1069ab8c72 100644 (file)
@@ -177,6 +177,7 @@ cpp_common_libcpp_common_la_SOURCES = \
        cpp-common/bt2c/libc-up.hpp \
        cpp-common/bt2c/logging.hpp \
        cpp-common/bt2c/make-span.hpp \
+       cpp-common/bt2c/observable.hpp \
        cpp-common/bt2c/parse-json.hpp \
        cpp-common/bt2c/parse-json-as-val.cpp \
        cpp-common/bt2c/parse-json-as-val.hpp \
diff --git a/src/cpp-common/bt2c/observable.hpp b/src/cpp-common/bt2c/observable.hpp
new file mode 100644 (file)
index 0000000..0125444
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2023 Simon Marchi <simon.marchi@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_CPP_COMMON_BT2C_OBSERVABLE_HPP
+#define BABELTRACE_CPP_COMMON_BT2C_OBSERVABLE_HPP
+
+#include <algorithm>
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "common/assert.h"
+
+namespace bt2c {
+
+/*
+ * An implementation of the observer pattern.
+ *
+ * Instantiate an observable with:
+ *
+ *     Observable<Args> myObservable;
+ *
+ * where `Args` is the parameter type(s) of the data passed from the
+ * entity notifying the observer to the observer callbacks.
+ *
+ * Attach an observer with the attach() method:
+ *
+ *     auto token = myObservable.attach([](Args...) {
+ *         // Do something
+ *     });
+ *
+ * attach() returns a token (`Token` instance) which identifies this
+ * specific observer within the observable. The destructor of the token
+ * detaches the observer from the observable.
+ *
+ * Notify all the observers with the notify() method:
+ *
+ *    myObservable.notify(args);
+ */
+template <typename... Args>
+class Observable
+{
+private:
+    using _TokenId = std::uint64_t;
+    using _ThisObservable = Observable<Args...>;
+
+    /* Type of user callback of an observer */
+    using _ObserverFunc = std::function<void(Args...)>;
+
+public:
+    /*
+     * A token, identified with a unique ID, is an observer handle
+     * within this observable. On destruction, the token detaches the
+     * observer from the observable.
+     */
+    class Token
+    {
+        friend class Observable;
+
+    private:
+        explicit Token(_ThisObservable& observable, const _TokenId tokenId) noexcept :
+            _mObservable {&observable}, _mTokenId(tokenId)
+        {
+        }
+
+    public:
+        ~Token()
+        {
+            if (_mTokenId != _invalidTokenId) {
+                _mObservable->_detach(_mTokenId);
+            }
+        }
+
+        Token(const Token&) = delete;
+
+        Token(Token&& other) noexcept :
+            _mObservable {other._mObservable}, _mTokenId {other._mTokenId}
+        {
+            other._mTokenId = _invalidTokenId;
+        }
+
+        Token& operator=(const Token&) = delete;
+
+        Token& operator=(Token&& other) noexcept
+        {
+            _mObservable = other._mObservable;
+            _mTokenId = other._mTokenId;
+            other._mTokenId = _invalidTokenId;
+        }
+
+    private:
+        static constexpr _TokenId _invalidTokenId = std::numeric_limits<_TokenId>::max();
+        _ThisObservable *_mObservable;
+        _TokenId _mTokenId;
+    };
+
+public:
+    Observable() = default;
+    Observable(const Observable&) = delete;
+    Observable(Observable&&) = default;
+    Observable& operator=(const Observable&) = delete;
+    Observable& operator=(Observable&&) = default;
+
+    /*
+     * Attaches an observer using the user callback `func` to this
+     * observable, returning a corresponding token.
+     */
+    Token attach(_ObserverFunc func)
+    {
+        const auto tokenId = _mNextTokenId;
+
+        ++_mNextTokenId;
+        _mObservers.emplace_back(tokenId, std::move(func));
+        return Token {*this, tokenId};
+    }
+
+    /*
+     * Notifies all the managed observers, passing `args` to their user
+     * callback.
+     */
+    void notify(Args... args)
+    {
+        for (auto& observer : _mObservers) {
+            observer.func(std::forward<Args>(args)...);
+        }
+    }
+
+private:
+    /* Element type of `_mObservers` */
+    struct _Observer
+    {
+        _Observer(const _TokenId tokenIdParam, _ObserverFunc funcParam) :
+            tokenId {tokenIdParam}, func {std::move(funcParam)}
+        {
+        }
+
+        _TokenId tokenId;
+        _ObserverFunc func;
+    };
+
+    /*
+     * Removes the observer having the token ID `tokenId` from this
+     * observable.
+     */
+    void _detach(const _TokenId tokenId)
+    {
+        const auto it =
+            std::remove_if(_mObservers.begin(), _mObservers.end(), [tokenId](_Observer& obs) {
+                return obs.tokenId == tokenId;
+            });
+
+        BT_ASSERT(_mObservers.end() - it == 1);
+        _mObservers.erase(it, _mObservers.end());
+    }
+
+    /* Next token ID to hand out */
+    _TokenId _mNextTokenId = 0;
+
+    /* List of observers */
+    mutable std::vector<_Observer> _mObservers;
+};
+
+} /* namespace bt2c */
+
+#endif /* BABELTRACE_CPP_COMMON_BT2C_OBSERVABLE_HPP */
This page took 0.027169 seconds and 4 git commands to generate.