conversation.cpp

Go to the documentation of this file.
00001 /*
00002  * conversation.cpp
00003  *
00004  * Copyright (C) 2008  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  *
00008  * Redistribution and use in source and binary forms, with or without
00009  * modification, are permitted provided that the following conditions are met:
00010  *     * Redistributions of source code must retain the above copyright
00011  *       notice, this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright
00013  *       notice, this list of conditions and the following disclaimer in the
00014  *       documentation and/or other materials provided with the distribution.
00015  *     * Neither the name of the <organization> nor the
00016  *       names of its contributors may be used to endorse or promote products
00017  *       derived from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THOMAS A. VAUGHAN ''AS IS'' AND ANY
00020  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THOMAS A. VAUGHAN BE LIABLE FOR ANY
00023  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  *
00030  */
00031 
00032 // includes -------------------------------------------------------------------
00033 #include "conversation.h"               // always include our own header first!
00034 
00035 #include "threadsafe/threadsafe_map.h"
00036 
00037 
00038 namespace converse {
00039 
00040 
00041 /// \ingroup conversation
00042 /*@{*/
00043 
00044 // public interface destructor implementation
00045 ConversationHost::~ConversationHost(void) throw() { }
00046 ConversationRouter::~ConversationRouter(void) throw() { }
00047 ConversationManager::~ConversationManager(void) throw() { }
00048 
00049 
00050 ////////////////////////////////////////////////////////////////////////////////
00051 //
00052 //      ConversationHost -- base class implementation
00053 //
00054 ////////////////////////////////////////////////////////////////////////////////
00055 
00056 bool
00057 ConversationHost::updateState
00058 (
00059 void
00060 )
00061 {
00062         // default implementation: do nothing, assume nothing changed
00063         return false;
00064 }
00065 
00066 
00067 
00068 ////////////////////////////////////////////////////////////////////////////////
00069 //
00070 //      ConMgr -- object that implements the ConversationManager interface
00071 //
00072 //      NOTE: all public entry points must be threadsafe!
00073 //      Private helper methods are not threadsafe.
00074 //
00075 ////////////////////////////////////////////////////////////////////////////////
00076 
00077 class ConMgr : public ConversationManager {
00078 public:
00079         ConMgr(void) throw();
00080         ~ConMgr(void) throw() { }
00081 
00082         // public class methods ------------------------------------------------
00083         void initialize(IN ConversationRouter * router);
00084 
00085         // converse::ConversationManager class interface methods ---------------
00086         bool updateConversationTS(IN const char * guid,
00087                                 IN conn_id_t conn_id,
00088                                 IN int playerId,
00089                                 IN smart_ptr<ConversationHost> host);
00090 
00091         bool handleDialogReplyTS(IN const char * guid,
00092                                 IN int dialogId,
00093                                 IN conn_id_t conn_id,
00094                                 IN int playerId,
00095                                 IN const Datahash * reply);
00096 
00097         bool deleteConversationTS(IN const char * guid);
00098 
00099         void updateAll(void);
00100 
00101 private:
00102         // private typedefs ----------------------------------------------------
00103         struct conv_rec_t {
00104                 // constructor, manipulators
00105                 conv_rec_t(void) throw() { this->clear(); }
00106                 void clear(void) throw() {
00107                                 guid = "";
00108                                 dialogId = 0;
00109                                 connId = 0;
00110                                 playerId = 0;
00111                                 host = NULL;
00112                         }
00113 
00114                 // data fields
00115                 std::string             guid;           ///< conversation guid
00116                 int                     dialogId;       ///< current dialog
00117                 conn_id_t               connId;         ///< connection
00118                 int                     playerId;       ///< local ID on host
00119                 smart_ptr<ConversationHost> host;       ///< host object
00120         };
00121 
00122         typedef threadsafe_map<std::string, conv_rec_t> conversation_map_t;
00123 
00124         // private helper functions --------------------------------------------
00125         bool deleteConversation(IN const char * guid);
00126         bool updateDialogId(IN const char * guid,
00127                                 IN conv_rec_t& cr,
00128                                 IN int dialogId);
00129         void route(IN conv_rec_t& cr);
00130 
00131         // private member data -------------------------------------------------
00132         conversation_map_t      m_conversations;// all registered conversations
00133         ConversationRouter *    m_router;       // weak ref to router
00134 };
00135 
00136 
00137 
00138 ConMgr::ConMgr(void)
00139 throw()
00140 {
00141         m_router = NULL;                // weak ref, do not delete!
00142 }
00143 
00144 
00145 
00146 void
00147 ConMgr::initialize
00148 (
00149 IN ConversationRouter * router
00150 )
00151 {
00152         ASSERT(router, "null");
00153 
00154         ASSERT(!m_router, "already have a router?");
00155         m_router = router;
00156 }
00157 
00158 
00159 
00160 ////////////////////////////////////////////////////////////////////////////////
00161 //
00162 //      ConMgr -- converse::ConversationManager class interface methods
00163 //
00164 //      All methods should be threadsafe, hence the TS suffix.
00165 //
00166 ////////////////////////////////////////////////////////////////////////////////
00167 
00168 bool
00169 ConMgr::updateConversationTS
00170 (
00171 IN const char * guid,
00172 IN conn_id_t connId,
00173 IN int playerId,
00174 IN smart_ptr<ConversationHost> host
00175 )
00176 {
00177         ASSERT(guid, "null");
00178         // ASSERT(connId, "null"); -- we don't care!
00179         ASSERT(playerId > 0, "Bad player id: %d", playerId);
00180         ASSERT(host, "null");
00181 
00182         // see if there is already a conversation with this guid
00183         conv_rec_t cr;
00184         if (m_conversations.lookup(guid, cr)) {
00185                 // validate with existing conversation
00186                 if (cr.playerId != playerId) {
00187                         DPRINTF("Player ID does not match");
00188                         return false;
00189                 }
00190                 if (cr.connId != connId) {
00191                         DPRINTF("Connection ID does not match");
00192                         return false;
00193                 }
00194 
00195                 // do NOT switch to new host!  We respect dialogs
00196                 //    already in progress...
00197                 // cr.host = host;
00198         } else {
00199                 // conversation does not already exist!  Create a record for it
00200                 //DPRINTF("Adding conversation with guid='%s'", guid);
00201 
00202                 // create conversation record
00203                 cr.guid = guid;
00204                 cr.connId = connId;
00205                 cr.playerId = playerId;
00206                 cr.host = host;
00207 
00208                 // add to our map
00209                 m_conversations.insert(guid, cr);
00210         }
00211 
00212         // tell the host that the conversation has started
00213         int newId = host->getCurrentDialogIdTS();
00214         this->updateDialogId(guid, cr, newId);
00215 
00216         // router should now route dialog
00217         this->route(cr);
00218 
00219         // all done!
00220         return true;
00221 }
00222 
00223 
00224 
00225 bool
00226 ConMgr::handleDialogReplyTS
00227 (
00228 IN const char * guid,
00229 IN int dialogId,
00230 IN conn_id_t conn_id,
00231 IN int playerId,
00232 IN const Datahash * reply
00233 )
00234 {
00235         ASSERT(guid, "null");
00236         ASSERT(dialogId > 0, "Bad dialog id: %d", dialogId);
00237 //      ASSERT(conn_id, "null");        -- we don't care!
00238         ASSERT(playerId > 0, "Bad player id: %d", playerId);
00239         ASSERT(reply, "null");
00240 
00241         //DPRINTF("handleDialogReplyTS  guid='%s'  dialog=%d",
00242         //    guid, dialogId);
00243 
00244         conv_rec_t cr;
00245         if (!m_conversations.lookup(guid, cr)) {
00246                 DPRINTF("Unrecognized conversation: %s", guid);
00247                 return false;
00248         }
00249         ASSERT(cr.host, "null host");
00250 
00251         if (cr.connId != conn_id) {
00252                 // weird!  either spoofing or a networking error
00253                 DPRINTF("Connection ID does not match");
00254                 return false;
00255         }
00256 
00257         if (cr.playerId != playerId) {
00258                 // spoofing?
00259                 DPRINTF("Player ID does not match");
00260                 return false;
00261         }
00262 
00263         if (cr.dialogId != dialogId) {
00264                 // out of sync?  Client should refresh
00265                 DPRINTF("Dialog ID does not match");
00266                 DPRINTF("  Local id: %d", cr.dialogId);
00267                 DPRINTF("  Remote id:%d", dialogId);
00268                 DPRINTF("  Forcing a refresh to the client...");
00269                 int newId = cr.host->getCurrentDialogIdTS();
00270                 this->updateDialogId(guid, cr, newId);
00271                 this->route(cr);
00272                 return false;
00273         }
00274 
00275         // seems like a valid reply!  Forward to host of conversation
00276         cr.host->handleReplyTS(dialogId, reply);
00277         int newId = cr.host->getCurrentDialogIdTS();
00278         if (this->updateDialogId(guid, cr, newId)) {
00279                 //DPRINTF("Dialog changed!  refreshing...");
00280                 this->route(cr);
00281         }
00282 
00283         // all done
00284         return true;
00285 }
00286 
00287 
00288 
00289 void
00290 ConMgr::updateAll
00291 (
00292 void
00293 )
00294 {
00295         conversation_map_t::iterator_t i;
00296         m_conversations.getIterator(i);
00297         std::string guidstr;
00298         conv_rec_t cr;
00299         while (m_conversations.getNextElement(i, guidstr, cr)) {
00300                 ASSERT(cr.host, "null");
00301                 if (cr.host->updateState() &&
00302                     cr.host->getCurrentDialogIdTS()) {
00303                         // host has asked that it be refreshed!
00304                         this->updateConversationTS(cr.guid.c_str(), cr.connId,
00305                             cr.playerId, cr.host);
00306                 }
00307         }
00308 }
00309 
00310 
00311 
00312 ////////////////////////////////////////////////////////////////////////////////
00313 //
00314 //      ConMgr -- private helper methods
00315 //
00316 ////////////////////////////////////////////////////////////////////////////////
00317 
00318 bool
00319 ConMgr::deleteConversation
00320 (
00321 IN const char * guid
00322 )
00323 {
00324         ASSERT(guid, "null");
00325         DPRINTF("Deleting conversation: '%s'", guid);
00326 
00327         conv_rec_t cr;
00328         if (!m_conversations.lookup(guid, cr)) {
00329                 return false;
00330         }
00331 
00332         // route so that router can destroy conversation
00333         std::string myGuid = guid;      // copy
00334         cr.dialogId = 0;
00335         this->route(cr);
00336         if (!m_conversations.remove(myGuid)) {
00337                 DPRINTF("WARNING: failed to remove conversation!");
00338                 DPRINTF("  guid='%s'", myGuid.c_str());
00339                 return false;
00340         }
00341         return true;
00342 }
00343 
00344 
00345 
00346 bool
00347 ConMgr::updateDialogId
00348 (
00349 IN const char * guid,
00350 IN conv_rec_t& cr,
00351 IN int dialogId
00352 )
00353 {
00354         ASSERT(guid, "null");
00355         ASSERT(dialogId >= 0, "bad dialog ID: %d", dialogId);
00356 
00357         if (!dialogId) {
00358                 DPRINTF("Host has requested this conversation end: %s", guid);
00359                 this->deleteConversation(guid);
00360                 return false;   // does not count as a change!
00361         } else if (cr.dialogId != dialogId) {
00362                 DPRINTF("Host has changed dialogs in conversation");
00363                 DPRINTF("  old dialog: %d", cr.dialogId);
00364                 DPRINTF("  new dialog: %d", dialogId);
00365 
00366                 cr.dialogId = dialogId;
00367 
00368                 // re-insert
00369                 m_conversations.insert(guid, cr);
00370 
00371                 return true;    // dialog ID changed
00372         }
00373 
00374         return false;           // no change
00375 }
00376 
00377 
00378 
00379 void
00380 ConMgr::route
00381 (
00382 IN conv_rec_t& cr
00383 )
00384 {
00385         ASSERT(cr.playerId > 0, "Bad player id: %d", cr.playerId);
00386         // ASSERT(connId) -- don't care!
00387         ASSERT(cr.host, "null");
00388         ASSERT(m_router, "null");
00389 
00390         std::string data = cr.host->getDialogDataTS();
00391         m_router->routeConversationTS(cr.guid.c_str(), cr.dialogId,
00392             cr.playerId, cr.connId, data.c_str());
00393 }
00394 
00395 
00396 
00397 ////////////////////////////////////////////////////////////////////////////////
00398 //
00399 //      public API
00400 //
00401 ////////////////////////////////////////////////////////////////////////////////
00402 
00403 smart_ptr<ConversationManager>
00404 ConversationManager::create
00405 (
00406 IN ConversationRouter * router
00407 )
00408 {
00409         ASSERT(router, "null");
00410 
00411         smart_ptr<ConMgr> local = new ConMgr;
00412         ASSERT(local, "out of memory");
00413 
00414         local->initialize(router);
00415 
00416         return local;
00417 }
00418 
00419 
00420 
00421 };      // converse namespace
00422