Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ CTestTestfile.cmake
_deps/

# IDEs and Editors
.clangd
.vscode/
.idea/
*.swp
Expand Down
38 changes: 38 additions & 0 deletions include/events/Delegate.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef DELEGATE_HPP
#define DELEGATE_HPP

#include <cstddef>
#include <memory>

#include "events/EventListener.hpp"
#include "events/Subscription.hpp"

struct Context;

class Delegate {
public:
Delegate() : store(std::make_shared<ListenerStore>()) {}
~Delegate() = default;

Delegate(const Delegate&) = delete;
Delegate& operator=(const Delegate&) = delete;
Delegate(Delegate&&) = delete;
Delegate& operator=(Delegate&&) = delete;

Subscription subscribe(EventListener* listener) {
const std::size_t subscriptionId = store->nextId++;
store->listeners.push_back({listener, subscriptionId});
return Subscription(store, subscriptionId);
}

void invoke(const Context& ctx) {
for (const auto& entry : store->listeners) {
entry.listener->onEventTriggered(ctx);
}
}

private:
std::shared_ptr<ListenerStore> store;
};

#endif // DELEGATE_HPP
19 changes: 19 additions & 0 deletions include/events/EventListener.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef EVENTLISTENER_HPP
#define EVENTLISTENER_HPP

struct Context;

class EventListener {
public:
EventListener() = default;
virtual ~EventListener() = default;

EventListener(const EventListener&) = delete;
EventListener& operator=(const EventListener&) = delete;
EventListener(EventListener&&) = delete;
EventListener& operator=(EventListener&&) = delete;

virtual void onEventTriggered(const Context& ctx) = 0;
};

#endif // EVENTLISTENER_HPP
24 changes: 24 additions & 0 deletions include/events/MarkerEventLink.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef MARKEREVENTLINK_HPP
#define MARKEREVENTLINK_HPP

#include <string>
#include <utility>

#include "data_structures/Context.hpp"
#include "events/EventListener.hpp"

class MarkerEventLink : public EventListener {
public:
explicit MarkerEventLink(std::string name) : eventName(std::move(name)) {}

void onEventTriggered(const Context& ctx) override {
if (ctx.markers != nullptr) {
ctx.markers->push_back(eventName);
}
}

private:
std::string eventName;
};

#endif // MARKEREVENTLINK_HPP
50 changes: 50 additions & 0 deletions include/events/Subscription.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#ifndef SUBSCRIPTION_HPP
#define SUBSCRIPTION_HPP

#include <cstddef>
#include <memory>
#include <utility>
#include <vector>

class EventListener;

// Shared listener bookkeeping. A Delegate owns one of these via std::shared_ptr and hands a
// std::weak_ptr to each Subscription it creates, so a Subscription can detect when its Delegate
// has already been destroyed and skip unsubscribing (a no-op instead of UB).
struct ListenerStore {
struct Entry {
EventListener* listener;
std::size_t subscriptionId;
};

std::vector<Entry> listeners;
std::size_t nextId = 0;
};

// RAII handle returned by Delegate::subscribe. Unsubscribes from its Delegate on destruction;
// if the Delegate is already gone the operation is a no-op.
class Subscription {
public:
Subscription(std::weak_ptr<ListenerStore> store, std::size_t subscriptionId)
: store(std::move(store)), subscriptionId(subscriptionId) {}

~Subscription() {
if (auto lockedStore = store.lock()) {
std::erase_if(lockedStore->listeners,
[subscriptionId = subscriptionId](const ListenerStore::Entry& entry) {
return entry.subscriptionId == subscriptionId;
});
}
}

Subscription(const Subscription&) = delete;
Subscription& operator=(const Subscription&) = delete;
Subscription(Subscription&&) noexcept = default;
Subscription& operator=(Subscription&&) noexcept = default;

private:
std::weak_ptr<ListenerStore> store;
std::size_t subscriptionId;
};

#endif // SUBSCRIPTION_HPP
5 changes: 5 additions & 0 deletions include/scene/Scene.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

#include <memory>
#include <string>
#include <string_view>
#include <vector>

class Delegate;
class SceneObject;
class SDL_Renderer;
struct Context;
Expand All @@ -24,6 +26,9 @@ class Scene {

void update(const Context& ctx);
void render(SDL_Renderer* renderer);

void onSceneReady();
Delegate& resolveEvents(std::string_view objectName, std::string_view eventName);
};

#endif // SCENE_HPP
9 changes: 9 additions & 0 deletions include/scene/components/Component.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
#define COMPONENT_HPP

#include <memory>
#include <string_view>

class Delegate;
class Scene;
class SceneObject;
class SDL_Renderer;
struct Context;
Expand All @@ -21,6 +24,12 @@ class Component {
virtual void update(const Context& context) = 0;
virtual void render(SDL_Renderer* renderer) = 0;

virtual void onSceneReady(Scene& scene) { (void)scene; }
virtual Delegate* getEvent(std::string_view eventName) {
(void)eventName;
return nullptr;
}

protected:
// NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)
std::weak_ptr<SceneObject> owner;
Expand Down
54 changes: 54 additions & 0 deletions include/scene/components/MarkerEmitterComponent.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef MARKEREMITTERCOMPONENT_HPP
#define MARKEREMITTERCOMPONENT_HPP

#include <memory>
#include <string>
#include <vector>

#include "events/MarkerEventLink.hpp"
#include "events/Subscription.hpp"
#include "scene/components/Component.hpp"

class Scene;

namespace NeuronIDE {
class Component;
}

class MarkerEmitterComponent : public Component {
public:
struct Binding {
std::string targetObject;
std::string targetEvent;
std::string markerName;
};

MarkerEmitterComponent(const std::shared_ptr<SceneObject>& owner,
std::vector<Binding> bindings);
~MarkerEmitterComponent() override = default;

MarkerEmitterComponent(const MarkerEmitterComponent&) = delete;
MarkerEmitterComponent& operator=(const MarkerEmitterComponent&) = delete;
MarkerEmitterComponent(MarkerEmitterComponent&&) = delete;
MarkerEmitterComponent& operator=(MarkerEmitterComponent&&) = delete;

void update(const Context& context) override;
void render(SDL_Renderer* renderer) override;
void onSceneReady(Scene& scene) override;

static std::unique_ptr<Component> createMarkerEmitter(
const NeuronIDE::Component& protoComp, const std::shared_ptr<SceneObject>& owner);

private:
struct ActiveSubscription {
// Declaration order matters: the Subscription is destroyed first, unsubscribing before
// the listener it points to is freed.
std::unique_ptr<MarkerEventLink> link;
Subscription subscription;
};

std::vector<Binding> bindings;
std::vector<ActiveSubscription> activeSubscriptions;
Comment thread
M1KUS3Q marked this conversation as resolved.
};

#endif // MARKEREMITTERCOMPONENT_HPP
10 changes: 10 additions & 0 deletions protoFiles/neuronide.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ message ScriptComponent {
string script_path = 1;
}

message MarkerEmitterComponent {
message Binding {
string target_object = 1;
string target_event = 2;
string marker_name = 3;
}
repeated Binding bindings = 1;
}

message Transform {
double x = 1;
double y = 2;
Expand All @@ -35,6 +44,7 @@ message Component {
TextRenderer text = 2;
BlinkComponent blinker = 3;
ScriptComponent script = 4;
MarkerEmitterComponent marker_emitter = 5;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/parser/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ std::shared_ptr<Scene> Parser::parseStream(std::istream& stream) {
scene->addObject(std::move(obj));
}

scene->onSceneReady();

return scene;
}

Expand Down
1 change: 1 addition & 0 deletions src/scene/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ add_library(scene OBJECT
Scene.cpp
components/ComponentRegistry.cpp
components/BlinkComponent.cpp
components/MarkerEmitterComponent.cpp
)
target_include_directories(scene PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../include)
target_link_libraries(scene PUBLIC neuronide_proto)
30 changes: 30 additions & 0 deletions src/scene/Scene.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#include "scene/Scene.hpp"

#include <stdexcept>

#include "events/Delegate.hpp"
#include "scene/SceneObject.hpp"
#include "scene/components/Component.hpp"

void Scene::update(const Context& ctx) {
for (const auto& obj : objects) {
Expand All @@ -14,4 +18,30 @@ void Scene::render(SDL_Renderer* renderer) {
obj->render(renderer);
}
}
}

void Scene::onSceneReady() {
for (const auto& obj : objects) {
for (const auto& comp : obj->components) {
comp->onSceneReady(*this);
}
}
}

Delegate& Scene::resolveEvents(std::string_view objectName, std::string_view eventName) {
for (const auto& obj : objects) {
if (obj->name != objectName) {
continue;
}
for (const auto& comp : obj->components) {
auto* delegate = comp->getEvent(eventName);
if (delegate != nullptr) {
return *delegate;
}
}
throw std::runtime_error("Scene::resolveEvents: event '" + std::string(eventName) +
"' not found on object '" + std::string(objectName) + "'");
}
throw std::runtime_error("Scene::resolveEvents: object '" + std::string(objectName) +
"' not found");
}
38 changes: 38 additions & 0 deletions src/scene/components/MarkerEmitterComponent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "scene/components/MarkerEmitterComponent.hpp"

#include "events/Delegate.hpp"
#include "neuronide.pb.h"
#include "scene/Scene.hpp"
#include "scene/components/ComponentRegistry.hpp"

MarkerEmitterComponent::MarkerEmitterComponent(const std::shared_ptr<SceneObject>& owner,
std::vector<Binding> bindings)
: Component(owner), bindings(std::move(bindings)) {}

void MarkerEmitterComponent::update(const Context& context) { (void)context; }

void MarkerEmitterComponent::render(SDL_Renderer* renderer) { (void)renderer; }

void MarkerEmitterComponent::onSceneReady(Scene& scene) {
for (const auto& binding : bindings) {
auto& delegate = scene.resolveEvents(binding.targetObject, binding.targetEvent);

auto link = std::make_unique<MarkerEventLink>(binding.markerName);
auto sub = delegate.subscribe(link.get());

activeSubscriptions.push_back({std::move(link), std::move(sub)});
}
}

std::unique_ptr<Component> MarkerEmitterComponent::createMarkerEmitter(
const NeuronIDE::Component& protoComp, const std::shared_ptr<SceneObject>& owner) {
std::vector<Binding> bindings;
for (const auto& protoBinding : protoComp.marker_emitter().bindings()) {
bindings.push_back({protoBinding.target_object(), protoBinding.target_event(),
protoBinding.marker_name()});
}
return std::make_unique<MarkerEmitterComponent>(owner, std::move(bindings));
}

REGISTER_COMPONENT(NeuronIDE::Component::kMarkerEmitter,
MarkerEmitterComponent::createMarkerEmitter)
Loading
Loading