From 7357ea2102eecda497b43fcd9ee6d01a918bfb65 Mon Sep 17 00:00:00 2001 From: athile Date: Thu, 1 Jul 2010 11:49:00 -0700 Subject: [PATCH 1/6] Add simple external console server/client --- CMakeLists.txt | 27 +++-- apps/clientconsole/CMakeLists.txt | 2 + apps/clientconsole/client.cpp | 98 +++++++++++++++ apps/openmw/engine.cpp | 31 +++++ apps/openmw/engine.hpp | 11 ++ components/commandserver/server.cpp | 177 ++++++++++++++++++++++++++++ components/commandserver/server.hpp | 62 ++++++++++ components/misc/tsdeque.hpp | 34 ++++++ 8 files changed, 433 insertions(+), 9 deletions(-) create mode 100755 apps/clientconsole/CMakeLists.txt create mode 100755 apps/clientconsole/client.cpp create mode 100755 components/commandserver/server.cpp create mode 100755 components/commandserver/server.hpp create mode 100755 components/misc/tsdeque.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e9073b7c..115242519 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,16 +98,22 @@ set(INPUT_HEADER components/engine/input/poller.hpp) source_group(input FILES ${INPUT} ${INPUT_HEADER}) -set(MISC - components/misc/stringops.cpp - components/misc/fileops.cpp) -set(MISC_HEADER - components/misc/fileops.hpp - components/misc/slice_array.hpp - components/misc/stringops.hpp) +set(COMMANDSERVER + components/commandserver/server.hpp + components/commandserver/server.cpp) +source_group(commandserver FILES ${COMMANDSERVER}) + +set(MISC + components/misc/stringops.cpp + components/misc/fileops.cpp) +set(MISC_HEADER + components/misc/fileops.hpp + components/misc/slice_array.hpp + components/misc/stringops.hpp + components/misc/tsdeque.hpp) source_group(misc FILES ${MISC} ${MISC_HEADER}) -set(COMPONENTS ${BSA} ${NIF} ${NIFOGRE} ${ESM_STORE} ${OGRE} ${INPUT} ${MISC}) +set(COMPONENTS ${BSA} ${NIF} ${NIFOGRE} ${ESM_STORE} ${OGRE} ${INPUT} ${COMMANDSERVER} ${MISC}) set(COMPONENTS_HEADER ${BSA_HEADER} ${NIF_HEADER} ${NIFOGRE_HEADER} ${ESM_STORE_HEADER} ${ESM_HEADER} ${OGRE_HEADER} ${INPUT_HEADER} ${MISC_HEADER}) @@ -137,7 +143,7 @@ include_directories("." ${CMAKE_HOME_DIRECTORY}/extern/caelum/include) link_directories(${Boost_LIBRARY_DIRS} ${OGRE_LIB_DIR}) -ADD_SUBDIRECTORY( extern/caelum ) +add_subdirectory( extern/caelum ) # Specify build paths @@ -189,6 +195,9 @@ if (APPLE) target_link_libraries(openmw ${CARBON_FRAMEWORK}) endif (APPLE) +# Other apps and tools +add_subdirectory( apps/clientconsole ) + # Apple bundling if (APPLE) set_source_files_properties( diff --git a/apps/clientconsole/CMakeLists.txt b/apps/clientconsole/CMakeLists.txt new file mode 100755 index 000000000..e6885841f --- /dev/null +++ b/apps/clientconsole/CMakeLists.txt @@ -0,0 +1,2 @@ +project(clientconsole) +add_executable(clientconsole client.cpp) diff --git a/apps/clientconsole/client.cpp b/apps/clientconsole/client.cpp new file mode 100755 index 000000000..dac95ce99 --- /dev/null +++ b/apps/clientconsole/client.cpp @@ -0,0 +1,98 @@ +#include +#include +#include + +using boost::asio::ip::tcp; + +#pragma warning( disable : 4966 ) + +class Client +{ +protected: + boost::asio::io_service mIOService; + tcp::socket* mpSocket; + +public: + + bool connect(const char* port) + { + tcp::resolver resolver(mIOService); + tcp::resolver::query query("localhost", port); + tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); + tcp::resolver::iterator end; + + mpSocket = new tcp::socket(mIOService); + boost::system::error_code error = boost::asio::error::host_not_found; + while (error && endpoint_iterator != end) + { + mpSocket->close(); + mpSocket->connect(*endpoint_iterator++, error); + } + + return (error) ? false : true; + } + void disconnect() + { + mpSocket->close(); + mIOService.stop(); + } + + bool send (const char* msg) + { + struct Header + { + char magic[4]; + boost::uint32_t dataLength; + }; + const size_t slen = strlen(msg); + const size_t plen = sizeof(Header) + slen + 1; + + std::vector packet(plen); + Header* pHeader = reinterpret_cast(&packet[0]); + strncpy(pHeader->magic, "OMW0", 4); + pHeader->dataLength = slen + 1; // Include the null terminator + strncpy(&packet[8], msg, pHeader->dataLength); + + boost::system::error_code ec; + boost::asio::write(*mpSocket, boost::asio::buffer(packet), + boost::asio::transfer_all(), ec); + if (ec) + std::cout << "Error: " << ec.message() << std::endl; + + return !ec; + } +}; + + +int main(int argc, char* argv[]) +{ + std::cout << "OpenMW client console" << std::endl; + std::cout << "=====================" << std::endl; + std::cout << "Type 'quit' to exit." << std::endl; + std::cout << "Connecting..."; + + Client client; + if (client.connect("27917")) + { + std::cout << "success." << std::endl; + + bool bDone = false; + do + { + std::cout << "> "; + char buffer[1024]; + gets(buffer); + + if (std::string(buffer) != "quit") + bDone = !client.send(buffer); + else + bDone = true; + } while (!bDone); + + client.disconnect(); + } + else + std::cout << "failed." << std::endl; + + return 0; +} diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f431e51bd..601df6354 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -14,10 +14,25 @@ #include "apps/openmw/mwrender/playerpos.hpp" #include "apps/openmw/mwrender/sky.hpp" +class ProcessCommandsHook : public Ogre::FrameListener +{ +public: + ProcessCommandsHook(OMW::Engine* pEngine) : mpEngine (pEngine) {} + virtual bool frameStarted(const Ogre::FrameEvent& evt) + { + mpEngine->processCommands(); + return true; + } +protected: + OMW::Engine* mpEngine; +}; + + OMW::Engine::Engine() : mEnableSky (false) , mpSkyManager (NULL) { + mspCommandServer.reset(new OMW::CommandServer::Server(&mCommands, kCommandServerPort)); } // Load all BSA files in data directory. @@ -83,6 +98,16 @@ void OMW::Engine::enableSky (bool bEnable) mEnableSky = bEnable; } +void OMW::Engine::processCommands() +{ + std::string msg; + while (mCommands.pop_front(msg)) + { + ///\todo Add actual processing of the received command strings + std::cout << "Command: '" << msg << "'" << std::endl; + } +} + // Initialise and enter main loop. void OMW::Engine::go() @@ -146,11 +171,17 @@ void OMW::Engine::go() // Sets up the input system MWInput::MWInputManager input(mOgre, player); + // Launch the console server + std::cout << "Starting command server on port " << kCommandServerPort << std::endl; + mspCommandServer->start(); + mOgre.getRoot()->addFrameListener( new ProcessCommandsHook(this) ); + std::cout << "\nStart! Press Q/ESC or close window to exit.\n"; // Start the main rendering loop mOgre.start(); + mspCommandServer->stop(); delete mpSkyManager; std::cout << "\nThat's all for now!\n"; diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index ae69f52b4..787407019 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -6,6 +6,9 @@ #include #include "apps/openmw/mwrender/mwscene.hpp" +#include "components/misc/tsdeque.hpp" +#include "components/commandserver/server.hpp" + namespace MWRender { @@ -18,6 +21,8 @@ namespace OMW class Engine { + enum { kCommandServerPort = 27917 }; + boost::filesystem::path mDataDir; Render::OgreRenderer mOgre; std::string mCellName; @@ -26,6 +31,9 @@ namespace OMW bool mEnableSky; MWRender::SkyManager* mpSkyManager; + TsDeque mCommands; + std::auto_ptr mspCommandServer; + // not implemented Engine (const Engine&); Engine& operator= (const Engine&); @@ -55,6 +63,9 @@ namespace OMW /// Enables rendering of the sky (off by default). void enableSky (bool bEnable); + /// Process pending commands + void processCommands(); + /// Initialise and enter main loop. void go(); }; diff --git a/components/commandserver/server.cpp b/components/commandserver/server.cpp new file mode 100755 index 000000000..94ce03f97 --- /dev/null +++ b/components/commandserver/server.cpp @@ -0,0 +1,177 @@ + +#include "server.hpp" + +using boost::asio::ip::tcp; + +// +// Namespace for containing implementation details that the +// rest of OpenMW doesn't need to worry about +// +namespace OMW { namespace CommandServer { namespace Detail { + + /// + /// Tracks an active connection to the CommandServer + /// + class Connection + { + public: + Connection (boost::asio::io_service& io_service, Server* pServer); + + void start(); + void stop(); + tcp::socket& socket(); + + protected: + void handle (); + + tcp::socket mSocket; + Server* mpServer; + boost::thread* mpThread; + }; + + Connection::Connection (boost::asio::io_service& io_service, Server* pServer) + : mSocket (io_service) + , mpServer (pServer) + { + } + + void Connection::start() + { + mpThread = new boost::thread(boost::bind(&Connection::handle, this)); + } + + /// + /// Stops and disconnects the connection + /// + void Connection::stop() + { + mSocket.close(); + mpThread->join(); + } + + tcp::socket& Connection::socket() + { + return mSocket; + } + + void Connection::handle () + { + bool bDone = false; + while (!bDone) + { + struct Header + { + char magic[4]; + size_t dataLength; + } header; + + // Read the header + boost::system::error_code error; + mSocket.read_some(boost::asio::buffer(&header, sizeof(Header)), error); + + if (error != boost::asio::error::eof) + { + if (strncmp(header.magic, "OMW0", 4) == 0) + { + std::vector msg; + msg.resize(header.dataLength); + + boost::system::error_code error; + mSocket.read_some(boost::asio::buffer(&msg[0], header.dataLength), error); + if (!error) + mpServer->postMessage( &msg[0] ); + else + bDone = true; + } + else + throw std::exception("Unexpected header!"); + } + else + bDone = true; + } + mpServer->removeConnection(this); + } + +}}} + +namespace OMW { namespace CommandServer { + + using namespace Detail; + + Server::Server (Deque* pDeque, const int port) + : mAcceptor (mIOService, tcp::endpoint(tcp::v4(), port)) + , mpCommands (pDeque) + , mbStopping (false) + { + } + + + void Server::start() + { + mIOService.run(); + mpThread = new boost::thread(boost::bind(&Server::threadMain, this)); + } + + void Server::stop() + { + // (1) Stop accepting new connections + // (2) Wait for the listener thread to finish + mAcceptor.close(); + mpThread->join(); + + // Now that no new connections are possible, close any existing + // open connections + { + boost::mutex::scoped_lock lock(mConnectionsMutex); + mbStopping = true; + for (ConnectionSet::iterator it = mConnections.begin(); + it != mConnections.end(); + ++it) + { + (*it)->stop(); + } + } + } + + void Server::removeConnection (Connection* ptr) + { + // If the server is shutting down (rather the client closing the + // connection), don't remove the connection from the list: that + // would corrupt the iterator the server is using to shutdown all + // clients. + if (!mbStopping) + { + boost::mutex::scoped_lock lock(mConnectionsMutex); + std::set::iterator it = mConnections.find(ptr); + if (it != mConnections.end()) + mConnections.erase(it); + } + delete ptr; + } + + void Server::postMessage (const char* s) + { + mpCommands->push_back(s); + } + + void Server::threadMain() + { + // Loop until accept() fails, which will cause the break statement to be hit + while (true) + { + std::auto_ptr spConnection(new Connection(mAcceptor.io_service(), this)); + boost::system::error_code ec; + mAcceptor.accept(spConnection->socket(), ec); + if (!ec) + { + boost::mutex::scoped_lock lock(mConnectionsMutex); + mConnections.insert(spConnection.get()); + spConnection->start(); + spConnection.release(); + } + else + break; + } + } + +}} diff --git a/components/commandserver/server.hpp b/components/commandserver/server.hpp new file mode 100755 index 000000000..8a2a74d1d --- /dev/null +++ b/components/commandserver/server.hpp @@ -0,0 +1,62 @@ +#ifndef CONSOLESERVER_H +#define CONSOLESERVER_H + +#include +#include +#include +#include +#include +#include + +#include "components/misc/tsdeque.hpp" + +namespace OMW { namespace CommandServer +{ + // + // Forward Declarations + // + namespace Detail + { + class Connection; + } + + // + // Server that opens a port to listen for string commands which will be + // put into the deque provided in the Server constructor. + // + class Server + { + public: + typedef TsDeque Deque; + + Server (Deque* pDeque, const int port); + + void start(); + void stop(); + + protected: + friend class Detail::Connection; + typedef std::set ConnectionSet; + + void removeConnection (Detail::Connection* ptr); + void postMessage (const char* s); + + void threadMain(); + + // Objects used to set up the listening server + boost::asio::io_service mIOService; + boost::asio::ip::tcp::acceptor mAcceptor; + boost::thread* mpThread; + bool mbStopping; + + // Track active connections + ConnectionSet mConnections; + mutable boost::mutex mConnectionsMutex; + + // Pointer to output queue in which to put received strings + Deque* mpCommands; + }; + +}} + +#endif // CONSOLESERVER_H diff --git a/components/misc/tsdeque.hpp b/components/misc/tsdeque.hpp new file mode 100755 index 000000000..db14428bc --- /dev/null +++ b/components/misc/tsdeque.hpp @@ -0,0 +1,34 @@ +#ifndef TSDEQUE_H +#define TSDEQUE_H + +#include + +template +class TsDeque +{ +public: + void push_back (const T& t) + { + boost::mutex::scoped_lock lock(mMutex); + mDeque.push_back(t); + } + + bool pop_front (T& t) + { + boost::mutex::scoped_lock lock(mMutex); + if (!mDeque.empty()) + { + t = mDeque.front(); + mDeque.pop_front(); + return true; + } + else + return false; + } + +protected: + std::deque mDeque; + mutable boost::mutex mMutex; +}; + +#endif // TSDEQUE_H From d44f322b8adaf14f292e4c0bc0a9329a9ed2654f Mon Sep 17 00:00:00 2001 From: athile Date: Thu, 1 Jul 2010 12:09:05 -0700 Subject: [PATCH 2/6] Add nesting to Visual Studio source groupings. --- CMakeLists.txt | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8289d7c84..fa3ad115d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ set(GAME set(GAME_HEADER apps/openmw/mwinput/inputmanager.hpp apps/openmw/engine.hpp) -source_group(game FILES ${GAME} ${GAME_HEADER}) +source_group(apps\\openmw FILES ${GAME} ${GAME_HEADER}) set(GAMEREND apps/openmw/mwrender/mwscene.cpp @@ -27,12 +27,12 @@ set(GAMEREND_HEADER apps/openmw/mwrender/interior.hpp apps/openmw/mwrender/playerpos.hpp apps/openmw/mwrender/sky.hpp) -source_group(game_renderer FILES ${GAMEREND} ${GAMEREND_HEADER}) +source_group(apps\\openmw\\mwrender FILES ${GAMEREND} ${GAMEREND_HEADER}) # set(GAMEINPUT) set(GAMEINPUT_HEADER apps/openmw/mwinput/inputmanager.hpp) -source_group(game_input FILES ${GAMEINPUT} ${GAMEINPUT_HEADER}) +source_group(apps\\openmw\\mwinput FILES ${GAMEINPUT} ${GAMEINPUT_HEADER}) set(APPS ${GAME} ${GAMEREND} ${GAMEINPUT}) set(APPS_HEADER ${GAME_HEADER} ${GAMEREND_HEADER} ${GAMEINPUT_HEADER}) @@ -45,7 +45,7 @@ set(BSA set(BSA_HEADER components/bsa/bsa_archive.hpp components/bsa/bsa_file.hpp) -source_group(bsa FILES ${BSA} ${BSA_HEADER}) +source_group(components\\bsa FILES ${BSA} ${BSA_HEADER}) set(NIF components/nif/nif_file.cpp) @@ -61,13 +61,13 @@ set(NIF_HEADER components/nif/data.hpp components/nif/nif_file.hpp components/nif/property.hpp) -source_group(nif FILES ${NIF} ${NIF_HEADER}) +source_group(components\\nif FILES ${NIF} ${NIF_HEADER}) set(NIFOGRE components/nifogre/ogre_nif_loader.cpp) set(NIFOGRE_HEADER components/nifogre/ogre_nif_loader.hpp) -source_group(nifogre FILES ${NIFOGRE} ${NIFOGRE_HEADER}) +source_group(components\\nifogre FILES ${NIFOGRE} ${NIFOGRE_HEADER}) set(ESM_STORE components/esm_store/store.cpp @@ -76,16 +76,16 @@ set(ESM_STORE_HEADER components/esm_store/cell_store.hpp components/esm_store/reclists.hpp components/esm_store/store.hpp) -source_group(esm_store FILES ${ESM_STORE} ${ESM_STORE_HEADER}) +source_group(components\\esm_store FILES ${ESM_STORE} ${ESM_STORE_HEADER}) file(GLOB ESM_HEADER components/esm/*.hpp) -source_group(esm_header FILES ${ESM_HEADER}) +source_group(components\\esm FILES ${ESM_HEADER}) set(OGRE components/engine/ogre/renderer.cpp) set(OGRE_HEADER components/engine/ogre/renderer.hpp) -source_group(ogre FILES ${OGRE} ${OGRE_HEADER}) +source_group(components\\engine\\ogre FILES ${OGRE} ${OGRE_HEADER}) set(INPUT components/engine/input/oismanager.cpp) @@ -96,12 +96,12 @@ set(INPUT_HEADER components/engine/input/dispatch_map.hpp components/engine/input/dispatcher.hpp components/engine/input/poller.hpp) -source_group(input FILES ${INPUT} ${INPUT_HEADER}) +source_group(components\\engine\\input FILES ${INPUT} ${INPUT_HEADER}) set(COMMANDSERVER components/commandserver/server.hpp components/commandserver/server.cpp) -source_group(commandserver FILES ${COMMANDSERVER}) +source_group(components\\commandserver FILES ${COMMANDSERVER}) set(MISC components/misc/stringops.cpp @@ -111,15 +111,15 @@ set(MISC_HEADER components/misc/slice_array.hpp components/misc/stringops.hpp components/misc/tsdeque.hpp) -source_group(misc FILES ${MISC} ${MISC_HEADER}) +source_group(components\\misc FILES ${MISC} ${MISC_HEADER}) file(GLOB COMPILER components/compiler/*.cpp) file(GLOB COMPILER_HEADER components/compiler/*.hpp) -source_group(compiler FILES ${COMPILER} ${COMPILER_HEADER}) +source_group(components\\compiler FILES ${COMPILER} ${COMPILER_HEADER}) file(GLOB INTERPRETER components/interpreter/*.cpp) file(GLOB INTERPRETER_HEADER components/interpreter/*.hpp) -source_group(interpreter FILES ${INTERPRETER} ${INTERPRETER_HEADER}) +source_group(components\\interpreter FILES ${INTERPRETER} ${INTERPRETER_HEADER}) set(COMPONENTS ${BSA} ${NIF} ${NIFOGRE} ${ESM_STORE} ${OGRE} ${INPUT} ${MISC} ${COMMANDSERVER} @@ -132,7 +132,7 @@ set(COMPONENTS_HEADER ${BSA_HEADER} ${NIF_HEADER} ${NIFOGRE_HEADER} ${ESM_STORE_ # source directory: libs set(MANGLE_VFS libs/mangle/vfs/servers/ogre_vfs.cpp) -source_group(mangle_vfs FILES ${MANGLE_VFS}) +source_group(libs\\mangle_vfs FILES ${MANGLE_VFS}) set(OPENMW_LIBS ${MANGLE_VFS}) set(OPENMW_LIBS_HEADER) From 7cc27d9b663adad06b6ac3a16a1fd2037aefd780 Mon Sep 17 00:00:00 2001 From: athile Date: Thu, 1 Jul 2010 15:50:24 -0700 Subject: [PATCH 3/6] Add command output string and client/server response. --- CMakeLists.txt | 1 + apps/clientconsole/client.cpp | 62 ++++++++++++++++++----- apps/openmw/engine.cpp | 14 ++++-- apps/openmw/engine.hpp | 3 +- components/commandserver/command.hpp | 19 +++++++ components/commandserver/server.cpp | 48 +++++++++++++----- components/commandserver/server.hpp | 11 +++-- components/misc/tsdeque.hpp | 74 ++++++++++++++++++---------- 8 files changed, 172 insertions(+), 60 deletions(-) create mode 100755 components/commandserver/command.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fa3ad115d..ae17b7930 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,7 @@ set(INPUT_HEADER source_group(components\\engine\\input FILES ${INPUT} ${INPUT_HEADER}) set(COMMANDSERVER + components/commandserver/command.hpp components/commandserver/server.hpp components/commandserver/server.cpp) source_group(components\\commandserver FILES ${COMMANDSERVER}) diff --git a/apps/clientconsole/client.cpp b/apps/clientconsole/client.cpp index dac95ce99..119f8c554 100755 --- a/apps/clientconsole/client.cpp +++ b/apps/clientconsole/client.cpp @@ -9,6 +9,12 @@ using boost::asio::ip::tcp; class Client { protected: + struct Header + { + char magic[4]; + boost::uint32_t dataLength; + }; + boost::asio::io_service mIOService; tcp::socket* mpSocket; @@ -39,11 +45,6 @@ public: bool send (const char* msg) { - struct Header - { - char magic[4]; - boost::uint32_t dataLength; - }; const size_t slen = strlen(msg); const size_t plen = sizeof(Header) + slen + 1; @@ -61,6 +62,33 @@ public: return !ec; } + + bool receive (std::string& reply) + { + Header header; + boost::system::error_code error; + mpSocket->read_some(boost::asio::buffer(&header, sizeof(Header)), error); + + if (error != boost::asio::error::eof) + { + if (strncmp(header.magic, "OMW0", 4) == 0) + { + std::vector msg; + msg.resize(header.dataLength); + + boost::system::error_code error; + mpSocket->read_some(boost::asio::buffer(&msg[0], header.dataLength), error); + if (!error) + { + reply = &msg[0]; + return true; + } + } + else + throw std::exception("Unexpected header!"); + } + return false; + } }; @@ -79,14 +107,26 @@ int main(int argc, char* argv[]) bool bDone = false; do { - std::cout << "> "; - char buffer[1024]; - gets(buffer); + std::cout << "Client> "; + std::string buffer; + std::getline(std::cin, buffer); - if (std::string(buffer) != "quit") - bDone = !client.send(buffer); - else + if (buffer == "quit") bDone = true; + else + { + if (client.send(buffer.c_str())) + { + std::string reply; + if (client.receive(reply)) + std::cout << "Server: " << reply << std::endl; + else + bDone = true; + } + else + bDone = true; + } + } while (!bDone); client.disconnect(); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 601df6354..12cb67cca 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -32,7 +32,8 @@ OMW::Engine::Engine() : mEnableSky (false) , mpSkyManager (NULL) { - mspCommandServer.reset(new OMW::CommandServer::Server(&mCommands, kCommandServerPort)); + mspCommandServer.reset( + new OMW::CommandServer::Server(&mCommandQueue, kCommandServerPort)); } // Load all BSA files in data directory. @@ -100,11 +101,16 @@ void OMW::Engine::enableSky (bool bEnable) void OMW::Engine::processCommands() { - std::string msg; - while (mCommands.pop_front(msg)) + Command cmd; + while (mCommandQueue.try_pop_front(cmd)) { ///\todo Add actual processing of the received command strings - std::cout << "Command: '" << msg << "'" << std::endl; + std::cout << "Command: '" << cmd.mCommand << "'" << std::endl; + + ///\todo Replace with real output. For now, echo back the string in uppercase + std::string reply(cmd.mCommand); + std::transform(reply.begin(), reply.end(), reply.begin(), toupper); + cmd.mReplyFunction(reply); } } diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 787407019..6ec9cadf5 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -8,6 +8,7 @@ #include "apps/openmw/mwrender/mwscene.hpp" #include "components/misc/tsdeque.hpp" #include "components/commandserver/server.hpp" +#include "components/commandserver/command.hpp" namespace MWRender @@ -31,7 +32,7 @@ namespace OMW bool mEnableSky; MWRender::SkyManager* mpSkyManager; - TsDeque mCommands; + TsDeque mCommandQueue; std::auto_ptr mspCommandServer; // not implemented diff --git a/components/commandserver/command.hpp b/components/commandserver/command.hpp new file mode 100755 index 000000000..b221d512b --- /dev/null +++ b/components/commandserver/command.hpp @@ -0,0 +1,19 @@ +#ifndef COMMANDSERVER_COMMAND_HPP +#define COMMANDSERVER_COMMAND_HPP + +namespace OMW +{ + /// + /// A Command is currently defined as a string input that, when processed, + /// will generate a string output. The string output is passed to the + /// mReplyFunction as soon as the command has been processed. + /// + class Command + { + public: + std::string mCommand; + boost::function1 mReplyFunction; + }; +} + +#endif COMMANDSERVER_COMMAND_HPP diff --git a/components/commandserver/server.cpp b/components/commandserver/server.cpp index 94ce03f97..7b9198e0b 100755 --- a/components/commandserver/server.cpp +++ b/components/commandserver/server.cpp @@ -9,6 +9,12 @@ using boost::asio::ip::tcp; // namespace OMW { namespace CommandServer { namespace Detail { + struct Header + { + char magic[4]; + size_t dataLength; + } header; + /// /// Tracks an active connection to the CommandServer /// @@ -19,7 +25,9 @@ namespace OMW { namespace CommandServer { namespace Detail { void start(); void stop(); + tcp::socket& socket(); + void reply (std::string s); protected: void handle (); @@ -53,18 +61,29 @@ namespace OMW { namespace CommandServer { namespace Detail { { return mSocket; } + + void Connection::reply (std::string reply) + { + const size_t plen = sizeof(Header) + reply.length() + 1; + + std::vector packet(plen); + Header* pHeader = reinterpret_cast(&packet[0]); + strncpy(pHeader->magic, "OMW0", 4); + pHeader->dataLength = reply.length() + 1; // Include the null terminator + strncpy(&packet[8], reply.c_str(), pHeader->dataLength); + + boost::system::error_code ec; + boost::asio::write(mSocket, boost::asio::buffer(packet), + boost::asio::transfer_all(), ec); + if (ec) + std::cout << "Error: " << ec.message() << std::endl; + } void Connection::handle () { bool bDone = false; while (!bDone) { - struct Header - { - char magic[4]; - size_t dataLength; - } header; - // Read the header boost::system::error_code error; mSocket.read_some(boost::asio::buffer(&header, sizeof(Header)), error); @@ -79,7 +98,7 @@ namespace OMW { namespace CommandServer { namespace Detail { boost::system::error_code error; mSocket.read_some(boost::asio::buffer(&msg[0], header.dataLength), error); if (!error) - mpServer->postMessage( &msg[0] ); + mpServer->postCommand(this, &msg[0]); else bDone = true; } @@ -98,10 +117,10 @@ namespace OMW { namespace CommandServer { using namespace Detail; - Server::Server (Deque* pDeque, const int port) - : mAcceptor (mIOService, tcp::endpoint(tcp::v4(), port)) - , mpCommands (pDeque) - , mbStopping (false) + Server::Server (Deque* pCommandQueue, const int port) + : mAcceptor (mIOService, tcp::endpoint(tcp::v4(), port)) + , mpCommandQueue (pCommandQueue) + , mbStopping (false) { } @@ -149,9 +168,12 @@ namespace OMW { namespace CommandServer { delete ptr; } - void Server::postMessage (const char* s) + void Server::postCommand (Connection* pConnection, const char* s) { - mpCommands->push_back(s); + Command cmd; + cmd.mCommand = s; + cmd.mReplyFunction = std::bind1st(std::mem_fun(&Connection::reply), pConnection); + mpCommandQueue->push_back(cmd); } void Server::threadMain() diff --git a/components/commandserver/server.hpp b/components/commandserver/server.hpp index 8a2a74d1d..b53e0f23a 100755 --- a/components/commandserver/server.hpp +++ b/components/commandserver/server.hpp @@ -9,6 +9,7 @@ #include #include "components/misc/tsdeque.hpp" +#include "components/commandserver/command.hpp" namespace OMW { namespace CommandServer { @@ -27,9 +28,9 @@ namespace OMW { namespace CommandServer class Server { public: - typedef TsDeque Deque; + typedef TsDeque Deque; - Server (Deque* pDeque, const int port); + Server (Deque* pCommandQueue, const int port); void start(); void stop(); @@ -39,7 +40,7 @@ namespace OMW { namespace CommandServer typedef std::set ConnectionSet; void removeConnection (Detail::Connection* ptr); - void postMessage (const char* s); + void postCommand (Detail::Connection*, const char* s); void threadMain(); @@ -53,8 +54,8 @@ namespace OMW { namespace CommandServer ConnectionSet mConnections; mutable boost::mutex mConnectionsMutex; - // Pointer to output queue in which to put received strings - Deque* mpCommands; + // Pointer to command queue + Deque* mpCommandQueue; }; }} diff --git a/components/misc/tsdeque.hpp b/components/misc/tsdeque.hpp index db14428bc..d63a132ef 100755 --- a/components/misc/tsdeque.hpp +++ b/components/misc/tsdeque.hpp @@ -3,32 +3,54 @@ #include -template -class TsDeque -{ -public: - void push_back (const T& t) - { - boost::mutex::scoped_lock lock(mMutex); - mDeque.push_back(t); - } - - bool pop_front (T& t) - { - boost::mutex::scoped_lock lock(mMutex); - if (!mDeque.empty()) - { - t = mDeque.front(); - mDeque.pop_front(); - return true; - } - else - return false; - } - -protected: - std::deque mDeque; - mutable boost::mutex mMutex; +// +// Adapted from http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html +// +template +class TsDeque +{ +private: + std::deque the_queue; + mutable boost::mutex the_mutex; + boost::condition_variable the_condition_variable; + +public: + void push_back(Data const& data) + { + boost::mutex::scoped_lock lock(the_mutex); + the_queue.push_back(data); + lock.unlock(); + the_condition_variable.notify_one(); + } + + bool empty() const + { + boost::mutex::scoped_lock lock(the_mutex); + return the_queue.empty(); + } + + bool try_pop_front(Data& popped_value) + { + boost::mutex::scoped_lock lock(the_mutex); + if(the_queue.empty()) + return false; + + popped_value=the_queue.front(); + the_queue.pop_front(); + return true; + } + + void wait_and_pop_front(Data& popped_value) + { + boost::mutex::scoped_lock lock(the_mutex); + while(the_queue.empty()) + { + the_condition_variable.wait(lock); + } + + popped_value=the_queue.front(); + the_queue.pop_front(); + } }; #endif // TSDEQUE_H From 9b2fa58b88524f4701b946761c6812d7e228b838 Mon Sep 17 00:00:00 2001 From: athile Date: Thu, 1 Jul 2010 16:29:22 -0700 Subject: [PATCH 4/6] Add separate CMakeLists.txt files for mwcompiler and mwinterpreter --- CMakeLists.txt | 12 ++---------- apps/mwcompiler/CMakeLists.txt | 9 +++++++++ apps/mwinterpreter/CMakeLists.txt | 9 +++++++++ 3 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 apps/mwcompiler/CMakeLists.txt create mode 100644 apps/mwinterpreter/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index ae17b7930..9e725d435 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,19 +228,11 @@ endif (APPLE) # Tools option(BUILD_MWCOMPILER "build standalone Morrowind script compiler" ON) - if (BUILD_MWCOMPILER) -set(TOOLS_MWCOMPILER ${COMPILER} apps/mwcompiler/main.cpp apps/mwcompiler/context.cpp - apps/mwcompiler/context.hpp) - -add_executable(mwcompiler ${TOOLS_MWCOMPILER}) + add_subdirectory( apps/mwcompiler ) endif() option(BUILD_MWINTERPRETER "build standalone Morrowind script code interpreter" ON) - if (BUILD_MWINTERPRETER) -set(TOOLS_MWINTERPRETER ${INTERPRETER} apps/mwinterpreter/main.cpp - apps/mwinterpreter/context.cpp apps/mwinterpreter/context.hpp) - -add_executable(mwinterpreter ${TOOLS_MWINTERPRETER}) + add_subdirectory( apps/mwinterpreter ) endif() diff --git a/apps/mwcompiler/CMakeLists.txt b/apps/mwcompiler/CMakeLists.txt new file mode 100644 index 000000000..b2243f255 --- /dev/null +++ b/apps/mwcompiler/CMakeLists.txt @@ -0,0 +1,9 @@ +project(MWCompiler) + +set(TOOLS_MWCOMPILER ${COMPILER} + main.cpp + context.cpp + context.hpp) + +add_executable(mwcompiler ${TOOLS_MWCOMPILER}) + diff --git a/apps/mwinterpreter/CMakeLists.txt b/apps/mwinterpreter/CMakeLists.txt new file mode 100644 index 000000000..078925657 --- /dev/null +++ b/apps/mwinterpreter/CMakeLists.txt @@ -0,0 +1,9 @@ +project(MWInterpreter) + +set(TOOLS_MWINTERPRETER + ${INTERPRETER} + main.cpp + context.cpp + context.hpp) + +add_executable(mwinterpreter ${TOOLS_MWINTERPRETER}) From 5825af45c3e6f38831f76bad0de21fd3d0f9600b Mon Sep 17 00:00:00 2001 From: athile Date: Thu, 1 Jul 2010 23:52:20 -0700 Subject: [PATCH 5/6] Visual Studio 2010 compile fixes --- components/compiler/exprparser.cpp | 1 + components/compiler/parser.cpp | 1 + components/compiler/scanner.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 658e9334a..2d932bede 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "generator.hpp" #include "scanner.hpp" diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp index 2c7686589..ff5bf33ee 100644 --- a/components/compiler/parser.cpp +++ b/components/compiler/parser.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "errorhandler.hpp" #include "exception.hpp" diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index e4869cba5..f0001fe85 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "exception.hpp" #include "errorhandler.hpp" From 450542b4b9a7cf6987c192abbf7e2cc44da286d2 Mon Sep 17 00:00:00 2001 From: athile Date: Fri, 2 Jul 2010 00:05:57 -0700 Subject: [PATCH 6/6] Fix Windows line feeds and chdmod --- apps/clientconsole/CMakeLists.txt | 4 +- apps/clientconsole/client.cpp | 2 +- components/commandserver/command.hpp | 38 ++++++------ components/misc/tsdeque.hpp | 90 ++++++++++++++-------------- 4 files changed, 67 insertions(+), 67 deletions(-) diff --git a/apps/clientconsole/CMakeLists.txt b/apps/clientconsole/CMakeLists.txt index e6885841f..702fbbf8a 100755 --- a/apps/clientconsole/CMakeLists.txt +++ b/apps/clientconsole/CMakeLists.txt @@ -1,2 +1,2 @@ -project(clientconsole) -add_executable(clientconsole client.cpp) +project(clientconsole) +add_executable(clientconsole client.cpp) diff --git a/apps/clientconsole/client.cpp b/apps/clientconsole/client.cpp index 119f8c554..d7d93e030 100755 --- a/apps/clientconsole/client.cpp +++ b/apps/clientconsole/client.cpp @@ -108,7 +108,7 @@ int main(int argc, char* argv[]) do { std::cout << "Client> "; - std::string buffer; + std::string buffer; std::getline(std::cin, buffer); if (buffer == "quit") diff --git a/components/commandserver/command.hpp b/components/commandserver/command.hpp index b221d512b..128b87697 100755 --- a/components/commandserver/command.hpp +++ b/components/commandserver/command.hpp @@ -1,19 +1,19 @@ -#ifndef COMMANDSERVER_COMMAND_HPP -#define COMMANDSERVER_COMMAND_HPP - -namespace OMW -{ - /// - /// A Command is currently defined as a string input that, when processed, - /// will generate a string output. The string output is passed to the - /// mReplyFunction as soon as the command has been processed. - /// - class Command - { - public: - std::string mCommand; - boost::function1 mReplyFunction; - }; -} - -#endif COMMANDSERVER_COMMAND_HPP +#ifndef COMMANDSERVER_COMMAND_HPP +#define COMMANDSERVER_COMMAND_HPP + +namespace OMW +{ + /// + /// A Command is currently defined as a string input that, when processed, + /// will generate a string output. The string output is passed to the + /// mReplyFunction as soon as the command has been processed. + /// + class Command + { + public: + std::string mCommand; + boost::function1 mReplyFunction; + }; +} + +#endif COMMANDSERVER_COMMAND_HPP diff --git a/components/misc/tsdeque.hpp b/components/misc/tsdeque.hpp index d63a132ef..88b2ce001 100755 --- a/components/misc/tsdeque.hpp +++ b/components/misc/tsdeque.hpp @@ -6,51 +6,51 @@ // // Adapted from http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html // -template -class TsDeque -{ -private: - std::deque the_queue; - mutable boost::mutex the_mutex; - boost::condition_variable the_condition_variable; - -public: - void push_back(Data const& data) - { - boost::mutex::scoped_lock lock(the_mutex); - the_queue.push_back(data); - lock.unlock(); - the_condition_variable.notify_one(); - } - - bool empty() const - { - boost::mutex::scoped_lock lock(the_mutex); - return the_queue.empty(); - } - - bool try_pop_front(Data& popped_value) - { - boost::mutex::scoped_lock lock(the_mutex); - if(the_queue.empty()) - return false; - - popped_value=the_queue.front(); - the_queue.pop_front(); - return true; - } - - void wait_and_pop_front(Data& popped_value) - { - boost::mutex::scoped_lock lock(the_mutex); - while(the_queue.empty()) - { - the_condition_variable.wait(lock); - } - - popped_value=the_queue.front(); - the_queue.pop_front(); - } +template +class TsDeque +{ +private: + std::deque the_queue; + mutable boost::mutex the_mutex; + boost::condition_variable the_condition_variable; + +public: + void push_back(Data const& data) + { + boost::mutex::scoped_lock lock(the_mutex); + the_queue.push_back(data); + lock.unlock(); + the_condition_variable.notify_one(); + } + + bool empty() const + { + boost::mutex::scoped_lock lock(the_mutex); + return the_queue.empty(); + } + + bool try_pop_front(Data& popped_value) + { + boost::mutex::scoped_lock lock(the_mutex); + if(the_queue.empty()) + return false; + + popped_value=the_queue.front(); + the_queue.pop_front(); + return true; + } + + void wait_and_pop_front(Data& popped_value) + { + boost::mutex::scoped_lock lock(the_mutex); + while(the_queue.empty()) + { + the_condition_variable.wait(lock); + } + + popped_value=the_queue.front(); + the_queue.pop_front(); + } }; #endif // TSDEQUE_H