diff --git a/.idea/ArduinoThread.iml b/.idea/ArduinoThread.iml
new file mode 100644
index 0000000..bc2cd87
--- /dev/null
+++ b/.idea/ArduinoThread.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..28a804d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..25b67c6
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/serialmonitor_settings.xml b/.idea/serialmonitor_settings.xml
new file mode 100644
index 0000000..b45564e
--- /dev/null
+++ b/.idea/serialmonitor_settings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..d644d68
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1566244945461
+
+
+ 1566244945461
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index b866337..f40ac01 100644
--- a/README.md
+++ b/README.md
@@ -1,208 +1,47 @@
-
+## ArduinoAsync
-## ArduinoThreads Motivation
-Arduino does not support isolated parallel tasks ([Threads](https://en.wikipedia.org/wiki/Thread_(computing))),
-but we can make the main `loop` switch function execution conditionally and
-thus simulate threading with [Protothread](https://en.wikipedia.org/wiki/Protothread) mechanism.
-This library implements it and helps you to:
+This is a fork of Ivan Seidel's [ArduinoThread](https://github.com/ivanseidel/ArduinoThread).
-- schedule, manage and simplify parallel, periodic tasks
-- define fixed or variable time between runs
-- organize the code in any type of project
- - put all sensor readings in a thread
- - keep the main loop clean
-- hide the complexity of thread management
-- run "pseudo-background" tasks using Timer interrupts
+The protothread mechanism used in ArduinoThread is used to create asynchronous contexts. With C++
+define statements, it is possible to create Python-like async code that runs on an Arduino.
-Blinking an LED is often the very first thing an Arduino user learns.
-And this demonstrates that periodically performing one single task, like toggling the LED state, is really easy.
-However, one may quickly discover that managing multiple periodic tasks is not so simple
-if the tasks have different schedule.
+Static Thread Controllers have been for the time being removed.
-The user defines a Thread object for each of those tasks, then lets the library manage their scheduled execution.
+The name thread sticks around from the original but it may be renamed in a future release.
-It should be noted that these are not “threads” in the real computer-science meaning of the term:
-tasks are implemented as functions that are run periodically.
-On the one hand, this means that the only way a task can *yield* the CPU is by returning to the caller,
-and it is thus inadvisable to `delay()` or do long waits inside any task.
-On the other hand, this makes ArduinoThreads memory friendly, as no stack need to be allocated per task.
+Documentation and library are both still very WIP. This may become its own seperate thing in the future.
-## Installation
+### Concepts
+Like in ArduinoThread, the `run` method of a thread is called once per loop. This allows a programmmer
+to think of several tasks as "running at once" and the loop context switching between tasks that
+are ready to run.
-1. Download [the Master branch](https://github.com/ivanseidel/ArduinoThread/archive/master.zip) from gitHub.
-2. Unzip and modify the Folder name to "ArduinoThread" (Remove the '-master' suffix)
-3. Paste the modified folder on your Library folder (On your `Libraries` folder inside Sketchbooks or Arduino software).
-4. Restart the Arduino IDE
+To accomplish this context switching, all threads have an internal `m_flag` int variable that makes
+them all act like finite state machines. Switch case statements or if statements can be
+used on the flag to allow the thread to execute code based on its state.
-**If you are here just because another library requires a class from ArduinoThread, then you are done now
-.**
+Setting the flag is as simple as calling `YIELD(flag_number)`, which yields execution back to the
+`ThreadController` and sets the flag. `YIELD_DEFAULT` can be used for simple applications and simply
+sets the flag to 0.
+If `m_flag` is set to -1 and the thread has yielded execution back to the `ThreadController`,
+the ThreadController removes the thread from the queue. The `HALT` keyword does exactly this.
-## Getting Started
+Threads can also be paused in this framework for a certain number of milliseconds using the
+`WAIT` keyword. Unlike `delay` `WAIT` will not block execution of the `ThreadController` and allows
+context switching to continue for other tasks. Threads can also be paused indefinetely with
+`FREEZE`. Threads paused with `FREEZE` will not enter an awake state again until another thread
+uses `UNFREEZE(thread_ptr)`.
-There are many examples showing many ways to use it. We will explain Class itself,
-what it does and how it does.
+In addition to these mechanisms which control the control flow, ArduinoAsync also supports producer
+consumer semantics.
-There are three main classes included in the library:
-`Thread`, `ThreadController` and `StaticThreadController` (both controllers inherit from `Thread`).
+All threads have an internal buffer of type void*. This is used to gather the return of an
+asynchronously executing thread. Users of the library will not have to worry about this, however.
-- `Thread`: Basic class, witch contains methods to set and run callbacks,
- check if the Thread should be run, and also creates a unique ThreadID on the instantiation.
+The `AWAIT` statement will take a pointer to a thread and check its buffer for the result which is
+otherwise `nulltpr`. If multiple consumers `AWAIT` on a producer, a semaphore internal to the producer
+will block the producer from clearing the result and entering a sleeping state until all consumers have
+gotten the result.
-- `ThreadController`: Responsible for managing multiple Threads. Can also be thought of
- as "a group of Threads", and is used to perform `run` in every Thread ONLY when needed.
-
-- `StaticThreadController`: Slightly faster and smaller version of the `ThreadController`.
- It works similar to `ThreadController`, but once constructed it can't add or remove threads to run.
-
-#### Create Thread instance:
-
-```c++
-Thread myThread = Thread();
-// or, if initializing a pointer
-Thread* myThread = new Thread();
-```
-
-#### Setup thread behaviour
-You can configure many things:
-
-```c++
-myThread.enabled = true; // Default enabled value is true
-myThread.setInterval(10); // Setts the wanted interval to be 10ms
-/*
- This is useful for debugging
- (Thread Name is disabled by default, to use less memory)
- (Enable it by definint USE_THREAD_NAMES on 'Thread.h')
-*/
-myThread.ThreadName = "myThread tag";
-// This will set the callback of the Thread: "What should I run"?
-myThread.onRun(callback_function); // callback_function is the name of the function
-```
-
-#### Running threads manually
-Ok, creating threads isn't too hard, but what do we do with them?
-
-```c++
-// First check if our Thread should be run
-if(myThread.shouldRun()){
- // Yes, the Thread should run, let's run it
- myThread.run();
-}
-```
-
-#### Running threads via a controller
-If you had 3, 5 or 100 threads, managing them manually could become tedious.
-That's when `ThreadController` or `StaticThreadController` comes into play and saves you the repetitive thread management parts of code.
-
-```c++
-// Instantiate new ThreadController
-ThreadController controller = ThreadController();
-// Now, put bunch of Threads inside it, FEED it!
-controller.add(&myThread); // Notice the '&' sign before the thread, IF it's not instantied as a pointer.
-controller.add(&hisThread);
-controller.add(&sensorReadings);
-...
-```
-or
-```c++
-// Instantiate a new StaticThreadController with the number of threads to be supplied as template parameter
-StaticThreadController<3> controller (&myThread, &hisThread, &sensorReadings);
-// You don't need to do anything else, controller now contains all the threads.
-...
-```
-
-You have created, configured, grouped it. What is missing? Yes, whe should RUN it!
-The following will run all the threads that NEED to run.
-
-```c++
-// call run on a Thread, a ThreadController or a StaticThreadController to run it
-controller.run();
-```
-
-Congratulations, you have learned the basics of the `ArduinoThread` library. If you want to learn more, see bellow.
-
-### Tips and Warnings
-
-* `ThreadController` is not of a dynamic size (like a `LinkedList`). The maximum number of threads that it can manage
- is defined in `ThreadController.h` (default is 15)
-
-* ☢ When extending the `Thread` class and overriding the `run()` function,
- remember to always call `runned();` at the end, otherwise the thread will hang forever.
-
-* It's a good idea, to create a `Timer` interrupt and call a `ThreadController.run()` there.
-That way, you don't need to worry about reading sensors and doing time-sensitive stuff
-in your main code (`loop`). Check `ControllerWithTimer` example.
-
-* Inheriting from `Thread` or even `ThreadController` is always a good idea.
-For example, I always create base classes of sensors that extends `Thread`,
-so that I can "register" the sensors inside a `ThreadController`, and forget
-about reading sensors, just having the values available in my main code.
-Check the `SensorThread` example.
-
-* Remember that `ThreadController` is in fact, a `Thread` itself. If you want to group threads and
-manage them together (enable or disable), think about putting all of them inside a `ThreadController`,
-and adding this `ThreadController` to another `ThreadController` (YES! One inside another).
-Check `ControllerInController` example.
-
-* `StaticThreadController` is optimal when you know the exact number of
-threads to run. You cannot add or remove threads at runtime, but it
-doesn't require additional memory to keep all the treads together, doesn't limit the number of thread
-(except for available memory) and the code may be slightly
-better optimized because all the threads always exist and no need to do any runtime checks.
-
-* Check the full example `CustomTimedThread` for a cool application of threads that run
-for a period, after a button is pressed.
-
-* Running tasks on the `Timer` interrupts must be thought though REALLY carefully
-
- - You mustn't use `sleep()` inside an interrupt, because it would cause an infinite loop.
-
- - Things execute quickly. Waiting too loooong on a interrupt, means waiting too
- loooong on the main code (`loop`)
-
- - Things might get "scrambled". Since Timers interrupts actually "BREAK" your code in half
- and start running the interrupt, you might want to call `noInterrupts` and `interrupts`
- on places where cannot be interrupted:
-
-```c++
-noInterrupts();
-// Put the code that CANNOT be interrupted...
-interrupts(); // This will enable the interrupts egain. DO NOT FORGET!
-```
-## Library Reference
-
-### Configuration options
-#### Thread
-- `bool Thread::enabled` - Enables or disables the thread. (doesn't prevent it from running, but will
- return `false` when `shouldRun()` is called)
-- `void Thread::setInterval()` - Schedules the thread run interval in milliseconds
-- `bool Thread::shouldRun()` - Returns true, if the thread should be run.
- (Basically,the logic is: (reached time AND is enabled?).
-- `void Thread::onRun()` - The target callback function to be called.
-- `void Thread::run()` - Runs the thread (executes the callback function).
-- `int Thread::ThreadID` - Theoretically, it's the memory address. It's unique, and can
- be used to compare if two threads are identical.
-- `int Thread::ThreadName` - A human-readable thread name.
- Default is "Thread ThreadID", eg.: "Thread 141515".
- Note that to enable this attribute, you must uncomment the line that disables it on `Thread.h`
-- protected: `void Thread::runned()` - Used to reset internal timer of the thread.
- This is automatically called AFTER a call to `run()`.
-
-#### ThreadController
-- `void ThreadController::run()` - Runs the all threads grouped by the controller,
- but only if needed (if `shouldRun()` returns true);
-- `bool ThreadController::add(Thread* _thread)` - Adds a the thread to the controller,
- and returns `true` if succeeded (returns false if the array is full).
-- `void ThreadController::remove(Thread* _thread)` - Removes the thread from the controller
-- `void ThreadController::remove(int index)` - Removes the thread at the `index` position
-- `void ThreadController::clear()` - Removes ALL threads from the controller
-- `int ThreadController::size(bool cached = true)` - Returns number of threads allocated
- in the ThreadController. Re-calculates thread count if `cached` is `false`
-- `Thread* ThreadController::get(int index)` - Returns the thread at the `index` position
-
-#### StaticThreadController
-- `void StaticThreadController::run()` - Runs all the threads within the controller,
- but only if needed (if `shouldRun()` returns true);
-- `int StaticThreadController::size()` - Returns how many Threads are allocated inside the controller.
-- `Thread* ThreadController::get(int index)` - Returns the thread at the `index` position - or `nullptr` if `index`
- is out of bounds.
+
diff --git a/Resource.cpp b/Resource.cpp
new file mode 100644
index 0000000..da82fbd
--- /dev/null
+++ b/Resource.cpp
@@ -0,0 +1,31 @@
+//
+// Created by emdash00 on 9/1/19.
+//
+
+#include "Resource.hpp"
+
+bool Resource::Acquire(Thread *thread, int priority)
+{
+ if (owner == nullptr || priority > owner_priority)
+ {
+ owner = thread;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool Resource::HaveOwnership(Thread *thread){
+ return (owner == thread);
+}
+
+
+void Resource::Release(Thread *thread)
+{
+ if (owner == thread)
+ {
+ owner = nullptr;
+ }
+}
\ No newline at end of file
diff --git a/Resource.hpp b/Resource.hpp
new file mode 100644
index 0000000..0b6636e
--- /dev/null
+++ b/Resource.hpp
@@ -0,0 +1,27 @@
+//
+// Created by emdash00 on 8/24/19.
+//
+
+#ifndef ARDUINOTHREAD_RESOURCE_HPP
+#define ARDUINOTHREAD_RESOURCE_HPP
+
+#include "Thread.h"
+
+class Resource
+{
+
+public:
+ bool Acquire(Thread *thread, int priority);
+ bool HaveOwnership(Thread *thread);
+ void Release(Thread *thread);
+
+protected:
+ Resource( const Resource& ) = delete; // non construction-copyable
+ Resource& operator=( const Resource& ) = delete; // non copyable
+
+ Thread *owner = nullptr;
+ int owner_priority = -1;
+};
+
+
+#endif //ARDUINOTHREAD_RESOURCE_HPP
diff --git a/SingularThread.cpp b/SingularThread.cpp
new file mode 100644
index 0000000..61b8bec
--- /dev/null
+++ b/SingularThread.cpp
@@ -0,0 +1,12 @@
+//
+// Created by emdash00 on 9/1/19.
+//
+
+#include "SingularThread.hpp"
+
+void SingularThread::runned(unsigned long time)
+{
+ // Mark thread for removal. No need to call HALT.
+ flag = -1;
+ super::runned(time);
+}
\ No newline at end of file
diff --git a/SingularThread.hpp b/SingularThread.hpp
new file mode 100644
index 0000000..8e6a4db
--- /dev/null
+++ b/SingularThread.hpp
@@ -0,0 +1,20 @@
+//
+// Created by emdash00 on 9/1/19.
+//
+
+#ifndef ARDUINOTHREAD_SINGULARTHREAD_HPP
+#define ARDUINOTHREAD_SINGULARTHREAD_HPP
+
+#include "Thread.h"
+
+class SingularThread : public Thread
+{
+public:
+ void runned(unsigned long time);
+
+private:
+ typedef Thread super;
+};
+
+
+#endif //ARDUINOTHREAD_SINGULARTHREAD_HPP
diff --git a/StaticThreadController.h b/StaticThreadController.h
deleted file mode 100644
index 6e316ea..0000000
--- a/StaticThreadController.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- StaticThreadController.h - Controlls a list of Threads with different timings
-
- Basicaly, what it does is to keep track of current Threads and run when
- necessary.
-
- StaticThreadController is an extended class of Thread, because of that,
- it allows you to add a StaticThreadController inside another kind of ThreadController...
-
- It works exact as ThreadController except you can't add or remove treads dynamically.
-
- Created by Alex Eremin, September, 2016.
- Released into the public domain.
-*/
-
-#ifndef StaticThreadController_h
-#define StaticThreadController_h
-
-#include "Thread.h"
-
-template
-class StaticThreadController: public Thread{
-protected:
- //since this is a static controller, the pointers themselves can be const
- //it should be distinguished from 'const Thread* thread[N]'
- Thread * const thread[N];
-public:
- template
- StaticThreadController(T... params) :
- Thread(),
- thread{params...}
- {
- #ifdef USE_THREAD_NAMES
- // Overrides name
- ThreadName = "StaticThreadController ";
- ThreadName = ThreadName + ThreadID;
- #endif
- };
-
- // run() Method is overrided
- void run() override
- {
- // Run this thread before
- if(_onRun != nullptr && shouldRun())
- _onRun();
-
- for(int i = 0; i < N; i++){
- // Is enabled? Timeout exceeded?
- if(thread[i]->shouldRun()){
- thread[i]->run();
- }
- }
-
- // StaticThreadController extends Thread, so we should flag as runned thread
- runned();
- }
-
- // Return the quantity of Threads
- static constexpr int size() { return N; };
-
- // Return the I Thread on the array
- // Returns nullptr if index is out of bounds
- Thread* get(int index) {
- return (index >= 0 && index < N) ? thread[index] : nullptr;
- };
-
- // Return the I Thread on the array
- // Doesn't perform any bounds checks and behaviour is
- // unpredictable in case of index > N
- Thread& operator[](int index) {
- return *thread[index];
- };
-};
-
-#endif
diff --git a/Thread.cpp b/Thread.cpp
index cd29d98..6c0fd65 100644
--- a/Thread.cpp
+++ b/Thread.cpp
@@ -1,12 +1,9 @@
#include "Thread.h"
-Thread::Thread(void (*callback)(void), unsigned long _interval){
- enabled = true;
- onRun(callback);
- _cached_next_run = 0;
+Thread::Thread(void (*callback)(void), unsigned long _interval, unsigned long _timeout) :
+_onRun(callback), ThreadID((int)this), timeout(_timeout){
last_run = millis();
- ThreadID = (int)this;
#ifdef USE_THREAD_NAMES
ThreadName = "Thread ";
ThreadName = ThreadName + ThreadID;
@@ -16,11 +13,20 @@ Thread::Thread(void (*callback)(void), unsigned long _interval){
};
void Thread::runned(unsigned long time){
- // Saves last_run
- last_run = time;
+ // Saves last_run
+ last_run = time;
+
+ // Cache next run
+ switch (pause_interval) {
+ case 0:
+ _cached_next_run = last_run + interval;
+ break;
+ default:
+ _cached_next_run = last_run + pause_interval;
+ break;
+
+ }
- // Cache next run
- _cached_next_run = last_run + interval;
}
void Thread::setInterval(unsigned long _interval){
@@ -31,12 +37,40 @@ void Thread::setInterval(unsigned long _interval){
_cached_next_run = last_run + interval;
}
+void Thread::setTimeout(unsigned long _timeout){
+ timeout = _timeout;
+}
+
bool Thread::shouldRun(unsigned long time){
+ // Nothing below matters if the thread is frozen.
+ if (frozen){
+ return false;
+ }
+
// If the "sign" bit is set the signed difference would be negative
bool time_remaining = (time - _cached_next_run) & 0x80000000;
- // Exceeded the time limit, AND is enabled? Then should run...
- return !time_remaining && enabled;
+ if(!time_remaining && enabled && !_started){
+ _started = true;
+ _t0 = time;
+ }
+
+ if (!time_remaining && pause_interval != 0){
+ pause_interval = 0;
+ }
+
+ switch (timeout)
+ {
+ // A timeout of 0 doesn't make sense and instead signifies a timeout that hasn't been set.
+ case 0:
+ // Exceeded the time limit, AND is enabled? Then should run...
+ return !time_remaining && enabled;
+ default:
+ bool timed_out = !((time - (_t0 + timeout)) & 0x80000000);
+
+ // Exceeded the time limit, AND not timed out, AND is enabled? Then should run...
+ return !time_remaining && !timeout && enabled;
+ }
}
void Thread::onRun(void (*callback)(void)){
@@ -44,9 +78,12 @@ void Thread::onRun(void (*callback)(void)){
}
void Thread::run(){
- if(_onRun != NULL)
+ if(_onRun != nullptr)
_onRun();
+
+
+
// Update last_run and _cached_next_run
runned();
}
diff --git a/Thread.h b/Thread.h
index 0e580a9..b03554b 100644
--- a/Thread.h
+++ b/Thread.h
@@ -22,6 +22,87 @@
#include
+#include "ThreadController.h"
+
+
+/*
+ The following macros provide async style programming using ThreadController with some limitations. See below.
+ Unfortunately, AWAIT, YIELD_TO, and HALT_TO cannot be used in a static context.
+ Since threads cannot be removed in a static context, HALT is the same as FREEZE in StaticThreadControllers.
+*/
+
+
+/*
+ YIELDs execution back to the ThreadController through the use of a return statement,
+ also setting the specified flag. Note: flag -1 is special. See HALT.
+ This and macros that depend on it must be used in run() because the return won't work as intended otherwise.
+*/
+#define YIELD(_flag) (flag = _flag); runned(); return
+
+// Called when YIELD is called without a custom flag. Sets default flag 0.
+#define YIELD_DEFAULT YIELD(0)
+
+// Links the specified thread and YIELDs. The linked thread is called immediately from the main loop.
+#define YIELD_TO(_thread, _flag) (_linked_thread = thread), YIELD(_flag)
+
+/*
+ Gets a pointer to the eventual result of a WorkerThread's work of type 'type'. A "future" if you may.
+ The pointer is from thread->result and is nullptr until the thread is done working.
+ The work will pause and wait for all awaiting threads to retrieve the result before continuing work.
+ Warning, if the thread doesn't have a result any code after the AWAIT in run() will never be executed.
+
+ CANNOT be used as a function parameter due to having returns within the macro body.
+*/
+#define AWAIT(type, _thread, _flag) \
+ (_thread->result ? (type)_thread->result : nullptr); \
+ awaiting = true; \
+ ({ \
+ if (!linked_thread) \
+ { \
+ (_thread->num_awaiting)++; \
+ YIELD_TO(_thread, _flag); \
+ } \
+ else \
+ { \
+ if (_thread_result == nullptr) \
+ { \
+ YIELD(_flag); \
+ } \
+ else \
+ { \
+ linked_thread = nullptr; \
+ awaiting = false; \
+ (_thread->num_awaiting)--; \
+ } \
+ } \
+ })
+
+#define ACQUIRE_RESOURCE(_resource, _flag) \
+ \
+ ({ \
+ if (!_resource.Acquire()){ \
+ YIELD(_flag); \
+ } \
+ })
+
+// Suspends the thread's execution for pause_interval ms.
+#define PAUSE(_pause_interval, _flag) (pause_interval = _pause_interval), YIELD(_flag)
+
+// Suspends the thread's execution indefinetely. Thread will resume on a call to RESUME
+#define FREEZE(_flag) (frozen = true), YIELD(_flag)
+
+// Unsuspends a suspended thread by either unfreezing and/or unpausing it.
+#define RESUME(_thread) (_thread->frozen = false, _thread->pause_interval = 0;)
+
+/*
+ YIELDs with flag -1. ThreadController will remove any thread with flag -1 after calling run()
+ In StaticThreadController, it will simply FREEZE the thread since it cannot be removed.
+*/
+#define HALT YIELD(-1)
+
+// HALTs the thread, offshooting another thread. Similar to YIELD_To but will remove the current thread.
+#define HALT_TO(_thread) YIELD_TO(_thread, -1)
+
/*
Uncomment this line to enable ThreadName Strings.
@@ -30,16 +111,40 @@
*/
// #define USE_THREAD_NAMES 1
+Thread _curentThread = nullptr;
+
class Thread{
protected:
// Desired interval between runs
unsigned long interval;
+ // Desired interval to pause a thread.
+ // shouldRun will return false until this interval is over, upon which the thread is immediately run.
+ unsigned long pause_interval = 0;
+
+ // If time exceeds timeout (ms), shouldRun() will always return false
+ unsigned long timeout;
+
+ // If the thread is frozen or not.
+ bool frozen = false;
+
// Last runned time in Ms
unsigned long last_run;
// Scheduled run in Ms (MUST BE CACHED)
- unsigned long _cached_next_run;
+ unsigned long _cached_next_run = 0;
+
+ // Time that the thread was started
+ unsigned long _t0;
+
+ // True when shouldRun returns true for the first time.
+ bool _started = false;
+
+ // The number of threads awaiting this one.
+ // Used by workers to determine when every thread that wants the results gets it before clearing it and continuing.
+ int num_awaiting = 0;
+
+
/*
IMPORTANT! Run after all calls to run()
@@ -53,26 +158,40 @@ class Thread{
void runned() { runned(millis()); }
// Callback for run() if not implemented
- void (*_onRun)(void);
+ void (*_onRun)(void) = nullptr;
public:
// If the current Thread is enabled or not
- bool enabled;
+ bool enabled = true;
+
+ // If the current thread is awaiting another thread.
+ bool awaiting = false;
// ID of the Thread (initialized from memory adr.)
int ThreadID;
- #ifdef USE_THREAD_NAMES
+ // Used to store and restore the state of a thread.
+ int flag = 0;
+
+ // Thread this thread has linked via YIELD_TO
+ Thread *linked_thread = nullptr;
+
+ //Represents the result of a thread's work, if any. "Void" threads have result always equal to nullptr.
+ void *result = nullptr;
+
+#ifdef USE_THREAD_NAMES
// Thread Name (used for better UI).
String ThreadName;
#endif
- Thread(void (*callback)(void) = NULL, unsigned long _interval = 0);
+ Thread(void (*callback)(void) = NULL, unsigned long _interval = 0, unsigned long _timeout = 0);
// Set the desired interval for calls, and update _cached_next_run
virtual void setInterval(unsigned long _interval);
+ void setTimeout(unsigned long _timeout);
+
// Return if the Thread should be runned or not
virtual bool shouldRun(unsigned long time);
@@ -84,6 +203,9 @@ class Thread{
// Runs Thread
virtual void run();
+
+ // Runs before the thread is removed.
+ virtual void cleanup();
};
#endif
diff --git a/ThreadController.cpp b/ThreadController.cpp
index 7d8e41c..0f03df3 100644
--- a/ThreadController.cpp
+++ b/ThreadController.cpp
@@ -2,8 +2,6 @@
#include "ThreadController.h"
ThreadController::ThreadController(unsigned long _interval): Thread(){
- cached_size = 0;
-
clear();
setInterval(_interval);
@@ -28,8 +26,24 @@ void ThreadController::run(){
// Object exists? Is enabled? Timeout exceeded?
if(thread[i]){
checks++;
+
if(thread[i]->shouldRun(time)){
thread[i]->run();
+
+
+ // Thread was linked using YIELD_TO or AWAIT
+ if (thread[i]->linked_thread)
+ {
+ add(thread[i]->linked_thread);
+
+ // Don't remove the linked thread if it's AWAITing a result.
+ if (!thread[i]->awaiting)
+ thread[i]->linked_thread = nullptr;
+
+ }
+ // HALT was called within the thread. Remove it.
+ if (thread[i]->flag == -1)
+ remove(thread[i]->ThreadID);
}
}
}
@@ -67,7 +81,8 @@ void ThreadController::remove(int id){
// Find Threads with the id, and removes
for(int i = 0; i < MAX_THREADS; i++){
if(thread[i]->ThreadID == id){
- thread[i] = NULL;
+ thread[i]->cleanup();
+ thread[i] = nullptr;
cached_size--;
return;
}
diff --git a/ThreadController.h b/ThreadController.h
index 8e2888c..5a9e970 100644
--- a/ThreadController.h
+++ b/ThreadController.h
@@ -24,7 +24,7 @@
class ThreadController: public Thread{
protected:
Thread* thread[MAX_THREADS];
- int cached_size;
+ int cached_size = 0;
public:
ThreadController(unsigned long _interval = 0);