#pragma once #include "concurrent/waitfree.h" #include #include #include #include #include #include EMSCRIPTEN_DECLARE_VAL_TYPE(PlaybackSpanList); struct Uuid { uint64_t lower; uint64_t upper; Uuid() : lower(0), upper(0) {} Uuid(uint64_t lower, uint64_t upper) : lower(lower), upper(upper) {} Uuid(const std::string &str); bool operator==(const Uuid &other) const { return lower == other.lower && upper == other.upper; } bool operator!=(const Uuid &other) const { return !(*this == other); } bool operator<(const Uuid &other) const { if (upper != other.upper) { return upper < other.upper; } return lower < other.lower; } std::string toString() const; }; struct PlaybackSpan { Uuid arrangement; time_units::Seconds begin; time_units::Seconds end; uint64_t renderCycleIndex; PlaybackSpan() : arrangement{}, begin{0.0}, end{0.0}, renderCycleIndex(0) {} PlaybackSpan(const Uuid &arrangement, time_units::Seconds begin, uint64_t renderCycleIndex) : arrangement(arrangement), begin(begin), end(begin), renderCycleIndex(renderCycleIndex) {} std::string getArrangementIdString() const { return arrangement.toString(); } double getBeginSeconds() const noexcept { return begin.raw(); } double getEndSeconds() const noexcept { return end.raw(); } void setArrangementIdString(const std::string &str) { arrangement = Uuid(str); } void setBeginSeconds(double begin) { this->begin = time_units::Seconds(begin); } void setEndSeconds(double end) { this->end = time_units::Seconds(end); } }; class AnalyticsObserver { private: std::map activeSpans; WaitFreeWrap> inactiveSpans; uint64_t currentRenderCycleIndex; public: AnalyticsObserver(); ~AnalyticsObserver(); // Called from real-time thread only void trackPlayback(const Uuid &arrangementId, time_units::Seconds position); void trackLooped(const Uuid &arrangementId); void trackRenderCycleEnd(); // Called from non-real-time thread only std::vector flushSpans(); PlaybackSpanList flushSpansJs(); };