Rework Timeline to work fine with scrolling and note insertion

This commit is contained in:
NaiJi ✨ 2021-12-08 21:00:47 +03:00
parent ebf736a0bb
commit 581e1fe6a4
12 changed files with 118 additions and 116 deletions

View File

@ -13,6 +13,7 @@ public:
virtual void input(PlayerInput&& inputdata) = 0; virtual void input(PlayerInput&& inputdata) = 0;
virtual void update(UpdateData&& updatedata) = 0; virtual void update(UpdateData&& updatedata) = 0;
virtual void draw() const = 0; virtual void draw() const = 0;
virtual void recalculate(const microsec& timestamp) = 0;
inline void setBPMSections(const std::set<BPMSection, BPMSectionCompt>& sections) noexcept inline void setBPMSections(const std::set<BPMSection, BPMSectionCompt>& sections) noexcept
{ {

View File

@ -9,7 +9,7 @@ public:
_perfect_offset(perfect_offset) {} _perfect_offset(perfect_offset) {}
virtual ~Note() = default; virtual ~Note() = default;
virtual bool isActive() const = 0; virtual bool isActive(const microsec& offset) const = 0;
virtual void update(const microsec& music_offset) = 0; virtual void update(const microsec& music_offset) = 0;
virtual void draw() const = 0; virtual void draw() const = 0;

View File

@ -3,6 +3,7 @@
#include <set> #include <set>
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <iostream>
#include "tools/mathutils.h" #include "tools/mathutils.h"
#include "core/note.h" #include "core/note.h"
@ -18,15 +19,46 @@ public:
typedef typename std::set<TNote*>::const_iterator Iterator; typedef typename std::set<TNote*>::const_iterator Iterator;
void recalculate(const microsec& offset)
{
_current_offset = offset;
expire(_first_visible_note);
expire(_last_visible_note);
expire(_top_note);
if (!_timeline.empty())
{
Iterator head_iterator = _timeline.begin();
while (!isExpired(head_iterator))
{
if ((*head_iterator)->offset() >= offset)
{
Iterator pre_head = head_iterator;
--pre_head;
_top_note = !isExpired(pre_head) && (*pre_head)->isActive(offset)
? pre_head
: head_iterator;
break;
}
++head_iterator;
}
if (isExpired(_top_note))
_top_note = _timeline.begin();
}
fetchVisibleNotes();
}
void setNotes(const std::set<TNote*, NotePtrCompt>& notes, const microsec& visibility) void setNotes(const std::set<TNote*, NotePtrCompt>& notes, const microsec& visibility)
{ {
_visibility_offset = visibility; _visibility_offset = visibility;
_timeline = std::move(notes); _timeline = std::move(notes);
_top_note = _timeline.begin(); recalculate(_current_offset);
expire(_first_visible_note);
expire(_last_visible_note);
expire(_active_note);
if (isExpired(_top_note)) if (isExpired(_top_note))
return; return;
@ -37,13 +69,16 @@ public:
void insertNote(TNote* note) void insertNote(TNote* note)
{ {
_top_note = _timeline.insert(note).first; _top_note = _timeline.insert(note).first;
recalculate(_current_offset);
update(_current_offset); update(_current_offset);
} }
void insertNotes(const std::set<TNote*, NotePtrCompt>& notes) void insertNotes(const std::set<TNote*, NotePtrCompt>& notes)
{ {
_timeline.insert(notes.begin(), notes.end()); _timeline.insert(notes.begin(), notes.end());
recalculate(_current_offset);
update(_current_offset); update(_current_offset);
} }
inline void clear() inline void clear()
@ -57,13 +92,7 @@ public:
void update(const microsec& offset) void update(const microsec& offset)
{ {
_current_offset = offset; _current_offset = offset;
updateTopNote(_current_offset);
if (isExpired(_top_note))
return;
checkTopNote(_current_offset);
checkCurrentActiveNote();
checkForNextActiveNote();
updateVisibleSprites(_current_offset); updateVisibleSprites(_current_offset);
} }
@ -83,9 +112,6 @@ public:
void findLastVisibleNote(const microsec& music_offset) void findLastVisibleNote(const microsec& music_offset)
{ {
if (isExpired(_top_note))
return;
Iterator note_iterator = _top_note; Iterator note_iterator = _top_note;
while (!isExpired(note_iterator) && isVisiblyClose(note_iterator, music_offset)) while (!isExpired(note_iterator) && isVisiblyClose(note_iterator, music_offset))
{ {
@ -113,15 +139,34 @@ public:
{ {
auto note = *note_iterator; auto note = *note_iterator;
if (note->shouldRemove()) if (note->shouldRemove())
{
++_first_visible_note; ++_first_visible_note;
}
++note_iterator; ++note_iterator;
} }
} }
inline Iterator getActiveNote() noexcept Iterator getActiveNote(const microsec& music_offset) noexcept
{ {
return _active_note; Iterator return_note = _timeline.end();
auto note_iterator = _top_note;
while (!isExpired(note_iterator))
{
const auto& note = *note_iterator;
if (note->isActive(music_offset))
{
return_note = note_iterator;
break;
}
else if (note->offset() > music_offset)
break;
++note_iterator;
}
return return_note;
} }
inline Iterator getNoteBy(const microsec& music_offset) noexcept inline Iterator getNoteBy(const microsec& music_offset) noexcept
@ -133,12 +178,12 @@ public:
}); });
} }
inline bool isExpired(const Iterator& iterator) const inline bool isExpired(const Iterator& iterator) const noexcept
{ {
return iterator == _timeline.end(); return iterator == _timeline.end();
} }
inline void expire(Iterator& iterator) inline void expire(Iterator& iterator) noexcept
{ {
iterator = _timeline.end(); iterator = _timeline.end();
} }
@ -148,6 +193,16 @@ private:
microsec _visibility_offset; microsec _visibility_offset;
microsec _current_offset; microsec _current_offset;
inline void updateTopNote(const microsec& music_offset) noexcept
{
if ((*_top_note)->offset() < music_offset //
&& _top_note == _first_visible_note // Maybe simplify
&& !(*_top_note)->isActive(music_offset)) //
{
++_top_note;
}
}
void updateVisibleSprites(const microsec& music_offset) void updateVisibleSprites(const microsec& music_offset)
{ {
if (nothingToDraw()) if (nothingToDraw())
@ -160,40 +215,7 @@ private:
}); });
} }
void checkCurrentActiveNote() inline bool isVisiblyClose(const Iterator& iterator, const microsec& music_offset) const noexcept
{
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; return ((*iterator)->offset() - _visibility_offset) <= music_offset;
} }
@ -203,22 +225,7 @@ private:
return isExpired(_first_visible_note); 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 _top_note;
Iterator _active_note;
Iterator _last_visible_note; Iterator _last_visible_note;
Iterator _first_visible_note; Iterator _first_visible_note;
}; };

View File

@ -12,23 +12,20 @@ ClassicEditor::ClassicEditor(std::shared_ptr<ClassicGraphicsManager>&& manager)
std::set<MockClassicNote*, NotePtrCompt> _set = {}; std::set<MockClassicNote*, NotePtrCompt> _set = {};
for (int i = 1; i < 5; ++i)
{
NoteInitializer init; NoteInitializer init;
init.context = &_context; init.context = &_context;
init.intervals = {}; init.intervals = {};
init.perfect_offset = basic_offset + (500000 * i); init.perfect_offset = basic_offset + (500000 * 20);
ElementInitializer elem_init; ElementInitializer elem_init;
elem_init.type = _selected_type; elem_init.type = _selected_type;
elem_init.coordinates = Coordinates{ 700 - (i * 120), 550 }; elem_init.coordinates = Coordinates{ 700 - (5 * 120), 550 };
elem_init.falling_curve_interpolation = {}; elem_init.falling_curve_interpolation = {};
MockArrowNoteInitializer mock_init; MockArrowNoteInitializer mock_init;
mock_init.elements = {elem_init}; mock_init.elements = {elem_init};
mock_init.initializer = init; mock_init.initializer = init;
_set.insert(new MockClassicNote(std::move(mock_init))); _set.insert(new MockClassicNote(std::move(mock_init)));
}
_timeline.setNotes(_set, 1648648); _timeline.setNotes(_set, 1648648);
} }
@ -89,6 +86,11 @@ void ClassicEditor::draw() const
}); });
} }
void ClassicEditor::recalculate(const microsec& timestamp)
{
_timeline.recalculate(timestamp);
}
void ClassicEditor::selectNoteType(Type type) noexcept void ClassicEditor::selectNoteType(Type type) noexcept
{ {
_selected_type = type; _selected_type = type;

View File

@ -17,6 +17,7 @@ public:
virtual void input(PlayerInput&& inputdata) override; virtual void input(PlayerInput&& inputdata) override;
virtual void update(UpdateData&& updatedata) override; virtual void update(UpdateData&& updatedata) override;
virtual void draw() const override; virtual void draw() const override;
virtual void recalculate(const microsec& timestamp) override;
void selectNoteType(Type type) noexcept; void selectNoteType(Type type) noexcept;

View File

@ -30,10 +30,9 @@ MockClassicNote::MockClassicNote(MockArrowNoteInitializer&& init) :
} }
} }
bool MockClassicNote::isActive() const bool MockClassicNote::isActive(const microsec& offset) const
{ {
return _state != State::DEAD return offset == Note::offset();
&& _state != State::NONE;
} }
bool MockClassicNote::isInGame() const bool MockClassicNote::isInGame() const
@ -44,21 +43,20 @@ bool MockClassicNote::isInGame() const
bool MockClassicNote::shouldRemove() const bool MockClassicNote::shouldRemove() const
{ {
return _state == State::DEAD; return _state == State::DEAD
|| _state == State::NONE;
} }
void MockClassicNote::putToGame(const microsec &music_offset) void MockClassicNote::putToGame(const microsec &music_offset)
{ {
_state = State::FLYING; _state = State::FLYING; (void)music_offset;
std::cout << "Put to game " << this << ": " << music_offset << '\n';
for (auto& element : _elements) for (auto& element : _elements)
{ {
element.sprite = _context->graphics_manager->getSprite(element.type); element.sprite = _context->graphics_manager->getSprite(element.type);
element.sprite->setCoordinates(element.coordinates); element.sprite->setCoordinates(element.coordinates);
element.sprite->setTrailCoordinates(Coordinates(0.f, 9.f)); element.sprite->setTrailCoordinates(Coordinates(0.f, 9.f));
element.animations[_state]->launch(element.sprite, music_offset, offset()); element.animations[_state]->launch(element.sprite, offset() - 1648648, offset());
} }
} }

View File

@ -25,7 +25,7 @@ public:
explicit MockClassicNote(MockArrowNoteInitializer&& init); explicit MockClassicNote(MockArrowNoteInitializer&& init);
virtual ~MockClassicNote() override = default; virtual ~MockClassicNote() override = default;
virtual bool isActive() const override final; virtual bool isActive(const microsec& offset) const override final;
virtual bool isInGame() const override final; virtual bool isInGame() const override final;
virtual bool shouldRemove() const override final; virtual bool shouldRemove() const override final;

View File

@ -22,7 +22,6 @@ ClassicArrowNote::ClassicArrowNote(ArrowNoteInitializer&& init) :
// Animations will be injected into note. // Animations will be injected into note.
_elements[i].animations[State::NONE] = nullptr; _elements[i].animations[State::NONE] = nullptr;
_elements[i].animations[State::FLYING] = std::make_shared<ClassicFlyingAnimationScenario>(); _elements[i].animations[State::FLYING] = std::make_shared<ClassicFlyingAnimationScenario>();
_elements[i].animations[State::ACTIVE] = _elements[i].animations[State::FLYING];
_elements[i].animations[State::DYING] = std::make_shared<ClassicDyingAnimationScenario>(); _elements[i].animations[State::DYING] = std::make_shared<ClassicDyingAnimationScenario>();
_elements[i].animations[State::DEAD] = nullptr; _elements[i].animations[State::DEAD] = nullptr;
} }
@ -100,9 +99,11 @@ void ClassicArrowNote::update(const microsec& music_offset)
break; break;
case State::FLYING: case State::FLYING:
if (_evaluator.isActive(music_offset)) { if (!_evaluator.isActive(music_offset) && music_offset > offset())
_state = State::ACTIVE; {
_state = State::DYING;
for (auto& element : _elements)
element.animations[_state]->launch(element.sprite, music_offset, offset());
} }
break; break;
@ -110,15 +111,6 @@ void ClassicArrowNote::update(const microsec& music_offset)
if (_elements[0].animations[_state]->isDone()) if (_elements[0].animations[_state]->isDone())
_state = State::DEAD; _state = State::DEAD;
break; break;
case State::ACTIVE:
if (!_evaluator.isActive(music_offset))
{
_state = State::DYING;
for (auto& element : _elements)
element.animations[_state]->launch(element.sprite, music_offset, offset());
}
break;
} }
for (auto& element : _elements) for (auto& element : _elements)
@ -138,7 +130,7 @@ bool ClassicArrowNote::allElementsPressed() const
bool ClassicArrowNote::isPressedAs(sf::Keyboard::Key key) const bool ClassicArrowNote::isPressedAs(sf::Keyboard::Key key) const
{ {
return std::any_of(_elements.begin(), _elements.end(), return std::any_of(_elements.begin(), _elements.end(),
[key=key](const auto& element) [key](const auto& element)
{ {
return key == element.pressed_as; return key == element.pressed_as;
}); });

View File

@ -73,7 +73,7 @@ void ClassicGame::input(PlayerInput&& inputdata)
case sf::Event::KeyPressed: case sf::Event::KeyPressed:
{ {
auto note_it = _timeline.getActiveNote(); auto note_it = _timeline.getActiveNote(inputdata.timestamp);
if (!_timeline.isExpired(note_it)) if (!_timeline.isExpired(note_it))
{ {

View File

@ -14,15 +14,15 @@ ClassicNote::ClassicNote(NoteInitializer &&init) :
_context(init.context) _context(init.context)
{} {}
bool ClassicNote::isActive() const bool ClassicNote::isActive(const microsec& offset) const
{ {
return _state == State::ACTIVE; return _evaluator.isActive(offset)
&& _state != State::DYING;
} }
bool ClassicNote::isInGame() const bool ClassicNote::isInGame() const
{ {
return _state == State::FLYING return _state == State::FLYING
|| _state == State::ACTIVE
|| _state == State::DYING; || _state == State::DYING;
} }

View File

@ -24,7 +24,6 @@ public:
NONE, NONE,
FLYING, FLYING,
ACTIVE,
DYING, DYING,
DEAD DEAD
}; };
@ -32,7 +31,7 @@ public:
explicit ClassicNote(NoteInitializer&& init); explicit ClassicNote(NoteInitializer&& init);
virtual ~ClassicNote() override = default; virtual ~ClassicNote() override = default;
virtual bool isActive() const override final; virtual bool isActive(const microsec& offset) const override final;
virtual bool isInGame() const override final; virtual bool isInGame() const override final;
virtual bool shouldRemove() const override final; virtual bool shouldRemove() const override final;

View File

@ -23,9 +23,6 @@ EditorState::~EditorState()
void EditorState::input(const sf::Event& event) void EditorState::input(const sf::Event& event)
{ {
if (event.key.code == sf::Keyboard::Space && event.type == sf::Event::KeyReleased)
_music.isPaused() ? _music.play() : _music.pause();
_group->input(event); _group->input(event);
} }
@ -156,8 +153,13 @@ void EditorState::enter()
callbacks.onInput = [&editor, &music](const sf::Event& event) callbacks.onInput = [&editor, &music](const sf::Event& event)
{ {
if (event.type == sf::Event::MouseWheelScrolled) if (event.key.code == sf::Keyboard::Space && event.type == sf::Event::KeyReleased)
music.isPaused() ? music.play() : music.pause();
else if (event.type == sf::Event::MouseWheelScrolled)
{
music.moveOffset(event.mouseWheelScroll.delta > 0 ? 500000 : -500000); music.moveOffset(event.mouseWheelScroll.delta > 0 ? 500000 : -500000);
editor->recalculate(music.fetchOffset());
}
else else
editor->input(PlayerInput{music.fetchOffset(), event}); editor->input(PlayerInput{music.fetchOffset(), event});
}; };