2018-02-17 08:53:14 -08:00
/* Copyright (C) 2018 Wildfire Games.
2010-08-06 15:16:05 -07:00
* This file is part of 0 A . D .
*
* 0 A . D . 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 2 of the License , or
* ( at your option ) any later version .
*
* 0 A . D . 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 0 A . D . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "precompiled.h"
# include "Replay.h"
2010-09-17 10:45:50 -07:00
# include "graphics/TerrainTextureManager.h"
2011-03-02 16:16:14 -08:00
# include "lib/timer.h"
2010-08-06 15:16:05 -07:00
# include "lib/file/file_system.h"
2011-10-27 14:22:41 -07:00
# include "lib/res/h_mgr.h"
2010-09-17 10:45:50 -07:00
# include "lib/tex/tex.h"
2010-08-06 15:16:05 -07:00
# include "ps/Game.h"
2016-08-02 04:11:10 -07:00
# include "ps/CLogger.h"
2010-08-06 15:16:05 -07:00
# include "ps/Loader.h"
2015-09-21 10:00:21 -07:00
# include "ps/Mod.h"
2010-09-17 10:45:50 -07:00
# include "ps/Profile.h"
# include "ps/ProfileViewer.h"
2015-09-21 10:00:21 -07:00
# include "ps/Pyrogenesis.h"
2018-02-17 08:53:14 -08:00
# include "ps/Mod.h"
2016-02-15 07:57:23 -08:00
# include "ps/Util.h"
# include "ps/VisualReplay.h"
2010-08-06 15:16:05 -07:00
# include "scriptinterface/ScriptInterface.h"
2018-05-24 11:08:56 -07:00
# include "scriptinterface/ScriptRuntime.h"
2010-09-17 10:45:50 -07:00
# include "scriptinterface/ScriptStats.h"
2010-08-06 15:16:05 -07:00
# include "simulation2/Simulation2.h"
# include "simulation2/helpers/SimulationCommand.h"
2016-02-15 07:57:23 -08:00
# include <ctime>
2010-08-10 12:48:35 -07:00
# include <fstream>
2010-08-06 15:16:05 -07:00
2018-03-22 06:53:04 -07:00
/**
* Number of turns between two saved profiler snapshots .
* Keep in sync with source / tools / replayprofile / graph . js
*/
static const int PROFILE_TURN_INTERVAL = 20 ;
2017-08-23 17:32:42 -07:00
CReplayLogger : : CReplayLogger ( const ScriptInterface & scriptInterface ) :
2015-09-11 10:44:50 -07:00
m_ScriptInterface ( scriptInterface ) , m_Stream ( NULL )
{
}
CReplayLogger : : ~ CReplayLogger ( )
{
delete m_Stream ;
}
void CReplayLogger : : StartGame ( JS : : MutableHandleValue attribs )
2010-08-06 15:16:05 -07:00
{
2018-02-17 08:53:14 -08:00
JSContext * cx = m_ScriptInterface . GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2015-09-21 10:00:21 -07:00
// Add timestamp, since the file-modification-date can change
2017-04-01 14:06:55 -07:00
m_ScriptInterface . SetProperty ( attribs , " timestamp " , ( double ) std : : time ( nullptr ) ) ;
2015-09-21 10:00:21 -07:00
// Add engine version and currently loaded mods for sanity checks when replaying
m_ScriptInterface . SetProperty ( attribs , " engine_version " , CStr ( engine_version ) ) ;
2018-02-23 12:17:47 -08:00
JS : : RootedValue mods ( cx , Mod : : GetLoadedModsWithVersions ( m_ScriptInterface ) ) ;
m_ScriptInterface . SetProperty ( attribs , " mods " , mods ) ;
2015-09-21 10:00:21 -07:00
2016-05-15 17:56:07 -07:00
m_Directory = createDateIndexSubdirectory ( VisualReplay : : GetDirectoryName ( ) ) ;
2016-02-15 07:57:23 -08:00
debug_printf ( " Writing replay to %s \n " , m_Directory . string8 ( ) . c_str ( ) ) ;
2010-08-06 15:16:05 -07:00
2015-09-21 10:00:21 -07:00
m_Stream = new std : : ofstream ( OsString ( m_Directory / L " commands.txt " ) . c_str ( ) , std : : ofstream : : out | std : : ofstream : : trunc ) ;
2014-08-02 15:21:50 -07:00
* m_Stream < < " start " < < m_ScriptInterface . StringifyJSON ( attribs , false ) < < " \n " ;
2010-08-06 15:16:05 -07:00
}
2015-01-24 06:46:52 -08:00
void CReplayLogger : : Turn ( u32 n , u32 turnLength , std : : vector < SimulationCommand > & commands )
2010-08-06 15:16:05 -07:00
{
2014-08-02 15:21:50 -07:00
JSContext * cx = m_ScriptInterface . GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2016-01-22 17:02:57 -08:00
2010-08-06 15:16:05 -07:00
* m_Stream < < " turn " < < n < < " " < < turnLength < < " \n " ;
2017-02-04 18:18:33 -08:00
for ( SimulationCommand & command : commands )
* m_Stream < < " cmd " < < command . player < < " " < < m_ScriptInterface . StringifyJSON ( & command . data , false ) < < " \n " ;
2010-08-06 15:16:05 -07:00
* m_Stream < < " end \n " ;
m_Stream - > flush ( ) ;
}
2011-03-05 14:30:32 -08:00
void CReplayLogger : : Hash ( const std : : string & hash , bool quick )
2010-08-06 15:16:05 -07:00
{
2011-03-05 14:30:32 -08:00
if ( quick )
* m_Stream < < " hash-quick " < < Hexify ( hash ) < < " \n " ;
else
* m_Stream < < " hash " < < Hexify ( hash ) < < " \n " ;
2010-08-06 15:16:05 -07:00
}
2015-09-21 10:00:21 -07:00
OsPath CReplayLogger : : GetDirectory ( ) const
{
return m_Directory ;
}
2010-08-06 15:16:05 -07:00
////////////////////////////////////////////////////////////////
CReplayPlayer : : CReplayPlayer ( ) :
m_Stream ( NULL )
{
}
CReplayPlayer : : ~ CReplayPlayer ( )
{
delete m_Stream ;
}
2017-06-25 07:54:00 -07:00
void CReplayPlayer : : Load ( const OsPath & path )
2010-08-06 15:16:05 -07:00
{
2011-04-30 06:01:45 -07:00
ENSURE ( ! m_Stream ) ;
2010-08-06 15:16:05 -07:00
2017-06-25 07:54:00 -07:00
m_Stream = new std : : ifstream ( OsString ( path ) . c_str ( ) ) ;
2011-04-30 06:01:45 -07:00
ENSURE ( m_Stream - > good ( ) ) ;
2010-08-06 15:16:05 -07:00
}
2018-03-22 08:26:27 -07:00
CStr CReplayPlayer : : ModListToString ( const std : : vector < std : : vector < CStr > > & list ) const
{
CStr text ;
for ( const std : : vector < CStr > & mod : list )
text + = mod [ 0 ] + " ( " + mod [ 1 ] + " ) \n " ;
return text ;
}
void CReplayPlayer : : CheckReplayMods ( const ScriptInterface & scriptInterface , JS : : HandleValue attribs ) const
{
JSContext * cx = scriptInterface . GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
std : : vector < std : : vector < CStr > > replayMods ;
scriptInterface . GetProperty ( attribs , " mods " , replayMods ) ;
std : : vector < std : : vector < CStr > > enabledMods ;
JS : : RootedValue enabledModsJS ( cx , Mod : : GetLoadedModsWithVersions ( scriptInterface ) ) ;
scriptInterface . FromJSVal ( cx , enabledModsJS , enabledMods ) ;
CStr warn ;
if ( replayMods . size ( ) ! = enabledMods . size ( ) )
warn = " The number of enabled mods does not match the mods of the replay. " ;
else
for ( size_t i = 0 ; i < replayMods . size ( ) ; + + i )
{
if ( replayMods [ i ] [ 0 ] ! = enabledMods [ i ] [ 0 ] )
{
warn = " The enabled mods don't match the mods of the replay. " ;
break ;
}
else if ( replayMods [ i ] [ 1 ] ! = enabledMods [ i ] [ 1 ] )
{
warn = " The mod ' " + replayMods [ i ] [ 0 ] + " ' with version ' " + replayMods [ i ] [ 1 ] + " ' is required by the replay file, but version ' " + enabledMods [ i ] [ 1 ] + " ' is present! " ;
break ;
}
}
if ( ! warn . empty ( ) )
LOGWARNING ( " %s \n The mods of the replay are: \n %s \n These mods are enabled: \n %s " , warn , ModListToString ( replayMods ) , ModListToString ( enabledMods ) ) ;
}
2018-05-28 19:14:38 -07:00
void CReplayPlayer : : Replay ( const bool serializationtest , const int rejointestturn , const bool ooslog , const bool testHashFull , const bool testHashQuick )
2010-08-06 15:16:05 -07:00
{
2011-04-30 06:01:45 -07:00
ENSURE ( m_Stream ) ;
2010-08-06 15:16:05 -07:00
2010-09-17 10:45:50 -07:00
new CProfileViewer ;
new CProfileManager ;
g_ScriptStatsTable = new CScriptStatsTable ;
g_ProfileViewer . AddRootTable ( g_ScriptStatsTable ) ;
2016-01-22 17:02:57 -08:00
2014-09-22 13:13:04 -07:00
const int runtimeSize = 384 * 1024 * 1024 ;
const int heapGrowthBytesGCTrigger = 20 * 1024 * 1024 ;
2015-01-24 06:46:52 -08:00
g_ScriptRuntime = ScriptInterface : : CreateRuntime ( shared_ptr < ScriptRuntime > ( ) , runtimeSize , heapGrowthBytesGCTrigger ) ;
2010-09-17 10:45:50 -07:00
2018-05-24 11:08:56 -07:00
Mod : : CacheEnabledModVersions ( g_ScriptRuntime ) ;
2016-01-22 17:02:57 -08:00
g_Game = new CGame ( true , false ) ;
2014-07-22 12:41:49 -07:00
if ( serializationtest )
2015-05-05 15:34:41 -07:00
g_Game - > GetSimulation2 ( ) - > EnableSerializationTest ( ) ;
2016-11-15 05:26:58 -08:00
if ( rejointestturn > 0 )
g_Game - > GetSimulation2 ( ) - > EnableRejoinTest ( rejointestturn ) ;
2015-04-11 11:12:35 -07:00
if ( ooslog )
2015-05-05 15:34:41 -07:00
g_Game - > GetSimulation2 ( ) - > EnableOOSLog ( ) ;
2010-08-06 15:16:05 -07:00
2010-09-17 10:45:50 -07:00
// Need some stuff for terrain movement costs:
// (TODO: this ought to be independent of any graphics code)
new CTerrainTextureManager ;
g_TexMan . LoadTerrainTextures ( ) ;
2011-10-27 14:22:41 -07:00
// Initialise h_mgr so it doesn't crash when emitting sounds
h_mgr_init ( ) ;
2010-09-17 10:45:50 -07:00
2010-08-06 15:16:05 -07:00
std : : vector < SimulationCommand > commands ;
2010-09-17 10:45:50 -07:00
u32 turn = 0 ;
2010-08-09 02:49:24 -07:00
u32 turnLength = 0 ;
2010-08-06 15:16:05 -07:00
2015-05-06 13:28:28 -07:00
{
JSContext * cx = g_Game - > GetSimulation2 ( ) - > GetScriptInterface ( ) . GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2010-08-06 15:16:05 -07:00
std : : string type ;
2018-03-22 08:26:27 -07:00
2010-08-06 15:16:05 -07:00
while ( ( * m_Stream > > type ) . good ( ) )
{
if ( type = = " start " )
{
std : : string line ;
std : : getline ( * m_Stream , line ) ;
2014-08-02 15:21:50 -07:00
JS : : RootedValue attribs ( cx ) ;
2015-05-05 15:34:41 -07:00
ENSURE ( g_Game - > GetSimulation2 ( ) - > GetScriptInterface ( ) . ParseJSON ( line , & attribs ) ) ;
2010-08-06 15:16:05 -07:00
2018-03-22 08:26:27 -07:00
CheckReplayMods ( g_Game - > GetSimulation2 ( ) - > GetScriptInterface ( ) , attribs ) ;
2016-08-02 04:11:10 -07:00
2015-05-05 15:34:41 -07:00
g_Game - > StartGame ( & attribs , " " ) ;
2011-04-06 19:32:16 -07:00
// TODO: Non progressive load can fail - need a decent way to handle this
2010-08-06 15:16:05 -07:00
LDR_NonprogressiveLoad ( ) ;
2011-04-06 19:32:16 -07:00
2015-05-05 15:34:41 -07:00
PSRETURN ret = g_Game - > ReallyStartGame ( ) ;
2011-04-30 06:01:45 -07:00
ENSURE ( ret = = PSRETURN_OK ) ;
2010-08-06 15:16:05 -07:00
}
else if ( type = = " turn " )
{
* m_Stream > > turn > > turnLength ;
2015-04-12 12:38:31 -07:00
debug_printf ( " Turn %u (%u)... \n " , turn , turnLength ) ;
2010-08-06 15:16:05 -07:00
}
else if ( type = = " cmd " )
{
2013-06-10 17:05:57 -07:00
player_id_t player ;
2010-08-06 15:16:05 -07:00
* m_Stream > > player ;
std : : string line ;
std : : getline ( * m_Stream , line ) ;
2014-08-02 15:21:50 -07:00
JS : : RootedValue data ( cx ) ;
2015-05-05 15:34:41 -07:00
g_Game - > GetSimulation2 ( ) - > GetScriptInterface ( ) . ParseJSON ( line , & data ) ;
2016-01-25 12:37:26 -08:00
g_Game - > GetSimulation2 ( ) - > GetScriptInterface ( ) . FreezeObject ( data , true ) ;
2015-01-24 06:46:52 -08:00
commands . emplace_back ( SimulationCommand ( player , cx , data ) ) ;
2010-08-06 15:16:05 -07:00
}
2011-03-05 14:30:32 -08:00
else if ( type = = " hash " | | type = = " hash-quick " )
2010-09-17 10:45:50 -07:00
{
std : : string replayHash ;
* m_Stream > > replayHash ;
2018-05-28 19:14:38 -07:00
TestHash ( type , replayHash , testHashFull , testHashQuick ) ;
2010-09-17 10:45:50 -07:00
}
2010-08-06 15:16:05 -07:00
else if ( type = = " end " )
{
2011-11-29 12:32:43 -08:00
{
g_Profiler2 . RecordFrameStart ( ) ;
PROFILE2 ( " frame " ) ;
g_Profiler2 . IncrementFrameNumber ( ) ;
PROFILE2_ATTR ( " %d " , g_Profiler2 . GetFrameNumber ( ) ) ;
2015-05-05 15:34:41 -07:00
g_Game - > GetSimulation2 ( ) - > Update ( turnLength , commands ) ;
2011-11-29 12:32:43 -08:00
commands . clear ( ) ;
}
2010-08-06 15:16:05 -07:00
2010-09-17 10:45:50 -07:00
g_Profiler . Frame ( ) ;
2018-03-22 06:53:04 -07:00
if ( turn % PROFILE_TURN_INTERVAL = = 0 )
2010-09-17 10:45:50 -07:00
g_ProfileViewer . SaveToFile ( ) ;
2010-08-06 15:16:05 -07:00
}
else
2015-02-13 17:45:13 -08:00
debug_printf ( " Unrecognised replay token %s \n " , type . c_str ( ) ) ;
2010-08-06 15:16:05 -07:00
}
2015-05-06 13:28:28 -07:00
}
2010-09-03 02:55:14 -07:00
2015-07-30 09:43:22 -07:00
SAFE_DELETE ( m_Stream ) ;
2011-11-29 12:32:43 -08:00
g_Profiler2 . SaveToFile ( ) ;
2010-09-03 02:55:14 -07:00
std : : string hash ;
2015-05-05 15:34:41 -07:00
bool ok = g_Game - > GetSimulation2 ( ) - > ComputeStateHash ( hash , false ) ;
2011-04-30 06:01:45 -07:00
ENSURE ( ok ) ;
2015-02-13 17:45:13 -08:00
debug_printf ( " # Final state: %s \n " , Hexify ( hash ) . c_str ( ) ) ;
2011-03-02 16:16:14 -08:00
timer_DisplayClientTotals ( ) ;
2015-05-06 13:28:28 -07:00
SAFE_DELETE ( g_Game ) ;
2014-03-28 13:26:32 -07:00
// Must be explicitly destructed here to avoid callbacks from the JSAPI trying to use g_Profiler2 when
// it's already destructed.
g_ScriptRuntime . reset ( ) ;
2016-01-22 17:02:57 -08:00
2010-09-17 10:45:50 -07:00
// Clean up
delete & g_TexMan ;
delete & g_Profiler ;
delete & g_ProfileViewer ;
2015-05-05 15:34:41 -07:00
SAFE_DELETE ( g_ScriptStatsTable ) ;
2010-08-06 15:16:05 -07:00
}
2018-05-28 19:14:38 -07:00
void CReplayPlayer : : TestHash ( const std : : string & hashType , const std : : string & replayHash , const bool testHashFull , const bool testHashQuick )
{
bool quick = ( hashType = = " hash-quick " ) ;
if ( ( quick & & ! testHashQuick ) | | ( ! quick & & ! testHashFull ) )
return ;
std : : string hash ;
ENSURE ( g_Game - > GetSimulation2 ( ) - > ComputeStateHash ( hash , quick ) ) ;
std : : string hexHash = Hexify ( hash ) ;
if ( hexHash = = replayHash )
debug_printf ( " %s ok (%s) \n " , hashType . c_str ( ) , hexHash . c_str ( ) ) ;
else
debug_printf ( " %s MISMATCH (%s != %s) \n " , hashType . c_str ( ) , hexHash . c_str ( ) , replayHash . c_str ( ) ) ;
}