2020-11-13 14:18:22 +01:00
/* Copyright (C) 2020 Wildfire Games.
2010-08-07 00:16:05 +02: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 19:45:50 +02:00
# include "graphics/TerrainTextureManager.h"
2011-03-03 01:16:14 +01:00
# include "lib/timer.h"
2010-08-07 00:16:05 +02:00
# include "lib/file/file_system.h"
2011-10-27 23:22:41 +02:00
# include "lib/res/h_mgr.h"
2010-09-17 19:45:50 +02:00
# include "lib/tex/tex.h"
2010-08-07 00:16:05 +02:00
# include "ps/Game.h"
2016-08-02 13:11:10 +02:00
# include "ps/CLogger.h"
2010-08-07 00:16:05 +02:00
# include "ps/Loader.h"
2015-09-21 19:00:21 +02:00
# include "ps/Mod.h"
2010-09-17 19:45:50 +02:00
# include "ps/Profile.h"
# include "ps/ProfileViewer.h"
2015-09-21 19:00:21 +02:00
# include "ps/Pyrogenesis.h"
2018-02-17 17:53:14 +01:00
# include "ps/Mod.h"
2016-02-15 16:57:23 +01:00
# include "ps/Util.h"
# include "ps/VisualReplay.h"
2010-08-07 00:16:05 +02:00
# include "scriptinterface/ScriptInterface.h"
2018-05-24 20:08:56 +02:00
# include "scriptinterface/ScriptRuntime.h"
2010-09-17 19:45:50 +02:00
# include "scriptinterface/ScriptStats.h"
2019-09-25 12:06:12 +02:00
# include "simulation2/components/ICmpGuiInterface.h"
# include "simulation2/helpers/Player.h"
2010-08-07 00:16:05 +02:00
# include "simulation2/helpers/SimulationCommand.h"
2019-09-25 12:06:12 +02:00
# include "simulation2/Simulation2.h"
# include "simulation2/system/CmpPtr.h"
2010-08-07 00:16:05 +02:00
2016-02-15 16:57:23 +01:00
# include <ctime>
2010-08-10 21:48:35 +02:00
# include <fstream>
2010-08-07 00:16:05 +02:00
2018-03-22 14:53:04 +01: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-24 02:32:42 +02:00
CReplayLogger : : CReplayLogger ( const ScriptInterface & scriptInterface ) :
2015-09-11 19:44:50 +02:00
m_ScriptInterface ( scriptInterface ) , m_Stream ( NULL )
{
}
CReplayLogger : : ~ CReplayLogger ( )
{
delete m_Stream ;
}
void CReplayLogger : : StartGame ( JS : : MutableHandleValue attribs )
2010-08-07 00:16:05 +02:00
{
2020-11-13 14:18:22 +01:00
ScriptInterface : : Request rq ( m_ScriptInterface ) ;
2018-02-17 17:53:14 +01:00
2015-09-21 19:00:21 +02:00
// Add timestamp, since the file-modification-date can change
2017-04-01 23:06:55 +02:00
m_ScriptInterface . SetProperty ( attribs , " timestamp " , ( double ) std : : time ( nullptr ) ) ;
2015-09-21 19:00:21 +02:00
// Add engine version and currently loaded mods for sanity checks when replaying
2019-09-13 02:56:51 +02:00
m_ScriptInterface . SetProperty ( attribs , " engine_version " , engine_version ) ;
2020-11-13 14:18:22 +01:00
JS : : RootedValue mods ( rq . cx , Mod : : GetLoadedModsWithVersions ( m_ScriptInterface ) ) ;
2018-02-23 21:17:47 +01:00
m_ScriptInterface . SetProperty ( attribs , " mods " , mods ) ;
2015-09-21 19:00:21 +02:00
2019-05-14 12:13:02 +02:00
m_Directory = createDateIndexSubdirectory ( VisualReplay : : GetDirectoryPath ( ) ) ;
2016-02-15 16:57:23 +01:00
debug_printf ( " Writing replay to %s \n " , m_Directory . string8 ( ) . c_str ( ) ) ;
2010-08-07 00:16:05 +02:00
2015-09-21 19:00:21 +02:00
m_Stream = new std : : ofstream ( OsString ( m_Directory / L " commands.txt " ) . c_str ( ) , std : : ofstream : : out | std : : ofstream : : trunc ) ;
2014-08-03 00:21:50 +02:00
* m_Stream < < " start " < < m_ScriptInterface . StringifyJSON ( attribs , false ) < < " \n " ;
2010-08-07 00:16:05 +02:00
}
2015-01-24 15:46:52 +01:00
void CReplayLogger : : Turn ( u32 n , u32 turnLength , std : : vector < SimulationCommand > & commands )
2010-08-07 00:16:05 +02:00
{
2020-11-13 14:18:22 +01:00
ScriptInterface : : Request rq ( m_ScriptInterface ) ;
2016-01-23 02:02:57 +01:00
2010-08-07 00:16:05 +02:00
* m_Stream < < " turn " < < n < < " " < < turnLength < < " \n " ;
2017-02-05 03:18:33 +01:00
for ( SimulationCommand & command : commands )
* m_Stream < < " cmd " < < command . player < < " " < < m_ScriptInterface . StringifyJSON ( & command . data , false ) < < " \n " ;
2010-08-07 00:16:05 +02:00
* m_Stream < < " end \n " ;
m_Stream - > flush ( ) ;
}
2011-03-05 23:30:32 +01:00
void CReplayLogger : : Hash ( const std : : string & hash , bool quick )
2010-08-07 00:16:05 +02:00
{
2011-03-05 23:30:32 +01:00
if ( quick )
* m_Stream < < " hash-quick " < < Hexify ( hash ) < < " \n " ;
else
* m_Stream < < " hash " < < Hexify ( hash ) < < " \n " ;
2010-08-07 00:16:05 +02:00
}
2019-09-25 12:06:12 +02:00
void CReplayLogger : : SaveMetadata ( const CSimulation2 & simulation )
{
CmpPtr < ICmpGuiInterface > cmpGuiInterface ( simulation , SYSTEM_ENTITY ) ;
if ( ! cmpGuiInterface )
{
LOGERROR ( " Could not save replay metadata! " ) ;
return ;
}
ScriptInterface & scriptInterface = simulation . GetScriptInterface ( ) ;
2020-11-13 14:18:22 +01:00
ScriptInterface : : Request rq ( scriptInterface ) ;
2019-09-25 12:06:12 +02:00
2020-11-13 14:18:22 +01:00
JS : : RootedValue arg ( rq . cx ) ;
JS : : RootedValue metadata ( rq . cx ) ;
2019-09-25 12:06:12 +02:00
cmpGuiInterface - > ScriptCall ( INVALID_PLAYER , L " GetReplayMetadata " , arg , & metadata ) ;
const OsPath fileName = g_Game - > GetReplayLogger ( ) . GetDirectory ( ) / L " metadata.json " ;
CreateDirectories ( fileName . Parent ( ) , 0700 ) ;
std : : ofstream stream ( OsString ( fileName ) . c_str ( ) , std : : ofstream : : out | std : : ofstream : : trunc ) ;
stream < < scriptInterface . StringifyJSON ( & metadata , false ) ;
stream . close ( ) ;
debug_printf ( " Saved replay metadata to %s \n " , fileName . string8 ( ) . c_str ( ) ) ;
}
2015-09-21 19:00:21 +02:00
OsPath CReplayLogger : : GetDirectory ( ) const
{
return m_Directory ;
}
2010-08-07 00:16:05 +02:00
////////////////////////////////////////////////////////////////
CReplayPlayer : : CReplayPlayer ( ) :
m_Stream ( NULL )
{
}
CReplayPlayer : : ~ CReplayPlayer ( )
{
delete m_Stream ;
}
2017-06-25 16:54:00 +02:00
void CReplayPlayer : : Load ( const OsPath & path )
2010-08-07 00:16:05 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( ! m_Stream ) ;
2010-08-07 00:16:05 +02:00
2017-06-25 16:54:00 +02:00
m_Stream = new std : : ifstream ( OsString ( path ) . c_str ( ) ) ;
2011-04-30 15:01:45 +02:00
ENSURE ( m_Stream - > good ( ) ) ;
2010-08-07 00:16:05 +02:00
}
2018-03-22 16:26:27 +01: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
{
2020-11-13 14:18:22 +01:00
ScriptInterface : : Request rq ( scriptInterface ) ;
2018-03-22 16:26:27 +01:00
std : : vector < std : : vector < CStr > > replayMods ;
scriptInterface . GetProperty ( attribs , " mods " , replayMods ) ;
std : : vector < std : : vector < CStr > > enabledMods ;
2020-11-13 14:18:22 +01:00
JS : : RootedValue enabledModsJS ( rq . cx , Mod : : GetLoadedModsWithVersions ( scriptInterface ) ) ;
scriptInterface . FromJSVal ( rq , enabledModsJS , enabledMods ) ;
2018-03-22 16:26:27 +01:00
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-29 04:14:38 +02:00
void CReplayPlayer : : Replay ( const bool serializationtest , const int rejointestturn , const bool ooslog , const bool testHashFull , const bool testHashQuick )
2010-08-07 00:16:05 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( m_Stream ) ;
2010-08-07 00:16:05 +02:00
2010-09-17 19:45:50 +02:00
new CProfileViewer ;
new CProfileManager ;
g_ScriptStatsTable = new CScriptStatsTable ;
g_ProfileViewer . AddRootTable ( g_ScriptStatsTable ) ;
2016-01-23 02:02:57 +01:00
2014-09-22 22:13:04 +02:00
const int runtimeSize = 384 * 1024 * 1024 ;
const int heapGrowthBytesGCTrigger = 20 * 1024 * 1024 ;
2020-11-12 10:34:40 +01:00
g_ScriptRuntime = ScriptRuntime : : CreateRuntime ( runtimeSize , heapGrowthBytesGCTrigger ) ;
2010-09-17 19:45:50 +02:00
2018-05-24 20:08:56 +02:00
Mod : : CacheEnabledModVersions ( g_ScriptRuntime ) ;
2019-08-25 13:02:55 +02:00
g_Game = new CGame ( false ) ;
2014-07-22 21:41:49 +02:00
if ( serializationtest )
2015-05-06 00:34:41 +02:00
g_Game - > GetSimulation2 ( ) - > EnableSerializationTest ( ) ;
2019-09-26 14:09:19 +02:00
if ( rejointestturn > = 0 )
2016-11-15 14:26:58 +01:00
g_Game - > GetSimulation2 ( ) - > EnableRejoinTest ( rejointestturn ) ;
2015-04-11 20:12:35 +02:00
if ( ooslog )
2015-05-06 00:34:41 +02:00
g_Game - > GetSimulation2 ( ) - > EnableOOSLog ( ) ;
2010-08-07 00:16:05 +02:00
2010-09-17 19:45:50 +02: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 23:22:41 +02:00
// Initialise h_mgr so it doesn't crash when emitting sounds
h_mgr_init ( ) ;
2010-09-17 19:45:50 +02:00
2010-08-07 00:16:05 +02:00
std : : vector < SimulationCommand > commands ;
2010-09-17 19:45:50 +02:00
u32 turn = 0 ;
2010-08-09 11:49:24 +02:00
u32 turnLength = 0 ;
2010-08-07 00:16:05 +02:00
2015-05-06 22:28:28 +02:00
{
2020-11-13 14:18:22 +01:00
ScriptInterface : : Request rq ( g_Game - > GetSimulation2 ( ) - > GetScriptInterface ( ) ) ;
2010-08-07 00:16:05 +02:00
std : : string type ;
2018-03-22 16:26:27 +01:00
2010-08-07 00:16:05 +02:00
while ( ( * m_Stream > > type ) . good ( ) )
{
if ( type = = " start " )
{
std : : string line ;
std : : getline ( * m_Stream , line ) ;
2020-11-13 14:18:22 +01:00
JS : : RootedValue attribs ( rq . cx ) ;
2015-05-06 00:34:41 +02:00
ENSURE ( g_Game - > GetSimulation2 ( ) - > GetScriptInterface ( ) . ParseJSON ( line , & attribs ) ) ;
2010-08-07 00:16:05 +02:00
2018-03-22 16:26:27 +01:00
CheckReplayMods ( g_Game - > GetSimulation2 ( ) - > GetScriptInterface ( ) , attribs ) ;
2016-08-02 13:11:10 +02:00
2015-05-06 00:34:41 +02:00
g_Game - > StartGame ( & attribs , " " ) ;
2011-04-07 04:32:16 +02:00
// TODO: Non progressive load can fail - need a decent way to handle this
2010-08-07 00:16:05 +02:00
LDR_NonprogressiveLoad ( ) ;
2011-04-07 04:32:16 +02:00
2015-05-06 00:34:41 +02:00
PSRETURN ret = g_Game - > ReallyStartGame ( ) ;
2011-04-30 15:01:45 +02:00
ENSURE ( ret = = PSRETURN_OK ) ;
2010-08-07 00:16:05 +02:00
}
else if ( type = = " turn " )
{
* m_Stream > > turn > > turnLength ;
2015-04-12 21:38:31 +02:00
debug_printf ( " Turn %u (%u)... \n " , turn , turnLength ) ;
2010-08-07 00:16:05 +02:00
}
else if ( type = = " cmd " )
{
2013-06-11 02:05:57 +02:00
player_id_t player ;
2010-08-07 00:16:05 +02:00
* m_Stream > > player ;
std : : string line ;
std : : getline ( * m_Stream , line ) ;
2020-11-13 14:18:22 +01:00
JS : : RootedValue data ( rq . cx ) ;
2015-05-06 00:34:41 +02:00
g_Game - > GetSimulation2 ( ) - > GetScriptInterface ( ) . ParseJSON ( line , & data ) ;
2016-01-25 21:37:26 +01:00
g_Game - > GetSimulation2 ( ) - > GetScriptInterface ( ) . FreezeObject ( data , true ) ;
2020-11-13 14:18:22 +01:00
commands . emplace_back ( SimulationCommand ( player , rq . cx , data ) ) ;
2010-08-07 00:16:05 +02:00
}
2011-03-05 23:30:32 +01:00
else if ( type = = " hash " | | type = = " hash-quick " )
2010-09-17 19:45:50 +02:00
{
std : : string replayHash ;
* m_Stream > > replayHash ;
2018-05-29 04:14:38 +02:00
TestHash ( type , replayHash , testHashFull , testHashQuick ) ;
2010-09-17 19:45:50 +02:00
}
2010-08-07 00:16:05 +02:00
else if ( type = = " end " )
{
2011-11-29 21:32:43 +01:00
{
g_Profiler2 . RecordFrameStart ( ) ;
PROFILE2 ( " frame " ) ;
g_Profiler2 . IncrementFrameNumber ( ) ;
PROFILE2_ATTR ( " %d " , g_Profiler2 . GetFrameNumber ( ) ) ;
2015-05-06 00:34:41 +02:00
g_Game - > GetSimulation2 ( ) - > Update ( turnLength , commands ) ;
2011-11-29 21:32:43 +01:00
commands . clear ( ) ;
}
2010-08-07 00:16:05 +02:00
2010-09-17 19:45:50 +02:00
g_Profiler . Frame ( ) ;
2018-03-22 14:53:04 +01:00
if ( turn % PROFILE_TURN_INTERVAL = = 0 )
2010-09-17 19:45:50 +02:00
g_ProfileViewer . SaveToFile ( ) ;
2010-08-07 00:16:05 +02:00
}
else
2015-02-14 02:45:13 +01:00
debug_printf ( " Unrecognised replay token %s \n " , type . c_str ( ) ) ;
2010-08-07 00:16:05 +02:00
}
2015-05-06 22:28:28 +02:00
}
2010-09-03 11:55:14 +02:00
2015-07-30 18:43:22 +02:00
SAFE_DELETE ( m_Stream ) ;
2011-11-29 21:32:43 +01:00
g_Profiler2 . SaveToFile ( ) ;
2010-09-03 11:55:14 +02:00
std : : string hash ;
2015-05-06 00:34:41 +02:00
bool ok = g_Game - > GetSimulation2 ( ) - > ComputeStateHash ( hash , false ) ;
2011-04-30 15:01:45 +02:00
ENSURE ( ok ) ;
2015-02-14 02:45:13 +01:00
debug_printf ( " # Final state: %s \n " , Hexify ( hash ) . c_str ( ) ) ;
2011-03-03 01:16:14 +01:00
timer_DisplayClientTotals ( ) ;
2015-05-06 22:28:28 +02:00
SAFE_DELETE ( g_Game ) ;
2014-03-28 21:26:32 +01: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-23 02:02:57 +01:00
2010-09-17 19:45:50 +02:00
// Clean up
delete & g_TexMan ;
delete & g_Profiler ;
delete & g_ProfileViewer ;
2015-05-06 00:34:41 +02:00
SAFE_DELETE ( g_ScriptStatsTable ) ;
2010-08-07 00:16:05 +02:00
}
2018-05-29 04:14:38 +02: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 ( ) ) ;
}