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 update(UpdateData&& updatedata) = 0;
virtual void draw() const = 0;
virtual void recalculate(const microsec& timestamp) = 0;
inline void setBPMSections(const std::set<BPMSection, BPMSectionCompt>& sections) noexcept
{

View File

@ -9,7 +9,7 @@ public:
_perfect_offset(perfect_offset) {}
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 draw() const = 0;

View File

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

View File

@ -12,23 +12,20 @@ ClassicEditor::ClassicEditor(std::shared_ptr<ClassicGraphicsManager>&& manager)
std::set<MockClassicNote*, NotePtrCompt> _set = {};
for (int i = 1; i < 5; ++i)
{
NoteInitializer init;
init.context = &_context;
init.intervals = {};
init.perfect_offset = basic_offset + (500000 * i);
NoteInitializer init;
init.context = &_context;
init.intervals = {};
init.perfect_offset = basic_offset + (500000 * 20);
ElementInitializer elem_init;
elem_init.type = _selected_type;
elem_init.coordinates = Coordinates{ 700 - (i * 120), 550 };
elem_init.falling_curve_interpolation = {};
ElementInitializer elem_init;
elem_init.type = _selected_type;
elem_init.coordinates = Coordinates{ 700 - (5 * 120), 550 };
elem_init.falling_curve_interpolation = {};
MockArrowNoteInitializer mock_init;
mock_init.elements = {elem_init};
mock_init.initializer = init;
_set.insert(new MockClassicNote(std::move(mock_init)));
}
MockArrowNoteInitializer mock_init;
mock_init.elements = {elem_init};
mock_init.initializer = init;
_set.insert(new MockClassicNote(std::move(mock_init)));
_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
{
_selected_type = type;

View File

@ -17,6 +17,7 @@ public:
virtual void input(PlayerInput&& inputdata) override;
virtual void update(UpdateData&& updatedata) override;
virtual void draw() const override;
virtual void recalculate(const microsec& timestamp) override;
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
&& _state != State::NONE;
return offset == Note::offset();
}
bool MockClassicNote::isInGame() const
@ -44,21 +43,20 @@ bool MockClassicNote::isInGame() const
bool MockClassicNote::shouldRemove() const
{
return _state == State::DEAD;
return _state == State::DEAD
|| _state == State::NONE;
}
void MockClassicNote::putToGame(const microsec &music_offset)
{
_state = State::FLYING;
std::cout << "Put to game " << this << ": " << music_offset << '\n';
_state = State::FLYING; (void)music_offset;
for (auto& element : _elements)
{
element.sprite = _context->graphics_manager->getSprite(element.type);
element.sprite->setCoordinates(element.coordinates);
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);
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 shouldRemove() const override final;

View File

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

View File

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

View File

@ -14,15 +14,15 @@ ClassicNote::ClassicNote(NoteInitializer &&init) :
_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
{
return _state == State::FLYING
|| _state == State::ACTIVE
|| _state == State::DYING;
}

View File

@ -24,7 +24,6 @@ public:
NONE,
FLYING,
ACTIVE,
DYING,
DEAD
};
@ -32,7 +31,7 @@ public:
explicit ClassicNote(NoteInitializer&& init);
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 shouldRemove() const override final;

View File

@ -23,9 +23,6 @@ EditorState::~EditorState()
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);
}
@ -156,8 +153,13 @@ void EditorState::enter()
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);
editor->recalculate(music.fetchOffset());
}
else
editor->input(PlayerInput{music.fetchOffset(), event});
};