You got me brainstorming again. Here we go! :-)
Thanks for the suggestion!
I am afraid I don't think that is an option as multiple websocket conditions can share the same connection.
If I would dequeue messages as soon as any one condition processes them other conditions will miss messages.
(Sorry if I misunderstood something your suggestion)
Yeah, that does put a wrinkle in it. It might take an intermediate layer to make that work. Keep both the new asynch buffer and the current full-frame buffer, with the asynch first. A receiving thread enqueues messages as they come, and a different macro-execution thread synchronously 1) dequeues them into its own buffer, 2) runs all the macros, and then 3) clears its own buffer.
All-synchronous management makes the full-frame buffer work, with no chance of a race, so that multiple conditions can look for the same thing, and the asynch buffer before it that works on individual messages, allows the message receiver to still be in a different thread. Depending on how the parallel macros work, that might be anywhere from easy to impossible.
I also noticed, last time you direct-linked to a bit of code, that you're using inline delays instead of a non-blocking state machine. That's fine for simple things, but another huge thing that I learned early on when writing embedded applications (8-bit single CPU, 5 MIPS, about 300 bytes of RAM, and no operating system - bare metal) is to never block for anything. Always use a state machine, and figure out something that you can check periodically to see if a delay has expired, or whatever it is that you're waiting for. If not, the same thread moves on to the next parallel task, without updating the flag or status variable for this one. The next scan sees that it's in the same state, and checks that condition again. (see footnote)
So in what is technically a single thread, my embedded applications drive a bunch of independent modules that all do their thing simultaneously, spending most of their time in various wait states. And because nothing blocks, it also gets to the single watchdog instruction quite often, at the top of the single main loop. The structure for each module is something like:
C:
void module_name()
{
if (paused)
{
return;
}
switch(state)
{
default:
//do starting stuff
state = A;
//fall-through to A stuff on the same scan (optional)
//scans are frequent enough that it doesn't really matter
case A:
if (A_not_ready)
{
return;
}
//do A stuff
state = B;
return; //don't fall-through: we'll look at B stuff on the next scan
case B:
if (B_not_ready)
{
return;
}
//start B stuff
state = B2;
return; //B stuff takes a while, so let the other modules have a scan here
case B2:
//finish B stuff
state = C;
return;
case C:
if (C_not_ready)
{
return;
}
//do C stuff
state = D;
return;
//etc.
}
}
And the main function:
C:
void main()
{
//init
while(1)
{
reset_watchdog(); //appears exactly once in the entire program, right here
// if I don't hit it often enough, I get a hardware reset
module_1(); //these modules are all strictly-non-blocking state machines as above
module_2(); // so they operate independently of each other
//etc.
}
}
This is somewhere between multi-threaded asynchronous and single-threaded synchronous. Or a version of cooperative multitasking if you'd rather think of it that way. Each block of each module is synchronous to itself, and guaranteed-atomic except for interrupts. (interrupts in my projects are so non-interactive that they hardly matter anyway except for some lost time) And with just a little bit of planning, the modules can communicate with each other synchronously as well. Only the ISR(s) *must* be asynch.
To complete the connection back to Adv. SS, I see the macros working as above, while an ISR handles incoming messages. In reality, those are just different threads that respond to different events - message or timer - but that's the mentality when I look at it:
- The message-event "ISR" enqueues each message individually into an asynch buffer, and then leaves.
- The timer-event "main loop" dequeues all messages individually into its own synch buffer, runs the macros as non-blocking state machines, clears the synch buffer, and repeats.
If Qt already gives you an asynch buffer for messages, that you can tap into directly without an event handler, then you might just use that. Drop the "ISR" entirely, and have the "main loop" dequeue individually from there into its own. Either way, tight loop until the asynch is empty (okay if a race misses the last one; we'll get it on the next scan), then synchronously use what it's accumulated in its own buffer.
Footnote:
Thinking about the macros at this point, there's no difference between waiting at the top for a condition to be true, and waiting somewhere in the middle before continuing. That could make things interesting!
So instead of just a time, the Wait action now has any condition or combination of conditions available to it, and there's no need to have separate conditions at the top...or you could keep the conditions at the top, and encode them as a required Wait action.
One reason to not have conditions at the top is what I might call an "offset loop", where the entry point is somewhere in the middle, and then it's a normal loop from there. But to make it work like that with a fixed entry point, the entire loop needs to be "rolled around" or "offset" like a belt around two pulleys, so the condition ends up in the middle.
Thus, another way to think about macros, is that each one is always an infinite loop, with a Conditional Wait at the top. But it may not necessarily be at the top anymore if it ends up working like this, nor is it limited to just one.
With that much flexibility though, the "Run Macro Actions" action would need to be re-done. Maybe it could change to "Force Continue", and have a "Forever" condition to put in the Wait action to make a useful subroutine again? Then, the "Force Macro Continue" action could have a checkbox to wait until the callee arrives back at the same Wait again (function call) or not (thread fork). For even more flexibility, you could have the option to wait until the callee arrives at the same Wait (full cycle) or any Wait (stepped sequence).
And of course, the forced condition doesn't have to be "Forever". It could be anything in any combination.