From 37e0a8cd931436d879c208ab620113c17238eaa9 Mon Sep 17 00:00:00 2001 From: Brad Phelan Date: Fri, 1 Mar 2024 15:50:29 +0100 Subject: [PATCH 01/17] Used a lambda based events --- include/threepp/core/EventDispatcher.hpp | 25 +++--- src/threepp/core/EventDispatcher.cpp | 48 ++++++----- src/threepp/renderers/GLRenderer.cpp | 26 +----- src/threepp/renderers/gl/GLGeometries.cpp | 66 ++++++--------- src/threepp/renderers/gl/GLObjects.cpp | 38 ++++----- src/threepp/renderers/gl/GLTextures.cpp | 36 +++------ src/threepp/renderers/gl/GLTextures.hpp | 21 ----- tests/core/EventDispatcher_test.cpp | 97 +++++++++-------------- 8 files changed, 131 insertions(+), 226 deletions(-) diff --git a/include/threepp/core/EventDispatcher.hpp b/include/threepp/core/EventDispatcher.hpp index ab457b0ba..d5618c04e 100644 --- a/include/threepp/core/EventDispatcher.hpp +++ b/include/threepp/core/EventDispatcher.hpp @@ -7,38 +7,41 @@ #include #include #include +#include namespace threepp { struct Event { - const std::string type; void* target; + bool unsubscribe = false; }; - struct EventListener { - - virtual void onEvent(Event& event) = 0; - - virtual ~EventListener() = default; - }; + using EventListener = std::function; + using Subscription = std::shared_ptr; class EventDispatcher { public: - void addEventListener(const std::string& type, EventListener* listener); + /// Adds an event listener and returns a subscription + [[nodiscard]] + Subscription addEventListener(const std::string& type, EventListener listener); - bool hasEventListener(const std::string& type, const EventListener* listener); + /// Adds an event listener and assumes event.unsubscribe = true will be + /// used to unsubscribe. Useful for one shot events + void addEventListenerOwned(const std::string& type, EventListener listener); - void removeEventListener(const std::string& type, const EventListener* listener); + /// Adds an event listener and unsubscribes after a single shot + void addEventListenerOneShot(const std::string& type, EventListener listener); void dispatchEvent(const std::string& type, void* target = nullptr); + virtual ~EventDispatcher() = default; private: - std::unordered_map> listeners_; + std::unordered_map> listeners_; }; }// namespace threepp diff --git a/src/threepp/core/EventDispatcher.cpp b/src/threepp/core/EventDispatcher.cpp index 3b3f3ee03..b1db97127 100644 --- a/src/threepp/core/EventDispatcher.cpp +++ b/src/threepp/core/EventDispatcher.cpp @@ -2,47 +2,45 @@ #include "threepp/core/EventDispatcher.hpp" #include +#include using namespace threepp; -void EventDispatcher::addEventListener(const std::string& type, EventListener* listener) { +static std::atomic id = 0; - listeners_[type].emplace_back(listener); +threepp::Subscription EventDispatcher::addEventListener(const std::string& type, EventListener listener) { + size_t current_id = id.load(); + listeners_[type].insert({current_id, listener}); + Subscription disposer((void*)nullptr, [this, type, current_id](void*) {listeners_[type].erase(current_id); }); + id=id+1; + return disposer; } -bool EventDispatcher::hasEventListener(const std::string& type, const EventListener* listener) { - - if (!listeners_.count(type)) return false; - - auto& listenerArray = listeners_.at(type); - return std::find(listenerArray.begin(), listenerArray.end(), listener) != listenerArray.end(); +void EventDispatcher::addEventListenerOwned(const std::string& type, EventListener listener) { + size_t current_id = id.load(); + listeners_[type].insert({current_id, listener}); + id=id+1; } -void EventDispatcher::removeEventListener(const std::string& type, const EventListener* listener) { - - if (!listeners_.count(type)) return; - - auto& listenerArray = listeners_.at(type); - if (listenerArray.empty()) return; - - auto find = std::find(listenerArray.begin(), listenerArray.end(), listener); - if (find != listenerArray.end()) { - listenerArray.erase(find); - } +void EventDispatcher::addEventListenerOneShot(const std::string& type, EventListener listener) { + this->addEventListenerOwned(type, [l=std::move(listener)](Event& e) { l(e); e.unsubscribe = true; }); } void EventDispatcher::dispatchEvent(const std::string& type, void* target) { if (listeners_.count(type)) { - Event e{type, target}; - - auto listenersOfType = listeners_.at(type);//copy - for (auto l : listenersOfType) { - if (l) { - l->onEvent(e); + std::unordered_map & listenersOfType = listeners_.at(type);//copy + std::vector toUnsubscribe; + for (auto const & item : listenersOfType) { + item.second(e); + if (e.unsubscribe) { + e.unsubscribe = false; + toUnsubscribe.push_back(item.first); } } + for (size_t id : toUnsubscribe) + listenersOfType.erase(id); } } diff --git a/src/threepp/renderers/GLRenderer.cpp b/src/threepp/renderers/GLRenderer.cpp index 16cc4349a..4ff8fa523 100644 --- a/src/threepp/renderers/GLRenderer.cpp +++ b/src/threepp/renderers/GLRenderer.cpp @@ -47,22 +47,6 @@ using namespace threepp; struct GLRenderer::Impl { - struct OnMaterialDispose: EventListener { - - explicit OnMaterialDispose(GLRenderer::Impl* scope): scope_(scope) {} - - void onEvent(Event& event) override { - - auto material = static_cast(event.target); - - material->removeEventListener("dispose", this); - - scope_->deallocateMaterial(material); - } - - private: - GLRenderer::Impl* scope_; - }; GLRenderer& scope; @@ -71,7 +55,6 @@ struct GLRenderer::Impl { std::unique_ptr _emptyScene; - OnMaterialDispose onMaterialDispose; gl::GLRenderList* currentRenderList = nullptr; gl::GLRenderState* currentRenderState = nullptr; @@ -154,8 +137,7 @@ struct GLRenderer::Impl { background(scope, cubemaps, state, objects, parameters.premultipliedAlpha), programCache(bindingStates, clipping), _currentDrawBuffers(GL_BACK), - _emptyScene(std::make_unique()), - onMaterialDispose(this) { + _emptyScene(std::make_unique()) { this->setViewport(0, 0, size.width, size.height); this->setScissor(0, 0, _size.width, _size.height); @@ -606,10 +588,10 @@ struct GLRenderer::Impl { } if (programs.empty()) { - // new material - - material->addEventListener("dispose", &onMaterialDispose); + material->addEventListenerOneShot("dispose", [this](Event& event) { + this->deallocateMaterial(static_cast(event.target)); + }); } gl::GLProgram* program = nullptr; diff --git a/src/threepp/renderers/gl/GLGeometries.cpp b/src/threepp/renderers/gl/GLGeometries.cpp index cbe62ff7b..1a4a64327 100644 --- a/src/threepp/renderers/gl/GLGeometries.cpp +++ b/src/threepp/renderers/gl/GLGeometries.cpp @@ -20,71 +20,57 @@ using namespace threepp::gl; struct GLGeometries::Impl { - struct OnGeometryDispose: EventListener { - explicit OnGeometryDispose(GLGeometries::Impl* scope) - : scope_(scope) {} + GLInfo& info_; + GLAttributes& attributes_; + GLBindingStates& bindingStates_; - void onEvent(Event& event) override { + std::unordered_map geometries_; + std::unordered_map> wireframeAttributes_; + + Impl(GLAttributes& attributes, GLInfo& info, GLBindingStates& bindingStates) + : info_(info), + attributes_(attributes), + bindingStates_(bindingStates) {} + + void get(Object3D* object, BufferGeometry* geometry) { + + if (geometries_.count(geometry) && geometries_.at(geometry)) return; + + geometry->addEventListenerOneShot("dispose", [this](Event& event) { auto geometry = static_cast(event.target); if (geometry->hasIndex()) { - scope_->attributes_.remove(geometry->getIndex()); + this->attributes_.remove(geometry->getIndex()); } for (const auto& [name, value] : geometry->getAttributes()) { - scope_->attributes_.remove(value.get()); + this->attributes_.remove(value.get()); } - geometry->removeEventListener("dispose", this); - scope_->geometries_.erase(geometry); + this->geometries_.erase(geometry); - if (scope_->wireframeAttributes_.count(geometry)) { + if (this->wireframeAttributes_.count(geometry)) { - const auto& attribute = scope_->wireframeAttributes_.at(geometry); + const auto& attribute = this->wireframeAttributes_.at(geometry); - scope_->attributes_.remove(attribute.get()); - scope_->wireframeAttributes_.erase(geometry); + this->attributes_.remove(attribute.get()); + this->wireframeAttributes_.erase(geometry); } - scope_->bindingStates_.releaseStatesOfGeometry(geometry); + this->bindingStates_.releaseStatesOfGeometry(geometry); if (auto ig = dynamic_cast(geometry)) { ig->_maxInstanceCount = 0; } - --scope_->info_.memory.geometries; - } - - private: - GLGeometries::Impl* scope_; - }; - - GLInfo& info_; - GLAttributes& attributes_; - GLBindingStates& bindingStates_; - - OnGeometryDispose onGeometryDispose_; - - std::unordered_map geometries_; - std::unordered_map> wireframeAttributes_; - - Impl(GLAttributes& attributes, GLInfo& info, GLBindingStates& bindingStates) - : info_(info), - attributes_(attributes), - bindingStates_(bindingStates), - onGeometryDispose_(this) {} - - void get(Object3D* object, BufferGeometry* geometry) { - - if (geometries_.count(geometry) && geometries_.at(geometry)) return; - - geometry->addEventListener("dispose", &onGeometryDispose_); + --this->info_.memory.geometries; + }); geometries_[geometry] = true; diff --git a/src/threepp/renderers/gl/GLObjects.cpp b/src/threepp/renderers/gl/GLObjects.cpp index 8b4f1fb00..266d7326f 100644 --- a/src/threepp/renderers/gl/GLObjects.cpp +++ b/src/threepp/renderers/gl/GLObjects.cpp @@ -18,36 +18,18 @@ using namespace threepp::gl; struct GLObjects::Impl { - struct OnInstancedMeshDispose: public EventListener { - - explicit OnInstancedMeshDispose(GLObjects::Impl* scope): scope(scope) {} - - void onEvent(Event& event) override { - auto instancedMesh = static_cast(event.target); - - instancedMesh->removeEventListener("dispose", this); - - scope->attributes_.remove(instancedMesh->instanceMatrix.get()); - - if (instancedMesh->instanceColor) scope->attributes_.remove(instancedMesh->instanceColor.get()); - } - - private: - GLObjects::Impl* scope; - }; - GLInfo& info_; GLGeometries& geometries_; GLAttributes& attributes_; - OnInstancedMeshDispose onInstancedMeshDispose; - std::unordered_map updateMap_; + /// Track subscriptions to instanceMeshDispose + std::unordered_map instanceMeshDisposeSubscriptions_; + Impl(GLGeometries& geometries, GLAttributes& attributes, GLInfo& info) : attributes_(attributes), - geometries_(geometries), info_(info), - onInstancedMeshDispose(this) {} + geometries_(geometries), info_(info) {} BufferGeometry* update(Object3D* object) { @@ -67,9 +49,17 @@ struct GLObjects::Impl { if (auto instancedMesh = object->as()) { - if (!object->hasEventListener("dispose", &onInstancedMeshDispose)) { + if (instanceMeshDisposeSubscriptions_.find(object) == instanceMeshDisposeSubscriptions_.end()) { + + auto onInstanceMeshDispose = [this,object](Event& event) { + auto instancedMesh = static_cast(event.target); + this->attributes_.remove(instancedMesh->instanceMatrix.get()); + if (instancedMesh->instanceColor) this->attributes_.remove(instancedMesh->instanceColor.get()); + // Remove our subscription + this->instanceMeshDisposeSubscriptions_.erase(object); + }; - object->addEventListener("dispose", &onInstancedMeshDispose); + instanceMeshDisposeSubscriptions_.insert({object, object->addEventListener("dispose", onInstanceMeshDispose)}); } attributes_.update(instancedMesh->instanceMatrix.get(), GL_ARRAY_BUFFER); diff --git a/src/threepp/renderers/gl/GLTextures.cpp b/src/threepp/renderers/gl/GLTextures.cpp index 0423e8707..256b601a2 100644 --- a/src/threepp/renderers/gl/GLTextures.cpp +++ b/src/threepp/renderers/gl/GLTextures.cpp @@ -86,9 +86,9 @@ gl::GLTextures::GLTextures(gl::GLState& state, gl::GLProperties& properties, gl: maxTextures(GLCapabilities::instance().maxTextures), maxCubemapSize(GLCapabilities::instance().maxCubemapSize), maxTextureSize(GLCapabilities::instance().maxTextureSize), - maxSamples(GLCapabilities::instance().maxSamples), - onTextureDispose_(this), - onRenderTargetDispose_(this) {} + maxSamples(GLCapabilities::instance().maxSamples) +{ +} void gl::GLTextures::generateMipmap(GLuint target, const Texture& texture, GLuint width, GLuint height) { @@ -206,7 +206,12 @@ void gl::GLTextures::initTexture(TextureProperties* textureProperties, Texture& textureProperties->glInit = true; - texture.addEventListener("dispose", &onTextureDispose_); + texture.addEventListenerOwned("dispose", [this](Event & event) { + auto texture = static_cast(event.target); + this->deallocateTexture(texture); + --this->info->memory.textures; + event.unsubscribe = true; + }); GLuint glTexture; glGenTextures(1, &glTexture); @@ -501,7 +506,10 @@ void gl::GLTextures::setupRenderTarget(GLRenderTarget* renderTarget) { auto renderTargetProperties = properties->renderTargetProperties.get(renderTarget->uuid); auto textureProperties = properties->textureProperties.get(texture->uuid); - renderTarget->addEventListener("dispose", &onRenderTargetDispose_); + renderTarget->addEventListenerOneShot("dispose", [this](Event& event) { + auto renderTarget = static_cast(event.target); + this->deallocateRenderTarget(renderTarget); + }); GLuint glTexture; glGenTextures(1, &glTexture); @@ -570,22 +578,4 @@ std::optional gl::GLTextures::getGlTexture(const Texture& texture) return textureProperties->glTexture; } -void gl::GLTextures::TextureEventListener::onEvent(Event& event) { - - auto texture = static_cast(event.target); - - texture->removeEventListener("dispose", this); - - scope_->deallocateTexture(texture); - - --scope_->info->memory.textures; -} - -void gl::GLTextures::RenderTargetEventListener::onEvent(Event& event) { - - auto renderTarget = static_cast(event.target); - renderTarget->removeEventListener("dispose", this); - - scope_->deallocateRenderTarget(renderTarget); -} diff --git a/src/threepp/renderers/gl/GLTextures.hpp b/src/threepp/renderers/gl/GLTextures.hpp index 78705c5c0..b61143af6 100644 --- a/src/threepp/renderers/gl/GLTextures.hpp +++ b/src/threepp/renderers/gl/GLTextures.hpp @@ -70,33 +70,12 @@ namespace threepp::gl { [[nodiscard]] std::optional getGlTexture(const Texture& texture) const; private: - struct TextureEventListener: EventListener { - explicit TextureEventListener(GLTextures* scope): scope_(scope) {} - - void onEvent(Event& event) override; - - private: - GLTextures* scope_; - }; - - struct RenderTargetEventListener: EventListener { - - explicit RenderTargetEventListener(GLTextures* scope): scope_(scope) {} - - void onEvent(Event& event) override; - - private: - GLTextures* scope_; - }; GLInfo* info; GLState* state; GLProperties* properties; - TextureEventListener onTextureDispose_; - RenderTargetEventListener onRenderTargetDispose_; - int textureUnits = 0; }; diff --git a/tests/core/EventDispatcher_test.cpp b/tests/core/EventDispatcher_test.cpp index 82750fbb2..94c38b4e9 100644 --- a/tests/core/EventDispatcher_test.cpp +++ b/tests/core/EventDispatcher_test.cpp @@ -6,75 +6,52 @@ using namespace threepp; -namespace { - struct LambdaEventListener: EventListener { - - explicit LambdaEventListener(std::function f): f_(std::move(f)) {} - - void onEvent(Event& event) override { - f_(event); - } - - private: - std::function f_; - }; - - - struct MyEventListener: EventListener { - - int numCalled = 0; - - void onEvent(Event& e) override { - ++numCalled; - } - }; - - struct OnMaterialDispose: EventListener { - - void onEvent(Event& event) override { - auto* material = static_cast(event.target); - material->removeEventListener("dispose", this); - } - }; - -}// namespace - -TEST_CASE("Test events") { +TEST_CASE("Test addEventListener") { EventDispatcher evt; - MyEventListener l; - - bool l1Called = false; - LambdaEventListener l1([&l1Called](Event& e) { - l1Called = true; - }); - - evt.addEventListener("test1", &l); - evt.addEventListener("test2", &l1); - - evt.dispatchEvent("test1"); - evt.dispatchEvent("test1"); + int nCalls = 0; + { + auto s0 = evt.addEventListener("foo", [&](Event& e) {nCalls++; }); - REQUIRE(2 == l.numCalled); + REQUIRE(nCalls == 0); + evt.dispatchEvent("foo"); + REQUIRE(nCalls == 1); + evt.dispatchEvent("foo"); + REQUIRE(nCalls == 2); + } + evt.dispatchEvent("foo"); + REQUIRE(nCalls == 2); - evt.removeEventListener("test1", &l); +} - evt.dispatchEvent("test1"); - evt.dispatchEvent("test2"); +TEST_CASE("Test addEventListenerOneOwned") { - REQUIRE(2 == l.numCalled); - REQUIRE(l1Called); + EventDispatcher evt; - REQUIRE(!evt.hasEventListener("test1", &l)); - REQUIRE(evt.hasEventListener("test2", &l1)); + int nCalls = 0; + evt.addEventListenerOwned("foo", [&](Event& e) { + nCalls++; + if (nCalls == 2) { e.unsubscribe = true; } }); + + REQUIRE(nCalls == 0); + evt.dispatchEvent("foo"); + REQUIRE(nCalls == 1); + evt.dispatchEvent("foo"); + REQUIRE(nCalls == 2); + evt.dispatchEvent("foo"); + REQUIRE(nCalls == 2); +} +TEST_CASE("Test addEventListenerOneShot") { - OnMaterialDispose onDispose; - auto material = MeshBasicMaterial::create(); - material->addEventListener("dispose", &onDispose); + EventDispatcher evt; - REQUIRE(material->hasEventListener("dispose", &onDispose)); - material->dispose(); - REQUIRE(!material->hasEventListener("dispose", &onDispose)); + int nCalls = 0; + evt.addEventListenerOneShot("foo", [&](Event& e) {nCalls++; }); + REQUIRE(nCalls == 0); + evt.dispatchEvent("foo"); + REQUIRE(nCalls == 1); + evt.dispatchEvent("foo"); + REQUIRE(nCalls == 1); } From 4797df24fee59b16124f4e43aa6c874622db2ce4 Mon Sep 17 00:00:00 2001 From: Brad Phelan Date: Fri, 1 Mar 2024 17:31:17 +0100 Subject: [PATCH 02/17] Create individual dispatcher for each event --- include/threepp/core/BufferGeometry.hpp | 6 +- include/threepp/core/EventDispatcher.hpp | 64 +++++++++++++++----- include/threepp/core/Object3D.hpp | 9 ++- include/threepp/materials/Material.hpp | 5 +- include/threepp/renderers/GLRenderTarget.hpp | 6 +- include/threepp/textures/Texture.hpp | 6 +- src/threepp/core/BufferGeometry.cpp | 2 +- src/threepp/core/EventDispatcher.cpp | 42 ------------- src/threepp/core/Object3D.cpp | 6 +- src/threepp/materials/Material.cpp | 2 +- src/threepp/objects/InstancedMesh.cpp | 2 +- src/threepp/renderers/GLRenderTarget.cpp | 2 +- src/threepp/renderers/GLRenderer.cpp | 2 +- src/threepp/renderers/gl/GLGeometries.cpp | 2 +- src/threepp/renderers/gl/GLObjects.cpp | 2 +- src/threepp/renderers/gl/GLTextures.cpp | 4 +- src/threepp/textures/Texture.cpp | 2 +- tests/core/EventDispatcher_test.cpp | 22 +++---- 18 files changed, 94 insertions(+), 92 deletions(-) diff --git a/include/threepp/core/BufferGeometry.hpp b/include/threepp/core/BufferGeometry.hpp index 8a4239b64..6787559d5 100644 --- a/include/threepp/core/BufferGeometry.hpp +++ b/include/threepp/core/BufferGeometry.hpp @@ -15,9 +15,11 @@ namespace threepp { - class BufferGeometry: public EventDispatcher { + class BufferGeometry { public: + EventDispatcher OnDispose; + const unsigned int id{++_id}; const std::string uuid; @@ -128,7 +130,7 @@ namespace threepp { [[nodiscard]] std::shared_ptr clone() const; - ~BufferGeometry() override; + ~BufferGeometry(); static std::shared_ptr create(); diff --git a/include/threepp/core/EventDispatcher.hpp b/include/threepp/core/EventDispatcher.hpp index d5618c04e..04c0d691a 100644 --- a/include/threepp/core/EventDispatcher.hpp +++ b/include/threepp/core/EventDispatcher.hpp @@ -8,40 +8,72 @@ #include #include #include +#include +#include namespace threepp { - struct Event { - const std::string type; - void* target; - bool unsubscribe = false; - }; - using EventListener = std::function; - using Subscription = std::shared_ptr; + template + using TEventListener = std::function; - class EventDispatcher { + using Subscription = std::shared_ptr; + template + class TEventDispatcher { public: + using EventListener = TEventListener; + /// Adds an event listener and returns a subscription - [[nodiscard]] - Subscription addEventListener(const std::string& type, EventListener listener); + [[nodiscard]] Subscription addEventListener(EventListener listener) { + size_t current_id = id_.load(); + listeners_.insert({current_id, listener}); + Subscription disposer((void*) nullptr, [this, current_id](void*) { listeners_.erase(current_id); }); + id_ = id_ + 1; + return disposer; + } /// Adds an event listener and assumes event.unsubscribe = true will be /// used to unsubscribe. Useful for one shot events - void addEventListenerOwned(const std::string& type, EventListener listener); + void addEventListenerOwned( EventListener listener) { + size_t current_id = id_.load(); + listeners_.insert({current_id, listener}); + id_ = id_ + 1; + } /// Adds an event listener and unsubscribes after a single shot - void addEventListenerOneShot(const std::string& type, EventListener listener); + void addEventListenerOneShot( EventListener listener) { + this->addEventListenerOwned([l = std::move(listener)](TEvent& e) { l(e); e.unsubscribe = true; }); + } + + void dispatchEvent(TEvent & e){ + std::vector toUnsubscribe; + for (auto const& item : listeners_) { + item.second(e); + if (e.unsubscribe) { + e.unsubscribe = false; + toUnsubscribe.push_back(item.first); + } + } + for (size_t id : toUnsubscribe) + listeners_.erase(id); + } + + virtual ~TEventDispatcher() = default; - void dispatchEvent(const std::string& type, void* target = nullptr); + private: + std::unordered_map listeners_; + std::atomic id_ = 0; + }; - virtual ~EventDispatcher() = default; + struct Event { + void* target; + bool unsubscribe = false; + }; + class EventDispatcher : public TEventDispatcher { - private: - std::unordered_map> listeners_; }; }// namespace threepp diff --git a/include/threepp/core/Object3D.hpp b/include/threepp/core/Object3D.hpp index be6fde0e2..8d9b5538a 100644 --- a/include/threepp/core/Object3D.hpp +++ b/include/threepp/core/Object3D.hpp @@ -30,7 +30,7 @@ namespace threepp { // This is the base class for most objects in three.js and provides a set of properties and methods for manipulating objects in 3D space. //Note that this can be used for grouping objects via the .add( object ) method which adds the object as a child, however it is better to use Group for this. - class Object3D: public EventDispatcher { + class Object3D { public: inline static Vector3 defaultUp{0, 1, 0}; @@ -254,7 +254,12 @@ namespace threepp { virtual std::shared_ptr clone(bool recursive = true); - ~Object3D() override; + EventDispatcher OnAdded; + EventDispatcher OnRemove; + EventDispatcher OnDispose; + + virtual ~Object3D() ; + private: inline static unsigned int _object3Did{0}; diff --git a/include/threepp/materials/Material.hpp b/include/threepp/materials/Material.hpp index 7e8250fd9..d67a4830a 100644 --- a/include/threepp/materials/Material.hpp +++ b/include/threepp/materials/Material.hpp @@ -15,7 +15,7 @@ namespace threepp { typedef std::variant> MaterialValue; - class Material: public EventDispatcher, public std::enable_shared_from_this { + class Material: public std::enable_shared_from_this { public: const unsigned int id = materialId++; @@ -105,8 +105,9 @@ namespace threepp { virtual std::shared_ptr clone() const { return nullptr; }; - ~Material() override; + ~Material() ; + EventDispatcher OnDispose; protected: Material(); diff --git a/include/threepp/renderers/GLRenderTarget.hpp b/include/threepp/renderers/GLRenderTarget.hpp index c2d63960d..e815635e8 100644 --- a/include/threepp/renderers/GLRenderTarget.hpp +++ b/include/threepp/renderers/GLRenderTarget.hpp @@ -15,7 +15,7 @@ namespace threepp { class DepthTexture; - class GLRenderTarget: public EventDispatcher { + class GLRenderTarget { public: struct Options { @@ -67,7 +67,9 @@ namespace threepp { static std::unique_ptr create(unsigned int width, unsigned int height, const Options& options); - ~GLRenderTarget() override; + EventDispatcher OnDispose; + + ~GLRenderTarget() ; protected: bool disposed = false; diff --git a/include/threepp/textures/Texture.hpp b/include/threepp/textures/Texture.hpp index c21e7c0ca..0a3112d3b 100644 --- a/include/threepp/textures/Texture.hpp +++ b/include/threepp/textures/Texture.hpp @@ -19,7 +19,7 @@ namespace threepp { - class Texture: public EventDispatcher { + class Texture { public: inline static Mapping DEFAULT_MAPPING = Mapping::UV; @@ -84,7 +84,7 @@ namespace threepp { [[nodiscard]] std::shared_ptr clone() const; - ~Texture() override; + virtual ~Texture() ; static std::shared_ptr create(); @@ -92,6 +92,8 @@ namespace threepp { static std::shared_ptr create(std::vector image); + EventDispatcher OnDispose; + protected: explicit Texture(std::vector image); diff --git a/src/threepp/core/BufferGeometry.cpp b/src/threepp/core/BufferGeometry.cpp index 6cc84b3bd..c06b77d03 100644 --- a/src/threepp/core/BufferGeometry.cpp +++ b/src/threepp/core/BufferGeometry.cpp @@ -672,7 +672,7 @@ void BufferGeometry::dispose() { if (!disposed_) { disposed_ = true; - this->dispatchEvent("dispose", this); + this->OnDispose.dispatchEvent(Event{ this }); } } diff --git a/src/threepp/core/EventDispatcher.cpp b/src/threepp/core/EventDispatcher.cpp index b1db97127..2fc20e084 100644 --- a/src/threepp/core/EventDispatcher.cpp +++ b/src/threepp/core/EventDispatcher.cpp @@ -1,46 +1,4 @@ #include "threepp/core/EventDispatcher.hpp" -#include -#include -using namespace threepp; - - -static std::atomic id = 0; - -threepp::Subscription EventDispatcher::addEventListener(const std::string& type, EventListener listener) { - size_t current_id = id.load(); - listeners_[type].insert({current_id, listener}); - Subscription disposer((void*)nullptr, [this, type, current_id](void*) {listeners_[type].erase(current_id); }); - id=id+1; - return disposer; -} - -void EventDispatcher::addEventListenerOwned(const std::string& type, EventListener listener) { - size_t current_id = id.load(); - listeners_[type].insert({current_id, listener}); - id=id+1; -} - -void EventDispatcher::addEventListenerOneShot(const std::string& type, EventListener listener) { - this->addEventListenerOwned(type, [l=std::move(listener)](Event& e) { l(e); e.unsubscribe = true; }); -} - -void EventDispatcher::dispatchEvent(const std::string& type, void* target) { - - if (listeners_.count(type)) { - Event e{type, target}; - std::unordered_map & listenersOfType = listeners_.at(type);//copy - std::vector toUnsubscribe; - for (auto const & item : listenersOfType) { - item.second(e); - if (e.unsubscribe) { - e.unsubscribe = false; - toUnsubscribe.push_back(item.first); - } - } - for (size_t id : toUnsubscribe) - listenersOfType.erase(id); - } -} diff --git a/src/threepp/core/Object3D.cpp b/src/threepp/core/Object3D.cpp index e9e7d0477..255717272 100644 --- a/src/threepp/core/Object3D.cpp +++ b/src/threepp/core/Object3D.cpp @@ -217,7 +217,7 @@ void Object3D::add(Object3D& object) { object.parent = this; this->children.emplace_back(&object); - object.dispatchEvent("added"); + object.OnAdded.dispatchEvent(Event{ this }); } void Object3D::remove(Object3D& object) { @@ -231,7 +231,7 @@ void Object3D::remove(Object3D& object) { children.erase(find); child->parent = nullptr; - child->dispatchEvent("remove", child); + child->OnRemove.dispatchEvent(Event{child}); } } {// owning @@ -259,7 +259,7 @@ void Object3D::clear() { object->parent = nullptr; - object->dispatchEvent("remove"); + object->OnRemove.dispatchEvent(Event{ object }); } this->children.clear(); diff --git a/src/threepp/materials/Material.cpp b/src/threepp/materials/Material.cpp index 758b96b4b..c2b2f7379 100644 --- a/src/threepp/materials/Material.cpp +++ b/src/threepp/materials/Material.cpp @@ -19,7 +19,7 @@ std::string Material::uuid() const { void Material::dispose() { if (!disposed_) { disposed_ = true; - dispatchEvent("dispose", this); + OnDispose.dispatchEvent(Event{this}); } } diff --git a/src/threepp/objects/InstancedMesh.cpp b/src/threepp/objects/InstancedMesh.cpp index 96aa7ee43..b8a32ea48 100644 --- a/src/threepp/objects/InstancedMesh.cpp +++ b/src/threepp/objects/InstancedMesh.cpp @@ -77,7 +77,7 @@ void InstancedMesh::dispose() { if (!disposed) { disposed = true; - dispatchEvent("dispose", this); + OnDispose.dispatchEvent(Event{ this }); } } diff --git a/src/threepp/renderers/GLRenderTarget.cpp b/src/threepp/renderers/GLRenderTarget.cpp index 620e92d38..31957dd18 100644 --- a/src/threepp/renderers/GLRenderTarget.cpp +++ b/src/threepp/renderers/GLRenderTarget.cpp @@ -72,7 +72,7 @@ void GLRenderTarget::dispose() { if (!disposed) { disposed = true; - this->dispatchEvent("dispose", this); + this->OnDispose.dispatchEvent(Event{ this }); } } diff --git a/src/threepp/renderers/GLRenderer.cpp b/src/threepp/renderers/GLRenderer.cpp index 54091df0b..a8ab0e99e 100644 --- a/src/threepp/renderers/GLRenderer.cpp +++ b/src/threepp/renderers/GLRenderer.cpp @@ -589,7 +589,7 @@ struct GLRenderer::Impl { if (programs.empty()) { // new material - material->addEventListenerOneShot("dispose", [this](Event& event) { + material->OnDispose.addEventListenerOneShot( [this](Event& event) { this->deallocateMaterial(static_cast(event.target)); }); } diff --git a/src/threepp/renderers/gl/GLGeometries.cpp b/src/threepp/renderers/gl/GLGeometries.cpp index 1a4a64327..fb31f0bc4 100644 --- a/src/threepp/renderers/gl/GLGeometries.cpp +++ b/src/threepp/renderers/gl/GLGeometries.cpp @@ -38,7 +38,7 @@ struct GLGeometries::Impl { if (geometries_.count(geometry) && geometries_.at(geometry)) return; - geometry->addEventListenerOneShot("dispose", [this](Event& event) { + geometry->OnDispose.addEventListenerOneShot([this](Event& event) { auto geometry = static_cast(event.target); if (geometry->hasIndex()) { diff --git a/src/threepp/renderers/gl/GLObjects.cpp b/src/threepp/renderers/gl/GLObjects.cpp index 6c37e38d5..04a5dd489 100644 --- a/src/threepp/renderers/gl/GLObjects.cpp +++ b/src/threepp/renderers/gl/GLObjects.cpp @@ -59,7 +59,7 @@ struct GLObjects::Impl { this->instanceMeshDisposeSubscriptions_.erase(object); }; - instanceMeshDisposeSubscriptions_.insert({object, object->addEventListener("dispose", onInstanceMeshDispose)}); + instanceMeshDisposeSubscriptions_.insert({object, object->OnDispose.addEventListener( onInstanceMeshDispose)}); } attributes_.update(instancedMesh->instanceMatrix(), GL_ARRAY_BUFFER); diff --git a/src/threepp/renderers/gl/GLTextures.cpp b/src/threepp/renderers/gl/GLTextures.cpp index 256b601a2..ca8c9b63d 100644 --- a/src/threepp/renderers/gl/GLTextures.cpp +++ b/src/threepp/renderers/gl/GLTextures.cpp @@ -206,7 +206,7 @@ void gl::GLTextures::initTexture(TextureProperties* textureProperties, Texture& textureProperties->glInit = true; - texture.addEventListenerOwned("dispose", [this](Event & event) { + texture.OnDispose.addEventListenerOwned([this](Event & event) { auto texture = static_cast(event.target); this->deallocateTexture(texture); --this->info->memory.textures; @@ -506,7 +506,7 @@ void gl::GLTextures::setupRenderTarget(GLRenderTarget* renderTarget) { auto renderTargetProperties = properties->renderTargetProperties.get(renderTarget->uuid); auto textureProperties = properties->textureProperties.get(texture->uuid); - renderTarget->addEventListenerOneShot("dispose", [this](Event& event) { + renderTarget->OnDispose.addEventListenerOneShot( [this](Event& event) { auto renderTarget = static_cast(event.target); this->deallocateRenderTarget(renderTarget); }); diff --git a/src/threepp/textures/Texture.cpp b/src/threepp/textures/Texture.cpp index c421be8a7..44e35fcdb 100644 --- a/src/threepp/textures/Texture.cpp +++ b/src/threepp/textures/Texture.cpp @@ -35,7 +35,7 @@ void Texture::dispose() { if (!disposed_) { disposed_ = true; - this->dispatchEvent("dispose", this); + this->OnDispose.dispatchEvent(threepp::Event{this}); } } diff --git a/tests/core/EventDispatcher_test.cpp b/tests/core/EventDispatcher_test.cpp index 94c38b4e9..8de8e56b9 100644 --- a/tests/core/EventDispatcher_test.cpp +++ b/tests/core/EventDispatcher_test.cpp @@ -13,15 +13,15 @@ TEST_CASE("Test addEventListener") { int nCalls = 0; { - auto s0 = evt.addEventListener("foo", [&](Event& e) {nCalls++; }); + auto s0 = evt.addEventListener( [&](Event& e) {nCalls++; }); REQUIRE(nCalls == 0); - evt.dispatchEvent("foo"); + evt.dispatchEvent(Event{}); REQUIRE(nCalls == 1); - evt.dispatchEvent("foo"); + evt.dispatchEvent(Event{}); REQUIRE(nCalls == 2); } - evt.dispatchEvent("foo"); + evt.dispatchEvent(Event{}); REQUIRE(nCalls == 2); } @@ -31,16 +31,16 @@ TEST_CASE("Test addEventListenerOneOwned") { EventDispatcher evt; int nCalls = 0; - evt.addEventListenerOwned("foo", [&](Event& e) { + evt.addEventListenerOwned([&](Event& e) { nCalls++; if (nCalls == 2) { e.unsubscribe = true; } }); REQUIRE(nCalls == 0); - evt.dispatchEvent("foo"); + evt.dispatchEvent(Event{}); REQUIRE(nCalls == 1); - evt.dispatchEvent("foo"); + evt.dispatchEvent(Event{}); REQUIRE(nCalls == 2); - evt.dispatchEvent("foo"); + evt.dispatchEvent(Event{}); REQUIRE(nCalls == 2); } TEST_CASE("Test addEventListenerOneShot") { @@ -48,10 +48,10 @@ TEST_CASE("Test addEventListenerOneShot") { EventDispatcher evt; int nCalls = 0; - evt.addEventListenerOneShot("foo", [&](Event& e) {nCalls++; }); + evt.addEventListenerOneShot([&](Event& e) {nCalls++; }); REQUIRE(nCalls == 0); - evt.dispatchEvent("foo"); + evt.dispatchEvent(Event{}); REQUIRE(nCalls == 1); - evt.dispatchEvent("foo"); + evt.dispatchEvent(Event{}); REQUIRE(nCalls == 1); } From 2b906e476088bd185d01b6ceee64a7eb66f03745 Mon Sep 17 00:00:00 2001 From: Brad Phelan Date: Fri, 1 Mar 2024 20:14:22 +0100 Subject: [PATCH 03/17] Add mouse support and refactor. * rename addEventListener to subscribe * rename dispatch to send --- examples/misc/mouse_key_listener.cpp | 83 ++--- examples/misc/raycast.cpp | 7 +- examples/objects/instancing.cpp | 7 +- examples/objects/sprite.cpp | 7 +- examples/projects/Pathfinding/main.cpp | 11 +- examples/projects/Snake/SnakeScene.hpp | 4 +- examples/projects/Snake/main.cpp | 2 +- include/threepp/core/EventDispatcher.hpp | 72 +++- include/threepp/input/KeyListener.hpp | 48 +-- include/threepp/input/MouseListener.hpp | 53 ++- .../threepp/input/PeripheralsEventSource.hpp | 21 +- src/threepp/canvas/Canvas.cpp | 14 +- src/threepp/controls/FlyControls.cpp | 327 ++++++++---------- src/threepp/controls/OrbitControls.cpp | 223 +++++------- src/threepp/core/BufferGeometry.cpp | 2 +- src/threepp/core/Object3D.cpp | 6 +- src/threepp/input/PeripheralsEventSource.cpp | 90 ----- src/threepp/materials/Material.cpp | 2 +- src/threepp/objects/HUD.cpp | 25 +- src/threepp/objects/InstancedMesh.cpp | 2 +- src/threepp/renderers/GLRenderTarget.cpp | 2 +- src/threepp/renderers/GLRenderer.cpp | 2 +- src/threepp/renderers/gl/GLGeometries.cpp | 2 +- src/threepp/renderers/gl/GLObjects.cpp | 2 +- src/threepp/renderers/gl/GLTextures.cpp | 5 +- src/threepp/textures/Texture.cpp | 2 +- tests/core/EventDispatcher_test.cpp | 26 +- 27 files changed, 432 insertions(+), 615 deletions(-) diff --git a/examples/misc/mouse_key_listener.cpp b/examples/misc/mouse_key_listener.cpp index bd88dab70..795909689 100644 --- a/examples/misc/mouse_key_listener.cpp +++ b/examples/misc/mouse_key_listener.cpp @@ -7,49 +7,35 @@ using namespace threepp; namespace { - struct MyMouseListener: MouseListener { + void onMouseDown(MouseButtonEvent & e, double t) { + std::cout << "onMouseDown, button= " << e.button << ", pos=" << e.pos << " at t=" << t << std::endl; + } - float& t; + void onMouseUp(MouseButtonEvent & e, double t) { + std::cout << "onMouseUp, button= " << e.button << ", pos=" << e.pos << " at t=" << t << std::endl; + } - explicit MyMouseListener(float& t): t(t) {} + void onMouseMove(MouseMoveEvent &e, double t) { + std::cout << "onMouseMove, " + << "pos=" << e.pos << " at t=" << t << std::endl; + } - void onMouseDown(int button, const Vector2& pos) override { - std::cout << "onMouseDown, button= " << button << ", pos=" << pos << " at t=" << t << std::endl; - } - - void onMouseUp(int button, const Vector2& pos) override { - std::cout << "onMouseUp, button= " << button << ", pos=" << pos << " at t=" << t << std::endl; - } - - void onMouseMove(const Vector2& pos) override { - std::cout << "onMouseMove, " - << "pos=" << pos << " at t=" << t << std::endl; - } - - void onMouseWheel(const Vector2& delta) override { - std::cout << "onMouseWheel, " - << "delta=" << delta << " at t=" << t << std::endl; - } - }; + void onMouseWheel(MouseWheelEvent &e, double t) { + std::cout << "onMouseWheel, " + << "delta=" << e.offset << " at t=" << t << std::endl; + } - struct MyKeyListener: KeyListener { + void onKeyPressed(KeyEvent evt, double t) { + std::cout << "onKeyPressed at t=" << t << std::endl; + } - float& t; + void onKeyReleased(KeyEvent evt, double t) { + std::cout << "onKeyReleased at t=" << t << std::endl; + } - explicit MyKeyListener(float& t): t(t) {} - - void onKeyPressed(KeyEvent evt) override { - std::cout << "onKeyPressed at t=" << t << std::endl; - } - - void onKeyReleased(KeyEvent evt) override { - std::cout << "onKeyReleased at t=" << t << std::endl; - } - - void onKeyRepeat(KeyEvent evt) override { - std::cout << "onKeyRepeat at t=" << t << std::endl; - } - }; + void onKeyRepeat(KeyEvent evt, double t) { + std::cout << "onKeyRepeat at t=" << t << std::endl; + } }// namespace @@ -58,22 +44,29 @@ int main() { Canvas canvas("Mouse and Key Listeners Demo"); Clock clock; - MyMouseListener ml{clock.elapsedTime}; - MyKeyListener kl{clock.elapsedTime}; - canvas.addMouseListener(ml); - canvas.addKeyListener(kl); + Subscriptions subs_; + subs_ << canvas.mouse.OnMouseDown.subscribe([&](auto& e) { onMouseDown(e, clock.elapsedTime); }); + subs_ << canvas.mouse.OnMouseUp.subscribe([&](auto& e) { onMouseUp(e, clock.elapsedTime); }); + subs_ << canvas.mouse.OnMouseMove.subscribe([&](auto& e) { onMouseMove(e, clock.elapsedTime); }); + subs_ << canvas.mouse.OnMouseWheel.subscribe([&](auto& e) { onMouseWheel(e, clock.elapsedTime); }); + + Subscriptions key_subs_; + auto subscribe_keys = [&]() { + key_subs_ << canvas.keys.OnKeyPressed.subscribe([&](auto& e) {onKeyPressed(e, clock.elapsedTime); }); + key_subs_ << canvas.keys.OnKeyReleased.subscribe([&](auto& e) {onKeyReleased(e, clock.elapsedTime); }); + key_subs_ << canvas.keys.OnKeyRepeat.subscribe([&](auto& e) {onKeyRepeat(e, clock.elapsedTime); }); + }; bool finish = false; canvas.animate([&]() { clock.getElapsedTime(); if (clock.elapsedTime > 2 && clock.elapsedTime < 4) { - if (canvas.removeKeyListener(kl)) { - std::cout << "removed key listener" << std::endl; - } + key_subs_.clear(); + std::cout << "removed key listener" << std::endl; } else if (!finish && clock.elapsedTime > 5) { + subscribe_keys(); std::cout << "re-added key listener" << std::endl; - canvas.addKeyListener(kl); finish = true; } }); diff --git a/examples/misc/raycast.cpp b/examples/misc/raycast.cpp index b6fcf9f69..087da5b1c 100644 --- a/examples/misc/raycast.cpp +++ b/examples/misc/raycast.cpp @@ -52,15 +52,14 @@ int main() { }); Vector2 mouse{-Infinity, -Infinity}; - MouseMoveListener l([&](Vector2 pos) { + auto sub = canvas.mouse.OnMouseMove.subscribe([&](MouseMoveEvent& e) { // calculate mouse position in normalized device coordinates // (-1 to +1) for both components auto size = canvas.size(); - mouse.x = (pos.x / static_cast(size.width)) * 2 - 1; - mouse.y = -(pos.y / static_cast(size.height)) * 2 + 1; + mouse.x = (e.pos.x / static_cast(size.width)) * 2 - 1; + mouse.y = -(e.pos.y / static_cast(size.height)) * 2 + 1; }); - canvas.addMouseListener(l); Raycaster raycaster; raycaster.params.lineThreshold = 0.1f; diff --git a/examples/objects/instancing.cpp b/examples/objects/instancing.cpp index a3b007b26..79d3c2147 100644 --- a/examples/objects/instancing.cpp +++ b/examples/objects/instancing.cpp @@ -108,12 +108,11 @@ int main() { }); Vector2 mouse{-Infinity, -Infinity}; - MouseMoveListener l([&](auto& pos) { + auto sub = canvas.mouse.OnMouseMove.subscribe([&](auto& e) { auto size = canvas.size(); - mouse.x = (pos.x / static_cast(size.width)) * 2 - 1; - mouse.y = -(pos.y / static_cast(size.height)) * 2 + 1; + mouse.x = (e.pos.x / static_cast(size.width)) * 2 - 1; + mouse.y = -(e.pos.y / static_cast(size.height)) * 2 + 1; }); - canvas.addMouseListener(l); Clock clock; diff --git a/examples/objects/sprite.cpp b/examples/objects/sprite.cpp index 3146983d7..38a63919a 100644 --- a/examples/objects/sprite.cpp +++ b/examples/objects/sprite.cpp @@ -98,12 +98,11 @@ int main() { }); Vector2 mouse{-Infinity, -Infinity}; - MouseMoveListener l([&](auto& pos) { + auto sub = canvas.mouse.OnMouseMove.subscribe([&](MouseMoveEvent& e) { auto size = canvas.size(); - mouse.x = (pos.x / static_cast(size.width)) * 2 - 1; - mouse.y = -(pos.y / static_cast(size.height)) * 2 + 1; + mouse.x = (e.pos.x / static_cast(size.width)) * 2 - 1; + mouse.y = -(e.pos.y / static_cast(size.height)) * 2 + 1; }); - canvas.addMouseListener(l); Clock clock; Raycaster raycaster; diff --git a/examples/projects/Pathfinding/main.cpp b/examples/projects/Pathfinding/main.cpp index eced21c48..2ce1128af 100644 --- a/examples/projects/Pathfinding/main.cpp +++ b/examples/projects/Pathfinding/main.cpp @@ -94,12 +94,12 @@ int main() { Raycaster raycaster; raycaster.layers.set(1); // ignore grid Vector2 mouse{-Infinity, -Infinity}; - MouseUpListener mouseListener([&](int button, Vector2 pos) { + TEventListener mouseListener = [&](MouseButtonEvent & e) { if (start && target) return; const auto s = canvas.size(); - mouse.x = (pos.x / static_cast(s.width)) * 2 - 1; - mouse.y = -(pos.y / static_cast(s.height)) * 2 + 1; + mouse.x = (e.pos.x / static_cast(s.width)) * 2 - 1; + mouse.y = -(e.pos.y / static_cast(s.height)) * 2 + 1; raycaster.setFromCamera(mouse, camera); auto intersects = raycaster.intersectObjects(scene.children); @@ -139,8 +139,9 @@ int main() { } } } - }); - canvas.addMouseListener(mouseListener); + }; + + auto sub = canvas.mouse.OnMouseUp.subscribe(mouseListener); canvas.onWindowResize([&](WindowSize s) { camera.left = -size * s.aspect() / 2; diff --git a/examples/projects/Snake/SnakeScene.hpp b/examples/projects/Snake/SnakeScene.hpp index 615233141..ac5593f92 100644 --- a/examples/projects/Snake/SnakeScene.hpp +++ b/examples/projects/Snake/SnakeScene.hpp @@ -9,7 +9,7 @@ using namespace threepp; -class SnakeScene: public Scene, public KeyListener { +class SnakeScene: public Scene { public: explicit SnakeScene(SnakeGame& game): game_(game) { @@ -34,7 +34,7 @@ class SnakeScene: public Scene, public KeyListener { add(snake_.back()); } - void onKeyPressed(KeyEvent evt) override { + void onKeyPressed(KeyEvent evt) { if (game_.isRunning()) { diff --git a/examples/projects/Snake/main.cpp b/examples/projects/Snake/main.cpp index 84e57b506..69a1ee35e 100644 --- a/examples/projects/Snake/main.cpp +++ b/examples/projects/Snake/main.cpp @@ -13,7 +13,7 @@ int main() { renderer.autoClear = false; auto scene = SnakeScene(game); - canvas.addKeyListener(scene); + auto sub = canvas.keys.OnKeyPressed.subscribe([&](auto& e) {scene.onKeyPressed(e); }); auto camera = OrthographicCamera::create(0, game.gridSize(), 0, game.gridSize()); camera->position.z = 1; diff --git a/include/threepp/core/EventDispatcher.hpp b/include/threepp/core/EventDispatcher.hpp index 04c0d691a..6ead38a8d 100644 --- a/include/threepp/core/EventDispatcher.hpp +++ b/include/threepp/core/EventDispatcher.hpp @@ -14,40 +14,77 @@ namespace threepp { - + namespace concepts { +#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) + template + concept Event = requires(TEvent e) { + { e.target } -> std::convertible_to; + { e.unsubscribe } -> std::convertible_to; + }; +#endif + } + + /// An event listener is just a function that takes an argument of type TEvent template using TEventListener = std::function; + /// A single subscription for an event using Subscription = std::shared_ptr; + /// For holding a large number of subscriptions to events + using Subscriptions= std::vector; + + /// Allows one to use the << to push subscriptions onto the vector + inline + void operator<<(std::vector& subs, Subscription const & sub) { + subs.push_back(sub); + } + + /// Generic event dispatch class template +#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) + requires concepts::Event +#endif + // C++20 (and later) code class TEventDispatcher { public: using EventListener = TEventListener; /// Adds an event listener and returns a subscription - [[nodiscard]] Subscription addEventListener(EventListener listener) { + [[nodiscard]] Subscription subscribe(EventListener listener) { size_t current_id = id_.load(); - listeners_.insert({current_id, listener}); + listeners_.insert({ current_id, listener }); Subscription disposer((void*) nullptr, [this, current_id](void*) { listeners_.erase(current_id); }); id_ = id_ + 1; return disposer; } - /// Adds an event listener and assumes event.unsubscribe = true will be - /// used to unsubscribe. Useful for one shot events - void addEventListenerOwned( EventListener listener) { - size_t current_id = id_.load(); - listeners_.insert({current_id, listener}); - id_ = id_ + 1; - } + /// Adds an event listener and never automatically unsubscribes. You + /// can set event.unsubscribe = true and the subscription will be + /// cancelled. Not recommended to be used directly. Build other + /// tools on this. + void subscribeForever(EventListener listener) { + size_t current_id = id_.load(); + listeners_.insert({ current_id, listener }); + id_ = id_ + 1; + } - /// Adds an event listener and unsubscribes after a single shot - void addEventListenerOneShot( EventListener listener) { - this->addEventListenerOwned([l = std::move(listener)](TEvent& e) { l(e); e.unsubscribe = true; }); - } + /// Adds an event listener that unsubscribes after a single shot + void subscribeOnce(EventListener listener) { + this->subscribeForever([l = std::move(listener)](TEvent& e) { l(e); e.unsubscribe = true; }); + } + + /// Hold onto the other subscription until the second source fires then + /// dispose the subscription. + template + void subscribeUntil(TEventDispatcher& s, EventListener listener) { + auto sub = subscribe(listener); + s.subscribeOnce([sub](auto&) {}); + } - void dispatchEvent(TEvent & e){ + + /// Send an event to all listeners. + void send(TEvent & e){ std::vector toUnsubscribe; for (auto const& item : listeners_) { item.second(e); @@ -60,6 +97,11 @@ namespace threepp { listeners_.erase(id); } + /// Handle r-value versions of send + void send(TEvent && e) { + send(e); + } + virtual ~TEventDispatcher() = default; private: diff --git a/include/threepp/input/KeyListener.hpp b/include/threepp/input/KeyListener.hpp index 56a8d6e94..7b3814f17 100644 --- a/include/threepp/input/KeyListener.hpp +++ b/include/threepp/input/KeyListener.hpp @@ -4,12 +4,13 @@ #include #include +#include namespace threepp { enum class Key; - struct KeyEvent { + struct KeyEvent : Event { const Key key; const int scancode; @@ -19,50 +20,13 @@ namespace threepp { : key(key), scancode(scancode), mods(mods) {} }; - struct KeyListener { - virtual void onKeyPressed(KeyEvent evt) {} - - virtual void onKeyReleased(KeyEvent evt) {} - - virtual void onKeyRepeat(KeyEvent evt) {} - - virtual ~KeyListener() = default; + struct Keys { + TEventDispatcher OnKeyPressed; + TEventDispatcher OnKeyReleased; + TEventDispatcher OnKeyRepeat; }; - struct KeyAdapter: KeyListener { - - enum Mode { - KEY_PRESSED = 1, - KEY_RELEASED = 2, - KEY_REPEAT = 4 - }; - - KeyAdapter(const Mode& mode, std::function f) - : mode_(mode), f_(std::move(f)) {} - - void onKeyPressed(KeyEvent evt) override { - if (mode_ == 1 || mode_ == 3 || mode_ == 5) f_(evt); - } - - void onKeyReleased(KeyEvent evt) override { - if (mode_ == 2 || mode_ == 3 || mode_ == 6) f_(evt); - } - - void onKeyRepeat(KeyEvent evt) override { - if (mode_ == 4 || mode_ == 5 || mode_ == 6) f_(evt); - } - - private: - Mode mode_; - std::function f_; - }; - - - inline KeyAdapter::Mode operator|(KeyAdapter::Mode a, KeyAdapter::Mode b) { - return static_cast(static_cast(a) | static_cast(b)); - } - enum class Key { UNKNOWN, diff --git a/include/threepp/input/MouseListener.hpp b/include/threepp/input/MouseListener.hpp index 77204e638..862060f25 100644 --- a/include/threepp/input/MouseListener.hpp +++ b/include/threepp/input/MouseListener.hpp @@ -9,42 +9,41 @@ namespace threepp { - struct MouseListener { - - virtual void onMouseDown(int button, const Vector2& pos) {} - virtual void onMouseUp(int button, const Vector2& pos) {} - virtual void onMouseMove(const Vector2& pos) {} - virtual void onMouseWheel(const Vector2& delta) {} - - virtual ~MouseListener() = default; + struct MouseEvent : Event { + MouseEvent(Vector2 pos) : + pos(pos) {} + Vector2 pos; }; - struct MouseMoveListener: MouseListener { - - explicit MouseMoveListener(std::function f) - : f_(std::move(f)) {} - - void onMouseMove(const Vector2& pos) override { - f_(pos); - } + struct MouseMoveEvent : MouseEvent + { + MouseMoveEvent(Vector2 pos, Vector2 delta): + MouseEvent(pos), delta(delta) + {} - private: - std::function f_; + Vector2 delta; }; - struct MouseUpListener: MouseListener { - - explicit MouseUpListener(std::function f) - : f_(std::move(f)) {} + struct MouseButtonEvent : MouseEvent { + MouseButtonEvent(int button, Vector2 pos) + :MouseEvent(pos), button(button) + {} + int button; + }; - void onMouseUp(int button, const Vector2& pos) override { - f_(button, pos); - } + struct MouseWheelEvent : Event { + MouseWheelEvent(Vector2 offset_) :offset(offset_) {} + Vector2 offset; + }; - private: - std::function f_; + struct Mouse { + TEventDispatcher OnMouseMove; + TEventDispatcher OnMouseWheel; + TEventDispatcher OnMouseDown; + TEventDispatcher OnMouseUp; }; + }// namespace threepp diff --git a/include/threepp/input/PeripheralsEventSource.hpp b/include/threepp/input/PeripheralsEventSource.hpp index b0b762413..5a7b89350 100644 --- a/include/threepp/input/PeripheralsEventSource.hpp +++ b/include/threepp/input/PeripheralsEventSource.hpp @@ -19,15 +19,13 @@ namespace threepp { void setIOCapture(IOCapture* capture); - void addKeyListener(KeyListener& listener); + // Events for keys + Keys keys; - bool removeKeyListener(const KeyListener& listener); + // Events for mouse + Mouse mouse; - void addMouseListener(MouseListener& listener); - - bool removeMouseListener(const MouseListener& listener); - - virtual ~PeripheralsEventSource() = default; + virtual ~PeripheralsEventSource() = default; protected: enum class KeyAction { @@ -41,18 +39,9 @@ namespace threepp { RELEASE }; - void onMousePressedEvent(int button, const Vector2& pos, MouseAction action); - - void onMouseMoveEvent(const Vector2& pos); - - void onMouseWheelEvent(const Vector2& eventData); - - void onKeyEvent(KeyEvent evt, KeyAction action); private: IOCapture* ioCapture_ = nullptr; - std::vector keyListeners_; - std::vector mouseListeners_; }; }// namespace threepp diff --git a/src/threepp/canvas/Canvas.cpp b/src/threepp/canvas/Canvas.cpp index 53a9505ce..602c38d01 100644 --- a/src/threepp/canvas/Canvas.cpp +++ b/src/threepp/canvas/Canvas.cpp @@ -316,7 +316,7 @@ struct Canvas::Impl { static void scroll_callback(GLFWwindow* w, double xoffset, double yoffset) { auto p = static_cast(glfwGetWindowUserPointer(w)); - p->scope.onMouseWheelEvent({static_cast(xoffset), static_cast(yoffset)}); + p->scope.mouse.OnMouseWheel.send(MouseWheelEvent(Vector2{static_cast(xoffset), static_cast(yoffset)})); } static void mouse_callback(GLFWwindow* w, int button, int action, int) { @@ -324,10 +324,10 @@ struct Canvas::Impl { switch (action) { case GLFW_PRESS: - p->scope.onMousePressedEvent(button, p->lastMousePos_, PeripheralsEventSource::MouseAction::PRESS); + p->scope.mouse.OnMouseDown.send(MouseButtonEvent(button, p->lastMousePos_)); break; case GLFW_RELEASE: - p->scope.onMousePressedEvent(button, p->lastMousePos_, PeripheralsEventSource::MouseAction::RELEASE); + p->scope.mouse.OnMouseUp.send(MouseButtonEvent(button, p->lastMousePos_)); break; default: break; @@ -338,7 +338,7 @@ struct Canvas::Impl { auto p = static_cast(glfwGetWindowUserPointer(w)); Vector2 mousePos(static_cast(xpos), static_cast(ypos)); - p->scope.onMouseMoveEvent(mousePos); + p->scope.mouse.OnMouseMove.send(MouseMoveEvent(mousePos, mousePos-p->lastMousePos_)); p->lastMousePos_.copy(mousePos); } @@ -354,15 +354,15 @@ struct Canvas::Impl { KeyEvent evt{glfwKeyCodeToKey(key), scancode, mods}; switch (action) { case GLFW_PRESS: { - p->scope.onKeyEvent(evt, PeripheralsEventSource::KeyAction::PRESS); + p->scope.keys.OnKeyPressed.send(evt); break; } case GLFW_RELEASE: { - p->scope.onKeyEvent(evt, PeripheralsEventSource::KeyAction::RELEASE); + p->scope.keys.OnKeyReleased.send(evt); break; } case GLFW_REPEAT: { - p->scope.onKeyEvent(evt, PeripheralsEventSource::KeyAction::REPEAT); + p->scope.keys.OnKeyRepeat.send(evt); break; } default: diff --git a/src/threepp/controls/FlyControls.cpp b/src/threepp/controls/FlyControls.cpp index 7f74ffce9..011b3a8a4 100644 --- a/src/threepp/controls/FlyControls.cpp +++ b/src/threepp/controls/FlyControls.cpp @@ -32,16 +32,17 @@ namespace { struct FlyControls::Impl { Impl(FlyControls& scope, PeripheralsEventSource& canvas, Object3D* object) - : eventSource(canvas), scope(scope), object(object), - keyUp(scope), keydown(scope), - mouseDown(scope), mouseMove(scope), mouseUp(scope) { + : eventSource(canvas), scope(scope), object(object) + { - canvas.addKeyListener(keydown); - canvas.addKeyListener(keyUp); + subs_ << canvas.keys.OnKeyPressed.subscribe([this, &scope](KeyEvent& key) { onKeyPressed(key, scope); }); + subs_ << canvas.keys.OnKeyReleased.subscribe([this, &scope](KeyEvent& key) { onKeyReleased(key, scope); }); - canvas.addMouseListener(mouseDown); - canvas.addMouseListener(mouseMove); - canvas.addMouseListener(mouseUp); + subs_ << canvas.mouse.OnMouseDown.subscribe([this, &scope](MouseButtonEvent& e) { onMouseDown(e, scope); }); + subs_ << canvas.mouse.OnMouseUp.subscribe([this, &scope](MouseButtonEvent& e) { onMouseUp(e, scope); }); + subs_ << canvas.mouse.OnMouseMove.subscribe([this, &scope](MouseEvent& e) { onMouseMove(e, scope); }); + // TODO + //subs_ << canvas.mouse.OnMouseWheel.subscribe([this, &scope](MouseEvent const& e) { onMouseWheel(e, scope); }); } void update(float delta) { @@ -81,211 +82,168 @@ struct FlyControls::Impl { rotationVector.z = (-moveState.rollRight + moveState.rollLeft); } - ~Impl() { + static void onKeyPressed(KeyEvent evt, FlyControls& scope) { - eventSource.removeKeyListener(keydown); - eventSource.removeKeyListener(keyUp); + if (evt.mods == 4) {// altKey - eventSource.removeMouseListener(mouseDown); - eventSource.removeMouseListener(mouseMove); - eventSource.removeMouseListener(mouseUp); - } - - struct KeyDownListener: KeyListener { - - FlyControls& scope; - - explicit KeyDownListener(FlyControls& scope): scope(scope) {} - - void onKeyPressed(KeyEvent evt) override { - - if (evt.mods == 4) {// altKey - - return; - } - - switch (evt.key) { - case Key::W: - scope.pimpl_->moveState.forward = 1; - break; - case Key::S: - scope.pimpl_->moveState.back = 1; - break; - case Key::A: - scope.pimpl_->moveState.left = 1; - break; - case Key::D: - scope.pimpl_->moveState.right = 1; - break; - case Key::R: - scope.pimpl_->moveState.up = 1; - break; - case Key::F: - scope.pimpl_->moveState.down = 1; - break; - case Key::UP: - scope.pimpl_->moveState.pitchUp = 1; - break; - case Key::DOWN: - scope.pimpl_->moveState.pitchDown = 1; - break; - case Key::LEFT: - scope.pimpl_->moveState.yawLeft = 1; - break; - case Key::RIGHT: - scope.pimpl_->moveState.yawRight = 1; - break; - case Key::Q: - scope.pimpl_->moveState.rollLeft = 1; - break; - case Key::E: - scope.pimpl_->moveState.rollRight = 1; - break; - default: - break; - } - - scope.pimpl_->updateMovementVector(); - scope.pimpl_->updateRotationVector(); + return; } - }; - - struct KeyUpListener: KeyListener { - - FlyControls& scope; - - explicit KeyUpListener(FlyControls& scope): scope(scope) {} - - void onKeyReleased(KeyEvent evt) override { - - switch (evt.key) { - case Key::W: - scope.pimpl_->moveState.forward = 0; - break; - case Key::S: - scope.pimpl_->moveState.back = 0; - break; - case Key::A: - scope.pimpl_->moveState.left = 0; - break; - case Key::D: - scope.pimpl_->moveState.right = 0; - break; - case Key::R: - scope.pimpl_->moveState.up = 0; - break; - case Key::F: - scope.pimpl_->moveState.down = 0; - break; - case Key::UP: - scope.pimpl_->moveState.pitchUp = 0; - break; - case Key::DOWN: - scope.pimpl_->moveState.pitchDown = 0; - break; - case Key::LEFT: - scope.pimpl_->moveState.yawLeft = 0; - break; - case Key::RIGHT: - scope.pimpl_->moveState.yawRight = 0; - break; - case Key::Q: - scope.pimpl_->moveState.rollLeft = 0; - break; - case Key::E: - scope.pimpl_->moveState.rollRight = 0; - break; - default: - break; - } - scope.pimpl_->updateMovementVector(); - scope.pimpl_->updateRotationVector(); + switch (evt.key) { + case Key::W: + scope.pimpl_->moveState.forward = 1; + break; + case Key::S: + scope.pimpl_->moveState.back = 1; + break; + case Key::A: + scope.pimpl_->moveState.left = 1; + break; + case Key::D: + scope.pimpl_->moveState.right = 1; + break; + case Key::R: + scope.pimpl_->moveState.up = 1; + break; + case Key::F: + scope.pimpl_->moveState.down = 1; + break; + case Key::UP: + scope.pimpl_->moveState.pitchUp = 1; + break; + case Key::DOWN: + scope.pimpl_->moveState.pitchDown = 1; + break; + case Key::LEFT: + scope.pimpl_->moveState.yawLeft = 1; + break; + case Key::RIGHT: + scope.pimpl_->moveState.yawRight = 1; + break; + case Key::Q: + scope.pimpl_->moveState.rollLeft = 1; + break; + case Key::E: + scope.pimpl_->moveState.rollRight = 1; + break; + default: + break; } - }; - - struct MouseDownListener: MouseListener { - - FlyControls& scope; - - explicit MouseDownListener(FlyControls& scope): scope(scope) {} - - void onMouseDown(int button, const Vector2& pos) override { - - if (scope.dragToLook) { - scope.pimpl_->mouseStatus++; + scope.pimpl_->updateMovementVector(); + scope.pimpl_->updateRotationVector(); + } - } else { + static void onKeyReleased(KeyEvent evt, FlyControls& scope) { + + switch (evt.key) { + case Key::W: + scope.pimpl_->moveState.forward = 0; + break; + case Key::S: + scope.pimpl_->moveState.back = 0; + break; + case Key::A: + scope.pimpl_->moveState.left = 0; + break; + case Key::D: + scope.pimpl_->moveState.right = 0; + break; + case Key::R: + scope.pimpl_->moveState.up = 0; + break; + case Key::F: + scope.pimpl_->moveState.down = 0; + break; + case Key::UP: + scope.pimpl_->moveState.pitchUp = 0; + break; + case Key::DOWN: + scope.pimpl_->moveState.pitchDown = 0; + break; + case Key::LEFT: + scope.pimpl_->moveState.yawLeft = 0; + break; + case Key::RIGHT: + scope.pimpl_->moveState.yawRight = 0; + break; + case Key::Q: + scope.pimpl_->moveState.rollLeft = 0; + break; + case Key::E: + scope.pimpl_->moveState.rollRight = 0; + break; + default: + break; + } - switch (button) { + scope.pimpl_->updateMovementVector(); + scope.pimpl_->updateRotationVector(); + } - case 0: - scope.pimpl_->moveState.forward = 1; - break; - case 1: - scope.pimpl_->moveState.back = 1; - break; - } + static void onMouseDown(MouseButtonEvent & e, FlyControls&scope) { - scope.pimpl_->updateMovementVector(); - } - } - }; + if (scope.dragToLook) { - struct MouseMoveListener: MouseListener { + scope.pimpl_->mouseStatus++; - FlyControls& scope; + } else { - explicit MouseMoveListener(FlyControls& scope): scope(scope) {} + switch (e.button) { - void onMouseMove(const Vector2& pos) override { + case 0: + scope.pimpl_->moveState.forward = 1; + break; + case 1: + scope.pimpl_->moveState.back = 1; + break; + } - if (!scope.dragToLook || scope.pimpl_->mouseStatus > 0) { + scope.pimpl_->updateMovementVector(); + } + } - const float halfWidth = static_cast(scope.pimpl_->eventSource.size().width) / 2; - const float halfHeight = static_cast(scope.pimpl_->eventSource.size().height) / 2; - scope.pimpl_->moveState.yawLeft = -((pos.x) - halfWidth) / halfWidth; - scope.pimpl_->moveState.pitchDown = ((pos.y) - halfHeight) / halfHeight; + static void onMouseMove(MouseEvent & e, FlyControls&scope) { - scope.pimpl_->updateRotationVector(); - } - } - }; + if (!scope.dragToLook || scope.pimpl_->mouseStatus > 0) { - struct MouseUpListener: MouseListener { + const float halfWidth = static_cast(scope.pimpl_->eventSource.size().width) / 2; + const float halfHeight = static_cast(scope.pimpl_->eventSource.size().height) / 2; - FlyControls& scope; + scope.pimpl_->moveState.yawLeft = -((e.pos.x) - halfWidth) / halfWidth; + scope.pimpl_->moveState.pitchDown = ((e.pos.y) - halfHeight) / halfHeight; - explicit MouseUpListener(FlyControls& scope): scope(scope) {} + scope.pimpl_->updateRotationVector(); + } + } - void onMouseUp(int button, const Vector2& pos) override { - if (scope.dragToLook) { + static void onMouseUp(MouseButtonEvent& e, FlyControls& scope) { - scope.pimpl_->mouseStatus--; + if (scope.dragToLook) { - scope.pimpl_->moveState.yawLeft = scope.pimpl_->moveState.pitchDown = 0; + scope.pimpl_->mouseStatus--; - } else { + scope.pimpl_->moveState.yawLeft = scope.pimpl_->moveState.pitchDown = 0; - switch (button) { + } else { - case 0: - scope.pimpl_->moveState.forward = 0; - break; - case 1: - scope.pimpl_->moveState.back = 0; - break; - } + switch (e.button) { - scope.pimpl_->updateMovementVector(); + case 0: + scope.pimpl_->moveState.forward = 0; + break; + case 1: + scope.pimpl_->moveState.back = 0; + break; } - scope.pimpl_->updateRotationVector(); + scope.pimpl_->updateMovementVector(); } - }; + + scope.pimpl_->updateRotationVector(); + } private: PeripheralsEventSource& eventSource; @@ -303,12 +261,7 @@ struct FlyControls::Impl { Vector3 moveVector; Vector3 rotationVector; - KeyUpListener keydown; - KeyDownListener keyUp; - - MouseDownListener mouseDown; - MouseMoveListener mouseMove; - MouseUpListener mouseUp; + std::vector subs_; }; FlyControls::FlyControls(Object3D& object, PeripheralsEventSource& eventSource) diff --git a/src/threepp/controls/OrbitControls.cpp b/src/threepp/controls/OrbitControls.cpp index fa7aa5e7a..5ca5e3d20 100644 --- a/src/threepp/controls/OrbitControls.cpp +++ b/src/threepp/controls/OrbitControls.cpp @@ -34,9 +34,6 @@ struct OrbitControls::Impl { OrbitControls& scope; Camera& camera; - std::unique_ptr keyListener; - std::unique_ptr mouseListener; - State state = State::NONE; // current position in spherical coordinates @@ -59,13 +56,17 @@ struct OrbitControls::Impl { Vector2 dollyEnd; Vector2 dollyDelta; + Subscriptions subs_; + Impl(OrbitControls& scope, PeripheralsEventSource& canvas, Camera& camera) - : scope(scope), canvas(canvas), camera(camera), - keyListener(std::make_unique(scope)), - mouseListener(std::make_unique(scope)) { + : scope(scope), canvas(canvas), camera(camera) + { + subs_ << canvas.keys.OnKeyPressed.subscribe([&](auto& e) mutable { onKeyPressed(e, scope); }); + subs_ << canvas.keys.OnKeyPressed.subscribe([&](auto& e) mutable { onKeyRepeat(e, scope); }); - canvas.addMouseListener(*mouseListener); - canvas.addKeyListener(*keyListener); + subs_ << canvas.mouse.OnMouseDown.subscribe([&](MouseButtonEvent& e) { + this->onMouseDown(e, scope); + }); update(); } @@ -391,130 +392,94 @@ struct OrbitControls::Impl { } ~Impl() { - - canvas.removeMouseListener(*mouseListener); - canvas.removeKeyListener(*keyListener); } - struct MyKeyListener: KeyListener { - - OrbitControls& controls; - - explicit MyKeyListener(OrbitControls& controls) - : controls(controls) {} - - void onKeyPressed(KeyEvent evt) override { - if (controls.enabled && controls.enableKeys && controls.enablePan) { - controls.pimpl_->handleKeyDown(evt.key); - } - } - - void onKeyRepeat(KeyEvent evt) override { - if (controls.enabled && controls.enableKeys && controls.enablePan) { - controls.pimpl_->handleKeyDown(evt.key); - } - } - }; - - struct MyMouseMoveListener: MouseListener { - - OrbitControls& controls; - - explicit MyMouseMoveListener(OrbitControls& controls): controls(controls) {} - - void onMouseMove(const Vector2& pos) override { - if (controls.enabled) { - - switch (controls.pimpl_->state) { - case State::ROTATE: - if (controls.enableRotate) { - controls.pimpl_->handleMouseMoveRotate(pos); - } - break; - case State::DOLLY: - if (controls.enableZoom) { - controls.pimpl_->handleMouseMoveDolly(pos); - } - break; - case State::PAN: - if (controls.enablePan) { - controls.pimpl_->handleMouseMovePan(pos); - } - break; - default: - //TODO ? - break; - } - } - } - }; - - struct MyMouseUpListener: MouseListener { - - OrbitControls& scope; - MouseListener* mouseMoveListener; - - MyMouseUpListener(OrbitControls& scope, MyMouseMoveListener* mouseMoveListener) - : scope(scope), mouseMoveListener(mouseMoveListener) {} - - void onMouseUp(int button, const Vector2& pos) override { - if (scope.enabled) { - - scope.pimpl_->canvas.removeMouseListener(*mouseMoveListener); - scope.pimpl_->canvas.removeMouseListener(*this); - scope.pimpl_->state = State::NONE; - } - } - }; - - struct MyMouseListener: MouseListener { - - OrbitControls& scope; - MyMouseMoveListener mouseMoveListener; - MyMouseUpListener mouseUpListener; - - explicit MyMouseListener(OrbitControls& scope) - : scope(scope), mouseMoveListener(scope), mouseUpListener(scope, &mouseMoveListener) {} - - void onMouseDown(int button, const Vector2& pos) override { - if (scope.enabled) { - switch (button) { - case 0:// LEFT - if (scope.enableRotate) { - scope.pimpl_->handleMouseDownRotate(pos); - scope.pimpl_->state = State::ROTATE; - } - break; - case 1:// RIGHT - if (scope.enablePan) { - scope.pimpl_->handleMouseDownRotate(pos); - scope.pimpl_->handleMouseDownPan(pos); - scope.pimpl_->state = State::PAN; - } - break; - case 2:// MIDDLE - if (scope.enableZoom) { - scope.pimpl_->handleMouseDownDolly(pos); - scope.pimpl_->state = State::DOLLY; - } - break; - } - } - - if (scope.pimpl_->state != State::NONE) { - - scope.pimpl_->canvas.addMouseListener(mouseMoveListener); - scope.pimpl_->canvas.addMouseListener(mouseUpListener); - } - } - - void onMouseWheel(const Vector2& delta) override { - if (scope.enabled && scope.enableZoom && !(scope.pimpl_->state != State::NONE && scope.pimpl_->state != State::ROTATE)) { - scope.pimpl_->handleMouseWheel(delta); - } - } - }; + static void onKeyPressed(KeyEvent evt, OrbitControls & controls) { + if (controls.enabled && controls.enableKeys && controls.enablePan) { + controls.pimpl_->handleKeyDown(evt.key); + } + } + + static void onKeyRepeat(KeyEvent evt, OrbitControls & controls) { + if (controls.enabled && controls.enableKeys && controls.enablePan) { + controls.pimpl_->handleKeyDown(evt.key); + } + } + + static void onMouseMove(MouseEvent & e, OrbitControls & controls) { + if (controls.enabled) { + switch (controls.pimpl_->state) { + case State::ROTATE: + if (controls.enableRotate) { + controls.pimpl_->handleMouseMoveRotate(e.pos); + } + break; + case State::DOLLY: + if (controls.enableZoom) { + controls.pimpl_->handleMouseMoveDolly(e.pos); + } + break; + case State::PAN: + if (controls.enablePan) { + controls.pimpl_->handleMouseMovePan(e.pos); + } + break; + default: + //TODO ? + break; + } + } + } + + + void onMouseUp(MouseButtonEvent e,OrbitControls & controls) { + if (scope.enabled) { + scope.pimpl_->state = State::NONE; + } + } + + void onMouseDown(MouseButtonEvent e, OrbitControls & scope) { + if (scope.enabled) { + switch (e.button) { + case 0:// LEFT + if (scope.enableRotate) { + scope.pimpl_->handleMouseDownRotate(e.pos); + scope.pimpl_->state = State::ROTATE; + } + break; + case 1:// RIGHT + if (scope.enablePan) { + scope.pimpl_->handleMouseDownRotate(e.pos); + scope.pimpl_->handleMouseDownPan(e.pos); + scope.pimpl_->state = State::PAN; + } + break; + case 2:// MIDDLE + if (scope.enableZoom) { + scope.pimpl_->handleMouseDownDolly(e.pos); + scope.pimpl_->state = State::DOLLY; + } + break; + } + } + + if (scope.pimpl_->state != State::NONE) { + auto& mouse = scope.pimpl_->canvas.mouse; + mouse.OnMouseUp.subscribeOnce([&](MouseButtonEvent& e) { + onMouseUp(e, scope); + }); + mouse.OnMouseMove.subscribeUntil(mouse.OnMouseUp, [&](MouseEvent& e) { + onMouseMove(e, scope); + }); + } + } + + void onMouseWheel(const Vector2& delta, OrbitControls & controls) { + if (scope.enabled && scope.enableZoom && !(scope.pimpl_->state != State::NONE && scope.pimpl_->state != State::ROTATE)) { + scope.pimpl_->handleMouseWheel(delta); + } + } }; OrbitControls::OrbitControls(Camera& camera, PeripheralsEventSource& eventSource) diff --git a/src/threepp/core/BufferGeometry.cpp b/src/threepp/core/BufferGeometry.cpp index c06b77d03..0ebf83bf7 100644 --- a/src/threepp/core/BufferGeometry.cpp +++ b/src/threepp/core/BufferGeometry.cpp @@ -672,7 +672,7 @@ void BufferGeometry::dispose() { if (!disposed_) { disposed_ = true; - this->OnDispose.dispatchEvent(Event{ this }); + this->OnDispose.send(Event{ this }); } } diff --git a/src/threepp/core/Object3D.cpp b/src/threepp/core/Object3D.cpp index 255717272..14387f76c 100644 --- a/src/threepp/core/Object3D.cpp +++ b/src/threepp/core/Object3D.cpp @@ -217,7 +217,7 @@ void Object3D::add(Object3D& object) { object.parent = this; this->children.emplace_back(&object); - object.OnAdded.dispatchEvent(Event{ this }); + object.OnAdded.send(Event{ this }); } void Object3D::remove(Object3D& object) { @@ -231,7 +231,7 @@ void Object3D::remove(Object3D& object) { children.erase(find); child->parent = nullptr; - child->OnRemove.dispatchEvent(Event{child}); + child->OnRemove.send(Event{child}); } } {// owning @@ -259,7 +259,7 @@ void Object3D::clear() { object->parent = nullptr; - object->OnRemove.dispatchEvent(Event{ object }); + object->OnRemove.send(Event{ object }); } this->children.clear(); diff --git a/src/threepp/input/PeripheralsEventSource.cpp b/src/threepp/input/PeripheralsEventSource.cpp index ed428fcb5..313bd66ee 100644 --- a/src/threepp/input/PeripheralsEventSource.cpp +++ b/src/threepp/input/PeripheralsEventSource.cpp @@ -9,93 +9,3 @@ void PeripheralsEventSource::setIOCapture(IOCapture* capture) { ioCapture_ = capture; } -void PeripheralsEventSource::addKeyListener(KeyListener& listener) { - auto find = std::find(keyListeners_.begin(), keyListeners_.end(), &listener); - if (find == keyListeners_.end()) { - keyListeners_.emplace_back(&listener); - } -} - -bool PeripheralsEventSource::removeKeyListener(const KeyListener& listener) { - auto find = std::find(keyListeners_.begin(), keyListeners_.end(), &listener); - if (find != keyListeners_.end()) { - keyListeners_.erase(find); - return true; - } - return false; -} - -void PeripheralsEventSource::addMouseListener(MouseListener& listener) { - auto find = std::find(mouseListeners_.begin(), mouseListeners_.end(), &listener); - if (find == mouseListeners_.end()) { - mouseListeners_.emplace_back(&listener); - } -} - -bool PeripheralsEventSource::removeMouseListener(const MouseListener& listener) { - auto find = std::find(mouseListeners_.begin(), mouseListeners_.end(), &listener); - if (find != mouseListeners_.end()) { - mouseListeners_.erase(find); - return true; - } - return false; -} - -void PeripheralsEventSource::onMousePressedEvent(int button, const Vector2& pos, PeripheralsEventSource::MouseAction action) { - if (ioCapture_ && ioCapture_->preventMouseEvent()) return; - - auto listeners = mouseListeners_;//copy - IMPORTANT - for (auto l : listeners) { - switch (action) { - case MouseAction::RELEASE: { - l->onMouseUp(button, pos); - break; - } - case MouseAction::PRESS: { - l->onMouseDown(button, pos); - break; - } - } - } -} - -void PeripheralsEventSource::onMouseMoveEvent(const Vector2& pos) { - if (ioCapture_ && ioCapture_->preventMouseEvent()) return; - - auto listeners = mouseListeners_;//copy - IMPORTANT - for (auto l : listeners) { - l->onMouseMove(pos); - } -} - -void PeripheralsEventSource::onMouseWheelEvent(const Vector2& eventData) { - if (ioCapture_ && ioCapture_->preventScrollEvent()) return; - - auto listeners = mouseListeners_;//copy - IMPORTANT - for (auto l : listeners) { - - l->onMouseWheel(eventData); - } -} - -void PeripheralsEventSource::onKeyEvent(KeyEvent evt, PeripheralsEventSource::KeyAction action) { - if (ioCapture_ && ioCapture_->preventKeyboardEvent()) return; - - auto listeners = keyListeners_;//copy - IMPORTANT - for (auto l : listeners) { - switch (action) { - case KeyAction::PRESS: { - l->onKeyPressed(evt); - break; - } - case KeyAction::RELEASE: { - l->onKeyReleased(evt); - break; - } - case KeyAction::REPEAT: { - l->onKeyRepeat(evt); - break; - } - } - } -} diff --git a/src/threepp/materials/Material.cpp b/src/threepp/materials/Material.cpp index c2b2f7379..dd5ff0d3e 100644 --- a/src/threepp/materials/Material.cpp +++ b/src/threepp/materials/Material.cpp @@ -19,7 +19,7 @@ std::string Material::uuid() const { void Material::dispose() { if (!disposed_) { disposed_ = true; - OnDispose.dispatchEvent(Event{this}); + OnDispose.send(Event{this}); } } diff --git a/src/threepp/objects/HUD.cpp b/src/threepp/objects/HUD.cpp index 7050cb59e..716c97614 100644 --- a/src/threepp/objects/HUD.cpp +++ b/src/threepp/objects/HUD.cpp @@ -41,14 +41,20 @@ void HUD::Options::updateElement(Object3D& o, WindowSize windowSize) { } -struct HUD::Impl: Scene, MouseListener { +struct HUD::Impl: Scene { + + Subscriptions subs_; Impl(PeripheralsEventSource* eventSource) : eventSource_(eventSource), size_(eventSource->size()), camera_(0, size_.width, size_.height, 0, 0.1, 10) { - eventSource->addMouseListener(*this); + auto& mouse = eventSource->mouse; + + subs_ << mouse.OnMouseDown.subscribe([this](MouseButtonEvent& e) { onMouseDown(e); }); + subs_ << mouse.OnMouseUp.subscribe([this](MouseButtonEvent& e) { onMouseUp(e); }); + subs_ << mouse.OnMouseMove.subscribe([this](MouseEvent& e) { onMouseMove(e); }); camera_.position.z = 1; } @@ -100,7 +106,7 @@ struct HUD::Impl: Scene, MouseListener { } } - void onMouseDown(int button, const Vector2& pos) override { + void onMouseDown(MouseButtonEvent & e) { raycaster_.setFromCamera(mouse_, camera_); @@ -108,12 +114,12 @@ struct HUD::Impl: Scene, MouseListener { if (!intersects.empty()) { auto front = intersects.front(); if (map_.count(front.object)) { - map_.at(front.object).onMouseDown_(button); + map_.at(front.object).onMouseDown_(e.button); } } } - void onMouseUp(int button, const Vector2& pos) override { + void onMouseUp(MouseButtonEvent & e) { raycaster_.setFromCamera(mouse_, camera_); @@ -121,19 +127,18 @@ struct HUD::Impl: Scene, MouseListener { if (!intersects.empty()) { auto front = intersects.front(); if (map_.count(front.object)) { - map_.at(front.object).onMouseUp_(button); + map_.at(front.object).onMouseUp_(e.button); } } } - void onMouseMove(const Vector2& pos) override { + void onMouseMove(MouseEvent & e) { - mouse_.x = (pos.x / static_cast(size_.width)) * 2 - 1; - mouse_.y = -(pos.y / static_cast(size_.height)) * 2 + 1; + mouse_.x = (e.pos.x / static_cast(size_.width)) * 2 - 1; + mouse_.y = -(e.pos.y / static_cast(size_.height)) * 2 + 1; } ~Impl() override { - eventSource_->removeMouseListener(*this); } private: diff --git a/src/threepp/objects/InstancedMesh.cpp b/src/threepp/objects/InstancedMesh.cpp index b8a32ea48..e95fe0081 100644 --- a/src/threepp/objects/InstancedMesh.cpp +++ b/src/threepp/objects/InstancedMesh.cpp @@ -77,7 +77,7 @@ void InstancedMesh::dispose() { if (!disposed) { disposed = true; - OnDispose.dispatchEvent(Event{ this }); + OnDispose.send(Event{ this }); } } diff --git a/src/threepp/renderers/GLRenderTarget.cpp b/src/threepp/renderers/GLRenderTarget.cpp index 31957dd18..b7edcfcd1 100644 --- a/src/threepp/renderers/GLRenderTarget.cpp +++ b/src/threepp/renderers/GLRenderTarget.cpp @@ -72,7 +72,7 @@ void GLRenderTarget::dispose() { if (!disposed) { disposed = true; - this->OnDispose.dispatchEvent(Event{ this }); + this->OnDispose.send(Event{ this }); } } diff --git a/src/threepp/renderers/GLRenderer.cpp b/src/threepp/renderers/GLRenderer.cpp index a8ab0e99e..0715f97d5 100644 --- a/src/threepp/renderers/GLRenderer.cpp +++ b/src/threepp/renderers/GLRenderer.cpp @@ -589,7 +589,7 @@ struct GLRenderer::Impl { if (programs.empty()) { // new material - material->OnDispose.addEventListenerOneShot( [this](Event& event) { + material->OnDispose.subscribeOnce( [this](Event& event) { this->deallocateMaterial(static_cast(event.target)); }); } diff --git a/src/threepp/renderers/gl/GLGeometries.cpp b/src/threepp/renderers/gl/GLGeometries.cpp index fb31f0bc4..2d3a5748c 100644 --- a/src/threepp/renderers/gl/GLGeometries.cpp +++ b/src/threepp/renderers/gl/GLGeometries.cpp @@ -38,7 +38,7 @@ struct GLGeometries::Impl { if (geometries_.count(geometry) && geometries_.at(geometry)) return; - geometry->OnDispose.addEventListenerOneShot([this](Event& event) { + geometry->OnDispose.subscribeOnce([this](Event& event) { auto geometry = static_cast(event.target); if (geometry->hasIndex()) { diff --git a/src/threepp/renderers/gl/GLObjects.cpp b/src/threepp/renderers/gl/GLObjects.cpp index 04a5dd489..6c382fb20 100644 --- a/src/threepp/renderers/gl/GLObjects.cpp +++ b/src/threepp/renderers/gl/GLObjects.cpp @@ -59,7 +59,7 @@ struct GLObjects::Impl { this->instanceMeshDisposeSubscriptions_.erase(object); }; - instanceMeshDisposeSubscriptions_.insert({object, object->OnDispose.addEventListener( onInstanceMeshDispose)}); + instanceMeshDisposeSubscriptions_.insert({object, object->OnDispose.subscribe( onInstanceMeshDispose)}); } attributes_.update(instancedMesh->instanceMatrix(), GL_ARRAY_BUFFER); diff --git a/src/threepp/renderers/gl/GLTextures.cpp b/src/threepp/renderers/gl/GLTextures.cpp index ca8c9b63d..bc41aa180 100644 --- a/src/threepp/renderers/gl/GLTextures.cpp +++ b/src/threepp/renderers/gl/GLTextures.cpp @@ -206,11 +206,10 @@ void gl::GLTextures::initTexture(TextureProperties* textureProperties, Texture& textureProperties->glInit = true; - texture.OnDispose.addEventListenerOwned([this](Event & event) { + texture.OnDispose.subscribeOnce([this](Event & event) { auto texture = static_cast(event.target); this->deallocateTexture(texture); --this->info->memory.textures; - event.unsubscribe = true; }); GLuint glTexture; @@ -506,7 +505,7 @@ void gl::GLTextures::setupRenderTarget(GLRenderTarget* renderTarget) { auto renderTargetProperties = properties->renderTargetProperties.get(renderTarget->uuid); auto textureProperties = properties->textureProperties.get(texture->uuid); - renderTarget->OnDispose.addEventListenerOneShot( [this](Event& event) { + renderTarget->OnDispose.subscribeOnce( [this](Event& event) { auto renderTarget = static_cast(event.target); this->deallocateRenderTarget(renderTarget); }); diff --git a/src/threepp/textures/Texture.cpp b/src/threepp/textures/Texture.cpp index 44e35fcdb..a037dac94 100644 --- a/src/threepp/textures/Texture.cpp +++ b/src/threepp/textures/Texture.cpp @@ -35,7 +35,7 @@ void Texture::dispose() { if (!disposed_) { disposed_ = true; - this->OnDispose.dispatchEvent(threepp::Event{this}); + this->OnDispose.send(threepp::Event{this}); } } diff --git a/tests/core/EventDispatcher_test.cpp b/tests/core/EventDispatcher_test.cpp index 8de8e56b9..afb650b5d 100644 --- a/tests/core/EventDispatcher_test.cpp +++ b/tests/core/EventDispatcher_test.cpp @@ -7,21 +7,21 @@ using namespace threepp; -TEST_CASE("Test addEventListener") { +TEST_CASE("Test subscribe") { EventDispatcher evt; int nCalls = 0; { - auto s0 = evt.addEventListener( [&](Event& e) {nCalls++; }); + auto s0 = evt.subscribe( [&](Event& e) {nCalls++; }); REQUIRE(nCalls == 0); - evt.dispatchEvent(Event{}); + evt.send(Event{}); REQUIRE(nCalls == 1); - evt.dispatchEvent(Event{}); + evt.send(Event{}); REQUIRE(nCalls == 2); } - evt.dispatchEvent(Event{}); + evt.send(Event{}); REQUIRE(nCalls == 2); } @@ -31,27 +31,27 @@ TEST_CASE("Test addEventListenerOneOwned") { EventDispatcher evt; int nCalls = 0; - evt.addEventListenerOwned([&](Event& e) { + evt.subscribeForever([&](Event& e) { nCalls++; if (nCalls == 2) { e.unsubscribe = true; } }); REQUIRE(nCalls == 0); - evt.dispatchEvent(Event{}); + evt.send(Event{}); REQUIRE(nCalls == 1); - evt.dispatchEvent(Event{}); + evt.send(Event{}); REQUIRE(nCalls == 2); - evt.dispatchEvent(Event{}); + evt.send(Event{}); REQUIRE(nCalls == 2); } -TEST_CASE("Test addEventListenerOneShot") { +TEST_CASE("Test subscribeOnce") { EventDispatcher evt; int nCalls = 0; - evt.addEventListenerOneShot([&](Event& e) {nCalls++; }); + evt.subscribeOnce([&](Event& e) {nCalls++; }); REQUIRE(nCalls == 0); - evt.dispatchEvent(Event{}); + evt.send(Event{}); REQUIRE(nCalls == 1); - evt.dispatchEvent(Event{}); + evt.send(Event{}); REQUIRE(nCalls == 1); } From 0162d1cc9ab060f21d2b4d76078a016b4e7f287a Mon Sep 17 00:00:00 2001 From: Brad Phelan Date: Fri, 1 Mar 2024 21:20:10 +0100 Subject: [PATCH 04/17] fix broken scroll wheel for orbit control --- src/threepp/controls/OrbitControls.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/threepp/controls/OrbitControls.cpp b/src/threepp/controls/OrbitControls.cpp index 5ca5e3d20..5868cefd5 100644 --- a/src/threepp/controls/OrbitControls.cpp +++ b/src/threepp/controls/OrbitControls.cpp @@ -68,6 +68,10 @@ struct OrbitControls::Impl { this->onMouseDown(e, scope); }); + subs_ << canvas.mouse.OnMouseWheel.subscribe([&](MouseWheelEvent& e) { + this->onMouseWheel(e, scope); + }); + update(); } @@ -475,9 +479,9 @@ struct OrbitControls::Impl { } } - void onMouseWheel(const Vector2& delta, OrbitControls & controls) { + void onMouseWheel(MouseWheelEvent & ev , OrbitControls & controls) { if (scope.enabled && scope.enableZoom && !(scope.pimpl_->state != State::NONE && scope.pimpl_->state != State::ROTATE)) { - scope.pimpl_->handleMouseWheel(delta); + scope.pimpl_->handleMouseWheel(ev.offset); } } }; From f0df38e50536d8b237e395338e8ef0cf9759e137 Mon Sep 17 00:00:00 2001 From: Brad Phelan Date: Fri, 1 Mar 2024 21:33:48 +0100 Subject: [PATCH 05/17] shorten the event handling util names --- examples/misc/mouse_key_listener.cpp | 14 +++++++------- examples/misc/raycast.cpp | 2 +- examples/objects/instancing.cpp | 2 +- examples/objects/sprite.cpp | 2 +- examples/projects/Pathfinding/main.cpp | 2 +- examples/projects/Snake/main.cpp | 2 +- include/threepp/input/KeyListener.hpp | 6 +++--- include/threepp/input/MouseListener.hpp | 8 ++++---- src/threepp/canvas/Canvas.cpp | 14 +++++++------- src/threepp/controls/FlyControls.cpp | 12 ++++++------ src/threepp/controls/OrbitControls.cpp | 12 ++++++------ src/threepp/objects/HUD.cpp | 6 +++--- 12 files changed, 41 insertions(+), 41 deletions(-) diff --git a/examples/misc/mouse_key_listener.cpp b/examples/misc/mouse_key_listener.cpp index 795909689..920e8c22b 100644 --- a/examples/misc/mouse_key_listener.cpp +++ b/examples/misc/mouse_key_listener.cpp @@ -45,16 +45,16 @@ int main() { Clock clock; Subscriptions subs_; - subs_ << canvas.mouse.OnMouseDown.subscribe([&](auto& e) { onMouseDown(e, clock.elapsedTime); }); - subs_ << canvas.mouse.OnMouseUp.subscribe([&](auto& e) { onMouseUp(e, clock.elapsedTime); }); - subs_ << canvas.mouse.OnMouseMove.subscribe([&](auto& e) { onMouseMove(e, clock.elapsedTime); }); - subs_ << canvas.mouse.OnMouseWheel.subscribe([&](auto& e) { onMouseWheel(e, clock.elapsedTime); }); + subs_ << canvas.mouse.Down.subscribe([&](auto& e) { onMouseDown(e, clock.elapsedTime); }); + subs_ << canvas.mouse.Up.subscribe([&](auto& e) { onMouseUp(e, clock.elapsedTime); }); + subs_ << canvas.mouse.Move.subscribe([&](auto& e) { onMouseMove(e, clock.elapsedTime); }); + subs_ << canvas.mouse.Wheel.subscribe([&](auto& e) { onMouseWheel(e, clock.elapsedTime); }); Subscriptions key_subs_; auto subscribe_keys = [&]() { - key_subs_ << canvas.keys.OnKeyPressed.subscribe([&](auto& e) {onKeyPressed(e, clock.elapsedTime); }); - key_subs_ << canvas.keys.OnKeyReleased.subscribe([&](auto& e) {onKeyReleased(e, clock.elapsedTime); }); - key_subs_ << canvas.keys.OnKeyRepeat.subscribe([&](auto& e) {onKeyRepeat(e, clock.elapsedTime); }); + key_subs_ << canvas.keys.Pressed.subscribe([&](auto& e) {onKeyPressed(e, clock.elapsedTime); }); + key_subs_ << canvas.keys.Released.subscribe([&](auto& e) {onKeyReleased(e, clock.elapsedTime); }); + key_subs_ << canvas.keys.Repeat.subscribe([&](auto& e) {onKeyRepeat(e, clock.elapsedTime); }); }; bool finish = false; diff --git a/examples/misc/raycast.cpp b/examples/misc/raycast.cpp index 087da5b1c..c6dce599d 100644 --- a/examples/misc/raycast.cpp +++ b/examples/misc/raycast.cpp @@ -52,7 +52,7 @@ int main() { }); Vector2 mouse{-Infinity, -Infinity}; - auto sub = canvas.mouse.OnMouseMove.subscribe([&](MouseMoveEvent& e) { + auto sub = canvas.mouse.Move.subscribe([&](MouseMoveEvent& e) { // calculate mouse position in normalized device coordinates // (-1 to +1) for both components diff --git a/examples/objects/instancing.cpp b/examples/objects/instancing.cpp index 79d3c2147..d1dd6cb55 100644 --- a/examples/objects/instancing.cpp +++ b/examples/objects/instancing.cpp @@ -108,7 +108,7 @@ int main() { }); Vector2 mouse{-Infinity, -Infinity}; - auto sub = canvas.mouse.OnMouseMove.subscribe([&](auto& e) { + auto sub = canvas.mouse.Move.subscribe([&](auto& e) { auto size = canvas.size(); mouse.x = (e.pos.x / static_cast(size.width)) * 2 - 1; mouse.y = -(e.pos.y / static_cast(size.height)) * 2 + 1; diff --git a/examples/objects/sprite.cpp b/examples/objects/sprite.cpp index 38a63919a..1ba853627 100644 --- a/examples/objects/sprite.cpp +++ b/examples/objects/sprite.cpp @@ -98,7 +98,7 @@ int main() { }); Vector2 mouse{-Infinity, -Infinity}; - auto sub = canvas.mouse.OnMouseMove.subscribe([&](MouseMoveEvent& e) { + auto sub = canvas.mouse.Move.subscribe([&](MouseMoveEvent& e) { auto size = canvas.size(); mouse.x = (e.pos.x / static_cast(size.width)) * 2 - 1; mouse.y = -(e.pos.y / static_cast(size.height)) * 2 + 1; diff --git a/examples/projects/Pathfinding/main.cpp b/examples/projects/Pathfinding/main.cpp index 2ce1128af..eeddc2632 100644 --- a/examples/projects/Pathfinding/main.cpp +++ b/examples/projects/Pathfinding/main.cpp @@ -141,7 +141,7 @@ int main() { } }; - auto sub = canvas.mouse.OnMouseUp.subscribe(mouseListener); + auto sub = canvas.mouse.Up.subscribe(mouseListener); canvas.onWindowResize([&](WindowSize s) { camera.left = -size * s.aspect() / 2; diff --git a/examples/projects/Snake/main.cpp b/examples/projects/Snake/main.cpp index 69a1ee35e..3d0e392cc 100644 --- a/examples/projects/Snake/main.cpp +++ b/examples/projects/Snake/main.cpp @@ -13,7 +13,7 @@ int main() { renderer.autoClear = false; auto scene = SnakeScene(game); - auto sub = canvas.keys.OnKeyPressed.subscribe([&](auto& e) {scene.onKeyPressed(e); }); + auto sub = canvas.keys.Pressed.subscribe([&](auto& e) {scene.onKeyPressed(e); }); auto camera = OrthographicCamera::create(0, game.gridSize(), 0, game.gridSize()); camera->position.z = 1; diff --git a/include/threepp/input/KeyListener.hpp b/include/threepp/input/KeyListener.hpp index 7b3814f17..cf87a6c3b 100644 --- a/include/threepp/input/KeyListener.hpp +++ b/include/threepp/input/KeyListener.hpp @@ -22,9 +22,9 @@ namespace threepp { struct Keys { - TEventDispatcher OnKeyPressed; - TEventDispatcher OnKeyReleased; - TEventDispatcher OnKeyRepeat; + TEventDispatcher Pressed; + TEventDispatcher Released; + TEventDispatcher Repeat; }; enum class Key { diff --git a/include/threepp/input/MouseListener.hpp b/include/threepp/input/MouseListener.hpp index 862060f25..55f23d182 100644 --- a/include/threepp/input/MouseListener.hpp +++ b/include/threepp/input/MouseListener.hpp @@ -37,10 +37,10 @@ namespace threepp { }; struct Mouse { - TEventDispatcher OnMouseMove; - TEventDispatcher OnMouseWheel; - TEventDispatcher OnMouseDown; - TEventDispatcher OnMouseUp; + TEventDispatcher Move; + TEventDispatcher Wheel; + TEventDispatcher Down; + TEventDispatcher Up; }; diff --git a/src/threepp/canvas/Canvas.cpp b/src/threepp/canvas/Canvas.cpp index 602c38d01..a4921f7cb 100644 --- a/src/threepp/canvas/Canvas.cpp +++ b/src/threepp/canvas/Canvas.cpp @@ -316,7 +316,7 @@ struct Canvas::Impl { static void scroll_callback(GLFWwindow* w, double xoffset, double yoffset) { auto p = static_cast(glfwGetWindowUserPointer(w)); - p->scope.mouse.OnMouseWheel.send(MouseWheelEvent(Vector2{static_cast(xoffset), static_cast(yoffset)})); + p->scope.mouse.Wheel.send(MouseWheelEvent(Vector2{static_cast(xoffset), static_cast(yoffset)})); } static void mouse_callback(GLFWwindow* w, int button, int action, int) { @@ -324,10 +324,10 @@ struct Canvas::Impl { switch (action) { case GLFW_PRESS: - p->scope.mouse.OnMouseDown.send(MouseButtonEvent(button, p->lastMousePos_)); + p->scope.mouse.Down.send(MouseButtonEvent(button, p->lastMousePos_)); break; case GLFW_RELEASE: - p->scope.mouse.OnMouseUp.send(MouseButtonEvent(button, p->lastMousePos_)); + p->scope.mouse.Up.send(MouseButtonEvent(button, p->lastMousePos_)); break; default: break; @@ -338,7 +338,7 @@ struct Canvas::Impl { auto p = static_cast(glfwGetWindowUserPointer(w)); Vector2 mousePos(static_cast(xpos), static_cast(ypos)); - p->scope.mouse.OnMouseMove.send(MouseMoveEvent(mousePos, mousePos-p->lastMousePos_)); + p->scope.mouse.Move.send(MouseMoveEvent(mousePos, mousePos-p->lastMousePos_)); p->lastMousePos_.copy(mousePos); } @@ -354,15 +354,15 @@ struct Canvas::Impl { KeyEvent evt{glfwKeyCodeToKey(key), scancode, mods}; switch (action) { case GLFW_PRESS: { - p->scope.keys.OnKeyPressed.send(evt); + p->scope.keys.Pressed.send(evt); break; } case GLFW_RELEASE: { - p->scope.keys.OnKeyReleased.send(evt); + p->scope.keys.Released.send(evt); break; } case GLFW_REPEAT: { - p->scope.keys.OnKeyRepeat.send(evt); + p->scope.keys.Repeat.send(evt); break; } default: diff --git a/src/threepp/controls/FlyControls.cpp b/src/threepp/controls/FlyControls.cpp index 011b3a8a4..571daf8dc 100644 --- a/src/threepp/controls/FlyControls.cpp +++ b/src/threepp/controls/FlyControls.cpp @@ -35,14 +35,14 @@ struct FlyControls::Impl { : eventSource(canvas), scope(scope), object(object) { - subs_ << canvas.keys.OnKeyPressed.subscribe([this, &scope](KeyEvent& key) { onKeyPressed(key, scope); }); - subs_ << canvas.keys.OnKeyReleased.subscribe([this, &scope](KeyEvent& key) { onKeyReleased(key, scope); }); + subs_ << canvas.keys.Pressed.subscribe([this, &scope](KeyEvent& key) { onKeyPressed(key, scope); }); + subs_ << canvas.keys.Released.subscribe([this, &scope](KeyEvent& key) { onKeyReleased(key, scope); }); - subs_ << canvas.mouse.OnMouseDown.subscribe([this, &scope](MouseButtonEvent& e) { onMouseDown(e, scope); }); - subs_ << canvas.mouse.OnMouseUp.subscribe([this, &scope](MouseButtonEvent& e) { onMouseUp(e, scope); }); - subs_ << canvas.mouse.OnMouseMove.subscribe([this, &scope](MouseEvent& e) { onMouseMove(e, scope); }); + subs_ << canvas.mouse.Down.subscribe([this, &scope](MouseButtonEvent& e) { onMouseDown(e, scope); }); + subs_ << canvas.mouse.Up.subscribe([this, &scope](MouseButtonEvent& e) { onMouseUp(e, scope); }); + subs_ << canvas.mouse.Move.subscribe([this, &scope](MouseEvent& e) { onMouseMove(e, scope); }); // TODO - //subs_ << canvas.mouse.OnMouseWheel.subscribe([this, &scope](MouseEvent const& e) { onMouseWheel(e, scope); }); + //subs_ << canvas.mouse.Wheel.subscribe([this, &scope](MouseEvent const& e) { onMouseWheel(e, scope); }); } void update(float delta) { diff --git a/src/threepp/controls/OrbitControls.cpp b/src/threepp/controls/OrbitControls.cpp index 5868cefd5..797fca673 100644 --- a/src/threepp/controls/OrbitControls.cpp +++ b/src/threepp/controls/OrbitControls.cpp @@ -61,14 +61,14 @@ struct OrbitControls::Impl { Impl(OrbitControls& scope, PeripheralsEventSource& canvas, Camera& camera) : scope(scope), canvas(canvas), camera(camera) { - subs_ << canvas.keys.OnKeyPressed.subscribe([&](auto& e) mutable { onKeyPressed(e, scope); }); - subs_ << canvas.keys.OnKeyPressed.subscribe([&](auto& e) mutable { onKeyRepeat(e, scope); }); + subs_ << canvas.keys.Pressed.subscribe([&](auto& e) mutable { onKeyPressed(e, scope); }); + subs_ << canvas.keys.Repeat.subscribe([&](auto& e) mutable { onKeyRepeat(e, scope); }); - subs_ << canvas.mouse.OnMouseDown.subscribe([&](MouseButtonEvent& e) { + subs_ << canvas.mouse.Down.subscribe([&](MouseButtonEvent& e) { this->onMouseDown(e, scope); }); - subs_ << canvas.mouse.OnMouseWheel.subscribe([&](MouseWheelEvent& e) { + subs_ << canvas.mouse.Wheel.subscribe([&](MouseWheelEvent& e) { this->onMouseWheel(e, scope); }); @@ -470,10 +470,10 @@ struct OrbitControls::Impl { if (scope.pimpl_->state != State::NONE) { auto& mouse = scope.pimpl_->canvas.mouse; - mouse.OnMouseUp.subscribeOnce([&](MouseButtonEvent& e) { + mouse.Up.subscribeOnce([&](MouseButtonEvent& e) { onMouseUp(e, scope); }); - mouse.OnMouseMove.subscribeUntil(mouse.OnMouseUp, [&](MouseEvent& e) { + mouse.Move.subscribeUntil(mouse.Up, [&](MouseEvent& e) { onMouseMove(e, scope); }); } diff --git a/src/threepp/objects/HUD.cpp b/src/threepp/objects/HUD.cpp index 716c97614..297a55ae2 100644 --- a/src/threepp/objects/HUD.cpp +++ b/src/threepp/objects/HUD.cpp @@ -52,9 +52,9 @@ struct HUD::Impl: Scene { auto& mouse = eventSource->mouse; - subs_ << mouse.OnMouseDown.subscribe([this](MouseButtonEvent& e) { onMouseDown(e); }); - subs_ << mouse.OnMouseUp.subscribe([this](MouseButtonEvent& e) { onMouseUp(e); }); - subs_ << mouse.OnMouseMove.subscribe([this](MouseEvent& e) { onMouseMove(e); }); + subs_ << mouse.Down.subscribe([this](MouseButtonEvent& e) { onMouseDown(e); }); + subs_ << mouse.Up.subscribe([this](MouseButtonEvent& e) { onMouseUp(e); }); + subs_ << mouse.Move.subscribe([this](MouseEvent& e) { onMouseMove(e); }); camera_.position.z = 1; } From 897a009cca4298a06e0a5a11ea719716e99b8850 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sun, 3 Mar 2024 16:44:03 +0100 Subject: [PATCH 06/17] implement ioCapture --- include/threepp/input/PeripheralsEventSource.hpp | 14 +++++++++++++- src/threepp/canvas/Canvas.cpp | 6 ++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/include/threepp/input/PeripheralsEventSource.hpp b/include/threepp/input/PeripheralsEventSource.hpp index 5a7b89350..985d35510 100644 --- a/include/threepp/input/PeripheralsEventSource.hpp +++ b/include/threepp/input/PeripheralsEventSource.hpp @@ -19,13 +19,25 @@ namespace threepp { void setIOCapture(IOCapture* capture); + bool preventMouseEvent() const { + return ioCapture_ && ioCapture_->preventMouseEvent(); + } + + bool preventKeyboardEvent() const { + return ioCapture_ && ioCapture_->preventKeyboardEvent(); + } + + bool preventScrollEvent() const { + return ioCapture_ && ioCapture_->preventScrollEvent(); + } + // Events for keys Keys keys; // Events for mouse Mouse mouse; - virtual ~PeripheralsEventSource() = default; + virtual ~PeripheralsEventSource() = default; protected: enum class KeyAction { diff --git a/src/threepp/canvas/Canvas.cpp b/src/threepp/canvas/Canvas.cpp index a4921f7cb..9d6ca83f6 100644 --- a/src/threepp/canvas/Canvas.cpp +++ b/src/threepp/canvas/Canvas.cpp @@ -315,6 +315,7 @@ struct Canvas::Impl { static void scroll_callback(GLFWwindow* w, double xoffset, double yoffset) { auto p = static_cast(glfwGetWindowUserPointer(w)); + if (p->scope.preventScrollEvent()) return; p->scope.mouse.Wheel.send(MouseWheelEvent(Vector2{static_cast(xoffset), static_cast(yoffset)})); } @@ -336,15 +337,16 @@ struct Canvas::Impl { static void cursor_callback(GLFWwindow* w, double xpos, double ypos) { auto p = static_cast(glfwGetWindowUserPointer(w)); + if (p->scope.preventMouseEvent()) return; Vector2 mousePos(static_cast(xpos), static_cast(ypos)); - p->scope.mouse.Move.send(MouseMoveEvent(mousePos, mousePos-p->lastMousePos_)); + p->scope.mouse.Move.send(MouseMoveEvent(mousePos, mousePos - p->lastMousePos_)); p->lastMousePos_.copy(mousePos); } static void key_callback(GLFWwindow* w, int key, int scancode, int action, int mods) { - auto p = static_cast(glfwGetWindowUserPointer(w)); + if (p->scope.preventKeyboardEvent()) return; if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS && p->exitOnKeyEscape_) { glfwSetWindowShouldClose(w, GLFW_TRUE); From 44a76b1b4922f89a470dd9c381fec7f24b263501 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sun, 3 Mar 2024 16:45:20 +0100 Subject: [PATCH 07/17] implement keys for Youbot --- examples/projects/Youbot/Youbot.hpp | 6 +++--- examples/projects/Youbot/youbot.cpp | 5 ++++- examples/projects/Youbot/youbot_kine.cpp | 4 +++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/projects/Youbot/Youbot.hpp b/examples/projects/Youbot/Youbot.hpp index dcbe94844..370889d1d 100644 --- a/examples/projects/Youbot/Youbot.hpp +++ b/examples/projects/Youbot/Youbot.hpp @@ -8,9 +8,9 @@ using namespace threepp; -struct Youbot: Object3D, KeyListener { +struct Youbot: Object3D { - void onKeyPressed(KeyEvent evt) override { + void onKeyPressed(KeyEvent evt) { if (evt.key == Key::W) { keyState_.up = true; } else if (evt.key == Key::S) { @@ -22,7 +22,7 @@ struct Youbot: Object3D, KeyListener { } } - void onKeyReleased(KeyEvent evt) override { + void onKeyReleased(KeyEvent evt) { if (evt.key == Key::W) { keyState_.up = false; } else if (evt.key == Key::S) { diff --git a/examples/projects/Youbot/youbot.cpp b/examples/projects/Youbot/youbot.cpp index b334d4d11..c85452291 100644 --- a/examples/projects/Youbot/youbot.cpp +++ b/examples/projects/Youbot/youbot.cpp @@ -44,10 +44,13 @@ int main() { utils::ThreadPool pool; std::shared_ptr youbot; + std::vector subs; pool.submit([&] { youbot = Youbot::create("data/models/collada/youbot.dae"); + canvas.invokeLater([&] { - canvas.addKeyListener(*youbot); + subs.emplace_back(canvas.keys.Pressed.subscribe([youbot](auto& e) { youbot->onKeyPressed(e); })); + subs.emplace_back(canvas.keys.Released.subscribe([youbot](auto& e) { youbot->onKeyReleased(e); })); scene->add(youbot); handle.setText("Use WASD keys to steer robot", opts); }); diff --git a/examples/projects/Youbot/youbot_kine.cpp b/examples/projects/Youbot/youbot_kine.cpp index 0e1f47f4b..0b5444d48 100644 --- a/examples/projects/Youbot/youbot_kine.cpp +++ b/examples/projects/Youbot/youbot_kine.cpp @@ -106,13 +106,15 @@ int main() { utils::ThreadPool pool; std::shared_ptr youbot; + std::vector subs; pool.submit([&] { youbot = Youbot::create("data/models/collada/youbot.dae"); youbot->add(targetHelper); youbot->add(endEffectorHelper); endEffectorHelper->visible = true; canvas.invokeLater([&] { - canvas.addKeyListener(*youbot); + subs.emplace_back(canvas.keys.Pressed.subscribe([youbot](auto& e){youbot->onKeyPressed(e);})); + subs.emplace_back(canvas.keys.Released.subscribe([youbot](auto& e){youbot->onKeyReleased(e);})); scene->add(youbot); hud.remove(handle); }); From 44cb47634c993c5c92d424f8dba17024ffd88163 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sun, 3 Mar 2024 19:13:20 +0100 Subject: [PATCH 08/17] Fix for instancing crash --- src/threepp/renderers/gl/GLObjects.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/threepp/renderers/gl/GLObjects.cpp b/src/threepp/renderers/gl/GLObjects.cpp index 6c382fb20..51b2ae792 100644 --- a/src/threepp/renderers/gl/GLObjects.cpp +++ b/src/threepp/renderers/gl/GLObjects.cpp @@ -25,7 +25,7 @@ struct GLObjects::Impl { std::unordered_map updateMap_; /// Track subscriptions to instanceMeshDispose - std::unordered_map instanceMeshDisposeSubscriptions_; + std::unordered_map instanceMeshDisposeSubscriptions_; Impl(GLGeometries& geometries, GLAttributes& attributes, GLInfo& info) : attributes_(attributes), @@ -49,17 +49,19 @@ struct GLObjects::Impl { if (auto instancedMesh = object->as()) { - if (instanceMeshDisposeSubscriptions_.find(object) == instanceMeshDisposeSubscriptions_.end()) { + if (!instanceMeshDisposeSubscriptions_.count(object)) { - auto onInstanceMeshDispose = [this,object](Event& event) { + auto onInstanceMeshDispose = [this, object](Event& event) { auto instancedMesh = static_cast(event.target); this->attributes_.remove(instancedMesh->instanceMatrix()); if (instancedMesh->instanceColor()) this->attributes_.remove(instancedMesh->instanceColor()); + // Remove our subscription this->instanceMeshDisposeSubscriptions_.erase(object); + event.unsubscribe = true; }; - - instanceMeshDisposeSubscriptions_.insert({object, object->OnDispose.subscribe( onInstanceMeshDispose)}); + object->OnDispose.subscribeForever(onInstanceMeshDispose); + instanceMeshDisposeSubscriptions_.insert({object, true}); } attributes_.update(instancedMesh->instanceMatrix(), GL_ARRAY_BUFFER); From b64cae4564617ee1661bedd81174c5ee7c1a52c0 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sun, 3 Mar 2024 19:42:44 +0100 Subject: [PATCH 09/17] missing conversions --- examples/misc/morphtargets.cpp | 7 +++---- examples/objects/decal.cpp | 17 +++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/misc/morphtargets.cpp b/examples/misc/morphtargets.cpp index 5abea2af7..30c2e393c 100644 --- a/examples/misc/morphtargets.cpp +++ b/examples/misc/morphtargets.cpp @@ -107,15 +107,14 @@ int main() { }); Vector2 mouse{-Infinity, -Infinity}; - MouseMoveListener l([&](Vector2 pos) { + auto sub = canvas.mouse.Move.subscribe([&](MouseMoveEvent& evt) { // calculate mouse position in normalized device coordinates // (-1 to +1) for both components auto size = canvas.size(); - mouse.x = (pos.x / static_cast(size.width)) * 2 - 1; - mouse.y = -(pos.y / static_cast(size.height)) * 2 + 1; + mouse.x = (evt.pos.x / static_cast(size.width)) * 2 - 1; + mouse.y = -(evt.pos.y / static_cast(size.height)) * 2 + 1; }); - canvas.addMouseListener(l); Box3 box; auto helper = Box3Helper::create(box); diff --git a/examples/objects/decal.cpp b/examples/objects/decal.cpp index 13768dcd2..8dc6ed2c2 100644 --- a/examples/objects/decal.cpp +++ b/examples/objects/decal.cpp @@ -30,12 +30,12 @@ namespace { return decalMaterial; } - class MyMouseListener: public MouseListener { + class MyMouseListener { public: Vector2 mouse{-Infinity, -Infinity}; - explicit MyMouseListener(Canvas& canvas): canvas(canvas) {} + explicit MyMouseListener(PeripheralsEventSource& canvas): canvas(canvas) {} bool mouseClick() { if (mouseDown) { @@ -46,18 +46,18 @@ namespace { } } - void onMouseDown(int button, const Vector2& pos) override { - if (button == 0) {// left mousebutton + void onMouseDown(MouseButtonEvent evt) { + if (evt.button == 0) {// left mousebutton mouseDown = true; } } - void onMouseMove(const Vector2& pos) override { - updateMousePos(pos); + void onMouseMove(MouseMoveEvent evt) { + updateMousePos(evt.pos); } private: - Canvas& canvas; + PeripheralsEventSource& canvas; bool mouseDown = false; void updateMousePos(Vector2 pos) { @@ -141,7 +141,8 @@ int main() { scene->add(line); MyMouseListener mouseListener(canvas); - canvas.addMouseListener(mouseListener); + auto sub = canvas.mouse.Move.subscribe([&mouseListener](auto& evt) {mouseListener.onMouseMove(evt);}); + auto sub1 = canvas.mouse.Down.subscribe([&mouseListener](auto& evt) {mouseListener.onMouseDown(evt);}); canvas.onWindowResize([&](WindowSize size) { camera->aspect = size.aspect(); From 769a744c58f0f5849f12488b30d790e9d1e36e5d Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sun, 3 Mar 2024 19:43:14 +0100 Subject: [PATCH 10/17] missing conversions --- examples/objects/decal.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/objects/decal.cpp b/examples/objects/decal.cpp index 8dc6ed2c2..bb673cf0b 100644 --- a/examples/objects/decal.cpp +++ b/examples/objects/decal.cpp @@ -141,8 +141,8 @@ int main() { scene->add(line); MyMouseListener mouseListener(canvas); - auto sub = canvas.mouse.Move.subscribe([&mouseListener](auto& evt) {mouseListener.onMouseMove(evt);}); - auto sub1 = canvas.mouse.Down.subscribe([&mouseListener](auto& evt) {mouseListener.onMouseDown(evt);}); + canvas.mouse.Move.subscribeForever([&mouseListener](auto& evt) {mouseListener.onMouseMove(evt);}); + canvas.mouse.Down.subscribeForever([&mouseListener](auto& evt) {mouseListener.onMouseDown(evt);}); canvas.onWindowResize([&](WindowSize size) { camera->aspect = size.aspect(); From 927534b8ff2a25b636258e62d636a880921ac41d Mon Sep 17 00:00:00 2001 From: Brad Phelan Date: Mon, 4 Mar 2024 09:22:05 +0100 Subject: [PATCH 11/17] #239 Fix UB in event dispatching --- include/threepp/core/EventDispatcher.hpp | 53 +++++++++++++++++------- tests/core/EventDispatcher_test.cpp | 6 ++- tests/utils/CMakeLists.txt | 2 + 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/include/threepp/core/EventDispatcher.hpp b/include/threepp/core/EventDispatcher.hpp index 6ead38a8d..5be24150b 100644 --- a/include/threepp/core/EventDispatcher.hpp +++ b/include/threepp/core/EventDispatcher.hpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include namespace threepp { @@ -29,7 +29,7 @@ namespace threepp { using TEventListener = std::function; /// A single subscription for an event - using Subscription = std::shared_ptr; + using Subscription = threepp::utils::ScopeExit; /// For holding a large number of subscriptions to events using Subscriptions= std::vector; @@ -43,20 +43,26 @@ namespace threepp { /// Generic event dispatch class template #if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) + // C++20 (and later) code requires concepts::Event #endif - // C++20 (and later) code class TEventDispatcher { + private: + void unsubscribe(size_t id) { + if (!sending_) + listeners_.erase(id); + else + to_unsubscribe_.push_back(id); + } public: using EventListener = TEventListener; /// Adds an event listener and returns a subscription [[nodiscard]] Subscription subscribe(EventListener listener) { - size_t current_id = id_.load(); - listeners_.insert({ current_id, listener }); - Subscription disposer((void*) nullptr, [this, current_id](void*) { listeners_.erase(current_id); }); - id_ = id_ + 1; - return disposer; + size_t current_id = id_; + id_++; + listeners_.insert({current_id, listener}); + return utils::at_scope_exit([this, current_id]() { unsubscribe(current_id); }); } /// Adds an event listener and never automatically unsubscribes. You @@ -64,7 +70,7 @@ namespace threepp { /// cancelled. Not recommended to be used directly. Build other /// tools on this. void subscribeForever(EventListener listener) { - size_t current_id = id_.load(); + size_t current_id = id_; listeners_.insert({ current_id, listener }); id_ = id_ + 1; } @@ -85,16 +91,28 @@ namespace threepp { /// Send an event to all listeners. void send(TEvent & e){ - std::vector toUnsubscribe; - for (auto const& item : listeners_) { - item.second(e); + /// Mark that we are in the sending state. + auto tmp = utils::reset_at_scope_exit(sending_, true); + + for(auto it = listeners_.begin(); it != listeners_.end();) + { + it->second(e); if (e.unsubscribe) { e.unsubscribe = false; - toUnsubscribe.push_back(item.first); + it = listeners_.erase(it); } + else + it++; } - for (size_t id : toUnsubscribe) + + // Unsubscribe listeners that disposed of their + // subscription by calling unsubscribe directly + // during the event sending phase above. + for(size_t id : to_unsubscribe_) listeners_.erase(id); + + if (to_unsubscribe_.size() > 0) + to_unsubscribe_.clear(); } /// Handle r-value versions of send @@ -106,7 +124,12 @@ namespace threepp { private: std::unordered_map listeners_; - std::atomic id_ = 0; + /// The id of the current listener + size_t id_ = 0; + /// Are we sending event? Used to detect unsubscriptions during sending. + bool sending_ = false; + /// Subscriptions to be delay unsubscribed. + std::vector to_unsubscribe_; }; diff --git a/tests/core/EventDispatcher_test.cpp b/tests/core/EventDispatcher_test.cpp index afb650b5d..d3d13c6aa 100644 --- a/tests/core/EventDispatcher_test.cpp +++ b/tests/core/EventDispatcher_test.cpp @@ -21,7 +21,7 @@ TEST_CASE("Test subscribe") { evt.send(Event{}); REQUIRE(nCalls == 2); } - evt.send(Event{}); + evt.send(Event{}); REQUIRE(nCalls == 2); } @@ -55,3 +55,7 @@ TEST_CASE("Test subscribeOnce") { evt.send(Event{}); REQUIRE(nCalls == 1); } + +TEST_CASE("Unsubscribe due to message") { + +} diff --git a/tests/utils/CMakeLists.txt b/tests/utils/CMakeLists.txt index 02b5842c8..1a2a81e41 100644 --- a/tests/utils/CMakeLists.txt +++ b/tests/utils/CMakeLists.txt @@ -1,2 +1,4 @@ add_test_executable(StringUtils_test) +add_test_executable(ScopeUtils_test) + From aca27f9ca2b65d997deb5d04103d09cb38060e19 Mon Sep 17 00:00:00 2001 From: Brad Phelan Date: Mon, 4 Mar 2024 11:31:40 +0100 Subject: [PATCH 12/17] #239 Fix UB in event dispatching --- include/threepp/utils/Scope.hpp | 35 +++++++++++++++++++++++++++++++++ tests/utils/ScopeUtils_test.cpp | 32 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 include/threepp/utils/Scope.hpp create mode 100644 tests/utils/ScopeUtils_test.cpp diff --git a/include/threepp/utils/Scope.hpp b/include/threepp/utils/Scope.hpp new file mode 100644 index 000000000..7a140b485 --- /dev/null +++ b/include/threepp/utils/Scope.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +namespace threepp::utils { + using ScopeExit = std::shared_ptr; + + /// Execute the function when the last copy of the returned value goes out of scope + template + [[nodiscard]] + ScopeExit at_scope_exit(TFn && fn) + { + return std::shared_ptr((void*) (nullptr), [fn=std::forward(fn)](auto) { + fn(); + }); + } + + /// Set @p var with @p value when the last copy of the returned value goes out of scope + template + [[nodiscard]] + ScopeExit set_at_scope_exit(T& var, T && value) { + return at_scope_exit([&var, value = std::forward(value)](){ + var = value; + }); + } + + /// Set @p var with @newValue immediately and then reset @p var with it's original value with the + /// last copy of the returned value goes out of scope. + template + [[nodiscard]] + ScopeExit reset_at_scope_exit(T& var, T && newValue) { + T oldValue = std::move(var); + var = std::forward(newValue); + return set_at_scope_exit(var, std::move(oldValue)); + } + +}// namespace threepp::utils \ No newline at end of file diff --git a/tests/utils/ScopeUtils_test.cpp b/tests/utils/ScopeUtils_test.cpp new file mode 100644 index 000000000..59760d1cd --- /dev/null +++ b/tests/utils/ScopeUtils_test.cpp @@ -0,0 +1,32 @@ +#include "threepp/utils/Scope.hpp" +#include + +TEST_CASE("scope exit") +{ + bool v = true; + { + auto tmp = threepp::utils::at_scope_exit([&] {v = false; }); + } + + REQUIRE(v == false); + +} + +TEST_CASE("set at scope exit") +{ + bool v = true; + { + auto tmp = threepp::utils::set_at_scope_exit(v, false); + } + REQUIRE(v == false); +} + +TEST_CASE("reset at scope exit") +{ + int i = 0; + { + auto tmp = threepp::utils::reset_at_scope_exit(i, 10); + REQUIRE(i == 10); + } + REQUIRE(i == 0); +} From eb198be84ea94c61ae421160342e53bb900415a0 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Thu, 14 Mar 2024 10:11:32 +0100 Subject: [PATCH 13/17] update new code --- examples/extras/physics/PxEngine.hpp | 35 ++++++++------------------ examples/extras/physics/physx_demo.cpp | 19 ++++++++------ 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/examples/extras/physics/PxEngine.hpp b/examples/extras/physics/PxEngine.hpp index 864efb949..ee7d734a9 100644 --- a/examples/extras/physics/PxEngine.hpp +++ b/examples/extras/physics/PxEngine.hpp @@ -45,8 +45,7 @@ class PxEngine: public threepp::Object3D { explicit PxEngine(float timeStep = 1.f / 100) : timeStep(timeStep), - sceneDesc(physics->getTolerancesScale()), - onMeshRemovedListener(this) { + sceneDesc(physics->getTolerancesScale()) { sceneDesc.gravity = physx::PxVec3(0.0f, -9.81f, 0.0f); sceneDesc.flags |= physx::PxSceneFlag::eENABLE_PCM; @@ -255,7 +254,15 @@ class PxEngine: public threepp::Object3D { } obj.matrixAutoUpdate = false; - obj.addEventListener("remove", &onMeshRemovedListener); + + obj.OnRemove.subscribeOnce([this](threepp::Event& event) { + auto m = static_cast(event.target); + if (bodies.count(m)) { + auto rb = bodies.at(m); + scene->removeActor(*rb.front()); + bodies.erase(m); + } + }); } physx::PxJoint* createJoint(const JointCreate& create, threepp::Object3D* o1, threepp::Object3D* o2, threepp::Vector3 anchor, threepp::Vector3 axis) { @@ -549,28 +556,6 @@ class PxEngine: public threepp::Object3D { } } } - - struct MeshRemovedListener: threepp::EventListener { - - explicit MeshRemovedListener(PxEngine* scope): scope(scope) {} - - void onEvent(threepp::Event& event) override { - if (event.type == "remove") { - auto m = static_cast(event.target); - if (scope->bodies.count(m)) { - auto rb = scope->bodies.at(m); - scope->scene->removeActor(*rb.front()); - scope->bodies.erase(m); - } - m->removeEventListener("remove", this); - } - } - - private: - PxEngine* scope; - }; - - MeshRemovedListener onMeshRemovedListener; }; #endif//THREEPP_PXENGINE_HPP diff --git a/examples/extras/physics/physx_demo.cpp b/examples/extras/physics/physx_demo.cpp index 12a658f21..7bb416c04 100644 --- a/examples/extras/physics/physx_demo.cpp +++ b/examples/extras/physics/physx_demo.cpp @@ -9,7 +9,7 @@ using namespace threepp; namespace { - struct KeyController: KeyListener { + struct KeyController { explicit KeyController(std::vector joints) : joints(std::move(joints)) { @@ -19,8 +19,8 @@ namespace { } } - void onKeyPressed(KeyEvent evt) override; - void onKeyReleased(KeyEvent evt) override; + void onKeyPressed(KeyEvent evt); + void onKeyReleased(KeyEvent evt); private: float speed = 2; @@ -211,14 +211,15 @@ int main() { auto* j3 = engine.getJoint(*box3); KeyController keyListener({j1, j2, j3}); - canvas.addKeyListener(keyListener); + canvas.keys.Pressed.subscribeForever([&](KeyEvent evt) { keyListener.onKeyPressed(evt); }); + canvas.keys.Released.subscribeForever([&](KeyEvent evt) { keyListener.onKeyReleased(evt); }); OrbitControls controls(camera, canvas); bool run = false; - KeyAdapter adapter(KeyAdapter::Mode::KEY_PRESSED | KeyAdapter::KEY_REPEAT, [&](KeyEvent evt) { - run = true; + canvas.keys.Pressed.subscribeOnce([&](KeyEvent) { run = true; }); + auto keyController = [&](KeyEvent evt){ if (evt.key == Key::SPACE) { auto obj = spawnObject(); obj->position = camera.position; @@ -236,8 +237,10 @@ int main() { } else if (evt.key == Key::D) { engine.debugVisualisation = !engine.debugVisualisation; } - }); - canvas.addKeyListener(adapter); + }; + + canvas.keys.Pressed.subscribeForever(keyController); + canvas.keys.Repeat.subscribeForever(keyController); canvas.onWindowResize([&](WindowSize size) { camera.aspect = size.aspect(); From 9c447c8d7e0e7f63232da0edb1bf1ec00863b499 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Thu, 14 Mar 2024 10:27:17 +0100 Subject: [PATCH 14/17] formatting and refactor --- include/threepp/core/EventDispatcher.hpp | 55 +++---- include/threepp/utils/Scope.hpp | 29 ++-- src/threepp/controls/FlyControls.cpp | 137 +++++++++-------- src/threepp/controls/OrbitControls.cpp | 178 +++++++++++------------ 4 files changed, 196 insertions(+), 203 deletions(-) diff --git a/include/threepp/core/EventDispatcher.hpp b/include/threepp/core/EventDispatcher.hpp index 5be24150b..c903fd7d0 100644 --- a/include/threepp/core/EventDispatcher.hpp +++ b/include/threepp/core/EventDispatcher.hpp @@ -3,26 +3,26 @@ #ifndef THREEPP_EVENTDISPATCHER_HPP #define THREEPP_EVENTDISPATCHER_HPP +#include +#include #include #include +#include #include #include -#include -#include -#include namespace threepp { namespace concepts { -#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) +#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) template concept Event = requires(TEvent e) { { e.target } -> std::convertible_to; { e.unsubscribe } -> std::convertible_to; }; #endif - } + }// namespace concepts /// An event listener is just a function that takes an argument of type TEvent template @@ -32,28 +32,29 @@ namespace threepp { using Subscription = threepp::utils::ScopeExit; /// For holding a large number of subscriptions to events - using Subscriptions= std::vector; + using Subscriptions = std::vector; /// Allows one to use the << to push subscriptions onto the vector - inline - void operator<<(std::vector& subs, Subscription const & sub) { + inline void operator<<(std::vector& subs, Subscription const& sub) { subs.push_back(sub); } /// Generic event dispatch class - template -#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) + template +#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) // C++20 (and later) code - requires concepts::Event + requires concepts::Event #endif class TEventDispatcher { private: void unsubscribe(size_t id) { - if (!sending_) + if (!sending_) { listeners_.erase(id); - else + } else { to_unsubscribe_.push_back(id); + } } + public: using EventListener = TEventListener; @@ -71,7 +72,7 @@ namespace threepp { /// tools on this. void subscribeForever(EventListener listener) { size_t current_id = id_; - listeners_.insert({ current_id, listener }); + listeners_.insert({current_id, listener}); id_ = id_ + 1; } @@ -82,7 +83,7 @@ namespace threepp { /// Hold onto the other subscription until the second source fires then /// dispose the subscription. - template + template void subscribeUntil(TEventDispatcher& s, EventListener listener) { auto sub = subscribe(listener); s.subscribeOnce([sub](auto&) {}); @@ -90,33 +91,34 @@ namespace threepp { /// Send an event to all listeners. - void send(TEvent & e){ + void send(TEvent& e) { /// Mark that we are in the sending state. auto tmp = utils::reset_at_scope_exit(sending_, true); - for(auto it = listeners_.begin(); it != listeners_.end();) - { + for (auto it = listeners_.begin(); it != listeners_.end();) { it->second(e); if (e.unsubscribe) { e.unsubscribe = false; it = listeners_.erase(it); - } - else + } else { it++; + } } // Unsubscribe listeners that disposed of their // subscription by calling unsubscribe directly - // during the event sending phase above. - for(size_t id : to_unsubscribe_) + // during the event sending phase above. + for (size_t id : to_unsubscribe_) { listeners_.erase(id); + } - if (to_unsubscribe_.size() > 0) + if (to_unsubscribe_.size() > 0) { to_unsubscribe_.clear(); + } } /// Handle r-value versions of send - void send(TEvent && e) { + void send(TEvent&& e) { send(e); } @@ -125,7 +127,7 @@ namespace threepp { private: std::unordered_map listeners_; /// The id of the current listener - size_t id_ = 0; + size_t id_ = 0; /// Are we sending event? Used to detect unsubscriptions during sending. bool sending_ = false; /// Subscriptions to be delay unsubscribed. @@ -137,9 +139,8 @@ namespace threepp { void* target; bool unsubscribe = false; }; - class EventDispatcher : public TEventDispatcher { - }; + using EventDispatcher = TEventDispatcher; }// namespace threepp diff --git a/include/threepp/utils/Scope.hpp b/include/threepp/utils/Scope.hpp index 7a140b485..0f4be045d 100644 --- a/include/threepp/utils/Scope.hpp +++ b/include/threepp/utils/Scope.hpp @@ -1,35 +1,38 @@ -#pragma once + +#ifndef THREEPP_UTILS_SCOPE_HPP +#define THREEPP_UTILS_SCOPE_HPP + #include + namespace threepp::utils { + using ScopeExit = std::shared_ptr; /// Execute the function when the last copy of the returned value goes out of scope - template - [[nodiscard]] - ScopeExit at_scope_exit(TFn && fn) - { - return std::shared_ptr((void*) (nullptr), [fn=std::forward(fn)](auto) { + template + [[nodiscard]] ScopeExit at_scope_exit(TFn&& fn) { + return std::shared_ptr((void*) (nullptr), [fn = std::forward(fn)](auto) { fn(); }); } /// Set @p var with @p value when the last copy of the returned value goes out of scope template - [[nodiscard]] - ScopeExit set_at_scope_exit(T& var, T && value) { - return at_scope_exit([&var, value = std::forward(value)](){ + [[nodiscard]] ScopeExit set_at_scope_exit(T& var, T&& value) { + return at_scope_exit([&var, value = std::forward(value)]() { var = value; }); } - /// Set @p var with @newValue immediately and then reset @p var with it's original value with the + /// Set @p var with @newValue immediately and then reset @p var with it's original value with the /// last copy of the returned value goes out of scope. template - [[nodiscard]] - ScopeExit reset_at_scope_exit(T& var, T && newValue) { + [[nodiscard]] ScopeExit reset_at_scope_exit(T& var, T&& newValue) { T oldValue = std::move(var); var = std::forward(newValue); return set_at_scope_exit(var, std::move(oldValue)); } -}// namespace threepp::utils \ No newline at end of file +}// namespace threepp::utils + +#endif//THREEPP_UTILS_SCOPE_HPP diff --git a/src/threepp/controls/FlyControls.cpp b/src/threepp/controls/FlyControls.cpp index 571daf8dc..19fe5afbe 100644 --- a/src/threepp/controls/FlyControls.cpp +++ b/src/threepp/controls/FlyControls.cpp @@ -32,17 +32,14 @@ namespace { struct FlyControls::Impl { Impl(FlyControls& scope, PeripheralsEventSource& canvas, Object3D* object) - : eventSource(canvas), scope(scope), object(object) - { + : eventSource(canvas), scope(scope), object(object) { - subs_ << canvas.keys.Pressed.subscribe([this, &scope](KeyEvent& key) { onKeyPressed(key, scope); }); - subs_ << canvas.keys.Released.subscribe([this, &scope](KeyEvent& key) { onKeyReleased(key, scope); }); + subs_ << canvas.keys.Pressed.subscribe([this](auto& key) { onKeyPressed(key); }); + subs_ << canvas.keys.Released.subscribe([this](auto& key) { onKeyReleased(key); }); - subs_ << canvas.mouse.Down.subscribe([this, &scope](MouseButtonEvent& e) { onMouseDown(e, scope); }); - subs_ << canvas.mouse.Up.subscribe([this, &scope](MouseButtonEvent& e) { onMouseUp(e, scope); }); - subs_ << canvas.mouse.Move.subscribe([this, &scope](MouseEvent& e) { onMouseMove(e, scope); }); - // TODO - //subs_ << canvas.mouse.Wheel.subscribe([this, &scope](MouseEvent const& e) { onMouseWheel(e, scope); }); + subs_ << canvas.mouse.Down.subscribe([this](auto& e) { onMouseDown(e); }); + subs_ << canvas.mouse.Up.subscribe([this](auto& e) { onMouseUp(e); }); + subs_ << canvas.mouse.Move.subscribe([this](auto& e) { onMouseMove(e); }); } void update(float delta) { @@ -82,7 +79,7 @@ struct FlyControls::Impl { rotationVector.z = (-moveState.rollRight + moveState.rollLeft); } - static void onKeyPressed(KeyEvent evt, FlyControls& scope) { + void onKeyPressed(KeyEvent evt) { if (evt.mods == 4) {// altKey @@ -91,158 +88,158 @@ struct FlyControls::Impl { switch (evt.key) { case Key::W: - scope.pimpl_->moveState.forward = 1; + moveState.forward = 1; break; case Key::S: - scope.pimpl_->moveState.back = 1; + moveState.back = 1; break; case Key::A: - scope.pimpl_->moveState.left = 1; + moveState.left = 1; break; case Key::D: - scope.pimpl_->moveState.right = 1; + moveState.right = 1; break; case Key::R: - scope.pimpl_->moveState.up = 1; + moveState.up = 1; break; case Key::F: - scope.pimpl_->moveState.down = 1; + moveState.down = 1; break; case Key::UP: - scope.pimpl_->moveState.pitchUp = 1; + moveState.pitchUp = 1; break; case Key::DOWN: - scope.pimpl_->moveState.pitchDown = 1; + moveState.pitchDown = 1; break; case Key::LEFT: - scope.pimpl_->moveState.yawLeft = 1; + moveState.yawLeft = 1; break; case Key::RIGHT: - scope.pimpl_->moveState.yawRight = 1; + moveState.yawRight = 1; break; case Key::Q: - scope.pimpl_->moveState.rollLeft = 1; + moveState.rollLeft = 1; break; case Key::E: - scope.pimpl_->moveState.rollRight = 1; + moveState.rollRight = 1; break; default: break; } - scope.pimpl_->updateMovementVector(); - scope.pimpl_->updateRotationVector(); + updateMovementVector(); + updateRotationVector(); } - static void onKeyReleased(KeyEvent evt, FlyControls& scope) { + void onKeyReleased(KeyEvent evt) { switch (evt.key) { case Key::W: - scope.pimpl_->moveState.forward = 0; + moveState.forward = 0; break; case Key::S: - scope.pimpl_->moveState.back = 0; + moveState.back = 0; break; case Key::A: - scope.pimpl_->moveState.left = 0; + moveState.left = 0; break; case Key::D: - scope.pimpl_->moveState.right = 0; + moveState.right = 0; break; case Key::R: - scope.pimpl_->moveState.up = 0; + moveState.up = 0; break; case Key::F: - scope.pimpl_->moveState.down = 0; + moveState.down = 0; break; case Key::UP: - scope.pimpl_->moveState.pitchUp = 0; + moveState.pitchUp = 0; break; case Key::DOWN: - scope.pimpl_->moveState.pitchDown = 0; + moveState.pitchDown = 0; break; case Key::LEFT: - scope.pimpl_->moveState.yawLeft = 0; + moveState.yawLeft = 0; break; case Key::RIGHT: - scope.pimpl_->moveState.yawRight = 0; + moveState.yawRight = 0; break; case Key::Q: - scope.pimpl_->moveState.rollLeft = 0; + moveState.rollLeft = 0; break; case Key::E: - scope.pimpl_->moveState.rollRight = 0; + moveState.rollRight = 0; break; default: break; } - scope.pimpl_->updateMovementVector(); - scope.pimpl_->updateRotationVector(); + updateMovementVector(); + updateRotationVector(); } - static void onMouseDown(MouseButtonEvent & e, FlyControls&scope) { + void onMouseDown(MouseButtonEvent& e) { - if (scope.dragToLook) { + if (scope.dragToLook) { - scope.pimpl_->mouseStatus++; + mouseStatus++; - } else { + } else { - switch (e.button) { + switch (e.button) { - case 0: - scope.pimpl_->moveState.forward = 1; - break; - case 1: - scope.pimpl_->moveState.back = 1; - break; - } + case 0: + moveState.forward = 1; + break; + case 1: + moveState.back = 1; + break; + } - scope.pimpl_->updateMovementVector(); - } - } + updateMovementVector(); + } + } - static void onMouseMove(MouseEvent & e, FlyControls&scope) { + void onMouseMove(MouseEvent& e) { - if (!scope.dragToLook || scope.pimpl_->mouseStatus > 0) { + if (!scope.dragToLook || mouseStatus > 0) { - const float halfWidth = static_cast(scope.pimpl_->eventSource.size().width) / 2; - const float halfHeight = static_cast(scope.pimpl_->eventSource.size().height) / 2; + const float halfWidth = static_cast(eventSource.size().width) / 2; + const float halfHeight = static_cast(eventSource.size().height) / 2; - scope.pimpl_->moveState.yawLeft = -((e.pos.x) - halfWidth) / halfWidth; - scope.pimpl_->moveState.pitchDown = ((e.pos.y) - halfHeight) / halfHeight; + moveState.yawLeft = -((e.pos.x) - halfWidth) / halfWidth; + moveState.pitchDown = ((e.pos.y) - halfHeight) / halfHeight; - scope.pimpl_->updateRotationVector(); - } - } + updateRotationVector(); + } + } - static void onMouseUp(MouseButtonEvent& e, FlyControls& scope) { + void onMouseUp(MouseButtonEvent& e) { if (scope.dragToLook) { - scope.pimpl_->mouseStatus--; + mouseStatus--; - scope.pimpl_->moveState.yawLeft = scope.pimpl_->moveState.pitchDown = 0; + moveState.yawLeft = moveState.pitchDown = 0; } else { switch (e.button) { case 0: - scope.pimpl_->moveState.forward = 0; + moveState.forward = 0; break; case 1: - scope.pimpl_->moveState.back = 0; + moveState.back = 0; break; } - scope.pimpl_->updateMovementVector(); + updateMovementVector(); } - scope.pimpl_->updateRotationVector(); + updateRotationVector(); } private: diff --git a/src/threepp/controls/OrbitControls.cpp b/src/threepp/controls/OrbitControls.cpp index 797fca673..64080eea0 100644 --- a/src/threepp/controls/OrbitControls.cpp +++ b/src/threepp/controls/OrbitControls.cpp @@ -59,18 +59,13 @@ struct OrbitControls::Impl { Subscriptions subs_; Impl(OrbitControls& scope, PeripheralsEventSource& canvas, Camera& camera) - : scope(scope), canvas(canvas), camera(camera) - { - subs_ << canvas.keys.Pressed.subscribe([&](auto& e) mutable { onKeyPressed(e, scope); }); - subs_ << canvas.keys.Repeat.subscribe([&](auto& e) mutable { onKeyRepeat(e, scope); }); + : scope(scope), canvas(canvas), camera(camera) { - subs_ << canvas.mouse.Down.subscribe([&](MouseButtonEvent& e) { - this->onMouseDown(e, scope); - }); + subs_ << canvas.keys.Pressed.subscribe([this](auto& e) { onKeyPressed(e); }); + subs_ << canvas.keys.Repeat.subscribe([this](auto& e) { onKeyRepeat(e); }); - subs_ << canvas.mouse.Wheel.subscribe([&](MouseWheelEvent& e) { - this->onMouseWheel(e, scope); - }); + subs_ << canvas.mouse.Down.subscribe([this](auto& e) { onMouseDown(e); }); + subs_ << canvas.mouse.Wheel.subscribe([this](auto& e) { onMouseWheel(e); }); update(); } @@ -395,95 +390,92 @@ struct OrbitControls::Impl { scope.update(); } - ~Impl() { + + void onKeyPressed(KeyEvent evt) { + if (scope.enabled && scope.enableKeys && scope.enablePan) { + handleKeyDown(evt.key); + } + } + + void onKeyRepeat(KeyEvent evt) { + if (scope.enabled && scope.enableKeys && scope.enablePan) { + handleKeyDown(evt.key); + } + } + + void onMouseMove(MouseEvent& e) { + if (scope.enabled) { + switch (state) { + case State::ROTATE: + if (scope.enableRotate) { + handleMouseMoveRotate(e.pos); + } + break; + case State::DOLLY: + if (scope.enableZoom) { + handleMouseMoveDolly(e.pos); + } + break; + case State::PAN: + if (scope.enablePan) { + handleMouseMovePan(e.pos); + } + break; + default: + //TODO ? + break; + } + } } - static void onKeyPressed(KeyEvent evt, OrbitControls & controls) { - if (controls.enabled && controls.enableKeys && controls.enablePan) { - controls.pimpl_->handleKeyDown(evt.key); - } - } - - static void onKeyRepeat(KeyEvent evt, OrbitControls & controls) { - if (controls.enabled && controls.enableKeys && controls.enablePan) { - controls.pimpl_->handleKeyDown(evt.key); - } - } - - static void onMouseMove(MouseEvent & e, OrbitControls & controls) { - if (controls.enabled) { - switch (controls.pimpl_->state) { - case State::ROTATE: - if (controls.enableRotate) { - controls.pimpl_->handleMouseMoveRotate(e.pos); - } - break; - case State::DOLLY: - if (controls.enableZoom) { - controls.pimpl_->handleMouseMoveDolly(e.pos); - } - break; - case State::PAN: - if (controls.enablePan) { - controls.pimpl_->handleMouseMovePan(e.pos); - } - break; - default: - //TODO ? - break; - } - } - } - - - void onMouseUp(MouseButtonEvent e,OrbitControls & controls) { - if (scope.enabled) { - scope.pimpl_->state = State::NONE; - } - } - - void onMouseDown(MouseButtonEvent e, OrbitControls & scope) { - if (scope.enabled) { - switch (e.button) { - case 0:// LEFT - if (scope.enableRotate) { - scope.pimpl_->handleMouseDownRotate(e.pos); - scope.pimpl_->state = State::ROTATE; - } - break; - case 1:// RIGHT - if (scope.enablePan) { - scope.pimpl_->handleMouseDownRotate(e.pos); - scope.pimpl_->handleMouseDownPan(e.pos); - scope.pimpl_->state = State::PAN; - } - break; - case 2:// MIDDLE - if (scope.enableZoom) { - scope.pimpl_->handleMouseDownDolly(e.pos); - scope.pimpl_->state = State::DOLLY; - } - break; - } - } - - if (scope.pimpl_->state != State::NONE) { + void onMouseUp() { + if (scope.enabled) { + scope.pimpl_->state = State::NONE; + } + } + + void onMouseDown(MouseButtonEvent e) { + if (scope.enabled) { + switch (e.button) { + case 0:// LEFT + if (scope.enableRotate) { + scope.pimpl_->handleMouseDownRotate(e.pos); + scope.pimpl_->state = State::ROTATE; + } + break; + case 1:// RIGHT + if (scope.enablePan) { + scope.pimpl_->handleMouseDownRotate(e.pos); + scope.pimpl_->handleMouseDownPan(e.pos); + scope.pimpl_->state = State::PAN; + } + break; + case 2:// MIDDLE + if (scope.enableZoom) { + scope.pimpl_->handleMouseDownDolly(e.pos); + scope.pimpl_->state = State::DOLLY; + } + break; + } + } + + if (scope.pimpl_->state != State::NONE) { auto& mouse = scope.pimpl_->canvas.mouse; - mouse.Up.subscribeOnce([&](MouseButtonEvent& e) { - onMouseUp(e, scope); - }); + mouse.Up.subscribeOnce([&](MouseButtonEvent&) { + onMouseUp(); + }); mouse.Move.subscribeUntil(mouse.Up, [&](MouseEvent& e) { - onMouseMove(e, scope); + onMouseMove(e); }); - } - } - - void onMouseWheel(MouseWheelEvent & ev , OrbitControls & controls) { - if (scope.enabled && scope.enableZoom && !(scope.pimpl_->state != State::NONE && scope.pimpl_->state != State::ROTATE)) { - scope.pimpl_->handleMouseWheel(ev.offset); - } - } + } + } + + void onMouseWheel(MouseWheelEvent& ev) { + if (scope.enabled && scope.enableZoom && !(scope.pimpl_->state != State::NONE && scope.pimpl_->state != State::ROTATE)) { + scope.pimpl_->handleMouseWheel(ev.offset); + } + } }; OrbitControls::OrbitControls(Camera& camera, PeripheralsEventSource& eventSource) From 5755cf90e42530879c065b1c4485801a0894128c Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Tue, 19 Mar 2024 21:51:17 +0100 Subject: [PATCH 15/17] adapt new code --- examples/projects/MapView/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/projects/MapView/main.cpp b/examples/projects/MapView/main.cpp index 7b3e427dd..a056a70c7 100644 --- a/examples/projects/MapView/main.cpp +++ b/examples/projects/MapView/main.cpp @@ -41,7 +41,8 @@ int main() { std::cout << "Switch between map providers with keys 1 (OpenStreetMaps), 2 (Bing - Road), 3 (Bing - Arial) and 4 (Debug)\n" << std::endl; - KeyAdapter keyAdapter(KeyAdapter::Mode::KEY_PRESSED, [&](KeyEvent event) { + + canvas.keys.Pressed.subscribeForever([&](auto& event) { if (event.key == Key::NUM_1) { map.setProvider(std::make_unique()); } else if (event.key == Key::NUM_2) { @@ -52,7 +53,6 @@ int main() { map.setProvider(std::make_unique(&renderer)); } }); - canvas.addKeyListener(keyAdapter); canvas.onWindowResize([&](WindowSize size) { camera.aspect = size.aspect(); From a9c25db9221b62037090be20ddc38781fedd8159 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sat, 23 Mar 2024 19:24:21 +0100 Subject: [PATCH 16/17] adapt new code --- examples/misc/lut.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/misc/lut.cpp b/examples/misc/lut.cpp index 6128969d8..6697e7a70 100644 --- a/examples/misc/lut.cpp +++ b/examples/misc/lut.cpp @@ -161,7 +161,7 @@ int main() { plane->add(wireframe); scene.add(plane); - KeyAdapter keyAdapter(KeyAdapter::Mode::KEY_PRESSED, [&](KeyEvent evt) { + canvas.keys.Pressed.subscribeForever([&](KeyEvent evt) { if (evt.key == Key::NUM_1) { applyFunc(*planeGeometry, rosenbrock); applyFunc(*planeGeometry2, rosenbrock); @@ -194,7 +194,6 @@ int main() { normalizeAndApplyLut(*planeGeometry, 2); normalizeAndApplyLut(*planeGeometry2, 2); }); - canvas.addKeyListener(keyAdapter); canvas.onWindowResize([&](WindowSize size) { camera.aspect = size.aspect(); From 8acd6ff9ccbaf96f24499eaa50aaa5f7efd4ee49 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sun, 24 Mar 2024 09:46:35 +0100 Subject: [PATCH 17/17] Adapt DragControls --- examples/controls/drag.cpp | 32 ++++++++------ include/threepp/controls/DragControls.hpp | 15 ++++--- src/threepp/controls/DragControls.cpp | 52 ++++++++++------------- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/examples/controls/drag.cpp b/examples/controls/drag.cpp index f773d59e1..9f6b6eb8f 100644 --- a/examples/controls/drag.cpp +++ b/examples/controls/drag.cpp @@ -64,18 +64,21 @@ int main() { DragControls controls(objects, camera, canvas); controls.rotateSpeed = 2; - struct HoverListener: public EventListener { - void onEvent(Event& event) override { + struct HoverListener { + void hoverOn(Event& event) { auto target = static_cast(event.target); auto& color = target->material()->as()->color; - if (event.type == "hoveron") { - prevColor = color; - color = Color::white; - } else if (event.type == "hoveroff") { - color = prevColor; - } + prevColor = color; + color = Color::white; + } + + void hoverOff(Event& event) { + auto target = static_cast(event.target); + auto& color = target->material()->as()->color; + + color = prevColor; } private: @@ -83,10 +86,15 @@ int main() { } hoverListener; - controls.addEventListener("hoveron", &hoverListener); - controls.addEventListener("hoveroff", &hoverListener); + controls.HoverOn.subscribeForever([&hoverListener](auto& evt) { + hoverListener.hoverOn(evt); + }); + controls.HoverOff.subscribeForever([&hoverListener](auto& evt) { + hoverListener.hoverOff(evt); + }); - KeyAdapter keyAdapter(KeyAdapter::Mode::KEY_PRESSED, [&](KeyEvent evt){ + + canvas.keys.Pressed.subscribeForever([&](KeyEvent evt) { if (evt.key == Key::M) { if (controls.mode == DragControls::Mode::Translate) { controls.mode = DragControls::Mode::Rotate; @@ -95,7 +103,7 @@ int main() { } } }); - canvas.addKeyListener(keyAdapter); + canvas.onWindowResize([&](WindowSize size) { camera.aspect = size.aspect(); diff --git a/include/threepp/controls/DragControls.hpp b/include/threepp/controls/DragControls.hpp index cd06bcadc..e9bae8d84 100644 --- a/include/threepp/controls/DragControls.hpp +++ b/include/threepp/controls/DragControls.hpp @@ -14,16 +14,14 @@ namespace threepp { class Object3D; class PeripheralsEventSource; - class DragControls : public EventDispatcher { + class DragControls { public: - enum class Mode { Translate, Rotate }; - bool enabled = true; bool recursive = true; bool transformGroup = false; @@ -36,14 +34,19 @@ namespace threepp { void setObjects(const std::vector& objects); - ~DragControls() override; + ~DragControls(); + + EventDispatcher HoverOn; + EventDispatcher HoverOff; + EventDispatcher Drag; + EventDispatcher Dragstart; + EventDispatcher Dragend; private: struct Impl; std::unique_ptr pimpl_; - }; -} +}// namespace threepp #endif//THREEPP_DRAGCONTROLS_HPP diff --git a/src/threepp/controls/DragControls.cpp b/src/threepp/controls/DragControls.cpp index d482ceca3..67cdff7f1 100644 --- a/src/threepp/controls/DragControls.cpp +++ b/src/threepp/controls/DragControls.cpp @@ -10,7 +10,7 @@ using namespace threepp; -struct DragControls::Impl: public MouseListener { +struct DragControls::Impl { Impl(DragControls& scope, const std::vector& objects, Camera& camera, PeripheralsEventSource& eventSource) : scope(&scope), _objects(objects), _camera(&camera), eventSource(&eventSource) { @@ -18,22 +18,6 @@ struct DragControls::Impl: public MouseListener { activate(); } - void onMouseMove(const Vector2& pos) override { - onPointerMove(pos); - } - - void onMouseDown(int button, const Vector2& pos) override { - if (button == 0) { - onPointerDown(pos); - } - } - - void onMouseUp(int button, const Vector2& pos) override { - if (button == 0) { - onPointerCancel(); - } - } - void onPointerMove(Vector2 pos) { if (scope->enabled == false) return; @@ -59,7 +43,7 @@ struct DragControls::Impl: public MouseListener { _selected->rotateOnWorldAxis(_right.normalize(), -_diff.y); } - scope->dispatchEvent("drag", _selected); + scope->Drag.send({_selected}); _previousPointer.copy(_pointer); @@ -80,14 +64,14 @@ struct DragControls::Impl: public MouseListener { if (_hovered && _hovered != object) { - scope->dispatchEvent("hoveroff", _hovered); + scope->HoverOff.send({_hovered}); _hovered = nullptr; } if (_hovered != object) { - scope->dispatchEvent("hoveron", object); + scope->HoverOn.send({object}); _hovered = object; } @@ -96,7 +80,7 @@ struct DragControls::Impl: public MouseListener { if (_hovered) { - scope->dispatchEvent("hoveroff", _hovered); + scope->HoverOff.send({_hovered}); _hovered = nullptr; } @@ -148,7 +132,7 @@ struct DragControls::Impl: public MouseListener { } } - scope->dispatchEvent("dragstart", _selected); + scope->Dragstart.send({_selected}); } _previousPointer.copy(_pointer); @@ -160,7 +144,7 @@ struct DragControls::Impl: public MouseListener { if (_selected) { - scope->dispatchEvent("dragend", _selected); + scope->Dragend.send({_selected}); _selected = nullptr; } @@ -185,16 +169,24 @@ struct DragControls::Impl: public MouseListener { void activate() { - eventSource->addMouseListener(*this); + _subs << eventSource->mouse.Down.subscribe([this](auto evt) { + if (evt.button == 0) { + onPointerDown(evt.pos); + } + }); + _subs << eventSource->mouse.Up.subscribe([this](auto evt) { + if (evt.button == 0) { + onPointerCancel(); + } + }); + _subs << eventSource->mouse.Move.subscribe([this](auto evt) { + onPointerMove(evt.pos); + }); } void deactivate() { - eventSource->removeMouseListener(*this); - } - - ~Impl() override { - deactivate(); + _subs.clear(); } private: @@ -210,6 +202,8 @@ struct DragControls::Impl: public MouseListener { Mode mode{Mode::Translate}; + Subscriptions _subs; + Plane _plane; Raycaster _raycaster;