Weaver: A Game Engine for GNU/Linux

Documentation Tutorial Examples Download

Weaver Main Loops

When you play a game you interact with some sort of virtual environment and with allies, enemies or neutral entities that also inhabit that environment. Even when you don't press any key, the game world keeps moving. The computer creates a simulation in this environment in a loop which runs forever until some kind of condition puts you in another environment, which also runs in another loop.

From the point of view of a character who lives inside your game, each iteration of the main loop is the minimal interval of time. If the character could make scientific experiments, he would discover that no phenomenon could happen in a interval of time smaller than one iteration of the main loop. Weaver tries to keep this interval of time at 0.04 seconds. This is faster than the frame rate of a movie and only people extremely quick can have reflex response to visual stimuli in such a small time interval. You can check the elapsed microseconds between the current iteration in the game loop and the last iteration in the variable W.dt. The value should be next to 40,000.

A game can have lots of main loops. Each of them acts like an isolated universe. One game loop won't have information about previous game loops, except if you choose to explicitly store information in variables and share them. The animation before the intro screen is a main loop, the title screen is a main loop, each stage of a game usually is a main loop and so are the Game Over screen and the ending of the game. This image shows all the main loops from Super Mario Bros. The title screen is yellow, the game over screen is red, the game ending is green and all the game stages are white:

Notice how the player always is in a game loop and each game loop can send you to two or more different game loops depending of what happens with your character in each scenario. If it dies, the game go to the Game Over main loop and from there to the Title Screen again. World 4-2 have two warp points, a normal ending area and some enemies which could kill you. So this stage can lead you to 6 different game loops.

When you create a new Weaver project, it is initialized with the following code in src/game.c:

MAIN_LOOP main_loop(void){ // The game loop
 LOOP_INIT: // Code executed during loop initialization

 LOOP_BODY: // Code executed every loop iteration
  if(W.keyboard[W_ANY])
    Wexit_loop();
 LOOP_END: // Code executed at the end of the loop
  return;
}

int main(void){
  Winit(); // Initializes Weaver
  Wloop(main_loop); // Enter a new game loop
  return 0;
}

The code has a main function like every C and C++ program. The main function starts calling Winit, the function which initializes Weaver and should be the first function called in a Weaver program. And then it runs a main loop called min_loop defined in the previous lines. The following return 0 never will be executed, Weaver never abandons a main loop after entering in the first.

A main loop looks like a function. But appearances can be deceiving. You should never treat a main loop like an ordinary function. For example, never declare variables in the beginning of a main loop. If you wish, you can declare variables in a block inside the loop. But if you want a variable whose scope should be the entire loop, declare it as a static variable outside the loop "function".

You can say that this doesn't look like a loop. In fact, the loop is hidden. You should read that main loop as:

 MAIN_LOOP main_loop(void){ // The game loop
 LOOP_INIT: // Code executed during loop initialization

 LOOP_BODY: // Code executed every loop iteration
 while(1){
    if(W.keyboard[W_ANY])
      Wexit_loop();
  }
 LOOP_END: // Code executed at the end of the loop
  return;
}

But that while is hidden and should never be put explicitly. The labels LOOP_INIT, LOOP_BODY and LOOP_END marks respectively the beginning of loop initialization, the beginning of loop body and the beginning of loop finalization. They never should be ommited.

Never put anything before LOOP_INIT.

Everything between LOOP_INIT and LOOP_BODY will be executed only in the beginning of the loop before the first iteration.

Everything between LOOP_BODY and LOOP_END will be executed in every iteration.

Everything after LOOP_END wil be executed only once before you exit the loop.

The function Wloop enters in the first main loop of the game. But once in a main loop, you can change to another main loop calling Wloop again. For example, if you were programming Super Mario Bros using Weaver, part of the code for World 1-1 should be:

MAIN_LOOP world_1-1(void){
 LOOP_INIT:

 LOOP_BODY:
  if(mario_jumped_in_the_flag)
    Wloop(world_1-2);
  else if(mario_died)
    Wloop(game_over);
 LOOP_END:
  return;
}

If Mario dies or if he complete the stage, we won't need the World 1-1 anymore. All the memory allocated and related to that scenario should be freed. Weaver does this automatically. The garbage collector always frees the memory when you change the current main loop for a new main loop.

But there's two problems. First, sometimes we want to go outside World 1-1 temporarily. Mario sometimes can enter in Warp Pipes, visit some subterranean hiding place full of coins and then return to World 1-1. And second, if the garbage collector frees all the memory related to the previous main loops, how could we prevent it from freeing the memory associated with Mario? We need some way to preserve objects between main loops.

The solution for this problem is the creation of subloops. Any main loop can be treated as a subloop using the function Wsubloop. Wih this function we can change World 1-1 to support our Warp Pipe:

MAIN_LOOP world_1-1(void){
 LOOP_INIT:

 LOOP_BODY:
  if(mario_enters_warp_pipe)
    Wsubloop(hidden_world_1-1);
  else if(mario_jumped_in_the_flag)
    Wloop(world_1-2);
  else if(mario_died)
    Wloop(game_over);
 LOOP_END:
  return;
}

When you enter a subloop, you pauses the current loop and execute a new one without freeing the memory associated with the previous loop. When the new loop exits (calling Wexit_subloop()), then the previous loop resumes.

But a new loop entered with Wsubloop could call another loop with Wloop instead of just exiting with Wexit_loop. Perhaps Mario can enter in another Warp Pipe after entering the first Warp Pipe. In this case, Weaver still wouldn't free the memory associated with World 1-1, just the memory associated with the first hidden place accessed by the first Warp Pipe.

To understand better what happens when we use the functions Wloop, Wsubloop and Wexit_loop, remember that Weaver treats main loops in a stack. The stack of main loops. Each functions in fact work this way:

If the stack is empty, Wloop puts the main loop received as argument in the stack and executes it. If the stack is not empty, the function pops a main loop from there, puts the main loop received as argument and executes it.

The function Wsubloop just puts te loop received as argument in the stack and executes. But the stack can't be empty.

The function Wexit_loop just pops a main loop from the stack and then resumes the execution of the main loop in the top of the stack. If there's no more elements in the stack, the function exits the game.

The macro W_MAX_SUBLOOP determines the stack size. You can't create more depth of subloops than it's value.

Using subloops you can also create objects that won't be erased by the garbage collector when you cross different main loops. For example, if you don't want to lose Mario when you finish each world, you can use the first loop (it can be the title screen) just to load Mario. Then load the first stage (world 1-1 in this case) as a subloop, and when you want to pass to other main loops, just use Wloop. To return to the title screen (in the Game Over main loop in this case), just call Wexit_loop.

When you declare a new main loop, it's better to put in in a separated source file. This way you can declare it's variables as static at that file and they won't mix with other variables. First choose a name for your new main loop and then call the following command inside your weaver directory:

weaver --loop LOOP_NAME

Your LOOP_NAME must be a valid C identifier, must be formed just by alphanumeric characters and it shouldn't have the same name than a global variable in your program. If you passed a valid name, Weaver will creat the files src/LOOP_NAME.c and src/LOOP_NAME.h and will declare and define the new main loop wih the correct labels.

In the majority of games, won't be difficult to identify the main loops and how to organize them. But there's algo games like Minecraft, where you have a huge world not divided in stages. In these cases, Weaver still can help using the main loops and the garbage collector to handle the title screen, perhaps the pause screen, inventory and menus. But the game won't have many main loops. And one of the game loops would be very big and would need to handle it's memory without much help from the garbage collector. In the case of a game like Minecraft, the loop initialization could create a pool of blocks and mobs. And the main loop will have the responsability to fill the pool only with blocks and mobs from the area visited by the player. Is a block or mob is too far away, the main loop should store it in the disk (if applicable) and load another sequence of blocks and mobs.