从 OO 语言到 C,如何避免循环依赖?

Coming from OO languages to C, how can I avoid circular dependencies?

当我在下面说“实体”时,我并不是特指任何与 ECS 模式相关的东西,我只是指一般的游戏实体。

在用 TypeScript 完成之前的游戏开发之后,我正在尝试用 C 语言开发游戏。我正在寻找一种 C 惯用的方法来重用我熟悉的模式:每个滴答声,游戏都会遍历实体列表,告诉每个实体更新自己,然后自己绘制。每个实体都知道如何更新自己,但需要有关整个游戏的信息才能进行此更新。

// 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 */}
}

在 C 中,我想要一个大的 Game 结构,其中包含所有实体的列表,并且每个实体都包含一个用于如何更新自身的函数指针。

// 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;
};

但是,这需要循环引用实体中的游戏和游戏中的实体,据我所知,这是不应该做的。我想到使这项工作起作用的唯一方法是在 game.h 中放置一个 tick_entity(Game *game, Entity *e) 函数,但我的 OO 大脑想要更多地分离我的关注点以避免让 Game 负责一切,尤其是当我有不同种类的实体时。有没有更惯用的方法来做我在这里想做的事情?

不要从 game.h #include "entity.h",反之亦然。只需向前声明您需要指向的指针。如果您还没有添加头部保护,也可以添加。

示例:

// 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