diff --git a/include/ircd/m/room/events.h b/include/ircd/m/room/events.h index ea4de2b3d..721205b7a 100644 --- a/include/ircd/m/room/events.h +++ b/include/ircd/m/room/events.h @@ -11,33 +11,6 @@ #pragma once #define HAVE_IRCD_M_ROOM_EVENTS_H -// The "viewport" is comprised of events starting from the tophead (most recent -// in room timeline) and covering about ~20 events leading up to that. Note -// that this is a completely ad hoc and configurable server value. Events in -// the viewport must be eval'ed and synced to clients in the order they will -// be displayed. Events not in the viewport are not /synced to clients and any -// client request provides event ordering: thus older events (backfills, etc) -// can be eval'ed without this constraint. -// -// The "sounding" is the depth of the first gap. In any attempt to trace -// the room timeline from the tophead to the m.room.create event: the sounding -// is the [highest number] depth preventing that. -// -// The "twain" marks the depth at the end of the first gap; the server is in -// possession of one or more events again at the twain. -// -// The "hazard" is the depth of the first gap starting from the m.room.create -// event toward the tophead. In any attempt to trace the room timeline with -// an increasing depth, the hazard is the next gap to frontfill. - -namespace ircd::m -{ - std::pair viewport(const room &); - std::pair sounding(const room &); // Last missing (one) - std::pair twain(const room &); - std::pair hazard(const room &); // First missing (one) -} - /// Interface to room events /// /// This interface has the form of an STL-style iterator over room events @@ -49,8 +22,8 @@ namespace ircd::m struct ircd::m::room::events { struct sounding; - struct horizon; struct missing; + struct horizon; static conf::item viewport_size; @@ -121,77 +94,3 @@ struct ircd::m::room::events static size_t count(const m::room &, const event::idx_range &); static size_t count(const event::idx_range &); }; - -/// Find missing room events. This is a breadth-first iteration of missing -/// references from the tophead (or at the event provided in the room arg) -/// -/// The closure is invoked with the first argument being the event_id unknown -/// to the server, followed by the depth and event::idx of the event making the -/// reference. -/// -struct ircd::m::room::events::missing -{ - using closure = std::function; - - m::room room; - - private: - bool _each(m::room::events &, const closure &) const; - - public: - bool rfor_each(const pair &depth, const closure &) const; - bool for_each(const pair &depth, const closure &) const; - bool for_each(const closure &) const; - size_t count() const; - - missing() = default; - missing(const m::room &room) - :room{room} - {} -}; - -/// Find gaps in the room's events. A gap is where this server has no events -/// at a certain depth. This is a path-finding diagnostic interface, useful to -/// understand what areas of the timeline have not been acquired by the server -/// to calculate backfill requests, etc. This interface is depth-first oriented, -/// rather than the breadth-first room::events::missing interface. -/// -struct ircd::m::room::events::sounding -{ - using range = std::pair; - using closure = std::function; - - m::room room; - - public: - bool for_each(const closure &) const; - bool rfor_each(const closure &) const; - - sounding() = default; - sounding(const m::room &room) - :room{room} - {} -}; - -/// Find missing room events. This is an interface to the event-horizon for -/// this room. The event horizon is keyed by event_id and the value is the -/// event::idx of the event referencing it. There can be multiple entries for -/// an event_id. The closure is also invoked with the depth of the referencer. -/// -struct ircd::m::room::events::horizon -{ - using closure = std::function; - - m::room room; - - public: - bool for_each(const closure &) const; - size_t count() const; - - size_t rebuild(); - - horizon() = default; - horizon(const m::room &room) - :room{room} - {} -}; diff --git a/include/ircd/m/room/events_horizon.h b/include/ircd/m/room/events_horizon.h new file mode 100644 index 000000000..709e9ab5d --- /dev/null +++ b/include/ircd/m/room/events_horizon.h @@ -0,0 +1,35 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2023 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_ROOM_EVENTS_HORIZON_H + +/// Find missing room events. This is an interface to the event-horizon for +/// this room. The event horizon is keyed by event_id and the value is the +/// event::idx of the event referencing it. There can be multiple entries for +/// an event_id. The closure is also invoked with the depth of the referencer. +/// +struct ircd::m::room::events::horizon +{ + using closure = std::function; + + m::room room; + + public: + bool for_each(const closure &) const; + size_t count() const; + + size_t rebuild(); + + horizon() = default; + horizon(const m::room &room) + :room{room} + {} +}; diff --git a/include/ircd/m/room/events_missing.h b/include/ircd/m/room/events_missing.h new file mode 100644 index 000000000..546eb24fd --- /dev/null +++ b/include/ircd/m/room/events_missing.h @@ -0,0 +1,40 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2023 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_ROOM_EVENTS_MISSING_H + +/// Find missing room events. This is a breadth-first iteration of missing +/// references from the tophead (or at the event provided in the room arg) +/// +/// The closure is invoked with the first argument being the event_id unknown +/// to the server, followed by the depth and event::idx of the event making the +/// reference. +/// +struct ircd::m::room::events::missing +{ + using closure = std::function; + + m::room room; + + private: + bool _each(m::room::events &, const closure &) const; + + public: + bool rfor_each(const pair &depth, const closure &) const; + bool for_each(const pair &depth, const closure &) const; + bool for_each(const closure &) const; + size_t count() const; + + missing() = default; + missing(const m::room &room) + :room{room} + {} +}; diff --git a/include/ircd/m/room/events_sounding.h b/include/ircd/m/room/events_sounding.h new file mode 100644 index 000000000..f328cbd30 --- /dev/null +++ b/include/ircd/m/room/events_sounding.h @@ -0,0 +1,62 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2023 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_ROOM_EVENTS_SOUNDING_H + +// The "viewport" is comprised of events starting from the tophead (most recent +// in room timeline) and covering about ~20 events leading up to that. Note +// that this is a completely ad hoc and configurable server value. Events in +// the viewport must be eval'ed and synced to clients in the order they will +// be displayed. Events not in the viewport are not /synced to clients and any +// client request provides event ordering: thus older events (backfills, etc) +// can be eval'ed without this constraint. +// +// The "sounding" is the depth of the first gap. In any attempt to trace +// the room timeline from the tophead to the m.room.create event: the sounding +// is the [highest number] depth preventing that. +// +// The "twain" marks the depth at the end of the first gap; the server is in +// possession of one or more events again at the twain. +// +// The "hazard" is the depth of the first gap starting from the m.room.create +// event toward the tophead. In any attempt to trace the room timeline with +// an increasing depth, the hazard is the next gap to frontfill. + +namespace ircd::m +{ + std::pair viewport(const room &); + std::pair sounding(const room &); // Last missing (one) + std::pair twain(const room &); + std::pair hazard(const room &); // First missing (one) +} + +/// Find gaps in the room's events. A gap is where this server has no events +/// at a certain depth. This is a path-finding diagnostic interface, useful to +/// understand what areas of the timeline have not been acquired by the server +/// to calculate backfill requests, etc. This interface is depth-first oriented, +/// rather than the breadth-first room::missing interface. +/// +struct ircd::m::room::events::sounding +{ + using range = std::pair; + using closure = std::function; + + m::room room; + + public: + bool for_each(const closure &) const; + bool rfor_each(const closure &) const; + + sounding() = default; + sounding(const m::room &room) + :room{room} + {} +}; diff --git a/include/ircd/m/room/room.h b/include/ircd/m/room/room.h index d374a1629..125fe249d 100644 --- a/include/ircd/m/room/room.h +++ b/include/ircd/m/room/room.h @@ -189,6 +189,9 @@ struct ircd::m::room }; #include "events.h" +#include "events_sounding.h" +#include "events_missing.h" +#include "events_horizon.h" #include "state.h" #include "state_space.h" #include "state_history.h" diff --git a/matrix/Makefile.am b/matrix/Makefile.am index e7883ae84..b4fac72da 100644 --- a/matrix/Makefile.am +++ b/matrix/Makefile.am @@ -109,6 +109,9 @@ libircd_matrix_la_SOURCES += room_aliases.cc libircd_matrix_la_SOURCES += room_bootstrap.cc libircd_matrix_la_SOURCES += room_create.cc libircd_matrix_la_SOURCES += room_events.cc +libircd_matrix_la_SOURCES += room_events_sounding.cc +libircd_matrix_la_SOURCES += room_events_missing.cc +libircd_matrix_la_SOURCES += room_events_horizon.cc libircd_matrix_la_SOURCES += room_head.cc libircd_matrix_la_SOURCES += room_head_fetch.cc libircd_matrix_la_SOURCES += room_state_fetch.cc diff --git a/matrix/room_events.cc b/matrix/room_events.cc index ef4df22ec..8df58f550 100644 --- a/matrix/room_events.cc +++ b/matrix/room_events.cc @@ -15,103 +15,6 @@ ircd::m::room::events::viewport_size { "default", 96L }, }; -std::pair -ircd::m::viewport(const room &room) -{ - std::pair ret - { - -1, 0 - }; - - m::room::events it - { - room - }; - - const ssize_t &max - { - room::events::viewport_size - }; - - for(auto i(0); it && i < max; --it, ++i) - { - ret.first = it.depth(); - ret.second = it.event_idx(); - } - - return ret; -} - -std::pair -ircd::m::twain(const room &room) -{ - std::pair ret - { - -1, 0 - }; - - const room::events::sounding s - { - room - }; - - s.rfor_each([&ret] - (const auto &range, const auto &event_idx) noexcept - { - ret.first = range.first - 1; - return false; - }); - - return ret; -} - -std::pair -ircd::m::sounding(const room &room) -{ - std::pair ret - { - -1, 0 - }; - - const room::events::sounding s - { - room - }; - - s.rfor_each([&ret] - (const auto &range, const auto &event_idx) noexcept - { - ret.first = range.second; - ret.second = event_idx; - return false; - }); - - return ret; -} - -std::pair -ircd::m::hazard(const room &room) -{ - std::pair ret - { - 0, 0 - }; - - const room::events::sounding s - { - room - }; - - s.for_each([&ret] - (const auto &range, const auto &event_idx) noexcept - { - ret.first = range.first; - return false; - }); - - return ret; -} - // // room::events // @@ -436,289 +339,3 @@ const return std::get<0>(part); } - -// -// room::events::missing -// - -size_t -ircd::m::room::events::missing::count() -const -{ - size_t ret{0}; - for_each([&ret] - (const auto &event_id, const auto &depth, const auto &event_idx) noexcept - { - ++ret; - return true; - }); - - return ret; -} - -bool -ircd::m::room::events::missing::for_each(const closure &closure) -const -{ - return for_each({0L, 0L}, closure); -} - -bool -ircd::m::room::events::missing::for_each(const pair &depth, - const closure &closure) -const -{ - room::events it - { - room, uint64_t(depth.first) - }; - - for(; it; ++it) - { - if(depth.second && int64_t(it.depth()) > depth.second) - break; - - if(!_each(it, closure)) - return false; - } - - return true; -} - -bool -ircd::m::room::events::missing::rfor_each(const pair &depth, - const closure &closure) -const -{ - room::events it - { - room, depth.second?: -1UL - }; - - for(; it; --it) - { - if(depth.second && int64_t(it.depth()) > depth.second) - continue; - - if(int64_t(it.depth()) < depth.first) - break; - - if(!_each(it, closure)) - return false; - } - - return true; -} - -bool -ircd::m::room::events::missing::_each(m::room::events &it, - const closure &closure) -const -{ - const m::event event - { - *it - }; - - const event::prev prev - { - event - }; - - event::idx idx_buf[event::prev::MAX]; - const auto idx - { - prev.idxs(idx_buf) - }; - - for(size_t i(0); i < idx.size(); ++i) - { - if(idx[i]) - continue; - - if(!closure(prev.prev_event(i), it.depth(), it.event_idx())) - return false; - } - - return true; -} - -// -// room::events::sounding -// - -bool -ircd::m::room::events::sounding::rfor_each(const closure &closure) -const -{ - const int64_t depth - { - room.event_id? - m::get(std::nothrow, m::index(std::nothrow, room.event_id), "depth", -1L): - -1L - }; - - room::events it - { - room, uint64_t(depth) - }; - - if(!it) - return true; - - event::idx idx{0}; - for(sounding::range range{0L, it.depth()}; it; --it) - { - range.first = it.depth(); - if(range.first == range.second) - { - idx = it.event_idx(); - continue; - } - - --range.second; - if(range.first == range.second) - { - idx = it.event_idx(); - continue; - } - - if(!closure({range.first+1, range.second+1}, idx)) - return false; - - range.second = range.first; - } - - return true; -} - -bool -ircd::m::room::events::sounding::for_each(const closure &closure) -const -{ - const int64_t depth - { - room.event_id? - m::get(std::nothrow, m::index(std::nothrow, room.event_id), "depth", 0L): - 0L - }; - - room::events it - { - room, uint64_t(depth) - }; - - for(sounding::range range{depth, 0L}; it; ++it) - { - range.second = it.depth(); - if(range.first == range.second) - continue; - - ++range.first; - if(range.first == range.second) - continue; - - if(!closure(range, it.event_idx())) - return false; - - range.first = range.second; - } - - return true; -} - -// -// room::events::horizon -// - -//TODO: XXX remove fwd decl -namespace ircd::m::dbs -{ - void _index_event_horizon(db::txn &, const event &, const opts &, const m::event::id &); -} - -size_t -ircd::m::room::events::horizon::rebuild() -{ - m::dbs::opts opts; - opts.appendix.reset(); - opts.appendix.set(dbs::appendix::EVENT_HORIZON); - db::txn txn - { - *dbs::events - }; - - size_t ret(0); - m::room::events it - { - room - }; - - for(; it; --it) - { - const m::event &event{*it}; - const event::prev prev_events{event}; - - opts.event_idx = it.event_idx(); - m::for_each(prev_events, [&] - (const m::event::id &event_id) - { - if(m::exists(event_id)) - return true; - - m::dbs::_index_event_horizon(txn, event, opts, event_id); - ++ret; - return true; - }); - } - - txn(); - return ret; -} - -size_t -ircd::m::room::events::horizon::count() -const -{ - size_t ret{0}; - for_each([&ret] - (const auto &event_id, const auto &depth, const auto &event_idx) noexcept - { - ++ret; - return true; - }); - - return ret; -} - -bool -ircd::m::room::events::horizon::for_each(const closure &closure) -const -{ - const std::function in_room - { - [this](const string_view &room_id) noexcept - { - return room_id == this->room.room_id; - } - }; - - return event::horizon::for_every([&in_room, &closure] - (const event::id &event_id, const event::idx &event_idx) - { - if(!m::query(event_idx, "room_id", false, in_room)) - return true; - - if(m::exists(event_id)) - return true; - - uint64_t depth; - if(!m::get(event_idx, "depth", depth)) - return true; - - if(!closure(event_id, depth, event_idx)) - return false; - - return true; - }); -} diff --git a/matrix/room_events_horizon.cc b/matrix/room_events_horizon.cc new file mode 100644 index 000000000..596f673f9 --- /dev/null +++ b/matrix/room_events_horizon.cc @@ -0,0 +1,101 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2023 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. + +//TODO: XXX remove fwd decl +namespace ircd::m::dbs +{ + void _index_event_horizon(db::txn &, const event &, const opts &, const m::event::id &); +} + +size_t +ircd::m::room::events::horizon::rebuild() +{ + m::dbs::opts opts; + opts.appendix.reset(); + opts.appendix.set(dbs::appendix::EVENT_HORIZON); + db::txn txn + { + *dbs::events + }; + + size_t ret(0); + m::room::events it + { + room + }; + + for(; it; --it) + { + const m::event &event{*it}; + const event::prev prev_events{event}; + + opts.event_idx = it.event_idx(); + m::for_each(prev_events, [&] + (const m::event::id &event_id) + { + if(m::exists(event_id)) + return true; + + m::dbs::_index_event_horizon(txn, event, opts, event_id); + ++ret; + return true; + }); + } + + txn(); + return ret; +} + +size_t +ircd::m::room::events::horizon::count() +const +{ + size_t ret{0}; + for_each([&ret] + (const auto &event_id, const auto &depth, const auto &event_idx) noexcept + { + ++ret; + return true; + }); + + return ret; +} + +bool +ircd::m::room::events::horizon::for_each(const closure &closure) +const +{ + const std::function in_room + { + [this](const string_view &room_id) noexcept + { + return room_id == this->room.room_id; + } + }; + + return event::horizon::for_every([&in_room, &closure] + (const event::id &event_id, const event::idx &event_idx) + { + if(!m::query(event_idx, "room_id", false, in_room)) + return true; + + if(m::exists(event_id)) + return true; + + uint64_t depth; + if(!m::get(event_idx, "depth", depth)) + return true; + + if(!closure(event_id, depth, event_idx)) + return false; + + return true; + }); +} diff --git a/matrix/room_events_missing.cc b/matrix/room_events_missing.cc new file mode 100644 index 000000000..b615696c1 --- /dev/null +++ b/matrix/room_events_missing.cc @@ -0,0 +1,111 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2023 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. + +size_t +ircd::m::room::events::missing::count() +const +{ + size_t ret{0}; + for_each([&ret] + (const auto &event_id, const auto &depth, const auto &event_idx) noexcept + { + ++ret; + return true; + }); + + return ret; +} + +bool +ircd::m::room::events::missing::for_each(const closure &closure) +const +{ + return for_each({0L, 0L}, closure); +} + +bool +ircd::m::room::events::missing::for_each(const pair &depth, + const closure &closure) +const +{ + room::events it + { + room, uint64_t(depth.first) + }; + + for(; it; ++it) + { + if(depth.second && int64_t(it.depth()) > depth.second) + break; + + if(!_each(it, closure)) + return false; + } + + return true; +} + +bool +ircd::m::room::events::missing::rfor_each(const pair &depth, + const closure &closure) +const +{ + room::events it + { + room, depth.second?: -1UL + }; + + for(; it; --it) + { + if(depth.second && int64_t(it.depth()) > depth.second) + continue; + + if(int64_t(it.depth()) < depth.first) + break; + + if(!_each(it, closure)) + return false; + } + + return true; +} + +bool +ircd::m::room::events::missing::_each(m::room::events &it, + const closure &closure) +const +{ + const m::event event + { + *it + }; + + const event::prev prev + { + event + }; + + event::idx idx_buf[event::prev::MAX]; + const auto idx + { + prev.idxs(idx_buf) + }; + + for(size_t i(0); i < idx.size(); ++i) + { + if(idx[i]) + continue; + + if(!closure(prev.prev_event(i), it.depth(), it.event_idx())) + return false; + } + + return true; +} diff --git a/matrix/room_events_sounding.cc b/matrix/room_events_sounding.cc new file mode 100644 index 000000000..f21c6b451 --- /dev/null +++ b/matrix/room_events_sounding.cc @@ -0,0 +1,190 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2023 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. + +std::pair +ircd::m::viewport(const room &room) +{ + std::pair ret + { + -1, 0 + }; + + m::room::events it + { + room + }; + + const ssize_t &max + { + room::events::viewport_size + }; + + for(auto i(0); it && i < max; --it, ++i) + { + ret.first = it.depth(); + ret.second = it.event_idx(); + } + + return ret; +} + +std::pair +ircd::m::twain(const room &room) +{ + std::pair ret + { + -1, 0 + }; + + const room::events::sounding s + { + room + }; + + s.rfor_each([&ret] + (const auto &range, const auto &event_idx) noexcept + { + ret.first = range.first - 1; + return false; + }); + + return ret; +} + +std::pair +ircd::m::sounding(const room &room) +{ + std::pair ret + { + -1, 0 + }; + + const room::events::sounding s + { + room + }; + + s.rfor_each([&ret] + (const auto &range, const auto &event_idx) noexcept + { + ret.first = range.second; + ret.second = event_idx; + return false; + }); + + return ret; +} + +std::pair +ircd::m::hazard(const room &room) +{ + std::pair ret + { + 0, 0 + }; + + const room::events::sounding s + { + room + }; + + s.for_each([&ret] + (const auto &range, const auto &event_idx) noexcept + { + ret.first = range.first; + return false; + }); + + return ret; +} + +// +// room::events::sounding +// + +bool +ircd::m::room::events::sounding::rfor_each(const closure &closure) +const +{ + const int64_t depth + { + room.event_id? + m::get(std::nothrow, m::index(std::nothrow, room.event_id), "depth", -1L): + -1L + }; + + room::events it + { + room, uint64_t(depth) + }; + + if(!it) + return true; + + event::idx idx{0}; + for(sounding::range range{0L, it.depth()}; it; --it) + { + range.first = it.depth(); + if(range.first == range.second) + { + idx = it.event_idx(); + continue; + } + + --range.second; + if(range.first == range.second) + { + idx = it.event_idx(); + continue; + } + + if(!closure({range.first+1, range.second+1}, idx)) + return false; + + range.second = range.first; + } + + return true; +} + +bool +ircd::m::room::events::sounding::for_each(const closure &closure) +const +{ + const int64_t depth + { + room.event_id? + m::get(std::nothrow, m::index(std::nothrow, room.event_id), "depth", 0L): + 0L + }; + + room::events it + { + room, uint64_t(depth) + }; + + for(sounding::range range{depth, 0L}; it; ++it) + { + range.second = it.depth(); + if(range.first == range.second) + continue; + + ++range.first; + if(range.first == range.second) + continue; + + if(!closure(range, it.event_idx())) + return false; + + range.first = range.second; + } + + return true; +}