// The ID allocator. // // This ID allocator attempts to allocate IDs in such a way that the // synchronous model gets the same IDs as the master model. // // Every logged-in player maintains a "batch queue". That's a fifo queue // of ID batches. An ID batch is a contiguous range of IDs, containing // between 128 and 256 contiguous IDs. The batch queue 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 // player's batch queue. When that thread allocates IDs, // it uses the batch it was allocated. If the batch is used up, then the // thread falls back to using a global fallback allocator. Such fallback IDs // are not likely to be predicted correctly. // // When a player creates a thread, he uses up one of his batches. In the // master model, the batch queue is 'refilled' by creating a new batch. // In the synchronous model, the batch queue 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 0, 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 master model will reclaim that batch, // putting it into a global salvage pool. The salvage batches are later // used when refilling batch queues. // // ID ranges are assigned as follows: // // 0x0000+ : reserved for future expansion. // 0x0001+ : used by master model to allocate batches. // 0x0010+ : used by master model to allocate unpredictable IDs. // 0x001E+ : used by synchronous model to allocate stopgap IDs. // // The operations in this class are: // // idalloc.initmaster() - reinitialize the ID allocator in master mode. // idalloc.initsynch() - reinitialize the ID allocator in synchronous mode. // idalloc.setqueue_fill() - override the default queue fill level. // idalloc.refill(bq) - get batches from the global pool until the batch queue is full. // idalloc.unqueue(bq) - push batches from the batch queue back into the global pool. // idalloc.preparethread(bq, co) - transfer a batch from the batch queue to the coroutine // idalloc.salvagethread(co) - push any batch in the dead coroutine back into the global pool // // idalloc.allocid() - get an ID using either the current thread's pool or the global pool #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 cunittests_idalloc(lua_State *L); public: IdGlobalPool(); ~IdGlobalPool(); void init_master(int qf); void init_synch(int qf); int64_t get_batch(); int64_t get_one(); void salvage(int64_t batch); int queue_fill() const { return queue_fill_; } void salvage_thread(lua_State *L); int64_t alloc_id_for_thread(lua_State *L); }; class IdPlayerPool { private: IdGlobalPool *global_; std::deque ranges_; friend int cunittests_idalloc(lua_State *L); public: IdPlayerPool(IdGlobalPool *gp); ~IdPlayerPool(); void refill(); void unqueue(); void purge(); int64_t get_batch(); void salvage_thread(lua_State *L); void prepare_thread(lua_State *L); }; #endif // IDALLOC_HPP