diff --git a/luprex/build.bat b/luprex/build.bat index 916327dd..9c6c3d7c 100644 --- a/luprex/build.bat +++ b/luprex/build.bat @@ -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 diff --git a/luprex/syscpp/cellgrid.cpp b/luprex/syscpp/cellgrid.cpp deleted file mode 100644 index b046106b..00000000 --- a/luprex/syscpp/cellgrid.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "luastack.hpp" -#include "cellgrid.hpp" -#include "table.hpp" -#include -#include - -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); diff --git a/luprex/syscpp/cellgrid.hpp b/luprex/syscpp/cellgrid.hpp deleted file mode 100644 index 19dcff43..00000000 --- a/luprex/syscpp/cellgrid.hpp +++ /dev/null @@ -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 - - diff --git a/luprex/syscpp/idalloc.cpp b/luprex/syscpp/idalloc.cpp index 97b5f7f8..1fdcd5b0 100644 --- a/luprex/syscpp/idalloc.cpp +++ b/luprex/syscpp/idalloc.cpp @@ -1,237 +1,231 @@ -#include "luastack.hpp" -#include "table.hpp" #include "idalloc.hpp" +#include +#include -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 &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); +IdGlobalPool::IdGlobalPool() { + salvaged_.clear(); + next_batch_ = 0; + next_id_ = 0; + queue_fill_ = 10; +} - // Use the registry by default. - if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry); +IdGlobalPool::~IdGlobalPool() { +} - // 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(); - } +void IdGlobalPool::init_master(int qf) { + salvaged_.clear(); + next_batch_ = 0x0001000000000000; + next_id_ = 0x0010000000000000; + queue_fill_ = qf; +} - // 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; +void IdGlobalPool::init_synch(int qf) { + salvaged_.clear(); + next_batch_ = 0; + next_id_ = 0x001E000000000000; + queue_fill_ = qf; +} - // 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"); +int64_t IdGlobalPool::get_one() { + return next_id_++; +} - // 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; +int64_t IdGlobalPool::get_batch() { + int64_t batch; + if (salvaged_.empty()) { + if (next_batch_ == 0) { + batch = 0; + } else { + batch = next_batch_; + next_batch_ += 256; } - - // 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(); -} - -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(); -} - -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(); -} - -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(); -} - -LuaDefine(idalloc_allocid, "c") { - LuaArg allocator; - LuaRet result; - LuaStack LS(L, allocator, result); - - 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); } else { - lua_Integer next = id + 1; - if ((next & 0xFF) == 0) { - next = 0; - } - lua_setnextid(L, next); + 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; } diff --git a/luprex/syscpp/idalloc.hpp b/luprex/syscpp/idalloc.hpp index 521d4eb9..7360db56 100644 --- a/luprex/syscpp/idalloc.hpp +++ b/luprex/syscpp/idalloc.hpp @@ -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 +#include +#include #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 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 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 diff --git a/luprex/syscpp/luastack.cpp b/luprex/syscpp/luastack.cpp index f99c6431..1e35fd3b 100644 --- a/luprex/syscpp/luastack.cpp +++ b/luprex/syscpp/luastack.cpp @@ -1,4 +1,5 @@ #include "luastack.hpp" +#include 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)) { diff --git a/luprex/syscpp/luastack.hpp b/luprex/syscpp/luastack.hpp index 66250d34..432b9022 100644 --- a/luprex/syscpp/luastack.hpp +++ b/luprex/syscpp/luastack.hpp @@ -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 +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 + static void delete_pointer(void *p) { delete (T*)p; } + static void do_not_delete(void *p) { } + public: template 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 + void newpointer(LuaSlot target, T *ptr, bool autodel) { + make_tagged_pointer(target, ptr, LuaTypeTagValue, autodel ? delete_pointer : do_not_delete); + } + + template + T *touserdata(LuaSlot target) { + TaggedPointer *tp = (TaggedPointer*)lua_touserdata(L_, target); + LuaTypeTag tag = LuaTypeTagValue; + if ((tp == 0) || (tp->tag != tag)) return 0; + return (T*)(tp->ptr); + } + + template + T *ckuserdata(LuaSlot target) { + TaggedPointer *tp = (TaggedPointer*)lua_touserdata(L_, target); + LuaTypeTag tag = LuaTypeTagValue; + 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) + #endif // LUASTACK_HPP diff --git a/luprex/syscpp/main.cpp b/luprex/syscpp/main.cpp index 16698f4f..47dd5c8c 100644 --- a/luprex/syscpp/main.cpp +++ b/luprex/syscpp/main.cpp @@ -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. diff --git a/luprex/syscpp/planemap.cpp b/luprex/syscpp/planemap.cpp new file mode 100644 index 00000000..9c12f54a --- /dev/null +++ b/luprex/syscpp/planemap.cpp @@ -0,0 +1,315 @@ +#include +#include +#include +#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; +} diff --git a/luprex/syscpp/planemap.hpp b/luprex/syscpp/planemap.hpp new file mode 100644 index 00000000..eb53af90 --- /dev/null +++ b/luprex/syscpp/planemap.hpp @@ -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 +#include +#include + +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; +private: + using Plane = std::map; + std::map 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 + + diff --git a/luprex/syscpp/util.cpp b/luprex/syscpp/util.cpp index 5e0ab71f..478127ef 100644 --- a/luprex/syscpp/util.cpp +++ b/luprex/syscpp/util.cpp @@ -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 diff --git a/luprex/syscpp/util.hpp b/luprex/syscpp/util.hpp index 71fef15e..30143e0a 100644 --- a/luprex/syscpp/util.hpp +++ b/luprex/syscpp/util.hpp @@ -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 diff --git a/luprex/syscpp/world.cpp b/luprex/syscpp/world.cpp new file mode 100644 index 00000000..62d4f4b7 --- /dev/null +++ b/luprex/syscpp/world.cpp @@ -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); + 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(); +}; \ No newline at end of file diff --git a/luprex/syscpp/world.hpp b/luprex/syscpp/world.hpp new file mode 100644 index 00000000..b37f44d1 --- /dev/null +++ b/luprex/syscpp/world.hpp @@ -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 + +class IdGlobalPool; + +class World { +public: + int id_; + std::unique_ptr id_pool_; + + World() : id_(0) {}; + ~World(); + + static void init(lua_State *L); + static World *fetch(lua_State *L); +}; + +#endif // WORLD_HPP diff --git a/luprex/syslua/control.lst b/luprex/syslua/control.lst index 392d9a30..03b13f84 100644 --- a/luprex/syslua/control.lst +++ b/luprex/syslua/control.lst @@ -5,8 +5,8 @@ utils.lua inspect.lua +ut-misc.lua ut-table.lua -ut-idalloc.lua ut-globaldb.lua ut-cellgrid.lua diff --git a/luprex/syslua/ut-cellgrid.lua b/luprex/syslua/ut-cellgrid.lua index 57f4b147..259f90a1 100644 --- a/luprex/syslua/ut-cellgrid.lua +++ b/luprex/syslua/ut-cellgrid.lua @@ -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) diff --git a/luprex/syslua/ut-idalloc.lua b/luprex/syslua/ut-idalloc.lua deleted file mode 100644 index b6bd44fc..00000000 --- a/luprex/syslua/ut-idalloc.lua +++ /dev/null @@ -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) - diff --git a/luprex/syslua/ut-misc.lua b/luprex/syslua/ut-misc.lua new file mode 100644 index 00000000..6fa49e78 --- /dev/null +++ b/luprex/syslua/ut-misc.lua @@ -0,0 +1,12 @@ + +local ut = {} + +function ut.idalloc() + cunittests.idalloc() +end + +function ut.planemap() + cunittests.planemap() +end + +rununittests(ut)