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

199 lines
6.8 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 {
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);
// 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) const;
void deserialize(StreamBuffer *sb);
// Generate a debug string.
std::string debug_string() const;
private:
std::vector<int64_t> 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 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(); }
// 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);
// Debug string.
std::string debug_string() const;
private:
IdGlobalPool *global_;
int fifo_capacity_;
std::deque<int64_t> ranges_;
friend int unittests_idalloc(lua_State *L);
};
#endif // IDALLOC_HPP