Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use subscription based events handling #237

Open
wants to merge 27 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
37e0a8c
Used a lambda based events
Mar 1, 2024
9349d97
Merge branch 'dev' into events
markaren Mar 1, 2024
4797df2
Create individual dispatcher for each event
Mar 1, 2024
2b906e4
Add mouse support and refactor.
Mar 1, 2024
0162d1c
fix broken scroll wheel for orbit control
Mar 1, 2024
f0df38e
shorten the event handling util names
Mar 1, 2024
095fbaf
Merge branch 'dev' into events
markaren Mar 2, 2024
897a009
implement ioCapture
markaren Mar 3, 2024
44a76b1
implement keys for Youbot
markaren Mar 3, 2024
44cb476
Fix for instancing crash
markaren Mar 3, 2024
b64cae4
missing conversions
markaren Mar 3, 2024
769a744
missing conversions
markaren Mar 3, 2024
927534b
#239 Fix UB in event dispatching
Mar 4, 2024
03675b2
Merge branch 'events' of github.com:bradphelan/threepp into events
Mar 4, 2024
aca27f9
#239 Fix UB in event dispatching
Mar 4, 2024
66ef3ee
Merge branch 'dev' into events
markaren Mar 4, 2024
3c8baca
Merge branch 'dev' into events
markaren Mar 4, 2024
764f47d
Merge branch 'dev' into events
markaren Mar 5, 2024
f606bc0
Merge branch 'dev' into events
markaren Mar 14, 2024
eb198be
update new code
markaren Mar 14, 2024
9c447c8
formatting and refactor
markaren Mar 14, 2024
014b9cc
Merge branch 'dev' into events
markaren Mar 19, 2024
5755cf9
adapt new code
markaren Mar 19, 2024
e569f77
Merge branch 'dev' into events
markaren Mar 23, 2024
a9c25db
adapt new code
markaren Mar 23, 2024
f6338b1
Merge branch 'dev' into events
markaren Mar 24, 2024
8acd6ff
Adapt DragControls
markaren Mar 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 38 additions & 45 deletions examples/misc/mouse_key_listener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.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.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;
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;
}
});
Expand Down
7 changes: 3 additions & 4 deletions examples/misc/raycast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,14 @@ int main() {
});

Vector2 mouse{-Infinity<float>, -Infinity<float>};
MouseMoveListener l([&](Vector2 pos) {
auto sub = canvas.mouse.Move.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<float>(size.width)) * 2 - 1;
mouse.y = -(pos.y / static_cast<float>(size.height)) * 2 + 1;
mouse.x = (e.pos.x / static_cast<float>(size.width)) * 2 - 1;
mouse.y = -(e.pos.y / static_cast<float>(size.height)) * 2 + 1;
});
canvas.addMouseListener(l);

Raycaster raycaster;
raycaster.params.lineThreshold = 0.1f;
Expand Down
7 changes: 3 additions & 4 deletions examples/objects/instancing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,11 @@ int main() {
});

Vector2 mouse{-Infinity<float>, -Infinity<float>};
MouseMoveListener l([&](auto& pos) {
auto sub = canvas.mouse.Move.subscribe([&](auto& e) {
auto size = canvas.size();
mouse.x = (pos.x / static_cast<float>(size.width)) * 2 - 1;
mouse.y = -(pos.y / static_cast<float>(size.height)) * 2 + 1;
mouse.x = (e.pos.x / static_cast<float>(size.width)) * 2 - 1;
mouse.y = -(e.pos.y / static_cast<float>(size.height)) * 2 + 1;
});
canvas.addMouseListener(l);


Clock clock;
Expand Down
7 changes: 3 additions & 4 deletions examples/objects/sprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,11 @@ int main() {
});

Vector2 mouse{-Infinity<float>, -Infinity<float>};
MouseMoveListener l([&](auto& pos) {
auto sub = canvas.mouse.Move.subscribe([&](MouseMoveEvent& e) {
auto size = canvas.size();
mouse.x = (pos.x / static_cast<float>(size.width)) * 2 - 1;
mouse.y = -(pos.y / static_cast<float>(size.height)) * 2 + 1;
mouse.x = (e.pos.x / static_cast<float>(size.width)) * 2 - 1;
mouse.y = -(e.pos.y / static_cast<float>(size.height)) * 2 + 1;
});
canvas.addMouseListener(l);

Clock clock;
Raycaster raycaster;
Expand Down
11 changes: 6 additions & 5 deletions examples/projects/Pathfinding/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,12 @@ int main() {
Raycaster raycaster;
raycaster.layers.set(1); // ignore grid
Vector2 mouse{-Infinity<float>, -Infinity<float>};
MouseUpListener mouseListener([&](int button, Vector2 pos) {
TEventListener<MouseButtonEvent> mouseListener = [&](MouseButtonEvent & e) {
if (start && target) return;

const auto s = canvas.size();
mouse.x = (pos.x / static_cast<float>(s.width)) * 2 - 1;
mouse.y = -(pos.y / static_cast<float>(s.height)) * 2 + 1;
mouse.x = (e.pos.x / static_cast<float>(s.width)) * 2 - 1;
mouse.y = -(e.pos.y / static_cast<float>(s.height)) * 2 + 1;

raycaster.setFromCamera(mouse, camera);
auto intersects = raycaster.intersectObjects(scene.children);
Expand Down Expand Up @@ -139,8 +139,9 @@ int main() {
}
}
}
});
canvas.addMouseListener(mouseListener);
};

auto sub = canvas.mouse.Up.subscribe(mouseListener);

canvas.onWindowResize([&](WindowSize s) {
camera.left = -size * s.aspect() / 2;
Expand Down
4 changes: 2 additions & 2 deletions examples/projects/Snake/SnakeScene.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using namespace threepp;


class SnakeScene: public Scene, public KeyListener {
class SnakeScene: public Scene {

public:
explicit SnakeScene(SnakeGame& game): game_(game) {
Expand All @@ -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()) {

Expand Down
2 changes: 1 addition & 1 deletion examples/projects/Snake/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ int main() {
renderer.autoClear = false;

auto scene = SnakeScene(game);
canvas.addKeyListener(scene);
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;
Expand Down
6 changes: 4 additions & 2 deletions include/threepp/core/BufferGeometry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@

namespace threepp {

class BufferGeometry: public EventDispatcher {
class BufferGeometry {

public:
EventDispatcher OnDispose;

const unsigned int id{++_id};

const std::string uuid;
Expand Down Expand Up @@ -128,7 +130,7 @@ namespace threepp {

[[nodiscard]] std::shared_ptr<BufferGeometry> clone() const;

~BufferGeometry() override;
~BufferGeometry();

static std::shared_ptr<BufferGeometry> create();

Expand Down
121 changes: 99 additions & 22 deletions include/threepp/core/EventDispatcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,115 @@
#include <string>
#include <unordered_map>
#include <vector>
#include <functional>
#include <algorithm>
#include <atomic>


namespace threepp {

struct Event {
namespace concepts {
#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L)
template<typename TEvent>
concept Event = requires(TEvent e) {
{ e.target } -> std::convertible_to<void*>;
{ e.unsubscribe } -> std::convertible_to<bool>;
};
#endif
}

/// An event listener is just a function that takes an argument of type TEvent
template<typename TEvent>
using TEventListener = std::function<void(TEvent&)>;

/// A single subscription for an event
using Subscription = std::shared_ptr<void>;

/// For holding a large number of subscriptions to events
using Subscriptions= std::vector<Subscription>;

/// Allows one to use the << to push subscriptions onto the vector
inline
void operator<<(std::vector<Subscription>& subs, Subscription const & sub) {
subs.push_back(sub);
}

/// Generic event dispatch class
template <typename TEvent>
#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L)
requires concepts::Event<TEvent>
#endif
// C++20 (and later) code
bradphelan marked this conversation as resolved.
Show resolved Hide resolved
class TEventDispatcher {
public:
using EventListener = TEventListener<TEvent>;

/// 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;
}

/// 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) {
markaren marked this conversation as resolved.
Show resolved Hide resolved
size_t current_id = id_.load();
listeners_.insert({ current_id, listener });
id_ = id_ + 1;
}

/// 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 <typename T>
void subscribeUntil(TEventDispatcher<T>& s, EventListener listener) {
auto sub = subscribe(listener);
s.subscribeOnce([sub](auto&) {});
}


/// Send an event to all listeners.
void send(TEvent & e){
std::vector<size_t> 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);
}

/// Handle r-value versions of send
void send(TEvent && e) {
send(e);
}

virtual ~TEventDispatcher() = default;

const std::string type;
void* target;
private:
std::unordered_map<size_t, EventListener> listeners_;
std::atomic<size_t> id_ = 0;
};

struct EventListener {

virtual void onEvent(Event& event) = 0;

virtual ~EventListener() = default;
struct Event {
void* target;
bool unsubscribe = false;
};
class EventDispatcher : public TEventDispatcher<Event> {

class EventDispatcher {

public:
void addEventListener(const std::string& type, EventListener* listener);

bool hasEventListener(const std::string& type, const EventListener* listener);

void removeEventListener(const std::string& type, const EventListener* listener);

void dispatchEvent(const std::string& type, void* target = nullptr);

virtual ~EventDispatcher() = default;

private:
std::unordered_map<std::string, std::vector<EventListener*>> listeners_;
};

}// namespace threepp
Expand Down
9 changes: 7 additions & 2 deletions include/threepp/core/Object3D.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -254,7 +254,12 @@ namespace threepp {

virtual std::shared_ptr<Object3D> clone(bool recursive = true);

~Object3D() override;
EventDispatcher OnAdded;
EventDispatcher OnRemove;
EventDispatcher OnDispose;
Copy link
Owner

@markaren markaren Mar 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have preferred to keep this generic. I.e. OnDispose is not apart of the Object3D API.
How is other types (user defined or future additions) handled?


virtual ~Object3D() ;


private:
inline static unsigned int _object3Did{0};
Expand Down
Loading