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,2 +1,2 @@
clear
g++ -std=c++17 -Wall -g -o main syscpp/util.cpp syscpp/main.cpp syscpp/planemap.cpp syscpp/world.cpp syscpp/traceback.cpp syscpp/luastack.cpp syscpp/source.cpp syscpp/table.cpp syscpp/idalloc.cpp syscpp/globaldb.cpp -Iinc -Llib lib/libluajit-dbg.a -Isyscpp
g++ -std=c++17 -Wall -g -o main syscpp/util.cpp syscpp/main.cpp syscpp/animqueue.cpp syscpp/planemap.cpp syscpp/world.cpp syscpp/traceback.cpp syscpp/luastack.cpp syscpp/source.cpp syscpp/table.cpp syscpp/idalloc.cpp syscpp/globaldb.cpp -Iinc -Llib lib/libluajit-dbg.a -Isyscpp

View File

@@ -0,0 +1,61 @@
#include "animqueue.hpp"
#include <limits>
AnimStep::AnimStep() {}
AnimStep::~AnimStep() {}
AnimQueue::AnimQueue(int size_limit) {
assert(size_limit >= 2);
size_limit_ = size_limit;
steps_.emplace_back();
AnimStep &init = steps_.back();
init.id_ = 0;
init.facing_ = 0;
init.xyz_ = util::XYZ(0,0,0);
init.graphic_ = "nothing";
init.plane_ = "nowhere";
init.bits_ = 0;
}
void AnimQueue::add(int64_t id, const std::string &action) {
steps_.emplace_back();
AnimStep &last = steps_.back();
last = steps_[steps_.size() - 2];
last.id_ = id;
last.action_ = action;
last.bits_ = 0;
while (int(steps_.size()) > size_limit_) {
steps_.pop_front();
}
AnimStep &init = steps_.front();
init.id_ = 0;
init.action_ = "";
init.bits_ = 0;
}
void AnimQueue::set_facing(float f) {
AnimStep &last = steps_.back();
last.bits_ |= AnimStep::HAS_FACING;
last.facing_ = f;
}
void AnimQueue::set_xyz(util::XYZ xyz) {
AnimStep &last = steps_.back();
last.bits_ |= AnimStep::HAS_XYZ;
last.xyz_ = xyz;
}
void AnimQueue::set_graphic(const std::string &g) {
AnimStep &last = steps_.back();
last.bits_ |= AnimStep::HAS_GRAPHIC;
last.graphic_ = g;
}
void AnimQueue::set_plane(const std::string &p) {
AnimStep &last = steps_.back();
last.bits_ |= AnimStep::HAS_PLANE;
last.plane_ = p;
}

View File

@@ -0,0 +1,91 @@
///////////////////////////////////////////////////////////////////
//
// ANIMATION QUEUES
//
// An animation queue is a fifo queue of animation steps. New animations are
// pushed on the back, and old ones are popped from the front.
//
// An animation step has an "action" which is usually the name of an animation,
// or it's a special token like "walk" or "warp." Each animation step shows the
// resulting AnimState that the player finds himself in after executing the
// action.
//
// The first step in an animation queue always has id=0 and action="". This step
// represents the initial state of the sprite before any animations or
// movements.
//
// To add new items to the AnimQueue, use this process: first, call add(id,
// action). This adds a new step to the queue. Then, call set_xyz, set_facing,
// set_plane, or an other setter. These setters are meant to only be used
// immediately after calling 'add' to populate the new step.
//
///////////////////////////////////////////////////////////////////
#ifndef ANIMQUEUE_HPP
#define ANIMQUEUE_HPP
#include <set>
#include <string>
#include <deque>
#include <cassert>
#include "util.hpp"
class AnimStep {
friend class AnimQueue;
public:
enum {
HAS_FACING = 1,
HAS_XYZ = 2,
HAS_GRAPHIC = 4,
HAS_PLANE = 8,
};
private:
int64_t id_;
std::string action_;
int bits_;
float facing_;
util::XYZ xyz_;
std::string graphic_;
std::string plane_;
public:
AnimStep();
~AnimStep();
int64_t id() const { return id_; }
const std::string &action() const { return action_; }
int bits() const { return bits_; }
double facing() const { return facing_; }
util::XYZ xyz() const { return xyz_; }
const std::string &graphic() const { return graphic_; }
const std::string &plane() const { return plane_; }
bool has_facing() const { return bits_ & AnimStep::HAS_FACING; }
bool has_xyz() const { return bits_ & AnimStep::HAS_XYZ; }
bool has_graphic() const { return bits_ & AnimStep::HAS_GRAPHIC; }
bool has_plane() const { return bits_ & AnimStep::HAS_PLANE; }
};
class AnimQueue {
private:
int size_limit_;
std::deque<AnimStep> steps_;
public:
AnimQueue(int size_limit);
const AnimStep &nth(int n) const { return steps_[n]; }
int size() const { return steps_.size(); }
// Mutators to create new steps.
void add(int64_t id, const std::string &action);
void set_facing(float f);
void set_xyz(util::XYZ xyz);
void set_graphic(const std::string &g);
void set_plane(const std::string &p);
};
#endif // ANIMQUEUE_HPP

View File

@@ -2,6 +2,17 @@
#include "globaldb.hpp"
#include "table.hpp"
LuaDefine(globaldb_enable, "c") {
LuaVar globaldb;
LuaStack LS(L, globaldb);
LS.getfield(globaldb, LuaRegistry, "globaldb");
if (!LS.istable(globaldb)) {
LS.newtable(globaldb);
LS.setfield(LuaRegistry, "globaldb", globaldb);
}
return LS.result();
}
// Get a table from the global database.
//
// GLOBALNAME
@@ -15,15 +26,14 @@ LuaDefine(globaldb_global, "f") {
LuaVar globaldb;
LuaStack LS(L, globalname, globaltab, globaldb);
LS.checkstring(globalname);
// Get a pointer to the globaldb.
LS.getfield(globaldb, LuaRegistry, "globaldb");
if (!LS.istable(globaldb)) {
LS.newtable(globaldb);
LS.setfield(LuaRegistry, "globaldb", globaldb);
luaL_error(L, "globaldb is not enabled");
}
LS.checkstring(globalname);
// Get the globaltab from the globaldb, sanity check it.
LS.rawget(globaltab, globaldb, globalname);
if (LS.istable(globaltab)) {

View File

@@ -1,10 +1,48 @@
////////////////////////////////////////////////////////////
//
// GLOBALDB
//
// The master world model is allowed to maintain global data structures.
//
// Unfortunately, any attempt to put a global data structure into the global
// environment will fail, because the global environment periodically gets wiped
// clean by the "source rebuild" procedure (see module 'source').
//
// This module creates a safe space where you can put global data structures
// that won't get wiped out.
//
// THE GLOBAL OPERATOR
//
// This module adds a new function to lua: "global". Global takes a global data
// structure name (a string) and returns a table for that global data structure.
// You can put anything you want into the table.
//
// Since you're only allowed to use global data structures in the master world
// model, any attempt to call "global" in a synchronous world model will
// result in a 'donotpredict' error.
//
////////////////////////////////////////////////////////////
#ifndef GLOBALDB_HPP
#define GLOBALDB_HPP
#include "luastack.hpp"
// globaldb_enable
//
// Enable the use of the global DB. This is meant to be invoked in
// the master world model only. In other world models, you should simply
// not call this function.
//
int globaldb_enable(lua_State *L);
// The lua 'global' operator.
//
// Given a global name, returns a table for that global.
//
// If you haven't enabled the global DB using globaldb_enable, this
// raises a donotpredict error.
//
int globaldb_global(lua_State *L);
#endif // GLOBALDB_HPP

View File

@@ -99,7 +99,9 @@ void IdPlayerPool::purge() {
void IdPlayerPool::refill() {
while (int(ranges_.size()) < global_->queue_fill()) {
ranges_.push_back(global_->get_batch());
int64_t batch = global_->get_batch();
if (batch == 0) break;
ranges_.push_back(batch);
}
}
@@ -112,11 +114,17 @@ void IdPlayerPool::unqueue() {
int64_t IdPlayerPool::get_batch() {
while (int(ranges_.size()) < global_->queue_fill() + 1) {
ranges_.push_back(global_->get_batch());
int64_t batch = global_->get_batch();
if (batch == 0) break;
ranges_.push_back(batch);
}
if (ranges_.empty()) {
return 0;
} else {
int64_t batch = ranges_.front();
ranges_.pop_front();
return batch;
}
}
void IdPlayerPool::salvage_thread(lua_State *L) {
@@ -184,6 +192,15 @@ LuaDefine(cunittests_idalloc, "c") {
gp.salvage(nthbatch(142) + 145);
assert(gp.get_batch() == nthbatch(2));
// In the synchronous model, refill should do nothing.
pp.purge();
gp.init_synch(3);
pp.refill();
assert(pp.size() == 0);
assert(pp.get_batch() == 0);
assert(pp.size() == 0);
assert(pp.get_batch() == 0);
// Test refill from master.
pp.purge();
gp.init_master(3);

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
// 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.
//
// idalloc.allocid() - get an ID using either the current thread's pool or the global pool
// 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

View File

@@ -185,8 +185,7 @@ static int pmain(lua_State *L)
// Initialize the builtins, then copy a snapshot of the
// builtins to the registry. This will allow us to restore
// the builtins during source_rebuild operations.
source_load_builtins(L);
source_snapshot_builtins(L);
source_install_and_snapshot_builtins(L);
// Load the lua source.
source_update(L);
@@ -194,9 +193,6 @@ static int pmain(lua_State *L)
// Rebuild the global environment.
source_rebuild(L);
// Run all the autoinit functions.
source_autoinit(L);
dotty(L);
return 0;
}

View File

@@ -68,7 +68,7 @@ void PlaneMap::remove(const std::string &plane, int64_t cellid, PlaneItem *clien
if (cellid == CELL_INVALID) {
return;
}
if (plane == "") {
if ((plane == "") || (plane == "nowhere")) {
return;
}
auto piter = planes_.find(plane);
@@ -94,7 +94,7 @@ void PlaneMap::insert(const std::string &plane, int64_t cellid, PlaneItem *clien
if (cellid == CELL_INVALID) {
return;
}
if (plane == "") {
if ((plane == "") || (plane == "nowhere")) {
return;
}
Plane &p = planes_[plane];
@@ -107,10 +107,10 @@ void PlaneItem::set_pos(const std::string &plane, double x, double y, double z)
int64_t new_cell = point_cell_id(x, y);
// Update the grid.
if (grid_ != 0) {
if (pmap_ != 0) {
if ((plane_ != plane) || (old_cell != new_cell)) {
grid_->remove(plane_, old_cell, this);
grid_->insert(plane, new_cell, this);
pmap_->remove(plane_, old_cell, this);
pmap_->insert(plane, new_cell, this);
}
}
@@ -121,32 +121,36 @@ void PlaneItem::set_pos(const std::string &plane, double x, double y, double z)
z_ = z;
}
void PlaneItem::use_map(PlaneMap *grid) {
int64_t cellid = point_cell_id(x_, y_);
if (grid_ != 0) {
grid_->remove(plane_, cellid, this);
}
grid_ = grid;
if (grid_ != 0) {
grid_->insert(plane_, cellid, this);
void PlaneItem::untrack() {
if (pmap_ != 0) {
pmap_->remove(plane_, point_cell_id(x_, y_), this);
pmap_ = 0;
}
}
PlaneItem::PlaneItem() : grid_(NULL), x_(0.0), y_(0.0), z_(0.0) {
void PlaneMap::track(PlaneItem *item) {
if (item->pmap_ != this) {
item->untrack();
insert(item->plane(), point_cell_id(item->x(), item->y()), item);
item->pmap_ = this;
}
}
PlaneItem::PlaneItem() : pmap_(NULL), x_(0.0), y_(0.0), z_(0.0) {
}
PlaneMap::PlaneMap() {
}
PlaneItem::~PlaneItem() {
use_map(NULL);
untrack();
}
PlaneMap::~PlaneMap() {
for (const auto &p : planes_) {
for (const auto &l : p.second) {
for (PlaneItem *i : l.second) {
i->grid_ = NULL;
i->pmap_ = NULL;
}
}
}
@@ -262,10 +266,10 @@ LuaDefine(cunittests_planemap, "c") {
pm.remove("foo", 12345, &pib);
assert(pm.total_cells() == 0);
// Test the insert function on an invalid plane.
// Test the insert function on the nowhere plane.
pm.clear();
pm.insert("", 12345, &pia);
pm.insert("", 12345, &pib);
pm.insert("nowhere", 12345, &pia);
pm.insert("nowhere", 12345, &pib);
assert(pm.total_cells() == 0);
// Test the insert function on an invalid cell.
@@ -283,17 +287,17 @@ LuaDefine(cunittests_planemap, "c") {
// Attach pia to the grid. This should record it.
pm.clear();
pia.use_map(&pm);
pm.track(&pia);
elts = pm.get_cell("foo", point_cell_id(3.0, 4.0));
assert(elts.size() == 1);
assert(elts[0] == &pia);
// Unattach pia from the grid. This should unrecord it.
pia.use_map(NULL);
pia.untrack();
assert(pm.total_cells() == 0);
// Reattach pia to the grid, then move it.
pia.use_map(&pm);
pm.track(&pia);
assert(pm.total_cells() == 1);
pia.set_pos("bar", 1000.0, 1000.0, 0.0);
assert(pm.total_cells() == 1);
@@ -302,7 +306,7 @@ LuaDefine(cunittests_planemap, "c") {
assert(elts[0] == &pia);
// Insert the four elements, then test the scan function.
pib.use_map(&pm);
pm.track(&pib);
pib.set_pos("bar", 1100.0, 1000.0, 0.0);
elts = pm.scan_radius("bar", 1000.0, 1000.0, 1.0);
assert(elts.size() == 1);

View File

@@ -1,40 +1,74 @@
//////////////////////////////////////////////////////////////
//
// PLANEMAP
//
// Stores a map of the items that are on each plane. Doesn't
// store terrain, only items.
// This module defines two classes: PlaneMap, and PlaneItem. A
// PlaneItem is an object that has a plane (a string) and an
// XYZ position. A PlaneMap keeps track of potentially thousands
// of PlaneItem objects, allowing you to search for PlaneItems
// by scanning a geographic region.
//
// CLASS SPRITE DERIVES FROM PLANEITEM
//
// A PlaneMap records the positions of PlaneItems. The intent is
// that class Sprite should derive from PlaneItem. That way, the
// PlaneMap can record the positions of sprites.
//
// When you put derived types like Sprite into a PlaneMap, the
// PlaneMap will work fine. However, when you scan the PlaneMap,
// it will give return PlaneItem pointers, not Sprite pointers.
//
// If you're sure that the PlaneMap contains only sprites, then
// you can use static_cast to convert those PlaneItem pointers to
// Sprite pointers. Sadly, there's no simple way to avoid the casting.
//
// THE SCANNABLE REGION IS FINITE
//
// A plane is a rectangle, with a finite size: if you stray more
// than 80,000,000 meters from the origin, then you are beyond the
// edge of the plane.
// than 80,000,000 meters from the origin, then that PlaneItem
// is beyond the scannable region.
//
// There's nothing stopping you from moving a PlaneItem farther
// than that. However, if you do move a PlaneItem farther than
// that, then it won't show up in radius-scans.
// There's nothing stopping you from moving a PlaneItem outside
// the scannable region. It is not an error to do so.
// However, if you do move a PlaneItem outside the scannable
// region, then that PlaneItem will not show up for scan_radius.
//
// The PlaneMap doesn't need you to "create" planes. You just
// set the plane of a PlaneItem to any string, and that plane will
// pop into existence if it wasn't already there.
// PLANES CANNOT BE DESTROYED BUT THEY CAN BE UNINHABITED
//
// If you use the empty string as a plane name, a special case is
// You can't "create" or "destroy" planes. Instead, we say
// that a plane is "uninhabited" until you put a PlaneItem
// there. Initially, all possible planes exist, but they're
// all uninhabited until you put a PlaneItem there. Planes
// that are uninhabited take no memory.
//
// THE NOWHERE PLANE
//
// If you use the literal "nowhere" as a plane name, a special case is
// triggered: you're "nowhere." Radius scans can never find items
// whose plane is the empty string.
// whose plane is "nowhere." The same also applies when you use the
// empty string as a plane name.
//
// INHERITANCE
// THE Z COORDINATE IS IGNORED
//
// The intent is that class Sprite should derive from PlaneItem.
// When you put sprites into a PlaneMap, it will work fine. However,
// when you scan the PlaneMap, it will give you PlaneItem pointers,
// not Sprite pointers. If you're sure that the PlaneMap contains
// only sprites, then you can use static_cast to convert those
// PlaneItem pointers to Sprite pointers. Sadly, there's no simple
// way to avoid the casting.
// Class PlaneItem stores X, Y, and Z. The Z coordinate is
// ignored for all plane-scanning operations. In other words,
// planes are 2D. The only reason we store a Z coordinate
// is for the consistency of storing the model's X, Y, and Z
// all in the same place.
//
// MEMORY MANAGEMENT
//
// The PlaneMap does not own the PlaneItems. You need to create,
// destroy, and manage the PlaneItems yourself.
// PlaneMaps do not own PlaneItems. This is a deliberate choice.
// We assume that sprites will be owned by the sprite ID table.
// So therefore, the PlaneMaps shouldn't own the sprites.
//
// So instead, we use this rule: a PlaneMap has a function 'track'
// that causes it to start tracking the location of a PlaneItem.
// However, the PlaneMap still doesn't own the PlaneItem.
// If you destroy a PlaneMap, all the PlaneItems
// will automatically be untracked, but they won't be deleted.
//
//////////////////////////////////////////////////////////////
#ifndef PLANEMAP_HPP
#define PLANEMAP_HPP
@@ -47,10 +81,12 @@ class PlaneMap;
class PlaneItem {
friend class PlaneMap;
private:
PlaneMap *grid_;
PlaneMap *pmap_;
std::string plane_;
double x_, y_, z_;
public:
PlaneItem();
~PlaneItem();
@@ -60,32 +96,35 @@ public:
const double y() const { return y_; }
const double z() const { return z_; }
void use_map(PlaneMap *map);
void untrack();
void set_pos(const std::string &plane, double x, double y, double z);
void set_xyz(double x, double y, double z) { set_pos(plane_, x, y, z); }
};
class PlaneMap {
friend class PlaneItem;
friend int cunittests_planemap(lua_State *L);
public:
using Elt = PlaneItem *;
using EltVec = std::vector<Elt>;
private:
using EltVec = std::vector<PlaneItem *>;
using Plane = std::map<int64_t, EltVec>;
std::map<std::string, Plane> planes_;
void remove(const std::string &plane, int64_t cell, PlaneItem *client);
void insert(const std::string &plane, int64_t cell, PlaneItem *client);
private:
// These functions are only used in unit tests.
EltVec get_cell(const std::string &plane, int64_t cell) const;
int total_cells() const;
void clear() { planes_.clear(); }
public:
PlaneMap();
~PlaneMap();
void track(PlaneItem *item);
EltVec scan_radius(const std::string &plane, double x, double y, double radius) const;
private:
// unit testing stuff.
friend int cunittests_planemap(lua_State *L);
EltVec get_cell(const std::string &plane, int64_t cell) const;
int total_cells() const;
void clear() { planes_.clear(); }
};
#endif // PLANEMAP_HPP

View File

@@ -14,6 +14,93 @@
#include "traceback.hpp"
LuaDefine(source_makeclass, "f") {
LuaArg classname;
LuaVar action, gname;
LuaRet classtab;
LuaStack LS(L, classname, classtab, action, gname);
LS.checkstring(classname);
// Special case: if the classname is _G, return global env.
LS.set(gname, "_G");
if (LS.equal(classname, gname)) {
LS.set(classtab, LuaGlobals);
return LS.result();
}
// Get the classtab from the global environment.
// Create it if it doesn't exist.
LS.rawget(classtab, LuaGlobals, classname);
if (LS.isnil(classtab)) {
LS.newtable(classtab);
LS.rawset(LuaGlobals, classname, classtab);
}
// If the name isn't bound to a table, abort.
if (!LS.istable(classtab)) {
luaL_error(L, "%s is not a class", LS.ckstring(classname).c_str());
}
// Repair the special fields.
LS.setfield(classtab, "__index", classtab);
LS.setfield(classtab, "__class", classname);
// Repair the action table.
LS.getfield(action, classtab, "action");
if (!LS.istable(action)) {
LS.setfield(classtab, "action", LuaNewTable);
}
return LS.result();
}
// Load the builtins.
static void load_builtin(lua_State *L, const char *name, lua_CFunction func) {
lua_pushcfunction(L, func);
lua_pushstring(L, name);
lua_call(L, 1, 0);
}
LuaDefine(source_install_builtins, "") {
LuaStack LS(L);
load_builtin(L, "base", luaopen_base);
load_builtin(L, "table", luaopen_table);
load_builtin(L, "string", luaopen_string);
load_builtin(L, "math", luaopen_math);
load_builtin(L, "bit", luaopen_math);
load_builtin(L, "debug", luaopen_debug);
// Do not load: package, io, os, debug, jit
return 0;
}
LuaDefine(source_install_and_snapshot_builtins, "") {
LuaVar key, value, skey, svalue, snapshot, ssnapshot;
LuaStack LS(L, snapshot, key, value, skey, svalue, ssnapshot);
// Note: the global environment contains _G. This routine
// perceives this as a subtable, so it backs up the top level
// as well.
source_install_builtins(L);
LS.newtable(snapshot);
LS.set(key, LuaNil);
while (LS.next(LuaGlobals, key, value) != 0) {
if (LS.istable(value)) {
LS.newtable(ssnapshot);
LS.rawset(snapshot, key, ssnapshot);
LS.set(skey, LuaNil);
while (LS.next(value, skey, svalue) != 0) {
if (LS.isfunction(svalue) || LS.isstring(svalue) || LS.isnumber(svalue)) {
LS.rawset(ssnapshot, skey, svalue);
}
}
}
}
LS.setfield(LuaRegistry, "source_snapshot_builtins", snapshot);
return LS.result();
}
LuaDefine(source_updatefile, "") {
LuaArg source, fn;
LuaRet info;
@@ -95,58 +182,8 @@ LuaDefine(source_update, "c") {
return LS.result();
}
// Make a class. A class is a table in the global environment.
//
// The global environment is protected, but this function can
// override that protection.
//
LuaDefine(source_makeclass, "f") {
LuaArg classname;
LuaVar action, gname;
LuaRet classtab;
LuaStack LS(L, classname, classtab, action, gname);
LS.checkstring(classname);
// Special case: if the classname is _G, return global env.
LS.set(gname, "_G");
if (LS.equal(classname, gname)) {
LS.set(classtab, LuaGlobals);
return LS.result();
}
// Get the classtab from the global environment.
// Create it if it doesn't exist.
LS.rawget(classtab, LuaGlobals, classname);
if (LS.isnil(classtab)) {
LS.newtable(classtab);
LS.rawset(LuaGlobals, classname, classtab);
}
// If the name isn't bound to a table, abort.
if (!LS.istable(classtab)) {
luaL_error(L, "%s is not a class", LS.ckstring(classname).c_str());
}
// Repair the special fields.
LS.setfield(classtab, "__index", classtab);
LS.setfield(classtab, "__class", classname);
// Repair the action table.
LS.getfield(action, classtab, "action");
if (!LS.istable(action)) {
LS.setfield(classtab, "action", LuaNewTable);
}
return LS.result();
}
// Clear the global environment.
//
// Clears out almost everything from the global
// environment. However, does not delete class tables.
//
// This is used during source_rebuild operations.
// Delete everything from the global environment except
// the class tables and the class action tables.
//
LuaDefine(source_clear_globals, "") {
LuaVar classname, classtab, action, key;
@@ -156,13 +193,16 @@ LuaDefine(source_clear_globals, "") {
LS.set(classname, LuaNil);
while (LS.next(LuaGlobals, classname, classtab) != 0) {
if (LS.istable(classtab)) {
bool keep_action = false;
LS.getfield(action, classtab, "action");
if (LS.istable(action)) {
LS.call(table_clear, action);
} else {
LS.newtable(action);
keep_action = true;
}
LS.call(table_clear, classtab);
if (keep_action) {
LS.setfield(classtab, "action", action);
}
} else {
LS.rawset(LuaGlobals, classname, LuaNil);
}
@@ -173,11 +213,7 @@ LuaDefine(source_clear_globals, "") {
// Restore the lua builtins from the backup snapshot.
//
// The global environment is expected to be clean, but
// the subtables are expected to still be present, in
// accordance with how 'source_clear_globals' works.
//
LuaDefine(source_restore_builtins, "g") {
LuaDefine(source_restore_builtins, "") {
LuaVar snapshot, key, value, skey, svalue, subglobal;
LuaStack LS(L, snapshot, key, value, skey, svalue, subglobal);
@@ -195,57 +231,8 @@ LuaDefine(source_restore_builtins, "g") {
return LS.result();
}
// Snapshot all the lua builtin functions.
// Load all the 'LuaDefine' C functions into the lua state.
//
// Precondition: this is meant to be used on a pristine lua
// intepreter, before the user dumps a bunch of crap into the
// global environment. It won't work if the global environment
// contains anything other than the lua builtins.
//
// Note: the global environment contains _G. This routine
// perceives this as a subtable, so it backs up the top level
// as well.
//
LuaDefine(source_snapshot_builtins, "c") {
LuaVar key, value, skey, svalue, snapshot, ssnapshot;
LuaStack LS(L, snapshot, key, value, skey, svalue, ssnapshot);
LS.newtable(snapshot);
LS.set(key, LuaNil);
while (LS.next(LuaGlobals, key, value) != 0) {
if (LS.istable(value)) {
LS.newtable(ssnapshot);
LS.rawset(snapshot, key, ssnapshot);
LS.set(skey, LuaNil);
while (LS.next(value, skey, svalue) != 0) {
if (LS.isfunction(svalue) || LS.isstring(svalue) || LS.isnumber(svalue)) {
LS.rawset(ssnapshot, skey, svalue);
}
}
}
}
LS.setfield(LuaRegistry, "source_snapshot_builtins", snapshot);
return LS.result();
}
static void load_builtin(lua_State *L, const char *name, lua_CFunction func) {
lua_pushcfunction(L, func);
lua_pushstring(L, name);
lua_call(L, 1, 0);
}
LuaDefine(source_load_builtins, "") {
LuaStack LS(L);
load_builtin(L, "base", luaopen_base);
load_builtin(L, "table", luaopen_table);
load_builtin(L, "string", luaopen_string);
load_builtin(L, "math", luaopen_math);
load_builtin(L, "bit", luaopen_math);
load_builtin(L, "debug", luaopen_debug);
// Do not load: package, io, os, debug, jit
return 0;
}
LuaDefine(source_load_cfunctions, "") {
auto regs = LuaFunctionReg::all();
for (const LuaFunctionReg *r : regs) {
@@ -275,10 +262,7 @@ LuaDefine(source_load_cfunctions, "") {
return 0;
}
// Fetches the source database and runs all the loadresults.
//
// Returns a single string, which is a bunch of concatenated error
// messages.
// Run all the closures from the source database.
//
LuaDefine(source_load_lfunctions, "") {
LuaRet errors;
@@ -336,13 +320,3 @@ LuaDefine(source_rebuild, "c") {
return LS.result();
}
LuaDefine(source_autoinit, "") {
auto regs = LuaFunctionReg::all();
for (const LuaFunctionReg *r : regs) {
std::string mode = r->get_mode();
if (mode.find('a') != std::string::npos) {
r->get_func()(L);
}
}
return 0;
}

View File

@@ -1,18 +1,102 @@
////////////////////////////////////////////////////////////
//
// SOURCE
//
// The source database is a lua table that maps filenames
// to file info. The source database is stored in the registry.
// This module manages the loading of lua source files into the Lua environment.
// Since the source files can be reloaded over and over, this module doesn't
// just load the source files. Instead, it does "source rebuilds" in which it
// purges everything from the global environment, then it reinstalls everything
// that should be there. That way, if you delete something from a lua source
// file, it gets removed from the lua global environment.
//
// In the source database, the keys are filenames, and the values
// are tables containing the following fields:
//
// name: filename as a string
// fingerprint: file modification, file length, as a string
// code: the entire contents of the source file as a string
// loadresult: a lua closure, or, an error message
// sequence: the position of the file in control.lst
// THE MAKECLASS OPERATOR
//
// This module provides a new lua 'builtin' operator: "makeclass". This creates
// a table and stores it in the global environment. The new table is meant to be
// used as a class. Three fields are initially created:
//
// __class --> the name of the class as a string
//
// __index --> points back to the class. Makes it convenient to use the
// class as a metatable.
//
// action --> a subtable of additional methods.
//
// If you invoke 'makeclass' on a class that already exists, the existing table
// is "repaired" - the __class and __index fields are restored and the action
// subtable, if not present, is recreated. If there are already functions or
// constants inside the class, they are not affected.
//
//
// THE LUA SOURCE DATABASE
//
// The function 'source_update' loads the lua source code from disk, and stores
// it in the "source database." The source database is a table inside the lua
// registry.
//
// The source database is a lua table where the keys are filenames, and the
// values are information about that file:
//
// "foo.lua" : {
// "name": "foo.lua",
// "fingerprint": "12893129385854",
// "code": "function xyz ...",
// "loadresult": <function-23>,
// "sequence": 13
// }
//
// Let's break that down a little. The "name" field is just the filename. The
// "fingerprint" is an encoding of the file modification date and the file
// length, it is used to detect whether the file has been altered on disk
// without having to reread the file. The "code" is the entire content of the
// source file. The "loadresult" is the closure that results from calling the
// lua 'load' function on the code. If load fails, then the "loadresult" is an
// error message. Finally, "sequence" indicates the order in which the source
// files are meant to be loaded.
//
// The operation "source_update" refreshes the source database from disk. It
// doesn't reread files whose fingerprints have not changed. In a synchronous
// model, we don't call source_update - instead, we update the source database
// by difference transmission.
//
// Note that updating the source database has *no effect* on the lua global
// variables (or lua function definitions). The source_update operation calls
// lua's "load" on the code, but all that does is return a loaded closure.
// Nothing happens to the lua invironment until you *invoke* the loaded closure.
// That doesn't happen until you do a source_rebuild operation, described below.
//
//
// SOURCE REBUILDS, IN DEPTH
//
// The function source_rebuild clears and then rebuilds the entire contents of
// the global environment. The reason to clear the environment is that if we
// didn't clear it, then removing a function from the lua source would not
// remove it from the lua environment. Rebuilding the lua environment is a
// multi-step process:
//
// * Delete everything from the global environment except class tables.
//
// - Class tables are kept, but the contents are cleared.
// - If a class has an "actions" subtable, that is kept and cleared.
// - Anything else is deleted.
//
// * Lua Builtin functions are reinstalled in the global environment.
//
// - To make this possible, a snapshot of these builtins is kept.
//
// * C++ functions registered with "LuaDefine" are reinstalled.
//
// - LuaDefine creates a registry, that registry is iterated over.
//
// * Lua code is reinstalled by running the closures in the source DB.
//
// - Simply call the "loadresult" closure to reinstall functions into the
// global environment.
//
// Note that if you've stored any global data in the lua environment, it's gone.
// So therefore, we have to provide a separate "safe" space for global data
// structures. That is provided elsewhere, in the module "globaldb".
//
////////////////////////////////////////////////////////////
@@ -22,24 +106,41 @@
#include "luastack.hpp"
// Get a class from the class database.
int source_class(lua_State *L);
// The Lua 'makeclass' operator.
//
// Creates a table in the global environment with the specified name.
// Adds a __class field and an __index field, and an action subtable.
// If there's already a table with this name in the global environment,
// leaves it there, and repairs the __class and __index fields.
//
int source_makeclass(lua_State *L);
// Update the source database from disk. No parameters, no return values.
// source_install_and_snapshot_builtins.
//
// Install the lua builtins into a brand new lua state, and then create the
// snapshot of the builtins (in the registry). This uses lua_openlibs, which
// only works if the lua environment is brand new. Do not try to use
// source_snapshot_builtins unless the lua environment has just been created
// using lua_newstate!
//
int source_install_and_snapshot_builtins(lua_State *L);
// source_update
//
// Read all the lua source files from disk and store them in the
// source database. Also compiles these files using lua's "load"
// function. Efficient: if a source file is already in the database
// and hasn't been modified, it is not reloaded.
//
int source_update(lua_State *L);
// Load the builtins into the global environment using lua_openlibs
int source_load_builtins(lua_State *L);
// Back up the pristine global environment to the registry.
int source_snapshot_builtins(lua_State *L);
// Rebuild the class database from the source database. No parameters, no return values.
// source_rebuild
//
// Rebuild the lua environment: clear it out, then reinstall all the
// functions that should be there. See above for more information.
//
int source_rebuild(lua_State *L);
// Run all 'autoinit' functions.
int source_autoinit(lua_State *L);
#endif // SOURCE_HPP

View File

@@ -29,16 +29,6 @@ LuaDefine(table_equal, "c") {
return LS.result();
}
LuaDefine(table_append, "c") {
luaL_checktype(L, -2, LUA_TTABLE);
int len = lua_objlen(L, -2);
lua_pushinteger(L, len+1);
lua_pushvalue(L, -2);
lua_rawset(L, -4);
lua_pop(L, 2);
return 0;
}
LuaDefine(table_findremove, "c") {
luaL_checktype(L, -2, LUA_TTABLE);
int src = 1;
@@ -75,6 +65,17 @@ LuaDefine(table_findremove, "c") {
}
}
LuaDefine(table_push, "c") {
luaL_checktype(L, -2, LUA_TTABLE);
int len = lua_objlen(L, -2);
lua_pushinteger(L, len+1);
lua_pushvalue(L, -2);
lua_rawset(L, -4);
lua_pop(L, 2);
return 0;
}
LuaDefine(table_find, "c") {
luaL_checktype(L, -2, LUA_TTABLE);
for (int i = 1; ; i++) {
@@ -126,8 +127,6 @@ LuaDefine(table_clear, "c") {
LuaStack LS(L, tab);
LS.checktable(tab);
LS.clearmetatable(tab);
lua_pushnil(L);
while (lua_next(L, tab.index()) != 0) {
lua_pop(L, 1); // Pop the old value.
@@ -139,14 +138,6 @@ LuaDefine(table_clear, "c") {
return LS.result();
}
LuaDefine(table_coerce, "c") {
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
}
return 1;
}
LuaDefine(queue_create, "c") {
LuaRet queue;
LuaStack LS(L, queue);

View File

@@ -1,20 +1,113 @@
////////////////////////////////////////////////////////////
//
// TABLE
//
// This module contains a library of lua functions
// for manipulating tables. Some of the functions only
// work on vectors (ie, tables with integer keys starting
// at one). When a function only works on vectors, it
// is noted.
//
// QUEUE
//
// This module contains a library of lua functions for
// manipulating queues. Queues are represented as a table
// with a "head" and a "tail", and with integer-keyed values.
// For example:
//
// {
// "tail": 100,
// "head": 103,
// 100: "a",
// 101: "b",
// 102: "c"
// }
//
// Values are pushed onto the head, and values are popped
// from the tail.
//
////////////////////////////////////////////////////////////
#ifndef TABLE_HPP
#define TABLE_HPP
#include "luastack.hpp"
// table_equal
//
// True if two tables contain the same key/value pairs.
//
int table_equal(lua_State *L);
// table_findremove
//
// Given a vector and a value, remove the specified value from
// the vector, and shift elements downward to fill the gaps.
//
int table_findremove(lua_State *L);
int table_append(lua_State *L);
// table_push
//
// Given a vector and a value, push the value onto the end.
//
int table_push(lua_State *L);
// table_find
//
// Given a vector and a value, search for the first occurrence
// of that value. Return the index.
//
int table_find(lua_State *L);
// table_empty
//
// Return true if the table has no key/value pairs.
//
int table_empty(lua_State *L);
// table_count
//
// Return the number of key/value pairs in the table.
//
int table_count(lua_State *L);
// table_clear
//
// Remove all key/value pairs from the table. Does not
// remove the metatable.
//
int table_clear(lua_State *L);
int table_coerce(lua_State *L);
// queue_create
//
// Create and return an empty queue.
//
int queue_create(lua_State *L);
// queue_push
//
// Given a queue and a value, pushes the value onto the queue.
//
int queue_push(lua_State *L);
// queue_pop
//
// Given a queue, pop and return a value. If the queue is empty,
// returns nil.
//
int queue_pop(lua_State *L);
// queue_size
//
// Return the number of values in the queue.
//
int queue_size(lua_State *L);
// queue_nth
//
// Return the nth element in the queue.
//
int queue_nth(lua_State *L);
#endif // TABLE_HPP

View File

@@ -1,27 +1,37 @@
/////////////////////////////////////////////////////////////////
//
// TRACEBACK ROUTINES
//
// Traceback routines.
//
// traceback_handler: a traceback routine meant to be used
// as a message handler routine for 'pcall'.
//
// traceback_coroutine: takes a coroutine and an error message.
// Returns a traceback of the coroutine.
//
// traceback_pcall: same as lua_pcall, except that it supplies
// the default traceback_handler as a message handler.
//
// The following routines are meant to help produce good-quality
// tracebacks from errors in lua code.
//
/////////////////////////////////////////////////////////////////
#ifndef TRACEBACK_HPP
#define TRACEBACK_HPP
#include "luastack.hpp"
// traceback_coroutine
//
// Given a coroutine and an error message, returns a traceback
// of the coroutine.
//
int traceback_coroutine(lua_State *L);
// traceback_handler
//
// The function 'pcall' expects you to pass in a message handler routine.
// 'traceback_handler' is designed to be used as this argument to pcall.
//
int traceback_handler(lua_State *L);
// traceback_pcall
//
// same as lua_pcall, except that it automatically supplies traceback_handler as
// a message handler.
//
int traceback_pcall(lua_State *L, int narg, int nret);
#endif // TRACEBACK_HPP

View File

@@ -5,6 +5,7 @@
#include <set>
#include <algorithm>
#include <sstream>
#include <tuple>
namespace util {
@@ -43,6 +44,7 @@ uint32_t hash3(uint32_t a, uint32_t b, uint32_t c);
// Returns a floating point value between lo and hi inclusive.
double hash_to_float(double lo, double hi, uint32_t a, uint32_t b, uint32_t c);
using XYZ = std::tuple<float, float, float>;
} // namespace util
#endif // UTIL_HPP

View File

@@ -2,6 +2,7 @@
local ut = {}
function ut.globaldb()
globaldb.enable()
local g1a = global("unittest-g1")
local g2a = global("unittest-g2")
local g1b = global("unittest-g1")

View File

@@ -7,25 +7,12 @@ function ut.table_count()
assert(table.count({[2]=5,[5]=3}) == 2)
end
function ut.table_coerce()
local t = {}
local t1 = table.coerce(t)
assert(t1==t)
t1 = table.coerce(0)
assert(type(t1) == "table")
t1 = table.coerce(nil)
assert(type(t1) == "table")
end
function ut.table_clear()
local t = { a = 1, b = 2 }
table.clear(t)
assert(t.a == nil)
assert(t.b == nil)
assert(table.count(t) == 0)
setmetatable(t, t)
table.clear(t)
assert(getmetatable(t) == nil)
end
function ut.table_empty()
@@ -46,13 +33,13 @@ function ut.table_equal()
assert(not table.equal({a=1,b=3},{a=1,b=2}))
end
function ut.table_append()
function ut.table_push()
t = {}
table.append(t, 1)
table.push(t, 1)
assert(table.equal(t, {1}))
table.append(t, 2)
table.push(t, 2)
assert(table.equal(t, {1,2}))
table.append(t, 3)
table.push(t, 3)
assert(table.equal(t, {1,2,3}))
end