mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-02-21 20:39:08 -06:00
Add event topic (#3010)
* Add event topic Add ability to subscribe to events via js-api * Apply suggestions from code review Co-authored-by: Alexander Bock <alexander.bock@liu.se> * Output an error if no status or event object is passed to payload * fixed typo * Report a warning if user tries to unsubscribe to a non registered event/type combination --------- Co-authored-by: Alexander Bock <alexander.bock@liu.se>
This commit is contained in:
@@ -80,7 +80,8 @@ struct Event {
|
||||
CameraPathStarted,
|
||||
CameraPathFinished,
|
||||
CameraMovedPosition,
|
||||
Custom
|
||||
Custom,
|
||||
Last // sentinel value
|
||||
};
|
||||
constexpr explicit Event(Type type_) : type(type_) {}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace events { struct Event; }
|
||||
|
||||
class EventEngine {
|
||||
public:
|
||||
using ScriptCallback = std::function<void(ghoul::Dictionary)>;
|
||||
|
||||
struct ActionInfo {
|
||||
events::Event::Type type;
|
||||
uint32_t id = std::numeric_limits<uint32_t>::max();
|
||||
@@ -44,6 +46,11 @@ public:
|
||||
std::optional<ghoul::Dictionary> filter;
|
||||
};
|
||||
|
||||
struct TopicInfo {
|
||||
uint32_t id = std::numeric_limits<uint32_t>::max();
|
||||
ScriptCallback callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function returns the first event stored in the EventEngine, or `nullptr` if
|
||||
* no event exists. To navigate the full list of events, you can access the returned
|
||||
@@ -87,6 +94,16 @@ public:
|
||||
void registerEventAction(events::Event::Type type, std::string identifier,
|
||||
std::optional<ghoul::Dictionary> filter = std::nullopt);
|
||||
|
||||
/**
|
||||
* Registers a new topic for a specific event type.
|
||||
*
|
||||
* \param topicId The id of the topic that will be triggered
|
||||
* \param type The type for which a new topic is registered
|
||||
* \param callback The callback function that will be called on triggered event
|
||||
*/
|
||||
void registerEventTopic(size_t topicId, events::Event::Type type,
|
||||
ScriptCallback callback);
|
||||
|
||||
/**
|
||||
* Removing registration for a type/action combination.
|
||||
*
|
||||
@@ -105,6 +122,15 @@ public:
|
||||
*/
|
||||
void unregisterEventAction(uint32_t identifier);
|
||||
|
||||
/**
|
||||
* Removing registration for a topic/type combination, does nothing if topicId or type
|
||||
* combination does not exist
|
||||
*
|
||||
* \param topicId The id of the topic that should be unregistered
|
||||
* \param type The type of the topic that should be unregistered
|
||||
*/
|
||||
void unregisterEventTopic(size_t topicId, events::Event::Type type);
|
||||
|
||||
/**
|
||||
* Returns the list of all registered actions, sorted by their identifiers.
|
||||
*
|
||||
@@ -134,6 +160,12 @@ public:
|
||||
*/
|
||||
void triggerActions() const;
|
||||
|
||||
/**
|
||||
* Triggers all topics that are registered for events that are in the current event
|
||||
* queue.
|
||||
*/
|
||||
void triggerTopics() const;
|
||||
|
||||
static scripting::LuaLibrary luaLibrary();
|
||||
|
||||
private:
|
||||
@@ -149,6 +181,8 @@ private:
|
||||
/// the lookup really fast. So having this extra wasted memory is probably worth it
|
||||
std::unordered_map<events::Event::Type, std::vector<ActionInfo>> _eventActions;
|
||||
|
||||
std::unordered_map<events::Event::Type, std::vector<TopicInfo>> _eventTopics;
|
||||
|
||||
static uint32_t nextRegisteredEventId;
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
@@ -37,6 +37,7 @@ set(HEADER_FILES
|
||||
include/topics/cameratopic.h
|
||||
include/topics/documentationtopic.h
|
||||
include/topics/enginemodetopic.h
|
||||
include/topics/eventtopic.h
|
||||
include/topics/flightcontrollertopic.h
|
||||
include/topics/getpropertytopic.h
|
||||
include/topics/luascripttopic.h
|
||||
@@ -65,6 +66,7 @@ set(SOURCE_FILES
|
||||
src/topics/cameratopic.cpp
|
||||
src/topics/documentationtopic.cpp
|
||||
src/topics/enginemodetopic.cpp
|
||||
src/topics/eventtopic.cpp
|
||||
src/topics/flightcontrollertopic.cpp
|
||||
src/topics/getpropertytopic.cpp
|
||||
src/topics/luascripttopic.cpp
|
||||
|
||||
53
modules/server/include/topics/eventtopic.h
Normal file
53
modules/server/include/topics/eventtopic.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2023 *
|
||||
* *
|
||||
* 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_SERVER___EVENT_TOPIC___H__
|
||||
#define __OPENSPACE_MODULE_SERVER___EVENT_TOPIC___H__
|
||||
|
||||
#include <modules/server/include/topics/topic.h>
|
||||
|
||||
#include <openspace/events/event.h>
|
||||
|
||||
namespace openspace::properties { class Property; }
|
||||
|
||||
namespace openspace {
|
||||
|
||||
class EventTopic : public Topic {
|
||||
public:
|
||||
EventTopic() = default;
|
||||
~EventTopic() override = default;
|
||||
|
||||
void handleJson(const nlohmann::json& json) override;
|
||||
bool isDone() const override;
|
||||
|
||||
private:
|
||||
// Returns true if there is at least one subscription active, false otherwise
|
||||
bool isSubscribed() const;
|
||||
|
||||
std::unordered_map<events::Event::Type, bool> _subscribedEvents;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SERVER___EVENT_TOPIC___H__
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <modules/server/include/topics/cameratopic.h>
|
||||
#include <modules/server/include/topics/documentationtopic.h>
|
||||
#include <modules/server/include/topics/enginemodetopic.h>
|
||||
#include <modules/server/include/topics/eventtopic.h>
|
||||
#include <modules/server/include/topics/flightcontrollertopic.h>
|
||||
#include <modules/server/include/topics/getpropertytopic.h>
|
||||
#include <modules/server/include/topics/luascripttopic.h>
|
||||
@@ -101,6 +102,7 @@ Connection::Connection(std::unique_ptr<ghoul::io::Socket> s, std::string address
|
||||
_topicFactory.registerClass<SkyBrowserTopic>("skybrowser");
|
||||
_topicFactory.registerClass<CameraTopic>("camera");
|
||||
_topicFactory.registerClass<CameraPathTopic>("cameraPath");
|
||||
_topicFactory.registerClass<EventTopic>("event");
|
||||
}
|
||||
|
||||
void Connection::handleMessage(const std::string& message) {
|
||||
|
||||
124
modules/server/src/topics/eventtopic.cpp
Normal file
124
modules/server/src/topics/eventtopic.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2023 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include <modules/server/include/topics/eventtopic.h>
|
||||
|
||||
#include <modules/server/include/connection.h>
|
||||
#include <modules/server/include/jsonconverters.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/events/event.h>
|
||||
#include <openspace/events/eventengine.h>
|
||||
#include <ghoul/fmt.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "EventTopic";
|
||||
|
||||
constexpr std::string_view StartSubscription = "start_subscription";
|
||||
constexpr std::string_view StopSubscription = "stop_subscription";
|
||||
} // namespace
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace openspace {
|
||||
|
||||
bool EventTopic::isDone() const {
|
||||
return !isSubscribed();
|
||||
}
|
||||
|
||||
void EventTopic::handleJson(const nlohmann::json& json) {
|
||||
std::vector<std::string> events;
|
||||
|
||||
auto eventJson = json.find("event");
|
||||
auto statusJson = json.find("status");
|
||||
|
||||
if (eventJson == json.end()) {
|
||||
LERROR("Payload does not contain 'event' key");
|
||||
return;
|
||||
}
|
||||
|
||||
if (statusJson == json.end() || !statusJson->is_string()) {
|
||||
LERROR("Status must be a string");
|
||||
return;
|
||||
}
|
||||
|
||||
if (json.at("event").is_array()) {
|
||||
events = json.at("event").get<std::vector<std::string>>();
|
||||
}
|
||||
else {
|
||||
const std::string& event = json.at("event").get<std::string>();
|
||||
if (event == "*" || event == "all") {
|
||||
// Iterate over all event types and add them to list
|
||||
const uint8_t lastEvent = static_cast<uint8_t>(events::Event::Type::Last);
|
||||
|
||||
for (uint8_t i = 0; i < lastEvent; i++) {
|
||||
auto type = static_cast<events::Event::Type>(i);
|
||||
events.push_back(std::string(events::toString(type)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
events.push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& status = json.at("status").get<std::string>();
|
||||
|
||||
for (const std::string& event : events) {
|
||||
if (status == StartSubscription) {
|
||||
const events::Event::Type type = events::fromString(event);
|
||||
|
||||
_subscribedEvents[type] = true;
|
||||
|
||||
auto onCallback = [this, event](ghoul::Dictionary params) {
|
||||
// Include the fired event to the caller
|
||||
params.setValue("Event", event);
|
||||
_connection->sendJson(wrappedPayload(params));
|
||||
};
|
||||
|
||||
global::eventEngine->registerEventTopic(_topicId, type, onCallback);
|
||||
}
|
||||
else if (status == StopSubscription) {
|
||||
events::Event::Type type = events::fromString(event);
|
||||
_subscribedEvents.erase(type);
|
||||
global::eventEngine->unregisterEventTopic(_topicId, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EventTopic::isSubscribed() const {
|
||||
if (_subscribedEvents.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasActiveSubscription = std::any_of(
|
||||
_subscribedEvents.begin(),
|
||||
_subscribedEvents.end(),
|
||||
[](const std::pair<const events::Event::Type, bool>& subscription) {
|
||||
return subscription.second;
|
||||
});
|
||||
|
||||
return hasActiveSubscription;
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
@@ -1321,12 +1321,7 @@ void OpenSpaceEngine::postDraw() {
|
||||
events::logAllEvents(e);
|
||||
}
|
||||
global::eventEngine->triggerActions();
|
||||
while (e) {
|
||||
// @TODO (abock, 2021-08-25) Need to send all events to a topic to be sent out to
|
||||
// others
|
||||
|
||||
e = e->next;
|
||||
}
|
||||
global::eventEngine->triggerTopics();
|
||||
|
||||
|
||||
global::eventEngine->postFrameCleanup();
|
||||
|
||||
@@ -595,6 +595,8 @@ void logAllEvents(const Event* e) {
|
||||
case Event::Type::Custom:
|
||||
log(i, *static_cast<const CustomEvent*>(e));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
|
||||
#include "eventengine_lua.inl"
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "EventEngine";
|
||||
}
|
||||
|
||||
namespace openspace {
|
||||
|
||||
uint32_t EventEngine::nextRegisteredEventId = 0;
|
||||
@@ -71,6 +75,16 @@ void EventEngine::registerEventAction(events::Event::Type type,
|
||||
nextRegisteredEventId++;
|
||||
}
|
||||
|
||||
void EventEngine::registerEventTopic(size_t topicId, events::Event::Type type,
|
||||
ScriptCallback callback)
|
||||
{
|
||||
TopicInfo ti;
|
||||
ti.id = topicId;
|
||||
ti.callback = callback;
|
||||
|
||||
_eventTopics[type].push_back(ti);
|
||||
}
|
||||
|
||||
void EventEngine::unregisterEventAction(events::Event::Type type,
|
||||
const std::string& identifier,
|
||||
std::optional<ghoul::Dictionary> filter)
|
||||
@@ -115,6 +129,37 @@ void EventEngine::unregisterEventAction(uint32_t identifier) {
|
||||
));
|
||||
}
|
||||
|
||||
void EventEngine::unregisterEventTopic(size_t topicId, events::Event::Type type) {
|
||||
const auto it = _eventTopics.find(type);
|
||||
if (it != _eventTopics.end()) {
|
||||
const auto jt = std::find_if(
|
||||
it->second.begin(), it->second.end(),
|
||||
[topicId](const TopicInfo& ti) {
|
||||
return ti.id == topicId;
|
||||
}
|
||||
);
|
||||
if (jt != it->second.end()) {
|
||||
it->second.erase(jt);
|
||||
|
||||
// This might have been the last action so we might need to remove the
|
||||
// entry alltogether
|
||||
if (it->second.empty()) {
|
||||
_eventTopics.erase(it);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LWARNING(fmt::format("Could not find registered event '{}' with topicId: {}",
|
||||
events::toString(type), topicId)
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LWARNING(fmt::format("Could not find registered event '{}'",
|
||||
events::toString(type))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<EventEngine::ActionInfo> EventEngine::registeredActions() const {
|
||||
std::vector<EventEngine::ActionInfo> result;
|
||||
result.reserve(_eventActions.size());
|
||||
@@ -179,6 +224,27 @@ void EventEngine::triggerActions() const {
|
||||
}
|
||||
}
|
||||
|
||||
void EventEngine::triggerTopics() const {
|
||||
if (_eventTopics.empty()) {
|
||||
// Nothing to do here
|
||||
return;
|
||||
}
|
||||
|
||||
const events::Event* e = _firstEvent;
|
||||
while (e) {
|
||||
const auto it = _eventTopics.find(e->type);
|
||||
|
||||
if (it != _eventTopics.end()) {
|
||||
ghoul::Dictionary params = toParameter(*e);
|
||||
for (const TopicInfo& ti : it->second) {
|
||||
ti.callback(params);
|
||||
}
|
||||
}
|
||||
|
||||
e = e->next;
|
||||
}
|
||||
}
|
||||
|
||||
scripting::LuaLibrary EventEngine::luaLibrary() {
|
||||
return {
|
||||
"event",
|
||||
|
||||
Reference in New Issue
Block a user