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                 // note: we'll keep using the old host!
00196         } else {
00197                 // conversation does not already exist!  Create a record for it
00198                 //DPRINTF("Adding conversation with guid='%s'", guid);
00199 
00200                 // create conversation record
00201                 cr.guid = guid;
00202                 cr.connId = connId;
00203                 cr.playerId = playerId;
00204                 cr.host = host;
00205 
00206                 // add to our map
00207                 m_conversations.insert(guid, cr);
00208         }
00209 
00210         // tell the host that the conversation has started
00211         int newId = host->getCurrentDialogIdTS();
00212         this->updateDialogId(guid, cr, newId);
00213 
00214         // router should now route dialog
00215         this->route(cr);
00216 
00217         // all done!
00218         return true;
00219 }
00220 
00221 
00222 
00223 bool
00224 ConMgr::handleDialogReplyTS
00225 (
00226 IN const char * guid,
00227 IN int dialogId,
00228 IN conn_id_t conn_id,
00229 IN int playerId,
00230 IN const Datahash * reply
00231 )
00232 {
00233         ASSERT(guid, "null");
00234         ASSERT(dialogId > 0, "Bad dialog id: %d", dialogId);
00235 //      ASSERT(conn_id, "null");        -- we don't care!
00236         ASSERT(playerId > 0, "Bad player id: %d", playerId);
00237         ASSERT(reply, "null");
00238 
00239         //DPRINTF("handleDialogReplyTS  guid='%s'  dialog=%d",
00240         //    guid, dialogId);
00241 
00242         conv_rec_t cr;
00243         if (!m_conversations.lookup(guid, cr)) {
00244                 DPRINTF("Unrecognized conversation: %s", guid);
00245                 return false;
00246         }
00247         ASSERT(cr.host, "null host");
00248 
00249         if (cr.connId != conn_id) {
00250                 // weird!  either spoofing or a networking error
00251                 DPRINTF("Connection ID does not match");
00252                 return false;
00253         }
00254 
00255         if (cr.playerId != playerId) {
00256                 // spoofing?
00257                 DPRINTF("Player ID does not match");
00258                 return false;
00259         }
00260 
00261         if (cr.dialogId != dialogId) {
00262                 // out of sync?  Client should refresh
00263                 DPRINTF("Dialog ID does not match");
00264                 DPRINTF("  Local id: %d", cr.dialogId);
00265                 DPRINTF("  Remote id:%d", dialogId);
00266                 DPRINTF("  Forcing a refresh to the client...");
00267                 int newId = cr.host->getCurrentDialogIdTS();
00268                 this->updateDialogId(guid, cr, newId);
00269                 this->route(cr);
00270                 return false;
00271         }
00272 
00273         // seems like a valid reply!  Forward to host of conversation
00274         cr.host->handleReplyTS(dialogId, reply);
00275         int newId = cr.host->getCurrentDialogIdTS();
00276         if (this->updateDialogId(guid, cr, newId)) {
00277                 //DPRINTF("Dialog changed!  refreshing...");
00278                 this->route(cr);
00279         }
00280 
00281         // all done
00282         return true;
00283 }
00284 
00285 
00286 
00287 void
00288 ConMgr::updateAll
00289 (
00290 void
00291 )
00292 {
00293         conversation_map_t::iterator_t i;
00294         m_conversations.getIterator(i);
00295         std::string guidstr;
00296         conv_rec_t cr;
00297         while (m_conversations.getNextElement(i, guidstr, cr)) {
00298                 ASSERT(cr.host, "null");
00299                 if (cr.host->updateState()) {
00300                         // host has asked that it be refreshed!
00301                         this->updateConversationTS(cr.guid.c_str(), cr.connId,
00302                             cr.playerId, cr.host);
00303                 }
00304         }
00305 }
00306 
00307 
00308 
00309 ////////////////////////////////////////////////////////////////////////////////
00310 //
00311 //      ConMgr -- private helper methods
00312 //
00313 ////////////////////////////////////////////////////////////////////////////////
00314 
00315 bool
00316 ConMgr::deleteConversation
00317 (
00318 IN const char * guid
00319 )
00320 {
00321         ASSERT(guid, "null");
00322 
00323         conv_rec_t cr;
00324         if (!m_conversations.lookup(guid, cr)) {
00325                 return false;
00326         }
00327 
00328         // route so that router can destroy conversation
00329         cr.dialogId = 0;
00330         this->route(cr);
00331 
00332         return m_conversations.remove(guid);
00333 }
00334 
00335 
00336 
00337 bool
00338 ConMgr::updateDialogId
00339 (
00340 IN const char * guid,
00341 IN conv_rec_t& cr,
00342 IN int dialogId
00343 )
00344 {
00345         ASSERT(guid, "null");
00346         ASSERT(dialogId >= 0, "bad dialog ID: %d", dialogId);
00347 
00348         if (!dialogId) {
00349                 DPRINTF("Host has requested this conversation end: %s", guid);
00350                 this->deleteConversation(guid);
00351                 return false;   // does not count as a change!
00352         } else if (cr.dialogId != dialogId) {
00353                 DPRINTF("Host has changed dialogs in conversation");
00354                 DPRINTF("  old dialog: %d", cr.dialogId);
00355                 DPRINTF("  new dialog: %d", dialogId);
00356 
00357                 cr.dialogId = dialogId;
00358 
00359                 // re-insert
00360                 m_conversations.insert(guid, cr);
00361 
00362                 return true;    // dialog ID changed
00363         }
00364 
00365         return false;           // no change
00366 }
00367 
00368 
00369 
00370 void
00371 ConMgr::route
00372 (
00373 IN conv_rec_t& cr
00374 )
00375 {
00376         ASSERT(cr.playerId > 0, "Bad player id: %d", cr.playerId);
00377         // ASSERT(connId) -- don't care!
00378         ASSERT(cr.host, "null");
00379         ASSERT(m_router, "null");
00380 
00381         std::string data = cr.host->getDialogDataTS();
00382         m_router->routeConversationTS(cr.guid.c_str(), cr.dialogId,
00383             cr.playerId, cr.connId, data.c_str());
00384 }
00385 
00386 
00387 
00388 ////////////////////////////////////////////////////////////////////////////////
00389 //
00390 //      public API
00391 //
00392 ////////////////////////////////////////////////////////////////////////////////
00393 
00394 smart_ptr<ConversationManager>
00395 ConversationManager::create
00396 (
00397 IN ConversationRouter * router
00398 )
00399 {
00400         ASSERT(router, "null");
00401 
00402         smart_ptr<ConMgr> local = new ConMgr;
00403         ASSERT(local, "out of memory");
00404 
00405         local->initialize(router);
00406 
00407         return local;
00408 }
00409 
00410 
00411 
00412 };      // converse namespace
00413