Redesign of animation queue for unreal, add get_tangibles_near to drivenengine

This commit is contained in:
2023-07-24 17:19:25 -04:00
parent 4da86e6f89
commit 87aa47b96d
19 changed files with 1406 additions and 980 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -5,40 +5,26 @@
// 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.
// An animation step is a set of key-value pairs, where each key is an
// identifier, and each value is either a number, a boolean, an XYZ coordinate,
// or a string. A key-value pair can be either persistent or nonpersistent.
// So a typical animation step might be:
//
// 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.
// action=walk [nonpersistent]
// xyz=3,4,5 [persistent]
// facing=320 [persistent]
// plane=earth [persistent]
//
// 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.
// Persistent values are retained from one animation step to the next,
// nonpersistent values exist for one animation step only.
//
// VERSION NUMBERS
// Animation steps are stored encoded as strings, which is convenient for
// passing the data to unreal, but it means that the animation step has to be
// decoded whenever you want access to the key-value pairs.
//
// The version number field: if the version number in the synchronous model is
// equal to the version number in the master model, then the two animqueues are
// guaranteed to be equal. Here's how we achieve that invariant:
//
// * In the master model, the version number starts at 1 and is auto-incremented
// every time the animation queue is mutated.
//
// * In the synchronous model, the version number is set to zero every time the
// animation queue is mutated. Note that master version numbers are never
// zero. Applying a patch also sets the version number to zero.
//
// * Serializing and deserializing causes the version number to be saved and
// restored in both master and synchronous models.
//
// * The routine 'update_version' should be called after difference
// transmission. This copies the version number from the master to the
// synchronous model. This is guaranteed to be safe because we just finished
// difference transmission, therefore, the queues should match.
// Each animation step has a hash value. The hash value is generated
// by mixing the hash value of the previous step with the hash value
// of the encoded string of key-value pairs.
//
///////////////////////////////////////////////////////////////////
@@ -50,6 +36,7 @@
#include "wrap-deque.hpp"
#include "wrap-unordered-map.hpp"
#include "luastack.hpp"
#include "streambuffer.hpp"
#include "debugcollector.hpp"
#include "util.hpp"
@@ -57,184 +44,226 @@
#include <cassert>
#include <ostream>
class AnimStep : public eng::nevernew {
friend class AnimQueue;
public:
enum {
HAS_FACING = 1,
HAS_X = 2,
HAS_Y = 4,
HAS_Z = 8,
HAS_XYZ = 14,
HAS_GRAPHIC = 16,
HAS_PLANE = 32,
HAS_EVERYTHING = 63,
};
enum AnimValueType {
T_STRING,
T_NUMBER,
T_BOOLEAN,
T_XYZ,
T_UNINITIALIZED
};
AnimStep();
~AnimStep();
struct AnimValue {
AnimValue() : persistent(false), type(T_UNINITIALIZED) {}
int64_t id() const { return id_; }
int bits() const { return bits_; }
bool persistent;
AnimValueType type;
eng::string str;
util::DXYZ xyz;
const eng::string &action() const { return action_; }
double facing() const { return facing_; }
float x() const { return xyz_.x; }
float y() const { return xyz_.y; }
float z() const { return xyz_.z; }
const util::XYZ &xyz() const { return xyz_; }
const eng::string &graphic() const { return graphic_; }
const eng::string &plane() const { return plane_; }
// These set the type, str, and xyz fields in a consistent way.
void set_boolean(bool b);
void set_number(double n);
void set_xyz(const util::DXYZ &xyz);
void set_string(std::string_view s);
bool has_facing() const { return bits_ & HAS_FACING; }
bool has_x() const { return bits_ & HAS_X; }
bool has_y() const { return bits_ & HAS_Y; }
bool has_z() const { return bits_ & HAS_Z; }
bool has_xyz() const { return (bits_ & HAS_XYZ) == HAS_XYZ; }
bool has_graphic() const { return bits_ & AnimStep::HAS_GRAPHIC; }
bool has_plane() const { return bits_ & AnimStep::HAS_PLANE; }
// The get the type, str, and xyz fields in a consistent way.
bool get_boolean() const;
double get_number() const;
const util::DXYZ &get_xyz() const;
std::string_view get_string() const;
void set_action(const eng::string &action);
void set_facing(float f);
void set_x(float f);
void set_y(float f);
void set_z(float z);
void set_xyz(const util::XYZ &xyz);
void set_graphic(const eng::string &g);
void set_plane(const eng::string &p);
void clear();
// ExactlyEqual compares all fields.
bool exactly_equal(const AnimStep &other) const;
// LogicallyEqual only compares fields whose HAS_XXX bits are set.
bool logically_equal(const AnimStep &other) const;
// StateEqual is true if the plane, graphic, facing, and xyz match.
bool state_equal(const AnimStep &other) const;
// read/write the step using a stream buffer.
//
void write_into(StreamBuffer *sb) const;
void read_from(StreamBuffer *sb);
// Create an AnimStep from a lua table.
//
// Lua stack must contain a table, which may contain:
// action: "action"
// facing: 0.0 - 360.0
// x: x-coordinate
// y: y-coordinate
// z: z-coordinate
// graphic: "graphic"
// plane: "plane"
//
// aqback: the animation queue back, from which relative
// moves are computed.
//
void configure(LuaKeywordParser &kp, const AnimStep &aqback);
// Make this step into a first-step of an anim queue.
void keep_state_only();
// For any values that are unchanged in this step,
// echo the values of the previous step.
void echo(const AnimStep &prev);
// Verify that this step echoes the previous step.
bool echoes(const AnimStep &prev) const;
// Convert to a string for debugging purposes.
eng::string debug_string() const;
// Convert a string to an animstep (for testing only).
bool from_string(const eng::string &s);
private:
int64_t id_;
int16_t bits_;
eng::string action_;
float facing_;
util::XYZ xyz_;
eng::string graphic_;
eng::string plane_;
void config_store_string(lua_State *L, int idx, eng::string *target, int16_t bits, const char *name);
void config_store_number(lua_State *L, int idx, float *target, float offset, int16_t bits, const char *name);
// Copy the value from another animvalue. Don't copy the persistent flag.
void copy_value(const AnimValue &other);
};
class AnimQueue : public eng::nevernew {
class AnimState
{
private:
using Map = eng::map<eng::string, AnimValue>;
Map map_;
// Set the default value, internal
//
eng::string add_default(const eng::string &name, const AnimValue &v, const AnimState *other);
public:
// World type determines whether versions increment or autozero
AnimQueue(WorldType wt);
// Clear everything
//
void clear() { map_.clear(); }
// Simple getters.
const AnimStep &nth(int n) const { return steps_[n]; }
size_t size() const { return steps_.size(); }
// Set the persistent flag on a single variable.
// If the variable isn't present, add it.
//
void set_persistent(const eng::string &name);
// Return if it contains a value for the specified name.
//
bool contains(const eng::string &name) { return map_.find(name) != map_.end(); }
// Get the value of a specific variable.
// If the variable isn't present, return a default value.
//
bool get_boolean(const eng::string &name);
double get_number(const eng::string &name);
util::DXYZ get_xyz(const eng::string &name);
std::string_view get_string(const eng::string &name);
// Set a single variable to a value of a specified type.
// If the variable isn't present, add it.
//
void set_boolean(const eng::string &name, bool v);
void set_number(const eng::string &name, double v);
void set_xyz(const eng::string &name, const util::DXYZ &value);
void set_string(const eng::string &name, std::string_view value);
// Print a debug string into the stringstream.
//
void print_debug_string(eng::ostringstream &oss);
// Return the debug string.
eng::string debug_string();
// Constructs an empty state.
//
AnimState() {}
// Convert to an encoded string.
//
eng::string encode() const;
// Decode an encoded string.
//
void decode(std::string_view enc);
// Decode an encoded string, discarding non-persistent data.
//
void decode_persistent(std::string_view enc);
// Add default values for all builtin persistent variables.
//
// For each builtin default (plane, xyz, facing, bp, model)
//
// - Will generate an error if a value is already present,
// but the present value is of the wrong type.
//
// - If 'other' is not nullptr, then we look in 'other' for a default
// value. If a valid value of the correct type is present, it is
// used as the default value.
//
// - If no default value can be found in 'other', then a hardwired
// default value is provided.
//
eng::string add_defaults(const AnimState *other);
// Apply a configuration from a lua table.
//
// If a key already exists in the state, then type type from the table
// must match the type from the existing state.
//
eng::string apply_lua(LuaCoreStack &LS0, LuaSlot tab, bool setpersist);
// Convert an animstate to a lua table.
//
// You can either convert the persistent key-value pairs, or the
// nonpersistent. So you'll need two lua tables to store both.
//
void to_lua(LuaCoreStack &LS, LuaSlot tab, bool persistent);
// Parse a string, for unit testing.
//
void parse(std::string_view s);
void clear_and_parse(std::string_view s);
// Constructor from an encoded string.
//
AnimState(std::string_view s) { decode(s); }
};
struct AnimCoreState
{
util::DXYZ xyz;
eng::string plane;
void decode(std::string_view enc);
};
class AnimQueue : public eng::nevernew {
private:
struct Step {
Step() { hash=0; }
Step(const eng::string &e, uint64_t h) : encoding(e), hash(h) {}
eng::string encoding;
uint64_t hash;
};
public:
// Construct an empty animation queue.
// clears the state to a valid state.
//
AnimQueue();
// Size limit.
//
int32_t size_limit() const { return size_limit_; }
int64_t version_number() const { return version_number_; }
bool version_identical(const AnimQueue &aq) const;
// Return true if the size limit and steps are identical.
bool size_and_steps_equal(const AnimQueue &aq) const;
// Clear and set the initial state.
//
void clear();
void clear(const AnimState &initial);
// Mutator to create a new step.
void add(int64_t id, const AnimStep &step);
// Set the size limit. Must be 2-250
//
void set_limit(int n);
// Clear and set the plane.
void clear(const eng::string &plane);
// Add an animation step.
//
// Note: add does not automatically compose the step with the previous
// step, you have to do that yourself.
//
void add(const AnimState &state);
// Serialize or deserialize to a StreamBuffer
//
// Caution: version numbers are not stored. On deserialize,
// version number is set to zero.
//
void serialize(StreamBuffer *sb) const;
void deserialize(StreamBuffer *sb);
// Difference transmission
//
bool diff(const AnimQueue &auth, StreamBuffer *sb) const;
void patch(StreamBuffer *sb, DebugCollector *dbc);
void update_version(const AnimQueue &auth);
// Get the final resting place after all animations are complete.
const AnimStep &back() const;
public:
////////////////////////////////////////////////////////////////////////////
// Check for exactly equal. This does the full check of all
// fields, it is used exclusively for debugging.
//
// TESTING SUPPORT
bool exactly_equal(const AnimQueue &aq) const;
// Check for exactly equal (fast). Compares the size, limit,
// and hash of the last entry. If these are equal, then the whole
// thing should be equal.
//
// The following functions are not designed to be useful for production
// code, they're designed to be helpful for unit testing.
bool exactly_equal_fast(const AnimQueue &aq) const;
// Debug strings.
//
////////////////////////////////////////////////////////////////////////////
// Change the size limit.
void full_clear_and_set_limit(int szl);
void set_limit(int szl);
// Make sure the invariants are preserved.
bool valid() const;
// Convert to a string for debugging purposes.
eng::string steps_debug_string() const;
eng::string full_debug_string() const;
// Convert to a
// Get the final entry, xyz and plane only.
//
AnimCoreState get_final_core_state() const;
// Get the final entry, all persistent variables.
//
AnimState get_final_persistent() const;
// Get the final entry, everything persistent and non-persistent.
//
AnimState get_final_everything() const;
private:
bool version_autoinc_;
int32_t size_limit_;
eng::deque<AnimStep> steps_;
int64_t version_number_;
// Called whenever the animation queue is mutated.
// serialization and deserialization
void mutated();
int size_limit_;
eng::deque<Step> steps_;
};
#endif // ANIMQUEUE_HPP

View File

@@ -2,6 +2,7 @@
#include "wrap-vector.hpp"
#include "util.hpp"
#include "drivenengine.hpp"
#include "world.hpp"
#include <string_view>
#include <utility>
@@ -138,6 +139,11 @@ void DrivenEngine::rescan_lua_source() {
rescan_lua_source_ = true;
}
void DrivenEngine::set_visible_world_and_actor(World *w, int64_t id) {
visible_world_ = w;
visible_actor_id_ = id;
}
void DrivenEngine::stop_driver() {
stop_driver_ = true;
for (int i = 0; i < DRV_MAX_CHAN; i++) {
@@ -190,8 +196,8 @@ inline static const char *action_string(DrvAction act) {
case PLAY_RECV_INCOMING: return "PLAY_RECV_INCOMING";
case PLAY_NOTIFY_CLOSE: return "PLAY_NOTIFY_CLOSE";
case PLAY_NOTIFY_ACCEPT: return "PLAY_NOTIFY_ACCEPT";
case PLAY_SET_LUA_SOURCE: return "PLAY_SET_LUA_SOURCE";
case PLAY_INVOKE_EVENT_UPDATE: return "PLAY_INVOKE_EVENT_UPDATE";
case PLAY_SET_LUA_SOURCE: return "PLAY_SET_LUA_SOURCE";
case PLAY_RELEASE: return "PLAY_RELEASE";
default: return "unknown";
}
@@ -403,6 +409,30 @@ bool DrivenEngine::drv_get_stop_driver() const {
return stop_driver_;
}
uint64_t DrivenEngine::drv_get_actor_id() const {
return visible_actor_id_;
}
void DrivenEngine::drv_get_tangibles_near(uint64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids) {
scan_result_.clear();
if ((visible_world_ != 0) && (tanid != 0)) {
PlaneScan scan;
scan.set_near(tanid, true);
scan.set_omit_nowhere(true);
scan.set_sorted(false);
scan.set_radius(util::XYZ(rx, ry, rz));
scan.set_shape(PlaneScan::CYLINDER);
visible_world_->get_near(scan, &scan_result_);
}
*count = scan_result_.size();
if (*count > 0) {
*ids = &scan_result_[0];
} else {
*ids = nullptr;
}
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
@@ -465,6 +495,8 @@ void DrivenEngine::drv_set_lua_source(uint32_t srcpklen, const char *srcpk) {
rescan_lua_source_ = false;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
@@ -522,6 +554,15 @@ static bool drv_get_stop_driver(EngineWrapper *w) {
return w->engine->drv_get_stop_driver();
}
static uint64_t drv_get_actor_id(EngineWrapper *w) {
return w->engine->drv_get_actor_id();
}
static void drv_get_tangibles_near(EngineWrapper *w, uint64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids) {
return w->engine->drv_get_tangibles_near(tanid, rx, ry, rz, count, ids);
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
@@ -798,6 +839,7 @@ void replay_set_lua_source(EngineWrapper *w) {
w->engine->drv_set_lua_source(srcpack.size(), srcpack.c_str());
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
@@ -864,21 +906,13 @@ static void replaycore_step(EngineWrapper *w) {
case PLAY_RECV_INCOMING: replay_recv_incoming(w); return;
case PLAY_NOTIFY_CLOSE: replay_notify_close(w); return;
case PLAY_NOTIFY_ACCEPT: replay_notify_accept(w); return;
case PLAY_SET_LUA_SOURCE: replay_set_lua_source(w); return;
case PLAY_INVOKE_EVENT_UPDATE: replay_invoke_event_update(w); return;
case PLAY_SET_LUA_SOURCE: replay_set_lua_source(w); return;
case PLAY_RELEASE: release(w); return;
default: return reset_wrapper(w, "Replay log corrupt in command dispatcher");
}
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// General Mutators
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
@@ -923,6 +957,8 @@ static void init_engine_wrapper_helper(EngineWrapper *w) {
w->get_clock = drv_get_clock;
w->get_rescan_lua_source = drv_get_rescan_lua_source;
w->get_stop_driver = drv_get_stop_driver;
w->get_actor_id = drv_get_actor_id;
w->get_tangibles_near = drv_get_tangibles_near;
w->play_initialize = play_initialize;
w->play_clear_new_outgoing = play_clear_new_outgoing;

View File

@@ -52,8 +52,10 @@
#include "util.hpp"
#include "streambuffer.hpp"
#include "enginewrapper.hpp"
#include "planemap.hpp"
class DrivenEngine;
class World;
using UniqueDrivenEngine = std::unique_ptr<DrivenEngine>;
using DrivenEngineMaker = UniqueDrivenEngine (*)();
using DrivenEngineInitializer = void (*)();
@@ -222,6 +224,14 @@ public:
//
void rescan_lua_source();
// Set the world pointer and the actor ID.
//
// This allows the graphics engine to query the DrivenEngine
// about the state of the world and the player. It is legal to set these
// to zero, in which case queries will return null results.
//
void set_visible_world_and_actor(World *w, int64_t actor);
// Stop the driver. The engine should call this when it's done
// and there's nothing left to do.
//
@@ -270,6 +280,8 @@ public:
double drv_get_clock() const;
bool drv_get_rescan_lua_source() const;
bool drv_get_stop_driver() const;
uint64_t drv_get_actor_id() const;
void drv_get_tangibles_near(uint64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids);
void drv_initialize(uint32_t srcpklen, const char *srcpk, int argc, char **argv);
void drv_clear_new_outgoing();
@@ -296,6 +308,9 @@ private:
eng::vector<uint32_t> new_outgoing_;
util::LuaSourcePtr lua_source_;
eng::vector<uint32_t> listen_ports_;
World *visible_world_;
int64_t visible_actor_id_;
util::IdVector scan_result_;
bool rescan_lua_source_;
double clock_;
bool stop_driver_;

View File

@@ -110,6 +110,14 @@ struct EngineWrapper {
//
bool (*get_stop_driver)(EngineWrapper *w);
// Get the actor ID. May return zero if the server is down.
//
uint64_t (*get_actor_id)(EngineWrapper *w);
// Get the results of the last scan radius.
//
void (*get_tangibles_near)(EngineWrapper *w, uint64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids);
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//

View File

@@ -52,6 +52,9 @@ public:
// Set the console prompt.
set_console_prompt(console_.get_prompt());
// Export stuff to the graphics engine.
set_visible_world_and_actor(master_.get(), admin_id_);
}
void do_luainvoke_command(const util::StringVec &words) {

View File

@@ -94,6 +94,10 @@ void LuaConsole::simplify(const StringVec &words) {
if (words.size() != 1) {
synerr("/work takes no arguments");
}
} else if (words[0] == "display") {
if (words.size() != 1) {
synerr("/display takes no arguments");
}
} else if (words[0] == "aborthttp") {
if (words.size() != 1) {
synerr("/aborthttp takes no arguments");

View File

@@ -224,13 +224,12 @@ public:
using NodeID = uint64_t;
using ChildBits = uint64_t;
using IdVector = util::IdVector;
// The PlaneMap that this tree is a part of.
PlaneMap *planemap_;
// The name of this plane.
eng::string plane_;
std::string plane_;
// Internal nodes in the tree just have bits indicating
// which children exist.
@@ -440,7 +439,7 @@ public:
(*os) << "| ";
print_node_id(node, os);
(*os) << " ";
util::IdVector ids;
IdVector ids;
collect_planeitem_ids(first, &ids);
std::sort(ids.begin(), ids.end());
util::print_id_vector(ids, os);
@@ -667,7 +666,7 @@ public:
}
// Construct a PlaneTree.
PlaneTree(PlaneMap *pmap, const eng::string &plane) {
PlaneTree(PlaneMap *pmap, std::string_view plane) {
planemap_ = pmap;
plane_ = plane;
total_count_ = 0;
@@ -779,30 +778,29 @@ PlaneMap::PlaneMap() : default_radius_(32768.0) {}
PlaneMap::~PlaneMap() {}
IdVector PlaneMap::scan(const PlaneScan &sc) const {
IdVector result;
void PlaneMap::scan(const PlaneScan &sc, util::IdVector *into) const {
into->clear();
int startpos = 0;
if (sc.near_ != 0) {
if (sc.include_near_) {
result.push_back(sc.near_);
into->push_back(sc.near_);
startpos = 1;
}
}
if (sc.omit_nowhere_ && (sc.plane_ == "nowhere")) {
return result;
return;
}
auto piter = planes_.find(sc.plane_);
auto piter = planes_.find(std::string_view(sc.plane_));
if (piter != planes_.end()) {
const std::unique_ptr<PlaneTree> &tree = piter->second;
tree->scan(sc, &result, nullptr);
tree->scan(sc, into, nullptr);
}
if (sc.sorted_) {
std::sort(result.begin() + startpos, result.end());
std::sort(into->begin() + startpos, into->end());
}
return result;
}
eng::string PlaneMap::tree_debug_string(const eng::string &plane) {
@@ -814,11 +812,11 @@ eng::string PlaneMap::outliers_debug_string(const eng::string &plane) {
}
eng::string PlaneMap::search_bboxes_debug_string(const PlaneScan &scan) {
return PlaneTree::get(this, scan.plane_)->search_bboxes_debug_string(scan);
return PlaneTree::get(this, eng::string(scan.plane_))->search_bboxes_debug_string(scan);
}
eng::string PlaneMap::scan_steps_debug_string(const PlaneScan &scan) {
return PlaneTree::get(this, scan.plane_)->scan_steps_debug_string(scan);
return PlaneTree::get(this, eng::string(scan.plane_))->scan_steps_debug_string(scan);
}
void PlaneMap::untrack_all() {
@@ -1161,7 +1159,7 @@ LuaDefine(unittests_planemap, "", "some unit tests") {
// The two corners are deliberately not in low-high order.
scan.clear();
scan.set_plane("p");
scan.set_bbox_given_center_radius(util::XYZ(0x23, 0x97, 0x103), 2.0f);
scan.set_center_and_radius(util::XYZ(0x23, 0x97, 0x103), 2.0f);
LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan),
"|Level 8 0,0,0 - 0,0,0"
"|Level 6 8,8,8 - 8,8,8"
@@ -1181,7 +1179,7 @@ LuaDefine(unittests_planemap, "", "some unit tests") {
// sure they only include the one cell.
scan.clear();
scan.set_plane("p");
scan.set_bbox_given_center_radius(util::XYZ(0x12, 0x34, 0x45), 0.0);
scan.set_center_and_radius(util::XYZ(0x12, 0x34, 0x45), 0.0);
LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan),
"|Level 8 0,0,0 - 0,0,0"
"|Level 6 8,8,8 - 8,8,8"
@@ -1207,7 +1205,7 @@ LuaDefine(unittests_planemap, "", "some unit tests") {
// from one cell to the next.
scan.clear();
scan.set_plane("p");
scan.set_bbox_given_center_radius(util::XYZ(0x12 + 0.5, 0x34, 0x45), 0.0);
scan.set_center_and_radius(util::XYZ(0x12 + 0.5, 0x34, 0x45), 0.0);
LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan),
"|Level 8 0,0,0 - 0,0,0"
"|Level 6 8,8,8 - 8,8,8"
@@ -1235,7 +1233,7 @@ LuaDefine(unittests_planemap, "", "some unit tests") {
// to make sure they cover the entire PlaneTree.
scan.clear();
scan.set_plane("p");
scan.set_bbox_given_center_radius(util::XYZ(0x12, 0x34, 0x45), 100000.0);
scan.set_center_and_radius(util::XYZ(0x12, 0x34, 0x45), 100000.0);
LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan),
"|Level 8 0,0,0 - 0,0,0"
"|Level 6 0,0,0 - f,f,f"
@@ -1292,7 +1290,7 @@ LuaDefine(unittests_planemap, "", "some unit tests") {
scan.clear();
scan.set_plane("p");
scan.set_shape(PlaneScan::SPHERE);
scan.set_bbox_given_center_radius(util::XYZ(0x12 + 0.1, 0x34, 0x45), 0.0);
scan.set_center_and_radius(util::XYZ(0x12 + 0.1, 0x34, 0x45), 0.0);
LuaAssertStrEq(L, pm.scan_steps_debug_string(scan),
"|L8:root"
"| L7:2,2,2"
@@ -1350,7 +1348,7 @@ LuaDefine(unittests_planemap, "", "some unit tests") {
// because the radius is nonzero.
scan.clear();
scan.set_plane("p");
scan.set_bbox_given_center_radius(util::XYZ(0x100000, 0x16, 0x23), 0.2);
scan.set_center_and_radius(util::XYZ(0x100000, 0x16, 0x23), 0.2);
LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan),
"|Level 8 0,0,0 - 0,0,0"
"|Level 6 f,8,8 - f,8,8"

View File

@@ -87,6 +87,7 @@
class PlaneMap;
class PlaneTree;
class PlaneScan : public eng::nevernew {
public:
friend class PlaneMap;
@@ -94,7 +95,11 @@ public:
enum Shape { BOX, CYLINDER, SPHERE };
private:
// The plane to scan.
eng::string plane_;
//
// Note: the reason this uses std::string instead of eng::string
// is that we want plane scans to not touch the engine heap.
//
std::string plane_;
// The bounding box of the scan.
util::XYZ center_, radius_;
@@ -119,13 +124,14 @@ private:
public:
void clear() {
plane_ = "";
plane_.clear();
shape_ = BOX;
sorted_ = true;
near_ = 0;
include_near_ = false;
omit_nowhere_ = false;
radius_ = center_ = util::XYZ();
radius_ = util::XYZ();
center_ = util::XYZ();
}
PlaneScan() { clear(); }
@@ -143,7 +149,7 @@ public:
//
void configure(LuaKeywordParser &kw);
void set_bbox_given_center_radius(const util::XYZ &center, float r) {
void set_center_and_radius(const util::XYZ &center, float r) {
set_center(center);
set_radius(r);
}
@@ -204,12 +210,12 @@ public:
class PlaneMap : public eng::nevernew {
friend class PlaneItem;
friend class PlaneTree;
public:
using IdVector = util::IdVector;
private:
float default_radius_;
eng::map<eng::string, std::unique_ptr<PlaneTree>> planes_;
// Plane Trees table.
//
eng::map<eng::string, std::unique_ptr<PlaneTree>, std::less<>> planes_;
public:
// No special code is needed for construction or destruction.
@@ -220,7 +226,11 @@ public:
// PlaneItem. See PlaneItem::track and PlaneItem::untrack.
// Scan the PlaneMap for items, return their IDs.
IdVector scan(const PlaneScan &s) const;
//
// Note: the reason this uses IdVector instead of IdVector
// is that we want scans to not touch the engine heap.
//
void scan(const PlaneScan &sc, util::IdVector *into) const;
// Set the default radius for all planes.
// Maybe we'll make it adaptive some day.

View File

@@ -281,6 +281,26 @@ void StreamBuffer::write_double(double d) {
write_cursor_ += 8;
}
void StreamBuffer::write_xyz(const util::XYZ &xyz) {
make_space(12);
memcpy(write_cursor_, &xyz.x, 4);
write_cursor_ += 4;
memcpy(write_cursor_, &xyz.y, 4);
write_cursor_ += 4;
memcpy(write_cursor_, &xyz.z, 4);
write_cursor_ += 4;
}
void StreamBuffer::write_dxyz(const util::DXYZ &xyz) {
make_space(24);
memcpy(write_cursor_, &xyz.x, 8);
write_cursor_ += 8;
memcpy(write_cursor_, &xyz.y, 8);
write_cursor_ += 8;
memcpy(write_cursor_, &xyz.z, 8);
write_cursor_ += 8;
}
int8_t StreamBuffer::read_int8() {
check_available(1);
int8_t v;
@@ -336,6 +356,32 @@ double StreamBuffer::read_double() {
return d;
}
util::XYZ StreamBuffer::read_xyz() {
check_available(12);
util::XYZ result;
memcpy(&result.x, read_cursor_, 4);
read_cursor_ += 4;
memcpy(&result.y, read_cursor_, 4);
read_cursor_ += 4;
memcpy(&result.z, read_cursor_, 4);
read_cursor_ += 4;
return result;
}
util::DXYZ StreamBuffer::read_dxyz() {
check_available(24);
util::DXYZ result;
memcpy(&result.x, read_cursor_, 8);
read_cursor_ += 8;
memcpy(&result.y, read_cursor_, 8);
read_cursor_ += 8;
memcpy(&result.z, read_cursor_, 8);
read_cursor_ += 8;
return result;
}
void StreamBuffer::write_hashvalue(const util::HashValue &hv) {
write_uint64(hv.first);
write_uint64(hv.second);

View File

@@ -313,6 +313,8 @@ public:
void write_char(char c);
void write_float(float f);
void write_double(double d);
void write_xyz(const util::XYZ &xyz);
void write_dxyz(const util::DXYZ &xyz);
// Read fixed-size integers from the buffer.
//
@@ -329,6 +331,8 @@ public:
char read_char();
float read_float();
double read_double();
util::XYZ read_xyz();
util::DXYZ read_dxyz();
// Write other types into the buffer.
//

View File

@@ -494,6 +494,15 @@ void print_id_vector(const IdVector &idv, std::ostream *os) {
}
}
void print_id_vector(const std::vector<uint64_t> &idv, std::ostream *os) {
bool first = true;
for (int64_t id : idv) {
if (!first) (*os) << ",";
(*os) << id;
first = false;
}
}
eng::string id_vector_debug_string(const IdVector &idv) {
eng::ostringstream oss;
print_id_vector(idv, &oss);
@@ -518,10 +527,17 @@ IdVector sort_union_id_vectors(const IdVector &v1, const IdVector &v2) {
return result;
}
HashValue hash_string(const eng::string &s) {
HashValue hash_string(std::string_view s) {
uint64_t hash1 = 0;
uint64_t hash2 = 0;
SpookyHash::ChainHash128(s.c_str(), s.size(), &hash1, &hash2);
SpookyHash::ChainHash128(s.data(), s.size(), &hash1, &hash2);
return util::HashValue(hash1, hash2);
}
HashValue hash_string(HashValue prev, std::string_view s) {
uint64_t hash1 = prev.first;
uint64_t hash2 = prev.second;
SpookyHash::ChainHash128(s.data(), s.size(), &hash1, &hash2);
return util::HashValue(hash1, hash2);
}
@@ -718,11 +734,6 @@ LuaSourcePtr make_lua_source(const eng::string &code) {
return result;
}
eng::string XYZ::debug_string() const {
eng::ostringstream oss;
oss << "(" << x << "," << y << "," << z << ")";
return oss.str();
}
void (*dprint_hook)(const char *oneline, size_t size);

View File

@@ -211,13 +211,18 @@ enum MessageType {
MSG_INVOKE,
};
// Note: IdVector is weird in that it deliberately uses std::vector
// instead of eng::vector. This is because we want plane scans
// to not touch the engine heap.
//
using IdVector = std::vector<int64_t>;
using StringVec = eng::vector<eng::string>;
using StringPair = std::pair<eng::string, eng::string>;
using StringSet = eng::set<eng::string>;
using LuaSourceVec = eng::vector<StringPair>;
using LuaSourcePtr = std::unique_ptr<LuaSourceVec>;
using HashValue = std::pair<uint64_t, uint64_t>;
using IdVector = eng::vector<int64_t>;
// Ascii uppercase and lowercase.
eng::string ascii_tolower(std::string_view c);
@@ -241,6 +246,7 @@ IdVector id_vector_create(int64_t id1=-1, int64_t id2=-1, int64_t id3=-1, int64_
// Print an ID vector to a stream.
void print_id_vector(const IdVector &idv, std::ostream *os);
void print_id_vector(const std::vector<uint64_t> &idv, std::ostream *os);
// ID vector debug string.
eng::string id_vector_debug_string(const IdVector &idv);
@@ -249,7 +255,10 @@ eng::string id_vector_debug_string(const IdVector &idv);
IdVector sort_union_id_vectors(const IdVector &v1, const IdVector &v2);
// Get a 128-bit hashvalue for a string.
HashValue hash_string(const eng::string &str);
HashValue hash_string(std::string_view str);
// Get a 128-bit hashvalue for a string, with a previous value.
HashValue hash_string(HashValue prev, std::string_view str);
// Get a 128-bit hashvalue for an ID vector.
HashValue hash_id_vector(const IdVector &idv);
@@ -322,18 +331,32 @@ void remove_marked_items(T &vec) {
}
// 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) const { return x==o.x && y == o.y && z==o.z; }
bool operator !=(const XYZ &o) const { return x!=o.x || y != o.y || z!=o.z; }
XYZ operator -(const XYZ &o) const { return XYZ(x-o.x, y-o.y, z-o.z); }
XYZ operator +(const XYZ &o) const { return XYZ(x+o.x, y+o.y, z+o.z); }
XYZ operator *(float scale) const { return XYZ(x*scale, y*scale, z*scale); }
eng::string debug_string() const;
template <typename NUMBER>
struct NumXYZ {
using Number = NUMBER;
Number x, y, z;
NumXYZ() { x=0; y=0; z=0; }
NumXYZ(Number ix, Number iy, Number iz) { x=ix; y=iy; z=iz; }
void operator =(const NumXYZ &other) { x = other.x; y = other.y; z = other.z; }
void operator =(Number n) { x = n; y = n; z = n; }
bool operator ==(const NumXYZ &o) const { return x==o.x && y == o.y && z==o.z; }
bool operator !=(const NumXYZ &o) const { return x!=o.x || y != o.y || z!=o.z; }
NumXYZ operator -(const NumXYZ &o) const { return NumXYZ(x-o.x, y-o.y, z-o.z); }
NumXYZ operator +(const NumXYZ &o) const { return NumXYZ(x+o.x, y+o.y, z+o.z); }
NumXYZ operator *(float scale) const { return NumXYZ(x*scale, y*scale, z*scale); }
template<typename ONUMBER>
const NumXYZ<ONUMBER> convert() const { NumXYZ<ONUMBER> r; r.x=ONUMBER(x); r.y=ONUMBER(y); r.z=ONUMBER(z); return r; }
eng::string debug_string() const {
eng::ostringstream oss;
oss << "(" << x << "," << y << "," << z << ")";
return oss.str();
}
};
using XYZ=NumXYZ<float>;
using DXYZ=NumXYZ<double>;
// util::ostringstream
//
// This is a variant of ostringstream in which it is possible
@@ -455,4 +478,9 @@ inline std::ostream &operator<<(std::ostream &oss, const util::XYZ &xyz) {
return oss;
}
inline std::ostream &operator<<(std::ostream &oss, const util::DXYZ &xyz) {
oss << xyz.x << "," << xyz.y << "," << xyz.z;
return oss;
}
#endif // UTIL_HPP

View File

@@ -17,56 +17,169 @@ static void tangible_getall(LuaCoreStack &LS0, LuaSlot list, const util::IdVecto
}
}
LuaDefine(tangible_animstate, "tan",
"|Get the entire animation state of the tangible."
"|Returns six values: graphic,plane,x,y,z,facing.") {
LuaArg tanobj;
LuaRet graphic, plane, x, y, z, facing;
LuaDefStack LS(L, tanobj, graphic, plane, x, y, z, facing);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
const AnimStep &aqback = tan->anim_queue_.back();
LS.set(graphic, aqback.graphic());
LS.set(plane, aqback.plane());
LS.set(x, aqback.xyz().x);
LS.set(y, aqback.xyz().y);
LS.set(z, aqback.xyz().z);
LS.set(facing, aqback.facing());
return LS.result();
}
LuaDefine(tangible_xyz, "tan",
"|Get the current coordinates of the tangible."
"|Returns three values: x, y, z") {
"|Get the current coordinates of the tangible and the plane."
"|Returns four values: x, y, z, plane") {
LuaArg tanobj;
LuaRet x, y, z;
LuaRet x, y, z, plane;
LuaDefStack LS(L, tanobj, x, y, z);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
const AnimStep &aqback = tan->anim_queue_.back();
LS.set(x, aqback.xyz().x);
LS.set(y, aqback.xyz().y);
LS.set(z, aqback.xyz().z);
AnimCoreState pos = tan->anim_queue_.get_final_core_state();
LS.set(x, pos.xyz.x);
LS.set(y, pos.xyz.y);
LS.set(z, pos.xyz.z);
LS.set(plane, pos.plane);
return LS.result();
}
LuaDefine(tangible_animate, "tan,configtable",
"|Add an animation step to the tangible."
"|The configtable is a table containing any of the following:"
"|action,graphic,plane,x,y,z,facing") {
LuaArg tanobj, config;
LuaDefStack LS(L, tanobj, config);
LuaKeywordParser kp(LS, config);
LuaDefine(tangible_animdebug, "tan",
"|Return a debug string showing the entire animation queue"
"|") {
LuaArg tanobj;
LuaRet result;
LuaDefStack LS(L, tanobj, result);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
int64_t id = w->alloc_id_predictable();
AnimStep step;
step.configure(kp, tan->anim_queue_.back());
kp.final_check_throw();
if (step.action() == "") {
luaL_error(L, "animation action must be specified");
LS.set(result, tan->anim_queue_.steps_debug_string());
return LS.result();
}
LuaDefine(tangible_animstate, "tan",
"|Return the animation state variables of the tangible as a table."
"|"
"|The animation system stores 'animation state variables. "
"|There are several builtin animation state variables. The"
"|following is a list, along with some example values that"
"|might be used if the object were a pirate treasure chest."
"|"
"| xyz={1,2,3} # xyz coordinate"
"| plane='earth' # plane where the chest is located"
"| facing=0 # rotation of the chest"
"| bp='BP_piratechest' # name of an unreal blueprint"
"| model='SM_piratechest' # name of an unreal mesh"
"|"
"|There can also be user-defined animation state variables."
"|For example, for a pirate chest, you might want to add two"
"|more state variables:"
"|"
"| open=true # chest can be open or closed"
"| fullness=0.8 # how big the heap of coins is"
"|"
"|All state variables must be one of four types: number, string,"
"|boolean, or coordinate. All state variables must have simple"
"|identifiers for names."
"|"
"|Animation state variables are updated when you use"
"|tangible.animate to create an animation record. For example,"
"|suppose your character is initialized at xyz={1,1,1}. Then"
"|he walks to xyz={2,2,2}, then he walks to xyz={3,3,3}."
"|The animation queue now contains three animation steps:"
"|"
"|Animation Queue:"
"| Step 1: action:initialize xyz={1,1,1} ..."
"| Step 2: action:walkto xyz={2,2,2} ..."
"| Step 3: action:walkto xyz={3,3,3} ..."
"|"
"|Notice that the state variable xyz is stored three times, once"
"|in each animation step. All animation state variables are stored"
"|in every animation step. When you use tangible.animstate to fetch"
"|the current value of the animation state variables, you're"
"|actually fetching the state variables from the last step in"
"|the animation queue."
"|"
"|You can use this function, tangible.animstate, to view the"
"|current set of state variables. You can use tangible.animinit"
"|to reconfigure the set of user-defined state variables. You"
"|can use tangible.animate to create animation steps, which can"
"|alter any of the state variables."
"|") {
LuaArg tanobj;
LuaRet result;
LuaDefStack LS(L, tanobj, result);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
AnimState state = tan->anim_queue_.get_final_persistent();
state.to_lua(LS, result, true);
return LS.result();
}
LuaDefine(tangible_animinit, "tan,config",
"|Reinitialize the animation queue."
"|"
"|The animation queue stores certain animation state variables."
"|See doc(tangible.animstate) for more information about animation"
"|state variables."
"|"
"|This function, tangible.animinit, is used to reconfigure the set of"
"|user-defined state variables. The config table that you pass in must"
"|be key-value pairs. Keys must be simple identifiers. Values can be"
"|numbers, strings, booleans, or coordinates."
"|"
"|After tangible.animinit, the tangible's animation state variables will"
"|consist of the user-defined variables listed in the config table,"
"|plus all the builtin animation state variables."
"|"
"|Optionally, the config table can also supply values for some or all"
"|of the builtin state variables. For example, you could supply xyz"
"|in the config table. If you do, the tangible will move to a new xyz"
"|coordinate. If not, the tangible will retain its old xyz coordinate."
"|") {
LuaArg tanobj, config;
LuaDefStack LS(L, tanobj, config);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
AnimState state;
eng::string error = state.apply_lua(LS, config, true);
if (!error.empty()) {
luaL_error(L, "%s", error.c_str());
}
tan->anim_queue_.add(id, step);
AnimState defsource = tan->anim_queue_.get_final_persistent();
error = state.add_defaults(&defsource);
if (!error.empty()) {
luaL_error(L, "%s", error.c_str());
}
tan->anim_queue_.clear(state);
tan->update_plane_item();
return LS.result();
}
LuaDefine(tangible_animate, "tan,config",
"|Add an animation step to the tangible."
"|"
"|The animation queue stores animation steps. This function, "
"|tangible.animate, adds one new animation step to the queue."
"|"
"|It might be useful to read about 'animation state variables' before"
"|reading this explanation. See doc(tangible.animstate)."
"|"
"|An animation step is just a list of key-value pairs. Therefore,"
"|the config table must be key-value pairs. Keys must be simple"
"|identifiers. Values can be numbers, strings, booleans, or"
"|coordinates."
"|"
"|Some of the key-value pairs may match the name of an animation state"
"|variable. If so, that key-value pair permanently changes the"
"|value of that animation state variable. The new value will"
"|be retained for all future animation steps."
"|"
"|Some of the key-value pairs may NOT match the name of any animation"
"|state variable. If so, that key-value pair is part of the"
"|animation step, but nothing is propagated forward to future animation"
"|steps."
"|"
"|") {
LuaArg tanobj, config;
LuaDefStack LS(L, tanobj, config);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
AnimState state = tan->anim_queue_.get_final_persistent();
eng::string error = state.apply_lua(LS, config, false);
if (!error.empty()) {
luaL_error(L, "%s", error.c_str());
}
tan->anim_queue_.add(state);
tan->update_plane_item();
return LS.result();
}
@@ -132,51 +245,53 @@ LuaDefine(tangible_delete, "tan",
LuaDefine(tangible_build, "config",
"|Build a new tangible object."
"|The config table must contain: class,x,y,z,plane,graphic."
"|The config table can optionally contain: facing.") {
"|"
"|The config table must contain: class,animstate."
"|" ){
LuaArg config;
LuaVar classname, classtab, mt;
LuaVar classname, classtab, mt, animstate;
LuaRet database;
LuaDefStack LS(L, config, classname, classtab, database, mt);
LuaDefStack LS(L, config, classname, classtab, database, mt, animstate);
LuaKeywordParser kp(LS, config);
// Get the class of the new tangible.
// Get the keyword arguments.
if (!kp.parse(classname, "class")) {
luaL_error(L, "You must specify a class for the tangible");
}
if (!kp.parse(animstate, "animstate")) {
luaL_error(L, "You must specify an animstate table");
}
kp.final_check_throw();
// Find the class.
eng::string err = LS.getclass(classtab, classname);
if (err != "") {
luaL_error(L, "%s", err.c_str());
}
// Parse the initial animation step.
AnimStep initstep, blank;
initstep.configure(kp, blank);
kp.final_check_throw();
if (!initstep.has_xyz()) {
luaL_error(L, "You must specify (X,Y,Z) for new tangible");
// Calculate the initial animation state.
AnimState state;
err = state.apply_lua(LS, animstate, true);
if (err != "") {
luaL_error(L, "%s", err.c_str());
}
if (!initstep.has_plane()) {
luaL_error(L, "You must specify plane for new tangible");
if (!state.contains("xyz") || !state.contains("plane")) {
luaL_error(L, "You must specify both xyz and plane in animstate");
}
if (!initstep.has_graphic()) {
luaL_error(L, "You must specify graphic for new tangible");
err = state.add_defaults(nullptr);
if (err != "") {
luaL_error(L, "%s", err.c_str());
}
// TODO: generate error if there's extra crap in the config table.
World *w = World::fetch_global_pointer(L);
int64_t new_id = w->alloc_id_predictable();
Tangible *tan = w->tangible_make(LS, database, new_id, "nowhere");
Tangible *tan = w->tangible_make(LS, database, new_id);
// Update the class of the new tangible.
LS.getmetatable(mt, database);
LS.rawset(mt, "__index", classtab);
// Update the animation queue and planemap of the new tangible.
int64_t stepid = w->alloc_id_predictable();
tan->anim_queue_.add(stepid, initstep);
tan->update_plane_item();
tan->anim_queue_.clear(state);
return LS.result();
}
@@ -259,34 +374,27 @@ LuaDefine(tangible_place, "",
}
LuaDefine(tangible_near, "tan,radius,omit_nowhere,omit_self",
"|Scan near the specified tangible."
"|If omit_nowhere is true, and the tangible is on the nowhere plane,"
"|then the scan returns empty. If omit_self is true, then the "
"|tangible passed in is omitted from the results.") {
"|Deprecated. Use tangible.find instead.") {
LuaArg ltan, lradius, lomit_nowhere, lomit_self;
LuaRet list;
LuaDefStack LS(L, ltan, lradius, lomit_nowhere, lomit_self, list);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, ltan, false);
const AnimStep &aqback = tan->anim_queue_.back();
PlaneScan scan;
scan.set_plane(aqback.plane());
scan.set_bbox_given_center_radius(aqback.xyz(), LS.cknumber(lradius));
scan.set_radius(LS.cknumber(lradius));
scan.set_shape(PlaneScan::SPHERE);
scan.set_sorted(true);
scan.set_near(tan->id(), !LS.ckboolean(lomit_self));
scan.set_omit_nowhere(LS.ckboolean(lomit_nowhere));
util::IdVector idv = w->plane_map_.scan(scan);
util::IdVector idv;
w->get_near(scan, &idv);
tangible_getall(LS, list, idv);
return LS.result();
}
LuaDefine(tangible_scan, "plane,x,y,radius,omit_nowhere",
"|Scan the specified plane."
"|If omit_nowhere is true, and the plane is 'nowhere', then"
"|the scan returns empty.") {
"|Deprecated. Use tangible.find instead.") {
LuaArg lplane, lx, ly, lradius, lomit_nowhere;
LuaRet list;
LuaDefStack LS(L, lplane, lx, ly, lradius, lomit_nowhere, list);
@@ -294,12 +402,13 @@ LuaDefine(tangible_scan, "plane,x,y,radius,omit_nowhere",
PlaneScan scan;
scan.set_plane(LS.ckstring(lplane));
scan.set_bbox_given_center_radius(util::XYZ(LS.cknumber(lx), LS.cknumber(ly), 0), LS.cknumber(lradius));
scan.set_center_and_radius(util::XYZ(LS.cknumber(lx), LS.cknumber(ly), 0), LS.cknumber(lradius));
scan.set_shape(PlaneScan::SPHERE);
scan.set_sorted(true);
scan.set_omit_nowhere(LS.ckboolean(lomit_nowhere));
util::IdVector idv = w->plane_map_.scan(scan);
util::IdVector idv;
w->get_near(scan, &idv);
tangible_getall(LS, list, idv);
return LS.result();
}
@@ -369,21 +478,9 @@ LuaDefine(tangible_find, "config",
scan.configure(kw);
kw.final_check_throw();
// When the configure routine sees the 'near' flag, it stores the tangible
// ID, but not the center and plane, because doing so would require it to
// know about world models. We have to handle center and plane for 'near'
// separately.
World *w = World::fetch_global_pointer(L);
int64_t near = scan.near();
if (near != 0) {
Tangible *t = w->tangible_get(near);
assert(t != nullptr); // Should never happen.
const AnimStep &aqback = t->anim_queue_.back();
scan.set_plane(aqback.plane());
scan.set_center(aqback.xyz());
}
// Do the scan.
util::IdVector idv = w->plane_map_.scan(scan);
util::IdVector idv;
w->get_near(scan, &idv);
tangible_getall(LS, result, idv);
return LS.result();
}

View File

@@ -77,14 +77,14 @@ World::World(WorldType wt) {
assign_seqno_ = 1;
}
Tangible::Tangible(World *w, int64_t id) : world_(w), anim_queue_(w->world_type_), id_player_pool_(&w->id_global_pool_) {
Tangible::Tangible(World *w, int64_t id) : world_(w), anim_queue_(), id_player_pool_(&w->id_global_pool_) {
plane_item_.set_id(id);
plane_item_.track(&w->plane_map_);
}
void Tangible::update_plane_item() {
const AnimStep &aqback = anim_queue_.back();
plane_item_.set_pos(aqback.plane(), aqback.xyz().x, aqback.xyz().y, aqback.xyz().z);
AnimCoreState pos = anim_queue_.get_final_core_state();
plane_item_.set_pos(pos.plane, pos.xyz.x, pos.xyz.y, pos.xyz.z);
}
void Tangible::serialize(StreamBuffer *sb) {
@@ -140,7 +140,7 @@ Tangible *World::tangible_get(const LuaCoreStack &LS, LuaSlot tab, bool allowdel
return result;
}
Tangible *World::tangible_make(const LuaCoreStack &LS0, LuaSlot database, int64_t id, const eng::string &plane) {
Tangible *World::tangible_make(const LuaCoreStack &LS0, LuaSlot database, int64_t id) {
assert(id != 0);
LuaVar metatab;
LuaExtStack LS(LS0.state(), metatab);
@@ -150,8 +150,10 @@ Tangible *World::tangible_make(const LuaCoreStack &LS0, LuaSlot database, int64_
assert (t == nullptr);
t.reset(new Tangible(this, id));
// Set up initial animation state.
t->anim_queue_.clear(plane);
// AnimQueue initializes itself to a valid default state.
AnimState state;
state.add_defaults(nullptr);
t->anim_queue_.clear(state);
t->update_plane_item();
// Fetch the tangible's Lua database and metatable.
@@ -165,10 +167,10 @@ Tangible *World::tangible_make(const LuaCoreStack &LS0, LuaSlot database, int64_
return t.get();
}
Tangible *World::tangible_make(int64_t id, const eng::string &plane) {
Tangible *World::tangible_make(int64_t id) {
LuaVar database;
LuaExtStack LS(state(), database);
return tangible_make(LS, database, id, plane);
return tangible_make(LS, database, id);
}
void World::tangible_delete(int64_t id) {
@@ -199,23 +201,33 @@ void World::tangible_delete(int64_t id) {
tangibles_.erase(iter);
}
util::IdVector World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted) const {
const Tangible *player = tangible_get(player_id);
void World::get_near(PlaneScan &scan, util::IdVector *into) const {
uint32_t hash1 = eng::memhash();
into->clear();
// If 'near' is set, update the plane and center.
int64_t actor_id = scan.near();
if (actor_id != 0) {
const Tangible *player = tangible_get(actor_id);
if (player == nullptr) {
return IdVector();
return;
}
const PlaneItem &pi = player->plane_item_;
scan.set_plane(pi.plane());
scan.set_center(util::XYZ(pi.x(), pi.y(), pi.z()));
}
plane_map_.scan(scan, into);
uint32_t hash2 = eng::memhash();
assert(hash1 == hash2);
}
// Find out where the player is.
const AnimStep &aqback = player->anim_queue_.back();
void World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted, util::IdVector *into) const {
PlaneScan scan;
scan.set_plane(aqback.plane());
scan.set_bbox_given_center_radius(aqback.xyz(), radius);
scan.set_radius(radius);
scan.set_shape(PlaneScan::SPHERE);
scan.set_sorted(sorted);
scan.set_omit_nowhere(exclude_nowhere);
scan.set_near(player_id, !omit_player);
return plane_map_.scan(scan);
get_near(scan, into);
}
World::Redirects World::fetch_redirects() {
@@ -229,7 +241,7 @@ int64_t World::create_login_actor() {
int64_t id = id_global_pool_.get_one();
LuaVar database, classtab, mt;
LuaExtStack LS(state(), database, classtab, mt);
Tangible *tan = tangible_make(LS, database, id, "nowhere");
Tangible *tan = tangible_make(LS, database, id);
LS.makeclass(classtab, "login");
LS.getmetatable(mt, database);
LS.rawset(mt, "__index", classtab);

View File

@@ -3,9 +3,10 @@
#include "serializelua.hpp"
util::IdVector World::get_visible_union(int64_t actor_id, World *master) {
return util::sort_union_id_vectors(
master->get_near(actor_id, RadiusVisibility, true, false, false),
get_near(actor_id, RadiusVisibility, true, false, false));
util::IdVector v1, v2;
master->get_near(actor_id, RadiusVisibility, true, false, false, &v1);
get_near(actor_id, RadiusVisibility, true, false, false, &v2);
return util::sort_union_id_vectors(v1, v2);
}
int64_t World::patch_actor(StreamBuffer *sb, DebugCollector *dbc) {
@@ -14,7 +15,7 @@ int64_t World::patch_actor(StreamBuffer *sb, DebugCollector *dbc) {
Tangible *s_actor = tangible_get(actor_id);
if (s_actor == nullptr) {
DebugLine(dbc) << "create new actor " << actor_id;
s_actor = tangible_make(actor_id, "");
s_actor = tangible_make(actor_id);
s_actor->id_player_pool_.deserialize(sb);
s_actor->anim_queue_.deserialize(sb);
s_actor->print_buffer_.deserialize(sb);
@@ -61,7 +62,7 @@ void World::patch_visible(StreamBuffer *sb, DebugCollector *dbc) {
for (int i = 0; i < count; i++) {
int64_t id = sb->read_int64();
DebugLine(dbc) << "patch_visible create tan " << id;
Tangible *t = tangible_make(id, "");
Tangible *t = tangible_make(id);
t->anim_queue_.deserialize(sb);
t->update_plane_item();
}
@@ -162,7 +163,6 @@ void World::diff_visible(const util::IdVector &visible, World *master,
s_tan = tangible_get(m_tan->id());
assert(s_tan != nullptr);
}
s_tan->anim_queue_.update_version(m_tan->anim_queue_);
}
}
}
@@ -172,8 +172,8 @@ void World::patch_luatabs(StreamBuffer *sb, DebugCollector *dbc) {
int64_t actor_id = sb->read_int64();
util::HashValue closehash = sb->read_hashvalue();
int ncreate = sb->read_int32();
util::IdVector closetans =
get_near(actor_id, RadiusClose, true, false, true);
util::IdVector closetans;
get_near(actor_id, RadiusClose, true, false, true, &closetans);
assert(closehash == util::hash_id_vector(closetans));
number_lua_tables(closetans);
create_new_tables(ncreate);
@@ -188,22 +188,23 @@ void World::diff_luatabs(int64_t actor_id, World *master, StreamBuffer *xsb) {
StreamBuffer tsb;
// Calculate the set of close tangibles.
util::IdVector closetans =
master->get_near(actor_id, RadiusClose, true, false, true);
assert(get_near(actor_id, RadiusClose, true, false, true) == closetans);
util::HashValue closehash = util::hash_id_vector(closetans);
util::IdVector mclosetans, sclosetans;
master->get_near(actor_id, RadiusClose, true, false, true, &mclosetans);
get_near(actor_id, RadiusClose, true, false, true, &sclosetans);
assert(mclosetans == sclosetans);
util::HashValue closehash = util::hash_id_vector(mclosetans);
// Number and pair tables in the synchronous and master model.
number_lua_tables(closetans);
pair_lua_tables(closetans, master->state());
int ncreate = number_remaining_tables(closetans, master->state());
number_lua_tables(mclosetans);
pair_lua_tables(mclosetans, master->state());
int ncreate = number_remaining_tables(mclosetans, master->state());
create_new_tables(ncreate);
// Difference transmit.
tsb.write_int64(actor_id);
tsb.write_hashvalue(closehash);
tsb.write_int32(ncreate);
diff_tangible_databases(closetans, master->state(), &tsb);
diff_tangible_databases(mclosetans, master->state(), &tsb);
diff_numbered_tables(master->state(), &tsb);
// Forward to client, and apply to server-synchronous.
@@ -257,8 +258,8 @@ void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) {
// Calculate the set of close tangibles.
// TODO: we've already calculated this in an earlier function. This is
// wasteful.
util::IdVector closetans =
master->get_near(actor_id, RadiusClose, true, false, true);
util::IdVector closetans;
master->get_near(actor_id, RadiusClose, true, false, true, &closetans);
tsb.write_int32(0);
int write_count_after = tsb.total_writes();

View File

@@ -3,18 +3,37 @@
#include "json.hpp"
#include <cassert>
void World::tangible_walkto(int64_t id, int64_t animid, float x, float y) {
void World::tangible_clear_anim_queue_to_empty(int64_t id) {
Tangible *t = tangible_get(id);
assert(animid != 0);
assert(t != nullptr);
AnimStep step;
step.set_action("walkto");
step.set_x(x);
step.set_y(y);
t->anim_queue_.add(animid, step);
AnimState state;
t->anim_queue_.clear(state);
t->update_plane_item();
}
void World::tangible_clear_plane_and_xyz(int64_t id, const eng::string &plane, const util::DXYZ &xyz) {
Tangible *t = tangible_get(id);
assert(t != nullptr);
AnimState state;
state.set_string("plane", plane);
state.set_xyz("xyz", xyz);
state.set_persistent("plane");
state.set_persistent("xyz");
t->anim_queue_.clear(state);
t->update_plane_item();
}
void World::tangible_walkto(int64_t id, float x, float y) {
Tangible *t = tangible_get(id);
assert(t != nullptr);
AnimState state = t->anim_queue_.get_final_persistent();
state.set_string("action", "walkto");
state.set_xyz("xyz", util::DXYZ(x,y,0.0));
t->anim_queue_.add(state);
}
eng::string World::tangible_anim_debug_string(int64_t id) const {
const Tangible *t = tangible_get(id);
if (t == 0) return "no such tangible";
@@ -40,10 +59,12 @@ eng::string World::tangible_ids_debug_string() const {
eng::string World::tangibles_near_debug_string(int64_t actor, int64_t distance) {
eng::ostringstream result;
for (int64_t id : get_near(actor, distance, true, false, true)) {
util::IdVector tans;
get_near(actor, distance, true, false, true, &tans);
for (int64_t id : tans) {
const Tangible *tan = tangible_get(id);
const AnimStep &aqback = tan->anim_queue_.back();
result << id << ": " << aqback.graphic() << " " << aqback.plane() << " " << aqback.xyz().debug_string() << std::endl;
AnimState state = tan->anim_queue_.get_final_everything();
result << id << ": " << state.debug_string() << std::endl;
}
return result.str();
}
@@ -267,53 +288,35 @@ LuaDefine(unittests_world1animdiff, "", "some unit tests") {
util::IdVector ids = util::id_vector_create(123, 345);
// Create some tangibles, and add some animations.
m->tangible_make(123, "somewhere");
m->tangible_make(345, "somewhere");
m->tangible_walkto(123, 770, 3, 4);
m->tangible_walkto(345, 771, 6, 2);
m->tangible_make(123);
m->tangible_make(345);
m->tangible_clear_anim_queue_to_empty(123);
m->tangible_clear_anim_queue_to_empty(345);
m->tangible_walkto(123, 3, 4);
m->tangible_walkto(345, 6, 2);
LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123,345");
LuaAssertStrEq(L, m->tangible_anim_debug_string(123),
"id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; "
"id=770 action=walkto x=3 y=4; ");
LuaAssertStrEq(L, m->tangible_anim_debug_string(345),
"id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; "
"id=771 action=walkto x=6 y=2; ");
LuaAssertStrEq(L, m->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0");
LuaAssertStrEq(L, m->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0");
// Now difference transmit all that to the client.
ss->diff_visible(ids, m.get(), &sb);
cs->patch_visible(&sb, nullptr);
LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123,345");
LuaAssertStrEq(L, ss->tangible_anim_debug_string(123),
"id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; "
"id=770 action=walkto x=3 y=4; ");
LuaAssertStrEq(L, ss->tangible_anim_debug_string(345),
"id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; "
"id=771 action=walkto x=6 y=2; ");
LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0");
LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0");
LuaAssert(L, worlds_identical(ss, cs));
// Now add some more animation records to the master.
m->tangible_walkto(123, 772, 7, 3);
m->tangible_walkto(345, 773, 2, 5);
LuaAssertStrEq(L, m->tangible_anim_debug_string(123),
"id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; "
"id=770 action=walkto x=3 y=4; "
"id=772 action=walkto x=7 y=3; ");
LuaAssertStrEq(L, m->tangible_anim_debug_string(345),
"id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; "
"id=771 action=walkto x=6 y=2; "
"id=773 action=walkto x=2 y=5; ");
m->tangible_walkto(123, 7, 3);
m->tangible_walkto(345, 2, 5);
LuaAssertStrEq(L, m->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0; action:walkto xyz:7,3,0");
LuaAssertStrEq(L, m->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0; action:walkto xyz:2,5,0");
// Now difference transmit all that to the client again.
ss->diff_visible(ids, m.get(), &sb);
cs->patch_visible(&sb, nullptr);
LuaAssertStrEq(L, ss->tangible_anim_debug_string(123),
"id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; "
"id=770 action=walkto x=3 y=4; "
"id=772 action=walkto x=7 y=3; ");
LuaAssertStrEq(L, ss->tangible_anim_debug_string(345),
"id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; "
"id=771 action=walkto x=6 y=2; "
"id=773 action=walkto x=2 y=5; ");
LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0; action:walkto xyz:7,3,0");
LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0; action:walkto xyz:2,5,0");
LuaAssert(L, worlds_identical(ss, cs));
// Delete tangible 345.
@@ -337,7 +340,7 @@ LuaDefine(unittests_world2pairtab, "", "some unit tests") {
// Create a master model containing some general tables, and
// some specialty tables (not numberable).
m->tangible_make(123, "somewhere");
m->tangible_make(123);
m->tangible_set_string(123, "inventory.TID", "inventory");
m->tangible_set_string(123, "transactions.TID", "transactions");
m->tangible_set_string(123, "skills.TID", "skills");
@@ -348,7 +351,7 @@ LuaDefine(unittests_world2pairtab, "", "some unit tests") {
// Now we're going to create a synchronous model that's similar to, but not
// exactly the same as that master model.
ss->tangible_make(123, "somewhere");
ss->tangible_make(123);
ss->tangible_set_string(123, "inventory.TID", "inventory");
ss->tangible_set_string(123, "skills.TID", "skills");
ss->tangible_set_string(123, "skills.crap.TID", "skills.crap");
@@ -384,12 +387,19 @@ LuaDefine(unittests_world3diffluatab, "", "some unit tests") {
StreamBuffer sb;
// Initialize all three models so that a tangible exists.
m->tangible_make(123, "somewhere");
ss->tangible_make(123, "somewhere");
cs->tangible_make(123, "somewhere");
m->tangible_make(345, "somewhere");
ss->tangible_make(345, "somewhere");
cs->tangible_make(345, "somewhere");
m->tangible_make(123);
ss->tangible_make(123);
cs->tangible_make(123);
m->tangible_make(345);
ss->tangible_make(345);
cs->tangible_make(345);
m->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0));
ss->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0));
cs->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0));
m->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0));
ss->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0));
cs->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0));
// Put some data into the master model.
m->tangible_set_string(123, "bacon", "crispy");
@@ -438,9 +448,9 @@ LuaDefine(unittests_world4difftanclass, "", "some unit tests") {
StreamBuffer sb;
// Initialize all three models so that a tangible exists.
m->tangible_make(123, "somewhere");
ss->tangible_make(123, "somewhere");
cs->tangible_make(123, "somewhere");
m->tangible_make(123);
ss->tangible_make(123);
cs->tangible_make(123);
// Change the lua class of the tangible.
m->tangible_set_class(123, "chicken");

View File

@@ -125,7 +125,15 @@ public:
// The unsorted version returns the tangibles in an unpredictable order. If sorted
// is false, return them in an unpredictable order.
//
IdVector get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted) const;
void get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted, IdVector *into) const;
// get_near
//
// Get a list of tangibles in any arbitrary region. This differs from
// PlaneMap::scan in that if the scan specifies a 'near', then the plane
// and center of the scan are updated from the near tangible.
//
void get_near(PlaneScan &sc, IdVector *into) const;
// Make a tangible.
//
@@ -133,8 +141,8 @@ public:
// stack untouched. Returns a pointer to the C++ part of the tangible, and
// optionally stores the Lua part in a stack slot.
//
Tangible *tangible_make(const LuaCoreStack &LS0, LuaSlot tan, int64_t id, const eng::string &plane);
Tangible *tangible_make(int64_t id, const eng::string &plane);
Tangible *tangible_make(const LuaCoreStack &LS0, LuaSlot tan, int64_t id);
Tangible *tangible_make(int64_t id);
// Get a pointer to the specified tangible.
//
@@ -362,9 +370,17 @@ public:
//
////////////////////////////////////////////////////////////////////////////
// Clear the animation queue and remove all persistent state.
//
void tangible_clear_anim_queue_to_empty(int64_t id);
// Clear the animation queue to a reasonable starting position.
//
void tangible_clear_plane_and_xyz(int64_t id, const eng::string &plane, const util::DXYZ &xyz);
// Add a 'walkto' animation to the specified tangible.
//
void tangible_walkto(int64_t id, int64_t animid, float x, float y);
void tangible_walkto(int64_t id, float x, float y);
// Get the tangible's animation queue as a debug string.
//

View File

@@ -6,7 +6,7 @@ function ug.the()
lis=tangible.scan('globals',0,0,0,true)
if #lis==0 then
local ugid=tangible.build{class='ug',x=0,y=0,z=0,plane='globals',graphic='box'}
local ugid=tangible.build{class='ug', animstate={xyz={0,0,0}, plane='globals'}}
print("The global table is "..tangible.id(ugid))
end