From 789be1174bf1db8093387d2451a185a2a3470e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=BChl?= Date: Mon, 11 Aug 2025 16:59:16 +0200 Subject: [PATCH] removed namespace interface --- .../sql/interface/parameter_binder.hpp | 2 +- .../matador/sql/interface/statement_impl.hpp | 12 +- .../matador/sql/interface/statement_proxy.hpp | 12 +- include/matador/sql/statement_cache.hpp | 13 + include/matador/utils/message_bus.hpp | 261 +++++++++++++----- source/core/utils/message_bus.cpp | 7 +- source/orm/sql/connection.cpp | 4 +- source/orm/sql/interface/statement_impl.cpp | 4 +- source/orm/sql/interface/statement_proxy.cpp | 4 +- source/orm/sql/statement_cache.cpp | 20 +- test/orm/backend/test_statement.cpp | 5 +- test/orm/backend/test_statement.hpp | 4 +- 12 files changed, 247 insertions(+), 101 deletions(-) diff --git a/include/matador/sql/interface/parameter_binder.hpp b/include/matador/sql/interface/parameter_binder.hpp index a5f554e..2399574 100644 --- a/include/matador/sql/interface/parameter_binder.hpp +++ b/include/matador/sql/interface/parameter_binder.hpp @@ -3,7 +3,7 @@ #include "matador/utils/attribute_writer.hpp" -namespace matador::sql::interface { +namespace matador::sql { using parameter_binder = utils::attribute_writer; diff --git a/include/matador/sql/interface/statement_impl.hpp b/include/matador/sql/interface/statement_impl.hpp index 6cea4d9..8d9724f 100644 --- a/include/matador/sql/interface/statement_impl.hpp +++ b/include/matador/sql/interface/statement_impl.hpp @@ -22,23 +22,23 @@ protected: public: virtual ~statement_impl() = default; - virtual utils::result execute(const interface::parameter_binder& bindings) = 0; - virtual utils::result, utils::error> fetch(const interface::parameter_binder& bindings) = 0; + virtual utils::result execute(const parameter_binder& bindings) = 0; + virtual utils::result, utils::error> fetch(const parameter_binder& bindings) = 0; template < class Type > - void bind_object(Type &obj, interface::parameter_binder& bindings) { + void bind_object(Type &obj, parameter_binder& bindings) { object_parameter_binder object_binder_; object_binder_.reset(start_index()); object_binder_.bind(obj, bindings); } template < class Type > - void bind(const size_t pos, Type &val, interface::parameter_binder& bindings) { + void bind(const size_t pos, Type &val, parameter_binder& bindings) { utils::data_type_traits::bind_value(bindings, adjust_index(pos), val); } - void bind(size_t pos, const char *value, size_t size, interface::parameter_binder& bindings) const; - void bind(size_t pos, std::string &val, size_t size, interface::parameter_binder& bindings) const; + void bind(size_t pos, const char *value, size_t size, parameter_binder& bindings) const; + void bind(size_t pos, std::string &val, size_t size, parameter_binder& bindings) const; virtual void reset() = 0; diff --git a/include/matador/sql/interface/statement_proxy.hpp b/include/matador/sql/interface/statement_proxy.hpp index d9fe77f..d5d3601 100644 --- a/include/matador/sql/interface/statement_proxy.hpp +++ b/include/matador/sql/interface/statement_proxy.hpp @@ -12,19 +12,19 @@ protected: public: virtual ~statement_proxy() = default; - virtual utils::result execute(interface::parameter_binder& bindings) = 0; - virtual utils::result, utils::error> fetch(interface::parameter_binder& bindings) = 0; + virtual utils::result execute(parameter_binder& bindings) = 0; + virtual utils::result, utils::error> fetch(parameter_binder& bindings) = 0; template - void bind(const Type &obj, interface::parameter_binder& bindings) { + void bind(const Type &obj, parameter_binder& bindings) { statement_->bind_object(obj, bindings); } template - void bind(size_t pos, Type &value, interface::parameter_binder& bindings) { + void bind(size_t pos, Type &value, parameter_binder& bindings) { statement_->bind(pos, value, bindings); } - void bind(size_t pos, const char *value, size_t size, interface::parameter_binder& bindings) const; - void bind(size_t pos, std::string &val, size_t size, interface::parameter_binder& bindings) const; + void bind(size_t pos, const char *value, size_t size, parameter_binder& bindings) const; + void bind(size_t pos, std::string &val, size_t size, parameter_binder& bindings) const; void reset() const; diff --git a/include/matador/sql/statement_cache.hpp b/include/matador/sql/statement_cache.hpp index 3f7da79..4e804b4 100644 --- a/include/matador/sql/statement_cache.hpp +++ b/include/matador/sql/statement_cache.hpp @@ -23,6 +23,19 @@ struct statement_accessed_event : statement_event {}; struct statement_added_event : statement_event {}; struct statement_evicted_event : statement_event {}; +struct statement_lock_failed_event : statement_event { + std::chrono::steady_clock::time_point lock_attempt_start; +}; + +struct statement_lock_acquired_event : statement_event { + std::chrono::steady_clock::time_point lock_attempt_start; + std::chrono::steady_clock::time_point lock_attempt_end; +}; + +struct statement_execution_event : statement_event { + std::chrono::steady_clock::time_point execution_start; + std::chrono::steady_clock::time_point execution_end; +}; struct statement_cache_config { size_t max_size; diff --git a/include/matador/utils/message_bus.hpp b/include/matador/utils/message_bus.hpp index a972e8a..e1b43ee 100644 --- a/include/matador/utils/message_bus.hpp +++ b/include/matador/utils/message_bus.hpp @@ -14,60 +14,109 @@ #include namespace matador::utils { -class message_bus; // forward +class message_bus; +/** + * @brief Represents a generic, type-safe message object used for communication. + */ class message { public: + /** + * @brief Default constructor for an empty message. + */ message(); - // Owning: copy into an owned shared_ptr (may allocate) - template - explicit message(const T& value) { - auto strong = std::make_shared(value); // allocate + /** + * @brief Constructs an owning message from a value. + * + * @tparam MessageType The type of the message. + * @param msg The message to be stored. A copy is made. + */ + template + explicit message(const MessageType& msg) { + auto strong = std::make_shared(msg); // allocate owner_ = std::static_pointer_cast(strong); // share ownership as void* ptr_ = static_cast(strong.get()); - type_ = std::type_index(typeid(T)); + type_ = std::type_index(typeid(MessageType)); } - // Non-owning: zero-copy reference to an existing object (no allocation) - // Caller must guarantee lifetime of `ref` for the usage duration. - template - static message from_ref(const T& ref) { + /** + * @brief Constructs a non-owning message from a reference. + * + * @tparam MessageType The type of the reference. + * @param msg A reference to the message data. The caller must ensure its lifetime during use. + * @return A non-owning message wrapping the reference. + */ + template + static message from_ref(const MessageType& msg) { message m; - m.ptr_ = static_cast(std::addressof(ref)); - m.type_ = std::type_index(typeid(T)); + m.ptr_ = static_cast(std::addressof(msg)); + m.type_ = std::type_index(typeid(MessageType)); // owner_ remains null => non-owning return m; } - // Owning from shared_ptr (no extra copy; shares ownership) - template - static message from_shared(std::shared_ptr sp) { + /** + * @brief Constructs an owning message from a shared pointer. + * + * @tparam MessageType The type of the object managed by the shared pointer. + * @param msg A shared pointer to the message. + * @return A message that shares ownership of the object. + */ + template + static message from_shared(std::shared_ptr msg) { message m; - m.owner_ = std::static_pointer_cast(sp); - m.ptr_ = static_cast(sp.get()); - m.type_ = std::type_index(typeid(T)); + m.owner_ = std::static_pointer_cast(msg); + m.ptr_ = static_cast(msg.get()); + m.type_ = std::type_index(typeid(MessageType)); return m; } + /** + * @brief Checks if the message is empty. + * + * @return True if the message is empty, false otherwise. + */ [[nodiscard]] bool empty() const; + + /** + * @brief Gets the type of the object stored in the message. + * + * @return The type of the stored object as a std::type_index. + */ [[nodiscard]] std::type_index type() const; - template - [[nodiscard]] bool is() const { return type_ == std::type_index(typeid(T)); } + /** + * @brief Checks if the stored object is of the specified type. + * + * @tparam MessageType The type to check. + * @return True if the object matches the type, false otherwise. + */ + template + [[nodiscard]] bool is() const { return type_ == std::type_index(typeid(MessageType)); } - // Access as typed reference. Works for both owning and non-owning. - // Throws std::bad_cast if type mismatch. - template - const T& get() const { - if (!is()) throw std::bad_cast(); - const void* p = msg_ptr(); + /** + * @brief Accesses the stored object as a typed reference. + * + * @tparam MessageType The expected type of the object. + * @return A constant reference to the stored object. + * @throws std::bad_cast if the type does not match. + * @throws std::runtime_error if the message is empty. + */ + template + const MessageType& get() const { + if (!is()) throw std::bad_cast(); + const void* p = raw_ptr(); if (!p) throw std::runtime_error("AnyMessage: empty pointer"); - return *static_cast(p); + return *static_cast(p); } - // Return the raw const void* pointer to the stored object (may be non-owning) - [[nodiscard]] const void* msg_ptr() const; + /** + * @brief Retrieves the raw pointer to the stored object. + * + * @return A const void* pointing to the stored object (may be non-owning). + */ + [[nodiscard]] const void* raw_ptr() const; private: const void* ptr_; // non-owning raw pointer (if set) @@ -75,22 +124,46 @@ private: std::type_index type_{typeid(void)}; }; -// Forward declare Subscription so MessageBus can return it. +/** + * @brief Represents a subscription to a message_bus, providing RAII-style unsubscription. + */ class subscription { public: + /** + * @brief Default constructor for an empty subscription. + */ subscription() = default; + /** + * @brief Constructs a subscription for a specific handler and message type. + * + * @param bus Pointer to the message bus managing the subscription. + * @param type The type_index of the message type. + * @param id The unique ID of the handler. + */ subscription(message_bus* bus, std::type_index type, uint64_t id); - // Move-only (non-copyable) + // Prohibit copying of subscription objects. subscription(const subscription&) = delete; subscription& operator=(const subscription&) = delete; + // Allow move semantics for subscription objects. subscription(subscription&& other) noexcept; subscription& operator=(subscription&& other) noexcept; + /** + * @brief Destructor that ensures cleanup by unsubscribing from the bus. + */ ~subscription(); + /** + * @brief Unsubscribes the handler from the message bus. + */ void unsubscribe(); + /** + * @brief Checks if the subscription is still valid. + * + * @return True if the subscription is valid, false otherwise. + */ [[nodiscard]] bool valid() const; private: @@ -99,90 +172,139 @@ private: uint64_t id_ = 0; }; -// === MessageBus: runtime-registered, thread-safe === +/** + * @brief A thread-safe message bus for runtime message publishing and subscription handling. + */ class message_bus { public: - using HandlerId = uint64_t; + using HandlerId = uint64_t; /**< Type alias for handler IDs. */ - message_bus() : next_id_{1} {} + /** + * @brief Constructs a new message_bus object. + */ + message_bus(); - // Subscribe with a free function or lambda - template - subscription subscribe(std::function handler, - std::function filter = nullptr) + /** + * @brief Subscribes to messages of a specific type using a free function or lambda. + * + * @tparam MessageType The type of the message to subscribe to. + * @param handler The function to execute when a message of type T is published. + * @param filter An optional filter function to determine if the handler should be invoked. + * @return A subscription object to manage the handler's registration. + */ + template + subscription subscribe(std::function handler, + std::function filter = nullptr) { auto id = next_id_.fetch_add(1, std::memory_order_relaxed); std::unique_lock writeLock(mutex_); - auto &vec = handlers_[std::type_index(typeid(T))]; + auto &vec = handlers_[std::type_index(typeid(MessageType))]; Entry e; e.id = id; e.handler = [h = std::move(handler)](const void* p) { - h(*static_cast(p)); + h(*static_cast(p)); }; if (filter) { e.filter = [f = std::move(filter)](const void* p) -> bool { - return f(*static_cast(p)); + return f(*static_cast(p)); }; } else { e.filter = nullptr; } vec.emplace_back(std::move(e)); - return {this, std::type_index(typeid(T)), id}; + return {this, std::type_index(typeid(MessageType)), id}; } - // Subscribe raw pointer instance; caller ensures lifetime - template - subscription subscribe(C* instance, void (C::*memberFn)(const T&), - std::function filter = nullptr) + /** + * @brief Subscribes to messages using a class member function bound to an instance. + * + * @tparam MessageType The type of the message to subscribe to. + * @tparam CallerClass The class of the instance. + * @param instance A pointer to the instance. + * @param memberFn A pointer to the member function to execute. + * @param filter An optional filter function. + * @return A subscription object to manage the handler's registration. + */ + template + subscription subscribe(CallerClass* instance, void (CallerClass::*memberFn)(const MessageType&), + std::function filter = nullptr) { - auto fn = [instance, memberFn](const T& m) { (instance->*memberFn)(m); }; - return subscribe(std::function(fn), std::move(filter)); + auto fn = [instance, memberFn](const MessageType& m) { (instance->*memberFn)(m); }; + return subscribe(std::function(fn), std::move(filter)); } - // Subscribe shared_ptr instance (safe) - template - subscription subscribe(std::shared_ptr instance, void (C::*memberFn)(const T&), - std::function filter = nullptr) + /** + * @brief Subscribes to messages using a member function bound to a shared_ptr instance. + * + * @tparam MessageType The type of the message to subscribe to. + * @tparam CallerClass The class of the shared_ptr instance. + * @param instance A shared pointer to the instance. + * @param memberFn A pointer to the member function to execute. + * @param filter An optional filter function. + * @return A subscription object to manage the handler's registration. + */ + template + subscription subscribe(std::shared_ptr instance, void (CallerClass::*memberFn)(const MessageType&), + std::function filter = nullptr) { - std::weak_ptr w = instance; - auto handler = [w, memberFn](const T& m) { + std::weak_ptr w = instance; + auto handler = [w, memberFn](const MessageType& m) { if (auto s = w.lock()) { (s.get()->*memberFn)(m); } }; - std::function local_filter = nullptr; + std::function local_filter = nullptr; if (filter) { - local_filter = [w, filter = std::move(filter)](const T& m) -> bool { + local_filter = [w, filter = std::move(filter)](const MessageType& m) -> bool { if (w.expired()) { return false; } return filter(m); }; } - return subscribe(std::move(handler), std::move(local_filter)); + return subscribe(std::move(handler), std::move(local_filter)); } - // Unsubscribe by type-specific id (RAII calls this) - template + /** + * @brief Unsubscribes a handler from the bus using its type and ID. + * + * @tparam MessageType The type of the message. + * @param id The unique handler ID to unsubscribe. + */ + template void unsubscribe(const HandlerId id) { - unsubscribe(std::type_index(typeid(T)), id); + unsubscribe(std::type_index(typeid(MessageType)), id); } - // Unsubscribe by explicit type_index and id + /** + * @brief Unsubscribes a handler by its type index and ID. + * + * @param type The type_index of the message type. + * @param id The unique handler ID to unsubscribe. + */ void unsubscribe(std::type_index type, HandlerId id); - // Unsubscribe by id regardless of type (O(#types) scan) + /** + * @brief Unsubscribes a handler by its unique ID, regardless of type. + * + * @param id The unique handler ID to unsubscribe. + */ void unsubscribe(HandlerId id); - // Publish a typed message (fast) - template - void publish(const T& msg) const { + /** + * @brief Publishes a message of a specific type. + * + * @tparam MessageType The type of the message to publish. + * @param msg The message to publish. + */ + template + void publish(const MessageType& msg) const { std::vector snapshot; { std::shared_lock readLock(mutex_); - const auto it = handlers_.find(std::type_index(typeid(T))); + const auto it = handlers_.find(std::type_index(typeid(MessageType))); if (it == handlers_.end()) { return; } @@ -193,7 +315,11 @@ public: } } - // Publish using AnyMessage (zero-copy if AnyMessage is non-owning) + /** + * @brief Publishes a generic message object. + * + * @param msg The message to publish. + */ void publish(const message& msg) const; private: @@ -210,7 +336,6 @@ private: std::atomic next_id_; }; -// RAII unsubscribe implementation inline void subscription::unsubscribe() { if (bus_) { bus_->unsubscribe(type_, id_); diff --git a/source/core/utils/message_bus.cpp b/source/core/utils/message_bus.cpp index e01351c..13c7bc3 100644 --- a/source/core/utils/message_bus.cpp +++ b/source/core/utils/message_bus.cpp @@ -14,7 +14,7 @@ std::type_index message::type() const { return type_; } -const void * message::msg_ptr() const { +const void * message::raw_ptr() const { return owner_ ? owner_.get() : ptr_; } @@ -49,6 +49,9 @@ bool subscription::valid() const { return bus_ != nullptr; } +message_bus::message_bus() +: next_id_{1} {} + void message_bus::unsubscribe(const std::type_index type, HandlerId id) { std::unique_lock writeLock(mutex_); const auto it = handlers_.find(type); @@ -89,7 +92,7 @@ void message_bus::publish(const message &msg) const { } snapshot = it->second; } - const void* p = msg.msg_ptr(); + const void* p = msg.raw_ptr(); for (const auto &e : snapshot) { if (!e.filter || e.filter(p)) e.handler(p); } diff --git a/source/orm/sql/connection.cpp b/source/orm/sql/connection.cpp index 16be520..b9ce757 100644 --- a/source/orm/sql/connection.cpp +++ b/source/orm/sql/connection.cpp @@ -19,10 +19,10 @@ public: explicit connection_statement_proxy(std::unique_ptr&& stmt) : statement_proxy(std::move(stmt)) {} - utils::result execute(interface::parameter_binder& bindings) override { + utils::result execute(parameter_binder& bindings) override { return statement_->execute(bindings); } - utils::result, utils::error> fetch(interface::parameter_binder& bindings) override { + utils::result, utils::error> fetch(parameter_binder& bindings) override { return statement_->fetch(bindings); } }; diff --git a/source/orm/sql/interface/statement_impl.cpp b/source/orm/sql/interface/statement_impl.cpp index 6cd9c20..ba31d01 100644 --- a/source/orm/sql/interface/statement_impl.cpp +++ b/source/orm/sql/interface/statement_impl.cpp @@ -6,11 +6,11 @@ statement_impl::statement_impl(query_context query) : query_(std::move(query)) {} -void statement_impl::bind(const size_t pos, const char *value, const size_t size, interface::parameter_binder& bindings) const { +void statement_impl::bind(const size_t pos, const char *value, const size_t size, parameter_binder& bindings) const { utils::data_type_traits::bind_value(bindings, adjust_index(pos), value, size); } -void statement_impl::bind(const size_t pos, std::string &val, const size_t size, interface::parameter_binder& bindings) const { +void statement_impl::bind(const size_t pos, std::string &val, const size_t size, parameter_binder& bindings) const { utils::data_type_traits::bind_value(bindings, adjust_index(pos), val, size); } diff --git a/source/orm/sql/interface/statement_proxy.cpp b/source/orm/sql/interface/statement_proxy.cpp index ce49980..429dc00 100644 --- a/source/orm/sql/interface/statement_proxy.cpp +++ b/source/orm/sql/interface/statement_proxy.cpp @@ -4,10 +4,10 @@ namespace matador::sql { statement_proxy::statement_proxy(std::unique_ptr&& stmt) : statement_(std::move(stmt)){} -void statement_proxy::bind(const size_t pos, const char* value, const size_t size, interface::parameter_binder& bindings) const { +void statement_proxy::bind(const size_t pos, const char* value, const size_t size, parameter_binder& bindings) const { statement_->bind(pos, value, size, bindings); } -void statement_proxy::bind(const size_t pos, std::string& val, const size_t size, interface::parameter_binder& bindings) const { +void statement_proxy::bind(const size_t pos, std::string& val, const size_t size, parameter_binder& bindings) const { statement_->bind(pos, val, size, bindings); } diff --git a/source/orm/sql/statement_cache.cpp b/source/orm/sql/statement_cache.cpp index f319598..1ac419d 100644 --- a/source/orm/sql/statement_cache.cpp +++ b/source/orm/sql/statement_cache.cpp @@ -24,24 +24,26 @@ public: size_t lock_attempts{0}; }; - explicit statement_cache_proxy(std::unique_ptr&& stmt, connection_pool &pool, const size_t connection_id) + statement_cache_proxy(utils::message_bus &bus, std::unique_ptr&& stmt, connection_pool &pool, const size_t connection_id) : statement_proxy(std::move(stmt)) , pool_(pool) - , connection_id_(connection_id) {} + , connection_id_(connection_id) + , bus_(bus) {} - utils::result execute(interface::parameter_binder& bindings) override { + utils::result execute(parameter_binder& bindings) override { execution_metrics metrics{std::chrono::steady_clock::now()}; auto result = try_with_retry([this, &bindings, &metrics]() -> utils::result { if (!try_lock()) { ++metrics.lock_attempts; + bus_.publish({sql(), std::chrono::steady_clock::now(), metrics.lock_attempt_start}); return utils::failure(utils::error{ error_code::STATEMENT_LOCKED, "Failed to execute statement because it is already in use" }); } metrics.lock_acquired = std::chrono::steady_clock::now(); - // publish + bus_.publish({sql(), std::chrono::steady_clock::now(), metrics.lock_attempt_start, metrics.lock_acquired}); auto guard = statement_guard(*this); if (const auto conn = pool_.acquire(connection_id_); !conn.valid()) { @@ -55,14 +57,15 @@ public: auto execution_result = statement_->execute(bindings); metrics.execution_end = std::chrono::steady_clock::now(); - // publish + bus_.publish({sql(), std::chrono::steady_clock::now(), metrics.execution_start, metrics.execution_end}); + return execution_result; }); return result; } - utils::result, utils::error> fetch(interface::parameter_binder& bindings) override { + utils::result, utils::error> fetch(parameter_binder& bindings) override { execution_metrics metrics{std::chrono::steady_clock::now()}; auto result = try_with_retry([this, &bindings, &metrics]() -> utils::result, utils::error> { @@ -104,7 +107,7 @@ protected: if (attempt + 1 < config_.max_attempts) { std::this_thread::sleep_for(current_wait); - current_wait = std::min(current_wait * 2, config_.max_wait); + current_wait = (std::min)(current_wait * 2, config_.max_wait); } } @@ -132,6 +135,7 @@ private: connection_pool &pool_; size_t connection_id_{}; retry_config config_{}; + utils::message_bus &bus_; }; } @@ -178,7 +182,7 @@ utils::result statement_cache::acquire(const query_cont const auto it = cache_map_.insert({ key, {statement{ - std::make_shared(std::move(stmt), pool_, id)}, + std::make_shared(bus_, std::move(stmt), pool_, id)}, std::chrono::steady_clock::now(), usage_list_.begin()} }).first; diff --git a/test/orm/backend/test_statement.cpp b/test/orm/backend/test_statement.cpp index 645a3f9..afff4c4 100644 --- a/test/orm/backend/test_statement.cpp +++ b/test/orm/backend/test_statement.cpp @@ -2,6 +2,7 @@ #include "test_result_reader.hpp" #include "test_parameter_binder.hpp" +#include #include #include @@ -9,7 +10,7 @@ namespace matador::test::orm { test_statement::test_statement(const sql::query_context &query) : statement_impl(query) {} -utils::result test_statement::execute(const sql::interface::parameter_binder &/*bindings*/) { +utils::result test_statement::execute(const sql::parameter_binder &/*bindings*/) { using namespace std::chrono_literals; std::mt19937 rng(query_.sql.size()); std::uniform_int_distribution dist(10, 40); @@ -17,7 +18,7 @@ utils::result test_statement::execute(const sql::interface return utils::ok(static_cast(8)); } -utils::result, utils::error> test_statement::fetch(const sql::interface::parameter_binder &/*bindings*/) { +utils::result, utils::error> test_statement::fetch(const sql::parameter_binder &/*bindings*/) { return utils::ok(std::make_unique(std::make_unique(), query_.prototype, query_.prototype.size())); } diff --git a/test/orm/backend/test_statement.hpp b/test/orm/backend/test_statement.hpp index c892e18..50dd818 100644 --- a/test/orm/backend/test_statement.hpp +++ b/test/orm/backend/test_statement.hpp @@ -8,8 +8,8 @@ namespace matador::test::orm { class test_statement final : public sql::statement_impl { public: explicit test_statement(const sql::query_context &query); - utils::result execute(const sql::interface::parameter_binder &bindings) override; - utils::result, utils::error> fetch(const sql::interface::parameter_binder &bindings) override; + utils::result execute(const sql::parameter_binder &bindings) override; + utils::result, utils::error> fetch(const sql::parameter_binder &bindings) override; void reset() override; protected: