初始化提交
This commit is contained in:
502
arduino-cli/libraries/CoopTask/LICENSE
Normal file
502
arduino-cli/libraries/CoopTask/LICENSE
Normal file
@@ -0,0 +1,502 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
123
arduino-cli/libraries/CoopTask/README.md
Normal file
123
arduino-cli/libraries/CoopTask/README.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Warning
|
||||
In this release on ESP8266 scanning WiFi networks and WPS are both broken in CoopTask tasks.
|
||||
Using these features directly from loop() is unaffected.
|
||||
This warning will be silently removed once ESP8266 Git master has accepted the necessary PR.
|
||||
|
||||
# CoopTask
|
||||
|
||||
An all-C++ implementation of a cooperative multitasking layer for ESP8266/ESP32,
|
||||
Arduino boards, Linux, and Windows x86 and x86_64
|
||||
|
||||
During regular development it's built and tested on the ESP MCUs and
|
||||
Arduino Pro/Pro Mini.
|
||||
|
||||
Tasks in this scheduler are stackful coroutines. They act almost the same as
|
||||
the main ``setup()``/``loop()`` code in Arduino sketches, but there can be many of them
|
||||
simultaneously on the same device. It's even powerful enough to run the
|
||||
ESP8266 and ESP32 WebServer in a CoopTask.
|
||||
|
||||
Use the normal global delay() function to suspend execution of a task for the
|
||||
given number of milliseconds, use yield() to give up the CPU, both return after
|
||||
other cooperative tasks have run awhile.
|
||||
|
||||
A simple blink task can be written just like this:
|
||||
|
||||
```
|
||||
#include <CoopTask.h>
|
||||
|
||||
int loopBlink()
|
||||
{
|
||||
// like setup():
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
|
||||
// like loop():
|
||||
for (;;)
|
||||
{
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
delay(2000);
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
delay(3000);
|
||||
}
|
||||
|
||||
// tasks can return or exit() and leave an exit code
|
||||
return 0;
|
||||
}
|
||||
|
||||
CoopTask<>* taskBlink;
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
taskBlink = createCoopTask(F("Blink"), loopBlink, 0x240);
|
||||
#else
|
||||
taskBlink = createCoopTask(F("Blink"), loopBlink, 0x40);
|
||||
#endif
|
||||
if (!taskBlink) Serial.println("CoopTask Blink not created");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
runCoopTasks();
|
||||
}
|
||||
```
|
||||
|
||||
The ``runCoopTasks()`` scheduling helper has two optional callback arguments.
|
||||
|
||||
The first, ``reaper``, gets called each time a task exits. Retrieving the
|
||||
exit code or deleting the CoopTask object would typically be performed in a
|
||||
task reaper function.
|
||||
|
||||
The second callback, ``onDelay``, is called after each scheduling rountrip with the
|
||||
total minimum delay (can be zero) of all managed tasks. A use scenario for this
|
||||
is to put the MCU into a power saving sleep mode for the given duration.
|
||||
|
||||
## Using Arduino or Linux default loop stack space for CoopTask
|
||||
Given that CoopTasks are scheduled from the Arduino default ``loop()`` or the
|
||||
``main()`` function on Linux, any code in these functions is non-cooperative.
|
||||
This is great for incompatible sketches or libraries, but otherwise puts the
|
||||
memory of that main stack to waste. It is therefore good practice to allocate
|
||||
the local stack for a single, infinitely running, CoopTask on the main stack.
|
||||
Reserve enough stack to remain for ``loop()`` internals. In its most simple
|
||||
form, borrowing from the example above, where taskBlink meets the requirement
|
||||
of never returning, a CoopTask that uses the default stack space is created
|
||||
like so:
|
||||
|
||||
```
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
taskBlink = createCoopTask<int, CoopTaskStackAllocatorFromLoop<>>(
|
||||
F("Blink"), loopBlink, 0x240);
|
||||
#else
|
||||
taskBlink = createCoopTask<int, CoopTaskStackAllocatorFromLoop<>>(
|
||||
F("Blink"), loopBlink, 0x40);
|
||||
#endif
|
||||
```
|
||||
|
||||
## ESP8266 Core For Arduino specifics
|
||||
ESP8266 Core For Arduino release 2.6.0 and later include all support for this
|
||||
release of CoopTask.
|
||||
|
||||
## Arduino-ESP32 specifics
|
||||
The ESP32 runs the Arduino API on top of the FreeRTOS real-time operating system.
|
||||
This OS has all the capabilities for real-time programming and offers prioritized,
|
||||
preemptive multitasking. The purpose of CoopTask on the other hand is to take
|
||||
the complexity out of multi-threaded/tasked programming, and offers a cooperative
|
||||
multi-tasking scheme instead.
|
||||
|
||||
Arduino-ESP32 has the necessary support for CoopTask beginning with
|
||||
commit-ish c2b3f2d, dated Oct 4 2019, in Github master branch post release 1.4.0.
|
||||
|
||||
For Arduino sketches, and the libraries used in these, that never use the global
|
||||
Arduino ``delay()``, don't make use of FreeRTOS ``vTaskDelay()``, and implement
|
||||
delays only ever using the CoopTask metaphor ``CoopTaskBase::delay()``, CoopTask
|
||||
would not require anything specific for the ESP32.
|
||||
|
||||
If the convenient Arduino ``delay()`` does get used, or there is any chance that
|
||||
the FreeRTOS ``vTaskDelay()`` gets used, though, on the ESP32 it is necessary to
|
||||
prevent unsolicited preemptive concurrency and control the CPU time for the
|
||||
idle task.
|
||||
|
||||
This is being taken care of by CoopTask when using the ``runCoopTasks()``
|
||||
scheduling helper in the Sketch ``loop()`` function.
|
||||
@@ -0,0 +1,99 @@
|
||||
#include <CoopTaskBase.h>
|
||||
#include <CoopTask.h>
|
||||
#include <CoopSemaphore.h>
|
||||
#include <CoopMutex.h>
|
||||
#include <BasicCoopTask.h>
|
||||
|
||||
#if defined(ARDUINO_attiny)
|
||||
#define LED_BUILTIN 1
|
||||
|
||||
struct DummySerial {
|
||||
void print(const __FlashStringHelper* s = nullptr) {}
|
||||
void println(const __FlashStringHelper* s = nullptr) {}
|
||||
void println(long unsigned int) {}
|
||||
void flush() {}
|
||||
};
|
||||
DummySerial Serial;
|
||||
#endif
|
||||
|
||||
CoopTask<void>* blinkTask = nullptr;
|
||||
CoopTask<void>* switchTask = nullptr;
|
||||
|
||||
void blinkFunction()
|
||||
{
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
for (;;)
|
||||
{
|
||||
yield(); // A
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
delay(5000); // B
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
CoopTask<void>::sleep(); // D
|
||||
}
|
||||
}
|
||||
|
||||
void switchFunction()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
yield(); // A
|
||||
Serial.println(F("Switch on"));
|
||||
delay(100); // B
|
||||
Serial.println(F("Switch off"));
|
||||
CoopTask<void>::sleep(); // C
|
||||
}
|
||||
}
|
||||
|
||||
bool delayCb(uint32_t ms)
|
||||
{
|
||||
Serial.print(F("delayDb, ms = "));
|
||||
Serial.println(ms);
|
||||
delay(ms);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sleepCb()
|
||||
{
|
||||
Serial.println(F("sleepCb"));
|
||||
Serial.flush();
|
||||
delay(10000);
|
||||
if (blinkTask) blinkTask->wakeup();
|
||||
if (switchTask) switchTask->wakeup();
|
||||
return true;
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
#if !defined(ARDUINO_attiny)
|
||||
Serial.begin(74880);
|
||||
while (!Serial);
|
||||
delay(100);
|
||||
Serial.println();
|
||||
Serial.println(F("runTasks callback test"));
|
||||
#endif
|
||||
|
||||
runCoopTasks(nullptr, delayCb, sleepCb);
|
||||
Serial.println(F("no tasks yet, sleepCb()?"));
|
||||
|
||||
blinkTask = new CoopTask<void>(F("blink"), blinkFunction);
|
||||
switchTask = new CoopTask<void>(F("switch"), switchFunction);
|
||||
blinkTask->scheduleTask();
|
||||
switchTask->scheduleTask();
|
||||
}
|
||||
|
||||
// Add the main program code into the continuous loop() function
|
||||
void loop()
|
||||
{
|
||||
runCoopTasks(nullptr, delayCb, sleepCb);
|
||||
Serial.println(F("A - both tasks yielded, no Cb?"));
|
||||
yield();
|
||||
runCoopTasks(nullptr, delayCb, sleepCb);
|
||||
Serial.println(F("B - both tasks delayed, delayCb(100)?"));
|
||||
yield();
|
||||
runCoopTasks(nullptr, delayCb, sleepCb);
|
||||
Serial.println(F("C - blink task delayed, switch task sleeping, delayCb(4900)?"));
|
||||
yield();
|
||||
runCoopTasks(nullptr, delayCb, sleepCb);
|
||||
Serial.println(F("D - both tasks sleeping, sleepCb()?"));
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
Multiple Blinks
|
||||
|
||||
Ported to CoopTask from the version that
|
||||
demonstrates the use of the Scheduler library for the Arduino Due.
|
||||
CoopTask works on Arduino AVR (including ATtiny), ESP8266, ESP32, ARM Linux and PC OSs.
|
||||
|
||||
created 8 Oct 2012
|
||||
by Cristian Maglie
|
||||
Modified by
|
||||
Scott Fitzgerald 19 Oct 2012
|
||||
Ported to CoopTask by
|
||||
Dirk O. Kaar 22 Dec 2019
|
||||
|
||||
This example code is in the public domain
|
||||
|
||||
http://www.arduino.cc/en/Tutorial/MultipleBlinks
|
||||
*/
|
||||
|
||||
// Include CoopTask since we want to manage multiple tasks.
|
||||
#include <CoopTask.h>
|
||||
#include <CoopSemaphore.h>
|
||||
|
||||
// ATtiny85 max. working memory utilization, AVR 1.8.2 toolchain:
|
||||
// "Minimum Memory Usage: 357 bytes (70% of a 512 byte maximum)"
|
||||
|
||||
#if defined(ARDUINO_attiny)
|
||||
#define LED_BUILTIN 1
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_AVR_MICRO)
|
||||
#define STACKSIZE_8BIT 92
|
||||
#else
|
||||
#define STACKSIZE_8BIT 40
|
||||
#endif
|
||||
|
||||
CoopSemaphore taskSema(1, 1);
|
||||
int taskToken = 1;
|
||||
|
||||
// Task no.1: blink LED with 1 second delay.
|
||||
void loop1() {
|
||||
for (;;) // explicitly run forever without returning
|
||||
{
|
||||
taskSema.wait();
|
||||
if (1 != taskToken)
|
||||
{
|
||||
taskSema.post();
|
||||
yield();
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
|
||||
// IMPORTANT:
|
||||
// When multiple tasks are running 'delay' passes control to
|
||||
// other tasks while waiting and guarantees they get executed.
|
||||
delay(1000);
|
||||
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
delay(1000);
|
||||
}
|
||||
taskToken = 2;
|
||||
taskSema.post();
|
||||
}
|
||||
}
|
||||
|
||||
// Task no.2: blink LED with 0.25 second delay.
|
||||
void loop2() {
|
||||
for (;;) // explicitly run forever without returning
|
||||
{
|
||||
taskSema.wait();
|
||||
if (2 != taskToken)
|
||||
{
|
||||
taskSema.post();
|
||||
yield();
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
|
||||
// IMPORTANT:
|
||||
// When multiple tasks are running 'delay' passes control to
|
||||
// other tasks while waiting and guarantees they get executed.
|
||||
delay(250);
|
||||
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
delay(250);
|
||||
}
|
||||
taskToken = 3;
|
||||
taskSema.post();
|
||||
}
|
||||
}
|
||||
|
||||
// Task no.3: blink LED with 0.05 second delay.
|
||||
void loop3() {
|
||||
for (;;) // explicitly run forever without returning
|
||||
{
|
||||
taskSema.wait();
|
||||
if (3 != taskToken)
|
||||
{
|
||||
taskSema.post();
|
||||
yield();
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
|
||||
// IMPORTANT:
|
||||
// When multiple tasks are running 'delay' passes control to
|
||||
// other tasks while waiting and guarantees they get executed.
|
||||
delay(50);
|
||||
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
delay(50);
|
||||
}
|
||||
taskToken = 1;
|
||||
taskSema.post();
|
||||
}
|
||||
}
|
||||
|
||||
BasicCoopTask<CoopTaskStackAllocatorAsMember<sizeof(unsigned) >= 4 ? 800 : STACKSIZE_8BIT>> task1("l1", loop1);
|
||||
BasicCoopTask<CoopTaskStackAllocatorAsMember<sizeof(unsigned) >= 4 ? 800 : STACKSIZE_8BIT>> task2("l2", loop2);
|
||||
BasicCoopTask<CoopTaskStackAllocatorFromLoop<sizeof(unsigned) >= 4 ? 800 : STACKSIZE_8BIT>> task3("l3", loop3, sizeof(unsigned) >= 4 ? 800 : STACKSIZE_8BIT);
|
||||
|
||||
void setup() {
|
||||
//Serial.begin(9600);
|
||||
// Setup the 3 pins as OUTPUT
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
|
||||
// Add "loop1", "loop2" and "loop3" to CoopTask scheduling.
|
||||
// "loop" is always started by default, and is not under the control of CoopTask.
|
||||
task1.scheduleTask();
|
||||
task2.scheduleTask();
|
||||
task3.scheduleTask();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// loops forever by default
|
||||
runCoopTasks();
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
#include <CoopTask.h>
|
||||
#include <CoopMutex.h>
|
||||
#include <CoopSemaphore.h>
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
|
||||
ESP8266WebServer server(80);
|
||||
#elif defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
WebServer server(80);
|
||||
#endif
|
||||
|
||||
#if !defined(ESP8266) && !defined(ESP32)
|
||||
#define IRAM_ATTR
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
constexpr auto LEDON = LOW;
|
||||
constexpr auto LEDOFF = HIGH;
|
||||
#else
|
||||
constexpr auto LEDON = HIGH;
|
||||
constexpr auto LEDOFF = LOW;
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
#define BUTTON1 17
|
||||
//#define BUTTON1 GPIO_NUM_27
|
||||
#elif defined(ARDUINO_ESP8266_WEMOS_D1MINI)
|
||||
#define BUTTON1 D3
|
||||
#else
|
||||
#define BUTTON1 0
|
||||
#endif
|
||||
|
||||
#define USE_BUILTIN_TASK_SCHEDULER
|
||||
|
||||
// enter your WiFi configuration below
|
||||
static const char AP_SSID[] PROGMEM = "SSID"; // your router's SSID here
|
||||
static const char AP_PASS[] PROGMEM = "PSK"; // your router's password here
|
||||
|
||||
CoopMutex serialMutex;
|
||||
|
||||
CoopSemaphore blinkSema(0);
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
class Button {
|
||||
protected:
|
||||
CoopSemaphore& reportSema;
|
||||
public:
|
||||
Button(uint8_t reqPin, CoopSemaphore& _reportSema) : reportSema(_reportSema), pushSema(0), PIN(reqPin) {
|
||||
pinMode(PIN, INPUT_PULLUP);
|
||||
attachInterruptArg(PIN, Button::buttonIsr_static, this, FALLING);
|
||||
};
|
||||
~Button() {
|
||||
detachInterrupt(PIN);
|
||||
}
|
||||
|
||||
CoopSemaphore pushSema;
|
||||
|
||||
void IRAM_ATTR buttonIsr() {
|
||||
numberKeyPresses += 1;
|
||||
pressed = true;
|
||||
pushSema.post();
|
||||
reportSema.post();
|
||||
}
|
||||
|
||||
static void IRAM_ATTR buttonIsr_static(void* const self) {
|
||||
reinterpret_cast<Button*>(self)->buttonIsr();
|
||||
}
|
||||
|
||||
unsigned testResetPressed() {
|
||||
if (pressed) {
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.printf_P(PSTR("Button on pin %u has been pressed %u times\n"), PIN, numberKeyPresses);
|
||||
pressed = false;
|
||||
}
|
||||
return numberKeyPresses;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t PIN;
|
||||
volatile unsigned numberKeyPresses = 0;
|
||||
volatile bool pressed = false;
|
||||
};
|
||||
#endif
|
||||
|
||||
void loopBlink() noexcept
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
digitalWrite(LED_BUILTIN, LEDOFF);
|
||||
blinkSema.wait(1000);
|
||||
digitalWrite(LED_BUILTIN, LEDON);
|
||||
CoopTask<>::delay(4000);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
Button* button1;
|
||||
|
||||
void loopButton() noexcept
|
||||
{
|
||||
int count = 0;
|
||||
for (;;)
|
||||
{
|
||||
if (!button1->pushSema.wait())
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.println(F("loopButton: wait failed"));
|
||||
yield();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
++count;
|
||||
}
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(F("loopButton: count = "));
|
||||
Serial.println(count);
|
||||
}
|
||||
if (nullptr != button1 && 8000 < button1->testResetPressed()) {
|
||||
delete button1;
|
||||
button1 = nullptr;
|
||||
CoopTask<>::exit();
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
void handleRoot() {
|
||||
server.send(200, F("text/plain"), F("hello from esp8266!"));
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
String message = F("File Not Found\n\n");
|
||||
message += F("URI: ");
|
||||
message += server.uri();
|
||||
message += F("\nMethod: ");
|
||||
message += (server.method() == HTTP_GET) ? F("GET") : F("POST");
|
||||
message += F("\nArguments: ");
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += ' ' + server.argName(i) + F(": ") + server.arg(i) + '\n';
|
||||
}
|
||||
server.send(404, F("text/plain"), message);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
CoopTask<void>* taskButton;
|
||||
#endif
|
||||
CoopTask<void, CoopTaskStackAllocatorFromLoop<>>* taskBlink;
|
||||
CoopTask<unsigned>* taskText;
|
||||
CoopTask<void>* taskReport0;
|
||||
CoopTask<void>* taskReport1;
|
||||
CoopTask<void>* taskReport2;
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
CoopTask<void>* taskReport3;
|
||||
CoopTask<void>* taskReport4;
|
||||
CoopTask<void>* taskWeb;
|
||||
#endif
|
||||
CoopSemaphore reportSema(0);
|
||||
|
||||
void printStackReport(CoopTaskBase* task)
|
||||
{
|
||||
if (!task) return;
|
||||
Serial.print(task->name().c_str());
|
||||
Serial.print(F(" free stack = "));
|
||||
Serial.println(task->getFreeStack());
|
||||
}
|
||||
|
||||
uint32_t iterations = 0;
|
||||
uint32_t start;
|
||||
|
||||
// to demonstrate that yield and delay work in subroutines
|
||||
void printReport()
|
||||
{
|
||||
//CoopTask<>::delayMicroseconds(4000000);
|
||||
Serial.print(F("cycle period/us = "));
|
||||
if (iterations)
|
||||
{
|
||||
Serial.println(1.0F * (micros() - start) / iterations);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println(F("N/A"));
|
||||
}
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
printStackReport(taskButton);
|
||||
#endif
|
||||
printStackReport(taskBlink);
|
||||
printStackReport(taskText);
|
||||
printStackReport(CoopTask<>::self());
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
printStackReport(taskWeb);
|
||||
#endif
|
||||
|
||||
iterations = 0;
|
||||
};
|
||||
|
||||
class RAIITest
|
||||
{
|
||||
public:
|
||||
~RAIITest()
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" stack unwound, RAIITest object destructed"));
|
||||
}
|
||||
};
|
||||
|
||||
void setup()
|
||||
{
|
||||
#ifdef ESP8266
|
||||
Serial.begin(74880);
|
||||
#else
|
||||
Serial.begin(115200);
|
||||
#endif
|
||||
while (!Serial) {}
|
||||
delay(500);
|
||||
|
||||
Serial.println(F("Scheduler test"));
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(FPSTR(AP_SSID), FPSTR(AP_PASS));
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print('.');
|
||||
}
|
||||
Serial.println();
|
||||
Serial.print(F("IP address: "));
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin(F("esp"))) {
|
||||
Serial.println(F("MDNS responder started"));
|
||||
}
|
||||
|
||||
server.on(F("/"), handleRoot);
|
||||
|
||||
server.on(F("/inline"), []() {
|
||||
server.send(200, F("text/plain"), F("this works as well"));
|
||||
});
|
||||
|
||||
server.onNotFound(handleNotFound);
|
||||
|
||||
server.begin();
|
||||
Serial.println(F("HTTP server started"));
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(ESP8266) && defined(USE_BUILTIN_TASK_SCHEDULER)
|
||||
CoopTaskBase::useBuiltinScheduler();
|
||||
#endif
|
||||
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
button1 = new Button(BUTTON1, reportSema);
|
||||
|
||||
taskButton = new CoopTask<void>(F("Button"), loopButton,
|
||||
#if defined(ESP8266)
|
||||
0x700);
|
||||
#elif defined(ESP32)
|
||||
0x940);
|
||||
#endif
|
||||
if (!*taskButton) Serial.printf_P(PSTR("CoopTask %s out of stack\n"), taskButton->name().c_str());
|
||||
#endif
|
||||
|
||||
taskBlink = new CoopTask<void, CoopTaskStackAllocatorFromLoop<>>(F("Blink"), loopBlink,
|
||||
#if defined(ESP8266)
|
||||
0x400);
|
||||
#elif defined(ESP32)
|
||||
0x540);
|
||||
#else
|
||||
0x40);
|
||||
#endif
|
||||
if (!*taskBlink) Serial.println(F("CoopTask Blink out of stack"));
|
||||
|
||||
taskText = new CoopTask<unsigned>(F("Text"), []() -> unsigned
|
||||
{
|
||||
RAIITest raii;
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.println(F("Task1 - A"));
|
||||
}
|
||||
yield();
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.println(F("Task1 - B"));
|
||||
}
|
||||
uint32_t start = millis();
|
||||
CoopTask<>::delay(6000);
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(F("!!!Task1 - C - "));
|
||||
Serial.println(millis() - start);
|
||||
printStackReport(taskText);
|
||||
}
|
||||
#if !defined(ARDUINO)
|
||||
throw static_cast<unsigned>(41);
|
||||
#endif
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(F("exiting from task "));
|
||||
Serial.println(CoopTaskBase::self()->name());
|
||||
//CoopTask<unsigned>::exit(42);
|
||||
return 43;
|
||||
}
|
||||
#if defined(ESP8266)
|
||||
, 0x380);
|
||||
#elif defined(ESP32)
|
||||
, 0x4c0);
|
||||
#else
|
||||
, 0x70);
|
||||
#endif
|
||||
if (!*taskText) Serial.println(F("CoopTask Text out of stack"));
|
||||
|
||||
auto reportFunc = []() noexcept
|
||||
{
|
||||
uint32_t count = 0;
|
||||
for (;;) {
|
||||
if (!reportSema.wait(120000))
|
||||
{
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name().c_str());
|
||||
Serial.println(F(": wait failed"));
|
||||
}
|
||||
yield();
|
||||
continue;
|
||||
}
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTask<>::self()->name());
|
||||
Serial.print(F(" ("));
|
||||
Serial.print(++count);
|
||||
Serial.println(F("x)"));
|
||||
printReport();
|
||||
}
|
||||
yield();
|
||||
reportSema.setval(0);
|
||||
}
|
||||
};
|
||||
taskReport0 = new CoopTask<void>(F("Report0"), reportFunc
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
, 0x600);
|
||||
#else
|
||||
, 0x70);
|
||||
#endif
|
||||
if (!*taskReport0) Serial.println(F("CoopTask Report out of stack"));
|
||||
taskReport1 = new CoopTask<void>(F("Report1"), reportFunc
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
, 0x600);
|
||||
#else
|
||||
, 0x70);
|
||||
#endif
|
||||
if (!*taskReport1) Serial.println(F("CoopTask Report out of stack"));
|
||||
taskReport2 = new CoopTask<void>(F("Report2"), reportFunc
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
, 0x600);
|
||||
#else
|
||||
, 0x70);
|
||||
#endif
|
||||
if (!*taskReport2) Serial.println(F("CoopTask Report out of stack"));
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
taskReport3 = new CoopTask<void>(F("Report3"), reportFunc
|
||||
, 0x600);
|
||||
if (!*taskReport3) Serial.println(F("CoopTask Report out of stack"));
|
||||
taskReport4 = new CoopTask<void>(F("Report4"), reportFunc
|
||||
, 0x600);
|
||||
if (!*taskReport4) Serial.println(F("CoopTask Report out of stack"));
|
||||
|
||||
taskWeb = new CoopTask<void>(F("Web"), []() noexcept
|
||||
{
|
||||
for (;;) {
|
||||
server.handleClient();
|
||||
#ifdef ESP8266
|
||||
MDNS.update();
|
||||
#endif
|
||||
yield();
|
||||
}
|
||||
},
|
||||
#if defined(ESP8266)
|
||||
0x800);
|
||||
#else
|
||||
0xa00);
|
||||
#endif
|
||||
if (!*taskWeb) Serial.printf_P(PSTR("CoopTask %s out of stack\n"), taskWeb->name().c_str());
|
||||
|
||||
if (!taskButton->scheduleTask()) { Serial.printf_P(PSTR("Could not schedule task %s\n"), taskButton->name().c_str()); }
|
||||
if (!taskReport3->scheduleTask()) { Serial.printf_P(PSTR("Could not schedule task %s\n"), taskReport3->name().c_str()); }
|
||||
if (!taskReport4->scheduleTask()) { Serial.printf_P(PSTR("Could not schedule task %s\n"), taskReport4->name().c_str()); }
|
||||
if (!taskWeb->scheduleTask()) { Serial.printf_P(PSTR("Could not schedule task %s\n"), taskWeb->name().c_str()); }
|
||||
#endif
|
||||
|
||||
if (!taskBlink->scheduleTask()) { Serial.print(F("Could not schedule task ")); Serial.println(taskBlink->name().c_str()); }
|
||||
if (!taskText->scheduleTask()) { Serial.print(F("Could not schedule task ")); Serial.println(taskText->name().c_str()); }
|
||||
if (!taskReport0->scheduleTask()) { Serial.print(F("Could not schedule task ")); Serial.println(taskReport0->name().c_str()); }
|
||||
if (!taskReport1->scheduleTask()) { Serial.print(F("Could not schedule task ")); Serial.println(taskReport1->name().c_str()); }
|
||||
if (!taskReport2->scheduleTask()) { Serial.print(F("Could not schedule task ")); Serial.println(taskReport2->name().c_str()); }
|
||||
|
||||
#ifdef ESP32
|
||||
Serial.print(F("Loop free stack = ")); Serial.println(uxTaskGetStackHighWaterMark(NULL));
|
||||
#endif
|
||||
}
|
||||
|
||||
void taskReaper(const CoopTaskBase* const task)
|
||||
{
|
||||
if (task == taskText)
|
||||
{
|
||||
delete task;
|
||||
taskText = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
#if defined(ESP8266) && defined(USE_BUILTIN_TASK_SCHEDULER)
|
||||
if (taskText && !*taskText)
|
||||
{
|
||||
taskReaper(taskText);
|
||||
}
|
||||
#else
|
||||
runCoopTasks(taskReaper);
|
||||
#endif
|
||||
|
||||
// taskReport sleeps on first run(), and after each report.
|
||||
// It resets iterations to 0 on each report.
|
||||
if (!iterations) start = micros();
|
||||
++iterations;
|
||||
#ifdef ESP32_FREERTOS
|
||||
if (iterations >= 50000)
|
||||
#else
|
||||
if (iterations >= 200000)
|
||||
#endif
|
||||
{
|
||||
reportSema.post();
|
||||
}
|
||||
}
|
||||
252
arduino-cli/libraries/CoopTask/examples/mutex/mutex.ino
Normal file
252
arduino-cli/libraries/CoopTask/examples/mutex/mutex.ino
Normal file
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
Name: mutex.ino
|
||||
Created: 2019-08-04 22:40:11
|
||||
Author: dok@dok-net.net
|
||||
*/
|
||||
|
||||
#include <CoopTask.h>
|
||||
#include <CoopSemaphore.h>
|
||||
#include <CoopMutex.h>
|
||||
|
||||
#define USE_BUILTIN_TASK_SCHEDULER
|
||||
|
||||
CoopMutex serialMutex;
|
||||
CoopMutex mutex;
|
||||
CoopTaskBase* hasMutex(nullptr);
|
||||
|
||||
void haveMutex()
|
||||
{
|
||||
if (hasMutex)
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.print(F(" called haveMutex, despite "));
|
||||
Serial.print(hasMutex->name());
|
||||
Serial.println(F(" is known to have mutex."));
|
||||
return;
|
||||
}
|
||||
hasMutex = CoopTaskBase::self();
|
||||
}
|
||||
|
||||
void yieldMutex()
|
||||
{
|
||||
if (hasMutex != CoopTaskBase::self())
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" called yieldMutex, but no task currently has the mutex."));
|
||||
return;
|
||||
}
|
||||
hasMutex = nullptr;
|
||||
}
|
||||
|
||||
CoopTask<int>* firstTask;
|
||||
CoopTask<int>* secondTask;
|
||||
CoopTask<int>* thirdTask;
|
||||
|
||||
void setup() {
|
||||
#ifdef ESP8266
|
||||
Serial.begin(74880);
|
||||
#else
|
||||
Serial.begin(115200);
|
||||
#endif
|
||||
while (!Serial) {}
|
||||
delay(500);
|
||||
Serial.println(F("Mutex test"));
|
||||
|
||||
#if defined(ESP8266) && defined(USE_BUILTIN_TASK_SCHEDULER)
|
||||
CoopTaskBase::useBuiltinScheduler();
|
||||
#endif
|
||||
|
||||
firstTask = createCoopTask(F("first"), []()
|
||||
{
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" starts"));
|
||||
}
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" locks mutex"));
|
||||
}
|
||||
{
|
||||
CoopMutexLock lock(mutex);
|
||||
if (!lock) {
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(F("failed to lock mutex in "));
|
||||
Serial.println(CoopTaskBase::self()->name());
|
||||
}
|
||||
haveMutex();
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" has mutex"));
|
||||
}
|
||||
yield();
|
||||
yieldMutex();
|
||||
}
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.print(F(" runs ("));
|
||||
Serial.print(i);
|
||||
Serial.println(')');
|
||||
}
|
||||
yield();
|
||||
}
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(F("exiting from task "));
|
||||
Serial.println(CoopTaskBase::self()->name());
|
||||
return 0;
|
||||
}
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
);
|
||||
#else
|
||||
, 0x120);
|
||||
#endif
|
||||
if (!firstTask) Serial.println(F("firstTask not created"));
|
||||
secondTask = createCoopTask(F("second"), []()
|
||||
{
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" starts"));
|
||||
}
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" locks mutex"));
|
||||
}
|
||||
{
|
||||
CoopMutexLock lock(mutex);
|
||||
if (!lock) {
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(F("failed to lock mutex in "));
|
||||
Serial.println(CoopTaskBase::self()->name());
|
||||
}
|
||||
haveMutex();
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" has mutex"));
|
||||
}
|
||||
yield();
|
||||
yieldMutex();
|
||||
}
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.print(F(" runs ("));
|
||||
Serial.print(i);
|
||||
Serial.println(')');
|
||||
}
|
||||
yield();
|
||||
}
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(F("exiting from task "));
|
||||
Serial.println(CoopTaskBase::self()->name());
|
||||
return 0;
|
||||
}
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
);
|
||||
#else
|
||||
, 0x120);
|
||||
#endif
|
||||
if (!secondTask) Serial.println(F("secondTask not created"));
|
||||
thirdTask = createCoopTask(F("third"), []()
|
||||
{
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" starts"));
|
||||
}
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" locks mutex"));
|
||||
}
|
||||
{
|
||||
CoopMutexLock lock(mutex);
|
||||
if (!lock) {
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(F("failed to lock mutex in "));
|
||||
Serial.println(CoopTaskBase::self()->name());
|
||||
}
|
||||
haveMutex();
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.println(F(" has mutex"));
|
||||
}
|
||||
yield();
|
||||
yieldMutex();
|
||||
}
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.print(F(" runs ("));
|
||||
Serial.print(i);
|
||||
Serial.println(')');
|
||||
}
|
||||
yield();
|
||||
}
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
{
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(CoopTaskBase::self()->name());
|
||||
Serial.print(F(" still runs ("));
|
||||
Serial.print(i);
|
||||
Serial.println(')');
|
||||
}
|
||||
yield();
|
||||
}
|
||||
CoopMutexLock serialLock(serialMutex);
|
||||
Serial.print(F("exiting from task "));
|
||||
Serial.println(CoopTaskBase::self()->name());
|
||||
return 0;
|
||||
}
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
);
|
||||
#else
|
||||
, 0x120);
|
||||
#endif
|
||||
if (!thirdTask) Serial.println(F("thirdTask not created"));
|
||||
|
||||
#ifdef ESP32
|
||||
Serial.print(F("Loop free stack = ")); Serial.println(uxTaskGetStackHighWaterMark(NULL));
|
||||
#endif
|
||||
}
|
||||
|
||||
void taskReaper(const CoopTaskBase* const task)
|
||||
{
|
||||
delete task;
|
||||
}
|
||||
|
||||
// the loop function runs over and over again until power down or reset
|
||||
void loop()
|
||||
{
|
||||
#if defined(ESP8266) && defined(USE_BUILTIN_TASK_SCHEDULER)
|
||||
if (firstTask && !*firstTask)
|
||||
{
|
||||
taskReaper(firstTask); firstTask = nullptr;
|
||||
}
|
||||
if (secondTask && !*secondTask)
|
||||
{
|
||||
taskReaper(secondTask); secondTask = nullptr;
|
||||
}
|
||||
if (thirdTask && !*thirdTask)
|
||||
{
|
||||
taskReaper(thirdTask); thirdTask = nullptr;
|
||||
}
|
||||
#else
|
||||
runCoopTasks(taskReaper);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// portable.cpp
|
||||
// This is a basic portable example, without a scheduler.
|
||||
// All tasks are run round-robin inside a for loop.
|
||||
// It shows CoopTask creation, synchronization, and termination.
|
||||
|
||||
#include <iostream>
|
||||
#include "CoopTask.h"
|
||||
#include "CoopSemaphore.h"
|
||||
#include "CoopMutex.h"
|
||||
|
||||
template<typename StackAllocator> void printStackReport(BasicCoopTask<StackAllocator>& task)
|
||||
{
|
||||
if (!task) return;
|
||||
std::cerr << task.name().c_str() << " free stack = " << task.getFreeStack() << std::endl;
|
||||
}
|
||||
|
||||
CoopMutex blinkMutex;
|
||||
|
||||
int main()
|
||||
{
|
||||
CoopSemaphore terminatorSema(0);
|
||||
CoopSemaphore helloSema(0);
|
||||
|
||||
auto& hello = *createCoopTask<void>(std::string("hello"), [&terminatorSema, &helloSema]() noexcept
|
||||
{
|
||||
std::cerr << "Hello" << std::endl;
|
||||
yield();
|
||||
for (int x = 0; x < 10; ++x)
|
||||
{
|
||||
{
|
||||
CoopMutexLock lock(blinkMutex);
|
||||
std::cerr << "Loop" << std::endl;
|
||||
}
|
||||
helloSema.wait(2000);
|
||||
}
|
||||
terminatorSema.post();
|
||||
}, 0x2000);
|
||||
if (!hello) std::cerr << hello.name() << " CoopTask not created" << std::endl;
|
||||
|
||||
|
||||
bool keepBlinking = true;
|
||||
|
||||
auto& terminator = *createCoopTask<void>(std::string("terminator"), [&keepBlinking, &terminatorSema]() noexcept
|
||||
{
|
||||
if (!terminatorSema.wait()) std::cerr << "terminatorSema.wait() failed" << std::endl;
|
||||
keepBlinking = false;
|
||||
}, 0x2000);
|
||||
if (!terminator) std::cerr << terminator.name() << " CoopTask not created" << std::endl;
|
||||
|
||||
auto& blink = *createCoopTask<std::string, CoopTaskStackAllocatorFromLoop<>>(std::string("blink"), [&keepBlinking]()
|
||||
{
|
||||
while (keepBlinking)
|
||||
{
|
||||
{
|
||||
CoopMutexLock lock(blinkMutex);
|
||||
std::cerr << "LED on" << std::endl;
|
||||
delay(1000);
|
||||
std::cerr << "LED off" << std::endl;
|
||||
}
|
||||
delay(1000);
|
||||
}
|
||||
throw std::string("sixtynine");
|
||||
return "fortytwo";
|
||||
}, 0x2000);
|
||||
if (!blink) std::cerr << blink.name() << " CoopTask not created" << std::endl;
|
||||
|
||||
auto& report = *createCoopTask<void>(std::string("report"), [&hello, &blink]() noexcept
|
||||
{
|
||||
for (;;) {
|
||||
delay(5000);
|
||||
{
|
||||
CoopMutexLock lock(blinkMutex);
|
||||
printStackReport(hello);
|
||||
printStackReport(blink);
|
||||
}
|
||||
}
|
||||
}, 0x2000);
|
||||
if (!report) std::cerr << report.name() << " CoopTask not created" << std::endl;
|
||||
|
||||
auto taskReaper = [&blink](const CoopTaskBase* const task)
|
||||
{
|
||||
// once: hello posts terminatorSema -> terminator sets keepBlinking = false -> blink exits -> break leaves for-loop -> program exits
|
||||
if (task == &blink)
|
||||
{
|
||||
std::cerr << task->name() << " returns = " << blink.exitCode() << std::endl;
|
||||
delete task;
|
||||
exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
for (;;)
|
||||
{
|
||||
runCoopTasks(taskReaper);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
21
arduino-cli/libraries/CoopTask/library.json
Normal file
21
arduino-cli/libraries/CoopTask/library.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "CoopTask",
|
||||
"version": "3.6.4",
|
||||
"description": "Portable C++ library for cooperative multitasking like Arduino Scheduler on ESP8266/ESP32, AVR, Linux, Windows",
|
||||
"keywords": [
|
||||
"multitasking", "timing"
|
||||
],
|
||||
"repository":
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/dok-net/CoopTask"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Dirk Kaar"
|
||||
}
|
||||
],
|
||||
"license": "LGPL-2.1+",
|
||||
"frameworks": "arduino",
|
||||
"platforms": "*"
|
||||
}
|
||||
9
arduino-cli/libraries/CoopTask/library.properties
Normal file
9
arduino-cli/libraries/CoopTask/library.properties
Normal file
@@ -0,0 +1,9 @@
|
||||
name=CoopTask
|
||||
version=3.6.4
|
||||
author=Dirk O. Kaar
|
||||
maintainer=Dirk O. Kaar <dok@dok-net.net>
|
||||
sentence=Portable C++ library for cooperative multitasking like Arduino Scheduler on ESP8266/ESP32, AVR, Linux, Windows
|
||||
paragraph=Run multiple concurrent setup()/loop() tasks in Arduino sketches. Use the normal global delay() function, use yield() to give up the CPU to other tasks and the main loop().
|
||||
category=Timing
|
||||
url=https://github.com/dok-net/CoopTask
|
||||
architectures=*
|
||||
61
arduino-cli/libraries/CoopTask/src/BasicCoopTask.cpp
Normal file
61
arduino-cli/libraries/CoopTask/src/BasicCoopTask.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
BasicCoopTask.cpp - Implementation of cooperative scheduling tasks
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "BasicCoopTask.h"
|
||||
|
||||
#if defined(ARDUINO) && !defined(ESP32_FREERTOS)
|
||||
#include <alloca.h>
|
||||
#endif
|
||||
|
||||
#if !defined(_MSC_VER) && !defined(ESP32_FREERTOS)
|
||||
|
||||
char* CoopTaskStackAllocator::allocateStack(size_t stackSize)
|
||||
{
|
||||
char* stackTop = nullptr;
|
||||
if (stackSize <= CoopTaskBase::MAXSTACKSPACE - (CoopTaskBase::FULLFEATURES ? 2 : 1) * sizeof(CoopTaskBase::STACKCOOKIE))
|
||||
{
|
||||
#if defined(ESP8266)
|
||||
stackTop = new (std::nothrow) char[stackSize + (CoopTaskBase::FULLFEATURES ? 2 : 1) * sizeof(CoopTaskBase::STACKCOOKIE)];
|
||||
#else
|
||||
stackTop = new char[stackSize + (CoopTaskBase::FULLFEATURES ? 2 : 1) * sizeof(CoopTaskBase::STACKCOOKIE)];
|
||||
#endif
|
||||
}
|
||||
return stackTop;
|
||||
}
|
||||
|
||||
#endif // !defined(_MSC_VER) && !defined(ESP32_FREERTOS)
|
||||
|
||||
#if (defined(ARDUINO) && !defined(ESP32_FREERTOS)) || defined(__GNUC__)
|
||||
|
||||
char* CoopTaskStackAllocatorFromLoopBase::allocateStack(size_t loopReserve, size_t stackSize)
|
||||
{
|
||||
char* bp = static_cast<char*>(alloca(
|
||||
(sizeof(unsigned) >= 4) ? ((loopReserve + sizeof(unsigned) - 1) / sizeof(unsigned)) * sizeof(unsigned) : loopReserve
|
||||
));
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
char* stackTop = nullptr;
|
||||
if (stackSize <= CoopTaskBase::MAXSTACKSPACE - (CoopTaskBase::FULLFEATURES ? 2 : 1) * sizeof(CoopTaskBase::STACKCOOKIE))
|
||||
{
|
||||
stackTop = reinterpret_cast<char*>(
|
||||
reinterpret_cast<long unsigned>(bp) - stackSize + (CoopTaskBase::FULLFEATURES ? 2 : 1) * sizeof(CoopTaskBase::STACKCOOKIE));
|
||||
}
|
||||
return stackTop;
|
||||
}
|
||||
|
||||
#endif // (defined(ARDUINO) && !defined(ESP32_FREERTOS)) || defined(__GNUC__)
|
||||
113
arduino-cli/libraries/CoopTask/src/BasicCoopTask.h
Normal file
113
arduino-cli/libraries/CoopTask/src/BasicCoopTask.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
BasicCoopTask.h - Implementation of cooperative scheduling tasks
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __BasicCoopTask_h
|
||||
#define __BasicCoopTask_h
|
||||
|
||||
#include "CoopTaskBase.h"
|
||||
|
||||
class CoopTaskStackAllocator
|
||||
{
|
||||
public:
|
||||
static constexpr size_t DEFAULTTASKSTACKSIZE = CoopTaskBase::DEFAULTTASKSTACKSIZE;
|
||||
#if !defined(_MSC_VER) && !defined(ESP32_FREERTOS)
|
||||
static char* allocateStack(size_t stackSize);
|
||||
static void disposeStack(char* stackTop) { delete[] stackTop; }
|
||||
#endif
|
||||
};
|
||||
|
||||
template<size_t StackSize = CoopTaskBase::DEFAULTTASKSTACKSIZE>
|
||||
class CoopTaskStackAllocatorAsMember
|
||||
{
|
||||
public:
|
||||
static constexpr size_t DEFAULTTASKSTACKSIZE =
|
||||
(sizeof(unsigned) >= 4) ? ((StackSize + sizeof(unsigned) - 1) / sizeof(unsigned)) * sizeof(unsigned) : StackSize;
|
||||
|
||||
#if !defined(_MSC_VER) && !defined(ESP32_FREERTOS)
|
||||
protected:
|
||||
char _stackTop[DEFAULTTASKSTACKSIZE + (CoopTaskBase::FULLFEATURES ? 2 : 1) * sizeof(CoopTaskBase::STACKCOOKIE)];
|
||||
public:
|
||||
char* allocateStack(size_t stackSize)
|
||||
{
|
||||
return (DEFAULTTASKSTACKSIZE >= stackSize) ?
|
||||
_stackTop : nullptr;
|
||||
}
|
||||
static void disposeStack(char* stackTop) { }
|
||||
#endif
|
||||
};
|
||||
|
||||
class CoopTaskStackAllocatorFromLoopBase
|
||||
{
|
||||
public:
|
||||
static constexpr size_t DEFAULTTASKSTACKSIZE = CoopTaskBase::DEFAULTTASKSTACKSIZE;
|
||||
#if (defined(ARDUINO) && !defined(ESP32_FREERTOS)) || defined(__GNUC__)
|
||||
protected:
|
||||
static char* allocateStack(size_t loopReserve, size_t stackSize);
|
||||
#endif
|
||||
public:
|
||||
static void disposeStack(char* stackTop) { }
|
||||
};
|
||||
|
||||
template<size_t LoopReserve = (CoopTaskBase::DEFAULTTASKSTACKSIZE / 2)>
|
||||
class CoopTaskStackAllocatorFromLoop : public CoopTaskStackAllocatorFromLoopBase
|
||||
{
|
||||
public:
|
||||
static constexpr size_t DEFAULTTASKSTACKSIZE = CoopTaskBase::DEFAULTTASKSTACKSIZE;
|
||||
#if (defined(ARDUINO) && !defined(ESP32_FREERTOS)) || defined(__GNUC__)
|
||||
static char* allocateStack(size_t stackSize)
|
||||
{
|
||||
return CoopTaskStackAllocatorFromLoopBase::allocateStack(LoopReserve, stackSize);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
template<class StackAllocator = CoopTaskStackAllocator> class BasicCoopTask : public CoopTaskBase
|
||||
{
|
||||
public:
|
||||
#ifdef ARDUINO
|
||||
BasicCoopTask(const String& name, taskfunction_t _func, size_t stackSize = StackAllocator::DEFAULTTASKSTACKSIZE) :
|
||||
#else
|
||||
BasicCoopTask(const std::string& name, taskfunction_t _func, size_t stackSize = StackAllocator::DEFAULTTASKSTACKSIZE) :
|
||||
#endif
|
||||
CoopTaskBase(name, _func, stackSize)
|
||||
{
|
||||
#if !defined(_MSC_VER) && !defined(ESP32_FREERTOS)
|
||||
taskStackTop = stackAllocator.allocateStack(taskStackSize);
|
||||
#endif
|
||||
}
|
||||
BasicCoopTask(const BasicCoopTask&) = delete;
|
||||
BasicCoopTask& operator=(const BasicCoopTask&) = delete;
|
||||
~BasicCoopTask()
|
||||
{
|
||||
#if !defined(_MSC_VER) && !defined(ESP32_FREERTOS)
|
||||
stackAllocator.disposeStack(taskStackTop);
|
||||
#endif
|
||||
}
|
||||
/// Every task is entered into this list by scheduleTask(). It is removed when it exits
|
||||
/// or gets deleted.
|
||||
static const std::array< std::atomic<BasicCoopTask* >, MAXNUMBERCOOPTASKS + 1>& getRunnableTasks()
|
||||
{
|
||||
// this is safe to do because CoopTaskBase ctor is protected.
|
||||
return reinterpret_cast<const std::array< std::atomic<BasicCoopTask* >, MAXNUMBERCOOPTASKS + 1>&>(CoopTaskBase::getRunnableTasks());
|
||||
}
|
||||
protected:
|
||||
StackAllocator stackAllocator;
|
||||
};
|
||||
|
||||
#endif // __BasicCoopTask_h
|
||||
93
arduino-cli/libraries/CoopTask/src/CoopMutex.h
Normal file
93
arduino-cli/libraries/CoopTask/src/CoopMutex.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
CoopMutex.h - Implementation of a mutex and an RAII lock for cooperative scheduling tasks
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __CoopMutex_h
|
||||
#define __CoopMutex_h
|
||||
|
||||
#include "CoopSemaphore.h"
|
||||
|
||||
/// A mutex that is safe to use from CoopTasks.
|
||||
class CoopMutex : private CoopSemaphore
|
||||
{
|
||||
protected:
|
||||
std::atomic<CoopTaskBase*> owner;
|
||||
|
||||
public:
|
||||
CoopMutex(size_t maxPending = 10) : CoopSemaphore(1, maxPending), owner(nullptr) {}
|
||||
CoopMutex(const CoopMutex&) = delete;
|
||||
CoopMutex& operator=(const CoopMutex&) = delete;
|
||||
|
||||
/// @returns: true, or false, if the current task does not own the mutex.
|
||||
bool unlock()
|
||||
{
|
||||
if (CoopTaskBase::running() && CoopTaskBase::self() == owner.load() && post())
|
||||
{
|
||||
owner.store(nullptr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @returns: true if the mutex becomes locked. false if it is already locked by the same task, or the maximum number of pending tasks is exceeded.
|
||||
bool lock()
|
||||
{
|
||||
if (CoopTaskBase::running() && CoopTaskBase::self() != owner.load() && wait())
|
||||
{
|
||||
owner.store(CoopTaskBase::self());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @returns: true if the mutex becomes freshly locked without waiting, otherwise false.
|
||||
bool try_lock()
|
||||
{
|
||||
if (CoopTaskBase::running() && CoopTaskBase::self() != owner.load() && try_wait())
|
||||
{
|
||||
owner.store(CoopTaskBase::self());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/// A RAII CoopMutex lock class.
|
||||
class CoopMutexLock {
|
||||
protected:
|
||||
CoopMutex& mutex;
|
||||
bool locked;
|
||||
public:
|
||||
/// The constructor returns if the mutex was locked, or locking failed.
|
||||
explicit CoopMutexLock(CoopMutex& _mutex) : mutex(_mutex) {
|
||||
locked = mutex.lock();
|
||||
}
|
||||
CoopMutexLock() = delete;
|
||||
CoopMutexLock(const CoopMutexLock&) = delete;
|
||||
CoopMutexLock& operator=(const CoopMutexLock&) = delete;
|
||||
/// @returns: true if the mutex became locked, potentially after blocking, otherwise false.
|
||||
operator bool() const {
|
||||
return locked;
|
||||
}
|
||||
/// The destructor unlocks the mutex.
|
||||
~CoopMutexLock() {
|
||||
if (locked) mutex.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __CoopMutex_h
|
||||
257
arduino-cli/libraries/CoopTask/src/CoopSemaphore.cpp
Normal file
257
arduino-cli/libraries/CoopTask/src/CoopSemaphore.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
CoopSemaphore.cpp - Implementation of a semaphore for cooperative scheduling tasks
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "CoopSemaphore.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <interrupts.h>
|
||||
using esp8266::InterruptLock;
|
||||
#elif defined(ESP32) || !defined(ARDUINO)
|
||||
using std::min;
|
||||
#else
|
||||
class InterruptLock {
|
||||
public:
|
||||
InterruptLock() {
|
||||
noInterrupts();
|
||||
}
|
||||
~InterruptLock() {
|
||||
interrupts();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef ARDUINO
|
||||
#include <chrono>
|
||||
namespace
|
||||
{
|
||||
uint32_t millis()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool CoopSemaphore::_wait(const bool withDeadline, const uint32_t ms)
|
||||
{
|
||||
const uint32_t start = withDeadline ? millis() : 0;
|
||||
uint32_t expired = 0;
|
||||
bool selfFirst = false;
|
||||
for (;;)
|
||||
{
|
||||
auto self = CoopTaskBase::self();
|
||||
unsigned val;
|
||||
#if !defined(ESP32) && defined(ARDUINO)
|
||||
{
|
||||
InterruptLock lock;
|
||||
val = value.load();
|
||||
if (val)
|
||||
{
|
||||
value.store(val - 1);
|
||||
}
|
||||
}
|
||||
#else
|
||||
val = 1;
|
||||
while (val && !value.compare_exchange_weak(val, val - 1)) {}
|
||||
#endif
|
||||
const unsigned valOnEntry = val;
|
||||
if (withDeadline) expired = millis() - start;
|
||||
if (!(selfFirst && valOnEntry))
|
||||
{
|
||||
if (pendingTasks.push(self))
|
||||
{
|
||||
if (!withDeadline) self->sleep(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
selfFirst = true;
|
||||
}
|
||||
}
|
||||
bool fwd = !selfFirst && val;
|
||||
bool stop = false;
|
||||
CoopTaskBase* pendingTask = nullptr;
|
||||
bool selfSuccess = false;
|
||||
for (;;)
|
||||
{
|
||||
if (pendingTasks.available())
|
||||
{
|
||||
#if !defined(ESP32) && defined(ARDUINO)
|
||||
{
|
||||
InterruptLock lock;
|
||||
pendingTask = pendingTask0.load();
|
||||
if (fwd || !pendingTask) pendingTask0.store(pendingTasks.pop());
|
||||
}
|
||||
#else
|
||||
pendingTask = nullptr;
|
||||
bool exchd = false;
|
||||
while ((fwd || !pendingTask) && !(exchd = pendingTask0.compare_exchange_weak(pendingTask, pendingTasks.peek()))) {}
|
||||
if (exchd) pendingTasks.pop();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !defined(ESP32) && defined(ARDUINO)
|
||||
{
|
||||
InterruptLock lock;
|
||||
pendingTask = pendingTask0.load();
|
||||
if (fwd && pendingTask) pendingTask0.store(nullptr);
|
||||
}
|
||||
#else
|
||||
pendingTask = nullptr;
|
||||
if (fwd) pendingTask = pendingTask0.exchange(nullptr);
|
||||
#endif
|
||||
stop = true;
|
||||
}
|
||||
if (!val)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!(pendingTask || stop))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (selfFirst)
|
||||
{
|
||||
selfFirst = false;
|
||||
if (!withDeadline) self->sleep(false);
|
||||
selfSuccess = true;
|
||||
}
|
||||
else if (pendingTask == self)
|
||||
{
|
||||
if (!selfSuccess)
|
||||
{
|
||||
if (!withDeadline) self->sleep(false);
|
||||
return true;
|
||||
}
|
||||
if (!stop) continue;
|
||||
}
|
||||
else if (pendingTask)
|
||||
{
|
||||
pendingTask->scheduleTask(true);
|
||||
}
|
||||
if (stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
val -= 1;
|
||||
fwd = val;
|
||||
}
|
||||
if (selfSuccess)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (valOnEntry)
|
||||
{
|
||||
#if !defined(ESP32) && defined(ARDUINO)
|
||||
InterruptLock lock;
|
||||
val = value.load();
|
||||
value.store(val + 1);
|
||||
#else
|
||||
while (!value.compare_exchange_weak(val, val + 1)) {}
|
||||
#endif
|
||||
}
|
||||
if (withDeadline)
|
||||
{
|
||||
if (expired >= ms)
|
||||
{
|
||||
pendingTasks.for_each_rev_requeue(notIsSelfTask);
|
||||
#if !defined(ESP32) && defined(ARDUINO)
|
||||
{
|
||||
InterruptLock lock;
|
||||
pendingTask = pendingTask0.load();
|
||||
if (pendingTask == self) pendingTask0.store(pendingTasks.available() ? pendingTasks.pop() : nullptr);
|
||||
}
|
||||
#else
|
||||
bool exchd = false;
|
||||
pendingTask = self;
|
||||
while ((pendingTask == self) && !(exchd = pendingTask0.compare_exchange_weak(pendingTask, pendingTasks.available() ? pendingTasks.peek() : nullptr))) {}
|
||||
if (exchd && pendingTasks.available()) pendingTasks.pop();
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
CoopTaskBase::delay(ms - expired);
|
||||
}
|
||||
else
|
||||
{
|
||||
CoopTaskBase::yield();
|
||||
}
|
||||
selfFirst = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool IRAM_ATTR CoopSemaphore::post()
|
||||
{
|
||||
CoopTaskBase* pendingTask;
|
||||
#if !defined(ESP32) && defined(ARDUINO)
|
||||
{
|
||||
InterruptLock lock;
|
||||
unsigned val = value.load();
|
||||
value.store(val + 1);
|
||||
pendingTask = pendingTask0.load();
|
||||
if (pendingTask) pendingTask0.store(nullptr);
|
||||
}
|
||||
#else
|
||||
unsigned val = 0;
|
||||
while (!value.compare_exchange_weak(val, val + 1)) {}
|
||||
pendingTask = pendingTask0.exchange(nullptr);
|
||||
#endif
|
||||
if (!pendingTask || !pendingTask->suspended()) return true;
|
||||
return pendingTask->scheduleTask(true);
|
||||
}
|
||||
|
||||
bool CoopSemaphore::setval(unsigned newVal)
|
||||
{
|
||||
CoopTaskBase* pendingTask = nullptr;
|
||||
unsigned val;
|
||||
#if !defined(ESP32) && defined(ARDUINO)
|
||||
{
|
||||
InterruptLock lock;
|
||||
val = value.load();
|
||||
value.store(newVal);
|
||||
if (newVal > val)
|
||||
{
|
||||
pendingTask = pendingTask0.load();
|
||||
pendingTask0.store(nullptr);
|
||||
}
|
||||
}
|
||||
#else
|
||||
val = value.exchange(newVal);
|
||||
if (newVal > val) pendingTask = pendingTask0.exchange(nullptr);
|
||||
#endif
|
||||
if (!pendingTask || !pendingTask->suspended()) return true;
|
||||
return pendingTask->scheduleTask(true);
|
||||
}
|
||||
|
||||
bool CoopSemaphore::try_wait()
|
||||
{
|
||||
unsigned val;
|
||||
#if !defined(ESP32) && defined(ARDUINO)
|
||||
{
|
||||
InterruptLock lock;
|
||||
val = value.load();
|
||||
if (val)
|
||||
{
|
||||
value.store(val - 1);
|
||||
}
|
||||
}
|
||||
#else
|
||||
val = 1;
|
||||
while (val && !value.compare_exchange_weak(val, val - 1)) {}
|
||||
#endif
|
||||
return val;
|
||||
}
|
||||
93
arduino-cli/libraries/CoopTask/src/CoopSemaphore.h
Normal file
93
arduino-cli/libraries/CoopTask/src/CoopSemaphore.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
CoopSemaphore.h - Implementation of a semaphore for cooperative scheduling tasks
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __CoopSemaphore_h
|
||||
#define __CoopSemaphore_h
|
||||
|
||||
#include "CoopTaskBase.h"
|
||||
#include "circular_queue/circular_queue.h"
|
||||
|
||||
/// A semaphore that is safe to use from CoopTasks.
|
||||
/// Only post() is safe to use from interrupt service routines,
|
||||
/// or concurrent OS threads that must synchronized with the singled thread running CoopTasks.
|
||||
class CoopSemaphore
|
||||
{
|
||||
protected:
|
||||
std::atomic<unsigned> value;
|
||||
std::atomic<CoopTaskBase*> pendingTask0;
|
||||
circular_queue<CoopTaskBase*> pendingTasks;
|
||||
|
||||
// capture-less functions for iterators.
|
||||
static void awakeAndSchedule(CoopTaskBase*&& task)
|
||||
{
|
||||
task->scheduleTask(true);
|
||||
}
|
||||
static bool notIsSelfTask(CoopTaskBase*& task)
|
||||
{
|
||||
return CoopTaskBase::self() != task;
|
||||
}
|
||||
|
||||
/// @param withDeadline true: the ms parameter specifies the relative timeout for a successful
|
||||
/// aquisition of the semaphore.
|
||||
/// false: there is no deadline, the ms parameter is disregarded.
|
||||
/// @param ms the relative timeout measured in milliseconds.
|
||||
/// @returns: true if it sucessfully acquired the semaphore, either immediately or after sleeping.
|
||||
/// false if the deadline expired, or the maximum number of pending tasks is exceeded.
|
||||
bool _wait(const bool withDeadline = false, const uint32_t ms = 0);
|
||||
|
||||
public:
|
||||
/// @param val the initial value of the semaphore.
|
||||
/// @param maxPending the maximum supported number of concurrently waiting tasks.
|
||||
CoopSemaphore(unsigned val, size_t maxPending = 10) : value(val), pendingTask0(nullptr), pendingTasks(maxPending) {}
|
||||
CoopSemaphore(const CoopSemaphore&) = delete;
|
||||
CoopSemaphore& operator=(const CoopSemaphore&) = delete;
|
||||
~CoopSemaphore()
|
||||
{
|
||||
// wake up all queued tasks
|
||||
pendingTasks.for_each(awakeAndSchedule);
|
||||
}
|
||||
|
||||
/// post() is the only operation that is allowed from an interrupt service routine,
|
||||
/// or a concurrent OS thread that is synchronized with the singled thread running CoopTasks.
|
||||
bool IRAM_ATTR post();
|
||||
|
||||
/// @param newVal: the semaphore is immediately set to the specified value. if newVal is greater
|
||||
/// than the current semaphore value, the behavior is identical to as many post operations.
|
||||
bool setval(unsigned newVal);
|
||||
|
||||
/// @returns: true if it sucessfully acquired the semaphore, either immediately or after sleeping.
|
||||
/// false if the maximum number of pending tasks is exceeded.
|
||||
bool wait()
|
||||
{
|
||||
return _wait();
|
||||
}
|
||||
|
||||
/// @param ms the relative timeout, measured in milliseconds, for a successful aquisition of the semaphore.
|
||||
/// @returns: true if it sucessfully acquired the semaphore, either immediately or after sleeping.
|
||||
/// false if the deadline expired, or the maximum number of pending tasks is exceeded.
|
||||
bool wait(uint32_t ms)
|
||||
{
|
||||
return _wait(true, ms);
|
||||
}
|
||||
|
||||
/// @returns: true if the semaphore was acquired immediately, otherwise false.
|
||||
bool try_wait();
|
||||
};
|
||||
|
||||
#endif // __CoopSemaphore_h
|
||||
129
arduino-cli/libraries/CoopTask/src/CoopTask.h
Normal file
129
arduino-cli/libraries/CoopTask/src/CoopTask.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
CoopTask.h - Implementation of cooperative scheduling tasks
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __CoopTask_h
|
||||
#define __CoopTask_h
|
||||
|
||||
#include "BasicCoopTask.h"
|
||||
|
||||
template<typename Result = int, class StackAllocator = CoopTaskStackAllocator> class CoopTask : public BasicCoopTask<StackAllocator>
|
||||
{
|
||||
public:
|
||||
using taskfunction_t = Delegate< Result() >;
|
||||
|
||||
#if defined(ARDUINO)
|
||||
CoopTask(const String& name, CoopTask::taskfunction_t _func, size_t stackSize = BasicCoopTask<StackAllocator>::DEFAULTTASKSTACKSIZE) :
|
||||
#else
|
||||
CoopTask(const std::string& name, CoopTask::taskfunction_t _func, size_t stackSize = BasicCoopTask<StackAllocator>::DEFAULTTASKSTACKSIZE) :
|
||||
#endif
|
||||
// Wrap _func into _exit() to capture return value as exit code
|
||||
BasicCoopTask<StackAllocator>(name, captureFuncReturn, stackSize), func(_func)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
Result _exitCode = {};
|
||||
|
||||
static void captureFuncReturn() noexcept
|
||||
{
|
||||
#if !defined(ARDUINO)
|
||||
try {
|
||||
#endif
|
||||
self()->_exitCode = self()->func();
|
||||
#if !defined(ARDUINO)
|
||||
}
|
||||
catch (const Result code)
|
||||
{
|
||||
self()->_exitCode = code;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void _exit(Result&& code = Result{}) noexcept
|
||||
{
|
||||
_exitCode = std::move(code);
|
||||
BasicCoopTask<StackAllocator>::_exit();
|
||||
}
|
||||
void _exit(const Result& code) noexcept
|
||||
{
|
||||
_exitCode = code;
|
||||
BasicCoopTask<StackAllocator>::_exit();
|
||||
}
|
||||
|
||||
private:
|
||||
taskfunction_t func;
|
||||
|
||||
public:
|
||||
/// @returns: The exit code is either the return value of of the task function, or set by using the exit() function.
|
||||
Result exitCode() const noexcept { return _exitCode; }
|
||||
|
||||
/// @returns: a pointer to the CoopTask instance that is running. nullptr if not called from a CoopTask function (running() == false).
|
||||
static CoopTask* self() noexcept { return static_cast<CoopTask*>(BasicCoopTask<StackAllocator>::self()); }
|
||||
|
||||
/// Use only in running CoopTask function. As stack unwinding is corrupted
|
||||
/// by exit(), which among other issues breaks the RAII idiom,
|
||||
/// using regular return or exceptions is to be preferred in most cases.
|
||||
/// @param code default exit code is default value of CoopTask<>'s template argument, use exit() to set a different value.
|
||||
static void exit(Result&& code = Result{}) noexcept { self()->_exit(std::move(code)); }
|
||||
|
||||
/// Use only in running CoopTask function. As stack unwinding is corrupted
|
||||
/// by exit(), which among other issues breaks the RAII idiom,
|
||||
/// using regular return or exceptions is to be preferred in most cases.
|
||||
/// @param code default exit code is default value of CoopTask<>'s template argument, use exit() to set a different value.
|
||||
static void exit(const Result& code) noexcept { self()->_exit(code); }
|
||||
};
|
||||
|
||||
template<class StackAllocator> class CoopTask<void, StackAllocator> : public BasicCoopTask<StackAllocator>
|
||||
{
|
||||
public:
|
||||
using CoopTaskBase::taskfunction_t;
|
||||
|
||||
#if defined(ARDUINO)
|
||||
CoopTask(const String& name, CoopTaskBase::taskfunction_t func, size_t stackSize = BasicCoopTask<StackAllocator>::DEFAULTTASKSTACKSIZE) :
|
||||
#else
|
||||
CoopTask(const std::string& name, CoopTaskBase::taskfunction_t func, size_t stackSize = BasicCoopTask<StackAllocator>::DEFAULTTASKSTACKSIZE) :
|
||||
#endif
|
||||
BasicCoopTask<StackAllocator>(name, func, stackSize)
|
||||
{
|
||||
}
|
||||
|
||||
/// @returns: a pointer to the CoopTask instance that is running. nullptr if not called from a CoopTask function (running() == false).
|
||||
static CoopTask* self() noexcept { return static_cast<CoopTask*>(BasicCoopTask<StackAllocator>::self()); }
|
||||
};
|
||||
|
||||
/// A convenience function that creates a new CoopTask instance for the supplied task function, with the
|
||||
/// given name and stack size, and schedules it.
|
||||
/// @returns: the pointer to the new CoopTask instance, or nullptr if the creation or preparing for scheduling failed.
|
||||
template<typename Result = int, class StackAllocator = CoopTaskStackAllocator>
|
||||
CoopTask<Result, StackAllocator>* createCoopTask(
|
||||
#if defined(ARDUINO)
|
||||
const String& name, typename CoopTask<Result, StackAllocator>::taskfunction_t func, size_t stackSize = CoopTaskBase::DEFAULTTASKSTACKSIZE)
|
||||
#else
|
||||
const std::string& name, typename CoopTask<Result, StackAllocator>::taskfunction_t func, size_t stackSize = CoopTaskBase::DEFAULTTASKSTACKSIZE)
|
||||
#endif
|
||||
{
|
||||
auto task = new CoopTask<Result, StackAllocator>(name, func, stackSize);
|
||||
if (task && task->scheduleTask()) return task;
|
||||
delete task;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif // __CoopTask_h
|
||||
965
arduino-cli/libraries/CoopTask/src/CoopTaskBase.cpp
Normal file
965
arduino-cli/libraries/CoopTask/src/CoopTaskBase.cpp
Normal file
@@ -0,0 +1,965 @@
|
||||
/*
|
||||
CoopTaskBase.cpp - Implementation of cooperative scheduling tasks
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "CoopTaskBase.h"
|
||||
#ifdef ARDUINO
|
||||
#include <alloca.h>
|
||||
#else
|
||||
#include <chrono>
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <Schedule.h>
|
||||
#include <interrupts.h>
|
||||
using esp8266::InterruptLock;
|
||||
#elif !defined(ESP32) && defined(ARDUINO)
|
||||
class InterruptLock {
|
||||
public:
|
||||
InterruptLock() {
|
||||
noInterrupts();
|
||||
}
|
||||
~InterruptLock() {
|
||||
interrupts();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
// Integration into global yield() and delay()
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
void __yield();
|
||||
|
||||
void yield()
|
||||
{
|
||||
auto self = CoopTaskBase::self();
|
||||
if (self) CoopTaskBase::yield(self);
|
||||
else __yield();
|
||||
}
|
||||
|
||||
#elif defined(ARDUINO)
|
||||
void yield()
|
||||
{
|
||||
auto self = CoopTaskBase::self();
|
||||
if (self) CoopTaskBase::yield(self);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
void __delay(unsigned long ms);
|
||||
|
||||
void delay(unsigned long ms)
|
||||
{
|
||||
auto self = CoopTaskBase::self();
|
||||
if (self) CoopTaskBase::delay(self, ms);
|
||||
else __delay(ms);
|
||||
}
|
||||
|
||||
#if defined(HAVE_ESP_SUSPEND)
|
||||
void __esp_suspend();
|
||||
|
||||
// disable CONT suspend, resume by esp_schedule pattern
|
||||
void esp_suspend()
|
||||
{
|
||||
auto self = CoopTaskBase::self();
|
||||
if (self) CoopTaskBase::yield(self);
|
||||
else __esp_suspend();
|
||||
}
|
||||
#else
|
||||
void __esp_yield();
|
||||
|
||||
// disable CONT suspend, resume by esp_schedule pattern
|
||||
void esp_yield()
|
||||
{
|
||||
auto self = CoopTaskBase::self();
|
||||
if (self) CoopTaskBase::yield(self);
|
||||
else __esp_yield();
|
||||
}
|
||||
#endif
|
||||
|
||||
void __esp_delay(unsigned long ms);
|
||||
|
||||
// disable CONT suspend, resume by esp_schedule pattern
|
||||
void esp_delay(unsigned long ms)
|
||||
{
|
||||
auto self = CoopTaskBase::self();
|
||||
if (self) CoopTaskBase::yield(self);
|
||||
else __esp_delay(ms);
|
||||
}
|
||||
|
||||
#elif defined(ESP32) && !defined(ESP32_FREERTOS)
|
||||
void __delay(uint32_t ms);
|
||||
|
||||
void delay(uint32_t ms)
|
||||
{
|
||||
auto self = CoopTaskBase::self();
|
||||
if (self) CoopTaskBase::delay(self, ms);
|
||||
else __delay(ms);
|
||||
}
|
||||
#endif // ESP32_FREERTOS
|
||||
}
|
||||
|
||||
std::array< std::atomic<CoopTaskBase* >, CoopTaskBase::MAXNUMBERCOOPTASKS + 1> CoopTaskBase::runnableTasks {};
|
||||
std::atomic<size_t> CoopTaskBase::runnableTasksCount(0);
|
||||
|
||||
CoopTaskBase* CoopTaskBase::current = nullptr;
|
||||
|
||||
#ifndef ARDUINO
|
||||
namespace
|
||||
{
|
||||
uint32_t millis()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
uint32_t micros()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
void delayMicroseconds(uint32_t us)
|
||||
{
|
||||
const uint32_t start = micros();
|
||||
while (micros() - start < us) {}
|
||||
}
|
||||
}
|
||||
#elif defined(ESP8266) || defined(ESP32)
|
||||
namespace
|
||||
{
|
||||
static const uint32_t CYCLES_PER_MS = ESP.getCpuFreqMHz() * 1000;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
bool CoopTaskBase::usingBuiltinScheduler = false;
|
||||
|
||||
bool CoopTaskBase::rescheduleTask(uint32_t repeat_us)
|
||||
{
|
||||
auto stat = run();
|
||||
if (sleeping()) return false;
|
||||
switch (stat)
|
||||
{
|
||||
case -1: // exited.
|
||||
return false;
|
||||
break;
|
||||
case 0: // runnable.
|
||||
// rather keep scheduling at wrong delayed interval than drop altogether
|
||||
if (repeat_us)
|
||||
{
|
||||
return !schedule_recurrent_function_us([this]() { return rescheduleTask(0); }, 0);
|
||||
}
|
||||
break;
|
||||
default: // delayed for stat milliseconds or microseconds, check delayIsMs().
|
||||
uint32_t next_repeat_us = delayIsMs() ? stat * 1000 : stat;
|
||||
if (next_repeat_us > 26000000) next_repeat_us = 26000000;
|
||||
if (next_repeat_us == repeat_us) break;
|
||||
// rather keep scheduling at wrong interval than drop altogether
|
||||
return !schedule_recurrent_function_us([this, next_repeat_us]() { return rescheduleTask(next_repeat_us); }, next_repeat_us, [this]() { return !delayed(); });
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool IRAM_ATTR CoopTaskBase::enrollRunnable()
|
||||
{
|
||||
bool enrolled = false;
|
||||
bool inserted = false;
|
||||
for (size_t i = 0; i < runnableTasks.size(); ++i)
|
||||
{
|
||||
#if !defined(ESP32) && defined(ARDUINO)
|
||||
InterruptLock lock;
|
||||
auto task = runnableTasks[i].load();
|
||||
if (!enrolled && nullptr == task)
|
||||
{
|
||||
runnableTasks[i].store(this);
|
||||
enrolled = true;
|
||||
inserted = true;
|
||||
}
|
||||
else if (this == task)
|
||||
{
|
||||
if (enrolled)
|
||||
{
|
||||
runnableTasks[i].store(nullptr);
|
||||
inserted = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
enrolled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inserted) runnableTasksCount.store(runnableTasksCount.load() + 1);
|
||||
#else
|
||||
CoopTaskBase* cmpTo = nullptr;
|
||||
if (!enrolled && runnableTasks[i].compare_exchange_strong(cmpTo, this))
|
||||
{
|
||||
enrolled = true;
|
||||
inserted = true;
|
||||
}
|
||||
else if (enrolled)
|
||||
{
|
||||
cmpTo = this;
|
||||
if (runnableTasks[i].compare_exchange_strong(cmpTo, nullptr))
|
||||
{
|
||||
inserted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (this == runnableTasks[i].load())
|
||||
{
|
||||
enrolled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inserted) ++runnableTasksCount;
|
||||
#endif
|
||||
return enrolled;
|
||||
}
|
||||
|
||||
void CoopTaskBase::delistRunnable()
|
||||
{
|
||||
#if !defined(ESP32) && defined(ARDUINO)
|
||||
InterruptLock lock;
|
||||
for (size_t i = 0; i < runnableTasks.size(); ++i)
|
||||
{
|
||||
if (runnableTasks[i].load() == this)
|
||||
{
|
||||
runnableTasks[i].store(nullptr);
|
||||
runnableTasksCount.store(runnableTasksCount.load() - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (size_t i = 0; i < runnableTasks.size(); ++i)
|
||||
{
|
||||
CoopTaskBase* self = this;
|
||||
if (runnableTasks[i].compare_exchange_strong(self, nullptr))
|
||||
{
|
||||
--runnableTasksCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IRAM_ATTR CoopTaskBase::scheduleTask(bool wakeup)
|
||||
{
|
||||
if (!*this || !enrollRunnable()) return false;
|
||||
#if defined(ESP8266)
|
||||
bool reschedule = usingBuiltinScheduler && sleeping();
|
||||
#endif
|
||||
if (wakeup)
|
||||
{
|
||||
sleep(false);
|
||||
}
|
||||
#if defined(ESP8266)
|
||||
return !reschedule || schedule_function([this]() { rescheduleTask(1); });
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
CoopTaskBase::~CoopTaskBase()
|
||||
{
|
||||
if (taskFiber) DeleteFiber(taskFiber);
|
||||
delistRunnable();
|
||||
}
|
||||
|
||||
LPVOID CoopTaskBase::primaryFiber = nullptr;
|
||||
|
||||
void __stdcall CoopTaskBase::taskFiberFunc(void* self)
|
||||
{
|
||||
static_cast<CoopTaskBase*>(self)->func();
|
||||
static_cast<CoopTaskBase*>(self)->_exit();
|
||||
}
|
||||
|
||||
|
||||
int32_t CoopTaskBase::initialize()
|
||||
{
|
||||
if (!cont || init) return -1;
|
||||
init = true;
|
||||
if (*this)
|
||||
{
|
||||
if (!primaryFiber) primaryFiber = ConvertThreadToFiber(nullptr);
|
||||
if (primaryFiber)
|
||||
{
|
||||
taskFiber = CreateFiber(taskStackSize, taskFiberFunc, this);
|
||||
if (taskFiber) return 0;
|
||||
}
|
||||
}
|
||||
cont = false;
|
||||
delistRunnable();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t CoopTaskBase::run()
|
||||
{
|
||||
if (!cont) return -1;
|
||||
if (sleeps.load()) return 0;
|
||||
if (delays.load())
|
||||
{
|
||||
if (delay_ms)
|
||||
{
|
||||
auto expired = millis() - delay_start;
|
||||
if (expired < delay_duration)
|
||||
{
|
||||
auto delay_rem = delay_duration - expired;
|
||||
return static_cast<int32_t>(delay_rem) < 0 ? DELAY_MAXINT : delay_rem;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto expired = micros() - delay_start;
|
||||
if (expired < delay_duration)
|
||||
{
|
||||
auto delay_rem = delay_duration - expired;
|
||||
if (delay_rem >= DELAYMICROS_THRESHOLD)
|
||||
{
|
||||
return static_cast<int32_t>(delay_rem) < 0 ? DELAY_MAXINT : delay_rem;
|
||||
}
|
||||
::delayMicroseconds(delay_rem);
|
||||
}
|
||||
}
|
||||
delays.store(false);
|
||||
delay_duration = 0;
|
||||
}
|
||||
current = this;
|
||||
if (!init && initialize() < 0) return -1;
|
||||
SwitchToFiber(taskFiber);
|
||||
current = nullptr;
|
||||
|
||||
// val = 0: init; -1: exit() task; 1: yield task; 2: sleep task; 3: delay task for delay_duration
|
||||
cont = cont && (val > 0);
|
||||
sleeps.store(sleeps.load() || (val == 2));
|
||||
delays.store(delays.load() || (val > 2));
|
||||
|
||||
if (!cont) {
|
||||
DeleteFiber(taskFiber);
|
||||
taskFiber = NULL;
|
||||
delistRunnable();
|
||||
return -1;
|
||||
}
|
||||
switch (val)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
return 0;
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
return static_cast<int32_t>(delay_duration) < 0 ? DELAY_MAXINT : delay_duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t CoopTaskBase::getFreeStack() const
|
||||
{
|
||||
return taskFiber ? taskStackSize : 0;
|
||||
}
|
||||
|
||||
void CoopTaskBase::doYield(unsigned val) noexcept
|
||||
{
|
||||
self()->val = val;
|
||||
SwitchToFiber(primaryFiber);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_delay(uint32_t ms) noexcept
|
||||
{
|
||||
delay_ms = true;
|
||||
delay_start = millis();
|
||||
delay_duration = ms;
|
||||
// CoopTask::run() defers task for delay_duration milliseconds.
|
||||
doYield(3);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_delayMicroseconds(uint32_t us) noexcept
|
||||
{
|
||||
if (us < DELAYMICROS_THRESHOLD) {
|
||||
::delayMicroseconds(us);
|
||||
return;
|
||||
}
|
||||
delay_ms = false;
|
||||
delay_start = micros();
|
||||
delay_duration = us;
|
||||
// CoopTask::run() defers task for delay_duration microseconds.
|
||||
doYield(3);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_exit() noexcept
|
||||
{
|
||||
self()->val = -1;
|
||||
SwitchToFiber(primaryFiber);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_yield() noexcept
|
||||
{
|
||||
doYield(1);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_sleep() noexcept
|
||||
{
|
||||
doYield(2);
|
||||
}
|
||||
|
||||
void IRAM_ATTR CoopTaskBase::sleep(const bool state) noexcept
|
||||
{
|
||||
sleeps.store(state);
|
||||
if (!state)
|
||||
{
|
||||
delays.store(false);
|
||||
delay_duration = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(ESP32_FREERTOS)
|
||||
|
||||
CoopTaskBase::~CoopTaskBase()
|
||||
{
|
||||
if (taskHandle) vTaskDelete(taskHandle);
|
||||
taskHandle = nullptr;
|
||||
delistRunnable();
|
||||
}
|
||||
|
||||
void CoopTaskBase::taskFunc(void* _self)
|
||||
{
|
||||
static_cast<CoopTaskBase*>(_self)->func();
|
||||
static_cast<CoopTaskBase*>(_self)->_exit();
|
||||
}
|
||||
|
||||
int32_t CoopTaskBase::initialize()
|
||||
{
|
||||
if (!cont || init) return -1;
|
||||
init = true;
|
||||
if (*this)
|
||||
{
|
||||
xTaskCreateUniversal(taskFunc, name().c_str(), taskStackSize, this, tskIDLE_PRIORITY, &taskHandle, CONFIG_ARDUINO_RUNNING_CORE);
|
||||
if (taskHandle)
|
||||
{
|
||||
vTaskSuspend(taskHandle);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
cont = false;
|
||||
delistRunnable();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t CoopTaskBase::run()
|
||||
{
|
||||
if (!cont) return -1;
|
||||
if (sleeps.load()) return 0;
|
||||
if (delays.load())
|
||||
{
|
||||
if (delay_ms)
|
||||
{
|
||||
if (0 == delay_duration && eSuspended != eTaskGetState(taskHandle))
|
||||
{
|
||||
// fall through, blocked during FreeRTOS delay or asynchronously ready after is specifically handled below
|
||||
}
|
||||
else
|
||||
{
|
||||
auto expired = (ESP.getCycleCount() - delay_start) / CYCLES_PER_MS;
|
||||
while (expired && delay_duration)
|
||||
{
|
||||
delay_start += CYCLES_PER_MS;
|
||||
--delay_duration;
|
||||
--expired;
|
||||
}
|
||||
if (expired < delay_duration)
|
||||
{
|
||||
auto delay_rem = delay_duration - expired;
|
||||
return static_cast<int32_t>(delay_rem) < 0 ? DELAY_MAXINT : delay_rem;
|
||||
}
|
||||
delays.store(false);
|
||||
delay_duration = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto expired = micros() - delay_start;
|
||||
if (expired < delay_duration)
|
||||
{
|
||||
auto delay_rem = delay_duration - expired;
|
||||
if (delay_rem >= DELAYMICROS_THRESHOLD)
|
||||
{
|
||||
return static_cast<int32_t>(delay_rem) < 0 ? DELAY_MAXINT : delay_rem;
|
||||
}
|
||||
::delayMicroseconds(delay_rem);
|
||||
}
|
||||
delays.store(false);
|
||||
delay_duration = 0;
|
||||
}
|
||||
}
|
||||
|
||||
current = this;
|
||||
|
||||
if (!init)
|
||||
{
|
||||
if (initialize() < 0)
|
||||
{
|
||||
current = nullptr;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
bool resume = true;
|
||||
for (;;)
|
||||
{
|
||||
auto taskState = eTaskGetState(taskHandle);
|
||||
if (eSuspended == taskState)
|
||||
{
|
||||
if (!resume)
|
||||
{
|
||||
vTaskPrioritySet(taskHandle, tskIDLE_PRIORITY);
|
||||
break;
|
||||
}
|
||||
resume = false;
|
||||
vTaskPrioritySet(taskHandle, 1);
|
||||
vTaskResume(taskHandle);
|
||||
continue;
|
||||
}
|
||||
else if (eReady == taskState)
|
||||
{
|
||||
if (resume)
|
||||
{
|
||||
resume = false;
|
||||
sleep(false);
|
||||
delay_duration = 0;
|
||||
vTaskPrioritySet(taskHandle, 1);
|
||||
continue;
|
||||
}
|
||||
vPortYield();
|
||||
continue;
|
||||
}
|
||||
else if (eBlocked == taskState)
|
||||
{
|
||||
if (resume)
|
||||
{
|
||||
if (!delays.load())
|
||||
{
|
||||
vTaskSuspend(taskHandle);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
vTaskPrioritySet(taskHandle, tskIDLE_PRIORITY);
|
||||
if (!delays.exchange(true))
|
||||
{
|
||||
delay_ms = true;
|
||||
delay_start = ESP.getCycleCount();
|
||||
delay_duration = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (eDeleted == taskState)
|
||||
{
|
||||
cont = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
current = nullptr;
|
||||
|
||||
if (!cont) {
|
||||
vTaskDelete(taskHandle);
|
||||
taskHandle = nullptr;
|
||||
delistRunnable();
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int32_t>(delay_duration) < 0 ? DELAY_MAXINT : delay_duration;
|
||||
}
|
||||
|
||||
size_t CoopTaskBase::getFreeStack() const
|
||||
{
|
||||
return taskHandle ? uxTaskGetStackHighWaterMark(taskHandle) : 0;
|
||||
}
|
||||
|
||||
void CoopTaskBase::_delay(uint32_t ms) noexcept
|
||||
{
|
||||
delays.store(true);
|
||||
delay_ms = true;
|
||||
delay_start = ESP.getCycleCount();
|
||||
delay_duration = ms;
|
||||
vTaskSuspend(taskHandle);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_delayMicroseconds(uint32_t us) noexcept
|
||||
{
|
||||
if (us < DELAYMICROS_THRESHOLD) {
|
||||
::delayMicroseconds(us);
|
||||
return;
|
||||
}
|
||||
delays.store(true);
|
||||
delay_ms = false;
|
||||
delay_start = micros();
|
||||
delay_duration = us;
|
||||
vTaskSuspend(taskHandle);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_sleep() noexcept
|
||||
{
|
||||
sleeps.store(true);
|
||||
vTaskSuspend(taskHandle);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_yield() noexcept
|
||||
{
|
||||
delay_duration = 0;
|
||||
delays.store(false);
|
||||
vTaskSuspend(taskHandle);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_exit() noexcept
|
||||
{
|
||||
cont = false;
|
||||
vTaskSuspend(taskHandle);
|
||||
}
|
||||
|
||||
void IRAM_ATTR CoopTaskBase::sleep(const bool state) noexcept
|
||||
{
|
||||
sleeps.store(state);
|
||||
if (!state)
|
||||
{
|
||||
delay_duration = 0;
|
||||
delays.store(false);
|
||||
}
|
||||
}
|
||||
|
||||
CoopTaskBase* CoopTaskBase::self() noexcept
|
||||
{
|
||||
const auto currentTaskHandle = xTaskGetCurrentTaskHandle();
|
||||
auto cur = current;
|
||||
if (cur && currentTaskHandle == cur->taskHandle) return cur;
|
||||
for (size_t i = 0; i < runnableTasks.size(); ++i)
|
||||
{
|
||||
cur = runnableTasks[i].load();
|
||||
if (cur && currentTaskHandle == cur->taskHandle) return cur;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
jmp_buf CoopTaskBase::env;
|
||||
|
||||
CoopTaskBase::~CoopTaskBase()
|
||||
{
|
||||
delistRunnable();
|
||||
}
|
||||
|
||||
int32_t CoopTaskBase::initialize()
|
||||
{
|
||||
if (!cont || init) return -1;
|
||||
init = true;
|
||||
// fill stack with magic values to check overflow, corruption, and high water mark
|
||||
for (size_t pos = 0; pos <= (taskStackSize + (FULLFEATURES ? sizeof(STACKCOOKIE) : 0)) / sizeof(STACKCOOKIE); ++pos)
|
||||
{
|
||||
reinterpret_cast<unsigned*>(taskStackTop)[pos] = STACKCOOKIE;
|
||||
}
|
||||
#if defined(__GNUC__) && (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64))
|
||||
asm volatile (
|
||||
"movq %0, %%rsp"
|
||||
:
|
||||
: "r" (((reinterpret_cast<long unsigned>(taskStackTop) + taskStackSize + (FULLFEATURES ? sizeof(STACKCOOKIE) : 0)) >> 4) << 4)
|
||||
);
|
||||
#elif defined(ARDUINO) || defined(__GNUC__)
|
||||
char* bp = static_cast<char*>(alloca(
|
||||
reinterpret_cast<long unsigned>(&bp) - reinterpret_cast<long unsigned>(taskStackTop) - (taskStackSize + (FULLFEATURES ? sizeof(STACKCOOKIE) : 0))
|
||||
));
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
#else
|
||||
#error Setting stack pointer is not implemented on this target
|
||||
#endif
|
||||
func();
|
||||
self()->_exit();
|
||||
cont = false;
|
||||
delistRunnable();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t CoopTaskBase::run()
|
||||
{
|
||||
if (!cont) return -1;
|
||||
if (sleeps.load()) return 0;
|
||||
if (delays.load())
|
||||
{
|
||||
if (delay_ms)
|
||||
{
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
uint32_t expired;
|
||||
#ifdef ESP8266
|
||||
if (usingBuiltinScheduler)
|
||||
{
|
||||
expired = millis() - delay_start;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
expired = (ESP.getCycleCount() - delay_start) / CYCLES_PER_MS;
|
||||
while (expired && delay_duration)
|
||||
{
|
||||
delay_start += CYCLES_PER_MS;
|
||||
--delay_duration;
|
||||
--expired;
|
||||
}
|
||||
}
|
||||
#else
|
||||
auto expired = millis() - delay_start;
|
||||
#endif
|
||||
if (expired < delay_duration)
|
||||
{
|
||||
auto delay_rem = delay_duration - expired;
|
||||
return static_cast<int32_t>(delay_rem) < 0 ? DELAY_MAXINT : delay_rem;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto expired = micros() - delay_start;
|
||||
if (expired < delay_duration)
|
||||
{
|
||||
auto delay_rem = delay_duration - expired;
|
||||
if (delay_rem >= DELAYMICROS_THRESHOLD)
|
||||
{
|
||||
return static_cast<int32_t>(delay_rem) < 0 ? DELAY_MAXINT : delay_rem;
|
||||
}
|
||||
::delayMicroseconds(delay_rem);
|
||||
}
|
||||
}
|
||||
delays.store(false);
|
||||
delay_duration = 0;
|
||||
}
|
||||
auto val = setjmp(env);
|
||||
// val = 0: init; -1: exit() task; 1: yield task; 2: sleep task; 3: delay task for delay_duration
|
||||
if (!val) {
|
||||
current = this;
|
||||
if (!init) return initialize();
|
||||
if (FULLFEATURES && *reinterpret_cast<unsigned*>(taskStackTop + taskStackSize + sizeof(STACKCOOKIE)) != STACKCOOKIE)
|
||||
{
|
||||
#ifndef ARDUINO_attiny
|
||||
::printf(PSTR("FATAL ERROR: CoopTask %s stack corrupted\n"), name().c_str());
|
||||
#endif
|
||||
::abort();
|
||||
}
|
||||
|
||||
longjmp(env_yield, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
current = nullptr;
|
||||
if (*reinterpret_cast<unsigned*>(taskStackTop) != STACKCOOKIE)
|
||||
{
|
||||
#ifndef ARDUINO_attiny
|
||||
::printf(PSTR("FATAL ERROR: CoopTask %s stack overflow\n"), name().c_str());
|
||||
#endif
|
||||
::abort();
|
||||
}
|
||||
cont = cont && (val > 0);
|
||||
sleeps.store(sleeps.load() || (val == 2));
|
||||
delays.store(delays.load() || (val > 2));
|
||||
}
|
||||
if (!cont) {
|
||||
delistRunnable();
|
||||
return -1;
|
||||
}
|
||||
switch (val)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
return 0;
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
return static_cast<int32_t>(delay_duration) < 0 ? DELAY_MAXINT : delay_duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CoopTaskBase::dumpStack() const
|
||||
{
|
||||
if (!taskStackTop) return;
|
||||
size_t pos;
|
||||
for (pos = 1; pos < (taskStackSize + (FULLFEATURES ? sizeof(STACKCOOKIE) : 0)) / sizeof(STACKCOOKIE); ++pos)
|
||||
{
|
||||
if (STACKCOOKIE != reinterpret_cast<unsigned*>(taskStackTop)[pos])
|
||||
break;
|
||||
}
|
||||
#ifndef ARDUINO_attiny
|
||||
::printf(PSTR(">>>stack>>>\n"));
|
||||
#endif
|
||||
while (pos < (taskStackSize + (FULLFEATURES ? sizeof(STACKCOOKIE) : 0)) / sizeof(STACKCOOKIE))
|
||||
{
|
||||
#ifndef ARDUINO_attiny
|
||||
auto* sp = &reinterpret_cast<unsigned*>(taskStackTop)[pos];
|
||||
|
||||
// rough indicator: stack frames usually have SP saved as the second word
|
||||
bool looksLikeStackFrame = (sp[2] == reinterpret_cast<size_t>(&sp[4]));
|
||||
::printf(PSTR("%08x: %08x %08x %08x %08x %c\n"),
|
||||
reinterpret_cast<size_t>(sp), sp[0], sp[1], sp[2], sp[3], looksLikeStackFrame ? '<' : ' ');
|
||||
#endif
|
||||
|
||||
pos += 4;
|
||||
}
|
||||
#ifndef ARDUINO_attiny
|
||||
::printf(PSTR("<<<stack<<<\n"));
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t CoopTaskBase::getFreeStack() const
|
||||
{
|
||||
if (!taskStackTop) return 0;
|
||||
size_t pos;
|
||||
for (pos = 1; pos < (taskStackSize + (FULLFEATURES ? sizeof(STACKCOOKIE) : 0)) / sizeof(STACKCOOKIE); ++pos)
|
||||
{
|
||||
if (STACKCOOKIE != reinterpret_cast<unsigned*>(taskStackTop)[pos])
|
||||
break;
|
||||
}
|
||||
return (pos - 1) * sizeof(unsigned);
|
||||
}
|
||||
|
||||
void CoopTaskBase::doYield(unsigned val) noexcept
|
||||
{
|
||||
if (!setjmp(env_yield))
|
||||
{
|
||||
longjmp(env, val);
|
||||
}
|
||||
}
|
||||
|
||||
void CoopTaskBase::_delay(uint32_t ms) noexcept
|
||||
{
|
||||
delay_ms = true;
|
||||
#ifdef ESP8266
|
||||
delay_start = usingBuiltinScheduler ? millis() : ESP.getCycleCount();
|
||||
#elif ESP32
|
||||
delay_start = ESP.getCycleCount();
|
||||
#else
|
||||
delay_start = millis();
|
||||
#endif
|
||||
delay_duration = ms;
|
||||
// CoopTask::run() defers task for delay_duration milliseconds.
|
||||
doYield(3);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_delayMicroseconds(uint32_t us) noexcept
|
||||
{
|
||||
if (us < DELAYMICROS_THRESHOLD) {
|
||||
::delayMicroseconds(us);
|
||||
return;
|
||||
}
|
||||
delay_ms = false;
|
||||
delay_start = micros();
|
||||
delay_duration = us;
|
||||
// CoopTask::run() defers task for delay_duration microseconds.
|
||||
doYield(3);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_exit() noexcept
|
||||
{
|
||||
longjmp(env, -1);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_yield() noexcept
|
||||
{
|
||||
doYield(1);
|
||||
}
|
||||
|
||||
void CoopTaskBase::_sleep() noexcept
|
||||
{
|
||||
doYield(2);
|
||||
}
|
||||
|
||||
void IRAM_ATTR CoopTaskBase::sleep(const bool state) noexcept
|
||||
{
|
||||
sleeps.store(state);
|
||||
if (!state)
|
||||
{
|
||||
delays.store(false);
|
||||
delay_duration = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
void runCoopTasks(const Delegate<void(const CoopTaskBase* const task)>& reaper,
|
||||
const Delegate<bool(uint32_t ms)>& onDelay, const Delegate<bool()>& onSleep)
|
||||
{
|
||||
#ifdef ESP32_FREERTOS
|
||||
static TaskHandle_t yieldGuardHandle = nullptr;
|
||||
if (!yieldGuardHandle)
|
||||
{
|
||||
xTaskCreateUniversal([](void*)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
vPortYield();
|
||||
}
|
||||
}, "YieldGuard", 0x200, nullptr, 1, &yieldGuardHandle, CONFIG_ARDUINO_RUNNING_CORE);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto taskCount = CoopTaskBase::getRunnableTasksCount();
|
||||
bool allSleeping = true;
|
||||
uint32_t minDelay_ms = ~(decltype(minDelay_ms))0U;
|
||||
for (size_t i = 0; taskCount && i < CoopTaskBase::getRunnableTasks().size(); ++i)
|
||||
{
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
optimistic_yield(10000);
|
||||
#endif
|
||||
auto task = CoopTaskBase::getRunnableTasks()[i].load();
|
||||
if (task)
|
||||
{
|
||||
--taskCount;
|
||||
auto runResult = task->run();
|
||||
if (runResult < 0 && reaper)
|
||||
reaper(task);
|
||||
else if (minDelay_ms)
|
||||
{
|
||||
if (task->delayed())
|
||||
{
|
||||
allSleeping = false;
|
||||
uint32_t delay_ms = task->delayIsMs() ? static_cast<uint32_t>(runResult) : static_cast<uint32_t>(runResult) / 1000UL;
|
||||
if (delay_ms < minDelay_ms)
|
||||
minDelay_ms = delay_ms;
|
||||
}
|
||||
else if (!task->sleeping())
|
||||
{
|
||||
allSleeping = false;
|
||||
minDelay_ms = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool cleanup = true;
|
||||
if (allSleeping && onSleep)
|
||||
{
|
||||
cleanup = onSleep();
|
||||
}
|
||||
else if (minDelay_ms && onDelay)
|
||||
{
|
||||
cleanup = onDelay(minDelay_ms);
|
||||
}
|
||||
if (cleanup)
|
||||
{
|
||||
#ifdef ESP32_FREERTOS
|
||||
vTaskSuspend(yieldGuardHandle);
|
||||
vTaskDelay(1);
|
||||
vTaskResume(yieldGuardHandle);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
270
arduino-cli/libraries/CoopTask/src/CoopTaskBase.h
Normal file
270
arduino-cli/libraries/CoopTask/src/CoopTaskBase.h
Normal file
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
CoopTaskBase.h - Implementation of cooperative scheduling tasks
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __CoopTaskBase_h
|
||||
#define __CoopTaskBase_h
|
||||
|
||||
#ifdef ESP32
|
||||
#define ESP32_FREERTOS
|
||||
#endif
|
||||
|
||||
#include "circular_queue/Delegate.h"
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <csetjmp>
|
||||
#include <Arduino.h>
|
||||
#elif defined(ARDUINO)
|
||||
#include <setjmp.h>
|
||||
#include <Arduino.h>
|
||||
#elif defined(_MSC_VER)
|
||||
#include <array>
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
#else
|
||||
#include <array>
|
||||
#include <csetjmp>
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
#include <atomic>
|
||||
#else
|
||||
#include "circular_queue/ghostl.h"
|
||||
#endif
|
||||
|
||||
#if !defined(ESP32) && !defined(ESP8266)
|
||||
#define IRAM_ATTR
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define __attribute__(_)
|
||||
#endif
|
||||
|
||||
class CoopTaskBase
|
||||
{
|
||||
public:
|
||||
static constexpr bool FULLFEATURES = sizeof(unsigned) >= 4;
|
||||
|
||||
protected:
|
||||
using taskfunction_t = Delegate< void() >;
|
||||
|
||||
#ifdef ARDUINO
|
||||
CoopTaskBase(const String& name, taskfunction_t _func, size_t stackSize = DEFAULTTASKSTACKSIZE) :
|
||||
#else
|
||||
CoopTaskBase(const std::string& name, taskfunction_t _func, size_t stackSize = DEFAULTTASKSTACKSIZE) :
|
||||
#endif
|
||||
taskName(name), sleeps(true), delays(false), func(_func)
|
||||
{
|
||||
taskStackSize = (sizeof(unsigned) >= 4) ? ((stackSize + sizeof(unsigned) - 1) / sizeof(unsigned)) * sizeof(unsigned) : stackSize;
|
||||
}
|
||||
CoopTaskBase(const CoopTaskBase&) = delete;
|
||||
CoopTaskBase& operator=(const CoopTaskBase&) = delete;
|
||||
|
||||
static constexpr int32_t DELAYMICROS_THRESHOLD = 50;
|
||||
static constexpr uint32_t DELAY_MAXINT = (~(uint32_t)0) >> 1;
|
||||
|
||||
#ifdef ARDUINO
|
||||
const String taskName;
|
||||
#else
|
||||
const std::string taskName;
|
||||
#endif
|
||||
|
||||
size_t taskStackSize;
|
||||
#if defined(_MSC_VER)
|
||||
static LPVOID primaryFiber;
|
||||
LPVOID taskFiber = nullptr;
|
||||
int val = 0;
|
||||
static void __stdcall taskFiberFunc(void* self);
|
||||
#elif defined(ESP32_FREERTOS)
|
||||
TaskHandle_t taskHandle = nullptr;
|
||||
static void taskFunc(void* _self);
|
||||
#else
|
||||
char* taskStackTop = nullptr;
|
||||
static jmp_buf env;
|
||||
jmp_buf env_yield;
|
||||
#endif
|
||||
static constexpr size_t MAXNUMBERCOOPTASKS = FULLFEATURES ? 32 : 8;
|
||||
// for lock-free insertion, must be one element larger than max task count
|
||||
static std::array< std::atomic<CoopTaskBase* >, MAXNUMBERCOOPTASKS + 1> runnableTasks;
|
||||
static std::atomic<size_t> runnableTasksCount;
|
||||
static CoopTaskBase* current;
|
||||
bool init = false;
|
||||
bool cont = true;
|
||||
std::atomic<bool> sleeps;
|
||||
// ESP32 FreeRTOS (#define ESP32_FREERTOS) handles delays, on this platfrom delays is always false
|
||||
std::atomic<bool> delays;
|
||||
|
||||
int32_t initialize();
|
||||
void doYield(unsigned val) noexcept;
|
||||
|
||||
#if defined(ESP8266)
|
||||
static bool usingBuiltinScheduler;
|
||||
bool rescheduleTask(uint32_t repeat_us);
|
||||
#endif
|
||||
bool IRAM_ATTR enrollRunnable();
|
||||
void delistRunnable();
|
||||
|
||||
void _exit() noexcept;
|
||||
void _yield() noexcept;
|
||||
void _sleep() noexcept;
|
||||
void _delay(uint32_t ms) noexcept;
|
||||
void _delayMicroseconds(uint32_t us) noexcept;
|
||||
|
||||
private:
|
||||
// true: delay_start/delay_duration are in milliseconds; false: delay_start/delay_duration are in microseconds.
|
||||
bool delay_ms = false;
|
||||
uint32_t delay_start = 0;
|
||||
uint32_t delay_duration = 0;
|
||||
|
||||
taskfunction_t func;
|
||||
|
||||
public:
|
||||
virtual ~CoopTaskBase();
|
||||
#if defined(ESP32)
|
||||
static constexpr size_t MAXSTACKSPACE = 0x2000;
|
||||
#elif defined(ESP8266)
|
||||
static constexpr size_t MAXSTACKSPACE = 0x1000;
|
||||
#elif defined(ARDUINO)
|
||||
static constexpr size_t MAXSTACKSPACE = FULLFEATURES ? 0x180 : 0xc0;
|
||||
#else
|
||||
static constexpr size_t MAXSTACKSPACE = 0x10000;
|
||||
#endif
|
||||
static constexpr unsigned STACKCOOKIE = FULLFEATURES ? 0xdeadbeefUL : 0xdeadU;
|
||||
static constexpr size_t DEFAULTTASKSTACKSIZE = MAXSTACKSPACE - (FULLFEATURES ? 2 : 1) * sizeof(STACKCOOKIE);
|
||||
|
||||
#ifdef ARDUINO
|
||||
const String& name() const noexcept { return taskName; }
|
||||
#else
|
||||
const std::string& name() const noexcept { return taskName; }
|
||||
#endif
|
||||
|
||||
/// @returns: true if the CoopTask object is ready to run, including stack allocation.
|
||||
/// false if either initialization has failed, or the task has exited().
|
||||
#if !defined(_MSC_VER) && !defined(ESP32_FREERTOS)
|
||||
operator bool() const noexcept { return cont && taskStackTop; }
|
||||
/// Prints the task stack, decodable by the ESP exception decoder
|
||||
void dumpStack() const;
|
||||
#else
|
||||
operator bool() const noexcept { return cont; }
|
||||
#endif
|
||||
|
||||
/// Ready the task for scheduling, by default waking up the task from both sleep and delay.
|
||||
/// @returns: true on success.
|
||||
bool IRAM_ATTR scheduleTask(bool wakeup = true);
|
||||
inline bool IRAM_ATTR wakeup() __attribute__((always_inline)) { return scheduleTask(true); }
|
||||
|
||||
#ifdef ESP8266
|
||||
/// For full access to all features, cyclic task scheduling, state evaluation
|
||||
/// and running are performed explicitly from user code. For convenience, the function
|
||||
/// runCoopTasks() implements the pattern as best practice. See the CoopTask examples for this.
|
||||
/// If only a pre-determined number of loop tasks need to run indefinitely
|
||||
/// without exit codes or explicit deep sleep system states, on platforms where a
|
||||
/// scheduler exists that is suffiently capable to iteratively run each of these CoopTasks,
|
||||
/// calling this function switches all task creation and scheduling to using that, obviating
|
||||
/// the need to call a scheduler explicitly from user code.
|
||||
/// The scheduler selection should be done before the first CoopTask is created, and not
|
||||
/// changed thereafter during runtime.
|
||||
/// By default, builtin schedulers are not used, for well-defined behavior and portability.
|
||||
/// @param state true: The parameter default value. All subsequent scheduling of tasks is
|
||||
/// handed to the builtin scheduler.
|
||||
static void useBuiltinScheduler(bool state = true)
|
||||
{
|
||||
usingBuiltinScheduler = state;
|
||||
}
|
||||
#endif
|
||||
/// Every task is entered into this list by scheduleTask(). It is removed when it exits
|
||||
/// or gets deleted.
|
||||
static const decltype(runnableTasks)& getRunnableTasks()
|
||||
{
|
||||
return runnableTasks;
|
||||
}
|
||||
/// @returns: the count of runnable, non-nullptr, tasks in the return of getRunnableTasks().
|
||||
static size_t getRunnableTasksCount()
|
||||
{
|
||||
return runnableTasksCount.load();
|
||||
}
|
||||
|
||||
/// @returns: -1: exited. 0: runnable or sleeping. >0: delayed for milliseconds or microseconds, check delayIsMs().
|
||||
int32_t run();
|
||||
|
||||
/// @returns: size of unused stack space. 0 if stack is not allocated yet or was deleted after task exited.
|
||||
size_t getFreeStack() const;
|
||||
|
||||
bool delayIsMs() const noexcept { return delay_ms; }
|
||||
|
||||
/// Modifies the sleep flag. if called from a running task, it is not immediately suspended.
|
||||
/// @param state true: a suspended task becomes sleeping, if call from the running task,
|
||||
/// the next call to yield() or delay() puts it into sleeping state.
|
||||
/// false: clears the sleeping and delay state of the task.
|
||||
void IRAM_ATTR sleep(const bool state) noexcept;
|
||||
|
||||
#ifdef ESP32_FREERTOS
|
||||
/// @returns: a pointer to the CoopTask instance that is running. nullptr if not called from a CoopTask function (running() == false).
|
||||
static CoopTaskBase* self() noexcept;
|
||||
#else
|
||||
/// @returns: a pointer to the CoopTask instance that is running. nullptr if not called from a CoopTask function (running() == false).
|
||||
static CoopTaskBase* self() noexcept { return current; }
|
||||
#endif
|
||||
/// @returns: true if called from the task function of a CoopTask, false otherwise.
|
||||
static bool running() noexcept { return self(); }
|
||||
|
||||
/// @returns: true if the task's is set to sleep.
|
||||
/// For a non-running task, this implies it is also currently not scheduled.
|
||||
inline bool IRAM_ATTR sleeping() const noexcept __attribute__((always_inline)) { return sleeps.load(); }
|
||||
inline bool IRAM_ATTR delayed() const noexcept __attribute__((always_inline)) { return delays.load(); }
|
||||
inline bool IRAM_ATTR suspended() const noexcept __attribute__((always_inline)) { return sleeps.load() || delays.load(); }
|
||||
|
||||
/// use only in running CoopTask function. As stack unwinding is corrupted
|
||||
/// by exit(), which among other issues breaks the RAII idiom,
|
||||
/// using regular return or exceptions is to be preferred in most cases.
|
||||
static void exit() noexcept { self()->_exit(); }
|
||||
/// use only in running CoopTask function.
|
||||
static void yield() noexcept { self()->_yield(); }
|
||||
static void yield(CoopTaskBase* self) noexcept { self->_yield(); }
|
||||
/// use only in running CoopTask function.
|
||||
static void sleep() noexcept { self()->_sleep(); }
|
||||
/// use only in running CoopTask function.
|
||||
static void delay(uint32_t ms) noexcept { self()->_delay(ms); }
|
||||
static void delay(CoopTaskBase* self, uint32_t ms) noexcept { self->_delay(ms); }
|
||||
/// use only in running CoopTask function.
|
||||
static void delayMicroseconds(uint32_t us) noexcept { self()->_delayMicroseconds(us); }
|
||||
};
|
||||
|
||||
#ifndef ARDUINO
|
||||
inline void yield() { CoopTaskBase::yield(); }
|
||||
inline void delay(uint32_t ms) { CoopTaskBase::delay(ms); }
|
||||
#endif
|
||||
|
||||
/// An optional convenience funtion that does all the work to cyclically perform CoopTask execution.
|
||||
/// @param reaper An optional function that is called once when a task exits.
|
||||
/// @param onDelay An optional function to handle a global delay greater or equal 1 millisecond, resulting
|
||||
/// from the minimum time interval for which at this time all CoopTasks are delayed.
|
||||
/// This can be used for power saving, if wake up by asynchronous events is properly considered.
|
||||
/// If onSleep is not set, onDelay() is called instead with the maximum value for the ms delay parameter.
|
||||
/// onDelay() must return a bool value, if true, runCoopTasks performs the default housekeeping actions,
|
||||
/// otherwise it skips those.
|
||||
/// @param onSleep An optional function indicating that all CoopTasks are sleeping, that is, are infinitely delayed.
|
||||
/// This can be used for power saving modes.
|
||||
/// onSleep(), like onDelay(), must return a bool value, if true, runCoopTasks performs the
|
||||
/// default housekeeping actions, otherwise it skips those.
|
||||
void runCoopTasks(const Delegate<void(const CoopTaskBase* const task)>& reaper = nullptr,
|
||||
const Delegate<bool(uint32_t ms)>& onDelay = nullptr, const Delegate<bool()>& onSleep = nullptr);
|
||||
|
||||
#endif // __CoopTaskBase_h
|
||||
2130
arduino-cli/libraries/CoopTask/src/circular_queue/Delegate.h
Normal file
2130
arduino-cli/libraries/CoopTask/src/circular_queue/Delegate.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,567 @@
|
||||
/*
|
||||
MultiDelegate.h - A queue or event multiplexer based on the efficient Delegate
|
||||
class
|
||||
Copyright (c) 2019-2020 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __MULTIDELEGATE_H
|
||||
#define __MULTIDELEGATE_H
|
||||
|
||||
#include <iterator>
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
#include <atomic>
|
||||
#else
|
||||
#include "circular_queue/ghostl.h"
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <interrupts.h>
|
||||
using esp8266::InterruptLock;
|
||||
#elif defined(ARDUINO)
|
||||
class InterruptLock {
|
||||
public:
|
||||
InterruptLock() {
|
||||
noInterrupts();
|
||||
}
|
||||
~InterruptLock() {
|
||||
interrupts();
|
||||
}
|
||||
};
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE = false, typename... P>
|
||||
struct CallP
|
||||
{
|
||||
static R execute(Delegate& del, P... args)
|
||||
{
|
||||
return del(std::forward<P...>(args...));
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, bool ISQUEUE, typename... P>
|
||||
struct CallP<Delegate, void, ISQUEUE, P...>
|
||||
{
|
||||
static bool execute(Delegate& del, P... args)
|
||||
{
|
||||
del(std::forward<P...>(args...));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE = false>
|
||||
struct Call
|
||||
{
|
||||
static R execute(Delegate& del)
|
||||
{
|
||||
return del();
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, bool ISQUEUE>
|
||||
struct Call<Delegate, void, ISQUEUE>
|
||||
{
|
||||
static bool execute(Delegate& del)
|
||||
{
|
||||
del();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace delegate
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32, typename... P>
|
||||
class MultiDelegatePImpl
|
||||
{
|
||||
public:
|
||||
MultiDelegatePImpl() = default;
|
||||
~MultiDelegatePImpl()
|
||||
{
|
||||
*this = nullptr;
|
||||
}
|
||||
|
||||
MultiDelegatePImpl(const MultiDelegatePImpl&) = delete;
|
||||
MultiDelegatePImpl& operator=(const MultiDelegatePImpl&) = delete;
|
||||
|
||||
MultiDelegatePImpl(MultiDelegatePImpl&& md)
|
||||
{
|
||||
first = md.first;
|
||||
last = md.last;
|
||||
unused = md.unused;
|
||||
nodeCount = md.nodeCount;
|
||||
md.first = nullptr;
|
||||
md.last = nullptr;
|
||||
md.unused = nullptr;
|
||||
md.nodeCount = 0;
|
||||
}
|
||||
|
||||
MultiDelegatePImpl(const Delegate& del)
|
||||
{
|
||||
add(del);
|
||||
}
|
||||
|
||||
MultiDelegatePImpl(Delegate&& del)
|
||||
{
|
||||
add(std::move(del));
|
||||
}
|
||||
|
||||
MultiDelegatePImpl& operator=(MultiDelegatePImpl&& md)
|
||||
{
|
||||
first = md.first;
|
||||
last = md.last;
|
||||
unused = md.unused;
|
||||
nodeCount = md.nodeCount;
|
||||
md.first = nullptr;
|
||||
md.last = nullptr;
|
||||
md.unused = nullptr;
|
||||
md.nodeCount = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MultiDelegatePImpl& operator=(std::nullptr_t)
|
||||
{
|
||||
if (last)
|
||||
last->mNext = unused;
|
||||
if (first)
|
||||
unused = first;
|
||||
while (unused)
|
||||
{
|
||||
auto to_delete = unused;
|
||||
unused = unused->mNext;
|
||||
delete(to_delete);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
MultiDelegatePImpl& operator+=(const Delegate& del)
|
||||
{
|
||||
add(del);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MultiDelegatePImpl& operator+=(Delegate&& del)
|
||||
{
|
||||
add(std::move(del));
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
struct Node_t
|
||||
{
|
||||
~Node_t()
|
||||
{
|
||||
mDelegate = nullptr; // special overload in Delegate
|
||||
}
|
||||
Node_t* mNext = nullptr;
|
||||
Delegate mDelegate;
|
||||
};
|
||||
|
||||
Node_t* first = nullptr;
|
||||
Node_t* last = nullptr;
|
||||
Node_t* unused = nullptr;
|
||||
size_t nodeCount = 0;
|
||||
|
||||
// Returns a pointer to an unused Node_t,
|
||||
// or if none are available allocates a new one,
|
||||
// or nullptr if limit is reached
|
||||
Node_t* IRAM_ATTR get_node_unsafe()
|
||||
{
|
||||
Node_t* result = nullptr;
|
||||
// try to get an item from unused items list
|
||||
if (unused)
|
||||
{
|
||||
result = unused;
|
||||
unused = unused->mNext;
|
||||
}
|
||||
// if no unused items, and count not too high, allocate a new one
|
||||
else if (nodeCount < QUEUE_CAPACITY)
|
||||
{
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
result = new (std::nothrow) Node_t;
|
||||
#else
|
||||
result = new Node_t;
|
||||
#endif
|
||||
if (result)
|
||||
++nodeCount;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void recycle_node_unsafe(Node_t* node)
|
||||
{
|
||||
node->mDelegate = nullptr; // special overload in Delegate
|
||||
node->mNext = unused;
|
||||
unused = node;
|
||||
}
|
||||
|
||||
#ifndef ARDUINO
|
||||
std::mutex mutex_unused;
|
||||
#endif
|
||||
public:
|
||||
class iterator : public std::iterator<std::forward_iterator_tag, Delegate>
|
||||
{
|
||||
public:
|
||||
Node_t* current = nullptr;
|
||||
Node_t* prev = nullptr;
|
||||
const Node_t* stop = nullptr;
|
||||
|
||||
iterator(MultiDelegatePImpl& md) : current(md.first), stop(md.last) {}
|
||||
iterator() = default;
|
||||
iterator(const iterator&) = default;
|
||||
iterator& operator=(const iterator&) = default;
|
||||
iterator& operator=(iterator&&) = default;
|
||||
operator bool() const
|
||||
{
|
||||
return current && stop;
|
||||
}
|
||||
bool operator==(const iterator& rhs) const
|
||||
{
|
||||
return current == rhs.current;
|
||||
}
|
||||
bool operator!=(const iterator& rhs) const
|
||||
{
|
||||
return !operator==(rhs);
|
||||
}
|
||||
Delegate& operator*() const
|
||||
{
|
||||
return current->mDelegate;
|
||||
}
|
||||
Delegate* operator->() const
|
||||
{
|
||||
return ¤t->mDelegate;
|
||||
}
|
||||
iterator& operator++() // prefix
|
||||
{
|
||||
if (current && stop != current)
|
||||
{
|
||||
prev = current;
|
||||
current = current->mNext;
|
||||
}
|
||||
else
|
||||
current = nullptr; // end
|
||||
return *this;
|
||||
}
|
||||
iterator& operator++(int) // postfix
|
||||
{
|
||||
iterator tmp(*this);
|
||||
operator++();
|
||||
return tmp;
|
||||
}
|
||||
};
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
return iterator(*this);
|
||||
}
|
||||
iterator end() const
|
||||
{
|
||||
return iterator();
|
||||
}
|
||||
|
||||
const Delegate* IRAM_ATTR add(const Delegate& del)
|
||||
{
|
||||
return add(Delegate(del));
|
||||
}
|
||||
|
||||
const Delegate* IRAM_ATTR add(Delegate&& del)
|
||||
{
|
||||
if (!del)
|
||||
return nullptr;
|
||||
|
||||
#ifdef ARDUINO
|
||||
InterruptLock lockAllInterruptsInThisScope;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(mutex_unused);
|
||||
#endif
|
||||
|
||||
Node_t* item = ISQUEUE ? get_node_unsafe() :
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
new (std::nothrow) Node_t;
|
||||
#else
|
||||
new Node_t;
|
||||
#endif
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
item->mDelegate = std::move(del);
|
||||
item->mNext = nullptr;
|
||||
|
||||
if (last)
|
||||
last->mNext = item;
|
||||
else
|
||||
first = item;
|
||||
last = item;
|
||||
|
||||
return &item->mDelegate;
|
||||
}
|
||||
|
||||
iterator erase(iterator it)
|
||||
{
|
||||
if (!it)
|
||||
return end();
|
||||
#ifdef ARDUINO
|
||||
InterruptLock lockAllInterruptsInThisScope;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(mutex_unused);
|
||||
#endif
|
||||
auto to_recycle = it.current;
|
||||
|
||||
if (last == it.current)
|
||||
last = it.prev;
|
||||
it.current = it.current->mNext;
|
||||
if (it.prev)
|
||||
{
|
||||
it.prev->mNext = it.current;
|
||||
}
|
||||
else
|
||||
{
|
||||
first = it.current;
|
||||
}
|
||||
if (ISQUEUE)
|
||||
recycle_node_unsafe(to_recycle);
|
||||
else
|
||||
delete to_recycle;
|
||||
return it;
|
||||
}
|
||||
|
||||
bool erase(const Delegate* const del)
|
||||
{
|
||||
auto it = begin();
|
||||
while (it)
|
||||
{
|
||||
if (del == &(*it))
|
||||
{
|
||||
erase(it);
|
||||
return true;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return first;
|
||||
}
|
||||
|
||||
R operator()(P... args)
|
||||
{
|
||||
auto it = begin();
|
||||
if (!it)
|
||||
return {};
|
||||
|
||||
static std::atomic<bool> fence(false);
|
||||
// prevent recursive calls
|
||||
#if defined(ARDUINO) && !defined(ESP32)
|
||||
if (fence.load()) return {};
|
||||
fence.store(true);
|
||||
#else
|
||||
if (fence.exchange(true)) return {};
|
||||
#endif
|
||||
|
||||
R result;
|
||||
do
|
||||
{
|
||||
result = CallP<Delegate, R, ISQUEUE, P...>::execute(*it, args...);
|
||||
if (result && ISQUEUE)
|
||||
it = erase(it);
|
||||
else
|
||||
++it;
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
// running callbacks might last too long for watchdog etc.
|
||||
optimistic_yield(10000);
|
||||
#endif
|
||||
} while (it);
|
||||
|
||||
fence.store(false);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, typename R = void, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32>
|
||||
class MultiDelegateImpl : public MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>
|
||||
{
|
||||
public:
|
||||
using MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>::MultiDelegatePImpl;
|
||||
|
||||
R operator()()
|
||||
{
|
||||
auto it = this->begin();
|
||||
if (!it)
|
||||
return {};
|
||||
|
||||
static std::atomic<bool> fence(false);
|
||||
// prevent recursive calls
|
||||
#if defined(ARDUINO) && !defined(ESP32)
|
||||
if (fence.load()) return {};
|
||||
fence.store(true);
|
||||
#else
|
||||
if (fence.exchange(true)) return {};
|
||||
#endif
|
||||
|
||||
R result;
|
||||
do
|
||||
{
|
||||
result = Call<Delegate, R, ISQUEUE>::execute(*it);
|
||||
if (result && ISQUEUE)
|
||||
it = this->erase(it);
|
||||
else
|
||||
++it;
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
// running callbacks might last too long for watchdog etc.
|
||||
optimistic_yield(10000);
|
||||
#endif
|
||||
} while (it);
|
||||
|
||||
fence.store(false);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> class MultiDelegate;
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P>
|
||||
class MultiDelegate<Delegate, R(P...), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY, P...>
|
||||
{
|
||||
public:
|
||||
using MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY, P...>::MultiDelegatePImpl;
|
||||
};
|
||||
|
||||
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY>
|
||||
class MultiDelegate<Delegate, R(), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegateImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>
|
||||
{
|
||||
public:
|
||||
using MultiDelegateImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>::MultiDelegateImpl;
|
||||
};
|
||||
|
||||
template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P>
|
||||
class MultiDelegate<Delegate, void(P...), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegatePImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY, P...>
|
||||
{
|
||||
public:
|
||||
using MultiDelegatePImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY, P...>::MultiDelegatePImpl;
|
||||
|
||||
void operator()(P... args)
|
||||
{
|
||||
auto it = this->begin();
|
||||
if (!it)
|
||||
return;
|
||||
|
||||
static std::atomic<bool> fence(false);
|
||||
// prevent recursive calls
|
||||
#if defined(ARDUINO) && !defined(ESP32)
|
||||
if (fence.load()) return;
|
||||
fence.store(true);
|
||||
#else
|
||||
if (fence.exchange(true)) return;
|
||||
#endif
|
||||
|
||||
do
|
||||
{
|
||||
CallP<Delegate, void, ISQUEUE, P...>::execute(*it, args...);
|
||||
if (ISQUEUE)
|
||||
it = this->erase(it);
|
||||
else
|
||||
++it;
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
// running callbacks might last too long for watchdog etc.
|
||||
optimistic_yield(10000);
|
||||
#endif
|
||||
} while (it);
|
||||
|
||||
fence.store(false);
|
||||
}
|
||||
};
|
||||
|
||||
template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY>
|
||||
class MultiDelegate<Delegate, void(), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegateImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY>
|
||||
{
|
||||
public:
|
||||
using MultiDelegateImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY>::MultiDelegateImpl;
|
||||
|
||||
void operator()()
|
||||
{
|
||||
auto it = this->begin();
|
||||
if (!it)
|
||||
return;
|
||||
|
||||
static std::atomic<bool> fence(false);
|
||||
// prevent recursive calls
|
||||
#if defined(ARDUINO) && !defined(ESP32)
|
||||
if (fence.load()) return;
|
||||
fence.store(true);
|
||||
#else
|
||||
if (fence.exchange(true)) return;
|
||||
#endif
|
||||
|
||||
do
|
||||
{
|
||||
Call<Delegate, void, ISQUEUE>::execute(*it);
|
||||
if (ISQUEUE)
|
||||
it = this->erase(it);
|
||||
else
|
||||
++it;
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
// running callbacks might last too long for watchdog etc.
|
||||
optimistic_yield(10000);
|
||||
#endif
|
||||
} while (it);
|
||||
|
||||
fence.store(false);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
The MultiDelegate class template can be specialized to either a queue or an event multiplexer.
|
||||
It is designed to be used with Delegate, the efficient runtime wrapper for C function ptr and C++ std::function.
|
||||
@tparam Delegate specifies the concrete type that MultiDelegate bases the queue or event multiplexer on.
|
||||
@tparam ISQUEUE modifies the generated MultiDelegate class in subtle ways. In queue mode (ISQUEUE == true),
|
||||
the value of QUEUE_CAPACITY enforces the maximum number of simultaneous items the queue can contain.
|
||||
This is exploited to minimize the use of new and delete by reusing already allocated items, thus
|
||||
reducing heap fragmentation. In event multiplexer mode (ISQUEUE = false), new and delete are
|
||||
used for allocation of the event handler items.
|
||||
If the result type of the function call operator of Delegate is void, calling a MultiDelegate queue
|
||||
removes each item after calling it; a Multidelegate event multiplexer keeps event handlers until
|
||||
explicitly removed.
|
||||
If the result type of the function call operator of Delegate is non-void, in a MultiDelegate queue
|
||||
the type-conversion to bool of that result determines if the item is immediately removed or kept
|
||||
after each call: if true is returned, the item is removed. A Multidelegate event multiplexer keeps event
|
||||
handlers until they are explicitly removed.
|
||||
@tparam QUEUE_CAPACITY is only used if ISQUEUE == true. Then, it sets the maximum capacity that the queue dynamically
|
||||
allocates from the heap. Unused items are not returned to the heap, but are managed by the MultiDelegate
|
||||
instance during its own lifetime for efficiency.
|
||||
*/
|
||||
template< typename Delegate, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32>
|
||||
class MultiDelegate : public delegate::detail::MultiDelegate<Delegate, typename Delegate::target_type, ISQUEUE, QUEUE_CAPACITY>
|
||||
{
|
||||
public:
|
||||
using delegate::detail::MultiDelegate<Delegate, typename Delegate::target_type, ISQUEUE, QUEUE_CAPACITY>::MultiDelegate;
|
||||
};
|
||||
|
||||
#endif // __MULTIDELEGATE_H
|
||||
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
circular_queue.h - Implementation of a lock-free circular queue for EspSoftwareSerial.
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __circular_queue_h
|
||||
#define __circular_queue_h
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include "Delegate.h"
|
||||
using std::min;
|
||||
#else
|
||||
#include "ghostl.h"
|
||||
#endif
|
||||
|
||||
#if !defined(ESP32) && !defined(ESP8266)
|
||||
#define IRAM_ATTR
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Instance class for a single-producer, single-consumer circular queue / ring buffer (FIFO).
|
||||
This implementation is lock-free between producer and consumer for the available(), peek(),
|
||||
pop(), and push() type functions.
|
||||
*/
|
||||
template< typename T, typename ForEachArg = void >
|
||||
class circular_queue
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
@brief Constructs a valid, but zero-capacity dummy queue.
|
||||
*/
|
||||
circular_queue() : m_bufSize(1)
|
||||
{
|
||||
m_inPos.store(0);
|
||||
m_outPos.store(0);
|
||||
}
|
||||
/*!
|
||||
@brief Constructs a queue of the given maximum capacity.
|
||||
*/
|
||||
circular_queue(const size_t capacity) : m_bufSize(capacity + 1), m_buffer(new T[m_bufSize])
|
||||
{
|
||||
m_inPos.store(0);
|
||||
m_outPos.store(0);
|
||||
}
|
||||
circular_queue(circular_queue&& cq) :
|
||||
m_bufSize(cq.m_bufSize), m_buffer(cq.m_buffer), m_inPos(cq.m_inPos.load()), m_outPos(cq.m_outPos.load())
|
||||
{}
|
||||
~circular_queue()
|
||||
{
|
||||
m_buffer.reset();
|
||||
}
|
||||
circular_queue(const circular_queue&) = delete;
|
||||
circular_queue& operator=(circular_queue&& cq)
|
||||
{
|
||||
m_bufSize = cq.m_bufSize;
|
||||
m_buffer = cq.m_buffer;
|
||||
m_inPos.store(cq.m_inPos.load());
|
||||
m_outPos.store(cq.m_outPos.load());
|
||||
}
|
||||
circular_queue& operator=(const circular_queue&) = delete;
|
||||
|
||||
/*!
|
||||
@brief Get the numer of elements the queue can hold at most.
|
||||
*/
|
||||
size_t capacity() const
|
||||
{
|
||||
return m_bufSize - 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Resize the queue. The available elements in the queue are preserved.
|
||||
This is not lock-free and concurrent producer or consumer access
|
||||
will lead to corruption.
|
||||
@return True if the new capacity could accommodate the present elements in
|
||||
the queue, otherwise nothing is done and false is returned.
|
||||
*/
|
||||
bool capacity(const size_t cap);
|
||||
|
||||
/*!
|
||||
@brief Discard all data in the queue.
|
||||
*/
|
||||
void flush()
|
||||
{
|
||||
m_outPos.store(m_inPos.load());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Get a snapshot number of elements that can be retrieved by pop.
|
||||
*/
|
||||
size_t available() const
|
||||
{
|
||||
int avail = static_cast<int>(m_inPos.load() - m_outPos.load());
|
||||
if (avail < 0) avail += m_bufSize;
|
||||
return avail;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Get the remaining free elementes for pushing.
|
||||
*/
|
||||
size_t available_for_push() const
|
||||
{
|
||||
int avail = static_cast<int>(m_outPos.load() - m_inPos.load()) - 1;
|
||||
if (avail < 0) avail += m_bufSize;
|
||||
return avail;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Peek at the next element pop will return without removing it from the queue.
|
||||
@return An rvalue copy of the next element that can be popped. If the queue is empty,
|
||||
return an rvalue copy of the element that is pending the next push.
|
||||
*/
|
||||
T peek() const
|
||||
{
|
||||
const auto outPos = m_outPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
return m_buffer[outPos];
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Peek at the next pending input value.
|
||||
@return A reference to the next element that can be pushed.
|
||||
*/
|
||||
inline T& IRAM_ATTR pushpeek() __attribute__((always_inline))
|
||||
{
|
||||
const auto inPos = m_inPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
return m_buffer[inPos];
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Release the next pending input value, accessible by pushpeek(), into the queue.
|
||||
@return true if the queue accepted the value, false if the queue
|
||||
was full.
|
||||
*/
|
||||
inline bool IRAM_ATTR push() __attribute__((always_inline))
|
||||
{
|
||||
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||
const size_t next = (inPos + 1) % m_bufSize;
|
||||
if (next == m_outPos.load(std::memory_order_relaxed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
m_inPos.store(next, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Move the rvalue parameter into the queue.
|
||||
@return true if the queue accepted the value, false if the queue
|
||||
was full.
|
||||
*/
|
||||
inline bool IRAM_ATTR push(T&& val) __attribute__((always_inline))
|
||||
{
|
||||
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||
const size_t next = (inPos + 1) % m_bufSize;
|
||||
if (next == m_outPos.load(std::memory_order_relaxed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
m_buffer[inPos] = std::move(val);
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
m_inPos.store(next, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Push a copy of the parameter into the queue.
|
||||
@return true if the queue accepted the value, false if the queue
|
||||
was full.
|
||||
*/
|
||||
inline bool IRAM_ATTR push(const T& val) __attribute__((always_inline))
|
||||
{
|
||||
T v(val);
|
||||
return push(std::move(v));
|
||||
}
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
/*!
|
||||
@brief Push copies of multiple elements from a buffer into the queue,
|
||||
in order, beginning at buffer's head.
|
||||
@return The number of elements actually copied into the queue, counted
|
||||
from the buffer head.
|
||||
*/
|
||||
size_t push_n(const T* buffer, size_t size);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Pop the next available element from the queue.
|
||||
@return An rvalue copy of the popped element, or a default
|
||||
value of type T if the queue is empty.
|
||||
*/
|
||||
T pop();
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
/*!
|
||||
@brief Pop multiple elements in ordered sequence from the queue to a buffer.
|
||||
If buffer is nullptr, simply discards up to size elements from the queue.
|
||||
@return The number of elements actually popped from the queue to
|
||||
buffer.
|
||||
*/
|
||||
size_t pop_n(T* buffer, size_t size);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Iterate over and remove each available element from queue,
|
||||
calling back fun with an rvalue reference of every single element.
|
||||
*/
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
void for_each(const Delegate<void(T&&), ForEachArg>& fun);
|
||||
#else
|
||||
void for_each(Delegate<void(T&&), ForEachArg> fun);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief In reverse order, iterate over, pop and optionally requeue each available element from the queue,
|
||||
calling back fun with a reference of every single element.
|
||||
Requeuing is dependent on the return boolean of the callback function. If it
|
||||
returns true, the requeue occurs.
|
||||
*/
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
bool for_each_rev_requeue(const Delegate<bool(T&), ForEachArg>& fun);
|
||||
#else
|
||||
bool for_each_rev_requeue(Delegate<bool(T&), ForEachArg> fun);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
const T defaultValue = {};
|
||||
size_t m_bufSize;
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
std::unique_ptr<T[]> m_buffer;
|
||||
#else
|
||||
std::unique_ptr<T> m_buffer;
|
||||
#endif
|
||||
std::atomic<size_t> m_inPos;
|
||||
std::atomic<size_t> m_outPos;
|
||||
};
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
bool circular_queue<T, ForEachArg>::capacity(const size_t cap)
|
||||
{
|
||||
if (cap + 1 == m_bufSize) return true;
|
||||
else if (available() > cap) return false;
|
||||
std::unique_ptr<T[] > buffer(new T[cap + 1]);
|
||||
const auto available = pop_n(buffer, cap);
|
||||
m_buffer.reset(buffer);
|
||||
m_bufSize = cap + 1;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
m_inPos.store(available, std::memory_order_relaxed);
|
||||
m_outPos.store(0, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
template< typename T, typename ForEachArg >
|
||||
size_t circular_queue<T, ForEachArg>::push_n(const T* buffer, size_t size)
|
||||
{
|
||||
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||
const auto outPos = m_outPos.load(std::memory_order_relaxed);
|
||||
|
||||
size_t blockSize = (outPos > inPos) ? outPos - 1 - inPos : (outPos == 0) ? m_bufSize - 1 - inPos : m_bufSize - inPos;
|
||||
blockSize = min(size, blockSize);
|
||||
if (!blockSize) return 0;
|
||||
int next = (inPos + blockSize) % m_bufSize;
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
auto dest = m_buffer.get() + inPos;
|
||||
std::copy_n(std::make_move_iterator(buffer), blockSize, dest);
|
||||
size = min(size - blockSize, outPos > 1 ? static_cast<size_t>(outPos - next - 1) : 0);
|
||||
next += size;
|
||||
dest = m_buffer.get();
|
||||
std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest);
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
m_inPos.store(next, std::memory_order_release);
|
||||
return blockSize + size;
|
||||
}
|
||||
#endif
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
T circular_queue<T, ForEachArg>::pop()
|
||||
{
|
||||
const auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||
if (m_inPos.load(std::memory_order_relaxed) == outPos) return defaultValue;
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
auto val = std::move(m_buffer[outPos]);
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
m_outPos.store((outPos + 1) % m_bufSize, std::memory_order_release);
|
||||
return val;
|
||||
}
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
template< typename T, typename ForEachArg >
|
||||
size_t circular_queue<T, ForEachArg>::pop_n(T* buffer, size_t size) {
|
||||
size_t avail = size = min(size, available());
|
||||
if (!avail) return 0;
|
||||
const auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||
size_t n = min(avail, static_cast<size_t>(m_bufSize - outPos));
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
if (buffer) {
|
||||
buffer = std::copy_n(std::make_move_iterator(m_buffer.get() + outPos), n, buffer);
|
||||
avail -= n;
|
||||
std::copy_n(std::make_move_iterator(m_buffer.get()), avail, buffer);
|
||||
}
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
m_outPos.store((outPos + size) % m_bufSize, std::memory_order_release);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
void circular_queue<T, ForEachArg>::for_each(const Delegate<void(T&&), ForEachArg>& fun)
|
||||
#else
|
||||
void circular_queue<T, ForEachArg>::for_each(Delegate<void(T&&), ForEachArg> fun)
|
||||
#endif
|
||||
{
|
||||
auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||
const auto inPos = m_inPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
while (outPos != inPos)
|
||||
{
|
||||
fun(std::move(m_buffer[outPos]));
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
outPos = (outPos + 1) % m_bufSize;
|
||||
m_outPos.store(outPos, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||
bool circular_queue<T, ForEachArg>::for_each_rev_requeue(const Delegate<bool(T&), ForEachArg>& fun)
|
||||
#else
|
||||
bool circular_queue<T, ForEachArg>::for_each_rev_requeue(Delegate<bool(T&), ForEachArg> fun)
|
||||
#endif
|
||||
{
|
||||
auto inPos0 = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_acquire);
|
||||
auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
if (outPos == inPos0) return false;
|
||||
auto pos = inPos0;
|
||||
auto outPos1 = inPos0;
|
||||
const auto posDecr = circular_queue<T, ForEachArg>::m_bufSize - 1;
|
||||
do {
|
||||
pos = (pos + posDecr) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||
T&& val = std::move(circular_queue<T, ForEachArg>::m_buffer[pos]);
|
||||
if (fun(val))
|
||||
{
|
||||
outPos1 = (outPos1 + posDecr) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||
if (outPos1 != pos) circular_queue<T, ForEachArg>::m_buffer[outPos1] = std::move(val);
|
||||
}
|
||||
} while (pos != outPos);
|
||||
circular_queue<T, ForEachArg>::m_outPos.store(outPos1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // __circular_queue_h
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
circular_queue_mp.h - Implementation of a lock-free circular queue for EspSoftwareSerial.
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __circular_queue_mp_h
|
||||
#define __circular_queue_mp_h
|
||||
|
||||
#include "circular_queue.h"
|
||||
|
||||
#ifdef ESP8266
|
||||
#include "interrupts.h"
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Instance class for a multi-producer, single-consumer circular queue / ring buffer (FIFO).
|
||||
This implementation is lock-free between producers and consumer for the available(), peek(),
|
||||
pop(), and push() type functions, but is guarded to safely allow only a single producer
|
||||
at any instant.
|
||||
*/
|
||||
template< typename T, typename ForEachArg = void >
|
||||
class circular_queue_mp : protected circular_queue<T, ForEachArg>
|
||||
{
|
||||
public:
|
||||
circular_queue_mp() = default;
|
||||
circular_queue_mp(const size_t capacity) : circular_queue<T, ForEachArg>(capacity)
|
||||
{}
|
||||
circular_queue_mp(circular_queue<T, ForEachArg>&& cq) : circular_queue<T, ForEachArg>(std::move(cq))
|
||||
{}
|
||||
using circular_queue<T, ForEachArg>::operator=;
|
||||
using circular_queue<T, ForEachArg>::capacity;
|
||||
using circular_queue<T, ForEachArg>::flush;
|
||||
using circular_queue<T, ForEachArg>::available;
|
||||
using circular_queue<T, ForEachArg>::available_for_push;
|
||||
using circular_queue<T, ForEachArg>::peek;
|
||||
using circular_queue<T, ForEachArg>::pop;
|
||||
using circular_queue<T, ForEachArg>::pop_n;
|
||||
using circular_queue<T, ForEachArg>::for_each;
|
||||
using circular_queue<T, ForEachArg>::for_each_rev_requeue;
|
||||
|
||||
/*!
|
||||
@brief Resize the queue. The available elements in the queue are preserved.
|
||||
This is not lock-free, but safe, concurrent producer or consumer access
|
||||
is guarded.
|
||||
@return True if the new capacity could accommodate the present elements in
|
||||
the queue, otherwise nothing is done and false is returned.
|
||||
*/
|
||||
bool capacity(const size_t cap)
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
return circular_queue<T, ForEachArg>::capacity(cap);
|
||||
}
|
||||
|
||||
bool IRAM_ATTR push() = delete;
|
||||
|
||||
/*!
|
||||
@brief Move the rvalue parameter into the queue, guarded
|
||||
for multiple concurrent producers.
|
||||
@return true if the queue accepted the value, false if the queue
|
||||
was full.
|
||||
*/
|
||||
bool IRAM_ATTR push(T&& val)
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
return circular_queue<T, ForEachArg>::push(std::move(val));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Push a copy of the parameter into the queue, guarded
|
||||
for multiple concurrent producers.
|
||||
@return true if the queue accepted the value, false if the queue
|
||||
was full.
|
||||
*/
|
||||
bool IRAM_ATTR push(const T& val)
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
return circular_queue<T, ForEachArg>::push(val);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Push copies of multiple elements from a buffer into the queue,
|
||||
in order, beginning at buffer's head. This is guarded for
|
||||
multiple producers, push_n() is atomic.
|
||||
@return The number of elements actually copied into the queue, counted
|
||||
from the buffer head.
|
||||
*/
|
||||
size_t push_n(const T* buffer, size_t size)
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
return circular_queue<T, ForEachArg>::push_n(buffer, size);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Pops the next available element from the queue, requeues
|
||||
it immediately.
|
||||
@return A reference to the just requeued element, or the default
|
||||
value of type T if the queue is empty.
|
||||
*/
|
||||
T& pop_requeue();
|
||||
|
||||
/*!
|
||||
@brief Iterate over, pop and optionally requeue each available element from the queue,
|
||||
calling back fun with a reference of every single element.
|
||||
Requeuing is dependent on the return boolean of the callback function. If it
|
||||
returns true, the requeue occurs.
|
||||
*/
|
||||
bool for_each_requeue(const Delegate<bool(T&), ForEachArg>& fun);
|
||||
|
||||
#ifndef ESP8266
|
||||
protected:
|
||||
std::mutex m_pushMtx;
|
||||
#endif
|
||||
};
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
T& circular_queue_mp<T, ForEachArg>::pop_requeue()
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
const auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_acquire);
|
||||
const auto inPos = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
if (inPos == outPos) return circular_queue<T, ForEachArg>::defaultValue;
|
||||
T& val = circular_queue<T, ForEachArg>::m_buffer[inPos] = std::move(circular_queue<T, ForEachArg>::m_buffer[outPos]);
|
||||
const auto bufSize = circular_queue<T, ForEachArg>::m_bufSize;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
circular_queue<T, ForEachArg>::m_outPos.store((outPos + 1) % bufSize, std::memory_order_relaxed);
|
||||
circular_queue<T, ForEachArg>::m_inPos.store((inPos + 1) % bufSize, std::memory_order_release);
|
||||
return val;
|
||||
}
|
||||
|
||||
template< typename T, typename ForEachArg >
|
||||
bool circular_queue_mp<T, ForEachArg>::for_each_requeue(const Delegate<bool(T&), ForEachArg>& fun)
|
||||
{
|
||||
auto inPos0 = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_acquire);
|
||||
auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
if (outPos == inPos0) return false;
|
||||
do {
|
||||
T&& val = std::move(circular_queue<T, ForEachArg>::m_buffer[outPos]);
|
||||
if (fun(val))
|
||||
{
|
||||
#ifdef ESP8266
|
||||
esp8266::InterruptLock lock;
|
||||
#else
|
||||
std::lock_guard<std::mutex> lock(m_pushMtx);
|
||||
#endif
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
auto inPos = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
circular_queue<T, ForEachArg>::m_buffer[inPos] = std::move(val);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
circular_queue<T, ForEachArg>::m_inPos.store((inPos + 1) % circular_queue<T, ForEachArg>::m_bufSize, std::memory_order_release);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
}
|
||||
outPos = (outPos + 1) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||
circular_queue<T, ForEachArg>::m_outPos.store(outPos, std::memory_order_release);
|
||||
} while (outPos != inPos0);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // __circular_queue_mp_h
|
||||
94
arduino-cli/libraries/CoopTask/src/circular_queue/ghostl.h
Normal file
94
arduino-cli/libraries/CoopTask/src/circular_queue/ghostl.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
ghostl.h - Implementation of a bare-bones, mostly no-op, C++ STL shell
|
||||
that allows building some Arduino ESP8266/ESP32
|
||||
libraries on Aruduino AVR.
|
||||
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __ghostl_h
|
||||
#define __ghostl_h
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
#include <atomic>
|
||||
#endif
|
||||
|
||||
using size_t = decltype(sizeof(char));
|
||||
|
||||
namespace std
|
||||
{
|
||||
#if !defined(ARDUINO_ARCH_SAMD)
|
||||
typedef enum memory_order {
|
||||
memory_order_relaxed,
|
||||
memory_order_acquire,
|
||||
memory_order_release,
|
||||
memory_order_seq_cst
|
||||
} memory_order;
|
||||
template< typename T > class atomic {
|
||||
private:
|
||||
T value;
|
||||
public:
|
||||
atomic() {}
|
||||
atomic(T desired) { value = desired; }
|
||||
void store(T desired, std::memory_order = std::memory_order_seq_cst) volatile noexcept { value = desired; }
|
||||
T load(std::memory_order = std::memory_order_seq_cst) const volatile noexcept { return value; }
|
||||
};
|
||||
inline void atomic_thread_fence(std::memory_order order) noexcept {}
|
||||
template< typename T > T&& move(T& t) noexcept { return static_cast<T&&>(t); }
|
||||
#endif
|
||||
|
||||
template< typename T, size_t long N > struct array
|
||||
{
|
||||
T _M_elems[N];
|
||||
decltype(sizeof(0)) size() const { return N; }
|
||||
T& operator[](decltype(sizeof(0)) i) { return _M_elems[i]; }
|
||||
const T& operator[](decltype(sizeof(0)) i) const { return _M_elems[i]; }
|
||||
};
|
||||
|
||||
template< typename T > class unique_ptr
|
||||
{
|
||||
public:
|
||||
using pointer = T*;
|
||||
unique_ptr() noexcept : ptr(nullptr) {}
|
||||
unique_ptr(pointer p) : ptr(p) {}
|
||||
pointer operator->() const noexcept { return ptr; }
|
||||
T& operator[](decltype(sizeof(0)) i) const { return ptr[i]; }
|
||||
void reset(pointer p = pointer()) noexcept
|
||||
{
|
||||
delete ptr;
|
||||
ptr = p;
|
||||
}
|
||||
T& operator*() const { return *ptr; }
|
||||
private:
|
||||
pointer ptr;
|
||||
};
|
||||
|
||||
template< typename T > using function = T*;
|
||||
using nullptr_t = decltype(nullptr);
|
||||
|
||||
template<typename T>
|
||||
struct identity {
|
||||
typedef T type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline T&& forward(typename identity<T>::type& t) noexcept
|
||||
{
|
||||
return static_cast<typename identity<T>::type&&>(t);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __ghostl_h
|
||||
Reference in New Issue
Block a user