Initial revision of animqueue

This commit is contained in:
2021-01-12 14:14:38 -05:00
parent 78f8610eb8
commit 25b9b4cb5d
18 changed files with 785 additions and 308 deletions

View File

@@ -1,51 +1,66 @@
// The ID allocator.
///////////////////////////////////////////////////////////////////
//
// This ID allocator attempts to allocate IDs in such a way that the
// 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.
//
// 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.
// 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
// 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.
// 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 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.
// 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 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.
// 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.
//
// ID ranges are assigned as follows:
// THE NUMERIC RANGES
//
// 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.
// * 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.
//
// The operations in this class are:
// BATCH REPRESENTATION
//
// 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
// 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
@@ -64,16 +79,44 @@ private:
friend int cunittests_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);
int64_t get_batch();
// Create a single unique ID. Ids allocated this way are
// unlikely to be predicted correctly.
int64_t get_one();
void salvage(int64_t batch);
// 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);
};
@@ -84,15 +127,34 @@ private:
friend int cunittests_idalloc(lua_State *L);
public:
// Construct a player pool.
// The Player pool stores a pointer to the global pool.
IdPlayerPool(IdGlobalPool *gp);
~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