/////////////////////////////////////////////////////////////////// // // THE ID ALLOCATOR // // This ID allocator's goal is to allocate IDs in such a way that the // synchronous model gets the same IDs as the master model. // // DESIGN PRINCIPLES // // There are two classes defined here: IdGlobalPool, and IdPlayerPool. // // Every logged-in player maintains an IdPlayerPool. That's basically a fifo // queue of ID batches. An ID batch is a contiguous range of IDs, containing // between 128 and 256 contiguous IDs. The IdPlayerPool is difference // transmitted, to ensure that the synchronous model has the same batches as the // master model. // // When a player creates a thread, that thread gets an ID batch from the // IdPlayerPool. To make this possible, the Lua runtime has been modified so // that a thread can contain an ID batch. When that thread allocates IDs, it // uses the batch it was allocated. In the unlikely event that a thread's batch // is used up, the thread falls back to using the IdGlobalPool. Such fallback // IDs are not likely to be predicted correctly. // // When a player creates a thread, he uses up one batch from his IdPlayerPool. // In the master model, the player pool is 'refilled' by asking the IdGlobalPool // to create a new batch. In the synchronous model, the IdPlayerPool is not // directly refilled, but the difference transmitter effectively refills it. It // is imperative that this difference transmission happen before the player's // asynchronous model runs out of batches, otherwise we'll get prediction // failures. // // It is common that a thread will only use 1 or 2 IDs. If a thread exits // without using up most of its IDs, then the batch it contains is still a // pretty usable batch. The batch gets returned to the IdGlobalPool, which will // eventually use that salvaged batch to satisfy an IdPlayerPool refill request. // // THE NUMERIC RANGES // // * 0x0000+ : reserved for manually-created tangible IDs. // * 0x0001+ : used by master model's IdGlobalPool to create batches. // * 0x0010+ : used by master model's IdGlobalPool to create individual IDs. // * 0x001E+ : used by sync model's IdGlobalPool to create individual IDs. // // BATCH REPRESENTATION // // A batch is represented as a 64-bit integer. The batch contains all IDs // starting with that integer, and incrementing, until the the two lowest digits // are 0x00. // // For example, consider the batch ID 0x11111111111111FC. That batch includes // these IDs: // // * 0x11111111111111FC // * 0x11111111111111FD // * 0x11111111111111FE // * 0x11111111111111FF // // But it does not include 0x1111111111111200. // // As a special case, the number 0 is used to indicate "invalid batch". // /////////////////////////////////////////////////////////////////// #ifndef IDALLOC_HPP #define IDALLOC_HPP #include "wrap-map.hpp" #include "wrap-sstream.hpp" #include "wrap-deque.hpp" #include "wrap-ostream.hpp" #include "luastack.hpp" #include "streambuffer.hpp" #include "debugcollector.hpp" #include class IdGlobalPool { public: // Construct and destroy global pools. Note that after constructing // a global pool, it is generally also necessary to initialize it // for Master or Synchronous operation using init_master or init_synch. // Any attempt to use a pool that hasn't been init_master or init_synch // will fail. IdGlobalPool(); ~IdGlobalPool(); // Initialize the pool for use in a master model. void init_master(); // Initialize the pool for use in a synchronous model. void init_synch(); // Create a single unique ID. Ids allocated this way are // unlikely to be predicted correctly. int64_t get_one(); // Obtain a batch of IDs from the global pool. In a master // model, the batch is guaranteed to contain at least 128 IDs. // In a synchronous model, the batch is always zero (invalid). int64_t get_batch(); // Try to return the specified batch to the global pool. // The salvage operation quietly does nothing if the batch is // zero, or the batch contains fewer than 128 IDs. void salvage(int64_t batch); // Serialize to or deserialize from a streambuffer. void serialize(StreamBuffer *sb) const; void deserialize(StreamBuffer *sb); // Generate a debug string. eng::string debug_string() const; private: eng::vector salvaged_; int64_t next_batch_; int64_t next_id_; friend int unittests_idalloc(lua_State *L); }; class IdPlayerPool { public: // Construct a player pool. // // The fifo is initially in the disabled state. In the disabled state, the // fifo is not kept filled. You can still use the allocator when the fifo is // disabled - doing so just fetches a batch from the global pool. // IdPlayerPool(IdGlobalPool *g); ~IdPlayerPool(); // Set the fifo capacity. Max=255. void set_fifo_capacity(int n); int get_fifo_capacity() const { return fifo_capacity_; } // Return all batches from the fifo to the global pool. void unqueue(); // Refill the fifo of batches from the global pool. void refill(); // Get a single ID from the fifo. Also refills the fifo. int64_t get_one(); // Return the size of the queue. int size() { return ranges_.size(); } // Return true if the two pools are identical. bool exactly_equal(const IdPlayerPool &other) const; // Check that the pool is valid. bool valid() const; // unit testing functions. // // These let you manipulate the deque explicitly. But that's // only useful for unit testing. // void test_push_back(int64_t range); void test_pop_front(); void test_clear_ranges(); // Serialize to or deserialize from a streambuffer. // Caution: the pointer to the global pool is not serialized or deserialized. void serialize(StreamBuffer *sb) const; void deserialize(StreamBuffer *sb); // Difference transmission // void diff(const IdPlayerPool &auth, StreamBuffer *sb) const; void patch(StreamBuffer *sb, DebugCollector *dbc); // Debug string. eng::string debug_string() const; private: IdGlobalPool *global_; int fifo_capacity_; eng::deque ranges_; friend int lfn_unittests_idalloc(lua_State *L); }; #endif // IDALLOC_HPP