diff --git a/Quotient/room.cpp b/Quotient/room.cpp index a6f55cb9..acafcc79 100644 --- a/Quotient/room.cpp +++ b/Quotient/room.cpp @@ -119,6 +119,8 @@ class Q_DECL_HIDDEN Room::Private { Timeline timeline; PendingEvents unsyncedEvents; + std::unordered_map> cachedEvents; + std::unordered_map> cachedEventPromises; QHash eventsIndex; // A map from event id/relation type pairs to a vector of event pointers. Not using QMultiHash, // because we want to quickly return a number of relations for a given event without enumerating @@ -258,11 +260,23 @@ class Q_DECL_HIDDEN Room::Private { return changes; } void addRelation(const ReactionEvent& reactionEvt); - void addRelations(auto from, auto to) + void finishEventPromises(const RoomEvent& evt) { - for (auto it = from; it != to; ++it) - if (const auto* reaction = it->template viewAs()) - addRelation(*reaction); + if (auto it = cachedEventPromises.find(evt.id()); it != cachedEventPromises.cend()) { + for (auto& p : it->second) { + p.addResult(std::cref(evt)); + p.finish(); + } + cachedEventPromises.erase(it); + } + } + void afterAddedMessages(auto from, auto to) + { + for (const auto& ti : std::ranges::subrange(from, to)) { + switchOnType(*ti, std::bind_front(&Private::addRelation, this)); + finishEventPromises(*ti); + cachedEvents.erase(ti->id()); + } } Changes addNewMessageEvents(RoomEvents&& events); @@ -2382,6 +2396,25 @@ JobHandle Room::getPreviousContent(int limit, const QString& f return d->getPreviousContent(limit, filter); } +EventFuture Room::getEvent(const QString& eventId) +{ + if (auto timelineIt = findInTimeline(eventId); timelineIt != historyEdge()) + return makeReadyValueFuture(std::cref(**timelineIt)); + auto baseStateObjects = d->baseState | std::views::values; + if (auto stateIt = std::ranges::find(baseStateObjects, eventId, &RoomEvent::id); + stateIt != end(baseStateObjects)) + return makeReadyValueFuture(std::cref(**stateIt)); + if (auto cachedIt = d->cachedEvents.find(eventId); cachedIt != d->cachedEvents.end()) + return makeReadyValueFuture(std::cref(*cachedIt->second)); + + connection()->callApi(id(), eventId).then([this](RoomEventPtr&& pEvt) { + const auto [it, cachedEventInserted] = d->cachedEvents.emplace(pEvt->id(), std::move(pEvt)); + QUO_CHECK(cachedEventInserted); + d->finishEventPromises(*(it->second)); + }); + return d->cachedEventPromises[eventId].emplace_back().future(); +} + EventFuture Room::ensureEvent(const QString& eventId, quint16 maxWaitSeconds) { if (auto eventIt = findInTimeline(eventId); eventIt != historyEdge()) @@ -3058,7 +3091,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) } if (totalInserted > 0) { - addRelations(from, syncEdge()); + afterAddedMessages(from, syncEdge()); qCDebug(MESSAGES) << "Room" << q->objectName() << "received" << totalInserted << "new events; the last event is now" @@ -3117,7 +3150,7 @@ std::pair Room::Private::addHistoricalMessageEv q->onAddHistoricalTimelineEvents(from); emit q->addedMessages(timeline.front().index(), from->index()); - addRelations(from, historyEdge()); + afterAddedMessages(from, historyEdge()); Q_ASSERT(timeline.size() == timelineSize + insertedSize); if (insertedSize > 9 || et.nsecsElapsed() >= ProfilerMinNsecs) qCDebug(PROFILER) << "Added" << insertedSize << "historical event(s) to" << q->objectName() diff --git a/Quotient/room.h b/Quotient/room.h index d5a5898b..0b0ab8f9 100644 --- a/Quotient/room.h +++ b/Quotient/room.h @@ -750,6 +750,15 @@ class QUOTIENT_API Room : public QObject { QJsonArray exportMegolmSessions(); + //! \brief Obtain an arbitrary room event by its id + //! + //! Looks through the timeline, state events that arrived out of the timeline, and finally + //! cached individual events; if the event is not found locally, requests this one event from + //! the homeserver. + //! \return a ready future with the event reference if an event with \p eventId is found locally; + //! otherwise, a running future connected to the homeserver request + EventFuture getEvent(const QString& eventId); + //! \brief Loads the message history until the specified event id is found //! //! This is potentially heavy; clients should use this sparingly. One intended use case is