Skip to content

Create MarkerEmiter component #28

Description

@AmeliaSroczynska

for more context check out class diagram https://mermaidviewer.com/diagrams/50hRyLmYGTKr7ixp1xZTa

Event System

1. Zasada nadrzędna

Komponent nie tworzy timestampów dla markerów oraz nie dotyka markerQueue ani DataWriter. timestamp
dokłada wyłącznie Renderer po SDL_RenderPresent — to gwarantuje wyrównanie
czasowe z odświeżeniem ekranu. Poniżej znajduje się diagram przedstawiający event system.

classDiagram
    class Context {
        +double deltaTime
        +std::vector~std::string~* markers
    }
    class EventListener {
        <<interface>>
        +onEventTriggered(const Context& ctx) void
    }
    class Delegate {
        -std::vector~EventListener*~ listeners
        +subscribe(EventListener*) void
        +unsubscribe(EventListener*) void
        +invoke(const Context& ctx) void
    }
    class MarkerEventLink {
        -std::string eventName
        +onEventTriggered(const Context& ctx) void
    }
    class MarkerEmitterComponent {
        -std::vector~unique_ptr~MarkerEventLink~~ bindings
        -std::vector~Delegate*~ subscribedTo
        +onSceneReady(Scene&) void
    }
    class Component {
        <<abstract>>
        +update(const Context&)* void
        +render(SDL_Renderer*)* void
        +onSceneReady(Scene&) void
    }

    MarkerEventLink ..|> EventListener
    Delegate o-- EventListener : "non-owning refs"
    MarkerEmitterComponent *-- MarkerEventLink : "owns"
    MarkerEmitterComponent ..> Delegate : "subscribes links to"
    Component <|-- MarkerEmitterComponent
    MarkerEventLink ..> Context : "writes name into ctx.markers"
Loading

2. Scenariusz przykładu

Obiekt sceny „stimulus" ma dwa komponenty:

  • LifeTimeComponent(duration = 2.0s) — producent zdarzenia, wystawia Delegate onTimeOut.
  • MarkerEmitterComponent z bindingiem { target: "stimulus", event: onTimeOut, markerName: "stimulus_end" }.

Cel: po 2 sekundach komponent „wygasa", odpala delegat, a podpięty
MarkerEventLink wrzuca nazwę "stimulus_end" do ctx.markers.

3. Faza wiązania (raz, po sparsowaniu sceny)

flowchart LR
    P[Parser buduje Scene] --> SR[Scene wywołuje onSceneReady na wszystkich komponentach]
    SR --> ME["MarkerEmitterComponent.onSceneReady(scene)"]
    ME --> RES["resolveEvent('stimulus', onTimeOut) → Delegate&"]
    RES --> SUB["onTimeOut.subscribe(markerEventLink)"]
    SUB --> RDY[Link gotowy: delegat zna swojego listenera]
Loading

Subskrypcja nie dzieje się w konstruktorze — cel (LifeTimeComponent) może
jeszcze nie istnieć. Dlatego potrzebny jest hook onSceneReady(Scene&)
uruchamiany po zbudowaniu całej sceny.

4. Przepływ w trakcie działania (klatka z timeoutem)

sequenceDiagram
    participant R as Renderer (render loop)
    participant LT as LifeTimeComponent
    participant D as Delegate onTimeOut
    participant L as MarkerEventLink
    participant C as ctx.markers
    participant MQ as markerQueue
    participant DW as DataWriter

    Note over R: nowa klatka
    R->>R: deltaTime
    R->>LT: update(ctx)
    LT->>LT: timeElapsed += ctx.deltaTime
    alt timeElapsed >= duration (pierwszy raz)
        LT->>D: invoke(ctx)
        D->>L: onEventTriggered(ctx)
        L->>C: markers->push_back("stimulus_end")
    end
    Note over R: po update + render całej sceny
    R->>R: SDL_RenderPresent()
    R->>R: exactTime = lsl::local_clock()
    R->>MQ: enqueue(Marker{"stimulus_end", exactTime})
    MQ-->>DW: try_dequeue
    DW->>DW: formatStrategy->writeMarker(...)
Loading

5. Szkic kodu

Producent — LifeTimeComponent

class LifeTimeComponent : public Component {
   public:
    /*
    some code ...
    */

    // Producent wystawia swój delegat, by MarkerEmitter mógł się podpiąć w fazie wiązania.
    Delegate& timeOutEvent() { return onTimeOut; }

    void update(const Context& ctx) override {
        if (hasTimedOut) {
            return;
        }

        timeElapsed += ctx.deltaTime;

        if (timeElapsed >= duration) {
            hasTimedOut = true;

            // WAZNE
            // Przekazujemy ctx, żeby listener trafił nazwą markera do bieżącej klatki
            // (ten sam timestamp vsync, niezależnie od kolejności update komponentów).
            onTimeOut.invoke(ctx);

            
        }
    }

    /*
    some code ...
    */

   private:
    Delegate onTimeOut;
    double   duration    = 0.0;
    double   timeElapsed = 0.0;
    bool     hasTimedOut = false;
};

Prymitywy zdarzeń

struct EventListener {
    virtual ~EventListener() = default;
    virtual void onEventTriggered(const Context& ctx) = 0;
};

class Delegate {
   public:
    void subscribe(EventListener* l);
    void unsubscribe(EventListener* l);
    void invoke(const Context& ctx) {
        for (auto* l : listeners) l->onEventTriggered(ctx);
    }

   private:
    std::vector<EventListener*> listeners;  // referencje nievłaścicielskie
};

Odbiorca — MarkerEventLink (most do ctx.markers)

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

    void onEventTriggered(const Context& ctx) override {
        if (ctx.markers) {
            ctx.markers->push_back(eventName);  // tylko NAZWA, bez timestampu
        }
    }

   private:
    std::string eventName;
};

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions