diff --git a/plugins/dm.gameconnection/AutomationEngine.cpp b/plugins/dm.gameconnection/AutomationEngine.cpp index 4ef35218a9..3a7511e77b 100644 --- a/plugins/dm.gameconnection/AutomationEngine.cpp +++ b/plugins/dm.gameconnection/AutomationEngine.cpp @@ -90,7 +90,7 @@ int AutomationEngine::generateNewSequenceNumber() return ++_seqno; } -AutomationEngine::Request* AutomationEngine::sendRequest(const std::string& request, int tag) { +AutomationEngine::Request* AutomationEngine::sendRequest(int tag, const std::string& request) { assert(tag < 31); if (!_connection) throw DisconnectException(); @@ -255,7 +255,7 @@ void AutomationEngine::waitForTags(int tagMask) std::string AutomationEngine::executeRequestBlocking(int tag, const std::string& request) { - Request* req = sendRequest(request, tag); + Request* req = sendRequest(tag, request); int seqno = req->_seqno; std::string response; @@ -272,7 +272,7 @@ std::string AutomationEngine::executeRequestBlocking(int tag, const std::string& int AutomationEngine::executeRequestAsync(int tag, const std::string& request, const std::function& callback) { - Request* req = sendRequest(request, tag); + Request* req = sendRequest(tag, request); req->_callback = callback; return req->_seqno; } diff --git a/plugins/dm.gameconnection/AutomationEngine.h b/plugins/dm.gameconnection/AutomationEngine.h index 513e13d396..2a8c39fa99 100644 --- a/plugins/dm.gameconnection/AutomationEngine.h +++ b/plugins/dm.gameconnection/AutomationEngine.h @@ -17,11 +17,11 @@ class DisconnectException : public std::runtime_error { DisconnectException() : std::runtime_error("Game connection lost") {} }; -//bitmask with all tag bits set +// Bitmask with all tag bits set. static const int TAGMASK_ALL = -1; // Putting this to wait-list of multistep procedure -// results in it waking up on next think (timer tick). +// results in waking up on next think (suitable for polling). static const int SEQNO_WAIT_POLL = -10000; struct MultistepProcReturn { @@ -35,7 +35,7 @@ struct MultistepProcReturn { /** - * stgatilov: This is engine for connection to TheDarkMod's automation via socket + * stgatilov: The engine for connection to TheDarkMod's automation via socket. */ class AutomationEngine { @@ -47,7 +47,6 @@ class AutomationEngine // Connect to TDM instance if not connected yet. // Returns false if failed to connect, true on success. bool connect(); - // Disconnect from TDM instance if connected. // If force = false, then it waits until all pending requests are finished. // If force = true, then all pending requests are dropped, no blocking for sure. @@ -59,22 +58,10 @@ class AutomationEngine bool hasLostConnection() const; - //check how socket is doing, accept responses, call callbacks - //this should be done regularly: in fact, timer calls it often + // Check how socket is doing, accept responses, call callbacks. + // This should be done regularly, e.g. in a timer. void think(); - // Return true iff any of the given requests of multistep procedures are not finished yet. - bool areInProgress(const std::vector& reqSeqnos, const std::vector& procIds); - // Wait for specified requests and multistep procedures to finish. - // Throws DisconnectException if it cannot be done due to lost connection. - void wait(const std::vector& reqSeqnos, const std::vector& procIds); - - // Return true iff any of the given requests of multistep procedures are not finished yet. - bool areTagsInProgress(int tagMask = TAGMASK_ALL); - // Wait for all active requests and multistep procedures matching the given mask to finish. - // Throws DisconnectException if it cannot be done due to lost connection. - void waitForTags(int tagMask = TAGMASK_ALL); - // Send given request synchronously, i.e. wait until its completition. // Returns response content. @@ -82,17 +69,33 @@ class AutomationEngine std::string executeRequestBlocking(int tag, const std::string& request); // Send request !a!synchronously, i.e. send it to socket and return immediately. - // Returns seqno of the request: it can be put to wait-list of multistep procedure or used for polling. + // Returns seqno of the request: it can be put to wait-list of multistep procedure or used in queries. // When request is finished, optional callback will be executed (from "think" method). - // Note that if connection is lost already of while request is being executed, NO special callback is called. - // May throw DisconnectException is connection is missing (but never throws if isAlive() is true). + // Note that if connection is lost, NO special callback is called. + // May throw DisconnectException if connection is missing (but never throws if isAlive() is true before call). int executeRequestAsync(int tag, const std::string& request, const std::function& callback = {}); // Execute given multistep procedure, starting on the next think. - // Returns ID of procedure for future queries. + // Returns ID of procedure for queries. + // Multistep procedure is like a DFA of "steps", each step is executed till start to end. + // When step is over, you can specify async requests to wait for, and which step to resume when they are finished. int executeMultistepProc(int tag, const std::function& function, int startStep = 0); + // Return true iff any of the given requests or multistep procedures are not finished yet. + bool areInProgress(const std::vector& reqSeqnos, const std::vector& procIds); + // Wait for specified requests and multistep procedures to finish. + // Throws DisconnectException if it cannot be done due to lost connection. + void wait(const std::vector& reqSeqnos, const std::vector& procIds); + + // Return true iff any request or multistep procedure matching the given mask is not finished yet. + // Note: tagMask is a bitmask, so pass (1< _connection; @@ -114,8 +117,8 @@ class AutomationEngine struct Request { int _seqno = 0; - bool _finished = false; int _tag = 0; + bool _finished = false; std::string _request; std::string _response; @@ -132,22 +135,14 @@ class AutomationEngine }; std::vector _multistepProcs; - // Every request should get unique seqno, otherwise we won't be able to distinguish their responses. int generateNewSequenceNumber(); + Request* sendRequest(int tag, const std::string& request); Request* findRequest(int seqno) const; MultistepProcedure* findMultistepProc(int id) const; bool isMultistepProcStillWaiting(const MultistepProcedure& proc, bool waitForPoll) const; - - - // Prepend seqno to specified request and send it to game. - // Returns pointer to the created request. - Request* sendRequest(const std::string& request, int tag); - - //execute next step of the current multistep procedure void resumeMultistepProcedure(int id); - }; } diff --git a/plugins/dm.gameconnection/GameConnection.cpp b/plugins/dm.gameconnection/GameConnection.cpp index cd46f7f8c8..347f7b9dc4 100644 --- a/plugins/dm.gameconnection/GameConnection.cpp +++ b/plugins/dm.gameconnection/GameConnection.cpp @@ -531,20 +531,20 @@ void GameConnection::enableGhostMode() executeSetTogglableFlag("notarget", true, "OFF"); } -bool GameConnection::setCameraSyncEnabled(bool enable) +void GameConnection::setCameraSyncEnabled(bool enable) { try { if (!enable) { _cameraChangedSignal.disconnect(); } if (enable) { + enableGhostMode(); + _cameraChangedSignal.disconnect(); _cameraChangedSignal = GlobalCameraManager().signal_cameraChanged().connect( sigc::mem_fun(this, &GameConnection::updateCamera) ); - enableGhostMode(); - //sync camera location right now updateCamera(); _engine->waitForTags(1 << TAG_CAMERA); @@ -554,9 +554,7 @@ bool GameConnection::setCameraSyncEnabled(bool enable) } catch (const DisconnectException&) { //disconnected: will be handled during next think - return false; } - return true; } bool GameConnection::isCameraSyncEnabled() const @@ -664,14 +662,13 @@ void GameConnection::onMapEvent(IMap::MapEvent ev) } } -bool GameConnection::setAutoReloadMapEnabled(bool enable) +void GameConnection::setAutoReloadMapEnabled(bool enable) { if (enable && !_engine->isAlive()) - return false; + return; _autoReloadMap = enable; signal_StatusChanged.emit(0); - return true; } bool GameConnection::isAutoReloadMapEnabled() const @@ -699,18 +696,17 @@ void GameConnection::setUpdateMapObserverEnabled(bool enable) signal_StatusChanged.emit(0); } -bool GameConnection::setAlwaysUpdateMapEnabled(bool enable) +void GameConnection::setAlwaysUpdateMapEnabled(bool enable) { if (enable) { if (!_engine->isAlive()) - return false; + return; if (enable) setUpdateMapObserverEnabled(true); } _updateMapAlways = enable; signal_StatusChanged.emit(0); - return true; } bool GameConnection::isAlwaysUpdateMapEnabled() const @@ -809,36 +805,30 @@ void GameConnection::initialiseModule(const IApplicationContext& ctx) // Construct toggles _camSyncToggle = GlobalEventManager().addAdvancedToggle( "GameConnectionToggleCameraSync", - [this](bool v) { return setCameraSyncEnabled(v); } - ); - GlobalEventManager().addAdvancedToggle( - "GameConnectionToggleAutoMapReload", - [this](bool v) { return setAutoReloadMapEnabled(v); } - ); - GlobalEventManager().addAdvancedToggle( - "GameConnectionToggleHotReload", - [this](bool v) { return setAlwaysUpdateMapEnabled(v); } + [this](bool v) { + bool oldEnabled = isCameraSyncEnabled(); + setCameraSyncEnabled(v); + return isCameraSyncEnabled() != oldEnabled; + } ); - // Add one-shot commands and associated toolbar buttons - GlobalCommandSystem().addCommand("GameConnectionBackSyncCamera", - [this](const cmd::ArgumentList&) { backSyncCamera(); }); + GlobalCommandSystem().addCommand( + "GameConnectionBackSyncCamera", + [this](const cmd::ArgumentList&) { + backSyncCamera(); + } + ); _camSyncBackButton = GlobalEventManager().addCommand( "GameConnectionBackSyncCamera", "GameConnectionBackSyncCamera", false ); - GlobalCommandSystem().addCommand("GameConnectionReloadMap", - [this](const cmd::ArgumentList&) { reloadMap(); }); - GlobalCommandSystem().addCommand("GameConnectionUpdateMap", - [this](const cmd::ArgumentList&) { doUpdateMap(); }); - GlobalCommandSystem().addCommand("GameConnectionPauseGame", - [this](const cmd::ArgumentList&) { togglePauseGame(); }); - GlobalCommandSystem().addCommand("GameConnectionRespawnSelected", - [this](const cmd::ArgumentList&) { respawnSelectedEntities(); }); + // Toolbar button(s) + GlobalMainFrame().signal_MainFrameConstructed().connect( + sigc::mem_fun(this, &GameConnection::addToolbarItems) + ); // Add menu items ui::menu::IMenuManager& mm = GlobalMenuManager(); mm.insert("main/help", "connection", ui::menu::ItemType::Folder, _("Connection"), "", ""); - // Add menu button which shows up the dialog GlobalCommandSystem().addCommand("GameConnectionDialogToggle", gameconn::GameConnectionDialog::toggleDialog); // Add the menu item @@ -850,11 +840,6 @@ void GameConnection::initialiseModule(const IApplicationContext& ctx) "stimresponse.png", // icon "GameConnectionDialogToggle" // event name ); - - // Toolbar button(s) - GlobalMainFrame().signal_MainFrameConstructed().connect( - sigc::mem_fun(this, &GameConnection::addToolbarItems) - ); } void GameConnection::shutdownModule() diff --git a/plugins/dm.gameconnection/GameConnection.h b/plugins/dm.gameconnection/GameConnection.h index 26033f372a..e58c99cdb0 100644 --- a/plugins/dm.gameconnection/GameConnection.h +++ b/plugins/dm.gameconnection/GameConnection.h @@ -29,79 +29,66 @@ class GameConnection : GameConnection(); ~GameConnection(); - /** - * Restart game and connect to it. - */ - void restartGame(bool dmap); - //returns true if restartGame sequence is in progress - bool isGameRestarting() const; - - - //connect to TDM instance if not connected yet - //return false if failed to connect + // Connect to TDM instance if not connected yet. + // Returns false if failed to connect. bool connect(); - //disconnect from TDM instance if connected - //if force = true, then it blocks until pending requests are finished - //if force = false, then all pending requests are dropped, no blocking for sure + // Disconnect from TDM instance if connected. + // If force = true, then it blocks until pending requests are finished. + // If force = false, then all pending requests are dropped (no blocking for sure). void disconnect(bool force = false); - //returns false if connection is not yet established or has been closed for whatever reason + // Returns false if connection is not yet established or has been closed recently. bool isAlive() const; - /** - * \brief - * Enable dynamic sync of camera to game position - * - * \return - * true on success, false if connection failed. - */ - bool setCameraSyncEnabled(bool enable); + // Starts async procedure including: + // * connect to existing game instance or start a new one + // * set current mod/mission and map + // * optionally dmap it + // * make sure game is started afresh + void restartGame(bool dmap); + // Returns true iff restartGame sequence is currently in progress. + bool isGameRestarting() const; + // Enable/disable continuous sync of camera: update in-game player to DarkRadiant camera. + void setCameraSyncEnabled(bool enable); + // Returns true iff continuous camera sync is enabled right now. bool isCameraSyncEnabled() const; - - /// Trigger one-off sync of game position back to Radiant camera + // Trigger one-off sync of game position back to DarkRadiant camera. void backSyncCamera(); - //pause game if it is live, unpause if it is paused - void togglePauseGame(); - //respawn all entities in the current selection set - void respawnSelectedEntities(); - - //ask TDM to reload .map file from disk + // Ask game to reload .map file from disk (right now, once). void reloadMap(); - - /** - * \brief - * Instruct TDM to reload .map from disk automatically after every map save - * - * \return - * true on success, false if the game connection failed. - */ - bool setAutoReloadMapEnabled(bool enable); - + // Enable/disable mode: force game to reload .map from disk every time DarkRadiant saves it. + void setAutoReloadMapEnabled(bool enable); + // Returns true iff .map reload mode is enabled. bool isAutoReloadMapEnabled() const; - /** - * \brief - * Enable hot reload of map entity changes. - * - * \return - * true on success, false if the game connection failed. - */ + // Enable/disable listening for all entity changes for "update map" feature. + // See doUpdateMap for more details. void setUpdateMapObserverEnabled(bool on); - + // Returns true iff observer for "update map" is enabled. bool isUpdateMapObserverEnabled() const; - - bool setAlwaysUpdateMapEnabled(bool on); - + // Send pending changes of map entities to game (right now, once). + // All changes a) since last successful call of this method, + // or b) since the observer was enabled; are sent as a diff. + // The game applies the diff on top of its current map state and hot reloads entities. + void doUpdateMap(); + // Enable/disable mode: doUpdateMap after every entity change. + // Note: the update is postponed to next think, so that mass changes go as one diff. + void setAlwaysUpdateMapEnabled(bool on); + // Returns true iff the mode for update map after every change is enabled. bool isAlwaysUpdateMapEnabled() const; - //send map update to TDM right now - void doUpdateMap(); + // Toggle game pause status: pause if it is live / unpause if it is paused. + // Note: there is no way to learn if game is paused right now. + void togglePauseGame(); + // Respawn all entities in the current selection set. + void respawnSelectedEntities(); - //signal is emitted when status changes: - // connected/disconnected - // restart game starts/ends - // any mode starts/ends + // This signal is emitted when status changes: + // * connected/disconnected + // * restart game starts/ends + // * any mode starts/ends + // GUI should update itself every time it is triggered. sigc::signal signal_StatusChanged; //RegisterableModule implementation @@ -112,73 +99,78 @@ class GameConnection : private: - // Add any required items to the application toolbars - void addToolbarItems(); - - // IEventPtrs corresponding to activatable menu options - IEventPtr _camSyncToggle; - IEventPtr _camSyncBackButton; - - //underlying engine for connection to TDM game + // Underlying engine for connection to TDM game. std::unique_ptr _engine; - //when connected, this timer calls Think periodically + // When connected, this timer calls "think" method periodically. std::unique_ptr _thinkTimer; + // Flag is used to block new timer events if an old timer event has not finished yet. bool _timerInProgress = false; - void onTimerEvent(wxTimerEvent& ev); - - //signal listener for when map is saved, loaded, unloaded, etc. + // Signal subscription for when map is saved, loaded, unloaded, etc. sigc::connection _mapEventListener; - //true <=> cameraOutData holds new camera position, which should be sent to TDM + // True iff _cameraOutData holds new camera position, which should be sent to game soon. bool _cameraOutPending = false; - //data for camera position (setviewpos format: X Y Z -pitch yaw roll) + // Data for camera position (setviewpos format: X Y Z -pitch yaw roll) Vector3 _cameraOutData[2]; - //the update subscription used when camera sync is enabled + // Signal subscription for when camera of DarkRadiant view changes. sigc::connection _cameraChangedSignal; - //observes over changes to map data + // Observes over changes to map data (mainly spawnargs of entities). MapObserver _mapObserver; - //set to true when "reload map automatically" is on + // True when "setAutoReloadMapEnabled" is enabled. bool _autoReloadMap = false; - //set to true when "update map" is set to "always" + // True when "setAlwaysUpdateMapEnabled" is enabled. bool _updateMapAlways = false; - //set to true when restartGame procedure is executed + // True when restartGame procedure is executed. bool _restartInProgress = false; - //if there are any pending async commands (camera update), send one now - //returns true iff anything was sent to game - bool sendAnyPendingAsync(); - //check how socket is doing, accept responses and send pending async requests - //this should be done regularly: in fact, timer calls it often - void think(); - //enable/disable timer calling think method regularly + // IEventPtrs corresponding to activatable menu options. + IEventPtr _camSyncToggle; + IEventPtr _camSyncBackButton; + +private: + + // Add any required items to the application toolbars. + void addToolbarItems(); + + // Enable/disable timer calling think method regularly. void setThinkLoop(bool enable); + // Callback for _thinkTimer. + void onTimerEvent(wxTimerEvent& ev); + // Check how socket is doing, accept responses and send pending async requests. + // This should be done regularly: in fact, timer calls it often. + void think(); + // If there are any pending async commands (e.g. camera update), send one now. + // Returns true iff anything was sent to game. + bool sendAnyPendingAsync(); - //waits for previous TAG_GENERIC requests to finish, then executes request as TAG_GENERIC in blocking fashion - std::string executeGenericRequest(const std::string& request); - //given a command to be executed in game console (no EOLs), returns its full request text (except for seqno) + // Given a command to be executed in game console (no EOLs), returns its full request text. + // The result is ready to be sent over to AutomationEngine, which will prepend seqno automatically. static std::string composeConExecRequest(std::string consoleLine); - //set noclip/god/notarget to specific state (blocking) - //toggleCommand is the command which toggles state - //offKeyword is the part of phrase printed to game console when the state becomes disabled + + // Waits for previous TAG_GENERIC requests to finish, then executes request as TAG_GENERIC (blocking). + std::string executeGenericRequest(const std::string& request); + // Set noclip or god or notarget to specific state (blocking). + // toggleCommand is the command which toggles state. + // offKeyword is the part of phrase printed to game console when the state becomes disabled. void executeSetTogglableFlag(const std::string &toggleCommand, bool enable, const std::string &offKeyword); - //learn state of the specified cvar (blocking) + // Learn state of the specified cvar (blocking). std::string executeGetCvarValue(const std::string &cvarName, std::string *defaultValue = nullptr); - //learn current status: installed mod/map, active gui, etc. (blocking) + // Learn current status: installed mod/map, active gui, etc. (blocking). std::map executeQueryStatus(); - //called from camera modification callback: schedules async "setviewpos" action for future + // Called from camera modification callback: schedules async "setviewpos" action for future. void updateCamera(); - //send request for camera update, which is pending yet + // Send request for camera update, which is pending yet. bool sendPendingCameraUpdate(); - //enable notarget/god/noclip to allow player to fly around without problems + // Enable notarget/god/noclip to allow player to fly around without problems. void enableGhostMode(); - //saves map using DR code if there are pending modifications + // Save map using DarkRadiant command if there are any pending modifications. void saveMapIfNeeded(); - //signal observer on map saving + // Callback called on map saving, loading and unloading. void onMapEvent(IMap::MapEvent ev); };