diff --git a/luprex/build.bat b/luprex/build.bat index 9c6c3d7c..e0243437 100644 --- a/luprex/build.bat +++ b/luprex/build.bat @@ -1,2 +1,2 @@ clear -g++ -std=c++17 -Wall -g -o main syscpp/util.cpp syscpp/main.cpp syscpp/planemap.cpp syscpp/world.cpp syscpp/traceback.cpp syscpp/luastack.cpp syscpp/source.cpp syscpp/table.cpp syscpp/idalloc.cpp syscpp/globaldb.cpp -Iinc -Llib lib/libluajit-dbg.a -Isyscpp +g++ -std=c++17 -Wall -g -o main syscpp/util.cpp syscpp/main.cpp syscpp/animqueue.cpp syscpp/planemap.cpp syscpp/world.cpp syscpp/traceback.cpp syscpp/luastack.cpp syscpp/source.cpp syscpp/table.cpp syscpp/idalloc.cpp syscpp/globaldb.cpp -Iinc -Llib lib/libluajit-dbg.a -Isyscpp diff --git a/luprex/syscpp/animqueue.cpp b/luprex/syscpp/animqueue.cpp new file mode 100644 index 00000000..d588711b --- /dev/null +++ b/luprex/syscpp/animqueue.cpp @@ -0,0 +1,61 @@ + +#include "animqueue.hpp" +#include + + +AnimStep::AnimStep() {} +AnimStep::~AnimStep() {} + +AnimQueue::AnimQueue(int size_limit) { + assert(size_limit >= 2); + size_limit_ = 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_ = 0; +} + +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_ = 0; +} + +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; +} diff --git a/luprex/syscpp/animqueue.hpp b/luprex/syscpp/animqueue.hpp new file mode 100644 index 00000000..002b0c9d --- /dev/null +++ b/luprex/syscpp/animqueue.hpp @@ -0,0 +1,91 @@ +/////////////////////////////////////////////////////////////////// +// +// 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 +#include +#include +#include +#include "util.hpp" + + +class AnimStep { + friend class AnimQueue; +public: + enum { + HAS_FACING = 1, + HAS_XYZ = 2, + HAS_GRAPHIC = 4, + HAS_PLANE = 8, + }; + +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: + int size_limit_; + std::deque steps_; +public: + AnimQueue(int size_limit); + 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); +}; + +#endif // ANIMQUEUE_HPP + diff --git a/luprex/syscpp/globaldb.cpp b/luprex/syscpp/globaldb.cpp index 9f80219a..446cfb80 100644 --- a/luprex/syscpp/globaldb.cpp +++ b/luprex/syscpp/globaldb.cpp @@ -2,6 +2,17 @@ #include "globaldb.hpp" #include "table.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 @@ -15,15 +26,14 @@ LuaDefine(globaldb_global, "f") { LuaVar globaldb; LuaStack LS(L, globalname, globaltab, globaldb); - LS.checkstring(globalname); - - // Get a pointer to the globaldb. + // Get a pointer to the globaldb. LS.getfield(globaldb, LuaRegistry, "globaldb"); if (!LS.istable(globaldb)) { - LS.newtable(globaldb); - LS.setfield(LuaRegistry, "globaldb", 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)) { diff --git a/luprex/syscpp/globaldb.hpp b/luprex/syscpp/globaldb.hpp index ddc1109c..cfbce727 100644 --- a/luprex/syscpp/globaldb.hpp +++ b/luprex/syscpp/globaldb.hpp @@ -1,10 +1,48 @@ - +//////////////////////////////////////////////////////////// +// +// 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 diff --git a/luprex/syscpp/idalloc.cpp b/luprex/syscpp/idalloc.cpp index 1fdcd5b0..6022d197 100644 --- a/luprex/syscpp/idalloc.cpp +++ b/luprex/syscpp/idalloc.cpp @@ -99,7 +99,9 @@ void IdPlayerPool::purge() { void IdPlayerPool::refill() { while (int(ranges_.size()) < global_->queue_fill()) { - ranges_.push_back(global_->get_batch()); + int64_t batch = global_->get_batch(); + if (batch == 0) break; + ranges_.push_back(batch); } } @@ -112,11 +114,17 @@ void IdPlayerPool::unqueue() { int64_t IdPlayerPool::get_batch() { while (int(ranges_.size()) < global_->queue_fill() + 1) { - ranges_.push_back(global_->get_batch()); + 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; } - int64_t batch = ranges_.front(); - ranges_.pop_front(); - return batch; } void IdPlayerPool::salvage_thread(lua_State *L) { @@ -184,6 +192,15 @@ LuaDefine(cunittests_idalloc, "c") { gp.salvage(nthbatch(142) + 145); assert(gp.get_batch() == nthbatch(2)); + // In the synchronous model, refill should do nothing. + pp.purge(); + gp.init_synch(3); + pp.refill(); + assert(pp.size() == 0); + assert(pp.get_batch() == 0); + assert(pp.size() == 0); + assert(pp.get_batch() == 0); + // Test refill from master. pp.purge(); gp.init_master(3); diff --git a/luprex/syscpp/idalloc.hpp b/luprex/syscpp/idalloc.hpp index 7360db56..dbc5386b 100644 --- a/luprex/syscpp/idalloc.hpp +++ b/luprex/syscpp/idalloc.hpp @@ -1,51 +1,66 @@ -// The ID allocator. +/////////////////////////////////////////////////////////////////// // -// This ID allocator attempts to allocate IDs in such a way that the +// 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. // -// Every logged-in player maintains a "batch queue". That's a fifo queue -// of ID batches. An ID batch is a contiguous range of IDs, containing -// between 128 and 256 contiguous IDs. The batch queue is difference -// transmitted, to ensure that the synchronous model has the same -// batches 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 -// player's batch queue. When that thread allocates IDs, -// it uses the batch it was allocated. If the batch is used up, then the -// thread falls back to using a global fallback allocator. Such fallback IDs -// are not likely to be predicted correctly. +// 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 of his batches. In the -// master model, the batch queue is 'refilled' by creating a new batch. -// In the synchronous model, the batch queue 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. +// 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 0, 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 master model will reclaim that batch, -// putting it into a global salvage pool. The salvage batches are later -// used when refilling batch queues. +// 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. // -// ID ranges are assigned as follows: +// THE NUMERIC RANGES // -// 0x0000+ : reserved for future expansion. -// 0x0001+ : used by master model to allocate batches. -// 0x0010+ : used by master model to allocate unpredictable IDs. -// 0x001E+ : used by synchronous model to allocate stopgap IDs. +// * 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. // -// The operations in this class are: +// BATCH REPRESENTATION // -// idalloc.initmaster() - reinitialize the ID allocator in master mode. -// idalloc.initsynch() - reinitialize the ID allocator in synchronous mode. -// idalloc.setqueue_fill() - override the default queue fill level. -// idalloc.refill(bq) - get batches from the global pool until the batch queue is full. -// idalloc.unqueue(bq) - push batches from the batch queue back into the global pool. -// idalloc.preparethread(bq, co) - transfer a batch from the batch queue to the coroutine -// idalloc.salvagethread(co) - push any batch in the dead coroutine back into the global pool -// -// idalloc.allocid() - get an ID using either the current thread's pool or the global pool +// 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 @@ -64,16 +79,44 @@ private: 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); - int64_t get_batch(); + + // Create a single unique ID. Ids allocated this way are + // unlikely to be predicted correctly. int64_t get_one(); - void salvage(int64_t batch); + + // 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); }; @@ -84,15 +127,34 @@ private: friend int cunittests_idalloc(lua_State *L); public: + // Construct a player pool. + // The Player pool stores a pointer to the global pool. IdPlayerPool(IdGlobalPool *gp); ~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 diff --git a/luprex/syscpp/main.cpp b/luprex/syscpp/main.cpp index 47dd5c8c..f3723409 100644 --- a/luprex/syscpp/main.cpp +++ b/luprex/syscpp/main.cpp @@ -185,8 +185,7 @@ static int pmain(lua_State *L) // Initialize the builtins, then copy a snapshot of the // builtins to the registry. This will allow us to restore // the builtins during source_rebuild operations. - source_load_builtins(L); - source_snapshot_builtins(L); + source_install_and_snapshot_builtins(L); // Load the lua source. source_update(L); @@ -194,9 +193,6 @@ static int pmain(lua_State *L) // Rebuild the global environment. source_rebuild(L); - // Run all the autoinit functions. - source_autoinit(L); - dotty(L); return 0; } diff --git a/luprex/syscpp/planemap.cpp b/luprex/syscpp/planemap.cpp index 9c12f54a..de6e48b2 100644 --- a/luprex/syscpp/planemap.cpp +++ b/luprex/syscpp/planemap.cpp @@ -68,7 +68,7 @@ void PlaneMap::remove(const std::string &plane, int64_t cellid, PlaneItem *clien if (cellid == CELL_INVALID) { return; } - if (plane == "") { + if ((plane == "") || (plane == "nowhere")) { return; } auto piter = planes_.find(plane); @@ -94,7 +94,7 @@ void PlaneMap::insert(const std::string &plane, int64_t cellid, PlaneItem *clien if (cellid == CELL_INVALID) { return; } - if (plane == "") { + if ((plane == "") || (plane == "nowhere")) { return; } Plane &p = planes_[plane]; @@ -107,10 +107,10 @@ void PlaneItem::set_pos(const std::string &plane, double x, double y, double z) int64_t new_cell = point_cell_id(x, y); // Update the grid. - if (grid_ != 0) { + if (pmap_ != 0) { if ((plane_ != plane) || (old_cell != new_cell)) { - grid_->remove(plane_, old_cell, this); - grid_->insert(plane, new_cell, this); + pmap_->remove(plane_, old_cell, this); + pmap_->insert(plane, new_cell, this); } } @@ -121,32 +121,36 @@ void PlaneItem::set_pos(const std::string &plane, double x, double y, double z) z_ = z; } -void PlaneItem::use_map(PlaneMap *grid) { - int64_t cellid = point_cell_id(x_, y_); - if (grid_ != 0) { - grid_->remove(plane_, cellid, this); - } - grid_ = grid; - if (grid_ != 0) { - grid_->insert(plane_, cellid, this); +void PlaneItem::untrack() { + if (pmap_ != 0) { + pmap_->remove(plane_, point_cell_id(x_, y_), this); + pmap_ = 0; } } -PlaneItem::PlaneItem() : grid_(NULL), x_(0.0), y_(0.0), z_(0.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() { - use_map(NULL); + untrack(); } PlaneMap::~PlaneMap() { for (const auto &p : planes_) { for (const auto &l : p.second) { for (PlaneItem *i : l.second) { - i->grid_ = NULL; + i->pmap_ = NULL; } } } @@ -262,10 +266,10 @@ LuaDefine(cunittests_planemap, "c") { pm.remove("foo", 12345, &pib); assert(pm.total_cells() == 0); - // Test the insert function on an invalid plane. + // Test the insert function on the nowhere plane. pm.clear(); - pm.insert("", 12345, &pia); - pm.insert("", 12345, &pib); + pm.insert("nowhere", 12345, &pia); + pm.insert("nowhere", 12345, &pib); assert(pm.total_cells() == 0); // Test the insert function on an invalid cell. @@ -283,17 +287,17 @@ LuaDefine(cunittests_planemap, "c") { // Attach pia to the grid. This should record it. pm.clear(); - pia.use_map(&pm); + pm.track(&pia); elts = pm.get_cell("foo", point_cell_id(3.0, 4.0)); assert(elts.size() == 1); assert(elts[0] == &pia); // Unattach pia from the grid. This should unrecord it. - pia.use_map(NULL); + pia.untrack(); assert(pm.total_cells() == 0); // Reattach pia to the grid, then move it. - pia.use_map(&pm); + pm.track(&pia); assert(pm.total_cells() == 1); pia.set_pos("bar", 1000.0, 1000.0, 0.0); assert(pm.total_cells() == 1); @@ -302,7 +306,7 @@ LuaDefine(cunittests_planemap, "c") { assert(elts[0] == &pia); // Insert the four elements, then test the scan function. - pib.use_map(&pm); + pm.track(&pib); pib.set_pos("bar", 1100.0, 1000.0, 0.0); elts = pm.scan_radius("bar", 1000.0, 1000.0, 1.0); assert(elts.size() == 1); diff --git a/luprex/syscpp/planemap.hpp b/luprex/syscpp/planemap.hpp index eb53af90..1e40794a 100644 --- a/luprex/syscpp/planemap.hpp +++ b/luprex/syscpp/planemap.hpp @@ -1,40 +1,74 @@ +////////////////////////////////////////////////////////////// // // PLANEMAP // -// Stores a map of the items that are on each plane. Doesn't -// store terrain, only items. +// 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 you are beyond the -// edge of the plane. +// 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 farther -// than that. However, if you do move a PlaneItem farther than -// that, then it won't show up in radius-scans. +// 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. // -// The PlaneMap doesn't need you to "create" planes. You just -// set the plane of a PlaneItem to any string, and that plane will -// pop into existence if it wasn't already there. +// PLANES CANNOT BE DESTROYED BUT THEY CAN BE UNINHABITED // -// If you use the empty string as a plane name, a special case is +// 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 the empty string. +// whose plane is "nowhere." The same also applies when you use the +// empty string as a plane name. // -// INHERITANCE +// THE Z COORDINATE IS IGNORED // -// The intent is that class Sprite should derive from PlaneItem. -// When you put sprites into a PlaneMap, it will work fine. However, -// when you scan the PlaneMap, it will give you PlaneItem pointers, -// not Sprite pointers. If you're sure that the PlaneMap contains -// only sprites, then you can use static_cast to convert those -// PlaneItem pointers to Sprite pointers. Sadly, there's no simple -// way to avoid the casting. +// 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 // -// The PlaneMap does not own the PlaneItems. You need to create, -// destroy, and manage the PlaneItems yourself. +// 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 @@ -47,10 +81,12 @@ class PlaneMap; class PlaneItem { friend class PlaneMap; + private: - PlaneMap *grid_; + PlaneMap *pmap_; std::string plane_; double x_, y_, z_; + public: PlaneItem(); ~PlaneItem(); @@ -60,32 +96,35 @@ public: const double y() const { return y_; } const double z() const { return z_; } - void use_map(PlaneMap *map); + 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; - friend int cunittests_planemap(lua_State *L); -public: - using Elt = PlaneItem *; - using EltVec = std::vector; private: + using EltVec = std::vector; using Plane = std::map; std::map planes_; void remove(const std::string &plane, int64_t cell, PlaneItem *client); void insert(const std::string &plane, int64_t cell, PlaneItem *client); -private: - // These functions are only used in unit tests. - EltVec get_cell(const std::string &plane, int64_t cell) const; - int total_cells() const; - void clear() { planes_.clear(); } + public: PlaneMap(); ~PlaneMap(); + void track(PlaneItem *item); EltVec scan_radius(const std::string &plane, double x, double y, double radius) const; + +private: + // unit testing stuff. + friend int cunittests_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 diff --git a/luprex/syscpp/source.cpp b/luprex/syscpp/source.cpp index da070262..4ac8b284 100644 --- a/luprex/syscpp/source.cpp +++ b/luprex/syscpp/source.cpp @@ -14,6 +14,93 @@ #include "traceback.hpp" +LuaDefine(source_makeclass, "f") { + LuaArg classname; + LuaVar action, gname; + LuaRet classtab; + LuaStack LS(L, classname, classtab, action, gname); + + LS.checkstring(classname); + + // Special case: if the classname is _G, return global env. + LS.set(gname, "_G"); + if (LS.equal(classname, gname)) { + LS.set(classtab, LuaGlobals); + return LS.result(); + } + + // Get the classtab from the global environment. + // Create it if it doesn't exist. + LS.rawget(classtab, LuaGlobals, classname); + if (LS.isnil(classtab)) { + LS.newtable(classtab); + LS.rawset(LuaGlobals, classname, classtab); + } + + // If the name isn't bound to a table, abort. + if (!LS.istable(classtab)) { + luaL_error(L, "%s is not a class", LS.ckstring(classname).c_str()); + } + + // Repair the special fields. + LS.setfield(classtab, "__index", classtab); + LS.setfield(classtab, "__class", classname); + + // Repair the action table. + LS.getfield(action, classtab, "action"); + if (!LS.istable(action)) { + LS.setfield(classtab, "action", LuaNewTable); + } + + 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); +} + +LuaDefine(source_install_builtins, "") { + 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); + // Do not load: package, io, os, debug, jit + return 0; +} + +LuaDefine(source_install_and_snapshot_builtins, "") { + 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); + return LS.result(); +} + LuaDefine(source_updatefile, "") { LuaArg source, fn; LuaRet info; @@ -95,58 +182,8 @@ LuaDefine(source_update, "c") { return LS.result(); } -// Make a class. A class is a table in the global environment. -// -// The global environment is protected, but this function can -// override that protection. -// -LuaDefine(source_makeclass, "f") { - LuaArg classname; - LuaVar action, gname; - LuaRet classtab; - LuaStack LS(L, classname, classtab, action, gname); - - LS.checkstring(classname); - - // Special case: if the classname is _G, return global env. - LS.set(gname, "_G"); - if (LS.equal(classname, gname)) { - LS.set(classtab, LuaGlobals); - return LS.result(); - } - - // Get the classtab from the global environment. - // Create it if it doesn't exist. - LS.rawget(classtab, LuaGlobals, classname); - if (LS.isnil(classtab)) { - LS.newtable(classtab); - LS.rawset(LuaGlobals, classname, classtab); - } - - // If the name isn't bound to a table, abort. - if (!LS.istable(classtab)) { - luaL_error(L, "%s is not a class", LS.ckstring(classname).c_str()); - } - - // Repair the special fields. - LS.setfield(classtab, "__index", classtab); - LS.setfield(classtab, "__class", classname); - - // Repair the action table. - LS.getfield(action, classtab, "action"); - if (!LS.istable(action)) { - LS.setfield(classtab, "action", LuaNewTable); - } - - return LS.result(); -} - -// Clear the global environment. -// -// Clears out almost everything from the global -// environment. However, does not delete class tables. -// -// This is used during source_rebuild operations. +// Delete everything from the global environment except +// the class tables and the class action tables. // LuaDefine(source_clear_globals, "") { LuaVar classname, classtab, action, key; @@ -156,13 +193,16 @@ LuaDefine(source_clear_globals, "") { LS.set(classname, LuaNil); while (LS.next(LuaGlobals, classname, classtab) != 0) { if (LS.istable(classtab)) { + bool keep_action = false; LS.getfield(action, classtab, "action"); if (LS.istable(action)) { LS.call(table_clear, action); - } else { - LS.newtable(action); + keep_action = true; } LS.call(table_clear, classtab); + if (keep_action) { + LS.setfield(classtab, "action", action); + } } else { LS.rawset(LuaGlobals, classname, LuaNil); } @@ -173,11 +213,7 @@ LuaDefine(source_clear_globals, "") { // Restore the lua builtins from the backup snapshot. // -// The global environment is expected to be clean, but -// the subtables are expected to still be present, in -// accordance with how 'source_clear_globals' works. -// -LuaDefine(source_restore_builtins, "g") { +LuaDefine(source_restore_builtins, "") { LuaVar snapshot, key, value, skey, svalue, subglobal; LuaStack LS(L, snapshot, key, value, skey, svalue, subglobal); @@ -195,57 +231,8 @@ LuaDefine(source_restore_builtins, "g") { return LS.result(); } -// Snapshot all the lua builtin functions. +// Load all the 'LuaDefine' C functions into the lua state. // -// Precondition: this is meant to be used on a pristine lua -// intepreter, before the user dumps a bunch of crap into the -// global environment. It won't work if the global environment -// contains anything other than the lua builtins. -// -// Note: the global environment contains _G. This routine -// perceives this as a subtable, so it backs up the top level -// as well. -// -LuaDefine(source_snapshot_builtins, "c") { - LuaVar key, value, skey, svalue, snapshot, ssnapshot; - LuaStack LS(L, snapshot, key, value, skey, svalue, ssnapshot); - - 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); - return LS.result(); -} - -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); -} - -LuaDefine(source_load_builtins, "") { - 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); - // Do not load: package, io, os, debug, jit - return 0; -} - LuaDefine(source_load_cfunctions, "") { auto regs = LuaFunctionReg::all(); for (const LuaFunctionReg *r : regs) { @@ -275,10 +262,7 @@ LuaDefine(source_load_cfunctions, "") { return 0; } -// Fetches the source database and runs all the loadresults. -// -// Returns a single string, which is a bunch of concatenated error -// messages. +// Run all the closures from the source database. // LuaDefine(source_load_lfunctions, "") { LuaRet errors; @@ -336,13 +320,3 @@ LuaDefine(source_rebuild, "c") { return LS.result(); } -LuaDefine(source_autoinit, "") { - auto regs = LuaFunctionReg::all(); - for (const LuaFunctionReg *r : regs) { - std::string mode = r->get_mode(); - if (mode.find('a') != std::string::npos) { - r->get_func()(L); - } - } - return 0; -} diff --git a/luprex/syscpp/source.hpp b/luprex/syscpp/source.hpp index de72ace2..337964ce 100644 --- a/luprex/syscpp/source.hpp +++ b/luprex/syscpp/source.hpp @@ -1,18 +1,102 @@ //////////////////////////////////////////////////////////// // +// SOURCE // -// The source database is a lua table that maps filenames -// to file info. The source database is stored in the registry. +// 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. // -// In the source database, the keys are filenames, and the values -// are tables containing the following fields: // -// name: filename as a string -// fingerprint: file modification, file length, as a string -// code: the entire contents of the source file as a string -// loadresult: a lua closure, or, an error message -// sequence: the position of the file in control.lst +// 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 +// +// The function 'source_update' loads the lua source code from disk, and stores +// it in the "source database." The source database is a table inside the lua +// registry. +// +// The source database is a lua table where the keys are filenames, and the +// values are information about that file: +// +// "foo.lua" : { +// "name": "foo.lua", +// "fingerprint": "12893129385854", +// "code": "function xyz ...", +// "loadresult": , +// "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 "source_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 source_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 source_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 source_rebuild operation, described below. +// +// +// SOURCE REBUILDS, IN DEPTH +// +// The function source_rebuild clears and then rebuilds the entire contents of +// the 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, a snapshot of these builtins is kept. +// +// * 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". // //////////////////////////////////////////////////////////// @@ -22,24 +106,41 @@ #include "luastack.hpp" -// Get a class from the class database. -int source_class(lua_State *L); +// 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); -// Update the source database from disk. No parameters, no return values. +// source_install_and_snapshot_builtins. +// +// Install the lua builtins into a brand new lua state, and then create the +// snapshot of the builtins (in the registry). This uses lua_openlibs, which +// only works if the lua environment is brand new. Do not try to use +// source_snapshot_builtins unless the lua environment has just been created +// using lua_newstate! +// +int source_install_and_snapshot_builtins(lua_State *L); + +// source_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. +// int source_update(lua_State *L); -// Load the builtins into the global environment using lua_openlibs -int source_load_builtins(lua_State *L); - -// Back up the pristine global environment to the registry. -int source_snapshot_builtins(lua_State *L); - -// Rebuild the class database from the source database. No parameters, no return values. +// source_rebuild +// +// Rebuild the lua environment: clear it out, then reinstall all the +// functions that should be there. See above for more information. +// int source_rebuild(lua_State *L); -// Run all 'autoinit' functions. -int source_autoinit(lua_State *L); - #endif // SOURCE_HPP diff --git a/luprex/syscpp/table.cpp b/luprex/syscpp/table.cpp index 73be7213..fe69a46b 100644 --- a/luprex/syscpp/table.cpp +++ b/luprex/syscpp/table.cpp @@ -29,16 +29,6 @@ LuaDefine(table_equal, "c") { return LS.result(); } -LuaDefine(table_append, "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_findremove, "c") { luaL_checktype(L, -2, LUA_TTABLE); int src = 1; @@ -75,6 +65,17 @@ LuaDefine(table_findremove, "c") { } } + +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++) { @@ -125,9 +126,7 @@ LuaDefine(table_clear, "c") { LuaArg tab; LuaStack LS(L, tab); - LS.checktable(tab); - LS.clearmetatable(tab); - + LS.checktable(tab); lua_pushnil(L); while (lua_next(L, tab.index()) != 0) { lua_pop(L, 1); // Pop the old value. @@ -139,14 +138,6 @@ LuaDefine(table_clear, "c") { return LS.result(); } -LuaDefine(table_coerce, "c") { - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - lua_newtable(L); - } - return 1; -} - LuaDefine(queue_create, "c") { LuaRet queue; LuaStack LS(L, queue); diff --git a/luprex/syscpp/table.hpp b/luprex/syscpp/table.hpp index 0654b720..84b784c4 100644 --- a/luprex/syscpp/table.hpp +++ b/luprex/syscpp/table.hpp @@ -1,20 +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); -int table_append(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); -int table_coerce(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 diff --git a/luprex/syscpp/traceback.hpp b/luprex/syscpp/traceback.hpp index d6ce6e1a..398acb43 100644 --- a/luprex/syscpp/traceback.hpp +++ b/luprex/syscpp/traceback.hpp @@ -1,27 +1,37 @@ ///////////////////////////////////////////////////////////////// // +// TRACEBACK ROUTINES // -// Traceback routines. -// -// traceback_handler: a traceback routine meant to be used -// as a message handler routine for 'pcall'. -// -// traceback_coroutine: takes a coroutine and an error message. -// Returns a traceback of the coroutine. -// -// traceback_pcall: same as lua_pcall, except that it supplies -// the default traceback_handler as a message handler. -// +// 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 diff --git a/luprex/syscpp/util.hpp b/luprex/syscpp/util.hpp index 30143e0a..57c767b5 100644 --- a/luprex/syscpp/util.hpp +++ b/luprex/syscpp/util.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace util { @@ -43,6 +44,7 @@ uint32_t hash3(uint32_t a, uint32_t b, uint32_t c); // Returns a floating point value between lo and hi inclusive. double hash_to_float(double lo, double hi, uint32_t a, uint32_t b, uint32_t c); +using XYZ = std::tuple; } // namespace util #endif // UTIL_HPP diff --git a/luprex/syslua/ut-globaldb.lua b/luprex/syslua/ut-globaldb.lua index 8d5c1ae5..93ad0daa 100644 --- a/luprex/syslua/ut-globaldb.lua +++ b/luprex/syslua/ut-globaldb.lua @@ -2,6 +2,7 @@ local ut = {} function ut.globaldb() + globaldb.enable() local g1a = global("unittest-g1") local g2a = global("unittest-g2") local g1b = global("unittest-g1") diff --git a/luprex/syslua/ut-table.lua b/luprex/syslua/ut-table.lua index 09c6c077..93145dc1 100644 --- a/luprex/syslua/ut-table.lua +++ b/luprex/syslua/ut-table.lua @@ -7,25 +7,12 @@ function ut.table_count() assert(table.count({[2]=5,[5]=3}) == 2) end -function ut.table_coerce() - local t = {} - local t1 = table.coerce(t) - assert(t1==t) - t1 = table.coerce(0) - assert(type(t1) == "table") - t1 = table.coerce(nil) - assert(type(t1) == "table") -end - function ut.table_clear() local t = { a = 1, b = 2 } table.clear(t) assert(t.a == nil) assert(t.b == nil) assert(table.count(t) == 0) - setmetatable(t, t) - table.clear(t) - assert(getmetatable(t) == nil) end function ut.table_empty() @@ -46,13 +33,13 @@ function ut.table_equal() assert(not table.equal({a=1,b=3},{a=1,b=2})) end -function ut.table_append() +function ut.table_push() t = {} - table.append(t, 1) + table.push(t, 1) assert(table.equal(t, {1})) - table.append(t, 2) + table.push(t, 2) assert(table.equal(t, {1,2})) - table.append(t, 3) + table.push(t, 3) assert(table.equal(t, {1,2,3})) end