The code on |tutorial_protothread2.c shows how to make an animation with several autonomous actors, each managed by a separate thread.
We will not comment on every single line but will limit ourselves to pointing out some fundamental points, to be explored. In particular, this tutorial uses the tiles and the rendering engine created for "The Elevator" game, to which reference is made for further information and clarifications.
MR_PT_CTX(walk, unsigned char floor; unsigned char frame; unsigned char pos; unsigned char to; mr_color color;);
This declaration allows you to reserve 5 local variables for each thread that will take care of moving the character. These variables are useful for representing some essential information, such as:
floor
) on which we will have to draw the character;frame
of the animation on the screen;pos
) where the character was last drawn;to
) that the character must reach; color
) to be used to draw the character.walk_protothread walkThread[FLOOR_COUNT];
With this instruction we allocate a thread for each floor where the character will walk. Note that, following this new definition (walk_protothread
) the occupation for each protothread goes up from 2 bytes to 7 bytes per protothread. So the memory space occupied, overall, by the multithreading of this tutorial is 35 bytes.
MR_PT_THREAD_EXT(walk, walk_protothread) { MR_PTI_BEGIN();
This is where the protothread code begins.
// ARRIVED? while (MR_PTI_CTX()->pos < MR_PTI_CTX()->to) {
Here we check the position reached by the character, compared to that expected. Notice how access to “local” variables occurs through the context extension.
// INCREASE PASSENGER FRAME ++MR_PTI_CTX()->frame;
Here we update the animation frame.
// EVEN FRAME? if ((MR_PTI_CTX()->frame & 1) == 0) { // INCREASE PASSENGER POSITION ++MR_PTI_CTX()->pos; }
We move the character forward one position if and only if the animation frame is even. This means that, every two frames, you move one step to the right.
// FRAME MAX REACHED? if (MR_PTI_CTX()->frame > WALK_FRAME_COUNT) { // RESET FRAME MR_PTI_CTX()->frame = 0; }
Here we ensure that the animation is cyclic, from the first to the last frame.
// (yeld) MR_PTI_YIELD();
Here we allow time for the scheduler to run other threads.
// DRAW PASSENGER draw_passenger_walking(MR_PTI_CTX()->floor, MR_PTI_CTX()->pos, MR_PTI_CTX()->frame, MR_PTI_CTX()->color);
Finally, let's draw the character.
}
MR_PTI_END();
Here ends the protothread.
In the main function of the tutorial, in addition to the purely graphic part, all the variables of each protothread are initialized. In particular, for each character:
for (i = 0; i < FLOOR_COUNT; ++i) { walkThread[i].floor = i;
The plane corresponding to the ordinal position is set.
walkThread[i].pos = rand() & 0xf;
A random starting position is set, from 0 to 15.
walkThread[i].frame = 0;
The starting frame is set.
walkThread[i].to = MR_SCREEN_WIDTH - 2;
The target position, which corresponds to the edge of the screen, is set.
switch (i) { case 0: walkThread[i].color = MR_COLOR_RED; break; case 1: walkThread[i].color = MR_COLOR_GREEN; break; case 2: walkThread[i].color = MR_COLOR_BLUE; break; case 3: walkThread[i].color = MR_COLOR_BLACK; break; case 4: walkThread[i].color = MR_COLOR_PURPLE; break; } }
The color to be used is set.
At this point, the cooperative loop enters the scene:
while (1) { mr_start_frame();
This function allows you to set a time synchronization position with the drawing.
for (i = 0; i < FLOOR_COUNT; ++i) { walk(&walkThread[i]); }
This loop corresponds to the scheduling of the protothreads.
mr_end_frame(4);
This function allows you to set the final synchronization position, ensuring that the entire frame drawing is performed within 4 jiffies (about 4/60 of a second).
You can compile the tutorial with this command line:
make tutorial=PROTOTHREAD2 target=... all
Target allowed are:
vic2024
);c64
);c128
);plus4
);atari
).