diff --git a/simulation/halsim_ws_client/src/main/native/cpp/HALSimWS.cpp b/simulation/halsim_ws_client/src/main/native/cpp/HALSimWS.cpp new file mode 100644 index 0000000000..0b40a61196 --- /dev/null +++ b/simulation/halsim_ws_client/src/main/native/cpp/HALSimWS.cpp @@ -0,0 +1,175 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2020 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "HALSimWS.h" + +#include +#include +#include + +#include "HALSimWSClientConnection.h" + +static constexpr int kTcpConnectAttemptTimeout = 1000; + +namespace uv = wpi::uv; + +using namespace wpilibws; + +HALSimWS::HALSimWS(wpi::uv::Loop& loop, ProviderContainer& providers, + HALSimWSProviderSimDevices& simDevicesProvider) + : m_loop(loop), + m_providers(providers), + m_simDevicesProvider(simDevicesProvider) { + m_loop.error.connect([](uv::Error err) { + wpi::errs() << "HALSim WS Client libuv Error: " << err.str() << "\n"; + }); + + m_tcp_client = uv::Tcp::Create(m_loop); + m_exec = UvExecFunc::Create(m_loop); + if (m_exec) { + m_exec->wakeup.connect([](auto func) { func(); }); + } + m_connect_timer = uv::Timer::Create(m_loop); +} + +bool HALSimWS::Initialize() { + if (!m_tcp_client || !m_exec || !m_connect_timer) { + return false; + } + + const char* host = std::getenv("HALSIMWS_HOST"); + if (host != NULL) { + m_host = host; + } else { + m_host = "localhost"; + } + + const char* port = std::getenv("HALSIMWS_PORT"); + if (port != NULL) { + try { + m_port = std::stoi(port); + } catch (const std::invalid_argument& err) { + wpi::errs() << "Error decoding HALSIMWS_PORT (" << err.what() << ")\n"; + return false; + } + } else { + m_port = 8080; + } + + const char* uri = std::getenv("HALSIMWS_URI"); + if (uri != NULL) { + m_uri = uri; + } else { + m_uri = "/wpilibws"; + } + + return true; +} + +void HALSimWS::Start() { + m_tcp_client->SetNoDelay(true); + + // Hook up TCP client events + m_tcp_client->error.connect( + [this, socket = m_tcp_client.get()](wpi::uv::Error err) { + if (m_tcp_connected) { + m_tcp_connected = false; + m_connect_attempts = 0; + return; + } + + // If we weren't previously connected, attempt a reconnection + m_connect_timer->Start(uv::Timer::Time(kTcpConnectAttemptTimeout)); + }); + + m_tcp_client->closed.connect( + []() { wpi::outs() << "TCP connection closed\n"; }); + + // Set up the connection timer + wpi::outs() << "HALSimWS Initialized\n"; + wpi::outs() << "Will attempt to connect to ws://" << m_host << ":" << m_port + << m_uri << "\n"; + + // Set up the timer to attempt connection + m_connect_timer->timeout.connect([this] { AttemptConnect(); }); + + // Run the initial connect immediately + m_connect_timer->Start(uv::Timer::Time(0)); + m_connect_timer->Unreference(); +} + +void HALSimWS::AttemptConnect() { + m_connect_attempts++; + + wpi::outs() << "Connection Attempt " << m_connect_attempts << "\n"; + + struct sockaddr_in dest; + uv::NameToAddr(m_host, m_port, &dest); + + m_tcp_client->Connect(dest, [this, socket = m_tcp_client.get()]() { + m_tcp_connected = true; + auto wsConn = std::make_shared(shared_from_this(), + m_tcp_client); + + wsConn->Initialize(); + }); +} + +bool HALSimWS::RegisterWebsocket( + std::shared_ptr hws) { + if (m_hws.lock()) { + return false; + } + + m_hws = hws; + + m_simDevicesProvider.OnNetworkConnected(hws); + + m_providers.ForEach([hws](std::shared_ptr provider) { + provider->OnNetworkConnected(hws); + }); + + return true; +} + +void HALSimWS::CloseWebsocket( + std::shared_ptr hws) { + // Inform the providers that they need to cancel callbacks + m_simDevicesProvider.OnNetworkDisconnected(); + + m_providers.ForEach([](std::shared_ptr provider) { + provider->OnNetworkDisconnected(); + }); + + if (hws == m_hws.lock()) { + m_hws.reset(); + } +} + +void HALSimWS::OnNetValueChanged(const wpi::json& msg) { + // Look for "type" and "device" fields so that we can + // generate the key + + try { + auto& type = msg.at("type").get_ref(); + auto& device = msg.at("device").get_ref(); + + wpi::SmallString<64> key; + key.append(type); + if (!device.empty()) { + key.append("/"); + key.append(device); + } + + auto provider = m_providers.Get(key.str()); + if (provider) { + provider->OnNetValueChanged(msg.at("data")); + } + } catch (wpi::json::exception& e) { + wpi::errs() << "Error with incoming message: " << e.what() << "\n"; + } +} diff --git a/simulation/halsim_ws_client/src/main/native/cpp/HALSimWSClient.cpp b/simulation/halsim_ws_client/src/main/native/cpp/HALSimWSClient.cpp index 3f117d7776..f019e27890 100644 --- a/simulation/halsim_ws_client/src/main/native/cpp/HALSimWSClient.cpp +++ b/simulation/halsim_ws_client/src/main/native/cpp/HALSimWSClient.cpp @@ -7,190 +7,50 @@ #include "HALSimWSClient.h" -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "HALSimWSClientConnection.h" +using namespace wpilibws; -static constexpr int kTcpConnectAttemptTimeout = 1000; +bool HALSimWSClient::Initialize() { + bool result = true; + runner.ExecSync([&](wpi::uv::Loop& loop) { + simws = std::make_shared(loop, providers, simDevices); -namespace uv = wpi::uv; - -namespace wpilibws { - -std::shared_ptr HALSimWS::g_instance; - -bool HALSimWS::Initialize() { - const char* host = std::getenv("HALSIMWS_HOST"); - if (host != NULL) { - m_host = host; - } else { - m_host = "localhost"; - } - - const char* port = std::getenv("HALSIMWS_PORT"); - if (port != NULL) { - try { - m_port = std::stoi(port); - } catch (const std::invalid_argument& err) { - wpi::errs() << "Error decoding HALSIMWS_PORT (" << err.what() << ")\n"; - return false; - } - } else { - m_port = 8080; - } - - const char* uri = std::getenv("HALSIMWS_URI"); - if (uri != NULL) { - m_uri = uri; - } else { - m_uri = "/wpilibws"; - } - - m_loop = uv::Loop::Create(); - if (!m_loop) { - return false; - } - - m_loop->error.connect([](uv::Error err) { - wpi::errs() << "HALSim WS Client libuv Error: " << err.str() << "\n"; - }); - - m_tcp_client = uv::Tcp::Create(m_loop); - if (!m_tcp_client) { - return false; - } - - m_tcp_client->SetNoDelay(true); - - // Hook up TCP client events - m_tcp_client->error.connect( - [this, socket = m_tcp_client.get()](wpi::uv::Error err) { - if (m_tcp_connected) { - m_tcp_connected = false; - m_connect_attempts = 0; - m_loop->Stop(); - return; - } - - // If we weren't previously connected, attempt a reconnection - m_connect_timer->Start(uv::Timer::Time(kTcpConnectAttemptTimeout)); - }); - - m_tcp_client->closed.connect( - []() { wpi::outs() << "TCP connection closed\n"; }); - - // Set up the connection timer - m_connect_timer = uv::Timer::Create(m_loop); - if (!m_connect_timer) { - return false; - } - - wpi::outs() << "HALSimWS Initialized\n"; - wpi::outs() << "Will attempt to connect to: " << m_host << ":" << m_port - << " " << m_uri << "\n"; - - return true; -} - -void HALSimWS::Main(void* param) { - GetInstance()->MainLoop(); - SetInstance(nullptr); -} - -void HALSimWS::MainLoop() { - // Set up the timer to attempt connection - m_connect_timer->timeout.connect([this] { AttemptConnect(); }); - - // Run the initial connect immediately - m_connect_timer->Start(uv::Timer::Time(0)); - - m_loop->Run(); -} - -void HALSimWS::AttemptConnect() { - m_connect_attempts++; - - wpi::outs() << "Connection Attempt " << m_connect_attempts << "\n"; - - struct sockaddr_in dest; - uv::NameToAddr(m_host, m_port, &dest); - - m_tcp_client->Connect(dest, [this, socket = m_tcp_client.get()]() { - m_tcp_connected = true; - auto wsConn = std::make_shared(m_tcp_client); - - wsConn->Initialize(); - }); -} - -void HALSimWS::Exit(void* param) { - auto inst = GetInstance(); - if (!inst) { - return; - } - - auto loop = inst->m_loop; - loop->Walk([](uv::Handle& h) { - h.SetLoopClosing(true); - h.Close(); - }); -} - -bool HALSimWS::RegisterWebsocket( - std::shared_ptr hws) { - if (m_hws.lock()) { - return false; - } - - m_hws = hws; - - m_simDevicesProvider.OnNetworkConnected(hws); - - m_providers.ForEach([hws](std::shared_ptr provider) { - provider->OnNetworkConnected(hws); - }); - - return true; -} - -void HALSimWS::CloseWebsocket( - std::shared_ptr hws) { - // Inform the providers that they need to cancel callbacks - m_simDevicesProvider.OnNetworkDisconnected(); - - m_providers.ForEach([](std::shared_ptr provider) { - provider->OnNetworkDisconnected(); - }); - - if (hws == m_hws.lock()) { - m_hws.reset(); - } -} - -void HALSimWS::OnNetValueChanged(const wpi::json& msg) { - // Look for "type" and "device" fields so that we can - // generate the key - - try { - auto& type = msg.at("type").get_ref(); - auto& device = msg.at("device").get_ref(); - - wpi::SmallString<64> key; - key.append(type); - if (!device.empty()) { - key.append("/"); - key.append(device); + if (!simws->Initialize()) { + result = false; + return; } - auto provider = m_providers.Get(key.str()); - if (provider) { - provider->OnNetValueChanged(msg.at("data")); - } - } catch (wpi::json::exception& e) { - wpi::errs() << "Error with incoming message: " << e.what() << "\n"; - } -} + WSRegisterFunc registerFunc = [&](auto key, auto provider) { + providers.Add(key, provider); + }; -} // namespace wpilibws + HALSimWSProviderAnalogIn::Initialize(registerFunc); + HALSimWSProviderAnalogOut::Initialize(registerFunc); + HALSimWSProviderDIO::Initialize(registerFunc); + HALSimWSProviderDigitalPWM::Initialize(registerFunc); + HALSimWSProviderDriverStation::Initialize(registerFunc); + HALSimWSProviderEncoder::Initialize(registerFunc); + HALSimWSProviderJoystick::Initialize(registerFunc); + HALSimWSProviderPWM::Initialize(registerFunc); + HALSimWSProviderRelay::Initialize(registerFunc); + HALSimWSProviderRoboRIO::Initialize(registerFunc); + + simDevices.Initialize(loop); + + simws->Start(); + }); + + return result; +} diff --git a/simulation/halsim_ws_client/src/main/native/cpp/HALSimWSClientConnection.cpp b/simulation/halsim_ws_client/src/main/native/cpp/HALSimWSClientConnection.cpp index 7a52691746..5cfec5ce67 100644 --- a/simulation/halsim_ws_client/src/main/native/cpp/HALSimWSClientConnection.cpp +++ b/simulation/halsim_ws_client/src/main/native/cpp/HALSimWSClientConnection.cpp @@ -10,22 +10,20 @@ #include #include -#include "HALSimWSClient.h" +#include "HALSimWS.h" namespace uv = wpi::uv; -namespace wpilibws { +using namespace wpilibws; void HALSimWSClientConnection::Initialize() { // Get a shared pointer to ourselves auto self = this->shared_from_this(); - auto hws = HALSimWS::GetInstance(); - std::string reqHost = - hws->GetTargetHost() + ":" + std::to_string(hws->GetTargetPort()); - auto ws = - wpi::WebSocket::CreateClient(*m_stream, hws->GetTargetUri(), reqHost); + wpi::WebSocket::CreateClient(*m_stream, m_client->GetTargetUri(), + wpi::Twine{m_client->GetTargetHost()} + ":" + + wpi::Twine{m_client->GetTargetPort()}); ws->SetData(self); @@ -35,21 +33,7 @@ void HALSimWSClientConnection::Initialize() { m_websocket->open.connect_extended([this](auto conn, wpi::StringRef) { conn.disconnect(); - m_buffers = std::make_unique(); - - m_exec = - UvExecFunc::Create(m_stream->GetLoop(), [](auto out, LoopFunc func) { - func(); - out.set_value(); - }); - - auto hws = HALSimWS::GetInstance(); - if (!hws) { - wpi::errs() << "Unable to get hws instance\n"; - return; - } - - if (!hws->RegisterWebsocket(shared_from_this())) { + if (!m_client->RegisterWebsocket(shared_from_this())) { wpi::errs() << "Unable to register websocket\n"; return; } @@ -59,8 +43,7 @@ void HALSimWSClientConnection::Initialize() { }); m_websocket->text.connect([this](wpi::StringRef msg, bool) { - auto hws = HALSimWS::GetInstance(); - if (!m_ws_connected || !hws) { + if (!m_ws_connected) { return; } @@ -75,7 +58,7 @@ void HALSimWSClientConnection::Initialize() { return; } - hws->OnNetValueChanged(j); + m_client->OnNetValueChanged(j); }); m_websocket->closed.connect([this](uint16_t, wpi::StringRef) { @@ -83,10 +66,7 @@ void HALSimWSClientConnection::Initialize() { wpi::outs() << "HALSimWS: Websocket Disconnected\n"; m_ws_connected = false; - auto hws = HALSimWS::GetInstance(); - if (hws) { - hws->CloseWebsocket(shared_from_this()); - } + m_client->CloseWebsocket(shared_from_this()); } }); } @@ -98,25 +78,24 @@ void HALSimWSClientConnection::OnSimValueChanged(const wpi::json& msg) { wpi::SmallVector sendBufs; wpi::raw_uv_ostream os{sendBufs, [this]() -> uv::Buffer { std::lock_guard lock(m_buffers_mutex); - return m_buffers->Allocate(); + return m_buffers.Allocate(); }}; os << msg; // Call the websocket send function on the uv loop - m_exec->Call([this, sendBufs]() mutable { - m_websocket->SendText(sendBufs, [this](auto bufs, wpi::uv::Error err) { - { - std::lock_guard lock(m_buffers_mutex); - m_buffers->Release(bufs); - } + m_client->GetExec().Send([self = shared_from_this(), sendBufs] { + self->m_websocket->SendText(sendBufs, + [self](auto bufs, wpi::uv::Error err) { + { + std::lock_guard lock(self->m_buffers_mutex); + self->m_buffers.Release(bufs); + } - if (err) { - wpi::errs() << err.str() << "\n"; - wpi::errs().flush(); - } - }); + if (err) { + wpi::errs() << err.str() << "\n"; + wpi::errs().flush(); + } + }); }); } - -} // namespace wpilibws diff --git a/simulation/halsim_ws_client/src/main/native/cpp/main.cpp b/simulation/halsim_ws_client/src/main/native/cpp/main.cpp index ee0d16780c..1112e61cf7 100644 --- a/simulation/halsim_ws_client/src/main/native/cpp/main.cpp +++ b/simulation/halsim_ws_client/src/main/native/cpp/main.cpp @@ -5,26 +5,16 @@ /* the project. */ /*----------------------------------------------------------------------------*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include #include #include "HALSimWSClient.h" using namespace wpilibws; -static ProviderContainer providers; -static HALSimWSProviderSimDevices simDevices(providers); +static std::unique_ptr gClient; extern "C" { #if defined(WIN32) || defined(_WIN32) @@ -34,32 +24,13 @@ __declspec(dllexport) int HALSIM_InitExtension(void) { wpi::outs() << "HALSim WS Client Extension Initializing\n"; - auto hws = std::make_shared(providers, simDevices); - HALSimWS::SetInstance(hws); + HAL_OnShutdown(nullptr, [](void*) { gClient.reset(); }); - if (!hws->Initialize()) { + gClient = std::make_unique(); + if (!gClient->Initialize()) { return -1; } - WSRegisterFunc registerFunc = [&](auto key, auto provider) { - providers.Add(key, provider); - }; - - HALSimWSProviderAnalogIn::Initialize(registerFunc); - HALSimWSProviderAnalogOut::Initialize(registerFunc); - HALSimWSProviderDIO::Initialize(registerFunc); - HALSimWSProviderDigitalPWM::Initialize(registerFunc); - HALSimWSProviderDriverStation::Initialize(registerFunc); - HALSimWSProviderEncoder::Initialize(registerFunc); - HALSimWSProviderJoystick::Initialize(registerFunc); - HALSimWSProviderPWM::Initialize(registerFunc); - HALSimWSProviderRelay::Initialize(registerFunc); - HALSimWSProviderRoboRIO::Initialize(registerFunc); - - simDevices.Initialize(hws->GetLoop()); - - HAL_SetMain(nullptr, HALSimWS::Main, HALSimWS::Exit); - wpi::outs() << "HALSim WS Client Extension Initialized\n"; return 0; } diff --git a/simulation/halsim_ws_client/src/main/native/include/HALSimWS.h b/simulation/halsim_ws_client/src/main/native/include/HALSimWS.h new file mode 100644 index 0000000000..e126ac1070 --- /dev/null +++ b/simulation/halsim_ws_client/src/main/native/include/HALSimWS.h @@ -0,0 +1,75 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2020 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace wpi { +class json; +} // namespace wpi + +namespace wpilibws { + +class HALSimWSClientConnection; + +class HALSimWS : public std::enable_shared_from_this { + public: + using LoopFunc = std::function; + using UvExecFunc = wpi::uv::Async; + + HALSimWS(wpi::uv::Loop& loop, ProviderContainer& providers, + HALSimWSProviderSimDevices& simDevicesProvider); + HALSimWS(const HALSimWS&) = delete; + HALSimWS& operator=(const HALSimWS&) = delete; + + bool Initialize(); + void Start(); + + bool RegisterWebsocket(std::shared_ptr hws); + void CloseWebsocket(std::shared_ptr hws); + + void OnNetValueChanged(const wpi::json& msg); + + wpi::StringRef GetTargetHost() const { return m_host; } + wpi::StringRef GetTargetUri() const { return m_uri; } + int GetTargetPort() const { return m_port; } + wpi::uv::Loop& GetLoop() { return m_loop; } + + UvExecFunc& GetExec() { return *m_exec; } + + private: + void AttemptConnect(); + + bool m_tcp_connected = false; + std::shared_ptr m_connect_timer; + int m_connect_attempts = 0; + + std::weak_ptr m_hws; + + wpi::uv::Loop& m_loop; + std::shared_ptr m_tcp_client; + std::shared_ptr m_exec; + + ProviderContainer& m_providers; + HALSimWSProviderSimDevices& m_simDevicesProvider; + + std::string m_host; + std::string m_uri; + int m_port; +}; + +} // namespace wpilibws diff --git a/simulation/halsim_ws_client/src/main/native/include/HALSimWSClient.h b/simulation/halsim_ws_client/src/main/native/include/HALSimWSClient.h index 24aa272eeb..5812140371 100644 --- a/simulation/halsim_ws_client/src/main/native/include/HALSimWSClient.h +++ b/simulation/halsim_ws_client/src/main/native/include/HALSimWSClient.h @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2020 FIRST. All Rights Reserved. */ +/* Copyright (c) 2017-2020 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -8,67 +8,27 @@ #pragma once #include -#include #include #include -#include -#include -#include -#include -#include +#include + +#include "HALSimWS.h" namespace wpilibws { -class HALSimWSClientConnection; - -class HALSimWS { +class HALSimWSClient { public: - static std::shared_ptr GetInstance() { return g_instance; } - static void SetInstance(std::shared_ptr inst) { g_instance = inst; } - - explicit HALSimWS(ProviderContainer& providers, - HALSimWSProviderSimDevices& simDevicesProvider) - : m_providers(providers), m_simDevicesProvider(simDevicesProvider) {} - HALSimWS(const HALSimWS&) = delete; - HALSimWS& operator=(const HALSimWS&) = delete; + HALSimWSClient() = default; + HALSimWSClient(const HALSimWSClient&) = delete; + HALSimWSClient& operator=(const HALSimWSClient&) = delete; bool Initialize(); - static void Main(void*); - static void Exit(void*); - bool RegisterWebsocket(std::shared_ptr hws); - void CloseWebsocket(std::shared_ptr hws); - - void OnNetValueChanged(const wpi::json& msg); - - std::string GetTargetHost() const { return m_host; } - std::string GetTargetUri() const { return m_uri; } - int GetTargetPort() { return m_port; } - std::shared_ptr GetLoop() { return m_loop; } - - private: - static std::shared_ptr g_instance; - - void MainLoop(); - - void AttemptConnect(); - - bool m_tcp_connected = false; - std::shared_ptr m_connect_timer; - int m_connect_attempts = 0; - - std::weak_ptr m_hws; - - ProviderContainer& m_providers; - HALSimWSProviderSimDevices& m_simDevicesProvider; - - std::shared_ptr m_loop; - std::shared_ptr m_tcp_client; - - std::string m_host; - std::string m_uri; - int m_port; + ProviderContainer providers; + HALSimWSProviderSimDevices simDevices{providers}; + wpi::EventLoopRunner runner; + std::shared_ptr simws; }; } // namespace wpilibws diff --git a/simulation/halsim_ws_client/src/main/native/include/HALSimWSClientConnection.h b/simulation/halsim_ws_client/src/main/native/include/HALSimWSClientConnection.h index f6d13432aa..1bd23e9a5e 100644 --- a/simulation/halsim_ws_client/src/main/native/include/HALSimWSClientConnection.h +++ b/simulation/halsim_ws_client/src/main/native/include/HALSimWSClientConnection.h @@ -8,15 +8,20 @@ #pragma once #include +#include #include #include -#include #include -#include #include #include +#include "HALSimWS.h" + +namespace wpi { +class json; +} // namespace wpi + namespace wpilibws { class HALSimWS; @@ -25,25 +30,24 @@ class HALSimWSClientConnection : public HALSimBaseWebSocketConnection, public std::enable_shared_from_this { public: - using BufferPool = wpi::uv::SimpleBufferPool<4>; - using LoopFunc = std::function; - using UvExecFunc = wpi::uv::AsyncFunction; - - explicit HALSimWSClientConnection(std::shared_ptr stream) - : m_stream(stream) {} + explicit HALSimWSClientConnection(std::shared_ptr client, + std::shared_ptr stream) + : m_client(std::move(client)), + m_stream(std::move(stream)), + m_buffers(128) {} public: void OnSimValueChanged(const wpi::json& msg) override; void Initialize(); private: + std::shared_ptr m_client; std::shared_ptr m_stream; bool m_ws_connected = false; wpi::WebSocket* m_websocket = nullptr; - std::shared_ptr m_exec; - std::unique_ptr m_buffers; + wpi::uv::SimpleBufferPool<4> m_buffers; std::mutex m_buffers_mutex; }; diff --git a/simulation/halsim_ws_core/src/main/native/cpp/WSProvider_SimDevice.cpp b/simulation/halsim_ws_core/src/main/native/cpp/WSProvider_SimDevice.cpp index df054725f3..ee8fdedc96 100644 --- a/simulation/halsim_ws_core/src/main/native/cpp/WSProvider_SimDevice.cpp +++ b/simulation/halsim_ws_core/src/main/native/cpp/WSProvider_SimDevice.cpp @@ -166,8 +166,7 @@ void HALSimWSProviderSimDevices::DeviceFreedCallback( m_providers.Delete(name); } -void HALSimWSProviderSimDevices::Initialize( - std::shared_ptr loop) { +void HALSimWSProviderSimDevices::Initialize(wpi::uv::Loop& loop) { m_deviceCreatedCbKey = HALSIM_RegisterSimDeviceCreatedCallback( "", this, HALSimWSProviderSimDevices::DeviceCreatedCallbackStatic, 1); m_deviceFreedCbKey = HALSIM_RegisterSimDeviceFreedCallback( diff --git a/simulation/halsim_ws_core/src/main/native/include/WSProvider_SimDevice.h b/simulation/halsim_ws_core/src/main/native/include/WSProvider_SimDevice.h index cde27409f7..63e5d22bc6 100644 --- a/simulation/halsim_ws_core/src/main/native/include/WSProvider_SimDevice.h +++ b/simulation/halsim_ws_core/src/main/native/include/WSProvider_SimDevice.h @@ -90,7 +90,7 @@ class HALSimWSProviderSimDevices { : m_providers(providers) {} ~HALSimWSProviderSimDevices(); - void Initialize(std::shared_ptr loop); + void Initialize(wpi::uv::Loop& loop); void OnNetworkConnected(std::shared_ptr hws); void OnNetworkDisconnected(); diff --git a/simulation/halsim_ws_server/src/main/native/cpp/HALSimHttpConnection.cpp b/simulation/halsim_ws_server/src/main/native/cpp/HALSimHttpConnection.cpp index 2a60bf2e48..73ebaca828 100644 --- a/simulation/halsim_ws_server/src/main/native/cpp/HALSimHttpConnection.cpp +++ b/simulation/halsim_ws_server/src/main/native/cpp/HALSimHttpConnection.cpp @@ -19,15 +19,12 @@ #include #include -#include "HALSimWSServer.h" - namespace uv = wpi::uv; -namespace wpilibws { +using namespace wpilibws; bool HALSimHttpConnection::IsValidWsUpgrade(wpi::StringRef protocol) { - auto hws = HALSimWeb::GetInstance(); - if (m_request.GetUrl() != hws->GetServerUri()) { + if (m_request.GetUrl() != m_server->GetServerUri()) { MySendError(404, "invalid websocket address"); return false; } @@ -39,21 +36,7 @@ void HALSimHttpConnection::ProcessWsUpgrade() { m_websocket->open.connect_extended([this](auto conn, wpi::StringRef) { conn.disconnect(); // one-shot - m_buffers = std::make_unique(); - m_exec = - UvExecFunc::Create(m_stream.GetLoop(), [](auto out, LoopFunc func) { - func(); - out.set_value(); - }); - - auto hws = HALSimWeb::GetInstance(); - if (!hws) { - Log(503); - m_websocket->Fail(503, "HALSimWeb unavailable"); - return; - } - - if (!hws->RegisterWebsocket(shared_from_this())) { + if (!m_server->RegisterWebsocket(shared_from_this())) { Log(409); m_websocket->Fail(409, "Only a single simulation websocket is allowed"); return; @@ -66,8 +49,7 @@ void HALSimHttpConnection::ProcessWsUpgrade() { // parse incoming JSON, dispatch to parent m_websocket->text.connect([this](wpi::StringRef msg, bool) { - auto hws = HALSimWeb::GetInstance(); - if (!m_isWsConnected || !hws) { + if (!m_isWsConnected) { return; } @@ -80,7 +62,7 @@ void HALSimHttpConnection::ProcessWsUpgrade() { m_websocket->Fail(400, err); return; } - hws->OnNetValueChanged(j); + m_server->OnNetValueChanged(j); }); m_websocket->closed.connect([this](uint16_t, wpi::StringRef) { @@ -89,10 +71,7 @@ void HALSimHttpConnection::ProcessWsUpgrade() { wpi::errs() << "HALWebSim: websocket disconnected\n"; m_isWsConnected = false; - auto hws = HALSimWeb::GetInstance(); - if (hws) { - hws->CloseWebsocket(shared_from_this()); - } + m_server->CloseWebsocket(shared_from_this()); } }); } @@ -102,84 +81,27 @@ void HALSimHttpConnection::OnSimValueChanged(const wpi::json& msg) { wpi::SmallVector sendBufs; wpi::raw_uv_ostream os{sendBufs, [this]() -> uv::Buffer { std::lock_guard lock(m_buffers_mutex); - return m_buffers->Allocate(); + return m_buffers.Allocate(); }}; os << msg; // call the websocket send function on the uv loop - m_exec->Call([this, sendBufs]() mutable { - m_websocket->SendText(sendBufs, [this](auto bufs, wpi::uv::Error err) { - { - std::lock_guard lock(m_buffers_mutex); - m_buffers->Release(bufs); - } + m_server->GetExec().Send([self = shared_from_this(), sendBufs] { + self->m_websocket->SendText(sendBufs, + [self](auto bufs, wpi::uv::Error err) { + { + std::lock_guard lock(self->m_buffers_mutex); + self->m_buffers.Release(bufs); + } - if (err) { - wpi::errs() << err.str() << "\n"; - wpi::errs().flush(); - } - }); + if (err) { + wpi::errs() << err.str() << "\n"; + wpi::errs().flush(); + } + }); }); } -class SendfileReq : public uv::RequestImpl { - public: - SendfileReq(uv_file out, uv_file in, int64_t inOffset, size_t len) - : m_out(out), m_in(in), m_inOffset(inOffset), m_len(len) { - error = [this](uv::Error err) { GetLoop().error(err); }; - } - - uv::Loop& GetLoop() const { - return *static_cast(GetRaw()->loop->data); - } - - int Send(uv::Loop& loop) { - int err = uv_fs_sendfile(loop.GetRaw(), GetRaw(), m_out, m_in, m_inOffset, - m_len, [](uv_fs_t* req) { - auto& h = *static_cast(req->data); - if (req->result < 0) { - h.ReportError(req->result); - h.complete(); - h.Release(); - return; - } - - h.m_inOffset += req->result; - h.m_len -= req->result; - if (h.m_len == 0) { - // done - h.complete(); - h.Release(); // this is always a one-shot - return; - } - - // need to send more - h.Send(h.GetLoop()); - }); - if (err < 0) { - ReportError(err); - complete(); - } - return err; - } - - wpi::sig::Signal<> complete; - - private: - uv_file m_out; - uv_file m_in; - int64_t m_inOffset; - size_t m_len; -}; - -void Sendfile(uv::Loop& loop, uv_file out, uv_file in, int64_t inOffset, - size_t len, std::function complete) { - auto req = std::make_shared(out, in, inOffset, len); - if (complete) req->complete.connect(complete); - int err = req->Send(loop); - if (err >= 0) req->Keep(); -} - void HALSimHttpConnection::SendFileResponse(int code, const wpi::Twine& codeText, const wpi::Twine& contentType, @@ -262,10 +184,12 @@ void HALSimHttpConnection::ProcessRequest() { std::string prefix = (wpi::sys::path::get_separator() + "user" + wpi::sys::path::get_separator()) .str(); - wpi::sys::path::replace_path_prefix(nativePath, prefix, m_webroot_user); + wpi::sys::path::replace_path_prefix(nativePath, prefix, + m_server->GetWebrootUser()); } else { - wpi::sys::path::replace_path_prefix( - nativePath, wpi::sys::path::get_separator(), m_webroot_sys); + wpi::sys::path::replace_path_prefix(nativePath, + wpi::sys::path::get_separator(), + m_server->GetWebrootSys()); } if (wpi::sys::fs::is_directory(nativePath)) { @@ -295,5 +219,3 @@ void HALSimHttpConnection::Log(int code) { << m_request.GetMajor() << "." << m_request.GetMinor() << " " << code << "\n"; } - -} // namespace wpilibws diff --git a/simulation/halsim_ws_server/src/main/native/cpp/HALSimWSServer.cpp b/simulation/halsim_ws_server/src/main/native/cpp/HALSimWSServer.cpp index 16e22b1603..2bc846b7a0 100644 --- a/simulation/halsim_ws_server/src/main/native/cpp/HALSimWSServer.cpp +++ b/simulation/halsim_ws_server/src/main/native/cpp/HALSimWSServer.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2017-2020 FIRST. All Rights Reserved. */ +/* Copyright (c) 2020 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -7,180 +7,49 @@ #include "HALSimWSServer.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "HALSimHttpConnection.h" +using namespace wpilibws; -namespace uv = wpi::uv; +bool HALSimWSServer::Initialize() { + bool result = true; + runner.ExecSync([&](wpi::uv::Loop& loop) { + simWeb = std::make_shared(loop, providers, simDevices); -namespace wpilibws { - -std::shared_ptr HALSimWeb::g_instance; - -bool HALSimWeb::Initialize() { - // determine where to get static content from - // wpi::SmallVector tmp; - wpi::SmallString<64> tmp; - - const char* webroot_sys = std::getenv("HALSIMWS_SYSROOT"); - if (webroot_sys != NULL) { - wpi::StringRef tstr(webroot_sys); - tmp.append(tstr); - } else { - wpi::sys::fs::current_path(tmp); - wpi::sys::path::append(tmp, "sim"); - } - wpi::sys::fs::make_absolute(tmp); - m_webroot_sys = wpi::Twine(tmp).str(); - - tmp.clear(); - const char* webroot_user = std::getenv("HALSIMWS_USERROOT"); - if (webroot_user != NULL) { - wpi::StringRef tstr(webroot_user); - tmp.append(tstr); - } else { - wpi::sys::fs::current_path(tmp); - wpi::sys::path::append(tmp, "sim", "user"); - } - wpi::sys::fs::make_absolute(tmp); - m_webroot_user = wpi::Twine(tmp).str(); - - const char* uri = std::getenv("HALSIMWS_URI"); - if (uri != NULL) { - m_uri = uri; - } else { - m_uri = "/wpilibws"; - } - - const char* port = std::getenv("HALSIMWS_PORT"); - if (port != NULL) { - try { - m_port = std::stoi(port); - } catch (const std::invalid_argument& err) { - wpi::errs() << "Error decoding HALSIMWS_PORT (" << err.what() << ")\n"; - return false; - } - } else { - m_port = 8080; - } - - // create libuv things - m_loop = uv::Loop::Create(); - if (!m_loop) { - return false; - } - - m_loop->error.connect([](uv::Error err) { - wpi::errs() << "HALSim WS Server libuv ERROR: " << err.str() << '\n'; - }); - - m_server = uv::Tcp::Create(m_loop); - if (!m_server) { - return false; - } - - m_server->Bind("", m_port); - return true; -} - -void HALSimWeb::Main(void* param) { - GetInstance()->MainLoop(); - SetInstance(nullptr); -} - -void HALSimWeb::MainLoop() { - // when we get a connection, accept it and start reading - m_server->connection.connect([this, srv = m_server.get()] { - auto tcp = srv->Accept(); - if (!tcp) return; - - tcp->SetNoDelay(true); - - auto conn = std::make_shared(tcp, m_webroot_sys, - m_webroot_user); - tcp->SetData(conn); - }); - - // start listening for incoming connections - m_server->Listen(); - wpi::outs() << "Listening at http://localhost:" << m_port << "\n"; - wpi::outs() << "WebSocket URI: " << m_uri << "\n"; - m_loop->Run(); -} - -void HALSimWeb::Exit(void* param) { - auto inst = GetInstance(); - if (!inst) return; - - auto loop = inst->m_loop; - loop->Walk([](uv::Handle& h) { - h.SetLoopClosing(true); - h.Close(); - }); -} - -bool HALSimWeb::RegisterWebsocket( - std::shared_ptr hws) { - if (m_hws.lock()) { - return false; - } - - m_hws = hws; - - m_simDevicesProvider.OnNetworkConnected(hws); - - // notify all providers that they should use this new websocket instead - m_providers.ForEach([hws](std::shared_ptr provider) { - provider->OnNetworkConnected(hws); - }); - - return true; -} - -void HALSimWeb::CloseWebsocket( - std::shared_ptr hws) { - // Inform the providers that they need to cancel callbacks - m_simDevicesProvider.OnNetworkDisconnected(); - - m_providers.ForEach([](std::shared_ptr provider) { - provider->OnNetworkDisconnected(); - }); - - if (hws == m_hws.lock()) { - m_hws.reset(); - } -} - -void HALSimWeb::OnNetValueChanged(const wpi::json& msg) { - // Look for "type" and "device" fields so that we can - // generate the key - - try { - auto& type = msg.at("type").get_ref(); - auto& device = msg.at("device").get_ref(); - - wpi::SmallString<64> key; - key.append(type); - if (!device.empty()) { - key.append("/"); - key.append(device); + if (!simWeb->Initialize()) { + result = false; + return; } - auto provider = m_providers.Get(key.str()); - if (provider) { - provider->OnNetValueChanged(msg.at("data")); - } - } catch (wpi::json::exception& e) { - wpi::errs() << "Error with incoming message: " << e.what() << "\n"; - } -} + WSRegisterFunc registerFunc = [&](auto key, auto provider) { + providers.Add(key, provider); + }; -} // namespace wpilibws + HALSimWSProviderAnalogIn::Initialize(registerFunc); + HALSimWSProviderAnalogOut::Initialize(registerFunc); + HALSimWSProviderDIO::Initialize(registerFunc); + HALSimWSProviderDigitalPWM::Initialize(registerFunc); + HALSimWSProviderDriverStation::Initialize(registerFunc); + HALSimWSProviderEncoder::Initialize(registerFunc); + HALSimWSProviderJoystick::Initialize(registerFunc); + HALSimWSProviderPWM::Initialize(registerFunc); + HALSimWSProviderRelay::Initialize(registerFunc); + HALSimWSProviderRoboRIO::Initialize(registerFunc); + + simDevices.Initialize(loop); + + simWeb->Start(); + }); + + return result; +} diff --git a/simulation/halsim_ws_server/src/main/native/cpp/HALSimWeb.cpp b/simulation/halsim_ws_server/src/main/native/cpp/HALSimWeb.cpp new file mode 100644 index 0000000000..fe5393d891 --- /dev/null +++ b/simulation/halsim_ws_server/src/main/native/cpp/HALSimWeb.cpp @@ -0,0 +1,170 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2017-2020 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "HALSimWeb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "HALSimHttpConnection.h" + +namespace uv = wpi::uv; + +using namespace wpilibws; + +HALSimWeb::HALSimWeb(wpi::uv::Loop& loop, ProviderContainer& providers, + HALSimWSProviderSimDevices& simDevicesProvider) + : m_loop(loop), + m_providers(providers), + m_simDevicesProvider(simDevicesProvider) { + m_loop.error.connect([](uv::Error err) { + wpi::errs() << "HALSim WS Server libuv ERROR: " << err.str() << '\n'; + }); + + m_server = uv::Tcp::Create(m_loop); + m_exec = UvExecFunc::Create(m_loop); + if (m_exec) { + m_exec->wakeup.connect([](auto func) { func(); }); + } +} + +bool HALSimWeb::Initialize() { + if (!m_server || !m_exec) { + return false; + } + + // determine where to get static content from + // wpi::SmallVector tmp; + wpi::SmallString<64> tmp; + + const char* webroot_sys = std::getenv("HALSIMWS_SYSROOT"); + if (webroot_sys != NULL) { + wpi::StringRef tstr(webroot_sys); + tmp.append(tstr); + } else { + wpi::sys::fs::current_path(tmp); + wpi::sys::path::append(tmp, "sim"); + } + wpi::sys::fs::make_absolute(tmp); + m_webroot_sys = wpi::Twine(tmp).str(); + + tmp.clear(); + const char* webroot_user = std::getenv("HALSIMWS_USERROOT"); + if (webroot_user != NULL) { + wpi::StringRef tstr(webroot_user); + tmp.append(tstr); + } else { + wpi::sys::fs::current_path(tmp); + wpi::sys::path::append(tmp, "sim", "user"); + } + wpi::sys::fs::make_absolute(tmp); + m_webroot_user = wpi::Twine(tmp).str(); + + const char* uri = std::getenv("HALSIMWS_URI"); + if (uri != NULL) { + m_uri = uri; + } else { + m_uri = "/wpilibws"; + } + + const char* port = std::getenv("HALSIMWS_PORT"); + if (port != NULL) { + try { + m_port = std::stoi(port); + } catch (const std::invalid_argument& err) { + wpi::errs() << "Error decoding HALSIMWS_PORT (" << err.what() << ")\n"; + return false; + } + } else { + m_port = 8080; + } + + return true; +} + +void HALSimWeb::Start() { + m_server->Bind("", m_port); + + // when we get a connection, accept it and start reading + m_server->connection.connect([this, srv = m_server.get()] { + auto tcp = srv->Accept(); + if (!tcp) return; + + tcp->SetNoDelay(true); + + auto conn = std::make_shared(shared_from_this(), tcp); + tcp->SetData(conn); + }); + + // start listening for incoming connections + m_server->Listen(); + wpi::outs() << "Listening at http://localhost:" << m_port << "\n"; + wpi::outs() << "WebSocket URI: " << m_uri << "\n"; +} + +bool HALSimWeb::RegisterWebsocket( + std::shared_ptr hws) { + if (m_hws.lock()) { + return false; + } + + m_hws = hws; + + m_simDevicesProvider.OnNetworkConnected(hws); + + // notify all providers that they should use this new websocket instead + m_providers.ForEach([hws](std::shared_ptr provider) { + provider->OnNetworkConnected(hws); + }); + + return true; +} + +void HALSimWeb::CloseWebsocket( + std::shared_ptr hws) { + // Inform the providers that they need to cancel callbacks + m_simDevicesProvider.OnNetworkDisconnected(); + + m_providers.ForEach([](std::shared_ptr provider) { + provider->OnNetworkDisconnected(); + }); + + if (hws == m_hws.lock()) { + m_hws.reset(); + } +} + +void HALSimWeb::OnNetValueChanged(const wpi::json& msg) { + // Look for "type" and "device" fields so that we can + // generate the key + + try { + auto& type = msg.at("type").get_ref(); + auto& device = msg.at("device").get_ref(); + + wpi::SmallString<64> key; + key.append(type); + if (!device.empty()) { + key.append("/"); + key.append(device); + } + + auto provider = m_providers.Get(key.str()); + if (provider) { + provider->OnNetValueChanged(msg.at("data")); + } + } catch (wpi::json::exception& e) { + wpi::errs() << "Error with incoming message: " << e.what() << "\n"; + } +} diff --git a/simulation/halsim_ws_server/src/main/native/cpp/main.cpp b/simulation/halsim_ws_server/src/main/native/cpp/main.cpp index 498031945e..025a902eaf 100644 --- a/simulation/halsim_ws_server/src/main/native/cpp/main.cpp +++ b/simulation/halsim_ws_server/src/main/native/cpp/main.cpp @@ -5,18 +5,9 @@ /* the project. */ /*----------------------------------------------------------------------------*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include #include #include "HALSimWSServer.h" @@ -24,44 +15,23 @@ using namespace std::placeholders; using namespace wpilibws; -// Currently, robots never terminate, so we keep static objects that are -// never properly released or cleaned up. -static ProviderContainer providers; -static HALSimWSProviderSimDevices simDevices(providers); +static std::unique_ptr gServer; extern "C" { #if defined(WIN32) || defined(_WIN32) __declspec(dllexport) #endif int HALSIM_InitExtension(void) { - wpi::outs() << "Websocket Simulator Initializing.\n"; - auto hsw = std::make_shared(providers, simDevices); - HALSimWeb::SetInstance(hsw); + wpi::outs() << "Websocket WS Server Initializing.\n"; - if (!hsw->Initialize()) { + HAL_OnShutdown(nullptr, [](void*) { gServer.reset(); }); + + gServer = std::make_unique(); + if (!gServer->Initialize()) { return -1; } - WSRegisterFunc registerFunc = [&](auto key, auto provider) { - providers.Add(key, provider); - }; - - HALSimWSProviderAnalogIn::Initialize(registerFunc); - HALSimWSProviderAnalogOut::Initialize(registerFunc); - HALSimWSProviderDIO::Initialize(registerFunc); - HALSimWSProviderDigitalPWM::Initialize(registerFunc); - HALSimWSProviderDriverStation::Initialize(registerFunc); - HALSimWSProviderEncoder::Initialize(registerFunc); - HALSimWSProviderJoystick::Initialize(registerFunc); - HALSimWSProviderPWM::Initialize(registerFunc); - HALSimWSProviderRelay::Initialize(registerFunc); - HALSimWSProviderRoboRIO::Initialize(registerFunc); - - simDevices.Initialize(hsw->GetLoop()); - - HAL_SetMain(nullptr, HALSimWeb::Main, HALSimWeb::Exit); - - wpi::outs() << "Websocket Simulator Initialized!\n"; + wpi::outs() << "Websocket WS Server Initialized!\n"; return 0; } } // extern "C" diff --git a/simulation/halsim_ws_server/src/main/native/include/HALSimHttpConnection.h b/simulation/halsim_ws_server/src/main/native/include/HALSimHttpConnection.h index 5b9ab49903..89e6c8303b 100644 --- a/simulation/halsim_ws_server/src/main/native/include/HALSimHttpConnection.h +++ b/simulation/halsim_ws_server/src/main/native/include/HALSimHttpConnection.h @@ -9,35 +9,31 @@ #include #include -#include -#include +#include #include #include -#include #include #include #include -namespace wpilibws { +#include "HALSimWeb.h" -class HALSimWeb; +namespace wpi { +class json; +} // namespace wpi + +namespace wpilibws { class HALSimHttpConnection : public wpi::HttpWebSocketServerConnection, public HALSimBaseWebSocketConnection { public: - using BufferPool = wpi::uv::SimpleBufferPool<4>; - - using LoopFunc = std::function; - using UvExecFunc = wpi::uv::AsyncFunction; - - explicit HALSimHttpConnection(std::shared_ptr stream, - wpi::StringRef webroot_sys, - wpi::StringRef webroot_user) + HALSimHttpConnection(std::shared_ptr server, + std::shared_ptr stream) : wpi::HttpWebSocketServerConnection(stream, {}), - m_webroot_sys(webroot_sys), - m_webroot_user(webroot_user) {} + m_server(std::move(server)), + m_buffers(128) {} public: // callable from any thread @@ -56,18 +52,13 @@ class HALSimHttpConnection void Log(int code); private: - // Absolute paths of folders to retrieve data from - // -> / - std::string m_webroot_sys; - // -> /user - std::string m_webroot_user; + std::shared_ptr m_server; // is the websocket connected? bool m_isWsConnected = false; // these are only valid if the websocket is connected - std::shared_ptr m_exec; - std::unique_ptr m_buffers; + wpi::uv::SimpleBufferPool<4> m_buffers; std::mutex m_buffers_mutex; }; diff --git a/simulation/halsim_ws_server/src/main/native/include/HALSimWSServer.h b/simulation/halsim_ws_server/src/main/native/include/HALSimWSServer.h index 22c5a31c0c..d75341b36d 100644 --- a/simulation/halsim_ws_server/src/main/native/include/HALSimWSServer.h +++ b/simulation/halsim_ws_server/src/main/native/include/HALSimWSServer.h @@ -8,74 +8,27 @@ #pragma once #include -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include +#include + +#include "HALSimWeb.h" namespace wpilibws { -class HALSimHttpConnection; - -class HALSimWeb { +class HALSimWSServer { public: - static std::shared_ptr GetInstance() { return g_instance; } - static void SetInstance(std::shared_ptr inst) { - g_instance = inst; - } - - explicit HALSimWeb(ProviderContainer& providers, - HALSimWSProviderSimDevices& simDevicesProvider) - : m_providers(providers), m_simDevicesProvider(simDevicesProvider) {} - - HALSimWeb(const HALSimWeb&) = delete; - HALSimWeb& operator=(const HALSimWeb&) = delete; + HALSimWSServer() = default; + HALSimWSServer(const HALSimWSServer&) = delete; + HALSimWSServer& operator=(const HALSimWSServer&) = delete; bool Initialize(); - static void Main(void*); - static void Exit(void*); - bool RegisterWebsocket(std::shared_ptr hws); - void CloseWebsocket(std::shared_ptr hws); - - // network -> sim - void OnNetValueChanged(const wpi::json& msg); - - std::string GetServerUri() const { return m_uri; } - int GetServerPort() { return m_port; } - std::shared_ptr GetLoop() { return m_loop; } - - private: - static std::shared_ptr g_instance; - - void MainLoop(); - - // connected http connection that contains active websocket - std::weak_ptr m_hws; - - // list of providers - ProviderContainer& m_providers; - HALSimWSProviderSimDevices& m_simDevicesProvider; - - std::shared_ptr m_loop; - std::shared_ptr m_server; - - // Absolute paths of folders to retrieve data from - // -> / - std::string m_webroot_sys; - // -> /user - std::string m_webroot_user; - - std::string m_uri; - int m_port; + ProviderContainer providers; + HALSimWSProviderSimDevices simDevices{providers}; + wpi::EventLoopRunner runner; + std::shared_ptr simWeb; }; } // namespace wpilibws diff --git a/simulation/halsim_ws_server/src/main/native/include/HALSimWeb.h b/simulation/halsim_ws_server/src/main/native/include/HALSimWeb.h new file mode 100644 index 0000000000..f71cbdf818 --- /dev/null +++ b/simulation/halsim_ws_server/src/main/native/include/HALSimWeb.h @@ -0,0 +1,78 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2017-2020 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace wpi { +class json; +} // namespace wpi + +namespace wpilibws { + +class HALSimWeb : public std::enable_shared_from_this { + public: + using LoopFunc = std::function; + using UvExecFunc = wpi::uv::Async; + + HALSimWeb(wpi::uv::Loop& loop, ProviderContainer& providers, + HALSimWSProviderSimDevices& simDevicesProvider); + + HALSimWeb(const HALSimWeb&) = delete; + HALSimWeb& operator=(const HALSimWeb&) = delete; + + bool Initialize(); + void Start(); + + bool RegisterWebsocket(std::shared_ptr hws); + void CloseWebsocket(std::shared_ptr hws); + + // network -> sim + void OnNetValueChanged(const wpi::json& msg); + + wpi::StringRef GetWebrootSys() const { return m_webroot_sys; } + wpi::StringRef GetWebrootUser() const { return m_webroot_user; } + wpi::StringRef GetServerUri() const { return m_uri; } + int GetServerPort() const { return m_port; } + wpi::uv::Loop& GetLoop() { return m_loop; } + + UvExecFunc& GetExec() { return *m_exec; } + + private: + // connected http connection that contains active websocket + std::weak_ptr m_hws; + + wpi::uv::Loop& m_loop; + std::shared_ptr m_server; + std::shared_ptr m_exec; + + // list of providers + ProviderContainer& m_providers; + HALSimWSProviderSimDevices& m_simDevicesProvider; + + // Absolute paths of folders to retrieve data from + // -> / + std::string m_webroot_sys; + // -> /user + std::string m_webroot_user; + + std::string m_uri; + int m_port; +}; + +} // namespace wpilibws diff --git a/simulation/halsim_ws_server/src/test/native/cpp/WebServerClientTest.cpp b/simulation/halsim_ws_server/src/test/native/cpp/WebServerClientTest.cpp index ff12c99f8e..6cdac5ad63 100644 --- a/simulation/halsim_ws_server/src/test/native/cpp/WebServerClientTest.cpp +++ b/simulation/halsim_ws_server/src/test/native/cpp/WebServerClientTest.cpp @@ -20,8 +20,6 @@ namespace uv = wpi::uv; namespace wpilibws { -std::shared_ptr WebServerClientTest::g_instance; - // Create Web Socket and specify event callbacks void WebServerClientTest::InitializeWebSocket(const std::string& host, int port, const std::string& uri) { @@ -59,11 +57,6 @@ void WebServerClientTest::InitializeWebSocket(const std::string& host, int port, } // Save last message received m_json = j; - - // If terminate flag set, end loop after message received - if (m_terminateFlag) { - m_loop->Stop(); - } }); m_websocket->closed.connect([this](uint16_t, wpi::StringRef) { @@ -75,9 +68,8 @@ void WebServerClientTest::InitializeWebSocket(const std::string& host, int port, } // Create tcp client, specify callbacks, and create timers for loop -bool WebServerClientTest::Initialize(std::shared_ptr& loop) { - m_loop = loop; - m_loop->error.connect( +bool WebServerClientTest::Initialize() { + m_loop.error.connect( [](uv::Error err) { wpi::errs() << "uv Error: " << err.str() << "\n"; }); m_tcp_client = uv::Tcp::Create(m_loop); @@ -92,7 +84,7 @@ bool WebServerClientTest::Initialize(std::shared_ptr& loop) { if (m_tcp_connected) { m_tcp_connected = false; m_connect_attempts = 0; - m_loop->Stop(); + m_loop.Stop(); return; } @@ -124,7 +116,7 @@ void WebServerClientTest::AttemptConnect() { if (m_connect_attempts >= 5) { wpi::errs() << "Test Client Timeout. Unable to connect\n"; - Exit(); + m_loop.Stop(); return; } @@ -136,13 +128,6 @@ void WebServerClientTest::AttemptConnect() { }); } -void WebServerClientTest::Exit() { - m_loop->Walk([](uv::Handle& h) { - h.SetLoopClosing(true); - h.Close(); - }); -} - void WebServerClientTest::SendMessage(const wpi::json& msg) { if (msg.empty()) { wpi::errs() << "Message to send is empty\n"; diff --git a/simulation/halsim_ws_server/src/test/native/cpp/main.cpp b/simulation/halsim_ws_server/src/test/native/cpp/main.cpp index b03ce50183..4172f1993a 100644 --- a/simulation/halsim_ws_server/src/test/native/cpp/main.cpp +++ b/simulation/halsim_ws_server/src/test/native/cpp/main.cpp @@ -5,6 +5,8 @@ /* the project. */ /*----------------------------------------------------------------------------*/ +#include + #include #include #include @@ -16,89 +18,66 @@ #include "WebServerClientTest.h" #include "gtest/gtest.h" +namespace uv = wpi::uv; + using namespace wpilibws; -extern "C" int HALSIM_InitExtension( - void); // from simulation/halsim_ws_server/src/main/native/cpp/main.cpp - -const int POLLING_SPEED = 10; // 10 ms polling +static const int POLLING_SPEED = 10; // 10 ms polling class WebServerIntegrationTest : public ::testing::Test { public: WebServerIntegrationTest() { - const int MAX_TEST_TIME = 1000; // 1 second - - // Initialize HAL layer including webserver - HALSIM_InitExtension(); + // Initialize server + m_server.Initialize(); // Create and initialize client - m_webserver_client = - std::shared_ptr((new WebServerClientTest())); - WebServerClientTest::SetInstance(m_webserver_client); - auto webserver = HALSimWeb::GetInstance(); - auto loop = webserver->GetLoop(); - m_webserver_client->Initialize(loop); - - // Create failover timer to prevent running forever - m_failoverTimer = wpi::uv::Timer::Create(loop); - m_failoverTimer->timeout.connect([loop] { loop->Stop(); }); - m_failoverTimer->Start(uv::Timer::Time(MAX_TEST_TIME)); + m_server.runner.ExecSync([=](auto& loop) { + m_webserverClient = std::make_shared(loop); + m_webserverClient->Initialize(); + }); } - ~WebServerIntegrationTest() { - // Exit HAL layer which exits webserver - HAL_ExitMain(); - HALSimWeb::Exit(nullptr); + bool IsConnectedClientWS() { return m_webserverClient->IsConnectedWS(); } - // Exit client - m_webserver_client->Exit(); - WebServerClientTest::SetInstance(nullptr); - - // Unreference timers from loop - m_failoverTimer->Unreference(); - } - - bool IsConnectedClientWS() { return m_webserver_client->IsConnectedWS(); } - void TerminateOnNextMessage(bool flag) { - m_webserver_client->SetTerminateFlag(flag); - } - - private: - std::shared_ptr m_webserver_client; - std::shared_ptr m_failoverTimer; + protected: + std::shared_ptr m_webserverClient; + HALSimWSServer m_server; }; TEST_F(WebServerIntegrationTest, DigitalOutput) { // Create expected results const bool EXPECTED_VALUE = false; const int PIN = 0; + bool done = false; // Attach timer to loop for test function - auto ws = HALSimWeb::GetInstance(); - auto loop = ws->GetLoop(); - auto timer = wpi::uv::Timer::Create(loop); - timer->timeout.connect([&] { - if (IsConnectedClientWS()) { - wpi::outs() << "***** Setting DIO value for pin " << PIN << " to " - << (EXPECTED_VALUE ? "true" : "false") << "\n"; - HALSIM_SetDIOValue(PIN, EXPECTED_VALUE); - TerminateOnNextMessage(true); - } else { - // Recheck in POLLING_SPEED ms - timer->Start(uv::Timer::Time(POLLING_SPEED)); - } + m_server.runner.ExecSync([&](auto& loop) { + auto timer = wpi::uv::Timer::Create(loop); + timer->timeout.connect([&] { + if (done) { + return; + } + if (IsConnectedClientWS()) { + wpi::outs() << "***** Setting DIO value for pin " << PIN << " to " + << (EXPECTED_VALUE ? "true" : "false") << "\n"; + HALSIM_SetDIOValue(PIN, EXPECTED_VALUE); + done = true; + } + }); + timer->Start(uv::Timer::Time(POLLING_SPEED), + uv::Timer::Time(POLLING_SPEED)); + timer->Unreference(); }); - timer->Start(uv::Timer::Time(POLLING_SPEED)); - HAL_RunMain(); - timer->Unreference(); + using namespace std::chrono_literals; + std::this_thread::sleep_for(1s); // Get values from JSON message std::string test_type; std::string test_device; bool test_value = true; // Default value from HAL initialization try { - auto& msg = WebServerClientTest::GetInstance()->GetLastMessage(); + auto& msg = m_webserverClient->GetLastMessage(); test_type = msg.at("type").get_ref(); test_device = msg.at("device").get_ref(); auto& data = msg.at("data"); @@ -120,28 +99,31 @@ TEST_F(WebServerIntegrationTest, DigitalInput) { // Create expected results const bool EXPECTED_VALUE = false; const int PIN = 0; + bool done = false; // Attach timer to loop for test function - auto ws = HALSimWeb::GetInstance(); - auto loop = ws->GetLoop(); - auto timer = wpi::uv::Timer::Create(loop); - timer->timeout.connect([&] { - if (IsConnectedClientWS()) { - wpi::json msg = {{"type", "DIO"}, - {"device", std::to_string(PIN)}, - {"data", {{"<>value", EXPECTED_VALUE}}}}; - wpi::outs() << "***** Input JSON: " << msg.dump() << "\n"; - WebServerClientTest::GetInstance()->SendMessage(msg); - loop->Stop(); - } else { - // Recheck in POLLING_SPEED ms - timer->Start(uv::Timer::Time(POLLING_SPEED)); - } + m_server.runner.ExecSync([&](auto& loop) { + auto timer = wpi::uv::Timer::Create(loop); + timer->timeout.connect([&] { + if (done) { + return; + } + if (IsConnectedClientWS()) { + wpi::json msg = {{"type", "DIO"}, + {"device", std::to_string(PIN)}, + {"data", {{"<>value", EXPECTED_VALUE}}}}; + wpi::outs() << "***** Input JSON: " << msg.dump() << "\n"; + m_webserverClient->SendMessage(msg); + done = true; + } + }); + timer->Start(uv::Timer::Time(POLLING_SPEED), + uv::Timer::Time(POLLING_SPEED)); + timer->Unreference(); }); - timer->Start(uv::Timer::Time(POLLING_SPEED)); - HAL_RunMain(); - timer->Unreference(); + using namespace std::chrono_literals; + std::this_thread::sleep_for(1s); // Compare results bool test_value = HALSIM_GetDIOValue(PIN); @@ -151,33 +133,32 @@ TEST_F(WebServerIntegrationTest, DigitalInput) { TEST_F(WebServerIntegrationTest, DriverStation) { // Create expected results const bool EXPECTED_VALUE = true; + bool done = false; // Attach timer to loop for test function - auto ws = HALSimWeb::GetInstance(); - auto loop = ws->GetLoop(); - auto timer = wpi::uv::Timer::Create(loop); - bool done = false; - timer->timeout.connect([&] { - if (done) { - loop->Stop(); - } else { - // Recheck in POLLING_SPEED ms - timer->Start(uv::Timer::Time(POLLING_SPEED)); - } - if (IsConnectedClientWS()) { - wpi::json msg = { - {"type", "DriverStation"}, - {"device", ""}, - {"data", {{">enabled", EXPECTED_VALUE}, {">new_data", true}}}}; - wpi::outs() << "***** Input JSON: " << msg.dump() << "\n"; - WebServerClientTest::GetInstance()->SendMessage(msg); - done = true; - } + m_server.runner.ExecSync([&](auto& loop) { + auto timer = wpi::uv::Timer::Create(loop); + timer->timeout.connect([&] { + if (done) { + return; + } + if (IsConnectedClientWS()) { + wpi::json msg = { + {"type", "DriverStation"}, + {"device", ""}, + {"data", {{">enabled", EXPECTED_VALUE}, {">new_data", true}}}}; + wpi::outs() << "***** Input JSON: " << msg.dump() << "\n"; + m_webserverClient->SendMessage(msg); + done = true; + } + }); + timer->Start(uv::Timer::Time(POLLING_SPEED), + uv::Timer::Time(POLLING_SPEED)); + timer->Unreference(); }); - timer->Start(uv::Timer::Time(POLLING_SPEED)); - HAL_RunMain(); - timer->Unreference(); + using namespace std::chrono_literals; + std::this_thread::sleep_for(1s); // Compare results HAL_ControlWord cw; diff --git a/simulation/halsim_ws_server/src/test/native/include/WebServerClientTest.h b/simulation/halsim_ws_server/src/test/native/include/WebServerClientTest.h index da2eec81bf..909c4bab7f 100644 --- a/simulation/halsim_ws_server/src/test/native/include/WebServerClientTest.h +++ b/simulation/halsim_ws_server/src/test/native/include/WebServerClientTest.h @@ -19,8 +19,6 @@ #include #include -namespace uv = wpi::uv; - namespace wpilibws { class WebServerClientTest { @@ -29,36 +27,25 @@ class WebServerClientTest { using LoopFunc = std::function; using UvExecFunc = wpi::uv::AsyncFunction; - static std::shared_ptr GetInstance() { - return g_instance; - } - static void SetInstance(std::shared_ptr inst) { - g_instance = inst; - } - - WebServerClientTest() {} + explicit WebServerClientTest(wpi::uv::Loop& loop) : m_loop(loop) {} WebServerClientTest(const WebServerClientTest&) = delete; WebServerClientTest& operator=(const WebServerClientTest&) = delete; - bool Initialize(std::shared_ptr& loop); + bool Initialize(); void AttemptConnect(); - void Exit(); void SendMessage(const wpi::json& msg); const wpi::json& GetLastMessage(); bool IsConnectedWS() { return m_ws_connected; } - void SetTerminateFlag(bool flag) { m_terminateFlag = flag; } private: void InitializeWebSocket(const std::string& host, int port, const std::string& uri); - bool m_terminateFlag = false; - static std::shared_ptr g_instance; bool m_tcp_connected = false; std::shared_ptr m_connect_timer; int m_connect_attempts = 0; - std::shared_ptr m_loop; + wpi::uv::Loop& m_loop; std::shared_ptr m_tcp_client; wpi::json m_json;