/////////////////////////////////////////////////////////////////// // // 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 future expansion. // * 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 #include #include #include "luastack.hpp" class IdGlobalPool { private: std::vector salvaged_; int64_t next_batch_; int64_t next_id_; int queue_fill_; friend int unittests_idalloc(lua_State *L); 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(int qf); // Initialize the pool for use in a synchronous model. void init_synch(int qf); // 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(); // When a player pool refills its fifo queue, it refills it // with this many batches. int queue_fill() const { return queue_fill_; } // 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); // Return the thread's batch to the global pool. If no batch // is present, that's okay. Set the thread's batch to zero (invalid). void salvage_thread(lua_State *L); // Allocate an ID for the specified thread. Uses the thread's // batch if possible. If not, fetches one ID from the global pool. int64_t alloc_id_for_thread(lua_State *L); }; class IdPlayerPool { private: IdGlobalPool *global_; std::deque ranges_; friend int unittests_idalloc(lua_State *L); public: // Construct a player pool. // The Player pool stores a pointer to the global pool. IdPlayerPool(IdGlobalPool *igp); ~IdPlayerPool(); // Refill the fifo queue of batches from the global pool. void refill(); // Return all batches to the global pool. Leave the fifo empty. void unqueue(); // Discard all batches. This is only for unit testing. void purge(); // Get a batch from the fifo. Also refills the fifo. int64_t get_batch(); // Return the thread's batch to the global pool. If no batch // is present, that's okay. Set the thread's batch to zero (invalid). void salvage_thread(lua_State *L); // Fetch a batch from the fifo and install it in the thread. // If the thread already had a batch, the thread's previous batch // is returned to the global pool. void prepare_thread(lua_State *L); // Return the size of the queue. int size() { return ranges_.size(); } }; #endif // IDALLOC_HPP