// Matrix Construct // // Copyright (C) Matrix Construct Developers, Authors & Contributors // Copyright (C) 2016-2018 Jason Volk // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. #pragma once #define HAVE_IRCD_M_HOOK_H /// Hooks allow dynamic functionality to be invoked as a result of an event /// matching some criteria. Hooks are comprised of two interfacing components: /// the hook function (callee) and the hook site (caller); these components /// link and delink to each other during initialization. This hook system is /// oriented around the m::event structure; every hook function has an m::event /// as its first argument. An optional second argument can be specified with /// the template to convey additional payload and options. /// /// Hook functions and hook sites are constructed out of json::members (pairs /// of json::value in an initializer list). We refer to this as the "feature." /// Each member with a name directly corresponding to an m::event property is /// a match parameter. The hook function is not called if a matching parameter /// is specified in the feature, but the event input at the hook::site does not /// match it. Undefined features match everything. /// /// One can create a hook pair anywhere, simply create a m::hook::site with a /// feature `{ "name", "myname" }` and a hook::hook with a similar feature /// `{ "_site", "myname" }` matching the site's name; these objects must have /// matching templates. /// namespace ircd::m::hook { IRCD_EXCEPTION(ircd::error, error) struct base; struct maps; template struct hook; template struct site; template<> struct hook; template<> struct site; } namespace ircd::m { template using hookfn = m::hook::hook; } /// Non-template base class for all hook functions. This is the handler /// (or callee) component of the hook /// struct ircd::m::hook::base :instance_list { struct site; json::strung _feature; json::object feature; m::event matching; bool registered {false}; size_t matchers {0}; size_t calls {0}; size_t calling {0}; public: uint id() const; site *find_site() const; string_view site_name() const; protected: base(const json::members &); base(base &&) = delete; base(const base &) = delete; virtual ~base() noexcept; }; template<> decltype(ircd::m::hook::base::list) ircd::instance_list::list; /// Non-template base class for all hook sites (dispatcher/caller component) /// struct ircd::m::hook::base::site :instance_list { json::strung _feature; json::object feature; size_t count {0}; std::unique_ptr maps; std::set hooks; size_t matchers {0}; bool exceptions {true}; bool interrupts {true}; size_t calls {0}; size_t calling {0}; public: uint id() const; string_view name() const; protected: friend class base; bool add(base &); bool del(base &); void match(const event &, const std::function &); site(const json::members &); site(site &&) = delete; site(const site &) = delete; virtual ~site() noexcept; }; template<> decltype(ircd::m::hook::base::site::list) ircd::instance_list::list; /// Hook function with no payload; only an m::event argument template<> struct ircd::m::hook::hook final :base { std::function function; public: hook(const json::members &feature, decltype(function) function); hook(decltype(function) function, const json::members &feature); }; /// Hook site for functions with no payload. template<> struct ircd::m::hook::site final :base::site { void call(hook &, const event &); public: void operator()(base **const &, const event &); void operator()(const event &); site(const json::members &feature); }; /// Hook function with a template class as the payload template struct ircd::m::hook::hook :base { std::function function; public: hook(const json::members &feature, decltype(function) function) :base{feature} ,function{std::move(function)} {} hook(decltype(function) function, const json::members &feature) :base{feature} ,function{std::move(function)} {} }; /// Hook site for functions with a template class as the payload template struct ircd::m::hook::site :base::site { void call(hook &hfn, const event &event, data_type d); public: void operator()(base **const &, const event &event, data_type d); void operator()(const event &event, data_type d); site(const json::members &feature) :base::site{feature} {} }; template void ircd::m::hook::site::operator()(const event &event, data d) { base *cur {nullptr}; operator()(&cur, event, std::forward(d)); } template void ircd::m::hook::site::operator()(base **const &cur, const event &event, data d) { const ctx::uninterruptible::nothrow ui { !interrupts }; // Iterate all matching hooks match(event, [this, &cur, &event, &d] (base &base) { // Indicate which hook we're entering const scope_restore entered { *cur, std::addressof(base) }; auto &hfn { dynamic_cast &>(base) }; call(hfn, event, d); return true; }); } template void ircd::m::hook::site::call(hook &hfn, const event &event, data d) try { // stats for site ++calls; const scope_count site_calling { calling }; // stats for hook ++hfn.calls; const scope_count hook_calling { hfn.calling }; // call hook hfn.function(event, d); } catch(const ctx::interrupted &e) { if(exceptions && interrupts) throw; log::logf { log, interrupts? log::DERROR: log::ERROR, "site:%u hook:%u %s error :%s", id(), hfn.id(), string_view{hfn.feature}, e.what(), }; } catch(const std::exception &e) { if(exceptions) throw; log::critical { log, "Unhandled site:%u hook:%u %s error :%s", id(), hfn.id(), string_view{hfn.feature}, e.what(), }; }