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:
749
indra/llcommon/aisyncclient.cpp
Normal file
749
indra/llcommon/aisyncclient.cpp
Normal file
@@ -0,0 +1,749 @@
|
|||||||
|
/**
|
||||||
|
* @file aisyncclient.cpp
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 13/12/2013
|
||||||
|
* - Initial version, written by Aleric Inglewood @ SL
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AISyncClient : the base class of a client object (LLMotion) that needs to be informed
|
||||||
|
* of the state of other such objects and/or to poll a server object about the state of
|
||||||
|
* other such objects, in order to be and stay synchronized with those other objects.
|
||||||
|
* In the case of an LLMotion (animation), all clients would be started and stopped at
|
||||||
|
* the same time, as long as they are part of the same synchronization group (AISyncServer).
|
||||||
|
*
|
||||||
|
* AISyncKey: object that determines what synchronization group a client belongs to.
|
||||||
|
* When a new client is created, a new AISyncKey is created too, using information from
|
||||||
|
* the client, the current frame number and the current frame time. If two AISyncKey
|
||||||
|
* compare equal, using operator==(AISyncKey const& key1, AISyncKey const& key2),
|
||||||
|
* then the clients that created them need to be synchronized.
|
||||||
|
*
|
||||||
|
* AISyncServer: object that represents a group of clients that need to be synchronized:
|
||||||
|
* it's a wrapper around a std::list<AISyncClientData> with pointers to all the clients
|
||||||
|
* that need to be synchronized. It also stores the AISyncKey of the first (oldest) client
|
||||||
|
* that was added. Clients with keys that compare equal to that will be added.
|
||||||
|
* If a client is added with a synckeytype_t that is larger then it always replaces
|
||||||
|
* the existing key however.
|
||||||
|
*
|
||||||
|
* AISyncServerMap: A wrapper around std::list<boost::intrusive_ptr<AISyncServer> >
|
||||||
|
* that stores pointers to all currently existing AISyncServer objects. New entries
|
||||||
|
* are added at the end, so the oldest key is at the front.
|
||||||
|
*
|
||||||
|
* AISyncServerMap list + - - - - - - + - - - - - - + ... The AISyncServerMap is
|
||||||
|
* | | | a list with refcounted
|
||||||
|
* V V V pointers to AISyncServers.
|
||||||
|
* AISyncClient +--> AISyncServer
|
||||||
|
* | <--- list key ---> AISyncKey Each AISyncServer is a
|
||||||
|
* DerivedClient . list of normal pointers
|
||||||
|
* . AISyncClients and one
|
||||||
|
* <--- . pointer to a AISyncKey.
|
||||||
|
* Each AISyncClient is the
|
||||||
|
* base class of a DerivedClient
|
||||||
|
* and pointers back to the
|
||||||
|
* server with a refcounted
|
||||||
|
* pointer.
|
||||||
|
*
|
||||||
|
* A new client is passed to the AISyncServerMap to be stored in a new or existing AISyncServer
|
||||||
|
* object, using the key that the client produces. A boost::intrusive_ptr<AISyncServer> member
|
||||||
|
* of the client is set to point to this server.
|
||||||
|
*
|
||||||
|
* The lifetime of the server objects is determined by the intrusive_ptr objects that
|
||||||
|
* point to it: all the clients (which have an externally determined lifetime) and one
|
||||||
|
* pointer in the AISyncServerMap. However, regularly a check is done on all servers in
|
||||||
|
* the list to find expired servers: objects with keys older than two frames and older
|
||||||
|
* than 0.1 seconds; if such a server is found and it has zero or one client, then the
|
||||||
|
* client is unregistered and the pointer (and thus the server) removed from the
|
||||||
|
* AISyncServerMap. If it has two or more clients then the entry is kept until both
|
||||||
|
* clients are removed, which therefore can only be detected in intrusive_ptr_release
|
||||||
|
* which only has access to the server object. The server then is removed from the list
|
||||||
|
* by searching through it for the pointer to the server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sys.h"
|
||||||
|
#include "aisyncclient.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
bool operator==(AISyncKey const& key1, AISyncKey const& key2)
|
||||||
|
{
|
||||||
|
// Test if these keys match based on time.
|
||||||
|
if (std::abs((S32)(key1.mStartFrameCount - key2.mStartFrameCount)) > 1 &&
|
||||||
|
std::abs(key1.mFrameTimer.getStartTime() - key2.mFrameTimer.getStartTime()) >= AISyncKey::sExpirationTime)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Time matches, let the derived classes determine if they also match otherwise.
|
||||||
|
return key1.equals(key2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CWDEBUG
|
||||||
|
struct SyncEventSet {
|
||||||
|
synceventset_t mBits;
|
||||||
|
SyncEventSet(synceventset_t bits) : mBits(bits) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, SyncEventSet const& ses)
|
||||||
|
{
|
||||||
|
for (int b = sizeof(ses.mBits) * 8 - 1; b >= 0; --b)
|
||||||
|
{
|
||||||
|
int m = 1 << b;
|
||||||
|
os << ((ses.mBits & m) ? '1' : '0');
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_clients(AISyncServer const* server, AISyncServer::client_list_t const& client_list)
|
||||||
|
{
|
||||||
|
Dout(dc::notice, "Clients of server " << server << ": ");
|
||||||
|
for (AISyncServer::client_list_t::const_iterator iter = client_list.begin(); iter != client_list.end(); ++ iter)
|
||||||
|
{
|
||||||
|
llassert(iter->mClientPtr->mReadyEvents == iter->mReadyEvents);
|
||||||
|
Dout(dc::notice, "-> " << iter->mClientPtr << " : " << SyncEventSet(iter->mReadyEvents));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void AISyncServerMap::register_client(AISyncClient* client)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_SYNCOUTPUT
|
||||||
|
DoutEntering(dc::notice, "AISyncServerMap::register_client(" << client << ")");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// client must always be a new client that has to be stored somewhere.
|
||||||
|
llassert(client->server() == NULL);
|
||||||
|
// Obviously the client can't be ready for anything when it isn't registered yet.
|
||||||
|
llassert(!client->mReadyEvents);
|
||||||
|
|
||||||
|
// First we need its sync key.
|
||||||
|
AISyncKey* new_key = client->createSyncKey();
|
||||||
|
|
||||||
|
// Find if a server with this key already exists.
|
||||||
|
AISyncServer* server = NULL;
|
||||||
|
for (server_list_t::iterator iter = mServers.begin(); iter != mServers.end();)
|
||||||
|
{
|
||||||
|
boost::intrusive_ptr<AISyncServer>& server_ptr = *iter++; // Immediately increment iter because the call to unregister_last_client will erase it.
|
||||||
|
AISyncKey const& server_key(server_ptr->key());
|
||||||
|
if (server_key.is_older_than(*new_key)) // This means that the server key is expired: a new key will never match.
|
||||||
|
{
|
||||||
|
if (server_ptr->never_synced()) // This means that it contains one or zero clients and will never contain more.
|
||||||
|
{
|
||||||
|
// Get rid of this server.
|
||||||
|
server_ptr->unregister_last_client(); // This will cause the server to be deleted, and erased from mServers.
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*new_key == server_key)
|
||||||
|
{
|
||||||
|
server = server_ptr.get();
|
||||||
|
// mServers stores new servers in strict order of the creation time of the keys,
|
||||||
|
// so once we find a server with a key that is equal, none of the remaining servers
|
||||||
|
// will have expired if they were never synced and we're done with the loop.
|
||||||
|
// Servers that synced might have been added later, but we don't unregister
|
||||||
|
// clients from those anyway because their sync partner might still show up.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server)
|
||||||
|
{
|
||||||
|
// A server already exists.
|
||||||
|
// Keep the oldest key, unless this new key has a synckeytype_t that is larger!
|
||||||
|
if (new_key->getkeytype() > server->key().getkeytype())
|
||||||
|
{
|
||||||
|
server->swapkey(new_key);
|
||||||
|
}
|
||||||
|
delete new_key;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create a new server for this client. Transfers the ownership of the key allocation to the server.
|
||||||
|
server = new AISyncServer(new_key);
|
||||||
|
// Add it to mServers, before the last server that is younger then the new key.
|
||||||
|
server_list_t::iterator where = mServers.end(); // Insert the new server before 'where',
|
||||||
|
#if 0 // This is probably not necessary.
|
||||||
|
server_list_t::iterator new_where = where;
|
||||||
|
while (where != mServers.begin()) // unless there exists a server before that
|
||||||
|
{
|
||||||
|
--new_where;
|
||||||
|
if (new_key->getCreationTime() > (*new_where)->key().getCreationTime()) // and the new key is not younger then that,
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
where = new_where; // then insert it before that element (etc).
|
||||||
|
}
|
||||||
|
#elif defined(SHOW_ASSERT)
|
||||||
|
server_list_t::iterator new_where = where;
|
||||||
|
llassert(where == mServers.begin() || new_key->getCreationTime() >= (*--new_where)->key().getCreationTime());
|
||||||
|
#endif
|
||||||
|
// This method causes a single call to intrusive_ptr_add_ref and none to intrusive_ptr_release.
|
||||||
|
server_ptr_t server_ptr = server;
|
||||||
|
mServers.insert(where, server_ptr_t())->swap(server_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the client to the server.
|
||||||
|
server->add(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
void AISyncServer::sanity_check(void) const
|
||||||
|
{
|
||||||
|
synceventset_t ready_events = (synceventset_t)-1; // All clients are ready.
|
||||||
|
client_list_t::const_iterator client_iter = mClients.begin();
|
||||||
|
while (client_iter != mClients.end())
|
||||||
|
{
|
||||||
|
ready_events &= client_iter->mReadyEvents;
|
||||||
|
++client_iter;
|
||||||
|
}
|
||||||
|
synceventset_t pending_events = 0; // At least one client is ready.
|
||||||
|
client_iter = mClients.begin();
|
||||||
|
while (client_iter != mClients.end())
|
||||||
|
{
|
||||||
|
pending_events |= client_iter->mReadyEvents;
|
||||||
|
++client_iter;
|
||||||
|
}
|
||||||
|
llassert(ready_events == mReadyEvents);
|
||||||
|
llassert(pending_events == mPendingEvents);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void AISyncServer::add(AISyncClient* client)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_SYNCOUTPUT
|
||||||
|
DoutEntering(dc::notice, "AISyncServer::add(" << client << "), with this = " << this);
|
||||||
|
print_clients(this, getClients());
|
||||||
|
#endif
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
sanity_check();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The client can't already be ready when it isn't even added to a server yet.
|
||||||
|
llassert(!client->mReadyEvents);
|
||||||
|
synceventset_t old_ready_events = mReadyEvents;
|
||||||
|
// A new client is not ready for anything.
|
||||||
|
mReadyEvents = 0;
|
||||||
|
// Set mSynchronized if after adding client we'll have more than 1 client (that prevents the
|
||||||
|
// server from being deleted unless all clients are actually destructed or explicitly unregistered).
|
||||||
|
if (!mSynchronized && mClients.size() > 0)
|
||||||
|
{
|
||||||
|
mSynchronized = true;
|
||||||
|
}
|
||||||
|
// Trigger the existing clients to be not-ready anymore (if we were before).
|
||||||
|
trigger(old_ready_events);
|
||||||
|
// Only then add the new client (so that it didn't get not-ready trigger).
|
||||||
|
mClients.push_back(client);
|
||||||
|
client->mServer = this;
|
||||||
|
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
sanity_check();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void AISyncServer::remove(AISyncClient* client)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_SYNCOUTPUT
|
||||||
|
DoutEntering(dc::notice, "AISyncServer::remove(" << client << "), with this = " << this);
|
||||||
|
print_clients(this, getClients());
|
||||||
|
#endif
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
sanity_check();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// A client may only be unregistered after it was marked not-ready for all events.
|
||||||
|
llassert(!client->mReadyEvents);
|
||||||
|
client_list_t::iterator client_iter = mClients.begin();
|
||||||
|
synceventset_t remaining_ready_events = (synceventset_t)-1; // All clients are ready.
|
||||||
|
synceventset_t remaining_pending_events = 0; // At least one client is ready (waiting for the other clients thus).
|
||||||
|
client_list_t::iterator found_client = mClients.end();
|
||||||
|
while (client_iter != mClients.end())
|
||||||
|
{
|
||||||
|
if (client_iter->mClientPtr == client)
|
||||||
|
{
|
||||||
|
found_client = client_iter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
remaining_ready_events &= client_iter->mReadyEvents;
|
||||||
|
remaining_pending_events |= client_iter->mReadyEvents;
|
||||||
|
}
|
||||||
|
++client_iter;
|
||||||
|
}
|
||||||
|
llassert(found_client != mClients.end());
|
||||||
|
// This must be the same as client->mReadyEvents.
|
||||||
|
llassert(!found_client->mReadyEvents);
|
||||||
|
mClients.erase(found_client);
|
||||||
|
client->mServer.reset();
|
||||||
|
synceventset_t old_ready_events = mReadyEvents;
|
||||||
|
mReadyEvents = remaining_ready_events;
|
||||||
|
// Since client->mReadyEvents is zero, this should be the same.
|
||||||
|
llassert(mPendingEvents == remaining_pending_events);
|
||||||
|
mPendingEvents = remaining_pending_events;
|
||||||
|
trigger(old_ready_events);
|
||||||
|
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
sanity_check();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void AISyncServer::unregister_last_client(void)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_SYNCOUTPUT
|
||||||
|
DoutEntering(dc::notice, "AISyncServer::unregister_last_client()");
|
||||||
|
print_clients(this, getClients());
|
||||||
|
#endif
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
sanity_check();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This function may only be called for servers with exactly one client that was never (potentially) synchronized.
|
||||||
|
llassert(!mSynchronized && mClients.size() == 1);
|
||||||
|
AISyncClient* client = mClients.begin()->mClientPtr;
|
||||||
|
mClients.clear();
|
||||||
|
client->mServer.reset();
|
||||||
|
llassert(mReadyEvents == client->mReadyEvents);
|
||||||
|
llassert(mPendingEvents == mReadyEvents);
|
||||||
|
// No need to update mReadyEvents/mPendingEvents because the server is going to be deleted,
|
||||||
|
// but inform the client that is no longer has a server.
|
||||||
|
client->deregistered();
|
||||||
|
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
sanity_check();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
synceventset_t AISyncServer::events_with_all_clients_ready(void) const
|
||||||
|
{
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
sanity_check();
|
||||||
|
#endif
|
||||||
|
return mReadyEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
synceventset_t AISyncServer::events_with_at_least_one_client_ready(void) const
|
||||||
|
{
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
sanity_check();
|
||||||
|
#endif
|
||||||
|
return mPendingEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AISyncServer::trigger(synceventset_t old_ready_events)
|
||||||
|
{
|
||||||
|
// If event 1 changed, informat all clients about it.
|
||||||
|
if (((old_ready_events ^ mReadyEvents) & 1))
|
||||||
|
{
|
||||||
|
for (client_list_t::iterator client_iter = mClients.begin(); client_iter != mClients.end(); ++client_iter)
|
||||||
|
{
|
||||||
|
if ((mReadyEvents & 1))
|
||||||
|
{
|
||||||
|
client_iter->mClientPtr->event1_ready();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
client_iter->mClientPtr->event1_not_ready();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AISyncServer::ready(synceventset_t events, synceventset_t yesno, AISyncClient* client)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_SYNCOUTPUT
|
||||||
|
DoutEntering(dc::notice, "AISyncServer::ready(" << SyncEventSet(events) << ", " << SyncEventSet(yesno) << ", " << client << ")");
|
||||||
|
print_clients(this, getClients());
|
||||||
|
#endif
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
sanity_check();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
synceventset_t added_events = events & yesno;
|
||||||
|
synceventset_t removed_events = events & ~yesno;
|
||||||
|
// May not add events that are already ready.
|
||||||
|
llassert(!(client->mReadyEvents & added_events));
|
||||||
|
// Cannot remove events that weren't ready.
|
||||||
|
llassert((client->mReadyEvents & removed_events) == removed_events);
|
||||||
|
|
||||||
|
// Run over all clients to find the client and calculate the current state.
|
||||||
|
synceventset_t remaining_ready_events = (synceventset_t)-1; // All clients are ready.
|
||||||
|
synceventset_t remaining_pending_events = 0; // At least one client is ready (waiting for the other clients thus).
|
||||||
|
client_list_t::iterator found_client = mClients.end();
|
||||||
|
for (client_list_t::iterator client_iter = mClients.begin(); client_iter != mClients.end(); ++client_iter)
|
||||||
|
{
|
||||||
|
if (client_iter->mClientPtr == client)
|
||||||
|
{
|
||||||
|
found_client = client_iter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
remaining_ready_events &= client_iter->mReadyEvents;
|
||||||
|
remaining_pending_events |= client_iter->mReadyEvents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
llassert(mReadyEvents == (remaining_ready_events & found_client->mReadyEvents));
|
||||||
|
llassert(mPendingEvents == (remaining_pending_events | found_client->mReadyEvents));
|
||||||
|
|
||||||
|
found_client->mReadyEvents &= ~removed_events;
|
||||||
|
found_client->mReadyEvents |= added_events;
|
||||||
|
#ifdef SHOW_ASSERT
|
||||||
|
client->mReadyEvents = found_client->mReadyEvents;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
synceventset_t old_ready_events = mReadyEvents;
|
||||||
|
|
||||||
|
mReadyEvents = remaining_ready_events & found_client->mReadyEvents;
|
||||||
|
mPendingEvents = remaining_pending_events | found_client->mReadyEvents;
|
||||||
|
|
||||||
|
trigger(old_ready_events);
|
||||||
|
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
sanity_check();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void intrusive_ptr_add_ref(AISyncServer* server)
|
||||||
|
{
|
||||||
|
server->mRefCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void intrusive_ptr_release(AISyncServer* server)
|
||||||
|
{
|
||||||
|
llassert(server->mRefCount > 0);
|
||||||
|
server->mRefCount--;
|
||||||
|
if (server->mRefCount == 0)
|
||||||
|
{
|
||||||
|
delete server;
|
||||||
|
}
|
||||||
|
else if (server->mRefCount == 1)
|
||||||
|
{
|
||||||
|
// If the the last pointer to this server is in the the AISyncServerMap, then delete that too.
|
||||||
|
AISyncServerMap::instance().remove_server(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AISyncServerMap::remove_server(AISyncServer* server)
|
||||||
|
{
|
||||||
|
for (server_list_t::iterator iter = mServers.begin(); iter != mServers.end(); ++iter)
|
||||||
|
{
|
||||||
|
if (server == iter->get())
|
||||||
|
{
|
||||||
|
mServers.erase(iter); // This causes server to be deleted too.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The server must be found: this function is only called from intrusive_ptr_release for servers
|
||||||
|
// with just a single server_ptr_t left, which must be the one in mServers (otherwise it wasn't
|
||||||
|
// even registered properly!)
|
||||||
|
llassert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// SYNC_TESTSUITE
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
#ifdef SYNC_TESTSUITE
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <boost/io/ios_state.hpp>
|
||||||
|
|
||||||
|
//static
|
||||||
|
U32 LLFrameTimer::sFrameCount;
|
||||||
|
|
||||||
|
double innerloop_count = 0;
|
||||||
|
|
||||||
|
double LLFrameTimer::getCurrentTime()
|
||||||
|
{
|
||||||
|
return innerloop_count * 0.001;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<synckeytype_t synckeytype>
|
||||||
|
class TestsuiteKey : public AISyncKey
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
int mIndex;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TestsuiteKey(int index) : mIndex(index) { }
|
||||||
|
|
||||||
|
int getIndex(void) const { return mIndex; }
|
||||||
|
|
||||||
|
// Virtual functions of AISyncKey.
|
||||||
|
public:
|
||||||
|
/*virtual*/ synckeytype_t getkeytype(void) const
|
||||||
|
{
|
||||||
|
// Return a unique identifier for this class, where the low 8 bits represent the syncgroup.
|
||||||
|
return synckeytype;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*virtual*/ bool equals(AISyncKey const& key) const
|
||||||
|
{
|
||||||
|
synckeytype_t const theotherkey = (synckeytype_t)(synckeytype ^ 0x100);
|
||||||
|
switch (key.getkeytype())
|
||||||
|
{
|
||||||
|
case synckeytype:
|
||||||
|
{
|
||||||
|
// The other key is of the same type.
|
||||||
|
TestsuiteKey<synckeytype> const& test_key = static_cast<TestsuiteKey<synckeytype> const&>(key);
|
||||||
|
return (mIndex & 1) == (test_key.mIndex & 1);
|
||||||
|
}
|
||||||
|
case theotherkey:
|
||||||
|
{
|
||||||
|
TestsuiteKey<theotherkey> const& test_key = static_cast<TestsuiteKey<theotherkey> const&>(key);
|
||||||
|
return (mIndex & 2) == (test_key.getIndex() & 2);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// The keys must be in the same syncgroup.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<synckeytype_t synckeytype>
|
||||||
|
class TestsuiteClient : public AISyncClient
|
||||||
|
{
|
||||||
|
// AISyncClient events.
|
||||||
|
protected:
|
||||||
|
/*virtual*/ AISyncKey* createSyncKey(void) const
|
||||||
|
{
|
||||||
|
return new TestsuiteKey<synckeytype>(mIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int mIndex;
|
||||||
|
bool mRequestedRegistered;
|
||||||
|
synceventset_t mRequestedReady;
|
||||||
|
bool mActualReady1;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TestsuiteClient() : mIndex(-1), mRequestedRegistered(false), mRequestedReady(0), mActualReady1(false) { }
|
||||||
|
~TestsuiteClient() { if (is_registered()) this->ready(mRequestedReady, (synceventset_t)0); }
|
||||||
|
|
||||||
|
void setIndex(int index) { mIndex = index; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*virtual*/ void event1_ready(void)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_SYNCOUTPUT
|
||||||
|
Dout(dc::notice, "Calling TestsuiteClient<" << synckeytype << ">::event1_ready() (mIndex = " << mIndex << ") of client " << this);
|
||||||
|
#endif
|
||||||
|
llassert(!mActualReady1);
|
||||||
|
mActualReady1 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*virtual*/ void event1_not_ready(void)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_SYNCOUTPUT
|
||||||
|
Dout(dc::notice, "Calling TestsuiteClient<" << synckeytype << ">::event1_not_ready() (mIndex = " << mIndex << ") of client " << this);
|
||||||
|
#endif
|
||||||
|
llassert(mActualReady1);
|
||||||
|
mActualReady1 = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called when the server expired and we're the only client on it.
|
||||||
|
/*virtual*/ void deregistered(void)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_SYNCOUTPUT
|
||||||
|
DoutEntering(dc::notice, "TestsuiteClient<" << synckeytype << ">::deregistered(), with this = " << this);
|
||||||
|
#endif
|
||||||
|
mRequestedRegistered = false;
|
||||||
|
mRequestedReady = 0;
|
||||||
|
mActualReady1 = false;
|
||||||
|
this->mReadyEvents = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool is_registered(void) const { return this->server(); }
|
||||||
|
|
||||||
|
public:
|
||||||
|
void change_state(unsigned long r);
|
||||||
|
bool getRequestedRegistered(void) const { return mRequestedRegistered; }
|
||||||
|
synceventset_t getRequestedReady(void) const { return mRequestedReady; }
|
||||||
|
};
|
||||||
|
|
||||||
|
TestsuiteClient<synckeytype_test1a>* client1ap;
|
||||||
|
TestsuiteClient<synckeytype_test1b>* client1bp;
|
||||||
|
TestsuiteClient<synckeytype_test2a>* client2ap;
|
||||||
|
TestsuiteClient<synckeytype_test2b>* client2bp;
|
||||||
|
|
||||||
|
int const number_of_clients_per_syncgroup = 8;
|
||||||
|
|
||||||
|
template<synckeytype_t synckeytype>
|
||||||
|
void TestsuiteClient<synckeytype>::change_state(unsigned long r)
|
||||||
|
{
|
||||||
|
bool change_registered = r & 1;
|
||||||
|
r >>= 1;
|
||||||
|
synceventset_t toggle_events = r & 15;
|
||||||
|
r >>= 4;
|
||||||
|
if (change_registered)
|
||||||
|
{
|
||||||
|
if (mRequestedRegistered && !mRequestedReady)
|
||||||
|
{
|
||||||
|
mRequestedRegistered = false;
|
||||||
|
this->unregister_client();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (toggle_events)
|
||||||
|
{
|
||||||
|
mRequestedReady ^= toggle_events;
|
||||||
|
mRequestedRegistered = true;
|
||||||
|
this->ready(toggle_events, mRequestedReady & toggle_events);
|
||||||
|
}
|
||||||
|
llassert(mRequestedRegistered == is_registered());
|
||||||
|
AISyncServer* server = this->server();
|
||||||
|
if (mRequestedRegistered)
|
||||||
|
{
|
||||||
|
synceventset_t all_ready = synceventset_t(-1);
|
||||||
|
synceventset_t any_ready = 0;
|
||||||
|
int nr = 0;
|
||||||
|
for (int cl = 0; cl < number_of_clients_per_syncgroup; ++cl)
|
||||||
|
{
|
||||||
|
switch ((synckeytype & 0xff))
|
||||||
|
{
|
||||||
|
case syncgroup_test1:
|
||||||
|
{
|
||||||
|
if (client1ap[cl].server() == server)
|
||||||
|
{
|
||||||
|
if (client1ap[cl].getRequestedRegistered())
|
||||||
|
{
|
||||||
|
++nr;
|
||||||
|
all_ready &= client1ap[cl].getRequestedReady();
|
||||||
|
any_ready |= client1ap[cl].getRequestedReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client1bp[cl].server() == server)
|
||||||
|
{
|
||||||
|
if (client1bp[cl].getRequestedRegistered())
|
||||||
|
{
|
||||||
|
++nr;
|
||||||
|
all_ready &= client1bp[cl].getRequestedReady();
|
||||||
|
any_ready |= client1bp[cl].getRequestedReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case syncgroup_test2:
|
||||||
|
{
|
||||||
|
if (client2ap[cl].server() == server)
|
||||||
|
{
|
||||||
|
if (client2ap[cl].getRequestedRegistered())
|
||||||
|
{
|
||||||
|
++nr;
|
||||||
|
all_ready &= client2ap[cl].getRequestedReady();
|
||||||
|
any_ready |= client2ap[cl].getRequestedReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client2bp[cl].server() == server)
|
||||||
|
{
|
||||||
|
if (client2bp[cl].getRequestedRegistered())
|
||||||
|
{
|
||||||
|
++nr;
|
||||||
|
all_ready &= client2bp[cl].getRequestedReady();
|
||||||
|
any_ready |= client2bp[cl].getRequestedReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
llassert(nr == server->getClients().size());
|
||||||
|
llassert(!!(all_ready & 1) == mActualReady1);
|
||||||
|
llassert(this->server()->events_with_all_clients_ready() == all_ready);
|
||||||
|
llassert(this->server()->events_with_at_least_one_client_ready() == any_ready);
|
||||||
|
llassert(nr == 0 || (any_ready & all_ready) == all_ready);
|
||||||
|
}
|
||||||
|
llassert(mRequestedReady == this->mReadyEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Debug(libcw_do.on());
|
||||||
|
Debug(dc::notice.on());
|
||||||
|
Debug(libcw_do.set_ostream(&std::cout));
|
||||||
|
Debug(list_channels_on(libcw_do));
|
||||||
|
|
||||||
|
unsigned short seed16v[3] = { 0x1234, 0xfedc, 0x7091 };
|
||||||
|
|
||||||
|
for (int k = 0;; ++k)
|
||||||
|
{
|
||||||
|
std::cout << "Loop: " << k << "; SEED: " << std::hex << seed16v[0] << ", " << seed16v[1] << ", " << seed16v[2] << std::dec << std::endl;
|
||||||
|
++LLFrameTimer::sFrameCount;
|
||||||
|
|
||||||
|
seed48(seed16v);
|
||||||
|
seed16v[0] = lrand48() & 0xffff;
|
||||||
|
seed16v[1] = lrand48() & 0xffff;
|
||||||
|
seed16v[2] = lrand48() & 0xffff;
|
||||||
|
|
||||||
|
TestsuiteClient<synckeytype_test1a> client1a[number_of_clients_per_syncgroup];
|
||||||
|
TestsuiteClient<synckeytype_test1b> client1b[number_of_clients_per_syncgroup];
|
||||||
|
TestsuiteClient<synckeytype_test2a> client2a[number_of_clients_per_syncgroup];
|
||||||
|
TestsuiteClient<synckeytype_test2b> client2b[number_of_clients_per_syncgroup];
|
||||||
|
client1ap = client1a;
|
||||||
|
client1bp = client1b;
|
||||||
|
client2ap = client2a;
|
||||||
|
client2bp = client2b;
|
||||||
|
|
||||||
|
for (int i = 0; i < number_of_clients_per_syncgroup; ++i)
|
||||||
|
{
|
||||||
|
client1a[i].setIndex(i);
|
||||||
|
client1b[i].setIndex(i);
|
||||||
|
client2a[i].setIndex(i);
|
||||||
|
client2b[i].setIndex(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < 1000000; ++j)
|
||||||
|
{
|
||||||
|
innerloop_count += 1;
|
||||||
|
|
||||||
|
#ifdef DEBUG_SYNCOUTPUT
|
||||||
|
Dout(dc::notice, "Innerloop: " << j);
|
||||||
|
#endif
|
||||||
|
unsigned long r = lrand48();
|
||||||
|
synckeytype_t keytype = (r & 1) ? ((r & 2) ? synckeytype_test1a : synckeytype_test1b) : ((r & 2) ? synckeytype_test2a : synckeytype_test2b);
|
||||||
|
r >>= 2;
|
||||||
|
int cl = (r & 255) % number_of_clients_per_syncgroup;
|
||||||
|
r >>= 8;
|
||||||
|
switch (keytype)
|
||||||
|
{
|
||||||
|
case synckeytype_test1a:
|
||||||
|
client1a[cl].change_state(r);
|
||||||
|
break;
|
||||||
|
case synckeytype_test1b:
|
||||||
|
client1b[cl].change_state(r);
|
||||||
|
break;
|
||||||
|
case synckeytype_test2a:
|
||||||
|
client2a[cl].change_state(r);
|
||||||
|
break;
|
||||||
|
case synckeytype_test2b:
|
||||||
|
client2b[cl].change_state(r);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
296
indra/llcommon/aisyncclient.h
Normal file
296
indra/llcommon/aisyncclient.h
Normal 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
|
||||||
Reference in New Issue
Block a user