diff --git a/data/assets/util/webgui.asset b/data/assets/util/webgui.asset index 85335141b1..0149cd9c8a 100644 --- a/data/assets/util/webgui.asset +++ b/data/assets/util/webgui.asset @@ -10,7 +10,10 @@ local frontend = asset.syncedResource({ Identifier = "WebGuiFrontend", Name = "Web Gui Frontend", Type = "UrlSynchronization", - Url = dataProvider .. "/frontend/" .. frontendHash .. "/frontend.zip" + -- Url = dataProvider .. "/frontend/" .. frontendHash .. "/frontend.zip" + -- TODO: Remove. This was added in order to use the new GUI for saving sessions. + -- in the Software Integration Module + Url = "openspacegui.vlq.se/e2b459d60bc7d2fc0ad8e4a89789b26febe60e57/frontend.zip" }) asset.onInitialize(function () diff --git a/include/openspace/util/syncbuffer.h b/include/openspace/util/syncbuffer.h index b6af66a048..3de860af2b 100644 --- a/include/openspace/util/syncbuffer.h +++ b/include/openspace/util/syncbuffer.h @@ -43,6 +43,9 @@ public: template void encode(const T& v); + template + void encode(std::vector& value); + std::string decode(); template @@ -57,6 +60,9 @@ public: template void decode(T& value); + template + void decode(std::vector& value); + void reset(); //void write(); diff --git a/include/openspace/util/syncbuffer.inl b/include/openspace/util/syncbuffer.inl index 84503a3392..d24358981a 100644 --- a/include/openspace/util/syncbuffer.inl +++ b/include/openspace/util/syncbuffer.inl @@ -41,6 +41,21 @@ void SyncBuffer::encode(const T& v) { _encodeOffset += size; } +template +void SyncBuffer::encode(std::vector& value) { + const size_t size = sizeof(T) * value.size(); + + encode(static_cast(value.size())); + + size_t anticpatedBufferSize = _encodeOffset + size; + if (anticpatedBufferSize >= _n) { + _dataStream.resize(anticpatedBufferSize); + } + + std::memcpy(_dataStream.data() + _encodeOffset, value.data(), size); + _encodeOffset += size; +} + template T SyncBuffer::decode() { const size_t size = sizeof(T); @@ -59,4 +74,16 @@ void SyncBuffer::decode(T& value) { _decodeOffset += size; } +template +void SyncBuffer::decode(std::vector& value) { + uint32_t size; + decode(size); + value.resize(size); + const size_t sizeInBytes = sizeof(T) * size; + ghoul_assert(_decodeOffset + sizeInBytes < _n, ""); + std::memcpy(value.data(), _dataStream.data() + _decodeOffset, sizeInBytes); + _decodeOffset += sizeInBytes; +} + + } // namespace openspace diff --git a/modules/softwareintegration/CMakeLists.txt b/modules/softwareintegration/CMakeLists.txt index 977af2b818..f6febd9ad8 100644 --- a/modules/softwareintegration/CMakeLists.txt +++ b/modules/softwareintegration/CMakeLists.txt @@ -25,26 +25,29 @@ include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake) set(HEADER_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/pointdatamessagehandler.h ${CMAKE_CURRENT_SOURCE_DIR}/softwareintegrationmodule.h + ${CMAKE_CURRENT_SOURCE_DIR}/messagehandler.h + ${CMAKE_CURRENT_SOURCE_DIR}/network/network.h ${CMAKE_CURRENT_SOURCE_DIR}/network/softwareconnection.h - ${CMAKE_CURRENT_SOURCE_DIR}/network/networkengine.h ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablepointscloud.h ${CMAKE_CURRENT_SOURCE_DIR}/utils.h ${CMAKE_CURRENT_SOURCE_DIR}/syncablefloatdatastorage.h ${CMAKE_CURRENT_SOURCE_DIR}/interruptibleconcurrentqueue.h ${CMAKE_CURRENT_SOURCE_DIR}/interruptibleconcurrentqueue.inl + ${CMAKE_CURRENT_SOURCE_DIR}/assethelper.h ) source_group("Header Files" FILES ${HEADER_FILES}) set(SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/pointdatamessagehandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/softwareintegrationmodule.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/softwareintegrationmodule_lua.inl + ${CMAKE_CURRENT_SOURCE_DIR}/messagehandler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/network/network.cpp ${CMAKE_CURRENT_SOURCE_DIR}/network/softwareconnection.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/network/networkengine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablepointscloud.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/syncablefloatdatastorage.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/assethelper.cpp ) source_group("Source Files" FILES ${SOURCE_FILES}) diff --git a/modules/softwareintegration/assethelper.cpp b/modules/softwareintegration/assethelper.cpp new file mode 100644 index 0000000000..dd5b74298e --- /dev/null +++ b/modules/softwareintegration/assethelper.cpp @@ -0,0 +1,285 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +constexpr const char* _loggerCat = "SoftwareIntegrationAssetHelper"; + +} // namespace + +namespace openspace { + +// Anonomous namespace +namespace { + +const std::string INDENT{" "}; + +bool writeToFile(std::filesystem::path path, const std::vector& buffer, std::string& errorMessage) { + try { + std::basic_ofstream outFile{path, std::ios::out | std::ios::binary}; + if(!outFile) { + throw std::ofstream::failure{"Could not open file"}; + } + + const auto bufferSize = static_cast(buffer.size()); + outFile.write(reinterpret_cast(&bufferSize), sizeof(uint32_t)); + outFile.write(buffer.data(), bufferSize); + outFile.close(); + return true; + } + catch (const std::ofstream::failure& e) { + errorMessage = fmt::format("Could not write to the file \"{}\". {}.", path.string(), e.what()); + return false; + } +} + +bool readFile(std::filesystem::path path, std::vector& buffer, std::string& errorMessage) { + try { + std::basic_ifstream inFile{path, std::ios::out | std::ios::binary}; + if(!inFile) { + throw std::ifstream::failure{"Could not open file"}; + } + + uint32_t bufferSize; + inFile.read(reinterpret_cast(&bufferSize), sizeof(uint32_t)); + buffer.resize(bufferSize); + inFile.read(buffer.data(), bufferSize); + inFile.close(); + return true; + } + catch (const std::ifstream::failure& e) { + errorMessage = fmt::format("Couldn't read the file \"{}\". {}.", path.string(), e.what()); + return false; + } +} + +bool saveSessionData(SyncableFloatDataStorage& storage, + const std::filesystem::path& filePath, + std::string& errorMessage) +{ + std::vector byteStream; + storage.dump(byteStream); + + if (byteStream.size() == 0) { + errorMessage = "Software Integration Storage is empty."; + return false; + } + + if (std::filesystem::exists(filePath)) { + errorMessage = fmt::format("The file \"{}\" already exists.", filePath.filename().string()); + return false; + } + + if (!writeToFile(filePath, byteStream, errorMessage)) { + return false; + } + + return true; +} + +} // namepsace + +bool AssetHelper::loadSessionData(SoftwareIntegrationModule* module, + const std::string& filePathString, + std::string& errorMessage +) { + auto filePath = std::filesystem::path{filePathString}; + if (!std::filesystem::exists(filePath) || !std::filesystem::is_regular_file(filePath)) { + errorMessage = fmt::format("File {} doesn't exists...", filePathString); + LERROR(errorMessage); + return false; + } + + std::vector byteStream; + + if (!readFile(filePath, byteStream, errorMessage)) { + LERROR(errorMessage); + return false; + } + + try { + module->_syncableFloatDataStorage.store(byteStream); + } + catch (const std::exception& e) { + errorMessage = fmt::format("Couldn't store loaded data in Software Integration storage", e.what()); + LERROR(errorMessage); + return false; + } + + return true; +} + +bool AssetHelper::saveSession(const std::string& wantedFileName, std::string& errorMessage) { + auto softwareIntegrationModule = global::moduleEngine->module(); + if (!softwareIntegrationModule) { + errorMessage = "Software Integration Module not found."; + return false; + } + + auto dirPath = absPath("${USER_ASSETS}") / wantedFileName; + if (std::filesystem::exists(dirPath)) { + errorMessage = fmt::format("A saved session with the name \"{}\" already exists.", dirPath.filename().string()); + return false; + } + + if (!std::filesystem::create_directory(dirPath)) { + errorMessage = fmt::format("Could not create the folder \"{}\" in the user assets folder.", dirPath); + return false; + } + + auto sessionDataFilePath = dirPath / std::filesystem::path{ dirPath.filename().string() + ".dat" }; + if ( + !saveSessionData( + softwareIntegrationModule->_syncableFloatDataStorage, + sessionDataFilePath, + errorMessage + ) + ) { + return false; + } + + auto filePath = dirPath / std::filesystem::path{ dirPath.filename().string() + ".asset" }; + std::ofstream assetFile; + try { + assetFile.open(filePath); + + assetFile << "local nodes = {\n"; + + auto identifiers = softwareIntegrationModule->_syncableFloatDataStorage.getAllIdentifiers(); + bool isFirstSgn = true; + for (auto& identifier : identifiers) { + auto r = renderable(identifier); + if (r == nullptr) continue; + + if (!isFirstSgn) { + assetFile << ",\n"; + } + else { + isFirstSgn = false; + } + + auto properties = r->properties(); + + assetFile << INDENT << "{\n" + << INDENT << INDENT << "GUI = {\n" + << INDENT << INDENT << INDENT << "Name = " << r->property("Name")->getStringValue() << ",\n" + << INDENT << INDENT << INDENT << "Path = \"/Software Integration\"" << "\n" + << INDENT << INDENT << "},\n" + << INDENT << INDENT << "Identifier = \"" << identifier << "\",\n" + << INDENT << INDENT << "Renderable = {\n" + << INDENT << INDENT << INDENT << "Identifier = \"" << identifier << "\",\n"; + + bool isFirstProp = true; + for (auto p : properties) { + if (!p) continue; + + // VOLATILE: This is because option (enum) properties does not play nice when parsed to string + // Either name them all with a name that includes "Option" or add the name here + if (p->identifier().find("Option") != std::string::npos) continue; + + if (!isFirstProp) { + assetFile << ",\n"; + } + else { + isFirstProp = false; + } + + assetFile << INDENT << INDENT << INDENT + << p->identifier() << " = "; + + std::string valueAsString = p->getStringValue(); + + if (std::string{ p->type().name() }.find("string") == std::string::npos) { + for( + auto pos = valueAsString.find('['); + pos != std::string::npos; + pos = valueAsString.find('[', ++pos) + ) { + valueAsString.replace(pos, 1, "{"); + valueAsString.insert(++pos, 1, ' '); + } + + for( + auto pos = valueAsString.find(']'); + pos != std::string::npos; + pos = valueAsString.find(']', ++pos) + ) { + valueAsString.replace(pos, 1, "}"); + valueAsString.insert(pos++, 1, ' '); + } + } + + assetFile << valueAsString; + } + + assetFile << '\n' << INDENT << INDENT << "}\n" + << INDENT << "}"; + } + + assetFile << "\n}\n\n" + << "local data = asset.localResource(\"" << sessionDataFilePath.filename().string() << "\")\n" + << "asset.onInitialize(function ()\n" + << INDENT << "openspace.softwareintegration.loadSessionData(data)\n" + << INDENT << "for _, node in ipairs(nodes) do\n" + << INDENT << INDENT << "openspace.addSceneGraphNode(node)\n" + << INDENT << "end\n" + << "end)\n\n" + << "asset.onDeinitialize(function ()\n" + << INDENT << "for i=1, #nodes do\n" + << INDENT << INDENT << "openspace.removeSceneGraphNode(nodes[#nodes + 1 - i].Identifier)\n" + << INDENT << "end\n" + << "end)\n\n" + << "asset.meta = {\n" + << INDENT << fmt::format("Name = \"{} (Software Integration session)\",", wantedFileName) + << "\n" + << INDENT << "Version = \"1.0\",\n" + << INDENT << "Description = [[]],\n" + << INDENT << "Author = \"\",\n" + << INDENT << "URL = \"\",\n" + << INDENT << "License = \"\"\n" + << "}\n"; + + assetFile.close(); + return true; + } + catch (std::ifstream::failure& err) { + errorMessage = fmt::format("An error occured when creating the asset file. {}.", err.what()); + return false; + } +} + +} // namespace openspace diff --git a/modules/softwareintegration/assethelper.h b/modules/softwareintegration/assethelper.h new file mode 100644 index 0000000000..55e4cd1d50 --- /dev/null +++ b/modules/softwareintegration/assethelper.h @@ -0,0 +1,46 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___ASSETHELPER___H__ +#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___ASSETHELPER___H__ + +namespace openspace { + +class SoftwareIntegrationModule; + +class AssetHelper { +public: + AssetHelper() = delete; + + static bool loadSessionData(SoftwareIntegrationModule* module, + const std::string& filePathString, + std::string& errorMessage); + + static bool saveSession(const std::string& wantedFileName, std::string& errorMessage); + +}; + +} // namespace openspace + +#endif // __OPENSPACE_MODULE_SOFTWAREINTEGRATION___ASSETHELPER___H__ diff --git a/modules/softwareintegration/include.cmake b/modules/softwareintegration/include.cmake index 98bffaa6bf..ffea0ac430 100644 --- a/modules/softwareintegration/include.cmake +++ b/modules/softwareintegration/include.cmake @@ -1,6 +1 @@ set(DEFAULT_MODULE ON) - -set(OPENSPACE_DEPENDENCIES - fitsfilereader - globebrowsing -) diff --git a/modules/softwareintegration/pointdatamessagehandler.cpp b/modules/softwareintegration/messagehandler.cpp similarity index 84% rename from modules/softwareintegration/pointdatamessagehandler.cpp rename to modules/softwareintegration/messagehandler.cpp index 5148761c60..dd2aea2758 100644 --- a/modules/softwareintegration/pointdatamessagehandler.cpp +++ b/modules/softwareintegration/messagehandler.cpp @@ -2,7 +2,7 @@ * * * OpenSpace * * * - * Copyright (c) 2014-2021 * + * Copyright (c) 2014-2022 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * @@ -22,7 +22,7 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include #include #include @@ -41,14 +41,188 @@ #include namespace { - constexpr const char* _loggerCat = "PDatMessHand"; + +constexpr const char* _loggerCat = "PDatMessHand"; + } // namespace -namespace openspace { +namespace openspace::softwareintegration::network { -using namespace softwareintegration; +// Anonymous namespace +namespace { -void PointDataMessageHandler::handlePointDataMessage(const std::vector& message, std::shared_ptr connection) { +CallbackMap callbacks{}; +std::mutex callbacksMutex{}; +size_t callbacksRetries{0}; + +const Renderable* getRenderable(const std::string& identifier) { + return renderable(identifier); +} + +void checkRenderable( + const std::vector& message, size_t& messageOffset, + std::shared_ptr connection, std::string& identifier +) { + std::string guiName; + + try { + // The following order of creating variables is the exact order they are received + // in the message. If the order is not the same, the global variable + // 'message offset' will be wrong + identifier = simp::readString(message, messageOffset); + guiName = simp::readString(message, messageOffset); + } + catch (const simp::SimpError& err) { + LERROR(fmt::format("Error when reading identifier and guiName from message: {}", err.message)); + return; + } + + connection->addSceneGraphNode(identifier); + + auto r = renderable(identifier); + bool hasCallbacks = false; + { + std::lock_guard guard(callbacksMutex); + hasCallbacks = callbacks.count(identifier) > 0; + } + if (!r && !hasCallbacks) { + LDEBUG(fmt::format("No renderable with identifier '{}' was found. Creating it.", identifier)); + + // Create a renderable, since it didn't exist + using namespace std::string_literals; + ghoul::Dictionary renderablePointsCloud; + renderablePointsCloud.setValue("Type", "RenderablePointsCloud"s); + renderablePointsCloud.setValue("Identifier", identifier); + renderablePointsCloud.setValue("Name", guiName); + + ghoul::Dictionary gui; + gui.setValue("Name", guiName); + gui.setValue("Path", "/Software Integration"s); + + ghoul::Dictionary node; + node.setValue("Identifier", identifier); + node.setValue("Renderable", renderablePointsCloud); + node.setValue("GUI", gui); + + global::scriptEngine->queueScript( + "openspace.addSceneGraphNode(" + ghoul::formatLua(node) + ")" + "openspace.setPropertyValueSingle('Modules.CefWebGui.Reload', nil)", // Reload WebGUI so that SoftwareIntegration GUI appears + scripting::ScriptEngine::RemoteScripting::Yes + ); + } +} + +void addCallback( + const std::string& identifier, + const Callback& newCallback +) { + std::lock_guard guard(callbacksMutex); + auto it = callbacks.find(identifier); + if (it == callbacks.end()) { + CallbackList newCallbackList{ newCallback }; + callbacks.emplace(identifier, newCallbackList); + } + else { + it->second.push_back(newCallback); + } +} + +void onFixedColorChange( + properties::Property* property, + const std::string& identifier, + std::shared_ptr connection +) { + if (!connection->isConnected()) { + connection->removePropertySubscription(property->identifier(), identifier); + return; + } + + // auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier()); + // if (!propertySubscription) return; + // if (!propertySubscription->shouldSendMessage) { + // propertySubscription->shouldSendMessage = true; + // return; + // } + + glm::vec4 color = std::any_cast(property->get()); + + const std::string message = simp::formatColorMessage(identifier, color); + connection->sendMessage(message); +} + +void onOpacityChange( + properties::Property* property, + const std::string& identifier, + std::shared_ptr connection +) { + if (!connection->isConnected()) { + connection->removePropertySubscription(property->identifier(), identifier); + return; + } + + // auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier()); + // if (!propertySubscription) return; + // if (!propertySubscription->shouldSendMessage) { + // propertySubscription->shouldSendMessage = true; + // return; + // } + + float value = std::any_cast(property->get()); + std::string hex_value = simp::floatToHex(value); + + const std::string message = simp::formatUpdateMessage(simp::MessageType::Opacity, identifier, hex_value); + connection->sendMessage(message); +} + +void onFixedPointSizeChange( + properties::Property* property, + const std::string& identifier, + std::shared_ptr connection +) { + if (!connection->isConnected()) { + connection->removePropertySubscription(property->identifier(), identifier); + return; + } + + // auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier()); + // if (!propertySubscription) return; + // if (!propertySubscription->shouldSendMessage) { + // propertySubscription->shouldSendMessage = true; + // return; + // } + + float value = std::any_cast(property->get()); + std::string hex_value = simp::floatToHex(value); + + const std::string message = simp::formatUpdateMessage(simp::MessageType::FixedSize, identifier, hex_value); + connection->sendMessage(message); +} + +void onVisibilityChange( + properties::Property* property, + const std::string& identifier, + std::shared_ptr connection +) { + if (!connection->isConnected()) { + connection->removePropertySubscription(property->identifier(), identifier); + return; + } + + // auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier()); + // if (!propertySubscription) return; + // if (!propertySubscription->shouldSendMessage) { + // propertySubscription->shouldSendMessage = true; + // return; + // } + + bool isVisible = std::any_cast(property->get()); + std::string_view visibilityFlag = isVisible ? "T" : "F"; + + const std::string message = simp::formatUpdateMessage(simp::MessageType::Visibility, identifier, visibilityFlag); + connection->sendMessage(message); +} + +void handlePointDataMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; @@ -86,7 +260,8 @@ void PointDataMessageHandler::handlePointDataMessage(const std::vector& me addCallback(identifier, { reanchorCallback, { storage::Key::DataPoints }, "reanchorCallback" }); } -void PointDataMessageHandler::handleVelocityDataMessage(const std::vector& message, std::shared_ptr connection) { +void handleVelocityDataMessage(const std::vector& message, std::shared_ptr connection) { + LWARNING(fmt::format("handleVelocityDataMessage()")); size_t messageOffset = 0; std::string identifier; @@ -125,8 +300,7 @@ void PointDataMessageHandler::handleVelocityDataMessage(const std::vector& } // Set units first to make sure they're available when converting during data loading - auto velocityUnitsCallback = [this, identifier, velocityDistanceUnitString, - velocityTimeUnitString, connection] { + auto velocityUnitsCallback = [identifier, velocityDistanceUnitString, velocityTimeUnitString, connection] { global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.VelocityDistanceUnit', \"{}\");", @@ -148,8 +322,8 @@ void PointDataMessageHandler::handleVelocityDataMessage(const std::vector& // Use the renderable identifier as the data key auto module = global::moduleEngine->module(); module->storeData(identifier, storage::Key::VelocityData, std::move(velocities)); - - auto velocityNaNModeCallback = [this, identifier, velocityNaNMode, connection] { + + auto velocityNaNModeCallback = [identifier, velocityNaNMode, connection] { global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.VelocityNaNMode', {});", @@ -195,7 +369,7 @@ void PointDataMessageHandler::handleVelocityDataMessage(const std::vector& ); } -void PointDataMessageHandler::handleFixedColorMessage(const std::vector& message, std::shared_ptr connection) { +void handleFixedColorMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; @@ -213,7 +387,7 @@ void PointDataMessageHandler::handleFixedColorMessage(const std::vector& m // Create weak_ptr, safer than shared_ptr for lambdas std::weak_ptr connWeakPtr{ connection }; - auto setFixedColorCallback = [this, identifier, color, connWeakPtr] { + auto setFixedColorCallback = [identifier, color, connWeakPtr] { // Get renderable auto r = getRenderable(identifier); if (!r) { @@ -258,7 +432,7 @@ void PointDataMessageHandler::handleFixedColorMessage(const std::vector& m addCallback(identifier, { setFixedColorCallback, {}, "setFixedColorCallback" }); // Create and set onChange for color - auto onChangeColorCallback = [this, identifier, connWeakPtr] { + auto onChangeColorCallback = [identifier, connWeakPtr] { // Get renderable auto r = getRenderable(identifier); if (!r) { @@ -273,7 +447,7 @@ void PointDataMessageHandler::handleFixedColorMessage(const std::vector& m properties::Property* colorProperty = r->property("Color"); if (!colorProperty || connWeakPtr.expired()) return; - auto updateColor = [this, colorProperty, identifier, connWeakPtr] { + auto updateColor = [colorProperty, identifier, connWeakPtr] { if (!colorProperty || connWeakPtr.expired()) return; onFixedColorChange(colorProperty, identifier, connWeakPtr.lock()); }; @@ -283,7 +457,7 @@ void PointDataMessageHandler::handleFixedColorMessage(const std::vector& m addCallback(identifier, { onChangeColorCallback, {}, "onChangeColorCallback" }); } -void PointDataMessageHandler::handleColormapMessage(const std::vector& message, std::shared_ptr connection) { +void handleColormapMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; @@ -325,9 +499,10 @@ void PointDataMessageHandler::handleColormapMessage(const std::vector& mes auto module = global::moduleEngine->module(); module->storeData(identifier, storage::Key::Colormap, std::move(colormap)); - auto colormapLimitsCallback = [this, identifier, min, max, connection] { + auto colormapLimitsCallback = [identifier, min, max, connection] { // Get renderable auto r = getRenderable(identifier); + if (!r) return; properties::Property* colormapMinProperty = r->property("ColormapMin"); // auto minPropertySub = connection->getPropertySubscription(identifier, colormapMinProperty->identifier()); @@ -364,10 +539,11 @@ void PointDataMessageHandler::handleColormapMessage(const std::vector& mes }; addCallback(identifier, { colormapLimitsCallback, {}, "colormapLimitsCallback" }); - auto colormapNaNModeCallback = [this, identifier, colormapNaNMode, colormapNaNColor, connection] { + auto colormapNaNModeCallback = [identifier, colormapNaNMode, colormapNaNColor, connection] { if (colormapNaNMode == simp::ColormapNaNRenderMode::FixedColor) { // Get renderable auto r = getRenderable(identifier); + if (!r) return; // Get colormapNaNColor of renderable properties::Property* colormapNaNColorProperty = r->property("ColormapNaNColor"); @@ -395,7 +571,7 @@ void PointDataMessageHandler::handleColormapMessage(const std::vector& mes }; addCallback(identifier, { colormapNaNModeCallback, {}, "colormapNaNModeCallback" }); - auto enableColormapCallback = [this, identifier] { + auto enableColormapCallback = [identifier] { global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});", @@ -409,7 +585,7 @@ void PointDataMessageHandler::handleColormapMessage(const std::vector& mes addCallback(identifier, { enableColormapCallback, std::move(dataToWaitFor), "enableColormapCallback" }); } -void PointDataMessageHandler::handleAttributeDataMessage(const std::vector& message, std::shared_ptr connection) { +void handleAttributeDataMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; @@ -449,7 +625,7 @@ void PointDataMessageHandler::handleAttributeDataMessage(const std::vector std::string callbackDescription = "handleAttributeDataMessage, key=" + storage::getStorageKeyString(key); switch (key) { case storage::Key::ColormapAttrData : { - auto callback = [this, identifier] { + auto callback = [identifier] { global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});", @@ -462,7 +638,7 @@ void PointDataMessageHandler::handleAttributeDataMessage(const std::vector break; } case storage::Key::LinearSizeAttrData: { - auto callback = [this, identifier] { + auto callback = [identifier] { global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeEnabled', {});", @@ -479,7 +655,7 @@ void PointDataMessageHandler::handleAttributeDataMessage(const std::vector } } -void PointDataMessageHandler::handleOpacityMessage(const std::vector& message, std::shared_ptr connection) { +void handleOpacityMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; @@ -497,7 +673,7 @@ void PointDataMessageHandler::handleOpacityMessage(const std::vector& mess // Create weak_ptr, safer than shared_ptr for lambdas std::weak_ptr connWeakPtr{ connection }; - auto setOpacityCallback = [this, identifier, opacity, connWeakPtr] { + auto setOpacityCallback = [identifier, opacity, connWeakPtr] { // Get renderable auto r = getRenderable(identifier); if (!r) { @@ -535,7 +711,7 @@ void PointDataMessageHandler::handleOpacityMessage(const std::vector& mess addCallback(identifier, { setOpacityCallback, {}, "setOpacityCallback" }); // Create and set onChange for opacity - auto onChangeOpacityCallback = [this, identifier, connWeakPtr] { + auto onChangeOpacityCallback = [identifier, connWeakPtr] { // Get renderable auto r = getRenderable(identifier); if (!r) { @@ -550,7 +726,7 @@ void PointDataMessageHandler::handleOpacityMessage(const std::vector& mess properties::Property* opacityProperty = r->property("Opacity"); if (!opacityProperty || connWeakPtr.expired()) return; - auto updateOpacity = [this, opacityProperty, identifier, connWeakPtr] { + auto updateOpacity = [opacityProperty, identifier, connWeakPtr] { if (!opacityProperty || connWeakPtr.expired()) return; onOpacityChange(opacityProperty, identifier, connWeakPtr.lock()); }; @@ -560,7 +736,7 @@ void PointDataMessageHandler::handleOpacityMessage(const std::vector& mess addCallback(identifier, { onChangeOpacityCallback, {}, "onChangeOpacityCallback" }); } -void PointDataMessageHandler::handleFixedPointSizeMessage(const std::vector& message, std::shared_ptr connection) { +void handleFixedPointSizeMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; @@ -578,7 +754,7 @@ void PointDataMessageHandler::handleFixedPointSizeMessage(const std::vector connWeakPtr{ connection }; - auto setFixedPointSizeCallback = [this, identifier, size, connWeakPtr] { + auto setFixedPointSizeCallback = [identifier, size, connWeakPtr] { // Get renderable auto r = getRenderable(identifier); if (!r) { @@ -626,7 +802,7 @@ void PointDataMessageHandler::handleFixedPointSizeMessage(const std::vectorproperty("Size"); if (!sizeProperty || connWeakPtr.expired()) return; - auto updateSize = [this, sizeProperty, identifier, connWeakPtr] { + auto updateSize = [sizeProperty, identifier, connWeakPtr] { if (!sizeProperty || connWeakPtr.expired()) return; onFixedPointSizeChange(sizeProperty, identifier, connWeakPtr.lock()); }; @@ -651,7 +827,7 @@ void PointDataMessageHandler::handleFixedPointSizeMessage(const std::vector& message, std::shared_ptr connection) { +void handleLinearPointSizeMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; @@ -670,9 +846,10 @@ void PointDataMessageHandler::handleLinearPointSizeMessage(const std::vectorproperty("Size"); @@ -716,7 +893,7 @@ void PointDataMessageHandler::handleLinearPointSizeMessage(const std::vectorqueueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeEnabled', {});", @@ -735,7 +912,7 @@ void PointDataMessageHandler::handleLinearPointSizeMessage(const std::vector& message, std::shared_ptr connection) { +void handleVisibilityMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; @@ -755,7 +932,7 @@ void PointDataMessageHandler::handleVisibilityMessage(const std::vector& m const bool visibility = visibilityMessage == "T"; - auto setVisibilityCallback = [this, identifier, visibility, connWeakPtr] { + auto setVisibilityCallback = [identifier, visibility, connWeakPtr] { // Get renderable auto r = getRenderable(identifier); if (!r) { @@ -796,7 +973,7 @@ void PointDataMessageHandler::handleVisibilityMessage(const std::vector& m addCallback(identifier, { setVisibilityCallback, {}, "setVisibilityCallback" }); // Create and set onChange for visibility - auto onChangeVisibilityCallback = [this, identifier, connWeakPtr] { + auto onChangeVisibilityCallback = [identifier, connWeakPtr] { // Get renderable auto r = getRenderable(identifier); if (!r) { @@ -811,7 +988,7 @@ void PointDataMessageHandler::handleVisibilityMessage(const std::vector& m properties::Property* visibilityProperty = r->property("Enabled"); if (!visibilityProperty || connWeakPtr.expired()) return; - auto toggleVisibility = [this, visibilityProperty, identifier, connWeakPtr] { + auto toggleVisibility = [visibilityProperty, identifier, connWeakPtr] { if (!visibilityProperty || connWeakPtr.expired()) return; onVisibilityChange(visibilityProperty, identifier, connWeakPtr.lock()); }; @@ -821,7 +998,7 @@ void PointDataMessageHandler::handleVisibilityMessage(const std::vector& m addCallback(identifier, { onChangeVisibilityCallback, {}, "onChangeVisibilityCallback" }); } -void PointDataMessageHandler::handleRemoveSGNMessage(const std::vector& message,std::shared_ptr connection) { +void handleRemoveSGNMessage(const std::vector& message,std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; @@ -854,12 +1031,95 @@ void PointDataMessageHandler::handleRemoveSGNMessage(const std::vector& me LDEBUG(fmt::format("Scene graph node '{}' removed.", identifier)); } -void PointDataMessageHandler::postSync() { - std::lock_guard guard(_onceNodeExistsCallbacksMutex); +} // namespace + +void handleMessage(IncomingMessage& incomingMessage) { + if(incomingMessage.connection.expired()) { + LDEBUG(fmt::format("Trying to handle message from disconnected peer. Aborting.")); + return; + } + + auto connectionPtr = incomingMessage.connection.lock(); + + const simp::MessageType messageType = incomingMessage.type; + std::vector& message = incomingMessage.content; + + switch (messageType) { + case simp::MessageType::Connection: { + LDEBUG(fmt::format("Message recieved... Connection: {}", connectionPtr->id())); + size_t offset = 0; + const std::string software = simp::readString(message, offset); + + // Send back message to software to complete handshake + connectionPtr->sendMessage(simp::formatConnectionMessage(software)); + LINFO(fmt::format("OpenSpace has connected with {} through socket", software)); + break; + } + case simp::MessageType::PointData: { + LDEBUG("Message recieved.. Point data"); + handlePointDataMessage(message, connectionPtr); + break; + } + case simp::MessageType::VelocityData: { + LDEBUG("Message recieved... Velocity data"); + handleVelocityDataMessage(message, connectionPtr); + break; + } + case simp::MessageType::RemoveSceneGraphNode: { + LDEBUG(fmt::format("Message recieved.. Remove SGN")); + handleRemoveSGNMessage(message, connectionPtr); + break; + } + case simp::MessageType::Color: { + LDEBUG(fmt::format("Message recieved.. New color")); + handleFixedColorMessage(message, connectionPtr); + break; + } + case simp::MessageType::Colormap: { + LDEBUG(fmt::format("Message recieved.. New colormap")); + handleColormapMessage(message, connectionPtr); + break; + } + case simp::MessageType::AttributeData: { + LDEBUG(fmt::format("Message recieved.. New attribute data")); + handleAttributeDataMessage(message, connectionPtr); + break; + } + case simp::MessageType::Opacity: { + LDEBUG(fmt::format("Message recieved.. New Opacity")); + handleOpacityMessage(message, connectionPtr); + break; + } + case simp::MessageType::FixedSize: { + LDEBUG(fmt::format("Message recieved.. New size")); + handleFixedPointSizeMessage(message, connectionPtr); + break; + } + case simp::MessageType::LinearSize: { + LDEBUG(fmt::format("Message recieved.. New linear size")); + handleLinearPointSizeMessage(message, connectionPtr); + break; + } + case simp::MessageType::Visibility: { + LDEBUG(fmt::format("Message recieved.. New visibility")); + handleVisibilityMessage(message, connectionPtr); + break; + } + default: { + LERROR(fmt::format( + "Unsupported message type: {}", incomingMessage.rawMessageType + )); + break; + } + } +} + +void postSyncCallbacks() { + std::lock_guard guard(callbacksMutex); // Check if the scene graph node has been created. // If so, call the corresponding callback functions to set up any subscriptions - auto callbackMapIt = _onceNodeExistsCallbacks.begin(); - while (callbackMapIt != _onceNodeExistsCallbacks.end()) { + auto callbackMapIt = callbacks.begin(); + while (callbackMapIt != callbacks.end()) { auto& [identifier, callbackList] = *callbackMapIt; try { @@ -891,193 +1151,19 @@ void PointDataMessageHandler::postSync() { } if (callbackList.empty()) { - callbackMapIt = _onceNodeExistsCallbacks.erase(callbackMapIt); - _onceNodeExistsCallbacksRetries = 0; + callbackMapIt = callbacks.erase(callbackMapIt); + callbacksRetries = 0; } else { callbackMapIt++; } } catch(std::exception &err) { - ++_onceNodeExistsCallbacksRetries; - ghoul_assert(_onceNodeExistsCallbacksRetries < 10, "Too many callback retries"); + ++callbacksRetries; + ghoul_assert(callbacksRetries < 10, "Too many callback retries"); LDEBUG(fmt::format("Error when trying to run callback: {}", err.what())); break; } } } -const Renderable* PointDataMessageHandler::getRenderable(const std::string& identifier) { - return renderable(identifier); -} - -void PointDataMessageHandler::checkRenderable( - const std::vector& message, size_t& messageOffset, - std::shared_ptr connection, std::string& identifier -) { - std::string guiName; - - try { - // The following order of creating variables is the exact order they are received - // in the message. If the order is not the same, the global variable - // 'message offset' will be wrong - identifier = simp::readString(message, messageOffset); - guiName = simp::readString(message, messageOffset); - } - catch (const simp::SimpError& err) { - LERROR(fmt::format("Error when reading identifier and guiName from message: {}", err.message)); - return; - } - - connection->addSceneGraphNode(identifier); - - const Renderable* r = renderable(identifier); - bool hasCallbacks = false; - { - std::lock_guard guard(_onceNodeExistsCallbacksMutex); - hasCallbacks = _onceNodeExistsCallbacks.count(identifier) > 0; - } - if (!r && !hasCallbacks) { - LDEBUG(fmt::format("No renderable with identifier '{}' was found. Creating it.", identifier)); - - // Create a renderable, since it didn't exist - using namespace std::string_literals; - ghoul::Dictionary renderablePointsCloud; - renderablePointsCloud.setValue("Type", "RenderablePointsCloud"s); - renderablePointsCloud.setValue("Identifier", identifier); - - ghoul::Dictionary gui; - gui.setValue("Name", guiName); - gui.setValue("Path", "/Software Integration"s); - - ghoul::Dictionary node; - node.setValue("Identifier", identifier); - node.setValue("Renderable", renderablePointsCloud); - node.setValue("GUI", gui); - - global::scriptEngine->queueScript( - "openspace.addSceneGraphNode(" + ghoul::formatLua(node) + ")" - "openspace.setPropertyValueSingle('Modules.CefWebGui.Reload', nil)", // Reload WebGUI so that SoftwareIntegration GUI appears - scripting::ScriptEngine::RemoteScripting::Yes - ); - } -} - -void PointDataMessageHandler::addCallback( - const std::string& identifier, - const Callback& newCallback -) { - std::lock_guard guard(_onceNodeExistsCallbacksMutex); - auto it = _onceNodeExistsCallbacks.find(identifier); - if (it == _onceNodeExistsCallbacks.end()) { - CallbackList newCallbackList{ newCallback }; - _onceNodeExistsCallbacks.emplace(identifier, newCallbackList); - } - else { - it->second.push_back(newCallback); - } -} - -void PointDataMessageHandler::onFixedColorChange( - properties::Property* property, - const std::string& identifier, - std::shared_ptr connection -) { - if (!connection->isConnected()) { - SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription( - connection, property->identifier(), identifier - ); - return; - } - - // auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier()); - // if (!propertySubscription) return; - // if (!propertySubscription->shouldSendMessage) { - // propertySubscription->shouldSendMessage = true; - // return; - // } - - glm::vec4 color = std::any_cast(property->get()); - - const std::string message = simp::formatColorMessage(identifier, color); - connection->sendMessage(message); -} - -void PointDataMessageHandler::onOpacityChange( - properties::Property* property, - const std::string& identifier, - std::shared_ptr connection -) { - if (!connection->isConnected()) { - SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription( - connection, property->identifier(), identifier - ); - return; - } - - // auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier()); - // if (!propertySubscription) return; - // if (!propertySubscription->shouldSendMessage) { - // propertySubscription->shouldSendMessage = true; - // return; - // } - - float value = std::any_cast(property->get()); - std::string hex_value = simp::floatToHex(value); - - const std::string message = simp::formatUpdateMessage(simp::MessageType::Opacity, identifier, hex_value); - connection->sendMessage(message); -} - -void PointDataMessageHandler::onFixedPointSizeChange( - properties::Property* property, - const std::string& identifier, - std::shared_ptr connection -) { - if (!connection->isConnected()) { - SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription( - connection, property->identifier(), identifier - ); - return; - } - - // auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier()); - // if (!propertySubscription) return; - // if (!propertySubscription->shouldSendMessage) { - // propertySubscription->shouldSendMessage = true; - // return; - // } - - float value = std::any_cast(property->get()); - std::string hex_value = simp::floatToHex(value); - - const std::string message = simp::formatUpdateMessage(simp::MessageType::FixedSize, identifier, hex_value); - connection->sendMessage(message); -} - -void PointDataMessageHandler::onVisibilityChange( - properties::Property* property, - const std::string& identifier, - std::shared_ptr connection -) { - if (!connection->isConnected()) { - SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription( - connection, property->identifier(), identifier - ); - return; - } - - // auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier()); - // if (!propertySubscription) return; - // if (!propertySubscription->shouldSendMessage) { - // propertySubscription->shouldSendMessage = true; - // return; - // } - - bool isVisible = std::any_cast(property->get()); - std::string_view visibilityFlag = isVisible ? "T" : "F"; - - const std::string message = simp::formatUpdateMessage(simp::MessageType::Visibility, identifier, visibilityFlag); - connection->sendMessage(message); -} - -} // namespace openspace +} // namespace openspace::softwareintegration::messagehandler diff --git a/modules/softwareintegration/messagehandler.h b/modules/softwareintegration/messagehandler.h new file mode 100644 index 0000000000..47daed4bb9 --- /dev/null +++ b/modules/softwareintegration/messagehandler.h @@ -0,0 +1,50 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___MESSAGEHANDLER___H__ +#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___MESSAGEHANDLER___H__ + +#include + +#include + +#include + +namespace openspace::softwareintegration::network { + +struct Callback { + std::function function; + std::vector waitForData = {}; + std::string description = "???"; // To help debugging. Maybe remove? +}; +using CallbackList = std::vector; +using CallbackMap = std::unordered_map; + +void postSyncCallbacks(); + +void handleMessage(IncomingMessage& incomingMessage); + +} // namespace openspace::softwareintegration::messagehandler + +#endif // __OPENSPACE_MODULE_SOFTWAREINTEGRATION___MESSAGEHANDLER___H__ diff --git a/modules/softwareintegration/network/network.cpp b/modules/softwareintegration/network/network.cpp new file mode 100644 index 0000000000..84645866f8 --- /dev/null +++ b/modules/softwareintegration/network/network.cpp @@ -0,0 +1,132 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace { + + constexpr const char* _loggerCat = "NetworkEngine"; + +} // namespace + +namespace openspace::softwareintegration::network { + +namespace { + +void eventLoop(std::weak_ptr networkStateWeakPtr) { + while (!networkStateWeakPtr.expired()) { + auto networkState = networkStateWeakPtr.lock(); + if (networkState->shouldStopThreads) break; + // The call to "pop" below will block execution + // on this thread until interrupt is called + try { + auto pm = networkState->incomingMessages.pop(); + handleMessage(pm); + } + catch (const ghoul::RuntimeError&) { + break; + } + } +} + +void serverLoop(std::weak_ptr networkStateWeakPtr) { + while (!networkStateWeakPtr.expired()) { + auto networkState = networkStateWeakPtr.lock(); + if (networkState->shouldStopThreads) break; + std::unique_ptr socket = networkState->server.awaitPendingTcpSocket(); + + if (!socket) return; + + socket->startStreams(); + + auto p = std::make_shared(std::move(socket)); + std::lock_guard guard(networkState->softwareConnectionsMutex); + auto [it, peerInserted] = networkState->softwareConnections.emplace(p->id(), std::move(p)); + + if (peerInserted) { + auto connectionWeak = std::weak_ptr{ it->second }; + auto thread = std::thread{ + [connectionWeak, networkStateWeakPtr] { + connection::eventLoop(connectionWeak, networkStateWeakPtr); + } + }; + it->second->setThread(thread); + } + } +} + +} // namespace + +std::shared_ptr serve(const int port) { + auto networkState = std::make_shared(); + + // 4700, is the defualt port where the tcp socket will be opened to the ext. software + networkState->server.listen(port); + + std::weak_ptr networkStateWeakPtr = networkState; + networkState->serverThread = std::thread{ [networkStateWeakPtr] { + serverLoop(networkStateWeakPtr); + } }; + + networkState->eventLoopThread = std::thread{ [networkStateWeakPtr] { + eventLoop(networkStateWeakPtr); + } }; + + return networkState; +}; + +void stopServer(std::shared_ptr networkState) { + networkState->shouldStopThreads = true; + + networkState->incomingMessages.interrupt(); + + networkState->server.close(); + + { + std::lock_guard guardSoftwareConnections(networkState->softwareConnectionsMutex); + networkState->softwareConnections.clear(); + } + + if (networkState->serverThread.joinable()) { + networkState->serverThread.join(); + } + if (networkState->eventLoopThread.joinable()) { + networkState->eventLoopThread.join(); + } +} + +SoftwareConnectionLostError::SoftwareConnectionLostError(const std::string& msg) + : ghoul::RuntimeError(fmt::format("{}{}", "Software connection lost", msg), "SoftwareConnection") +{} + +} // namespace openspace::softwareintegration::network diff --git a/modules/softwareintegration/network/networkengine.h b/modules/softwareintegration/network/network.h similarity index 69% rename from modules/softwareintegration/network/networkengine.h rename to modules/softwareintegration/network/network.h index 7e68b14dd8..13546c0616 100644 --- a/modules/softwareintegration/network/networkengine.h +++ b/modules/softwareintegration/network/network.h @@ -26,54 +26,45 @@ #define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___NETWORKENGINE___H__ #include -#include -#include #include #include +#include -namespace openspace { +#include +#include -class NetworkEngine { +namespace openspace::softwareintegration::network { + +class SoftwareConnectionLostError : public ghoul::RuntimeError { public: - NetworkEngine(const int port = 4700); - ~NetworkEngine(); - - struct IncomingMessage { - size_t connection_id; - SoftwareConnection::Message message{ softwareintegration::simp::MessageType::Unknown }; - }; - - void start(); - void stop(); - void postSync(); - -private: - void handleNewSoftwareConnections(); - void handleIncomingMessage(IncomingMessage incomingMessage); - void peerEventLoop(size_t connection_id); - void eventLoop(); - - // The destuction of the object a shared_ptr is pointing to, occurs when the pointer no longer has any owners - std::shared_ptr getSoftwareConnection(size_t id); - - std::unordered_map> _softwareConnections; - std::mutex _softwareConnectionsMutex; - - ghoul::io::TcpSocketServer _socketServer; - std::thread _serverThread; - std::atomic_bool _shouldStopServerThread = false; - std::thread _eventLoopThread; - std::atomic_bool _shouldStopEventThread = false; - - - const int _port; - - // Message handlers - PointDataMessageHandler _pointDataMessageHandler; - - InterruptibleConcurrentQueue _incomingMessages; + explicit SoftwareConnectionLostError(const std::string& msg); }; -} // namespace openspace +struct IncomingMessage { + std::weak_ptr connection; + softwareintegration::simp::MessageType type{ softwareintegration::simp::MessageType::Unknown }; + std::vector content{}; + std::string rawMessageType{""}; +}; + +struct NetworkState { + ghoul::io::TcpSocketServer server; + + std::thread serverThread; + std::thread eventLoopThread; + + std::unordered_map> softwareConnections{}; + std::mutex softwareConnectionsMutex{}; + + std::atomic_bool shouldStopThreads{ false }; + + InterruptibleConcurrentQueue incomingMessages{}; +}; + +std::shared_ptr serve(const int port = 4700); + +void stopServer(std::shared_ptr networkState); + +} // namespace openspace::softwareintegration::network #endif // __OPENSPACE_MODULE_SOFTWAREINTEGRATION___NETWORKENGINE___H__ diff --git a/modules/softwareintegration/network/networkengine.cpp b/modules/softwareintegration/network/networkengine.cpp deleted file mode 100644 index 9cade135c5..0000000000 --- a/modules/softwareintegration/network/networkengine.cpp +++ /dev/null @@ -1,245 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2022 * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this * - * software and associated documentation files (the "Software"), to deal in the Software * - * without restriction, including without limitation the rights to use, copy, modify, * - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * - * permit persons to whom the Software is furnished to do so, subject to the following * - * conditions: * - * * - * The above copyright notice and this permission notice shall be included in all copies * - * or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * - * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - ****************************************************************************************/ - -#include - -#include -#include -#include -#include -#include - - -namespace { - constexpr const char* _loggerCat = "NetworkEngine"; -} // namespace - -namespace openspace { - -using namespace softwareintegration; - -NetworkEngine::NetworkEngine(const int port) - : _port{port} -{} - -NetworkEngine::~NetworkEngine() { - stop(); -} - -void NetworkEngine::start() { - _socketServer.listen(_port); - - _serverThread = std::thread([this]() { handleNewSoftwareConnections(); }); - _eventLoopThread = std::thread([this]() { eventLoop(); }); -} - -void NetworkEngine::stop() { - _shouldStopServerThread = true; - - { - std::lock_guard guardSoftwareConnections(_softwareConnectionsMutex); - for (auto& [id, connectionPtr] : _softwareConnections) { - SoftwareConnection::NetworkEngineFriends::stopThread(connectionPtr); - } - } - - _incomingMessages.interrupt(); - - _shouldStopEventThread = true; - _socketServer.close(); - _softwareConnections.clear(); - - if (_serverThread.joinable()) { - _serverThread.join(); - } - if (_eventLoopThread.joinable()) { - _eventLoopThread.join(); - } -} - -void NetworkEngine::postSync() { - _pointDataMessageHandler.postSync(); -} - -void NetworkEngine::handleNewSoftwareConnections() { - while (!_shouldStopServerThread) { - std::unique_ptr socket = _socketServer.awaitPendingTcpSocket(); - - if (!socket) return; - - socket->startStreams(); - - auto p = std::make_shared(std::move(socket)); - std::lock_guard guard(_softwareConnectionsMutex); - auto [it, peerInserted] = _softwareConnections.emplace(p->id(), p); - - if (peerInserted) { - auto& connectionPtr = it->second; - auto thread = std::thread{ - [this, &connectionPtr] { - peerEventLoop(connectionPtr->id()); - } - }; - connectionPtr->setThread(thread); - } - } -} - -void NetworkEngine::peerEventLoop(size_t connection_id) { - using namespace std::literals::chrono_literals; - auto connectionPtr = getSoftwareConnection(connection_id); - - while (!connectionPtr->shouldStopThread()) { - try { - SoftwareConnection::Message m = connectionPtr->receiveMessageFromSoftware(); - _incomingMessages.push({ connection_id, m }); - } - catch (const SoftwareConnection::SoftwareConnectionLostError& err) { - if (connectionPtr->shouldStopThread()) break; - - if (connectionPtr && (!connectionPtr->shouldStopThread() || !connectionPtr->isConnectedOrConnecting())) { - LDEBUG(fmt::format("Connection lost to {}: {}", connection_id, err.message)); - _incomingMessages.push({ - connection_id, - SoftwareConnection::Message{ simp::MessageType::InternalDisconnection } - }); - } - break; - } - } -} - -void NetworkEngine::eventLoop() { - while (!_shouldStopEventThread) { - // The call to "pop" below will block execution - // on this thread until interrupt is called - try { - auto pm = _incomingMessages.pop(); - handleIncomingMessage(pm); - } - catch (const ghoul::RuntimeError&) { - break; - } - } -} - -std::shared_ptr NetworkEngine::getSoftwareConnection(size_t id) { - std::lock_guard guard(_softwareConnectionsMutex); - auto it = _softwareConnections.find(id); - if (it == _softwareConnections.end()) return nullptr; - return it->second; -} - -void NetworkEngine::handleIncomingMessage(IncomingMessage incomingMessage) { - auto connectionPtr = getSoftwareConnection(incomingMessage.connection_id); - - if(!connectionPtr) { - LDEBUG(fmt::format("Trying to handle message from disconnected peer. Aborting.")); - return; - } - - const simp::MessageType messageType = incomingMessage.message.type; - std::vector& message = incomingMessage.message.content; - - switch (messageType) { - case simp::MessageType::Connection: { - LDEBUG(fmt::format("Message recieved... Connection: {}", incomingMessage.connection_id)); - size_t offset = 0; - const std::string software = simp::readString(message, offset); - - // Send back message to software to complete handshake - connectionPtr->sendMessage(simp::formatConnectionMessage(software)); - LINFO(fmt::format("OpenSpace has connected with {} through socket", software)); - break; - } - case simp::MessageType::PointData: { - LDEBUG("Message recieved... Point data"); - _pointDataMessageHandler.handlePointDataMessage(message, connectionPtr); - break; - } - case simp::MessageType::VelocityData: { - LDEBUG("Message recieved... Velocity data"); - _pointDataMessageHandler.handleVelocityDataMessage(message, connectionPtr); - break; - } - case simp::MessageType::RemoveSceneGraphNode: { - LDEBUG(fmt::format("Message recieved... Remove SGN")); - _pointDataMessageHandler.handleRemoveSGNMessage(message, connectionPtr); - break; - } - case simp::MessageType::Color: { - LDEBUG(fmt::format("Message recieved... Color")); - _pointDataMessageHandler.handleFixedColorMessage(message, connectionPtr); - break; - } - case simp::MessageType::Colormap: { - LDEBUG(fmt::format("Message recieved... Colormap")); - _pointDataMessageHandler.handleColormapMessage(message, connectionPtr); - break; - } - case simp::MessageType::AttributeData: { - LDEBUG(fmt::format("Message recieved... Attribute data")); - _pointDataMessageHandler.handleAttributeDataMessage(message, connectionPtr); - break; - } - case simp::MessageType::Opacity: { - LDEBUG(fmt::format("Message recieved... Opacity")); - _pointDataMessageHandler.handleOpacityMessage(message, connectionPtr); - break; - } - case simp::MessageType::FixedSize: { - LDEBUG(fmt::format("Message recieved... Size")); - _pointDataMessageHandler.handleFixedPointSizeMessage(message, connectionPtr); - break; - } - case simp::MessageType::LinearSize: { - LDEBUG(fmt::format("Message recieved... Linear size")); - _pointDataMessageHandler.handleLinearPointSizeMessage(message, connectionPtr); - break; - } - case simp::MessageType::Visibility: { - LDEBUG(fmt::format("Message recieved... Visibility")); - _pointDataMessageHandler.handleVisibilityMessage(message, connectionPtr); - break; - } - case simp::MessageType::InternalDisconnection: { - LDEBUG(fmt::format("Message recieved... Disconnection from software connection: {}", incomingMessage.connection_id)); - std::lock_guard guard(_softwareConnectionsMutex); - SoftwareConnection::NetworkEngineFriends::stopThread(connectionPtr); - - if (_softwareConnections.count(incomingMessage.connection_id)) { - _softwareConnections.erase(incomingMessage.connection_id); - } - break; - } - default: { - LERROR(fmt::format( - "Unsupported message type: {}", incomingMessage.message.rawMessageType - )); - break; - } - } -} - -} // namespace openspace diff --git a/modules/softwareintegration/network/softwareconnection.cpp b/modules/softwareintegration/network/softwareconnection.cpp index d3d7d9f87a..6a06f76231 100644 --- a/modules/softwareintegration/network/softwareconnection.cpp +++ b/modules/softwareintegration/network/softwareconnection.cpp @@ -2,7 +2,7 @@ * * * OpenSpace * * * - * Copyright (c) 2014-2021 * + * Copyright (c) 2014-2022 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -39,10 +40,6 @@ namespace openspace { std::atomic_size_t SoftwareConnection::_nextConnectionId = 1; -SoftwareConnection::SoftwareConnectionLostError::SoftwareConnectionLostError(const std::string& msg) - : ghoul::RuntimeError(fmt::format("{}{}", "Software connection lost", msg), "SoftwareConnection") -{} - SoftwareConnection::SoftwareConnection(std::unique_ptr socket) : _id{ _nextConnectionId++ }, _socket{ std::move(socket) }, _sceneGraphNodes{}, _thread{}, _shouldStopThread{ false } @@ -52,7 +49,7 @@ SoftwareConnection::SoftwareConnection(std::unique_ptr soc SoftwareConnection::SoftwareConnection(SoftwareConnection&& sc) : _id{ std::move(sc._id) }, _socket{ std::move(sc._socket) }, - _isConnected{ sc._isConnected }, _sceneGraphNodes{ std::move(sc._sceneGraphNodes) }, + _sceneGraphNodes{ std::move(sc._sceneGraphNodes) }, _thread{}, _shouldStopThread{ false } {} @@ -61,16 +58,13 @@ SoftwareConnection::~SoftwareConnection() { // destructor is called when disconnecting external // since NetworkEngine and MessageHandler has // shared_ptrs to SoftwareConnection, which can cause - // bugs if not handled properly. + // bugs if not handled properly. // Tips: use weak_ptr instead of shared_ptr in callbacks. LDEBUG(fmt::format("Removing software connection {}", _id)); - if (!_isConnected) return; - _isConnected = false; - - if (_socket) { - _socket->disconnect(); - } + _shouldStopThread = true; + _thread.detach(); + disconnect(); } void SoftwareConnection::addPropertySubscription( @@ -199,8 +193,7 @@ void SoftwareConnection::removePropertySubscriptions(const std::string& identifi _subscribedProperties.erase(propertySubscriptions); } -void SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription( - std::shared_ptr connectionPtr, +void SoftwareConnection::removePropertySubscription( const std::string& propertyName, const std::string& identifier ) { @@ -224,8 +217,8 @@ void SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscript auto property = r->property(propertyName); - auto propertySubscriptions = connectionPtr->_subscribedProperties.find(identifier); - if (propertySubscriptions != connectionPtr->_subscribedProperties.end()) { + auto propertySubscriptions = _subscribedProperties.find(identifier); + if (propertySubscriptions != _subscribedProperties.end()) { // At least one property have been subscribed to on this SGN auto propertySubscription = propertySubscriptions->second.find(propertyName); if (propertySubscription != propertySubscriptions->second.end()) { @@ -246,11 +239,11 @@ void SoftwareConnection::disconnect() { } bool SoftwareConnection::isConnected() const { - return _isConnected && _socket && _socket->isConnected(); + return _socket && _socket->isConnected(); } bool SoftwareConnection::isConnectedOrConnecting() const { - return _isConnected && _socket && (_socket->isConnected() || _socket->isConnecting()); + return _socket && (_socket->isConnected() || _socket->isConnecting()); } bool SoftwareConnection::sendMessage(const std::string& message) { @@ -285,7 +278,51 @@ void SoftwareConnection::removeSceneGraphNode(const std::string& identifier) { } } -SoftwareConnection::Message SoftwareConnection::receiveMessageFromSoftware() { +size_t SoftwareConnection::id() { + return _id; +} + +void SoftwareConnection::setThread(std::thread& t) { + _thread = std::move(t); +} + +ghoul::io::TcpSocket* SoftwareConnection::socket() { + return _socket.get(); +} + +namespace softwareintegration::network::connection { + +void eventLoop( + std::weak_ptr connectionWeakPtr, + std::weak_ptr networkStateWeakPtr +) { + while (!connectionWeakPtr.expired()) { + auto connectionPtr = connectionWeakPtr.lock(); + if (connectionPtr->_shouldStopThread) break; + + try { + IncomingMessage m = receiveMessageFromSoftware(connectionPtr); + if (networkStateWeakPtr.expired()) break; + networkStateWeakPtr.lock()->incomingMessages.push(m); + } + catch (const SoftwareConnectionLostError& err) { + if (!networkStateWeakPtr.expired() + & (!connectionPtr->_shouldStopThread || !connectionPtr->isConnectedOrConnecting()) + ) { + LDEBUG(fmt::format("Connection lost to {}: {}", connectionPtr->id(), err.message)); + auto networkState = networkStateWeakPtr.lock(); + if (networkState->softwareConnections.count(connectionPtr->id())) { + networkState->softwareConnections.erase(connectionPtr->id()); + } + } + break; + } + } +} + +IncomingMessage receiveMessageFromSoftware( + std::shared_ptr connectionPtr +) { // Header consists of version (3 char), message type (4 char) & subject size (15 char) size_t headerSize = 22 * sizeof(char); @@ -294,7 +331,7 @@ SoftwareConnection::Message SoftwareConnection::receiveMessageFromSoftware() { std::vector subjectBuffer; // Receive the header data - if (!_socket->get(headerBuffer.data(), headerSize)) { + if (!connectionPtr->socket()->get(headerBuffer.data(), headerSize)) { throw SoftwareConnectionLostError("Failed to read header from socket. Disconnecting."); } @@ -329,34 +366,16 @@ SoftwareConnection::Message SoftwareConnection::receiveMessageFromSoftware() { auto typeEnum = softwareintegration::simp::getMessageType(type); // Receive the message data - if (typeEnum != softwareintegration::simp::MessageType::InternalDisconnection && typeEnum != softwareintegration::simp::MessageType::Unknown) { + if (typeEnum != softwareintegration::simp::MessageType::Unknown) { subjectBuffer.resize(subjectSize); - if (!_socket->get(subjectBuffer.data(), subjectSize)) { + if (!connectionPtr->socket()->get(subjectBuffer.data(), subjectSize)) { throw SoftwareConnectionLostError("Failed to read message from socket. Disconnecting."); } } - return Message{ typeEnum, subjectBuffer, type }; + return { connectionPtr, typeEnum, subjectBuffer, type }; } -bool SoftwareConnection::shouldStopThread() { - return _shouldStopThread; -} - -size_t SoftwareConnection::id() { - return _id; -} - -void SoftwareConnection::setThread(std::thread& t) { - _thread = std::move(t); -} - -void SoftwareConnection::NetworkEngineFriends::stopThread(std::shared_ptr connectionPtr) { - connectionPtr->_shouldStopThread = true; - connectionPtr->disconnect(); - if (connectionPtr->_thread.joinable()) { - connectionPtr->_thread.join(); - } -} +} // namespace softwareintegration::network::connection } // namespace openspace diff --git a/modules/softwareintegration/network/softwareconnection.h b/modules/softwareintegration/network/softwareconnection.h index 4de782f99a..dee240acd7 100644 --- a/modules/softwareintegration/network/softwareconnection.h +++ b/modules/softwareintegration/network/softwareconnection.h @@ -2,7 +2,7 @@ * * * OpenSpace * * * - * Copyright (c) 2014-2021 * + * Copyright (c) 2014-2022 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * @@ -25,8 +25,8 @@ #ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SOFTWARECONNECTION___H__ #define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SOFTWARECONNECTION___H__ -#include #include +#include #include #include @@ -36,8 +36,30 @@ namespace openspace { class Renderable; +class SoftwareConnection; + +namespace softwareintegration::network { + +struct NetworkState; +struct IncomingMessage; + +namespace connection { + void eventLoop( + std::weak_ptr connectionWeakPtr, + std::weak_ptr networkStateWeakPtr + ); + + IncomingMessage receiveMessageFromSoftware( + std::shared_ptr connectionPtr + ); +} // namespace connection + +} // namespace softwareintegration::network + +using namespace softwareintegration::network; class SoftwareConnection { + public: using OnChangeHandle = properties::Property::OnChangeHandle; struct PropertySubscription { @@ -49,14 +71,6 @@ public: using Identifier = std::string; using SubscribedProperties = std::unordered_map; - struct Message { - softwareintegration::simp::MessageType type; - std::vector content{}; - std::string rawMessageType{""}; - }; - - class SoftwareConnectionLostError; - explicit SoftwareConnection(std::unique_ptr socket); SoftwareConnection(SoftwareConnection&& p); ~SoftwareConnection(); @@ -76,40 +90,33 @@ public: // const std::string& identifier // ); - SoftwareConnection::Message receiveMessageFromSoftware(); - void addSceneGraphNode(const std::string& identifier); void removeSceneGraphNode(const std::string& identifier); size_t id(); - size_t nConnections(); void setThread(std::thread& t); - bool shouldStopThread(); - class NetworkEngineFriends { - private: - static void stopThread(std::shared_ptr connectionPtr); - friend class NetworkEngine; - }; + friend void connection::eventLoop( + std::weak_ptr connectionWeakPtr, + std::weak_ptr networkStateWeakPtr + ); - class PointDataMessageHandlerFriends { - private: - static void removePropertySubscription( - std::shared_ptr connectionPtr, - const std::string& propertyName, - const std::string& identifier - ); - friend class PointDataMessageHandler; - }; + friend IncomingMessage connection::receiveMessageFromSoftware( + std::shared_ptr connectionPtr + ); + + void removePropertySubscription(const std::string& propertyName, const std::string& identifier); + + void removePropertySubscriptions(const std::string& identifier); + + ghoul::io::TcpSocket* socket(); private: - void removePropertySubscriptions(const std::string& identifier); SubscribedProperties _subscribedProperties; std::unordered_set _sceneGraphNodes; - bool _isConnected = true; std::unique_ptr _socket; size_t _id; @@ -119,11 +126,6 @@ private: static std::atomic_size_t _nextConnectionId; }; -class SoftwareConnection::SoftwareConnectionLostError : public ghoul::RuntimeError { -public: - explicit SoftwareConnectionLostError(const std::string& msg); -}; - } // namespace openspace #endif // __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SOFTWAREINTEGRATIONMODULE___H__ diff --git a/modules/softwareintegration/pointdatamessagehandler.h b/modules/softwareintegration/pointdatamessagehandler.h deleted file mode 100644 index f98c6ae82b..0000000000 --- a/modules/softwareintegration/pointdatamessagehandler.h +++ /dev/null @@ -1,101 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2021 * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this * - * software and associated documentation files (the "Software"), to deal in the Software * - * without restriction, including without limitation the rights to use, copy, modify, * - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * - * permit persons to whom the Software is furnished to do so, subject to the following * - * conditions: * - * * - * The above copyright notice and this permission notice shall be included in all copies * - * or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * - * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - ****************************************************************************************/ - -#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___POINTDATAMESSAGEHANDLER___H__ -#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___POINTDATAMESSAGEHANDLER___H__ - -#include - -#include - -#include - -namespace openspace { - -class Renderable; - -class PointDataMessageHandler { - struct Callback { - std::function function; - std::vector waitForData = {}; - std::string description = "???"; // To help debugging. Maybe remove? - }; - using CallbackList = std::vector; - using CallbackMap = std::unordered_map; - -public: - void handlePointDataMessage(const std::vector& message, std::shared_ptr connection); - void handleVelocityDataMessage(const std::vector& message, std::shared_ptr connection); - void handleFixedColorMessage(const std::vector& message, std::shared_ptr connection); - void handleColormapMessage(const std::vector& message, std::shared_ptr connection); - void handleAttributeDataMessage(const std::vector& message, std::shared_ptr connection); - void handleOpacityMessage(const std::vector& message, std::shared_ptr connection); - void handleFixedPointSizeMessage(const std::vector& message, std::shared_ptr connection); - void handleLinearPointSizeMessage(const std::vector& message, std::shared_ptr connection); - void handleVisibilityMessage(const std::vector& message, std::shared_ptr connection); - void handleRemoveSGNMessage(const std::vector& message, std::shared_ptr connection); - - void postSync(); - -private: - const Renderable* getRenderable(const std::string& identifier); - void checkRenderable( - const std::vector& message, size_t& messageOffset, - std::shared_ptr connection, std::string& identifier - ); - - void addCallback( - const std::string& identifier, - const Callback& newCallback - ); - - CallbackMap _onceNodeExistsCallbacks; - std::mutex _onceNodeExistsCallbacksMutex; - size_t _onceNodeExistsCallbacksRetries{0}; - - void onFixedColorChange( - properties::Property* property, - const std::string& identifier, - std::shared_ptr connection - ); - void onOpacityChange( - properties::Property* property, - const std::string& identifier, - std::shared_ptr connection - ); - void onFixedPointSizeChange( - properties::Property* property, - const std::string& identifier, - std::shared_ptr connection - ); - void onVisibilityChange( - properties::Property* property, - const std::string& identifier, - std::shared_ptr connection - ); -}; - -} // namespace openspace - -#endif // __OPENSPACE_MODULE_SOFTWAREINTEGRATION___POINTDATAMESSAGEHANDLER___H__ diff --git a/modules/softwareintegration/rendering/renderablepointscloud.cpp b/modules/softwareintegration/rendering/renderablepointscloud.cpp index baa89f9492..373e180a22 100644 --- a/modules/softwareintegration/rendering/renderablepointscloud.cpp +++ b/modules/softwareintegration/rendering/renderablepointscloud.cpp @@ -2,7 +2,7 @@ * * * OpenSpace * * * - * Copyright (c) 2014-2021 * + * Copyright (c) 2014-2022 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * @@ -158,6 +158,12 @@ namespace { "Boolean to determine whether to use motion or not." }; + constexpr openspace::properties::Property::PropertyInfo NameInfo = { + "Name", + "Name", + "The name of the points cloud" + }; + struct [[codegen::Dictionary(RenderablePointsCloud)]] Parameters { // [[codegen::verbatim(ColorInfo.description)]] std::optional color; @@ -207,6 +213,9 @@ namespace { // [[codegen::verbatim(MotionEnabledInfo.description)]] std::optional motionEnabled; + + // [[codegen::verbatim(NameInfo.description)]] + std::optional name; enum class SizeOption : uint32_t { Uniform, @@ -242,11 +251,16 @@ RenderablePointsCloud::RenderablePointsCloud(const ghoul::Dictionary& dictionary , _velocityDistanceUnit(VelocityDistanceUnitInfo, "") , _velocityTimeUnit(VelocityTimeUnitInfo, "") , _velocityNaNMode(VelocityNaNModeInfo) + , _name(NameInfo) , _motionEnabled(MotionEnabledInfo, false) { const Parameters p = codegen::bake(dictionary); _identifier = p.identifier.value(); + + _name = p.name.value_or(_name); + _name.setVisibility(properties::Property::Visibility::Hidden); + addProperty(_name); _color = p.color.value_or(_color); _color.setViewOption(properties::Property::ViewOptions::Color); diff --git a/modules/softwareintegration/rendering/renderablepointscloud.h b/modules/softwareintegration/rendering/renderablepointscloud.h index 16ad6836fb..b1606b7780 100644 --- a/modules/softwareintegration/rendering/renderablepointscloud.h +++ b/modules/softwareintegration/rendering/renderablepointscloud.h @@ -2,7 +2,7 @@ * * * OpenSpace * * * - * Copyright (c) 2014-2021 * + * Copyright (c) 2014-2022 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * @@ -62,7 +62,7 @@ public: static documentation::Documentation Documentation(); -protected: +private: void loadData(SoftwareIntegrationModule* softwareIntegrationModule); void loadColormap(SoftwareIntegrationModule* softwareIntegrationModule); void loadColormapAttributeData(SoftwareIntegrationModule* softwareIntegrationModule); @@ -87,6 +87,7 @@ protected: linearSizeEnabled, velocityNaNMode, motionEnabled, time ) _uniformCache; + properties::StringProperty _name; properties::FloatProperty _size; properties::Vec4Property _color; properties::OptionProperty _sizeOption; @@ -106,6 +107,7 @@ protected: properties::StringProperty _velocityTimeUnit; properties::IntProperty _velocityNaNMode; + std::optional _identifier = std::nullopt; bool _hasLoadedColormapAttributeData = false; diff --git a/modules/softwareintegration/softwareintegrationmodule.cpp b/modules/softwareintegration/softwareintegrationmodule.cpp index f1baab073d..e0d295aa7a 100644 --- a/modules/softwareintegration/softwareintegrationmodule.cpp +++ b/modules/softwareintegration/softwareintegrationmodule.cpp @@ -2,7 +2,7 @@ * * * OpenSpace * * * - * Copyright (c) 2014-2021 * + * Copyright (c) 2014-2022 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * @@ -24,53 +24,55 @@ #include +#include #include #include #include +#include +#include #include #include #include #include -#include #include +#include +#include + +#include "softwareintegrationmodule_lua.inl" namespace { - constexpr const char* _loggerCat = "SoftwareIntegrationModule"; -} // namespace + +constexpr const char* _loggerCat = "SoftwareIntegrationModule"; + +} // namespace namespace openspace { -SoftwareIntegrationModule::SoftwareIntegrationModule() : OpenSpaceModule(Name) { - if (global::windowDelegate->isMaster()) { - // The Master node will handle all communication with the external software - // and forward it to the Client nodes - // 4700, is the defualt port where the tcp socket will be opened to the ext. software - _networkEngine = std::make_unique(); - } -} +SoftwareIntegrationModule::SoftwareIntegrationModule() + : OpenSpaceModule(Name) +{} SoftwareIntegrationModule::~SoftwareIntegrationModule() { internalDeinitialize(); } void SoftwareIntegrationModule::storeData( - const SyncableFloatDataStorage::Identifier& identifier, - const storage::Key key, - const std::vector& data + const SyncableFloatDataStorage::Identifier& identifier, + const storage::Key key, const std::vector& data ) { _syncableFloatDataStorage.store(identifier, key, data); } const std::vector& SoftwareIntegrationModule::fetchData( - const SyncableFloatDataStorage::Identifier& identifier, - const storage::Key key + const SyncableFloatDataStorage::Identifier& identifier, + const storage::Key key ) { return _syncableFloatDataStorage.fetch(identifier, key); } bool SoftwareIntegrationModule::isDataDirty( - const SyncableFloatDataStorage::Identifier& identifier, - const storage::Key key + const SyncableFloatDataStorage::Identifier& identifier, + const storage::Key key ) { return _syncableFloatDataStorage.isDirty(identifier, key); } @@ -97,17 +99,21 @@ void SoftwareIntegrationModule::internalInitialize(const ghoul::Dictionary&) { fRenderable->registerClass("RenderablePointsCloud"); if (global::windowDelegate->isMaster()) { - _networkEngine->start(); + // The Master node will handle all communication with the external software + // and forward it to the Client nodes + _networkState = softwareintegration::network::serve(); global::callback::postSyncPreDraw->emplace_back([this]() { - if (!_networkEngine) return; - _networkEngine->postSync(); + softwareintegration::network::postSyncCallbacks(); }); } } void SoftwareIntegrationModule::internalDeinitialize() { global::syncEngine->removeSyncables(getSyncables()); + if (_networkState) { + softwareintegration::network::stopServer(_networkState); + } } std::vector @@ -122,10 +128,16 @@ std::vector SoftwareIntegrationModule::getSyncables() { return { &_syncableFloatDataStorage }; } - -// Helper function for debugging +// Helper function for debugging std::string SoftwareIntegrationModule::getStringOfAllKeysInStorage() { - return _syncableFloatDataStorage.getStringOfAllKeysInStorage(); + return _syncableFloatDataStorage.getStringOfAllKeysInStorage(); } -} // namespace openspace +scripting::LuaLibrary SoftwareIntegrationModule::luaLibrary() const { + return { + "softwareintegration", + { codegen::lua::LoadSessionData, codegen::lua::SaveSession } + }; +} + +} // namespace openspace diff --git a/modules/softwareintegration/softwareintegrationmodule.h b/modules/softwareintegration/softwareintegrationmodule.h index 3faae838fd..bbce1472b6 100644 --- a/modules/softwareintegration/softwareintegrationmodule.h +++ b/modules/softwareintegration/softwareintegrationmodule.h @@ -2,7 +2,7 @@ * * * OpenSpace * * * - * Copyright (c) 2014-2021 * + * Copyright (c) 2014-2022 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * @@ -26,14 +26,17 @@ #define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SOFTWAREINTEGRATIONMODULE___H__ #include - -#include #include #include +#include namespace openspace { +class AssetHelper; + class SoftwareIntegrationModule : public OpenSpaceModule { + friend class AssetHelper; + public: constexpr static const char* Name = "SoftwareIntegration"; @@ -65,18 +68,18 @@ public: std::vector documentations() const override; + scripting::LuaLibrary luaLibrary() const override; + private: void internalInitialize(const ghoul::Dictionary&) override; void internalDeinitialize() override; - - std::vector getSyncables(); + std::vector getSyncables(); // Centralized storage for datasets SyncableFloatDataStorage _syncableFloatDataStorage; - // Network engine - std::unique_ptr _networkEngine; + std::shared_ptr _networkState; }; } // namespace openspace diff --git a/modules/softwareintegration/softwareintegrationmodule_lua.inl b/modules/softwareintegration/softwareintegrationmodule_lua.inl new file mode 100644 index 0000000000..cc1b0c6011 --- /dev/null +++ b/modules/softwareintegration/softwareintegrationmodule_lua.inl @@ -0,0 +1,81 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include +#include +#include +#include + +namespace { + +/** + * Loads the data associated to the loaded session into the syncable data storage in the + * Software Integration Module + * + */ +[[codegen::luawrap]] std::string loadSessionData(std::string filePath) { + using namespace openspace; + + if (!global::windowDelegate->isMaster()) { + throw ghoul::lua::LuaError("Not on master..."); + } + + auto softwareIntegrationModule = global::moduleEngine->module(); + if (!softwareIntegrationModule) { + throw ghoul::lua::LuaError("Module not found..."); + } + + std::string errorMessage; + if (!AssetHelper::loadSessionData(softwareIntegrationModule, filePath, errorMessage)) { + return errorMessage; + } + else { + return "Success"; + } +} + +/** + * Saves the current state of the Software Integration Module + * + */ +[[codegen::luawrap]] std::string saveSession(std::string wantedFilePath) { + using namespace openspace; + + if (!global::windowDelegate->isMaster()) { + throw ghoul::lua::LuaError("Not on master..."); + } + + std::string errorMessage; + if (!AssetHelper::saveSession(wantedFilePath, errorMessage)) { + LERRORC("SoftwareIntegration::saveSession", errorMessage); + return errorMessage; + } + else { + return "Success"; + } +} + +#include "softwareintegrationmodule_lua_codegen.cpp" + +} // namespace diff --git a/modules/softwareintegration/syncablefloatdatastorage.cpp b/modules/softwareintegration/syncablefloatdatastorage.cpp index de4504f054..5837dfd658 100644 --- a/modules/softwareintegration/syncablefloatdatastorage.cpp +++ b/modules/softwareintegration/syncablefloatdatastorage.cpp @@ -30,7 +30,9 @@ #include namespace { - constexpr const char* _loggerCat = "SyncableFloatDataStorage"; + +constexpr const char* _loggerCat = "SyncableFloatDataStorage"; + } // namespace namespace openspace { @@ -39,138 +41,68 @@ using namespace softwareintegration; /* ============== SyncEngine functions ============== */ void SyncableFloatDataStorage::encode(SyncBuffer* syncBuffer) { - ZoneScopedN("SyncableFloatDataStorage::encode") + ZoneScopedN("SyncableFloatDataStorage::encode"); - std::lock_guard guard(_mutex); - - size_t nSGNs = _storage.size(); - syncBuffer->encode(nSGNs); - - for (auto& [identifier, sgnStorage] : _storage) { - syncBuffer->encode(identifier.size()); - syncBuffer->encode(identifier); - - size_t nStorageEntries = sgnStorage.size(); - syncBuffer->encode(nStorageEntries); - - for (auto& [key, storageEntry] : sgnStorage) { - bool isSyncDirty = storageEntry.syncDirty; - syncBuffer->encode(isSyncDirty); - if (!isSyncDirty) continue; - - syncBuffer->encode(static_cast(key)); - - size_t nValues = storageEntry.data.size(); - syncBuffer->encode(nValues); - - // Go trough all data in data entry. - // Sequentially structured as: x1, y1, z1, x2, y2, z2... - for (auto val : storageEntry.data) { - syncBuffer->encode(val); - } - - // TODO: Maybe combine solution with syncDirty? - storageEntry.hasEncoded = true; - } - } + encodeStorage(syncBuffer); } void SyncableFloatDataStorage::decode(SyncBuffer* syncBuffer) { - ZoneScopedN("SyncableFloatDataStorage::decode") + ZoneScopedN("SyncableFloatDataStorage::decode"); - std::lock_guard guard(_mutex); - - size_t nSGNs; - syncBuffer->decode(nSGNs); - - for (size_t i = 0; i < nSGNs; ++i) { - size_t identifierLength; - syncBuffer->decode(identifierLength); - - std::string identifier; - identifier.resize(identifierLength); - syncBuffer->decode(identifier); - - size_t nStorageEntries; - syncBuffer->decode(nStorageEntries); - - for (size_t j = 0; j < nStorageEntries; ++j) { - bool isSyncDirty; - syncBuffer->decode(isSyncDirty); - if (!isSyncDirty) continue; - - uint32_t keyRaw; - syncBuffer->decode(keyRaw); - auto key = static_cast(keyRaw); - - size_t nValues; - syncBuffer->decode(nValues); - - std::vector dataEntry{}; - dataEntry.reserve(nValues); - for (size_t k = 0; k < nValues; ++k) { - float value; - syncBuffer->decode(value); - dataEntry.push_back(std::move(value)); - } - - insertAssign(identifier, key, Value{ dataEntry }); - } - } + decodeStorage(syncBuffer); } void SyncableFloatDataStorage::postSync(bool isMaster) { - if (isMaster) { - std::lock_guard guard(_mutex); - for (auto& sgnStorage : _storage) { - for (auto& storageEntry : sgnStorage.second) { - if (storageEntry.second.syncDirty && storageEntry.second.hasEncoded) { - storageEntry.second.syncDirty = false; - storageEntry.second.hasEncoded = false; - } - } - } - } + if (isMaster) { + std::lock_guard guard(_mutex); + for (auto& sgnStorage : _storage) { + for (auto& storageEntry : sgnStorage.second) { + if (storageEntry.second.syncDirty && storageEntry.second.hasEncoded) { + storageEntry.second.syncDirty = false; + storageEntry.second.hasEncoded = false; + } + } + } + } } /* ================================================== */ const SyncableFloatDataStorage::ValueData& SyncableFloatDataStorage::fetch( - const Identifier& identifier, const storage::Key key + const Identifier& identifier, + const storage::Key key ) { - LDEBUG(fmt::format("Loading data from float data storage: {}-{}", identifier, storage::getStorageKeyString(key))); - std::lock_guard guard(_mutex); - if (!count(identifier)) { - LERROR(fmt::format( - "Could not find any data for SceneGraphNode '{}' in the centralized data storage", identifier - )); - return ValueData{}; - } + LDEBUG(fmt::format("Loading data from float data storage: {}-{}", identifier, storage::getStorageKeyString(key))); + std::lock_guard guard(_mutex); + if (!count(identifier)) { + LERROR(fmt::format( + "Could not find any data for SceneGraphNode '{}' in the centralized data storage", + identifier + )); + return ValueData{}; + } - if (!count(identifier, key)) { - LERROR(fmt::format( - "SceneGraphNode {} has no data with key '{}' in the centralized data storage", - identifier, - storage::getStorageKeyString(key) - )); - return ValueData{}; - } + if (!count(identifier, key)) { + LERROR(fmt::format( + "SceneGraphNode {} has no data with key '{}' in the centralized data storage", identifier, + storage::getStorageKeyString(key) + )); + return ValueData{}; + } - auto& value = _storage.find(identifier)->second.find(key)->second; + auto& value = _storage.find(identifier)->second.find(key)->second; - value.dirty = false; + value.dirty = false; - return value.data; + return value.data; } -bool SyncableFloatDataStorage::isDirty( - const Identifier& identifier, const storage::Key key -) { - if (!count(identifier, key)) { - return false; - } +bool SyncableFloatDataStorage::isDirty(const Identifier& identifier, const storage::Key key) { + if (!count(identifier, key)) { + return false; + } - return _storage.find(identifier)->second.find(key)->second.dirty; + return _storage.find(identifier)->second.find(key)->second.dirty; } void SyncableFloatDataStorage::setLoaded(const Identifier& identifier, const storage::Key key) { @@ -194,59 +126,144 @@ bool SyncableFloatDataStorage::hasLoaded(const Identifier& identifier, const sto } void SyncableFloatDataStorage::store( - const Identifier& identifier, const storage::Key key, const ValueData& data + const Identifier& identifier, + const storage::Key key, + const ValueData& data ) { - LDEBUG(fmt::format("Storing data in float data storage: {}-{}", identifier, storage::getStorageKeyString(key))); - std::lock_guard guard(_mutex); - insertAssign(identifier, key, { data }); + LDEBUG(fmt::format("Storing data in float data storage: {}-{}", identifier, storage::getStorageKeyString(key))); + std::lock_guard guard(_mutex); + insertAssign(identifier, key, { data }); +} + +void SyncableFloatDataStorage::encodeStorage(SyncBuffer* syncBuffer, bool skipNonSynced) { + std::lock_guard guard(_mutex); + + syncBuffer->encode(static_cast(_storage.size())); + + for (auto& [identifier, sgnStorage] : _storage) { + syncBuffer->encode(identifier); + + syncBuffer->encode(static_cast(sgnStorage.size())); + + for (auto& [key, storageEntry] : sgnStorage) { + if (skipNonSynced) { + bool isSyncDirty = storageEntry.syncDirty; + syncBuffer->encode(isSyncDirty); + if (!isSyncDirty) continue; + } + + syncBuffer->encode(static_cast(key)); + + syncBuffer->encode(storageEntry.data); + + // TODO: Maybe combine solution with syncDirty? + storageEntry.hasEncoded = true; + } + } +} + +void SyncableFloatDataStorage::decodeStorage(SyncBuffer* syncBuffer, bool skipNonSynced) { + std::lock_guard guard(_mutex); + + uint16_t nSGNs; + syncBuffer->decode(nSGNs); + + for (uint16_t i = 0; i < nSGNs; ++i) { + std::string identifier; + syncBuffer->decode(identifier); + + uint16_t nStorageEntries; + syncBuffer->decode(nStorageEntries); + + for (uint16_t j = 0; j < nStorageEntries; ++j) { + if (skipNonSynced) { + bool isSyncDirty; + syncBuffer->decode(isSyncDirty); + if (!isSyncDirty) continue; + } + + uint8_t keyRaw; + syncBuffer->decode(keyRaw); + auto key = static_cast(keyRaw); + + std::vector dataEntry{}; + syncBuffer->decode(dataEntry); + + insertAssign(identifier, key, Value{ dataEntry }); + } + } +} + +void SyncableFloatDataStorage::store(const std::vector& storageDump) { + ZoneScopedN("SyncableFloatDataStorage::store"); + auto syncBuffer = new SyncBuffer{ 0 }; + syncBuffer->setData(storageDump); + decodeStorage(syncBuffer, false); +} + +void SyncableFloatDataStorage::dump(std::vector& storageDump) { + ZoneScopedN("SyncableFloatDataStorage::dump"); + + auto syncBuffer = new SyncBuffer{ 0 }; + encodeStorage(syncBuffer, false); + storageDump = syncBuffer->data(); } /* =============== Utility functions ================ */ bool SyncableFloatDataStorage::erase(const Identifier& identifier, const storage::Key key) { - if (count(identifier, key) == 0) return false; + if (count(identifier, key) == 0) return false; - auto nErased = _storage.find(identifier)->second.erase(key); - return nErased > 0; + auto nErased = _storage.find(identifier)->second.erase(key); + return nErased > 0; } void SyncableFloatDataStorage::insertAssign(const Identifier& identifier, const storage::Key key, const Value& value) { - if (count(identifier)) { - if (count(identifier, key)) { - _storage.find(identifier)->second.find(key)->second = value; - } - else { - _storage.find(identifier)->second.emplace(key, value); - } - } - else { - SceneStorage newSceneStorage{ { key, value } }; - _storage.emplace(identifier, std::move(newSceneStorage)); - } + if (count(identifier)) { + if (count(identifier, key)) { + _storage.find(identifier)->second.find(key)->second = value; + } + else { + _storage.find(identifier)->second.emplace(key, value); + } + } + else { + SceneStorage newSceneStorage{ { key, value } }; + _storage.emplace(identifier, std::move(newSceneStorage)); + } } size_t SyncableFloatDataStorage::count(const Identifier& identifier) { - return _storage.count(identifier); + return _storage.count(identifier); } size_t SyncableFloatDataStorage::count(const Identifier& identifier, const storage::Key key) { - auto sceneIt = _storage.find(identifier); - if (sceneIt == _storage.end()) return 0; + auto sceneIt = _storage.find(identifier); + if (sceneIt == _storage.end()) return 0; - return sceneIt->second.count(key); + return sceneIt->second.count(key); } -// Helper function for debugging +std::vector SyncableFloatDataStorage::getAllIdentifiers() { + std::vector identifiers; + identifiers.reserve(_storage.size()); + for (auto [identifier, sceneStorage] : _storage) { + identifiers.push_back(identifier); + } + return std::move(identifiers); +} + +// Helper function for debugging std::string SyncableFloatDataStorage::getStringOfAllKeysInStorage() { - std::string keysString; - - for (auto [id, sceneStorage]: _storage) { - keysString += '(' + id + ')' + ": "; - for(auto [key, val]: sceneStorage) { - keysString += storage::getStorageKeyString(key) + " "; - } - keysString += '\n'; - } - return keysString; + std::string keysString; + + for (auto [id, sceneStorage] : _storage) { + keysString += '(' + id + ')' + ": "; + for (auto [key, val] : sceneStorage) { + keysString += storage::getStorageKeyString(key) + " "; + } + keysString += '\n'; + } + return keysString; } /* ================================================== */ diff --git a/modules/softwareintegration/syncablefloatdatastorage.h b/modules/softwareintegration/syncablefloatdatastorage.h index b5f3dfa2d3..b7fd9ae086 100644 --- a/modules/softwareintegration/syncablefloatdatastorage.h +++ b/modules/softwareintegration/syncablefloatdatastorage.h @@ -25,12 +25,13 @@ #ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SYNCABLEFLOATDATASTORAGE___H__ #define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SYNCABLEFLOATDATASTORAGE___H__ -#include -#include #include #include +#include +#include + namespace openspace { using namespace softwareintegration; @@ -38,47 +39,55 @@ using namespace softwareintegration; class SyncableFloatDataStorage : public Syncable { public: /* ====================== Types ===================== */ - struct Value { - // a dataset stored like x1, y1, z1, x2, y2 .... - std::vector data; - bool hasEncoded = false; - bool syncDirty = true; - bool hasLoaded = false; - bool dirty = true; - }; - using ValueData = decltype(Value::data); - using SceneStorage = std::unordered_map; - using Identifier = std::string; - using Storage = std::unordered_map; - using Iterator = Storage::iterator; - using SceneIterator = SceneStorage::iterator; + struct Value { + // a dataset stored like x1, y1, z1, x2, y2 .... + std::vector data; + bool hasEncoded = false; + bool syncDirty = true; + bool hasLoaded = false; + bool dirty = true; + }; + using ValueData = decltype(Value::data); + using SceneStorage = std::unordered_map; + using Identifier = std::string; + using Storage = std::unordered_map; + using Iterator = Storage::iterator; + using SceneIterator = SceneStorage::iterator; /* ================================================== */ - /* ============== SyncEngine functions ============== */ - virtual void encode(SyncBuffer* syncBuffer) override; - virtual void decode(SyncBuffer* syncBuffer) override; - virtual void postSync(bool isMaster) override; - /* ================================================== */ + /* ============== SyncEngine functions ============== */ + virtual void encode(SyncBuffer* syncBuffer) override; + virtual void decode(SyncBuffer* syncBuffer) override; + virtual void postSync(bool isMaster) override; + /* ================================================== */ - const ValueData& fetch(const Identifier& identifier, const storage::Key key); - bool isDirty(const Identifier& identifier, const storage::Key key); - void setLoaded(const Identifier& identifier, const storage::Key key); - bool hasLoaded(const Identifier& identifier, const storage::Key key); - void store(const Identifier& identifier, const storage::Key key, const ValueData& data); - std::string getStringOfAllKeysInStorage(); + const ValueData& fetch(const Identifier& identifier, const storage::Key key); + void setLoaded(const Identifier& identifier, const storage::Key key); + bool hasLoaded(const Identifier& identifier, const storage::Key key); + bool isDirty(const Identifier& identifier, const storage::Key key); + bool isSyncDirty(const Identifier& identifier, const storage::Key key); + void store(const Identifier& identifier, const storage::Key key, const ValueData& data); + + void encodeStorage(SyncBuffer* syncBuffer, bool skipNonSynced = true); + void decodeStorage(SyncBuffer* syncBuffer, bool skipNonSynced = true); + void dump(std::vector& storageDump); + void store(const std::vector& storageDump); + std::vector getAllIdentifiers(); + + std::string getStringOfAllKeysInStorage(); private: - /* =============== Utility functions ================ */ - bool erase(const Identifier& identifier, const storage::Key key); + /* =============== Utility functions ================ */ + bool erase(const Identifier& identifier, const storage::Key key); - void insertAssign(const Identifier& identifier, const storage::Key key, const Value& value); + void insertAssign(const Identifier& identifier, const storage::Key key, const Value& value); - size_t count(const Identifier& identifier, const storage::Key key); - size_t count(const Identifier& identifier); - /* ================================================== */ + size_t count(const Identifier& identifier, const storage::Key key); + size_t count(const Identifier& identifier); + /* ================================================== */ - std::mutex _mutex; - Storage _storage; + std::mutex _mutex; + Storage _storage; }; } // namespace openspace diff --git a/modules/softwareintegration/utils.cpp b/modules/softwareintegration/utils.cpp index d5e619b094..ef0fdec292 100644 --- a/modules/softwareintegration/utils.cpp +++ b/modules/softwareintegration/utils.cpp @@ -33,48 +33,105 @@ namespace { constexpr const char* _loggerCat = "SoftwareIntegrationMessageFormat"; } // namespace -namespace openspace { +namespace openspace::softwareintegration { -namespace softwareintegration { namespace storage { - bool hasStorageKey(const std::string& key) { - return _keyStringFromKey.count(key) > 0; + +// Anonymous namespace +namespace { + + const std::unordered_map _keyStringFromKey{ + { "DataPoints", Key::DataPoints }, + { "VelocityData", Key::VelocityData }, + { "Colormap", Key::Colormap }, + { "ColormapAttributeData", Key::ColormapAttrData }, + { "LinearSizeAttributeData", Key::LinearSizeAttrData }, + }; + +} // namespace + +bool hasStorageKey(const std::string& key) { + return _keyStringFromKey.count(key) > 0; +} + +Key getStorageKey(const std::string& key) { + if (hasStorageKey(key)) { + return _keyStringFromKey.at(key); } - Key getStorageKey(const std::string& key) { - if (hasStorageKey(key)) { - return _keyStringFromKey.at(key); + return Key::Unknown; +} + +std::string getStorageKeyString(const Key key) { + auto it = std::find_if( + _keyStringFromKey.begin(), + _keyStringFromKey.end(), + [key](const std::pair& p) { + return key == p.second; } - - return Key::Unknown; - } - - std::string getStorageKeyString(const Key key) { - auto it = std::find_if( - _keyStringFromKey.begin(), - _keyStringFromKey.end(), - [key](const std::pair& p) { - return key == p.second; - } - ); - if (it == _keyStringFromKey.end()) return ""; - return it->first; - } + ); + if (it == _keyStringFromKey.end()) return ""; + return it->first; +} } // namespace storage namespace simp { -SimpError::SimpError(const std::string& msg) - : errorCode{tools::ErrorCode::Generic}, ghoul::RuntimeError(fmt::format("{}: Error Code: {} - {}", "SIMP error", static_cast(errorCode), msg), "Software Integration Messaging Protocol error") -{} +// Anonymous namespace +namespace { -SimpError::SimpError(const tools::ErrorCode _errorCode, const std::string& msg) - : errorCode{errorCode}, ghoul::RuntimeError(fmt::format("{}: Error Code: {} - {}", "SIMP error", static_cast(errorCode), msg), "Software Integration Messaging Protocol error") -{} +const std::unordered_map _messageTypeFromSIMPType { + {"CONN", MessageType::Connection}, + {"PDAT", MessageType::PointData}, + {"VDAT", MessageType::VelocityData}, + {"RSGN", MessageType::RemoveSceneGraphNode}, + {"FCOL", MessageType::Color}, + {"LCOL", MessageType::Colormap}, + {"ATDA", MessageType::AttributeData}, + {"FOPA", MessageType::Opacity}, + {"FPSI", MessageType::FixedSize}, + {"LPSI", MessageType::LinearSize}, + {"TOVI", MessageType::Visibility}, +}; -bool tools::isEndOfCurrentValue(const std::vector& message, size_t offset) { +const std::unordered_map _colormapNaNRenderModeFromString { + {"Hide", ColormapNaNRenderMode::Hide}, + {"FixedColor", ColormapNaNRenderMode::FixedColor} +}; + +const std::unordered_map _velocityNaNRenderModeFromString { + {"Hide", VelocityNaNRenderMode::Hide}, + {"Static", VelocityNaNRenderMode::Static} +}; + +glm::vec4 readSingleColor(const std::vector& message, size_t& offset) { + if (message[offset] != '[') { + throw SimpError( + tools::ErrorCode::Generic, + fmt::format("Expected to read '[', got {} in 'readColor'", message[offset]) + ); + } + ++offset; + + float r = readFloatValue(message, offset); + float g = readFloatValue(message, offset); + float b = readFloatValue(message, offset); + float a = readFloatValue(message, offset); + + if (message[offset] != ']') { + throw SimpError( + tools::ErrorCode::Generic, + fmt::format("Expected to read ']', got {} in 'readColor'", message[offset]) + ); + } + ++offset; + + return { r, g, b, a }; +} + +bool isEndOfCurrentValue(const std::vector& message, size_t offset) { if (offset >= message.size()) { throw SimpError( tools::ErrorCode::OffsetLargerThanMessageSize, @@ -92,31 +149,41 @@ bool tools::isEndOfCurrentValue(const std::vector& message, size_t offset) return offset != 0 && message[offset] == SEP && message[offset - 1] != '\\'; } +} // namespace + +SimpError::SimpError(const tools::ErrorCode _errorCode, const std::string& msg) + : errorCode{errorCode}, ghoul::RuntimeError(fmt::format("{}: Error Code: {} - {}", "SIMP error", static_cast(_errorCode), msg), "Software Integration Messaging Protocol error") +{} + +SimpError::SimpError(const std::string& msg) + : errorCode{tools::ErrorCode::Generic}, ghoul::RuntimeError(fmt::format("{}: Error Code: {} - {}", "SIMP error", static_cast(errorCode), msg), "Software Integration Messaging Protocol error") +{} + MessageType getMessageType(const std::string& type) { - if (tools::_messageTypeFromSIMPType.count(type) == 0) return MessageType::Unknown; - return tools::_messageTypeFromSIMPType.at(type); + if (_messageTypeFromSIMPType.count(type) == 0) return MessageType::Unknown; + return _messageTypeFromSIMPType.at(type); } std::string getSIMPType(const MessageType& type) { auto it = std::find_if( - tools::_messageTypeFromSIMPType.begin(), - tools::_messageTypeFromSIMPType.end(), + _messageTypeFromSIMPType.begin(), + _messageTypeFromSIMPType.end(), [type](const std::pair& p) { return type == p.second; } ); - if (it == tools::_messageTypeFromSIMPType.end()) return "UNKN"; + if (it == _messageTypeFromSIMPType.end()) return "UNKN"; return it->first; } ColormapNaNRenderMode getColormapNaNRenderMode(const std::string& type) { - if (tools::_colormapNaNRenderModeFromString.count(type) == 0) return ColormapNaNRenderMode::Unknown; - return tools::_colormapNaNRenderModeFromString.at(type); + if (_colormapNaNRenderModeFromString.count(type) == 0) return ColormapNaNRenderMode::Unknown; + return _colormapNaNRenderModeFromString.at(type); } VelocityNaNRenderMode getVelocityNaNRenderMode(const std::string& type) { - if (tools::_velocityNaNRenderModeFromString.count(type) == 0) return VelocityNaNRenderMode::Unknown; - return tools::_velocityNaNRenderModeFromString.at(type); + if (_velocityNaNRenderModeFromString.count(type) == 0) return VelocityNaNRenderMode::Unknown; + return _velocityNaNRenderModeFromString.at(type); } std::string formatLengthOfSubject(size_t lengthOfSubject) { @@ -187,7 +254,7 @@ int readIntValue(const std::vector& message, size_t& offset) { int value; bool isHex = false; - while (!tools::isEndOfCurrentValue(message, offset)) { + while (!isEndOfCurrentValue(message, offset)) { char c = message[offset]; if (c == 'x' || c == 'X') isHex = true; string_value.push_back(c); @@ -212,7 +279,7 @@ float readFloatValue(const std::vector& message, size_t& offset) { std::string string_value; float value; - while (!tools::isEndOfCurrentValue(message, offset)) { + while (!isEndOfCurrentValue(message, offset)) { string_value.push_back(message[offset]); offset++; } @@ -236,7 +303,7 @@ void readColormap( ) { colorMap.reserve(nColors * 4); while (message[offset] != SEP) { - glm::vec4 color = tools::readSingleColor(message, offset); + glm::vec4 color = readSingleColor(message, offset); // Colormap should be stored in a sequential vector // of floats for syncing between nodes and when @@ -250,40 +317,15 @@ void readColormap( offset++; } -glm::vec4 tools::readSingleColor(const std::vector& message, size_t& offset) { - if (message[offset] != '[') { - throw SimpError( - tools::ErrorCode::Generic, - fmt::format("Expected to read '[', got {} in 'readColor'", message[offset]) - ); - } - ++offset; - - float r = readFloatValue(message, offset); - float g = readFloatValue(message, offset); - float b = readFloatValue(message, offset); - float a = readFloatValue(message, offset); - - if (message[offset] != ']') { - throw SimpError( - tools::ErrorCode::Generic, - fmt::format("Expected to read ']', got {} in 'readColor'", message[offset]) - ); - } - ++offset; - - return { r, g, b, a }; -} - glm::vec4 readColor(const std::vector& message, size_t& offset) { - glm::vec4 color = tools::readSingleColor(message, offset); + glm::vec4 color = readSingleColor(message, offset); ++offset; return color; } std::string readString(const std::vector& message, size_t& offset) { std::string value; - while (!tools::isEndOfCurrentValue(message, offset)) { + while (!isEndOfCurrentValue(message, offset)) { value.push_back(message[offset]); ++offset; } @@ -301,7 +343,7 @@ void readPointData( ) { pointData.reserve(nPoints * dimensionality); - while (!tools::isEndOfCurrentValue(message, offset)) { + while (!isEndOfCurrentValue(message, offset)) { if (message[offset] != '[') { throw SimpError( tools::ErrorCode::Generic, @@ -328,6 +370,4 @@ void readPointData( } // namespace simp -} // namespace softwareintegration - -} // namespace openspace +} // namespace openspace::softwareintegration diff --git a/modules/softwareintegration/utils.h b/modules/softwareintegration/utils.h index a0134f30fa..1588fea38d 100644 --- a/modules/softwareintegration/utils.h +++ b/modules/softwareintegration/utils.h @@ -2,7 +2,7 @@ * * * OpenSpace * * * - * Copyright (c) 2014-2021 * + * Copyright (c) 2014-2022 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * @@ -27,13 +27,11 @@ #include -namespace openspace { - -namespace softwareintegration { +namespace openspace::softwareintegration { namespace storage { - -enum class Key : uint32_t { + +enum class Key : uint8_t { DataPoints = 0, VelocityData, Colormap, @@ -42,14 +40,6 @@ enum class Key : uint32_t { Unknown }; -const std::unordered_map _keyStringFromKey { - {"DataPoints", Key::DataPoints}, - {"VelocityData", Key::VelocityData}, - {"Colormap", Key::Colormap}, - {"ColormapAttributeData", Key::ColormapAttrData}, - {"LinearSizeAttributeData", Key::LinearSizeAttrData}, -}; - Key getStorageKey(const std::string& key); std::string getStorageKeyString(const Key key); @@ -76,7 +66,6 @@ enum class MessageType : uint32_t { FixedSize, LinearSize, Visibility, - InternalDisconnection, Unknown }; @@ -101,45 +90,15 @@ enum class ErrorCode : uint32_t { Generic, }; -const std::unordered_map _messageTypeFromSIMPType { - {"CONN", MessageType::Connection}, - {"PDAT", MessageType::PointData}, - {"VDAT", MessageType::VelocityData}, - {"RSGN", MessageType::RemoveSceneGraphNode}, - {"FCOL", MessageType::Color}, - {"LCOL", MessageType::Colormap}, - {"ATDA", MessageType::AttributeData}, - {"FOPA", MessageType::Opacity}, - {"FPSI", MessageType::FixedSize}, - {"LPSI", MessageType::LinearSize}, - {"TOVI", MessageType::Visibility}, - {"DISC", MessageType::InternalDisconnection}, -}; - -const std::unordered_map _colormapNaNRenderModeFromString { - {"Hide", ColormapNaNRenderMode::Hide}, - {"FixedColor", ColormapNaNRenderMode::FixedColor} -}; - -const std::unordered_map _velocityNaNRenderModeFromString { - {"Hide", VelocityNaNRenderMode::Hide}, - {"Static", VelocityNaNRenderMode::Static} -}; - -glm::vec4 readSingleColor(const std::vector& message, size_t& offset); - -bool isEndOfCurrentValue(const std::vector& message, size_t offset); - -} // namespace tools +} // namespace tools class SimpError : public ghoul::RuntimeError { public: - tools::ErrorCode errorCode; - explicit SimpError(const std::string& msg); - explicit SimpError(const tools::ErrorCode _errorCode, const std::string& msg); + tools::ErrorCode errorCode; + explicit SimpError(const std::string& msg); + explicit SimpError(const tools::ErrorCode _errorCode, const std::string& msg); }; - MessageType getMessageType(const std::string& type); std::string getSIMPType(const MessageType& type); @@ -150,12 +109,10 @@ VelocityNaNRenderMode getVelocityNaNRenderMode(const std::string& type); std::string formatLengthOfSubject(size_t lengthOfSubject); -std::string formatUpdateMessage(MessageType messageType, - std::string_view identifier, - std::string_view value); - +std::string formatUpdateMessage(MessageType messageType, std::string_view identifier, std::string_view value); + std::string formatConnectionMessage(std::string_view value); - + std::string formatColorMessage(std::string_view identifier, glm::vec4 color); std::string formatPointDataCallbackMessage(std::string_view identifier); @@ -173,19 +130,22 @@ std::string readString(const std::vector& message, size_t& offset); glm::vec4 readColor(const std::vector& message, size_t& offset); void readPointData( - const std::vector& message, size_t& offset, size_t nPoints, - size_t dimensionality, std::vector& pointData + const std::vector& message, + size_t& offset, + size_t nPoints, + size_t dimensionality, + std::vector& pointData ); void readColormap( - const std::vector& message, size_t& offset, size_t nColors, + const std::vector& message, + size_t& offset, + size_t nColors, std::vector& colorMap ); } // namespace simp -} // namespace softwareintegration - -} // namespace openspace +} // namespace openspace::softwareintegration #endif // __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SIMP___H__ diff --git a/openspace.cfg b/openspace.cfg index 0f47b35259..9d73bda19a 100644 --- a/openspace.cfg +++ b/openspace.cfg @@ -8,15 +8,11 @@ -- or a dome cluster system -- A regular 1280x720 window ---SGCTConfig = sgct.config.single{vsync=false} ---SGCTConfig = "${CONFIG}/sci_powerwall_7nodes.json" ---SGCTConfig = "${CONFIG}/sci_powerwall_7nodes_laptop.json" ---SGCTConfig = "${CONFIG}/sci_powerwall01_node_scixopenspace.json" ---SGCTConfig = "${CONFIG}/sci_powerwall02_node_scixopenspace.json" +SGCTConfig = sgct.config.single{vsync=false} -- A regular 1920x1080 window -SGCTConfig = sgct.config.single{1920, 1080} +-- SGCTConfig = sgct.config.single{1920, 1080} -- A regular 1280x720 window ---SGCTConfig = sgct.config.single{1280, 720} +-- SGCTConfig = sgct.config.single{1280, 720} -- A windowed 1920x1080 fullscreen -- SGCTConfig = sgct.config.single{1920, 1080, border=false, windowPos={0,0}} -- A 1k fisheye rendering @@ -67,11 +63,9 @@ SGCTConfig = sgct.config.single{1920, 1080} -- Variable: Profile -- Sets the profile that should be loaded by OpenSpace. -Profile = "jacobs" ---Profile = "gaia_dr2" ---Profile = "default" +Profile = "default" -- Profile = "default_full" ---Profile = "gaia" +-- Profile = "gaia" -- Profile = "insight" -- Profile = "juno" -- Profile = "jwst"