Add deterministic record/playback for a run. Recording captures `(seed, config hash, ordered tick-tagged commands)` and re-simulates on playback — no state snapshots. `DotaFactory.exe --replay <file>` re-plays a recorded run view-only with manual speed/pause. Reviewed-on: #4 Co-authored-by: Malte Langkabel <malte.langkabel@gmail.com> Co-committed-by: Malte Langkabel <malte.langkabel@gmail.com>
93 lines
2.4 KiB
C++
93 lines
2.4 KiB
C++
#include <memory>
|
|
#include <optional>
|
|
#include <random>
|
|
#include <string>
|
|
|
|
#include <QApplication>
|
|
#include <QDir>
|
|
|
|
#include "ConfigLoader.h"
|
|
#include "ConsoleLogger.h"
|
|
#include "logging.h"
|
|
#include "LogManager.h"
|
|
#include "MainWindow.h"
|
|
#include "ReplayReader.h"
|
|
#include "ReplayRecorder.h"
|
|
#include "Simulation.h"
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
LogManager::getInstance()->addLogger(std::make_shared<ConsoleLogger>());
|
|
LogManager::getInstance()->setLoggingEnabled(true);
|
|
LOG_INFO("Logging enabled");
|
|
|
|
QApplication::setApplicationName("DotaFactory");
|
|
|
|
if (QSysInfo::windowsVersion() != QSysInfo::WV_None)
|
|
{
|
|
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
|
|
}
|
|
|
|
QApplication application(argc, argv);
|
|
|
|
const QDir dataDir("data");
|
|
if (!dataDir.exists())
|
|
{
|
|
QDir().mkdir(dataDir.dirName());
|
|
}
|
|
|
|
// Optional "--replay <file>" launches view-only playback of a recorded run.
|
|
std::optional<std::string> replayPath;
|
|
for (int i = 1; i + 1 < argc; ++i)
|
|
{
|
|
if (std::string(argv[i]) == "--replay")
|
|
{
|
|
replayPath = argv[i + 1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
GameConfig config = ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
|
|
|
unsigned int seed = 0;
|
|
std::shared_ptr<ParsedReplay> replay;
|
|
if (replayPath.has_value())
|
|
{
|
|
std::optional<ParsedReplay> parsed = readReplayFile(*replayPath);
|
|
if (!parsed.has_value())
|
|
{
|
|
LOG_ERROR("Failed to read replay file: " + *replayPath);
|
|
return 1;
|
|
}
|
|
// Warn (but proceed) on identity mismatches: a different config or build
|
|
// can desync playback (see docs/replay_design.md).
|
|
if (parsed->header.version != 1)
|
|
{
|
|
LOG_WARNING_STREAM(<< "Replay format version " << parsed->header.version
|
|
<< " differs from 1; playback may fail");
|
|
}
|
|
if (computeReplayConfigHash(CONFIG_DIR) != parsed->header.configHash)
|
|
{
|
|
LOG_WARNING("Replay config hash mismatch; playback may desync");
|
|
}
|
|
seed = parsed->header.seed;
|
|
replay = std::make_shared<ParsedReplay>(std::move(*parsed));
|
|
}
|
|
else
|
|
{
|
|
// Random seed generated outside the sim so the Simulation stays a pure
|
|
// function of (seed, config, commands); written to the replay header
|
|
// (see docs/replay_design.md "Seed and config").
|
|
seed = std::random_device{}();
|
|
}
|
|
|
|
std::unique_ptr<Simulation> sim = std::make_unique<Simulation>(std::move(config), seed);
|
|
|
|
MainWindow window(sim.get(), std::string(CONFIG_DIR), replay);
|
|
window.show();
|
|
|
|
const int ret = application.exec();
|
|
|
|
return ret;
|
|
}
|