Skip to content

Instantly share code, notes, and snippets.

@CreeperMario
Created January 6, 2019 23:54
Show Gist options
  • Save CreeperMario/a694aed8999e0f6429abf4b2498104c9 to your computer and use it in GitHub Desktop.
Save CreeperMario/a694aed8999e0f6429abf4b2498104c9 to your computer and use it in GitHub Desktop.
An example of how cooperative threading works on the Wii U, with an analogy to help explain.
cmake_minimum_required(VERSION 3.2)
project(thread-example)
include("$ENV{WUT_ROOT}/share/wut.cmake" REQUIRED)
add_executable(thread-example.elf thread-example.c)
target_link_libraries(thread-example.elf
whb
coreinit
nsysnet
sysapp)
wut_enable_newlib(thread-example.elf)
wut_create_rpx(thread-example.rpx thread-example.elf)
#include <coreinit/thread.h>
#include <sysapp/launch.h>
#include <whb/crash.h>
#include <whb/log.h>
#include <whb/log_cafe.h>
#include <whb/log_udp.h>
// Imagine that PowerPC core 0 is a 12-year-old child, who has to put
// the dishes away and clean their room. The child wants to play a game, but
// they cannot do so, as their chores have higher priority, and so must be
// completed first. Only once the chores have been completed can the child
// begin playing games. Eventually they do start playing games.
//
// Their mother walks into the room and says, "You forgot to take out the
// rubbish." Taking out the rubbish is a higher priority task than playing
// games, and so the child will immediately pause the game, take out the
// rubbish, and then return to the game.
//
// This program is an implentation of that analogy using the Wii U threading
// functions, created to explain the idea of cooperative threading.
#define THREAD_STACK_SIZE 4096
// I don't think we can use OSSleepTicks, as that would result in the threads
// entering a "waiting state" which would allow lower priority threads to
// start running. For this example, this cannot happen.
#define STALL_FOR_TIME(x) {int timeout=x; while(timeout) timeout--;}
OSThread dishes_thread;
OSThread clean_room_thread;
OSThread games_thread;
OSThread rubbish_thread;
char dishes_stack[THREAD_STACK_SIZE];
char clean_room_stack[THREAD_STACK_SIZE];
char games_stack[THREAD_STACK_SIZE];
char rubbish_stack[THREAD_STACK_SIZE];
// Priority level 18, higher priority than levels 19 and 20
int dishes_entry(int argc, const char ** argv)
{
// There's a little bit of mumbo-jumbo in which lower priority threads
// that are created after this one will run for a brief period of time,
// before realising that they are lower priority and stopping again.
// This stops the threads from doing anything while that mumbo-jumbo
// occurs.
STALL_FOR_TIME(1000000);
for(int i = 0; i < 3; i++) {
WHBLogPrint("Putting the dishes away...");
STALL_FOR_TIME(200000000); // approximately 1 second
}
return 0;
}
// Priority level 19, higher priority than level 20
int clean_room_entry(int argc, const char ** argv)
{
STALL_FOR_TIME(1000000);
for(int i = 0; i < 5; i++) {
WHBLogPrint("Cleaning my room...");
STALL_FOR_TIME(200000000);
}
return 0;
}
// Priority level 20, least important thread
int games_entry(int argc, const char ** argv)
{
STALL_FOR_TIME(1000000);
for(int i = 0; i < 10; i++) {
WHBLogPrint("Playing games...");
STALL_FOR_TIME(200000000);
}
return 0;
}
// Priority level 17, higher priority than levels 18, 19 and 20
// (though in this example, the level 18/19 threads have already finished)
int rubbish_entry(int argc, const char ** argv)
{
STALL_FOR_TIME(1000000);
for(int i = 0; i < 3; i++) {
WHBLogPrint("Putting out the rubbish...");
STALL_FOR_TIME(200000000);
}
return 0;
}
// Remember that the main() thread runs on core 1, while all the threads we
// create will run on core 0 - so main() will actually be running thrughout the
// entire program execution.
int main(int argc, const char ** argv)
{
WHBInitCrashHandler();
WHBLogCafeInit();
WHBLogUdpInit();
OSCreateThread(&dishes_thread, &dishes_entry, 0, NULL, dishes_stack + THREAD_STACK_SIZE, THREAD_STACK_SIZE, 18, OS_THREAD_ATTRIB_AFFINITY_CPU0);
OSCreateThread(&clean_room_thread, &clean_room_entry, 0, NULL, clean_room_stack + THREAD_STACK_SIZE, THREAD_STACK_SIZE, 19, OS_THREAD_ATTRIB_AFFINITY_CPU0);
OSCreateThread(&games_thread, &games_entry, 0, NULL, games_stack + THREAD_STACK_SIZE, THREAD_STACK_SIZE, 20, OS_THREAD_ATTRIB_AFFINITY_CPU0);
OSCreateThread(&rubbish_thread, &rubbish_entry, 0, NULL, rubbish_stack + THREAD_STACK_SIZE, THREAD_STACK_SIZE, 17, OS_THREAD_ATTRIB_AFFINITY_CPU0);
// This will start dishes_thread, clean_room_thread and games_thread.
// Each of them will run for a little bit of time (see above,
// re: mumbo-jumbo), and then dishes_thread (the thread of highest
// priority) will run until it finishes. Then clean_room_thread will run,
// and then games_thread.
WHBLogPrint("Doing chores...");
OSResumeThread(&dishes_thread);
OSResumeThread(&clean_room_thread);
OSResumeThread(&games_thread);
// Once both dishes_thread and clean_room_thread have finished, put a log
// message saying that chores are finished.
OSJoinThread(&dishes_thread, NULL);
OSJoinThread(&clean_room_thread, NULL);
WHBLogPrint("Chores are done! Game should be running now!");
// After ~3.5 seconds of games_thread running, start running
// rubbish_thread. rubbish_thread is of higher priority than games_thread
// and so games_thread will automatically stop to let rubbish_thread run.
// Once rubbish_thread finishes, games_thread will resume automatically.
STALL_FOR_TIME(700000000);
WHBLogPrint("Oops, mum wants the rubbish taken out. Stop the game!");
OSResumeThread(&rubbish_thread);
// Wait for all the remaining threads to finish before doing anything else.
OSJoinThread(&games_thread, NULL);
OSJoinThread(&rubbish_thread, NULL);
WHBLogPrint("Time for bed. :)");
WHBLogCafeDeinit();
WHBLogUdpDeinit();
SYSRelaunchTitle(0, NULL);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment