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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions