0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-10-31 19:08:59 +01:00

ircd:Ⓜ️ Add templated payload for hook.

This commit is contained in:
Jason Volk 2018-05-26 22:05:56 -07:00
parent 9a697783a9
commit 0d833cda4f
19 changed files with 406 additions and 245 deletions

View file

@ -11,23 +11,33 @@
#pragma once
#define HAVE_IRCD_M_HOOK_H
namespace ircd::m
namespace ircd::m::hook
{
struct hook;
}
IRCD_EXCEPTION(ircd::error, error)
struct ircd::m::hook
:instance_list<hook>
{
struct site;
struct base;
struct maps;
IRCD_EXCEPTION(ircd::error, error)
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>;
}
struct ircd::m::hook::base
:instance_list<base>
{
struct site;
json::strung _feature;
json::object feature;
m::event matching;
std::function<void (const m::event &)> function;
bool registered {false};
size_t matchers {0};
size_t calls {0};
@ -35,44 +45,124 @@ struct ircd::m::hook
string_view site_name() const;
site *find_site() const;
public:
hook(const json::members &, decltype(function));
hook(decltype(function), const json::members &);
hook(hook &&) = delete;
hook(const hook &) = delete;
virtual ~hook() noexcept;
protected:
base(const json::members &);
base(base &&) = delete;
base(const base &) = delete;
virtual ~base() noexcept;
};
/// The hook::site is the call-site for a hook. Each hook site is named
/// and registers itself with the master extern hook::site::list. Each hook
/// then registers itself with a hook::site. The site contains internal
/// state to manage the efficient calling of the participating hooks.
///
/// A hook::site can be created or destroyed at any time (for example if it's
/// in a module which is reloaded) while being agnostic to the hooks it
/// cooperates with.
struct ircd::m::hook::site
struct ircd::m::hook::base::site
:instance_list<site>
{
friend class hook;
json::strung _feature;
json::object feature;
size_t count {0};
std::unique_ptr<hook::maps> maps;
std::set<hook *> hooks;
std::unique_ptr<struct maps> maps;
std::set<base *> hooks;
size_t matchers {0};
friend class base;
string_view name() const;
bool add(hook &);
bool del(hook &);
bool add(base &);
bool del(base &);
void call(hook &, const event &);
void match(const event &, const std::function<bool (base &)> &);
protected:
site(const json::members &);
site(site &&) = delete;
site(const site &) = delete;
virtual ~site() noexcept;
};
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);
};
template<>
struct ircd::m::hook::site<void>
final
:base::site
{
void call(hook<void> &, const event &);
public:
void operator()(const event &);
site(const json::members &);
site(site &&) = delete;
site(const site &) = delete;
~site() noexcept;
site(const json::members &feature);
};
template<class data>
struct ircd::m::hook::hook
:base
{
std::function<void (const m::event &, data)> 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)}
{}
};
template<class data>
struct ircd::m::hook::site
:base::site
{
void call(hook<data> &hfn, const event &event, data d);
public:
void operator()(const event &event, data d);
site(const json::members &feature)
:base::site{feature}
{}
};
template<class data>
void
ircd::m::hook::site<data>::operator()(const event &event,
data d)
{
match(event, [this, &event, &d]
(base &base)
{
call(dynamic_cast<hook<data> &>(base), event, d);
return true;
});
}
template<class data>
void
ircd::m::hook::site<data>::call(hook<data> &hfn,
const event &event,
data d)
try
{
++hfn.calls;
hfn.function(event, d);
}
catch(const std::exception &e)
{
log::critical
{
"Unhandled hookfn(%p) %s error :%s",
&hfn,
string_view{hfn.feature},
e.what()
};
}

View file

@ -2983,26 +2983,175 @@ namespace ircd::m
static json::strung _hook_make_feature(const json::members &);
}
//
// hook::maps
//
struct ircd::m::hook::maps
{
std::multimap<string_view, base *> origin;
std::multimap<string_view, base *> room_id;
std::multimap<string_view, base *> sender;
std::multimap<string_view, base *> state_key;
std::multimap<string_view, base *> type;
std::vector<base *> always;
size_t match(const event &match, const std::function<bool (base &)> &) const;
size_t add(base &hook, const event &matching);
size_t del(base &hook, const event &matching);
maps();
~maps() noexcept;
};
ircd::m::hook::maps::maps()
{
}
ircd::m::hook::maps::~maps()
noexcept
{
}
size_t
ircd::m::hook::maps::add(base &hook,
const event &matching)
{
size_t ret{0};
const auto map{[&hook, &ret]
(auto &map, const string_view &value)
{
map.emplace(value, &hook);
++ret;
}};
if(json::get<"origin"_>(matching))
map(origin, at<"origin"_>(matching));
if(json::get<"room_id"_>(matching))
map(room_id, at<"room_id"_>(matching));
if(json::get<"sender"_>(matching))
map(sender, at<"sender"_>(matching));
if(json::get<"state_key"_>(matching))
map(state_key, at<"state_key"_>(matching));
if(json::get<"type"_>(matching))
map(type, at<"type"_>(matching));
// Hook had no mappings which means it will match everything.
// We don't increment the matcher count for this case.
if(!ret)
always.emplace_back(&hook);
return ret;
}
size_t
ircd::m::hook::maps::del(base &hook,
const event &matching)
{
size_t ret{0};
const auto unmap{[&hook, &ret]
(auto &map, const string_view &key)
{
auto pit{map.equal_range(key)};
for(; pit.first != pit.second; ++pit.first)
if(pit.first->second == &hook)
{
++ret;
return map.erase(pit.first);
}
assert(0);
return end(map);
}};
// Unconditional attempt to remove from always.
std::remove(begin(always), end(always), &hook);
if(json::get<"origin"_>(matching))
unmap(origin, at<"origin"_>(matching));
if(json::get<"room_id"_>(matching))
unmap(room_id, at<"room_id"_>(matching));
if(json::get<"sender"_>(matching))
unmap(sender, at<"sender"_>(matching));
if(json::get<"state_key"_>(matching))
unmap(state_key, at<"state_key"_>(matching));
if(json::get<"type"_>(matching))
unmap(type, at<"type"_>(matching));
return ret;
}
size_t
ircd::m::hook::maps::match(const event &event,
const std::function<bool (base &)> &callback)
const
{
std::set<base *> matching
{
begin(always), end(always)
};
const auto site_match{[&matching]
(auto &map, const string_view &key)
{
auto pit{map.equal_range(key)};
for(; pit.first != pit.second; ++pit.first)
matching.emplace(pit.first->second);
}};
if(json::get<"origin"_>(event))
site_match(origin, at<"origin"_>(event));
if(json::get<"room_id"_>(event))
site_match(room_id, at<"room_id"_>(event));
if(json::get<"sender"_>(event))
site_match(sender, at<"sender"_>(event));
if(json::get<"type"_>(event))
site_match(type, at<"type"_>(event));
if(json::get<"state_key"_>(event))
site_match(state_key, at<"state_key"_>(event));
auto it(begin(matching));
while(it != end(matching))
{
const base &hook(**it);
if(!_hook_match(hook.matching, event))
it = matching.erase(it);
else
++it;
}
size_t ret{0};
for(auto it(begin(matching)); it != end(matching); ++it, ++ret)
if(!callback(**it))
return ret;
return ret;
}
//
// hook::base
//
/// Instance list linkage for all hooks
template<>
decltype(ircd::util::instance_list<ircd::m::hook>::list)
ircd::util::instance_list<ircd::m::hook>::list
decltype(ircd::util::instance_list<ircd::m::hook::base>::list)
ircd::util::instance_list<ircd::m::hook::base>::list
{};
/// Alternative hook ctor simply allowing the the function argument
/// first and description after.
ircd::m::hook::hook(decltype(function) function,
const json::members &members)
:hook
{
members, std::move(function)
}
{
}
/// Primary hook ctor
ircd::m::hook::hook(const json::members &members,
decltype(function) function)
ircd::m::hook::base::base(const json::members &members)
try
:_feature
{
@ -3016,10 +3165,6 @@ try
{
feature
}
,function
{
std::move(function)
}
{
site *site;
if((site = find_site()))
@ -3035,7 +3180,7 @@ catch(...)
site->del(*this);
}
ircd::m::hook::~hook()
ircd::m::hook::base::~base()
noexcept
{
if(!registered)
@ -3046,8 +3191,8 @@ noexcept
site->del(*this);
}
ircd::m::hook::site *
ircd::m::hook::find_site()
ircd::m::hook::base::site *
ircd::m::hook::base::find_site()
const
{
const auto &site_name
@ -3058,7 +3203,7 @@ const
if(!site_name)
return nullptr;
for(auto *const &site : m::hook::site::list)
for(auto *const &site : m::hook::base::site::list)
if(site->name() == site_name)
return site;
@ -3066,7 +3211,7 @@ const
}
ircd::string_view
ircd::m::hook::site_name()
ircd::m::hook::base::site_name()
const try
{
return unquote(feature.at("_site"));
@ -3079,47 +3224,21 @@ catch(const std::out_of_range &e)
};
}
//
// hook::maps
//
struct ircd::m::hook::maps
{
std::multimap<string_view, hook *> origin;
std::multimap<string_view, hook *> room_id;
std::multimap<string_view, hook *> sender;
std::multimap<string_view, hook *> state_key;
std::multimap<string_view, hook *> type;
std::vector<hook *> always;
maps();
~maps() noexcept;
};
ircd::m::hook::maps::maps()
{
}
ircd::m::hook::maps::~maps()
noexcept
{
}
//
// hook::site
//
/// Instance list linkage for all hook sites
template<>
decltype(ircd::util::instance_list<ircd::m::hook::site>::list)
ircd::util::instance_list<ircd::m::hook::site>::list
decltype(ircd::util::instance_list<ircd::m::hook::base::site>::list)
ircd::util::instance_list<ircd::m::hook::base::site>::list
{};
//
// hook::site::site
//
ircd::m::hook::site::site(const json::members &members)
ircd::m::hook::base::site::site(const json::members &members)
:_feature
{
members
@ -3130,7 +3249,7 @@ ircd::m::hook::site::site(const json::members &members)
}
,maps
{
std::make_unique<hook::maps>()
std::make_unique<struct maps>()
}
{
for(const auto &site : list)
@ -3144,15 +3263,15 @@ ircd::m::hook::site::site(const json::members &members)
// Find and register all of the orphan hooks which were constructed before
// this site was constructed.
for(auto *const &hook : m::hook::list)
if(hook->site_name() == name())
for(auto *const &hook : m::hook::base::list)
if(hook->site_name() == name())
add(*hook);
}
ircd::m::hook::site::~site()
ircd::m::hook::base::site::~site()
noexcept
{
const std::vector<hook *> hooks
const std::vector<base *> hooks
{
begin(this->hooks), end(this->hooks)
};
@ -3162,71 +3281,14 @@ noexcept
}
void
ircd::m::hook::site::operator()(const event &event)
ircd::m::hook::base::site::match(const event &event,
const std::function<bool (base &)> &callback)
{
std::set<hook *> matching //TODO: allocator
{
begin(maps->always), end(maps->always)
};
const auto site_match{[&matching]
(auto &map, const string_view &key)
{
auto pit{map.equal_range(key)};
for(; pit.first != pit.second; ++pit.first)
matching.emplace(pit.first->second);
}};
if(json::get<"origin"_>(event))
site_match(maps->origin, at<"origin"_>(event));
if(json::get<"room_id"_>(event))
site_match(maps->room_id, at<"room_id"_>(event));
if(json::get<"sender"_>(event))
site_match(maps->sender, at<"sender"_>(event));
if(json::get<"type"_>(event))
site_match(maps->type, at<"type"_>(event));
if(json::get<"state_key"_>(event))
site_match(maps->state_key, at<"state_key"_>(event));
auto it(begin(matching));
while(it != end(matching))
{
const hook &hook(**it);
if(!_hook_match(hook.matching, event))
it = matching.erase(it);
else
++it;
}
for(const auto &hook : matching)
call(*hook, event);
}
void
ircd::m::hook::site::call(hook &hook,
const event &event)
try
{
++hook.calls;
hook.function(event);
}
catch(const std::exception &e)
{
log::critical
{
"Unhandled hookfn(%p) %s error :%s",
&hook,
string_view{hook.feature},
e.what()
};
maps->match(event, callback);
}
bool
ircd::m::hook::site::add(hook &hook)
ircd::m::hook::base::site::add(base &hook)
{
assert(!hook.registered);
assert(hook.site_name() == name());
@ -3242,91 +3304,46 @@ ircd::m::hook::site::add(hook &hook)
return false;
}
const auto map{[&hook]
(auto &map, const string_view &value)
assert(maps);
const size_t matched
{
map.emplace(value, &hook);
++hook.matchers;
}};
maps->add(hook, hook.matching)
};
if(json::get<"origin"_>(hook.matching))
map(maps->origin, at<"origin"_>(hook.matching));
if(json::get<"room_id"_>(hook.matching))
map(maps->room_id, at<"room_id"_>(hook.matching));
if(json::get<"sender"_>(hook.matching))
map(maps->sender, at<"sender"_>(hook.matching));
if(json::get<"state_key"_>(hook.matching))
map(maps->state_key, at<"state_key"_>(hook.matching));
if(json::get<"type"_>(hook.matching))
map(maps->type, at<"type"_>(hook.matching));
// Hook had no mappings which means it will match everything.
// We don't increment the matcher count for this case.
if(!hook.matchers)
maps->always.emplace_back(&hook);
++count;
hook.matchers = matched;
hook.registered = true;
matchers += matched;
++count;
return true;
}
bool
ircd::m::hook::site::del(hook &hook)
ircd::m::hook::base::site::del(base &hook)
{
assert(hook.registered);
assert(hook.site_name() == name());
const auto unmap{[&hook]
(auto &map, const string_view &key)
const size_t matched
{
auto pit{map.equal_range(key)};
for(; pit.first != pit.second; ++pit.first)
if(pit.first->second == &hook)
{
--hook.matchers;
return map.erase(pit.first);
}
assert(0);
return end(map);
}};
// Unconditional attempt to remove from always.
std::remove(begin(maps->always), end(maps->always), &hook);
if(json::get<"origin"_>(hook.matching))
unmap(maps->origin, at<"origin"_>(hook.matching));
if(json::get<"room_id"_>(hook.matching))
unmap(maps->room_id, at<"room_id"_>(hook.matching));
if(json::get<"sender"_>(hook.matching))
unmap(maps->sender, at<"sender"_>(hook.matching));
if(json::get<"state_key"_>(hook.matching))
unmap(maps->state_key, at<"state_key"_>(hook.matching));
if(json::get<"type"_>(hook.matching))
unmap(maps->type, at<"type"_>(hook.matching));
maps->del(hook, hook.matching)
};
const auto erased
{
hooks.erase(&hook)
};
assert(erased);
assert(hook.matchers == 0);
--count;
hook.matchers -= matched;
hook.registered = false;
matchers -= matched;
--count;
assert(hook.matchers == 0);
assert(erased);
return true;
}
ircd::string_view
ircd::m::hook::site::name()
ircd::m::hook::base::site::name()
const try
{
return unquote(feature.at("name"));
@ -3339,6 +3356,59 @@ catch(const std::out_of_range &e)
};
}
//
// hook<void>
//
ircd::m::hook::hook<void>::hook(const json::members &feature,
decltype(function) function)
:base{feature}
,function{std::move(function)}
{
}
ircd::m::hook::hook<void>::hook(decltype(function) function,
const json::members &feature)
:base{feature}
,function{std::move(function)}
{
}
ircd::m::hook::site<void>::site(const json::members &feature)
:base::site{feature}
{
}
void
ircd::m::hook::site<void>::operator()(const event &event)
{
match(event, [this, &event]
(base &base)
{
call(dynamic_cast<hook<void> &>(base), event);
return true;
});
}
void
ircd::m::hook::site<void>::call(hook<void> &hfn,
const event &event)
try
{
++hfn.calls;
hfn.function(event);
}
catch(const std::exception &e)
{
log::critical
{
"Unhandled hookfn(%p) %s error :%s",
&hfn,
string_view{hfn.feature},
e.what()
};
}
//
// hook internal
//

View file

@ -241,7 +241,7 @@ room_alias_fetch(const mutable_buffer &out,
return response;
}
const m::hook
const m::hookfn<>
_create_alias_room
{
{
@ -249,6 +249,7 @@ _create_alias_room
{ "room_id", "!ircd" },
{ "type", "m.room.create" },
},
[](const m::event &)
{
m::create(alias_room_id, m::me.user_id);

View file

@ -387,7 +387,7 @@ handle_my_presence_changed(const m::event &event)
}
const m::hook
const m::hookfn<>
my_presence_changed
{
handle_my_presence_changed,

View file

@ -389,7 +389,7 @@ handle_my_profile_changed(const m::event &event)
return handle_my_profile_changed__avatar_url(event);
}
const m::hook
const m::hookfn<>
my_profile_changed
{
handle_my_profile_changed,

View file

@ -164,7 +164,7 @@ _create_public_room(const m::event &)
/// Create the public rooms room at the appropriate time on startup.
/// The startup event chosen here is when @ircd joins the !ircd room,
/// which is a fundamental notification toward the end of init.
const m::hook
const m::hookfn<>
_create_public_hook
{
_create_public_room,

View file

@ -655,7 +655,7 @@ console_cmd__conf__rehash(opt &out, const string_view &line)
bool
console_cmd__hook__list(opt &out, const string_view &line)
{
for(const auto &site : m::hook::site::list)
for(const auto &site : m::hook::base::site::list)
{
out << std::setw(24) << std::left << site->name()
<< std::endl;

View file

@ -389,7 +389,7 @@ create_my_key(const m::event &)
cache_set(json::strung{my_key});
}
const m::hook
const m::hookfn<>
create_my_key_hook
{
create_my_key,

View file

@ -35,7 +35,7 @@ presence_valid_state(const string_view &state)
static void handle_edu_m_presence_(const m::event &, const m::edu::m_presence &edu);
static void handle_edu_m_presence(const m::event &);
const m::hook
const m::hookfn<>
_m_presence_eval
{
handle_edu_m_presence,

View file

@ -22,7 +22,7 @@ static void handle_m_receipt_m_read(const m::room::id &, const json::object &);
static void handle_m_receipt(const m::room::id &, const json::object &);
static void handle_edu_m_receipt(const m::event &);
const m::hook
const m::hookfn<>
_m_receipt_eval
{
handle_edu_m_receipt,

View file

@ -36,7 +36,7 @@ _can_create_room(const m::event &event)
};
}
const m::hook
const m::hookfn<>
_can_create_room_hookfn
{
_can_create_room,
@ -63,7 +63,7 @@ _created_room(const m::event &event)
send(m::my_room, at<"sender"_>(event), "ircd.room", room_id, json::object{});
}
const m::hook
const m::hookfn<>
_created_room_hookfn
{
_created_room,

View file

@ -18,7 +18,7 @@ IRCD_MODULE
namespace ircd::m
{
extern hook::site visible_hook;
extern hook::site<> visible_hook;
}
decltype(ircd::m::visible_hook)
@ -56,7 +56,7 @@ _changed_visibility(const m::event &event)
};
}
const m::hook
const m::hookfn<>
_changed_visibility_hookfn
{
_changed_visibility,
@ -72,7 +72,7 @@ _event_visible(const m::event &event)
}
const m::hook
const m::hookfn<>
_event_visible_hookfn
{
_event_visible,

View file

@ -40,7 +40,7 @@ _changed_rules(const m::event &event)
send(public_room, sender, "ircd.room", room_id, json::strung{event});
}
const m::hook
const m::hookfn<>
_changed_rules_hookfn
{
_changed_rules,

View file

@ -43,7 +43,7 @@ affect_user_room(const m::event &event)
send(user_room, sender, "ircd.member", room_id, at<"content"_>(event));
}
const m::hook
const m::hookfn<>
affect_user_room_hookfn
{
{
@ -59,7 +59,7 @@ _can_join_room(const m::event &event)
}
const m::hook
const m::hookfn<>
_can_join_room_hookfn
{
{
@ -76,7 +76,7 @@ _join_room(const m::event &event)
}
const m::hook
const m::hookfn<>
_join_room_hookfn
{
{
@ -127,7 +127,7 @@ invite_foreign(const m::event &event)
}
const m::hook
const m::hookfn<>
invite_foreign_hookfn
{
{

View file

@ -19,7 +19,7 @@ IRCD_MODULE
static void _handle_edu_m_typing(const m::event &, const m::edu::m_typing &edu);
static void handle_edu_m_typing(const m::event &);
const m::hook
const m::hookfn<>
_m_typing_eval
{
handle_edu_m_typing,

View file

@ -107,7 +107,7 @@ catch(const std::exception &e)
};
}
const m::hook
const m::hookfn<>
conf_updated_hook
{
conf_updated,
@ -133,7 +133,7 @@ init_conf_items(const m::event &)
});
}
const m::hook
const m::hookfn<>
init_conf_items_hook
{
init_conf_items,
@ -181,7 +181,7 @@ create_conf_room(const m::event &)
}
}
const m::hook
const m::hookfn<>
create_conf_room_hook
{
create_conf_room,

View file

@ -105,7 +105,7 @@ catch(const std::exception &e)
notice(control_room, e.what());
}
const m::hook
const m::hookfn<>
command_control_hook
{
command_control,
@ -134,7 +134,7 @@ create_control_room(const m::event &)
notice(control_room, m::me.user_id, "I am the daemon. You can talk to me in this room by highlighting me.");
}
const m::hook
const m::hookfn<>
create_control_hook
{
create_control_room,

View file

@ -54,7 +54,7 @@ create_my_node_room(const m::event &)
create(m::my_node.room_id(), m::me.user_id);
}
const m::hook
const m::hookfn<>
create_my_node_hook
{
create_my_node_room,
@ -71,7 +71,7 @@ create_nodes_room(const m::event &)
create(nodes_room, m::me.user_id);
}
const m::hook
const m::hookfn<>
create_nodes_hook
{
create_nodes_room,

View file

@ -11,9 +11,9 @@
namespace ircd::m::vm
{
extern log::log log;
extern hook::site commit_hook;
extern hook::site eval_hook;
extern hook::site notify_hook;
extern hook::site<> commit_hook;
extern hook::site<> eval_hook;
extern hook::site<> notify_hook;
static void write(eval &);
static fault _eval_edu(eval &, const event &);