diff --git a/include/echo/Network/NetworkExecutionModel.h b/include/echo/Network/NetworkExecutionModel.h --- a/include/echo/Network/NetworkExecutionModel.h +++ b/include/echo/Network/NetworkExecutionModel.h @@ -1,78 +1,96 @@ #ifndef _ECHO_NETWORKEXECUTIONMODEL_H_ #define _ECHO_NETWORKEXECUTIONMODEL_H_ #include #include #include #include #include +#include #include +#include +#include +#include namespace Echo { class Kernel; /** * A NetworkExecutionModel is designed to update a kernel on network events. * While the NetworkExecutionModel is waiting for network events it will either behave in one of two ways: * * 1. If configured to use command processing (default), events it will be waiting for user input on std::in. * Functions can be registered with the object as named commands to allow the kernel to receive input. * The execution model needs to be set in order for a Kernel to execute. In the case of Application * the execution model can be set before Initialise() is called which then overrides the default * execution model. * - * 2. If configured to not use command processing, the model will wait on a condition variable until a network - * event occurs. Unfortunately when using mode Connections won't attempt to auto reconnect until a network - * event occurs. + * 2. If configured to not use command processing, the model will sleep the thread until the update frequency + * time has passed then the kernel will update a Frame. This is important for allowing connections to + * auto reconnect if they are configured to do so. One side effect of this is that Connection auto reconnect + * times may be extended to up to just short of twice what is configured. It also means that reconnect times + * won't be less than the update frequency. * * To set up a NetworkExecutionModel: * - Create a shared pointer pointing to an instance of a NetworkExecutionModel. * - If you're using Application set the execution model with SetExecutionModel() * - If you're using Application initialise the network interface if it has not already been done, * if you're not using Application then create a NetworkManager object and initialise it. * - Add the NetworkExecutionModel to the NetworkManager's event listener list. * * Example code: * * InitialiseNetworkManager(); * shared_ptr executionModel(new NetworkExecutionModel(*this)); * SetExecutionModel(executionModel); * GetNetworkManager()->AddNetworkEventListener(executionModel); * //Initialise application as usual. * Application::Initialise("YourApplication","resources/YourApplication.config"); */ class NetworkExecutionModel : public ExecutionModel, public NetworkEventListener { public: typedef function CommandProcessorFunction; static boost::arg<1> CommandPlaceholder; typedef function DefaultCommandProcessorFunction; static boost::arg<2> ParametersPlaceholder; NetworkExecutionModel(Kernel& kernel); ~NetworkExecutionModel(); void OnNetworkEvent(NetworkEventType eventType); bool SupportsModel(Models::ModelEnum model) override; bool ProcessEvents(f32) override; bool EnterSystemEventManager() override; bool SendUpdateRequest() override; void SetCommandProcessor(const std::string& command, CommandProcessorFunction func); void SetDefaultCommandProcessorFunction(DefaultCommandProcessorFunction func); - void SetUseCommandProcessing(bool useCommandProcessing); + void SetUseCommandProcessing(bool useCommandProcessing); + + /** + * Set the update frequency regardless of network events. + * This method will adjust the provided Kernel's maximum update time to the update frequency. + */ + void SetUpdateFrequency(Seconds updateFrequency); private: void DefaultCommandProcessor(); + void SetupAndStartUpdateTimer(); + void OnUpdateTimer(); bool mUseCommandProcessing; + Seconds mUpdateFrequency; Kernel& mKernel; bool mUpdateFrame; std::mutex mUpdateMutex; std::condition_variable mUpdateCondition; + std::thread mServiceThread; + boost::asio::io_service mIOService; + shared_ptr mUpdateTimer; typedef std::map< std::string, CommandProcessorFunction > ProcessorMap; ProcessorMap mProcessors; DefaultCommandProcessorFunction mDefaultCommandProcessorFunction; }; } #endif diff --git a/src/Network/NetworkExecutionModel.cpp b/src/Network/NetworkExecutionModel.cpp --- a/src/Network/NetworkExecutionModel.cpp +++ b/src/Network/NetworkExecutionModel.cpp @@ -1,115 +1,150 @@ #include #include #include #include #include #include +#include +#include namespace Echo { NetworkExecutionModel::NetworkExecutionModel(Kernel& kernel) : ExecutionModel(ExecutionModel::Models::CONTROLLER), mKernel(kernel), mUseCommandProcessing(true), - mUpdateFrame(false) + mUpdateFrame(false), + mUpdateFrequency(5) { SetDefaultCommandProcessorFunction(bind(&NetworkExecutionModel::DefaultCommandProcessor,this)); } NetworkExecutionModel::~NetworkExecutionModel() { } void NetworkExecutionModel::SetUseCommandProcessing(bool useCommandProcessing) { mUseCommandProcessing = useCommandProcessing; } + void NetworkExecutionModel::SetUpdateFrequency(Seconds updateFrequency) + { + mUpdateFrequency = updateFrequency; + mKernel.GetFrameRateLimiter().SetMaxFrameTime(updateFrequency); + } + void NetworkExecutionModel::OnNetworkEvent(NetworkEventType eventType) { mUpdateFrame = true; mUpdateCondition.notify_all(); } bool NetworkExecutionModel::SupportsModel(Models::ModelEnum model) { switch (model) { case Models::CONTROLLER: return true; case Models::EXTERNAL_CONTROLLER: case Models::COOPERATE: case Models::NONE: return false; } return false; } bool NetworkExecutionModel::ProcessEvents(f32) { return false; } bool NetworkExecutionModel::EnterSystemEventManager() { if(mUseCommandProcessing) { std::string command; std::cout << "Type 'quit' to quit" << std::endl; while(command!="quit") { std::getline(std::cin,command); if(!std::cin.good()) { // The process might be launched with a detached stdin which means cin will be bad. // If that is the case we'll sleep and check back every 10 seconds (this is just arbitrary). Thread::Sleep(Seconds(10)); } size_t space = command.find_first_of(' '); std::string parameters; if(space!=std::string::npos) { parameters = command.substr(space+1); command = command.substr(0,space); } ProcessorMap::iterator it = mProcessors.find(command); if(it!=mProcessors.end()) { it->second(parameters); }else { mDefaultCommandProcessorFunction(command,parameters); } } }else { + SetupAndStartUpdateTimer(); + mServiceThread=std::thread([this]{ + mIOService.run(); + }); + bool kernelActive = true; while(kernelActive) { std::unique_lock lock(mUpdateMutex); mUpdateCondition.wait(lock, [this](){return mUpdateFrame;}); mUpdateFrame = false; kernelActive = mKernel.ProcessFrame(); } + + mIOService.stop(); + mServiceThread.join(); } return true; } bool NetworkExecutionModel::SendUpdateRequest() { return true; } void NetworkExecutionModel::SetCommandProcessor(const std::string& command, CommandProcessorFunction func) { mProcessors[command] = func; } void NetworkExecutionModel::SetDefaultCommandProcessorFunction(DefaultCommandProcessorFunction func) { mDefaultCommandProcessorFunction = func; } void NetworkExecutionModel::DefaultCommandProcessor() { } + + void NetworkExecutionModel::SetupAndStartUpdateTimer() + { + mUpdateTimer.reset(new boost::asio::deadline_timer(mIOService, boost::posix_time::seconds(mUpdateFrequency.count()))); + mUpdateTimer->async_wait(bind(&NetworkExecutionModel::OnUpdateTimer,this)); + } + + void NetworkExecutionModel::OnUpdateTimer() + { + if(mKernel.HasAtLeastOneTask()) + { + OnNetworkEvent(NetworkEventType::UNKNOWN); + ECHO_LOG_INFO("Timer trigger " << mUpdateFrequency); + mUpdateTimer->expires_at(mUpdateTimer->expires_at() + boost::posix_time::seconds(mUpdateFrequency.count())); + mUpdateTimer->async_wait(bind(&NetworkExecutionModel::OnUpdateTimer,this)); + } + } + }