diff --git a/library.json b/library.json index 6178106e..5e2a1b97 100644 --- a/library.json +++ b/library.json @@ -20,7 +20,7 @@ "dependencies": [ { "name": "RB3201-RBProtocol", - "version": "https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v12.0.0.zip" + "version": "https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v13.0.0.zip" } ], "build": { diff --git a/platformio.ini b/platformio.ini index 2b12d4b6..09f3d6e8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -16,4 +16,4 @@ framework = arduino upload_speed = 921600 monitor_speed = 115200 -lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v12.0.0.zip +lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v13.0.0.zip diff --git a/src/builder/widget.h b/src/builder/widget.h index cacac963..d6045f27 100644 --- a/src/builder/widget.h +++ b/src/builder/widget.h @@ -50,15 +50,8 @@ class BuilderMixin { protected: void addCallback(const std::string& name, callback_t cb) { - auto& all = self().m_state.callbacks(); - - auto old = all.find(name); - if (old != all.end()) { - delete static_cast(old->second); - } - - auto* cbHeap = new callback_t(cb); - all[name] = static_cast(cbHeap); // fuj + auto* cbHeap = static_cast(new callback_t(cb)); // fuj + self().m_state.addCallback(&callbackTrampoline, &callbackDeleter, name, cbHeap); } private: @@ -69,6 +62,11 @@ class BuilderMixin { Constructed w(state); (*static_cast(cb))(w); } + + static void callbackDeleter(void *cb) { + auto *cb_typed = static_cast(cb); + delete cb_typed; + } }; class Widget { diff --git a/src/gridui.cpp b/src/gridui.cpp index 923e4187..01de3c07 100644 --- a/src/gridui.cpp +++ b/src/gridui.cpp @@ -21,6 +21,9 @@ static void defaultOnPacketReceived(const std::string& cmd, rbjson::Object* pkt) _GridUi::_GridUi() : m_protocol(nullptr) + , m_protocol_ours(false) + , m_update_timer(nullptr) + , m_web_server_task(nullptr) , m_state_mustarrive_id(UINT32_MAX) , m_states_modified(false) { } @@ -36,6 +39,7 @@ void _GridUi::begin(rb::Protocol* protocol, int cols, int rows, bool enableSplit } m_protocol = protocol; + m_protocol_ours = false; m_layout.reset(new rbjson::Object); m_layout->set("cols", cols); @@ -44,15 +48,18 @@ void _GridUi::begin(rb::Protocol* protocol, int cols, int rows, bool enableSplit } rb::Protocol* _GridUi::begin(const char* owner, const char* deviceName) { - auto protocol = new rb::Protocol(owner, deviceName, "Compiled at " __DATE__ " " __TIME__, defaultOnPacketReceived); + // Start serving the web page + m_web_server_task = rb_web_start(80); + if(m_web_server_task == NULL) { + ESP_LOGE("GridUI", "failed to call rb_web_start"); + return nullptr; + } + auto protocol = new rb::Protocol(owner, deviceName, "Compiled at " __DATE__ " " __TIME__, defaultOnPacketReceived); protocol->start(); - // Start serving the web page - rb_web_start(80); - begin(protocol); - + m_protocol_ours = true; return protocol; } @@ -79,6 +86,45 @@ uint16_t _GridUi::generateUuidLocked() const { } } +void _GridUi::end() { + auto *protocol = m_protocol.load(); + if(!protocol) { + ESP_LOGE("GridUI", "end() called when not initialized!"); + return; + } + + if(m_protocol_ours) { + protocol->stop(); + delete protocol; + } + m_protocol = nullptr; + + if(m_update_timer) { + esp_timer_stop(m_update_timer); + esp_timer_delete(m_update_timer); + m_update_timer = nullptr; + } + + if(m_web_server_task) { + rb_web_stop(m_web_server_task); + m_web_server_task = nullptr; + } + + { + std::lock_guard guard(m_states_mu); + m_states.clear(); + m_states.shrink_to_fit(); + m_widgets.clear(); + m_widgets.shrink_to_fit(); + } + + m_layout.reset(); + m_state_mustarrive_id = 0; + m_states_modified = false; + m_tab_changed = false; + m_tab = 0; +} + void _GridUi::commit() { std::lock_guard l(m_states_mu); if (!m_layout) { @@ -124,9 +170,9 @@ void _GridUi::commit() { .skip_unhandled_events = false, #endif }; - esp_timer_handle_t timer; - esp_timer_create(&args, &timer); - esp_timer_start_periodic(timer, 50 * 1000); + + esp_timer_create(&args, &m_update_timer); + esp_timer_start_periodic(m_update_timer, 50 * 1000); } bool _GridUi::handleRbPacket(const std::string& cmd, rbjson::Object* pkt) { @@ -181,7 +227,7 @@ void _GridUi::stateChangeTask(void* selfVoid) { if (self->m_states_modified.exchange(false)) { std::unique_ptr pkt(new rbjson::Object); { - std::lock_guard(self->m_states_mu); + std::lock_guard guard(self->m_states_mu); char buf[6]; std::unique_ptr state(new rbjson::Object); diff --git a/src/gridui.h b/src/gridui.h index 1d3fdf88..323cad23 100644 --- a/src/gridui.h +++ b/src/gridui.h @@ -7,6 +7,8 @@ #include #include +#include "esp_timer.h" + #include "builder/arm.h" #include "builder/bar.h" #include "builder/button.h" @@ -68,6 +70,9 @@ class _GridUi { void commit(); + // Deinitializes GridUi. Frees the protocol, if it was created by GridUI and not by the app. + void end(); + bool handleRbPacket(const std::string& command, rbjson::Object* pkt); rb::Protocol* protocol() const { return m_protocol.load(); } @@ -148,8 +153,8 @@ class _GridUi { if (!checkUuidFreeLocked(uuid)) uuid = generateUuidLocked(); - auto* state = new WidgetState(uuid, x, y, w, h, tab, &T::callbackTrampoline); - m_states.push_back(std::unique_ptr(state)); + auto* state = new WidgetState(uuid, x, y, w, h, tab); + m_states.emplace_back(std::unique_ptr(state)); auto* widget = new T(T::name(), *state); m_widgets.push_back(std::unique_ptr(widget)); @@ -181,7 +186,11 @@ class _GridUi { std::vector> m_states; std::atomic m_protocol; + bool m_protocol_ours; + std::unique_ptr m_layout; + esp_timer_handle_t m_update_timer; + TaskHandle_t m_web_server_task; mutable std::mutex m_states_mu; uint32_t m_state_mustarrive_id; diff --git a/src/widgets/widget.cpp b/src/widgets/widget.cpp index 7deb8c89..74f3de0c 100644 --- a/src/widgets/widget.cpp +++ b/src/widgets/widget.cpp @@ -6,11 +6,12 @@ namespace gridui { -WidgetState Widget::emptyState(0, 0, 0, 0, 0, 0, [](void* cb, WidgetState* state) {}); +std::mutex WidgetState::m_mutex; -WidgetState::WidgetState(uint16_t uuid, float x, float y, float w, float h, uint16_t tab, cb_trampoline_t cb_trampoline) - : m_cb_trampoline(cb_trampoline) - , m_uuid(uuid) +WidgetState Widget::emptyState(0, 0, 0, 0, 0, 0); + +WidgetState::WidgetState(uint16_t uuid, float x, float y, float w, float h, uint16_t tab) + : m_uuid(uuid) , m_bloom_global(0) , m_bloom_tick(0) { diff --git a/src/widgets/widget.h b/src/widgets/widget.h index fe6d19df..3ff37076 100644 --- a/src/widgets/widget.h +++ b/src/widgets/widget.h @@ -18,6 +18,48 @@ template class BuilderMixin; }; +class WidgetState; + +class CallbacksHolder { + friend class WidgetState; +public: + typedef void (*cb_trampoline_t)(void*, WidgetState*); + typedef void (*cb_deleter_t)(void*); + + ~CallbacksHolder() { + for(const auto& itr : m_callbacks) { + m_cb_deleter(itr.second); + } + } + + void call(WidgetState* state, const std::string& event) { + auto itr = m_callbacks.find(event); + if (itr != m_callbacks.end()) { + (*m_cb_trampoline)(itr->second, state); + } + } + + void add(const std::string& event, void *cb) { + auto itr = m_callbacks.find(event); + if(itr != m_callbacks.end()) { + m_cb_deleter(cb); + itr->second = cb; + } else { + m_callbacks[event] = cb; + } + } + +private: + CallbacksHolder(cb_trampoline_t trampoline, cb_deleter_t deleter) : m_cb_trampoline(trampoline), m_cb_deleter(deleter) { } + + CallbacksHolder(const WidgetState&) = delete; + CallbacksHolder& operator=(const WidgetState&) = delete; + + std::map m_callbacks; + const cb_trampoline_t m_cb_trampoline; + const cb_deleter_t m_cb_deleter; +}; + /** * @defgroup widgets_constructed Layout widgets * Classes in this module are used to modify state of the already constructed Layout. @@ -30,10 +72,8 @@ class WidgetState { template friend class builder::BuilderMixin; - typedef void (*cb_trampoline_t)(void*, WidgetState*); - public: - WidgetState(uint16_t uuid, float x, float y, float w, float h, uint16_t tab, cb_trampoline_t cb_trampoline); + WidgetState(uint16_t uuid, float x, float y, float w, float h, uint16_t tab); uint16_t uuid() const { return m_uuid; } const rbjson::Object& data() const { return m_data; } @@ -45,6 +85,9 @@ class WidgetState { void markChanged(const std::string& key); private: + // Each mutex is ~100 bytes of heap allocation. Let's keep just one for this. + static std::mutex m_mutex; + WidgetState(const WidgetState&) = delete; WidgetState& operator=(const WidgetState&) = delete; @@ -59,20 +102,17 @@ class WidgetState { m_mutex.unlock(); } - std::map& callbacks() { - if (!m_callbacks) { - m_callbacks.reset(new std::map); + void addCallback(CallbacksHolder::cb_trampoline_t trampoline, CallbacksHolder::cb_deleter_t deleter, const std::string& event, void *cb) { + if (!m_cb_holder) { + m_cb_holder.reset(new CallbacksHolder(trampoline, deleter)); } - return *m_callbacks.get(); + m_cb_holder->add(event, cb); } void call(const std::string& event) { - if (!m_callbacks) + if (!m_cb_holder) return; - auto itr = m_callbacks->find(event); - if (itr != m_callbacks->end()) { - (*m_cb_trampoline)(itr->second, this); - } + m_cb_holder->call(this, event); } void markChangedLocked(const std::string& key); @@ -83,10 +123,7 @@ class WidgetState { bool remarkAllChanges(); rbjson::Object m_data; - const cb_trampoline_t m_cb_trampoline; - std::unique_ptr> m_callbacks; - - mutable std::mutex m_mutex; + std::unique_ptr m_cb_holder; const uint16_t m_uuid; diff --git a/test-inis/esp32-idf3-arduino.ini b/test-inis/esp32-idf3-arduino.ini index 479d4e3b..2f69d687 100644 --- a/test-inis/esp32-idf3-arduino.ini +++ b/test-inis/esp32-idf3-arduino.ini @@ -23,4 +23,4 @@ build_flags = -fmax-errors=5 -DLX16A_ARDUINO=1 -lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v12.0.0.zip +lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v13.0.0.zip diff --git a/test-inis/esp32-idf4-arduino.ini b/test-inis/esp32-idf4-arduino.ini index b0f6ab92..90bc55e5 100644 --- a/test-inis/esp32-idf4-arduino.ini +++ b/test-inis/esp32-idf4-arduino.ini @@ -23,4 +23,4 @@ build_flags = -fmax-errors=5 -DLX16A_ARDUINO=1 -lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v12.0.0.zip +lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v13.0.0.zip diff --git a/test-inis/esp32-idf5-idf.ini b/test-inis/esp32-idf5-idf.ini index d6712a08..128547c2 100644 --- a/test-inis/esp32-idf5-idf.ini +++ b/test-inis/esp32-idf5-idf.ini @@ -22,4 +22,4 @@ build_flags = -std=gnu++14 -fmax-errors=5 -lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v12.0.0.zip +lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v13.0.0.zip diff --git a/test-inis/esp32c3-idf4-arduino.ini b/test-inis/esp32c3-idf4-arduino.ini index 30b34a34..d5e5c3f1 100644 --- a/test-inis/esp32c3-idf4-arduino.ini +++ b/test-inis/esp32c3-idf4-arduino.ini @@ -23,4 +23,4 @@ build_flags = -fmax-errors=5 -DLX16A_ARDUINO=1 -lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v12.0.0.zip +lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v13.0.0.zip diff --git a/test-inis/esp32c3-idf5-idf.ini b/test-inis/esp32c3-idf5-idf.ini index f6abeeae..1dfb01c4 100644 --- a/test-inis/esp32c3-idf5-idf.ini +++ b/test-inis/esp32c3-idf5-idf.ini @@ -22,4 +22,4 @@ build_flags = -std=gnu++14 -fmax-errors=5 -lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v12.0.0.zip +lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v13.0.0.zip diff --git a/test-inis/esp32s3-idf4-arduino.ini b/test-inis/esp32s3-idf4-arduino.ini index 0466ae8f..d84e17c2 100644 --- a/test-inis/esp32s3-idf4-arduino.ini +++ b/test-inis/esp32s3-idf4-arduino.ini @@ -23,4 +23,4 @@ build_flags = -fmax-errors=5 -DLX16A_ARDUINO=1 -lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v12.0.0.zip +lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v13.0.0.zip diff --git a/test-inis/esp32s3-idf5-idf.ini b/test-inis/esp32s3-idf5-idf.ini index d7df2a56..be4a40eb 100644 --- a/test-inis/esp32s3-idf5-idf.ini +++ b/test-inis/esp32s3-idf5-idf.ini @@ -22,4 +22,4 @@ build_flags = -std=gnu++14 -fmax-errors=5 -lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v12.0.0.zip +lib_deps = https://github.com/RoboticsBrno/RB3201-RBProtocol-library/archive/refs/tags/v13.0.0.zip