xref: /netbsd-src/external/gpl3/gdb/dist/gdbsupport/observable.h (revision 5ba1f45f2a09259cc846f20c7c5501604d633c90)
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