mirror of
https://github.com/matrix-construct/construct
synced 2024-12-11 08:02:59 +01:00
277 lines
6.3 KiB
C++
277 lines
6.3 KiB
C++
// Matrix Construct
|
|
//
|
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
|
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
|
//
|
|
// 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<class data = void> struct hook;
|
|
template<class data = void> struct site;
|
|
|
|
template<> struct hook<void>;
|
|
template<> struct site<void>;
|
|
}
|
|
|
|
namespace ircd::m
|
|
{
|
|
template<class data = void> using hookfn = m::hook::hook<data>;
|
|
}
|
|
|
|
/// 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<base>
|
|
{
|
|
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<ircd::m::hook::base>::list;
|
|
|
|
/// Non-template base class for all hook sites (dispatcher/caller component)
|
|
///
|
|
struct ircd::m::hook::base::site
|
|
:instance_list<site>
|
|
{
|
|
json::strung _feature;
|
|
json::object feature;
|
|
size_t count {0};
|
|
std::unique_ptr<struct maps> maps;
|
|
std::set<base *> 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<bool (base &)> &);
|
|
|
|
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<ircd::m::hook::base::site>::list;
|
|
|
|
/// Hook function with no payload; only an m::event argument
|
|
template<>
|
|
struct ircd::m::hook::hook<void>
|
|
final
|
|
:base
|
|
{
|
|
std::function<void (const m::event &)> 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<void>
|
|
final
|
|
:base::site
|
|
{
|
|
void call(hook<void> &, 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<class data_type>
|
|
struct ircd::m::hook::hook
|
|
:base
|
|
{
|
|
std::function<void (const m::event &, data_type)> 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<class data_type>
|
|
struct ircd::m::hook::site
|
|
:base::site
|
|
{
|
|
void call(hook<data_type> &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<class data>
|
|
void
|
|
ircd::m::hook::site<data>::operator()(const event &event,
|
|
data d)
|
|
{
|
|
base *cur {nullptr};
|
|
operator()(&cur, event, std::forward<data>(d));
|
|
}
|
|
|
|
template<class data>
|
|
void
|
|
ircd::m::hook::site<data>::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<hook<data> &>(base)
|
|
};
|
|
|
|
call(hfn, event, d);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
template<class data>
|
|
void
|
|
ircd::m::hook::site<data>::call(hook<data> &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(),
|
|
};
|
|
}
|