summaryrefslogblamecommitdiff
path: root/src/utils/tokencollector.hpp
blob: b94643bef529d35e601b8230599ad90b12efe926 (plain) (tree)






















































































































































                                                                               





                                                                            





                                                                              

















































                                                                              
                                                                   
                                       

                                                                      





















































                                                                                
                                                                  
                                      

                                                                      


















































                                                                               
/*
 *  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 <string>
#include <list>
#include <time.h>

#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 <typename T>
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 T, class U, class V>
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 <class T, class U, class V>
TokenCollector<T, U, V>::
TokenCollector(T * owner):
    mNumberOfActions(0), mTimeStart(time(NULL)), mTimeNow(0), mOwner(owner)
{
}

template <class T, class U, class V>
TokenCollector<T, U, V>::
~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 <class T, class U, class V>
void TokenCollector<T, U, V>::
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 <class T, class U, class V>
void TokenCollector<T, U, V>::
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 <class T, class U, class V>
void TokenCollector<T, U, V>::
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 <class T, class U, class V>
void TokenCollector<T, U, V>::
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