Skip to content

[GEN][ZH] Add Replay Simulation feature #923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 109 commits into from
Jun 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
8005a7b
Fix messy code and potential null pointer dereference in RecorderClas…
helmutbuhler Apr 13, 2025
c2f8d14
Fix Replay playback not working after running analysis on a replay.
helmutbuhler Apr 13, 2025
942e5e1
Add replay simulation functionality
helmutbuhler Apr 13, 2025
120fa4b
Add -simReplay command line option to run replays without graphics
helmutbuhler Apr 13, 2025
a77e2b3
Return Exitcode 1 if mismatch is detected in a replay while simulating.
helmutbuhler Apr 13, 2025
f0057b7
Make Replay Simulation compile in Release and remove VERIFY_CRC.
helmutbuhler Apr 13, 2025
3241aef
Fix draw assignment in StealthUpdate::changeVisualDisguise()
helmutbuhler Apr 13, 2025
9fa390b
Show analyze and simulate button in replaymenu when DEBUG_LOGGING is …
helmutbuhler Apr 18, 2025
7900ab1
Add code to write out replay list to textfile
helmutbuhler Apr 26, 2025
3499ccd
Update WriteOutReplayList()
helmutbuhler Apr 26, 2025
818161f
Make Replay extra buttons show up always
helmutbuhler Apr 29, 2025
19b8a42
Add m_playbackFrameDuration to Recorder
helmutbuhler Apr 29, 2025
4e562e2
Update WriteOutReplayList and add ReadReplayListFromCsv and -simRepla…
helmutbuhler Apr 29, 2025
60789e6
Add more logging to SimulateReplayList and run each replay in separat…
helmutbuhler Apr 29, 2025
3852327
SimulateReplayList: Count errors
helmutbuhler Apr 29, 2025
8faca18
Factor CreateProcessA into helper function
helmutbuhler Apr 30, 2025
6b23bf8
Make Performance Timers work (WIP)
helmutbuhler May 2, 2025
c0671bd
Make SimulateReplayList faster by calling TheParticleSystemManager->r…
helmutbuhler May 2, 2025
dffca9e
Add GameClient::updateHeadless()
helmutbuhler May 3, 2025
6b3348f
Use pipes to retrieve worker process console output
helmutbuhler May 3, 2025
a2e7c5a
Add SimulateReplayListMultiProcess. It seems to work now!
helmutbuhler May 3, 2025
efd7c14
Use TerminateProcess to work around crash
helmutbuhler May 4, 2025
4673944
Expand columns in WriteOutReplayList and fix ReadLineFromFile
helmutbuhler May 4, 2025
9b7eadc
Add Job stuff to ReplayProcess
helmutbuhler May 4, 2025
a82de61
Fix Jobs for VC6
helmutbuhler May 4, 2025
4835c7f
Revert "Make Performance Timers work (WIP)"
helmutbuhler May 8, 2025
7efcf7c
Merge tag 'maaaaaaaaaaaaain' into sim_replay_sh
helmutbuhler May 8, 2025
7352e5b
Fix date formats in comments
helmutbuhler May 8, 2025
2f01c18
Remove copied comment
helmutbuhler May 8, 2025
08fad6f
Remove bugfix that doesn't belong here
helmutbuhler May 8, 2025
3873f69
Remove added dump code in RecorderClass::handleCRCMessage
helmutbuhler May 8, 2025
eac7e88
Remove merged headless references
helmutbuhler May 8, 2025
c6ede9d
Fix simulation loop in ReplayMenu
helmutbuhler May 9, 2025
8c6bfef
Make ReadReplayListFromCsv work in subfolders
helmutbuhler May 12, 2025
95e6769
Make subprocesses call with -headless
helmutbuhler May 12, 2025
9820db2
Allow more iniCRCs in WriteOutReplayList
helmutbuhler May 12, 2025
1582909
Move Worker Process stuff into separate file
helmutbuhler May 19, 2025
dbeb149
Move command creation out of WorkerProcess::StartProcess
helmutbuhler May 19, 2025
b924e23
Put Replay Simulation Code into own file
helmutbuhler May 22, 2025
8875ac2
Put ReadReplayListFromCsv and WriteOutReplayList into separate file
helmutbuhler May 23, 2025
e493557
Add -jobs commandline
helmutbuhler May 23, 2025
4f59916
Fix whitespace and revert TheFileSystem->getFileListInDirectory fix
helmutbuhler May 23, 2025
1fc78f3
Merge branch 'main' into sim_replay_sh
helmutbuhler May 23, 2025
03bf7b3
Fix wrong merge
helmutbuhler May 23, 2025
7f44033
Don't check for multiple instances in headless mode
helmutbuhler May 23, 2025
0b15614
Add -writeReplayList commandline and make it work with relative paths
helmutbuhler May 24, 2025
417cc01
Use headless mode for -writeReplayList, -simReplay, -simReplayList. R…
helmutbuhler May 24, 2025
208365a
Remove obsolete headless checks
helmutbuhler May 24, 2025
cc1c163
After using simulation button, return to scorescreen instead of Repla…
helmutbuhler May 24, 2025
889fd8e
Remove obsolete headless check in GameLogic::startNewGame
helmutbuhler May 24, 2025
a969df5
Remove Int mismatchFrame simplification in Recorder
helmutbuhler May 24, 2025
cd802df
Remove -writeReplayList and -simReplayList commandline options (goes …
helmutbuhler May 24, 2025
f640caa
Remove "Debug: Simulate Replay"-button in replaymenu (goes into other…
helmutbuhler May 24, 2025
e8b6be5
Remove unnecessary TheControlBar != NULL check
helmutbuhler May 24, 2025
fe00285
Merge branch 'main' into sim_replay_sh
helmutbuhler Jun 5, 2025
1219542
Remove refactoring out of RecorderClass::appendNextCommand()
helmutbuhler Jun 5, 2025
4eb8988
Add stupid const
helmutbuhler Jun 6, 2025
03c530b
Rename Replay Simulation stuff
helmutbuhler Jun 6, 2025
928c624
Fix minor syntax stuff
helmutbuhler Jun 6, 2025
a872b93
Use TheCommandList in RecorderClass::stopPlayback() for consistency
helmutbuhler Jun 6, 2025
c937813
Rename Replay Header member FrameDuration to FrameCount
helmutbuhler Jun 6, 2025
2319046
Make methods of WorkerProcess lowercase
helmutbuhler Jun 6, 2025
8208f9d
Create helper method fetchStdOutput() in WorkerProcess
helmutbuhler Jun 7, 2025
a3c07e9
Some minor changes
helmutbuhler Jun 7, 2025
1b715d2
"-simReplayList" checks belong in other PR
helmutbuhler Jun 7, 2025
115b153
Remove out parameters in WorkerProcess::isDone
helmutbuhler Jun 7, 2025
c2d58ac
variable renaming
helmutbuhler Jun 7, 2025
283e5c6
More renaming
helmutbuhler Jun 7, 2025
8c9612b
remove hacks
helmutbuhler Jun 7, 2025
6a362bd
Add SIMULATE_REPLAYS_SEQUENTIAL and some refactoring
helmutbuhler Jun 7, 2025
1558a3a
more minor stuff
helmutbuhler Jun 7, 2025
568832b
Update comment
helmutbuhler Jun 7, 2025
d90e11a
Move new files to Core
helmutbuhler Jun 7, 2025
ed64972
Merge branch 'main' into sim_replay_sh
helmutbuhler Jun 7, 2025
d660a93
Make -simReplay work without headless
helmutbuhler Jun 14, 2025
020ee42
Add -headless option back
helmutbuhler Jun 14, 2025
b7d559d
Rename -simReplay to -replay
helmutbuhler Jun 14, 2025
702dfa0
Fix merge
helmutbuhler Jun 14, 2025
1e70e82
Merge branch 'main' into sim_replay_sh
helmutbuhler Jun 16, 2025
6ddfdf7
Add m_avoidFirstInstance and m_multiInstance and make -replay work wh…
helmutbuhler Jun 17, 2025
30fc12f
Merge branch 'main' of https://github.com/TheSuperHackers/GeneralsGam…
helmutbuhler Jun 17, 2025
b24eca8
Remove now unused GameMain( int argc, char *argv[] ) parameters
helmutbuhler Jun 17, 2025
d3febea
Changed error handling of -replay commandline parsing to output somet…
helmutbuhler Jun 18, 2025
b3243c8
Allow wildcards in -replay commandline
helmutbuhler Jun 18, 2025
c353b94
Update -replay comment
helmutbuhler Jun 18, 2025
0a63c92
Use mismatchFrame to simplify printf
helmutbuhler Jun 18, 2025
5fced5b
Move m_multiInstance and m_avoidFirstInstance to ClientInstance
helmutbuhler Jun 19, 2025
2d2576f
Take out -multiInstance
helmutbuhler Jun 19, 2025
89431bf
Change comment
helmutbuhler Jun 19, 2025
87462b7
Move CommandLine::parseCommandLineForStartup(); to DebugInit
helmutbuhler Jun 19, 2025
dbf9d82
Convert one missed #if defined(RTS_MULTI_INSTANCE) to use new global
helmutbuhler Jun 19, 2025
a1266af
spelling occured -> occurred
helmutbuhler Jun 19, 2025
6d5b60c
Remove unused var
helmutbuhler Jun 19, 2025
754b054
Add comment
helmutbuhler Jun 19, 2025
104a25b
Add console output in case map is not found
helmutbuhler Jun 19, 2025
a3a21b9
Revert "spelling occured -> occurred" in part
xezon Jun 20, 2025
cb021ff
Refactor ClientInstance code
xezon Jun 20, 2025
a4bf57f
Remove GlobalData::m_showReplayContinueButton
xezon Jun 20, 2025
a0ecf5a
Fix SIMULATE_REPLAYS_SEQUENTIAL constexpr'ness
xezon Jun 20, 2025
ee4a554
Return WorkerProcess::startProcess if CreatePipe has failed
xezon Jun 20, 2025
f80d432
Add assert for m_readHandle
xezon Jun 20, 2025
6b72afd
Clarify -jobs range in comment
xezon Jun 20, 2025
7dbe6f7
Move logic to count running processes into separate function
xezon Jun 20, 2025
ede82f8
Fix constexpr in VC6 build
xezon Jun 20, 2025
118ef4d
Fix debug compile error
xezon Jun 20, 2025
e2947b4
Merge branch 'main' into sim_replay_sh
helmutbuhler Jun 21, 2025
05ca54e
Auto Sync with Generals
helmutbuhler Jun 21, 2025
df74c7f
Apply manual sync for failed autosync
helmutbuhler Jun 21, 2025
1a7d27a
Revert TheCommandList back to TheMessageStream in RecorderClass::stop…
helmutbuhler Jun 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Core/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ set(GAMEENGINE_SRC
# Include/Common/RandomValue.h
# Include/Common/Recorder.h
# Include/Common/Registry.h
Include/Common/ReplaySimulation.h
# Include/Common/ResourceGatheringManager.h
# Include/Common/Science.h
# Include/Common/ScopedMutex.h
Expand Down Expand Up @@ -129,6 +130,7 @@ set(GAMEENGINE_SRC
# Include/Common/UserPreferences.h
# Include/Common/version.h
# Include/Common/WellKnownKeys.h
Include/Common/WorkerProcess.h
Include/Common/Xfer.h
Include/Common/XferCRC.h
Include/Common/XferDeepCRC.h
Expand Down Expand Up @@ -607,6 +609,7 @@ set(GAMEENGINE_SRC
# Source/Common/PerfTimer.cpp
# Source/Common/RandomValue.cpp
# Source/Common/Recorder.cpp
Source/Common/ReplaySimulation.cpp
# Source/Common/RTS/AcademyStats.cpp
# Source/Common/RTS/ActionManager.cpp
# Source/Common/RTS/Energy.cpp
Expand Down Expand Up @@ -676,6 +679,7 @@ set(GAMEENGINE_SRC
# Source/Common/Thing/ThingTemplate.cpp
# Source/Common/UserPreferences.cpp
# Source/Common/version.cpp
Source/Common/WorkerProcess.cpp
# Source/GameClient/ClientInstance.cpp
# Source/GameClient/Color.cpp
# Source/GameClient/Credits.cpp
Expand Down
48 changes: 48 additions & 0 deletions Core/GameEngine/Include/Common/ReplaySimulation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 TheSuperHackers
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

class ReplaySimulation
{
public:

// TheSuperHackers @feature helmutbuhler 13/04/2025
// Simulate a list of replays without graphics.
// Returns exit code 1 if mismatch or other error occurred
// Returns exit code 0 if all replays were successfully simulated without mismatches
static int simulateReplays(const std::vector<AsciiString> &filenames, int maxProcesses);

static void stop() { s_isRunning = false; }

static Bool isRunning() { return s_isRunning; }
static UnsignedInt getCurrentReplayIndex() { return s_replayIndex; }
static UnsignedInt getReplayCount() { return s_replayCount; }

private:

static int simulateReplaysInThisProcess(const std::vector<AsciiString> &filenames);
static int simulateReplaysInWorkerProcesses(const std::vector<AsciiString> &filenames, int maxProcesses);
static std::vector<AsciiString> resolveFilenameWildcards(const std::vector<AsciiString> &filenames);

private:

static Bool s_isRunning;
static UnsignedInt s_replayIndex;
static UnsignedInt s_replayCount;
};
56 changes: 56 additions & 0 deletions Core/GameEngine/Include/Common/WorkerProcess.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 TheSuperHackers
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

// Helper class that allows you to start a worker process and retrieve its exit code
// and console output as a string.
// It also makes sure that the started process is killed in case our process exits in any way.
class WorkerProcess
{
public:
WorkerProcess();

bool startProcess(UnicodeString command);

void update();

bool isRunning() const;

// returns true iff the process exited.
bool isDone() const;

DWORD getExitCode() const;
AsciiString getStdOutput() const;

// Terminate Process if it's running
void kill();

private:
// returns true if all output has been received
// returns false if the worker is still running
bool fetchStdOutput();

private:
HANDLE m_processHandle;
HANDLE m_readHandle;
HANDLE m_jobHandle;
AsciiString m_stdOutput;
DWORD m_exitcode;
bool m_isDone;
};
255 changes: 255 additions & 0 deletions Core/GameEngine/Source/Common/ReplaySimulation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 TheSuperHackers
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine

#include "Common/ReplaySimulation.h"

#include "Common/GameEngine.h"
#include "Common/LocalFileSystem.h"
#include "Common/Recorder.h"
#include "Common/WorkerProcess.h"
#include "GameLogic/GameLogic.h"
#include "GameClient/GameClient.h"


Bool ReplaySimulation::s_isRunning = false;
UnsignedInt ReplaySimulation::s_replayIndex = 0;
UnsignedInt ReplaySimulation::s_replayCount = 0;

namespace
{
int countProcessesRunning(const std::vector<WorkerProcess>& processes)
{
int numProcessesRunning = 0;
size_t i = 0;
for (; i < processes.size(); ++i)
{
if (processes[i].isRunning())
++numProcessesRunning;
}
return numProcessesRunning;
}
} // namespace

int ReplaySimulation::simulateReplaysInThisProcess(const std::vector<AsciiString> &filenames)
{
int numErrors = 0;

if (!TheGlobalData->m_headless)
{
s_isRunning = true;
s_replayIndex = 0;
s_replayCount = static_cast<UnsignedInt>(filenames.size());

// If we are not in headless mode, we need to run the replay in the engine.
for (; s_replayIndex < s_replayCount; ++s_replayIndex)
{
TheRecorder->playbackFile(filenames[s_replayIndex]);
TheGameEngine->execute();
if (TheRecorder->sawCRCMismatch())
numErrors++;
if (!s_isRunning)
break;
TheGameEngine->setQuitting(FALSE);
}
s_isRunning = false;
s_replayIndex = 0;
s_replayCount = 0;
return numErrors != 0 ? 1 : 0;
}
// Note that we use printf here because this is run from cmd.
DWORD totalStartTimeMillis = GetTickCount();
for (size_t i = 0; i < filenames.size(); i++)
{
AsciiString filename = filenames[i];
printf("Simulating Replay \"%s\"\n", filename.str());
fflush(stdout);
DWORD startTimeMillis = GetTickCount();
if (TheRecorder->simulateReplay(filename))
{
UnsignedInt totalTimeSec = TheRecorder->getPlaybackFrameCount() / LOGICFRAMES_PER_SECOND;
while (TheRecorder->isPlaybackInProgress())
{
TheGameClient->updateHeadless();

const int progressFrameInterval = 10*60*LOGICFRAMES_PER_SECOND;
if (TheGameLogic->getFrame() != 0 && TheGameLogic->getFrame() % progressFrameInterval == 0)
{
// Print progress report
UnsignedInt gameTimeSec = TheGameLogic->getFrame() / LOGICFRAMES_PER_SECOND;
UnsignedInt realTimeSec = (GetTickCount()-startTimeMillis) / 1000;
printf("Elapsed Time: %02d:%02d Game Time: %02d:%02d/%02d:%02d\n",
realTimeSec/60, realTimeSec%60, gameTimeSec/60, gameTimeSec%60, totalTimeSec/60, totalTimeSec%60);
fflush(stdout);
}
TheGameLogic->UPDATE();
if (TheRecorder->sawCRCMismatch())
{
numErrors++;
break;
}
}
UnsignedInt gameTimeSec = TheGameLogic->getFrame() / LOGICFRAMES_PER_SECOND;
UnsignedInt realTimeSec = (GetTickCount()-startTimeMillis) / 1000;
printf("Elapsed Time: %02d:%02d Game Time: %02d:%02d/%02d:%02d\n",
realTimeSec/60, realTimeSec%60, gameTimeSec/60, gameTimeSec%60, totalTimeSec/60, totalTimeSec%60);
fflush(stdout);
}
else
{
printf("Cannot open replay\n");
numErrors++;
}
}
if (filenames.size() > 1)
{
printf("Simulation of all replays completed. Errors occurred: %d\n", numErrors);

UnsignedInt realTime = (GetTickCount()-totalStartTimeMillis) / 1000;
printf("Total Time: %d:%02d:%02d\n", realTime/60/60, realTime/60%60, realTime%60);
fflush(stdout);
}

return numErrors != 0 ? 1 : 0;
}

int ReplaySimulation::simulateReplaysInWorkerProcesses(const std::vector<AsciiString> &filenames, int maxProcesses)
{
DWORD totalStartTimeMillis = GetTickCount();

WideChar exePath[1024];
GetModuleFileNameW(NULL, exePath, ARRAY_SIZE(exePath));

std::vector<WorkerProcess> processes;
int filenamePositionStarted = 0;
int filenamePositionDone = 0;
int numErrors = 0;

while (true)
{
int i;
for (i = 0; i < processes.size(); i++)
processes[i].update();

// Get result of finished processes and print output in order
while (!processes.empty())
{
if (!processes[0].isDone())
break;
AsciiString stdOutput = processes[0].getStdOutput();
printf("%d/%d %s", filenamePositionDone+1, (int)filenames.size(), stdOutput.str());
DWORD exitcode = processes[0].getExitCode();
if (exitcode != 0)
printf("Error!\n");
fflush(stdout);
numErrors += exitcode == 0 ? 0 : 1;
processes.erase(processes.begin());
filenamePositionDone++;
}

int numProcessesRunning = countProcessesRunning(processes);

// Add new processes when we are below the limit and there are replays left
while (numProcessesRunning < maxProcesses && filenamePositionStarted < filenames.size())
{
UnicodeString filenameWide;
filenameWide.translate(filenames[filenamePositionStarted]);
UnicodeString command;
command.format(L"\"%s\"%s%s -replay \"%s\"",
exePath,
TheGlobalData->m_windowed ? L" -win" : L"",
TheGlobalData->m_headless ? L" -headless" : L"",
filenameWide.str());

processes.push_back(WorkerProcess());
processes.back().startProcess(command);

filenamePositionStarted++;
numProcessesRunning++;
}

if (processes.empty())
break;

// Don't waste CPU here, our workers need every bit of CPU time they can get
Sleep(100);
}

DEBUG_ASSERTCRASH(filenamePositionStarted == filenames.size(), ("inconsistent file position 1"));
DEBUG_ASSERTCRASH(filenamePositionDone == filenames.size(), ("inconsistent file position 2"));

printf("Simulation of all replays completed. Errors occurred: %d\n", numErrors);

UnsignedInt realTime = (GetTickCount()-totalStartTimeMillis) / 1000;
printf("Total Wall Time: %d:%02d:%02d\n", realTime/60/60, realTime/60%60, realTime%60);
fflush(stdout);

return numErrors != 0 ? 1 : 0;
}

std::vector<AsciiString> ReplaySimulation::resolveFilenameWildcards(const std::vector<AsciiString> &filenames)
{
// If some filename contains wildcards, search for actual filenames.
// Note that we cannot do this in parseReplay because we require TheLocalFileSystem initialized.
std::vector<AsciiString> filenamesResolved;
for (std::vector<AsciiString>::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename)
{
if (filename->find('*') || filename->find('?'))
{
AsciiString dir1 = TheRecorder->getReplayDir();
AsciiString dir2 = *filename;
AsciiString wildcard = *filename;
{
int len = dir2.getLength();
while (len)
{
char c = dir2.getCharAt(len-1);
if (c == '/' || c == '\\')
{
wildcard.set(wildcard.str()+dir2.getLength());
break;
}
dir2.removeLastChar();
len--;
}
}

FilenameList files;
TheLocalFileSystem->getFileListInDirectory(dir2.str(), dir1.str(), wildcard, files, FALSE);
for (FilenameList::iterator it = files.begin(); it != files.end(); ++it)
{
AsciiString file;
file.set(it->str() + dir1.getLength());
filenamesResolved.push_back(file);
}
}
else
filenamesResolved.push_back(*filename);
}
return filenamesResolved;
}

int ReplaySimulation::simulateReplays(const std::vector<AsciiString> &filenames, int maxProcesses)
{
std::vector<AsciiString> filenamesResolved = resolveFilenameWildcards(filenames);
if (maxProcesses == SIMULATE_REPLAYS_SEQUENTIAL)
return simulateReplaysInThisProcess(filenamesResolved);
else
return simulateReplaysInWorkerProcesses(filenamesResolved, maxProcesses);
}
Loading
Loading