mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-05 15:03:33 +08:00
fast forwarding replays
This commit is contained in:
parent
069cd054a0
commit
d45caaea92
@ -120,7 +120,7 @@ from _bascenev1 import (
|
|||||||
release_keyboard_input,
|
release_keyboard_input,
|
||||||
reset_random_player_names,
|
reset_random_player_names,
|
||||||
resume_replay,
|
resume_replay,
|
||||||
rewind_replay,
|
seek_replay,
|
||||||
broadcastmessage,
|
broadcastmessage,
|
||||||
SessionData,
|
SessionData,
|
||||||
SessionPlayer,
|
SessionPlayer,
|
||||||
@ -401,7 +401,7 @@ __all__ = [
|
|||||||
'release_keyboard_input',
|
'release_keyboard_input',
|
||||||
'reset_random_player_names',
|
'reset_random_player_names',
|
||||||
'resume_replay',
|
'resume_replay',
|
||||||
'rewind_replay',
|
'seek_replay',
|
||||||
'safecolor',
|
'safecolor',
|
||||||
'screenmessage',
|
'screenmessage',
|
||||||
'SceneV1AppMode',
|
'SceneV1AppMode',
|
||||||
|
|||||||
@ -1568,32 +1568,37 @@ static PyMethodDef PyResumeReplayDef = {
|
|||||||
"Resumes replay.",
|
"Resumes replay.",
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------ rewind_replay --------------------------------------
|
// -------------------------- seek_replay --------------------------------------
|
||||||
|
|
||||||
static auto PyRewindReplay(PyObject* self, PyObject* args) -> PyObject* {
|
static auto PySeekReplay(PyObject* self, PyObject* args) -> PyObject* {
|
||||||
BA_PYTHON_TRY;
|
BA_PYTHON_TRY;
|
||||||
auto* appmode = SceneV1AppMode::GetActiveOrThrow();
|
auto* appmode = SceneV1AppMode::GetActiveOrThrow();
|
||||||
auto* session =
|
auto* session =
|
||||||
dynamic_cast<ClientSessionReplay*>(appmode->GetForegroundSession());
|
dynamic_cast<ClientSessionReplay*>(appmode->GetForegroundSession());
|
||||||
if (session == nullptr) {
|
if (session == nullptr) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"Attempting to rewind a replay not in replay session context.");
|
"Attempting to seek a replay not in replay session context.");
|
||||||
}
|
}
|
||||||
session->RestoreState(session->base_time() - 2'000);
|
float delta;
|
||||||
|
if (!PyArg_ParseTuple(args, "f", &delta)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
session->SeekTo(session->base_time()
|
||||||
|
+ static_cast<millisecs_t>(delta * 1'000));
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
BA_PYTHON_CATCH;
|
BA_PYTHON_CATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyMethodDef PyRewindReplayDef = {
|
static PyMethodDef PySeekReplayDef = {
|
||||||
"rewind_replay", // name
|
"seek_replay", // name
|
||||||
PyRewindReplay, // method
|
PySeekReplay, // method
|
||||||
METH_VARARGS, // flags
|
METH_VARARGS, // flags
|
||||||
|
|
||||||
"rewind_replay() -> None\n"
|
"seek_replay(delta: float) -> None\n"
|
||||||
"\n"
|
"\n"
|
||||||
"(internal)\n"
|
"(internal)\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Rewinds replay.",
|
"Rewind or fast-forward replay.",
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------- reset_random_player_names ---------------------------
|
// ----------------------- reset_random_player_names ---------------------------
|
||||||
@ -1874,7 +1879,7 @@ auto PythonMethodsScene::GetMethods() -> std::vector<PyMethodDef> {
|
|||||||
PySetReplaySpeedExponentDef,
|
PySetReplaySpeedExponentDef,
|
||||||
PyGetReplaySpeedExponentDef,
|
PyGetReplaySpeedExponentDef,
|
||||||
PyIsReplayPausedDef,
|
PyIsReplayPausedDef,
|
||||||
PyRewindReplayDef,
|
PySeekReplayDef,
|
||||||
PyPauseReplayDef,
|
PyPauseReplayDef,
|
||||||
PyResumeReplayDef,
|
PyResumeReplayDef,
|
||||||
PySetDebugSpeedExponentDef,
|
PySetDebugSpeedExponentDef,
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "ballistica/scene_v1/support/client_session_replay.h"
|
#include "ballistica/scene_v1/support/client_session_replay.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "ballistica/base/assets/assets.h"
|
#include "ballistica/base/assets/assets.h"
|
||||||
#include "ballistica/base/networking/networking.h"
|
#include "ballistica/base/networking/networking.h"
|
||||||
#include "ballistica/base/support/huffman.h"
|
#include "ballistica/base/support/huffman.h"
|
||||||
@ -14,11 +16,22 @@
|
|||||||
|
|
||||||
namespace ballistica::scene_v1 {
|
namespace ballistica::scene_v1 {
|
||||||
|
|
||||||
|
static const millisecs_t kReplayStateDumpIntervalMillisecs = 500;
|
||||||
|
|
||||||
auto ClientSessionReplay::GetActualTimeAdvanceMillisecs(
|
auto ClientSessionReplay::GetActualTimeAdvanceMillisecs(
|
||||||
double base_advance_millisecs) -> double {
|
double base_advance_millisecs) -> double {
|
||||||
|
if (is_fast_forwarding_) {
|
||||||
|
if (base_time() < fast_forward_base_time_) {
|
||||||
|
return std::min(
|
||||||
|
base_advance_millisecs * 8,
|
||||||
|
static_cast<double>(fast_forward_base_time_ - base_time()));
|
||||||
|
}
|
||||||
|
is_fast_forwarding_ = false;
|
||||||
|
}
|
||||||
auto* appmode = SceneV1AppMode::GetActiveOrFatal();
|
auto* appmode = SceneV1AppMode::GetActiveOrFatal();
|
||||||
if (appmode->is_replay_paused()) {
|
if (appmode->is_replay_paused()) {
|
||||||
return 0.001;
|
// FIXME: seeking a replay results in black screen here
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return base_advance_millisecs * pow(2.0f, appmode->replay_speed_exponent());
|
return base_advance_millisecs * pow(2.0f, appmode->replay_speed_exponent());
|
||||||
}
|
}
|
||||||
@ -136,8 +149,8 @@ void ClientSessionReplay::FetchMessages() {
|
|||||||
while (commands().empty()) {
|
while (commands().empty()) {
|
||||||
// Before we read next message, let's save our current state
|
// Before we read next message, let's save our current state
|
||||||
// if we didn't that for too long.
|
// if we didn't that for too long.
|
||||||
unsaved_messages_count_ += 1;
|
if (base_time() >= (states_.empty() ? 0 : states_.back().base_time_)
|
||||||
if (unsaved_messages_count_ > 50) {
|
+ kReplayStateDumpIntervalMillisecs) {
|
||||||
SessionStream out(nullptr, false);
|
SessionStream out(nullptr, false);
|
||||||
DumpFullState(&out);
|
DumpFullState(&out);
|
||||||
|
|
||||||
@ -148,7 +161,7 @@ void ClientSessionReplay::FetchMessages() {
|
|||||||
fflush(file_);
|
fflush(file_);
|
||||||
current_state_.file_position_ = ftell(file_);
|
current_state_.file_position_ = ftell(file_);
|
||||||
current_state_.message_ = out.GetOutMessage();
|
current_state_.message_ = out.GetOutMessage();
|
||||||
SaveState();
|
states_.push_back(current_state_);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> buffer;
|
std::vector<uint8_t> buffer;
|
||||||
@ -218,7 +231,6 @@ void ClientSessionReplay::FetchMessages() {
|
|||||||
for (auto&& i : connections_to_clients_) {
|
for (auto&& i : connections_to_clients_) {
|
||||||
i->SendReliableMessage(data_decompressed);
|
i->SendReliableMessage(data_decompressed);
|
||||||
}
|
}
|
||||||
message_fetch_num_++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,6 +250,10 @@ void ClientSessionReplay::OnReset(bool rewind) {
|
|||||||
// Handles base resetting.
|
// Handles base resetting.
|
||||||
ClientSession::OnReset(rewind);
|
ClientSession::OnReset(rewind);
|
||||||
|
|
||||||
|
// Hack or not, but let's reset our fast-forward flag here, in case we were
|
||||||
|
// asked to seek replay further than it's length.
|
||||||
|
is_fast_forwarding_ = false;
|
||||||
|
|
||||||
// If we've got any clients attached to us, tell them to reset as well.
|
// If we've got any clients attached to us, tell them to reset as well.
|
||||||
for (auto&& i : connections_to_clients_) {
|
for (auto&& i : connections_to_clients_) {
|
||||||
i->SendReliableMessage(std::vector<uint8_t>(1, BA_MESSAGE_SESSION_RESET));
|
i->SendReliableMessage(std::vector<uint8_t>(1, BA_MESSAGE_SESSION_RESET));
|
||||||
@ -282,29 +298,44 @@ void ClientSessionReplay::OnReset(bool rewind) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientSessionReplay::SaveState() {
|
void ClientSessionReplay::SeekTo(millisecs_t to_base_time) {
|
||||||
unsaved_messages_count_ = 0;
|
is_fast_forwarding_ = false;
|
||||||
states_.push_back(current_state_);
|
if (to_base_time < base_time()) {
|
||||||
}
|
auto it = std::lower_bound(
|
||||||
|
states_.rbegin(), states_.rend(), to_base_time,
|
||||||
void ClientSessionReplay::RestoreState(millisecs_t to_base_time) {
|
[&](const IntermediateState& state, millisecs_t time) -> bool {
|
||||||
ScreenMessage("was: " + std::to_string(base_time()) + "ms");
|
return state.base_time_ > time;
|
||||||
ScreenMessage("want: " + std::to_string(to_base_time) + "ms");
|
});
|
||||||
|
if (it == states_.rend()) {
|
||||||
while (!states_.empty() && states_.back().base_time_ > to_base_time) {
|
Reset(true);
|
||||||
states_.pop_back();
|
} else {
|
||||||
}
|
current_state_ = *it;
|
||||||
|
RestoreFromCurrentState();
|
||||||
if (states_.empty()) {
|
}
|
||||||
Reset(true);
|
|
||||||
} else {
|
} else {
|
||||||
current_state_ = states_.back();
|
auto it = std::lower_bound(
|
||||||
RestoreFromCurrentState();
|
states_.begin(), states_.end(), to_base_time,
|
||||||
|
[&](const IntermediateState& state, millisecs_t time) -> bool {
|
||||||
|
return state.base_time_ < time;
|
||||||
|
});
|
||||||
|
if (it == states_.end()) {
|
||||||
|
if (!states_.empty()) {
|
||||||
|
current_state_ = states_.back();
|
||||||
|
RestoreFromCurrentState();
|
||||||
|
}
|
||||||
|
// Let's speed up replay a bit
|
||||||
|
// (and we'll collect needed states along).
|
||||||
|
is_fast_forwarding_ = true;
|
||||||
|
fast_forward_base_time_ = to_base_time;
|
||||||
|
} else {
|
||||||
|
current_state_ = *it;
|
||||||
|
RestoreFromCurrentState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientSessionReplay::RestoreFromCurrentState() {
|
void ClientSessionReplay::RestoreFromCurrentState() {
|
||||||
// what to do with messages_fetch_num_? is it used somewhere at all?
|
// FIXME: calling reset here causes background music to start over
|
||||||
Reset(true);
|
Reset(true);
|
||||||
fseek(file_, current_state_.file_position_, SEEK_SET);
|
fseek(file_, current_state_.file_position_, SEEK_SET);
|
||||||
|
|
||||||
|
|||||||
@ -28,8 +28,8 @@ class ClientSessionReplay : public ClientSession,
|
|||||||
|
|
||||||
void Error(const std::string& description) override;
|
void Error(const std::string& description) override;
|
||||||
void FetchMessages() override;
|
void FetchMessages() override;
|
||||||
void SaveState();
|
|
||||||
void RestoreState(millisecs_t to_base_time);
|
void SeekTo(millisecs_t to_base_time);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct IntermediateState {
|
struct IntermediateState {
|
||||||
@ -38,7 +38,7 @@ class ClientSessionReplay : public ClientSession,
|
|||||||
std::vector<std::vector<uint8_t>> correction_messages_;
|
std::vector<std::vector<uint8_t>> correction_messages_;
|
||||||
|
|
||||||
// A position in replay file where we should continue from.
|
// A position in replay file where we should continue from.
|
||||||
long file_position_;
|
int64_t file_position_;
|
||||||
|
|
||||||
millisecs_t base_time_;
|
millisecs_t base_time_;
|
||||||
};
|
};
|
||||||
@ -49,9 +49,9 @@ class ClientSessionReplay : public ClientSession,
|
|||||||
std::vector<IntermediateState> states_;
|
std::vector<IntermediateState> states_;
|
||||||
IntermediateState current_state_;
|
IntermediateState current_state_;
|
||||||
|
|
||||||
int unsaved_messages_count_{};
|
bool is_fast_forwarding_{};
|
||||||
|
millisecs_t fast_forward_base_time_{};
|
||||||
|
|
||||||
uint32_t message_fetch_num_{};
|
|
||||||
bool have_sent_client_message_{};
|
bool have_sent_client_message_{};
|
||||||
std::vector<ConnectionToClient*> connections_to_clients_;
|
std::vector<ConnectionToClient*> connections_to_clients_;
|
||||||
std::vector<ConnectionToClient*> connections_to_clients_ignored_;
|
std::vector<ConnectionToClient*> connections_to_clients_ignored_;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user