|  | /* Observers | 
|  |  | 
|  | Copyright (C) 2016-2023 Free Software Foundation, Inc. | 
|  |  | 
|  | This file is part of GDB. | 
|  |  | 
|  | This program is free software; you can redistribute it and/or modify | 
|  | it under the terms of the GNU General Public License as published by | 
|  | the Free Software Foundation; either version 3 of the License, or | 
|  | (at your option) any later version. | 
|  |  | 
|  | This program is distributed in the hope that it will be useful, | 
|  | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | GNU General Public License for more details. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License | 
|  | along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  | #ifndef COMMON_OBSERVABLE_H | 
|  | #define COMMON_OBSERVABLE_H | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <functional> | 
|  | #include <vector> | 
|  |  | 
|  | /* Print an "observer" debug statement.  */ | 
|  |  | 
|  | #define observer_debug_printf(fmt, ...) \ | 
|  | debug_prefixed_printf_cond (observer_debug, "observer", fmt, ##__VA_ARGS__) | 
|  |  | 
|  | /* Print "observer" start/end debug statements.  */ | 
|  |  | 
|  | #define OBSERVER_SCOPED_DEBUG_START_END(fmt, ...) \ | 
|  | scoped_debug_start_end (observer_debug, "observer", fmt, ##__VA_ARGS__) | 
|  |  | 
|  | namespace gdb | 
|  | { | 
|  |  | 
|  | namespace observers | 
|  | { | 
|  |  | 
|  | extern bool observer_debug; | 
|  |  | 
|  | /* An observer is an entity which is interested in being notified | 
|  | when GDB reaches certain states, or certain events occur in GDB. | 
|  | The entity being observed is called the observable.  To receive | 
|  | notifications, the observer attaches a callback to the observable. | 
|  | One observable can have several observers. | 
|  |  | 
|  | The observer implementation is also currently not reentrant.  In | 
|  | particular, it is therefore not possible to call the attach or | 
|  | detach routines during a notification.  */ | 
|  |  | 
|  | /* The type of a key that can be passed to attach, which can be passed | 
|  | to detach to remove associated observers.  Tokens have address | 
|  | identity, and are thus usually const globals.  */ | 
|  | struct token | 
|  | { | 
|  | token () = default; | 
|  |  | 
|  | DISABLE_COPY_AND_ASSIGN (token); | 
|  | }; | 
|  |  | 
|  | namespace detail | 
|  | { | 
|  | /* Types that don't depend on any template parameter.  This saves a | 
|  | bit of code and debug info size, compared to putting them inside | 
|  | class observable.  */ | 
|  |  | 
|  | /* Use for sorting algorithm, to indicate which observer we have | 
|  | visited.  */ | 
|  | enum class visit_state | 
|  | { | 
|  | NOT_VISITED, | 
|  | VISITING, | 
|  | VISITED, | 
|  | }; | 
|  | } | 
|  |  | 
|  | template<typename... T> | 
|  | class observable | 
|  | { | 
|  | public: | 
|  | typedef std::function<void (T...)> func_type; | 
|  |  | 
|  | private: | 
|  | struct observer | 
|  | { | 
|  | observer (const struct token *token, func_type func, const char *name, | 
|  | const std::vector<const struct token *> &dependencies) | 
|  | : token (token), func (func), name (name), dependencies (dependencies) | 
|  | {} | 
|  |  | 
|  | const struct token *token; | 
|  | func_type func; | 
|  | const char *name; | 
|  | std::vector<const struct token *> dependencies; | 
|  | }; | 
|  |  | 
|  | public: | 
|  | explicit observable (const char *name) | 
|  | : m_name (name) | 
|  | { | 
|  | } | 
|  |  | 
|  | DISABLE_COPY_AND_ASSIGN (observable); | 
|  |  | 
|  | /* Attach F as an observer to this observable.  F cannot be detached or | 
|  | specified as a dependency. | 
|  |  | 
|  | DEPENDENCIES is a list of tokens of observers to be notified before this | 
|  | one. | 
|  |  | 
|  | NAME is the name of the observer, used for debug output purposes.  Its | 
|  | lifetime must be at least as long as the observer is attached.  */ | 
|  | void attach (const func_type &f, const char *name, | 
|  | const std::vector<const struct token *> &dependencies = {}) | 
|  | { | 
|  | attach (f, nullptr, name, dependencies); | 
|  | } | 
|  |  | 
|  | /* Attach F as an observer to this observable. | 
|  |  | 
|  | T is a reference to a token that can be used to later remove F or specify F | 
|  | as a dependency of another observer. | 
|  |  | 
|  | DEPENDENCIES is a list of tokens of observers to be notified before this | 
|  | one. | 
|  |  | 
|  | NAME is the name of the observer, used for debug output purposes.  Its | 
|  | lifetime must be at least as long as the observer is attached.  */ | 
|  | void attach (const func_type &f, const token &t, const char *name, | 
|  | const std::vector<const struct token *> &dependencies = {}) | 
|  | { | 
|  | attach (f, &t, name, dependencies); | 
|  | } | 
|  |  | 
|  | /* Remove observers associated with T from this observable.  T is | 
|  | the token that was previously passed to any number of "attach" | 
|  | calls.  */ | 
|  | void detach (const token &t) | 
|  | { | 
|  | auto iter = std::remove_if (m_observers.begin (), | 
|  | m_observers.end (), | 
|  | [&] (const observer &o) | 
|  | { | 
|  | return o.token == &t; | 
|  | }); | 
|  |  | 
|  | observer_debug_printf ("Detaching observable %s from observer %s", | 
|  | iter->name, m_name); | 
|  |  | 
|  | m_observers.erase (iter, m_observers.end ()); | 
|  | } | 
|  |  | 
|  | /* Notify all observers that are attached to this observable.  */ | 
|  | void notify (T... args) const | 
|  | { | 
|  | OBSERVER_SCOPED_DEBUG_START_END ("observable %s notify() called", m_name); | 
|  |  | 
|  | for (auto &&e : m_observers) | 
|  | { | 
|  | OBSERVER_SCOPED_DEBUG_START_END ("calling observer %s of observable %s", | 
|  | e.name, m_name); | 
|  | e.func (args...); | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  |  | 
|  | std::vector<observer> m_observers; | 
|  | const char *m_name; | 
|  |  | 
|  | /* Helper method for topological sort using depth-first search algorithm. | 
|  |  | 
|  | Visit all dependencies of observer at INDEX in M_OBSERVERS (later referred | 
|  | to as "the observer").  Then append the observer to SORTED_OBSERVERS. | 
|  |  | 
|  | If the observer is already visited, do nothing.  */ | 
|  | void visit_for_sorting (std::vector<observer> &sorted_observers, | 
|  | std::vector<detail::visit_state> &visit_states, | 
|  | int index) | 
|  | { | 
|  | if (visit_states[index] == detail::visit_state::VISITED) | 
|  | return; | 
|  |  | 
|  | /* If we are already visiting this observer, it means there's a cycle.  */ | 
|  | gdb_assert (visit_states[index] != detail::visit_state::VISITING); | 
|  |  | 
|  | visit_states[index] = detail::visit_state::VISITING; | 
|  |  | 
|  | /* For each dependency of this observer...  */ | 
|  | for (const token *dep : m_observers[index].dependencies) | 
|  | { | 
|  | /* ... find the observer that has token DEP.  If found, visit it.  */ | 
|  | auto it_dep | 
|  | = std::find_if (m_observers.begin (), m_observers.end (), | 
|  | [&] (observer o) { return o.token == dep; }); | 
|  | if (it_dep != m_observers.end ()) | 
|  | { | 
|  | int i = std::distance (m_observers.begin (), it_dep); | 
|  | visit_for_sorting (sorted_observers, visit_states, i); | 
|  | } | 
|  | } | 
|  |  | 
|  | visit_states[index] = detail::visit_state::VISITED; | 
|  | sorted_observers.push_back (m_observers[index]); | 
|  | } | 
|  |  | 
|  | /* Sort the observers, so that dependencies come before observers | 
|  | depending on them. | 
|  |  | 
|  | Uses depth-first search algorithm for topological sorting, see | 
|  | https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search .  */ | 
|  | void sort_observers () | 
|  | { | 
|  | std::vector<observer> sorted_observers; | 
|  | std::vector<detail::visit_state> visit_states | 
|  | (m_observers.size (), detail::visit_state::NOT_VISITED); | 
|  |  | 
|  | for (size_t i = 0; i < m_observers.size (); i++) | 
|  | visit_for_sorting (sorted_observers, visit_states, i); | 
|  |  | 
|  | m_observers = std::move (sorted_observers); | 
|  | } | 
|  |  | 
|  | void attach (const func_type &f, const token *t, const char *name, | 
|  | const std::vector<const struct token *> &dependencies) | 
|  | { | 
|  |  | 
|  | observer_debug_printf ("Attaching observable %s to observer %s", | 
|  | name, m_name); | 
|  |  | 
|  | m_observers.emplace_back (t, f, name, dependencies); | 
|  |  | 
|  | /* The observer has been inserted at the end of the vector, so it will be | 
|  | after any of its potential dependencies attached earlier.  If the | 
|  | observer has a token, it means that other observers can specify it as | 
|  | a dependency, so sorting is necessary to ensure those will be after the | 
|  | newly inserted observer afterwards.  */ | 
|  | if (t != nullptr) | 
|  | sort_observers (); | 
|  | }; | 
|  | }; | 
|  |  | 
|  | } /* namespace observers */ | 
|  |  | 
|  | } /* namespace gdb */ | 
|  |  | 
|  | #endif /* COMMON_OBSERVABLE_H */ |