Rewrite planemap and idalloc in pure C++
This commit is contained in:
@@ -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);
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
315
luprex/syscpp/planemap.cpp
Normal 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;
|
||||
}
|
||||
91
luprex/syscpp/planemap.hpp
Normal file
91
luprex/syscpp/planemap.hpp
Normal 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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
45
luprex/syscpp/world.cpp
Normal 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
31
luprex/syscpp/world.hpp
Normal 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
|
||||
Reference in New Issue
Block a user