#pragma once #include #include #include #include "tools/mathutils.h" #include "core/note.h" template ::value>> class Timeline { public: explicit Timeline() : _visibility_offset(0), _current_offset(0) {} typedef typename std::set::const_iterator Iterator; void setNotes(const std::set& notes, const microsec& visibility) { _visibility_offset = visibility; _timeline = std::move(notes); _top_note = _timeline.begin(); expire(_first_visible_note); expire(_last_visible_note); expire(_active_note); if (isExpired(_top_note)) return; fetchVisibleNotes(); } void insertNote(TNote* note) { _top_note = _timeline.insert(note).first; update(_current_offset); } void insertNotes(const std::set& notes) { _timeline.insert(notes.begin(), notes.end()); update(_current_offset); } inline void clear() { for (auto& note : _timeline) delete note; _timeline.clear(); } void update(const microsec& offset) { _current_offset = offset; if (isExpired(_top_note)) return; checkTopNote(_current_offset); checkCurrentActiveNote(); checkForNextActiveNote(); updateVisibleSprites(_current_offset); } std::pair getVisibleNotes() const { if (nothingToDraw()) return std::pair(); return std::make_pair(_first_visible_note, _last_visible_note); } void fetchVisibleNotes() { findLastVisibleNote(_current_offset); findFirstVisibleNote(); } void findLastVisibleNote(const microsec& music_offset) { if (isExpired(_top_note)) return; Iterator note_iterator = _top_note; while (!isExpired(note_iterator) && isVisiblyClose(note_iterator, music_offset)) { if (nothingToDraw()) _first_visible_note = note_iterator; auto note = *note_iterator; if (!note->isInGame()) note->putToGame(music_offset); ++note_iterator; } _last_visible_note = note_iterator; } void findFirstVisibleNote() { if (nothingToDraw()) return; auto note_iterator = _first_visible_note; while (note_iterator != _last_visible_note) { auto note = *note_iterator; if (note->shouldRemove()) ++_first_visible_note; ++note_iterator; } } inline Iterator getActiveNote() noexcept { return _active_note; } inline Iterator getNoteBy(const microsec& music_offset) noexcept { return std::find_if(_timeline.begin(), _timeline.end(), [music_offset](const auto& note) { return note->offset() == music_offset; }); } inline bool isExpired(const Iterator& iterator) const { return iterator == _timeline.end(); } inline void expire(Iterator& iterator) { iterator = _timeline.end(); } private: std::set _timeline; microsec _visibility_offset; microsec _current_offset; void updateVisibleSprites(const microsec& music_offset) { if (nothingToDraw()) return; std::for_each(_first_visible_note, _last_visible_note, [&music_offset](const auto& note) { note->update(music_offset); }); } void checkCurrentActiveNote() { if (isExpired(_active_note)) return; auto note = *_active_note; if (!note->isActive()) { expire(_active_note); ++_top_note; } } void checkTopNote(const microsec& offset) { if (isExpired(_top_note) || !isExpired(_active_note)) return; while ((*_top_note)->offset() < offset) ++_top_note; } void checkForNextActiveNote() { if (!isExpired(_active_note)) return; auto top_note = *_top_note; if (top_note->isActive()) _active_note = _top_note; } inline bool isVisiblyClose(const Iterator& iterator, const microsec& music_offset) const { return ((*iterator)->offset() - _visibility_offset) <= music_offset; } inline bool nothingToDraw() const noexcept { return isExpired(_first_visible_note); } /* Difference between top and active note is that * top note is the note handling input right now * OR it's the closest note from current music offset * position, not necessarily active. A note stops being top only * after dying or being tapped by player, even if it's already * past her perfect offset. * * Meanwhile active note is the note which is currently handling * player input for grade. * * An active note is always top note but a top note * is not always active note. * */ Iterator _top_note; Iterator _active_note; Iterator _last_visible_note; Iterator _first_visible_note; };