Primitive Multi-Tasking Using Switch Statement

In all things that must be done, there’s always the right way, and then there is my way.

Today at work, I suddenly realized that my embedded application has to be multi-threaded, because there are a few tasks with strange timing requirements (that’s not the topic of this post).

This is on an embedded system with no OS, so no scheduler, setjmp/longjmp, etc.

I could install a proper RTOS (eg. FreeRTOS) to get a real scheduler, and rewrite my whole program to be multi-threaded. But it’s a big program, and it has to work by the end of the week.

So reality happened – I decided to retrofit cooperative multi-tasking into the program, using what I think is a novel approach I just invented – switch statements!

This is what the code looked like. Just regular update loop.

void UpdateSubsystem1()
{
	...
}

void UpdateSubsystem2()
{
	...
}

void UpdateSubsystem3()
{
	...
}

void Update()
{
	UpdateSubsystem1();
	UpdateSubsystem2();
	UpdateSubsystem3();
}

Some of the subsystem update functions take a long time, because, for example, they send things over communication buses.

I can rewrite everything as state machines, but time is of the essence… So here is what I did to each subsystem update function.

First, I enclosed the whole body of the function in a switch statement, and inserted case labels at my “restart points” (points where I want to be able to “yield”). I also added a static variable to hold which stage the function/task is in.

So if my original code looked like this –

void UpdateSubsystem()
{
	doSomething();
	
	while (!SomethingHappened()) {}
	
	doSomething();
	
	while (!SomethingHappened()) {}
	
	doSomething();
}

Now it looks like this –

void UpdateSubsystem()
{
	static int stage = 0;

	switch(stage)
	{
	case 0:
		doSomething();

	case 1:
		while (!SomethingHappened()) {}

		doSomething();

	case 2:
		while (!SomethingHappened()) {}

		doSomething();
	}
}

Note that there are no breaks. This is intentional. We want the program to fall through everything.

Then, when I want the task to yield, I just have to set “stage” to the restart point, and simply return!

void UpdateSubsystem()
{
	static int stage = 0;

	switch(stage)
	{
	case 0:
		doSomething();

	case 1:
		if (!SomethingHappened())
		{
			stage = 1;
			return;
		}

		doSomething();

	case 2:
		if (!SomethingHappened())
		{
			stage = 2;
			return;
		}

		doSomething();

		stage = 0;
	}
}

Cool eh?

A few caveats –
1. Most importantly, all variables must be static/global. Local variables can only be used between 2 restart points, because the stack frame for the call is destroyed when the function returns.
2. Cannot start the same task more than once. Function is no longer re-entrant due to the use of static variables.

Probably a few more I missed.

This is not patented already, right?

Update:
Macro implementation:

#define RESTARTABLE_BEGIN static int restartable_stage = 0; switch(restartable_stage) { case 0:
#define RESTARTABLE_YIELD restartable_stage = __LINE__; return false; case __LINE__: 
#define RESTARTABLE_END } restartable_stage = 0; return true;

Example:

bool task1()
{
	RESTARTABLE_BEGIN;

	std::cout << "t1 -> 1" << std::endl;

	RESTARTABLE_YIELD;

	std::cout << "t1 -> 2" << std::endl;

	RESTARTABLE_YIELD;

	std::cout << "t1 -> 3" << std::endl;

	RESTARTABLE_END;
}

bool task2()
{
	RESTARTABLE_BEGIN;

	std::cout << "t2 -> 1" << std::endl;

	RESTARTABLE_YIELD;

	std::cout << "t2 -> 2" << std::endl;

	RESTARTABLE_YIELD;

	std::cout << "t2 -> 3" << std::endl;

	RESTARTABLE_YIELD;

	std::cout << "t2 -> 4" << std::endl;

	RESTARTABLE_YIELD;

	std::cout << "t2 -> 5" << std::endl;

	RESTARTABLE_END;
}

int main(int argc, char* argv[])
{
	bool task1_done = false;
	bool task2_done = false;

	while (!(task1_done && task2_done))
	{
		if (!task1_done)
		{
			task1_done = task1();
		}

		if (!task2_done)
		{
			task2_done = task2();
		}
	}

	return 0;
}

This program actually triggers a bug in VS2010. It doesn't like __LINE__ as a case label (saying it's not constant) if edit and continue debugging is turned on.

Workaround provided by putty (http://rc.quest.com/viewvc/putty/branches/group-policy/putty/ssh.c?view=markup) -

337 * In particular, if you are getting `case expression not constant'
338 * errors when building with MS Visual Studio, this is because MS's
339 * Edit and Continue debugging feature causes their compiler to
340 * violate ANSI C. To disable Edit and Continue debugging:
341 *
342 * - right-click ssh.c in the FileView
343 * - click Settings
344 * - select the C/C++ tab and the General category
345 * - under `Debug info:', select anything _other_ than `Program
346 * Database for Edit and Continue'.
347 */

5 thoughts on “Primitive Multi-Tasking Using Switch Statement”

    1. Yeah the structure looks very similar to a state machine.

      I think it’s like, state machines with implicit state transitions.

  1. I know this is kind of off-topic, but is it just me, or are the only people who say things like “call the function” or “grab the variable” complete amateurs? Those phrases always sound strange to me.

    1. Hmm functions are supposed to be called. That’s standard terminology.

      grab is only standard terminology for me :).

      1. Whoops, I meant “call the page” when they’re talking about web development. Always sounds bizarre to me.

        Anyway, speaking of things I hate, do NOT take this certain course at UBC with a certain professor I will not name. His idea of “robust code” is checking impossible things “just in case”.

        if (var == true) {

        } else if (var == false) {

        } else {

        }

        Even worse since Java booleans are primitive so they can’t be null.

Comments are closed.