Version 2 of AISyncClient et al.

A tool to synchronize objects:
Objects derived from AISyncClient can signal that they are
'ready' or 'not ready' for up to 32 events (using a bitmask)
at a time. Clients that are created at roughly the same time
as other clients, and which return the same 'key' (a virtual
function returning an AISyncKey object) will be grouped together
and receive events (by means of virtual functions being called)
to notify them of all clients being ready or not for one of the
events (the least significant bit)). The other events can be polled.

This new version does away with all the templates and explicitly
remembers what events each client is ready for instead of just
updating a counter of the number of clients. This was necessary
because a client is removed then the server needs to know if it
was ready or not when it has to be able to update those counters.
This time I chose to just run over all stored clients and AND
and OR the per-client-ready-masks because 1) that information
is available now and 2) the lists will normally contain only
one or two clients, so it's fast enough.
The new version also allows for real key comparison (and derived
keys) instead of just using "hash" value that is compared.
This commit is contained in:
Aleric Inglewood
2013-12-21 19:18:52 +01:00
parent 66aaa9cf80
commit 1bcb6ad20d
2 changed files with 1045 additions and 0 deletions

View File

@@ -0,0 +1,296 @@
/**
* @file aisyncclient.h
* @brief Declaration of AISyncClient.
*
* Copyright (c) 2013, Aleric Inglewood.
*
* This program 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
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution.
*
* CHANGELOG
* and additional copyright holders.
*
* 12/12/2013
* Initial version, written by Aleric Inglewood @ SL
*/
#ifndef AI_SYNC_CLIENT_H
#define AI_SYNC_CLIENT_H
#ifdef SYNC_TESTSUITE
/*
* To compile the testsuite, run:
*
* cd indra/llcommon
* g++ -O3 -DCWDEBUG -DSYNC_TESTSUITE -I. -I../cwdebug aisyncclient.cpp -lcwd
*/
#include <stdint.h>
#include <cassert>
typedef uint32_t U32;
typedef int32_t S32;
typedef uint64_t U64;
typedef float F32;
typedef double F64;
#define LL_COMMON_API
#define SHOW_ASSERT
#define ASSERT_ONLY_COMMA(...) , __VA_ARGS__
#define llassert assert
struct LLFrameTimer
{
double mStartTime;
double mExpiry;
static double getCurrentTime(void);
static U32 sFrameCount;
static U32 getFrameCount() { return sFrameCount; }
F64 getStartTime() const { return mStartTime; }
void reset(double expiration) { mStartTime = getCurrentTime(); mExpiry = mStartTime + expiration; }
bool hasExpired(void) const { return getCurrentTime() > mExpiry; }
};
template<typename T>
struct LLSingleton
{
static T sInstance;
static T& instance(void) { return sInstance; }
};
template<typename T>
T LLSingleton<T>::sInstance;
#else // !SYNC_TESTSUITE
#include "llsingleton.h"
#include "llframetimer.h"
#endif
#include <list>
#include <boost/intrusive_ptr.hpp>
//---------------------------------------------------------------------------------------------------------------------
// Keys with a different syncgroup are never equal (so they are never synchronized).
enum syncgroups
{
#ifdef SYNC_TESTSUITE
syncgroup_test1,
syncgroup_test2,
#else
syncgroup_motions, // Syncgroup used for animations.
#endif
syncgroup_size
};
// Each key type must return a unique identifier that exists of its syncgroup (the least significant 8 bit) plus a few bit to make it unique (bit 9 and higher).
enum synckeytype_t
{
#ifdef SYNC_TESTSUITE
synckeytype_test1a = 0x000 + syncgroup_test1,
synckeytype_test1b = 0x100 + syncgroup_test1,
synckeytype_test2a = 0x000 + syncgroup_test2,
synckeytype_test2b = 0x100 + syncgroup_test2,
#else
synckeytype_motion = syncgroup_motions // There is currently only one key type in the syncgroup_motions group: AISyncKeyMotion.
#endif
};
typedef U32 synceventset_t; // A mask where each bit represents a ready state.
class LL_COMMON_API AISyncKey
{
private:
LLFrameTimer mFrameTimer; // This timer is started at the moment the sync key is created.
U32 mStartFrameCount; // The frame count at which the timer was started.
static F32 const sExpirationTime = 0.1; // In seconds.
public:
// Constructor.
AISyncKey(void) : mStartFrameCount(LLFrameTimer::getFrameCount())
{
mFrameTimer.reset(sExpirationTime);
}
// Destructor.
virtual ~AISyncKey() { }
// Return true if this key expired.
bool expired(void) const
{
// The key has expired when sExpirationTime seconds have elapsed AND at least two frames have passed.
return mFrameTimer.getFrameCount() > mStartFrameCount + 1 && mFrameTimer.hasExpired();
}
// Returns true if this object and key would not compare equal based on time because this object is too old.
bool is_older_than(AISyncKey const& key) const
{
return key.mStartFrameCount > mStartFrameCount + 1 && key.mFrameTimer.getStartTime() > mFrameTimer.getStartTime() + sExpirationTime;
}
// Return the creation time of this key (in number of seconds since application start).
F64 getCreationTime(void) const { return mFrameTimer.getStartTime(); }
// Returns true if the two keys match, meaning that they should be synchronized.
friend bool operator==(AISyncKey const& key1, AISyncKey const& key2);
// Returns an ID that uniquely identifies the derived type.
// Currently the only derived type is AISyncKeyMotion with ID synckeytype_motion.
virtual synckeytype_t getkeytype(void) const = 0;
// Returns true if the data in the derived objects match, meaning that they should be synchronized.
virtual bool equals(AISyncKey const& key) const = 0;
};
// Forward declaration.
class AISyncClient;
class AISyncServer;
LL_COMMON_API extern void intrusive_ptr_add_ref(AISyncServer* server);
LL_COMMON_API extern void intrusive_ptr_release(AISyncServer* server);
struct LL_COMMON_API AISyncClientData
{
AISyncClient* mClientPtr;
synceventset_t mReadyEvents;
AISyncClientData(AISyncClient* client) : mClientPtr(client), mReadyEvents(0) { }
};
class LL_COMMON_API AISyncServer
{
public:
typedef std::list<AISyncClientData> client_list_t;
private:
int mRefCount; // Number of boost::intrusive_ptr objects pointing to this object.
AISyncKey* mKey; // The key of the first client that was added.
client_list_t mClients; // A list with pointers to all registered clients.
bool mSynchronized; // Set when a server gets more than one client, and not reset anymore after that.
synceventset_t mReadyEvents; // 0xFFFFFFFF bitwise-AND-ed with all clients.
synceventset_t mPendingEvents; // The bitwise-OR of all clients.
public:
AISyncServer(AISyncKey* key) : mRefCount(0), mKey(key), mSynchronized(false), mReadyEvents((synceventset_t)-1), mPendingEvents(0) { }
~AISyncServer() { delete mKey; }
// Add a new client to this server.
void add(AISyncClient* client);
// Add a new client to this server.
void remove(AISyncClient* client);
// Return the key associated to this server (which is the key produced by the first client of the largest synckeytype_t that was added).
AISyncKey const& key(void) const { return *mKey; }
// Replace they key with another key (of larger synckeytype_t).
void swapkey(AISyncKey*& key_ptr) { AISyncKey* tmp = key_ptr; key_ptr = mKey; mKey = tmp; }
// Returns true if this server never had more than one client.
bool never_synced(void) const { return !mSynchronized; }
// Set readiness of all events at once.
bool ready(synceventset_t events, synceventset_t yesno, AISyncClient* client);
// Unregister the (only) client because it's own its own and will never need synchronization.
void unregister_last_client(void);
// Return the events that all clients for.
synceventset_t events_with_all_clients_ready(void) const;
// Return events that at least one client is ready for.
synceventset_t events_with_at_least_one_client_ready(void) const;
#ifdef SHOW_ASSERT
client_list_t const& getClients(void) const { return mClients; }
#endif
private:
// Call event1_ready() or event1_not_ready() on all clients if the least significant bit of mReadyEvents changed.
void trigger(synceventset_t old_ready_events);
#ifdef SYNC_TESTSUITE
void sanity_check(void) const;
#endif
private:
friend void intrusive_ptr_add_ref(AISyncServer* server);
friend void intrusive_ptr_release(AISyncServer* server);
};
class LL_COMMON_API AISyncServerMap : public LLSingleton<AISyncServerMap>
{
public:
typedef boost::intrusive_ptr<AISyncServer> server_ptr_t; // The type of a (stored) pointer to the server objects.
typedef std::list<server_ptr_t> server_list_t; // The type of the list with pointers to the server objects.
private:
server_list_t mServers; // A list with pointers to all server objects.
public:
// Find or create a server object that the client belongs to and store the client in it.
// If a new server is created, it is stored in mServers.
void register_client(AISyncClient* client);
private:
friend void intrusive_ptr_release(AISyncServer* server);
// Remove a server from the map, only called by intrusive_ptr_release when there is one pointer left;
// therefore, the server should not have any clients.
void remove_server(AISyncServer* server);
};
class LL_COMMON_API AISyncClient
{
private:
friend class AISyncServer;
boost::intrusive_ptr<AISyncServer> mServer; // The server this client was registered with, or NULL when unregistered.
public:
#ifdef SHOW_ASSERT
synceventset_t mReadyEvents;
AISyncClient(void) : mReadyEvents(0) { }
#endif
virtual ~AISyncClient() { }
virtual AISyncKey* createSyncKey(void) const = 0;
virtual void event1_ready(void) = 0;
virtual void event1_not_ready(void) = 0;
// Only client. Client was forcefully deregistered from expired server because it was the only client.
virtual void deregistered(void)
{
#ifdef SHOW_ASSERT
mReadyEvents = 0;
#endif
}
AISyncServer* server(void) const { return mServer.get(); }
// Add this client to a server with matching sync key. Optionally the server is first created.
void register_client(void) { AISyncServerMap::instance().register_client(this); }
// Remove this client from its server.
void unregister_client(void) { mServer->remove(this); }
// Call 'ready' when you are ready (or not) to get a call to start().
// Returns true if that call was made (immediately), otherwise it may happen later.
bool ready(synceventset_t events, synceventset_t yesno) // Set readiness of all events at once.
{
if (!mServer)
{
register_client();
}
return mServer->ready(events, yesno, this);
}
};
#endif