Home > Software engineering >  Coming from OO languages to C, how can I avoid circular dependencies?
Coming from OO languages to C, how can I avoid circular dependencies?

Time:02-14

When I say "Entity" below, I'm not specifically referring to anything relating to the ECS pattern, I just mean a general game entity.

I'm trying my hand at game development in C, after having done previous game dev in TypeScript. I'm looking for a C idiomatic way to reuse a pattern with which I'm familiar: each tick, the game iterates through a list of entities, telling each one to update itself, then draw itself. Each entity knows how to update itself, but requires information about the game as a whole to make this update.

// Game.ts
import Entity from './Entity.js'
class Game {
  entities: List<Entity>;

  tick(dt: number) {
    entities.forEach(e => e.tick(dt));
    entities.forEach(e => e.draw());
  }
}

// Entity.ts
import Game from './Game.ts'
class Entity {
  game: Game;

  constructor(g: Game) {
    this.game = g;
  }

  tick(dt: number) {
    this.move(dt);
    this.collide(this.game);
  }
  draw() { /* snip */}
}

In C, I would like to have a big Game struct that has a list of all Entities inside it, and each Entity contains a function pointer for how to update itself.

// game.h
#include "entity.h"
typedef struct Game {
  Entity *entities;
} Game;

// entity.h
#include "game.h"
typedef struct Entity Entity;

typedef void (*tick) (Entity*);

struct Entity {
  Game *game;
  char* name;
  int x, y;
  tick* t;
};

This, however, requires a circular reference to Game in Entity and Entity in Game, which I've gathered that one is not supposed to do. The only way I've thought of to make this work is to put a tick_entity(Game *game, Entity *e) function in game.h, but my OO brain wants to separate my concerns some more to avoid making Game responsible for everything, especially once I have different kinds of Entities. Is there a more idiomatic way to do what I'm trying to do here?

CodePudding user response:

Don't #include "entity.h" from game.h and vice versa. Just forward declare what you need a pointer to. Also add header guards if you haven't already.

Example:

// game.h
#ifndef GAME_H                 // header guard
#define GAME_H

//#include "entity.h"          // remove this
typedef struct Entity Entity;  // forward declare

typedef struct Game {
    Entity* entities;
} Game;

#endif
// entity.h
#ifndef ENTITY_H               // header guard
#define ENTITY_H

//#include "game.h"            // remove this
typedef struct Game Game;      // forward declare
typedef struct Entity Entity;

typedef void (*tick)(Entity*);

struct Entity {
    Game* game;
    char* name;
    int x, y;
    tick* t;
};

#endif
  • Related