From 61f5a879b35a44d6d9a3c3e3cb88c08d9149de1b Mon Sep 17 00:00:00 2001 From: Maciej Bocianski Date: Tue, 27 Aug 2019 09:34:32 +0200 Subject: [PATCH] UserAllocatedEvent implementation UserAllocatedEvent provides mechanism for event posting and dispatching without utilization of queue internal memory. UserAllocatedEvent embeds all underlying event data and doesn't require any memory allocation while posting and dispatching. All of these makes it cannot fail due to memory exhaustion while posting. --- events/EventQueue.h | 16 +- events/UserAllocatedEvent.h | 420 ++++++++++++++++++++++++++++++++++++ events/mbed_events.h | 1 + 3 files changed, 431 insertions(+), 6 deletions(-) create mode 100644 events/UserAllocatedEvent.h diff --git a/events/EventQueue.h b/events/EventQueue.h index ee501255a947..9037df7159e6 100644 --- a/events/EventQueue.h +++ b/events/EventQueue.h @@ -45,6 +45,8 @@ namespace events { // Predeclared classes template class Event; +template +class UserAllocatedEvent; /** * \defgroup events_EventQueue EventQueue class @@ -1071,6 +1073,8 @@ class EventQueue : private mbed::NonCopyable { #if !defined(DOXYGEN_ONLY) template friend class Event; + template + friend class UserAllocatedEvent; struct equeue _equeue; mbed::Callback _update; @@ -1095,7 +1099,7 @@ class EventQueue : private mbed::NonCopyable { struct context { F f; - context(F f) + constexpr context(F f) : f(f) {} template @@ -1110,7 +1114,7 @@ class EventQueue : private mbed::NonCopyable { F f; C0 c0; - context(F f, C0 c0) + constexpr context(F f, C0 c0) : f(f), c0(c0) {} template @@ -1126,7 +1130,7 @@ class EventQueue : private mbed::NonCopyable { C0 c0; C1 c1; - context(F f, C0 c0, C1 c1) + constexpr context(F f, C0 c0, C1 c1) : f(f), c0(c0), c1(c1) {} template @@ -1143,7 +1147,7 @@ class EventQueue : private mbed::NonCopyable { C1 c1; C2 c2; - context(F f, C0 c0, C1 c1, C2 c2) + constexpr context(F f, C0 c0, C1 c1, C2 c2) : f(f), c0(c0), c1(c1), c2(c2) {} template @@ -1161,7 +1165,7 @@ class EventQueue : private mbed::NonCopyable { C2 c2; C3 c3; - context(F f, C0 c0, C1 c1, C2 c2, C3 c3) + constexpr context(F f, C0 c0, C1 c1, C2 c2, C3 c3) : f(f), c0(c0), c1(c1), c2(c2), c3(c3) {} template @@ -1180,7 +1184,7 @@ class EventQueue : private mbed::NonCopyable { C3 c3; C4 c4; - context(F f, C0 c0, C1 c1, C2 c2, C3 c3, C4 c4) + constexpr context(F f, C0 c0, C1 c1, C2 c2, C3 c3, C4 c4) : f(f), c0(c0), c1(c1), c2(c2), c3(c3), c4(c4) {} template diff --git a/events/UserAllocatedEvent.h b/events/UserAllocatedEvent.h new file mode 100644 index 000000000000..b83dbce5d0d5 --- /dev/null +++ b/events/UserAllocatedEvent.h @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2019 ARM Limited + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef USER_ALLOCATED_EVENT_H +#define USER_ALLOCATED_EVENT_H + +#include "events/EventQueue.h" +#include "platform/mbed_assert.h" +#include "platform/mbed_atomic.h" + +namespace events { +/** + * \addtogroup events-public-api Events + * \ingroup mbed-os-public + * @{ + */ +template +class UserAllocatedEvent; + +/** + * \defgroup events_Event UserAllocatedEvent class + * @{ + */ + +/** UserAllocatedEvent + * + * Representation of an static event for fine-grain dispatch control. + * + * UserAllocatedEvent provides mechanism for event posting and dispatching + * without utilization of queue internal memory. It embeds all underlying + * event data and doesn't require any memory allocation while posting and dispatching. + * All of these makes it cannot fail due to memory exhaustion while posting + * + * Usage: + * @code + * #include "mbed.h" + * + * void handler(int data) { ... } + * + * int main() + * { + * // queue with not internal storage for dynamic events + * // accepts only user allocated events + * EventQueue queue(1); + * + * // Create events not bounded to any queue + * auto e1 = make_user_allocated_event(handler, 1); + * auto e2 = make_user_allocated_event(handler, 2); + * auto e3 = make_user_allocated_event(handler, 3); + * + * // bind & post + * e1.call_on(&queue); + * e2.call_on(&queue); + * e3.call_on(&queue); + * + * queue.dispatch(1); + * } + * @endcode + */ +template +class UserAllocatedEvent { +public: + typedef EventQueue::context C; + + /** Create an event + * + * Constructs an event. The specified callback acts as the target + * for the event and is executed in the context of the + * event queue's dispatch loop once posted. + * + * @param f Function to execute when the event is dispatched + * @param args Arguments to bind to the callback + */ + constexpr UserAllocatedEvent(F f, ArgTs... args) : _e(get_default_equeue_event()), _c(f, args...), _equeue(), _post_ref() + { + } + + /** Create an event + * + * Constructs an event. The specified callback acts as the target + * for the event and is executed in the context of the + * event queue's dispatch loop once posted. + * + * @param queue Event queue to dispatch on + * @param f Function to execute when the event is dispatched + * @param args Arguments to bind to the callback + */ + constexpr UserAllocatedEvent(EventQueue *queue, F f, ArgTs... args) : _e(get_default_equeue_event()), _c(f, args...), _equeue(&queue->_equeue), _post_ref() + { + } + + /** Destructor for events + */ +#if !defined(NDEBUG) + // Remove the user provided destructor in release to allow constexpr optimization + // constexpr requires destructor to be trivial and only default one is treated as trivial + ~UserAllocatedEvent() + { + MBED_ASSERT(!_post_ref); + } +#endif + + /** Posts an event onto the underlying event queue, returning void + * + * The event is posted to the underlying queue and is executed in the + * context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * The post function is IRQ safe and can act as a mechanism for moving + * events out of IRQ contexts. + * + */ + void call() + { + MBED_ASSERT(!_post_ref); + MBED_UNUSED int id = post(); + MBED_ASSERT(id); + } + + /** Posts an event onto the event queue passed as argument, returning void + * + * The event is posted to the event queue passed as argument + * and is executed in the context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * The post function is IRQ safe and can act as a mechanism for moving + * events out of IRQ contexts. + * + * @param queue Event queue to dispatch on. Will replace earlier bound EventQueue. + * + */ + void call_on(EventQueue *queue) + { + MBED_ASSERT(!_post_ref); + _equeue = &(queue->_equeue); + MBED_UNUSED int id = post(); + MBED_ASSERT(id); + } + + /** Posts an event onto the underlying event queue + * + * The event is posted to the underlying queue and is executed in the + * context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * @return false if the event was already posted + * true otherwise + * + */ + bool try_call() + { + if (_post_ref) { + return false; + } + MBED_UNUSED int id = post(); + MBED_ASSERT(id); + return true; + } + + /** Posts an event onto the underlying event queue + * + * The event is posted to the underlying queue and is executed in the + * context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * The post function is IRQ safe and can act as a mechanism for moving + * events out of IRQ contexts. + * + * @return A unique id that represents the posted event and can + * be passed to EventQueue::cancel. + */ + int post() + { + MBED_ASSERT(_equeue); + core_util_atomic_incr_u8(&_post_ref, 1); + return equeue_post(_equeue, &EventQueue::function_call, &_e + 1); + } + + /** Posts an event onto the event queue passed as argument + * + * The event is posted to the event queue passed as argument + * and is executed in the context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * The post function is IRQ safe and can act as a mechanism for moving + * events out of IRQ contexts. + * @param queue Event queue to dispatch on. Will replace earlier bound EventQueue. + * @return A unique id that represents the posted event and can + * be passed to EventQueue::cancel. + */ + int post_on(EventQueue *queue) + { + _equeue = &(queue->_equeue); + return post(); + } + + /** Posts an event onto the underlying event queue, returning void + * + * The event is posted to the underlying queue and is executed in the + * context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * The post function is IRQ safe and can act as a mechanism for moving + * events out of IRQ contexts. + * + */ + void operator()() + { + return call(); + } + + /** Configure the delay of an event + * + * @param delay Millisecond delay before dispatching the event + */ + void delay(int delay) + { + equeue_event_delay(&_e + 1, delay); + } + + /** Configure the period of an event + * + * @param period Millisecond period for repeatedly dispatching an event + */ + void period(int period) + { + equeue_event_period(&_e + 1, period); + } + + /** Cancels posted event + * + * Attempts to cancel posted event. It is safe to call + * cancel after an event has already been dispatched. + * + * The cancel function is IRQ safe. + * + * If called while the event queue's dispatch loop is active, the cancel + * function does not guarantee that the event will not execute after it + * returns, as the event may have already begun executing. + * + * @return true if event was successfully cancelled + */ + bool cancel() const + { + equeue_cancel(_equeue, (int)((intptr_t)this | 1)); + return true; + } + + +private: + equeue_event _e; + C _c; + struct equeue *_equeue; + uint8_t _post_ref; + + static void event_dtor(void *p) + { + UserAllocatedEvent *instance = (UserAllocatedEvent *)(((equeue_event *)p) - 1); + core_util_atomic_decr_u8(&instance->_post_ref, 1); + MBED_ASSERT(!instance->_post_ref); + } + + constexpr static equeue_event get_default_equeue_event() + { + return equeue_event{ 0, 0, 0, NULL, NULL, NULL, 0, -1, &UserAllocatedEvent::event_dtor, NULL }; + } + +public: + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(T *obj, R(T::*method)(ArgTs...), ArgTs... args) : + UserAllocatedEvent(mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(EventQueue *q, T *obj, R(T::*method)(ArgTs...), ArgTs... args) : + UserAllocatedEvent(q, mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(const T *obj, R(T::*method)(ArgTs...) const, ArgTs... args) : + UserAllocatedEvent(mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(EventQueue *q, const T *obj, R(T::*method)(ArgTs...) const, ArgTs... args) : + UserAllocatedEvent(q, mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(volatile T *obj, R(T::*method)(ArgTs...) volatile, ArgTs... args) : + UserAllocatedEvent(mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(EventQueue *q, volatile T *obj, R(T::*method)(ArgTs...) volatile, ArgTs... args) : + UserAllocatedEvent(q, mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(const volatile T *obj, R(T::*method)(ArgTs...) const volatile, ArgTs... args) : + UserAllocatedEvent(mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(EventQueue *q, const volatile T *obj, R(T::*method)(ArgTs...) const volatile, ArgTs... args) : + UserAllocatedEvent(q, mbed::callback(obj, method), args...) { } +}; + +/** Creates a UserAllocatedEvent object, deducing the target type from the types of arguments + * + * UserAllocatedEvent doesn't utilize EventQueue internal memory, + * therefore it can be posted on the queue without being afraid + * of post fail due to queue memory exhaustion + * + * @return UserAllocatedEvent object instance + * + * @code + * #include "mbed.h" + * + * void handler(int data) { ... } + * + * int main() + * { + * EventQueue queue(1); + * + * // Create event not bounded to any queue + * auto e = make_user_allocated_event(handler, 2); + * e.call_on(&queue); + * queue.dispatch(); + * } + * @endcode + */ +template +constexpr UserAllocatedEvent make_user_allocated_event(F f, ArgTs... args) +{ + return UserAllocatedEvent(f, args...); +} + +/** Creates a UserAllocatedEvent object, deducing the target type from the types of arguments + * + * UserAllocatedEvent doesn't utilize EventQueue internal memory, + * therefore it can be posted on the queue without being afraid + * of post fail due to queue memory exhaustion + * + * @return UserAllocatedEvent object instance + * + * @code + * #include "mbed.h" + * + * class Device { + * public: + * void handler(int data) { ... } + * }; + * + * int main() + * { + * EventQueue queue(1); + * + * Device dev; + * // Create event not bounded to any queue + * auto e = make_user_allocated_event(&dev, Device::handler, 2); + * e.call_on(&queue); + * queue.dispatch(); + * } + * @endcode + */ +template +constexpr UserAllocatedEvent, void(ArgTs...)> make_user_allocated_event(T *obj, R(T::*method)(ArgTs... args), ArgTs... args) +{ + return UserAllocatedEvent, void(ArgTs...)>(mbed::callback(obj, method), args...); +} + +/** @}*/ + +/** @}*/ +} +#endif diff --git a/events/mbed_events.h b/events/mbed_events.h index 6243c7ab21dd..991211cfae66 100644 --- a/events/mbed_events.h +++ b/events/mbed_events.h @@ -20,6 +20,7 @@ #include "events/EventQueue.h" #include "events/Event.h" +#include "events/UserAllocatedEvent.h" #include "events/mbed_shared_queues.h"