From a18f505fe7ca66556d66538a7c9b9ff31d2c1b29 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 9 Aug 2022 18:42:24 +0200 Subject: EventMetaType, QUO_EVENT, QUO_BASE_EVENT The new metatype framework replaces EventFactory/DEFINE_EVENT_TYPEID/REGISTER_EVENT_TYPE; it is faster, more functional and extensible. Of note: - EventMetaType mostly reproduces the logic of EventFactory but supports custom base event types not just for loading (that part EventFactory also supported) but also for matching - previously you had to have Event::is*Event() for base type matching. Now Quotient::is() can match against both base and leaf types. - Instead of DEFINE_EVENT_TYPEID and REGISTER_EVENT_TYPE there's now a single macro, QUO_EVENT, intended for use in the way similar to Q_OBJECT. Actually, the entire framework borrows heavily from QMetaObject and Q_OBJECT. Making event types full-fledged QObjects is still not considered because half of QObject functions would not be applicable (e.g. signals/slots) while another half (in particular, using Matrix type ids to select event types) would still have to be done on top of QObject. And QML can just access events as const QJsonObjects which is arguably more lightweight as well. - QUO_BASE_EVENT is a new macro replacing EventFactory object definitions. This was necessary for the same reason why Q_OBJECT is a macro: aside from a static object definition, this macro introduces a virtual function override to resolve the metatype at runtime. This very mechanism is used to make event type matching/casting as quick as possible - QUO_BASE_EVENT and QUO_EVENT use the C++20 __VA_OPT__ feature that is only available with the new MSVC preprocessor (see https://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-experimental-overview); the respective switch was added to CMakeLists.txt. --- CMakeLists.txt | 2 +- lib/events/callanswerevent.h | 3 +- lib/events/callcandidatesevent.h | 4 +- lib/events/callhangupevent.h | 4 +- lib/events/callinviteevent.h | 4 +- lib/events/directchatevent.h | 3 +- lib/events/encryptedevent.h | 4 +- lib/events/encryptionevent.h | 3 +- lib/events/event.cpp | 41 +++- lib/events/event.h | 355 ++++++++++++++++++++++++----------- lib/events/keyverificationevent.h | 26 +-- lib/events/receiptevent.h | 3 +- lib/events/redactionevent.h | 3 +- lib/events/roomavatarevent.h | 3 +- lib/events/roomcanonicalaliasevent.h | 3 +- lib/events/roomcreateevent.h | 3 +- lib/events/roomevent.h | 16 +- lib/events/roomkeyevent.h | 3 +- lib/events/roommemberevent.h | 12 +- lib/events/roommessageevent.h | 4 +- lib/events/roompowerlevelsevent.h | 3 +- lib/events/roomtombstoneevent.h | 3 +- lib/events/simplestateevents.h | 5 +- lib/events/stateevent.h | 30 +-- lib/events/stickerevent.h | 3 +- 25 files changed, 326 insertions(+), 217 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 06e754aa..9983f860 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ endif(CMAKE_BUILD_TYPE) message(STATUS "Using compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}" ) include(CheckCXXCompilerFlag) if (MSVC) - add_compile_options(/EHsc /W4 + add_compile_options(/Zc:preprocessor /EHsc /W4 /wd4100 /wd4127 /wd4242 /wd4244 /wd4245 /wd4267 /wd4365 /wd4456 /wd4459 /wd4464 /wd4505 /wd4514 /wd4571 /wd4619 /wd4623 /wd4625 /wd4626 /wd4706 /wd4710 /wd4774 /wd4820 /wd4946 /wd5026 /wd5027) diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 4d539b85..70ca1c7e 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -9,7 +9,7 @@ namespace Quotient { class QUOTIENT_API CallAnswerEvent : public CallEventBase { public: - DEFINE_EVENT_TYPEID("m.call.answer", CallAnswerEvent) + QUO_EVENT(CallAnswerEvent, "m.call.answer") explicit CallAnswerEvent(const QJsonObject& obj); @@ -20,5 +20,4 @@ public: return contentPart("answer"_ls).value("sdp"_ls).toString(); } }; -REGISTER_EVENT_TYPE(CallAnswerEvent) } // namespace Quotient diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index e949f722..cb96f358 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -11,7 +11,7 @@ namespace Quotient { class CallCandidatesEvent : public CallEventBase { public: - DEFINE_EVENT_TYPEID("m.call.candidates", CallCandidatesEvent) + QUO_EVENT(CallCandidatesEvent, "m.call.candidates") explicit CallCandidatesEvent(const QJsonObject& obj) : CallEventBase(typeId(), obj) @@ -27,6 +27,4 @@ public: QUO_CONTENT_GETTER(QString, callId) QUO_CONTENT_GETTER(int, version) }; - -REGISTER_EVENT_TYPE(CallCandidatesEvent) } // namespace Quotient diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index b0017c59..e4d9bb78 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -9,7 +9,7 @@ namespace Quotient { class QUOTIENT_API CallHangupEvent : public CallEventBase { public: - DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) + QUO_EVENT(CallHangupEvent, "m.call.hangup") explicit CallHangupEvent(const QJsonObject& obj) : CallEventBase(typeId(), obj) @@ -18,6 +18,4 @@ public: : CallEventBase(typeId(), matrixTypeId(), callId, 0) {} }; - -REGISTER_EVENT_TYPE(CallHangupEvent) } // namespace Quotient diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index 5b4ca0df..f96f416d 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -9,7 +9,7 @@ namespace Quotient { class QUOTIENT_API CallInviteEvent : public CallEventBase { public: - DEFINE_EVENT_TYPEID("m.call.invite", CallInviteEvent) + QUO_EVENT(CallInviteEvent, "m.call.invite") explicit CallInviteEvent(const QJsonObject& obj); @@ -22,6 +22,4 @@ public: return contentPart("offer"_ls).value("sdp"_ls).toString(); } }; - -REGISTER_EVENT_TYPE(CallInviteEvent) } // namespace Quotient diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h index 2018d3d6..942edba4 100644 --- a/lib/events/directchatevent.h +++ b/lib/events/directchatevent.h @@ -8,11 +8,10 @@ namespace Quotient { class QUOTIENT_API DirectChatEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.direct", DirectChatEvent) + QUO_EVENT(DirectChatEvent, "m.direct") explicit DirectChatEvent(const QJsonObject& obj) : Event(typeId(), obj) {} QMultiHash usersToDirectChats() const; }; -REGISTER_EVENT_TYPE(DirectChatEvent) } // namespace Quotient diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index ddd5e415..22e51cb8 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -27,7 +27,7 @@ namespace Quotient { */ class QUOTIENT_API EncryptedEvent : public RoomEvent { public: - DEFINE_EVENT_TYPEID("m.room.encrypted", EncryptedEvent) + QUO_EVENT(EncryptedEvent, "m.room.encrypted") /* In case with Olm, the encrypted content of the event is * a map from the recipient Curve25519 identity key to ciphertext @@ -59,6 +59,4 @@ public: void setRelation(const QJsonObject& relation); }; -REGISTER_EVENT_TYPE(EncryptedEvent) - } // namespace Quotient diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 91452c3f..60e77451 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -28,7 +28,7 @@ public: class QUOTIENT_API EncryptionEvent : public StateEvent { public: - DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent) + QUO_EVENT(EncryptionEvent, "m.room.encryption") using EncryptionType [[deprecated("Use Quotient::EncryptionType instead")]] = @@ -48,5 +48,4 @@ public: bool useEncryption() const { return !algorithm().isEmpty(); } }; -REGISTER_EVENT_TYPE(EncryptionEvent) } // namespace Quotient diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 1f1eebaa..595e20a5 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -4,6 +4,7 @@ #include "event.h" #include "logging.h" +#include "stateevent.h" #include @@ -11,12 +12,38 @@ using namespace Quotient; QString EventTypeRegistry::getMatrixType(event_type_t typeId) { return typeId; } -void _impl::EventFactoryBase::logAddingMethod(event_type_t TypeId, - size_t newSize) +void AbstractEventMetaType::addDerived(AbstractEventMetaType* newType) { - qDebug(EVENTS) << "Adding factory method for" << TypeId << "events;" - << newSize << "methods will be in the" << name - << "chain"; + if (const auto existing = + std::find_if(derivedTypes.cbegin(), derivedTypes.cend(), + [&newType](const AbstractEventMetaType* t) { + return t->matrixId == newType->matrixId; + }); + existing != derivedTypes.cend()) + { + if (*existing == newType) + return; + // Two different metatype objects claim the same Matrix type id; this + // is not normal, so give as much information as possible to diagnose + if ((*existing)->className == newType->className) { + qCritical(EVENTS) + << newType->className << "claims" << newType->matrixId + << "repeatedly; check that it's exported across translation " + "units or shared objects"; + Q_ASSERT(false); // That situation is plain wrong + return; // So maybe std::terminate() even? + } + qWarning(EVENTS).nospace() + << newType->matrixId << " is already mapped to " + << (*existing)->className << " before " << newType->className + << "; unless the two have different isValid() conditions, the " + "latter class will never be used"; + } + derivedTypes.emplace_back(newType); + qDebug(EVENTS).nospace() + << newType->matrixId << " -> " << newType->className << "; " + << derivedTypes.size() << " derived type(s) registered for " + << className; } Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json) @@ -48,6 +75,10 @@ const QJsonObject Event::unsignedJson() const return fullJson()[UnsignedKeyL].toObject(); } +bool Event::isStateEvent() const { return is(); } + +bool Event::isCallEvent() const { return is(); } + void Event::dumpTo(QDebug dbg) const { dbg << QJsonDocument(contentJson()).toJson(QJsonDocument::Compact); diff --git a/lib/events/event.h b/lib/events/event.h index 711f8fd4..ea827244 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -85,91 +85,167 @@ inline event_ptr_tt makeEvent(ArgTs&&... args) return std::make_unique(std::forward(args)...); } -namespace _impl { - class QUOTIENT_API EventFactoryBase { - public: - EventFactoryBase(const EventFactoryBase&) = delete; - - protected: // This class is only to inherit from - explicit EventFactoryBase(const char* name) - : name(name) - {} - void logAddingMethod(event_type_t TypeId, size_t newSize); - - private: - const char* const name; - }; -} // namespace _impl - -//! \brief A family of event factories to create events from CS API responses -//! -//! Each of these factories, as instantiated by event base types (Event, -//! RoomEvent etc.) is capable of producing an event object derived from -//! \p BaseEventT, using the JSON payload and the event type passed to its -//! make() method. Don't use these directly to make events; use loadEvent() -//! overloads as the frontend for these. Never instantiate new factories -//! outside of base event classes. -//! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory, -//! StateEventBase::factory -template -class EventFactory : public _impl::EventFactoryBase { -private: - using method_t = event_ptr_tt (*)(const QJsonObject&, - const QString&); - std::vector methods {}; +// === EventMetaType === - template - static event_ptr_tt makeIfMatches(const QJsonObject& json, - const QString& matrixType) +class Event; + +template +bool is(const Event& e); + +//! \brief The base class for event metatypes +//! +//! You should not normally have to use this directly, unless you need to devise +//! a whole new kind of event metatypes. +class QUOTIENT_API AbstractEventMetaType { +public: + // The public fields here are const and are not to be changeable anyway. + // NOLINTBEGIN(misc-non-private-member-variables-in-classes) + const char* const className; + const event_type_t matrixId; + const AbstractEventMetaType* const baseType = nullptr; + // NOLINTEND(misc-non-private-member-variables-in-classes) + + explicit AbstractEventMetaType(const char* className) + : className(className) + {} + explicit AbstractEventMetaType(const char* className, event_type_t matrixId, + AbstractEventMetaType& nearestBase) + : className(className), matrixId(matrixId), baseType(&nearestBase) { - // If your matrix event type is not all ASCII, it's your problem - // (see https://github.com/matrix-org/matrix-doc/pull/2758) - return EventT::TypeId == matrixType ? makeEvent(json) : nullptr; + nearestBase.addDerived(this); } + void addDerived(AbstractEventMetaType *newType); + + virtual ~AbstractEventMetaType() = default; + +protected: + // Allow template specialisations to call into one another + template + friend class EventMetaType; + + // The returned value indicates whether a generic object has to be created + // on the top level when `event` is empty, instead of returning nullptr + virtual bool doLoadFrom(const QJsonObject& fullJson, const QString& type, + Event*& event) const = 0; + +private: + std::vector derivedTypes{}; + Q_DISABLE_COPY_MOVE(AbstractEventMetaType) +}; + +// Any event metatype is unique (note Q_DISABLE_COPY_MOVE above) so can be +// identified by its address +inline bool operator==(const AbstractEventMetaType& lhs, + const AbstractEventMetaType& rhs) +{ + return &lhs == &rhs; +} + +//! \brief A family of event meta-types to load and match events +//! +//! TL;DR for the loadFrom() story: +//! - for base event types, use QUO_BASE_EVENT and, if you have additional +//! validation (e.g., JSON has to contain a certain key - see StateEventBase +//! for a real example), define it in the static EventT::isValid() member +//! function accepting QJsonObject and returning bool. +//! - for leaf (specific) event types - simply use QUO_EVENT and it will do +//! everything necessary, including the TypeId definition. +//! \sa QUO_EVENT, QUO_BASE_EVENT +template +class QUOTIENT_API EventMetaType : public AbstractEventMetaType { + // Above: can't constrain EventT to be EventClass because it's incomplete + // at the point of EventMetaType instantiation. public: - explicit EventFactory(const char* fName) - : EventFactoryBase { fName } - {} + using AbstractEventMetaType::AbstractEventMetaType; - //! \brief Add a method to create events of a given type + //! \brief Try to load an event from JSON, with dynamic type resolution //! - //! Adds a standard factory method (makeIfMatches) for \p EventT so that - //! event objects of this type can be created dynamically by loadEvent. - //! The caller is responsible for ensuring this method is called only - //! once per type. - //! \sa loadEvent, Quotient::loadEvent - template - const auto& addMethod() + //! The generic logic defined in this class template and invoked applies to + //! all event types defined in the library and boils down to the following: + //! 1. + //! a. If EventT has TypeId defined (which normally is a case of + //! all leaf - specific - event types, via QUO_EVENT macro) and + //! \p type doesn't exactly match it, nullptr is immediately returned. + //! b. In absence of TypeId, an event type is assumed to be a base; + //! its derivedTypes are examined, and this algorithm is applied + //! recursively on each. + //! 2. Optional validation: if EventT (or, due to the way inheritance works, + //! any of its base event types) has a static isValid() predicate and + //! the event JSON does not satisfy it, nullptr is immediately returned + //! to the upper level or to the loadFrom() caller. This is how existence + //! of `state_key` is checked in any type derived from StateEventBase. + //! 3. If step 1b above returned non-nullptr, immediately return it. + //! 4. + //! a. If EventT::isValid() or EventT::TypeId (either, or both) exist and + //! are satisfied (see steps 1a and 2 above), an object of this type + //! is created from the passed JSON and returned. In case of a base + //! event type, this will be a generic (aka "unknown") event. + //! b. If neither exists, a generic event is only created and returned + //! when on the top level (i.e., outside of recursion into + //! derivedTypes); lower levels return nullptr instead and the type + //! lookup continues. The latter is a case of a derived base event + //! metatype (e.g. RoomEvent) called from its base event metatype + //! (i.e., Event). If no matching type derived from RoomEvent is found, + //! the nested lookup returns nullptr rather than a generic RoomEvent, + //! so that other types derived from Event could be examined. + event_ptr_tt loadFrom(const QJsonObject& fullJson, + const QString& type) const { - const auto m = &makeIfMatches; - const auto it = std::find(methods.cbegin(), methods.cend(), m); - if (it != methods.cend()) - return *it; - logAddingMethod(EventT::TypeId, methods.size() + 1); - return methods.emplace_back(m); + Event* event = nullptr; + const bool goodEnough = doLoadFrom(fullJson, type, event); + if (!event && goodEnough) + return event_ptr_tt{ makeEvent(fullJson) }; + return event_ptr_tt{ static_cast(event) }; } - auto loadEvent(const QJsonObject& json, const QString& matrixType) +private: + bool doLoadFrom(const QJsonObject& fullJson, const QString& type, + Event*& event) const override + { + if constexpr (requires { EventT::TypeId; }) { + if (EventT::TypeId != type) + return false; + } else { + for (const auto& p : derivedTypes) { + p->doLoadFrom(fullJson, type, event); + if (event) { + Q_ASSERT(is(*event)); + return false; + } + } + } + if constexpr (requires { EventT::isValid; }) { + if (!EventT::isValid(fullJson)) + return false; + } else if constexpr (!requires { EventT::TypeId; }) + return true; // Create a generic event object if on the top level + event = makeEvent(fullJson); + return false; + } + static auto makeEvent(const QJsonObject& fullJson) { - for (const auto& f : methods) - if (auto e = f(json, matrixType)) - return e; - return makeEvent(UnknownEventTypeId, json); + if constexpr (requires { EventT::TypeId; }) + return new EventT(fullJson); + else + return new EventT(UnknownEventTypeId, fullJson); } }; -//! \brief Point of customisation to dynamically load events -//! -//! The default specialisation of this calls BaseEventT::factory.loadEvent() -//! and if that fails (i.e. returns nullptr) creates an unknown event of -//! BaseEventT. Other specialisations may reuse other factories, add validations -//! common to BaseEventT events, and so on. -template -event_ptr_tt doLoadEvent(const QJsonObject& json, - const QString& matrixType) +template +constexpr const auto& mostSpecificMetaType() { - return BaseEventT::factory.loadEvent(json, matrixType); + if constexpr (requires { EventT::MetaType; }) + return EventT::MetaType; + else + return EventT::BaseMetaType; +} + +template +inline event_ptr_tt doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + return mostSpecificMetaType().loadFrom(json, matrixType); } // === Event === @@ -177,7 +253,12 @@ event_ptr_tt doLoadEvent(const QJsonObject& json, class QUOTIENT_API Event { public: using Type = event_type_t; - static inline EventFactory factory { "Event" }; + static inline EventMetaType BaseMetaType { "Event" }; + virtual const AbstractEventMetaType& metaType() const + { + return BaseMetaType; + } + explicit Event(Type type, const QJsonObject& json); explicit Event(Type type, event_mtype_t matrixType, @@ -194,8 +275,26 @@ public: return { { TypeKey, matrixType }, { ContentKey, content } }; } - Type type() const { return _type; } + //! \brief Event Matrix type, as identified by its metatype object + //! + //! For generic/unknown events it will contain a descriptive/generic string + //! defined by the respective base event type (that can be empty). + //! \sa matrixType + Type type() const { return metaType().matrixId; } + + //! \brief Exact Matrix type stored in JSON + //! + //! Coincides with the result of type() (but is slower) for events defined + //! in C++ (not necessarily in the library); for generic/unknown events + //! the returned value will be different. QString matrixType() const; + + template + bool is() const + { + return Quotient::is(*this); + } + [[deprecated("Use fullJson() and stringify it with QJsonDocument::toJson() " "or by other means")]] QByteArray originalJson() const; @@ -237,13 +336,17 @@ public: friend QUOTIENT_API QDebug operator<<(QDebug dbg, const Event& e) { QDebugStateSaver _dss { dbg }; - dbg.noquote().nospace() << e.matrixType() << '(' << e.type() << "): "; + dbg.noquote().nospace() + << e.matrixType() << '(' << e.metaType().className << "): "; e.dumpTo(dbg); return dbg; } - virtual bool isStateEvent() const { return false; } - virtual bool isCallEvent() const { return false; } + // State events are quite special in Matrix; so isStateEvent() is here, + // as an exception. For other base events, Event::is<>() and + // Quotient::is<>() should be used; don't add is* methods here + bool isStateEvent() const; + [[deprecated("Use is() instead")]] bool isCallEvent() const; protected: QJsonObject& editJson() { return _json; } @@ -259,11 +362,64 @@ template using EventsArray = std::vector>; using Events = EventsArray; +// === Facilities for event class definitions === + +//! \brief Supply event metatype information in base event types +//! +//! Use this macro in a public section of your base event class to provide +//! type identity and enable dynamic loading of generic events of that type. +//! Do _not_ add this macro if your class is an intermediate wrapper and is not +//! supposed to be instantiated on its own. Provides BaseMetaType static field +//! initialised by parameters passed to the macro, and a metaType() override +//! pointing to that BaseMetaType. +//! \sa EventMetaType, EventMetaType::SuppressLoadDerived +#define QUO_BASE_EVENT(CppType_, ...) \ + static inline EventMetaType BaseMetaType{ \ + #CppType_ __VA_OPT__(,) __VA_ARGS__ }; \ + const AbstractEventMetaType& metaType() const override \ + { \ + return BaseMetaType; \ + } \ + // End of macro + +//! Supply event metatype information in (specific) event types +//! +//! Use this macro in a public section of your event class to provide type +//! identity and enable dynamic loading of generic events of that type. +//! Do _not_ use this macro if your class is an intermediate wrapper and is not +//! supposed to be instantiated on its own. Provides MetaType static field +//! initialised as described below; a metaType() override pointing to it; and +//! the TypeId static field that is equal to MetaType.matrixId. +//! +//! The first two macro parameters are used as the first two EventMetaType +//! constructor parameters; the third EventMetaType parameter is always +//! BaseMetaType; and additional base types can be passed in extra macro +//! parameters if you need to include the same event type in more than one +//! event factory hierarchy (e.g., EncryptedEvent). +//! \sa EventMetaType +#define QUO_EVENT(CppType_, MatrixType_, ...) \ + static inline const auto& TypeId = MatrixType_##_ls; \ + static inline const EventMetaType MetaType{ \ + #CppType_, TypeId, BaseMetaType __VA_OPT__(,) __VA_ARGS__ \ + }; \ + const AbstractEventMetaType& metaType() const override \ + { \ + return MetaType; \ + } \ + [[deprecated("Use " #CppType_ "::TypeId directly instead")]] \ + static constexpr const char* matrixTypeId() { return MatrixType_; } \ + [[deprecated("Use " #CppType_ "::TypeId directly instead")]] \ + static event_type_t typeId() { return TypeId; } \ + // End of macro + +//! \deprecated This is the old name for what is now known as QUO_EVENT +#define DEFINE_EVENT_TYPEID(Type_, Id_) QUO_EVENT(Type_, Id_) + #define QUO_CONTENT_GETTER_X(PartType_, PartName_, JsonKey_) \ - PartType_ PartName_() const \ - { \ - static const auto PartName_##JsonKey = JsonKey_; \ - return contentPart(PartName_##JsonKey); \ + PartType_ PartName_() const \ + { \ + static const auto PartName_##JsonKey = JsonKey_; \ + return contentPart(PartName_##JsonKey); \ } //! \brief Define an inline method obtaining a content part @@ -277,25 +433,9 @@ using Events = EventsArray; #define QUO_CONTENT_GETTER(PartType_, PartName_) \ QUO_CONTENT_GETTER_X(PartType_, PartName_, toSnakeCase(#PartName_##_ls)) -// === Facilities for event class definitions === - -// This macro should be used in a public section of an event class to -// provide matrixTypeId() and typeId(). -#define DEFINE_EVENT_TYPEID(Id_, Type_) \ - static constexpr event_type_t TypeId = Id_##_ls; \ - [[deprecated("Use " #Type_ "::TypeId directly instead")]] \ - static constexpr event_mtype_t matrixTypeId() { return Id_; } \ - [[deprecated("Use " #Type_ "::TypeId directly instead")]] \ - static event_type_t typeId() { return TypeId; } \ - // End of macro - -// This macro should be put after an event class definition (in .h or .cpp) -// to enable its deserialisation from a /sync and other -// polymorphic event arrays -#define REGISTER_EVENT_TYPE(Type_) \ - [[maybe_unused]] inline const auto& factoryMethodFor##Type_ = \ - Type_::factory.addMethod(); \ - // End of macro +//! \deprecated This macro was used after an event class definition +//! to enable its dynamic loading; it is completely superseded by QUO_EVENT +#define REGISTER_EVENT_TYPE(Type_) /// \brief Define a new event class with a single key-value pair in the content /// @@ -309,7 +449,7 @@ using Events = EventsArray; JsonKey_) \ class QUOTIENT_API Name_ : public Base_ { \ public: \ - DEFINE_EVENT_TYPEID(TypeId_, Name_) \ + QUO_EVENT(Name_, TypeId_) \ using value_type = ValueType_; \ explicit Name_(const QJsonObject& obj) : Base_(TypeId, obj) {} \ explicit Name_(const value_type& v) \ @@ -318,7 +458,6 @@ using Events = EventsArray; QUO_CONTENT_GETTER_X(ValueType_, GetterName_, JsonKey) \ static inline const auto JsonKey = toSnakeCase(#GetterName_##_ls); \ }; \ - REGISTER_EVENT_TYPE(Name_) \ // End of macro // === is<>(), eventCast<>() and switchOnType<>() === @@ -326,12 +465,16 @@ using Events = EventsArray; template inline bool is(const Event& e) { - return e.type() == typeId(); -} - -inline bool isUnknown(const Event& e) -{ - return e.type() == UnknownEventTypeId; + if constexpr (requires { EventT::MetaType; }) { + return &e.metaType() == &EventT::MetaType; + } else { + const auto* p = &e.metaType(); + do { + if (p == &EventT::BaseMetaType) + return true; + } while ((p = p->baseType) != nullptr); + return false; + } } //! \brief Cast the event pointer down in a type-safe way diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index 5b587522..5b5a518f 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -13,7 +13,7 @@ static constexpr auto SasV1Method = "m.sas.v1"_ls; /// Typically sent as a to-device event. class QUOTIENT_API KeyVerificationRequestEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent) + QUO_EVENT(KeyVerificationRequestEvent, "m.key.verification.request") explicit KeyVerificationRequestEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -45,11 +45,10 @@ public: /// by the receiver. QUO_CONTENT_GETTER(QDateTime, timestamp) }; -REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) class QUOTIENT_API KeyVerificationReadyEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.ready", KeyVerificationReadyEvent) + QUO_EVENT(KeyVerificationReadyEvent, "m.key.verification.ready") explicit KeyVerificationReadyEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -72,13 +71,11 @@ public: /// The verification methods supported by the sender. QUO_CONTENT_GETTER(QStringList, methods) }; -REGISTER_EVENT_TYPE(KeyVerificationReadyEvent) - /// Begins a key verification process. class QUOTIENT_API KeyVerificationStartEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent) + QUO_EVENT(KeyVerificationStartEvent, "m.key.verification.start") explicit KeyVerificationStartEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -146,13 +143,12 @@ public: return contentPart("short_authentification_string"_ls); } }; -REGISTER_EVENT_TYPE(KeyVerificationStartEvent) /// Accepts a previously sent m.key.verification.start message. /// Typically sent as a to-device event. class QUOTIENT_API KeyVerificationAcceptEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent) + QUO_EVENT(KeyVerificationAcceptEvent, "m.key.verification.accept") explicit KeyVerificationAcceptEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -199,11 +195,10 @@ public: /// canonical JSON representation of the m.key.verification.start message. QUO_CONTENT_GETTER(QString, commitment) }; -REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) class QUOTIENT_API KeyVerificationCancelEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent) + QUO_EVENT(KeyVerificationCancelEvent, "m.key.verification.cancel") explicit KeyVerificationCancelEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -228,13 +223,12 @@ public: /// The error code for why the process/request was cancelled by the user. QUO_CONTENT_GETTER(QString, code) }; -REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) /// Sends the ephemeral public key for a device to the partner device. /// Typically sent as a to-device event. class QUOTIENT_API KeyVerificationKeyEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) + QUO_EVENT(KeyVerificationKeyEvent, "m.key.verification.key") explicit KeyVerificationKeyEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -251,12 +245,11 @@ public: /// The device's ephemeral public key, encoded as unpadded base64. QUO_CONTENT_GETTER(QString, key) }; -REGISTER_EVENT_TYPE(KeyVerificationKeyEvent) /// Sends the MAC of a device's key to the partner device. class QUOTIENT_API KeyVerificationMacEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent) + QUO_EVENT(KeyVerificationMacEvent, "m.key.verification.mac") explicit KeyVerificationMacEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -280,11 +273,10 @@ public: return contentPart>("mac"_ls); } }; -REGISTER_EVENT_TYPE(KeyVerificationMacEvent) class QUOTIENT_API KeyVerificationDoneEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.done", KeyVerificationDoneEvent) + QUO_EVENT(KeyVerificationDoneEvent, "m.key.verification.done") explicit KeyVerificationDoneEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -297,6 +289,4 @@ public: /// The same transactionId as before QUO_CONTENT_GETTER(QString, transactionId) }; -REGISTER_EVENT_TYPE(KeyVerificationDoneEvent) - } // namespace Quotient diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index 5e077e47..a02f4592 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -21,11 +21,10 @@ using EventsWithReceipts = QVector; class QUOTIENT_API ReceiptEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) + QUO_EVENT(ReceiptEvent, "m.receipt") explicit ReceiptEvent(const EventsWithReceipts& ewrs); explicit ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) {} EventsWithReceipts eventsWithReceipts() const; }; -REGISTER_EVENT_TYPE(ReceiptEvent) } // namespace Quotient diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index 63617e54..c193054a 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -8,7 +8,7 @@ namespace Quotient { class QUOTIENT_API RedactionEvent : public RoomEvent { public: - DEFINE_EVENT_TYPEID("m.room.redaction", RedactionEvent) + QUO_EVENT(RedactionEvent, "m.room.redaction") explicit RedactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) {} @@ -19,5 +19,4 @@ public: } QUO_CONTENT_GETTER(QString, reason) }; -REGISTER_EVENT_TYPE(RedactionEvent) } // namespace Quotient diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index af291696..2ebe29bf 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -14,7 +14,7 @@ class QUOTIENT_API RoomAvatarEvent // without a thumbnail. But The Spec says there be thumbnails, and // we follow The Spec. public: - DEFINE_EVENT_TYPEID("m.room.avatar", RoomAvatarEvent) + QUO_EVENT(RoomAvatarEvent, "m.room.avatar") explicit RoomAvatarEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} explicit RoomAvatarEvent(const EventContent::ImageContent& avatar) @@ -31,5 +31,4 @@ public: QUrl url() const { return content().url(); } }; -REGISTER_EVENT_TYPE(RoomAvatarEvent) } // namespace Quotient diff --git a/lib/events/roomcanonicalaliasevent.h b/lib/events/roomcanonicalaliasevent.h index 60ca68ac..e1c7888e 100644 --- a/lib/events/roomcanonicalaliasevent.h +++ b/lib/events/roomcanonicalaliasevent.h @@ -34,7 +34,7 @@ inline auto toJson(const EventContent::AliasesEventContent& c) class QUOTIENT_API RoomCanonicalAliasEvent : public StateEvent { public: - DEFINE_EVENT_TYPEID("m.room.canonical_alias", RoomCanonicalAliasEvent) + QUO_EVENT(RoomCanonicalAliasEvent, "m.room.canonical_alias") explicit RoomCanonicalAliasEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) @@ -55,5 +55,4 @@ public: QString alias() const { return content().canonicalAlias; } QStringList altAliases() const { return content().altAliases; } }; -REGISTER_EVENT_TYPE(RoomCanonicalAliasEvent) } // namespace Quotient diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index 989030ac..f22752b4 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -9,7 +9,7 @@ namespace Quotient { class QUOTIENT_API RoomCreateEvent : public StateEventBase { public: - DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent) + QUO_EVENT(RoomCreateEvent, "m.room.create") explicit RoomCreateEvent(const QJsonObject& obj) : StateEventBase(typeId(), obj) @@ -26,5 +26,4 @@ public: bool isUpgrade() const; RoomType roomType() const; }; -REGISTER_EVENT_TYPE(RoomCreateEvent) } // namespace Quotient diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 9461340b..532e72e2 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -10,10 +10,11 @@ namespace Quotient { class RedactionEvent; -/** This class corresponds to m.room.* events */ +// That check could look into Event and find most stuff already deleted... +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) class QUOTIENT_API RoomEvent : public Event { public: - static inline EventFactory factory { "RoomEvent" }; + QUO_BASE_EVENT(RoomEvent, {}, Event::BaseMetaType) // RedactionEvent is an incomplete type here so we cannot inline // constructors and destructors and we cannot use 'using'. @@ -80,21 +81,14 @@ using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; using RoomEventsRange = Range; -template <> -inline EventPtr doLoadEvent(const QJsonObject& json, const QString& matrixType) -{ - if (matrixType == "m.room.encrypted") - return RoomEvent::factory.loadEvent(json, matrixType); - return Event::factory.loadEvent(json, matrixType); -} - class QUOTIENT_API CallEventBase : public RoomEvent { public: + QUO_BASE_EVENT(CallEventBase, "m.call.*"_ls, RoomEvent::BaseMetaType) + CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, int version, const QJsonObject& contentJson = {}); CallEventBase(Type type, const QJsonObject& json); ~CallEventBase() override = default; - bool isCallEvent() const override { return true; } QUO_CONTENT_GETTER(QString, callId) QUO_CONTENT_GETTER(int, version) diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 0dfdf383..6883a2a5 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -9,7 +9,7 @@ namespace Quotient { class QUOTIENT_API RoomKeyEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) + QUO_EVENT(RoomKeyEvent, "m.room_key") explicit RoomKeyEvent(const QJsonObject& obj); explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, @@ -23,5 +23,4 @@ public: return contentPart("session_key"_ls).toLatin1(); } }; -REGISTER_EVENT_TYPE(RoomKeyEvent) } // namespace Quotient diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index dd33ea6b..c690586e 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -31,7 +31,7 @@ using MembershipType [[deprecated("Use Membership instead")]] = Membership; class QUOTIENT_API RoomMemberEvent : public StateEvent { Q_GADGET public: - DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) + QUO_EVENT(RoomMemberEvent, "m.room.member") using MembershipType [[deprecated("Use Quotient::Membership instead")]] = Membership; @@ -79,14 +79,4 @@ public: bool isRename() const; bool isAvatarUpdate() const; }; - -template <> -inline event_ptr_tt -doLoadEvent(const QJsonObject& json, const QString& matrixType) -{ - if (matrixType == QLatin1String(RoomMemberEvent::matrixTypeId())) - return makeEvent(json); - return makeEvent(unknownEventTypeId(), json); -} -REGISTER_EVENT_TYPE(RoomMemberEvent) } // namespace Quotient diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 6968ad70..889fc4dc 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -20,7 +20,7 @@ namespace MessageEventContent = EventContent; // Back-compatibility class QUOTIENT_API RoomMessageEvent : public RoomEvent { Q_GADGET public: - DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent) + QUO_EVENT(RoomMessageEvent, "m.room.message") enum class MsgType { Text, @@ -94,7 +94,7 @@ private: Q_ENUM(MsgType) }; -REGISTER_EVENT_TYPE(RoomMessageEvent) + using MessageEventType = RoomMessageEvent::MsgType; namespace EventContent { diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h index a1638a27..7ac12db0 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -33,7 +33,7 @@ struct QUOTIENT_API PowerLevelsEventContent { class QUOTIENT_API RoomPowerLevelsEvent : public StateEvent { public: - DEFINE_EVENT_TYPEID("m.room.power_levels", RoomPowerLevelsEvent) + QUO_EVENT(RoomPowerLevelsEvent, "m.room.power_levels") explicit RoomPowerLevelsEvent(PowerLevelsEventContent&& content) : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content)) @@ -61,5 +61,4 @@ public: int powerLevelForState(const QString& eventId) const; int powerLevelForUser(const QString& userId) const; }; -REGISTER_EVENT_TYPE(RoomPowerLevelsEvent) } // namespace Quotient diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index 15d26923..97586587 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -8,7 +8,7 @@ namespace Quotient { class QUOTIENT_API RoomTombstoneEvent : public StateEventBase { public: - DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent) + QUO_EVENT(RoomTombstoneEvent, "m.room.tombstone") explicit RoomTombstoneEvent(const QJsonObject& obj) : StateEventBase(typeId(), obj) @@ -17,5 +17,4 @@ public: QString serverMessage() const; QString successorRoomId() const; }; -REGISTER_EVENT_TYPE(RoomTombstoneEvent) } // namespace Quotient diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index a8eaab56..c79d03b0 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -14,7 +14,7 @@ namespace Quotient { EventContent::SingleKeyValue<_ValueType, &_Name##Key>> { \ public: \ using value_type = _ValueType; \ - DEFINE_EVENT_TYPEID(_TypeId, _Name) \ + QUO_EVENT(_Name, _TypeId) \ template \ explicit _Name(T&& value) \ : StateEvent(TypeId, matrixTypeId(), QString(), \ @@ -25,7 +25,6 @@ namespace Quotient { {} \ auto _ContentKey() const { return content().value; } \ }; \ - REGISTER_EVENT_TYPE(_Name) \ // End of macro DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) @@ -38,7 +37,7 @@ class QUOTIENT_API RoomAliasesEvent : public StateEvent< EventContent::SingleKeyValue> { public: - DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent) + QUO_EVENT(RoomAliasesEvent, "m.room.aliases") explicit RoomAliasesEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 9f1d7118..74876803 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -9,7 +9,12 @@ namespace Quotient { class QUOTIENT_API StateEventBase : public RoomEvent { public: - static inline EventFactory factory { "StateEvent" }; + QUO_BASE_EVENT(StateEventBase, "json.contains('state_key')"_ls, + RoomEvent::BaseMetaType) + static bool isValid(const QJsonObject& fullJson) + { + return fullJson.contains(StateKeyKeyL); + } StateEventBase(Type type, const QJsonObject& json); StateEventBase(Type type, event_mtype_t matrixType, @@ -27,7 +32,6 @@ public: { ContentKey, contentJson } }; } - bool isStateEvent() const override { return true; } QString replacedState() const; void dumpTo(QDebug dbg) const override; @@ -44,28 +48,6 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, return StateEventBase::basicJson(matrixTypeId, stateKey, content); } -//! \brief Override RoomEvent factory with that from StateEventBase if JSON has -//! stateKey -//! -//! This means in particular that an event with a type known to RoomEvent but -//! having stateKey set (even to an empty value) will be treated as a state -//! event and most likely end up as unknown (consider, e.g., m.room.message -//! that has stateKey set). -template <> -inline RoomEventPtr doLoadEvent(const QJsonObject& json, - const QString& matrixType) -{ - if (json.contains(StateKeyKeyL)) - return StateEventBase::factory.loadEvent(json, matrixType); - return RoomEvent::factory.loadEvent(json, matrixType); -} - -template <> -inline bool is(const Event& e) -{ - return e.isStateEvent(); -} - /** * A combination of event type and state key uniquely identifies a piece * of state in Matrix. diff --git a/lib/events/stickerevent.h b/lib/events/stickerevent.h index e378422d..67905481 100644 --- a/lib/events/stickerevent.h +++ b/lib/events/stickerevent.h @@ -14,7 +14,7 @@ namespace Quotient { class QUOTIENT_API StickerEvent : public RoomEvent { public: - DEFINE_EVENT_TYPEID("m.sticker", StickerEvent) + QUO_EVENT(StickerEvent, "m.sticker") explicit StickerEvent(const QJsonObject& obj) : RoomEvent(TypeId, obj) @@ -45,5 +45,4 @@ public: private: EventContent::ImageContent m_imageContent; }; -REGISTER_EVENT_TYPE(StickerEvent) } // namespace Quotient -- cgit v1.2.3