From 006b319c3a9be55181e0c2e02d6b9040402151f2 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Thu, 5 Dec 2013 14:53:16 +0100 Subject: [PATCH] Add AISyncClient<> (and AISyncServer). A tool to synchronize objects: Objects derived from AISyncClient can signal that they are 'ready' or 'not ready' for up to 4 events (using a bitmask) at a time. Clients that signal to be ready for anything at roughly the same time as other clients, and which return the same 'hash' (a virtual function returning a 64bit value) 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 (syncevent1). The other three events can be polled. The memory usage is low (one pointer per client that points to its AISyncServer object), servers are released to a cache after about 100 ms (unless there is actual need for synchronization), so there aren't much of those either. The CPU usage is extremely low: all events are handled in parallel in a 32 bit value (6 bits per event to count the number of registered clients and the number of ready clients for each event, and the remaining 8 bits to count the number of reference pointers (which should only be a constant higher, so that is overkill). To signal to a server that a client has become ready or not is mostly a function call, which then takes 1 clock cycle or so before returning. Registration of a client is slightly more expensive as it requires a pointer to be added to the end of a std::list. This tool could easily be used as part of the graphics engine (not that I intend to do that :p). --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/aisyncclient.cpp | 538 ++++++++++++++++++++++++++++++++ indra/llcommon/aisyncclient.h | 417 +++++++++++++++++++++++++ 3 files changed, 957 insertions(+) create mode 100644 indra/llcommon/aisyncclient.cpp create mode 100644 indra/llcommon/aisyncclient.h 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 +