6
\$\begingroup\$

I made a simple scene manager in C99. I tried to make it readable, reusable and reliable. I would be grateful if you would help me to improve it. I want to use it for my upcoming OpenGL game. I think that code is sufficiently self-documenting for you to skip the comments.

The headers

scene_manager.h

// Defines client interface of Scene Manager
#ifndef SCENE_MANAGER_H
#define SCENE_MANAGER_H
#include "scene.h"
// Initialization and termination functions will not fail or yield undefined
// behaviour if called multiple times or in the wrong order. Other functions
// are safe to be called at all times, too. Scenes are the weakest component
// Initializes scene manager. Warning: scenes are not deep copied!
bool initialize_scene_manager(int n_scenes, Scene *scenes, int initial_id);
// Executes the top scene on the scene stack
bool execute_top_scene(void);
// Terminates the scene manager.
void terminate_scene_manager(void);
// Returns whether the scene stack is empty
bool scene_stack_is_empty(void);
// Returns whether the scene stack is not empty
bool scene_stack_is_not_empty(void);
#endif // SCENE_MANAGER_H

scene.h

// Defines interface used by scene manager (SM) to communicate with its scenes
#ifndef SCENE_H
#define SCENE_H
#include <stdbool.h>
#define POP_REQUEST (-1)
// Sends a critical error signal which results in immediate SM termination and
// stderr output if error is not equal to 0 OR description is not equal to NULL
void send_critical_error_signal(int error, const char *description);
// Invalid pointers cause undefined behavior - most likely a segmentation fault
typedef struct Scene
{
 // It initializes the scene
 // On success, returns a pointer to its private data
 // If the scene has no private data, it can return a dummy string literal
 // On failure, returns a NULL pointer
 // If the INITIAL scene fails to initialize, so does the scene manager
 // If a NON-INITIAL scene fails to initialize, the scene manager attempts
 // to proceed as if no PUSH request was received (as if nothing happened)
 // You can send a critical error signal to override default behavior
 // It must not be NULL; else it will result in SM initialization failure
 void *(*initialize)(void);
 // It reinitializes the scene after it becomes the top one again
 // On success, returns true
 // On failure, returns false
 // Failure will result in the scene being popped from the stack
 // It can be NULL. It being NULL will cause it to be ignored
 bool (*reinitialize)(void *data);
 // It executes the scene
 // On success, returns a POP or a valid PUSH request
 // On failure, returns an invalid PUSH request
 // A valid PUSH request is an a integer between 0 and number of scenes
 // Success makes run_top_scene (SM client interface) return true
 // Failure makes run_top_scene (SM client interface) return false
 // It must not be NULL; else it will result in SM initialization failure
 int (*execute)(void *data);
 // It terminates the scene.
 // It must free all the dynamically allocated data to avoid memory leaks
 // It should not fail. If it does, you can send a critical error signal
 // If it does but you do not consider the memory leak or other implications
 // to be very serious, you can ignore it, though I would not recommend it
 // It must not be NULL; else it will result in SM initialization failure
 void (*terminate)(void *data);
} Scene;
#endif // SCENE_H

misc_util.h

// Miscellaneous utilities
#ifndef MISC_UTIL_H
#define MISC_UTIL_H
#include <stdbool.h>
#include <stddef.h> // to get size_t
// Function used to allocate memory. Returns true on success, false on failure
bool safer_realloc(void **p, size_t increased_size);
// Function used to deallocate memory. Always succeeds!
void safe_dealloc(void **p, size_t decreased_size);
#endif // MISC_UTIL_H

The source files

scene_manager.c

#include "scene_manager.h"
#include "misc_util.h"
#include <assert.h>
#include <stdio.h>
// Type definitions used in the SM
typedef struct SceneInstance
{
 int id;
 void *data;
} SceneInstance;
// Data structures of the SM
struct SceneManagerData
{
 bool initialized;
 int n_scenes;
 Scene *scenes;
} sm_data = {false, 0, NULL};
struct SceneManagerStack
{
 int height;
 SceneInstance *stack;
} sm_stack = {0, NULL};
// Prototypes of subroutine functions used in the client interface of the SM
static bool push_scene(int id); // Tries to push a new scene onto the stack
static void pop_scene(void); // Pops the topmost scene of the stack
void send_critical_error_signal(int error, const char *description)
{
 if (error != 0 || description != NULL)
 {
 fprintf(stderr,
 "Critical scene manager error #%d: '%s'\n",
 error, description);
 }
 terminate_scene_manager();
}
bool initialize_scene_manager(int n_scenes, Scene *scenes, int initial_id)
{
 if (sm_data.initialized == true)
 {
 fputs("Warning: attempt to reinitialize scene manager\n", stderr);
 return true;
 }
 else if (n_scenes <= 0 || scenes == NULL ||
 initial_id < 0 || initial_id >= n_scenes)
 {
 fprintf(stderr,
 "Invalid scene manager initialization data: %d, %p, %d\n",
 n_scenes, (void *) scenes, initial_id);
 return false;
 }
 else
 {
 for (int i = 0; i < n_scenes; ++i)
 {
 if ( scenes[i].initialize == NULL
 || scenes[i].execute == NULL
 || scenes[i].terminate == NULL)
 {
 fprintf(stderr, "Scene (i = %d) has a NULL pointer\n", i);
 return false;
 }
 }
 sm_data = (struct SceneManagerData) {true, n_scenes, scenes};
 if (push_scene(initial_id) == false)
 {
 sm_data = (struct SceneManagerData) {false, 0, NULL};
 fputs("Failed to initialize initial scene\n", stderr);
 return false;
 }
 else
 {
 return true;
 }
 }
}
bool execute_top_scene(void)
{
 if (sm_data.initialized == false)
 {
 fputs("Warning: attempt to run a scene in "
 "uninitialized scene manager\n", stderr);
 return false;
 }
 else if (scene_stack_is_empty())
 {
 fputs("Warning: attempt to run a scene in "
 "empty scene manager\n", stderr);
 return false;
 }
 else
 {
 assert(sm_data.n_scenes > 0);
 assert(sm_data.scenes != NULL);
 assert(sm_stack.stack != NULL);
 int index = sm_stack.height - 1;
 int id = sm_stack.stack[index].id;
 void *data = sm_stack.stack[index].data;
 assert(id >= 0);
 assert(id < sm_data.n_scenes);
 assert(data != NULL);
 assert(sm_data.scenes[id].execute != NULL);
 int return_value = sm_data.scenes[id].execute(data);
 // CASE 1: POP REQUEST
 if (return_value == POP_REQUEST)
 {
 pop_scene();
 // Reinitialize the new top scene if there is one
 if (scene_stack_is_not_empty())
 {
 bool reinit = true;
 do // While there IS a top scene with a reinitialize
 { // function and it fails, pop another scene
 int new_index = sm_stack.height - 1;
 int new_id = sm_stack.stack[new_index].id;
 void *new_data = sm_stack.stack[new_index].data;
 assert(new_id >= 0);
 assert(new_id < sm_data.n_scenes);
 assert(new_data != NULL);
 reinit = true;
 if (sm_data.scenes[new_id].reinitialize)
 {
 reinit = sm_data.scenes[new_id].reinitialize(new_data);
 if (reinit == false)
 {
 fprintf(stderr,
 "Warning: scene (id=%d) failed to reinit\n",
 new_id);
 pop_scene();
 }
 }
 } while (scene_stack_is_not_empty() && reinit == false);
 }
 return true;
 }
 // CASE 2: PUSH REQUEST
 else if (return_value >= 0 && return_value < sm_data.n_scenes)
 {
 return push_scene(return_value); // stderr output inside
 }
 // CASE 3: INVALID VALUE
 else
 {
 fprintf(stderr,
 "Warning: scene (id = %d) returned an invalid value\n",
 id);
 return false;
 }
 }
}
static bool push_scene(int id)
{
 assert(sm_data.initialized == true);
 assert(sm_data.n_scenes > 0);
 assert(sm_data.scenes != NULL);
 assert(sm_stack.height >= 0);
 assert(id >= 0);
 assert(id < sm_data.n_scenes);
 assert(sm_data.scenes[id].initialize != NULL);
 size_t old_size = sm_stack.height * sizeof(*sm_stack.stack);
 size_t new_size = old_size + sizeof(*sm_stack.stack);
 if(!safer_realloc((void **) &sm_stack.stack, new_size))
 {
 fputs("Failed to expand scene stack\n", stderr);
 return false;
 }
 else
 {
 void *data = sm_data.scenes[id].initialize();
 if(data == NULL)
 {
 fprintf(stderr, "Warning: scene (%d) failed to initialize\n", id);
 safe_dealloc((void **) &sm_stack.stack, old_size);
 return false;
 }
 else
 {
 int index = sm_stack.height;
 sm_stack.stack[index] = (SceneInstance) {id, data};
 sm_stack.height += 1;
 return true;
 }
 }
}
static void pop_scene(void)
{
 assert(sm_data.initialized == true);
 assert(sm_data.n_scenes > 0);
 assert(sm_data.scenes != NULL);
 assert(sm_stack.height > 0);
 assert(sm_stack.stack != NULL);
 int index = sm_stack.height - 1;
 int id = sm_stack.stack[index].id;
 void *data = sm_stack.stack[index].data;
 assert(id >= 0);
 assert(id < sm_data.n_scenes);
 assert(data != NULL);
 assert(sm_data.scenes[id].terminate != NULL);
 sm_stack.height -= 1;
 size_t new_size = sm_stack.height * sizeof(*sm_stack.stack);
 sm_data.scenes[id].terminate(data);
 safe_dealloc((void **) &sm_stack.stack, new_size);
}
void terminate_scene_manager(void)
{
 if (sm_data.initialized == false)
 {
 fputs("Warning: attempt to terminate "
 "uninitinialized scene manager\n", stderr);
 }
 else
 {
 while (scene_stack_is_not_empty())
 {
 pop_scene();
 }
 sm_data = (struct SceneManagerData) {false, 0, NULL};
 }
}
bool scene_stack_is_empty(void)
{
 if (sm_data.initialized == false)
 {
 fputs("Warning: inquiry of stack height of an "
 "uninitialized scene manager\n", stderr);
 assert(sm_stack.height == 0);
 }
 assert(sm_stack.height >= 0);
 return sm_stack.height == 0;
}
bool scene_stack_is_not_empty(void)
{
 return !scene_stack_is_empty();
}

misc_util.c

#include "misc_util.h"
#include <assert.h>
#include <stdlib.h>
bool safer_realloc(void **p, size_t increased_size)
{
 assert(p != NULL);
 void *tmp = realloc(*p, increased_size);
 if (tmp == NULL)
 {
 return false;
 }
 else
 {
 *p = tmp;
 return true;
 }
}
void safe_dealloc(void **p, size_t decreased_size)
{
 assert(p != NULL);
 if (decreased_size == 0)
 {
 free(*p);
 *p = NULL;
 }
 else
 {
 void *tmp = realloc(*p, decreased_size);
 *p = tmp ? tmp : *p;
 }
}

A quick and dirty demonstration

main.c

// A quick and dirty, short demonstration of Scene Manager capabilities
#include "scene_manager.h"
#include <stdlib.h>
#include <stdio.h>
enum Scenes
{
 SCENE_ONE,
 SCENE_TWO,
 NUM_SCENES
};
// Function prototypes
void *initialize_scene_one(void);
bool reinitialize_scene_one(void *data);
int execute_scene_one(void *data);
void terminate_scene_one(void *data);
void *initialize_scene_two(void);
int execute_scene_two(void *data);
void terminate_scene_two(void *data);
// Scenes
Scene scene_one = {initialize_scene_one,
 reinitialize_scene_one,
 execute_scene_one,
 terminate_scene_one};
Scene scene_two = {initialize_scene_two,
 NULL,
 execute_scene_two,
 terminate_scene_two};
// Function implementations
void *initialize_scene_one(void)
{
 puts("Initializing scene ONE");
 return malloc(100);
}
bool reinitialize_scene_one(void *data)
{
 puts("Reinitializing scene ONE");
 return true;
}
int execute_scene_one(void *data)
{
 puts("Executing scene ONE");
 static bool first_run = true;
 if (first_run == true)
 {
 first_run = false;
 return SCENE_TWO;
 }
 else
 {
 return POP_REQUEST;
 }
}
void terminate_scene_one(void *data)
{
 puts("Terminating scene ONE");
 free(data);
}
void *initialize_scene_two(void)
{
 puts("Initializing scene TWO");
 return "Executing scene TWO";
}
int execute_scene_two(void *data)
{
 puts(data);
 return POP_REQUEST;
}
void terminate_scene_two(void *data)
{
 puts("Terminating scene TWO");
}
// The main function
int main(void)
{
 Scene scenes[NUM_SCENES];
 scenes[0] = scene_one;
 scenes[1] = scene_two;
 if (initialize_scene_manager(NUM_SCENES, scenes, SCENE_ONE) == true)
 {
 while (scene_stack_is_not_empty())
 {
 if (execute_top_scene() == false)
 {
 break;
 }
 }
 terminate_scene_manager();
 }
 return EXIT_SUCCESS;
}

stdout output (everything works as expected)

Initializing scene ONE
Executing scene ONE
Initializing scene TWO
Executing scene TWO
Terminating scene TWO
Reinitializing scene ONE
Executing scene ONE
Terminating scene ONE

GitHub repository if you would like to test it

Note: I don't know how to make a proper makefile because I'm used to IDEs. I'm fine with them for now.

asked Feb 1, 2016 at 19:00
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Small review

safe_dealloc() and safer_realloc() could be rolled into one. safer_realloc(0) would benefit clear size==0 functionality.

Pedantic: size_t new_size = old_size + sizeof(*sm_stack.stack); lacks overflow protection.


[Edit] Sample merge

Since the calling code essentially needs to keep track of the present size, pass then both in by address and let the pointer and size variables get updated together in this function.

bool safer_realloc(void **p, size_t *current_size, size_t new_size) {
 assert(p != NULL);
 assert(Current_size != NULL);
 if (new_size == 0) {
 free(*p);
 *p = NULL;
 *current_size = 0;
 return true; // OK;
 }
 void *tmp = realloc(*p, new_size);
 if (tmp == NULL) {
 // no change to *p or *current_size 
 return false; // !OK
 }
 *p = tmp;
 *current_size = new_size;
 return true; // OK;
}

When calling code is shrinking, no strong need to check the return value.

answered Feb 2, 2016 at 3:08
\$\endgroup\$
6
  • \$\begingroup\$ How would you merge safe_dealloc and safer_realloc? I understand safer_realloc(&ptr, 0) functionality, but how could one know whether memory is allocated or deallocated in safer_realloc(&ptr, 42) scenario? Do you suggest passing a boolean flag? To clarify, in the proposed scenario, both functions would behave distinctly differently. safe_dealloc would deliver a bulletproof pointer while safer_realloc might fail if the machine is short of 42 bytes. Now that I think of it, maybe I should rename safer_realloc to safer_alloc? \$\endgroup\$ Commented Feb 2, 2016 at 17:44
  • \$\begingroup\$ "When calling code is shrinking, no strong need to check the return value." What about this? Is this phenomena too rare to be bothered about? \$\endgroup\$ Commented Feb 2, 2016 at 18:00
  • \$\begingroup\$ @Sand Freight No strong need for the calling code to check the result of safer_realloc() when it is shrinking. safer_realloc() still needs to check the result of realloc() on shrink/grow/same-size. \$\endgroup\$ Commented Feb 2, 2016 at 18:02
  • \$\begingroup\$ @Sand Freight Since the code uses a pointer and size as a pair, consider making that a structure. \$\endgroup\$ Commented Feb 2, 2016 at 18:05
  • 1
    \$\begingroup\$ Okay, I will let this stay for another 24 hours. \$\endgroup\$ Commented Feb 2, 2016 at 18:09

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.