Files
integration/luprex/core/cpp/idalloc.hpp

172 lines
6.1 KiB
C++

///////////////////////////////////////////////////////////////////
//
// 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 <cstdint>
#include <vector>
#include <deque>
#include "luastack.hpp"
#include "streambuffer.hpp"
class IdGlobalPool {
private:
std::vector<int64_t> salvaged_;
int64_t next_batch_;
int64_t next_id_;
int32_t 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);
// Serialize to or deserialize from a streambuffer.
void serialize(StreamBuffer *sb);
void deserialize(StreamBuffer *sb);
};
class IdPlayerPool {
private:
IdGlobalPool *global_;
std::deque<int64_t> 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(); }
// Serialize to or deserialize from a streambuffer.
// Caution: the pointer to the global pool is not serialized or deserialized.
void serialize(StreamBuffer *sb);
void deserialize(StreamBuffer *sb);
};
#endif // IDALLOC_HPP