You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

189 lines
4.5 KiB
C++

#pragma once
#include <vector>
#include <memory>
#include <algorithm>
#include "tools/mathutils.h"
#include "core/note.h"
template <class TNote, class = std::enable_if_t<std::is_base_of<Note, TNote>::value>>
class Timeline
{
public:
explicit Timeline() :
_current_offset(0)
{}
typedef typename std::vector<TNote*>::const_iterator Iterator;
void setNotes(const std::vector<TNote*>& 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);
fetchVisibleNotes();
}
inline void clear()
{
for (auto& note : _timeline)
delete note;
_timeline.clear();
}
void update(const microsec& offset)
{
_current_offset = offset;
checkCurrentActiveNote();
checkForNextActiveNote();
updateVisibleSprites(_current_offset);
}
void drawVisibleNotes() const
{
if (nothingToDraw())
return;
std::for_each(_first_visible_note, _last_visible_note,
[](const auto& note)
{
note->draw();
});
}
void fetchVisibleNotes()
{
findLastVisibleNote(_current_offset);
findFirstVisibleNote();
}
void findLastVisibleNote(const microsec& music_offset)
{
Iterator note_iterator = _top_note;
while (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 bool isExpired(const Iterator& iterator) const
{
return iterator == _timeline.end();
}
inline void expire(Iterator& iterator)
{
iterator = _timeline.end();
}
private:
std::vector<microsec> _input_intervals;
std::vector<TNote*> _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 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;
};