131 lines
3.5 KiB
C++
131 lines
3.5 KiB
C++
#include "ReplayRecorder.h"
|
|
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
#include <QByteArray>
|
|
#include <QDateTime>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
|
|
#include "Command.h"
|
|
#include "CommandSerializer.h"
|
|
#include "StateChecksum.h"
|
|
|
|
namespace
|
|
{
|
|
constexpr const char* kReplayFormatVersion = "1";
|
|
|
|
// Build fingerprint: even a new local build can desync old replays (float
|
|
// reasons), so the header carries a per-build tag to warn on mismatch.
|
|
const std::string kBuildTag = std::string(__DATE__) + " " + __TIME__;
|
|
|
|
std::string toHex(std::uint64_t value)
|
|
{
|
|
std::ostringstream out;
|
|
out << std::hex << std::setw(16) << std::setfill('0') << value;
|
|
return out.str();
|
|
}
|
|
} // namespace
|
|
|
|
ReplayRecorder::ReplayRecorder(std::string configDir, std::string outputDir)
|
|
: m_configDir(std::move(configDir))
|
|
, m_outputDir(std::move(outputDir))
|
|
{
|
|
}
|
|
|
|
ReplayRecorder::~ReplayRecorder()
|
|
{
|
|
close();
|
|
}
|
|
|
|
std::string computeReplayConfigHash(const std::string& configDir)
|
|
{
|
|
Hasher hasher;
|
|
QDir dir(QString::fromStdString(configDir));
|
|
const QStringList files =
|
|
dir.entryList(QStringList() << "*.toml", QDir::Files, QDir::Name);
|
|
for (const QString& name : files)
|
|
{
|
|
hasher.append(name.toStdString());
|
|
QFile file(dir.filePath(name));
|
|
if (file.open(QIODevice::ReadOnly))
|
|
{
|
|
const QByteArray bytes = file.readAll();
|
|
hasher.appendBytes(bytes.constData(), static_cast<std::size_t>(bytes.size()));
|
|
}
|
|
}
|
|
return toHex(hasher.getValue());
|
|
}
|
|
|
|
void ReplayRecorder::startNewRun(unsigned int seed, std::uint64_t initialRngFingerprint)
|
|
{
|
|
close();
|
|
|
|
QDir().mkpath(QString::fromStdString(m_outputDir));
|
|
const QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
|
|
const QString fileName = timestamp + "_" + QString::number(seed) + ".replay";
|
|
m_filePath = QDir(QString::fromStdString(m_outputDir)).filePath(fileName).toStdString();
|
|
|
|
m_stream.open(m_filePath, std::ios::out | std::ios::trunc);
|
|
if (!m_stream.is_open())
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_stream << "# dota_factory replay\n";
|
|
m_stream << "version " << kReplayFormatVersion << "\n";
|
|
m_stream << "build " << kBuildTag << "\n";
|
|
m_stream << "seed " << seed << "\n";
|
|
m_stream << "config_hash " << computeReplayConfigHash(m_configDir) << "\n";
|
|
m_stream << "timestamp "
|
|
<< QDateTime::currentDateTime().toString(Qt::ISODate).toStdString() << "\n";
|
|
m_stream << "---\n";
|
|
m_stream << "# checksum 0 " << toHex(initialRngFingerprint) << "\n";
|
|
m_stream.flush();
|
|
}
|
|
|
|
void ReplayRecorder::recordCommand(Tick tick, const Command& command,
|
|
std::uint64_t rngFingerprint)
|
|
{
|
|
if (!m_stream.is_open())
|
|
{
|
|
return;
|
|
}
|
|
m_stream << tick << ' ' << serializeCommand(command) << "\n";
|
|
m_stream << "# checksum " << tick << ' ' << toHex(rngFingerprint) << "\n";
|
|
m_stream.flush();
|
|
}
|
|
|
|
void ReplayRecorder::recordChecksum(Tick tick, std::uint64_t rngFingerprint)
|
|
{
|
|
if (!m_stream.is_open())
|
|
{
|
|
return;
|
|
}
|
|
m_stream << "# checksum " << tick << ' ' << toHex(rngFingerprint) << "\n";
|
|
m_stream.flush();
|
|
}
|
|
|
|
void ReplayRecorder::close()
|
|
{
|
|
if (m_stream.is_open())
|
|
{
|
|
m_stream.flush();
|
|
m_stream.close();
|
|
}
|
|
}
|
|
|
|
bool ReplayRecorder::isOpen() const
|
|
{
|
|
return m_stream.is_open();
|
|
}
|
|
|
|
const std::string& ReplayRecorder::getCurrentFilePath() const
|
|
{
|
|
return m_filePath;
|
|
}
|