From c1a8f2d0f0214e3a57ae1a77d60f02cf52fb7b75 Mon Sep 17 00:00:00 2001 From: Dmitriy Shilin Date: Fri, 25 Sep 2026 17:47:11 +0200 Subject: [PATCH] Introduce functional value monitor --- components/ocs_core/func_value_monitor.h | 50 +++++++++++ components/ocs_core/test/CMakeLists.txt | 1 + .../ocs_core/test/test_func_value_monitor.cpp | 88 +++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 components/ocs_core/func_value_monitor.h create mode 100644 components/ocs_core/test/test_func_value_monitor.cpp diff --git a/components/ocs_core/func_value_monitor.h b/components/ocs_core/func_value_monitor.h new file mode 100644 index 00000000..cd420c1d --- /dev/null +++ b/components/ocs_core/func_value_monitor.h @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2026 Tendry Lab + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "ocs_core/ivalue_monitor.h" +#include "ocs_core/noncopyable.h" +#include "ocs_status/code.h" + +namespace ocs { +namespace core { + +//! Value monitor that delegates the update to the provided function. +template +class FuncValueMonitor : public IValueMonitor, private NonCopyable<> { +public: + //! Function to update the underlying monitor. + using Func = std::function; + + //! Initialize. + //! + //! @params + //! - @p monitor to get the latest sampled value. + //! - @p func to update the @p monitor, e.g. asynchronously on a specific task. + FuncValueMonitor(IValueMonitor& monitor, Func func) + : monitor_(monitor) + , func_(func) { + } + + //! Get latest sampled value from the underlying monitor. + T get() const override { + return monitor_.get(); + } + + //! Call the function to update the underlying monitor. + status::StatusCode update() override { + return func_(); + } + +private: + IValueMonitor& monitor_; + Func func_; +}; + +} // namespace core +} // namespace ocs diff --git a/components/ocs_core/test/CMakeLists.txt b/components/ocs_core/test/CMakeLists.txt index 31e18871..cc883484 100644 --- a/components/ocs_core/test/CMakeLists.txt +++ b/components/ocs_core/test/CMakeLists.txt @@ -9,6 +9,7 @@ idf_component_register( "test_byte_writer.cpp" "test_byte_reader.cpp" "test_byte_transceiver.cpp" + "test_func_value_monitor.cpp" REQUIRES "unity" diff --git a/components/ocs_core/test/test_func_value_monitor.cpp b/components/ocs_core/test/test_func_value_monitor.cpp new file mode 100644 index 00000000..2ea2386a --- /dev/null +++ b/components/ocs_core/test/test_func_value_monitor.cpp @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2026 Tendry Lab + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" + +#include "ocs_core/func_value_monitor.h" +#include "ocs_core/noncopyable.h" + +namespace ocs { +namespace core { + +namespace { + +class TestValueMonitor : public IValueMonitor, private NonCopyable<> { +public: + int get() const override { + return value; + } + + status::StatusCode update() override { + ++update_call_count; + + return status::StatusCode::OK; + } + + int value { 0 }; + size_t update_call_count { 0 }; +}; + +} // namespace + +TEST_CASE("Func value monitor: get value from the underlying monitor", + "[func_value_monitor], [ocs_core]") { + TestValueMonitor monitor; + size_t func_call_count = 0; + + FuncValueMonitor func_monitor(monitor, [&func_call_count]() { + ++func_call_count; + + return status::StatusCode::OK; + }); + + monitor.value = 42; + TEST_ASSERT_EQUAL(42, func_monitor.get()); + + monitor.value = -1; + TEST_ASSERT_EQUAL(-1, func_monitor.get()); + + TEST_ASSERT_EQUAL(0, func_call_count); + TEST_ASSERT_EQUAL(0, monitor.update_call_count); +} + +TEST_CASE("Func value monitor: update calls the function", + "[func_value_monitor], [ocs_core]") { + TestValueMonitor monitor; + size_t func_call_count = 0; + + FuncValueMonitor func_monitor(monitor, [&func_call_count]() { + ++func_call_count; + + return status::StatusCode::OK; + }); + + TEST_ASSERT_EQUAL(status::StatusCode::OK, func_monitor.update()); + TEST_ASSERT_EQUAL(1, func_call_count); + + TEST_ASSERT_EQUAL(status::StatusCode::OK, func_monitor.update()); + TEST_ASSERT_EQUAL(2, func_call_count); + + // The underlying monitor is only updated by the function. + TEST_ASSERT_EQUAL(0, monitor.update_call_count); +} + +TEST_CASE("Func value monitor: update propagates the function failure", + "[func_value_monitor], [ocs_core]") { + TestValueMonitor monitor; + + FuncValueMonitor func_monitor(monitor, []() { + return status::StatusCode::Error; + }); + + TEST_ASSERT_EQUAL(status::StatusCode::Error, func_monitor.update()); +} + +} // namespace core +} // namespace ocs