Rewrite planemap and idalloc in pure C++

This commit is contained in:
2021-01-06 15:10:21 -05:00
parent b03aada315
commit 78f8610eb8
18 changed files with 913 additions and 520 deletions

View File

@@ -1,2 +1,2 @@
clear
g++ -std=c++17 -Wall -g -o main syscpp/util.cpp syscpp/main.cpp syscpp/cellgrid.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/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

@@ -1,150 +0,0 @@
#include "luastack.hpp"
#include "cellgrid.hpp"
#include "table.hpp"
#include <math.h>
#include <cfloat>
LuaDefine(cellgrid_initregistry, "a") {
LuaVar grid;
LuaStack LS(L, grid);
LS.getfield(grid, LuaRegistry, "cellgrid_cells");
if (LS.isnil(grid)) {
LS.newtable(grid);
LS.setfield(LuaRegistry, "cellgrid_cells", grid);
}
return LS.result();
}
LuaDefine(cellgrid_addplane, "c") {
LuaArg grid, plane;
LuaVar planegrid;
LuaStack LS(L, grid, plane, planegrid);
// Get the grid.
if (LS.isnil(grid)) LS.getfield(grid, LuaRegistry, "cellgrid_cells");
LS.checktable(grid);
// Check for the plane. If not present, add it.
LS.checkstring(plane);
LS.rawget(planegrid, grid, plane);
if (LS.isnil(planegrid)) {
LS.rawset(grid, plane, LuaNewTable);
}
return LS.result();
}
#define CELL_SCALE 10.0
#define CELL_INVALID 0
LuaDefine(cellgrid_scale, "c") {
LuaRet scale;
LuaStack LS(L, scale);
LS.set(scale, CELL_SCALE);
return LS.result();
}
lua_Number calc_cellid(lua_Number x, lua_Number y, lua_Number z) {
lua_Number cellx = floor(x / CELL_SCALE);
lua_Number celly = floor(y / CELL_SCALE);
lua_Number cellz = floor(z / CELL_SCALE);
if ((cellx > 32767)||(celly > 32767)||(cellz > 32767)) {
return CELL_INVALID;
}
if ((cellx < -32767)||(celly < -32767)||(cellz < -32767)) {
return CELL_INVALID;
}
int64_t icellx = int64_t(cellx) & 0xFFFF;
int64_t icelly = int64_t(celly) & 0xFFFF;
int64_t icellz = int64_t(cellz) & 0xFFFF;
return lua_Number(0x0001000000000000 | (icellx << 32) | (icelly << 16) | (icellz << 0));
}
LuaDefine(cellgrid_cellid, "c") {
LuaArg x, y, z;
LuaRet cellid;
LuaStack LS(L, x, y, z, cellid);
lua_Number nx = LS.cknumber(x);
lua_Number ny = LS.cknumber(y);
lua_Number nz = LS.cknumber(z);
LS.set(cellid, calc_cellid(nx, ny, nz));
return LS.result();
}
LuaDefine(cellgrid_setpos, "c") {
LuaArg grid, sprite, x, y, z, nplane;
LuaVar smt, oplane, planegrid, cell;
LuaStack LS(L, grid, sprite, x, y, z, nplane, smt, oplane, planegrid, cell);
// Get the grid.
if (LS.isnil(grid)) LS.getfield(grid, LuaRegistry, "cellgrid_cells");
LS.checktable(grid);
// Calculate the new plane and cell ID.
lua_Number nx, ny, nz;
nx = LS.cknumber(x);
ny = LS.cknumber(y);
nz = LS.cknumber(z);
if (!LS.isnil(nplane)) LS.checkstring(nplane);
lua_Number ncellid = calc_cellid(nx, ny, nz);
// Check that the new plane is an existing plane.
LS.rawget(planegrid, grid, nplane);
if (!LS.istable(planegrid)) {
luaL_error(L, "invalid plane");
}
// Get the sprite metatable.
LS.checktable(sprite);
LS.getmetatable(smt, sprite);
LS.checktable(smt);
// Get the sprite's old plane and cell ID.
lua_Number ox, oy, oz;
LS.getfield(LuaAcceptNil(ox), smt, "x");
LS.getfield(LuaAcceptNil(oy), smt, "y");
LS.getfield(LuaAcceptNil(oz), smt, "z");
LS.getfield(oplane, smt, "plane");
lua_Number ocellid = calc_cellid(ox, oy, oz);
// The sprite 'moved' if it's in a new cell.
bool moved = (!LS.equal(nplane, oplane)) || (ncellid != ocellid);
// Change the sprite position.
LS.rawset(smt, "x", x);
LS.rawset(smt, "y", y);
LS.rawset(smt, "z", z);
LS.rawset(smt, "plane", nplane);
// Remove sprite from the old cell.
if (moved && LS.isstring(oplane) && (ocellid != CELL_INVALID)) {
LS.rawget(planegrid, grid, oplane);
if (LS.istable(planegrid)) {
LS.rawget(cell, planegrid, ocellid);
if (LS.istable(cell)) {
LS.call(LuaDiscard, table_findremove, cell, sprite);
if (LS.isemptytable(cell)) {
LS.rawset(planegrid, ocellid, LuaNil);
}
}
}
}
// Insert sprite into the new cell
if (moved && LS.isstring(nplane) && (ncellid != CELL_INVALID)) {
LS.rawget(planegrid, grid, nplane);
if (LS.istable(planegrid)) {
LS.rawget(cell, planegrid, ncellid);
if (!LS.istable(cell)) {
LS.newtable(cell);
LS.rawset(planegrid, ncellid, cell);
}
LS.call(table_append, cell, sprite);
}
}
return LS.result();
}
int cellgrid_scanradius(lua_State *L);
int cellgrid_scanregion(lua_State *L);

View File

@@ -1,38 +0,0 @@
//
// Cellgrid: stores sprites in a grid.
//
// To create a grid, just create an empty table.
// In actual usage, the cellgrid will be stored in the registry
// in the field "cellgrid_cells". If you pass grid=nil to
// any of these routines, the system will automatically use
// the table in the registry.
//
// For purposes of this module, a "sprite" is any table
// having a metatable. The (X,Y,Z,Plane) of the sprite are
// stored as hidden fields inside the metatable.
//
// We preserve the invariant that sprites whose plane is nil
// are not stored in the grid. All sprites whose plane
// is a string are stored in the grid.
//
// When creating a new sprite, initialize plane to nil. That
// fits the invariant that sprites whose plane is nil are
// not stored in the grid. If you ever want to change the
// sprite's position, use cellgrid_setpos: that will
// preserve the invariant.
//
#ifndef CELLGRID_HPP
#define CELLGRID_HPP
#include "luastack.hpp"
int cellgrid_initgrid(lua_State *L);
int cellgrid_addplane(lua_State *L);
int cellgrid_setpos(lua_State *L);
int cellgrid_scanradius(lua_State *L);
int cellgrid_scanregion(lua_State *L);
#endif // CELLGRID_HPP

View File

@@ -1,237 +1,231 @@
#include "luastack.hpp"
#include "table.hpp"
#include "idalloc.hpp"
#include <iostream>
#include <cassert>
LuaDefine(idalloc_initmaster, "c") {
LuaArg allocator, queuefill;
LuaVar salvqueue;
LuaStack LS(L, allocator, queuefill, salvqueue);
LuaDefineType(IdGlobalPool);
LuaDefineType(IdPlayerPool);
// Use the registry by default.
if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry);
LS.checktable(allocator);
LS.checknumber(queuefill);
LS.call(salvqueue, queue_create);
LS.setfield(allocator, "id_salvaged", salvqueue);
LS.setfield(allocator, "id_nextbatch", 0x0001000000000000);
LS.setfield(allocator, "id_nextid", 0x0010000000000000);
LS.setfield(allocator, "id_queuefill", queuefill);
return LS.result();
static int64_t nthbatch(int64_t n) {
return int64_t(0x0001000000000000) + n*256;
}
LuaDefine(idalloc_initsynch, "c") {
LuaArg allocator, queuefill;
LuaStack LS(L, allocator, queuefill);
// Use the registry by default.
if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry);
LS.checktable(allocator);
LS.checknumber(queuefill);
LS.setfield(allocator, "id_salvaged", LuaNil);
LS.setfield(allocator, "id_nextbatch", LuaNil);
LS.setfield(allocator, "id_nextid", 0x001E000000000000);
LS.setfield(allocator, "id_queuefill", queuefill);
return LS.result();
static bool ranges_equal(const std::deque<int64_t> &dq, int64_t a, int64_t b, int64_t c) {
if (dq.size() != 3) return false;
if (dq[0] != a) return false;
if (dq[1] != b) return false;
if (dq[2] != c) return false;
return true;
}
LuaDefine(idalloc_refill, "c") {
LuaArg allocator, queue;
LuaVar salvaged, batch;
lua_Integer qhead, qtail, shead, stail, nextb, queuefill;
LuaStack LS(L, allocator, queue, salvaged, batch);
// Use the registry by default.
if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry);
// Get salvaged batch table. If there is none, we're in donotpredict mode.
LS.getfield(salvaged, allocator, "id_salvaged");
if (LS.isnil(salvaged)) {
return LS.result();
}
// Get the head and tail of the queue.
LS.checktable(queue);
LS.getfield(qhead, queue, "head");
LS.getfield(qtail, queue, "tail");
lua_Integer oqhead = qhead;
// Try using salvaged batches.
LS.getfield(queuefill, allocator, "id_queuefill");
if (qhead - qtail < queuefill) {
// Grab the head and tail of the salvaged batches.
LS.checktable(salvaged);
LS.getfield(shead, salvaged, "head");
LS.getfield(stail, salvaged, "tail");
// Get salvaged batches where possible.
while ((stail < shead) && (qhead - qtail < queuefill)) {
LS.rawget(batch, salvaged, stail);
LS.rawset(salvaged, stail, LuaNil);
stail += 1;
LS.checknumber(batch);
LS.rawset(queue, qhead, batch);
qhead += 1;
}
// Update the head and tail of the salvaged batches.
LS.setfield(salvaged, "head", shead);
LS.setfield(salvaged, "tail", stail);
}
// Try using newly-created batches.
if (qhead - qtail < queuefill) {
// Grab the next batch counter.
LS.getfield(nextb, allocator, "id_nextbatch");
// Get newly-allocated batches to fill the rest.
while (qhead - qtail < queuefill) {
LS.rawset(queue, qhead, nextb);
nextb += 256;
qhead += 1;
}
// Update the counter in the registry.
LS.setfield(allocator, "id_nextbatch", nextb);
}
// Update the head of the queue.
if (oqhead != qhead) {
LS.setfield(queue, "head", qhead);
}
return LS.result();
IdGlobalPool::IdGlobalPool() {
salvaged_.clear();
next_batch_ = 0;
next_id_ = 0;
queue_fill_ = 10;
}
LuaDefine(idalloc_unqueue, "c") {
LuaArg allocator, queue;
LuaVar salvaged, batch;
lua_Integer qhead, qtail, shead, stail;
LuaStack LS(L, allocator, queue, salvaged, batch);
// Use the registry by default.
if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry);
// Get the head and tail of the queue.
LS.checktable(queue);
LS.getfield(qhead, queue, "head");
LS.getfield(qtail, queue, "tail");
// Grab the table of salvaged batches.
// If there is none, we're in donotpredict mode. In that
// case, just empty the queue and dump the batches.
LS.getfield(salvaged, allocator, "id_salvaged");
if (LS.isnil(salvaged)) {
while (qhead > qtail) {
LS.rawset(queue, qtail, LuaNil);
qtail += 1;
}
LS.setfield(queue, "tail", qtail);
return LS.result();
}
// We're in master mode. Transfer batches from the queue
// into the salvaged batches table.
LS.checktable(salvaged);
LS.getfield(shead, salvaged, "head");
LS.getfield(stail, salvaged, "tail");
while (qhead > qtail) {
LS.rawget(batch, queue, qtail);
LS.rawset(queue, qtail, LuaNil);
qtail += 1;
LS.checknumber(batch);
LS.rawset(salvaged, shead, batch);
shead += 1;
}
// Update the queue pointers.
LS.setfield(queue, "tail", qtail);
LS.setfield(salvaged, "head", shead);
return LS.result();
IdGlobalPool::~IdGlobalPool() {
}
LuaDefine(idalloc_preparethread, "c") {
LuaArg queue, thread;
LuaVar batch;
LuaStack LS(L, queue, thread, batch);
// Get the thread.
lua_State *TH = LS.ckthread(thread);
// Pop a batch from the queue. If there's nothing in
// the queue, just leave the thread unprepped.
LS.call(batch, queue_pop, queue);
if (LS.isnil(batch)) {
return LS.result();
}
// Store the batch into the thread.
lua_setnextid(TH, LS.ckinteger(batch));
return LS.result();
void IdGlobalPool::init_master(int qf) {
salvaged_.clear();
next_batch_ = 0x0001000000000000;
next_id_ = 0x0010000000000000;
queue_fill_ = qf;
}
LuaDefine(idalloc_salvagethread, "c") {
LuaArg allocator, thread;
LuaVar salvaged;
LuaStack LS(L, allocator, thread, salvaged);
lua_State *TH = LS.ckthread(thread);
lua_Integer idbatch = lua_getnextid(TH);
lua_setnextid(TH, 0);
if (idbatch == 0) {
return LS.result();
}
if ((idbatch & 0xFF) >= 128) {
return LS.result();
}
// Use the registry by default.
if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry);
// Push the batch onto the queue of salvaged batches.
// If the table of salvaged batches is nil, we're in donotpredict
// mode. In that case, don't bother salvaging.
LS.getfield(salvaged, allocator, "id_salvaged");
if (LS.isnil(salvaged)) {
return LS.result();
}
LS.call(queue_push, salvaged, idbatch);
return LS.result();
void IdGlobalPool::init_synch(int qf) {
salvaged_.clear();
next_batch_ = 0;
next_id_ = 0x001E000000000000;
queue_fill_ = qf;
}
LuaDefine(idalloc_allocid, "c") {
LuaArg allocator;
LuaRet result;
LuaStack LS(L, allocator, result);
int64_t IdGlobalPool::get_one() {
return next_id_++;
}
lua_Integer id = lua_getnextid(L);
if (id == 0) {
// Use the registry by default.
if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry);
LS.getfield(id, allocator, "id_nextid");
LS.setfield(allocator, "id_nextid", id + 1);
int64_t IdGlobalPool::get_batch() {
int64_t batch;
if (salvaged_.empty()) {
if (next_batch_ == 0) {
batch = 0;
} else {
lua_Integer next = id + 1;
if ((next & 0xFF) == 0) {
next = 0;
batch = next_batch_;
next_batch_ += 256;
}
lua_setnextid(L, next);
} else {
batch = salvaged_.back();
salvaged_.pop_back();
}
LS.set(result, id);
return LS.result();
return batch;
}
LuaDefine(idalloc_getthreadbatch, "c") {
LuaArg thread;
LuaRet result;
LuaStack LS(L, thread, result);
lua_State *TH = LS.ckthread(thread);
lua_Integer id = lua_getnextid(TH);
LS.set(result, id);
return LS.result();
void IdGlobalPool::salvage(int64_t batch) {
if (batch == 0) return;
if (next_batch_ == 0) return;
if ((batch & 0xFF) >= 128) return;
salvaged_.push_back(batch);
}
void IdGlobalPool::salvage_thread(lua_State *L) {
salvage(lua_getnextid(L));
lua_setnextid(L, 0);
}
int64_t IdGlobalPool::alloc_id_for_thread(lua_State *L) {
int64_t batch = lua_getnextid(L);
if (batch != 0) {
int64_t id = batch;
batch += 1;
if ((batch & 0xFF) == 0) batch = 0;
lua_setnextid(L, batch);
return id;
} else {
return get_one();
}
}
IdPlayerPool::IdPlayerPool(IdGlobalPool *gp) {
global_ = gp;
}
IdPlayerPool::~IdPlayerPool() {
}
void IdPlayerPool::purge() {
ranges_.clear();
}
void IdPlayerPool::refill() {
while (int(ranges_.size()) < global_->queue_fill()) {
ranges_.push_back(global_->get_batch());
}
}
void IdPlayerPool::unqueue() {
while (!ranges_.empty()) {
global_->salvage(ranges_.front());
ranges_.pop_front();
}
}
int64_t IdPlayerPool::get_batch() {
while (int(ranges_.size()) < global_->queue_fill() + 1) {
ranges_.push_back(global_->get_batch());
}
int64_t batch = ranges_.front();
ranges_.pop_front();
return batch;
}
void IdPlayerPool::salvage_thread(lua_State *L) {
global_->salvage_thread(L);
}
void IdPlayerPool::prepare_thread(lua_State *L) {
global_->salvage_thread(L);
lua_setnextid(L, get_batch());
}
LuaDefine(cunittests_idalloc, "c") {
IdGlobalPool gp;
IdPlayerPool pp(&gp);
// Synchronous pools produce IDs starting at 0x001E000000000000
gp.init_synch(3);
assert(gp.get_one() == 0x001E000000000000);
assert(gp.get_one() == 0x001E000000000001);
assert(gp.get_one() == 0x001E000000000002);
// Master pools produce IDs starting at 0x0010000000000000
gp.init_master(3);
assert(gp.get_one() == 0x0010000000000000);
assert(gp.get_one() == 0x0010000000000001);
assert(gp.get_one() == 0x0010000000000002);
// Synchronous pools produce only null batches.
gp.init_synch(3);
assert(gp.get_batch() == 0);
assert(gp.get_batch() == 0);
gp.salvage(nthbatch(5));
assert(gp.get_batch() == 0);
// Simple fetch batches with a few salvages.
gp.init_master(3);
assert(gp.get_batch() == nthbatch(0));
assert(gp.get_batch() == nthbatch(1));
assert(gp.get_batch() == nthbatch(2));
gp.salvage(nthbatch(182));
gp.salvage(nthbatch(183));
assert(gp.get_batch() == nthbatch(183));
assert(gp.get_batch() == nthbatch(182));
assert(gp.get_batch() == nthbatch(3));
// Salvage of a zero-batch does nothing.
gp.init_master(3);
assert(gp.get_batch() == nthbatch(0));
assert(gp.get_batch() == nthbatch(1));
gp.salvage(0);
assert(gp.get_batch() == nthbatch(2));
// Salvage of a partial batch.
gp.init_master(3);
assert(gp.get_batch() == nthbatch(0));
assert(gp.get_batch() == nthbatch(1));
gp.salvage(nthbatch(142) + 10);
assert(gp.get_batch() == nthbatch(142) + 10);
assert(gp.get_batch() == nthbatch(2));
// Salvage of a half-empty batch does nothing.
gp.init_master(3);
assert(gp.get_batch() == nthbatch(0));
assert(gp.get_batch() == nthbatch(1));
gp.salvage(nthbatch(142) + 145);
assert(gp.get_batch() == nthbatch(2));
// Test refill from master.
pp.purge();
gp.init_master(3);
pp.refill();
assert(ranges_equal(pp.ranges_, nthbatch(0), nthbatch(1), nthbatch(2)));
// Now test that get_batch keeps the pool filled from master.
assert(pp.get_batch() == nthbatch(0));
assert(ranges_equal(pp.ranges_, nthbatch(1), nthbatch(2), nthbatch(3)));
// Test unqueueing the batches.
assert(gp.get_batch() == nthbatch(4));
assert(gp.get_batch() == nthbatch(5));
pp.unqueue();
assert(gp.get_batch() == nthbatch(3));
assert(gp.get_batch() == nthbatch(2));
assert(gp.get_batch() == nthbatch(1));
assert(gp.get_batch() == nthbatch(6));
// Try preparing a thread and salvaging a thread.
pp.purge();
gp.init_master(3);
lua_setnextid(L, 0);
pp.prepare_thread(L);
assert(lua_getnextid(L) == nthbatch(0));
lua_setnextid(L, 0);
pp.prepare_thread(L);
assert(lua_getnextid(L) == nthbatch(1));
// Try salvaging the pool from the thread.
pp.salvage_thread(L);
assert(lua_getnextid(L) == 0);
assert(gp.get_batch() == nthbatch(1));
// Allocate IDs from inside a thread.
lua_setnextid(L, 0xFD);
gp.init_master(3);
assert(gp.alloc_id_for_thread(L) == 0xFD);
assert(gp.alloc_id_for_thread(L) == 0xFE);
assert(gp.alloc_id_for_thread(L) == 0xFF);
assert(gp.alloc_id_for_thread(L) == 0x0010000000000000);
assert(lua_getnextid(L) == 0);
return 0;
}

View File

@@ -39,7 +39,7 @@
//
// idalloc.initmaster() - reinitialize the ID allocator in master mode.
// idalloc.initsynch() - reinitialize the ID allocator in synchronous mode.
// idalloc.setqueuefill() - override the default queue fill level.
// 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
@@ -50,15 +50,50 @@
#ifndef IDALLOC_HPP
#define IDALLOC_HPP
#include <cstdint>
#include <vector>
#include <deque>
#include "luastack.hpp"
int idalloc_initmaster(lua_State *L);
int idalloc_initsynch(lua_State *L);
int idalloc_refill(lua_State *L);
int idalloc_unqueue(lua_State *L);
int idalloc_preparethread(lua_State *L);
int idalloc_salvagethread(lua_State *L);
int idalloc_allocid(lua_State *L);
class IdGlobalPool {
private:
std::vector<int64_t> 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<int64_t> 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

View File

@@ -1,4 +1,5 @@
#include "luastack.hpp"
#include <iostream>
LuaSpecial LuaRegistry(LUA_REGISTRYINDEX);
LuaSpecial LuaGlobals(LUA_GLOBALSINDEX);
@@ -6,7 +7,48 @@ LuaNilMarker LuaNil;
LuaNewTableMarker LuaNewTable;
LuaDiscardMarker LuaDiscard;
LuaFunctionReg *LuaFunctionReg::LuaFunctionRegistry;
void LuaStack::make_tagged_pointer(LuaSlot target, void *ptr, LuaTypeTag tag, LuaDeleterFn del) {
TaggedPointer *tp = (TaggedPointer*)lua_newuserdata(L_, sizeof(TaggedPointer));
tp->ptr = ptr;
tp->tag = tag;
tp->del = del;
lua_pushlightuserdata(L_, (void*)tag);
lua_rawget(L_, LUA_REGISTRYINDEX);
if (lua_isnil(L_, -1)) luaL_error(L_, "type not registered with LuaDefineType");
lua_setmetatable(L_, -2);
lua_replace(L_, target);
}
int LuaStack::collect_tagged_pointer(lua_State *L) {
LuaStack::TaggedPointer *p = (LuaStack::TaggedPointer*)lua_touserdata(L, 1);
if (p==0) {
luaL_error(L, "lua deleter function received a non-userdata");
}
if (p->ptr == 0) {
luaL_error(L, "lua object already deleted");
}
p->del(p->ptr);
p->ptr = 0;
return 0;
}
void LuaStack::register_all_userdata(lua_State *L) {
LuaVar tab, lud;
LuaStack LS(L, tab, lud);
auto regs = LuaFunctionReg::all();
for (const LuaFunctionReg *r : regs) {
const std::string &name = r->get_name();
lua_CFunction tag = r->get_func();
std::string mode = r->get_mode();
if (mode.find('t') != std::string::npos) { // Register type
LS.newtable(tab);
LS.setfield(tab, "type", name);
LS.setfield(tab, "__gc", collect_tagged_pointer);
LS.setlightuserdata(lud, (void *)tag);
LS.rawset(LuaRegistry, lud, tab);
}
}
}
LuaFunctionReg::LuaFunctionReg(const char *m, const char *n, lua_CFunction f) {
mode_ = m;
@@ -24,6 +66,9 @@ LuaFunctionReg::List LuaFunctionReg::all() {
return result;
}
LuaFunctionReg *LuaFunctionReg::LuaFunctionRegistry;
void LuaStack::count_slots_finalize(int narg, int nvar, int nret) {
narg_ = narg;
nret_ = nret;
@@ -151,6 +196,12 @@ void LuaStack::newtable(LuaSlot target) const {
lua_replace(L_, target);
}
void LuaStack::setlightuserdata(LuaSlot target, void *p) const {
lua_pushlightuserdata(L_, p);
lua_replace(L_, target);
}
void LuaStack::check_nret(int xnret, int otop, int nret) const {
int ntop = lua_gettop(L_);
if ((nret != xnret)||(ntop != otop + xnret)) {

View File

@@ -222,6 +222,12 @@ inline LuaAcceptNilNumber &LuaAcceptNil(lua_Number &x) { return *(LuaAcceptNilNu
inline LuaAcceptNilInteger &LuaAcceptNil(lua_Integer &x) { return *(LuaAcceptNilInteger *)(&x); }
inline LuaAcceptNilString &LuaAcceptNil(std::string &x) { return *(LuaAcceptNilString *)(&x); }
using LuaDeleterFn = void (*)(void *);
using LuaTypeTag = lua_CFunction;
template<typename T>
int LuaTypeTagValue(lua_State *L) { return 0; }
class LuaStack {
private:
int narg_;
@@ -296,6 +302,7 @@ private:
void push_any_value(double s) const { lua_pushnumber(L_, s); }
void push_any_value(int s) const { lua_pushinteger(L_, s); }
void push_any_value(lua_Integer s) const { lua_pushinteger(L_, s); }
void push_any_value(lua_CFunction s) const { lua_pushcfunction(L_, s); }
void push_any_value(bool b) const { lua_pushboolean(L_, b ? 1:0); }
// Pop any value off the stack, by type.
@@ -354,6 +361,23 @@ private:
// original stack top, and number of declared return values.
void check_nret(int xnret, int otop, int nret) const;
// Tagged pointers: we expect all userdata to be tagged pointers.
// This starts with a void pointer, then a type tag that identifies the
// underlying C++ type, then a deleter function. In addition, there will
// be a metatable that contains the type name as a string and a collect
// function.
struct TaggedPointer {
void *ptr;
LuaTypeTag tag;
LuaDeleterFn del;
};
static int collect_tagged_pointer(lua_State *L);
void make_tagged_pointer(LuaSlot target, void *ptr, LuaTypeTag tag, LuaDeleterFn del);
template<typename T>
static void delete_pointer(void *p) { delete (T*)p; }
static void do_not_delete(void *p) { }
public:
template<class... SS>
LuaStack(lua_State *L, SS & ... stackslots) {
@@ -401,6 +425,30 @@ public:
void checknometa(LuaSlot index) const;
void newtable(LuaSlot target) const;
void setlightuserdata(LuaSlot target, void *p) const;
template <typename T>
void newpointer(LuaSlot target, T *ptr, bool autodel) {
make_tagged_pointer(target, ptr, LuaTypeTagValue<T>, autodel ? delete_pointer<T> : do_not_delete);
}
template <typename T>
T *touserdata(LuaSlot target) {
TaggedPointer *tp = (TaggedPointer*)lua_touserdata(L_, target);
LuaTypeTag tag = LuaTypeTagValue<T>;
if ((tp == 0) || (tp->tag != tag)) return 0;
return (T*)(tp->ptr);
}
template <typename T>
T *ckuserdata(LuaSlot target) {
TaggedPointer *tp = (TaggedPointer*)lua_touserdata(L_, target);
LuaTypeTag tag = LuaTypeTagValue<T>;
if ((tp == 0) || (tp->tag != tag) || (tp->ptr==0)) {
luaL_error(L_, "wrong userdata type");
}
return (T*)(tp->ptr);
}
int next(LuaSlot tab, LuaSlot key, LuaSlot value) const;
bool isemptytable(LuaSlot s) const;
@@ -448,8 +496,11 @@ public:
void call(T&... args) {
call_cfunction<0>(lua_gettop(L_), args...);
}
static void register_all_userdata(lua_State *L);
};
class LuaFunctionReg {
private:
const char *mode_;
@@ -470,11 +521,15 @@ public:
lua_CFunction get_func() const { return func_; }
};
#define LuaDefine(name, mode) \
int name(lua_State *L); \
LuaFunctionReg reg_##name(mode, #name, name); \
int name(lua_State *L)
#define LuaDefineType(name) \
LuaFunctionReg regt_##name("t", #name, LuaTypeTagValue<name>)
#endif // LUASTACK_HPP

View File

@@ -180,6 +180,8 @@ static int pmain(lua_State *L)
LUAJIT_VERSION_SYM(); /* Linker-enforced version check. */
LuaStack::register_all_userdata(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.

315
luprex/syscpp/planemap.cpp Normal file
View File

@@ -0,0 +1,315 @@
#include <cmath>
#include <algorithm>
#include <cassert>
#include "luastack.hpp"
#include "planemap.hpp"
#include "util.hpp"
// Cell X, Y coordinates are packed such that they have 24 bits for X and Y.
// A cell is 10 Meters square.
// Cell ID zero is used to represent an invalid position.
//
#define CELL_LIMIT 0x7FFFFF
#define CELL_SCALE 10.0
#define CELL_INVALID 0
// Round a float and return as integer. Clamp result to the specified range.
static int round_and_clamp(double x, int lo, int hi) {
x = round(x);
if (x < lo) return lo;
if (x > hi) return hi;
return int(x);
}
// A cell range is inclusive.
struct CellRange {
int xlo;
int ylo;
int xhi;
int yhi;
bool equal(int xl, int yl, int xh, int yh) {
return ((xlo==xl)&&(ylo==yl)&&(xh==xhi)&&(yh==yhi));
}
};
// Get the range of cells that includes everything in the rectangle.
//
// Gracefully handles the case that some or all of the rectangle is off
// the map: in that case, returns exactly the valid cells and not the
// invalid ones.
//
static CellRange rect_cell_range(double x1, double y1, double x2, double y2) {
CellRange result;
result.xlo = round_and_clamp(x1 / CELL_SCALE, -CELL_LIMIT, CELL_LIMIT + 1);
result.ylo = round_and_clamp(y1 / CELL_SCALE, -CELL_LIMIT, CELL_LIMIT + 1);
result.xhi = round_and_clamp(x2 / CELL_SCALE, -CELL_LIMIT - 1, CELL_LIMIT);
result.yhi = round_and_clamp(y2 / CELL_SCALE, -CELL_LIMIT - 1, CELL_LIMIT);
return result;
}
static int64_t cell_id(int64_t cellx, int64_t celly) {
int64_t icellx = cellx & 0xFFFFFF;
int64_t icelly = celly & 0xFFFFFF;
return 0x0001000000000000 | (icellx << 24) | (icelly << 0);
}
// Get the cell ID of the specified point, or CELL_INVALID if the point is off the map.
static int64_t point_cell_id(double x, double y) {
double cellx = round(x / CELL_SCALE);
double celly = round(y / CELL_SCALE);
if ((cellx < -CELL_LIMIT) || (celly < -CELL_LIMIT) || (cellx > CELL_LIMIT) || (celly > CELL_LIMIT)) {
return CELL_INVALID;
}
return cell_id(int64_t(cellx), int64_t(celly));
}
void PlaneMap::remove(const std::string &plane, int64_t cellid, PlaneItem *client) {
if (cellid == CELL_INVALID) {
return;
}
if (plane == "") {
return;
}
auto piter = planes_.find(plane);
if (piter == planes_.end()) {
return;
}
Plane &p = piter->second;
auto liter = p.find(cellid);
if (liter == p.end()) {
return;
}
EltVec &l = liter->second;
l.erase(std::remove(l.begin(), l.end(), client), l.end());
if (l.empty()) {
p.erase(liter);
}
if (p.empty()) {
planes_.erase(piter);
}
}
void PlaneMap::insert(const std::string &plane, int64_t cellid, PlaneItem *client) {
if (cellid == CELL_INVALID) {
return;
}
if (plane == "") {
return;
}
Plane &p = planes_[plane];
EltVec &l = p[cellid];
l.push_back(client);
}
void PlaneItem::set_pos(const std::string &plane, double x, double y, double z) {
int64_t old_cell = point_cell_id(x_, y_);
int64_t new_cell = point_cell_id(x, y);
// Update the grid.
if (grid_ != 0) {
if ((plane_ != plane) || (old_cell != new_cell)) {
grid_->remove(plane_, old_cell, this);
grid_->insert(plane, new_cell, this);
}
}
// Update the client position.
plane_ = plane;
x_ = x;
y_ = y;
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);
}
}
PlaneItem::PlaneItem() : grid_(NULL), x_(0.0), y_(0.0), z_(0.0) {
}
PlaneMap::PlaneMap() {
}
PlaneItem::~PlaneItem() {
use_map(NULL);
}
PlaneMap::~PlaneMap() {
for (const auto &p : planes_) {
for (const auto &l : p.second) {
for (PlaneItem *i : l.second) {
i->grid_ = NULL;
}
}
}
}
PlaneMap::EltVec PlaneMap::get_cell(const std::string &plane, int64_t cellid) const {
PlaneMap::EltVec result;
auto piter = planes_.find(plane);
if (piter != planes_.end()) {
const Plane &p = piter->second;
auto liter = p.find(cellid);
if (liter != p.end()) {
result = liter->second;
}
}
return result;
}
int PlaneMap::total_cells() const {
int total = 0;
for (const auto &p : planes_) {
total += p.second.size();
}
return total;
}
PlaneMap::EltVec PlaneMap::scan_radius(const std::string &plane, double x, double y, double radius) const {
PlaneMap::EltVec result;
auto piter = planes_.find(plane);
if (piter != planes_.end()) {
const Plane &p = piter->second;
CellRange range = rect_cell_range(x - radius, y - radius, x + radius, y + radius);
double radsq = radius*radius;
for (int cy = range.ylo; cy <= range.yhi; cy++) {
for (int cx = range.xlo; cx <= range.xhi; cx++) {
auto liter = p.find(cell_id(cx, cy));
if (liter != p.end()) {
for (PlaneItem *client : liter->second) {
if (util::distance_squared(client->x(), client->y(), x, y) <= radsq) {
result.push_back(client);
}
}
}
}
}
}
return result;
}
LuaDefine(cunittests_planemap, "c") {
double SC = CELL_SCALE;
double E = CELL_SCALE * 0.4;
int LO = -CELL_LIMIT;
int HI = CELL_LIMIT;
PlaneMap pm;
PlaneItem pia, pib;
PlaneMap::EltVec elts;
// Simple test.
assert(rect_cell_range(-7*SC, -15*SC, 87*SC, 21*SC).equal(-7, -15, 87, 21));
// Adding an epsilon doesn't change result, if epsilon is less than half of cell scale.
assert(rect_cell_range(-7*SC+E, -15*SC+E, 87*SC-E, 21*SC-E).equal(-7, -15, 87, 21));
// Rectangle that crosses the high end of the range.
assert(rect_cell_range((HI-7)*SC, (HI-5)*SC, (HI+3)*SC, (HI+6)*SC).equal(HI-7, HI-5, HI, HI));
// Rectangle that exceeds the high end of the range.
assert(rect_cell_range((HI+7)*SC, (HI+5)*SC, (HI+15)*SC, (HI+12)*SC).equal(HI+1, HI+1, HI, HI));
// Rectangle that crosses the low end of the range.
assert(rect_cell_range((LO-7)*SC, (LO-5)*SC, (LO+3)*SC, (LO+4)*SC).equal(LO, LO, LO+3, LO+4));
// Rectangle that exceeds the low end of the range.
assert(rect_cell_range((LO-15)*SC, (LO-17)*SC, (LO-7)*SC, (LO-5)*SC).equal(LO, LO, LO-1, LO-1));
// Simple test.
assert(point_cell_id(-7*SC, 15*SC) == cell_id(-7, 15));
// Adding epsilon doesn't change the result if less than half cell scale.
assert(point_cell_id(-7*SC+E, 15*SC+E) == cell_id(-7, 15));
// Right at the top edge of the range.
assert(point_cell_id(HI*SC, HI*SC) == cell_id(HI, HI));
// Right at the bottom edge of the range.
assert(point_cell_id(LO*SC, LO*SC) == cell_id(LO, LO));
// Beyond various edges.
assert(point_cell_id((LO-1)*SC, 0) == CELL_INVALID);
assert(point_cell_id((HI+1)*SC, 0) == CELL_INVALID);
assert(point_cell_id(0, (LO-1)*SC) == CELL_INVALID);
assert(point_cell_id(0, (HI+1)*SC) == CELL_INVALID);
// Test using the insert function.
pm.clear();
assert(pm.total_cells() == 0);
pm.insert("foo", 12345, &pia);
assert(pm.total_cells() == 1);
pm.insert("foo", 12345, &pib);
assert(pm.total_cells() == 1);
elts = pm.get_cell("foo", 12345);
assert(elts.size() == 2);
assert(elts[0] == &pia);
assert(elts[1] == &pib);
// Test the remove function.
pm.remove("foo", 12345, &pia);
assert(pm.total_cells() == 1);
elts = pm.get_cell("foo", 12345);
assert(elts.size() == 1);
assert(elts[0] == &pib);
pm.remove("foo", 12345, &pib);
assert(pm.total_cells() == 0);
// Test the insert function on an invalid plane.
pm.clear();
pm.insert("", 12345, &pia);
pm.insert("", 12345, &pib);
assert(pm.total_cells() == 0);
// Test the insert function on an invalid cell.
pm.clear();
pm.insert("foo", CELL_INVALID, &pia);
pm.insert("foo", CELL_INVALID, &pib);
assert(pm.total_cells() == 0);
// Try moving a plane item around without it being connected to a grid.
pia.set_pos("foo", 3, 4, 5);
assert(pia.plane() == "foo");
assert(pia.x() == 3.0);
assert(pia.y() == 4.0);
assert(pia.z() == 5.0);
// Attach pia to the grid. This should record it.
pm.clear();
pia.use_map(&pm);
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);
assert(pm.total_cells() == 0);
// Reattach pia to the grid, then move it.
pia.use_map(&pm);
assert(pm.total_cells() == 1);
pia.set_pos("bar", 1000.0, 1000.0, 0.0);
assert(pm.total_cells() == 1);
elts = pm.get_cell("bar", point_cell_id(1000.0, 1000.0));
assert(elts.size() == 1);
assert(elts[0] == &pia);
// Insert the four elements, then test the scan function.
pib.use_map(&pm);
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);
elts = pm.scan_radius("bar", 1000.0, 1000.0, 99.9);
assert(elts.size() == 1);
elts = pm.scan_radius("bar", 1000.0, 1000.0, 100.0);
assert(elts.size() == 2);
return 0;
}

View File

@@ -0,0 +1,91 @@
//
// PLANEMAP
//
// Stores a map of the items that are on each plane. Doesn't
// store terrain, only items.
//
// 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.
//
// 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.
//
// 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.
//
// If you use the empty string as a plane name, a special case is
// triggered: you're "nowhere." Radius scans can never find items
// whose plane is the empty string.
//
// INHERITANCE
//
// 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.
//
// MEMORY MANAGEMENT
//
// The PlaneMap does not own the PlaneItems. You need to create,
// destroy, and manage the PlaneItems yourself.
//
#ifndef PLANEMAP_HPP
#define PLANEMAP_HPP
#include <cstdint>
#include <vector>
#include <map>
class PlaneMap;
class PlaneItem {
friend class PlaneMap;
private:
PlaneMap *grid_;
std::string plane_;
double x_, y_, z_;
public:
PlaneItem();
~PlaneItem();
const std::string &plane() const { return plane_; }
const double x() const { return x_; }
const double y() const { return y_; }
const double z() const { return z_; }
void use_map(PlaneMap *map);
void set_pos(const std::string &plane, double x, double y, double z);
};
class PlaneMap {
friend class PlaneItem;
friend int cunittests_planemap(lua_State *L);
public:
using Elt = PlaneItem *;
using EltVec = std::vector<Elt>;
private:
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();
EltVec scan_radius(const std::string &plane, double x, double y, double radius) const;
};
#endif // PLANEMAP_HPP

View File

@@ -58,4 +58,45 @@ const stringvec trim_and_uncomment(const stringvec &lines) {
return result;
}
double distance_squared(double x1, double y1, double x2, double y2) {
double dx = x1 - x2;
double dy = y1 - y2;
return dx*dx + dy*dy;
}
// These are Bob Jenkins' Lookup3.
#define hash_rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
#define hash_mix(a,b,c) { \
a -= c; a ^= hash_rot(c, 4); c += b; \
b -= a; b ^= hash_rot(a, 6); a += c; \
c -= b; c ^= hash_rot(b, 8); b += a; \
a -= c; a ^= hash_rot(c,16); c += b; \
b -= a; b ^= hash_rot(a,19); a += c; \
c -= b; c ^= hash_rot(b, 4); b += a; \
}
#define hash_final(a,b,c) { \
c ^= b; c -= hash_rot(b,14); \
a ^= c; a -= hash_rot(c,11); \
b ^= a; b -= hash_rot(a,25); \
c ^= b; c -= hash_rot(b,16); \
a ^= c; a -= hash_rot(c,4); \
b ^= a; b -= hash_rot(a,14); \
c ^= b; c -= hash_rot(b,24); \
}
uint32_t hash3(uint32_t a, uint32_t b, uint32_t c) {
hash_final(a, b, c);
return c;
}
double hash_to_float(double lo, double hi, uint32_t a, uint32_t b, uint32_t c) {
double result = hash3(a, b, c); // Lossless.
result *= ((hi-lo) * (1.0 / 0xFFFFFFFF));
result += lo;
return result;
}
} // namespace util

View File

@@ -35,5 +35,14 @@ const stringvec trim_and_uncomment(const stringvec &lines);
std::string get_file_fingerprint(const std::string &path);
std::string get_file_contents(const std::string &fn);
double distance_squared(double x1, double y1, double x2, double y2);
// Return a pseudorandom number which is a hash function of A,B,C.
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);
} // namespace util
#endif // UTIL_HPP

45
luprex/syscpp/world.cpp Normal file
View File

@@ -0,0 +1,45 @@
#include "world.hpp"
#include "idalloc.hpp"
LuaDefineType(World);
World::~World() {
}
void World::init(lua_State *L) {
LuaVar world;
LuaStack LS(L, world);
LS.newpointer(world, new World, false);
LS.setfield(LuaRegistry, "world", world);
}
World *World::fetch(lua_State *L) {
LuaVar world;
LuaStack LS(L, world);
LS.getfield(world, LuaRegistry, "world");
World *w = LS.ckuserdata<World>(world);
LS.result();
return w;
}
LuaDefine(world_init, "c") {
World::init(L);
return 0;
}
LuaDefine(world_setid, "c") {
LuaArg id;
LuaStack LS(L, id);
World *w = World::fetch(L);
w->id_ = LS.ckinteger(id);
return LS.result();
}
LuaDefine(world_getid, "c") {
LuaRet id;
LuaStack LS(L, id);
World *w = World::fetch(L);
LS.set(id, w->id_);
return LS.result();
};

31
luprex/syscpp/world.hpp Normal file
View File

@@ -0,0 +1,31 @@
// Note about header file dependencies:
//
// world.hpp contains a lot of forward declarations.
// That's because it is at the bottom of the dependency stack:
// everyone includes world.hpp.
//
// However, world.cpp is at the top of the dependency stack,
// it includes everyone else.
//
#ifndef WORLD_HPP
#define WORLD_HPP
#include "luastack.hpp"
#include <memory>
class IdGlobalPool;
class World {
public:
int id_;
std::unique_ptr<IdGlobalPool> id_pool_;
World() : id_(0) {};
~World();
static void init(lua_State *L);
static World *fetch(lua_State *L);
};
#endif // WORLD_HPP

View File

@@ -5,8 +5,8 @@
utils.lua
inspect.lua
ut-misc.lua
ut-table.lua
ut-idalloc.lua
ut-globaldb.lua
ut-cellgrid.lua

View File

@@ -1,12 +1,7 @@
local ut = {}
function ut.cellid()
local sc = cellgrid.scale()
local cid = cellgrid.cellid(0,0,0)
assert(cellgrid.cellid(0,0,0) == 0x0001000000000000)
assert(cellgrid.cellid( 1*sc, 2*sc, 3*sc) == 0x0001000100020003)
assert(cellgrid.cellid(-1*sc, -2*sc, -3*sc) == 0x0001FFFFFFFEFFFD)
assert(cellgrid.cellid(10000000, 0, 0) == 0)
function ut.cellgrid()
end
rununittests(ut)

View File

@@ -1,95 +0,0 @@
local ut = {}
local function nthbatch(i)
return 0x0001000000000000 + 256 * (i - 1)
end
local function equalbatches(q, batches)
if queue.size(q) ~= #batches then
return false
end
for i = 1, #batches do
if queue.nth(q, i) ~= nthbatch(batches[i]) then
return false
end
end
return true
end
function ut.idalloc_synch()
local allocator = {}
idalloc.initsynch(allocator, 10)
assert(allocator.id_salvaged == nil)
assert(allocator.id_nextbatch == nil)
assert(allocator.id_nextid == 0x001E000000000000)
assert(allocator.id_queuefill == 10)
local q1 = queue.create()
idalloc.refill(allocator, q1)
assert(queue.size(q1) == 0)
end
function ut.idalloc_master()
local allocator = {}
idalloc.initmaster(allocator, 3)
assert(queue.size(allocator.id_salvaged) == 0)
assert(allocator.id_nextbatch == nthbatch(1))
assert(allocator.id_nextid == 0x0010000000000000)
assert(allocator.id_queuefill == 3)
local q1 = queue.create()
local q2 = queue.create()
local q3 = queue.create()
local salv = allocator.id_salvaged
idalloc.refill(allocator, q1)
idalloc.refill(allocator, q2)
assert(equalbatches(q1, { 1, 2, 3 }))
assert(equalbatches(q2, { 4, 5, 6 }))
idalloc.unqueue(allocator, q1)
assert(equalbatches(salv, { 1, 2, 3 }))
queue.pop(q2)
assert(equalbatches(q2, { 5, 6 }))
idalloc.refill(allocator, q2)
assert(equalbatches(q2, { 5, 6, 1 }))
assert(equalbatches(salv, { 2, 3 }))
idalloc.refill(allocator, q3)
assert(equalbatches(q3, { 2, 3, 7 }))
end
function ut.idalloc_thread()
local allocator = {}
idalloc.initmaster(allocator, 3)
local salv = allocator.id_salvaged
local q = queue.create()
idalloc.refill(allocator, q)
local id = 0
local function useids()
for i = 1,10000 do
id = idalloc.allocid(allocator)
coroutine.yield()
end
end
local co = coroutine.create(useids)
coroutine.resume(co)
assert(id == 0x0010000000000000)
coroutine.resume(co)
assert(id == 0x0010000000000001)
idalloc.preparethread(q, co)
coroutine.resume(co)
assert(id == nthbatch(1) + 0)
coroutine.resume(co)
assert(id == nthbatch(1) + 1)
idalloc.salvagethread(allocator, co)
coroutine.resume(co)
assert(id == 0x0010000000000002)
assert(queue.size(salv) == 1)
assert(queue.nth(salv, 1) == nthbatch(1) + 2)
end
rununittests(ut)

12
luprex/syslua/ut-misc.lua Normal file
View File

@@ -0,0 +1,12 @@
local ut = {}
function ut.idalloc()
cunittests.idalloc()
end
function ut.planemap()
cunittests.planemap()
end
rununittests(ut)