Combining two repos

This commit is contained in:
2021-02-10 13:05:58 -05:00
parent 8f557ff387
commit abb474d1ce
258 changed files with 139990 additions and 4 deletions

View File

@@ -0,0 +1,132 @@
#include <limits>
#include "luastack.hpp"
#include "animqueue.hpp"
AnimStep::AnimStep() {}
AnimStep::~AnimStep() {}
AnimQueue::AnimQueue() {
size_limit_ = 10; // Default size limit.
steps_.emplace_back();
AnimStep &init = steps_.back();
init.id_ = 0;
init.facing_ = 0;
init.xyz_ = util::XYZ(0,0,0);
init.graphic_ = "nothing";
init.plane_ = "nowhere";
init.bits_ = AnimStep::HAS_EVERYTHING;
}
void AnimQueue::set_size_limit(int n) {
assert(n >= 2);
size_limit_ = n;
}
void AnimQueue::add(int64_t id, const std::string &action) {
steps_.emplace_back();
AnimStep &last = steps_.back();
last = steps_[steps_.size() - 2];
last.id_ = id;
last.action_ = action;
last.bits_ = 0;
while (int(steps_.size()) > size_limit_) {
steps_.pop_front();
}
AnimStep &init = steps_.front();
init.id_ = 0;
init.action_ = "";
init.bits_ = AnimStep::HAS_EVERYTHING;
}
void AnimQueue::set_facing(float f) {
AnimStep &last = steps_.back();
last.bits_ |= AnimStep::HAS_FACING;
last.facing_ = f;
}
void AnimQueue::set_xyz(util::XYZ xyz) {
AnimStep &last = steps_.back();
last.bits_ |= AnimStep::HAS_XYZ;
last.xyz_ = xyz;
}
void AnimQueue::set_graphic(const std::string &g) {
AnimStep &last = steps_.back();
last.bits_ |= AnimStep::HAS_GRAPHIC;
last.graphic_ = g;
}
void AnimQueue::set_plane(const std::string &p) {
AnimStep &last = steps_.back();
last.bits_ |= AnimStep::HAS_PLANE;
last.plane_ = p;
}
const std::string &AnimQueue::get_graphic() const {
const AnimStep &last = steps_.back();
return last.graphic_;
}
const std::string &AnimQueue::get_plane() const {
const AnimStep &last = steps_.back();
return last.plane_;
}
const util::XYZ &AnimQueue::get_xyz() const {
const AnimStep &last = steps_.back();
return last.xyz_;
}
LuaDefine(unittests_animqueue, "c") {
// Check initial state.
AnimQueue aq;
aq.set_size_limit(3);
LuaAssert(L, aq.size() == 1);
const AnimStep *st = &aq.nth(0);
LuaAssert(L, st->id() == 0);
LuaAssert(L, st->action() == "");
LuaAssert(L, st->bits() == AnimStep::HAS_EVERYTHING);
LuaAssert(L, st->facing() == 0.0);
LuaAssert(L, st->xyz() == util::XYZ(0,0,0));
LuaAssert(L, st->graphic() == "nothing");
LuaAssert(L, st->plane() == "nowhere");
// Add a step.
aq.add(12345, "walk");
LuaAssert(L, aq.size() == 2);
st = &aq.nth(1);
LuaAssert(L, st->id() == 12345);
LuaAssert(L, st->action() == "walk");
LuaAssert(L, st->bits() == 0);
LuaAssert(L, st->facing() == 0.0);
LuaAssert(L, st->xyz() == util::XYZ(0,0,0));
LuaAssert(L, st->graphic() == "nothing");
LuaAssert(L, st->plane() == "nowhere");
// Test the setters.
aq.set_facing(180);
LuaAssert(L, st->facing() == 180);
LuaAssert(L, st->bits() == AnimStep::HAS_FACING);
aq.set_xyz(util::XYZ(3,4,5));
LuaAssert(L, st->xyz() == util::XYZ(3, 4, 5));
LuaAssert(L, st->bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ));
aq.set_plane("somewhere");
LuaAssert(L, st->plane() == "somewhere");
LuaAssert(L, st->bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ | AnimStep::HAS_PLANE));
aq.set_graphic("something");
LuaAssert(L, st->graphic() == "something");
LuaAssert(L, st->bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ | AnimStep::HAS_PLANE | AnimStep::HAS_GRAPHIC));
// Exceed the length limit, dropping first element.
aq.add(12346, "walk");
aq.add(12347, "walk");
LuaAssert(L, aq.size() == 3);
LuaAssert(L, aq.nth(0).id() == 0);
LuaAssert(L, aq.nth(0).action() == "");
LuaAssert(L, aq.nth(0).bits() == AnimStep::HAS_EVERYTHING);
LuaAssert(L, aq.nth(1).id() == 12346);
LuaAssert(L, aq.nth(2).id() == 12347);
return 0;
}

View File

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

View File

@@ -0,0 +1,49 @@
#include "luastack.hpp"
#include "globaldb.hpp"
LuaDefine(globaldb_enable, "c") {
LuaVar globaldb;
LuaStack LS(L, globaldb);
LS.getfield(globaldb, LuaRegistry, "globaldb");
if (!LS.istable(globaldb)) {
LS.newtable(globaldb);
LS.setfield(LuaRegistry, "globaldb", globaldb);
}
return LS.result();
}
// Get a table from the global database.
//
// GLOBALNAME
// if globalname is already present, and is a table, return it.
// if globalname is already present, and not a table, error.
// if globalname is not present, create and initialize it.
//
LuaDefine(globaldb_global, "f") {
LuaArg globalname;
LuaRet globaltab;
LuaVar globaldb;
LuaStack LS(L, globalname, globaltab, globaldb);
// Get a pointer to the globaldb.
LS.getfield(globaldb, LuaRegistry, "globaldb");
if (!LS.istable(globaldb)) {
luaL_error(L, "globaldb is not enabled");
}
LS.checkstring(globalname);
// Get the globaltab from the globaldb, sanity check it.
LS.rawget(globaltab, globaldb, globalname);
if (LS.istable(globaltab)) {
return LS.result();
} else if (!LS.isnil(globaltab)) {
luaL_error(L, "%s is not a global", LS.ckstring(globalname).c_str());
}
// Create a new globaltab and store it in the globaldb.
LS.newtable(globaltab);
LS.rawset(globaldb, globalname, globaltab);
LS.setfield(globaltab, "__global", globalname);
return LS.result();
}

View File

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

27
luprex/core/cpp/gui.cpp Normal file
View File

@@ -0,0 +1,27 @@
#include "gui.hpp"
LuaDefineType(Gui);
void Gui::add_menu_item(const std::string &id, const std::string &label) {
GuiElt elt;
elt.type_ = GuiElt::TYPE_MENU_ITEM;
elt.id_ = id;
elt.label_ = label;
elts_.push_back(elt);
}
LuaDefine(gui_create, "c") {
LuaRet lgui;
LuaStack LS(L, lgui);
LS.newpointer<Gui>(lgui, new Gui, true);
return LS.result();
}
LuaDefine(gui_add_menu_item, "c") {
LuaArg lgui, lid;
LuaStack LS(L, lgui, lid);
Gui *gui = LS.ckuserdata<Gui>(lgui);
std::string id = LS.ckstring(lid);
gui->add_menu_item(id, id);
return LS.result();
}

39
luprex/core/cpp/gui.hpp Normal file
View File

@@ -0,0 +1,39 @@
#ifndef GUI_HPP
#define GUI_HPP
#include <string>
#include <vector>
#include "luastack.hpp"
class GuiElt {
friend class Gui;
public:
enum Type {
TYPE_MENU_ITEM,
};
private:
Type type_;
std::string id_;
std::string label_;
GuiElt() {}
public:
~GuiElt() {}
Type type() const { return type_; }
const std::string &id() const { return id_; }
const std::string &label() const { return label_; }
};
class Gui {
public:
using EltVec = std::vector<GuiElt>;
private:
EltVec elts_;
public:
const EltVec &elts() const { return elts_; }
void clear() { elts_.clear(); }
void add_menu_item(const std::string &id, const std::string &label);
};
#endif // GUI_HPP

247
luprex/core/cpp/idalloc.cpp Normal file
View File

@@ -0,0 +1,247 @@
#include "idalloc.hpp"
#include <iostream>
LuaDefineType(IdGlobalPool);
LuaDefineType(IdPlayerPool);
static int64_t nthbatch(int64_t n) {
return int64_t(0x0001000000000000) + n*256;
}
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;
}
IdGlobalPool::IdGlobalPool() {
salvaged_.clear();
next_batch_ = 0;
next_id_ = 0;
queue_fill_ = 10;
}
IdGlobalPool::~IdGlobalPool() {
}
void IdGlobalPool::init_master(int qf) {
salvaged_.clear();
next_batch_ = 0x0001000000000000;
next_id_ = 0x0010000000000000;
queue_fill_ = qf;
}
void IdGlobalPool::init_synch(int qf) {
salvaged_.clear();
next_batch_ = 0;
next_id_ = 0x001E000000000000;
queue_fill_ = qf;
}
int64_t IdGlobalPool::get_one() {
return next_id_++;
}
int64_t IdGlobalPool::get_batch() {
int64_t batch;
if (salvaged_.empty()) {
if (next_batch_ == 0) {
batch = 0;
} else {
batch = next_batch_;
next_batch_ += 256;
}
} else {
batch = salvaged_.back();
salvaged_.pop_back();
}
return batch;
}
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()) {
int64_t batch = global_->get_batch();
if (batch == 0) break;
ranges_.push_back(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) {
int64_t batch = global_->get_batch();
if (batch == 0) break;
ranges_.push_back(batch);
}
if (ranges_.empty()) {
return 0;
} else {
int64_t batch = ranges_.front();
ranges_.pop_front();
return batch;
}
}
void IdPlayerPool::salvage_thread(lua_State *L) {
global_->salvage_thread(L);
}
void IdPlayerPool::prepare_thread(lua_State *L) {
global_->salvage_thread(L);
lua_setnextid(L, get_batch());
}
LuaDefine(unittests_idalloc, "c") {
IdGlobalPool gp;
IdPlayerPool pp(&gp);
// Synchronous pools produce IDs starting at 0x001E000000000000
gp.init_synch(3);
LuaAssert(L, gp.get_one() == 0x001E000000000000);
LuaAssert(L, gp.get_one() == 0x001E000000000001);
LuaAssert(L, gp.get_one() == 0x001E000000000002);
// Master pools produce IDs starting at 0x0010000000000000
gp.init_master(3);
LuaAssert(L, gp.get_one() == 0x0010000000000000);
LuaAssert(L, gp.get_one() == 0x0010000000000001);
LuaAssert(L, gp.get_one() == 0x0010000000000002);
// Synchronous pools produce only null batches.
gp.init_synch(3);
LuaAssert(L, gp.get_batch() == 0);
LuaAssert(L, gp.get_batch() == 0);
gp.salvage(nthbatch(5));
LuaAssert(L, gp.get_batch() == 0);
// Simple fetch batches with a few salvages.
gp.init_master(3);
LuaAssert(L, gp.get_batch() == nthbatch(0));
LuaAssert(L, gp.get_batch() == nthbatch(1));
LuaAssert(L, gp.get_batch() == nthbatch(2));
gp.salvage(nthbatch(182));
gp.salvage(nthbatch(183));
LuaAssert(L, gp.get_batch() == nthbatch(183));
LuaAssert(L, gp.get_batch() == nthbatch(182));
LuaAssert(L, gp.get_batch() == nthbatch(3));
// Salvage of a zero-batch does nothing.
gp.init_master(3);
LuaAssert(L, gp.get_batch() == nthbatch(0));
LuaAssert(L, gp.get_batch() == nthbatch(1));
gp.salvage(0);
LuaAssert(L, gp.get_batch() == nthbatch(2));
// Salvage of a partial batch.
gp.init_master(3);
LuaAssert(L, gp.get_batch() == nthbatch(0));
LuaAssert(L, gp.get_batch() == nthbatch(1));
gp.salvage(nthbatch(142) + 10);
LuaAssert(L, gp.get_batch() == nthbatch(142) + 10);
LuaAssert(L, gp.get_batch() == nthbatch(2));
// Salvage of a half-empty batch does nothing.
gp.init_master(3);
LuaAssert(L, gp.get_batch() == nthbatch(0));
LuaAssert(L, gp.get_batch() == nthbatch(1));
gp.salvage(nthbatch(142) + 145);
LuaAssert(L, gp.get_batch() == nthbatch(2));
// In the synchronous model, refill should do nothing.
pp.purge();
gp.init_synch(3);
pp.refill();
LuaAssert(L, pp.size() == 0);
LuaAssert(L, pp.get_batch() == 0);
LuaAssert(L, pp.size() == 0);
LuaAssert(L, pp.get_batch() == 0);
// Test refill from master.
pp.purge();
gp.init_master(3);
pp.refill();
LuaAssert(L, ranges_equal(pp.ranges_, nthbatch(0), nthbatch(1), nthbatch(2)));
// Now test that get_batch keeps the pool filled from master.
LuaAssert(L, pp.get_batch() == nthbatch(0));
LuaAssert(L, ranges_equal(pp.ranges_, nthbatch(1), nthbatch(2), nthbatch(3)));
// Test unqueueing the batches.
LuaAssert(L, gp.get_batch() == nthbatch(4));
LuaAssert(L, gp.get_batch() == nthbatch(5));
pp.unqueue();
LuaAssert(L, gp.get_batch() == nthbatch(3));
LuaAssert(L, gp.get_batch() == nthbatch(2));
LuaAssert(L, gp.get_batch() == nthbatch(1));
LuaAssert(L, 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);
LuaAssert(L, lua_getnextid(L) == nthbatch(0));
lua_setnextid(L, 0);
pp.prepare_thread(L);
LuaAssert(L, lua_getnextid(L) == nthbatch(1));
// Try salvaging the pool from the thread.
pp.salvage_thread(L);
LuaAssert(L, lua_getnextid(L) == 0);
LuaAssert(L, gp.get_batch() == nthbatch(1));
// Allocate IDs from inside a thread.
lua_setnextid(L, 0xFD);
gp.init_master(3);
LuaAssert(L, gp.alloc_id_for_thread(L) == 0xFD);
LuaAssert(L, gp.alloc_id_for_thread(L) == 0xFE);
LuaAssert(L, gp.alloc_id_for_thread(L) == 0xFF);
LuaAssert(L, gp.alloc_id_for_thread(L) == 0x0010000000000000);
LuaAssert(L, lua_getnextid(L) == 0);
return 0;
}

161
luprex/core/cpp/idalloc.hpp Normal file
View File

@@ -0,0 +1,161 @@
///////////////////////////////////////////////////////////////////
//
// THE ID ALLOCATOR
//
// This ID allocator's goal is to allocate IDs in such a way that the
// synchronous model gets the same IDs as the master model.
//
// DESIGN PRINCIPLES
//
// There are two classes defined here: IdGlobalPool, and IdPlayerPool.
//
// Every logged-in player maintains an IdPlayerPool. That's basically a fifo
// queue of ID batches. An ID batch is a contiguous range of IDs, containing
// between 128 and 256 contiguous IDs. The IdPlayerPool is difference
// transmitted, to ensure that the synchronous model has the same batches as the
// master model.
//
// When a player creates a thread, that thread gets an ID batch from the
// IdPlayerPool. To make this possible, the Lua runtime has been modified so
// that a thread can contain an ID batch. When that thread allocates IDs, it
// uses the batch it was allocated. In the unlikely event that a thread's batch
// is used up, the thread falls back to using the IdGlobalPool. Such fallback
// IDs are not likely to be predicted correctly.
//
// When a player creates a thread, he uses up one batch from his IdPlayerPool.
// In the master model, the player pool is 'refilled' by asking the IdGlobalPool
// to create a new batch. In the synchronous model, the IdPlayerPool is not
// directly refilled, but the difference transmitter effectively refills it. It
// is imperative that this difference transmission happen before the player's
// asynchronous model runs out of batches, otherwise we'll get prediction
// failures.
//
// It is common that a thread will only use 1 or 2 IDs. If a thread exits
// without using up most of its IDs, then the batch it contains is still a
// pretty usable batch. The batch gets returned to the IdGlobalPool, which will
// eventually use that salvaged batch to satisfy an IdPlayerPool refill request.
//
// THE NUMERIC RANGES
//
// * 0x0000+ : reserved for future expansion.
// * 0x0001+ : used by master model's IdGlobalPool to create batches.
// * 0x0010+ : used by master model's IdGlobalPool to create individual IDs.
// * 0x001E+ : used by sync model's IdGlobalPool to create individual IDs.
//
// BATCH REPRESENTATION
//
// A batch is represented as a 64-bit integer. The batch contains all IDs
// starting with that integer, and incrementing, until the the two lowest digits
// are 0x00.
//
// For example, consider the batch ID 0x11111111111111FC. That batch includes
// these IDs:
//
// * 0x11111111111111FC
// * 0x11111111111111FD
// * 0x11111111111111FE
// * 0x11111111111111FF
//
// But it does not include 0x1111111111111200.
//
// As a special case, the number 0 is used to indicate "invalid batch".
//
///////////////////////////////////////////////////////////////////
#ifndef IDALLOC_HPP
#define IDALLOC_HPP
#include <cstdint>
#include <vector>
#include <deque>
#include "luastack.hpp"
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:
// Construct and destroy global pools. Note that after constructing
// a global pool, it is generally also necessary to initialize it
// for Master or Synchronous operation using init_master or init_synch.
// Any attempt to use a pool that hasn't been init_master or init_synch
// will fail.
IdGlobalPool();
~IdGlobalPool();
// Initialize the pool for use in a master model.
void init_master(int qf);
// Initialize the pool for use in a synchronous model.
void init_synch(int qf);
// Create a single unique ID. Ids allocated this way are
// unlikely to be predicted correctly.
int64_t get_one();
// Obtain a batch of IDs from the global pool. In a master
// model, the batch is guaranteed to contain at least 128 IDs.
// In a synchronous model, the batch is always zero (invalid).
int64_t get_batch();
// When a player pool refills its fifo queue, it refills it
// with this many batches.
int queue_fill() const { return queue_fill_; }
// Try to return the specified batch to the global pool.
// The salvage operation quietly does nothing if the batch is
// zero, or the batch contains fewer than 128 IDs.
void salvage(int64_t batch);
// Return the thread's batch to the global pool. If no batch
// is present, that's okay. Set the thread's batch to zero (invalid).
void salvage_thread(lua_State *L);
// Allocate an ID for the specified thread. Uses the thread's
// batch if possible. If not, fetches one ID from the global pool.
int64_t alloc_id_for_thread(lua_State *L);
};
class IdPlayerPool {
private:
IdGlobalPool *global_;
std::deque<int64_t> ranges_;
friend int unittests_idalloc(lua_State *L);
public:
// Construct a player pool.
// The Player pool stores a pointer to the global pool.
IdPlayerPool(IdGlobalPool *igp);
~IdPlayerPool();
// Refill the fifo queue of batches from the global pool.
void refill();
// Return all batches to the global pool. Leave the fifo empty.
void unqueue();
// Discard all batches. This is only for unit testing.
void purge();
// Get a batch from the fifo. Also refills the fifo.
int64_t get_batch();
// Return the thread's batch to the global pool. If no batch
// is present, that's okay. Set the thread's batch to zero (invalid).
void salvage_thread(lua_State *L);
// Fetch a batch from the fifo and install it in the thread.
// If the thread already had a batch, the thread's previous batch
// is returned to the global pool.
void prepare_thread(lua_State *L);
// Return the size of the queue.
int size() { return ranges_.size(); }
};
#endif // IDALLOC_HPP

View File

@@ -0,0 +1,106 @@
#include <string.h>
#include "luaconsole.hpp"
LuaConsole::LuaConsole() {
lua_state_ = lua_open();
clear();
}
LuaConsole::~LuaConsole() {
lua_close(lua_state_);
}
void LuaConsole::clear() {
raw_input_ = "";
lines_ = 0;
lua_expression_ = "";
words_.clear();
syntax_ = "";
action_ = DO_NOTHING;
}
void LuaConsole::split_words() {
words_.clear();
std::string acc;
for (char c : raw_input_) {
if ((c == ' ')||(c == '\n')) {
if (!acc.empty()) {
words_.push_back(acc);
acc = "";
}
} else {
acc += c;
}
}
if (!acc.empty()) {
words_.push_back(acc);
}
}
void LuaConsole::add(std::string line) {
if (action_ != DO_NOTHING) {
clear();
}
for (int i = 0; i < int(line.size()); i++) {
if (line[i] == '\n') line[i] = ' ';
}
raw_input_ += line;
raw_input_ += '\n';
lines_ += 1;
// Try to interpret it as a special command.
if (lines_ == 1) {
split_words();
if ((words_.size() >= 1)&&(words_[0].size() == 1)) {
action_ = DO_COMMAND;
return;
}
}
words_.clear();
// Strip the leading punctuation from lua commands.
std::string partial;
if (raw_input_[0] == '=') {
partial = std::string("return ") + raw_input_.substr(1);
} else {
partial = raw_input_;
}
// Analyze lua expressions.
int top = lua_gettop(lua_state_);
int status = luaL_loadbuffer(lua_state_, partial.c_str(), partial.size(), "=stdin");
if (status == LUA_ERRSYNTAX)
{
const char *eof = "'<eof>'";
size_t lmsg;
const char *msg = lua_tolstring(lua_state_, -1, &lmsg);
const char *tp = msg + lmsg - (sizeof(eof) - 1);
if (strstr(msg, eof) == tp) {
action_ = DO_NOTHING;
} else {
action_ = DO_SYNTAX;
syntax_ = msg;
}
} else {
action_ = DO_LUA;
lua_expression_ = partial;
}
lua_settop(lua_state_, top);
}
void LuaConsole::add_stdin() {
if (action_ != DO_NOTHING) {
clear();
}
const int MAXINPUT = 1000;
char buf[MAXINPUT];
fputs(raw_input_.empty() ? "> " : ">> ", stdout);
fflush(stdout);
if (fgets(buf, MAXINPUT, stdin)) {
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n')
buf[len - 1] = '\0';
add(buf);
}
}

View File

@@ -0,0 +1,78 @@
//////////////////////////////////////////////////////
//
// LuaConsole:
//
// Used to parse commands that are being interactively typed
// in by a user.
//
// The command syntax is always a single character command,
// followed by arguments. Only two commands are hardwired:
//
// l - evaluate lua expression
// r - evaluate lua expression with 'return' prepended
//
// The console expects you to add lines of text one at a time.
// After adding a line, the console will recommend one of these
// actions:
//
// 1. DO_NOTHING: Add more lines of text.
// 2. DO_LUA: Evaluate a lua expression.
// 3. DO_OTHER: Execute a non-lua command.
// 4. DO_SYNTAX: Print a lua syntax error.
//
//////////////////////////////////////////////////////
#ifndef LUACONSOLE_HPP
#define LUACONSOLE_HPP
#include <string>
#include "luastack.hpp"
class LuaConsole {
public:
enum {
DO_NOTHING, // We need more input text.
DO_COMMAND, // We have a valid slash command.
DO_LUA, // We have a valid lua expression.
DO_SYNTAX, // We have a syntax error.
};
using StringVec = std::vector<std::string>;
private:
lua_State *lua_state_;
int action_;
std::string raw_input_;
int lines_;
std::string lua_expression_;
StringVec words_;
std::string syntax_;
void split_words();
public:
LuaConsole();
~LuaConsole();
// Get the recommended action.
int action() const { return action_; }
// When action is DO_COMMAND, get the command words.
const StringVec &words() const { return words_; }
// When action is DO_LUA, get the valid lua expression.
std::string lua_expression() const { return lua_expression_; }
// When action is DO_SYNTAX, get the syntax error.
const std::string &syntax() const { return syntax_; }
// Add a line of text that was just read from the console.
void add(std::string line);
// Read a line of text from stdin and add it.
void add_stdin();
// Clear the state.
void clear();
};
#endif // LUACONSOLE_HPP

195
luprex/core/cpp/luasnap.cpp Normal file
View File

@@ -0,0 +1,195 @@
#include "luasnap.hpp"
#include <iostream>
#include <cassert>
extern "C" {
void *lj_alloc_create();
void *lj_alloc_f(void *, void *, size_t, size_t);
lua_State *lj_state_newstate(lua_Alloc f, void *ud);
}
static void *lsnap_create() {
return lj_alloc_create();
}
static lua_State *lsnap_newstate(lua_Alloc f, void *ud) {
return lj_state_newstate(f, ud);
}
static void *lsnap_malloc(void *mptr, size_t nsize) {
return lj_alloc_f(mptr, 0, 0, nsize);
}
static void *lsnap_realloc(void *mptr, void *ptr, size_t osize, size_t nsize) {
return lj_alloc_f(mptr, ptr, osize, nsize);
}
static void *lsnap_free(void *mptr, size_t osize, void *ptr) {
return lj_alloc_f(mptr, ptr, osize, 0);
}
struct BlockHeader {
BlockHeader *prev_;
BlockHeader *next_;
int32_t sanity_; // 0x12345678 only when linked.
size_t size_;
bool in_use_;
void *snapshot_;
};
size_t blocksize(size_t base) {
return base + sizeof(BlockHeader);
}
BlockHeader *pointer_to_header(void *ptr) {
return (BlockHeader*)(((char *)ptr) - sizeof(BlockHeader));
}
void *header_to_pointer(BlockHeader *blk) {
return (void *)(((char *)blk) + sizeof(BlockHeader));
}
class LuaSnapData {
public:
lua_State *state_;
bool have_snapshot_;
void *lj_mstate_;
BlockHeader sentinel_;
LuaSnapData();
~LuaSnapData();
lua_State *state() const { return state_; }
bool have_snapshot() const { return have_snapshot_; }
void snapshot();
void rollback();
void link(BlockHeader *blk);
void unlink(BlockHeader *blk);
void *allocf(void *ptr, size_t osize, size_t nsize);
};
void *lsd_alloc_f(void *lsd, void *ptr, size_t osize, size_t nsize) {
return ((LuaSnapData*)lsd)->allocf(ptr, osize, nsize);
}
LuaSnapData::LuaSnapData() {
sentinel_.prev_ = &sentinel_;
sentinel_.next_ = &sentinel_;
sentinel_.size_ = 0;
sentinel_.in_use_ = false;
sentinel_.snapshot_ = 0;
lj_mstate_ = lsnap_create();
have_snapshot_ = false;
state_ = lsnap_newstate(lsd_alloc_f, (void*)this);
}
LuaSnapData::~LuaSnapData() {
std::cerr << "LuaSnapData destructor not implemented." << std::endl;
exit(1);
}
void LuaSnapData::snapshot() {
assert(!have_snapshot_);
for (BlockHeader *blk = sentinel_.next_; blk != &sentinel_; blk = blk->next_) {
assert(blk->in_use_);
blk->snapshot_ = malloc(blk->size_);
memcpy(blk->snapshot_, header_to_pointer(blk), blk->size_);
}
have_snapshot_ = true;
}
void LuaSnapData::rollback() {
assert(have_snapshot_);
for (BlockHeader *blk = sentinel_.next_; blk != &sentinel_; ) {
BlockHeader *next = blk->next_;
if (blk->snapshot_) {
memcpy(header_to_pointer(blk), blk->snapshot_, blk->size_);
free(blk->snapshot_);
blk->snapshot_ = nullptr;
blk->in_use_ = true;
} else {
assert(blk->in_use_);
unlink(blk);
lsnap_free(lj_mstate_, blocksize(blk->size_), blk);
}
blk = next;
}
have_snapshot_ = false;
}
void LuaSnapData::link(BlockHeader *blk) {
blk->prev_ = sentinel_.prev_;
blk->next_ = &sentinel_;
blk->prev_->next_ = blk;
blk->next_->prev_ = blk;
blk->sanity_ = 0x12345678;
}
void LuaSnapData::unlink(BlockHeader *blk) {
blk->prev_->next_ = blk->next_;
blk->next_->prev_ = blk->prev_;
blk->prev_ = nullptr;
blk->next_ = nullptr;
blk->sanity_ = 0;
}
void *LuaSnapData::allocf(void *ptr, size_t osize, size_t nsize) {
if (nsize == 0) {
if (ptr == 0) return nullptr;
BlockHeader *blk = pointer_to_header(ptr);
assert(blk->sanity_ = 0x12345678);
assert(blk->in_use_);
if (blk->snapshot_ == nullptr) {
unlink(blk);
lsnap_free(lj_mstate_, blocksize(blk->size_), blk);
} else {
blk->in_use_ = false;
}
return nullptr;
} else if (ptr == NULL) {
BlockHeader *blk = (BlockHeader*)lsnap_malloc(lj_mstate_, blocksize(nsize));
link(blk);
blk->size_ = nsize;
blk->in_use_ = true;
blk->snapshot_ = nullptr;
return header_to_pointer(blk);
} else {
BlockHeader *blk = pointer_to_header(ptr);
assert(blk->sanity_ = 0x12345678);
assert(blk->in_use_);
if (blk->snapshot_ == nullptr) {
unlink(blk);
blk = (BlockHeader*)lsnap_realloc(lj_mstate_, blk, blocksize(blk->size_), blocksize(nsize));
blk->size_ = nsize;
link(blk);
return header_to_pointer(blk);
} else {
BlockHeader *nblk = (BlockHeader*)lsnap_malloc(lj_mstate_, blocksize(nsize));
memcpy(nblk, blk, (blk->size_ < nsize) ? blk->size_ : nsize);
blk->in_use_ = false;
link(nblk);
nblk->size_ = nsize;
nblk->in_use_ = true;
nblk->snapshot_ = nullptr;
return header_to_pointer(nblk);
}
}
}
LuaSnap::LuaSnap() {
data_ = new LuaSnapData;
state_ = data_->state();
}
LuaSnap::~LuaSnap() {
delete data_;
}
bool LuaSnap::have_snapshot() const {
return data_->have_snapshot();
}
void LuaSnap::snapshot() {
data_->snapshot();
}
void LuaSnap::rollback() {
data_->rollback();
}

View File

@@ -0,0 +1,60 @@
////////////////////////////////////////////////////////////
//
// LUASNAP
//
// A lua interpreter that can be checkpointed (snapshotted).
// This makes it possible to roll the entire interpreter back
// to a previously-snapshotted state.
//
// To accomplish this, we take advantage of the 'allocf' parameter
// to lua_newstate. This lets us hook the lua allocator, which
// in turn lets us find every block of memory currently in use by
// lua. To snapshot, we 'memcpy' every block of memory
// that lua is using into a buffer. To rollback, we just 'memcpy'
// the data back.
//
// The current implementation is a proof-of-concept. It's quite
// wasteful of memory, roughly doubling the amount of RAM that
// LUA uses. But it does demonstrate that this method of snapshot
// and rollback is feasible.
//
////////////////////////////////////////////////////////////
#ifndef LUASNAP_HPP
#define LUASNAP_HPP
#include "luastack.hpp"
class LuaSnapData;
class LuaSnap {
private:
lua_State *state_;
LuaSnapData *data_;
public:
LuaSnap();
~LuaSnap();
// Get the lua intepreter.
//
lua_State *state() const { return state_; }
// Return true if there's a saved snapshot.
//
bool have_snapshot() const;
// snapshot the state of the lua interpreter.
//
// If there is already a snapshot, this panics.
//
void snapshot();
// Rollback the lua intepreter to the snapshotted state.
//
// If there is no snapshot, this panics.
//
void rollback();
};
#endif // LUASNAP_HPP

View File

@@ -0,0 +1,265 @@
#include "luastack.hpp"
#include "util.hpp"
#include <iostream>
LuaSpecial LuaRegistry(LUA_REGISTRYINDEX);
LuaSpecial LuaGlobals(LUA_GLOBALSINDEX);
LuaNilMarker LuaNil;
LuaNewTableMarker LuaNewTable;
LuaDiscardMarker LuaDiscard;
void LuaStack::clear_tagged_pointer(LuaSlot target) {
TaggedPointer *tp = (TaggedPointer*)lua_touserdata(L_, target.index());
tp->ptr = 0;
}
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) {
TaggedPointer *p = (TaggedPointer*)lua_touserdata(L, 1);
if (p==0) {
luaL_error(L, "lua deleter function received a non-userdata");
}
if (p->ptr != 0) {
p->del(p->ptr);
p->ptr = 0;
}
return 0;
}
void LuaStack::register_all_userdata(lua_State *L) {
LuaVar tab, lud, classtab, classname;
LuaStack LS(L, tab, lud, classtab, classname);
auto regs = LuaFunctionReg::all();
for (const LuaFunctionReg *r : regs) {
const std::string &name = util::tolower(r->get_name());
lua_CFunction tag = r->get_func();
std::string mode = r->get_mode();
LS.set(classname, name);
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.makeclass(classtab, classname);
LS.setfield(tab, "__index", classtab);
LS.setlightuserdata(lud, (void *)tag);
LS.rawset(LuaRegistry, lud, tab);
}
}
LS.result();
}
LuaFunctionReg::LuaFunctionReg(const char *m, const char *n, lua_CFunction f) {
mode_ = m;
name_ = n;
func_ = f;
next_ = LuaFunctionRegistry;
LuaFunctionRegistry = this;
}
LuaFunctionReg::List LuaFunctionReg::all() {
LuaFunctionReg::List result;
for (const LuaFunctionReg *r = LuaFunctionRegistry; r != 0; r = r->next_) {
result.push_back(r);
}
return result;
}
LuaFunctionReg *LuaFunctionReg::LuaFunctionRegistry;
void LuaStack::count_slots_finalize(int narg, int nvar, int nret) {
narg_ = narg;
nret_ = nret;
nvar_ = nvar;
ngap_ = nret - nvar - narg;
if (ngap_ < 0) ngap_ = 0;
int argtop = lua_gettop(L_);
argpos_ = argtop + 1 - narg_;
gappos_ = argpos_ + narg_;
varpos_ = gappos_ + ngap_;
retpos_ = varpos_ + nvar_;
rettop_ = retpos_ + nret_ - 1;
finaltop_ = argpos_ + nret_ - 1;
}
void LuaStack::clear_frame() {
lua_settop(L_, varpos_ - 1);
for (int i = 0; i < nvar_ + nret_; i++) {
lua_pushnil(L_);
}
}
int LuaStack::result() {
lua_settop(L_, rettop_);
int i = finaltop_;
for (int j = 0; j < nret_; j++) {
lua_replace(L_, i);
i -= 1;
}
lua_settop(L_, finaltop_);
return nret_;
}
void LuaStack::pop_any_value(std::string &s) const {
size_t len;
const char *str = luaL_cklstring(L_, -1, &len);
s = std::string(str, len);
lua_pop(L_, 1);
}
void LuaStack::pop_any_value(LuaAcceptNilNumber &s) const {
if (lua_isnil(L_, -1)) {
s.v = 0.0;
} else {
s.v = luaL_cknumber(L_, -1);
}
lua_pop(L_, 1);
}
void LuaStack::pop_any_value(LuaAcceptNilInteger &s) const {
if (lua_isnil(L_, -1)) {
s.v = 0;
} else {
s.v = luaL_ckinteger(L_, -1);
}
lua_pop(L_, 1);
}
void LuaStack::pop_any_value(LuaAcceptNilString &s) const {
if (lua_isnil(L_, -1)) {
s.v = "";
} else {
size_t len;
const char *str = luaL_cklstring(L_, -1, &len);
s.v = std::string(str, len);
}
lua_pop(L_, 1);
}
std::string LuaStack::ckstring(LuaSlot s) const {
size_t len;
const char *str = luaL_cklstring(L_, s, &len);
return std::string(str, len);
}
void LuaStack::clearmetatable(LuaSlot tab) const {
lua_pushnil(L_);
lua_setmetatable(L_, tab);
}
void LuaStack::setmetatable(LuaSlot tab, LuaSlot mt) const {
lua_pushvalue(L_, mt);
lua_setmetatable(L_, tab);
}
void LuaStack::getmetatable(LuaSlot mt, LuaSlot tab) const {
lua_getmetatable(L_, tab);
lua_replace(L_, mt);
}
void LuaStack::checknometa(LuaSlot index) const {
if (lua_istable(L_, index)) {
if (!lua_getmetatable(L_, index)) {
return;
}
}
luaL_error(L_, "expected simple table with no metatable");
}
int LuaStack::next(LuaSlot tab, LuaSlot key, LuaSlot value) const {
lua_pushvalue(L_, key);
int ret = lua_next(L_, tab);
if (ret != 0) {
lua_replace(L_, value);
lua_replace(L_, key);
}
return ret;
}
bool LuaStack::isemptytable(LuaSlot tab) const {
if (lua_istable(L_, tab)) {
lua_pushnil(L_);
if (lua_next(L_, tab) == 0) {
return true;
}
lua_pop(L_, 2);
}
return false;
}
void LuaStack::newtable(LuaSlot target) const {
lua_newtable(L_);
lua_replace(L_, target);
}
void LuaStack::makeclass(LuaSlot classtab, LuaSlot classname) const {
int top = lua_gettop(L_);
checkstring(classname);
// Special case: if the classname is _G, return global env.
lua_pushstring(L_, "_G");
int eqlg = lua_equal(L_, -1, classname.index());
lua_settop(L_, top);
if (eqlg) {
set(classtab, LuaGlobals);
return;
}
// Get the classtab from the global environment.
// Create it if it doesn't exist.
rawget(classtab, LuaGlobals, classname);
if (isnil(classtab)) {
newtable(classtab);
rawset(LuaGlobals, classname, classtab);
}
// If the name isn't bound to a table, abort.
if (!istable(classtab)) {
luaL_error(L_, "%s is not a class", ckstring(classname).c_str());
}
// Repair the special fields.
setfield(classtab, "__index", classtab);
setfield(classtab, "__class", classname);
}
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)) {
luaL_error(L_, "expected %d return values", xnret);
}
}
LuaDefine(system_type, "f") {
LuaArg obj;
LuaRet tname;
LuaVar mt;
LuaStack LS(L, obj, tname, mt);
int type = LS.type(obj);
if (type == LUA_TUSERDATA) {
LS.getmetatable(mt, obj);
LS.getfield(tname, mt, "type");
} else {
LS.set(tname, lua_typename(L, type));
}
return LS.result();
}

View File

@@ -0,0 +1,549 @@
/////////////////////////////////////////////////////////
//
//
// LUASTACK
//
// The standard lua C API asks you to work with a stack machine. You're supposed
// to manually push and pop values on the lua stack. I find this difficult, I
// find it hard to remember what stack position contains what value.
//
// To make it easier, I've created this module, "LuaStack." This module
// creates the illusion that you're working with local variables that contain
// lua values.
//
// Of course, this is all using the lua stack under the covers. Lua
// local variables are actually just lua stack addresses. But that's
// all kept fairly well hidden. When you use Lua local variables, and
// the accessors inside class LuaStack, it appears that you're
// manipulating data using local variables instead of using a stack.
// For people like me, that's easier to think about.
//
// Here's an example.
//
// let's say you have a function that takes two arguments
// ARG1 and ARG2, has a single return value RET1, and needs two local
// variables LOC1 and LOC2. We would declare it like this:
//
// int myfunc(lua_State *L) {
//
// LuaArg arg1, arg2; // Declare local variables to hold the arguments.
// LuaRet ret1; // Declare local variables to hold the return values.
// LuaVar loc1, loc2, loc3; // Declare local variables for other purposes.
//
// // Assign every local var a stack index.
// LuaStack LS(L, arg1, arg2, ret1, loc1, loc2, loc3);
//
// // manipulate the data in the lua local variables...
// LS.rawget(loc1, arg1, arg2);
// ... etc ...
// }
//
// Class LuaArg, LuaRet, and LuaVar are all lua local variables.
// The luastack constructor assigns each one of them a position on
// the lua stack. It also makes sure that the arguments are in
// the LuaArg variables, and it makes sure that the LuaRet values
// are the only thing left on the stack at return time.
//
// Class LuaStack provides a complete catalog of accessors
// like 'rawget' - roughly speaking, it provides equivalents to
// every major accessor in the lua API. However, the accessors
// provided by LuaStack take input and output from lua locals, not
// from the stack. For example, consider this:
//
// LS.rawget(value, tab, key);
//
// In the above, value, tab, and key should be lua local variables.
// This does a rawget on 'table', with the specified 'key', and
// stores the result in 'value'. Nothing is added to or removed
// from the lua stack. In general, none of the accessors in class
// LuaStack add anything to the stack, or pop anything from the
// stack.
//
// Class LuaStack can also do automatic type conversions. For
// example, suppose you do this:
//
// LS.rawget(value, tab, key);
//
// Nominally, you would expect value, tab, and key to be lua local
// variables. But if you pass a std::string for key, then LuaStack will
// automatically convert it. In general, class LuaStack can
// convert lua_Integer, lua_Number, std::string, bool, and LuaNil.
//
// On output, LuaStack can convert lua_Integers, lua_Numbers, and
// std::strings. In this case, strict type checking is done. If
// there is a type mismatch, a lua error is thrown.
//
// You can use the operator 'set' to assign a value to a lua local
// variable:
//
// LS.set(val1, val2);
//
// This is actually a copy operation that copies from one lua local
// variable to another. But using type conversions, it can also be
// used to assign arbitrary values to lua local variables, or to
// get values from lua local variables.
//
// Passing LuaNewTable as an input will cause a new table to be
// created before calling the specified operation.
//
//
/////////////////////////////////////////////////////////
//
//
// LuaStack type checking
//
// LuaStack contains accessors for type checking. These include:
//
// bool LuaStack::isnumber(LuaSlot s)
// bool LuaStack::isinteger(LuaSlot s)
// bool LuaStack::isstring(LuaSlot s)
// etc...
//
// And it also contains operations that throw errors:
//
// void LuaStack::checknumber(LuaSlot s)
// void LuaStack::checkinteger(LuaSlot s)
// void LuaStack::checkstring(LuaSlot s)
// etc...
//
// These are different from the lua builtins in that they are strict.
// For example, 'isnumber' only returns true if the value in the
// lua local variable is already a number. No conversions are done.
//
// These functions do checking and also conversion at the same time:
//
// lua_Integer LuaStack::ckinteger(LuaSlot s)
// lua_Number LuaStack::cknumber(LuaSlot s)
// std::string LuaStack::ckstring(LuaSlot s)
// lua_State *LuaStack::ckthread(LuaSlot s)
//
// Like the other operations, they are strict.
//
//
// LUADEFINE
//
// LuaDefine is a macro that defines a C function which is
// exposed to lua. It creates a global registry of functions
// created with LuaDefine. You use it like so:
//
// LuaDefine(function_name, "modebits") {
// ...
// }
//
// This macroexpands into a function definition and a function
// registration. The function definition looks like this:
//
// int function_name(lua_State *L) {
// ...
// }
//
// The macro expansion generates this function definition, but it
// also generates a "registration object" whose constructor puts
// this function into a global registry of lua-callable C functions.
// This global registry is later used to inject these C functions
// into the lua intepreter. The mode is a string that contain
// the following characters:
//
// c - create a class, and put a function into it.
// f - create a global function not inside a class.
//
//
/////////////////////////////////////////////////////////
#ifndef LUASTACK_HPP
#define LUASTACK_HPP
extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "luajit.h"
}
#include <string>
#include <vector>
class LuaSlot {
protected:
int index_;
private:
inline operator int() const {
return index_;
}
public:
LuaSlot() {
index_ = 0;
}
int index() const {
return index_;
}
friend class LuaStack;
};
class LuaArg : public LuaSlot {};
class LuaRet : public LuaSlot {};
class LuaVar : public LuaSlot {};
class LuaSpecial : public LuaSlot {
public:
LuaSpecial(int n) {
index_ = n;
}
};
extern LuaSpecial LuaRegistry;
extern LuaSpecial LuaGlobals;
class LuaUpvalue : public LuaSlot {
public:
LuaUpvalue(int n) {
index_ = lua_upvalueindex(n);
}
};
class LuaNilMarker {};
extern LuaNilMarker LuaNil;
class LuaNewTableMarker {};
extern LuaNewTableMarker LuaNewTable;
class LuaDiscardMarker {};
extern LuaDiscardMarker LuaDiscard;
struct LuaAcceptNilNumber { lua_Number v; };
struct LuaAcceptNilInteger { lua_Integer v; };
struct LuaAcceptNilString { std::string v; };
inline LuaAcceptNilNumber &LuaAcceptNil(lua_Number &x) { return *(LuaAcceptNilNumber *)(&x); }
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_;
int ngap_;
int nvar_;
int nret_;
int argpos_;
int gappos_;
int varpos_;
int retpos_;
int rettop_;
int finaltop_;
lua_State *L_;
template<int NARG, int NVAR, int NRET, class... SS>
void count_slots(LuaArg &v, SS & ... stackslots)
{
count_slots<NARG+1, NRET, NVAR>(stackslots...);
}
template<int NARG, int NVAR, int NRET, class... SS>
void count_slots(LuaVar &v, SS & ... stackslots)
{
count_slots<NARG, NVAR+1, NRET>(stackslots...);
}
template<int NARG, int NVAR, int NRET, class... SS>
void count_slots(LuaRet &v, SS & ... stackslots)
{
count_slots<NARG, NVAR, NRET+1>(stackslots...);
}
template<int NARG, int NVAR, int NRET>
void count_slots() {
count_slots_finalize(NARG, NVAR, NRET);
}
void count_slots_finalize(int narg, int nvar, int nret);
template<class... SS>
void assign_slots(int argp, int varp, int retp, LuaArg &v, SS & ... stackslots) {
v.index_ = argp;
assign_slots(argp + 1, varp, retp, stackslots...);
}
template<class... SS>
void assign_slots(int argp, int varp, int retp, LuaVar &v, SS & ... stackslots) {
v.index_ = varp;
assign_slots(argp, varp+1, retp, stackslots...);
}
template<class... SS>
void assign_slots(int argp, int varp, int retp, LuaRet &v, SS & ... stackslots) {
v.index_ = retp;
assign_slots(argp, varp, retp+1, stackslots...);
}
void assign_slots(int argp, int varp, int retp) {}
void clear_frame();
private:
// Push any value on the stack, by type.
void push_any_value(LuaNewTableMarker s) const { lua_newtable(L_); }
void push_any_value(LuaNilMarker s) const { lua_pushnil(L_); }
void push_any_value(LuaSlot s) const { lua_pushvalue(L_, s); }
void push_any_value(const std::string &s) const { lua_pushlstring(L_, s.c_str(), s.size()); }
void push_any_value(const char *s) const { lua_pushstring(L_, s); }
void push_any_value(float s) const { lua_pushnumber(L_, s); }
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.
void pop_any_value(LuaSlot &s) const { lua_replace(L_, s); }
void pop_any_value(lua_Integer &s) const { s = luaL_ckinteger(L_, -1); lua_pop(L_, 1); }
void pop_any_value(lua_Number &s) const { s = luaL_cknumber(L_, -1); lua_pop(L_, 1); }
void pop_any_value(std::string &s) const;
void pop_any_value(LuaAcceptNilNumber &s) const;
void pop_any_value(LuaAcceptNilInteger &s) const;
void pop_any_value(LuaAcceptNilString &s) const;
void pop_any_value(LuaDiscardMarker &s) const { lua_pop(L_, 1); }
// Push multiple values on the stack, in order, by type.
template<typename T0, typename... T>
void push_any_values(T0 arg0, T... args) {
push_any_value(arg0);
push_any_values(args...);
}
void push_any_values() {
}
// Call the CFunction, pushing and popping arguments appropriately.
template<int NRET, typename... T>
void call_cfunction(int otop, LuaSlot s, T... args) {
call_cfunction<NRET+1>(otop, args...);
pop_any_value(s);
}
template<int NRET, typename... T>
void call_cfunction(int otop, lua_Integer &s, T... args) {
call_cfunction<NRET+1>(otop, args...);
pop_any_value(s);
}
template<int NRET, typename... T>
void call_cfunction(int otop, lua_Number &s, T... args) {
call_cfunction<NRET+1>(otop, args...);
pop_any_value(s);
}
template<int NRET, typename... T>
void call_cfunction(int otop, std::string &s, T... args) {
call_cfunction<NRET+1>(otop, args...);
pop_any_value(s);
}
template<int NRET, typename... T>
void call_cfunction(int otop, LuaDiscardMarker &s, T... args) {
call_cfunction<NRET+1>(otop, args...);
pop_any_value(s);
}
template<int NRET, typename... T>
void call_cfunction(int otop, lua_CFunction fn, T... args) {
push_any_values(args...);
int nret = fn(L_);
check_nret(NRET, otop, nret);
}
// Check number of return values: xpected number of return values,
// 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);
void clear_tagged_pointer(LuaSlot target);
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) {
L_ = L;
count_slots<0, 0, 0>(stackslots...);
if (lua_gettop(L) < narg_) {
luaL_error(L, "not enough arguments on stack");
}
assign_slots(argpos_, varpos_, retpos_, stackslots...);
clear_frame();
}
~LuaStack() {};
int result();
public:
int type(LuaSlot s) const { return lua_type(L_, s); }
void checktype(LuaSlot s, int type) const { luaL_checktype(L_, s, type); }
bool istable(LuaSlot s) const { return lua_type(L_, s) == LUA_TTABLE; }
bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; }
bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; }
bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; }
bool isfunction(LuaSlot s) const { return lua_type(L_, s) == LUA_TFUNCTION; }
bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; }
bool isnil(LuaSlot s) const { return lua_type(L_, s) == LUA_TNIL; }
void checktable(LuaSlot index) const { checktype(index, LUA_TTABLE); }
void checkstring(LuaSlot index) const { checktype(index, LUA_TSTRING); }
void checknumber(LuaSlot index) const { checktype(index, LUA_TNUMBER); }
void checkthread(LuaSlot index) const { checktype(index, LUA_TTHREAD); }
void checkfunction(LuaSlot index) const { checktype(index, LUA_TFUNCTION); }
void checkboolean(LuaSlot index) const { checktype(index, LUA_TBOOLEAN); }
void checknil(LuaSlot index) const { checktype(index, LUA_TNIL); }
lua_Integer ckinteger(LuaSlot s) const { return luaL_ckinteger(L_, s); }
double cknumber(LuaSlot s) const { return luaL_cknumber(L_, s); }
std::string ckstring(LuaSlot s) const;
lua_State *ckthread(LuaSlot s) const { return luaL_ckthread(L_, s); }
void clearmetatable(LuaSlot tab) const;
void setmetatable(LuaSlot tab, LuaSlot mt) const;
void getmetatable(LuaSlot mt, LuaSlot tab) const;
void checknometa(LuaSlot index) const;
void newtable(LuaSlot target) const;
void makeclass(LuaSlot tab, LuaSlot name) 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);
}
void clearuserdata(LuaSlot target) { clear_tagged_pointer(target); }
int next(LuaSlot tab, LuaSlot key, LuaSlot value) const;
bool isemptytable(LuaSlot s) const;
bool equal(LuaSlot v1, LuaSlot v2) {
return lua_equal(L_, v1, v2);
}
template<typename T1, typename T2>
void set(T1 &target, T2 value) const {
push_any_value(value);
pop_any_value(target);
}
template<typename RT, typename KT>
void gettable(RT &target, LuaSlot tab, KT key) const {
push_any_value(key);
lua_gettable(L_, tab);
pop_any_value(target);
}
template<typename RT, typename KT>
void rawget(RT &target, LuaSlot tab, KT key) const {
push_any_value(key);
lua_rawget(L_, tab);
pop_any_value(target);
}
template<typename KT, typename VT>
void rawset(LuaSlot tab, KT key, VT value) const {
push_any_value(key);
push_any_value(value);
lua_rawset(L_, tab);
}
template<typename VT>
void setfield(LuaSlot tab, const char *field, VT value) const {
push_any_value(value);
lua_setfield(L_, tab, field);
}
template<typename RT>
void getfield(RT &target, LuaSlot tab, const char *field) const {
lua_getfield(L_, tab, field);
pop_any_value(target);
}
// Call invokes any C function. It pushes the arguments on the stack,
// calls the cfunction, verifies that the number of return values is as
// expected, and pops the return values into LuaVars.
template<typename... T>
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_;
const char *name_;
lua_CFunction func_;
LuaFunctionReg *next_;
static LuaFunctionReg *LuaFunctionRegistry;
public:
using List = std::vector<const LuaFunctionReg *>;
LuaFunctionReg(const char *mode, const char *n, lua_CFunction f);
static List all();
const char *get_mode() const { return mode_; }
const char *get_name() const { return name_; }
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>)
#define LuaStringify(x) #x
#define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); }
#endif // LUASTACK_HPP

8
luprex/core/cpp/main.cpp Normal file
View File

@@ -0,0 +1,8 @@
#include "textgame.hpp"
int main(int argc, char **argv)
{
TextGame tg;
tg.run();
}

View File

@@ -0,0 +1,330 @@
#include <cmath>
#include <algorithm>
#include "luastack.hpp"
#include "util.hpp"
#include "planemap.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 == "") || (plane == "nowhere")) {
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 == "") || (plane == "nowhere")) {
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 (pmap_ != 0) {
if ((plane_ != plane) || (old_cell != new_cell)) {
pmap_->remove(plane_, old_cell, this);
pmap_->insert(plane, new_cell, this);
}
}
// Update the client position.
plane_ = plane;
x_ = x;
y_ = y;
z_ = z;
}
void PlaneItem::untrack() {
if (pmap_ != 0) {
pmap_->remove(plane_, point_cell_id(x_, y_), this);
pmap_ = 0;
}
}
void PlaneMap::track(PlaneItem *item) {
if (item->pmap_ != this) {
item->untrack();
insert(item->plane(), point_cell_id(item->x(), item->y()), item);
item->pmap_ = this;
}
}
PlaneItem::PlaneItem() : pmap_(NULL), x_(0.0), y_(0.0), z_(0.0) {
}
PlaneMap::PlaneMap() {
}
PlaneItem::~PlaneItem() {
untrack();
}
PlaneMap::~PlaneMap() {
for (const auto &p : planes_) {
for (const auto &l : p.second) {
for (PlaneItem *i : l.second) {
i->pmap_ = 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::IdVec PlaneMap::scan_radius(const std::string &plane, double x, double y, double radius, int64_t prepend) const {
PlaneMap::IdVec result;
if (prepend != 0) {
result.push_back(prepend);
}
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) {
if (client->id() != prepend) {
result.push_back(client->id());
}
}
}
}
}
}
}
return result;
}
LuaDefine(unittests_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;
PlaneMap::IdVec ids;
// Simple test.
LuaAssert(L, 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.
LuaAssert(L, 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.
LuaAssert(L, 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.
LuaAssert(L, 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.
LuaAssert(L, 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.
LuaAssert(L, rect_cell_range((LO-15)*SC, (LO-17)*SC, (LO-7)*SC, (LO-5)*SC).equal(LO, LO, LO-1, LO-1));
// Simple test.
LuaAssert(L, point_cell_id(-7*SC, 15*SC) == cell_id(-7, 15));
// Adding epsilon doesn't change the result if less than half cell scale.
LuaAssert(L, point_cell_id(-7*SC+E, 15*SC+E) == cell_id(-7, 15));
// Right at the top edge of the range.
LuaAssert(L, point_cell_id(HI*SC, HI*SC) == cell_id(HI, HI));
// Right at the bottom edge of the range.
LuaAssert(L, point_cell_id(LO*SC, LO*SC) == cell_id(LO, LO));
// Beyond various edges.
LuaAssert(L, point_cell_id((LO-1)*SC, 0) == CELL_INVALID);
LuaAssert(L, point_cell_id((HI+1)*SC, 0) == CELL_INVALID);
LuaAssert(L, point_cell_id(0, (LO-1)*SC) == CELL_INVALID);
LuaAssert(L, point_cell_id(0, (HI+1)*SC) == CELL_INVALID);
// Test using the insert function.
pm.clear();
LuaAssert(L, pm.total_cells() == 0);
pm.insert("foo", 12345, &pia);
LuaAssert(L, pm.total_cells() == 1);
pm.insert("foo", 12345, &pib);
LuaAssert(L, pm.total_cells() == 1);
elts = pm.get_cell("foo", 12345);
LuaAssert(L, elts.size() == 2);
LuaAssert(L, elts[0] == &pia);
LuaAssert(L, elts[1] == &pib);
// Test the remove function.
pm.remove("foo", 12345, &pia);
LuaAssert(L, pm.total_cells() == 1);
elts = pm.get_cell("foo", 12345);
LuaAssert(L, elts.size() == 1);
LuaAssert(L, elts[0] == &pib);
pm.remove("foo", 12345, &pib);
LuaAssert(L, pm.total_cells() == 0);
// Test the insert function on the nowhere plane.
pm.clear();
pm.insert("nowhere", 12345, &pia);
pm.insert("nowhere", 12345, &pib);
LuaAssert(L, 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);
LuaAssert(L, pm.total_cells() == 0);
// Try moving a plane item around without it being connected to a grid.
pia.set_pos("foo", 3, 4, 5);
LuaAssert(L, pia.plane() == "foo");
LuaAssert(L, pia.x() == 3.0);
LuaAssert(L, pia.y() == 4.0);
LuaAssert(L, pia.z() == 5.0);
// Attach pia to the grid. This should record it.
pm.clear();
pm.track(&pia);
elts = pm.get_cell("foo", point_cell_id(3.0, 4.0));
LuaAssert(L, elts.size() == 1);
LuaAssert(L, elts[0] == &pia);
// Unattach pia from the grid. This should unrecord it.
pia.untrack();
LuaAssert(L, pm.total_cells() == 0);
// Reattach pia to the grid, then move it.
pm.track(&pia);
LuaAssert(L, pm.total_cells() == 1);
pia.set_pos("bar", 1000.0, 1000.0, 0.0);
LuaAssert(L, pm.total_cells() == 1);
elts = pm.get_cell("bar", point_cell_id(1000.0, 1000.0));
LuaAssert(L, elts.size() == 1);
LuaAssert(L, elts[0] == &pia);
// Insert the four elements, then test the scan function.
pm.track(&pib);
pia.set_id(123);
pib.set_id(456);
pib.set_pos("bar", 1100.0, 1000.0, 0.0);
ids = pm.scan_radius("bar", 1000.0, 1000.0, 1.0, 0);
LuaAssert(L, ids.size() == 1);
LuaAssert(L, ids[0] == 123);
ids = pm.scan_radius("bar", 1000.0, 1000.0, 99.9, 0);
LuaAssert(L, ids.size() == 1);
LuaAssert(L, ids[0] == 123);
ids = pm.scan_radius("bar", 1000.0, 1000.0, 100.0, 0);
LuaAssert(L, ids.size() == 2);
LuaAssert(L, ids[0] == 123);
LuaAssert(L, ids[1] == 456);
return 0;
}

View File

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

333
luprex/core/cpp/source.cpp Normal file
View File

@@ -0,0 +1,333 @@
#include <string>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#include <sstream>
#include <fstream>
#include <iostream>
#include "util.hpp"
#include "luastack.hpp"
#include "traceback.hpp"
#include "table.hpp"
#include "source.hpp"
// Read control.lst
//
// - trim all lines
// - remove blank lines
// - remove comment lines
//
util::stringvec read_control_lst(const std::string &path) {
util::stringvec lines = util::get_file_lines(path);
util::stringvec result;
for (int i = 0; i < int(lines.size()); i++) {
std::string trimmed = util::trim(lines[i]);
if ((trimmed.size() > 0) && (trimmed[0] != '#')) {
result.push_back(trimmed);
}
}
return result;
}
LuaDefine(source_makeclass, "f") {
LuaArg classname;
LuaRet classtab;
LuaStack LS(L, classname, classtab);
LS.makeclass(classtab, classname);
return LS.result();
}
// Load the builtins.
static void load_builtin(lua_State *L, const char *name, lua_CFunction func) {
lua_pushcfunction(L, func);
lua_pushstring(L, name);
lua_call(L, 1, 0);
}
static void source_install_builtins(lua_State *L) {
LuaStack LS(L);
load_builtin(L, "base", luaopen_base);
load_builtin(L, "table", luaopen_table);
load_builtin(L, "string", luaopen_string);
load_builtin(L, "math", luaopen_math);
load_builtin(L, "bit", luaopen_math);
load_builtin(L, "debug", luaopen_debug);
}
void SourceDB::initialize(lua_State *L) {
lua_state_ = L;
LuaVar key, value, skey, svalue, snapshot, ssnapshot;
LuaStack LS(L, snapshot, key, value, skey, svalue, ssnapshot);
// Note: the global environment contains _G. This routine
// perceives this as a subtable, so it backs up the top level
// as well.
source_install_builtins(L);
LS.newtable(snapshot);
LS.set(key, LuaNil);
while (LS.next(LuaGlobals, key, value) != 0) {
if (LS.istable(value)) {
LS.newtable(ssnapshot);
LS.rawset(snapshot, key, ssnapshot);
LS.set(skey, LuaNil);
while (LS.next(value, skey, svalue) != 0) {
if (LS.isfunction(svalue) || LS.isstring(svalue) || LS.isnumber(svalue)) {
LS.rawset(ssnapshot, skey, svalue);
}
}
}
}
LS.setfield(LuaRegistry, "source_snapshot_builtins", snapshot);
LS.result();
}
static int source_updatefile(lua_State *L) {
LuaArg source, fn;
LuaRet info;
LuaVar fingerprint, null;
LuaStack LS(L, source, fn, info, fingerprint, null);
// Get the existing info table from the source DB.
if (LS.istable(source)) {
LS.rawget(info, source, fn);
if (!LS.istable(info)) {
LS.newtable(info);
}
} else {
LS.newtable(info);
}
// If the file modification is wrong, update
// these fields: code, fingerprint, closure, error
// Otherwise, update nothing.
std::string cfn = LS.ckstring(fn);
LS.getfield(fingerprint, info, "fingerprint");
std::string old_fingerprint;
if (LS.isstring(fingerprint)) {
old_fingerprint = LS.ckstring(fingerprint);
}
// std::cerr << "Probing " << cfn << std::endl;
std::string new_fingerprint = util::get_file_fingerprint("lua/" + cfn);
LS.set(null, LuaNil);
if ((old_fingerprint == "") || (old_fingerprint != new_fingerprint)) {
std::cerr << "Rereading " << cfn << std::endl;
std::string ccode = util::get_file_contents("lua/" + cfn);
LS.setfield(info, "name", fn);
LS.setfield(info, "fingerprint", new_fingerprint);
LS.setfield(info, "code", ccode);
if ((new_fingerprint == "")||(ccode == "")) {
LS.setfield(info, "loadresult", "cannot read source file");
} else {
std::string chunk = "=" + cfn;
luaL_loadbuffer(L, ccode.c_str(), ccode.size(), chunk.c_str());
lua_setfield(L, info.index(), "loadresult");
}
}
return LS.result();
}
void SourceDB::update() {
lua_State *L = lua_state_;
LuaVar sourcedb, newdb, info, fn, seq;
LuaStack LS(L, newdb, sourcedb, info, fn, seq);
// Get the (old) source database.
LS.getfield(sourcedb, LuaRegistry, "sourcedb");
if (!LS.istable(sourcedb)) {
LS.newtable(sourcedb);
}
// Read the list of filenames.
util::stringvec filenames = read_control_lst("lua/control.lst");
if (filenames.empty()) {
luaL_error(L, "cannot read source database control.lst");
}
// Process the files one by one.
LS.newtable(newdb);
for (int i = 0; i < int(filenames.size()); i++) {
LS.set(fn, filenames[i]);
// Call source_updatefile to get the updated info for one file.
LS.call(info, source_updatefile, sourcedb, fn);
// Insert the sequence number and put finalized info into the new database.
LS.set(seq, i + 1);
LS.setfield(info, "sequence", seq);
LS.rawset(newdb, fn, info);
}
// Store the new source db.
LS.setfield(LuaRegistry, "sourcedb", newdb);
LS.result();
}
// Delete everything from the global environment except
// the class tables.
//
static void source_clear_globals(lua_State *L) {
LuaVar classname, classtab, key;
LuaStack LS(L, classname, classtab, key);
LS.setfield(LuaGlobals, "_G", LuaNil);
LS.set(classname, LuaNil);
while (LS.next(LuaGlobals, classname, classtab) != 0) {
if (LS.istable(classtab)) {
LS.call(table_clear, classtab);
} else {
LS.rawset(LuaGlobals, classname, LuaNil);
}
}
LS.setfield(LuaGlobals, "_G", LuaGlobals);
LS.result();
}
// Restore the lua builtins from the backup snapshot.
//
static void source_restore_builtins(lua_State *L) {
LuaVar snapshot, key, value, skey, svalue, subglobal;
LuaStack LS(L, snapshot, key, value, skey, svalue, subglobal);
LS.getfield(snapshot, LuaRegistry, "source_snapshot_builtins");
LS.setfield(LuaGlobals, "_G", LuaGlobals);
LS.set(key, LuaNil);
while (LS.next(snapshot, key, value) != 0) {
LS.checktable(value);
LS.makeclass(subglobal, key);
LS.set(skey, LuaNil);
while (LS.next(value, skey, svalue) != 0) {
LS.rawset(subglobal, skey, svalue);
}
}
LS.result();
}
// Load all the 'LuaDefine' C functions into the lua state.
//
static void source_load_cfunctions(lua_State *L) {
auto regs = LuaFunctionReg::all();
for (const LuaFunctionReg *r : regs) {
const std::string &name = r->get_name();
size_t upos = name.find('_');
std::string classname;
std::string funcname;
if (upos == std::string::npos) {
continue;
} else {
funcname = name.substr(upos + 1);
classname = name.substr(0, upos);
}
lua_CFunction func = r->get_func();
std::string mode = r->get_mode();
if (mode.find('c') != std::string::npos) { // Insert into class
lua_pushlstring(L, classname.c_str(), classname.size());
source_makeclass(L);
lua_pushcfunction(L, func);
lua_setfield(L, -2, funcname.c_str());
}
if (mode.find('f') != std::string::npos) { // Make global function
lua_pushcfunction(L, func);
lua_setglobal(L, funcname.c_str());
}
}
}
// Run all the closures from the source database.
//
static void source_load_lfunctions(lua_State *L) {
LuaRet errors;
LuaVar sourcedb, key, info, seq, closure, err;
LuaStack LS(L, sourcedb, errors, key, info, seq, closure, err);
// Get the source database.
LS.getfield(sourcedb, LuaRegistry, "sourcedb");
if (LS.type(sourcedb) != LUA_TTABLE) {
LS.newtable(sourcedb);
LS.setfield(LuaRegistry, "sourcedb", sourcedb);
}
// Sort the keys by sequence number.
std::map<int, std::string> indices;
LS.set(key, LuaNil);
while (LS.next(sourcedb, key, info) != 0) {
LS.getfield(seq, info, "sequence");
indices[LS.ckinteger(seq)] = LS.ckstring(key);
}
// Now call the closures in the proper order.
std::stringstream errss;
for (const auto &p : indices) {
LS.rawget(info, sourcedb, p.second);
LS.getfield(closure, info, "loadresult");
// If there's already an error in the sourcedb, collect it.
if (!LS.isfunction(closure)) {
errss << LS.ckstring(closure) << "\n";
continue;
}
// Call the closure. If there's an error, collect it.
lua_pushvalue(L, closure.index());
if (traceback_pcall(L, 0, 0) != 0) {
lua_replace(L, err.index());
errss << LS.ckstring(err);
}
}
LS.set(errors, errss.str());
LS.result();
}
void SourceDB::rebuild() {
lua_State *L = lua_state_;
LuaVar errs;
LuaStack LS(L, errs);
source_clear_globals(L);
source_restore_builtins(L);
source_load_cfunctions(L);
source_load_lfunctions(L);
lua_replace(L, errs.index());
std::string errstr = LS.ckstring(errs);
std::cerr << errstr;
LS.result();
}
void SourceDB::run_unittests() {
lua_State *L = lua_state_;
LuaVar unittests, name, func, err;
LuaStack LS(L, unittests, name, func, err);
LS.getfield(unittests, LuaGlobals, "unittests");
// Sort the unit test names.
std::set<std::string> names;
LS.set(name, LuaNil);
while (LS.next(unittests, name, func) != 0) {
if (LS.isfunction(func) && LS.isstring(name)) {
names.insert(LS.ckstring(name));
}
}
// Run the functions in order
bool any = false;
for (const std::string &name : names) {
std::cerr << "Running unittests." << name << std::endl;
LS.rawget(func, unittests, name);
lua_pushvalue(L, func.index());
if (traceback_pcall(L, 0, 0) != 0) {
lua_replace(L, err.index());
std::cerr << LS.ckstring(err);
any = true;
}
}
if (any) {
exit(1);
}
LS.result();
}

177
luprex/core/cpp/source.hpp Normal file
View File

@@ -0,0 +1,177 @@
////////////////////////////////////////////////////////////
//
// SOURCEDB
//
// This module manages the loading of lua source files into the Lua environment.
// Since the source files can be reloaded over and over, this module doesn't
// just load the source files. Instead, it does "source rebuilds" in which it
// purges everything from the global environment, then it reinstalls everything
// that should be there. That way, if you delete something from a lua source
// file, it gets removed from the lua global environment.
//
// THE MAKECLASS OPERATOR
//
// This module provides a new lua 'builtin' operator: "makeclass". This creates
// a table and stores it in the global environment. The new table is meant to be
// used as a class. Three fields are initially created:
//
// __class --> the name of the class as a string
//
// __index --> points back to the class. Makes it convenient to use the
// class as a metatable.
//
// action --> a subtable of additional methods.
//
// If you invoke 'makeclass' on a class that already exists, the existing table
// is "repaired" - the __class and __index fields are restored and the action
// subtable, if not present, is recreated. If there are already functions or
// constants inside the class, they are not affected.
//
//
// THE LUA SOURCE DATABASE
//
// Class SourceDB only contains a single pointer to the lua environment. That's
// because all the data for SourceDB is stored in the lua registry.
// Specifically, the registry contains these keys:
//
// 1. The source database proper (registry key "sourcedb")
// 2. The snapshot of builtins (registry key "source_snapshot_builtins")
//
// The source database proper is a table where the keys are filenames, and the
// values are records containing information about that file:
//
// "foo.lua" : {
// "name": "foo.lua",
// "fingerprint": "12893129385854",
// "code": "function xyz ...",
// "loadresult": <function-23>,
// "sequence": 13
// }
//
// Let's break that down a little. The "name" field is just the filename. The
// "fingerprint" is an encoding of the file modification date and the file
// length, it is used to detect whether the file has been altered on disk
// without having to reread the file. The "code" is the entire content of the
// source file. The "loadresult" is the closure that results from calling the
// lua 'load' function on the code. If load fails, then the "loadresult" is an
// error message. Finally, "sequence" indicates the order in which the source
// files are meant to be loaded.
//
// The operation SourceDB::update refreshes the source database from disk. It
// doesn't reread files whose fingerprints have not changed. In a synchronous
// model, we don't call SourceDB::update - instead, we update the source
// database by difference transmission.
//
// Note that updating the source database has *no effect* on the lua global
// variables (or lua function definitions). The SourceDB::update operation
// calls lua's "load" on the code, but all that does is return a loaded closure.
// Nothing happens to the lua invironment until you *invoke* the loaded closure.
// That doesn't happen until you do a SourceDB::rebuild operation, described
// below.
//
//
// SOURCE REBUILDS, IN DEPTH
//
// The function SourceDB::rebuild clears and then rebuilds the entire contents
// of the lua global environment. The reason to clear the environment is that
// if we didn't clear it, then removing a function from the lua source would not
// remove it from the lua environment. Rebuilding the lua environment is a
// multi-step process:
//
// * Delete everything from the global environment except class tables.
//
// - Class tables are kept, but the contents are cleared.
// - If a class has an "actions" subtable, that is kept and cleared.
// - Anything else is deleted.
//
// * Lua Builtin functions are reinstalled in the global environment.
//
// - To make this possible, the snapshot of builtins is used.
//
// * C++ functions registered with "LuaDefine" are reinstalled.
//
// - LuaDefine creates a registry, that registry is iterated over.
//
// * Lua code is reinstalled by running the closures in the source DB.
//
// - Simply call the "loadresult" closure to reinstall functions into the
// global environment.
//
// Note that if you've stored any global data in the lua environment, it's gone.
// So therefore, we have to provide a separate "safe" space for global data
// structures. That is provided elsewhere, in the module "globaldb".
//
//
// UNITTESTS
//
// We reserve the lua class name 'unittests' for storing unit tests. Any
// function placed into this class is considered a unit test. We don't separate
// unit tests from the rest of the code - they're compiled right into the main
// binary.
//
// Unit tests can be either lua functions, or Lua-registered C functions. Unit
// tests are executed by calling 'source_run_unittests'.
//
// Each unit test is run in a protected 'pcall' environment. Any errors are
// printed out to console. (At least for now).
//
////////////////////////////////////////////////////////////
#ifndef SOURCE_HPP
#define SOURCE_HPP
#include "luastack.hpp"
class SourceDB {
private:
lua_State *lua_state_;
public:
// Initialize
//
// This must be called while the lua state is still pristine.
// This will install the builtin operators into the lua state
// and will snapshot those builtins to the registry.
//
void initialize(lua_State *L);
// Update
//
// Read all the lua source files from disk and store them in the
// source database. Also compiles these files using lua's "load"
// function. Efficient: if a source file is already in the database
// and hasn't been modified, it is not reloaded.
//
void update();
// Rebuild
//
// Rebuild the lua environment: clear it out, then reinstall all the
// functions that should be there. See above for more information.
// If an error exists in any of the source files, or when loading any
// of the closures, the error is (currently) printed. We'll come up
// with better error handling later.
//
void rebuild();
// run_unittests
//
// Run all the unit tests. Print any errors to console. If there
// are any errors, exits the program.
//
void run_unittests();
};
// The Lua 'makeclass' operator.
//
// Creates a table in the global environment with the specified name.
// Adds a __class field and an __index field, and an action subtable.
// If there's already a table with this name in the global environment,
// leaves it there, and repairs the __class and __index fields.
//
int source_makeclass(lua_State *L);
#endif // SOURCE_HPP

214
luprex/core/cpp/table.cpp Normal file
View File

@@ -0,0 +1,214 @@
#include "table.hpp"
#include "source.hpp"
LuaDefine(table_equal, "c") {
LuaArg t1, t2;
LuaRet eql;
LuaStack LS(L, t1, t2, eql);
LS.checktable(t1);
LS.checktable(t2);
lua_pushnil(L);
int total1 = 0;
while (lua_next(L, t1.index()) != 0) {
lua_pushvalue(L, -2); // k v1 k
lua_rawget(L, t2.index()); // k v1 v2
if (!lua_equal(L, -1, -2)) {
LS.set(eql, false);
return LS.result();
}
lua_pop(L, 2);
total1 += 1;
}
int total2 = 0;
lua_pushnil(L);
while (lua_next(L, t2.index()) != 0) {
lua_pop(L, 1);
total2 += 1;
}
LS.set(eql, total1 == total2);
return LS.result();
}
LuaDefine(table_findremove, "c") {
luaL_checktype(L, -2, LUA_TTABLE);
int src = 1;
int dst = 1;
while (true) {
lua_pushinteger(L, src);
lua_rawget(L, -3);
if (lua_equal(L, -1, -2)) {
src++;
lua_pop(L, 1);
} else if (lua_isnil(L, -1)) {
lua_pop(L, 1);
int removed = src - dst;
while (src > dst) {
lua_pushinteger(L, dst);
lua_pushnil(L);
lua_rawset(L, -4);
dst++;
}
lua_pop(L, 2);
lua_pushinteger(L, removed);
return 1;
} else {
if (src > dst) {
lua_pushinteger(L, dst);
lua_insert(L, lua_gettop(L) - 1);
lua_rawset(L, -4);
} else {
lua_pop(L, 1);
}
src++;
dst++;
}
}
}
LuaDefine(table_push, "c") {
luaL_checktype(L, -2, LUA_TTABLE);
int len = lua_objlen(L, -2);
lua_pushinteger(L, len+1);
lua_pushvalue(L, -2);
lua_rawset(L, -4);
lua_pop(L, 2);
return 0;
}
LuaDefine(table_find, "c") {
luaL_checktype(L, -2, LUA_TTABLE);
for (int i = 1; ; i++) {
lua_pushinteger(L, i);
lua_rawget(L, -3);
if (lua_equal(L, -1, -2)) {
lua_pop(L, 3);
lua_pushinteger(L, i);
return 1;
} else if (lua_isnil(L, -1)) {
lua_pop(L, 3);
lua_pushnil(L);
return 1;
} else {
lua_pop(L, 1);
}
}
}
LuaDefine(table_empty, "c") {
luaL_checktype(L, -1, LUA_TTABLE);
lua_pushnil(L);
if (lua_next(L, -2) != 0) {
lua_pop(L, 3);
lua_pushboolean(L, 0);
return 1;
} else {
lua_pop(L, 1);
lua_pushboolean(L, 1);
return 1;
}
}
LuaDefine(table_count, "c") {
luaL_checktype(L, -1, LUA_TTABLE);
lua_pushnil(L);
lua_Integer total = 0;
while (lua_next(L, -2) != 0) {
total += 1;
lua_pop(L, 1);
}
lua_pop(L, 1);
lua_pushinteger(L, total);
return 1;
}
LuaDefine(table_clear, "c") {
LuaArg tab;
LuaStack LS(L, tab);
LS.checktable(tab);
lua_pushnil(L);
while (lua_next(L, tab.index()) != 0) {
lua_pop(L, 1); // Pop the old value.
lua_pushvalue(L, -1); // Clone the key
lua_pushnil(L); // Push the new value.
lua_settable(L, tab.index());
}
return LS.result();
}
LuaDefine(queue_create, "c") {
LuaRet queue;
LuaStack LS(L, queue);
LS.newtable(queue);
LS.setfield(queue, "head", 1000000);
LS.setfield(queue, "tail", 1000000);
return LS.result();
}
LuaDefine(queue_push, "c") {
LuaArg queue, elt;
lua_Integer head;
LuaStack LS(L, queue, elt);
LS.getfield(head, queue, "head");
LS.rawset(queue, head, elt);
LS.setfield(queue, "head", head+1);
return LS.result();
}
LuaDefine(queue_pop, "c") {
LuaArg queue;
LuaRet elt;
lua_Integer head, tail;
LuaStack LS(L, queue, elt);
LS.getfield(tail, queue, "tail");
LS.getfield(head, queue, "head");
if (head == tail) {
LS.set(elt, LuaNil);
} else {
LS.rawget(elt, queue, tail);
LS.rawset(queue, tail, LuaNil);
LS.setfield(queue, "tail", tail + 1);
}
return LS.result();
}
LuaDefine(queue_size, "c") {
LuaArg queue;
LuaRet size;
lua_Number head, tail;
LuaStack LS(L, queue, size);
LS.getfield(head, queue, "head");
LS.getfield(tail, queue, "tail");
LS.set(size, head - tail);
return LS.result();
}
LuaDefine(queue_nth, "c") {
LuaArg queue, n;
LuaRet elt;
lua_Integer nth, head, tail;
LuaStack LS(L, queue, n, elt);
nth = LS.ckinteger(n) - 1;
LS.getfield(head, queue, "head");
LS.getfield(tail, queue, "tail");
if ((nth < 0) || (nth + tail >= head)) {
luaL_error(L, "index out of range");
}
LS.rawget(elt, queue, tail + nth);
return LS.result();
}
LuaDefine(table_getregistry, "f") {
LuaArg key;
LuaRet result;
LuaStack LS(L, key, result);
LS.rawget(result, LuaRegistry, key);
return LS.result();
}

113
luprex/core/cpp/table.hpp Normal file
View File

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

View File

@@ -0,0 +1,173 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <vector>
#include <string>
#include <iostream>
#include "luastack.hpp"
#include "util.hpp"
#include "gui.hpp"
#include "viewer.hpp"
#include "traceback.hpp"
#include "textgame.hpp"
#include "luaconsole.hpp"
// Add another error status.
static lua_State *globalL = NULL;
static void lstop(lua_State *L, lua_Debug *ar)
{
(void)ar; /* unused arg. */
lua_sethook(L, NULL, 0, 0);
/* Avoid luaL_error -- a C hook doesn't add an extra frame. */
luaL_where(L, 0);
lua_pushfstring(L, "%sinterrupted!", lua_tostring(L, -1));
lua_error(L);
}
static void laction(int i)
{
signal(i, SIG_DFL); /* if another SIGINT happens before lstop,
terminate process (default action) */
lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1);
}
static void l_message(const char *msg)
{
fputs(msg, stderr);
fputc('\n', stderr);
fflush(stderr);
}
void TextGame::do_lua(const std::string &exp) {
lua_State *L = viewer_.state();
int status = luaL_loadbuffer(L, exp.c_str(), exp.size(), "=stdin");
assert(status == LUA_OK);
globalL = L;
signal(SIGINT, laction);
status = traceback_pcall(L, 0, LUA_MULTRET);
signal(SIGINT, SIG_DFL);
if (status == LUA_OK) {
if (lua_gettop(L) > 0) {
lua_getglobal(L, "pprint");
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
lua_getglobal(L, "print");
}
lua_insert(L, 1);
if (lua_pcall(L, lua_gettop(L) - 1, 0, 0) != 0) {
l_message(
lua_pushfstring(L, "error calling 'print' (%s)",
lua_tostring(L, -1)));
}
}
} else {
const char *msg = lua_tostring(L, -1);
if (msg == NULL) {
msg = "(error object is not a string)";
}
l_message(msg);
lua_pop(L, 1);
lua_gc(L, LUA_GCCOLLECT, 0);
}
}
void TextGame::do_view_command(const StringVec &cmd) {
if (cmd.size() != 1) {
std::cerr << "v command (view) takes no arguments" << std::endl;
return;
}
for (int64_t id : viewer_.get_near()) {
const Tangible *tan = viewer_.tangible_get(id);
const AnimQueue &aq = tan->anim_queue_;
std::cerr << id << ": " << aq.get_graphic() << " " << aq.get_plane() << " " << aq.get_xyz() << std::endl;
}
}
void TextGame::do_menu_command(const StringVec &cmd) {
int64_t id;
if (cmd.size() == 1) {
id = 1;
} else if (cmd.size() == 2) {
id = util::strtoint(cmd[1], -1);
} else {
std::cerr << "m command (menu) expects a tangible ID or defaults to 1" << std::endl;
return;
}
gui_id_ = id;
viewer_.update_gui(id, &gui_);
int index = 0;
for (const GuiElt &elt : gui_.elts()) {
std::cerr << index << " " << elt.label() << std::endl;
index += 1;
}
}
void TextGame::do_choose_command(const StringVec &cmd) {
int64_t index;
if (cmd.size() == 2) {
index = util::strtoint(cmd[1], -1);
} else {
std::cerr << "c command (choose) expects a menu line number" << std::endl;
return;
}
std::cerr << "Choose command (index " << index << ") not implemented yet." << std::endl;
}
void TextGame::do_snapshot_command(const StringVec &cmd) {
if (cmd.size() != 1) {
std::cerr << "s command (snapshot) takes no arguments" << std::endl;
return;
}
viewer_.snapshot();
}
void TextGame::do_rollback_command(const StringVec &cmd) {
if (cmd.size() != 1) {
std::cerr << "r command (rollback) takes no arguments" << std::endl;
return;
}
viewer_.rollback();
}
void TextGame::do_quit_command(const StringVec &cmd) {
if (cmd.size() != 1) {
std::cerr << "q command (quit) takes no arguments" << std::endl;
return;
}
exit(0);
}
void TextGame::do_command(const StringVec &words) {
switch (words[0][0]) {
case 'v': do_view_command(words); break;
case 'm': do_menu_command(words); break;
case 'c': do_choose_command(words); break;
case 'q': do_quit_command(words); break;
case 's': do_snapshot_command(words); break;
case 'r': do_rollback_command(words); break;
default:
std::cerr << "Unknown command: " << words[0] << std::endl;
}
}
void TextGame::run()
{
console_.clear();
while (true) {
console_.add_stdin();
int action = console_.action();
if (action == LuaConsole::DO_LUA) {
do_lua(console_.lua_expression());
} else if (action == LuaConsole::DO_COMMAND) {
do_command(console_.words());
} else if (action == LuaConsole::DO_SYNTAX) {
std::cerr << console_.syntax() << std::endl;
}
}
}

View File

@@ -0,0 +1,30 @@
#ifndef TEXTGAME_HPP
#define TEXTGAME_HPP
#include "viewer.hpp"
#include "luaconsole.hpp"
class TextGame {
private:
using StringVec = LuaConsole::StringVec;
Viewer viewer_;
LuaConsole console_;
Gui gui_;
int64_t gui_id_;
void do_view_command(const StringVec &cmd);
void do_menu_command(const StringVec &cmd);
void do_choose_command(const StringVec &cmd);
void do_quit_command(const StringVec &cmd);
void do_snapshot_command(const StringVec &cmd);
void do_rollback_command(const StringVec &cmd);
void do_lua(const std::string &exp);
void do_command(const StringVec &exp);
public:
void run();
};
#endif // TEXTGAME_HPP

View File

@@ -0,0 +1,89 @@
#include "traceback.hpp"
#define TRACEBACK_LEVELS1 12
#define TRACEBACK_LEVELS2 10
static int traceback_general(lua_State *L, lua_State *L1, int msgindex) {
int top = lua_gettop(L);
// Convert message to a string and push the string.
if (lua_tostring(L, msgindex)) {
lua_pushvalue(L, msgindex);
} else {
luaL_callmeta(L, msgindex, "__tostring");
}
// If we didn't end up with exactly one string on
// the stack, then clear the stack and push 'unknown error'.
if ((lua_gettop(L) != top + 1) || (!lua_tostring(L, -1))) {
lua_settop(L, top);
lua_pushstring(L, "unknown error");
}
// Append the traceback.
lua_Debug ar;
int level = 1;
int firstpart = 1;
while (lua_getstack(L1, level++, &ar)) {
if (level > TRACEBACK_LEVELS1 && firstpart) {
/* no more than `LEVELS2' more levels? */
if (!lua_getstack(L1, level + TRACEBACK_LEVELS2, &ar))
level--; /* keep going */
else {
lua_pushliteral(L, "\n\t..."); /* too many levels */
while (lua_getstack(L1, level + TRACEBACK_LEVELS2, &ar)) /* find last levels */
level++;
}
firstpart = 0;
continue;
}
lua_getinfo(L1, "Snl", &ar);
if ((ar.currentline > 0) || (*ar.namewhat != 0) || (*ar.what != 'C')) {
lua_pushliteral(L, "\n\t");
lua_pushfstring(L, "%s:", ar.short_src);
if (ar.currentline > 0)
lua_pushfstring(L, "%d:", ar.currentline);
if (*ar.namewhat != '\0') /* is there a name? */
lua_pushfstring(L, " in function " LUA_QS, ar.name);
else {
if (*ar.what == 'm') /* main? */
lua_pushfstring(L, " in main chunk");
else if (*ar.what == 'C' || *ar.what == 't')
lua_pushliteral(L, " ?"); /* C function or tail call */
else
lua_pushfstring(L, " in function <%s:%d>",
ar.short_src, ar.linedefined);
}
if (lua_gettop(L) - top > 5) {
lua_concat(L, lua_gettop(L) - top);
}
}
}
lua_pushstring(L, "\n");
if (lua_gettop(L) - top > 1) {
lua_concat(L, lua_gettop(L) - top);
}
return 1;
}
LuaDefine(traceback_handler, "c") {
return traceback_general(L, L, lua_gettop(L));
}
LuaDefine(traceback_coroutine, "c") {
LuaArg thread, message;
LuaStack LS(L, thread, message);
lua_State *L1 = LS.ckthread(thread);
return traceback_general(L, L1, message.index());
}
int traceback_pcall(lua_State *L, int narg, int nret) {
int status;
int base = lua_gettop(L) - narg; /* function index */
lua_pushcfunction(L, traceback_handler); /* push traceback function */
lua_insert(L, base); /* put it under chunk and args */
status = lua_pcall(L, narg, nret, base);
lua_remove(L, base); /* remove traceback function */
return status;
}

View File

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

134
luprex/core/cpp/util.cpp Normal file
View File

@@ -0,0 +1,134 @@
#include <string>
#include <vector>
#include <fstream>
#include <cstdlib>
#include "util.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
#define stat _stat
#endif
namespace util {
std::string tolower(std::string input) {
for (int i = 0; i < int(input.size()); i++) {
input[i] = std::tolower(input[i]);
}
return input;
}
std::string toupper(std::string input) {
for (int i = 0; i < int(input.size()); i++) {
input[i] = std::toupper(input[i]);
}
return input;
}
int64_t strtoint(const std::string &value, int64_t errval) {
char *endptr;
int64_t result = strtoll(value.c_str(), &endptr, 10);
if (endptr == value.c_str() + value.size()) {
return result;
} else {
return errval;
}
}
std::string ltrim(std::string s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
std::not1(std::ptr_fun<int, int>(std::isspace))));
return s;
}
std::string rtrim(std::string s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
return s;
}
std::string trim(std::string s) {
return ltrim(rtrim(s));
}
std::string get_file_contents(const std::string &fn) {
std::ifstream fs(fn);
std::stringstream buffer;
buffer << fs.rdbuf();
return buffer.str();
}
stringvec get_file_lines(const std::string &path) {
stringvec result;
std::ifstream f;
f.open(path);
if (f) {
std::string line;
while (std::getline(f, line)) {
result.push_back(line);
}
f.close();
}
return result;
}
std::string get_file_fingerprint(const std::string &fn) {
struct stat result;
if(stat(fn.c_str(), &result)==0)
{
std::stringstream ss;
ss << result.st_mtime;
return ss.str();
}
return "";
}
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 hash functions are part of 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 *= (1.0 / 0xFFFFFFFF);
result *= (hi-lo);
result += lo;
return result;
}
std::ostream & operator << (std::ostream &out, const XYZ &xyz) {
out << "(" << xyz.x << "," << xyz.y << "," << xyz.z << ")";
return out;
}
} // namespace util

57
luprex/core/cpp/util.hpp Normal file
View File

@@ -0,0 +1,57 @@
#ifndef UTIL_HPP
#define UTIL_HPP
#include <string>
#include <set>
#include <algorithm>
#include <sstream>
#include <ostream>
#include <tuple>
#include <utility>
namespace util {
using stringvec = std::vector<std::string>;
using stringset = std::set<std::string>;
// String to lowercase/uppercase
std::string tolower(std::string input);
std::string toupper(std::string input);
// String to integer. Returns errval if the number is not parseable.
int64_t strtoint(const std::string &value, int64_t errval);
// Trim strings: left end, right end, both ends.
std::string ltrim(std::string s);
std::string rtrim(std::string s);
std::string trim(std::string s);
// Read a file as one big string.
std::string get_file_contents(const std::string &path);
// Read a file as a vector of lines.
stringvec get_file_lines(const std::string &path);
// Get a file's fingerprint - ie, size and modification time.
std::string get_file_fingerprint(const std::string &path);
// Calculate distance between two points
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);
// Return a pseudorandom float between lo and hi inclusive.
double hash_to_float(double lo, double hi, uint32_t a, uint32_t b, uint32_t c);
// An XYZ coordinate, general purpose.
struct XYZ {
float x, y, z;
XYZ() { x=0; y=0; z=0; }
XYZ(float ix, float iy, float iz) { x=ix; y=iy; z=iz; }
bool operator ==(const XYZ &o) { return x==o.x && y == o.y && z==o.z; }
};
std::ostream & operator << (std::ostream &out, const XYZ &xyz);
} // namespace util
#endif // UTIL_HPP

View File

@@ -0,0 +1,19 @@
#include "viewer.hpp"
Viewer::Viewer() {
world_.reset(new World);
world_->init_standalone();
}
Viewer::~Viewer() {
}
int64_t Viewer::get_player_id() {
return 1;
}
void Viewer::update_gui(int64_t place, Gui *gui) {
world_->update_gui(get_player_id(), place, gui);
}

View File

@@ -0,0 +1,46 @@
#ifndef VIEWER_HPP
#define VIEWER_HPP
#include "world.hpp"
#include "animqueue.hpp"
#include "gui.hpp"
#include <vector>
#include <string>
#include <memory>
class Viewer {
private:
std::unique_ptr<World> world_;
public:
Viewer();
~Viewer();
// Snapshot/rollback the lua state (temporary hack)
void snapshot() { world_->lua_snap_.snapshot(); }
void rollback() { world_->lua_snap_.rollback(); }
// Get the lua state for interaction.
//
lua_State *state() { return world_->state(); }
// Get the player ID of the current player.
//
int64_t get_player_id();
// Get the list of tangibles near the player.
//
std::vector<int64_t> get_near() { return world_->get_near(1, 100); }
// Get the specified tangible.
//
const Tangible *tangible_get(int64_t id) { return world_->tangible_get(id); }
// Update the GUI for the specified sprite.
//
// The gui passed in will be overwritten.
//
void update_gui(int64_t id, Gui *g);
};
#endif // VIEWER_HPP

199
luprex/core/cpp/world.cpp Normal file
View File

@@ -0,0 +1,199 @@
#include "world.hpp"
#include "idalloc.hpp"
#include "animqueue.hpp"
#include "gui.hpp"
#include "traceback.hpp"
#include <iostream>
LuaDefineType(World);
Tangible::Tangible() : world_(nullptr) {
}
World::~World() {
}
World::World() {
// Initialize the userdata metatables.
LuaStack::register_all_userdata(state());
// Initialize the ID allocator in master mode.
id_global_pool_.init_master(10);
// Prepare to manipulate the lua state.
LuaVar world;
LuaStack LS(state(), world);
// Put the world pointer into the lua registry.
LS.newpointer(world, this, false);
LS.setfield(LuaRegistry, "world", world);
// Create the tangibles table in the registry.
LS.setfield(LuaRegistry, "tangibles", LuaNewTable);
// Initialize the SourceDB
source_db_.initialize(state());
source_db_.rebuild();
LS.result();
assert (lua_gettop(state()) == 0);
}
void Tangible::be_a_player() {
if (id_player_pool_ == nullptr) {
id_player_pool_.reset(new IdPlayerPool(&world_->id_global_pool_));
anim_queue_.add(world_->id_global_pool_.get_one(), "");
anim_queue_.set_graphic("player");
LuaVar classtab, mt, place, tangibles;
LuaStack LS(world_->state(), classtab, mt, place, tangibles);
LS.call(classtab, source_makeclass, "player");
LS.getfield(tangibles, LuaRegistry, "tangibles");
LS.rawget(place, tangibles, anim_queue_.get_id());
LS.getmetatable(mt, place);
LS.setfield(mt, "__index", classtab);
LS.result();
}
}
void World::init_standalone() {
// Load the lua source from disk then rebuild the environment.
source_db_.update();
source_db_.rebuild();
// Run unit tests.
source_db_.run_unittests();
// Create the player tangible.
Tangible *player = tangible_make(state(), 1, false);
player->be_a_player();
}
Tangible *World::tangible_get(int64_t id) {
auto iter = tangibles_.find(id);
if (iter == tangibles_.end()) {
return nullptr;
} else {
return &iter->second;
}
}
std::vector<int64_t> World::get_near(int64_t player_id, float radius) {
Tangible *player = tangible_get(player_id);
// Find out where's the center of the world.
std::string plane = player->anim_queue_.get_plane();
util::XYZ xyz = player->anim_queue_.get_xyz();
return plane_map_.scan_radius(plane, xyz.x, xyz.y, radius, player_id);
}
Tangible *World::tangible_make(lua_State *L, int64_t id, bool pushdb) {
LuaVar tangibles, metatab;
LuaRet database;
LuaStack LS(L, tangibles, database, metatab);
// Allocate an ID if we don't already have one.
if (id == 0) id = id_global_pool_.alloc_id_for_thread(L);
// Create the C++ part of the structure.
Tangible *t = &tangibles_[id];
assert(t->world_ == nullptr);
t->world_ = this;
t->plane_item_.set_id(id);
t->anim_queue_.set_id(id);
plane_map_.track(&t->plane_item_);
// Create the tangible's database and metatable.
LS.set(database, LuaNewTable);
LS.set(metatab, LuaNewTable);
LS.setmetatable(database, metatab);
// Store the database into the tangibles table.
LS.getfield(tangibles, LuaRegistry, "tangibles");
LS.rawset(tangibles, id, database);
// Populate the database and metatable with initial stuff.
LS.setfield(database, "inventory", LuaNewTable);
LS.setfield(database, "id", id);
LS.setfield(metatab, "id", id);
// LS.setfield(metatab, "__metatable", LuaNil);
LS.result();
if (!pushdb) lua_pop(L, 1);
return t;
}
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;
}
void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) {
gui->clear();
lua_State *L = state();
LuaVar actor, place, ugui, func, tangibles;
LuaStack LS(L, actor, place, ugui, func, tangibles);
// Get the actor and place.
LS.getfield(tangibles, LuaRegistry, "tangibles");
LS.rawget(actor, tangibles, actor_id);
LS.rawget(place, tangibles, place_id);
if (!LS.istable(actor) || !LS.istable(place)) {
LS.result();
return;
}
// Get the interface closure.
LS.getfield(func, place, "interface");
if (!LS.isfunction(func)) {
LS.result();
return;
}
// Construct the userdata with the GUI pointer.
LS.newpointer<Gui>(ugui, gui, false);
// Call the interface function.
lua_pushvalue(L, func.index());
lua_pushvalue(L, actor.index());
lua_pushvalue(L, place.index());
lua_pushvalue(L, ugui.index());
int status = traceback_pcall(L, 3, 0);
if (status != 0) {
gui->clear();
std::cerr << lua_tostring(L, -1);
LS.result();
return;
}
// Nuke the userdata, in case somebody saved a pointer to it.
LS.clearuserdata(ugui);
// And we're done.
LS.result();
}
LuaDefine(tangible_get, "c") {
LuaArg id;
LuaRet database;
LuaVar tangibles;
LuaStack LS(L, id, database, tangibles);
LS.getfield(tangibles, LuaRegistry, "tangibles");
LS.rawget(database, tangibles, id);
return LS.result();
}
LuaDefine(tangible_make, "c") {
World::fetch(L)->tangible_make(L, 0, true);
return 1;
}

123
luprex/core/cpp/world.hpp Normal file
View File

@@ -0,0 +1,123 @@
#ifndef WORLD_HPP
#define WORLD_HPP
#include "luastack.hpp"
#include "planemap.hpp"
#include "idalloc.hpp"
#include "animqueue.hpp"
#include "source.hpp"
#include "gui.hpp"
#include "luasnap.hpp"
#include <memory>
#include <unordered_map>
class World;
class Tangible {
public:
// Simple constructor initializes everything to null.
//
Tangible();
// Always points back to the world model.
World *world_;
// Animation queue.
//
AnimQueue anim_queue_;
// Plane Item.
//
// The PlaneItem also contains this tangible's ID.
// To move this Tangible, update the anim_queue first, then update
// this plane_item_ from the anim_queue.
PlaneItem plane_item_;
// Player ID pool
//
// Note: this is only allocated if this Tangible is a player.
std::unique_ptr<IdPlayerPool> id_player_pool_;
void be_a_player();
};
class World {
public:
// A lua intepreter with snapshot function.
//
LuaSnap lua_snap_;
// The Global ID Pool.
//
IdGlobalPool id_global_pool_;
// Source Database.
//
SourceDB source_db_;
PlaneMap plane_map_;
std::unordered_map<int64_t, Tangible> tangibles_;
public:
// Constructor.
//
// The constructor also calls 'lua_open' to create a new
// lua interpreter for this world model. The lua interpreter
// is stored in world::lua_state_. A significant amount of
// initial setup is done by this constructor.
//
World();
// Destructor.
//
// Not currently functional.
//
~World();
// Initialize for single-player mode.
//
void init_standalone();
// get_lua_state
//
// Get the lua interpreter associated with this world model.
//
lua_State *state() { return lua_snap_.state(); }
// get_near
//
// Get a list of the tangibles that are near the player.
//
std::vector<int64_t> get_near(int64_t player_id, float radius);
// Make a tangible.
//
// If the ID is zero, allocates an ID using the thread's ID allocator. If
// pushdb is true, pushes the tangible's database onto the lua stack.
// Otherwise, leaves the lua stack untouched.
//
Tangible *tangible_make(lua_State *L, int64_t id, bool pushdb);
// Get a pointer to the specified tangible.
//
Tangible *tangible_get(int64_t id);
// Delete a tangible with the specified ID.
//
// If the tangible doesn't exist, does nothing.
//
void tangible_del(int64_t id);
// Probe the 'interface' function of the specified sprite.
//
void update_gui(int64_t actor_id, int64_t place_id, Gui *gui);
// fetch
//
// Given a lua state, fetch the world model associated with
// that lua state.
//
static World *fetch(lua_State *L);
};
#endif // WORLD_HPP