#pragma once #include "wrap-set.hpp" #include "wrap-unordered-map.hpp" #include "wrap-map.hpp" #include #include #include "luastack.hpp" #include "planemap.hpp" #include "idalloc.hpp" #include "animqueue.hpp" #include "invocation.hpp" #include "streambuffer.hpp" #include "debugcollector.hpp" #include "printbuffer.hpp" #include "sched.hpp" #include "http.hpp" #include "source.hpp" #include "luasnap.hpp" struct EngineWrapper; enum WorldType { WORLD_TYPE_MASTER = 1, WORLD_TYPE_PREDICTIVE = 2, }; class World; class Tangible : public eng::opnew { private: friend class World; // Serialize and deserialize // // The tangible's ID is not serialized. When you serialize a tangible, you // should probably serialize the ID separately. // // The Lua portion of the tangible is not serialized here. Instead, the lua // portion is serialized when you serialize the lua state as a whole. // // PlaneItem is not serialized. The deserialize routine rebuilds the // PlaneItem from the AnimQueue. // // World pointer is not serialized. // void serialize(StreamBuffer *sb); void deserialize(StreamBuffer *sb); public: // Animation queue. // AnimQueue anim_queue_; // Plane Item. // // The PlaneItem also contains this tangible's ID. // To move this PlaneItem, update the anim_queue first, then call // update_plane_item, which copies the data from the anim_queue. // PlaneItem plane_item_; // Player ID pool // // This is present in every tangible, whether a player or not. // However, the fifo is only enabled in logged-in players. // IdPlayerPool id_player_pool_; // Print Buffer // // Stores the console output for this actor until it can be // probed by the client. Most tangibles have empty printbuffers, // which are stored as just a null pointer internally. // PrintBuffer print_buffer_; // Can-Be-Controlled flag. // // This flag indicates whether the tangible can be controlled // by a client. Clients will not be allowed to attach to tangibles // who don't have this flag. If this flag is true, the // tangible cannot be deleted using a mere 'tangible.delete', instead, // you have to use 'tangible.deleteplayer'. // bool can_be_controlled_; // Is Controlled Flag. // // This flag is set to true when a client is controlling this player. // It gets set back to false when the client logs out or attaches // to a different player. This can only be set in master models. // bool is_controlled_; // Force disconnect flag. // // This flag is used to force the client to log out ASAP. This flag // can only be set in master models. // bool force_disconnect_; // Delete on Logout Flag. // // This flag can be set on a controlled player. When the player // disconnects, their character will be deleted. This flag can only // be set if the is_controlled_ flag is true. // bool delete_on_disconnect_; // constructor. // Tangible(World *w, int64_t id); // Get the ID // int64_t id() const { return plane_item_.id(); } void update_plane_item(); bool is_an_actor() { return (id_player_pool_.get_fifo_capacity() > 0); } void configure_id_pool_for_actor() { id_player_pool_.set_fifo_capacity(3); id_player_pool_.refill(); } }; using UniqueTangible = std::unique_ptr; class World : public eng::opnew { public: using IdVector = util::IdVector; using TanVector = eng::vector; using Redirects = eng::map; const float RadiusVisibility = 1000.0; const float RadiusClose = 1000.0; // Constructor. // // The constructor also calls 'lua_open' to create a new // lua interpreter for this world model. // World(WorldType wt); // Destructor. // // Not currently functional. // ~World(); // get_lua_state // // Get the lua interpreter associated with this world model. // lua_State *state() const { return lua_snap_.state(); } // get_near // // Get a list of tangibles in any arbitrary region. // void get_near(PlaneScan &sc, IdVector *into) const; // get_near // // Get a list of the tangibles that are near the player. If // 'exclude_nowhere' is true, exclude any tangibles on the nowhere plane // (but still include the player himself). The unsorted version returns the // tangibles in an unpredictable order. If sorted is false, return them in // an unpredictable order. This is a thin wrapper around the more general // form of 'get_near', above. // void get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted, IdVector *into) const; // Get tangibles near the specified tangible. // // Returns a count and pointer to an array of tangible IDs. The // returned pointer remains valid until the next call to // get_tangibles_near. // void get_tangibles_near(uint64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids); // Get the animation queues for the specified tangibles. // // For each tangible ID, returns the animation queue as a serialized // string via lengths/strings output arrays. The returned pointers // remain valid until the next call to get_animation_queues. // void get_animation_queues(uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings); // Make a tangible. // // You must provide a valid previously-unused ID. Otherwise, leaves the lua // 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); Tangible *tangible_make(int64_t id); // Get a pointer to the specified tangible. // // If there's no such tangible, or if the tangible is deleted, // returns nullptr. // Tangible *tangible_get(int64_t id); const Tangible *tangible_get(int64_t id) const; // Get a pointer to the specified tangible. // // The value on the lua stack should be a lua tangible. // // If the 'allowdel' flag is true, then it is valid to pass in // a deleted tangible. In that case, this function returns nullptr, // but this is not a Lua error. // Tangible *tangible_get(const LuaCoreStack &LS, LuaSlot slot, bool allowdel); // Get pointers to many tangibles. // TanVector tangible_get_all(const IdVector &ids) const; // Delete the specified tangible. // // If there's no such tangible, this is a no-op. // void tangible_delete(int64_t id); // Create a login actor. // // Creates a tangible of class 'login' and returns its ID. // This is used to create a temporary actor which is used during // the login process. // // If this is a master model, The function 'login.init' // called. Then, the following login flags are set: // can_be_controlled, is_controlled, and delete_on_disconnect. // // In a client model, 'login.init' is not called, // and the login flags are not used in client models. // int64_t create_login_actor(); // Log out a connected player. // // This is to be called after a client disconnects. // void disconnected(int64_t actor_id); // Fetch all redirects and clear the redirects table. // Redirects fetch_redirects(); // Probe an arbitrary lua expression. // // Any print-statements in the lua code are sent into // a stringstream. The return value of probe_lua is the string // from the stringstream. If the lua expression returns a // value, that is also printed to the stringstream. // eng::string probe_lua_expr(int64_t actor_id, std::string_view lua); // Probe that calls lua function, passing arguments. // // Print statements are discarded. The lua function may return a vector // of values. If so, the values are packed into a StreamBuffer. // void probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view datapack, StreamBuffer *retvals); // Invoke an Invocation object. // // This is the primary dispatcher for all operations that mutate a world model. // To mutate a world model, create an invocation, then invoke it. // // It is legal to mutate a world model without using 'Invoke', but // only in authoritative world models. // void invoke(const Invocation &inv); // Get the PrintBuffer of the actor. // const PrintBuffer *get_printbuffer(int64_t actor_id); // Get the source database. // SourceDB &get_source() { return source_db_; } // Rebuild the global environment from the source database. // // Returns true if the rebuild goes without errors. // bool rebuild_sourcedb(); // Update the source database from disk, then rebuild the global environment. // // Special case: if the source pointer is nullptr, does not update. // // Returns true if the update goes without errors. // bool update_source(const util::LuaSourceVec &source); bool update_source(const util::LuaSourcePtr &source); bool update_source(std::string_view sourcepk); // Supply an HTTP response to an outstanding HTTP request. // void http_response(const HttpParser &response); void http_responses(const HttpParserVec &responses); // Abort all HTTP requests. This is typically used after // reloading a world from a save-game. The http requests that // were in progress are long-since dead. // void abort_all_http_requests(int status_code, std::string_view error); // Serve an HTTP query coming in from outside. // // Note: the lua code for the http_serve runs in a nonblocking // context. It must produce a result instantly. // HttpServerResponse http_serve(const HttpParser &request); // Install this world into an EngineWrapper's function pointers. // void expose_world_to_driver(EngineWrapper *w); // fetch_global_pointer // // Given a lua state, fetch the world model associated with // that lua state. // static World *fetch_global_pointer(lua_State *L); // Check if the world is authoritative. // bool is_authoritative() const { return world_type_ == WORLD_TYPE_MASTER; } // Get a table showing all outstanding HTTP requests. // const HttpClientRequestMap &http_requests() const { return http_requests_; } // Serialize and deserialize. // void serialize(StreamBuffer *sb); void deserialize(StreamBuffer *sb); // Snapshot and rollback. // // These are used by the client to convert the synchronous model // to an asynchronous model and back. // void snapshot(); void rollback(); bool snapshot_empty() { return snapshot_.empty(); } // Run any threads which according to the scheduler queue are ready. // void run_scheduled_threads(); // Check that the main thread has nothing on the stack // bool stack_is_clear() const { return lua_gettop(state()) == 0; } // Set the lthread state. // // Whenever lua code is running, and ONLY when lua code is running, // we store the following information in the world model: // // * lthread_actor_id: current actor // * lthread_place_id: current place // * lthread_use_ppool: true if we should use the player ID pool. // * lthread_prints_: a stringstream which will collect 'print' statements. // // As soon as the lua code stops executing, these variables are // cleared. // void clear_lthread_state(); void open_lthread_state(int64_t actor_id, int64_t place_id, int64_t thread_id, bool ppool); std::ostream *lthread_print_stream() { return <hread_prints_; } void lthread_prints_to_printbuffer(); void lthread_prints_to_dprint(); // Set a lua global variable. // // The table just stores strings, and the difference transmitter // just difference transmits those strings. The strings are meant // to be serialized lua data structures, but there is no enforcement // of that here. // void set_global(const eng::string &var, std::string_view value); // Get a lua global variable. // const eng::string &get_global(const eng::string &var); // Allocate a single ID. // // The rules are as follows: // * if lthread_use_ppool is false, uses the global pool. // * if lthread_actor_id is not a valid actor id, uses the global pool. // * otherwise, uses the player pool of lthread_actor_id. // int64_t alloc_id_predictable(); // If we're in a probe, generate an error. // If we're in a nonauthoritative model, do a nopredict yield. // Otherwise, return. void guard_blockable(lua_State *L, const char *fn); // If we're in a probe, return. // If we're in a nonauthoritative model, do a nopredict yield. // Otherwise, return. void guard_nopredict(lua_State *L, const char *fn); private: // Add a thread to the scheduler queue. // void schedule(int64_t clk, int64_t thid, int64_t plid); // Store a pointer to a world model into a lua registry. // static void store_global_pointer(lua_State *L, World *w); // Invoke the lua_call operation. // void invoke_lua_call(int64_t actor_id, int64_t place_id, std::string_view datapack); // Invoke a lua string. // void invoke_lua_expr(int64_t actor_id, int64_t place_id, std::string_view datapack); // Invoke the flush-prints operation. // void invoke_flush_prints(int64_t actor_id, int64_t place_id, std::string_view datapack); // Invoke the tick operation. // void invoke_tick(int64_t actor_id, int64_t place_id, std::string_view datapack); // Invoke the lua_source operation. // void invoke_lua_source(int64_t actor_id, int64_t place_id, std::string_view datapack); // Low level spawn thread function. // bool spawn(LuaCoreStack &LS0, int64_t actor_id, int64_t place_id, LuaSlot func, int nargs, bool print); public: //////////////////////////////////////////////////////////////////////////// // // TESTING SUPPORT // // The following functions are not designed to be useful for production // code, they're designed to be helpful for unit testing. // //////////////////////////////////////////////////////////////////////////// // 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, float x, float y); // Get the tangible's animation queue as a debug string. // eng::string tangible_anim_debug_string(int64_t id) const; // Get the tangible's ID Pool as a debug string. // eng::string tangible_id_pool_debug_string(int64_t id) const; // Get a list of all existing tangibles as a comma-separated string. // eng::string tangible_ids_debug_string() const; // Get a list of all tangibles near the target as a string. // eng::string tangibles_near_debug_string(int64_t actor, int64_t distance); // Shows the TID (table ID) of the tables that were numbered. // TIDs are in alphabetical order. Any table without a TID // shows up as "unknown" // eng::string numbered_tables_debug_string() const; // Paired tables debug string. Shows TID=TID pairs, sorted alphabetically. // eng::string paired_tables_debug_string(lua_State *master) const; // Store a string in the tangible's database. // void tangible_set_string(int64_t id, const eng::string &path, const eng::string &value); // Copy a from the lua global environment into the tangible's database. // // This is for unit testing. // void tangible_copy_global(int64_t id, const eng::string &path, const eng::string &global); // Pretty-print the entire tangible database and return it as a string. // eng::string tangible_pprint(int64_t id) const; // Set the tangible's lua class. // void tangible_set_class(int64_t id, const eng::string &c) const; // Get the tangible's lua class (returns empty string if none). // eng::string tangible_get_class(int64_t id) const; public: /////////////////////////////////////////////////////////// // // difference transmission internals related to table comparison // // These routines compare tables in the master lua to the corresponding // tables in the synchronous lua. This is a nonrecursive process, because // the recursion has already been done during the table enumeration process. // /////////////////////////////////////////////////////////// void patch_numbered_tables(StreamBuffer *sb, DebugCollector *dbc); void diff_numbered_tables(lua_State *master, StreamBuffer *sb); void patch_tangible_databases(StreamBuffer *sb, DebugCollector *dbc); void diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb); void patch_tangible_classes(StreamBuffer *sb, DebugCollector *dbc); void diff_tangible_classes(const IdVector &basis, lua_State *master, StreamBuffer *sb); /////////////////////////////////////////////////////////// // // Difference transmission internals // /////////////////////////////////////////////////////////// util::IdVector get_visible_union(int64_t actor_id, World *master); int64_t patch_actor(StreamBuffer *sb, DebugCollector *dbc); void diff_actor(int64_t actor_id, World *master, StreamBuffer *sb); void patch_visible(StreamBuffer *sb, DebugCollector *dbc); void diff_visible(const util::IdVector &ids, World *master, StreamBuffer *sb); void patch_luatabs(StreamBuffer *sb, DebugCollector *dbc); void diff_luatabs(int64_t actor_id, World *master, StreamBuffer *sb); void patch_tanclass(StreamBuffer *sb, DebugCollector *dbc); void diff_tanclass(int64_t actor_id, World *master, StreamBuffer *sb); void patch_source(StreamBuffer *sb, DebugCollector *dbc); void diff_source(World *master, StreamBuffer *sb); void patch_globals(StreamBuffer *sb, DebugCollector *dbc); void diff_globals(World *master, StreamBuffer *sb); /////////////////////////////////////////////////////////// // // Difference transmission entry point. // /////////////////////////////////////////////////////////// int64_t patch(StreamBuffer *sb, DebugCollector *dbc); void diff(int64_t actor, bool full, World *master, StreamBuffer *sb); public: /////////////////////////////////////////////////////////// // // world-pairtab: Numbering and pairing of lua tables. // // The following routines pair up tables in the synchronous // model with tables in the master model, by assigning matching // table numbers. This is not one subroutine but several, because // some of the steps happen on the server, some on the client, // and so forth. // // The goal of these routines is to build these data structures: // // Table-to-number mapping is stored in registry.tnmap // Number-to-table mapping is stored in registry.ntmap // /////////////////////////////////////////////////////////// // In the synchronous models, number tables recursively. // // This is a simple recursive traversal, which numbers tables. // This creates the initial ntmap in the synchronous models. // int number_lua_tables(const IdVector &basis); // Pair tables in the master model to tables in the synch model. // // Recursively walk the master and synchronous model in parallel, // copying table numbers from the synchronous ntmap into the master's ntmap. // void pair_lua_tables(const IdVector &basis, lua_State *master); // Number previously unpaired tables in the master model. // // This finds every not-yet-numbered table in the master model, // and appends these tables to the master's ntmap. Once they're // in the ntmap, they can be paired by simply creating new tables // in the synchronous model. // int number_remaining_tables(const IdVector &basis, lua_State *master); // Create new tables in the synchronous models. // // Creates new tables in the synchronous model and appends these // new tables to the synchronous model's ntmap. // void create_new_tables(int n); // Delete the table numbering. // // This simply removes registry.tnmap and registry.ntmap // void unnumber_lua_tables(); private: // Type of model WorldType world_type_; // A lua intepreter with snapshot function. // LuaSnap lua_snap_; // The Global ID Pool. // IdGlobalPool id_global_pool_; // Source Database. // SourceDB source_db_; PlaneMap plane_map_; // Lua Global Variables // // assign_seqno: sequence number generator for global variable assignments (master and client) // gvname_to_serial: global variable name to serialized data. (master and client) // gvname_to_seqno: global variable name to sequence number. (master only) // seqno_to_gvname: sequence number to global variable name. (master only) // gvname_modified: set of global variables recently locally modified. (client only) // int64_t assign_seqno_; eng::map gvname_to_serial_; eng::map gvname_to_seqno_; eng::map seqno_to_gvname_; eng::set gvname_modified_; // Tangibles table. // eng::unordered_map tangibles_; // Current time. // int64_t clock_; // Thread schedule: must include every thread, except // for the one currently-executing thread. // Schedule thread_sched_; // Outstanding HTTP requests, indexed by request ID. // Authoritative models only. // HttpClientRequestMap http_requests_; // Serialized snapshot of world model. // StreamBuffer snapshot_; // Redirects. // Redirects redirects_; // Storage for wrapper_get_tangibles_near and wrapper_get_animation_queues. // These hold results alive while the driver reads from the raw pointers. // util::IdVector wrapper_scan_result_; util::SharedStdStringVec wrapper_anim_queues_; // lthread variables: see set_lthread_state for explanation. // int64_t lthread_actor_id_; int64_t lthread_place_id_; int64_t lthread_thread_id_; int64_t lthread_use_ppool_; eng::ostringstream lthread_prints_; friend class Tangible; friend int lfn_tangible_animate(lua_State *L); friend int lfn_tangible_build(lua_State *L); friend int lfn_tangible_redirect(lua_State *L); friend int lfn_tangible_actor(lua_State *L); friend int lfn_tangible_place(lua_State *L); friend int lfn_tangible_nopredict(lua_State *L); friend int lfn_tangible_near(lua_State *L); friend int lfn_tangible_scan(lua_State *L); friend int lfn_tangible_find(lua_State *L); friend int lfn_tangible_start(lua_State *L); friend int lfn_math_random(lua_State *L); friend int lfn_math_randomstate(lua_State *L); friend int lfn_time(lua_State *L); friend int lfn_wait(lua_State *L); friend int lfn_nopredict(lua_State *L); friend int lfn_http_request(lua_State *L, const char *method); friend int lfn_global_set(lua_State *L); }; using UniqueWorld = std::unique_ptr;