| /* Observers |
| |
| Copyright (C) 2016-2024 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 */ |