From 76dd45e5ce32cfe51d67883650ea8cd27ace1746 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Mon, 11 Oct 2021 21:53:00 +0200 Subject: [PATCH] Event System (#1741) * Add implementation of the EventEngine to handle global event chains * Add properties to SceneGraphNodes to determine two distance radii for camera-based events --- data/assets/actions/toggle_trail.asset | 105 ++++ data/assets/events/toggle_trail.asset | 27 + data/assets/examples/approachevents.asset | 68 ++ include/openspace/engine/configuration.h | 1 + include/openspace/engine/globals.h | 2 + include/openspace/engine/openspaceengine.h | 3 + include/openspace/events/event.h | 393 ++++++++++++ include/openspace/events/eventengine.h | 125 ++++ include/openspace/events/eventengine.inl | 51 ++ .../openspace/navigation/navigationhandler.h | 5 + include/openspace/scene/scenegraphnode.h | 15 + include/openspace/util/memorymanager.h | 4 +- include/openspace/util/tstring.h | 64 ++ .../base/rendering/renderabletrailorbit.cpp | 7 + modules/globebrowsing/src/layergroup.cpp | 34 +- openspace.cfg | 1 + src/CMakeLists.txt | 8 + src/documentation/core_registration.cpp | 4 +- src/engine/configuration.cpp | 6 + src/engine/globals.cpp | 51 +- src/engine/openspaceengine.cpp | 50 +- src/events/event.cpp | 595 ++++++++++++++++++ src/events/eventengine.cpp | 133 ++++ src/events/eventengine_lua.inl | 49 ++ src/interaction/actionmanager.cpp | 11 +- src/interaction/sessionrecording.cpp | 18 +- src/navigation/navigationhandler.cpp | 117 ++++ src/navigation/orbitalnavigator.cpp | 18 +- src/network/parallelpeer.cpp | 69 +- src/properties/propertyowner.cpp | 3 + src/rendering/renderengine.cpp | 13 +- src/scene/profile.cpp | 8 +- src/scene/scene.cpp | 6 + src/scene/scene_lua.inl | 2 + src/scene/scenegraphnode.cpp | 118 +++- src/util/tstring.cpp | 51 ++ 36 files changed, 2176 insertions(+), 59 deletions(-) create mode 100644 data/assets/actions/toggle_trail.asset create mode 100644 data/assets/events/toggle_trail.asset create mode 100644 data/assets/examples/approachevents.asset create mode 100644 include/openspace/events/event.h create mode 100644 include/openspace/events/eventengine.h create mode 100644 include/openspace/events/eventengine.inl create mode 100644 include/openspace/util/tstring.h create mode 100644 src/events/event.cpp create mode 100644 src/events/eventengine.cpp create mode 100644 src/events/eventengine_lua.inl create mode 100644 src/util/tstring.cpp diff --git a/data/assets/actions/toggle_trail.asset b/data/assets/actions/toggle_trail.asset new file mode 100644 index 0000000000..d3c5848d6c --- /dev/null +++ b/data/assets/actions/toggle_trail.asset @@ -0,0 +1,105 @@ +local toggle_trail = { + Identifier = "os.toggle_trail", + Name = "Toggle Trail", + Command = [[ + local node + if is_declared("args") then + node = args.Node + else + node = openspace.navigation.getNavigationState().Anchor + end + + local trail + if openspace.hasSceneGraphNode(node .. "Trail") then + trail = node .. "Trail" + elseif openspace.hasSceneGraphNode(node .. "_trail") then + trail = node .. "_trail" + else + -- No trail found, so nothing more to do here + return + end + + local visibility + if is_declared("args") then + if args.Transition == "Approaching" then + visibility = false + elseif args.Transition == "Exiting" then + visibility = true + else + return + end + else + visibility = not openspace.getPropertyValue("Scene." .. trail .. ".Renderable.Enabled") + end + + openspace.setPropertyValueSingle("Scene." .. trail .. ".Renderable.Enabled", visibility) + ]], + Documentation = [[Toggles the visibility of the associated trail of a scene graph node. + This action takes optional arguments to 1) determine which trail to hide (as the + 'Node') and 2) the transition direction (as 'After' and 'Before').]], + GuiPath = "/Trails", + IsLocal = true +} +asset.export("toggle_trail", toggle_trail.Identifier) + +local hide_trail = { + Identifier = "os.hide_trail", + Name = "Hide Trail", + Command = [[ + local node + if is_declared("args") then + node = args.Node + else + node = openspace.navigation.getNavigationState().Anchor + end + + if openspace.hasSceneGraphNode(node .. "Trail") then + openspace.setPropertyValue("Scene." .. node .. "Trail.Renderable.Enabled", false) + elseif openspace.hasSceneGraphNode(node .. "_trail") then + openspace.setPropertyValue("Scene." .. node .. "_trail.Renderable.Enabled", false) + end + ]], + Documentation = [[Hides the associated trail of a scene graph node. This action takes an + optional argument to determine whose trail to hide. If no argument is provided, the + current focus node is used instead]], + GuiPath = "/Trails", + IsLocal = true +} +asset.export("hide_trail", hide_trail.Identifier) + +local show_trail = { + Identifier = "os.show_trail", + Name = "Show Trail", + Command = [[ + local node + if is_declared("args") then + node = args.Node + else + node = openspace.navigation.getNavigationState().Anchor + end + + if openspace.hasSceneGraphNode(node .. "Trail") then + openspace.setPropertyValue("Scene." .. node .. "Trail.Renderable.Enabled", true) + elseif openspace.hasSceneGraphNode(node .. "_trail") then + openspace.setPropertyValue("Scene." .. node .. "_trail.Renderable.Enabled", true) + end + ]], + Documentation = [[Shows the associated trail of a scene graph node. This action takes an + optional argument to determine whose trail to hide. If no argument is provided, the + current focus node is used instead]], + GuiPath = "/Trails", + IsLocal = true +} +asset.export("show_trail", show_trail.Identifier) + +asset.onInitialize(function() + openspace.action.registerAction(toggle_trail) + openspace.action.registerAction(show_trail) + openspace.action.registerAction(hide_trail) +end) + +asset.onDeinitialize(function() + openspace.action.removeAction(toggle_trail.Identifier) + openspace.action.removeAction(show_trail.Identifier) + openspace.action.removeAction(hide_trail.Identifier) +end) diff --git a/data/assets/events/toggle_trail.asset b/data/assets/events/toggle_trail.asset new file mode 100644 index 0000000000..2e397ed7d8 --- /dev/null +++ b/data/assets/events/toggle_trail.asset @@ -0,0 +1,27 @@ +local action = asset.require('actions/toggle_trail') + +asset.onInitialize(function() + openspace.event.registerEventAction( + "CameraFocusTransition", + action.show_trail, + { Transition = "Exiting" } + ); + openspace.event.registerEventAction( + "CameraFocusTransition", + action.hide_trail, + { Transition = "Approaching" } + ); +end) + +asset.onDeinitialize(function() + openspace.event.unregisterEventAction( + "CameraFocusTransition", + action.show_trail, + { Transition = "Exiting" } + ); + openspace.event.unregisterEventAction( + "CameraFocusTransition", + action.hide_trail, + { Transition = "Approaching" } + ); +end) diff --git a/data/assets/examples/approachevents.asset b/data/assets/examples/approachevents.asset new file mode 100644 index 0000000000..47ddc3281c --- /dev/null +++ b/data/assets/examples/approachevents.asset @@ -0,0 +1,68 @@ +local assetHelper = asset.require('util/asset_helper') +local sunTransforms = asset.require('scene/solarsystem/sun/transforms') +local transforms = asset.require('scene/solarsystem/planets/earth/transforms') + +local generic_action = { + Identifier = "os.example.generic", + Name = "Generic Example", + Command = [[ + openspace.printInfo("Node: " .. args.Node) + openspace.printInfo("Transition: " .. args.Transition) + ]], + Documentation = "Prints the argument information for camera transitions to the log", + GuiPath = "/Examples/Events", + IsLocal = true +} + +local model = asset.syncedResource({ + Name = "Animated Box", + Type = "HttpSynchronization", + Identifier = "animated_box", + Version = 1 +}) + +local obj = { + Identifier = "ExampleEventModel", + Parent = transforms.EarthCenter.Identifier, + Transform = { + Translation = { + Type = "StaticTranslation", + Position = { 0.0, 11E7, 0.0 } + } + }, + Renderable = { + Type = "RenderableModel", + GeometryFile = model .. "/BoxAnimated.glb", + ModelScale = 1.0, + LightSources = { + { + Type = "SceneGraphLightSource", + Identifier = "Sun", + Node = sunTransforms.SolarSystemBarycenter.Identifier, + Intensity = 1.0 + } + }, + PerformShading = true, + DisableFaceCulling = true + }, + InteractionSphere = 1000.0, + OnApproach = { "os.example.generic" }, + OnReach = { "os.example.generic" }, + OnRecede = { "os.example.generic" }, + OnExit = { "os.example.generic" }, + GUI = { + Name = "Example Event Model", + Path = "/Example", + Description = "", + } +} + +asset.onInitialize(function() + openspace.action.registerAction(generic_action) + openspace.addSceneGraphNode(obj) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(obj.Identifier) + openspace.action.removeAction(generic_action.Identifier) +end) diff --git a/include/openspace/engine/configuration.h b/include/openspace/engine/configuration.h index 9748891b38..cb4303c5bb 100644 --- a/include/openspace/engine/configuration.h +++ b/include/openspace/engine/configuration.h @@ -89,6 +89,7 @@ struct Configuration { bool isCheckingOpenGLState = false; bool isLoggingOpenGLCalls = false; + bool isPrintingEvents = false; float shutdownCountdown = 0.f; diff --git a/include/openspace/engine/globals.h b/include/openspace/engine/globals.h index 0055117b3d..38b7d37915 100644 --- a/include/openspace/engine/globals.h +++ b/include/openspace/engine/globals.h @@ -36,6 +36,7 @@ namespace openspace { class Dashboard; class DeferredcasterManager; class DownloadManager; +class EventEngine; class LuaConsole; class MemoryManager; class MissionManager; @@ -74,6 +75,7 @@ inline ghoul::fontrendering::FontManager* fontManager; inline Dashboard* dashboard; inline DeferredcasterManager* deferredcasterManager; inline DownloadManager* downloadManager; +inline EventEngine* eventEngine; inline LuaConsole* luaConsole; inline MemoryManager* memoryManager; inline MissionManager* missionManager; diff --git a/include/openspace/engine/openspaceengine.h b/include/openspace/engine/openspaceengine.h index aaadc9cd18..5b48f40b2e 100644 --- a/include/openspace/engine/openspaceengine.h +++ b/include/openspace/engine/openspaceengine.h @@ -26,6 +26,7 @@ #define __OPENSPACE_CORE___OPENSPACEENGINE___H__ #include +#include #include #include #include @@ -117,6 +118,8 @@ private: std::string generateFilePath(std::string openspaceRelativePath); void resetPropertyChangeFlagsOfSubowners(openspace::properties::PropertyOwner* po); + properties::BoolProperty _printEvents; + std::unique_ptr _scene; std::unique_ptr _assetManager; bool _shouldAbortLoading = false; diff --git a/include/openspace/events/event.h b/include/openspace/events/event.h new file mode 100644 index 0000000000..9c903917be --- /dev/null +++ b/include/openspace/events/event.h @@ -0,0 +1,393 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___EVENT___H__ +#define __OPENSPACE_CORE___EVENT___H__ + +#include +#include +#include + +namespace openspace { + namespace properties { class Property; } + + class Camera; + class Layer; + class Profile; + class SceneGraphNode; + class ScreenSpaceRenderable; + class Time; +} // namespace openspace + +namespace openspace::events { + +struct Event { + // Steps to add a new event type: + // 1. Add a new entry into this enum list + // 2. Create a new subclass of Event in this file with a constructor that sets the + // Event's `type` to this new enum entry + // 3. In the cpp file, add a new `log` message that takes the new type as an argument + // and that prints something useful when the log is encountered and the user wants + // to see all events. + // 4. Add a new case into the logAllEvents function that handles the new enum entry + // 5. If the new event type has any parameters it takes in its constructor, go into + // the `toParameter` function and add a case label for the new enum type and + // return a dictionary with these parameters. This dictionary is passed to actions + // if they are triggered by events + // 6. Add the new enum entry into the `toString` and `fromString` methods + enum class Type { + SceneGraphNodeAdded, + SceneGraphNodeRemoved, + ParallelConnection, + ProfileLoadingFinished, + ApplicationShutdown, + ScreenSpaceRenderableAdded, + ScreenSpaceRenderableRemoved, + CameraFocusTransition, + TimeOfInterestReached, + MissionEventReached, + PlanetEclipsed, + InterpolationFinished, + FocusNodeChanged, + LayerAdded, + LayerRemoved, + SessionRecordingPlayback, + Custom + }; + constexpr explicit Event(Type type_) : type(type_) {} + + const Type type; + const Event* next = nullptr; +}; + +template +T* asType(Event* e) { + ghoul_assert(e->type == T::Type, "Wrong type requested, check 'isType'"); + return static_cast(e); +} + +template +bool isType(Event* e) { + return e->type == T::Type; +} + +std::string_view toString(Event::Type type); +Event::Type fromString(std::string_view str); + +ghoul::Dictionary toParameter(const Event& e); + +void logAllEvents(const Event* e); + +// +// Events +// + +/** + * This event is created whenever a new scene graph node is added to the system. By the + * time this event is signalled, the scene graph node has already been created and added + * to the scene. + * + * \param Node The identifier of the node that was added + */ +struct EventSceneGraphNodeAdded : public Event { + static const Type Type = Event::Type::SceneGraphNodeAdded; + + explicit EventSceneGraphNodeAdded(const SceneGraphNode* node_); + const tstring node; +}; + +/** + * This event is created whenever a scene graph node was removed. By the time this event + * is signalled, the scene graph node has already been removed. + * + * \param Node The identifier of that node that was removed + */ +struct EventSceneGraphNodeRemoved : public Event { + static const Type Type = Event::Type::SceneGraphNodeRemoved; + + explicit EventSceneGraphNodeRemoved(const SceneGraphNode* node_); + const tstring node; +}; + +/** + * This event is created whenever something in the parallel connection subsystem changes. + * The new state is sent as an argument with this event. + * + * \param State The new state of the parallel connection system; is one of `Established`, + * `Lost`, `HostshipGained`, or `HostshipLost` + */ +struct EventParallelConnection : public Event { + static const Type Type = Event::Type::ParallelConnection; + + enum class State : uint8_t { + Established, + Lost, + HostshipGained, + HostshipLost + }; + explicit EventParallelConnection(State state_); + State state; +}; + +/** + * This event is created when the loading of a profile is finished. This is emitted + * regardless of whether it is the initial profile, or any subsequent profile is loaded. + */ +struct EventProfileLoadingFinished : public Event { + static const Type Type = Event::Type::ProfileLoadingFinished; + + EventProfileLoadingFinished(); +}; + +/** + * This event is created whenever some information about the application shutdown sequence + * changes. This can either be that the seqeuence started, was aborted, or is finished, + * which means that OpenSpace is just about the shutdown. + * + * \param State The next state of the application shutdown sequence; is one of `Started`, + * `Aborted`, or `Finished` + */ +struct EventApplicationShutdown : public Event { + static const Type Type = Event::Type::ApplicationShutdown; + + enum class State : uint8_t { + Started, + Aborted, + Finished + }; + + explicit EventApplicationShutdown(State state_); + const State state; +}; + +/** + * This event is created when a new screenspace renderable has been created. By the time + * this event is craeted, the screenspace renderable is already registered and available. + * + * \param Renderable The identifier of the new screenspace renderable that was just added + * to the system + */ +struct EventScreenSpaceRenderableAdded : public Event { + static const Type Type = Event::Type::ScreenSpaceRenderableAdded; + + explicit EventScreenSpaceRenderableAdded(const ScreenSpaceRenderable* renderable_); + const tstring renderable; +}; + +/** + * This event is created when a screenspace renderable has been removed from the system. + * When this event is created, the screenspace renderable has already been removed and is + * no longer available + * + * \param Renderable The identifier of the screenspace renderable that was removed + */ +struct EventScreenSpaceRenderableRemoved : public Event { + static const Type Type = Event::Type::ScreenSpaceRenderableRemoved; + + explicit EventScreenSpaceRenderableRemoved(const ScreenSpaceRenderable* renderable_); + const tstring renderable; +}; + +/** + * This event is created when the camera transitions between different interaction sphere + * distances. Right now, only movement relative to camera's focus node is considered. + * Each scene graph node has an interaction sphere radius that serves as the reference + * distance for all spheres. +``` +Diagram of events for a camera moving from right-to-left. Interaction sphere is 'O' in middle, and ')' are spherical boundaries. The approach factor, reach factor, and interaction sphere radius are all taken from the current focus node. + +|<------------------->| Approach factor * Interaction sphere + |<------>| Reach Factor * Interaction sphere + +( ( O ) ) +^ ^ ^ ^ +Exiting Receding Reaching Approaching +``` + * + * \param Node The name of the node the camera is transitioning relative to. Currently is + * always the same as the camera's focus node + * \param Transition The transition type that the camera just finished; is one of + * `Approaching`, `Reaching`, `Receding`, or `Exiting` + */ +struct EventCameraFocusTransition : public Event { + static const Type Type = Event::Type::CameraFocusTransition; + + enum class Transition { + Approaching, + Reaching, + Receding, + Exiting + }; + + EventCameraFocusTransition(const Camera* camera_, const SceneGraphNode* node_, + Transition transition_); + + const Camera* camera = nullptr; + const tstring node; + const Transition transition; +}; + + +/** + * This event is created with a specific time of interest is reached. This event is + * currently unused. + */ +struct EventTimeOfInterestReached : public Event { + static const Type Type = Event::Type::TimeOfInterestReached; + + EventTimeOfInterestReached(const Time* time_, const Camera* camera_); + const Time* time = nullptr; + const Camera* camera = nullptr; +}; + + +/** + * This event is created when the end of a mission phase is reached. This event is + * currently unused. + */ +struct EventMissionEventReached : public Event { + static const Type Type = Event::Type::MissionEventReached; + + // Not sure which kind of parameters we want to pass here + EventMissionEventReached(); +}; + +/** + * This event is created when a planet is eclipsed by a moon or a different planet. This + * event is currently unused. + * + * \param Eclipsee The identifier of the scene graph node that is eclipsed by another + * object + * \param Eclipser The identifier of the scene graph node that is eclipsing the other + * object + */ +struct EventPlanetEclipsed : public Event { + static const Type Type = Event::Type::PlanetEclipsed; + + EventPlanetEclipsed(const SceneGraphNode* eclipsee_, const SceneGraphNode* eclipser_); + const tstring eclipsee; + const tstring eclipser; +}; + +/** + * This event is created when the interpolation of a property value is finished. If the + * interpolation time of a property change is 0s, this event is not fired + * + * \param Property The URI of the property whose interpolation has finished + */ +struct EventInterpolationFinished : public Event { + static const Type Type = Event::Type::InterpolationFinished; + + EventInterpolationFinished(const properties::Property* property_); + const tstring property; +}; + +/** + * This event is created when the camera changes focus nodes. Even if the camera position + * is interpolated, the node change happens instantaneously and the event is fired at the + * same time. + * + * \param OldNode The identifier of the scene graph node which was the old focus node + * \param NewNode The identifier of the scene graph node that is the new focus node + */ +struct EventFocusNodeChanged : public Event { + static const Type Type = Event::Type::FocusNodeChanged; + + EventFocusNodeChanged(const SceneGraphNode* oldNode_, const SceneGraphNode* newNode_); + const tstring oldNode; + const tstring newNode; +}; + +/** + * This event is created when a layer is added to to a globe. + * + * \param Globe The identifier of the globe to which the layer is added + * \param Group The identifier of the layer group to which the layer is added + * \param Layer The identifier of the layer that was added + */ +struct EventLayerAdded : public Event { + static const Type Type = Event::Type::LayerAdded; + + explicit EventLayerAdded(std::string_view node_, std::string_view layerGroup_, + std::string_view layer_); + const tstring node; + const tstring layerGroup; + const tstring layer; +}; + +/** + * This event is created when a layer is removed from a globe. + * + * \param Globe The identifier of the globe from which the layer is removed + * \param Group The identifier of the layer group from which the layer is removed + * \param Layer The identifier of the layer that was removed + */ +struct EventLayerRemoved : public Event { + static const Type Type = Event::Type::LayerRemoved; + + explicit EventLayerRemoved(std::string_view node_, std::string_view layerGroup_, + std::string_view layer_); + const tstring node; + const tstring layerGroup; + const tstring layer; +}; + +/** + * This event is created when something regarding a session recording playback changes. + * The event contains information about the new state of the session recording subsystem. + * + * \param State The new state of the session recording; one of `Started`, `Paused`, + * `Resumed`, `Finished` + */ +struct EventSessionRecordingPlayback : public Event { + static const Type Type = Event::Type::SessionRecordingPlayback; + + enum class State { + Started, + Paused, + Resumed, + Finished + }; + + EventSessionRecordingPlayback(State state_); + const State state; +}; + +/** + * A custom event type that can be used in a pinch when no explicit event type is + * available. This should only be used in special circumstances and it should be + * transitioned to a specific event type, if it is deemed to be useful. + */ +struct CustomEvent : public Event { + static const Type Type = Event::Type::Custom; + + CustomEvent(std::string_view subtype_, const void* payload_); + + const tstring subtype; + const void* payload = nullptr; +}; + +} // namespace openspace::events + +#endif // __OPENSPACE_CORE___EVENT___H__ diff --git a/include/openspace/events/eventengine.h b/include/openspace/events/eventengine.h new file mode 100644 index 0000000000..70e945012b --- /dev/null +++ b/include/openspace/events/eventengine.h @@ -0,0 +1,125 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___EVENTENGINE___H__ +#define __OPENSPACE_CORE___EVENTENGINE___H__ + +#include +#include +#include +#include + +namespace openspace { + +namespace events { struct Event; } + +class EventEngine { +public: + /** + * This function returns the first event stored in the EventEngine, or \c nullptr if + * no event exists. To navigate the full list of events, you can access the returned + * Event's next function. If the end of the list is reached, the next pointer will be + * a nullptr + * + * \return The first event stored in the EventEngine or nullptr if no event exists + */ + events::Event* firstEvent() const; + + /** + * Publish a new event of type T by providing optional arguments Args to the Event's + * constructor. An example of usage is + * engine.publishEvent("a", 2.0); which would call the + * constructor of \c MyEvent with a const char* and \c double parameter. + * + * \param args The arguments that are passed to the constructor of T + * \tparam T The subclass of Event that is to be published + */ + template + void publishEvent(Args&&... args); + + /** + * This function cleans up the memory for all published events.After this function + * has been called, no previously published events are valid any longer. This means + * that pointers retrieved from events before this call must be kept beyond this call. + */ + void postFrameCleanup(); + + /** + * Registers a new action for a specific event type. + * + * \param type The type for which a new action is registered + * \param actionIdentifier The identifier of the action that will be triggered the + * identifier must not exist at this moment, but must exist by the time the + * event is encountered next + * \param filter If the filter is provided, it describes the event parameters that are + * checked and only if an event passes the filter, the corresponding action is + * triggered + */ + void registerEventAction(events::Event::Type type, std::string identifier, + std::optional filter = std::nullopt); + + /** + * Removing registration for a type/action combination. + * + * \param type The type of the action that should be unregistered + * \param actionIdentifier The identifier of the action that should be unregistered + * \param filter The optional filter applied to the event-action combination + */ + void unregisterEventAction(events::Event::Type type, + const std::string& identifier, + std::optional filter = std::nullopt); + + /** + * Triggers all actions that are registered for events that are in the current event + * queue + */ + void triggerActions() const; + + static scripting::LuaLibrary luaLibrary(); + +private: + /// The storage space in which Events are stored + ghoul::MemoryPool<4096> _memory; + /// The first event in the chain of events stored in the memory pool + events::Event* _firstEvent = nullptr; + /// The last event in the chain of events stored in the memory pool + events::Event* _lastEvent = nullptr; + + struct ActionInfo { + std::string action; + std::optional filter; + }; + std::unordered_map> _eventActions; + +#ifdef _DEBUG + /// Stores the total number of events during this frame for debugging purposes + static uint64_t nEvents; +#endif // _DEBUG +}; + +} // namespace openspace + +#include "eventengine.inl" + +#endif // __OPENSPACE_CORE___EVENTENGINE___H__ diff --git a/include/openspace/events/eventengine.inl b/include/openspace/events/eventengine.inl new file mode 100644 index 0000000000..019540b48b --- /dev/null +++ b/include/openspace/events/eventengine.inl @@ -0,0 +1,51 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +namespace openspace { + +template +void EventEngine::publishEvent(Args&&... args) { + static_assert( + std::is_base_of::value, + "T must be a subclass of Event" + ); + + T* e = _memory.alloc(args...); + if (!_firstEvent) { + _firstEvent = e; + _lastEvent = e; + } + else { + _lastEvent->next = e; + _lastEvent = e; + } + +#ifdef _DEBUG + nEvents++; +#endif // _DEBUG +} + +} // namespace openspace diff --git a/include/openspace/navigation/navigationhandler.h b/include/openspace/navigation/navigationhandler.h index b25a14dfe5..8738c04caa 100644 --- a/include/openspace/navigation/navigationhandler.h +++ b/include/openspace/navigation/navigationhandler.h @@ -140,6 +140,7 @@ public: private: void applyNavigationState(const NavigationState& ns); + void updateCameraTransitions(); bool _playbackModeEnabled = false; @@ -147,6 +148,10 @@ private: Camera* _camera = nullptr; std::function _playbackEndCallback; + inline static const double InteractionHystersis = 0.0125; + bool _inAnchorApproachSphere = false; + bool _inAnchorReachSphere = false; + OrbitalNavigator _orbitalNavigator; KeyframeNavigator _keyframeNavigator; PathNavigator _pathNavigator; diff --git a/include/openspace/scene/scenegraphnode.h b/include/openspace/scene/scenegraphnode.h index d83fda6315..48aae21f1c 100644 --- a/include/openspace/scene/scenegraphnode.h +++ b/include/openspace/scene/scenegraphnode.h @@ -128,9 +128,17 @@ public: SceneGraphNode* parent() const; std::vector children() const; + const std::vector& onApproachAction() const; + const std::vector& onReachAction() const; + const std::vector& onRecedeAction() const; + const std::vector& onExitAction() const; + double boundingSphere() const; double interactionSphere() const; + double reachFactor() const; + double approachFactor() const; + SceneGraphNode* childNode(const std::string& identifier); const Renderable* renderable() const; @@ -155,6 +163,11 @@ private: std::vector _dependentNodes; Scene* _scene = nullptr; + std::vector _onApproachAction; + std::vector _onReachAction; + std::vector _onRecedeAction; + std::vector _onExitAction; + // If this value is 'true' GUIs are asked to hide this node from collections, as it // might be a node that is not very interesting (for example barycenters) properties::BoolProperty _guiHidden; @@ -183,6 +196,8 @@ private: properties::DoubleProperty _boundingSphere; properties::DoubleProperty _interactionSphere; + properties::DoubleProperty _approachFactor; + properties::DoubleProperty _reachFactor; properties::BoolProperty _computeScreenSpaceValues; properties::IVec2Property _screenSpacePosition; properties::BoolProperty _screenVisibility; diff --git a/include/openspace/util/memorymanager.h b/include/openspace/util/memorymanager.h index e6f8771d9e..bf8121a342 100644 --- a/include/openspace/util/memorymanager.h +++ b/include/openspace/util/memorymanager.h @@ -31,11 +31,11 @@ namespace openspace { class MemoryManager { public: - ghoul::MemoryPool<8 * 1024 * 1024, false> PersistentMemory; + ghoul::MemoryPool<8 * 1024 * 1024> PersistentMemory; // This should be replaced with a std::pmr::memory_resource wrapper around our own // Memory pool so that we can get a high-water mark out of it - ghoul::MemoryPool<100 * 4096, false> TemporaryMemory; + ghoul::MemoryPool<100 * 4096, false, true> TemporaryMemory; }; } // namespace openspace diff --git a/include/openspace/util/tstring.h b/include/openspace/util/tstring.h new file mode 100644 index 0000000000..26f41e7ea9 --- /dev/null +++ b/include/openspace/util/tstring.h @@ -0,0 +1,64 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include +#include + +namespace openspace { + +/** + * This string is a temporary string that is generated using the temporary memory + * storage. This means that under no circumstances must an instance of a tstring be kept + * across frame boundaries as the temporary storage is reset at between frames. In + * exchange, the allocation of these objects is extreme fast and with barely any overhead + * associated with it. The memory accessed through a tstring object shall never be + * released manually. + */ +using tstring = std::string_view; + +/** + * Allocate and create a temporary string from the passed std::string. + * + * \param str The string to be copied into a newly allocated tstring + * \return The copy of the str as a temporary string + */ +tstring temporaryString(const std::string& str); + +/** + * Allocate and create a temporary string from the passed std::string_view. + * + * \param str The string to be copied into a newly allocated tstring + * \return The copy of the str as a temporary string + */ +tstring temporaryString(std::string_view str); + +/** + * Allocate and create a temporary string from the passed char array. + * + * \param str The string to be copied into a newly allocated tstring + * \return The copy of the str as a temporary string + */ +tstring temporaryString(const char str[]); + +} // namespace openspace diff --git a/modules/base/rendering/renderabletrailorbit.cpp b/modules/base/rendering/renderabletrailorbit.cpp index fffd3effd8..c4a20f212f 100644 --- a/modules/base/rendering/renderabletrailorbit.cpp +++ b/modules/base/rendering/renderabletrailorbit.cpp @@ -28,6 +28,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include + #include #include #include diff --git a/modules/globebrowsing/src/layergroup.cpp b/modules/globebrowsing/src/layergroup.cpp index dc67b2e62b..3254049cb1 100644 --- a/modules/globebrowsing/src/layergroup.cpp +++ b/modules/globebrowsing/src/layergroup.cpp @@ -26,6 +26,9 @@ #include #include +#include +#include +#include #include #include @@ -125,17 +128,18 @@ Layer* LayerGroup::addLayer(const ghoul::Dictionary& layerDict) { } if (!layerDict.hasValue("Identifier")) { - LERROR("'Identifier' must be specified for layer."); + LERROR("'Identifier' must be specified for layer"); return nullptr; } - std::unique_ptr layer = std::make_unique(_groupId, layerDict, *this); - layer->onChange(_onChangeCallback); - if (hasPropertySubOwner(layer->identifier())) { - LINFO("Layer with identifier " + layer->identifier() + " already exists."); + std::string identifier = layerDict.value("Identifier"); + if (hasPropertySubOwner(identifier)) { + LINFO("Layer with identifier '" + identifier + "' already exists"); _levelBlendingEnabled.setVisibility(properties::Property::Visibility::User); return nullptr; } + std::unique_ptr layer = std::make_unique(_groupId, layerDict, *this); + layer->onChange(_onChangeCallback); Layer* ptr = layer.get(); _layers.push_back(std::move(layer)); update(); @@ -144,6 +148,17 @@ Layer* LayerGroup::addLayer(const ghoul::Dictionary& layerDict) { } addPropertySubOwner(ptr); _levelBlendingEnabled.setVisibility(properties::Property::Visibility::User); + + properties::PropertyOwner* layerGroup = ptr->owner(); + properties::PropertyOwner* layerManager = layerGroup->owner(); + properties::PropertyOwner* globe = layerManager->owner(); + properties::PropertyOwner* sceneGraphNode = globe->owner(); + + global::eventEngine->publishEvent( + sceneGraphNode->identifier(), + layerGroup->identifier(), + ptr->identifier() + ); return ptr; } @@ -159,6 +174,15 @@ void LayerGroup::deleteLayer(const std::string& layerName) { removePropertySubOwner(it->get()); (*it)->deinitialize(); _layers.erase(it); + properties::PropertyOwner* layerGroup = it->get()->owner(); + properties::PropertyOwner* layerManager = layerGroup->owner(); + properties::PropertyOwner* globe = layerManager->owner(); + properties::PropertyOwner* sceneGraphNode = globe->owner(); + global::eventEngine->publishEvent( + sceneGraphNode->identifier(), + layerGroup->identifier(), + it->get()->identifier() + ); update(); if (_onChangeCallback) { _onChangeCallback(nullptr); diff --git a/openspace.cfg b/openspace.cfg index 6828473069..9be037a435 100644 --- a/openspace.cfg +++ b/openspace.cfg @@ -222,6 +222,7 @@ LoadingScreen = { } CheckOpenGLState = false LogEachOpenGLCall = false +PrintEvents = false ShutdownCountdown = 3 ScreenshotUseDate = true diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 187628bdce..9fe2d9fbdd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,6 +43,9 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/engine/openspaceengine_lua.inl ${OPENSPACE_BASE_DIR}/src/engine/syncengine.cpp ${OPENSPACE_BASE_DIR}/src/engine/virtualpropertymanager.cpp + ${OPENSPACE_BASE_DIR}/src/events/event.cpp + ${OPENSPACE_BASE_DIR}/src/events/eventengine.cpp + ${OPENSPACE_BASE_DIR}/src/events/eventengine_lua.inl ${OPENSPACE_BASE_DIR}/src/interaction/actionmanager.cpp ${OPENSPACE_BASE_DIR}/src/interaction/actionmanager_lua.inl ${OPENSPACE_BASE_DIR}/src/interaction/camerainteractionstates.cpp @@ -176,6 +179,7 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/util/spicemanager_lua.inl ${OPENSPACE_BASE_DIR}/src/util/syncbuffer.cpp ${OPENSPACE_BASE_DIR}/src/util/synchronizationwatcher.cpp + ${OPENSPACE_BASE_DIR}/src/util/tstring.cpp ${OPENSPACE_BASE_DIR}/src/util/histogram.cpp ${OPENSPACE_BASE_DIR}/src/util/task.cpp ${OPENSPACE_BASE_DIR}/src/util/taskloader.cpp @@ -223,6 +227,9 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/engine/syncengine.h ${OPENSPACE_BASE_DIR}/include/openspace/engine/virtualpropertymanager.h ${OPENSPACE_BASE_DIR}/include/openspace/engine/windowdelegate.h + ${OPENSPACE_BASE_DIR}/include/openspace/events/event.h + ${OPENSPACE_BASE_DIR}/include/openspace/events/eventengine.h + ${OPENSPACE_BASE_DIR}/include/openspace/events/eventengine.inl ${OPENSPACE_BASE_DIR}/include/openspace/interaction/action.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/actionmanager.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/delayedvariable.h @@ -381,6 +388,7 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/util/timemanager.h ${OPENSPACE_BASE_DIR}/include/openspace/util/timerange.h ${OPENSPACE_BASE_DIR}/include/openspace/util/touch.h + ${OPENSPACE_BASE_DIR}/include/openspace/util/tstring.h ${OPENSPACE_BASE_DIR}/include/openspace/util/universalhelpers.h ${OPENSPACE_BASE_DIR}/include/openspace/util/updatestructures.h ${OPENSPACE_BASE_DIR}/include/openspace/util/versionchecker.h diff --git a/src/documentation/core_registration.cpp b/src/documentation/core_registration.cpp index 0e604f34e5..f9d5d63c20 100644 --- a/src/documentation/core_registration.cpp +++ b/src/documentation/core_registration.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -77,13 +78,14 @@ void registerCoreClasses(documentation::DocumentationEngine& engine) { // documentation version. void registerCoreClasses(scripting::ScriptEngine& engine) { engine.addLibrary(Dashboard::luaLibrary()); + engine.addLibrary(EventEngine::luaLibrary()); engine.addLibrary(MissionManager::luaLibrary()); engine.addLibrary(ModuleEngine::luaLibrary()); engine.addLibrary(OpenSpaceEngine::luaLibrary()); engine.addLibrary(ParallelPeer::luaLibrary()); + engine.addLibrary(Profile::luaLibrary()); engine.addLibrary(RenderEngine::luaLibrary()); engine.addLibrary(SpiceManager::luaLibrary()); - engine.addLibrary(Profile::luaLibrary()); engine.addLibrary(Scene::luaLibrary()); engine.addLibrary(Time::luaLibrary()); engine.addLibrary(interaction::ActionManager::luaLibrary()); diff --git a/src/engine/configuration.cpp b/src/engine/configuration.cpp index 454ee1f6a3..681315b554 100644 --- a/src/engine/configuration.cpp +++ b/src/engine/configuration.cpp @@ -295,6 +295,11 @@ namespace { // 'false' std::optional logEachOpenGLCall; + // Determines whether events are printed as debug messages to the console each + // frame. If this value is set it determines the default value of the property of + // the OpenSpaceEngine with the same name + std::optional printEvents; + // This value determines whether the initialization of the scene graph should // occur multithreaded, that is, whether multiple scene graph nodes should // initialize in parallel. The only use for this value is to disable it for @@ -391,6 +396,7 @@ void parseLuaState(Configuration& configuration) { p.useMultithreadedInitialization.value_or(c.useMultithreadedInitialization); c.isCheckingOpenGLState = p.checkOpenGLState.value_or(c.isCheckingOpenGLState); c.isLoggingOpenGLCalls = p.logEachOpenGLCall.value_or(c.isLoggingOpenGLCalls); + c.isPrintingEvents = p.printEvents.value_or(c.isPrintingEvents); c.shutdownCountdown = p.shutdownCountdown.value_or(c.shutdownCountdown); c.shouldUseScreenshotDate = p.screenshotUseDate.value_or(c.shouldUseScreenshotDate); if (p.onScreenTextScaling.has_value()) { diff --git a/src/engine/globals.cpp b/src/engine/globals.cpp index 79667a34bd..e2740f9679 100644 --- a/src/engine/globals.cpp +++ b/src/engine/globals.cpp @@ -24,14 +24,15 @@ #include -#include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -71,12 +72,13 @@ namespace { // in some random global randoms #ifdef WIN32 constexpr const int TotalSize = + sizeof(MemoryManager) + + sizeof(EventEngine) + sizeof(ghoul::fontrendering::FontManager) + sizeof(Dashboard) + sizeof(DeferredcasterManager) + sizeof(DownloadManager) + sizeof(LuaConsole) + - sizeof(MemoryManager) + sizeof(MissionManager) + sizeof(ModuleEngine) + sizeof(OpenSpaceEngine) + @@ -119,6 +121,22 @@ void create() { std::byte* currentPos = DataStorage.data(); #endif // WIN32 +#ifdef WIN32 + memoryManager = new (currentPos) MemoryManager; + ghoul_assert(memoryManager, "No memoryManager"); + currentPos += sizeof(MemoryManager); +#else // ^^^ WIN32 / !WIN32 vvv + memoryManager = new MemoryManager; +#endif // WIN32 + +#ifdef WIN32 + eventEngine = new (currentPos) EventEngine; + ghoul_assert(eventEngine, "No eventEngine"); + currentPos += sizeof(EventEngine); +#else // ^^^ WIN32 / !WIN32 vvv + downloadManager = new EventEngine; +#endif // WIN32 + #ifdef WIN32 fontManager = new (currentPos) ghoul::fontrendering::FontManager({ 1536, 1536, 1 }); ghoul_assert(fontManager, "No fontManager"); @@ -159,14 +177,6 @@ void create() { luaConsole = new LuaConsole; #endif // WIN32 -#ifdef WIN32 - memoryManager = new (currentPos) MemoryManager; - ghoul_assert(memoryManager, "No memoryManager"); - currentPos += sizeof(MemoryManager); -#else // ^^^ WIN32 / !WIN32 vvv - memoryManager = new MemoryManager; -#endif // WIN32 - #ifdef WIN32 missionManager = new (currentPos) MissionManager; ghoul_assert(missionManager, "No missionManager"); @@ -573,13 +583,6 @@ void destroy() { delete missionManager; #endif // WIN32 - LDEBUGC("Globals", "Destroying 'MemoryManager'"); -#ifdef WIN32 - memoryManager->~MemoryManager(); -#else // ^^^ WIN32 / !WIN32 vvv - delete memoryManager; -#endif // WIN32 - LDEBUGC("Globals", "Destroying 'LuaConsole'"); #ifdef WIN32 luaConsole->~LuaConsole(); @@ -615,6 +618,20 @@ void destroy() { delete fontManager; #endif // WIN32 + LDEBUGC("Globals", "Destroying 'EventEngine'"); +#ifdef WIN32 + eventEngine->~EventEngine(); +#else // ^^^ WIN32 / !WIN32 vvv + delete eventEngine; +#endif // WIN32 + + LDEBUGC("Globals", "Destroying 'MemoryManager'"); +#ifdef WIN32 + memoryManager->~MemoryManager(); +#else // ^^^ WIN32 / !WIN32 vvv + delete memoryManager; +#endif // WIN32 + callback::destroy(); } diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index f0c98acbe8..efbaae7daa 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include #include #include @@ -53,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -101,13 +104,22 @@ namespace { template overloaded(Ts...)->overloaded; constexpr const char* _loggerCat = "OpenSpaceEngine"; + + openspace::properties::Property::PropertyInfo PrintEventsInfo = { + "PrintEvents", + "Print Events", + "If this is enabled, all events that are propagated through the system are " + "printed to the log." + }; } // namespace namespace openspace { class Scene; -OpenSpaceEngine::OpenSpaceEngine() { +OpenSpaceEngine::OpenSpaceEngine() + : _printEvents(PrintEventsInfo, false) +{ FactoryManager::initialize(); FactoryManager::ref().addFactory( std::make_unique>(), @@ -186,6 +198,8 @@ void OpenSpaceEngine::initialize() { global::initialize(); + _printEvents = global::configuration->isPrintingEvents; + const std::string versionCheckUrl = global::configuration->versionCheckUrl; if (!versionCheckUrl.empty()) { global::versionChecker->requestLatestVersion(versionCheckUrl); @@ -895,13 +909,16 @@ void OpenSpaceEngine::deinitialize() { TransformationManager::deinitialize(); SpiceManager::deinitialize(); + if (_printEvents) { + events::Event* e = global::eventEngine->firstEvent(); + events::logAllEvents(e); + } + ghoul::fontrendering::FontRenderer::deinitialize(); ghoul::logging::LogManager::deinitialize(); LTRACE("deinitialize(end)"); - - LTRACE("OpenSpaceEngine::deinitialize(end)"); } @@ -1101,6 +1118,7 @@ void OpenSpaceEngine::preSynchronization() { resetPropertyChangeFlagsOfSubowners(global::rootPropertyOwner); _hasScheduledAssetLoading = false; _scheduledAssetPathToLoad.clear(); + global::eventEngine->publishEvent(); } else if (_isRenderingFirstFrame) { global::profile->ignoreUpdates = true; @@ -1186,6 +1204,9 @@ void OpenSpaceEngine::postSynchronizationPreDraw() { if (_shutdown.inShutdown) { if (_shutdown.timer <= 0.f) { + global::eventEngine->publishEvent( + events::EventApplicationShutdown::State::Finished + ); global::windowDelegate->terminate(); return; } @@ -1311,6 +1332,23 @@ void OpenSpaceEngine::postDraw() { _isRenderingFirstFrame = false; } + // + // Handle events + // + const events::Event* e = global::eventEngine->firstEvent(); + if (_printEvents) { + 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->postFrameCleanup(); global::memoryManager->PersistentMemory.housekeeping(); LTRACE("OpenSpaceEngine::postDraw(end)"); @@ -1544,11 +1582,17 @@ void OpenSpaceEngine::toggleShutdownMode() { if (_shutdown.inShutdown) { // If we are already in shutdown mode, we want to disable it _shutdown.inShutdown = false; + global::eventEngine->publishEvent( + events::EventApplicationShutdown::State::Aborted + ); } else { // Else, we have to enable it _shutdown.timer = _shutdown.waitTime; _shutdown.inShutdown = true; + global::eventEngine->publishEvent( + events::EventApplicationShutdown::State::Started + ); } } diff --git a/src/events/event.cpp b/src/events/event.cpp new file mode 100644 index 0000000000..f056c2ad33 --- /dev/null +++ b/src/events/event.cpp @@ -0,0 +1,595 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char _loggerCat[] = "EventInfo"; +} // namespace + +using namespace std::string_literals; + +namespace openspace::events { + +void log(int i, const EventSceneGraphNodeAdded& e) { + ghoul_assert(e.type == EventSceneGraphNodeAdded::Type, "Wrong type"); + LINFO(fmt::format("[{}] SceneGraphNodeAdded: {}", i, e.node)); +} + +void log(int i, const EventSceneGraphNodeRemoved& e) { + ghoul_assert(e.type == EventSceneGraphNodeRemoved::Type, "Wrong type"); + LINFO(fmt::format("[{}] SceneGraphNodeRemoved: {}", i, e.node)); +} + +void log(int i, const EventParallelConnection& e) { + ghoul_assert(e.type == EventParallelConnection::Type, "Wrong type"); + std::string_view state = [](EventParallelConnection::State s) { + switch (s) { + case EventParallelConnection::State::Established: return "Established"; + case EventParallelConnection::State::Lost: return "Lost"; + case EventParallelConnection::State::HostshipGained: return "HostshipGained"; + case EventParallelConnection::State::HostshipLost: return "HostshipLost"; + default: throw ghoul::MissingCaseException(); + } + }(e.state); + LINFO(fmt::format("[{}] ParallelConnection ({})", i, state)); +} + +void log(int i, [[ maybe_unused ]] const EventProfileLoadingFinished& e) { + ghoul_assert(e.type == EventProfileLoadingFinished::Type, "Wrong type"); + LINFO(fmt::format("[{}] ProfileLoadingFinished", i)); +} + +void log(int i, const EventApplicationShutdown& e) { + ghoul_assert(e.type == EventApplicationShutdown::Type, "Wrong type"); + std::string t = [](EventApplicationShutdown::State state) { + switch (state) { + case EventApplicationShutdown::State::Started: return "started"; + case EventApplicationShutdown::State::Aborted: return "aborted"; + case EventApplicationShutdown::State::Finished: return "finished"; + default: throw ghoul::MissingCaseException(); + } + }(e.state); + LINFO(fmt::format("[{}] ApplicationShutdown", i)); +} + +void log(int i, const EventScreenSpaceRenderableAdded& e) { + ghoul_assert(e.type == EventScreenSpaceRenderableAdded::Type, "Wrong type"); + LINFO(fmt::format("[{}] ScreenSpaceRenderableAdded: {}", i, e.renderable)); +} + +void log(int i, const EventScreenSpaceRenderableRemoved& e) { + ghoul_assert(e.type == EventScreenSpaceRenderableRemoved::Type, "Wrong type"); + LINFO(fmt::format("[{}] ScreenSpaceRenderableRemoved: {}", i, e.renderable)); +} + +void log(int i, const EventCameraFocusTransition& e) { + ghoul_assert(e.type == EventCameraFocusTransition::Type, "Wrong type"); + std::string_view t = [](EventCameraFocusTransition::Transition transition) { + switch (transition) { + case EventCameraFocusTransition::Transition::Approaching: + return "Approaching"; + case EventCameraFocusTransition::Transition::Reaching: + return "Reaching"; + case EventCameraFocusTransition::Transition::Receding: + return "Receding"; + case EventCameraFocusTransition::Transition::Exiting: + return "Exiting"; + default: throw ghoul::MissingCaseException(); + } + }(e.transition); + + LINFO(fmt::format( + "[{}] CameraTransition: {}, {} ({})", + i, reinterpret_cast(e.camera), e.node, t + )); +} + +void log(int i, const EventTimeOfInterestReached& e) { + ghoul_assert(e.type == EventTimeOfInterestReached::Type, "Wrong type"); + LINFO(fmt::format( + "[{}] TimeOfInterestReached: {}, {}", + i, e.time->UTC(), reinterpret_cast(e.camera) + )); +} + +void log(int i, [[ maybe_unused ]] const EventMissionEventReached& e) { + ghoul_assert(e.type == EventMissionEventReached::Type, "Wrong type"); + LINFO(fmt::format("[{}] MissionEventReached", i)); +} + +void log(int i, const EventPlanetEclipsed& e) { + ghoul_assert(e.type == EventPlanetEclipsed::Type, "Wrong type"); + LINFO(fmt::format("[{}] PlanetEclipsed: {} -> {}", i, e.eclipsee, e.eclipser)); +} + +void log(int i, [[ maybe_unused ]] const EventInterpolationFinished& e) { + ghoul_assert(e.type == EventInterpolationFinished::Type, "Wrong type"); + LINFO(fmt::format("[{}] InterpolationFinished", i)); +} + +void log(int i, const EventFocusNodeChanged& e) { + ghoul_assert(e.type == EventFocusNodeChanged::Type, "Wrong type"); + LINFO(fmt::format("[{}] FocusNodeChanged: {} -> {}", i, e.oldNode, e.newNode)); +} + +void log(int i, const EventLayerAdded& e) { + ghoul_assert(e.type == EventLayerAdded::Type, "Wrong type"); + LINFO(fmt::format("[{}] LayerAdded: {}", i, e.layer)); +} + +void log(int i, const EventLayerRemoved& e) { + ghoul_assert(e.type == EventLayerRemoved::Type, "Wrong type"); + LINFO(fmt::format("[{}] LayerRemoved: {}", i, e.layer)); +} + +void log(int i, const EventSessionRecordingPlayback& e) { + ghoul_assert(e.type == EventSessionRecordingPlayback::Type, "Wrong type"); + + std::string_view state = [](EventSessionRecordingPlayback::State s) { + switch (s) { + case EventSessionRecordingPlayback::State::Started: return "Started"; + case EventSessionRecordingPlayback::State::Paused: return "Paused"; + case EventSessionRecordingPlayback::State::Resumed: return "Resumed"; + case EventSessionRecordingPlayback::State::Finished: return "Finished"; + default: throw ghoul::MissingCaseException(); + } + }(e.state); + + LINFO(fmt::format("[{}] SessionRecordingPlayback: {}", i, state)); +} + +void log(int i, const CustomEvent& e) { + ghoul_assert(e.type == CustomEvent::Type, "Wrong type"); + LINFO(fmt::format("[{}] CustomEvent: {} ({})", i, e.subtype, e.payload)); +} + +std::string_view toString(Event::Type type) { + switch (type) { + case Event::Type::SceneGraphNodeAdded: return "SceneGraphNodeAdded"; + case Event::Type::SceneGraphNodeRemoved: return "SceneGraphNodeRemoved"; + case Event::Type::ParallelConnection: return "ParallelConnection"; + case Event::Type::ProfileLoadingFinished: return "ProfileLoadingFinished"; + case Event::Type::ApplicationShutdown: return "ApplicationShutdown"; + case Event::Type::ScreenSpaceRenderableAdded: return "ScreenSpaceRenderableAdded"; + case Event::Type::ScreenSpaceRenderableRemoved: + return "ScreenSpaceRenderableRemoved"; + case Event::Type::CameraFocusTransition: return "CameraFocusTransition"; + case Event::Type::TimeOfInterestReached: return "TimeOfInterestReached"; + case Event::Type::MissionEventReached: return "MissionEventReached"; + case Event::Type::PlanetEclipsed: return "PlanetEclipsed"; + case Event::Type::InterpolationFinished: return "InterpolationFinished"; + case Event::Type::FocusNodeChanged: return "FocusNodeChanged"; + case Event::Type::LayerAdded: return "LayerAdded"; + case Event::Type::LayerRemoved: return "LayerRemoved"; + case Event::Type::SessionRecordingPlayback: return "SessionRecordingPlayback"; + case Event::Type::Custom: return "Custom"; + default: + throw ghoul::MissingCaseException(); + } +} + +Event::Type fromString(std::string_view str) { + if (str == "SceneGraphNodeAdded") { + return Event::Type::SceneGraphNodeAdded; + } + else if (str == "SceneGraphNodeRemoved") { + return Event::Type::SceneGraphNodeRemoved; + } + else if (str == "ParallelConnection") { + return Event::Type::ParallelConnection; + } + else if (str == "ProfileLoadingFinished") { + return Event::Type::ProfileLoadingFinished; + } + else if (str == "ApplicationShutdown") { + return Event::Type::ApplicationShutdown; + } + else if (str == "ScreenSpaceRenderableAdded") { + return Event::Type::ScreenSpaceRenderableAdded; + } + else if (str == "ScreenSpaceRenderableRemoved") { + return Event::Type::ScreenSpaceRenderableRemoved; + } + else if (str == "CameraFocusTransition") { + return Event::Type::CameraFocusTransition; + } + else if (str == "TimeOfInterestReached") { + return Event::Type::TimeOfInterestReached; + } + else if (str == "MissionEventReached") { + return Event::Type::MissionEventReached; + } + else if (str == "PlanetEclipsed") { + return Event::Type::PlanetEclipsed; + } + else if (str == "InterpolationFinished") { + return Event::Type::InterpolationFinished; + } + else if (str == "FocusNodeChanged") { + return Event::Type::FocusNodeChanged; + } + else if (str == "LayerAdded") { + return Event::Type::LayerAdded; + } + else if (str == "LayerRemoved") { + return Event::Type::LayerRemoved; + } + else if (str == "SessionRecordingPlayback") { + return Event::Type::SessionRecordingPlayback; + } + else if (str == "Custom") { + return Event::Type::Custom; + } + + throw ghoul::RuntimeError(fmt::format("Unknown event type '{}'", str)); +} + +ghoul::Dictionary toParameter(const Event& e) { + ghoul::Dictionary d; + switch (e.type) { + case Event::Type::SceneGraphNodeAdded: + d.setValue( + "Node", + std::string(static_cast(e).node) + ); + break; + case Event::Type::SceneGraphNodeRemoved: + d.setValue( + "Node", + std::string(static_cast(e).node) + ); + break; + case Event::Type::ParallelConnection: + switch (static_cast(e).state) { + case EventParallelConnection::State::Established: + d.setValue("State", "Established"s); + break; + case EventParallelConnection::State::Lost: + d.setValue("State", "Lost"s); + break; + case EventParallelConnection::State::HostshipGained: + d.setValue("State", "HostshipGained"s); + break; + case EventParallelConnection::State::HostshipLost: + d.setValue("State", "HostshipLost"s); + break; + default: + throw ghoul::MissingCaseException(); + } + break; + case Event::Type::ApplicationShutdown: + switch (static_cast(e).state) { + case EventApplicationShutdown::State::Started: + d.setValue("State", "Started"s); + break; + case EventApplicationShutdown::State::Aborted: + d.setValue("State", "Aborted"s); + break; + case EventApplicationShutdown::State::Finished: + d.setValue("State", "Finished"s); + break; + } + break; + case Event::Type::ScreenSpaceRenderableAdded: + d.setValue( + "Renderable", + std::string( + static_cast(e).renderable + ) + ); + break; + case Event::Type::ScreenSpaceRenderableRemoved: + d.setValue( + "Renderable", + std::string( + static_cast(e).renderable + ) + ); + break; + case Event::Type::CameraFocusTransition: + d.setValue( + "Node", + std::string(static_cast(e).node) + ); + switch (static_cast(e).transition) { + case EventCameraFocusTransition::Transition::Approaching: + d.setValue("Transition", "Approaching"s); + break; + case EventCameraFocusTransition::Transition::Reaching: + d.setValue("Transition", "Reaching"s); + break; + case EventCameraFocusTransition::Transition::Receding: + d.setValue("Transition", "Receding"s); + break; + case EventCameraFocusTransition::Transition::Exiting: + d.setValue("Transition", "Exiting"s); + break; + default: + throw ghoul::MissingCaseException(); + } + break; + case Event::Type::PlanetEclipsed: + d.setValue( + "Eclipsee", + std::string(static_cast(e).eclipsee) + ); + d.setValue( + "Eclipser", + std::string(static_cast(e).eclipser) + ); + break; + case Event::Type::InterpolationFinished: + d.setValue( + "Property", + std::string(static_cast(e).property) + ); + break; + case Event::Type::FocusNodeChanged: + d.setValue( + "OldNode", + std::string(static_cast(e).oldNode) + ); + d.setValue( + "NewNode", + std::string(static_cast(e).newNode) + ); + break; + case Event::Type::LayerAdded: + d.setValue( + "Globe", + std::string(static_cast(e).node) + ); + d.setValue( + "Group", + std::string(static_cast(e).layerGroup) + ); + d.setValue( + "Layer", + std::string(static_cast(e).layer) + ); + break; + case Event::Type::LayerRemoved: + d.setValue( + "Globe", + std::string(static_cast(e).node) + ); + d.setValue( + "Group", + std::string(static_cast(e).layerGroup) + ); + d.setValue( + "Layer", + std::string(static_cast(e).layer) + ); + break; + case Event::Type::SessionRecordingPlayback: + switch (static_cast(e).state) { + case EventSessionRecordingPlayback::State::Started: + d.setValue("State", "Started"s); + break; + case EventSessionRecordingPlayback::State::Paused: + d.setValue("State", "Paused"s); + break; + case EventSessionRecordingPlayback::State::Resumed: + d.setValue("State", "Resumed"s); + break; + case EventSessionRecordingPlayback::State::Finished: + d.setValue("State", "Finished"s); + break; + } + break; + case Event::Type::Custom: + d.setValue( + "Subtype", std::string(static_cast(e).subtype) + ); + break; + default: + break; + } + return d; +} + +void logAllEvents(const Event* e) { + int i = 0; + while (e) { + switch (e->type) { + case Event::Type::SceneGraphNodeAdded: + log(i, *static_cast(e)); + break; + case Event::Type::SceneGraphNodeRemoved: + log(i, *static_cast(e)); + break; + case Event::Type::ParallelConnection: + log(i, *static_cast(e)); + break; + case Event::Type::ProfileLoadingFinished: + log(i, *static_cast(e)); + break; + case Event::Type::ApplicationShutdown: + log(i, *static_cast(e)); + break; + case Event::Type::ScreenSpaceRenderableAdded: + log(i, *static_cast(e)); + break; + case Event::Type::ScreenSpaceRenderableRemoved: + log(i, *static_cast(e)); + break; + case Event::Type::CameraFocusTransition: + log(i, *static_cast(e)); + break; + case Event::Type::TimeOfInterestReached: + log(i, *static_cast(e)); + break; + case Event::Type::MissionEventReached: + log(i, *static_cast(e)); + break; + case Event::Type::PlanetEclipsed: + log(i, *static_cast(e)); + break; + case Event::Type::InterpolationFinished: + log(i, *static_cast(e)); + break; + case Event::Type::FocusNodeChanged: + log(i, *static_cast(e)); + break; + case Event::Type::LayerAdded: + log(i, *static_cast(e)); + break; + case Event::Type::LayerRemoved: + log(i, *static_cast(e)); + break; + case Event::Type::SessionRecordingPlayback: + log(i, *static_cast(e)); + break; + case Event::Type::Custom: + log(i, *static_cast(e)); + break; + default: + LINFO(fmt::format("[{}]: Unknown {}", typeid(e).name())); + break; + } + + i++; + e = e->next; + } +} + +EventSceneGraphNodeAdded::EventSceneGraphNodeAdded(const SceneGraphNode* node_) + : Event(Type) + , node(temporaryString(node_->identifier())) +{} + +EventSceneGraphNodeRemoved::EventSceneGraphNodeRemoved(const SceneGraphNode* node_) + : Event(Type) + , node(temporaryString(node_->identifier())) +{} + +EventParallelConnection::EventParallelConnection(State state_) + : Event(Type) + , state(state_) +{} + +EventProfileLoadingFinished::EventProfileLoadingFinished() + : Event(Type) +{} + +EventApplicationShutdown::EventApplicationShutdown(State state_) + : Event(Type) + , state(state_) +{} + +EventScreenSpaceRenderableAdded::EventScreenSpaceRenderableAdded( + const ScreenSpaceRenderable* renderable_) + : Event(Type) + , renderable(temporaryString(renderable_->identifier())) +{} + +EventScreenSpaceRenderableRemoved::EventScreenSpaceRenderableRemoved( + const ScreenSpaceRenderable* renderable_) + : Event(Type) + , renderable(temporaryString(renderable_->identifier())) +{} + +EventCameraFocusTransition::EventCameraFocusTransition(const Camera* camera_, + const SceneGraphNode* node_, + Transition transition_) + : Event(Type) + , camera(camera_) + , node(temporaryString(node_->identifier())) + , transition(transition_) +{} + +EventTimeOfInterestReached::EventTimeOfInterestReached(const Time* time_, + const Camera* camera_) + : Event(Type) + , time(time_) + , camera(camera_) +{} + +EventMissionEventReached::EventMissionEventReached() + : Event(Type) +{} + +EventPlanetEclipsed::EventPlanetEclipsed(const SceneGraphNode* eclipsee_, + const SceneGraphNode* eclipser_) + : Event(Type) + , eclipsee(temporaryString(eclipsee_->identifier())) + , eclipser(temporaryString(eclipser_->identifier())) +{} + +EventInterpolationFinished::EventInterpolationFinished( + const properties::Property* property_) + : Event(Type) + , property(temporaryString(property_->fullyQualifiedIdentifier())) +{} + +EventFocusNodeChanged::EventFocusNodeChanged(const SceneGraphNode* oldNode_, + const SceneGraphNode* newNode_) + : Event(Type) + , oldNode(oldNode_ ? temporaryString(oldNode_->identifier()) : "") + , newNode(temporaryString(newNode_->identifier())) +{ + ghoul_assert(newNode_, "There must be a new node"); +} + +EventLayerAdded::EventLayerAdded(std::string_view node_, std::string_view layerGroup_, + std::string_view layer_) + : Event(Type) + , node(temporaryString(node_)) + , layerGroup(temporaryString(layerGroup_)) + , layer(temporaryString(layer_)) +{} + +EventLayerRemoved::EventLayerRemoved(std::string_view node_, std::string_view layerGroup_, + std::string_view layer_) + : Event(Type) + , node(temporaryString(node_)) + , layerGroup(temporaryString(layerGroup_)) + , layer(temporaryString(layer_)) +{} + +EventSessionRecordingPlayback::EventSessionRecordingPlayback(State state_) + : Event(Type) + , state(state_) +{} + +CustomEvent::CustomEvent(std::string_view subtype_, const void* payload_) + : Event(Type) + , subtype(subtype_) + , payload(payload_) +{} + +} // namespace openspace::events diff --git a/src/events/eventengine.cpp b/src/events/eventengine.cpp new file mode 100644 index 0000000000..b01e1502a0 --- /dev/null +++ b/src/events/eventengine.cpp @@ -0,0 +1,133 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include + +#include "eventengine_lua.inl" + +namespace openspace { + +#ifdef _DEBUG +uint64_t EventEngine::nEvents = 0; +#endif // _DEBUG + +events::Event* EventEngine::firstEvent() const { + return _firstEvent; +} + +void EventEngine::postFrameCleanup() { + _memory.reset(); + _firstEvent = nullptr; + _lastEvent = nullptr; +#ifdef _DEBUG + nEvents = 0; +#endif // _DEBUG +} + +void EventEngine::registerEventAction(events::Event::Type type, + std::string identifier, + std::optional filter) +{ + ActionInfo ai; + ai.action = std::move(identifier); + ai.filter = std::move(filter); + const auto it = _eventActions.find(type); + if (it != _eventActions.end()) { + it->second.push_back(ai); + } + else { + _eventActions[type] = { std::move(ai) }; + } +} + +void EventEngine::unregisterEventAction(events::Event::Type type, + const std::string& identifier, + std::optional filter) +{ + const auto it = _eventActions.find(type); + if (it != _eventActions.end()) { + const auto jt = std::find_if( + it->second.begin(), it->second.end(), + [identifier, filter](const ActionInfo& ai) { + const bool a = ai.action == identifier; + const bool f = !filter.has_value() || *filter == ai.filter; + return a && f; + } + ); + if (jt != it->second.end()) { + it->second.erase(jt); + } + } +} + +void EventEngine::triggerActions() const { + if (_eventActions.empty()) { + // Nothing to do here + return; + } + + const events::Event* e = _firstEvent; + while (e) { + const auto it = _eventActions.find(e->type); + if (it != _eventActions.end()) { + ghoul::Dictionary params = toParameter(*e); + for (const ActionInfo& ai : it->second) { + if (!ai.filter.has_value() || params.isSubset(*ai.filter)) { + global::actionManager->triggerAction(ai.action, params); + } + } + } + + e = e->next; + } +} + +scripting::LuaLibrary EventEngine::luaLibrary() { + scripting::LuaLibrary res; + res.name = "event"; + res.functions.push_back({ + "registerEventAction", + &luascriptfunctions::registerEventAction, + {}, + "string, string [, table]", + "Registers an action (second parameter) to be executed whenever an event (first " + "parameter) is encountered. If the optional third parameter is provided, it " + "describes a filter that the event is being checked against and only if it " + "passes the filter, the action is triggered" + }); + res.functions.push_back({ + "unregisterEventAction", + &luascriptfunctions::unregisterEventAction, + {}, + "string, string [, table]", + "Unregisters a specific combination of event (first parameter), action (second " + "parameter), and potentially a filter (optional third argument)" + }); + return res; +} + +} // namespace openspace diff --git a/src/events/eventengine_lua.inl b/src/events/eventengine_lua.inl new file mode 100644 index 0000000000..0d6bef77ba --- /dev/null +++ b/src/events/eventengine_lua.inl @@ -0,0 +1,49 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +namespace openspace::luascriptfunctions { + +int registerEventAction(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, { 2, 3 }, "lua::registerEventAction"); + auto [event, action, filter] = + ghoul::lua::values>(L); + + events::Event::Type type = events::fromString(event); + + global::eventEngine->registerEventAction(type, std::move(action), std::move(filter)); + return 0; +} + +int unregisterEventAction(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, { 2, 3 }, "lua::unregisterEventAction"); + auto [event, action, filter] = + ghoul::lua::values>(L); + + events::Event::Type type = events::fromString(event); + + global::eventEngine->unregisterEventAction(type, action, filter); + return 0; +} + +} // namespace openspace::luascriptfunctions diff --git a/src/interaction/actionmanager.cpp b/src/interaction/actionmanager.cpp index 89a112b0d5..b16f79c5d6 100644 --- a/src/interaction/actionmanager.cpp +++ b/src/interaction/actionmanager.cpp @@ -86,7 +86,14 @@ void ActionManager::triggerAction(const std::string& identifier, const ghoul::Dictionary& arguments) const { ghoul_assert(!identifier.empty(), "Identifier must not be empty"); - ghoul_assert(hasAction(identifier), "Action was not found in the list"); + + if (!hasAction(identifier)) { + LWARNINGC( + "ActionManager", + fmt::format("Action '{}' not found in the list", identifier) + ); + return; + } const Action& a = action(identifier); if (arguments.isEmpty()) { @@ -97,7 +104,7 @@ void ActionManager::triggerAction(const std::string& identifier, } else { global::scriptEngine->queueScript( - fmt::format("local args = {}\n{}", ghoul::formatLua(arguments), a.command), + fmt::format("args = {}\n{}", ghoul::formatLua(arguments), a.command), scripting::ScriptEngine::RemoteScripting(a.synchronization) ); } diff --git a/src/interaction/sessionrecording.cpp b/src/interaction/sessionrecording.cpp index fe390e3648..2c4b8da045 100644 --- a/src/interaction/sessionrecording.cpp +++ b/src/interaction/sessionrecording.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -461,6 +462,9 @@ bool SessionRecording::startPlayback(std::string& filename, (_playbackForceSimTimeAtStart ? 1 : 0) )); + global::eventEngine->publishEvent( + events::EventSessionRecordingPlayback::State::Started + ); initializePlayback_triggerStart(); global::navigationHandler->orbitalNavigator().updateOnCameraInteraction(); @@ -527,12 +531,18 @@ void SessionRecording::setPlaybackPause(bool pause) { global::timeManager->setPause(true); } _state = SessionState::PlaybackPaused; + global::eventEngine->publishEvent( + events::EventSessionRecordingPlayback::State::Paused + ); } else if (!pause && _state == SessionState::PlaybackPaused) { if (!_playbackPausedWithinDeltaTimePause) { global::timeManager->setPause(false); } _state = SessionState::Playback; + global::eventEngine->publishEvent( + events::EventSessionRecordingPlayback::State::Resumed + ); } } @@ -575,7 +585,7 @@ void SessionRecording::signalPlaybackFinishedForComponent(RecordedType type) { if (!_playbackActive_camera && !_playbackActive_time && !_playbackActive_script) { if (_playbackLoopMode) { - //Loop back to the beginning to replay + // Loop back to the beginning to replay _saveRenderingDuringPlayback = false; initializePlayback_time(global::windowDelegate->applicationTime()); initializePlayback_modeFlags(); @@ -586,6 +596,9 @@ void SessionRecording::signalPlaybackFinishedForComponent(RecordedType type) { _state = SessionState::Idle; _cleanupNeeded = true; LINFO("Playback session finished"); + global::eventEngine->publishEvent( + events::EventSessionRecordingPlayback::State::Finished + ); } } } @@ -606,6 +619,9 @@ void SessionRecording::stopPlayback() { _state = SessionState::Idle; _cleanupNeeded = true; LINFO("Session playback stopped"); + global::eventEngine->publishEvent( + events::EventSessionRecordingPlayback::State::Finished + ); } } diff --git a/src/navigation/navigationhandler.cpp b/src/navigation/navigationhandler.cpp index 73d74d4c66..f3d18229d5 100644 --- a/src/navigation/navigationhandler.cpp +++ b/src/navigation/navigationhandler.cpp @@ -27,6 +27,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -175,6 +178,7 @@ void NavigationHandler::updateCamera(double deltaTime) { } else if (_pathNavigator.isPlayingPath()) { _pathNavigator.updateCamera(deltaTime); + updateCameraTransitions(); } else { if (_disableJoystickInputs) { @@ -186,6 +190,7 @@ void NavigationHandler::updateCamera(double deltaTime) { } _orbitalNavigator.updateStatesFromInput(_inputState, deltaTime); _orbitalNavigator.updateCameraStateFromStates(deltaTime); + updateCameraTransitions(); } _orbitalNavigator.updateCameraScalingFromAnchor(deltaTime); @@ -208,6 +213,118 @@ void NavigationHandler::applyNavigationState(const NavigationState& ns) { _orbitalNavigator.clearPreviousState(); } +void NavigationHandler::updateCameraTransitions() { + // This function is concerned with managing transitions of the camera between + // different distances of interest relative to the focus node. For each transition two + // scenarios are handled; SceneGraphNodes can have attached actions for each + // transition, which are automatically triggered. Additionally, an + // EventCameraTransition event is fired that contains information about the focus node + // and the transition state that caused the vent to fire. + + // Diagram of events for a camera moving from right-to-left. + // Interaction sphere is 'O' in middle, and ')' are spherical boundaries. The approach + // factor, reach factor, and interaction sphere radius are all taken from the current + // focus node. + // + // |<------------------->| Approach factor * Interaction sphere + // |<------>| Reach Factor * Interaction sphere + // + // ( ( O ) ) + // ^ ^ ^ ^ + // OnExit OnMoveAway OnReach OnApproach + const glm::dvec3 anchorPos = anchorNode()->worldPosition(); + const glm::dvec3 cameraPos = _camera->positionVec3(); + const double currDistance = glm::distance(anchorPos, cameraPos); + const double d = anchorNode()->interactionSphere(); + const double af = anchorNode()->approachFactor(); + const double rf = anchorNode()->reachFactor(); + + using namespace std::string_literals; + if (_inAnchorApproachSphere) { + if (currDistance > d * (af + InteractionHystersis)) { + // We left the approach sphere outwards + _inAnchorApproachSphere = false; + + if (!anchorNode()->onExitAction().empty()) { + ghoul::Dictionary dict; + dict.setValue("Node", anchorNode()->identifier()); + dict.setValue("Transition", "Exiting"s); + for (const std::string& action : anchorNode()->onExitAction()) { + global::actionManager->triggerAction(action, dict); + } + } + + global::eventEngine->publishEvent( + _camera, + anchorNode(), + events::EventCameraFocusTransition::Transition::Exiting + ); + } + else if (currDistance < d * (rf - InteractionHystersis)) { + // We transitioned from the approach sphere into the reach sphere + _inAnchorApproachSphere = false; + _inAnchorReachSphere = true; + + if (!anchorNode()->onReachAction().empty()) { + ghoul::Dictionary dict; + dict.setValue("Node", anchorNode()->identifier()); + dict.setValue("Transition", "Reaching"s); + for (const std::string& action : anchorNode()->onReachAction()) { + global::actionManager->triggerAction(action, dict); + } + } + + global::eventEngine->publishEvent( + _camera, + anchorNode(), + events::EventCameraFocusTransition::Transition::Reaching + ); + } + } + else if (_inAnchorReachSphere && currDistance > d * (rf + InteractionHystersis)) { + // We transitioned from the reach sphere to the approach sphere + _inAnchorReachSphere = false; + _inAnchorApproachSphere = true; + + if (!anchorNode()->onRecedeAction().empty()) { + ghoul::Dictionary dict; + dict.setValue("Node", anchorNode()->identifier()); + dict.setValue("Transition", "Receding"s); + for (const std::string& action : anchorNode()->onRecedeAction()) { + global::actionManager->triggerAction(action, dict); + } + } + + global::eventEngine->publishEvent( + _camera, + anchorNode(), + events::EventCameraFocusTransition::Transition::Receding + ); + } + else if (!_inAnchorApproachSphere && !_inAnchorReachSphere && + currDistance < d * (af - InteractionHystersis)) + { + // We moved into the approach sphere + _inAnchorApproachSphere = true; + + if (!anchorNode()->onApproachAction().empty()) { + ghoul::Dictionary dict; + dict.setValue("Node", anchorNode()->identifier()); + dict.setValue("Transition", "Approaching"s); + for (const std::string& action : anchorNode()->onApproachAction()) { + global::actionManager->triggerAction(action, dict); + } + } + + global::eventEngine->publishEvent( + _camera, + anchorNode(), + events::EventCameraFocusTransition::Transition::Approaching + ); + } +} + + void NavigationHandler::setEnableKeyFrameInteraction() { _useKeyFrameInteraction = true; } diff --git a/src/navigation/orbitalnavigator.cpp b/src/navigation/orbitalnavigator.cpp index 0be1d03c89..13623fb3e3 100644 --- a/src/navigation/orbitalnavigator.cpp +++ b/src/navigation/orbitalnavigator.cpp @@ -27,6 +27,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -353,7 +356,12 @@ OrbitalNavigator::OrbitalNavigator() } SceneGraphNode* node = sceneGraphNode(_anchor.value()); if (node) { + const SceneGraphNode* previousAnchor = _anchorNode; setAnchorNode(node); + global::eventEngine->publishEvent( + previousAnchor, + node + ); } else { LERROR(fmt::format( @@ -804,8 +812,14 @@ glm::dvec3 OrbitalNavigator::cameraToSurfaceVector(const glm::dvec3& cameraPos, void OrbitalNavigator::setFocusNode(const SceneGraphNode* focusNode, bool resetVelocitiesOnChange) { + const SceneGraphNode* previousAnchor = _anchorNode; setAnchorNode(focusNode, resetVelocitiesOnChange); setAimNode(nullptr); + + global::eventEngine->publishEvent( + previousAnchor, + focusNode + ); } void OrbitalNavigator::setFocusNode(const std::string& focusNode, bool) { @@ -953,8 +967,8 @@ bool OrbitalNavigator::shouldFollowAnchorRotation(const glm::dvec3& cameraPositi const double distanceToCamera = glm::distance(cameraPosition, _anchorNode->worldPosition()); - - return distanceToCamera < maximumDistanceForRotation; + bool shouldFollow = distanceToCamera < maximumDistanceForRotation; + return shouldFollow; } bool OrbitalNavigator::followingAnchorRotation() const { diff --git a/src/network/parallelpeer.cpp b/src/network/parallelpeer.cpp index a4f5f77f8a..b5ee89dd38 100644 --- a/src/network/parallelpeer.cpp +++ b/src/network/parallelpeer.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -215,7 +217,7 @@ void ParallelPeer::handleMessage(const ParallelConnection::Message& message) { nConnectionsMessageReceived(message.content); break; default: - //unknown message type + // unknown message type break; } } @@ -259,8 +261,7 @@ double ParallelPeer::latencyStandardDeviation() const { return std::sqrt(latencyVariance); } -void ParallelPeer::dataMessageReceived(const std::vector& message) -{ +void ParallelPeer::dataMessageReceived(const std::vector& message) { size_t offset = 0; // The type of data message received @@ -290,8 +291,10 @@ void ParallelPeer::dataMessageReceived(const std::vector& message) pose.scale = kf._scale; pose.followFocusNodeRotation = kf._followNodeRotation; - global::navigationHandler->keyframeNavigator().addKeyframe(convertedTimestamp, - pose); + global::navigationHandler->keyframeNavigator().addKeyframe( + convertedTimestamp, + pose + ); break; } case datamessagestructures::Type::TimelineData: { @@ -359,10 +362,9 @@ void ParallelPeer::dataMessageReceived(const std::vector& message) } } -void ParallelPeer::connectionStatusMessageReceived(const std::vector& message) - { +void ParallelPeer::connectionStatusMessageReceived(const std::vector& message) { if (message.size() < 2 * sizeof(uint32_t)) { - LERROR("Malformed connection status message."); + LERROR("Malformed connection status message"); return; } size_t pointer = 0; @@ -376,7 +378,7 @@ void ParallelPeer::connectionStatusMessageReceived(const std::vector& mess pointer += sizeof(uint32_t); if (hostNameSize > message.size() - pointer) { - LERROR("Malformed connection status message."); + LERROR("Malformed connection status message"); return; } @@ -387,7 +389,7 @@ void ParallelPeer::connectionStatusMessageReceived(const std::vector& mess pointer += hostNameSize; if (status > ParallelConnection::Status::Host) { - LERROR("Invalid status."); + LERROR("Invalid status"); return; } @@ -410,7 +412,7 @@ void ParallelPeer::connectionStatusMessageReceived(const std::vector& mess void ParallelPeer::nConnectionsMessageReceived(const std::vector& message) { if (message.size() < sizeof(uint32_t)) { - LERROR("Malformed host info message."); + LERROR("Malformed host info message"); return; } const uint32_t nConnections = *(reinterpret_cast(&message[0])); @@ -532,9 +534,54 @@ void ParallelPeer::preSynchronization() { void ParallelPeer::setStatus(ParallelConnection::Status status) { if (_status != status) { + ParallelConnection::Status prevStatus = _status; _status = status; _timeJumped = true; _connectionEvent->publish("statusChanged"); + + + EventEngine* ee = global::eventEngine; + const bool isConnected = + status == ParallelConnection::Status::ClientWithoutHost || + status == ParallelConnection::Status::ClientWithHost || + status == ParallelConnection::Status::Host; + const bool wasConnected = + prevStatus == ParallelConnection::Status::ClientWithoutHost || + prevStatus == ParallelConnection::Status::ClientWithHost || + prevStatus == ParallelConnection::Status::Host; + const bool isDisconnected = status == ParallelConnection::Status::Disconnected; + const bool wasDisconnected = + prevStatus == ParallelConnection::Status::Disconnected; + const bool isHost = status == ParallelConnection::Status::Host; + const bool wasHost = prevStatus == ParallelConnection::Status::Host; + const bool isClient = + status == ParallelConnection::Status::ClientWithoutHost || + status == ParallelConnection::Status::ClientWithHost; + const bool wasClient = + prevStatus == ParallelConnection::Status::ClientWithoutHost || + prevStatus == ParallelConnection::Status::ClientWithHost; + + + if (isDisconnected && wasConnected) { + ee->publishEvent( + events::EventParallelConnection::State::Lost + ); + } + if (isConnected && wasDisconnected) { + ee->publishEvent( + events::EventParallelConnection::State::Established + ); + } + if (isHost && (wasClient || wasDisconnected)) { + ee->publishEvent( + events::EventParallelConnection::State::HostshipGained + ); + } + if ((isClient || isDisconnected) && wasHost) { + ee->publishEvent( + events::EventParallelConnection::State::HostshipLost + ); + } } if (isHost()) { global::timeManager->addTimeJumpCallback([this]() { diff --git a/src/properties/propertyowner.cpp b/src/properties/propertyowner.cpp index 94c716e826..4426580f8d 100644 --- a/src/properties/propertyowner.cpp +++ b/src/properties/propertyowner.cpp @@ -24,6 +24,9 @@ #include +#include +#include +#include #include #include #include diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index d7c68c6e1b..7bdb20d222 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include #include @@ -1105,8 +1107,11 @@ void RenderEngine::addScreenSpaceRenderable(std::unique_ptrinitialize(); s->initializeGL(); - global::screenSpaceRootPropertyOwner->addPropertySubOwner(s.get()); + ScreenSpaceRenderable* ssr = s.get(); + global::screenSpaceRootPropertyOwner->addPropertySubOwner(ssr); global::screenSpaceRenderables->push_back(std::move(s)); + + global::eventEngine->publishEvent(ssr); } void RenderEngine::removeScreenSpaceRenderable(ScreenSpaceRenderable* s) { @@ -1119,9 +1124,10 @@ void RenderEngine::removeScreenSpaceRenderable(ScreenSpaceRenderable* s) { if (it != global::screenSpaceRenderables->end()) { s->deinitialize(); global::screenSpaceRootPropertyOwner->removePropertySubOwner(s); - global::screenSpaceRenderables->erase(it); } + + global::eventEngine->publishEvent(s); } void RenderEngine::removeScreenSpaceRenderable(const std::string& identifier) { @@ -1131,8 +1137,7 @@ void RenderEngine::removeScreenSpaceRenderable(const std::string& identifier) { } } -ScreenSpaceRenderable* RenderEngine::screenSpaceRenderable( - const std::string& identifier) +ScreenSpaceRenderable* RenderEngine::screenSpaceRenderable(const std::string& identifier) { const auto it = std::find_if( global::screenSpaceRenderables->begin(), diff --git a/src/scene/profile.cpp b/src/scene/profile.cpp index 938335bcdc..9ded721ecc 100644 --- a/src/scene/profile.cpp +++ b/src/scene/profile.cpp @@ -598,13 +598,9 @@ void Profile::removeAsset(const std::string& path) { } const auto it = std::find(assets.cbegin(), assets.cend(), path); - if (it == assets.end()) { - throw ghoul::RuntimeError(fmt::format( - "Tried to remove non-existing asset '{}'", path - )); + if (it != assets.end()) { + assets.erase(it); } - - assets.erase(it); } std::string Profile::serialize() const { diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index e17b48f11e..f673ecf336 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -125,6 +125,7 @@ void Scene::registerNode(SceneGraphNode* node) { _nodesByIdentifier[node->identifier()] = node; addPropertySubOwner(node); _dirtyNodeRegistry = true; + global::eventEngine->publishEvent(node); } void Scene::unregisterNode(SceneGraphNode* node) { @@ -144,6 +145,7 @@ void Scene::unregisterNode(SceneGraphNode* node) { } removePropertySubOwner(node); _dirtyNodeRegistry = true; + global::eventEngine->publishEvent(node); } void Scene::markNodeRegistryDirty() { @@ -585,6 +587,10 @@ void Scene::updateInterpolations() { i.prop->interpolateValue(t, i.easingFunction); i.isExpired = (t == 1.f); + + if (i.isExpired) { + global::eventEngine->publishEvent(i.prop); + } } _propertyInterpolationInfos.erase( diff --git a/src/scene/scene_lua.inl b/src/scene/scene_lua.inl index 96df5a47a8..c05919350a 100644 --- a/src/scene/scene_lua.inl +++ b/src/scene/scene_lua.inl @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/src/scene/scenegraphnode.cpp b/src/scene/scenegraphnode.cpp index 6ce162c4fa..d7c5efdc80 100644 --- a/src/scene/scenegraphnode.cpp +++ b/src/scene/scenegraphnode.cpp @@ -111,6 +111,20 @@ namespace { openspace::properties::Property::Visibility::Developer }; + constexpr openspace::properties::Property::PropertyInfo ApproachFactorInfo = { + "ApproachFactor", + "Approach Factor", + "This value is a multiplication factor for the interaction sphere that " + "determines when the camera is 'approaching' the scene graph node" + }; + + constexpr openspace::properties::Property::PropertyInfo ReachFactorInfo = { + "ReachFactor", + "Reach Factor", + "This value is a multiplication factor for the interaction sphere that " + "determines when the camera has 'reached' the scene graph node" + }; + constexpr openspace::properties::Property::PropertyInfo GuiPathInfo = { "GuiPath", "Gui Path", @@ -174,13 +188,10 @@ namespace { // children std::optional renderable [[codegen::reference("renderable")]]; - // A hard-coded bounding sphere to be used for the cases where the Renderable is - // not able to provide a reasonable bounding sphere or the calculated bounding - // sphere needs to be overwritten for some reason + // [[codegen::verbatim(BoundingSphereInfo.description)]] std::optional boundingSphere; - // A hard-coded radius for limiting the interaction radius, meaning the minimal - // distance that the camera can approach this scene graph node + // [[codegen::verbatim(InteractionSphereInfo.description)]] std::optional interactionSphere; struct Transform { @@ -208,6 +219,36 @@ namespace { // corresponding to a 'Translation', a 'Rotation', and a 'Scale' std::optional transform; + // This value is a multiplication factor for the interaction sphere that + // determines when the camera is 'approaching' the scene graph node. If this value + // is not specified, a default value of 5 is used instead. This value must be + // larger than the reachFactor or unexpected things might happen + std::optional approachFactor [[codegen::greaterequal(0.0)]]; + + // This value is a multiplication factor for the interaction sphere that + // determines when the camera has 'reached' the scene graph node. If this value is + // not specified, a default value of 1.25 is used instead. This value must be + // smaller than the approachFactor or unexpected things might happen + std::optional reachFactor [[codegen::greaterequal(0.0)]]; + + // One or multiple actions that are executed whenever the camera is focused on + // this scene graph node and if it enters the interaction sphere of the node + std::optional>> onApproach; + + // One or multiple actions that are executed whenever the camera is focused on + // this scene graph node and if it transitions from the approach distance to the + // reach distance of the node + std::optional>> onReach; + + // One or multiple actions that are executed whenever the camera is focused on + // this scene graph node and if it transitions from the reach distance to the + // approach distance of the node + std::optional>> onRecede; + + // One or multiple actions that are executed whenever the camera is focused on + // this scene graph node and if it exits the interaction sphere of the node + std::optional>> onExit; + // Specifies the time frame for when this node should be active std::optional timeFrame [[codegen::reference("core_time_frame")]]; @@ -287,6 +328,8 @@ ghoul::mm_unique_ptr SceneGraphNode::createFromDictionary( result->_overrideBoundingSphere = p.boundingSphere; result->_overrideInteractionSphere = p.interactionSphere; + result->_approachFactor = p.approachFactor.value_or(result->_approachFactor); + result->_reachFactor = p.reachFactor.value_or(result->_reachFactor); if (p.transform.has_value()) { if (p.transform->translation.has_value()) { @@ -378,6 +421,43 @@ ghoul::mm_unique_ptr SceneGraphNode::createFromDictionary( )); } + // Extracting the actions from the dictionary + if (p.onApproach.has_value()) { + if (std::holds_alternative(*p.onApproach)) { + result->_onApproachAction = { std::get(*p.onApproach) }; + } + else { + result->_onApproachAction = std::get>(*p.onApproach); + } + } + + if (p.onReach.has_value()) { + if (std::holds_alternative(*p.onReach)) { + result->_onReachAction = { std::get(*p.onReach) }; + } + else { + result->_onReachAction = std::get>(*p.onReach); + } + } + + if (p.onRecede.has_value()) { + if (std::holds_alternative(*p.onRecede)) { + result->_onRecedeAction = { std::get(*p.onRecede) }; + } + else { + result->_onRecedeAction = std::get>(*p.onRecede); + } + } + + if (p.onExit.has_value()) { + if (std::holds_alternative(*p.onExit)) { + result->_onExitAction = { std::get(*p.onExit) }; + } + else { + result->_onExitAction = std::get>(*p.onExit); + } + } + if (p.tag.has_value()) { if (std::holds_alternative(*p.tag)) { result->addTag(std::get(*p.tag)); @@ -425,6 +505,8 @@ SceneGraphNode::SceneGraphNode() } , _boundingSphere(BoundingSphereInfo, -1.0, -1.0, 1e12) , _interactionSphere(InteractionSphereInfo, -1.0, -1.0, 1e12) + , _approachFactor(ApproachFactorInfo, 5.0) + , _reachFactor(ReachFactorInfo, 1.25) , _computeScreenSpaceValues(ComputeScreenSpaceInfo, false) , _screenSpacePosition(ScreenSpacePositionInfo, glm::ivec2(-1, -1)) , _screenVisibility(ScreenVisibilityInfo, false) @@ -463,6 +545,8 @@ SceneGraphNode::SceneGraphNode() // negative values //_interactionSphere.setExponent(10.f); addProperty(_interactionSphere); + addProperty(_reachFactor); + addProperty(_approachFactor); addProperty(_showDebugSphere); } @@ -1066,6 +1150,22 @@ std::vector SceneGraphNode::children() const { return nodes; } +const std::vector& SceneGraphNode::onApproachAction() const { + return _onApproachAction; +} + +const std::vector& SceneGraphNode::onReachAction() const { + return _onReachAction; +} + +const std::vector& SceneGraphNode::onRecedeAction() const { + return _onRecedeAction; +} + +const std::vector& SceneGraphNode::onExitAction() const { + return _onExitAction; +} + double SceneGraphNode::boundingSphere() const { if (_overrideBoundingSphere.has_value()) { return glm::compMax(scale() * *_overrideBoundingSphere); @@ -1092,6 +1192,14 @@ double SceneGraphNode::interactionSphere() const { } } +double SceneGraphNode::reachFactor() const { + return _reachFactor; +} + +double SceneGraphNode::approachFactor() const { + return _approachFactor; +} + const Renderable* SceneGraphNode::renderable() const { return _renderable.get(); } diff --git a/src/util/tstring.cpp b/src/util/tstring.cpp new file mode 100644 index 0000000000..15df8237fd --- /dev/null +++ b/src/util/tstring.cpp @@ -0,0 +1,51 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include + +namespace openspace { + +tstring temporaryString(const std::string& str) { + void* ptr = global::memoryManager->TemporaryMemory.do_allocate(str.size(), 8); + std::strcpy(reinterpret_cast(ptr), str.data()); + return tstring(reinterpret_cast(ptr), str.size()); +} + +tstring temporaryString(std::string_view str) { + void* ptr = global::memoryManager->TemporaryMemory.do_allocate(str.size(), 8); + std::strcpy(reinterpret_cast(ptr), str.data()); + return tstring(reinterpret_cast(ptr), str.size()); +} + +tstring temporaryString(const char str[]) { + size_t size = strlen(str); + void* ptr = global::memoryManager->TemporaryMemory.do_allocate(size, 8); + std::strcpy(reinterpret_cast(ptr), str); + return tstring(reinterpret_cast(ptr), size); +} + +} // namespace openspace