Accessors

[ LiB ]

The final topic I want to discuss in this chapter is the accessor classes. These classes are very similar to the database pointer classes of the SimpleMUD, except that they are a bit more abstracted; they don't actually allow direct access to the entities that they point to, but instead wrap around them. This is because the accessor classes are designed to be used as wrappers into the Python language, and also as lightweight pointer classes.

Accessors are simple; they all contain a pointer to the object they point to, and they all manage reference counts to the entities they point to. If you've got 10 accessors pointing to an object in the game, that object's reference count is also going to be 10; the game always knows when something is pointing to an entity.

Unfortunately, I ran into the same problem with accessors that I did with database pointers from the SimpleMUD: circular dependencies and templates just don't mix . It's an unfortunate side effect of the 1-pass nature of C++, but there's not much you can do to fix it.

Accessor Problems

Unfortunately, the problem with C++ circular dependencies and templates is much bigger for the BetterMUD, since accessors are wrapper (or proxy ) classes. The accessor classes actually need to be given all the functions you want to use with them, and there are many. For example, an item accessor needs all the entity wrapper functions (accessors for ID, name , and description), the "has room" functions, "has templateid" functions, and so on. You could end up with an accessor looking like this (warning, do not attempt this at home!):

 class item { public:     item( entityid p_id );     item( const item& p_right );     item& operator=( const item& p_right );     ~item();     entityid ID();     std::string Name();     std::string Description();     void SetID( entityid p_id );     void SetName( const std::string& p_name );     void SetDescription( const std::string& p_desc ); ... <SNIP> ... protected:     Item* m_item; }; 

And then you could start on the character accessor class:

 class character { public:     character ( entityid p_id );     character ( const character& p_right );     character& operator=( const character& p_right );     ~character ();     entityid ID();     std::string Name();     std::string Description();     void SetID( entityid p_id );     void SetName( const std::string& p_name );     void SetDescription( const std::string& p_desc ); ... <SNIP> ... protected:     Character* m_character; }; 

Notice anything similar? Almost everything! Well, this is a predicament. It gets even worse when you implement the functions in the .cpp file; even more code duplication!

Solution

I solved this problem using macros. I know macro use is frowned upon by almost everyone, but sometimes you just have to choose the right tool for the job. Using macros in this case will save you hours and hours of trying to figure out how to implement accessors using templates instead (with circular dependencies, you can't), or wasting time copying the same code over and over.

In the /BetterMUD/accessors/AccessorMacros.h file, I've included a bunch of macros to be used in accessor classes. Here's a listing of the "header" macrosthe macros that define the required function headers:

ENTITYHEADERS( AC )constructor, destructor, copy constructor, ID, Name, Description accessors

ENTITYTEMPLATEHEADERS( AC )accessors for template classes, like ItemTemplate and CharacterTemplate

HASREGIONHEADERSregion accessors

HASROOMHEADERSroom accessors

HASTEMPLATEIDHEADERStemplate ID accessors

HASCHARACTERSHEADERScharacter collection accessors

HASITEMSHEADERSitem collection accessors

HASROOMSHEADERSroom collection accessors

HASPORTALSHEADERSportal collection accessors

HASDATABANKHEADERSdatabank accessors

HASLOGICHEADERSlogic module accessors

HASCOMMANDSHEADERScommand accessors

I'll show you a listing of all the functions these macros define a little bit later on. All these macros have implementation equivalents, which contain implementation code. Instead of HEADERS at the end of the macro, you have IMPLEMENTATIONS instead.

Example

Let me show you how to use macros with a simple example. This is the item accessor class:

 class item { public:     ENTITYHEADERS( item );     HASROOMHEADERS;     HASREGIONHEADERS;     HASTEMPLATEIDHEADERS;     HASDATABANKHEADERS;     HASLOGICHEADERS;     bool IsQuantity();     int GetQuantity();     void SetQuantity( int p_quantity ); protected:     Item* m_item; };  // end class item 

The item accessor has entity headers, room headers, region headers, template ID headers, databank headers, and logic headers. I'll get to the parameters in a bit.

Once you have your accessor class defined, you can go ahead and shove the implementation macros into your .cpp files:

 ENTITYIMPLEMENTATIONS( item, m_item, ItemDB ); HASROOMIMPLEMENTATIONS( item, m_item ); HASREGIONIMPLEMENTATIONS( item, m_item ); HASTEMPLATEIDIMPLEMENTATIONS( item, m_item ); HASLOGICIMPLEMENTATIONS( item, m_item ); HASDATABANKIMPLEMENTATIONS( item, m_item ); bool item::IsQuantity()                 { return m_item->IsQuantity(); } int item::GetQuantity()                 { return m_item->GetQuantity(); } void item::SetQuantity( int p_quantity ){ m_item->SetQuantity( p_quantity ); } 

Note that the functions that are unique to items, such as the quantity functions, are actually defined and implemented normally; everything that's in a macro is repeatable, and used in other accessor classes.

A Macro

I bet you're a bit confused at the moment about the macros and their parameters, so to clear your head I'm going to show you two of the macros.

Here is the ENTITYHEADERS macro:

 #define ENTITYHEADERS( AC )                                                 \     AC( entityid p_id );                                                    \     AC( const AC& p_right );                                                \     AC& operator=( const AC& p_right );                                     \     ~AC();                                                                  \     entityid ID();                                                          \     std::string Name();                                                     \     std::string Description();                                              \     void SetID( entityid p_id );                                            \     void SetName( const std::string& p_name );                              \     void SetDescription( const std::string& p_desc ); 

This macro takes a single parameter, which is meant to be the name of the class. In the item class definition you can see that I passed in item . When expanding the macro, the compiler searches for any instances of AC , and replaces it with the parameter, which is item . So when processing this macro, the compiler actually sees this:

 item( entityid p_id );     item( const item& p_right );     item& operator=( const item& p_right );     ~item();     entityid ID();     std::string Name();     std::string Description();     void SetID( entityid p_id );     void SetName( const std::string& p_name );     void SetDescription( const std::string& p_desc ); 

Your item class automatically has all these functions declared.

Of course, function declarations aren't worth anything unless you define them, which is what the ENTITYIMPLEMENTATIONS macro does:

 #define ENTITYIMPLEMENTATIONS( AC, PT, DB )                                 \ AC::AC( entityid p_id )                                                     \ {                                                                           \     PT = &(DB.get( p_id ) );                                                \     PT->AddRef();                                                           \ }                                                                           \ AC::AC( const AC& p_right )                                                 \ {                                                                           \     PT->DelRef();                                                           \     PT = p_right.PT;                                                        \     PT->AddRef();                                                           \ }                                                                           \ AC& AC::operator=( const AC& p_right )                                      \ {                                                                           \     PT = p_right.PT;                                                        \     PT->AddRef();                                                           \     return *this;                                                           \ }                                                                           \ AC::~AC()  { PT->DelRef(); }                                                \ entityid AC::ID()                       { return PT->ID(); }                \ std::string AC::Name()                  { return PT->Name(); }              \ std::string AC::Description()           { return PT->Description(); }       \ void AC::SetID( entityid p_id )         { PT->SetID( p_id ); }              \ void AC::SetName( const std::string& p_name )                               \                                         { PT->SetName( p_name ); }          \ void AC::SetDescription( const std::string& p_desc )                        \                                          { PT->SetDescription( p_desc ); } 

For the implementation macro, you pass in three parameters: the name of the accessor class ( item ), the name of the pointer member variable that the class has ( m_item ), and the database used to look up entities ( ItemDB ). So for example, the constructor would turn from this:

 AC::AC( entityid p_id )                                                     \ {                                                                           \     PT = &(DB.get( p_id ) );                                                \     PT->AddRef();                                                           \ }                                                                           \ 

into this:

 item::item( entityid p_id ) {     m_item = &(ItemDB.get( p_id ) );     m_item->AddRef(); } 

Isn't that cool? You can do this for all the entity types, and keep code duplication at a minimum.

Iterators

I have mentioned this before, but not really in depth. Accessors act like iterators to collections that they point to. For example, if you have a character accessor, you can use that accessor object as an iterator over the items the character has in his inventory.

While I haven't shown you the functions available to the accessors yet, I'm sure you'll have no problem picking up this example of a character accessor iterating over his items:

 character c( 10 ); c.BeginItem();                  // reset to first item while( c.IsValidItem() ) {      // loop while item is valid     process( c.CurrentItem );   // some imaginary process function     c.NextItem();               // go to next item } 

Most of the accessors have iterator seek functions, which will seek the iterator to a specific position, like this:

 c.SeekItem( "sword" ); 

If the character has a sword, c.CurrentItem returns the ID of it, or c.IsValidItem will be false .

Members of the Accessors and Macros

In this section, I'm giving you listings of the functions available in each of the accessor macros and accessor types, for easy reference.

Entity Functions
 accessor( entityid p_id ); accessor( const accessor& p_right ); accessor& operator=( const accessor& p_right ); ~accessor(); entityid ID(); std::string Name(); std::string Description(); void SetID( entityid p_id ); void SetName( const std::string& p_name ); void SetDescription( const std::string& p_desc ); 

For the previous listing, I've replaced the name of the actual accessor class with the string accessor .

Region Functions
 entityid Region(); void SetRegion( entityid p_region ); 

Room Functions
 entityid Room(); void SetRoom( entityid p_room ); 

TemplateID Functions
 entityid TemplateID(); void SetTemplateID( entityid p_templateid ); 

Character Container Functions
 void AddCharacter( entityid p_id ); void DelCharacter( entityid p_id ); size_t Characters(); void BeginCharacter(); entityid CurrentCharacter(); void NextCharacter(); bool IsValidCharacter(); void SeekCharacter( const std::string& p_name ); 

Item Container Functions
 void AddItem( entityid p_id ); void DelItem( entityid p_id ); size_t Items(); void BeginItem(); entityid CurrentItem(); void NextItem(); bool IsValidItem(); void SeekItem( const std::string& p_name ); 

Room Container Functions
 void AddRoom( entityid p_id ); void DelRoom( entityid p_id ); size_t Rooms(); void BeginRoom(); entityid CurrentRoom(); void NextRoom(); bool IsValidRoom(); void SeekRoom( const std::string& p_name ); 

Portal Container Functions
 void AddPortal( entityid p_id ); void DelPortal( entityid p_id ); size_t Portals(); void BeginPortal(); entityid CurrentPortal(); void NextPortal(); bool IsValidPortal(); void SeekPortal( const std::string& p_name ); 

Databank Functions
 int GetAttribute( const std::string& p_name ); void SetAttribute( const std::string& p_name, int p_val ); bool HasAttribute( const std::string& p_name ); void AddAttribute( const std::string& p_name, int p_initialval ); void DelAttribute( const std::string& p_name ); 

Logic Module Functions
 bool AddLogic( const std::string& p_logic ); bool AddExistingLogic( Logic* p_logic ); bool DelLogic( const std::string& p_logic ); Logic* GetLogic( const std::string& p_logic ); bool HasLogic( const std::string& p_logic ); int DoAction( const Action& p_action ); int DoAction( const std::string& p_act,               entityid p_data1 = 0,               entityid p_data2 = 0,               entityid p_data3 = 0,               entityid p_data4 = 0,               const std::string& p_data = "" ); int GetLogicAttribute( const std::string& p_logic,                        const std::string& p_attr ); void AddHook( TimedAction* p_hook ); void DelHook( TimedAction* p_hook ); size_t Hooks(); void ClearHooks(); void ClearLogicHooks( const std::string& p_logic ); 

I get to logic modules in Chapters 14 and 15, so don't worry if you don't yet know what the previous listed functions do. The same goes for the command functions.

Command Functions
 bool HasCommand( const std::string& p_command ); bool AddCommand( const std::string& p_command ); bool DelCommand( const std::string& p_command ); void BeginCommands(); std::string CurrentCommand(); std::string CurrentCommandUsage(); std::string CurrentCommandDescription(); void NextCommand(); bool IsValidCommand(); void SeekCommand( const std::string& p_name ); 

Those are all the functions defined by the macros; everything else that the accessors have is defined normally in the accessor classes.

Accessor Dependencies

Table 13.1 shows the accessor class used by each accessor macro.

Table 13.1. Accessor Classes and the Macros They Need

Macro

Account

Character

Item

Room

Region

Portal

Entity

yes

yes

yes

yes

yes

yes

Region

no

yes

yes

yes

no

yes

Room

no

yes

yes

no

no

no

TemplateID

no

yes

yes

no

no

no

Characters

yes

no

no

yes

yes

no

Items

no

yes

no

yes

yes

no

Rooms

no

no

no

no

yes

no

Portals

no

no

no

yes

yes

no

Databank

no

yes

yes

yes

yes

yes

Logic

no

yes

yes

yes

yes

yes

Commands

no

yes

no

no

no

no


Account Accessor Functions

Account accessors have these functions in addition to their macros (as listed in Table 13.1):

 std::string Password(); BasicLib::sint64 LoginTime(); int AccessLevel(); bool Banned(); int AllowedCharacters(); void SetPass( const std::string& p_pass ); void SetLoginTime( BasicLib::sint64 p_time ); void SetAccessLevel( int p_level ); void SetBanned( bool p_banned ); void SetAllowedCharacters( int p_num ); 

Character Accessor Functions

Characters have these extra accessor functions:

 bool Quiet(); bool IsPlayer(); bool Verbose(); void SetQuiet( bool p_quiet ); void SetAccount( entityid p_account ); bool IsLoggedIn(); void SetLoggedIn( bool p_loggedin ); std::string LastCommand(); 

Item Accessor Functions

Items have these extra accessor functions:

 bool IsQuantity(); int GetQuantity(); void SetQuantity( int p_quantity ); 

Portal Accessor Functions

Portals add a bunch of iterator functions that iterate through their list of path entries:

 void BeginPath(); entityid CurrentStart();          // get current starting room std::string CurrentDirection();   // get current direction name entityid CurrentEnd();            // get current ending room void NextPath(); bool IsValidPath(); void SeekStartRoom( entityid p_room ); void SeekEndRoom( entityid p_room ); 

The path iterator is a bit different from previous iterators, since you're actually accessing a complex portalentry structure, rather than just an entity ID, as the other container iterators do. It's not that difficult though; for each path in a portal, CurrentStart returns the ID of the starting room, CurrentDirection returns the name of that path, and CurrentEnd returns the ending point of that path.

Likewise, there are two seek functions: one that seeks to the first path that has a starting room of p_room ; and the other that seeks to the first path that has an ending room of p_room .

[ LiB ]


MUD Game Programming
MUD Game Programming (Premier Press Game Development)
ISBN: 1592000908
EAN: 2147483647
Year: 2003
Pages: 147
Authors: Ron Penton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net