/***************************************************************************************** * * * 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 namespace { constexpr const char* _loggerCat = "MessageHandler"; } // namespace namespace openspace::softwareintegration::messagehandler { // Anonymous namespace namespace { CallbackMap callbacks{}; std::mutex callbacksMutex{}; size_t callbacksRetries{0}; 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 simp::readValue(message, messageOffset, identifier); simp::readValue(message, messageOffset, guiName); } 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 ); auto reanchorCallback = [identifier] { global::scriptEngine->queueScript( "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.RetargetAnchor', nil)" "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', '" + identifier + "')" "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')", scripting::ScriptEngine::RemoteScripting::Yes ); }; addCallback(identifier, { reanchorCallback, { storage::Key::DataPoints }, "reanchorCallback" }); // Set large time steps for the GUI (so you for example // can see the movement of stars at 5000 years/second) // Values set in seconds: Real time, 5k years, // 10k year, 50k year, 100k year, 500k year, 1M year std::string largeTimeSteps = "{ 1.0, 157680000000.0, 315360000000.0," " 1576800000000.0, 3153600000000.0," " 15768000000000.0, 3153600000000.0 }"; global::scriptEngine->queueScript( fmt::format( "openspace.time.setDeltaTimeSteps({});", largeTimeSteps ), scripting::ScriptEngine::RemoteScripting::Yes ); } } void onFixedColorChange( properties::Property* property, const std::string& identifier, std::shared_ptr connection ) { glm::vec4 color = std::any_cast(property->get()); std::lock_guard guard(connection->outgoingMessagesMutex()); { std::vector red; simp::toByteBuffer(red, 0, color.r); connection->addToMessageQueue(identifier, simp::DataKey::Red, { red, 1 }); } { std::vector green; simp::toByteBuffer(green, 0, color.g); connection->addToMessageQueue(identifier, simp::DataKey::Green, { green, 1 }); } { std::vector blue; simp::toByteBuffer(blue, 0, color.b); connection->addToMessageQueue(identifier, simp::DataKey::Blue, { blue, 1 }); } { std::vector alpha; simp::toByteBuffer(alpha, 0, color.a); connection->addToMessageQueue(identifier, simp::DataKey::Alpha, { alpha, 1 }); } } void onFixedPointSizeChange( properties::Property* property, const std::string& identifier, std::shared_ptr connection ) { float pointSizeValue = std::any_cast(property->get()); std::lock_guard guard(connection->outgoingMessagesMutex()); std::vector size{}; simp::toByteBuffer(size, 0, pointSizeValue); connection->addToMessageQueue(identifier, simp::DataKey::FixedSize, { size, 1 }); } void onVisibilityChange( properties::Property* property, const std::string& identifier, std::shared_ptr connection ) { bool isVisible = std::any_cast(property->get()); std::lock_guard guard(connection->outgoingMessagesMutex()); std::vector visibility; simp::toByteBuffer(visibility, 0, isVisible); connection->addToMessageQueue(identifier, simp::DataKey::Visibility, { visibility, 1 }); } void checkAddOnChangeCallback( const std::string& identifier, const std::string& propertyName, std::shared_ptr connection, const std::function< void( properties::Property* property, const std::string& identifier, std::shared_ptr connection ) >& onChange ) { if (connection->hasPropertySubscription(identifier, propertyName)) { connection->setShouldNotSendData(identifier, propertyName); } // Shouldn't add another property subscription if (connection->hasPropertySubscription(identifier, propertyName)) return; // Weak pointer better for lambdas std::weak_ptr connWeakPtr{ connection }; // Create and set onChange for property auto onChangeCallback = [identifier, connWeakPtr, propertyName, onChange] { // Get renderable auto r = renderable(identifier); if (!r) return; // Get property auto property = r->property(propertyName); if (!property || connWeakPtr.expired()) return; auto update = [propertyName, identifier, connWeakPtr, onChange] { if (connWeakPtr.expired()) return; auto connection = connWeakPtr.lock(); // Get renderable auto r = renderable(identifier); if (!r) return; // Get property auto property = r->property(propertyName); if (!property) return; if (connWeakPtr.lock()->hasPropertySubscription(identifier, propertyName)) return; if (!connection->isConnected()) { connection->removePropertySubscription(identifier, propertyName); return; } if (!connection->shouldSendData(identifier, propertyName)) { return; } onChange(property, identifier, connection); }; auto conn = connWeakPtr.lock(); conn->addPropertySubscription(propertyName, identifier, update); }; addCallback( identifier, { onChangeCallback, {}, fmt::format("onChangeCallback on property {}", propertyName), } ); } bool handleSingleFloatValue( const std::vector& message, size_t& offset, const std::string& identifier, const simp::DataKey& dataKey, const std::string& propertyName, std::shared_ptr connection = nullptr, const std::function< void( properties::Property* property, const std::string& identifier, std::shared_ptr connection ) >& onChangeCallback = nullptr ) { float newValue; try { simp::readValue(message, offset, newValue); } catch (const simp::SimpError& err) { LERROR(fmt::format( "Error when parsing float in {} message: {}", simp::getStringFromDataKey(dataKey), err.message )); return false; } auto setValueCallback = [identifier, newValue, propertyName] { // Get renderable auto r = renderable(identifier); if (!r) return; // Get property of renderable auto property = r->property(propertyName); if (!property) return; // Update property of renderable auto currentValue = std::any_cast(property->get()); if (abs(newValue - currentValue) > std::numeric_limits::epsilon()) { global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.{}', {});", identifier, propertyName, ghoul::to_string(newValue) ), scripting::ScriptEngine::RemoteScripting::Yes ); } }; addCallback( identifier, { setValueCallback, {}, fmt::format("Callback for {} on property {}", simp::getStringFromDataKey(dataKey), propertyName), } ); if (onChangeCallback && connection) { checkAddOnChangeCallback(identifier, propertyName, connection, onChangeCallback); } return true; } bool handleColorValue( const std::string& identifier, const glm::vec4& _newColor, const std::string& propertyName, std::shared_ptr connection = nullptr, const std::function< void( properties::Property* property, const std::string& identifier, std::shared_ptr connection ) >& onChangeCallback = nullptr ) { auto setColorCallback = [identifier, _newColor, propertyName] { // Get renderable auto r = renderable(identifier); if (!r) return; // Get color of renderable auto property = r->property(propertyName); if (!property) return; auto currentColor = std::any_cast(property->get()); // Update new color channel values auto newColor = _newColor; for (glm::vec4::length_type i = 0; i < glm::vec4::length(); ++i) { if (newColor[i] < 0) { newColor[i] = currentColor[i]; } } // Update color of renderable if (glm::any(glm::epsilonNotEqual(newColor, currentColor, std::numeric_limits::epsilon()))) { global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.{}', {});", identifier, propertyName, ghoul::to_string(newColor) ), scripting::ScriptEngine::RemoteScripting::Yes ); } }; addCallback( identifier, { setColorCallback, {}, fmt::format("Callback on property {}", propertyName), } ); if (onChangeCallback && connection) { checkAddOnChangeCallback(identifier, propertyName, connection, onChangeCallback); } return true; } bool handleDateValue( const std::string& identifier, const glm::ivec3& _newDate, const std::string& propertyName, std::shared_ptr connection = nullptr, const std::function< void( properties::Property* property, const std::string& identifier, std::shared_ptr connection ) >& onChangeCallback = nullptr ) { auto setDateCallback = [identifier, _newDate, propertyName] { // Get renderable auto r = renderable(identifier); if (!r) return; // Get date value of renderable auto property = r->property(propertyName); if (!property) return; auto currentDate = std::any_cast(property->get()); // Update new date values auto newDate = _newDate; for (glm::ivec3::length_type i = 0; i < glm::ivec3::length(); ++i) { // Keep the parts of currentDate that won't be updated if (newDate[i] < 0) { newDate[i] = currentDate[i]; } } // Update date of renderable if (glm::any(glm::notEqual(newDate, currentDate))) { global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.{}', {});", identifier, propertyName, newDate ), scripting::ScriptEngine::RemoteScripting::Yes ); } }; addCallback( identifier, { setDateCallback, {}, fmt::format("Callback on property {}", propertyName), } ); if (onChangeCallback && connection) { checkAddOnChangeCallback(identifier, propertyName, connection, onChangeCallback); } return true; } bool handleBoolValue( const std::vector& message, size_t& offset, const std::string& identifier, const simp::DataKey& dataKey, const std::string& propertyName, const std::vector& _waitFor, std::shared_ptr connection = nullptr, const std::function< void( properties::Property* property, const std::string& identifier, std::shared_ptr connection ) >& onChangeCallback = nullptr ) { bool newValue; try { simp::readValue(message, offset, newValue); } catch (const simp::SimpError& err) { LERROR(fmt::format( "Error when parsing bool in DATA.{} message: {}", simp::getStringFromDataKey(dataKey), err.message )); return false; } auto setEnabledCallback = [identifier, propertyName, newValue] { // Get renderable auto r = renderable(identifier); if (!r) return; // Get property auto property = r->property(propertyName); if (!property) return; // Update bool property of renderable auto currentValue = std::any_cast(property->get()); if (newValue != currentValue) { std::string newValueString = newValue ? "true" : "false"; global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.{}', {});", identifier, propertyName, newValueString ), scripting::ScriptEngine::RemoteScripting::Yes ); } }; std::vector waitFor{}; if (newValue) { waitFor = _waitFor; } addCallback( identifier, { setEnabledCallback, waitFor, fmt::format("Callback on property {}", propertyName), } ); if (onChangeCallback && connection) { checkAddOnChangeCallback(identifier, propertyName, connection, onChangeCallback); } return true; } bool handleStringValue( const std::vector& message, size_t& offset, const std::string& identifier, const simp::DataKey& dataKey, const std::string& propertyName, std::shared_ptr connection = nullptr, const std::function< void( properties::Property* property, const std::string& identifier, std::shared_ptr connection ) >& onChangeCallback = nullptr ) { std::string newStringValue; try { simp::readValue(message, offset, newStringValue); } catch (const simp::SimpError& err) { LERROR(fmt::format( "Error when parsing string in DATA.{} message: {}", simp::getStringFromDataKey(dataKey), err.message )); return false; } auto setStringCallback = [identifier, propertyName, newStringValue] { // Get renderable auto r = renderable(identifier); if (!r) return; // Get property auto property = r->property(propertyName); if (!property) return; // Update string property of renderable auto currentStringValue = property->getStringValue(); if (newStringValue != currentStringValue) { global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.{}', \"{}\");", identifier, propertyName, newStringValue ), scripting::ScriptEngine::RemoteScripting::Yes ); } }; addCallback( identifier, { setStringCallback, {}, fmt::format("Callback for {} on property {}", simp::getStringFromDataKey(dataKey), propertyName), } ); if (onChangeCallback && connection) { checkAddOnChangeCallback(identifier, propertyName, connection, onChangeCallback); } return true; } void handleDataMessage(const std::vector& message, std::shared_ptr connection) { // LDEBUG(fmt::format("Message recieved on connection {}... New Data", connectionPtr->id())); // will_send_message = false size_t offset = 0; std::string identifier; checkRenderable(message, offset, connection, identifier); glm::vec4 newColor{ -1.0 }; bool hasNewColor = false; glm::vec4 newColormapNanColor{ -1.0 }; bool hasNewNanColor = false; glm::ivec3 newVelocityDateRecorded{ -1 }; bool hasNewVelocityDateRecorded = false; while (offset < message.size()) { std::string dataKeyStr; simp::DataKey dataKey; try { simp::readValue(message, offset, dataKeyStr); dataKey = simp::getDataKey(dataKeyStr); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading data message: {}", err.message)); return; } LINFO(fmt::format( "Handling '{}':", simp::getStringFromDataKey(dataKey) )); // Handle multi-valued data key if (dataKey == simp::DataKey::X || dataKey == simp::DataKey::Y || dataKey == simp::DataKey::Z || dataKey == simp::DataKey::U || dataKey == simp::DataKey::V || dataKey == simp::DataKey::W || dataKey == simp::DataKey::ColormapReds || dataKey == simp::DataKey::ColormapGreens || dataKey == simp::DataKey::ColormapBlues || dataKey == simp::DataKey::ColormapAlphas || dataKey == simp::DataKey::ColormapAttributeData || dataKey == simp::DataKey::LinearSizeAttributeData ) { // Add values to syncable storage std::vector dataBuffer; int32_t nValues = -1; try { simp::readValue(message, offset, nValues); if (nValues < 0) { throw simp::SimpError(fmt::format( "Number of values should be >0. Got {}", nValues )); } size_t nBytesToCopy = static_cast(nValues) * 4; // Should be positive int dataBuffer.resize(nBytesToCopy); std::memcpy( dataBuffer.data(), message.data() + offset, nBytesToCopy ); offset += nBytesToCopy; } catch (const simp::SimpError& err) { if (nValues != -1) { LERROR(fmt::format( "Error when reading {} values in {} message: {}", nValues, dataKeyStr, err.message )); } else { LERROR(fmt::format( "Error when parsing number of values in {} message: {}", dataKeyStr, err.message )); } break; } // Get module auto module = global::moduleEngine->module(); if (!module) continue; module->storeData(identifier, dataKey, std::move(dataBuffer)); continue; } // Handle single-valued data key if (dataKey == simp::DataKey::PointUnit) { if (!handleStringValue(message, offset, identifier, dataKey, "PointUnit")) break; } // Handle fixed color else if (dataKey == simp::DataKey::Red) { if (!simp::readColorChannel(message, offset, dataKey, newColor, 0)) break; hasNewColor = true; } else if (dataKey == simp::DataKey::Green) { if (!simp::readColorChannel(message, offset, dataKey, newColor, 1)) break; hasNewColor = true; } else if (dataKey == simp::DataKey::Blue) { if (!simp::readColorChannel(message, offset, dataKey, newColor, 2)) break; hasNewColor = true; } else if (dataKey == simp::DataKey::Alpha) { if (!simp::readColorChannel(message, offset, dataKey, newColor, 3)) break; hasNewColor = true; } // Handle color mode else if (dataKey == simp::DataKey::ColormapEnabled) { if (!handleBoolValue( message, offset, identifier, dataKey, "ColormapEnabled", { storage::Key::Colormap, storage::Key::ColormapAttrData } )) { break; } } // Handle colormap min else if (dataKey == simp::DataKey::ColormapMin) { if (!handleSingleFloatValue(message, offset, identifier, dataKey, "ColormapMin")) break; } // Handle colormap max else if (dataKey == simp::DataKey::ColormapMax) { if (!handleSingleFloatValue(message, offset, identifier, dataKey, "ColormapMax")) break; } // Handle colormap NaN color else if (dataKey == simp::DataKey::ColormapNanR) { if (!simp::readColorChannel(message, offset, dataKey, newColormapNanColor, 0)) break; hasNewNanColor = true; } else if (dataKey == simp::DataKey::ColormapNanG) { if (!simp::readColorChannel(message, offset, dataKey, newColormapNanColor, 1)) break; hasNewNanColor = true; } else if (dataKey == simp::DataKey::ColormapNanB) { if (!simp::readColorChannel(message, offset, dataKey, newColormapNanColor, 2)) break; hasNewNanColor = true; } else if (dataKey == simp::DataKey::ColormapNanA) { if (!simp::readColorChannel(message, offset, dataKey, newColormapNanColor, 3)) break; hasNewNanColor = true; } // Handle colormap NaN mode else if (dataKey == simp::DataKey::ColormapNanMode) { if (!handleEnumValue(message, offset, identifier, dataKey, "ColormapNanMode")) break; } else if (dataKey == simp::DataKey::FixedSize) { if (!handleSingleFloatValue(message, offset, identifier, dataKey, "Size", connection, &onFixedPointSizeChange)) break; } else if (dataKey == simp::DataKey::LinearSizeEnabled) { if (!handleBoolValue( message, offset, identifier, dataKey, "LinearSizeEnabled", { storage::Key::LinearSizeAttrData } )) { break; } } else if (dataKey == simp::DataKey::LinearSizeMin) { if (!handleSingleFloatValue(message, offset, identifier, dataKey, "LinearSizeMin")) break; } else if (dataKey == simp::DataKey::LinearSizeMax) { if (!handleSingleFloatValue(message, offset, identifier, dataKey, "LinearSizeMax")) break; } else if (dataKey == simp::DataKey::Visibility) { if (!handleBoolValue( message, offset, identifier, dataKey, "Enabled", {}, connection, &onVisibilityChange )) { break; } } else if (dataKey == simp::DataKey::VelocityDistanceUnit) { if (!handleStringValue(message, offset, identifier, dataKey, "VelocityDistanceUnit")) break; } else if (dataKey == simp::DataKey::VelocityTimeUnit) { if (!handleStringValue(message, offset, identifier, dataKey, "VelocityTimeUnit")) break; } else if (dataKey == simp::DataKey::VelocityYearRecorded) { if (!simp::readDateValue(message, offset, dataKey, newVelocityDateRecorded, 0)) break; hasNewVelocityDateRecorded = true; } else if (dataKey == simp::DataKey::VelocityMonthRecorded) { if (!simp::readDateValue(message, offset, dataKey, newVelocityDateRecorded, 1)) break; hasNewVelocityDateRecorded = true; } else if (dataKey == simp::DataKey::VelocityDayRecorded) { if (!simp::readDateValue(message, offset, dataKey, newVelocityDateRecorded, 2)) break; hasNewVelocityDateRecorded = true; } else if (dataKey == simp::DataKey::VelocityNanMode) { if (!handleEnumValue(message, offset, identifier, dataKey, "VelocityNanMode")) break; } else if (dataKey == simp::DataKey::VelocityEnabled) { if (!handleBoolValue( message, offset, identifier, dataKey, "MotionEnabled", { storage::Key::VelocityData } )) { break; } } } if (hasNewColor) { handleColorValue(identifier, newColor, "Color", connection, &onFixedColorChange); } if (hasNewNanColor) { handleColorValue(identifier, newColormapNanColor, "ColormapNanColor"); } if (hasNewVelocityDateRecorded) { handleDateValue(identifier, newVelocityDateRecorded, "VelocityDateRecorded"); } } void handleRemoveSGNMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; try { simp::readValue(message, messageOffset, identifier); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading message: {}", err.message)); return; } const std::string currentAnchor = global::navigationHandler->orbitalNavigator().anchorNode()->identifier(); if (currentAnchor == identifier) { // If the deleted node is the current anchor, first change focus to the Sun global::scriptEngine->queueScript( "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', 'Sun')" "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')", scripting::ScriptEngine::RemoteScripting::Yes ); } global::scriptEngine->queueScript( "openspace.removeSceneGraphNode('" + identifier + "');", scripting::ScriptEngine::RemoteScripting::Yes ); connection->removeSceneGraphNode(identifier); LDEBUG(fmt::format("Scene graph node '{}' removed.", identifier)); } } // namespace 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 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())); if (connectionPtr->handshakeHasBeenMade()) { LERROR(fmt::format("Connection {} is already connected. Can't connect again.", connectionPtr->id())); return; } size_t offset = 0; std::string software; try { simp::readValue(message, offset, software); } catch (const simp::SimpError& err) { LERROR(fmt::format( "Error when parsing software name in {} message: {}", simp::getStringFromMessageType(simp::MessageType::Connection), err.message )); break; } std::string sendBack = fmt::format("{}{}", software, simp::DELIM); // Send back message to software to complete handshake std::vector subject; simp::toByteBuffer(subject, 0, sendBack); connectionPtr->sendMessage(messageType, subject); LINFO(fmt::format("OpenSpace has connected with {} through socket", software)); connectionPtr->setHandshakeHasBeenMade(); break; } case simp::MessageType::Data: { LDEBUG(fmt::format("Message recieved on connection {}... New Data", connectionPtr->id())); handleDataMessage(message, connectionPtr); break; } case simp::MessageType::RemoveSceneGraphNode: { LDEBUG(fmt::format("Message recieved on connection {}... Remove SGN", connectionPtr->id())); handleRemoveSGNMessage(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 = callbacks.begin(); while (callbackMapIt != callbacks.end()) { auto& [identifier, callbackList] = *callbackMapIt; try { LINFO(fmt::format("Callbacks for {}:", identifier)); const SceneGraphNode* sgn = global::renderEngine->scene()->sceneGraphNode(identifier); if (!sgn) throw std::exception{}; auto r = renderable(identifier); if (!r) throw std::exception{}; auto softwareIntegrationModule = global::moduleEngine->module(); auto callbacksIt = callbackList.begin(); while (callbacksIt != callbackList.end()) { auto& [callback, waitForData, description] = *callbacksIt; try { for (auto& waitFor : waitForData) { if (!softwareIntegrationModule->dataLoaded(identifier, waitFor)) { LINFO(fmt::format( "Callback '{}' NOT executed. Waiting for '{}':", description, storage::getStorageKeyString(waitFor) )); throw std::exception{}; } } callback(); callbacksIt = callbackList.erase(callbacksIt); LINFO(fmt::format("Callback '{}' executed", description)); } catch (std::exception&) { ++callbacksIt; } } if (callbackList.empty()) { callbackMapIt = callbacks.erase(callbackMapIt); callbacksRetries = 0; } else { callbackMapIt++; } } catch(std::exception &err) { ++callbacksRetries; ghoul_assert(callbacksRetries < 10, "Too many callback retries"); LDEBUG(fmt::format("Error when trying to run callback: {}", err.what())); break; } } } } // namespace openspace::softwareintegration::messagehandler