/* * The Mana World Server * Copyright 2007 The Mana World Development Team * * This file is part of The Mana World. * * The Mana World is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or any later version. * * The Mana World is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with The Mana World; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id$ */ #ifndef _TMW_TOKENCOLLECTOR_HPP #define _TMW_TOKENCOLLECTOR_HPP #include #include #include #include "utils/logger.h" #define NB_ACTIONS_BETWEEN_OUTDATED_CHECKS 100 /** * Because the timeStamp is not updated for every new list item and the * removeOutdated function is not called on regular intervals, a list item * might persist significantly longer then this. */ #define TIMEOUT 10 // in seconds /** * A structure used for the list items, * prefixed with 'TC_' to avoid any conflicts. */ template struct TC_ListItem { /** The magic_token aka passToken */ std::string token; /** The actual data, which will be sent back when matched */ T payload; /** * The amount of seconds since TokenCollector was running, * at the last outdated check. */ time_t timeStamp; }; // Prototype /** * \brief A class for storing and matching magic_tokens * * T is used for the class of the owner, U is the client payload, * V is the connect payload * * The owning class must implement as it's member functions; * - destroyPendingClient(U clientPayload), * - destroyPendingConnect(V connectPayload) and * - tokenMatched(U clientPayload, V connectPayload), * in a way that TokenCollector can reach them * (public, or declare TokenCollector a friend class). */ template class TokenCollector { public: /** * Constructor. */ TokenCollector(T * owner); /** * Destructor. */ ~TokenCollector(); /** * \brief Adds a pending client * * Searches the pending connects for a match. * Calls mOwner->tokenMatched when a match is found. * Inserts in mPendingClients if no match is found. */ void addPendingClient(const std::string & token, U clientPayload); /** * \brief Removes a pending client from the list * * Searches the pending client for the payload. */ void deletePendingClient(U clientPayload); /** * \brief Adds a pending connect * * Searches the pending clients for a match. * Calls mOwner->tokenMatched when a match is found. * Inserts in mPendingConnects if no match is found. */ void addPendingConnect(const std::string & token, V connectPayload); private: /** * \brief A simple list of all pending clients * * On a well setup system, this list is mostly empty. * The servers send the magic_token to the other server, before they * send it to the client. In a well setup system, the route * server1 -> server2 is faster then server1 -> client -> server2. * * A std::list is used because the basic work cycle for one client * (search PendingClients, insert PendingConnects, search * PendingConnects, remove pendingConnect) will run in (allmost) * constant time, when the size of PendingClients is small. * This can not be said for a map or sorted vector. * * The list items are pointers to void, because a list can not be * instantiationised with an incomplete type. */ std::list< void* > mPendingClients; /** * \brief A simple list of all pending clients. * * See mPendingClients for details. */ std::list< void* > mPendingConnects; /** * \brief The number of actions since the last time that the lists * where checked for outdated pendingClients or * pendingConnects. */ int mNumberOfActions; /** * \brief The time that TokenCollector was created, used for keeping * the variable times low numbers. */ time_t mTimeStart; /** * \brief The number of seconds between the creation of TokenCollector * and the last removeOutdated. */ time_t mTimeNow; /** * \brief Pointer to the owner of this TokenCollector object. */ T * mOwner; /** * \brief Removes outdated entries. */ void removeOutdated(); }; // Implementation template TokenCollector:: TokenCollector(T * owner): mNumberOfActions(0), mTimeStart(time(NULL)), mTimeNow(0), mOwner(owner) { } template TokenCollector:: ~TokenCollector() { if (mPendingClients.size()) LOG_INFO("TokenCollector deleted with " << mPendingClients.size() << " clients still pending."); if (mPendingConnects.size()) LOG_INFO("TokenCollector deleted with " << mPendingConnects.size() << " connects still pending."); } template void TokenCollector:: addPendingClient(const std::string & token, U clientPayload) { // Find could also be used for a list, but because new items are // inserted at the top, we want to start looking there. for (std::list< void* >::iterator it = mPendingConnects.begin(), it_end = mPendingConnects.end(); it != it_end; it++) { // Because the pointer to the listItem was stored as a pointer to // void, we have to cast it back to a pointer to a listItem. // Examples: it = void**; *it = void*; // ((TC_ListItem< V >*)*it) = TC_ListItem< V >*; // --------------------------------------------- if (((TC_ListItem< V >*)*it)->token == token) // Found a match { TC_ListItem< V >* tempConnect = (TC_ListItem< V >*)*it; mPendingConnects.erase(it); mOwner->tokenMatched(clientPayload, tempConnect->payload); delete tempConnect; return; // Done } } TC_ListItem< U >* listItem = new TC_ListItem< U >; listItem->token = token; listItem->payload = clientPayload; listItem->timeStamp = mTimeNow; mPendingClients.push_front((void*)listItem); if (!(++mNumberOfActions < NB_ACTIONS_BETWEEN_OUTDATED_CHECKS)) removeOutdated(); } template void TokenCollector:: deletePendingClient(U clientPayload) { // Find could also be used for a list, but because new items are // inserted at the top, we want to start looking there. for (std::list< void* >::iterator it = mPendingClients.begin(), it_end = mPendingClients.end(); it != it_end; it++) { // Because the pointer to the listItem was stored as a pointer to // void, we have to cast it back to a pointer to a listItem. // Examples: it = void**; *it = void*; // ((TC_ListItem< U >*)*it) = TC_ListItem< U >*; // --------------------------------------------- if (((TC_ListItem< U >*)*it)->payload == clientPayload) // Found a match { delete ((TC_ListItem< U >*)*it); mPendingConnects.erase(it); return; // Done } } } template void TokenCollector:: addPendingConnect(const std::string & token, V connectPayload) { // Find could also be used for a list, but because new items are // inserted at the top, we want to start looking there. for (std::list< void* >::iterator it = mPendingClients.begin(), it_end = mPendingClients.end(); it != it_end; it++) { // Because the pointer to the listItem was stored as a pointer to // void, we have to cast it back to a pointer to a listItem. // Examples: it = void**; *it = void*; // ((TC_ListItem< U >*)*it) = TC_ListItem< U >*; // --------------------------------------------- if (((TC_ListItem< U >*)*it)->token == token) // Found a match { TC_ListItem< U >* tempClient = (TC_ListItem< U >*)*it; mPendingClients.erase(it); mOwner->tokenMatched(tempClient->payload, connectPayload); delete tempClient; return; // Done } } TC_ListItem< V >* listItem = new TC_ListItem< V >; listItem->token = token; listItem->payload = connectPayload; listItem->timeStamp = mTimeNow; mPendingConnects.push_front((void*)listItem); if (!(++mNumberOfActions < NB_ACTIONS_BETWEEN_OUTDATED_CHECKS)) removeOutdated(); } template void TokenCollector:: removeOutdated() { time_t eraseTime = (mTimeNow > (time_t)TIMEOUT) ? (mTimeNow - (time_t)TIMEOUT) : (time_t) 0; // See addPendingClient for a comment about the casting. while ((mPendingClients.size()) && (((TC_ListItem< U >*)(mPendingClients.back()))->timeStamp < eraseTime)) { mOwner->deletePendingClient( ((TC_ListItem< U >*)(mPendingClients.back()))->payload); delete ((TC_ListItem< U >*)(mPendingClients.back())); mPendingClients.pop_back(); } while ((mPendingConnects.size()) && (((TC_ListItem< V >*)(mPendingConnects.back()))->timeStamp < eraseTime)) { mOwner->deletePendingConnect( ((TC_ListItem< V >*)(mPendingConnects.back()))->payload); delete ((TC_ListItem< U >*)(mPendingConnects.back())); mPendingConnects.pop_back(); } /** * Change the timeStap after the check, else everything that was just * inserted might be thrown away. */ mTimeNow = time(NULL) - mTimeStart; mNumberOfActions = 0; // Reset the counter. } #endif // _TMW_TOKENCOLLECTOR_HPP