Full support of colormaps. Revamp of entire softwareintegration module. Update to SIMP version 1.8.

Co-authored-by: Jacob Molin <jacobmolin@users.noreply.github.com>
This commit is contained in:
Victor Lindquist
2022-05-20 00:23:00 -06:00
parent d62b9a49e5
commit 47a5c8e299
20 changed files with 1743 additions and 1064 deletions

View File

@@ -26,23 +26,25 @@ include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake)
set(HEADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/pointdatamessagehandler.h
${CMAKE_CURRENT_SOURCE_DIR}/simp.h
${CMAKE_CURRENT_SOURCE_DIR}/softwareintegrationmodule.h
${CMAKE_CURRENT_SOURCE_DIR}/syncabledatastorage.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}/simp.h
${CMAKE_CURRENT_SOURCE_DIR}/syncablefloatdatastorage.h
${CMAKE_CURRENT_SOURCE_DIR}/interruptibleconcurrentqueue.h
${CMAKE_CURRENT_SOURCE_DIR}/interruptibleconcurrentqueue.inl
)
source_group("Header Files" FILES ${HEADER_FILES})
set(SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/pointdatamessagehandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/simp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/softwareintegrationmodule.cpp
${CMAKE_CURRENT_SOURCE_DIR}/syncabledatastorage.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}/simp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/syncablefloatdatastorage.cpp
)
source_group("Source Files" FILES ${SOURCE_FILES})

View File

@@ -0,0 +1,54 @@
/*****************************************************************************************
* *
* 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___INTERRUPTABLECONCURRENTQUEUE___H__
#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___INTERRUPTABLECONCURRENTQUEUE___H__
#include <condition_variable>
#include <mutex>
#include <queue>
namespace openspace {
template <typename T>
class InterruptibleConcurrentQueue {
public:
T pop();
void interrupt();
void push(const T& item);
void push(T&& item);
private:
std::atomic_bool _interrupted{ false };
std::queue<T> _queue;
mutable std::mutex _mutex;
mutable std::condition_variable _notifyForPop;
};
} // namespace openspace
#include "interruptibleconcurrentqueue.inl"
#endif // __OPENSPACE_MODULE_SOFTWAREINTEGRATION___INTERRUPTABLECONCURRENTQUEUE___H__

View File

@@ -0,0 +1,73 @@
/*****************************************************************************************
* *
* 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. *
****************************************************************************************/
namespace openspace {
template <typename T>
T InterruptibleConcurrentQueue<T>::pop() {
auto isInterruptedOrNotEmpty = [this]() {
if (_interrupted) return true;
return !_queue.empty();
};
std::unique_lock lock(_mutex);
// Block execution until interrupted or queue not empty
if (!isInterruptedOrNotEmpty()) {
_notifyForPop.wait(lock, isInterruptedOrNotEmpty);
}
if (_interrupted) throw ghoul::RuntimeError{""};
auto item = _queue.front();
_queue.pop();
return item;
}
template <typename T>
void InterruptibleConcurrentQueue<T>::interrupt() {
_interrupted = true;
_notifyForPop.notify_all();
}
template <typename T>
void InterruptibleConcurrentQueue<T>::push(const T& item) {
if (_interrupted) return;
std::unique_lock<std::mutex> mlock(_mutex);
_queue.push(item);
mlock.unlock();
_notifyForPop.notify_one();
}
template <typename T>
void InterruptibleConcurrentQueue<T>::push(T&& item) {
if (_interrupted) return;
std::unique_lock<std::mutex> mlock(_mutex);
_queue.push(std::move(item));
mlock.unlock();
_notifyForPop.notify_one();
}
} // namespace openspace

View File

@@ -24,16 +24,11 @@
#include <modules/softwareintegration/network/networkengine.h>
#include <modules/softwareintegration/simp.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/globalscallbacks.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/scene/scene.h>
#include <openspace/scripting/scriptengine.h>
// #include <openspace/engine/syncengine.h>
// #include <openspace/engine/windowdelegate.h>
#include <ghoul/logging/logmanager.h>
// #include <openspace/util/timemanager.h>
namespace {
@@ -41,241 +36,196 @@ namespace {
} // namespace
namespace openspace {
NetworkEngine::NetworkEngine(const int port)
: _port{port}
{}
NetworkEngine::~NetworkEngine() {
stop();
}
void NetworkEngine::start() {
_socketServer.listen(_port);
_serverThread = std::thread([this]() { handleNewPeers(); });
_serverThread = std::thread([this]() { handleNewSoftwareConnections(); });
_eventLoopThread = std::thread([this]() { eventLoop(); });
}
void NetworkEngine::stop() {
for (auto [id, peer] : _peers) {
disconnect(peer);
_shouldStopServerThread = true;
{
std::lock_guard guardSoftwareConnections(_softwareConnectionsMutex);
for (auto& [id, connectionPtr] : _softwareConnections) {
SoftwareConnection::NetworkEngineFriends::stopThread(connectionPtr);
}
}
_shouldStop = true;
_incomingMessages.interrupt();
_shouldStopEventThread = true;
_socketServer.close();
_softwareConnections.clear();
if (_serverThread.joinable()) {
_serverThread.join();
}
}
if (_eventLoopThread.joinable()) {
_eventLoopThread.join();
}
}
void NetworkEngine::update() {
_pointDataMessageHandler.preSyncUpdate();
void NetworkEngine::postSync() {
_pointDataMessageHandler.postSync();
}
bool NetworkEngine::isConnected(const std::shared_ptr<Peer> peer) const {
return peer->status == Peer::Status::Connected;
}
std::shared_ptr<NetworkEngine::Peer> NetworkEngine::peer(size_t id) {
// std::lock_guard<std::mutex> lock(_peerListMutex);
auto it = _peers.find(id);
if (it == _peers.end()) return nullptr;
return it->second;
}
void NetworkEngine::disconnect(std::shared_ptr<Peer> peer) {
if (peer == nullptr) return;
if (!isConnected(peer)) return; // Already disconnected
--_nConnections;
peer->status = Peer::Status::Disconnected;
peer->connection.disconnect();
// auto sgnIterator = peer->sceneGraphNodes.begin();
// while (sgnIterator != peer->sceneGraphNodes.end()) {
// LDEBUG(fmt::format("t{}: disconnect() - removing SGN '{}'", std::this_thread::get_id(), *sgnIterator));
// removeSceneGraphNode(*sgnIterator);
// peer->sceneGraphNodes.erase(sgnIterator);
// ++sgnIterator;
// }
if (peer->thread.joinable()) peer->thread.join();
_peers.erase(peer->id);
}
void NetworkEngine::eventLoop() {
while (!_shouldStop) {
if (!_incomingMessages.empty()) {
auto pm = _incomingMessages.pop();
handlePeerMessage(std::move(pm));
}
}
}
void NetworkEngine::handleNewPeers() {
while (!_shouldStop) {
std::unique_ptr<ghoul::io::TcpSocket> socket =
_socketServer.awaitPendingTcpSocket();
void NetworkEngine::handleNewSoftwareConnections() {
while (!_shouldStopServerThread) {
std::unique_ptr<ghoul::io::TcpSocket> socket = _socketServer.awaitPendingTcpSocket();
if (!socket) return;
socket->startStreams();
std::shared_ptr<Peer> p = std::make_shared<Peer>(Peer{
_nextConnectionId++,
"",
{},
SoftwareConnection(std::move(socket)),
{},
Peer::Status::Connected
});
auto [it, peerInserted] = _peers.emplace(p->id, p);
if (peerInserted){
// The thread 'it.first->second->thread' will run 'peerEventLoop()' as fast as it can until stopped
it->second->thread = std::thread(
[this, &p] () {
peerEventLoop(p->id);
});
auto p = std::make_shared<SoftwareConnection>(SoftwareConnection{ 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 id) {
void NetworkEngine::peerEventLoop(size_t connection_id) {
using namespace std::literals::chrono_literals;
auto connectionPtr = getSoftwareConnection(connection_id);
while (!_shouldStop) {
std::shared_ptr<Peer> p = peer(id);
if (!p || !p->connection.isConnectedOrConnecting()
|| p->status == Peer::Status::Disconnected) {
break;
}
while (!connectionPtr->shouldStopThread()) {
try {
SoftwareConnection::Message m = p->connection.receiveMessageFromSoftware();
_incomingMessages.push({ id, m });
SoftwareConnection::Message m = connectionPtr->receiveMessageFromSoftware();
_incomingMessages.push({ connection_id, m });
}
catch (const SoftwareConnection::SoftwareConnectionLostError& err) {
if (p->status == Peer::Status::Connected) {
LERROR(fmt::format("Connection lost to {}: {}", p->id, err.message));
disconnect(p);
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::Disconnection }
});
}
break;
}
}
}
void NetworkEngine::handlePeerMessage(PeerMessage peerMessage) {
const size_t peerId = peerMessage.peerId;
std::shared_ptr<Peer> peerPtr = peer(peerId);
if(peerPtr == nullptr) {
LERROR(fmt::format("No peer with peerId {} could be found", peerId));
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<SoftwareConnection> 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 = peerMessage.message.type;
std::vector<char>& message = peerMessage.message.content;
const simp::MessageType messageType = incomingMessage.message.type;
std::vector<char>& message = incomingMessage.message.content;
switch (messageType) {
case simp::MessageType::Connection:
{
const std::string software{ message.begin(), message.end() };
LINFO(fmt::format("OpenSpace has connected with {} through socket", software));
// Send back message to software to complete handshake
peerPtr->connection.sendMessage(simp::formatConnectionMessage(software));
break;
}
case simp::MessageType::ReadPointData: {
LDEBUG("Message recieved.. Point Data");
_pointDataMessageHandler.handlePointDataMessage(message, peerPtr->connection, peerPtr->sceneGraphNodes);
break;
}
case simp::MessageType::RemoveSceneGraphNode: {
const std::string identifier(message.begin(), message.end());
LDEBUG(fmt::format("Message recieved.. Delete SGN: {}", identifier));
removeSceneGraphNode(identifier);
case simp::MessageType::Connection: {
size_t offset = 0;
const std::string software = simp::readString(message, offset);
auto sgnIterator = peerPtr->sceneGraphNodes.begin();
while (sgnIterator != peerPtr->sceneGraphNodes.end()) {
if (*sgnIterator == identifier) {
peerPtr->sceneGraphNodes.erase(sgnIterator);
break;
}
++sgnIterator;
// 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::RemoveSceneGraphNode: {
LDEBUG(fmt::format("Message recieved.. Remove SGN"));
_pointDataMessageHandler.handleRemoveSGNMessage(message, connectionPtr);
break;
}
case simp::MessageType::Color: {
LDEBUG(fmt::format("Message recieved.. New Color"));
_pointDataMessageHandler.handleFixedColorMessage(message, connectionPtr);
break;
}
case simp::MessageType::Colormap: {
LDEBUG(fmt::format("Message recieved.. New Colormap"));
_pointDataMessageHandler.handleColormapMessage(message, connectionPtr);
break;
}
case simp::MessageType::AttributeData: {
LDEBUG(fmt::format("Message recieved.. New Colormap scalar values"));
_pointDataMessageHandler.handleAttributeDataMessage(message, connectionPtr);
break;
}
case simp::MessageType::Opacity: {
LDEBUG(fmt::format("Message recieved.. New Opacity"));
_pointDataMessageHandler.handleOpacityMessage(message, connectionPtr);
break;
}
case simp::MessageType::Size: {
LDEBUG(fmt::format("Message recieved.. New Size"));
_pointDataMessageHandler.handleFixedPointSizeMessage(message, connectionPtr);
break;
}
case simp::MessageType::Visibility: {
LDEBUG(fmt::format("Message recieved.. New Visibility"));
_pointDataMessageHandler.handleVisiblityMessage(message, connectionPtr);
break;
}
case simp::MessageType::Disconnection: {
LDEBUG(fmt::format("Message recieved.. Disconnect software connection: {}", incomingMessage.connection_id));
std::lock_guard guard(_softwareConnectionsMutex);
if (_softwareConnections.count(incomingMessage.connection_id)) {
_softwareConnections.erase(incomingMessage.connection_id);
}
SoftwareConnection::NetworkEngineFriends::stopThread(connectionPtr);
break;
}
default: {
LERROR(fmt::format(
"Unsupported message type: {}", incomingMessage.message.rawMessageType
));
break;
}
break;
}
case simp::MessageType::Color: {
const std::string colorMessage(message.begin(), message.end());
LDEBUG(fmt::format("Message recieved.. New Color: {}", colorMessage));
_pointDataMessageHandler.handleColorMessage(message);
break;
}
case simp::MessageType::ColorMap: {
const std::string colorMapMessage(message.begin(), message.end());
LDEBUG(fmt::format("Message recieved.. New ColorMap"));
_pointDataMessageHandler.handleColorMapMessage(message);
break;
}
case simp::MessageType::Opacity: {
const std::string opacityMessage(message.begin(), message.end());
LDEBUG(fmt::format("Message recieved.. New Opacity: {}", opacityMessage));
_pointDataMessageHandler.handleOpacityMessage(message);
break;
}
case simp::MessageType::Size: {
const std::string sizeMessage(message.begin(), message.end());
LDEBUG(fmt::format("Message recieved.. New Size: {}", sizeMessage));
_pointDataMessageHandler.handlePointSizeMessage(message);
break;
}
case simp::MessageType::Visibility: {
const std::string visibilityMessage(message.begin(), message.end());
LDEBUG(fmt::format("Message recieved.. New Visibility: {}", visibilityMessage));
_pointDataMessageHandler.handleVisiblityMessage(message);
break;
}
case simp::MessageType::Disconnection: {
LDEBUG(fmt::format("Message recieved.. Disconnect peer: {}", peerPtr->id));
disconnect(peerPtr);
break;
}
default:
LERROR(fmt::format(
"Unsupported message type: {}", static_cast<int>(messageType)
));
break;
}
}
void NetworkEngine::removeSceneGraphNode(const std::string &identifier) {
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
openspace::global::scriptEngine->queueScript(
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', 'Sun')"
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')",
scripting::ScriptEngine::RemoteScripting::Yes
);
}
openspace::global::scriptEngine->queueScript(
"openspace.removeSceneGraphNode('" + identifier + "');",
scripting::ScriptEngine::RemoteScripting::Yes
);
// TODO: remove from sceneGraphNodes
// peer->sceneGraphNodes.erase(sgnIterator);
LDEBUG(fmt::format("Scene graph node '{}' removed.", identifier));
}
} // namespace openspace

View File

@@ -27,7 +27,8 @@
#include <modules/softwareintegration/network/softwareconnection.h>
#include <modules/softwareintegration/pointdatamessagehandler.h>
#include <openspace/util/concurrentqueue.h>
#include <modules/softwareintegration/interruptibleconcurrentqueue.h>
#include <modules/softwareintegration/simp.h>
#include <ghoul/io/socket/tcpsocketserver.h>
namespace openspace {
@@ -35,61 +36,42 @@ namespace openspace {
class NetworkEngine {
public:
NetworkEngine(const int port = 4700);
~NetworkEngine();
struct Peer {
enum class Status : uint32_t {
Disconnected = 0,
Connected
};
size_t id;
std::string name;
std::thread thread;
SoftwareConnection connection;
std::list<std::string> sceneGraphNodes; // TODO: Change to std::unordered_set?
Status status;
};
struct PeerMessage {
size_t peerId;
SoftwareConnection::Message message;
struct IncomingMessage {
size_t connection_id{1};
SoftwareConnection::Message message{ simp::MessageType::Unknown };
};
void start();
void stop();
void update();
void postSync();
private:
void disconnect(std::shared_ptr<Peer> peer);
void handleNewPeers();
void peerEventLoop(size_t id);
void handlePeerMessage(PeerMessage peerMessage);
void handleNewSoftwareConnections();
void handleIncomingMessage(IncomingMessage incomingMessage);
void peerEventLoop(size_t connection_id);
void eventLoop();
bool isConnected(const std::shared_ptr<Peer> peer) const;
// The destuction of the object a shared_ptr is pointing to, occurs when the pointer no longer has any owners
std::shared_ptr<SoftwareConnection> getSoftwareConnection(size_t id);
void removeSceneGraphNode(const std::string &identifier);
std::shared_ptr<Peer> peer(size_t id);
std::unordered_map<size_t, std::shared_ptr<Peer>> _peers;
mutable std::mutex _peerListMutex;
std::unordered_map<size_t, std::shared_ptr<SoftwareConnection>> _softwareConnections;
std::mutex _softwareConnectionsMutex;
ghoul::io::TcpSocketServer _socketServer;
size_t _nextConnectionId = 1;
std::atomic_size_t _nConnections = 0;
std::thread _serverThread;
std::atomic_bool _shouldStopServerThread = false;
std::thread _eventLoopThread;
std::atomic_bool _shouldStopEventThread = false;
std::atomic_bool _shouldStop = false;
const int _port;
// Message handlers
PointDataMessageHandler _pointDataMessageHandler;
ConcurrentQueue<PeerMessage> _incomingMessages;
InterruptibleConcurrentQueue<IncomingMessage> _incomingMessages;
};
} // namespace openspace

View File

@@ -24,11 +24,12 @@
#include <modules/softwareintegration/network/softwareconnection.h>
#include <modules/softwareintegration/simp.h>
#include <ghoul/logging/logmanager.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/syncengine.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/query/query.h>
#include <openspace/rendering/renderable.h>
namespace {
constexpr const char* _loggerCat = "SoftwareConnection";
@@ -36,52 +37,172 @@ namespace {
namespace openspace {
SoftwareConnection::Message::Message(simp::MessageType type, std::vector<char> content)
: type{ type }, content{ std::move(content) }
{}
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<ghoul::io::TcpSocket> socket)
: _socket(std::move(socket))
: _id{ _nextConnectionId++ }, _socket{ std::move(socket) }, _sceneGraphNodes{},
_thread{}, _shouldStopThread{ false }
{}
bool SoftwareConnection::isConnected() const {
return _socket->isConnected();
}
SoftwareConnection::SoftwareConnection(SoftwareConnection&& sc)
: _id{ std::move(sc._id) }, _socket{ std::move(sc._socket) },
_isConnected{ sc._isConnected }, _sceneGraphNodes{ std::move(sc._sceneGraphNodes) },
_thread{}, _shouldStopThread{ false }
{}
bool SoftwareConnection::isConnectedOrConnecting() const {
return _socket->isConnected() || _socket->isConnecting();
}
SoftwareConnection::~SoftwareConnection() {
LINFO(fmt::format("Remove software connection {}", _id));
bool SoftwareConnection::sendMessage(std::string message) {
LDEBUG(fmt::format("In SoftwareConnection::sendMessage()", 0));
if (!_isConnected) return;
_isConnected = false;
if (_isListening) {
if (!_socket->put<char>(message.data(), message.size())) {
return false;
}
LDEBUG(fmt::format("Message sent: {}", message));
}
else {
_isListening = true;
return false;
for (auto& identifier : _sceneGraphNodes) {
removePropertySubscriptions(identifier);
}
return true;
}
void SoftwareConnection::disconnect() {
if (_socket) {
sendMessage(simp::formatDisconnectionMessage());
_socket->disconnect();
}
}
ghoul::io::TcpSocket* SoftwareConnection::socket() {
return _socket.get();
void SoftwareConnection::addPropertySubscription(
const std::string propertyName,
const std::string& identifier,
std::function<void()> newHandler
) {
// Get renderable
auto r = renderable(identifier);
if (!r) {
LWARNING(fmt::format(
"Couldn't add property subscription. Renderable \"{}\" doesn't exist",
identifier
));
return;
}
if (!r->hasProperty(propertyName)) {
LWARNING(fmt::format(
"Couldn't add property subscription. Property \"{}\" doesn't exist on \"{}\"",
propertyName, identifier
));
return;
}
auto property = r->property(propertyName);
OnChangeHandle onChangeHandle = property->onChange(newHandler);
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()) {
// Property subscription already exists
removeExistingPropertySubscription(identifier, property, onChangeHandle);
propertySubscription->second = onChangeHandle;
}
else {
// Property subscription doesn't exist
propertySubscriptions->second.emplace(propertyName, onChangeHandle);
}
}
else {
// No properties have been subscribed to on this SGN
PropertySubscriptions newPropertySubscriptionMap{ { propertyName, onChangeHandle } };
_subscribedProperties.emplace(identifier, std::move(newPropertySubscriptionMap));
}
}
void SoftwareConnection::removePropertySubscriptions(const std::string& identifier) {
// Get renderable
auto r = renderable(identifier);
if (!r) {
LWARNING(fmt::format(
"Couldn't remove property subscriptions, renderable {} doesn't exist",
identifier
));
return;
}
auto propertySubscriptions = _subscribedProperties.find(identifier);
if (propertySubscriptions == _subscribedProperties.end()) return;
for (auto& [propertyName, onChangeHandle] : propertySubscriptions->second) {
if (!r->hasProperty(propertyName)) {
LWARNING(fmt::format(
"Couldn't remove property subscription. Property \"{}\" doesn't exist on \"{}\"",
propertyName, identifier
));
continue;
}
auto propertySubscription = propertySubscriptions->second.find(propertyName);
if (propertySubscription == propertySubscriptions->second.end()) continue;
auto property = r->property(propertyName);
removeExistingPropertySubscription(identifier, property, onChangeHandle);
}
_subscribedProperties.erase(propertySubscriptions);
}
void SoftwareConnection::removeExistingPropertySubscription(
const std::string& identifier,
properties::Property *property,
OnChangeHandle onChangeHandle
) {
property->removeOnChange(onChangeHandle);
auto propertySubscriptions = _subscribedProperties.find(identifier);
propertySubscriptions->second.erase(property->identifier());
}
void SoftwareConnection::disconnect() {
_socket->disconnect();
}
bool SoftwareConnection::isConnected() const {
return _isConnected && _socket && _socket->isConnected();
}
bool SoftwareConnection::isConnectedOrConnecting() const {
return _isConnected && _socket && (_socket->isConnected() || _socket->isConnecting());
}
bool SoftwareConnection::sendMessage(const std::string& message) {
try {
if (!_socket || !isConnected()) {
throw SoftwareConnectionLostError("Connection lost...");
}
if (_socket->put<char>(message.data(), message.size())) {
LDEBUG(fmt::format("Message sent: {}", message));
return true;
}
}
catch (const SoftwareConnectionLostError& err) {
LERROR(fmt::format("Couldn't send message: \"{}\", due to: {}", message, err.message));
}
catch (const std::exception& err) {
LERROR(fmt::format("Couldn't send message: \"{}\", due to: {}", message, err.what()));
}
return false;
}
void SoftwareConnection::addSceneGraphNode(const std::string& identifier) {
_sceneGraphNodes.insert(identifier);
}
void SoftwareConnection::removeSceneGraphNode(const std::string& identifier) {
size_t amountRemoved = _sceneGraphNodes.erase(identifier);
if (amountRemoved > 0) {
removePropertySubscriptions(identifier);
}
}
/**
@@ -102,15 +223,14 @@ SoftwareConnection::Message SoftwareConnection::receiveMessageFromSoftware() {
throw SoftwareConnectionLostError("Failed to read header from socket. Disconnecting.");
}
// Read and convert version number: Byte 0-2
std::string version;
// Read the protocol version number: Byte 0-2
std::string protocolVersionIn;
for (int i = 0; i < 3; i++) {
version.push_back(headerBuffer[i]);
protocolVersionIn.push_back(headerBuffer[i]);
}
const float protocolVersionIn = std::stof(version);
// Make sure that header matches the protocol version
if (abs(protocolVersionIn - simp::ProtocolVersion) >= FLT_EPSILON) {
if (protocolVersionIn != simp::ProtocolVersion) {
throw SoftwareConnectionLostError(fmt::format(
"Protocol versions do not match. Remote version: {}, Local version: {}",
protocolVersionIn,
@@ -131,36 +251,44 @@ SoftwareConnection::Message SoftwareConnection::receiveMessageFromSoftware() {
}
const size_t subjectSize = std::stoi(subjectSizeIn);
std::string rawHeader;
for (int i = 0; i < 22; i++) {
rawHeader.push_back(headerBuffer[i]);
}
LDEBUG(fmt::format("Message received with header: {}", rawHeader));
// TODO: Remove this
// std::string rawHeader;
// for (int i = 0; i < 22; i++) {
// rawHeader.push_back(headerBuffer[i]);
// }
// LDEBUG(fmt::format("Message received with header: {}", rawHeader));
auto typeEnum = simp::getMessageType(type);
// Receive the message data
if (typeEnum != simp::MessageType::Disconnection) {
if (typeEnum != simp::MessageType::Disconnection && typeEnum != simp::MessageType::Unknown) {
subjectBuffer.resize(subjectSize);
if (!_socket->get(subjectBuffer.data(), subjectSize)) {
throw SoftwareConnectionLostError("Failed to read message from socket. Disconnecting.");
}
}
// Delegate decoding depending on message type
if (typeEnum == simp::MessageType::Color
|| typeEnum == simp::MessageType::Opacity
|| typeEnum == simp::MessageType::Size
|| typeEnum == simp::MessageType::Visibility
) {
_isListening = false;
}
return Message{ typeEnum, subjectBuffer, type };
}
if (typeEnum == simp::MessageType::Unknown) {
LERROR(fmt::format("Unsupported message type: {}. Disconnecting...", type));
}
bool SoftwareConnection::shouldStopThread() {
return _shouldStopThread;
}
return Message(typeEnum, subjectBuffer);
size_t SoftwareConnection::id() {
return _id;
}
void SoftwareConnection::setThread(std::thread& t) {
_thread = std::move(t);
}
void SoftwareConnection::NetworkEngineFriends::stopThread(std::shared_ptr<SoftwareConnection> connectionPtr) {
connectionPtr->_shouldStopThread = true;
connectionPtr->disconnect();
if (connectionPtr->_thread.joinable()) {
connectionPtr->_thread.join();
}
}
} // namespace openspace

View File

@@ -28,38 +28,85 @@
#include <openspace/network/messagestructures.h>
#include <modules/softwareintegration/simp.h>
#include <ghoul/io/socket/tcpsocket.h>
#include <openspace/properties/property.h>
#include <unordered_set>
#include <unordered_map>
namespace openspace {
class Renderable;
class SoftwareConnection {
public:
using OnChangeHandle = properties::Property::OnChangeHandle;
using PropertySubscriptions = std::unordered_map<std::string, properties::Property::OnChangeHandle>;
using SubscribedProperties = std::unordered_map<std::string, PropertySubscriptions>;
struct Message {
Message() = default;
Message(simp::MessageType type, std::vector<char> content);
simp::MessageType type;
std::vector<char> content;
std::vector<char> content{};
std::string rawMessageType{ "" };
};
class SoftwareConnectionLostError : public ghoul::RuntimeError {
public:
explicit SoftwareConnectionLostError(const std::string& msg);
};
class SoftwareConnectionLostError;
SoftwareConnection(std::unique_ptr<ghoul::io::TcpSocket> socket);
explicit SoftwareConnection(std::unique_ptr<ghoul::io::TcpSocket> socket);
SoftwareConnection(SoftwareConnection&& p);
~SoftwareConnection();
void disconnect();
bool isConnected() const;
bool isConnectedOrConnecting() const;
bool sendMessage(std::string message);
void disconnect();
bool sendMessage(const std::string& message);
ghoul::io::TcpSocket* socket();
void addPropertySubscription(
const std::string propertyName,
const std::string& identifier,
std::function<void()> newHandler
);
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<SoftwareConnection> connectionPtr);
friend class NetworkEngine;
};
private:
bool _isListening = true;
void removePropertySubscriptions(const std::string& identifier);
void removeExistingPropertySubscription(
const std::string& identifier,
properties::Property *property,
OnChangeHandle onChangeHandle
);
SubscribedProperties _subscribedProperties;
std::unordered_set<std::string> _sceneGraphNodes;
bool _isConnected = true;
std::unique_ptr<ghoul::io::TcpSocket> _socket;
size_t _id;
std::thread _thread;
std::atomic_bool _shouldStopThread;
static std::atomic_size_t _nextConnectionId;
};
class SoftwareConnection::SoftwareConnectionLostError : public ghoul::RuntimeError {
public:
explicit SoftwareConnectionLostError(const std::string& msg);
};
} // namespace openspace

View File

@@ -23,9 +23,10 @@
****************************************************************************************/
#include <modules/softwareintegration/pointdatamessagehandler.h>
#include <modules/softwareintegration/softwareintegrationmodule.h>
#include <modules/softwareintegration/simp.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/query/query.h>
@@ -44,272 +45,462 @@ namespace {
namespace openspace {
void PointDataMessageHandler::handlePointDataMessage(const std::vector<char>& message,
SoftwareConnection& connection,
std::list<std::string>& sceneGraphNodes)
{
void PointDataMessageHandler::handlePointDataMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
size_t messageOffset = 0;
std::string identifier;
glm::vec4 color;
float opacity;
float size;
std::string guiName;
int nPoints;
int dimensionality;
checkRenderable(message, messageOffset, connection, identifier);
size_t nPoints;
size_t dimensionality;
std::vector<float> points;
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 = readString(message, messageOffset);
sceneGraphNodes.push_back(identifier);
color = readColor(message, messageOffset);
opacity = readFloatValue(message, messageOffset);
size = readFloatValue(message, messageOffset);
guiName = readString(message, messageOffset);
nPoints = readIntValue(message, messageOffset);
dimensionality = readIntValue(message, messageOffset);
points.reserve(nPoints * dimensionality);
points = readPointData(message, messageOffset, nPoints, dimensionality);
nPoints = static_cast<size_t>(simp::readIntValue(message, messageOffset));
dimensionality = static_cast<size_t>(simp::readIntValue(message, messageOffset));
simp::readPointData(message, messageOffset, nPoints, dimensionality, points);
}
catch (const simp::SimpError& err) {
LERROR(fmt::format("Error when reading point data message: {}", err.message));
return;
}
using namespace std::string_literals;
// Create a renderable
ghoul::Dictionary renderable;
renderable.setValue("Type", "RenderablePointsCloud"s);
renderable.setValue("Color", static_cast<glm::dvec3>(glm::vec3{color.r, color.g, color.b}));
renderable.setValue("Opacity", static_cast<double>(opacity));
renderable.setValue("Size", static_cast<double>(size));
renderable.setValue("Identifier", identifier);
// Use the renderable identifier as the data key
const std::string key = identifier + "-DataPoints";
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
module->storeData(key, std::move(points));
renderable.setValue("DataStorageKey", key);
ghoul::Dictionary gui;
gui.setValue("Name", guiName);
gui.setValue("Path", "/Software Integration"s);
ghoul::Dictionary node;
node.setValue("Identifier", identifier);
node.setValue("Renderable", renderable);
node.setValue("GUI", gui);
openspace::global::scriptEngine->queueScript(
"openspace.addSceneGraphNode(" + ghoul::formatLua(node) + ")",
scripting::ScriptEngine::RemoteScripting::Yes
);
openspace::global::scriptEngine->queueScript(
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.RetargetAnchor', nil)"
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', '" + identifier + "')"
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')"
"openspace.setPropertyValueSingle('Modules.CefWebGui.Reload', nil)", // Reload WebGUI so that SoftwareIntegration GUI appears
scripting::ScriptEngine::RemoteScripting::Yes
);
// We have to wait until the renderable exists before we can subscribe to
// changes in its properties
auto callback = [this, identifier, &connection]() {
subscribeToRenderableUpdates(identifier, connection);
};
_onceNodeExistsCallbacks.emplace(identifier, callback);
}
void PointDataMessageHandler::handleColorMessage(const std::vector<char>& message) {
void PointDataMessageHandler::handleFixedColorMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
size_t messageOffset = 0;
const std::string identifier = readString(message, messageOffset);
const glm::vec3 color = readColor(message, messageOffset);
std::string identifier;
// Get renderable
auto r = getRenderable(identifier);
if (!r) return;
checkRenderable(message, messageOffset, connection, identifier);
// Get color of renderable
properties::Property* colorProperty = r->property("Color");
std::any propertyAny = colorProperty->get();
glm::vec3 propertyColor = std::any_cast<glm::vec3>(propertyAny);
if (propertyColor != glm::vec3(0, 0, 0)) {
LWARNING(fmt::format("propertyColor '{}'", propertyColor));
glm::vec4 color;
try {
color = simp::readColor(message, messageOffset);
}
// Update color of renderable
if (propertyColor != color) {
std::string colorScript = fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Color', {});",
identifier, ghoul::to_string(color)
);
std::string disableColorMapScript = fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColorMapEnabled', {});",
identifier, false
);
openspace::global::scriptEngine->queueScript(
colorScript,
scripting::ScriptEngine::RemoteScripting::Yes
);
openspace::global::scriptEngine->queueScript(
disableColorMapScript,
scripting::ScriptEngine::RemoteScripting::Yes
);
}
}
void PointDataMessageHandler::handleColorMapMessage(const std::vector<char>& message) {
size_t messageOffset = 0;
const std::string identifier = readString(message, messageOffset);
const std::vector<float> colorMap = readColorMap(message, messageOffset);
// Use the renderable identifier as the data key
const std::string key = identifier + "-ColorMap";
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
module->storeData(key, std::move(colorMap));
// Get renderable
auto r = getRenderable(identifier);
if (!r) return;
std::string script = fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.LoadNewColorMap', {});",
identifier, true
);
openspace::global::scriptEngine->queueScript(
script,
scripting::ScriptEngine::RemoteScripting::Yes
);
}
void PointDataMessageHandler::handleOpacityMessage(const std::vector<char>& message) {
size_t messageOffset = 0;
const std::string identifier = readString(message, messageOffset);
const float opacity = readFloatValue(message, messageOffset);
// Get renderable
auto r = getRenderable(identifier);
if (!r) return;
// Get opacity of renderable
properties::Property* opacityProperty = r->property("Opacity");
auto propertyAny = opacityProperty->get();
float propertyOpacity = std::any_cast<float>(propertyAny);
// Update opacity of renderable
if (propertyOpacity != opacity) {
std::string script = fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Opacity', {});",
identifier, ghoul::to_string(opacity)
);
openspace::global::scriptEngine->queueScript(
script,
scripting::ScriptEngine::RemoteScripting::Yes
);
}
}
void PointDataMessageHandler::handlePointSizeMessage(const std::vector<char>& message) {
size_t messageOffset = 0;
const std::string identifier = readString(message, messageOffset);
float size = readFloatValue(message, messageOffset);
// Get renderable
auto r = getRenderable(identifier);
if (!r) return;
// Get size of renderable
properties::Property* sizeProperty = r->property("Size");
auto propertyAny = sizeProperty->get();
float propertySize = std::any_cast<float>(propertyAny);
// Update size of renderable
if (propertySize != size) {
// TODO: Add to script when the "send back to glue" stuff is done
// "openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {}, 1);",
std::string script = fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {});",
identifier, ghoul::to_string(size)
);
openspace::global::scriptEngine->queueScript(
script,
scripting::ScriptEngine::RemoteScripting::Yes
);
}
}
void PointDataMessageHandler::handleVisiblityMessage(const std::vector<char>& message) {
size_t messageOffset = 0;
const std::string identifier = readString(message, messageOffset);
std::string visibilityMessage;
visibilityMessage.push_back(message[messageOffset]);
// Get renderable
auto r = getRenderable(identifier);
if (!r) return;
// Toggle visibility of renderable
const std::string visability = visibilityMessage == "T" ? "true" : "false";
std::string script = fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Enabled', {});",
identifier, visability
);
openspace::global::scriptEngine->queueScript(
script,
scripting::ScriptEngine::RemoteScripting::Yes
);
}
void PointDataMessageHandler::preSyncUpdate() {
if (_onceNodeExistsCallbacks.empty()) {
catch (const simp::SimpError& err) {
LERROR(fmt::format("Error when reading fixed color message: {}", err.message));
return;
}
// Check if the scene graph node has been created. If so, call the corresponding
// callback function to set up any subscriptions
auto it = _onceNodeExistsCallbacks.begin();
while (it != _onceNodeExistsCallbacks.end()) {
const std::string& identifier = it->first;
const std::function<void()>& callback = it->second;
const SceneGraphNode* sgn =
global::renderEngine->scene()->sceneGraphNode(identifier);
auto callback = [this, identifier, color] {
// Get renderable
auto r = getRenderable(identifier);
if (sgn) {
callback();
it = _onceNodeExistsCallbacks.erase(it);
continue;
// Get color of renderable
properties::Property* colorProperty = r->property("Color");
glm::vec4 propertyColor = std::any_cast<glm::vec4>(colorProperty->get());
// Update color of renderable
if (propertyColor != color) {
openspace::global::scriptEngine->queueScript(
fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Color', {});",
identifier, ghoul::to_string(color)
),
scripting::ScriptEngine::RemoteScripting::Yes
);
}
openspace::global::scriptEngine->queueScript(
fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});",
identifier, "false"
),
scripting::ScriptEngine::RemoteScripting::Yes
);
};
addCallback(identifier, callback);
}
void PointDataMessageHandler::handleColormapMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
size_t messageOffset = 0;
std::string identifier;
checkRenderable(message, messageOffset, connection, identifier);
float min;
float max;
size_t nColors;
std::vector<float> colorMap;
try {
min = simp::readFloatValue(message, messageOffset);
max = simp::readFloatValue(message, messageOffset);
nColors = static_cast<size_t>(simp::readIntValue(message, messageOffset));
simp::readColormap(message, messageOffset, nColors, colorMap);
}
catch (const simp::SimpError& err) {
LERROR(fmt::format("Error when reading color map message: {}", err.message));
return;
}
// Use the renderable identifier as the data key
const std::string key = identifier + "-Colormap";
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
module->storeData(key, std::move(colorMap));
auto callback = [this, identifier, min, max] {
// Get renderable
auto r = getRenderable(identifier);
openspace::global::scriptEngine->queueScript(
fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});",
identifier, "true"
),
scripting::ScriptEngine::RemoteScripting::Yes
);
properties::Property* colormapMinProperty = r->property("ColormapMin");
float colormapMin = std::any_cast<float>(colormapMinProperty->get());
if (min != colormapMin) {
openspace::global::scriptEngine->queueScript(
fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapMin', {});",
identifier, ghoul::to_string(min)
),
scripting::ScriptEngine::RemoteScripting::Yes
);
}
properties::Property* colormapMaxProperty = r->property("ColormapMax");
float colormapMax = std::any_cast<float>(colormapMaxProperty->get());
if (max != colormapMax) {
openspace::global::scriptEngine->queueScript(
fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapMax', {});",
identifier, ghoul::to_string(max)
),
scripting::ScriptEngine::RemoteScripting::Yes
);
}
};
addCallback(identifier, callback);
}
void PointDataMessageHandler::handleAttributeDataMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
size_t messageOffset = 0;
std::string identifier;
checkRenderable(message, messageOffset, connection, identifier);
std::string usedFor;
size_t nValues;
std::vector<float> attributeData;
try {
usedFor = simp::readString(message, messageOffset);
nValues = static_cast<size_t>(simp::readIntValue(message, messageOffset));
attributeData.reserve(nValues);
for (size_t i = 0; i < nValues; ++i)
attributeData.push_back(simp::readFloatValue(message, messageOffset));
}
catch (const simp::SimpError& err) {
LERROR(fmt::format("Error when reading message with scalars for color map: {}", err.message));
return;
}
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
// Use the renderable identifier as the data key
std::string key = identifier;
if (usedFor == "ColormapAttributeData") {
key += "-CmapAttributeData";
}
else {
LERROR(fmt::format(
"The received attribute data had the \"usedFor\" value {}, which is unrecognized.",
usedFor
));
return;
}
module->storeData(key, std::move(attributeData));
auto callback = [this, identifier, usedFor] {
if (usedFor == "ColormapAttributeData") {
openspace::global::scriptEngine->queueScript(
fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});",
identifier, "true"
),
scripting::ScriptEngine::RemoteScripting::Yes
);
}
};
addCallback(identifier, callback);
}
void PointDataMessageHandler::handleOpacityMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
size_t messageOffset = 0;
std::string identifier;
checkRenderable(message, messageOffset, connection, identifier);
float opacity;
try {
opacity = simp::readFloatValue(message, messageOffset);
}
catch (const simp::SimpError& err) {
LERROR(fmt::format("Error when reading opacity message: {}", err.message));
return;
}
auto callback = [this, identifier, opacity] {
// Get renderable
auto r = getRenderable(identifier);
// Get opacity from renderable
properties::Property* opacityProperty = r->property("Opacity");
auto propertyAny = opacityProperty->get();
float propertyOpacity = std::any_cast<float>(propertyAny);
// Update opacity of renderable
if (propertyOpacity != opacity) {
std::string script = fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Opacity', {});",
identifier, ghoul::to_string(opacity)
);
openspace::global::scriptEngine->queueScript(
script,
scripting::ScriptEngine::RemoteScripting::Yes
);
}
};
addCallback(identifier, callback);
}
void PointDataMessageHandler::handleFixedPointSizeMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
size_t messageOffset = 0;
std::string identifier;
checkRenderable(message, messageOffset, connection, identifier);
float size;
try {
size = simp::readFloatValue(message, messageOffset);
}
catch (const simp::SimpError& err) {
LERROR(fmt::format("Error when reading fixed point size message: {}", err.message));
return;
}
auto callback = [this, identifier, size] {
// Get renderable
auto r = getRenderable(identifier);
// Get size from renderable
properties::Property* sizeProperty = r->property("Size");
auto propertyAny = sizeProperty->get();
float propertySize = std::any_cast<float>(propertyAny);
// Update size of renderable
if (propertySize != size) {
// TODO: Add interpolation to script, but do not send back
// updates to external software until the interpolation is done
// Use this: "openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {}, 1);",
std::string script = fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {});",
identifier, ghoul::to_string(size)
);
openspace::global::scriptEngine->queueScript(
script,
scripting::ScriptEngine::RemoteScripting::Yes
);
}
};
addCallback(identifier, callback);
}
void PointDataMessageHandler::handleVisiblityMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
size_t messageOffset = 0;
std::string identifier;
checkRenderable(message, messageOffset, connection, identifier);
std::string visibilityMessage;
try {
visibilityMessage = simp::readString(message, messageOffset);
}
catch (const simp::SimpError& err) {
LERROR(fmt::format("Error when reading visibility message: {}", err.message));
return;
}
auto callback = [this, identifier, visibilityMessage] {
// Toggle visibility from renderable
const std::string visability = visibilityMessage == "T" ? "true" : "false";
std::string script = fmt::format(
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Enabled', {});",
identifier, visability
);
openspace::global::scriptEngine->queueScript(
script,
scripting::ScriptEngine::RemoteScripting::Yes
);
};
addCallback(identifier, callback);
}
void PointDataMessageHandler::handleRemoveSGNMessage(const std::vector<char>& message,std::shared_ptr<SoftwareConnection> connection) {
size_t messageOffset = 0;
std::string identifier;
try {
identifier = simp::readString(message, messageOffset);
}
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
openspace::global::scriptEngine->queueScript(
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', 'Sun')"
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')",
scripting::ScriptEngine::RemoteScripting::Yes
);
}
openspace::global::scriptEngine->queueScript(
"openspace.removeSceneGraphNode('" + identifier + "');",
scripting::ScriptEngine::RemoteScripting::Yes
);
connection->removeSceneGraphNode(identifier);
LDEBUG(fmt::format("Scene graph node '{}' removed.", identifier));
}
void PointDataMessageHandler::postSync() {
std::lock_guard guard(_onceNodeExistsCallbacksMutex);
// 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& [identifier, callbackList] = *callbackMapIt;
try {
const SceneGraphNode* sgn = global::renderEngine->scene()->sceneGraphNode(identifier);
if (!sgn) throw std::exception{};
auto r = getRenderable(identifier);
if (!r) throw std::exception{};
auto callbacksIt = callbackList.begin();
while (callbacksIt != callbackList.end()) {
auto& callback = *callbacksIt;
try {
callback();
callbacksIt = callbackList.erase(callbacksIt);
}
catch (std::exception&) {
++callbacksIt;
}
}
callbackMapIt = _onceNodeExistsCallbacks.erase(callbackMapIt);
_onceNodeExistsCallbacksRetries = 0;
}
catch(std::exception &err) {
++_onceNodeExistsCallbacksRetries;
ghoul_assert(_onceNodeExistsCallbacksRetries < 10, "Too many callback retries");
LDEBUG(fmt::format("Error when trying to run callback: {}", err.what()));
break;
}
it++;
}
}
const Renderable* PointDataMessageHandler::getRenderable(const std::string& identifier) const {
const Renderable* PointDataMessageHandler::getRenderable(const std::string& identifier) {
return renderable(identifier);
}
void PointDataMessageHandler::checkRenderable(
const std::vector<char>& message, size_t& messageOffset,
std::shared_ptr<SoftwareConnection> 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);
if (!r) {
LERROR(fmt::format("No renderable with identifier '{}' was found", identifier));
return nullptr;
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);
openspace::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 subscriptionCallback = [this, identifier, connection] {
subscribeToRenderableUpdates(identifier, connection);
};
addCallback(identifier, subscriptionCallback);
}
else {
subscribeToRenderableUpdates(identifier, connection);
}
auto reanchorCallback = [identifier] {
openspace::global::scriptEngine->queueScript(
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.RetargetAnchor', nil)"
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', '" + identifier + "')"
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')",
scripting::ScriptEngine::RemoteScripting::Yes
);
};
return r;
addCallback(identifier, reanchorCallback);
}
void PointDataMessageHandler::subscribeToRenderableUpdates(
const std::string& identifier,
SoftwareConnection& connection
std::shared_ptr<SoftwareConnection> connection
) {
// Get renderable
auto aRenderable = getRenderable(identifier);
if (!aRenderable) return;
if (!connection.isConnected()) {
if (!connection->isConnected()) {
LERROR(fmt::format(
"Could not subscribe to updates for renderable '{}' due to lost connection",
identifier
@@ -320,206 +511,67 @@ void PointDataMessageHandler::subscribeToRenderableUpdates(
properties::Property* colorProperty = aRenderable->property("Color");
properties::Property* opacityProperty = aRenderable->property("Opacity");
properties::Property* sizeProperty = aRenderable->property("Size");
properties::Property* visibilityProperty = aRenderable->property("ToggleVisibility");
properties::Property* visibilityProperty = aRenderable->property("Enabled");
// Update color of renderable
auto updateColor = [colorProperty, identifier, &connection]() {
const std::string value = colorProperty->getStringValue();
const std::string message = simp::formatUpdateMessage(simp::MessageType::Color, identifier, value);
connection.sendMessage(message);
auto updateColor = [colorProperty, identifier, connection]() {
glm::vec4 color = std::any_cast<glm::vec4>(colorProperty->get());
const std::string message = simp::formatColorMessage(identifier, color);
connection->sendMessage(message);
};
if (colorProperty) {
colorProperty->onChange(updateColor);
connection->addPropertySubscription("Color", identifier, updateColor);
}
// Update opacity of renderable
auto updateOpacity = [opacityProperty, identifier, &connection]() {
const std::string value = opacityProperty->getStringValue();
const std::string message = simp::formatUpdateMessage(simp::MessageType::Opacity, identifier, value);
connection.sendMessage(message);
auto updateOpacity = [opacityProperty, identifier, connection]() {
float value = std::any_cast<float>(opacityProperty->get());
std::string hex_value = simp::floatToHex(value);
const std::string message = simp::formatUpdateMessage(simp::MessageType::Opacity, identifier, hex_value);
connection->sendMessage(message);
};
if (opacityProperty) {
opacityProperty->onChange(updateOpacity);
connection->addPropertySubscription("Opacity", identifier, updateOpacity);
}
// Update size of renderable
auto updateSize = [sizeProperty, identifier, &connection]() {
const std::string value = sizeProperty->getStringValue();
const std::string message = simp::formatUpdateMessage(simp::MessageType::Size, identifier, value);
connection.sendMessage(message);
auto updateSize = [sizeProperty, identifier, connection]() {
float value = std::any_cast<float>(sizeProperty->get());
std::string hex_value = simp::floatToHex(value);
const std::string message = simp::formatUpdateMessage(simp::MessageType::Size, identifier, hex_value);
connection->sendMessage(message);
};
if (sizeProperty) {
sizeProperty->onChange(updateSize);
connection->addPropertySubscription("Size", identifier, updateSize);
}
// Toggle visibility of renderable
auto toggleVisibility = [visibilityProperty, identifier, &connection]() {
bool isVisible = visibilityProperty->getStringValue() == "true";
auto toggleVisibility = [visibilityProperty, identifier, connection]() {
bool isVisible = std::any_cast<bool>(visibilityProperty->get());
std::string_view visibilityFlag = isVisible ? "T" : "F";
const std::string message = simp::formatUpdateMessage(simp::MessageType::Visibility, identifier, visibilityFlag);
connection.sendMessage(message);
connection->sendMessage(message);
};
if (visibilityProperty) {
visibilityProperty->onChange(toggleVisibility);
connection->addPropertySubscription("Enabled", identifier, toggleVisibility);
}
}
int PointDataMessageHandler::readIntValue(const std::vector<char>& message, size_t& offset) {
std::string string_value;
int value;
bool isHex = false;
while (!simp::isEndOfCurrentValue(message, offset)) {
char c = message[offset];
if (c == 'x' || c == 'X') isHex = true;
string_value.push_back(c);
offset++;
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 callbacks{ std::move(newCallback) };
_onceNodeExistsCallbacks.emplace(std::move(identifier), std::move(callbacks) );
}
try {
value = std::stoi(string_value, nullptr, isHex ? 16 : 10);
else {
it->second.push_back(std::move(newCallback));
}
catch(std::exception &err) {
throw simp::SimpError(
simp::ErrorCode::Generic,
fmt::format("Error when trying to parse the integer {}: {}", string_value, err.what())
);
}
++offset;
return value;
}
float PointDataMessageHandler::readFloatValue(const std::vector<char>& message, size_t& offset) {
std::string string_value;
float value;
while (!simp::isEndOfCurrentValue(message, offset)) {
string_value.push_back(message[offset]);
offset++;
}
try {
// long l;
// l = std::strtol(string_value.data(), (char**)NULL, 16);
// value = (float)l;
value = std::stof(string_value);
// if ((*s == '0') && ((*s == 'X') || (*s == 'x'))) {
// unsigned long ul = strtoul(d, NULL, 16);
// return (float) ul;
// }
// double d = atof(s, NULL);
// return (float) d;
}
catch(std::exception &err) {
throw simp::SimpError(
simp::ErrorCode::Generic,
fmt::format("Error when trying to parse the float {}: {}", string_value, err.what())
);
}
++offset;
return value;
}
std::vector<float> PointDataMessageHandler::readColorMap(const std::vector<char>& message,
size_t& offset) {
std::vector<float> colorMap;
while (message[offset] != simp::SEP) {
glm::vec4 color = readSingleColor(message, offset);
// ColorMap should be stored in a sequential vector
// of floats for syncing between nodes and when
// loaded to as a texture in the shader.
colorMap.push_back(color[0]);
colorMap.push_back(color[1]);
colorMap.push_back(color[2]);
colorMap.push_back(color[3]);
}
offset++;
return colorMap;
}
glm::vec4 PointDataMessageHandler::readSingleColor(const std::vector<char>& message, size_t& offset) {
if (message[offset] != '[') {
throw simp::SimpError(
simp::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 simp::SimpError(
simp::ErrorCode::Generic,
fmt::format("Expected to read ']', got {} in 'readColor'", message[offset])
);
}
++offset;
return { r, g, b, a };
}
glm::vec4 PointDataMessageHandler::readColor(const std::vector<char>& message, size_t& offset) {
glm::vec4 color = readSingleColor(message, offset);
++offset;
return color;
}
std::string PointDataMessageHandler::readString(const std::vector<char>& message, size_t& offset) {
std::string value;
while (!simp::isEndOfCurrentValue(message, offset)) {
value.push_back(message[offset]);
++offset;
}
++offset;
return value;
}
std::vector<float> PointDataMessageHandler::readPointData(
const std::vector<char>& message,
size_t& offset,
int nPoints,
int dimensionality
) {
std::vector<float> result;
result.reserve(nPoints * dimensionality);
while (!simp::isEndOfCurrentValue(message, offset)) {
if (message[offset] != '[') {
throw simp::SimpError(
simp::ErrorCode::Generic,
fmt::format("Expected to read '[', got {} in 'readPointData'", message[offset])
);
}
++offset;
for (int i = 0; i < dimensionality; ++i) {
result.push_back(readFloatValue(message, offset));
}
if (message[offset] != ']') {
throw simp::SimpError(
simp::ErrorCode::Generic,
fmt::format("Expected to read ']', got {} in 'readPointData'", message[offset])
);
}
++offset;
}
++offset;
return result;
}
} // namespace openspace

View File

@@ -25,6 +25,8 @@
#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___POINTDATAMESSAGEHANDLER___H__
#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___POINTDATAMESSAGEHANDLER___H__
#include <unordered_map>
#include <openspace/properties/propertyowner.h>
#include <modules/softwareintegration/network/softwareconnection.h>
@@ -34,34 +36,36 @@ namespace openspace {
class Renderable;
class PointDataMessageHandler {
public:
void handlePointDataMessage(const std::vector<char>& message,
SoftwareConnection& connection, std::list<std::string>& sceneGraphNodes);
void handleColorMessage(const std::vector<char>& message);
void handleColorMapMessage(const std::vector<char>& message);
void handleOpacityMessage(const std::vector<char>& message);
void handlePointSizeMessage(const std::vector<char>& message);
void handleVisiblityMessage(const std::vector<char>& message);
using Callback = std::function<void()>;
using CallbackList = std::vector<std::function<void()>>;
using CallbackMap = std::unordered_map<std::string, CallbackList>;
void preSyncUpdate();
public:
void handlePointDataMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
void handleFixedColorMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
void handleColormapMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
void handleAttributeDataMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
void handleOpacityMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
void handleFixedPointSizeMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
void handleVisiblityMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
void handleRemoveSGNMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
void postSync();
private:
const Renderable* getRenderable(const std::string& identifier) const;
void subscribeToRenderableUpdates(const std::string& identifier,
SoftwareConnection& connection);
float readFloatValue(const std::vector<char>& message, size_t& offset);
int readIntValue(const std::vector<char>& message, size_t& offset);
glm::vec4 readSingleColor(const std::vector<char>& message, size_t& offset);
glm::vec4 readColor(const std::vector<char>& message, size_t& offset);
std::string readString(const std::vector<char>& message, size_t& offset);
std::vector<float> readPointData(
const std::vector<char>& message, size_t& offset, int nPoints, int dimensionality
const Renderable* getRenderable(const std::string& identifier);
void checkRenderable(
const std::vector<char>& message, size_t& messageOffset,
std::shared_ptr<SoftwareConnection> connection, std::string& identifier
);
std::vector<float> readColorMap(const std::vector<char>& message, size_t& offset);
std::unordered_map<std::string, std::function<void()>> _onceNodeExistsCallbacks;
void subscribeToRenderableUpdates(const std::string& identifier, std::shared_ptr<SoftwareConnection> connection);
void addCallback(const std::string& identifier, const Callback& newCallback);
CallbackMap _onceNodeExistsCallbacks;
std::mutex _onceNodeExistsCallbacksMutex;
size_t _onceNodeExistsCallbacksRetries{0};
};
} // namespace openspace

View File

@@ -36,17 +36,17 @@
#include <ghoul/opengl/openglstatecache.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/opengl/texture.h>
#include <ghoul/opengl/textureunit.h>
#include <fstream>
#include <optional>
namespace {
constexpr const char* _loggerCat = "PointsCloud";
constexpr const std::array<const char*, 8> UniformNames = {
"color", "opacity", "size", "modelMatrix",
"cameraUp", "cameraViewProjectionMatrix", "eyePosition", "sizeOption"
constexpr const std::array<const char*, 12> UniformNames = {
"color", "opacity", "size", "modelMatrix", "cameraUp", "cameraViewProjectionMatrix",
"eyePosition", "sizeOption", "colormapTexture", "colormapMin", "colormapMax",
"colormapEnabled"
};
//"colorMapTexture",
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
"Color",
@@ -60,28 +60,16 @@ namespace {
"The size of the points."
};
constexpr openspace::properties::Property::PropertyInfo ToggleVisibilityInfo = {
"ToggleVisibility",
"Toggle Visibility",
"Enables/Disables the drawing of points."
};
constexpr openspace::properties::Property::PropertyInfo DataInfo = {
"Data",
"Data",
"Data to use for the positions of the points, given in Parsec."
};
constexpr openspace::properties::Property::PropertyInfo DataStorageKeyInfo = {
"DataStorageKey",
"Data Storage Key",
"Key used to access a dataset in the module's centralized storage, which is synced to all nodes."
};
constexpr openspace::properties::Property::PropertyInfo IdentifierInfo = {
"Identifier",
"Identifier",
"Identifier used as part of key to access data in syncable central storage."
"Identifier used as part of key to access data in centralized central storage."
};
constexpr openspace::properties::Property::PropertyInfo SizeOptionInfo = {
@@ -90,44 +78,47 @@ namespace {
"This value determines how the size of the data points are rendered."
};
constexpr openspace::properties::Property::PropertyInfo ColorMapEnabledInfo = {
"ColorMapEnabled",
"ColorMap Enabled",
constexpr openspace::properties::Property::PropertyInfo ColormapEnabledInfo = {
"ColormapEnabled",
"Colormap Enabled",
"Boolean to determine whether to use colormap or not."
};
constexpr openspace::properties::Property::PropertyInfo LoadNewColorMapInfo = {
"LoadNewColorMap",
"Load New ColorMap",
"Boolean to determine whether to load new colormap or not."
constexpr openspace::properties::Property::PropertyInfo ColormapMinInfo = {
"ColormapMin",
"Colormap min",
"Minimum value to sample from color map."
};
constexpr openspace::properties::Property::PropertyInfo ColormapMaxInfo = {
"ColormapMax",
"Colormap max",
"Maximum value to sample from color map."
};
struct [[codegen::Dictionary(RenderablePointsCloud)]] Parameters {
// [[codegen::verbatim(ColorInfo.description)]]
std::optional<glm::vec3> color;
std::optional<glm::vec4> color;
// [[codegen::verbatim(SizeInfo.description)]]
std::optional<float> size;
// [[codegen::verbatim(ToggleVisibilityInfo.description)]]
std::optional<bool> toggleVisiblity;
// [[codegen::verbatim(DataInfo.description)]]
std::optional<std::vector<glm::vec3>> data;
// [[codegen::verbatim(DataStorageKeyInfo.description)]]
std::optional<std::string> dataStorageKey;
// [[codegen::verbatim(IdentifierInfo.description)]]
std::optional<std::string> identifier;
// [[codegen::verbatim(ColorMapEnabledInfo.description)]]
std::optional<std::string> colorMapEnabled;
// [[codegen::verbatim(ColormapMinInfo.description)]]
std::optional<float> colormapMin;
// [[codegen::verbatim(LoadNewColorMapInfo.description)]]
std::optional<std::string> loadNewColorMap;
// [[codegen::verbatim(ColormapMaxInfo.description)]]
std::optional<float> colormapMax;
enum class SizeOption {
// [[codegen::verbatim(ColormapEnabledInfo.description)]]
std::optional<bool> colormapEnabled;
enum class SizeOption : uint32_t {
Uniform,
NonUniform
};
@@ -145,12 +136,12 @@ documentation::Documentation RenderablePointsCloud::Documentation() {
RenderablePointsCloud::RenderablePointsCloud(const ghoul::Dictionary& dictionary)
: Renderable(dictionary)
, _color(ColorInfo, glm::vec3(0.5f), glm::vec3(0.f), glm::vec3(1.f))
, _size(SizeInfo, 1.f, 0.f, 150.f)
, _isVisible(ToggleVisibilityInfo, true)
, _color(ColorInfo, glm::vec4(glm::vec3(0.5f), 1.f), glm::vec4(0.f), glm::vec4(1.f), glm::vec4(.001f))
, _size(SizeInfo, 1.f, 0.f, 30.f, .001f)
, _sizeOption(SizeOptionInfo, properties::OptionProperty::DisplayType::Dropdown)
, _colorMapEnabled(ColorMapEnabledInfo, false)
, _loadNewColorMap(LoadNewColorMapInfo, false)
, _colormapEnabled(ColormapEnabledInfo, false)
, _colormapMin(ColormapMinInfo)
, _colormapMax(ColormapMaxInfo)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
@@ -158,27 +149,37 @@ RenderablePointsCloud::RenderablePointsCloud(const ghoul::Dictionary& dictionary
_color.setViewOption(properties::Property::ViewOptions::Color);
addProperty(_color);
// Check if a key to data stored in the module's centralized memory was included
_nValuesPerPoint = 3;
_dataStorageKey = p.dataStorageKey.value();
_identifier = p.identifier.value();
_size = p.size.value_or(_size);
addProperty(_size);
_isVisible = p.toggleVisiblity.value_or(_isVisible);
addProperty(_isVisible);
addProperty(_opacity);
addProperty(_colorMapEnabled);
auto colormapMinMaxChecker = [this] {
if (_colormapMin.value() > _colormapMax.value()) {
auto temp = _colormapMin.value();
_colormapMin = _colormapMax.value();
_colormapMax = temp;
}
};
addProperty(_loadNewColorMap);
_colormapMin = p.colormapMin.value_or(_colormapMin);
_colormapMin.setVisibility(properties::Property::Visibility::Hidden);
_colormapMin.onChange(colormapMinMaxChecker);
addProperty(_colormapMin);
_colormapMax = p.colormapMax.value_or(_colormapMax);
_colormapMax.setVisibility(properties::Property::Visibility::Hidden);
_colormapMax.onChange(colormapMinMaxChecker);
addProperty(_colormapMax);
_colormapEnabled = p.colormapEnabled.value_or(_colormapEnabled);
addProperty(_colormapEnabled);
_sizeOption.addOptions({
{ SizeOption::Uniform, "Uniform" },
{ SizeOption::NonUniform, "NonUniform" }
{ SizeOption::NonUniform, "Non Uniform" }
});
if (p.sizeOption.has_value()) {
switch (*p.sizeOption) {
@@ -190,16 +191,11 @@ RenderablePointsCloud::RenderablePointsCloud(const ghoul::Dictionary& dictionary
break;
}
}
_sizeOption.onChange([&] { _isDirty = true; });
addProperty(_sizeOption);
}
bool RenderablePointsCloud::isReady() const {
return _shaderProgram && (!_fullData.empty());
}
void RenderablePointsCloud::initialize() {
loadData();
return _shaderProgram && _identifier.has_value() && _identifier.value() != "";
}
void RenderablePointsCloud::initializeGL() {
@@ -214,11 +210,12 @@ void RenderablePointsCloud::initializeGL() {
}
void RenderablePointsCloud::deinitializeGL() {
glDeleteVertexArrays(1, &_vertexArrayObjectID);
_vertexArrayObjectID = 0;
glDeleteBuffers(1, &_vbo);
_vbo = 0;
glDeleteVertexArrays(1, &_vao);
_vao = 0;
glDeleteBuffers(1, &_vertexBufferObjectID);
_vertexBufferObjectID = 0;
_colormapTexture.reset();
if (_shaderProgram) {
global::renderEngine->removeRenderProgram(_shaderProgram.get());
@@ -227,13 +224,8 @@ void RenderablePointsCloud::deinitializeGL() {
}
void RenderablePointsCloud::render(const RenderData& data, RendererTasks&) {
if (_fullData.empty()) {
return;
}
if (!_isVisible) {
return;
}
auto pointDataSlice = getDataSlice(DataSliceKey::Points);
if (pointDataSlice->empty()) return;
_shaderProgram->activate();
@@ -259,17 +251,28 @@ void RenderablePointsCloud::render(const RenderData& data, RendererTasks&) {
cameraViewProjectionMatrix
);
if (_loadNewColorMap) loadColorMap();
if (_colorMapEnabled && _colorMapTexture) {
// TODO: Set _colorMapTexture in shader. A trasnfer function similar to
ghoul::opengl::TextureUnit colorUnit;
if (_colormapTexture) {
// _colormapAttributeData
// TODO: Set _colormapTextre in shader. A trasnfer function similar to
// 'bv2rgb' in C:\OpenSpace\SoftwareIntegration\modules\space\shaders\star_fs.glsl
// should probably be used.
colorUnit.activate();
_colormapTexture->bind();
_shaderProgram->setUniform(_uniformCache.colormapTexture, colorUnit);
}
else {
_shaderProgram->setUniform(_uniformCache.color, _color);
// We need to set the uniform to something, or the shader doesn't work
_shaderProgram->setUniform(_uniformCache.colormapTexture, colorUnit);
}
_shaderProgram->setUniform(_uniformCache.colormapMin, _colormapMin);
_shaderProgram->setUniform(_uniformCache.colormapMax, _colormapMax);
_shaderProgram->setUniform(_uniformCache.colormapEnabled, _colormapEnabled);
_shaderProgram->setUniform(_uniformCache.color, _color);
_shaderProgram->setUniform(_uniformCache.opacity, _opacity);
_shaderProgram->setUniform(_uniformCache.size, _size);
_shaderProgram->setUniform(_uniformCache.sizeOption, _sizeOption);
@@ -279,8 +282,8 @@ void RenderablePointsCloud::render(const RenderData& data, RendererTasks&) {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(false);
glBindVertexArray(_vertexArrayObjectID);
const GLsizei nPoints = static_cast<GLsizei>(_fullData.size() / _nValuesPerPoint);
glBindVertexArray(_vao);
const GLsizei nPoints = static_cast<GLsizei>(pointDataSlice->size() / 3);
glDrawArrays(GL_POINTS, 0, nPoints);
glBindVertexArray(0);
@@ -297,130 +300,228 @@ void RenderablePointsCloud::update(const UpdateData&) {
ghoul::opengl::updateUniformLocations(*_shaderProgram, _uniformCache, UniformNames);
}
if (!_isDirty) {
bool updatedDataSlices = checkDataStorage();
if (updatedDataSlices) {
if (_vao == 0) {
glGenVertexArrays(1, &_vao);
LDEBUG(fmt::format("Generating Vertex Array id '{}'", _vao));
}
if (_vbo == 0) {
glGenBuffers(1, &_vbo);
LDEBUG(fmt::format("Generating Vertex Buffer Object id '{}'", _vbo));
}
glBindVertexArray(_vao);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
auto pointDataSlice = getDataSlice(DataSliceKey::Points);
auto colormapAttrDataSlice = getDataSlice(DataSliceKey::ColormapAttributes);
if (pointDataSlice->empty()) return;
// ========================== Create resulting data slice and buffer it ==========================
std::vector<float> bufferData;
bufferData.reserve(pointDataSlice->size() / 3);
for(size_t i = 0, j = 0; j < pointDataSlice->size(); ++i, j += 3) {
bufferData.push_back(pointDataSlice->at(j));
bufferData.push_back(pointDataSlice->at(j + 1));
bufferData.push_back(pointDataSlice->at(j + 2));
if (colormapAttrDataSlice->size() > i) {
bufferData.push_back(colormapAttrDataSlice->at(i));
}
else {
bufferData.push_back(0.0);
}
}
glBufferData(
GL_ARRAY_BUFFER,
bufferData.size() * sizeof(GLfloat),
bufferData.data(),
GL_STATIC_DRAW
);
// ==============================================================================================
// ========================================= VAO stuff =========================================
GLsizei stride = static_cast<GLsizei>(sizeof(GLfloat) * 4);
GLint positionAttribute = _shaderProgram->attributeLocation("in_position");
glEnableVertexAttribArray(positionAttribute);
glVertexAttribPointer(
positionAttribute,
3,
GL_FLOAT,
GL_FALSE,
stride,
nullptr
);
if (_hasLoadedColormapAttributeData) {
GLint colormapScalarsAttribute = _shaderProgram->attributeLocation("in_colormapAttributeScalar");
glEnableVertexAttribArray(colormapScalarsAttribute);
glVertexAttribPointer(
colormapScalarsAttribute,
1,
GL_FLOAT,
GL_FALSE,
stride,
reinterpret_cast<void*>(sizeof(GLfloat) * 3)
);
}
// ==============================================================================================
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
// This can happen if the user checks the "_colormapEnabled" checkbox in the GUI
auto colormapAttributeData = getDataSlice(DataSliceKey::ColormapAttributes);
if (_colormapEnabled.value() && (!_colormapTexture || colormapAttributeData->empty())) {
if (!_colormapTexture) {
LINFO("Color map not loaded. Has it been sent from external software?");
}
else {
LINFO("Color map attribute data not loaded. Has it been sent from external software?");
}
_colormapEnabled = false;
}
}
bool RenderablePointsCloud::checkDataStorage() {
if (!_identifier.has_value()) {
LERROR("No identifier found in renderable");
return false;
}
bool updatedDataSlices = false;
auto softwareIntegrationModule = global::moduleEngine->module<SoftwareIntegrationModule>();
std::string dataPointsKey = _identifier.value() + "-DataPoints";
std::string colorMapKey = _identifier.value() + "-Colormap";
std::string colorMapScalarsKey = _identifier.value() + "-CmapAttributeData";
if (softwareIntegrationModule->isDataDirty(dataPointsKey)) {
loadData(dataPointsKey, softwareIntegrationModule);
updatedDataSlices = true;
}
if (softwareIntegrationModule->isDataDirty(colorMapKey)) {
loadColormap(colorMapKey, softwareIntegrationModule);
}
if (softwareIntegrationModule->isDataDirty(colorMapScalarsKey)) {
loadCmapAttributeData(colorMapScalarsKey, softwareIntegrationModule);
updatedDataSlices = true;
}
return updatedDataSlices;
}
void RenderablePointsCloud::loadData(
const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule
) {
// Fetch data from module's centralized storage
auto fullPointData = softwareIntegrationModule->fetchData(storageKey);
if (fullPointData.empty()) {
LWARNING("There was an issue trying to fetch the point data from the centralized storage.");
return;
}
LDEBUG("Regenerating data");
// @TODO (emmbr26 2021-02-11) This 'createDataSlice'step doesn't really seem
// necessary, but could rather be combined with the loadData() step?
createDataSlice();
int size = static_cast<int>(_slicedData.size());
if (_vertexArrayObjectID == 0) {
glGenVertexArrays(1, &_vertexArrayObjectID);
LDEBUG(fmt::format("Generating Vertex Array id '{}'", _vertexArrayObjectID));
}
if (_vertexBufferObjectID == 0) {
glGenBuffers(1, &_vertexBufferObjectID);
LDEBUG(fmt::format("Generating Vertex Buffer Object id '{}'", _vertexBufferObjectID));
}
glBindVertexArray(_vertexArrayObjectID);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferObjectID);
glBufferData(
GL_ARRAY_BUFFER,
size * sizeof(float),
_slicedData.data(),
GL_STATIC_DRAW
);
GLint positionAttribute = _shaderProgram->attributeLocation("in_position");
glEnableVertexAttribArray(positionAttribute);
glVertexAttribPointer(
positionAttribute,
4,
GL_FLOAT,
GL_FALSE,
0,
nullptr
);
glBindVertexArray(0);
_isDirty = false;
}
void RenderablePointsCloud::createDataSlice() {
_slicedData.clear();
_slicedData.reserve(4 * (_fullData.size() / _nValuesPerPoint));
auto pointDataSlice = getDataSlice(DataSliceKey::Points);
pointDataSlice->clear();
pointDataSlice->reserve(fullPointData.size() * 3);
// Create data slice
auto addPosition = [&](const glm::vec4& pos) {
for (int j = 0; j < 4; ++j) {
_slicedData.push_back(pos[j]);
for (glm::vec4::length_type j = 0; j < glm::vec4::length() - 1; ++j) {
pointDataSlice->push_back(pos[j]);
}
};
for (size_t i = 0; i < _fullData.size(); i += _nValuesPerPoint) {
glm::dvec4 transformedPos = glm::dvec4(
_fullData[i + 0],
_fullData[i + 1],
_fullData[i + 2],
for (size_t i = 0; i < fullPointData.size(); i += 3) {
glm::dvec4 transformedPos = {
fullPointData[i + 0],
fullPointData[i + 1],
fullPointData[i + 2],
1.0
);
};
// W-normalization
transformedPos /= transformedPos.w;
transformedPos *= openspace::distanceconstants::Parsec;
transformedPos *= distanceconstants::Parsec;
addPosition(transformedPos);
}
}
void RenderablePointsCloud::loadData() {
if (!_dataStorageKey.has_value()) {
LWARNING("No point data found");
return;
}
// @TODO: potentially combine point data with additional data about the points,
// such as luminosity
// Fetch data from module's centralized storage
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
_fullData = module->fetchData(_dataStorageKey.value());
_isDirty = true;
}
void RenderablePointsCloud::loadColorMap() {
if (!_identifier.has_value()) {
LWARNING("No data storage identifier found");
return;
}
// Fetch data from module's centralized storage
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
std::vector<float> colorMap = module->fetchData(_identifier.value() + "-ColorMap");
void RenderablePointsCloud::loadColormap(
const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule
) {
std::vector<float> colorMap = softwareIntegrationModule->fetchData(storageKey);
if (colorMap.empty()) {
LWARNING("There was an issue trying to fetch the colormap data from the syncable storage.");
LWARNING("There was an issue trying to fetch the colormap data from the centralized storage.");
return;
}
int width = colorMap.size();
uint8_t* values = new uint8_t[width];
size_t nValues = colorMap.size();
uint8_t* values = new uint8_t[nValues];
int i = 0;
while (i < width) {
values[i++] = static_cast<uint8_t>(colorMap[i] * 255);
values[i++] = static_cast<uint8_t>(colorMap[i] * 255);
values[i++] = static_cast<uint8_t>(colorMap[i] * 255);
values[i++] = static_cast<uint8_t>(colorMap[i] * 255);
for (size_t i = 0; i < nValues; ++i) {
values[i] = static_cast<uint8_t>(colorMap[i] * 255);
}
GLenum type = GL_TEXTURE_1D;
_colorMapTexture = std::make_unique<ghoul::opengl::Texture>(
_colormapTexture.reset();
_colormapTexture = std::make_unique<ghoul::opengl::Texture>(
values,
glm::uvec3(width, 1, 1),
type,
glm::size3_t(nValues / 4, 1, 1),
GL_TEXTURE_1D,
ghoul::opengl::Texture::Format::RGBA
);
_colormapTexture->uploadTexture();
_isDirty = true;
_loadNewColorMap = false;
_colorMapEnabled = true;
_hasLoadedColormap = true;
}
void RenderablePointsCloud::loadCmapAttributeData(
const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule
) {
auto colormapAttributeData = softwareIntegrationModule->fetchData(storageKey);
if (colormapAttributeData.empty()) {
LWARNING("There was an issue trying to fetch the colormap data from the centralized storage.");
return;
}
auto pointDataSlice = getDataSlice(DataSliceKey::Points);
if (pointDataSlice->size() / 3 != colormapAttributeData.size()) {
LWARNING(fmt::format(
"There is a mismatch in the amount of colormap attribute scalars ({}) and the amount of points ({})",
colormapAttributeData.size(), pointDataSlice->size() / 3
));
_colormapEnabled = false;
return;
}
auto colormapAttributeDataSlice = getDataSlice(DataSliceKey::ColormapAttributes);
colormapAttributeDataSlice->clear();
colormapAttributeDataSlice->reserve(colormapAttributeData.size());
for (size_t i = 0; i < (pointDataSlice->size() / 3); ++i) {
colormapAttributeDataSlice->push_back(colormapAttributeData[i]);
}
_hasLoadedColormapAttributeData = true;
LDEBUG("Rerendering colormap attribute data");
}
std::shared_ptr<RenderablePointsCloud::DataSlice> RenderablePointsCloud::getDataSlice(DataSliceKey key) {
if (!_dataSlices.count(key)) {
_dataSlices.insert({ key, std::make_shared<DataSlice>() });
}
return _dataSlices.at(key);
}
} // namespace openspace

View File

@@ -25,15 +25,17 @@
#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___RENDERABLEPOINTSCLOUD___H__
#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___RENDERABLEPOINTSCLOUD___H__
#include <optional>
#include <openspace/rendering/renderable.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/vector/vec3property.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/vector/vec4property.h>
#include <openspace/properties/optionproperty.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <ghoul/opengl/uniformcache.h>
#include <optional>
namespace openspace::documentation { struct Documentation; }
@@ -45,56 +47,69 @@ namespace ghoul::opengl {
namespace openspace {
class SoftwareIntegrationModule;
class RenderablePointsCloud : public Renderable {
public:
RenderablePointsCloud(const ghoul::Dictionary& dictionary);
void initialize() override;
void initializeGL() override;
void deinitializeGL() override;
bool isReady() const override;
void render(const RenderData& data, RendererTasks& rendererTask) override;
void render(const RenderData& data, RendererTasks&) override;
void update(const UpdateData& data) override;
static documentation::Documentation Documentation();
protected:
void createDataSlice();
void loadData();
void loadColorMap();
void loadData(const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule);
void loadColormap(const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule);
void loadCmapAttributeData(const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule);
bool _hasPointData = false;
bool _isDirty = true;
bool checkDataStorage();
std::unique_ptr<ghoul::opengl::ProgramObject> _shaderProgram = nullptr;
UniformCache(color, opacity, size, modelMatrix, cameraUp,
cameraViewProjectionMatrix, eyePosition, sizeOption) _uniformCache;
UniformCache(
color, opacity, size, modelMatrix, cameraUp,
cameraViewProjectionMatrix, eyePosition, sizeOption,
colormapTexture, colormapMin, colormapMax, colormapEnabled
) _uniformCache;
properties::BoolProperty _isVisible;
properties::FloatProperty _size;
properties::Vec3Property _color;
properties::Vec4Property _color;
properties::OptionProperty _sizeOption;
std::vector<float> _fullData;
std::vector<float> _slicedData;
std::unique_ptr<ghoul::opengl::Texture> _colorMapTexture;
properties::FloatProperty _colormapMin;
properties::FloatProperty _colormapMax;
std::unique_ptr<ghoul::opengl::Texture> _colormapTexture;
properties::BoolProperty _colormapEnabled;
std::optional<std::string> _dataStorageKey = std::nullopt;
std::optional<std::string> _identifier = std::nullopt;
properties::BoolProperty _colorMapEnabled;
properties::BoolProperty _loadNewColorMap;
int _nValuesPerPoint = 0;
bool _hasLoadedColormapAttributeData = false;
bool _hasLoadedColormap = false;
GLuint _vertexArrayObjectID = 0;
GLuint _vertexBufferObjectID = 0;
enum class DataSliceKey : size_t {
Points = 0,
ColormapAttributes
};
using DataSlice = std::vector<float>;
std::unordered_map<DataSliceKey, std::shared_ptr<DataSlice>> _dataSlices;
std::shared_ptr<DataSlice> getDataSlice(DataSliceKey key);
enum SizeOption {
Uniform = 0,
NonUniform = 1
};
struct VBOLayout {
glm::vec3 position;
float colormapAttributeData;
};
GLuint _vbo = 0;
GLuint _vao = 0;
};
}// namespace openspace

View File

@@ -25,25 +25,43 @@
#include "fragment.glsl"
#include "PowerScaling/powerScaling_fs.hglsl"
flat in float ge_colormapAttributeScalar;
in vec2 coords;
flat in float ge_screenSpaceDepth;
in vec4 ge_positionViewSpace;
uniform vec3 color;
uniform vec4 color;
uniform float opacity;
Fragment getFragment() {
if (opacity < 0.01) discard;
uniform float colormapMin;
uniform float colormapMax;
uniform bool colormapEnabled;
uniform sampler1D colormapTexture;
vec4 attributeScalarToRgb(float attributeData) {
// Linear interpolation
float t = (attributeData - colormapMin) / (colormapMax - colormapMin);
// Sample texture with interpolated value
return texture(colormapTexture, t);
}
Fragment getFragment() {
const float radius = 0.5;
float d = length(coords - radius);
if (d > 0.5) discard;
float distance = length(coords - radius);
if (distance > 0.6) discard;
// calculate distance from the origin point
float circle = smoothstep(radius, radius - (radius * 0.3), d);
float circle = smoothstep(radius, radius - (radius * 0.2), distance);
vec4 outputColor = vec4(color.rgb, color.a * opacity);
if (colormapEnabled) {
vec4 colorFromColormap = attributeScalarToRgb(ge_colormapAttributeScalar);
outputColor = vec4(colorFromColormap.rgb, colorFromColormap.a * opacity);
}
Fragment frag;
frag.color = vec4(color, opacity) * vec4(circle);
frag.color = outputColor * vec4(circle);
frag.depth = ge_screenSpaceDepth;
frag.gPosition = ge_positionViewSpace;
frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0);

View File

@@ -29,10 +29,13 @@
layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
in float vs_colormapAttributeScalar[];
flat out float ge_screenSpaceDepth;
out vec4 ge_positionViewSpace;
out vec2 coords;
out float ge_colormapAttributeScalar;
uniform dvec3 eyePosition;
uniform dvec3 cameraUp;
@@ -59,6 +62,8 @@ double scaleForUniform(vec3 pos) {
}
void main() {
ge_colormapAttributeScalar = vs_colormapAttributeScalar[0];
vec3 pos = gl_in[0].gl_Position.xyz;
double scaleMultiply = 1.0;

View File

@@ -27,7 +27,12 @@
#include "PowerScaling/powerScaling_vs.hglsl"
layout(location = 0) in vec3 in_position;
in float in_colormapAttributeScalar;
out float vs_colormapAttributeScalar;
void main() {
vs_colormapAttributeScalar = in_colormapAttributeScalar;
gl_Position = vec4(in_position, 1.0);
}

View File

@@ -37,21 +37,21 @@ namespace openspace {
namespace simp {
SimpError::SimpError(const ErrorCode _errorCode, const std::string& msg)
SimpError::SimpError(const utils::ErrorCode _errorCode, const std::string& msg)
: errorCode{errorCode}, ghoul::RuntimeError(fmt::format("{}: Error Code: {} - {}", "SIMP error", static_cast<uint32_t>(_errorCode), msg), "Software Integration Messaging Protocol error")
{}
bool isEndOfCurrentValue(const std::vector<char>& message, size_t offset) {
bool utils::isEndOfCurrentValue(const std::vector<char>& message, size_t offset) {
if (offset >= message.size()) {
throw SimpError(
ErrorCode::OffsetLargerThanMessageSize,
utils::ErrorCode::OffsetLargerThanMessageSize,
"Unexpectedly reached the end of the message..."
);
}
if (message.size() > 0 && offset == message.size() - 1 && message[offset] != SEP) {
throw SimpError(
ErrorCode::ReachedEndBeforeSeparator,
utils::ErrorCode::ReachedEndBeforeSeparator,
"Reached end of message before reading separator character..."
);
}
@@ -60,15 +60,20 @@ bool isEndOfCurrentValue(const std::vector<char>& message, size_t offset) {
}
MessageType getMessageType(const std::string& type) {
if (_messageTypeFromSIMPType.count(type) == 0) return MessageType::Unknown;
return _messageTypeFromSIMPType.at(type);
if (utils::_messageTypeFromSIMPType.count(type) == 0) return MessageType::Unknown;
return utils::_messageTypeFromSIMPType.at(type);
}
std::string getSIMPType(const MessageType& type){
for (auto [key, messageType] : _messageTypeFromSIMPType) {
if (messageType == type) return key;
}
return "";
std::string getSIMPType(const MessageType& type) {
auto it = std::find_if(
utils::_messageTypeFromSIMPType.begin(),
utils::_messageTypeFromSIMPType.end(),
[type](const std::pair<const std::string, MessageType>& p) {
return type == p.second;
}
);
if (it == utils::_messageTypeFromSIMPType.end()) return "UNKN";
return it->first;
}
std::string formatLengthOfSubject(size_t lengthOfSubject) {
@@ -82,10 +87,8 @@ std::string formatUpdateMessage(MessageType messageType,
std::string_view identifier,
std::string_view value)
{
const int lengthOfIdentifier = static_cast<int>(identifier.length());
const int lengthOfValue = static_cast<int>(value.length());
std::string subject = fmt::format(
"{}{}{}{}", lengthOfIdentifier, identifier, lengthOfValue, value
"{}{}{}{}", identifier, SEP, value, SEP
);
const std::string lengthOfSubject = formatLengthOfSubject(subject.length());
@@ -95,19 +98,191 @@ std::string formatUpdateMessage(MessageType messageType,
std::string formatConnectionMessage(std::string_view software) {
std::string subject = fmt::format(
"{}", software
"{}{}", software, SEP
);
const std::string lengthOfSubject = formatLengthOfSubject(subject.length());
return fmt::format("{}{}{}{}", ProtocolVersion, "CONN", lengthOfSubject, subject);
return fmt::format("{}{}{}{}", ProtocolVersion, getSIMPType(MessageType::Connection), lengthOfSubject, subject);
}
std::string formatDisconnectionMessage() {
const std::string lengthOfSubject = formatLengthOfSubject(0);
return fmt::format("{}{}{}", ProtocolVersion, "DISC", lengthOfSubject);
std::string formatColorMessage(std::string_view identifier, glm::vec4 color) {
std::ostringstream value_stream;
value_stream << "[" << floatToHex(color.r) << SEP << floatToHex(color.g) << SEP
<< floatToHex(color.b) << SEP << floatToHex(color.a) << SEP << "]";
return formatUpdateMessage(MessageType::Color, identifier, value_stream.str());
}
std::string formatPointDataCallbackMessage(std::string_view identifier) {
std::string subject = fmt::format(
"{}{}", identifier, SEP
);
const std::string lengthOfSubject = formatLengthOfSubject(subject.length());
return fmt::format("{}{}{}{}", ProtocolVersion, getSIMPType(MessageType::PointData), lengthOfSubject, subject);
}
std::string floatToHex(const float f) {
const uint32_t *int_representation = reinterpret_cast<const uint32_t *>(&f);
std::ostringstream stream;
stream << "0x" << std::setfill ('0') << std::setw(sizeof(float)*2) << std::hex << *int_representation;
return stream.str();
}
float hexToFloat(const std::string& f) {
uint32_t int_value = static_cast<uint32_t>(std::stoul(f, nullptr, 16));
std::ostringstream stream;
stream << std::dec << int_value;
return *reinterpret_cast<float*>(&int_value);
}
int readIntValue(const std::vector<char>& message, size_t& offset) {
std::string string_value;
int value;
bool isHex = false;
while (!utils::isEndOfCurrentValue(message, offset)) {
char c = message[offset];
if (c == 'x' || c == 'X') isHex = true;
string_value.push_back(c);
offset++;
}
try {
value = std::stoi(string_value, nullptr, isHex ? 16 : 10);
}
catch(std::exception &err) {
throw SimpError(
utils::ErrorCode::Generic,
fmt::format("Error when trying to parse the integer {}: {}", string_value, err.what())
);
}
++offset;
return value;
}
float readFloatValue(const std::vector<char>& message, size_t& offset) {
std::string string_value;
float value;
while (!utils::isEndOfCurrentValue(message, offset)) {
string_value.push_back(message[offset]);
offset++;
}
try {
value = hexToFloat(string_value);
}
catch(std::exception &err) {
throw SimpError(
utils::ErrorCode::Generic,
fmt::format("Error when trying to parse the float {}: {}", string_value, err.what())
);
}
++offset;
return value;
}
void readColormap(
const std::vector<char>& message, size_t& offset, size_t nColors, std::vector<float>& colorMap
) {
colorMap.reserve(nColors * 4);
while (message[offset] != SEP) {
glm::vec4 color = utils::readSingleColor(message, offset);
// Colormap should be stored in a sequential vector
// of floats for syncing between nodes and when
// loaded to as a texture in the shader.
colorMap.push_back(color[0]);
colorMap.push_back(color[1]);
colorMap.push_back(color[2]);
colorMap.push_back(color[3]);
}
offset++;
}
glm::vec4 utils::readSingleColor(const std::vector<char>& message, size_t& offset) {
if (message[offset] != '[') {
throw SimpError(
utils::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(
utils::ErrorCode::Generic,
fmt::format("Expected to read ']', got {} in 'readColor'", message[offset])
);
}
++offset;
return { r, g, b, a };
}
glm::vec4 readColor(const std::vector<char>& message, size_t& offset) {
glm::vec4 color = utils::readSingleColor(message, offset);
++offset;
return color;
}
std::string readString(const std::vector<char>& message, size_t& offset) {
std::string value;
while (!utils::isEndOfCurrentValue(message, offset)) {
value.push_back(message[offset]);
++offset;
}
++offset;
return value;
}
void readPointData(
const std::vector<char>& message,
size_t& offset,
size_t nPoints,
size_t dimensionality,
std::vector<float>& pointData
) {
pointData.reserve(nPoints * dimensionality);
while (!utils::isEndOfCurrentValue(message, offset)) {
if (message[offset] != '[') {
throw SimpError(
utils::ErrorCode::Generic,
fmt::format("Expected to read '[', got {} in 'readPointData'", message[offset])
);
}
++offset;
for (int i = 0; i < dimensionality; ++i) {
pointData.push_back(readFloatValue(message, offset));
}
if (message[offset] != ']') {
throw SimpError(
utils::ErrorCode::Generic,
fmt::format("Expected to read ']', got {} in 'readPointData'", message[offset])
);
}
++offset;
}
++offset;
}
} // namespace simp
} // namespace openspace
} // namespace openspace

View File

@@ -25,14 +25,32 @@
#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SIMP___H__
#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SIMP___H__
#include <unordered_map>
namespace openspace {
namespace simp {
const float ProtocolVersion = 1.6;
const std::string ProtocolVersion = "1.8";
const char SEP = ';';
enum class MessageType : uint32_t {
Connection = 0,
PointData,
RemoveSceneGraphNode,
Color,
Colormap,
AttributeData,
Opacity,
Size,
Visibility,
Disconnection,
Unknown
};
namespace utils {
enum class ErrorCode : uint32_t {
ReachedEndBeforeSeparator = 0,
OffsetLargerThanMessageSize,
@@ -40,39 +58,32 @@ enum class ErrorCode : uint32_t {
Generic,
};
enum class MessageType : uint32_t {
Connection = 0,
ReadPointData,
RemoveSceneGraphNode,
Color,
ColorMap,
Opacity,
Size,
Visibility,
Disconnection,
Unknown
};
const std::map<std::string, MessageType> _messageTypeFromSIMPType {
const std::unordered_map<std::string, MessageType> _messageTypeFromSIMPType {
{"CONN", MessageType::Connection},
{"PDAT", MessageType::ReadPointData},
{"PDAT", MessageType::PointData},
{"RSGN", MessageType::RemoveSceneGraphNode},
{"UPCO", MessageType::Color},
{"LCOL", MessageType::ColorMap},
{"UPOP", MessageType::Opacity},
{"UPSI", MessageType::Size},
{"FCOL", MessageType::Color},
{"LCOL", MessageType::Colormap},
{"ATDA", MessageType::AttributeData},
{"FOPA", MessageType::Opacity},
{"FPSI", MessageType::Size},
{"TOVI", MessageType::Visibility},
{"DISC", MessageType::Disconnection},
};
class SimpError : public ghoul::RuntimeError {
public:
ErrorCode errorCode;
explicit SimpError(const ErrorCode _errorCode, const std::string& msg);
};
glm::vec4 readSingleColor(const std::vector<char>& message, size_t& offset);
bool isEndOfCurrentValue(const std::vector<char>& message, size_t offset);
} // namespace utils
class SimpError : public ghoul::RuntimeError {
public:
utils::ErrorCode errorCode;
explicit SimpError(const utils::ErrorCode _errorCode, const std::string& msg);
};
MessageType getMessageType(const std::string& type);
std::string getSIMPType(const MessageType& type);
@@ -85,8 +96,32 @@ std::string formatUpdateMessage(MessageType messageType,
std::string_view value);
std::string formatConnectionMessage(std::string_view value);
std::string formatColorMessage(std::string_view identifier, glm::vec4 color);
std::string formatDisconnectionMessage();
std::string formatPointDataCallbackMessage(std::string_view identifier);
std::string floatToHex(const float f);
float hexToFloat(const std::string& f);
float readFloatValue(const std::vector<char>& message, size_t& offset);
int readIntValue(const std::vector<char>& message, size_t& offset);
std::string readString(const std::vector<char>& message, size_t& offset);
glm::vec4 readColor(const std::vector<char>& message, size_t& offset);
void readPointData(
const std::vector<char>& message, size_t& offset, size_t nPoints,
size_t dimensionality, std::vector<float>& pointData
);
void readColormap(
const std::vector<char>& message, size_t& offset, size_t nColors,
std::vector<float>& colorMap
);
} // namespace simp

View File

@@ -45,34 +45,24 @@ SoftwareIntegrationModule::SoftwareIntegrationModule() : OpenSpaceModule(Name) {
// 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
_server = new NetworkEngine();
_networkEngine = std::make_unique<NetworkEngine>();
}
}
SoftwareIntegrationModule::~SoftwareIntegrationModule() {
if (global::windowDelegate->isMaster()) {
delete _server;
}
internalDeinitialize();
}
void SoftwareIntegrationModule::storeData(const std::string& key,
const std::vector<float> data) {
_syncableFloatDataStorage.emplace(key, std::move(data));
void SoftwareIntegrationModule::storeData(const std::string& key, const std::vector<float>& data) {
_syncableFloatDataStorage.store(key, data);
}
std::vector<float> SoftwareIntegrationModule::fetchData(const std::string& key) {
auto it = _syncableFloatDataStorage.find(key);
if (it == _syncableFloatDataStorage.end()) {
LERROR(fmt::format(
"Could not find data with key '{}' in the temporary data storage", key
));
return std::vector<float>();
}
const std::vector<float>& SoftwareIntegrationModule::fetchData(const std::string& key) {
return _syncableFloatDataStorage.fetch(key);
}
std::vector<float> data = it->second;
_syncableFloatDataStorage.erase(it);
return std::move(data);
bool SoftwareIntegrationModule::isDataDirty(const std::string& key) {
return _syncableFloatDataStorage.isDirty(key);
}
void SoftwareIntegrationModule::internalInitialize(const ghoul::Dictionary&) {
@@ -83,17 +73,17 @@ void SoftwareIntegrationModule::internalInitialize(const ghoul::Dictionary&) {
fRenderable->registerClass<RenderablePointsCloud>("RenderablePointsCloud");
if (global::windowDelegate->isMaster()) {
_server->start();
_networkEngine->start();
global::callback::preSync->emplace_back([this]() { _server->update(); });
global::callback::postSyncPreDraw->emplace_back([this]() {
if (!_networkEngine) return;
_networkEngine->postSync();
});
}
}
void SoftwareIntegrationModule::internalDeinitialize() {
global::syncEngine->removeSyncables(getSyncables());
if (global::windowDelegate->isMaster()) {
_server->stop();
}
}
std::vector<documentation::Documentation>

View File

@@ -40,8 +40,9 @@ public:
SoftwareIntegrationModule();
~SoftwareIntegrationModule();
void storeData(const std::string& key, const std::vector<float> data);
std::vector<float> fetchData(const std::string& key);
void storeData(const std::string& key, const std::vector<float>& data);
const std::vector<float>& fetchData(const std::string& key);
bool isDataDirty(const std::string& key);
std::vector<documentation::Documentation> documentations() const override;
@@ -51,10 +52,12 @@ private:
std::vector<Syncable*> getSyncables();
NetworkEngine* _server;
// Centralized storage for datasets
SyncableFloatDataStorage _syncableFloatDataStorage;
// Network engine
std::unique_ptr<NetworkEngine> _networkEngine;
};
} // namespace openspace

View File

@@ -41,15 +41,20 @@ void SyncableFloatDataStorage::encode(SyncBuffer* syncBuffer) {
size_t nDataEntries = _storage.size();
syncBuffer->encode(nDataEntries);
for (const auto& [key, dataEntry] : _storage) {
for (auto& [key, value] : _storage) {
syncBuffer->encode(key);
// Go trough all data in data entry.
// Sequentially structured (ex: x1, y1, z1, x2, y2, z2...)
size_t nItemsInDataEntry = dataEntry.size();
syncBuffer->encode(nItemsInDataEntry);
for (auto value : dataEntry) {
syncBuffer->encode(value);
syncBuffer->encode(value.dirty);
// Only encode data if it is dirty, to save bandwidth
if (value.dirty) {
// Go trough all data in data entry.
// Sequentially structured as: x1, y1, z1, x2, y2, z2...
size_t nItemsInDataEntry = value.data.size();
syncBuffer->encode(nItemsInDataEntry);
for (auto val : value.data) {
syncBuffer->encode(val);
}
}
}
}
@@ -64,60 +69,112 @@ void SyncableFloatDataStorage::decode(SyncBuffer* syncBuffer) {
std::string key;
syncBuffer->decode(key);
size_t nItemsInDataEntry;
syncBuffer->decode(nItemsInDataEntry);
// TODO: Change to a glm::fvec3 so we can use an overload
// of decode(glm::fvec3) instead of using a for-loop over floats?
std::vector<float> dataEntry;
dataEntry.reserve(nItemsInDataEntry);
for (size_t j = 0; j < nItemsInDataEntry; ++j) {
float value;
syncBuffer->decode(value);
dataEntry.push_back(value);
}
bool dirty;
syncBuffer->decode(dirty);
_storage[key] = dataEntry;
if (dirty) {
size_t nItemsInDataEntry;
syncBuffer->decode(nItemsInDataEntry);
std::vector<float> dataEntry;
dataEntry.reserve(nItemsInDataEntry);
for (size_t j = 0; j < nItemsInDataEntry; ++j) {
float value;
syncBuffer->decode(value);
dataEntry.push_back(value);
}
insertAssign(key, Value{ dataEntry, dirty });
}
}
}
void SyncableFloatDataStorage::postSync(bool isMaster) {
if (isMaster) {
if (_storage.size() > 0) {
// LWARNING(fmt::format("SyncableFloatDataStorage.size() (MASTER): {}", _storage.size()));
}
}
else {
if (_storage.size() > 0) {
// LWARNING(fmt::format("SyncableFloatDataStorage.size() (CLIENT): {}", _storage.size()));
for (auto& [key, value] : _storage) {
if (value.dirty) {
value.dirty = false;
}
}
}
}
/* ================================================== */
const SyncableFloatDataStorage::ValueData& SyncableFloatDataStorage::fetch(const Key& key) {
LDEBUG(fmt::format("Loading data from float data storage: {}", key));
auto it = find(key);
if (it == end()) {
LERROR(fmt::format(
"Could not find data with key '{}' in the centralized data storage", key
));
return std::move(ValueData{});
}
it->second.localDirty = false;
return it->second.data;
}
bool SyncableFloatDataStorage::isDirty(const Key& key) {
auto it = find(key);
if (it == end()) {
return false;
}
return it->second.localDirty;
}
void SyncableFloatDataStorage::store(const Key& key, const ValueData& data) {
LDEBUG(fmt::format("Storing data in float data storage: {}", key));
Value value{ data, true, true };
auto old = find(key);
if (old != end()) {
glm::vec3 firstValueOld{};
for (glm::vec3::length_type i = 0; i < glm::vec3::length(); ++i) {
firstValueOld[i] = old->second.data[i];
}
LDEBUG(fmt::format(
"First data point: old: {}", firstValueOld
));
}
insertAssign(key, std::move(value));
auto newVal = find(key);
if (newVal != end()) {
glm::vec3 firstValueNew{};
for (glm::vec3::length_type i = 0; i < glm::vec3::length(); ++i) {
firstValueNew[i] = newVal->second.data[i];
}
LDEBUG(fmt::format(
"First data point: new {}", firstValueNew
));
}
}
/* =============== Utility functions ================ */
SyncableFloatDataStorage::Iterator SyncableFloatDataStorage::erase(SyncableFloatDataStorage::Iterator pos) {
return _storage.erase(pos);
}
SyncableFloatDataStorage::Iterator SyncableFloatDataStorage::erase(const SyncableFloatDataStorage::Iterator first, const SyncableFloatDataStorage::Iterator last) {
return _storage.erase(first, last);
}
size_t SyncableFloatDataStorage::erase(const SyncableFloatDataStorage::Key& key) {
return _storage.erase(key);
}
std::pair<SyncableFloatDataStorage::Iterator, bool> SyncableFloatDataStorage::emplace(SyncableFloatDataStorage::Key key, SyncableFloatDataStorage::Value value) {
return _storage.emplace(key, value);
void SyncableFloatDataStorage::insertAssign(Key key, const Value& value) {
auto it = find(key);
if (it == end()) {
_storage.emplace(key, value);
}
else {
it->second = value;
}
}
SyncableFloatDataStorage::Value& SyncableFloatDataStorage::at(const SyncableFloatDataStorage::Key& key) {
return _storage.at(key);
}
const SyncableFloatDataStorage::Value& SyncableFloatDataStorage::at(const SyncableFloatDataStorage::Key& key) const {
SyncableFloatDataStorage::Value& SyncableFloatDataStorage::at(const Key& key) {
return _storage.at(key);
}
SyncableFloatDataStorage::Iterator SyncableFloatDataStorage::find(const SyncableFloatDataStorage::Key& key) {
SyncableFloatDataStorage::Iterator SyncableFloatDataStorage::find(const Key& key) {
return _storage.find(key);
}
/* ================================================== */
@@ -132,10 +189,4 @@ SyncableFloatDataStorage::Iterator SyncableFloatDataStorage::begin() noexcept {
}
/* ================================================== */
/* =============== Operator overloads =============== */
SyncableFloatDataStorage::Value& SyncableFloatDataStorage::operator[](SyncableFloatDataStorage::Key&& key) {
return _storage[key];
}
/* ================================================== */
} // namespace openspace

View File

@@ -25,48 +25,45 @@
#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SYNCABLEFLOATDATASTORAGE___H__
#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SYNCABLEFLOATDATASTORAGE___H__
#include <openspace/util/syncable.h>
#include <mutex>
#include <unordered_map>
#include <openspace/util/syncable.h>
namespace openspace {
/**
* A double buffered implementation of the Syncable interface.
* Users are encouraged to used this class as a default way to synchronize different
* C++ data types using the SyncEngine.
*
* This class aims to handle the synchronization parts and yet act like a regular
* instance of T. Implicit casts are supported, however, when accessing member functions
* or variables, user may have to do explicit casts.
*
* ((T&) t).method();
*
*/
class SyncableFloatDataStorage : public Syncable {
public:
/* ====================== Types ===================== */
typedef std::string Key;
typedef std::vector<float> Value; // a dataset stored like x1, y1, z1, x2, y2 ....
typedef std::map<Key, Value> Storage;
typedef Storage::iterator Iterator;
struct Value {
// a dataset stored like x1, y1, z1, x2, y2 ....
std::vector<float> data;
bool dirty;
bool localDirty;
};
using ValueData = decltype(Value::data);
using Key = std::string;
using Storage = std::unordered_map<Key, Value>;
using Iterator = Storage::iterator;
/* ================================================== */
/* ============== SyncEngine functions ============== */
// virtual void preSync(bool isMaster) override;
virtual void encode(SyncBuffer* syncBuffer) override;
virtual void decode(SyncBuffer* syncBuffer) override;
virtual void postSync(bool isMaster) override;
/* ================================================== */
const ValueData& fetch(const Key& key);
bool isDirty(const Key& key);
void store(const Key& key, const ValueData& data);
private:
/* =============== Utility functions ================ */
Iterator erase(Iterator pos);
Iterator erase(const Iterator first, const Iterator last);
size_t erase(const Key& key);
std::pair<Iterator, bool> emplace(Key key, Value value);
void insertAssign(Key key, const Value& value);
Value& at(const Key& key);
const Value& at(const Key& key) const;
Iterator find(const Key& key);
/* ================================================== */
@@ -77,16 +74,8 @@ public:
Iterator begin() noexcept;
/* ================================================== */
/* =============== Operator overloads =============== */
Value& operator[](Key&& key);
/* ================================================== */
private:
std::mutex _mutex;
Storage _storage;
bool showMessageEncode = true;
bool showMessageDecode = true;
};
} // namespace openspace