Combining two repos
This commit is contained in:
132
luprex/core/cpp/animqueue.cpp
Normal file
132
luprex/core/cpp/animqueue.cpp
Normal 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;
|
||||
}
|
||||
104
luprex/core/cpp/animqueue.hpp
Normal file
104
luprex/core/cpp/animqueue.hpp
Normal 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
|
||||
|
||||
49
luprex/core/cpp/globaldb.cpp
Normal file
49
luprex/core/cpp/globaldb.cpp
Normal 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();
|
||||
}
|
||||
50
luprex/core/cpp/globaldb.hpp
Normal file
50
luprex/core/cpp/globaldb.hpp
Normal 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
27
luprex/core/cpp/gui.cpp
Normal 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
39
luprex/core/cpp/gui.hpp
Normal 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
247
luprex/core/cpp/idalloc.cpp
Normal 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
161
luprex/core/cpp/idalloc.hpp
Normal 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
|
||||
|
||||
106
luprex/core/cpp/luaconsole.cpp
Normal file
106
luprex/core/cpp/luaconsole.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
78
luprex/core/cpp/luaconsole.hpp
Normal file
78
luprex/core/cpp/luaconsole.hpp
Normal 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
195
luprex/core/cpp/luasnap.cpp
Normal 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();
|
||||
}
|
||||
60
luprex/core/cpp/luasnap.hpp
Normal file
60
luprex/core/cpp/luasnap.hpp
Normal 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
|
||||
265
luprex/core/cpp/luastack.cpp
Normal file
265
luprex/core/cpp/luastack.cpp
Normal 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();
|
||||
}
|
||||
549
luprex/core/cpp/luastack.hpp
Normal file
549
luprex/core/cpp/luastack.hpp
Normal 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
8
luprex/core/cpp/main.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
#include "textgame.hpp"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
TextGame tg;
|
||||
tg.run();
|
||||
}
|
||||
330
luprex/core/cpp/planemap.cpp
Normal file
330
luprex/core/cpp/planemap.cpp
Normal 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;
|
||||
}
|
||||
136
luprex/core/cpp/planemap.hpp
Normal file
136
luprex/core/cpp/planemap.hpp
Normal 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
333
luprex/core/cpp/source.cpp
Normal 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
177
luprex/core/cpp/source.hpp
Normal 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
214
luprex/core/cpp/table.cpp
Normal 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
113
luprex/core/cpp/table.hpp
Normal 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
|
||||
173
luprex/core/cpp/textgame.cpp
Normal file
173
luprex/core/cpp/textgame.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
luprex/core/cpp/textgame.hpp
Normal file
30
luprex/core/cpp/textgame.hpp
Normal 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
|
||||
|
||||
89
luprex/core/cpp/traceback.cpp
Normal file
89
luprex/core/cpp/traceback.cpp
Normal 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;
|
||||
}
|
||||
37
luprex/core/cpp/traceback.hpp
Normal file
37
luprex/core/cpp/traceback.hpp
Normal 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
134
luprex/core/cpp/util.cpp
Normal 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
57
luprex/core/cpp/util.hpp
Normal 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
|
||||
19
luprex/core/cpp/viewer.cpp
Normal file
19
luprex/core/cpp/viewer.cpp
Normal 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);
|
||||
}
|
||||
|
||||
46
luprex/core/cpp/viewer.hpp
Normal file
46
luprex/core/cpp/viewer.hpp
Normal 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
199
luprex/core/cpp/world.cpp
Normal 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
123
luprex/core/cpp/world.hpp
Normal 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
|
||||
Reference in New Issue
Block a user