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 +