18dffb485Schristos /* Observers 28dffb485Schristos 3*5ba1f45fSchristos Copyright (C) 2016-2024 Free Software Foundation, Inc. 48dffb485Schristos 58dffb485Schristos This file is part of GDB. 68dffb485Schristos 78dffb485Schristos This program is free software; you can redistribute it and/or modify 88dffb485Schristos it under the terms of the GNU General Public License as published by 98dffb485Schristos the Free Software Foundation; either version 3 of the License, or 108dffb485Schristos (at your option) any later version. 118dffb485Schristos 128dffb485Schristos This program is distributed in the hope that it will be useful, 138dffb485Schristos but WITHOUT ANY WARRANTY; without even the implied warranty of 148dffb485Schristos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 158dffb485Schristos GNU General Public License for more details. 168dffb485Schristos 178dffb485Schristos You should have received a copy of the GNU General Public License 188dffb485Schristos along with this program. If not, see <http://www.gnu.org/licenses/>. */ 198dffb485Schristos 208dffb485Schristos #ifndef COMMON_OBSERVABLE_H 218dffb485Schristos #define COMMON_OBSERVABLE_H 228dffb485Schristos 238dffb485Schristos #include <algorithm> 248dffb485Schristos #include <functional> 258dffb485Schristos #include <vector> 268dffb485Schristos 274b169a6bSchristos /* Print an "observer" debug statement. */ 284b169a6bSchristos 294b169a6bSchristos #define observer_debug_printf(fmt, ...) \ 304b169a6bSchristos debug_prefixed_printf_cond (observer_debug, "observer", fmt, ##__VA_ARGS__) 314b169a6bSchristos 324b169a6bSchristos /* Print "observer" start/end debug statements. */ 334b169a6bSchristos 344b169a6bSchristos #define OBSERVER_SCOPED_DEBUG_START_END(fmt, ...) \ 354b169a6bSchristos scoped_debug_start_end (observer_debug, "observer", fmt, ##__VA_ARGS__) 364b169a6bSchristos 378dffb485Schristos namespace gdb 388dffb485Schristos { 398dffb485Schristos 408dffb485Schristos namespace observers 418dffb485Schristos { 428dffb485Schristos 434b169a6bSchristos extern bool observer_debug; 448dffb485Schristos 458dffb485Schristos /* An observer is an entity which is interested in being notified 468dffb485Schristos when GDB reaches certain states, or certain events occur in GDB. 478dffb485Schristos The entity being observed is called the observable. To receive 488dffb485Schristos notifications, the observer attaches a callback to the observable. 498dffb485Schristos One observable can have several observers. 508dffb485Schristos 518dffb485Schristos The observer implementation is also currently not reentrant. In 528dffb485Schristos particular, it is therefore not possible to call the attach or 538dffb485Schristos detach routines during a notification. */ 548dffb485Schristos 558dffb485Schristos /* The type of a key that can be passed to attach, which can be passed 568dffb485Schristos to detach to remove associated observers. Tokens have address 578dffb485Schristos identity, and are thus usually const globals. */ 588dffb485Schristos struct token 598dffb485Schristos { 608dffb485Schristos token () = default; 618dffb485Schristos 628dffb485Schristos DISABLE_COPY_AND_ASSIGN (token); 638dffb485Schristos }; 648dffb485Schristos 654b169a6bSchristos namespace detail 664b169a6bSchristos { 674b169a6bSchristos /* Types that don't depend on any template parameter. This saves a 684b169a6bSchristos bit of code and debug info size, compared to putting them inside 694b169a6bSchristos class observable. */ 704b169a6bSchristos 714b169a6bSchristos /* Use for sorting algorithm, to indicate which observer we have 724b169a6bSchristos visited. */ 734b169a6bSchristos enum class visit_state 744b169a6bSchristos { 754b169a6bSchristos NOT_VISITED, 764b169a6bSchristos VISITING, 774b169a6bSchristos VISITED, 784b169a6bSchristos }; 794b169a6bSchristos } 804b169a6bSchristos 818dffb485Schristos template<typename... T> 828dffb485Schristos class observable 838dffb485Schristos { 848dffb485Schristos public: 858dffb485Schristos typedef std::function<void (T...)> func_type; 868dffb485Schristos 874b169a6bSchristos private: 884b169a6bSchristos struct observer 894b169a6bSchristos { 904b169a6bSchristos observer (const struct token *token, func_type func, const char *name, 914b169a6bSchristos const std::vector<const struct token *> &dependencies) 924b169a6bSchristos : token (token), func (func), name (name), dependencies (dependencies) 934b169a6bSchristos {} 944b169a6bSchristos 954b169a6bSchristos const struct token *token; 964b169a6bSchristos func_type func; 974b169a6bSchristos const char *name; 984b169a6bSchristos std::vector<const struct token *> dependencies; 994b169a6bSchristos }; 1004b169a6bSchristos 1014b169a6bSchristos public: 1028dffb485Schristos explicit observable (const char *name) 1038dffb485Schristos : m_name (name) 1048dffb485Schristos { 1058dffb485Schristos } 1068dffb485Schristos 1078dffb485Schristos DISABLE_COPY_AND_ASSIGN (observable); 1088dffb485Schristos 1094b169a6bSchristos /* Attach F as an observer to this observable. F cannot be detached or 1104b169a6bSchristos specified as a dependency. 1114b169a6bSchristos 1124b169a6bSchristos DEPENDENCIES is a list of tokens of observers to be notified before this 1134b169a6bSchristos one. 1144b169a6bSchristos 1154b169a6bSchristos NAME is the name of the observer, used for debug output purposes. Its 1164b169a6bSchristos lifetime must be at least as long as the observer is attached. */ 1174b169a6bSchristos void attach (const func_type &f, const char *name, 1184b169a6bSchristos const std::vector<const struct token *> &dependencies = {}) 1198dffb485Schristos { 1204b169a6bSchristos attach (f, nullptr, name, dependencies); 1218dffb485Schristos } 1228dffb485Schristos 1234b169a6bSchristos /* Attach F as an observer to this observable. 1244b169a6bSchristos 1254b169a6bSchristos T is a reference to a token that can be used to later remove F or specify F 1264b169a6bSchristos as a dependency of another observer. 1274b169a6bSchristos 1284b169a6bSchristos DEPENDENCIES is a list of tokens of observers to be notified before this 1294b169a6bSchristos one. 1304b169a6bSchristos 1314b169a6bSchristos NAME is the name of the observer, used for debug output purposes. Its 1324b169a6bSchristos lifetime must be at least as long as the observer is attached. */ 1334b169a6bSchristos void attach (const func_type &f, const token &t, const char *name, 1344b169a6bSchristos const std::vector<const struct token *> &dependencies = {}) 1358dffb485Schristos { 1364b169a6bSchristos attach (f, &t, name, dependencies); 1378dffb485Schristos } 1388dffb485Schristos 1398dffb485Schristos /* Remove observers associated with T from this observable. T is 1408dffb485Schristos the token that was previously passed to any number of "attach" 1418dffb485Schristos calls. */ 1428dffb485Schristos void detach (const token &t) 1438dffb485Schristos { 1448dffb485Schristos auto iter = std::remove_if (m_observers.begin (), 1458dffb485Schristos m_observers.end (), 1464b169a6bSchristos [&] (const observer &o) 1478dffb485Schristos { 1484b169a6bSchristos return o.token == &t; 1498dffb485Schristos }); 1508dffb485Schristos 1514b169a6bSchristos observer_debug_printf ("Detaching observable %s from observer %s", 1524b169a6bSchristos iter->name, m_name); 1534b169a6bSchristos 1548dffb485Schristos m_observers.erase (iter, m_observers.end ()); 1558dffb485Schristos } 1568dffb485Schristos 1578dffb485Schristos /* Notify all observers that are attached to this observable. */ 1588dffb485Schristos void notify (T... args) const 1598dffb485Schristos { 1604b169a6bSchristos OBSERVER_SCOPED_DEBUG_START_END ("observable %s notify() called", m_name); 1614b169a6bSchristos 1628dffb485Schristos for (auto &&e : m_observers) 1634b169a6bSchristos { 1644b169a6bSchristos OBSERVER_SCOPED_DEBUG_START_END ("calling observer %s of observable %s", 1654b169a6bSchristos e.name, m_name); 1664b169a6bSchristos e.func (args...); 1674b169a6bSchristos } 1688dffb485Schristos } 1698dffb485Schristos 1708dffb485Schristos private: 1718dffb485Schristos 1724b169a6bSchristos std::vector<observer> m_observers; 1738dffb485Schristos const char *m_name; 1744b169a6bSchristos 1754b169a6bSchristos /* Helper method for topological sort using depth-first search algorithm. 1764b169a6bSchristos 1774b169a6bSchristos Visit all dependencies of observer at INDEX in M_OBSERVERS (later referred 1784b169a6bSchristos to as "the observer"). Then append the observer to SORTED_OBSERVERS. 1794b169a6bSchristos 1804b169a6bSchristos If the observer is already visited, do nothing. */ 1814b169a6bSchristos void visit_for_sorting (std::vector<observer> &sorted_observers, 1824b169a6bSchristos std::vector<detail::visit_state> &visit_states, 1834b169a6bSchristos int index) 1844b169a6bSchristos { 1854b169a6bSchristos if (visit_states[index] == detail::visit_state::VISITED) 1864b169a6bSchristos return; 1874b169a6bSchristos 1884b169a6bSchristos /* If we are already visiting this observer, it means there's a cycle. */ 1894b169a6bSchristos gdb_assert (visit_states[index] != detail::visit_state::VISITING); 1904b169a6bSchristos 1914b169a6bSchristos visit_states[index] = detail::visit_state::VISITING; 1924b169a6bSchristos 1934b169a6bSchristos /* For each dependency of this observer... */ 1944b169a6bSchristos for (const token *dep : m_observers[index].dependencies) 1954b169a6bSchristos { 1964b169a6bSchristos /* ... find the observer that has token DEP. If found, visit it. */ 1974b169a6bSchristos auto it_dep 1984b169a6bSchristos = std::find_if (m_observers.begin (), m_observers.end (), 1994b169a6bSchristos [&] (observer o) { return o.token == dep; }); 2004b169a6bSchristos if (it_dep != m_observers.end ()) 2014b169a6bSchristos { 2024b169a6bSchristos int i = std::distance (m_observers.begin (), it_dep); 2034b169a6bSchristos visit_for_sorting (sorted_observers, visit_states, i); 2044b169a6bSchristos } 2054b169a6bSchristos } 2064b169a6bSchristos 2074b169a6bSchristos visit_states[index] = detail::visit_state::VISITED; 2084b169a6bSchristos sorted_observers.push_back (m_observers[index]); 2094b169a6bSchristos } 2104b169a6bSchristos 2114b169a6bSchristos /* Sort the observers, so that dependencies come before observers 2124b169a6bSchristos depending on them. 2134b169a6bSchristos 2144b169a6bSchristos Uses depth-first search algorithm for topological sorting, see 2154b169a6bSchristos https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search . */ 2164b169a6bSchristos void sort_observers () 2174b169a6bSchristos { 2184b169a6bSchristos std::vector<observer> sorted_observers; 2194b169a6bSchristos std::vector<detail::visit_state> visit_states 2204b169a6bSchristos (m_observers.size (), detail::visit_state::NOT_VISITED); 2214b169a6bSchristos 2224b169a6bSchristos for (size_t i = 0; i < m_observers.size (); i++) 2234b169a6bSchristos visit_for_sorting (sorted_observers, visit_states, i); 2244b169a6bSchristos 2254b169a6bSchristos m_observers = std::move (sorted_observers); 2264b169a6bSchristos } 2274b169a6bSchristos 2284b169a6bSchristos void attach (const func_type &f, const token *t, const char *name, 2294b169a6bSchristos const std::vector<const struct token *> &dependencies) 2304b169a6bSchristos { 2314b169a6bSchristos 2324b169a6bSchristos observer_debug_printf ("Attaching observable %s to observer %s", 2334b169a6bSchristos name, m_name); 2344b169a6bSchristos 2354b169a6bSchristos m_observers.emplace_back (t, f, name, dependencies); 2364b169a6bSchristos 2374b169a6bSchristos /* The observer has been inserted at the end of the vector, so it will be 2384b169a6bSchristos after any of its potential dependencies attached earlier. If the 2394b169a6bSchristos observer has a token, it means that other observers can specify it as 2404b169a6bSchristos a dependency, so sorting is necessary to ensure those will be after the 2414b169a6bSchristos newly inserted observer afterwards. */ 2424b169a6bSchristos if (t != nullptr) 2434b169a6bSchristos sort_observers (); 2444b169a6bSchristos }; 2458dffb485Schristos }; 2468dffb485Schristos 2478dffb485Schristos } /* namespace observers */ 2488dffb485Schristos 2498dffb485Schristos } /* namespace gdb */ 2508dffb485Schristos 2518dffb485Schristos #endif /* COMMON_OBSERVABLE_H */ 252