Next Previous Contents

7. Edicts and Entities

7.1 Spawn function LUT

In the g_spawn module you will find a lookup like this:


typedef struct
{
        char    *name;
        void    (*spawn)(edict_t *ent);
} spawn_t;

followed by a list of void SP_* (edict_t *self) function prototypes which are implemented all over the G moldules. For example, SP_monster_* you will typically find at the end of the respective m_*.c module implementing the monster in question.

The spawn_t spawns[] Lookup Table in the same file assigns strings (as used in MAP files) to spawn function pointers. The strings are copied from the MAP file into the BSP file during processing the level, and the main engine will simply use this LUT to retrieve a function pointer for a string. It will hand the spawn function an already allocated edict_t, which is filled in by the spawn function.

The reason why the spawn function is usually at the end of a module is that it stores all pointers to all other functions in the edict. There are no global protoypes for any of these, not even for the spawn functions.

7.2 The Server - Game Subsystem interface

In game.h::game_export_t we find that the edict array is the only global variable currently shared between game and server. The edict array is allocated in the game dll, so it can vary in size from one game to another. The size has to be fixed when ge->Init() is called, which is by default g_save.c::InitGame() (the protoype is in g_main.c, where the pointer is set).

The function is called when the dll is first loaded, which only happens when a new game is begun. We have the following global variables exported:


        struct edict_s*         edicts;
        int                     edict_size;
        // current number, <= max_edicts
        int                     num_edicts;            
        int                     max_edicts;

and the Q2 DLL InitGame function handles them in the following way:
        // initialize all entities for this game
        game.maxentities = maxentities->value;
        if (maxclients->value * 8 > game.maxentities)
            game.maxentities = maxclients->value * 8;
        g_edicts =  
           gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
        globals.edicts = g_edicts;
        globals.max_edicts = game.maxentities;

        ...

        globals.num_edicts = game.maxclients+1;

The DLL global variable game_local_t game that is referred to here is previously filled by lookin up several CVARS, e.g.
  maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);

7.3 Spawning during game

If you consider item dropping, you will recognize that permanent entities can be created at runtime, as long as the fixed size edict table has empty slots. Take a look at g_item.c, namely:


edict_t *Drop_Item (edict_t *ent, gitem_t *item)
{
        edict_t *dropped;
        vec3_t  forward, right;
        vec3_t  offset;

        dropped = G_Spawn();

        ...

}

In g_utils.c we find the edict_t *G_Spawn (void) function, which either finds a free edict, or allocates a new one. The comment advises us to try to avoid reusing an entity that was recently freed, because it can cause the client to think the entity morphed into something else instead of being removed and recreated, which can cause interpolated angles and bad trails. This morphing effect might be desirable under some circumstances, though.

7.4 Freeing during game.

The corresponding g_utils.c::G_FreeEdict() can be called to reset an edict. As the spawn function, this does not truly allocate/de-allocate - basically, the pair of functions implements a pool allocator using the edict array as a pool.

These functions also keep you from trying to use entity 0 (also known as worldspawn) and other Really Bad Things.

7.5 Linking entities

When you found a free entity and filled its fields with the proper values, you have to call gi.linkentity, a function imported from the main engine (server side), and opaque to the Game Subsystem. It will exist in the game until gi.unlinkentity is called. As the comments in game.h::game_import_t point out, an entity will never be sent to a client or used for collision detection if it is not passed to linkentity. Also, if the size, position, or solidity changes, it must be relinked.

7.6 OOP view of spawning

The spawn function corresponds to a constructor, but as we do not have dynamic allocation, and use a fixed pool of edicts instead, it simply serves as filling in the memory lot. All monsters share the same edict structure (in fact, triggers, items, targets, everything does).

The function pointers, e.g.


        self->monsterinfo.stand = berserk_stand;
        self->monsterinfo.walk = berserk_walk;
        self->monsterinfo.run = berserk_run;
        self->monsterinfo.dodge = NULL;
        self->monsterinfo.attack = NULL;
        self->monsterinfo.melee = berserk_melee;
        self->monsterinfo.sight = berserk_sight;
        self->monsterinfo.search = berserk_search;

can be considered member functions. In C++, the actual pointers would be stored in a VTABLE, and the object itself would only store a pointer to that table, which introduces another indirection. For virtual functions, the situation would be identical to the one above/in Q2.


Next Previous Contents