TrevorPeyton
TrevorPeyton

Reputation: 659

Wrong way to use enum?

I am working on an event system for a game. My original way of going about this was to have a group of listeners stored in a map with a string as the key (event name) and callback for when the event is triggered. This worked fine (In my head, no actual tests done yet), but it didn't take long to find some flaws.

  1. Speed - I hope to make this a medium sized game. This would mean 100's of "listeners" waiting different events. After an event is called, the search would have to go through every listener string name to find a match. A huge amount of events will be sent and processed as fast as possible to avoid hogging all the time in each frame.
  2. Naming - "Shoot_arrow_player", "Shoot_arrow_ai", "Shoot_arrow_player3". It's not a very easy system having to remember every event name and could easily have typos and make for annoying debugging.

Then I thought of an odd (In my mind) solution to this. Using enums to categorize and speed up performance.

Don't worry I'm getting to my question.

This was the way I setup the enum list:

struct Events
{
    enum class Player {
        Start = 1,
        Shoot_arrow,
        Reloading_bow,
        Draw_Bow,
        End
    };
    enum class Enemy {
        Start = (int)Player::End+1,
        Check_for_object,
        Object_spotted,
        Attacking_object,
        End
    };
    enum class Car {
        Start = (int)Enemy::End+1,
        Starting_engine,
        Out_of_gas,
        Car_started,
        End
    };
};

With this system, I can use an even faster way of searching for an event if I "categorize" the events. I was thinking of doing something like having a map where the key is the category (start int for each category) and the data is another map, in that map the key is the event (int) and data is the callback. This way I could quickly find the category and then have a lot less to search through. This would be a single function that could return the category:

if (event > Event::Enemy)
    return Event::Bear::Start;
else if (event < Event::Enemy)
    return Event::Player::Start;
else
    return Event::Enemy::Start;

Then you could search for the event with a much smaller list of possibilities than searching through ever single event. The only downside (That I can think of) is the amount of hard coding I will be doing for each category.

Now, my question is if this is a correct way to use enums. I know that the compiler shouldn't throw any errors but I'm wandering if I were to publish this would this be considered bad programming. I'm trying to avoid not being constructive as much as I can but since this will be a critical system I need to make sure it is not a bad thing to do.

Upvotes: 1

Views: 149

Answers (2)

Thomas Matthews
Thomas Matthews

Reputation: 57688

I suggest you send information in your event structure rather than in the enum name.

enum Player {/*...*/};
enum Action {Shoot, /*...*/};
enum Weapon {Bow_Arrow, /*...*/};

struct Event
{
  Player receiving_player;
  Action event_action;
  Weapon event_weapon;
};

//...
Event e = {Enemy1, Shoot, Bow_Arrow};
Send_Events(e);

This technique can be expanded, such as having a parent event or other events (such as movement).

The concept here is to place the information into a variable rather than the identifier name.

Edit 1: Player receiving from enemy.
Let's add another field to the event:

struct Event
{
  Player sending_player;
  Player receiving_player;
  Action event_action;
  Weapon event_weapon;
};

The event creator would fill in the fields:

Event e = {Enemy1, Player2, Shoot, Bow_Arrow);  

The above event describes the action of Enemy1 shooting an arrow from a bow at Player2.

The next thing is to have an event handler that sends the event to zero or more listeners:

struct Event_Listener
{
  virtual void receive_event(const Event& e) = 0;
};

typedef std::vector<Event_Listener *> Event_Listener_Container;
//...
Event_Listener_Container listeners;
Event_Listener_Container::iterator iter;
for (iter = listeners.begin();
     iter != listeners.end();
   ++iter)
{
  (*iter)->receive_event(e);
}

Keep in mind, there are various implementations of Listeners, Subscribers and Publishers. Look up these design patterns.

Upvotes: 3

Peter M
Peter M

Reputation: 7493

IMHO your proposed use of enums smells to high heaven and is dependent on what I would call coding by coincidence. It will be fragile, and once broken really really hard to debug.

For example, suppose I code a reasonable (from the point of view of the language) change to your enums:

struct Events
{
    enum class Player {
        Start = 1,
        Shoot_arrow,
        Reloading_bow,
        Draw_Bow,
        End = 0
    };
    enum class Enemy {
        Start = (int)Player::End+1,
        Check_for_object,
        Object_spotted,
        Attacking_object,
        End
    };
    enum class Car {
        Start = (int)Enemy::End+1,
        Starting_engine,
        Out_of_gas,
        Car_started,
        End
    };
};

Then your whole strategy is toast. Your code should not break so easily.

Upvotes: 1

Related Questions