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.
750 lines
24 KiB
C++
750 lines
24 KiB
C++
/**
|
|
* @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
|