diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 31e4fa484..fd0e8208c 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -20,6 +20,7 @@ set(llcommon_SOURCE_FILES
aialert.cpp
aifile.cpp
aiframetimer.cpp
+ aisyncclient.cpp
aithreadid.cpp
imageids.cpp
indra_constants.cpp
@@ -112,6 +113,7 @@ set(llcommon_HEADER_FILES
aifile.h
aiframetimer.h
airecursive.h
+ aisyncclient.h
aithreadid.h
aithreadsafe.h
bitpack.h
diff --git a/indra/llcommon/aisyncclient.cpp b/indra/llcommon/aisyncclient.cpp
new file mode 100644
index 000000000..18ca33edc
--- /dev/null
+++ b/indra/llcommon/aisyncclient.cpp
@@ -0,0 +1,538 @@
+/**
+ * @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 .
+ *
+ * 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.
+ *
+ * 05/12/2013
+ * - Initial version, written by Aleric Inglewood @ SL
+ */
+
+#include "sys.h"
+#include "aisyncclient.h"
+#include
+#include "debug.h"
+
+typedef std::deque > servers_type;
+static servers_type servers[syncgroup_size];
+
+//static
+template
+void AISyncServer::register_client(AISyncClient_ServerPtr* client)
+{
+#ifdef DEBUG_SYNCOUTPUT
+ DoutEntering(dc::notice, "AISyncServer::register_client(" << client << ")");
+#endif
+
+ // Determine which server to use.
+ boost::intrusive_ptr server;
+ int expired = 0;
+ AISyncKey const sync_key(client->sync_key_hash());
+ for (servers_type::iterator server_iter = servers[syncgroup].begin(); server_iter != servers[syncgroup].end(); ++server_iter)
+ {
+ boost::intrusive_ptr& server_ptr = *server_iter;
+ if (server_ptr->mSyncKey.expired())
+ {
+ ++expired;
+ // If the server only contains a single client, then unregister it and put the server (back) in the server cache.
+ if (server_ptr->get_refcount() == 2)
+ {
+ server_ptr->unregister_last_client();
+ AISyncServer::dispose_server(server_ptr);
+ }
+ continue;
+ }
+ if (server_ptr->mSyncKey.matches(sync_key))
+ {
+ server = server_ptr;
+ break;
+ }
+ }
+ // Remove servers with expired keys.
+ if (expired)
+ {
+ if (expired == servers[syncgroup].size())
+ {
+ servers[syncgroup].clear();
+ }
+ else
+ {
+ servers[syncgroup].erase(servers[syncgroup].begin(), servers[syncgroup].begin() + expired);
+ }
+ }
+ if (!server)
+ {
+ AISyncServer::create_server(server, sync_key);
+ servers[syncgroup].push_back(server);
+ }
+
+ // Sanity check: the client should never already be registered.
+ llassert(!client->mSyncServer);
+ // Recover from assertion failure.
+ if (client->mSyncServer)
+ {
+ if (client->mSyncServer == server)
+ {
+ return;
+ }
+ client->mSyncServer->unregister_client(client, syncgroup);
+ register_client(client);
+ return;
+ }
+
+ // Obviously...
+ llassert(!client->mReadyEvents);
+
+ // Check if the current clients are all ready: adding a new one will cause the group to become not-ready.
+ bool all_old_clients_are_ready = server->mNrReady && !((server->mNrClients - server->mNrReady) & synccountmask1);
+ // Add new client to the group.
+ client->mSyncServer = server;
+ server->mNrClients += syncevents;
+ llassert((server->mNrClients & syncoverflowbits) == 0);
+ if (all_old_clients_are_ready)
+ {
+ server->trigger_not_ready(); // Tell all old clients that the group is not ready.
+ }
+ server->mClients.push_back(client); // Actually add the new client to the list.
+}
+
+void AISyncServer::unregister_client(AISyncClient_ServerPtr* client, syncgroups syncgroup)
+{
+#ifdef DEBUG_SYNCOUTPUT
+ DoutEntering(dc::notice, "unregister_client(" << client << ", " << syncgroup << "), with this = " << this);
+#endif
+
+ // The client must be registered with this server.
+ llassert(client->mSyncServer == this);
+ // A client may only be unregistered after it was marked not-ready for all events.
+ llassert(!client->mReadyEvents);
+ // Run over all registered clients.
+ for (client_list_type::iterator client_iter = mClients.begin(); client_iter != mClients.end(); ++client_iter)
+ {
+ // Found it?
+ if (*client_iter == client)
+ {
+ mClients.erase(client_iter);
+ // Are the remaining clients ready?
+ if (mNrReady && !((mNrClients - syncevents - mNrReady) & synccountmask1))
+ {
+ trigger_ready();
+ }
+ mNrClients -= syncevents;
+ llassert((mNrClients & syncoverflowbits) == 0);
+ client->mSyncServer.reset(); // This might delete the current object.
+ break;
+ }
+ }
+ // The client must have been found.
+ llassert(!client->mSyncServer);
+}
+
+void AISyncServer::unregister_last_client(void)
+{
+#ifdef DEBUG_SYNCOUTPUT
+ DoutEntering(dc::notice, "unregister_last_client(), with this = " << this);
+#endif
+
+ // This function may only be called for servers with exactly one client.
+ llassert(mClients.size() == 1);
+ AISyncClient_ServerPtr* client = *mClients.begin();
+#ifdef DEBUG_SYNCOUTPUT
+ Dout(dc::notice, "unregistering client " << client);
+#endif
+ mClients.clear();
+ mNrClients -= syncevents;
+ mNrReady = 0;
+ llassert((mNrClients & syncoverflowbits) == 0);
+ client->mSyncServer.reset();
+ client->deregistered();
+}
+
+synceventset AISyncServer::events_with_all_clients_ready(void) const
+{
+ synccount nrNotReady = mNrClients - mNrReady;
+ synceventset result1 = !(nrNotReady & synccountmask1) ? syncevent1 : 0;
+ synceventset result2 = !(nrNotReady & synccountmask2) ? syncevent2 : 0;
+ synceventset result3 = !(nrNotReady & synccountmask3) ? syncevent3 : 0;
+ synceventset result4 = !(nrNotReady & synccountmask4) ? syncevent4 : 0;
+ result1 |= result2;
+ result3 |= result4;
+ return result1 | result3;
+}
+
+synceventset AISyncServer::events_with_at_least_one_client_ready(void) const
+{
+ synceventset result1 = (mNrReady & synccountmask1) ? syncevent1 : 0;
+ synceventset result2 = (mNrReady & synccountmask2) ? syncevent2 : 0;
+ synceventset result3 = (mNrReady & synccountmask3) ? syncevent3 : 0;
+ synceventset result4 = (mNrReady & synccountmask4) ? syncevent4 : 0;
+ result1 |= result2;
+ result3 |= result4;
+ return result1 | result3;
+}
+
+#ifdef CWDEBUG
+struct SyncEventSet {
+ synceventset mBits;
+ SyncEventSet(synceventset bits) : mBits(bits) { }
+};
+
+std::ostream& operator<<(std::ostream& os, SyncEventSet const& ses)
+{
+ os << ((ses.mBits & syncevent4) ? '1' : '0');
+ os << ((ses.mBits & syncevent3) ? '1' : '0');
+ os << ((ses.mBits & syncevent2) ? '1' : '0');
+ os << ((ses.mBits & syncevent1) ? '1' : '0');
+ return os;
+}
+#endif
+
+bool AISyncServer::ready(synceventset events, synceventset yesno/*,*/ ASSERT_ONLY_COMMA(AISyncClient_ServerPtr* client))
+{
+#ifdef DEBUG_SYNCOUTPUT
+ DoutEntering(dc::notice, "AISyncServer::ready(" << SyncEventSet(events) << ", " << SyncEventSet(yesno) << ", " << client << ")");
+#endif
+
+ synceventset added_events = events & yesno;
+ synceventset 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);
+ // Were all clients ready for event 1?
+ bool ready_before = !((mNrClients - mNrReady) & synccountmask1);
+ // Update mNrReady counters.
+ mNrReady += added_events;
+ mNrReady -= removed_events;
+ // Test for under and overflow, this limits the maximum number of clients to 127 instead of 255, but well :p.
+ llassert((mNrReady & syncoverflowbits) == 0);
+ // Are all clients ready for event 1?
+ bool ready_after = !((mNrClients - mNrReady) & synccountmask1);
+ if (ready_before && !ready_after)
+ {
+ trigger_not_ready();
+ }
+#ifdef SHOW_ASSERT
+ // Update debug administration.
+ client->mReadyEvents ^= events;
+#ifdef DEBUG_SYNCOUTPUT
+ Dout(dc::notice, "Client " << client << " now has ready: " << SyncEventSet(client->mReadyEvents));
+#endif
+#endif
+ if (!ready_before && ready_after)
+ {
+ trigger_ready();
+ }
+}
+
+void AISyncServer::trigger_ready(void)
+{
+ for (client_list_type::iterator client_iter = mClients.begin(); client_iter != mClients.end(); ++client_iter)
+ {
+ llassert(((*client_iter)->mReadyEvents & syncevent1));
+ (*client_iter)->event1_ready();
+ }
+}
+
+void AISyncServer::trigger_not_ready(void)
+{
+ for (client_list_type::iterator client_iter = mClients.begin(); client_iter != mClients.end(); ++client_iter)
+ {
+ llassert(((*client_iter)->mReadyEvents & syncevent1));
+ (*client_iter)->event1_not_ready();
+ }
+}
+
+void intrusive_ptr_add_ref(AISyncServer* server)
+{
+ server->mNrClients += syncrefcountunit;
+ llassert((server->mNrClients & syncoverflowbits) == 0);
+}
+
+void intrusive_ptr_release(AISyncServer* server)
+{
+ llassert(server->mNrClients >= syncrefcountunit);
+ server->mNrClients -= syncrefcountunit;
+ // If there are no more pointers pointing to this server, then obviously it can't have any registered clients.
+ llassert(server->mNrClients >= syncrefcountunit || server->mNrClients == 0);
+ if (server->mNrClients == 0)
+ {
+ delete server;
+ }
+}
+
+//static
+sync_key_hash_type const AISyncKey::sNoRequirementHash = 0x91b42f98a9fef15cULL;
+
+
+//=============================================================================
+// SYNC_TESTSUITE
+//=============================================================================
+
+#ifdef SYNC_TESTSUITE
+#include
+#include
+#include
+#include
+
+//static
+U32 LLFrameTimer::sFrameCount;
+
+double innerloop_count = 0;
+
+double LLFrameTimer::getCurrentTime()
+{
+ return innerloop_count * 0.001;
+}
+
+class TestsuiteServerBase : public AISyncServer
+{
+ public:
+ TestsuiteServerBase(AISyncKey const& sync_key) : AISyncServer(sync_key) { }
+ client_list_type& clients(void) { return mClients; }
+};
+
+class TestsuiteServer1 : public TestsuiteServerBase
+{
+ public:
+ TestsuiteServer1(AISyncKey const& sync_key) : TestsuiteServerBase(sync_key) { }
+};
+
+// Specialitations that link TestsuiteServer1 to syncgroup_test1.
+//
+//static
+template<>
+void AISyncServer::create_server(boost::intrusive_ptr& server, AISyncKey const& sync_key)
+{
+ AISyncServerCache::create_server(server, sync_key);
+}
+//static
+template<>
+void AISyncServer::dispose_server(boost::intrusive_ptr& server)
+{
+ AISyncServerCache::dispose_server(server);
+}
+
+class TestsuiteServer2 : public TestsuiteServerBase
+{
+ public:
+ TestsuiteServer2(AISyncKey const& sync_key) : TestsuiteServerBase(sync_key) { }
+};
+
+// Specialitations that link TestsuiteServer2 to syncgroup_test2.
+//
+//static
+template<>
+void AISyncServer::create_server(boost::intrusive_ptr& server, AISyncKey const& sync_key)
+{
+ AISyncServerCache::create_server(server, sync_key);
+}
+//static
+template<>
+void AISyncServer::dispose_server(boost::intrusive_ptr& server)
+{
+ AISyncServerCache::dispose_server(server);
+}
+
+template
+class TestsuiteClient : public AISyncClient
+{
+ private:
+ int mIndex;
+ bool mRequestedRegistered;
+ synceventset mRequestedReady;
+ bool mActualReady1;
+
+ public:
+ TestsuiteClient() : mIndex(-1), mRequestedRegistered(false), mRequestedReady(0), mActualReady1(false) { }
+ ~TestsuiteClient() { this->ready(mRequestedReady, (synceventset)0); }
+
+ void setIndex(int index) { mIndex = index; }
+
+ protected:
+ /*virtual*/ void event1_ready(void)
+ {
+ llassert(!mActualReady1);
+ mActualReady1 = true;
+ }
+
+ /*virtual*/ void event1_not_ready(void)
+ {
+ llassert(mActualReady1);
+ mActualReady1 = false;
+ }
+
+ /*virtual*/ sync_key_hash_type sync_key_hash(void) const
+ {
+ // Sync odd clients with eachother, and even clients with eachother.
+ return 0xb3c919ff + (mIndex & 1);
+ }
+
+ // 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<" << syncgroup << ">::deregistered(), with this = " << this);
+#endif
+ mRequestedRegistered = false;
+ mRequestedReady = 0;
+ mActualReady1 = false;
+ this->mReadyEvents = 0;
+ }
+
+ private:
+ bool is_registered(void) const { return !!this->mSyncServer; }
+
+ public:
+ void change_state(unsigned long r);
+ bool getRequestedRegistered(void) const { return mRequestedRegistered; }
+ synceventset getRequestedReady(void) const { return mRequestedReady; }
+};
+
+TestsuiteClient* client1p;
+TestsuiteClient* client2p;
+
+int const number_of_clients_per_syncgroup = 8;
+
+template
+void TestsuiteClient::change_state(unsigned long r)
+{
+ bool change_registered = r & 1;
+ r >>= 1;
+ synceventset toggle_events = ((r & 1) ? syncevent1 : 0) | ((r & 2) ? syncevent2 : 0) | ((r & 4) ? syncevent3 : 0) | ((r & 8) ? syncevent4 : 0);
+ 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());
+ TestsuiteServerBase* server = this->server();
+ llassert(!mRequestedRegistered || server->number_of_clients() == server->clients().size());
+ if (mRequestedRegistered)
+ {
+ synceventset all_ready = syncevents;
+ synceventset any_ready = 0;
+ int nr = 0;
+ for (int cl = 0; cl < number_of_clients_per_syncgroup; ++cl)
+ {
+ if (syncgroup == syncgroup_test1)
+ {
+ if (client1p[cl].server() != server)
+ {
+ continue;
+ }
+ if (client1p[cl].getRequestedRegistered())
+ {
+ ++nr;
+ all_ready &= client1p[cl].getRequestedReady();
+ any_ready |= client1p[cl].getRequestedReady();
+ }
+ }
+ else
+ {
+ if (client2p[cl].server() != server)
+ {
+ continue;
+ }
+ if (client2p[cl].getRequestedRegistered())
+ {
+ ++nr;
+ all_ready &= client2p[cl].getRequestedReady();
+ any_ready |= client2p[cl].getRequestedReady();
+ }
+ }
+ }
+ llassert(nr == server->number_of_clients());
+ llassert(!!(all_ready & syncevent1) == 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 client1[number_of_clients_per_syncgroup];
+ TestsuiteClient client2[number_of_clients_per_syncgroup];
+ client1p = client1;
+ client2p = client2;
+
+ for (int i = 0; i < number_of_clients_per_syncgroup; ++i)
+ {
+ client1[i].setIndex(i);
+ client2[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();
+ syncgroups grp = (r & 1) ? syncgroup_test1 : syncgroup_test2;
+ r >>= 1;
+ int cl = (r & 255) % number_of_clients_per_syncgroup;
+ r >>= 8;
+ switch (grp)
+ {
+ case syncgroup_test1:
+ client1[cl].change_state(r);
+ break;
+ case syncgroup_test2:
+ client2[cl].change_state(r);
+ break;
+ }
+ }
+ }
+}
+
+#endif
diff --git a/indra/llcommon/aisyncclient.h b/indra/llcommon/aisyncclient.h
new file mode 100644
index 000000000..b6fcbd103
--- /dev/null
+++ b/indra/llcommon/aisyncclient.h
@@ -0,0 +1,417 @@
+/**
+ * @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 .
+ *
+ * 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.
+ *
+ * 05/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
+#include
+typedef uint32_t U32;
+typedef uint64_t U64;
+#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; }
+ void reset(double expiration) { mStartTime = getCurrentTime(); mExpiry = mStartTime + expiration; }
+ bool hasExpired(void) const { return getCurrentTime() > mExpiry; }
+};
+
+#else // !SYNC_TESTSUITE
+#include "llpreprocessor.h"
+#include "stdtypes.h"
+#include "llerror.h"
+#include "llframetimer.h"
+#endif
+#include
+#include
+
+class AISyncServer;
+
+/*
+ * AISyncClient
+ *
+ * This class allows to synchronize events between multiple client objects,
+ * derived from AISyncClient, where AISYNCSERVER must be
+ * derived from AISyncServer.
+ *
+ * Client objects call ready(mask) (or ready(event, bool), where each bit can be
+ * set or unset, which will result in alternating calls to event1_ready(mask) and
+ * event1_not_ready(mask) for all registered clients at the same time depending on
+ * when all clients are ready for syncevent1 or not (the other events can be
+ * polled, see below).
+ *
+ * For example, let a and b be two clients:
+ *
+ * {
+ * AISyncClient a;
+ * {
+ * AISyncClient b;
+ *
+ * a.ready(syncevent1, true); // Calls a.event1_ready().
+ * a.ready(syncevent1, false); // Calls a.event1_not_ready().
+ * b.ready(syncevent1, true); // Nothing happens.
+ * b.ready(syncevent1, false); // Nothing happens.
+ * b.ready(syncevent1, true); // Nothing happens.
+ * a.ready(syncevent1, true); // Calls a.event1_ready() and b.event1_ready(2).
+ * b.ready(syncevent1, false); // Calls a.event1_not_ready() and b.event1_not_ready().
+ * } // Calls a.event1_ready() because b was destructed and a is still ready.
+ * a.ready(syncevent1, false); // Calls a.event1_not_ready(). Must set not-ready before destructing.
+ * }
+ *
+ * Clients can access the server by calling server() and poll it:
+ *
+ * AISyncServer* server = server();
+ *
+ * if (server)
+ * {
+ * int n = server->number_of_clients();
+ * synceventset set1 = server->events_with_all_clients_ready();
+ * synceventset set2 = server->events_with_zero_clients_ready();
+ * synceventset set3 = server->events_with_at_least_one_client_ready();
+ * if (n == 0)
+ * {
+ * llassert(set1 == syncevents); // We define a server with no clients to have all clients ready.
+ * llassert(set2 == syncevents); // If there are no clients then every event has zero ready clients.
+ * llassert(set3 == 0); // If there are no clients then obviously set3 is the empty set.
+ * }
+ * else
+ * {
+ * llassert((set3 & set1) == set1); // set1 is a subset of set3.
+ * }
+ * llassert((set3 & (set1 & ~set2)) == (set1 & ~set2));
+ * // The set of events that have all clients ready, but not zero clients,
+ * // is a subset of the set of events with at least one client ready.
+ * llassert((set2 ^ set3) == syncevents); // Each event belongs to either set2 or set3.
+ * }
+ *
+ * Clients can also unregister themselves without destruction, by calling unregister();
+ * This might cause a call to event1_ready() for the remaining clients, if all of them
+ * are ready for syncevent1.
+ *
+ * Clients must be set not-ready for all events before unregistering them.
+ *
+ * Finally, it is possible to cause a server-side unregister for all clients,
+ * by calling server->dissolve(). This will cause an immediate call to event1_ready()
+ * for all clients that are ready, unless all were already ready.
+ */
+
+enum syncgroups
+{
+#ifdef SYNC_TESTSUITE
+ syncgroup_test1,
+ syncgroup_test2,
+#else
+ syncgroup_motions, // Syncgroup used for animations.
+#endif
+ syncgroup_size
+};
+
+typedef U32 sync_canonical_type;
+typedef U64 sync_key_hash_type;
+
+int const sync_canonical_type_bits = sizeof(sync_canonical_type) * 8;
+int const sync_number_of_events = 4;
+int const sync_bits_per_event_counter = sync_canonical_type_bits / (sync_number_of_events + 1);
+
+// Each type exists of 4 event bytes.
+typedef sync_canonical_type syncevent; // A single event: the least significant bit in one of the bytes.
+typedef sync_canonical_type synceventset; // Zero or more events: the least significant bit in zero or more of the bytes.
+typedef sync_canonical_type synccount; // A count [0, 255] in each of the bytes.
+typedef sync_canonical_type synccountmask; // A single count mask: all eight bits set in one of the bytes.
+typedef sync_canonical_type synccountmaskset; // Zero or more count masks: all eight bits set in zero or more of the bytes.
+
+syncevent const syncevent1 = 1;
+syncevent const syncevent2 = syncevent1 << sync_bits_per_event_counter;
+syncevent const syncevent3 = syncevent2 << sync_bits_per_event_counter;
+syncevent const syncevent4 = syncevent3 << sync_bits_per_event_counter;
+sync_canonical_type const syncrefcountunit = syncevent4 << sync_bits_per_event_counter;
+
+synccountmask const synccountmask1 = syncevent2 - 1;
+synccountmask const synccountmask2 = synccountmask1 << sync_bits_per_event_counter;
+synccountmask const synccountmask3 = synccountmask2 << sync_bits_per_event_counter;
+synccountmask const synccountmask4 = synccountmask3 << sync_bits_per_event_counter;
+synccountmask const syncrefcountmask = ((synccountmask)-1) & ~(syncrefcountunit - 1);
+
+synceventset const syncevents = syncevent1 | syncevent2 | syncevent3 | syncevent4;
+synccountmask const syncoverflowbits = (syncevents << (sync_bits_per_event_counter - 1)) | ~(~(sync_canonical_type)0 >> 1);
+
+// Convert an event set into its corresponding count mask set.
+inline synccountmaskset synceventset2countmaskset(synceventset events)
+{
+ synccountmaskset tmp1 = events << sync_bits_per_event_counter;
+ return (tmp1 & ~events) - (events & ~tmp1);
+}
+
+// Convert a count mask set into an event set.
+inline synceventset synccountmask2eventset(synccountmask mask)
+{
+ return mask & syncevents;
+}
+
+// Interface class used to determined if a client and a server fit together.
+class LL_COMMON_API AISyncKey : private LLFrameTimer
+{
+ private:
+ sync_key_hash_type mHash;
+ U32 mFrameCount;
+
+ public:
+ // A static hash used for clients with no specific requirement.
+ static sync_key_hash_type const sNoRequirementHash;
+
+ // A sync key object that returns the sNoRequirementHash.
+ static AISyncKey const sNoRequirement;
+
+ public:
+ AISyncKey(sync_key_hash_type hash) : mHash(hash) { mFrameCount = getFrameCount(); reset(0.1); }
+ virtual ~AISyncKey() { }
+
+ // Derived class should return a hash that is the same for clients which
+ // should be synchronized (assuming they meet the time slot requirement as well).
+ // The hash does not need to depend on on clock or frame number thus.
+ virtual sync_key_hash_type hash(void) const { return sNoRequirementHash; }
+
+ // Return true if this key expired.
+ bool expired(void) const { return getFrameCount() > mFrameCount + 1 || hasExpired(); }
+
+ // Return true if both keys are close enough to warrant synchronization.
+ bool matches(AISyncKey const& key) const { return key.mHash == mHash; }
+};
+
+class LL_COMMON_API AISyncClient_ServerPtr
+{
+ protected:
+ friend class AISyncServer;
+ boost::intrusive_ptr mSyncServer;
+ virtual ~AISyncClient_ServerPtr() { }
+
+ // All-ready event.
+ virtual void event1_ready(void) = 0; // Called when, or after ready(), with the syncevent1 bit set, is called. Never called after a call to ready() with that bit unset.
+ // Not all-ready event.
+ virtual void event1_not_ready(void) = 0; // A call to event1_not_ready() follows (at most once) the call to event1_ready(), at some unspecified moment.
+ // 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
+ }
+
+ // Derived classes must return a hash that determines whether or not clients can be synchronized (should be synchronized when registered at the same time).
+ virtual sync_key_hash_type sync_key_hash(void) const = 0;
+
+#ifdef SHOW_ASSERT
+ AISyncClient_ServerPtr(void) : mReadyEvents(0) { }
+
+ protected:
+ synceventset mReadyEvents; // Used by AISyncServer for debugging only.
+#endif
+};
+
+// AISyncServer
+//
+// This class represents a single group of AISyncClient's (a list of pointers to
+// their AISyncClient_ServerPtr base class) that need to synchronize events.
+//
+class LL_COMMON_API AISyncServer
+{
+ public:
+ typedef std::list client_list_type;
+
+ protected:
+ synccount mNrClients; // Four times (once in each byte) the number of registered clients (the size of mClients).
+ synccount mNrReady; // The number of clients that are ready (four counts, one for each syncevent).
+ client_list_type mClients; // The registered clients.
+ AISyncKey mSyncKey; // The characteristic of clients belonging to this synchronization server.
+
+ public:
+ AISyncServer(AISyncKey const& sync_key) : mNrClients(0), mNrReady(0), mSyncKey(sync_key) { }
+ virtual ~AISyncServer() { }
+
+ // Default creation of a server for 'syncgroup'.
+ template
+ static void create_server(boost::intrusive_ptr& server, AISyncKey const& sync_key);
+ // Default removal of a server for 'syncgroup'.
+ template
+ static void dispose_server(boost::intrusive_ptr& server);
+
+ // Called from AISyncServerCache.
+ void setSyncKey(AISyncKey const& sync_key) { mSyncKey = sync_key; }
+
+ // Register client with a server from group syncgroup.
+ template
+ static void register_client(AISyncClient_ServerPtr* client);
+ // Unregister the client (syncgroup must be the same as what was passed to register it).
+ void unregister_client(AISyncClient_ServerPtr* client, syncgroups syncgroup);
+
+ // Set readiness of all events at once.
+ bool ready(synceventset events, synceventset yesno/*,*/ ASSERT_ONLY_COMMA(AISyncClient_ServerPtr* client));
+ // One event became ready or not ready.
+ bool ready(syncevent event, bool yesno/*,*/ ASSERT_ONLY_COMMA(AISyncClient_ServerPtr* client)) { return ready(event, yesno ? event : 0/*,*/ ASSERT_ONLY_COMMA(client)); }
+
+ // Returns bitwise OR of syncevent1 through syncevent4 for
+ // events for which all clients are ready.
+ synceventset events_with_all_clients_ready(void) const;
+ // events for which at least one client is ready.
+ synceventset events_with_at_least_one_client_ready(void) const;
+ // events for which zero clients are ready.
+ synceventset events_with_zero_clients_ready(void) const { return syncevents ^ events_with_at_least_one_client_ready(); }
+
+ // Return the number registered clients.
+ int number_of_clients(void) const { return (int)(mNrClients & synccountmask1); }
+ int get_refcount(void) const { return (int)(mNrClients >> (sync_number_of_events * sync_bits_per_event_counter)); }
+
+ private:
+ void trigger_ready(void); // Calls event1_ready() for all clients.
+ void trigger_not_ready(void); // Calls event1_not_ready for all clients.
+
+ void unregister_last_client(void);
+
+ friend void intrusive_ptr_add_ref(AISyncServer* server);
+ friend void intrusive_ptr_release(AISyncServer* server);
+};
+
+template
+class LL_COMMON_API AISyncClient_SyncGroup : public AISyncClient_ServerPtr
+{
+ public:
+ // 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 events, synceventset yesno) // Set readiness of all events at once.
+ {
+ if (!mSyncServer)
+ {
+ AISyncServer::register_client(this);
+ }
+ return mSyncServer->ready(events, yesno/*,*/ ASSERT_ONLY_COMMA(this));
+ }
+ bool ready(syncevent event, bool yesno = true) // One event became ready or not ready.
+ {
+ if (!mSyncServer)
+ {
+ AISyncServer::register_client(this);
+ }
+ return mSyncServer->ready(event, yesno/*,*/ ASSERT_ONLY_COMMA(this));
+ }
+
+ void unregister_client(void)
+ {
+ if (mSyncServer)
+ {
+ mSyncServer->unregister_client(this, syncgroup);
+ }
+ }
+
+ protected:
+ virtual ~AISyncClient_SyncGroup() { unregister_client(); }
+};
+
+template
+class LL_COMMON_API AISyncClient : public AISyncClient_SyncGroup
+{
+ public:
+ AISYNCSERVER* server(void) { return static_cast(this->mSyncServer.get()); }
+ AISYNCSERVER const* server(void) const { return static_cast(this->mSyncServer.get()); }
+
+ // By default return a general sync key that will cause synchronizing solely based on time.
+ /*virtual*/ AISyncKey const& getSyncKey(void) const { return AISyncKey::sNoRequirement; }
+};
+
+template
+class AISyncServerCache
+{
+ private:
+ // The server cache exists of a few pointers to unused server instances.
+ // The cache is empty when sSize == 0 and full when sSize == 16;
+ static int const cache_size = 16;
+ static boost::intrusive_ptr sServerCache[cache_size];
+ static int sSize;
+
+ public:
+ static void create_server(boost::intrusive_ptr& server, AISyncKey const& sync_key)
+ {
+ if (sSize == 0) // Cache empty?
+ {
+ server.reset(new AISYNCSERVER(sync_key));
+ }
+ else
+ {
+ sServerCache[--sSize].swap(server);
+ server->setSyncKey(sync_key);
+ }
+ }
+
+ static void dispose_server(boost::intrusive_ptr& server)
+ {
+ if (sSize < cache_size)
+ {
+ sServerCache[sSize++].swap(server);
+ }
+ server.reset();
+ }
+};
+
+template
+boost::intrusive_ptr AISyncServerCache::sServerCache[cache_size];
+
+template
+int AISyncServerCache::sSize;
+
+template
+inline void AISyncServer::create_server(boost::intrusive_ptr& server, AISyncKey const& sync_key)
+{
+ AISyncServerCache::create_server(server, sync_key);
+}
+
+template
+inline void AISyncServer::dispose_server(boost::intrusive_ptr& server)
+{
+ AISyncServerCache::dispose_server(server);
+}
+
+#endif // AI_SYNC_CLIENT_H
+