mirror of https://github.com/ARMmbed/mbed-os.git
Added mbed-events library
Added mbed-events from https://github.com/ARMMbed/mbed-events. Changes from upstream: - the whole code is licensed under the Apache license. Sources and headers were updates with this information. - removed the porting layers for Windows and FreeRTOS and the references to these porting layers in equeue_platform.h. - moved the TESTS directory in mbed-events to the TESTS directory of mbed-os.pull/2860/head
parent
2564a833c0
commit
e7abc11f59
|
@ -0,0 +1,267 @@
|
|||
#include "mbed_events.h"
|
||||
#include "mbed.h"
|
||||
#include "rtos.h"
|
||||
#include "greentea-client/test_env.h"
|
||||
#include "unity.h"
|
||||
#include "utest.h"
|
||||
|
||||
using namespace utest::v1;
|
||||
|
||||
|
||||
// flag for called
|
||||
volatile bool touched = false;
|
||||
|
||||
// static functions
|
||||
void func5(int a0, int a1, int a2, int a3, int a4) {
|
||||
touched = true;
|
||||
TEST_ASSERT_EQUAL(a0 | a1 | a2 | a3 | a4, 0x1f);
|
||||
}
|
||||
|
||||
void func4(int a0, int a1, int a2, int a3) {
|
||||
touched = true;
|
||||
TEST_ASSERT_EQUAL(a0 | a1 | a2 | a3, 0xf);
|
||||
}
|
||||
|
||||
void func3(int a0, int a1, int a2) {
|
||||
touched = true;
|
||||
TEST_ASSERT_EQUAL(a0 | a1 | a2, 0x7);
|
||||
}
|
||||
|
||||
void func2(int a0, int a1) {
|
||||
touched = true;
|
||||
TEST_ASSERT_EQUAL(a0 | a1, 0x3);
|
||||
}
|
||||
|
||||
void func1(int a0) {
|
||||
touched = true;
|
||||
TEST_ASSERT_EQUAL(a0, 0x1);
|
||||
}
|
||||
|
||||
void func0() {
|
||||
touched = true;
|
||||
}
|
||||
|
||||
#define SIMPLE_POSTS_TEST(i, ...) \
|
||||
void simple_posts_test##i() { \
|
||||
EventQueue queue; \
|
||||
\
|
||||
touched = false; \
|
||||
queue.call(func##i,##__VA_ARGS__); \
|
||||
queue.dispatch(0); \
|
||||
TEST_ASSERT(touched); \
|
||||
\
|
||||
touched = false; \
|
||||
queue.call_in(1, func##i,##__VA_ARGS__); \
|
||||
queue.dispatch(2); \
|
||||
TEST_ASSERT(touched); \
|
||||
\
|
||||
touched = false; \
|
||||
queue.call_every(1, func##i,##__VA_ARGS__); \
|
||||
queue.dispatch(2); \
|
||||
TEST_ASSERT(touched); \
|
||||
}
|
||||
|
||||
SIMPLE_POSTS_TEST(5, 0x01, 0x02, 0x04, 0x08, 0x010)
|
||||
SIMPLE_POSTS_TEST(4, 0x01, 0x02, 0x04, 0x08)
|
||||
SIMPLE_POSTS_TEST(3, 0x01, 0x02, 0x04)
|
||||
SIMPLE_POSTS_TEST(2, 0x01, 0x02)
|
||||
SIMPLE_POSTS_TEST(1, 0x01)
|
||||
SIMPLE_POSTS_TEST(0)
|
||||
|
||||
|
||||
void time_func(Timer *t, int ms) {
|
||||
TEST_ASSERT_INT_WITHIN(2, ms, t->read_ms());
|
||||
t->reset();
|
||||
}
|
||||
|
||||
template <int N>
|
||||
void call_in_test() {
|
||||
Timer tickers[N];
|
||||
|
||||
EventQueue queue;
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
tickers[i].start();
|
||||
queue.call_in((i+1)*100, time_func, &tickers[i], (i+1)*100);
|
||||
}
|
||||
|
||||
queue.dispatch(N*100);
|
||||
}
|
||||
|
||||
template <int N>
|
||||
void call_every_test() {
|
||||
Timer tickers[N];
|
||||
|
||||
EventQueue queue;
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
tickers[i].start();
|
||||
queue.call_every((i+1)*100, time_func, &tickers[i], (i+1)*100);
|
||||
}
|
||||
|
||||
queue.dispatch(N*100);
|
||||
}
|
||||
|
||||
struct big { char data[1024]; } big;
|
||||
|
||||
void allocate_failure_test1() {
|
||||
EventQueue queue(32);
|
||||
int id = queue.call((void (*)(struct big))0, big);
|
||||
TEST_ASSERT(!id);
|
||||
}
|
||||
|
||||
void allocate_failure_test2() {
|
||||
EventQueue queue;
|
||||
int id;
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
id = queue.call((void (*)())0);
|
||||
}
|
||||
|
||||
TEST_ASSERT(!id);
|
||||
}
|
||||
|
||||
void no() {
|
||||
TEST_ASSERT(false);
|
||||
}
|
||||
|
||||
template <int N>
|
||||
void cancel_test1() {
|
||||
EventQueue queue;
|
||||
|
||||
int ids[N];
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
ids[i] = queue.call_in(1000, no);
|
||||
}
|
||||
|
||||
for (int i = N-1; i >= 0; i--) {
|
||||
queue.cancel(ids[i]);
|
||||
}
|
||||
|
||||
queue.dispatch(0);
|
||||
}
|
||||
|
||||
|
||||
// Testing the dynamic arguments to the event class
|
||||
unsigned counter = 0;
|
||||
|
||||
void count5(unsigned a0, unsigned a1, unsigned a2, unsigned a3, unsigned a5) {
|
||||
counter += a0 + a1 + a2 + a3 + a5;
|
||||
}
|
||||
|
||||
void count4(unsigned a0, unsigned a1, unsigned a2, unsigned a3) {
|
||||
counter += a0 + a1 + a2 + a3;
|
||||
}
|
||||
|
||||
void count3(unsigned a0, unsigned a1, unsigned a2) {
|
||||
counter += a0 + a1 + a2;
|
||||
}
|
||||
|
||||
void count2(unsigned a0, unsigned a1) {
|
||||
counter += a0 + a1;
|
||||
}
|
||||
|
||||
void count1(unsigned a0) {
|
||||
counter += a0;
|
||||
}
|
||||
|
||||
void count0() {
|
||||
counter += 0;
|
||||
}
|
||||
|
||||
void event_class_test() {
|
||||
counter = 0;
|
||||
EventQueue queue(2048);
|
||||
|
||||
Event<void(int, int, int, int, int)> e5(&queue, count5);
|
||||
Event<void(int, int, int, int)> e4(&queue, count5, 1);
|
||||
Event<void(int, int, int)> e3(&queue, count5, 1, 1);
|
||||
Event<void(int, int)> e2(&queue, count5, 1, 1, 1);
|
||||
Event<void(int)> e1(&queue, count5, 1, 1, 1, 1);
|
||||
Event<void()> e0(&queue, count5, 1, 1, 1, 1, 1);
|
||||
|
||||
e5.post(1, 1, 1, 1, 1);
|
||||
e4.post(1, 1, 1, 1);
|
||||
e3.post(1, 1, 1);
|
||||
e2.post(1, 1);
|
||||
e1.post(1);
|
||||
e0.post();
|
||||
|
||||
queue.dispatch(0);
|
||||
|
||||
TEST_ASSERT_EQUAL(counter, 30);
|
||||
}
|
||||
|
||||
void event_class_helper_test() {
|
||||
counter = 0;
|
||||
EventQueue queue(2048);
|
||||
|
||||
Event<void()> e5 = queue.event(count5, 1, 1, 1, 1, 1);
|
||||
Event<void()> e4 = queue.event(count4, 1, 1, 1, 1);
|
||||
Event<void()> e3 = queue.event(count3, 1, 1, 1);
|
||||
Event<void()> e2 = queue.event(count2, 1, 1);
|
||||
Event<void()> e1 = queue.event(count1, 1);
|
||||
Event<void()> e0 = queue.event(count0);
|
||||
|
||||
e5.post();
|
||||
e4.post();
|
||||
e3.post();
|
||||
e2.post();
|
||||
e1.post();
|
||||
e0.post();
|
||||
|
||||
queue.dispatch(0);
|
||||
|
||||
TEST_ASSERT_EQUAL(counter, 15);
|
||||
}
|
||||
|
||||
void event_inference_test() {
|
||||
counter = 0;
|
||||
EventQueue queue (2048);
|
||||
|
||||
queue.event(count5, 1, 1, 1, 1, 1).post();
|
||||
queue.event(count5, 1, 1, 1, 1).post(1);
|
||||
queue.event(count5, 1, 1, 1).post(1, 1);
|
||||
queue.event(count5, 1, 1).post(1, 1, 1);
|
||||
queue.event(count5, 1).post(1, 1, 1, 1);
|
||||
queue.event(count5).post(1, 1, 1, 1, 1);
|
||||
|
||||
queue.dispatch(0);
|
||||
|
||||
TEST_ASSERT_EQUAL(counter, 30);
|
||||
}
|
||||
|
||||
|
||||
// Test setup
|
||||
utest::v1::status_t test_setup(const size_t number_of_cases) {
|
||||
GREENTEA_SETUP(20, "default_auto");
|
||||
return verbose_test_setup_handler(number_of_cases);
|
||||
}
|
||||
|
||||
const Case cases[] = {
|
||||
Case("Testing calls with 5 args", simple_posts_test5),
|
||||
Case("Testing calls with 4 args", simple_posts_test4),
|
||||
Case("Testing calls with 3 args", simple_posts_test3),
|
||||
Case("Testing calls with 2 args", simple_posts_test2),
|
||||
Case("Testing calls with 1 args", simple_posts_test1),
|
||||
Case("Testing calls with 0 args", simple_posts_test0),
|
||||
|
||||
Case("Testing call_in", call_in_test<20>),
|
||||
Case("Testing call_every", call_every_test<20>),
|
||||
|
||||
Case("Testing allocate failure 1", allocate_failure_test1),
|
||||
Case("Testing allocate failure 2", allocate_failure_test2),
|
||||
|
||||
Case("Testing event cancel 1", cancel_test1<20>),
|
||||
Case("Testing the event class", event_class_test),
|
||||
Case("Testing the event class helpers", event_class_helper_test),
|
||||
Case("Testing the event inference", event_inference_test),
|
||||
};
|
||||
|
||||
Specification specification(test_setup, cases);
|
||||
|
||||
int main() {
|
||||
return !Harness::run(specification);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,66 @@
|
|||
/* events
|
||||
* Copyright (c) 2016 ARM Limited
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include "EventQueue.h"
|
||||
|
||||
#include "mbed_events.h"
|
||||
#include "mbed.h"
|
||||
|
||||
|
||||
EventQueue::EventQueue(unsigned event_size, unsigned char *event_pointer) {
|
||||
if (!event_pointer) {
|
||||
equeue_create(&_equeue, event_size);
|
||||
} else {
|
||||
equeue_create_inplace(&_equeue, event_size, event_pointer);
|
||||
}
|
||||
}
|
||||
|
||||
EventQueue::~EventQueue() {
|
||||
equeue_destroy(&_equeue);
|
||||
}
|
||||
|
||||
void EventQueue::dispatch(int ms) {
|
||||
return equeue_dispatch(&_equeue, ms);
|
||||
}
|
||||
|
||||
void EventQueue::break_dispatch() {
|
||||
return equeue_break(&_equeue);
|
||||
}
|
||||
|
||||
unsigned EventQueue::tick() {
|
||||
return equeue_tick();
|
||||
}
|
||||
|
||||
void EventQueue::cancel(int id) {
|
||||
return equeue_cancel(&_equeue, id);
|
||||
}
|
||||
|
||||
void EventQueue::background(Callback<void(int)> update) {
|
||||
_update = update;
|
||||
|
||||
if (_update) {
|
||||
equeue_background(&_equeue, &Callback<void(int)>::thunk, &_update);
|
||||
} else {
|
||||
equeue_background(&_equeue, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void EventQueue::chain(EventQueue *target) {
|
||||
if (target) {
|
||||
equeue_chain(&_equeue, &target->_equeue);
|
||||
} else {
|
||||
equeue_chain(&_equeue, 0);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,165 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
|
@ -0,0 +1,153 @@
|
|||
## The mbed-events library ##
|
||||
|
||||
The mbed-events library provides a flexible queue for scheduling events.
|
||||
|
||||
``` cpp
|
||||
#include "mbed_events.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
// creates a queue with the default size
|
||||
EventQueue queue;
|
||||
|
||||
// events are simple callbacks
|
||||
queue.call(printf, "called immediately\n");
|
||||
queue.call_in(2000, printf, "called in 2 seconds\n");
|
||||
queue.call_every(1000, printf, "called every 1 seconds\n");
|
||||
|
||||
// events are executed by the dispatch method
|
||||
queue.dispatch();
|
||||
}
|
||||
```
|
||||
|
||||
The mbed-events library can be used as a normal event loop, or it can be
|
||||
backgrounded on a single hardware timer or even another event loop. It is
|
||||
both thread and irq safe, and provides functions for easily composing
|
||||
independent event queues.
|
||||
|
||||
The mbed-events library can act as a drop-in scheduler, provide synchronization
|
||||
between multiple threads, or just act as a mechanism for moving events out of
|
||||
interrupt contexts.
|
||||
|
||||
### Usage ###
|
||||
|
||||
The core of the mbed-events library is the [EventQueue](EventQueue.h) class,
|
||||
which represents a single event queue. The `EventQueue::dispatch` function
|
||||
runs the queue, providing the context for executing events.
|
||||
|
||||
``` cpp
|
||||
// Creates an event queue enough buffer space for 32 Callbacks. This
|
||||
// is the default if no argument was provided. Alternatively the size
|
||||
// can just be specified in bytes.
|
||||
EventQueue queue(32*EVENTS_EVENT_SIZE);
|
||||
|
||||
// Events can be posted to the underlying event queue with dynamic
|
||||
// context allocated from the specified buffer
|
||||
queue.call(printf, "hello %d %d %d %d\n", 1, 2, 3, 4);
|
||||
queue.call(&serial, &Serial::printf, "hi\n");
|
||||
|
||||
// The dispatch function provides the context for the running the queue
|
||||
// and can take a millisecond timeout to run for a fixed time or to just
|
||||
// dispatch any pending events
|
||||
queue.dispatch();
|
||||
```
|
||||
|
||||
The EventQueue class provides several call functions for posting events
|
||||
to the underlying event queue. The call functions are thread and irq safe,
|
||||
don't need the underlying loop to be running, and provide an easy mechanism
|
||||
for moving events out of interrupt contexts.
|
||||
|
||||
``` cpp
|
||||
// Simple call function registers events to be called as soon as possible
|
||||
queue.call(doit);
|
||||
queue.call(printf, "called immediately\n");
|
||||
|
||||
// The call_in function registers events to be called after a delay
|
||||
// specified in milliseconds
|
||||
queue.call_in(2000, doit_in_two_seconds);
|
||||
queue.call_in(300, printf, "called in 0.3 seconds\n");
|
||||
|
||||
// The call_every function registers events to be called repeatedly
|
||||
// with a period specified in milliseconds
|
||||
queue.call_every(2000, doit_every_two_seconds);
|
||||
queue.call_every(400, printf, "called every 0.4 seconds\n");
|
||||
```
|
||||
|
||||
The call functions return an id that uniquely represents the event in the
|
||||
the event queue. This id can be passed to `EventQueue::cancel` to cancel
|
||||
an in-flight event.
|
||||
|
||||
``` cpp
|
||||
// The event id uniquely represents the event in the queue
|
||||
int id = queue.call_in(100, printf, "will this work?\n");
|
||||
|
||||
// If there was not enough memory necessary to allocate the event,
|
||||
// an id of 0 is returned from the call functions
|
||||
if (id) {
|
||||
error("oh no!");
|
||||
}
|
||||
|
||||
// Events can be cancelled as long as they have not been dispatched. If the
|
||||
// event has already expired, cancel has no side-effects.
|
||||
queue.cancel(id);
|
||||
```
|
||||
|
||||
For a more fine-grain control of event dispatch, the `Event` class can be
|
||||
manually instantiated and configured. An `Event` represents an event as
|
||||
a C++ style function object and can be directly passed to other APIs that
|
||||
expect a callback.
|
||||
|
||||
``` cpp
|
||||
// Creates an event bound to the specified event queue
|
||||
EventQueue queue;
|
||||
Event<void()> event(&queue, doit);
|
||||
|
||||
// The event can be manually configured for special timing requirements
|
||||
// specified in milliseconds
|
||||
event.delay(10);
|
||||
event.period(10000);
|
||||
|
||||
// Posted events are dispatched in the context of the queue's
|
||||
// dispatch function
|
||||
queue.dispatch();
|
||||
|
||||
// Events can also pass arguments to the underlying callback when both
|
||||
// initially constructed and posted.
|
||||
Event<void(int, int)> event(&queue, printf, "recieved %d and %d\n");
|
||||
|
||||
// Events can be posted multiple times and enqueue gracefully until
|
||||
// the dispatch function is called.
|
||||
event.post(1, 2);
|
||||
event.post(3, 4);
|
||||
event.post(5, 6);
|
||||
|
||||
queue.dispatch();
|
||||
```
|
||||
|
||||
Event queues easily align with module boundaries, where internal state can
|
||||
be implicitly synchronized through event dispatch. Multiple modules can
|
||||
use independent event queues, but still be composed through the
|
||||
`EventQueue::chain` function.
|
||||
|
||||
``` cpp
|
||||
// Create some event queues with pending events
|
||||
EventQueue a;
|
||||
a.call(printf, "hello from a!\n");
|
||||
|
||||
EventQueue b;
|
||||
b.call(printf, "hello from b!\n");
|
||||
|
||||
EventQueue c;
|
||||
c.call(printf, "hello from c!\n");
|
||||
|
||||
// Chain c and b onto a's event queue. Both c and b will be dispatched
|
||||
// in the context of a's dispatch function.
|
||||
c.chain(&a);
|
||||
b.chain(&a);
|
||||
|
||||
// Dispatching a will in turn dispatch b and c, printing hello from
|
||||
// all three queues
|
||||
a.dispatch();
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
tests/*
|
|
@ -0,0 +1,60 @@
|
|||
TARGET = libequeue.a
|
||||
|
||||
CC = gcc
|
||||
AR = ar
|
||||
SIZE = size
|
||||
|
||||
SRC += $(wildcard *.c)
|
||||
OBJ := $(SRC:.c=.o)
|
||||
DEP := $(SRC:.c=.d)
|
||||
ASM := $(SRC:.c=.s)
|
||||
|
||||
ifdef DEBUG
|
||||
CFLAGS += -O0 -g3
|
||||
else
|
||||
CFLAGS += -O2
|
||||
endif
|
||||
ifdef WORD
|
||||
CFLAGS += -m$(WORD)
|
||||
endif
|
||||
CFLAGS += -I.
|
||||
CFLAGS += -std=c99
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += -D_XOPEN_SOURCE=600
|
||||
|
||||
LFLAGS += -pthread
|
||||
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
test: tests/tests.o $(OBJ)
|
||||
$(CC) $(CFLAGS) $^ $(LFLAGS) -o tests/tests
|
||||
tests/tests
|
||||
|
||||
prof: tests/prof.o $(OBJ)
|
||||
$(CC) $(CFLAGS) $^ $(LFLAGS) -o tests/prof
|
||||
tests/prof
|
||||
|
||||
asm: $(ASM)
|
||||
|
||||
size: $(OBJ)
|
||||
$(SIZE) -t $^
|
||||
|
||||
-include $(DEP)
|
||||
|
||||
%.a: $(OBJ)
|
||||
$(AR) rcs $@ $^
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -MMD $(CFLAGS) $< -o $@
|
||||
|
||||
%.s: %.c
|
||||
$(CC) -S $(CFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
rm -f tests/tests tests/tests.o tests/tests.d
|
||||
rm -f tests/prof tests/prof.o tests/prof.d
|
||||
rm -f $(OBJ)
|
||||
rm -f $(DEP)
|
||||
rm -f $(ASM)
|
|
@ -0,0 +1,210 @@
|
|||
## The equeue library ##
|
||||
|
||||
The equeue library is designed as a simple but powerful library for scheduling
|
||||
events on composable queues.
|
||||
|
||||
``` c
|
||||
#include "equeue.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
// creates a queue with space for 32 basic events
|
||||
equeue_t queue;
|
||||
equeue_create(&queue, 32*EQUEUE_EVENT_SIZE);
|
||||
|
||||
// events can be simple callbacks
|
||||
equeue_call(&queue, print, "called immediately");
|
||||
equeue_call_in(&queue, 2000, print, "called in 2 seconds");
|
||||
equeue_call_every(&queue, 1000, print, "called every 1 seconds");
|
||||
|
||||
// events are executed in equeue_dispatch
|
||||
equeue_dispatch(&queue, 3000);
|
||||
|
||||
print("called after 3 seconds");
|
||||
|
||||
equeue_destroy(&queue);
|
||||
}
|
||||
```
|
||||
|
||||
The equeue library can be used as a normal event loop, or it can be
|
||||
backgrounded on a single hardware timer or even another event loop. It
|
||||
is both thread and irq safe, and provides functions for easily composing
|
||||
multiple queues.
|
||||
|
||||
The equeue library can act as a drop-in scheduler, provide synchronization
|
||||
between multiple threads, or just act as a mechanism for moving events
|
||||
out of interrupt contexts.
|
||||
|
||||
## Documentation ##
|
||||
|
||||
The in-depth documentation on specific functions can be found in
|
||||
[equeue.h](equeue.h).
|
||||
|
||||
The core of the equeue library is the `equeue_t` type which represents a
|
||||
single event queue, and the `equeue_dispatch` function which runs the equeue,
|
||||
providing the context for executing events.
|
||||
|
||||
On top of this, `equeue_call`, `equeue_call_in`, and `equeue_call_every`
|
||||
provide easy methods for posting events to execute in the context of the
|
||||
`equeue_dispatch` function.
|
||||
|
||||
``` c
|
||||
#include "equeue.h"
|
||||
#include "game.h"
|
||||
|
||||
equeue_t queue;
|
||||
struct game game;
|
||||
|
||||
// button_isr may be in interrupt context
|
||||
void button_isr(void) {
|
||||
equeue_call(&queue, game_button_update, &game);
|
||||
}
|
||||
|
||||
// a simple user-interface framework
|
||||
int main() {
|
||||
equeue_create(&queue, 4096);
|
||||
game_create(&game);
|
||||
|
||||
// call game_screen_udpate at 60 Hz
|
||||
equeue_call_every(&queue, 1000/60, game_screen_update, &game);
|
||||
|
||||
// dispatch forever
|
||||
equeue_dispatch(&queue, -1);
|
||||
}
|
||||
```
|
||||
|
||||
In addition to simple callbacks, an event can be manually allocated with
|
||||
`equeue_alloc` and posted with `equeue_post` to allow passing an arbitrary
|
||||
amount of context to the execution of the event. This memory is allocated out
|
||||
of the equeue's buffer, and dynamic memory can be completely avoided.
|
||||
|
||||
The equeue allocator is designed to minimize jitter in interrupt contexts as
|
||||
well as avoid memory fragmentation on small devices. The allocator achieves
|
||||
both constant-runtime and zero-fragmentation for fixed-size events, however
|
||||
grows linearly as the quantity of differently-sized allocations increases.
|
||||
|
||||
``` c
|
||||
#include "equeue.h"
|
||||
|
||||
equeue_t queue;
|
||||
|
||||
// arbitrary data can be moved to a different context
|
||||
int enet_consume(void *buffer, int size) {
|
||||
if (size > 512) {
|
||||
size = 512;
|
||||
}
|
||||
|
||||
void *data = equeue_alloc(&queue, 512);
|
||||
memcpy(data, buffer, size);
|
||||
equeue_post(&queue, handle_data_elsewhere, data);
|
||||
|
||||
return size;
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, in-flight events can be cancelled with `equeue_cancel`. Events
|
||||
are given unique ids on post, allowing safe cancellation of expired events.
|
||||
|
||||
``` c
|
||||
#include "equeue.h"
|
||||
|
||||
equeue_t queue;
|
||||
int sonar_value;
|
||||
int sonar_timeout_id;
|
||||
|
||||
void sonar_isr(int value) {
|
||||
equeue_cancel(&queue, sonar_timeout_id);
|
||||
sonar_value = value;
|
||||
}
|
||||
|
||||
void sonar_timeout(void *) {
|
||||
sonar_value = -1;
|
||||
}
|
||||
|
||||
void sonar_read(void) {
|
||||
sonar_timeout_id = equeue_call_in(&queue, 300, sonar_timeout, 0);
|
||||
sonar_start();
|
||||
}
|
||||
```
|
||||
|
||||
From an architectural standpoint, event queues easily align with module
|
||||
boundaries, where internal state can be implicitly synchronized through
|
||||
event dispatch.
|
||||
|
||||
On platforms where multiple threads are unavailable, multiple modules
|
||||
can use independent event queues and still be composed through the
|
||||
`equeue_chain` function.
|
||||
|
||||
``` c
|
||||
#include "equeue.h"
|
||||
|
||||
// run a simultaneous localization and mapping loop in one queue
|
||||
struct slam {
|
||||
equeue_t queue;
|
||||
};
|
||||
|
||||
void slam_create(struct slam *s, equeue_t *target) {
|
||||
equeue_create(&s->queue, 4096);
|
||||
equeue_chain(&s->queue, target);
|
||||
equeue_call_every(&s->queue, 100, slam_filter);
|
||||
}
|
||||
|
||||
// run a sonar with it's own queue
|
||||
struct sonar {
|
||||
equeue_t equeue;
|
||||
struct slam *slam;
|
||||
};
|
||||
|
||||
void sonar_create(struct sonar *s, equeue_t *target) {
|
||||
equeue_create(&s->queue, 64);
|
||||
equeue_chain(&s->queue, target);
|
||||
equeue_call_in(&s->queue, 5, sonar_update, s);
|
||||
}
|
||||
|
||||
// all of the above queues can be combined into a single thread of execution
|
||||
int main() {
|
||||
equeue_t queue;
|
||||
equeue_create(&queue, 1024);
|
||||
|
||||
struct sonar s1, s2, s3;
|
||||
sonar_create(&s1, &queue);
|
||||
sonar_create(&s2, &queue);
|
||||
sonar_create(&s3, &queue);
|
||||
|
||||
struct slam slam;
|
||||
slam_create(&slam, &queue);
|
||||
|
||||
// dispatches events from all of the modules
|
||||
equeue_dispatch(&queue, -1);
|
||||
}
|
||||
```
|
||||
|
||||
## Platform ##
|
||||
|
||||
The equeue library has a minimal porting layer that is flexible depending
|
||||
on the requirements of the underlying platform. Platform specific declarations
|
||||
and more information can be found in [equeue_platform.h](equeue_platform.h).
|
||||
|
||||
## Tests ##
|
||||
|
||||
The equeue library uses a set of local tests based on the posix implementation.
|
||||
|
||||
Runtime tests are located in [tests.c](tests/tests.c):
|
||||
|
||||
``` bash
|
||||
make test
|
||||
```
|
||||
|
||||
Profiling tests based on rdtsc are located in [prof.c](tests/prof.c):
|
||||
|
||||
``` bash
|
||||
make prof
|
||||
```
|
||||
|
||||
To make profiling results more tangible, the profiler also supports percentage
|
||||
comparison with previous runs:
|
||||
``` bash
|
||||
make prof | tee results.txt
|
||||
cat results.txt | make prof
|
||||
```
|
||||
|
|
@ -0,0 +1,569 @@
|
|||
/*
|
||||
* Flexible event queue for dispatching events
|
||||
*
|
||||
* Copyright (c) 2016 Christopher Haster
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include "equeue.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
// calculate the relative-difference between absolute times while
|
||||
// correctly handling overflow conditions
|
||||
static inline int equeue_tickdiff(unsigned a, unsigned b) {
|
||||
return (int)(a - b);
|
||||
}
|
||||
|
||||
// calculate the relative-difference between absolute times, but
|
||||
// also clamp to zero, resulting in only non-zero values.
|
||||
static inline int equeue_clampdiff(unsigned a, unsigned b) {
|
||||
int diff = equeue_tickdiff(a, b);
|
||||
return ~(diff >> (8*sizeof(int)-1)) & diff;
|
||||
}
|
||||
|
||||
// Increment the unique id in an event, hiding the event from cancel
|
||||
static inline void equeue_incid(equeue_t *q, struct equeue_event *e) {
|
||||
e->id += 1;
|
||||
if (!(e->id << q->npw2)) {
|
||||
e->id = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// equeue lifetime management
|
||||
int equeue_create(equeue_t *q, size_t size) {
|
||||
// dynamically allocate the specified buffer
|
||||
void *buffer = malloc(size);
|
||||
if (!buffer) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int err = equeue_create_inplace(q, size, buffer);
|
||||
q->allocated = buffer;
|
||||
return err;
|
||||
}
|
||||
|
||||
int equeue_create_inplace(equeue_t *q, size_t size, void *buffer) {
|
||||
// setup queue around provided buffer
|
||||
q->buffer = buffer;
|
||||
q->allocated = 0;
|
||||
|
||||
q->npw2 = 0;
|
||||
for (unsigned s = size; s; s >>= 1) {
|
||||
q->npw2++;
|
||||
}
|
||||
|
||||
q->chunks = 0;
|
||||
q->slab.size = size;
|
||||
q->slab.data = buffer;
|
||||
|
||||
q->queue = 0;
|
||||
q->tick = equeue_tick();
|
||||
q->generation = 0;
|
||||
q->breaks = 0;
|
||||
|
||||
q->background.active = false;
|
||||
q->background.update = 0;
|
||||
q->background.timer = 0;
|
||||
|
||||
// initialize platform resources
|
||||
int err;
|
||||
err = equeue_sema_create(&q->eventsema);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = equeue_mutex_create(&q->queuelock);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = equeue_mutex_create(&q->memlock);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void equeue_destroy(equeue_t *q) {
|
||||
// call destructors on pending events
|
||||
for (struct equeue_event *es = q->queue; es; es = es->next) {
|
||||
for (struct equeue_event *e = q->queue; e; e = e->sibling) {
|
||||
if (e->dtor) {
|
||||
e->dtor(e + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notify background timer
|
||||
if (q->background.update) {
|
||||
q->background.update(q->background.timer, -1);
|
||||
}
|
||||
|
||||
// clean up platform resources + memory
|
||||
equeue_mutex_destroy(&q->memlock);
|
||||
equeue_mutex_destroy(&q->queuelock);
|
||||
equeue_sema_destroy(&q->eventsema);
|
||||
free(q->allocated);
|
||||
}
|
||||
|
||||
|
||||
// equeue chunk allocation functions
|
||||
static struct equeue_event *equeue_mem_alloc(equeue_t *q, size_t size) {
|
||||
// add event overhead
|
||||
size += sizeof(struct equeue_event);
|
||||
size = (size + sizeof(void*)-1) & ~(sizeof(void*)-1);
|
||||
|
||||
equeue_mutex_lock(&q->memlock);
|
||||
|
||||
// check if a good chunk is available
|
||||
for (struct equeue_event **p = &q->chunks; *p; p = &(*p)->next) {
|
||||
if ((*p)->size >= size) {
|
||||
struct equeue_event *e = *p;
|
||||
if (e->sibling) {
|
||||
*p = e->sibling;
|
||||
(*p)->next = e->next;
|
||||
} else {
|
||||
*p = e->next;
|
||||
}
|
||||
|
||||
equeue_mutex_unlock(&q->memlock);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise allocate a new chunk out of the slab
|
||||
if (q->slab.size >= size) {
|
||||
struct equeue_event *e = (struct equeue_event *)q->slab.data;
|
||||
q->slab.data += size;
|
||||
q->slab.size -= size;
|
||||
e->size = size;
|
||||
e->id = 1;
|
||||
|
||||
equeue_mutex_unlock(&q->memlock);
|
||||
return e;
|
||||
}
|
||||
|
||||
equeue_mutex_unlock(&q->memlock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void equeue_mem_dealloc(equeue_t *q, struct equeue_event *e) {
|
||||
equeue_mutex_lock(&q->memlock);
|
||||
|
||||
// stick chunk into list of chunks
|
||||
struct equeue_event **p = &q->chunks;
|
||||
while (*p && (*p)->size < e->size) {
|
||||
p = &(*p)->next;
|
||||
}
|
||||
|
||||
if (*p && (*p)->size == e->size) {
|
||||
e->sibling = *p;
|
||||
e->next = (*p)->next;
|
||||
} else {
|
||||
e->sibling = 0;
|
||||
e->next = *p;
|
||||
}
|
||||
*p = e;
|
||||
|
||||
equeue_mutex_unlock(&q->memlock);
|
||||
}
|
||||
|
||||
void *equeue_alloc(equeue_t *q, size_t size) {
|
||||
struct equeue_event *e = equeue_mem_alloc(q, size);
|
||||
if (!e) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
e->target = 0;
|
||||
e->period = -1;
|
||||
e->dtor = 0;
|
||||
|
||||
return e + 1;
|
||||
}
|
||||
|
||||
void equeue_dealloc(equeue_t *q, void *p) {
|
||||
struct equeue_event *e = (struct equeue_event*)p - 1;
|
||||
|
||||
if (e->dtor) {
|
||||
e->dtor(e+1);
|
||||
}
|
||||
|
||||
equeue_mem_dealloc(q, e);
|
||||
}
|
||||
|
||||
|
||||
// equeue scheduling functions
|
||||
static int equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned tick) {
|
||||
// setup event and hash local id with buffer offset for unique id
|
||||
int id = (e->id << q->npw2) | ((unsigned char *)e - q->buffer);
|
||||
e->target = tick + equeue_clampdiff(e->target, tick);
|
||||
e->generation = q->generation;
|
||||
|
||||
equeue_mutex_lock(&q->queuelock);
|
||||
|
||||
// find the event slot
|
||||
struct equeue_event **p = &q->queue;
|
||||
while (*p && equeue_tickdiff((*p)->target, e->target) < 0) {
|
||||
p = &(*p)->next;
|
||||
}
|
||||
|
||||
// insert at head in slot
|
||||
if (*p && (*p)->target == e->target) {
|
||||
e->next = (*p)->next;
|
||||
if (e->next) {
|
||||
e->next->ref = &e->next;
|
||||
}
|
||||
|
||||
e->sibling = *p;
|
||||
e->sibling->ref = &e->sibling;
|
||||
} else {
|
||||
e->next = *p;
|
||||
if (e->next) {
|
||||
e->next->ref = &e->next;
|
||||
}
|
||||
|
||||
e->sibling = 0;
|
||||
}
|
||||
|
||||
*p = e;
|
||||
e->ref = p;
|
||||
|
||||
// notify background timer
|
||||
if ((q->background.update && q->background.active) &&
|
||||
(q->queue == e && !e->sibling)) {
|
||||
q->background.update(q->background.timer,
|
||||
equeue_clampdiff(e->target, tick));
|
||||
}
|
||||
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static struct equeue_event *equeue_unqueue(equeue_t *q, int id) {
|
||||
// decode event from unique id and check that the local id matches
|
||||
struct equeue_event *e = (struct equeue_event *)
|
||||
&q->buffer[id & ((1 << q->npw2)-1)];
|
||||
|
||||
equeue_mutex_lock(&q->queuelock);
|
||||
if (e->id != id >> q->npw2) {
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// clear the event and check if already in-flight
|
||||
e->cb = 0;
|
||||
e->period = -1;
|
||||
|
||||
int diff = equeue_tickdiff(e->target, q->tick);
|
||||
if (diff < 0 || (diff == 0 && e->generation != q->generation)) {
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// disentangle from queue
|
||||
if (e->sibling) {
|
||||
e->sibling->next = e->next;
|
||||
if (e->sibling->next) {
|
||||
e->sibling->next->ref = &e->sibling->next;
|
||||
}
|
||||
|
||||
*e->ref = e->sibling;
|
||||
e->sibling->ref = e->ref;
|
||||
} else {
|
||||
*e->ref = e->next;
|
||||
if (e->next) {
|
||||
e->next->ref = e->ref;
|
||||
}
|
||||
}
|
||||
|
||||
equeue_incid(q, e);
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static struct equeue_event *equeue_dequeue(equeue_t *q, unsigned target) {
|
||||
equeue_mutex_lock(&q->queuelock);
|
||||
|
||||
// find all expired events and mark a new generation
|
||||
q->generation += 1;
|
||||
if (equeue_tickdiff(q->tick, target) <= 0) {
|
||||
q->tick = target;
|
||||
}
|
||||
|
||||
struct equeue_event *head = q->queue;
|
||||
struct equeue_event **p = &head;
|
||||
while (*p && equeue_tickdiff((*p)->target, target) <= 0) {
|
||||
p = &(*p)->next;
|
||||
}
|
||||
|
||||
q->queue = *p;
|
||||
if (q->queue) {
|
||||
q->queue->ref = &q->queue;
|
||||
}
|
||||
|
||||
*p = 0;
|
||||
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
|
||||
// reverse and flatten each slot to match insertion order
|
||||
struct equeue_event **tail = &head;
|
||||
struct equeue_event *ess = head;
|
||||
while (ess) {
|
||||
struct equeue_event *es = ess;
|
||||
ess = es->next;
|
||||
|
||||
struct equeue_event *prev = 0;
|
||||
for (struct equeue_event *e = es; e; e = e->sibling) {
|
||||
e->next = prev;
|
||||
prev = e;
|
||||
}
|
||||
|
||||
*tail = prev;
|
||||
tail = &es->next;
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
int equeue_post(equeue_t *q, void (*cb)(void*), void *p) {
|
||||
struct equeue_event *e = (struct equeue_event*)p - 1;
|
||||
unsigned tick = equeue_tick();
|
||||
e->cb = cb;
|
||||
e->target = tick + e->target;
|
||||
|
||||
int id = equeue_enqueue(q, e, tick);
|
||||
equeue_sema_signal(&q->eventsema);
|
||||
return id;
|
||||
}
|
||||
|
||||
void equeue_cancel(equeue_t *q, int id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct equeue_event *e = equeue_unqueue(q, id);
|
||||
if (e) {
|
||||
equeue_dealloc(q, e + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void equeue_break(equeue_t *q) {
|
||||
equeue_mutex_lock(&q->queuelock);
|
||||
q->breaks++;
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
equeue_sema_signal(&q->eventsema);
|
||||
}
|
||||
|
||||
void equeue_dispatch(equeue_t *q, int ms) {
|
||||
unsigned tick = equeue_tick();
|
||||
unsigned timeout = tick + ms;
|
||||
q->background.active = false;
|
||||
|
||||
while (1) {
|
||||
// collect all the available events and next deadline
|
||||
struct equeue_event *es = equeue_dequeue(q, tick);
|
||||
|
||||
// dispatch events
|
||||
while (es) {
|
||||
struct equeue_event *e = es;
|
||||
es = e->next;
|
||||
|
||||
// actually dispatch the callbacks
|
||||
void (*cb)(void *) = e->cb;
|
||||
if (cb) {
|
||||
cb(e + 1);
|
||||
}
|
||||
|
||||
// reenqueue periodic events or deallocate
|
||||
if (e->period >= 0) {
|
||||
e->target += e->period;
|
||||
equeue_enqueue(q, e, equeue_tick());
|
||||
} else {
|
||||
equeue_incid(q, e);
|
||||
equeue_dealloc(q, e+1);
|
||||
}
|
||||
}
|
||||
|
||||
int deadline = -1;
|
||||
tick = equeue_tick();
|
||||
|
||||
// check if we should stop dispatching soon
|
||||
if (ms >= 0) {
|
||||
deadline = equeue_tickdiff(timeout, tick);
|
||||
if (deadline <= 0) {
|
||||
// update background timer if necessary
|
||||
if (q->background.update) {
|
||||
equeue_mutex_lock(&q->queuelock);
|
||||
if (q->background.update && q->queue) {
|
||||
q->background.update(q->background.timer,
|
||||
equeue_clampdiff(q->queue->target, tick));
|
||||
}
|
||||
q->background.active = true;
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// find closest deadline
|
||||
equeue_mutex_lock(&q->queuelock);
|
||||
if (q->queue) {
|
||||
int diff = equeue_clampdiff(q->queue->target, tick);
|
||||
if ((unsigned)diff < (unsigned)deadline) {
|
||||
deadline = diff;
|
||||
}
|
||||
}
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
|
||||
// wait for events
|
||||
equeue_sema_wait(&q->eventsema, deadline);
|
||||
|
||||
// check if we were notified to break out of dispatch
|
||||
if (q->breaks) {
|
||||
equeue_mutex_lock(&q->queuelock);
|
||||
if (q->breaks > 0) {
|
||||
q->breaks--;
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
return;
|
||||
}
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
}
|
||||
|
||||
// update tick for next iteration
|
||||
tick = equeue_tick();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// event functions
|
||||
void equeue_event_delay(void *p, int ms) {
|
||||
struct equeue_event *e = (struct equeue_event*)p - 1;
|
||||
e->target = ms;
|
||||
}
|
||||
|
||||
void equeue_event_period(void *p, int ms) {
|
||||
struct equeue_event *e = (struct equeue_event*)p - 1;
|
||||
e->period = ms;
|
||||
}
|
||||
|
||||
void equeue_event_dtor(void *p, void (*dtor)(void *)) {
|
||||
struct equeue_event *e = (struct equeue_event*)p - 1;
|
||||
e->dtor = dtor;
|
||||
}
|
||||
|
||||
|
||||
// simple callbacks
|
||||
struct ecallback {
|
||||
void (*cb)(void*);
|
||||
void *data;
|
||||
};
|
||||
|
||||
static void ecallback_dispatch(void *p) {
|
||||
struct ecallback *e = (struct ecallback*)p;
|
||||
e->cb(e->data);
|
||||
}
|
||||
|
||||
int equeue_call(equeue_t *q, void (*cb)(void*), void *data) {
|
||||
struct ecallback *e = equeue_alloc(q, sizeof(struct ecallback));
|
||||
if (!e) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
e->cb = cb;
|
||||
e->data = data;
|
||||
return equeue_post(q, ecallback_dispatch, e);
|
||||
}
|
||||
|
||||
int equeue_call_in(equeue_t *q, int ms, void (*cb)(void*), void *data) {
|
||||
struct ecallback *e = equeue_alloc(q, sizeof(struct ecallback));
|
||||
if (!e) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
equeue_event_delay(e, ms);
|
||||
e->cb = cb;
|
||||
e->data = data;
|
||||
return equeue_post(q, ecallback_dispatch, e);
|
||||
}
|
||||
|
||||
int equeue_call_every(equeue_t *q, int ms, void (*cb)(void*), void *data) {
|
||||
struct ecallback *e = equeue_alloc(q, sizeof(struct ecallback));
|
||||
if (!e) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
equeue_event_delay(e, ms);
|
||||
equeue_event_period(e, ms);
|
||||
e->cb = cb;
|
||||
e->data = data;
|
||||
return equeue_post(q, ecallback_dispatch, e);
|
||||
}
|
||||
|
||||
|
||||
// backgrounding
|
||||
void equeue_background(equeue_t *q,
|
||||
void (*update)(void *timer, int ms), void *timer) {
|
||||
equeue_mutex_lock(&q->queuelock);
|
||||
if (q->background.update) {
|
||||
q->background.update(q->background.timer, -1);
|
||||
}
|
||||
|
||||
q->background.update = update;
|
||||
q->background.timer = timer;
|
||||
|
||||
if (q->background.update && q->queue) {
|
||||
q->background.update(q->background.timer,
|
||||
equeue_clampdiff(q->queue->target, equeue_tick()));
|
||||
}
|
||||
q->background.active = true;
|
||||
equeue_mutex_unlock(&q->queuelock);
|
||||
}
|
||||
|
||||
struct equeue_chain_context {
|
||||
equeue_t *q;
|
||||
equeue_t *target;
|
||||
int id;
|
||||
};
|
||||
|
||||
static void equeue_chain_dispatch(void *p) {
|
||||
equeue_dispatch((equeue_t *)p, 0);
|
||||
}
|
||||
|
||||
static void equeue_chain_update(void *p, int ms) {
|
||||
struct equeue_chain_context *c = (struct equeue_chain_context *)p;
|
||||
equeue_cancel(c->target, c->id);
|
||||
|
||||
if (ms >= 0) {
|
||||
c->id = equeue_call_in(c->target, ms, equeue_chain_dispatch, c->q);
|
||||
} else {
|
||||
equeue_dealloc(c->target, c);
|
||||
}
|
||||
}
|
||||
|
||||
void equeue_chain(equeue_t *q, equeue_t *target) {
|
||||
struct equeue_chain_context *c = equeue_alloc(q,
|
||||
sizeof(struct equeue_chain_context));
|
||||
|
||||
c->q = q;
|
||||
c->target = target;
|
||||
c->id = 0;
|
||||
|
||||
equeue_background(q, equeue_chain_update, c);
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Flexible event queue for dispatching events
|
||||
*
|
||||
* Copyright (c) 2016 Christopher Haster
|
||||
*
|
||||
* 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 EQUEUE_H
|
||||
#define EQUEUE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Platform specific files
|
||||
#include "equeue_platform.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// The minimum size of an event
|
||||
// This size is guaranteed to fit events created by event_call
|
||||
#define EQUEUE_EVENT_SIZE (sizeof(struct equeue_event) + 2*sizeof(void*))
|
||||
|
||||
// Internal event structure
|
||||
struct equeue_event {
|
||||
unsigned size;
|
||||
uint8_t id;
|
||||
uint8_t generation;
|
||||
|
||||
struct equeue_event *next;
|
||||
struct equeue_event *sibling;
|
||||
struct equeue_event **ref;
|
||||
|
||||
unsigned target;
|
||||
int period;
|
||||
void (*dtor)(void *);
|
||||
|
||||
void (*cb)(void *);
|
||||
// data follows
|
||||
};
|
||||
|
||||
// Event queue structure
|
||||
typedef struct equeue {
|
||||
struct equeue_event *queue;
|
||||
unsigned tick;
|
||||
unsigned breaks;
|
||||
uint8_t generation;
|
||||
|
||||
unsigned char *buffer;
|
||||
unsigned npw2;
|
||||
void *allocated;
|
||||
|
||||
struct equeue_event *chunks;
|
||||
struct equeue_slab {
|
||||
size_t size;
|
||||
unsigned char *data;
|
||||
} slab;
|
||||
|
||||
struct equeue_background {
|
||||
bool active;
|
||||
void (*update)(void *timer, int ms);
|
||||
void *timer;
|
||||
} background;
|
||||
|
||||
equeue_sema_t eventsema;
|
||||
equeue_mutex_t queuelock;
|
||||
equeue_mutex_t memlock;
|
||||
} equeue_t;
|
||||
|
||||
|
||||
// Queue lifetime operations
|
||||
//
|
||||
// Creates and destroys an event queue. The event queue either allocates a
|
||||
// buffer of the specified size with malloc or uses a user provided buffer
|
||||
// if constructed with equeue_create_inplace.
|
||||
//
|
||||
// If the event queue creation fails, equeue_create returns a negative,
|
||||
// platform-specific error code.
|
||||
int equeue_create(equeue_t *queue, size_t size);
|
||||
int equeue_create_inplace(equeue_t *queue, size_t size, void *buffer);
|
||||
void equeue_destroy(equeue_t *queue);
|
||||
|
||||
// Dispatch events
|
||||
//
|
||||
// Executes events until the specified milliseconds have passed. If ms is
|
||||
// negative, equeue_dispatch will dispatch events indefinitely or until
|
||||
// equeue_break is called on this queue.
|
||||
//
|
||||
// When called with a finite timeout, the equeue_dispatch function is
|
||||
// guaranteed to terminate. When called with a timeout of 0, the
|
||||
// equeue_dispatch does not wait and is irq safe.
|
||||
void equeue_dispatch(equeue_t *queue, int ms);
|
||||
|
||||
// Break out of a running event loop
|
||||
//
|
||||
// Forces the specified event queue's dispatch loop to terminate. Pending
|
||||
// events may finish executing, but no new events will be executed.
|
||||
void equeue_break(equeue_t *queue);
|
||||
|
||||
// Simple event calls
|
||||
//
|
||||
// The specified callback will be executed in the context of the event queue's
|
||||
// dispatch loop. When the callback is executed depends on the call function.
|
||||
//
|
||||
// equeue_call - Immediately post an event to the queue
|
||||
// equeue_call_in - Post an event after a specified time in milliseconds
|
||||
// equeue_call_every - Post an event periodically every milliseconds
|
||||
//
|
||||
// All equeue_call functions are irq safe and can act as a mechanism for
|
||||
// moving events out of irq contexts.
|
||||
//
|
||||
// The return value is a unique id that represents the posted event and can
|
||||
// be passed to equeue_cancel. If there is not enough memory to allocate the
|
||||
// event, equeue_call returns an id of 0.
|
||||
int equeue_call(equeue_t *queue, void (*cb)(void *), void *data);
|
||||
int equeue_call_in(equeue_t *queue, int ms, void (*cb)(void *), void *data);
|
||||
int equeue_call_every(equeue_t *queue, int ms, void (*cb)(void *), void *data);
|
||||
|
||||
// Allocate memory for events
|
||||
//
|
||||
// The equeue_alloc function allocates an event that can be manually dispatched
|
||||
// with equeue_post. The equeue_dealloc function may be used to free an event
|
||||
// that has not been posted. Once posted, an event's memory is managed by the
|
||||
// event queue and should not be deallocated.
|
||||
//
|
||||
// Both equeue_alloc and equeue_dealloc are irq safe.
|
||||
//
|
||||
// The equeue allocator is designed to minimize jitter in interrupt contexts as
|
||||
// well as avoid memory fragmentation on small devices. The allocator achieves
|
||||
// both constant-runtime and zero-fragmentation for fixed-size events, however
|
||||
// grows linearly as the quantity of different sized allocations increases.
|
||||
//
|
||||
// The equeue_alloc function returns a pointer to the event's allocated memory
|
||||
// and acts as a handle to the underlying event. If there is not enough memory
|
||||
// to allocate the event, equeue_alloc returns null.
|
||||
void *equeue_alloc(equeue_t *queue, size_t size);
|
||||
void equeue_dealloc(equeue_t *queue, void *event);
|
||||
|
||||
// Configure an allocated event
|
||||
//
|
||||
// equeue_event_delay - Millisecond delay before dispatching an event
|
||||
// equeue_event_period - Millisecond period for repeating dispatching an event
|
||||
// equeue_event_dtor - Destructor to run when the event is deallocated
|
||||
void equeue_event_delay(void *event, int ms);
|
||||
void equeue_event_period(void *event, int ms);
|
||||
void equeue_event_dtor(void *event, void (*dtor)(void *));
|
||||
|
||||
// Post an event onto the event queue
|
||||
//
|
||||
// The equeue_post function takes a callback and a pointer to an event
|
||||
// allocated by equeue_alloc. The specified callback will be executed in the
|
||||
// context of the event queue's dispatch loop with the allocated event
|
||||
// as its argument.
|
||||
//
|
||||
// The equeue_post function is irq safe and can act as a mechanism for
|
||||
// moving events out of irq contexts.
|
||||
//
|
||||
// The return value is a unique id that represents the posted event and can
|
||||
// be passed to equeue_cancel.
|
||||
int equeue_post(equeue_t *queue, void (*cb)(void *), void *event);
|
||||
|
||||
// Cancel an in-flight event
|
||||
//
|
||||
// Attempts to cancel an event referenced by the unique id returned from
|
||||
// equeue_call or equeue_post. It is safe to call equeue_cancel after an event
|
||||
// has already been dispatched.
|
||||
//
|
||||
// The equeue_cancel function is irq safe.
|
||||
//
|
||||
// If called while the event queue's dispatch loop is active, equeue_cancel
|
||||
// does not guarantee that the event will not not execute after it returns as
|
||||
// the event may have already begun executing.
|
||||
void equeue_cancel(equeue_t *queue, int id);
|
||||
|
||||
// Background an event queue onto a single-shot timer
|
||||
//
|
||||
// The provided update function will be called to indicate when the queue
|
||||
// should be dispatched. A negative timeout will be passed to the update
|
||||
// function when the timer is no longer needed.
|
||||
//
|
||||
// Passing a null update function disables the existing timer.
|
||||
//
|
||||
// The equeue_background function allows an event queue to take advantage
|
||||
// of hardware timers or even other event loops, allowing an event queue to
|
||||
// be effectively backgrounded.
|
||||
void equeue_background(equeue_t *queue,
|
||||
void (*update)(void *timer, int ms), void *timer);
|
||||
|
||||
// Chain an event queue onto another event queue
|
||||
//
|
||||
// After chaining a queue to a target, calling equeue_dispatch on the
|
||||
// target queue will also dispatch events from this queue. The queues
|
||||
// use their own buffers and events must be managed independently.
|
||||
//
|
||||
// Passing a null queue as the target will unchain the existing queue.
|
||||
//
|
||||
// The equeue_chain function allows multiple equeues to be composed, sharing
|
||||
// the context of a dispatch loop while still being managed independently.
|
||||
void equeue_chain(equeue_t *queue, equeue_t *target);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Implementation for the mbed library
|
||||
* https://github.com/mbedmicro/mbed
|
||||
*
|
||||
* Copyright (c) 2016 Christopher Haster
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include "equeue_platform.h"
|
||||
|
||||
#if defined(EQUEUE_PLATFORM_MBED)
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "mbed.h"
|
||||
|
||||
|
||||
// Ticker operations
|
||||
static bool equeue_tick_inited = false;
|
||||
static unsigned equeue_minutes = 0;
|
||||
static unsigned equeue_timer[
|
||||
(sizeof(Timer)+sizeof(unsigned)-1)/sizeof(unsigned)];
|
||||
static unsigned equeue_ticker[
|
||||
(sizeof(Ticker)+sizeof(unsigned)-1)/sizeof(unsigned)];
|
||||
|
||||
static void equeue_tick_update() {
|
||||
reinterpret_cast<Timer*>(equeue_timer)->reset();
|
||||
equeue_minutes += 1;
|
||||
}
|
||||
|
||||
static void equeue_tick_init() {
|
||||
MBED_ASSERT(sizeof(equeue_timer) >= sizeof(Timer));
|
||||
MBED_ASSERT(sizeof(equeue_ticker) >= sizeof(Ticker));
|
||||
new (equeue_timer) Timer;
|
||||
new (equeue_ticker) Ticker;
|
||||
|
||||
equeue_minutes = 0;
|
||||
reinterpret_cast<Timer*>(equeue_timer)->start();
|
||||
reinterpret_cast<Ticker*>(equeue_ticker)
|
||||
->attach_us(equeue_tick_update, (1 << 16)*1000);
|
||||
|
||||
equeue_tick_inited = true;
|
||||
}
|
||||
|
||||
unsigned equeue_tick() {
|
||||
if (!equeue_tick_inited) {
|
||||
equeue_tick_init();
|
||||
}
|
||||
|
||||
unsigned equeue_ms = reinterpret_cast<Timer*>(equeue_timer)->read_ms();
|
||||
return (equeue_minutes << 16) + equeue_ms;
|
||||
}
|
||||
|
||||
|
||||
// Mutex operations
|
||||
int equeue_mutex_create(equeue_mutex_t *m) { return 0; }
|
||||
void equeue_mutex_destroy(equeue_mutex_t *m) { }
|
||||
|
||||
void equeue_mutex_lock(equeue_mutex_t *m) {
|
||||
core_util_critical_section_enter();
|
||||
}
|
||||
|
||||
void equeue_mutex_unlock(equeue_mutex_t *m) {
|
||||
core_util_critical_section_exit();
|
||||
}
|
||||
|
||||
|
||||
// Semaphore operations
|
||||
#ifdef MBED_CONF_RTOS_PRESENT
|
||||
|
||||
int equeue_sema_create(equeue_sema_t *s) {
|
||||
MBED_ASSERT(sizeof(equeue_sema_t) >= sizeof(Semaphore));
|
||||
new (s) Semaphore(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void equeue_sema_destroy(equeue_sema_t *s) {
|
||||
reinterpret_cast<Semaphore*>(s)->~Semaphore();
|
||||
}
|
||||
|
||||
void equeue_sema_signal(equeue_sema_t *s) {
|
||||
reinterpret_cast<Semaphore*>(s)->release();
|
||||
}
|
||||
|
||||
bool equeue_sema_wait(equeue_sema_t *s, int ms) {
|
||||
if (ms < 0) {
|
||||
ms = osWaitForever;
|
||||
}
|
||||
|
||||
return (reinterpret_cast<Semaphore*>(s)->wait(ms) > 0);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Semaphore operations
|
||||
int equeue_sema_create(equeue_sema_t *s) {
|
||||
*s = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void equeue_sema_destroy(equeue_sema_t *s) {
|
||||
}
|
||||
|
||||
void equeue_sema_signal(equeue_sema_t *s) {
|
||||
*s = 1;
|
||||
}
|
||||
|
||||
static void equeue_sema_timeout(equeue_sema_t *s) {
|
||||
*s = -1;
|
||||
}
|
||||
|
||||
bool equeue_sema_wait(equeue_sema_t *s, int ms) {
|
||||
int signal = 0;
|
||||
Timeout timeout;
|
||||
timeout.attach_us(s, equeue_sema_timeout, ms*1000);
|
||||
|
||||
core_util_critical_section_enter();
|
||||
while (!*s) {
|
||||
sleep();
|
||||
core_util_critical_section_exit();
|
||||
core_util_critical_section_enter();
|
||||
}
|
||||
|
||||
signal = *s;
|
||||
*s = false;
|
||||
core_util_critical_section_exit();
|
||||
|
||||
return (signal > 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* System specific implementation
|
||||
*
|
||||
* Copyright (c) 2016 Christopher Haster
|
||||
*
|
||||
* 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 EQUEUE_PLATFORM_H
|
||||
#define EQUEUE_PLATFORM_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Currently supported platforms
|
||||
//
|
||||
// Uncomment to select a supported platform or reimplement this file
|
||||
// for a specific target.
|
||||
//#define EQUEUE_PLATFORM_POSIX
|
||||
//#define EQUEUE_PLATFORM_MBED
|
||||
|
||||
// Try to infer a platform if none was manually selected
|
||||
#if !defined(EQUEUE_PLATFORM_POSIX) \
|
||||
&& !defined(EQUEUE_PLATFORM_MBED)
|
||||
#if defined(__unix__)
|
||||
#define EQUEUE_PLATFORM_POSIX
|
||||
#elif defined(__MBED__)
|
||||
#define EQUEUE_PLATFORM_MBED
|
||||
#else
|
||||
#warning "Unknown platform! Please update equeue_platform.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Platform includes
|
||||
#if defined(EQUEUE_PLATFORM_POSIX)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
|
||||
// Platform millisecond counter
|
||||
//
|
||||
// Return a tick that represents the number of milliseconds that have passed
|
||||
// since an arbitrary point in time. The granularity does not need to be at
|
||||
// the millisecond level, however the accuracy of the equeue library is
|
||||
// limited by the accuracy of this tick.
|
||||
//
|
||||
// Must intentionally overflow to 0 after 2^32-1
|
||||
unsigned equeue_tick(void);
|
||||
|
||||
|
||||
// Platform mutex type
|
||||
//
|
||||
// The equeue library requires at minimum a non-recursive mutex that is
|
||||
// safe in interrupt contexts. The mutex section is help for a bounded
|
||||
// amount of time, so simply disabling interrupts is acceptable
|
||||
//
|
||||
// If irq safety is not required, a regular blocking mutex can be used.
|
||||
#if defined(EQUEUE_PLATFORM_POSIX)
|
||||
typedef pthread_mutex_t equeue_mutex_t;
|
||||
#elif defined(EQUEUE_PLATFORM_WINDOWS)
|
||||
typedef CRITICAL_SECTION equeue_mutex_t;
|
||||
#elif defined(EQUEUE_PLATFORM_MBED)
|
||||
typedef unsigned equeue_mutex_t;
|
||||
#elif defined(EQUEUE_PLATFORM_FREERTOS)
|
||||
typedef UBaseType_t equeue_mutex_t;
|
||||
#endif
|
||||
|
||||
// Platform mutex operations
|
||||
//
|
||||
// The equeue_mutex_create and equeue_mutex_destroy manage the lifetime
|
||||
// of the mutex. On error, equeue_mutex_create should return a negative
|
||||
// error code.
|
||||
//
|
||||
// The equeue_mutex_lock and equeue_mutex_unlock lock and unlock the
|
||||
// underlying mutex.
|
||||
int equeue_mutex_create(equeue_mutex_t *mutex);
|
||||
void equeue_mutex_destroy(equeue_mutex_t *mutex);
|
||||
void equeue_mutex_lock(equeue_mutex_t *mutex);
|
||||
void equeue_mutex_unlock(equeue_mutex_t *mutex);
|
||||
|
||||
|
||||
// Platform semaphore type
|
||||
//
|
||||
// The equeue library requires a binary semaphore type that can be safely
|
||||
// signaled from interrupt contexts and from inside a equeue_mutex section.
|
||||
//
|
||||
// The equeue_signal_wait is relied upon by the equeue library to sleep the
|
||||
// processor between events. Spurious wakeups have no negative-effects.
|
||||
//
|
||||
// A counting semaphore will also work, however may cause the event queue
|
||||
// dispatch loop to run unnecessarily. For that matter, equeue_signal_wait
|
||||
// may even be implemented as a single return statement.
|
||||
#if defined(EQUEUE_PLATFORM_POSIX)
|
||||
typedef struct equeue_sema {
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
bool signal;
|
||||
} equeue_sema_t;
|
||||
#elif defined(EQUEUE_PLATFORM_MBED) && defined(MBED_CONF_RTOS_PRESENT)
|
||||
typedef unsigned equeue_sema_t[8];
|
||||
#elif defined(EQUEUE_PLATFORM_MBED)
|
||||
typedef volatile int equeue_sema_t;
|
||||
#endif
|
||||
|
||||
// Platform semaphore operations
|
||||
//
|
||||
// The equeue_sema_create and equeue_sema_destroy manage the lifetime
|
||||
// of the semaphore. On error, equeue_sema_create should return a negative
|
||||
// error code.
|
||||
//
|
||||
// The equeue_sema_signal marks a semaphore as signalled such that the next
|
||||
// equeue_sema_wait will return true.
|
||||
//
|
||||
// The equeue_sema_wait waits for a semaphore to be signalled or returns
|
||||
// immediately if equeue_sema_signal had been called since the last
|
||||
// equeue_sema_wait. The equeue_sema_wait returns true if it detected that
|
||||
// equeue_sema_signal had been called.
|
||||
int equeue_sema_create(equeue_sema_t *sema);
|
||||
void equeue_sema_destroy(equeue_sema_t *sema);
|
||||
void equeue_sema_signal(equeue_sema_t *sema);
|
||||
bool equeue_sema_wait(equeue_sema_t *sema, int ms);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Implementation for Posix compliant platforms
|
||||
*
|
||||
* Copyright (c) 2016 Christopher Haster
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include "equeue_platform.h"
|
||||
|
||||
#if defined(EQUEUE_PLATFORM_POSIX)
|
||||
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <errno.h>
|
||||
|
||||
|
||||
// Tick operations
|
||||
unsigned equeue_tick(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
return (unsigned)(tv.tv_sec*1000 + tv.tv_usec/1000);
|
||||
}
|
||||
|
||||
|
||||
// Mutex operations
|
||||
int equeue_mutex_create(equeue_mutex_t *m) {
|
||||
return pthread_mutex_init(m, 0);
|
||||
}
|
||||
|
||||
void equeue_mutex_destroy(equeue_mutex_t *m) {
|
||||
pthread_mutex_destroy(m);
|
||||
}
|
||||
|
||||
void equeue_mutex_lock(equeue_mutex_t *m) {
|
||||
pthread_mutex_lock(m);
|
||||
}
|
||||
|
||||
void equeue_mutex_unlock(equeue_mutex_t *m) {
|
||||
pthread_mutex_unlock(m);
|
||||
}
|
||||
|
||||
|
||||
// Semaphore operations
|
||||
int equeue_sema_create(equeue_sema_t *s) {
|
||||
int err = pthread_mutex_init(&s->mutex, 0);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = pthread_cond_init(&s->cond, 0);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
s->signal = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void equeue_sema_destroy(equeue_sema_t *s) {
|
||||
pthread_cond_destroy(&s->cond);
|
||||
pthread_mutex_destroy(&s->mutex);
|
||||
}
|
||||
|
||||
void equeue_sema_signal(equeue_sema_t *s) {
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
s->signal = true;
|
||||
pthread_cond_signal(&s->cond);
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
}
|
||||
|
||||
bool equeue_sema_wait(equeue_sema_t *s, int ms) {
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (!s->signal) {
|
||||
if (ms < 0) {
|
||||
pthread_cond_wait(&s->cond, &s->mutex);
|
||||
} else {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
|
||||
struct timespec ts = {
|
||||
.tv_sec = ms/1000 + tv.tv_sec,
|
||||
.tv_nsec = ms*1000000 + tv.tv_usec*1000,
|
||||
};
|
||||
|
||||
pthread_cond_timedwait(&s->cond, &s->mutex, &ts);
|
||||
}
|
||||
}
|
||||
|
||||
bool signal = s->signal;
|
||||
s->signal = false;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
return signal;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
* Profiling framework for the events library
|
||||
*
|
||||
* Copyright (c) 2016 Christopher Haster
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include "equeue.h"
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
|
||||
// Performance measurement utils
|
||||
#define PROF_RUNS 5
|
||||
#define PROF_INTERVAL 100000000
|
||||
|
||||
#define prof_volatile(t) __attribute__((unused)) volatile t
|
||||
|
||||
typedef uint64_t prof_cycle_t;
|
||||
|
||||
static volatile prof_cycle_t prof_start_cycle;
|
||||
static volatile prof_cycle_t prof_stop_cycle;
|
||||
static prof_cycle_t prof_accum_cycle;
|
||||
static prof_cycle_t prof_baseline_cycle;
|
||||
static prof_cycle_t prof_iterations;
|
||||
static const char *prof_units;
|
||||
|
||||
#define prof_cycle() ({ \
|
||||
uint32_t a, b; \
|
||||
__asm__ volatile ("rdtsc" : "=a" (a), "=d" (b)); \
|
||||
((uint64_t)b << 32) | (uint64_t)a; \
|
||||
})
|
||||
|
||||
#define prof_loop() \
|
||||
for (prof_iterations = 0; \
|
||||
prof_accum_cycle < PROF_INTERVAL; \
|
||||
prof_iterations++)
|
||||
|
||||
#define prof_start() ({ \
|
||||
prof_start_cycle = prof_cycle(); \
|
||||
})
|
||||
|
||||
#define prof_stop() ({ \
|
||||
prof_stop_cycle = prof_cycle(); \
|
||||
prof_accum_cycle += prof_stop_cycle - prof_start_cycle; \
|
||||
})
|
||||
|
||||
#define prof_result(value, units) ({ \
|
||||
prof_accum_cycle = value+prof_baseline_cycle; \
|
||||
prof_iterations = 1; \
|
||||
prof_units = units; \
|
||||
})
|
||||
|
||||
#define prof_measure(func, ...) ({ \
|
||||
printf("%s: ...", #func); \
|
||||
fflush(stdout); \
|
||||
\
|
||||
prof_units = "cycles"; \
|
||||
prof_cycle_t runs[PROF_RUNS]; \
|
||||
for (int i = 0; i < PROF_RUNS; i++) { \
|
||||
prof_accum_cycle = 0; \
|
||||
prof_iterations = 0; \
|
||||
func(__VA_ARGS__); \
|
||||
runs[i] = prof_accum_cycle / prof_iterations; \
|
||||
} \
|
||||
\
|
||||
prof_cycle_t res = runs[0]; \
|
||||
for (int i = 0; i < PROF_RUNS; i++) { \
|
||||
if (runs[i] < res) { \
|
||||
res = runs[i]; \
|
||||
} \
|
||||
} \
|
||||
res -= prof_baseline_cycle; \
|
||||
printf("\r%s: %"PRIu64" %s", #func, res, prof_units); \
|
||||
\
|
||||
if (!isatty(0)) { \
|
||||
prof_cycle_t prev; \
|
||||
while (scanf("%*[^0-9]%"PRIu64, &prev) == 0); \
|
||||
int64_t perc = 100*((int64_t)prev - (int64_t)res) / (int64_t)prev; \
|
||||
\
|
||||
if (perc > 10) { \
|
||||
printf(" (\e[32m%+"PRId64"%%\e[0m)", perc); \
|
||||
} else if (perc < -10) { \
|
||||
printf(" (\e[31m%+"PRId64"%%\e[0m)", perc); \
|
||||
} else { \
|
||||
printf(" (%+"PRId64"%%)", perc); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
printf("\n"); \
|
||||
res; \
|
||||
})
|
||||
|
||||
#define prof_baseline(func, ...) ({ \
|
||||
prof_baseline_cycle = 0; \
|
||||
prof_baseline_cycle = prof_measure(func, __VA_ARGS__); \
|
||||
})
|
||||
|
||||
|
||||
// Various test functions
|
||||
void no_func(void *eh) {
|
||||
}
|
||||
|
||||
|
||||
// Actual performance tests
|
||||
void baseline_prof(void) {
|
||||
prof_loop() {
|
||||
prof_start();
|
||||
__asm__ volatile ("");
|
||||
prof_stop();
|
||||
}
|
||||
}
|
||||
|
||||
void equeue_tick_prof(void) {
|
||||
prof_volatile(unsigned) res;
|
||||
prof_loop() {
|
||||
prof_start();
|
||||
res = equeue_tick();
|
||||
prof_stop();
|
||||
}
|
||||
}
|
||||
|
||||
void equeue_alloc_prof(void) {
|
||||
struct equeue q;
|
||||
equeue_create(&q, 32*EQUEUE_EVENT_SIZE);
|
||||
|
||||
prof_loop() {
|
||||
prof_start();
|
||||
void *e = equeue_alloc(&q, 8 * sizeof(int));
|
||||
prof_stop();
|
||||
|
||||
equeue_dealloc(&q, e);
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_alloc_many_prof(int count) {
|
||||
struct equeue q;
|
||||
equeue_create(&q, count*EQUEUE_EVENT_SIZE);
|
||||
|
||||
void *es[count];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
es[i] = equeue_alloc(&q, (i % 4) * sizeof(int));
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
equeue_dealloc(&q, es[i]);
|
||||
}
|
||||
|
||||
prof_loop() {
|
||||
prof_start();
|
||||
void *e = equeue_alloc(&q, 8 * sizeof(int));
|
||||
prof_stop();
|
||||
|
||||
equeue_dealloc(&q, e);
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_post_prof(void) {
|
||||
struct equeue q;
|
||||
equeue_create(&q, EQUEUE_EVENT_SIZE);
|
||||
|
||||
prof_loop() {
|
||||
void *e = equeue_alloc(&q, 0);
|
||||
|
||||
prof_start();
|
||||
int id = equeue_post(&q, no_func, e);
|
||||
prof_stop();
|
||||
|
||||
equeue_cancel(&q, id);
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_post_many_prof(int count) {
|
||||
struct equeue q;
|
||||
equeue_create(&q, count*EQUEUE_EVENT_SIZE);
|
||||
|
||||
for (int i = 0; i < count-1; i++) {
|
||||
equeue_call(&q, no_func, 0);
|
||||
}
|
||||
|
||||
prof_loop() {
|
||||
void *e = equeue_alloc(&q, 0);
|
||||
|
||||
prof_start();
|
||||
int id = equeue_post(&q, no_func, e);
|
||||
prof_stop();
|
||||
|
||||
equeue_cancel(&q, id);
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_post_future_prof(void) {
|
||||
struct equeue q;
|
||||
equeue_create(&q, EQUEUE_EVENT_SIZE);
|
||||
|
||||
prof_loop() {
|
||||
void *e = equeue_alloc(&q, 0);
|
||||
equeue_event_delay(e, 1000);
|
||||
|
||||
prof_start();
|
||||
int id = equeue_post(&q, no_func, e);
|
||||
prof_stop();
|
||||
|
||||
equeue_cancel(&q, id);
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_post_future_many_prof(int count) {
|
||||
struct equeue q;
|
||||
equeue_create(&q, count*EQUEUE_EVENT_SIZE);
|
||||
|
||||
for (int i = 0; i < count-1; i++) {
|
||||
equeue_call(&q, no_func, 0);
|
||||
}
|
||||
|
||||
prof_loop() {
|
||||
void *e = equeue_alloc(&q, 0);
|
||||
equeue_event_delay(e, 1000);
|
||||
|
||||
prof_start();
|
||||
int id = equeue_post(&q, no_func, e);
|
||||
prof_stop();
|
||||
|
||||
equeue_cancel(&q, id);
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_dispatch_prof(void) {
|
||||
struct equeue q;
|
||||
equeue_create(&q, EQUEUE_EVENT_SIZE);
|
||||
|
||||
prof_loop() {
|
||||
equeue_call(&q, no_func, 0);
|
||||
|
||||
prof_start();
|
||||
equeue_dispatch(&q, 0);
|
||||
prof_stop();
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_dispatch_many_prof(int count) {
|
||||
struct equeue q;
|
||||
equeue_create(&q, count*EQUEUE_EVENT_SIZE);
|
||||
|
||||
prof_loop() {
|
||||
for (int i = 0; i < count; i++) {
|
||||
equeue_call(&q, no_func, 0);
|
||||
}
|
||||
|
||||
prof_start();
|
||||
equeue_dispatch(&q, 0);
|
||||
prof_stop();
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_cancel_prof(void) {
|
||||
struct equeue q;
|
||||
equeue_create(&q, EQUEUE_EVENT_SIZE);
|
||||
|
||||
prof_loop() {
|
||||
int id = equeue_call(&q, no_func, 0);
|
||||
|
||||
prof_start();
|
||||
equeue_cancel(&q, id);
|
||||
prof_stop();
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_cancel_many_prof(int count) {
|
||||
struct equeue q;
|
||||
equeue_create(&q, count*EQUEUE_EVENT_SIZE);
|
||||
|
||||
for (int i = 0; i < count-1; i++) {
|
||||
equeue_call(&q, no_func, 0);
|
||||
}
|
||||
|
||||
prof_loop() {
|
||||
int id = equeue_call(&q, no_func, 0);
|
||||
|
||||
prof_start();
|
||||
equeue_cancel(&q, id);
|
||||
prof_stop();
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_alloc_size_prof(void) {
|
||||
size_t size = 32*EQUEUE_EVENT_SIZE;
|
||||
|
||||
struct equeue q;
|
||||
equeue_create(&q, size);
|
||||
equeue_alloc(&q, 0);
|
||||
|
||||
prof_result(size - q.slab.size, "bytes");
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_alloc_many_size_prof(int count) {
|
||||
size_t size = count*EQUEUE_EVENT_SIZE;
|
||||
|
||||
struct equeue q;
|
||||
equeue_create(&q, size);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
equeue_alloc(&q, (i % 4) * sizeof(int));
|
||||
}
|
||||
|
||||
prof_result(size - q.slab.size, "bytes");
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void equeue_alloc_fragmented_size_prof(int count) {
|
||||
size_t size = count*EQUEUE_EVENT_SIZE;
|
||||
|
||||
struct equeue q;
|
||||
equeue_create(&q, size);
|
||||
|
||||
void *es[count];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
es[i] = equeue_alloc(&q, (i % 4) * sizeof(int));
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
equeue_dealloc(&q, es[i]);
|
||||
}
|
||||
|
||||
for (int i = count-1; i >= 0; i--) {
|
||||
es[i] = equeue_alloc(&q, (i % 4) * sizeof(int));
|
||||
}
|
||||
|
||||
for (int i = count-1; i >= 0; i--) {
|
||||
equeue_dealloc(&q, es[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
equeue_alloc(&q, (i % 4) * sizeof(int));
|
||||
}
|
||||
|
||||
prof_result(size - q.slab.size, "bytes");
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
|
||||
// Entry point
|
||||
int main() {
|
||||
printf("beginning profiling...\n");
|
||||
|
||||
prof_baseline(baseline_prof);
|
||||
|
||||
prof_measure(equeue_tick_prof);
|
||||
prof_measure(equeue_alloc_prof);
|
||||
prof_measure(equeue_post_prof);
|
||||
prof_measure(equeue_post_future_prof);
|
||||
prof_measure(equeue_dispatch_prof);
|
||||
prof_measure(equeue_cancel_prof);
|
||||
|
||||
prof_measure(equeue_alloc_many_prof, 1000);
|
||||
prof_measure(equeue_post_many_prof, 1000);
|
||||
prof_measure(equeue_post_future_many_prof, 1000);
|
||||
prof_measure(equeue_dispatch_many_prof, 100);
|
||||
prof_measure(equeue_cancel_many_prof, 100);
|
||||
|
||||
prof_measure(equeue_alloc_size_prof);
|
||||
prof_measure(equeue_alloc_many_size_prof, 1000);
|
||||
prof_measure(equeue_alloc_fragmented_size_prof, 1000);
|
||||
|
||||
printf("done!\n");
|
||||
}
|
|
@ -0,0 +1,681 @@
|
|||
/*
|
||||
* Testing framework for the events library
|
||||
*
|
||||
* Copyright (c) 2016 Christopher Haster
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include "equeue.h"
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
|
||||
|
||||
// Testing setup
|
||||
static jmp_buf test_buf;
|
||||
static int test_line;
|
||||
static int test_failure;
|
||||
|
||||
#define test_assert(test) ({ \
|
||||
if (!(test)) { \
|
||||
test_line = __LINE__; \
|
||||
longjmp(test_buf, 1); \
|
||||
} \
|
||||
})
|
||||
|
||||
#define test_run(func, ...) ({ \
|
||||
printf("%s: ...", #func); \
|
||||
fflush(stdout); \
|
||||
\
|
||||
if (!setjmp(test_buf)) { \
|
||||
func(__VA_ARGS__); \
|
||||
printf("\r%s: \e[32mpassed\e[0m\n", #func); \
|
||||
} else { \
|
||||
printf("\r%s: \e[31mfailed\e[0m at line %d\n", #func, test_line); \
|
||||
test_failure = true; \
|
||||
} \
|
||||
})
|
||||
|
||||
|
||||
// Test functions
|
||||
void pass_func(void *eh) {
|
||||
}
|
||||
|
||||
void simple_func(void *p) {
|
||||
(*(int *)p)++;
|
||||
}
|
||||
|
||||
void sloth_func(void *p) {
|
||||
usleep(10000);
|
||||
(*(int *)p)++;
|
||||
}
|
||||
|
||||
struct indirect {
|
||||
int *touched;
|
||||
uint8_t buffer[7];
|
||||
};
|
||||
|
||||
void indirect_func(void *p) {
|
||||
struct indirect *i = (struct indirect*)p;
|
||||
(*i->touched)++;
|
||||
}
|
||||
|
||||
struct timing {
|
||||
unsigned tick;
|
||||
unsigned delay;
|
||||
};
|
||||
|
||||
void timing_func(void *p) {
|
||||
struct timing *timing = (struct timing*)p;
|
||||
unsigned tick = equeue_tick();
|
||||
|
||||
unsigned t1 = timing->delay;
|
||||
unsigned t2 = tick - timing->tick;
|
||||
test_assert(t1 > t2 - 10 && t1 < t2 + 10);
|
||||
|
||||
timing->tick = tick;
|
||||
}
|
||||
|
||||
struct fragment {
|
||||
equeue_t *q;
|
||||
size_t size;
|
||||
struct timing timing;
|
||||
};
|
||||
|
||||
void fragment_func(void *p) {
|
||||
struct fragment *fragment = (struct fragment*)p;
|
||||
timing_func(&fragment->timing);
|
||||
|
||||
struct fragment *nfragment = equeue_alloc(fragment->q, fragment->size);
|
||||
test_assert(nfragment);
|
||||
|
||||
*nfragment = *fragment;
|
||||
equeue_event_delay(nfragment, fragment->timing.delay);
|
||||
|
||||
int id = equeue_post(nfragment->q, fragment_func, nfragment);
|
||||
test_assert(id);
|
||||
}
|
||||
|
||||
struct cancel {
|
||||
equeue_t *q;
|
||||
int id;
|
||||
};
|
||||
|
||||
void cancel_func(void *p) {
|
||||
struct cancel *cancel = (struct cancel *)p;
|
||||
equeue_cancel(cancel->q, cancel->id);
|
||||
}
|
||||
|
||||
struct nest {
|
||||
equeue_t *q;
|
||||
void (*cb)(void *);
|
||||
void *data;
|
||||
};
|
||||
|
||||
void nest_func(void *p) {
|
||||
struct nest *nest = (struct nest *)p;
|
||||
equeue_call(nest->q, nest->cb, nest->data);
|
||||
|
||||
usleep(10000);
|
||||
}
|
||||
|
||||
|
||||
// Simple call tests
|
||||
void simple_call_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
bool touched = false;
|
||||
equeue_call(&q, simple_func, &touched);
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(touched);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void simple_call_in_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
bool touched = false;
|
||||
int id = equeue_call_in(&q, 10, simple_func, &touched);
|
||||
test_assert(id);
|
||||
|
||||
equeue_dispatch(&q, 15);
|
||||
test_assert(touched);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void simple_call_every_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
bool touched = false;
|
||||
int id = equeue_call_every(&q, 10, simple_func, &touched);
|
||||
test_assert(id);
|
||||
|
||||
equeue_dispatch(&q, 15);
|
||||
test_assert(touched);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void simple_post_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
int touched = false;
|
||||
struct indirect *i = equeue_alloc(&q, sizeof(struct indirect));
|
||||
test_assert(i);
|
||||
|
||||
i->touched = &touched;
|
||||
int id = equeue_post(&q, indirect_func, i);
|
||||
test_assert(id);
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(*i->touched);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
// Misc tests
|
||||
void destructor_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
int touched;
|
||||
struct indirect *e;
|
||||
int ids[3];
|
||||
|
||||
touched = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
e = equeue_alloc(&q, sizeof(struct indirect));
|
||||
test_assert(e);
|
||||
|
||||
e->touched = &touched;
|
||||
equeue_event_dtor(e, indirect_func);
|
||||
int id = equeue_post(&q, pass_func, e);
|
||||
test_assert(id);
|
||||
}
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(touched == 3);
|
||||
|
||||
touched = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
e = equeue_alloc(&q, sizeof(struct indirect));
|
||||
test_assert(e);
|
||||
|
||||
e->touched = &touched;
|
||||
equeue_event_dtor(e, indirect_func);
|
||||
ids[i] = equeue_post(&q, pass_func, e);
|
||||
test_assert(ids[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
equeue_cancel(&q, ids[i]);
|
||||
}
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(touched == 3);
|
||||
|
||||
touched = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
e = equeue_alloc(&q, sizeof(struct indirect));
|
||||
test_assert(e);
|
||||
|
||||
e->touched = &touched;
|
||||
equeue_event_dtor(e, indirect_func);
|
||||
int id = equeue_post(&q, pass_func, e);
|
||||
test_assert(id);
|
||||
}
|
||||
|
||||
equeue_destroy(&q);
|
||||
test_assert(touched == 3);
|
||||
}
|
||||
|
||||
void allocation_failure_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
void *p = equeue_alloc(&q, 4096);
|
||||
test_assert(!p);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
p = equeue_alloc(&q, 0);
|
||||
}
|
||||
test_assert(!p);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void cancel_test(int N) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
bool touched = false;
|
||||
int *ids = malloc(N*sizeof(int));
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
ids[i] = equeue_call(&q, simple_func, &touched);
|
||||
}
|
||||
|
||||
for (int i = N-1; i >= 0; i--) {
|
||||
equeue_cancel(&q, ids[i]);
|
||||
}
|
||||
|
||||
free(ids);
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(!touched);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void cancel_inflight_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
bool touched = false;
|
||||
|
||||
int id = equeue_call(&q, simple_func, &touched);
|
||||
equeue_cancel(&q, id);
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(!touched);
|
||||
|
||||
id = equeue_call(&q, simple_func, &touched);
|
||||
equeue_cancel(&q, id);
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(!touched);
|
||||
|
||||
struct cancel *cancel = equeue_alloc(&q, sizeof(struct cancel));
|
||||
test_assert(cancel);
|
||||
cancel->q = &q;
|
||||
cancel->id = 0;
|
||||
|
||||
id = equeue_post(&q, cancel_func, cancel);
|
||||
test_assert(id);
|
||||
|
||||
cancel->id = equeue_call(&q, simple_func, &touched);
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(!touched);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void cancel_unnecessarily_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
int id = equeue_call(&q, pass_func, 0);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
equeue_cancel(&q, id);
|
||||
}
|
||||
|
||||
id = equeue_call(&q, pass_func, 0);
|
||||
equeue_dispatch(&q, 0);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
equeue_cancel(&q, id);
|
||||
}
|
||||
|
||||
bool touched = false;
|
||||
equeue_call(&q, simple_func, &touched);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
equeue_cancel(&q, id);
|
||||
}
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(touched);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void loop_protect_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
bool touched = false;
|
||||
equeue_call_every(&q, 0, simple_func, &touched);
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(touched);
|
||||
|
||||
touched = false;
|
||||
equeue_call_every(&q, 1, simple_func, &touched);
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(touched);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void break_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
bool touched = false;
|
||||
equeue_call_every(&q, 0, simple_func, &touched);
|
||||
|
||||
equeue_break(&q);
|
||||
equeue_dispatch(&q, -1);
|
||||
test_assert(touched);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void period_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
int count = 0;
|
||||
equeue_call_every(&q, 10, simple_func, &count);
|
||||
|
||||
equeue_dispatch(&q, 55);
|
||||
test_assert(count == 5);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void nested_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
int touched = 0;
|
||||
struct nest *nest = equeue_alloc(&q, sizeof(struct nest));
|
||||
test_assert(nest);
|
||||
nest->q = &q;
|
||||
nest->cb = simple_func;
|
||||
nest->data = &touched;
|
||||
|
||||
int id = equeue_post(&q, nest_func, nest);
|
||||
test_assert(id);
|
||||
|
||||
equeue_dispatch(&q, 5);
|
||||
test_assert(touched == 0);
|
||||
|
||||
equeue_dispatch(&q, 5);
|
||||
test_assert(touched == 1);
|
||||
|
||||
touched = 0;
|
||||
nest = equeue_alloc(&q, sizeof(struct nest));
|
||||
test_assert(nest);
|
||||
nest->q = &q;
|
||||
nest->cb = simple_func;
|
||||
nest->data = &touched;
|
||||
|
||||
id = equeue_post(&q, nest_func, nest);
|
||||
test_assert(id);
|
||||
|
||||
equeue_dispatch(&q, 20);
|
||||
test_assert(touched == 1);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void sloth_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
int touched = 0;
|
||||
int id = equeue_call(&q, sloth_func, &touched);
|
||||
test_assert(id);
|
||||
|
||||
id = equeue_call_in(&q, 5, simple_func, &touched);
|
||||
test_assert(id);
|
||||
|
||||
id = equeue_call_in(&q, 15, simple_func, &touched);
|
||||
test_assert(id);
|
||||
|
||||
equeue_dispatch(&q, 20);
|
||||
test_assert(touched == 3);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void *multithread_thread(void *p) {
|
||||
equeue_t *q = (equeue_t *)p;
|
||||
equeue_dispatch(q, -1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void multithread_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
int touched = 0;
|
||||
equeue_call_every(&q, 1, simple_func, &touched);
|
||||
|
||||
pthread_t thread;
|
||||
err = pthread_create(&thread, 0, multithread_thread, &q);
|
||||
test_assert(!err);
|
||||
|
||||
usleep(10000);
|
||||
equeue_break(&q);
|
||||
err = pthread_join(thread, 0);
|
||||
test_assert(!err);
|
||||
|
||||
test_assert(touched);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void background_func(void *p, int ms) {
|
||||
*(unsigned *)p = ms;
|
||||
}
|
||||
|
||||
void background_test(void) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
int id = equeue_call_in(&q, 20, pass_func, 0);
|
||||
test_assert(id);
|
||||
|
||||
unsigned ms;
|
||||
equeue_background(&q, background_func, &ms);
|
||||
test_assert(ms == 20);
|
||||
|
||||
id = equeue_call_in(&q, 10, pass_func, 0);
|
||||
test_assert(id);
|
||||
test_assert(ms == 10);
|
||||
|
||||
id = equeue_call(&q, pass_func, 0);
|
||||
test_assert(id);
|
||||
test_assert(ms == 0);
|
||||
|
||||
equeue_dispatch(&q, 0);
|
||||
test_assert(ms == 10);
|
||||
|
||||
equeue_destroy(&q);
|
||||
test_assert(ms == -1);
|
||||
}
|
||||
|
||||
void chain_test(void) {
|
||||
equeue_t q1;
|
||||
int err = equeue_create(&q1, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
equeue_t q2;
|
||||
err = equeue_create(&q2, 2048);
|
||||
test_assert(!err);
|
||||
|
||||
equeue_chain(&q2, &q1);
|
||||
|
||||
int touched = 0;
|
||||
|
||||
int id1 = equeue_call_in(&q1, 20, simple_func, &touched);
|
||||
int id2 = equeue_call_in(&q2, 20, simple_func, &touched);
|
||||
test_assert(id1 && id2);
|
||||
|
||||
id1 = equeue_call(&q1, simple_func, &touched);
|
||||
id2 = equeue_call(&q2, simple_func, &touched);
|
||||
test_assert(id1 && id2);
|
||||
|
||||
id1 = equeue_call_in(&q1, 5, simple_func, &touched);
|
||||
id2 = equeue_call_in(&q2, 5, simple_func, &touched);
|
||||
test_assert(id1 && id2);
|
||||
|
||||
equeue_cancel(&q1, id1);
|
||||
equeue_cancel(&q2, id2);
|
||||
|
||||
id1 = equeue_call_in(&q1, 10, simple_func, &touched);
|
||||
id2 = equeue_call_in(&q2, 10, simple_func, &touched);
|
||||
test_assert(id1 && id2);
|
||||
|
||||
equeue_dispatch(&q1, 30);
|
||||
|
||||
test_assert(touched == 6);
|
||||
}
|
||||
|
||||
// Barrage tests
|
||||
void simple_barrage_test(int N) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, N*(EQUEUE_EVENT_SIZE+sizeof(struct timing)));
|
||||
test_assert(!err);
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
struct timing *timing = equeue_alloc(&q, sizeof(struct timing));
|
||||
test_assert(timing);
|
||||
|
||||
timing->tick = equeue_tick();
|
||||
timing->delay = (i+1)*100;
|
||||
equeue_event_delay(timing, timing->delay);
|
||||
equeue_event_period(timing, timing->delay);
|
||||
|
||||
int id = equeue_post(&q, timing_func, timing);
|
||||
test_assert(id);
|
||||
}
|
||||
|
||||
equeue_dispatch(&q, N*100);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
void fragmenting_barrage_test(int N) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q,
|
||||
2*N*(EQUEUE_EVENT_SIZE+sizeof(struct fragment)+N*sizeof(int)));
|
||||
test_assert(!err);
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
size_t size = sizeof(struct fragment) + i*sizeof(int);
|
||||
struct fragment *fragment = equeue_alloc(&q, size);
|
||||
test_assert(fragment);
|
||||
|
||||
fragment->q = &q;
|
||||
fragment->size = size;
|
||||
fragment->timing.tick = equeue_tick();
|
||||
fragment->timing.delay = (i+1)*100;
|
||||
equeue_event_delay(fragment, fragment->timing.delay);
|
||||
|
||||
int id = equeue_post(&q, fragment_func, fragment);
|
||||
test_assert(id);
|
||||
}
|
||||
|
||||
equeue_dispatch(&q, N*100);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
struct ethread {
|
||||
pthread_t thread;
|
||||
equeue_t *q;
|
||||
int ms;
|
||||
};
|
||||
|
||||
static void *ethread_dispatch(void *p) {
|
||||
struct ethread *t = (struct ethread*)p;
|
||||
equeue_dispatch(t->q, t->ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void multithreaded_barrage_test(int N) {
|
||||
equeue_t q;
|
||||
int err = equeue_create(&q, N*(EQUEUE_EVENT_SIZE+sizeof(struct timing)));
|
||||
test_assert(!err);
|
||||
|
||||
struct ethread t;
|
||||
t.q = &q;
|
||||
t.ms = N*100;
|
||||
err = pthread_create(&t.thread, 0, ethread_dispatch, &t);
|
||||
test_assert(!err);
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
struct timing *timing = equeue_alloc(&q, sizeof(struct timing));
|
||||
test_assert(timing);
|
||||
|
||||
timing->tick = equeue_tick();
|
||||
timing->delay = (i+1)*100;
|
||||
equeue_event_delay(timing, timing->delay);
|
||||
equeue_event_period(timing, timing->delay);
|
||||
|
||||
int id = equeue_post(&q, timing_func, timing);
|
||||
test_assert(id);
|
||||
}
|
||||
|
||||
err = pthread_join(t.thread, 0);
|
||||
test_assert(!err);
|
||||
|
||||
equeue_destroy(&q);
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
printf("beginning tests...\n");
|
||||
|
||||
test_run(simple_call_test);
|
||||
test_run(simple_call_in_test);
|
||||
test_run(simple_call_every_test);
|
||||
test_run(simple_post_test);
|
||||
test_run(destructor_test);
|
||||
test_run(allocation_failure_test);
|
||||
test_run(cancel_test, 20);
|
||||
test_run(cancel_inflight_test);
|
||||
test_run(cancel_unnecessarily_test);
|
||||
test_run(loop_protect_test);
|
||||
test_run(break_test);
|
||||
test_run(period_test);
|
||||
test_run(nested_test);
|
||||
test_run(sloth_test);
|
||||
test_run(background_test);
|
||||
test_run(chain_test);
|
||||
test_run(multithread_test);
|
||||
test_run(simple_barrage_test, 20);
|
||||
test_run(fragmenting_barrage_test, 20);
|
||||
test_run(multithreaded_barrage_test, 20);
|
||||
|
||||
printf("done!\n");
|
||||
return test_failure;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/* events
|
||||
* Copyright (c) 2016 ARM Limited
|
||||
*
|
||||
* 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 MBED_EVENTS_H
|
||||
#define MBED_EVENTS_H
|
||||
|
||||
|
||||
#include "equeue/equeue.h"
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "EventQueue.h"
|
||||
#include "Event.h"
|
||||
|
||||
using namespace events;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue