sys_savegame.cpp (27807B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. 23 24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. 25 26 =========================================================================== 27 */ 28 #pragma hdrstop 29 #include "../idlib/precompiled.h" 30 #include "sys_session_local.h" 31 32 33 idCVar saveGame_verbose( "saveGame_verbose", "0", CVAR_BOOL | CVAR_ARCHIVE, "debug spam" ); 34 idCVar saveGame_checksum( "saveGame_checksum", "1", CVAR_BOOL, "data integrity check" ); 35 idCVar saveGame_enable( "saveGame_enable", "1", CVAR_BOOL, "are savegames enabled" ); 36 37 void Sys_ExecuteSavegameCommandAsyncImpl( idSaveLoadParms * savegameParms ); 38 39 40 /* 41 ======================== 42 Sys_ExecuteSavegameCommandAsync 43 ======================== 44 */ 45 void Sys_ExecuteSavegameCommandAsync( idSaveLoadParms * savegameParms ) { 46 if ( savegameParms == NULL ) { 47 idLib::Error( "Programming Error with [%s]", __FUNCTION__ ); 48 return; 49 } 50 51 if ( !saveGame_enable.GetBool() ) { 52 idLib::Warning( "Savegames are disabled (saveGame_enable = 0). Skipping physical save to media." ); 53 savegameParms->errorCode = SAVEGAME_E_CANCELLED; 54 savegameParms->callbackSignal.Raise(); 55 return; 56 } 57 58 Sys_ExecuteSavegameCommandAsyncImpl( savegameParms ); 59 } 60 61 #define ASSERT_ENUM_STRING_BITFIELD( string, index ) ( 1 / (int)!( string - ( 1 << index ) ) ) ? #string : "" 62 63 const char * saveGameErrorStrings[ SAVEGAME_E_NUM ] = { 64 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_CANCELLED, 0 ), 65 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INSUFFICIENT_ROOM, 1 ), 66 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_CORRUPTED, 2 ), 67 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE, 3 ), 68 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_UNKNOWN, 4 ), 69 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INVALID_FILENAME, 5 ), 70 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_STEAM_ERROR, 6 ), 71 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_FOLDER_NOT_FOUND, 7 ), 72 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_FILE_NOT_FOUND, 8 ), 73 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_DLC_NOT_FOUND, 9 ), 74 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INVALID_USER, 10 ), 75 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_PROFILE_TOO_BIG, 11 ), 76 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_DISC_SWAP, 12 ), 77 ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION, 13 ), 78 }; 79 80 CONSOLE_COMMAND( savegamePrintErrors, "Prints error code corresponding to each bit", 0 ) { 81 idLib::Printf( "Bit Description\n" 82 "--- -----------\n" ); 83 for ( int i = 0; i < SAVEGAME_E_BITS_USED; i++ ) { 84 idLib::Printf( "%03d %s\n", i, saveGameErrorStrings[i] ); 85 } 86 } 87 88 /* 89 ======================== 90 GetSaveGameErrorString 91 92 This returns a comma delimited string of all the errors within the bitfield. We don't ever expect more than one error, 93 the errors are bitfields so that they can be used to mask which errors we want to handle in the engine or leave up to the 94 game. 95 96 Example: 97 SAVEGAME_E_LOAD, SAVEGAME_E_INVALID_FILENAME 98 ======================== 99 */ 100 idStr GetSaveGameErrorString( int errorMask ) { 101 idStr errorString; 102 bool continueProcessing = errorMask > 0; 103 int localError = errorMask; 104 105 for ( int i = 0; i < SAVEGAME_E_NUM && continueProcessing; ++i ) { 106 int mask = ( 1 << i ); 107 108 if ( localError & mask ) { 109 localError ^= mask; // turn off this error so we can quickly see if we are done 110 111 continueProcessing = localError > 0; 112 113 errorString.Append( saveGameErrorStrings[i] ); 114 115 if ( continueProcessing ) { 116 errorString.Append( ", " ); 117 } 118 } 119 } 120 121 return errorString; 122 } 123 124 /* 125 ======================== 126 GetSaveFolder 127 128 Prefixes help the PS3 filter what to show in lists 129 Files using the hidden prefix are not shown in the load game screen 130 Directory name for savegames, slot number or user-defined name appended to it 131 TRC R116 - PS3 folder must start with the product code 132 ======================== 133 */ 134 const idStr & GetSaveFolder( idSaveGameManager::packageType_t type ) { 135 static bool initialized = false; 136 static idStrStatic<MAX_FOLDER_NAME_LENGTH> saveFolder[idSaveGameManager::PACKAGE_NUM]; 137 138 if ( !initialized ) { 139 initialized = true; 140 141 idStr ps3Header = ""; 142 143 saveFolder[idSaveGameManager::PACKAGE_GAME].Format( "%s%s", ps3Header.c_str(), SAVEGAME_GAME_DIRECTORY_PREFIX ); 144 saveFolder[idSaveGameManager::PACKAGE_PROFILE].Format( "%s%s", ps3Header.c_str(), SAVEGAME_PROFILE_DIRECTORY_PREFIX ); 145 saveFolder[idSaveGameManager::PACKAGE_RAW].Format( "%s%s", ps3Header.c_str(), SAVEGAME_RAW_DIRECTORY_PREFIX ); 146 } 147 148 return saveFolder[type]; 149 } 150 151 /* 152 ======================== 153 idStr AddSaveFolderPrefix 154 155 input = RAGE_0 156 output = GAMES-RAGE_0 157 ======================== 158 */ 159 idStr AddSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type ) { 160 idStr dir = GetSaveFolder( type ); 161 dir.Append( folder ); 162 163 164 return dir; 165 } 166 167 /* 168 ======================== 169 RemoveSaveFolderPrefix 170 171 input = GAMES-RAGE_0 172 output = RAGE_0 173 ======================== 174 */ 175 idStr RemoveSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type ) { 176 idStr dir = folder; 177 idStr prefix = GetSaveFolder( type ); 178 dir.StripLeading( prefix ); 179 return dir; 180 } 181 182 /* 183 ======================== 184 bool SavegameReadDetailsFromFile 185 186 returns false when catastrophic error occurs, not when damaged 187 ======================== 188 */ 189 bool SavegameReadDetailsFromFile( idFile * file, idSaveGameDetails & details ) { 190 details.damaged = false; 191 192 // Read the DETAIL file for the enumerated data 193 if ( !details.descriptors.ReadFromIniFile( file ) ) { 194 details.damaged = true; 195 } 196 197 bool ignoreChecksum = details.descriptors.GetBool( "ignore_checksum", false ); 198 if ( !ignoreChecksum ) { 199 // Get the checksum from the dict 200 int readChecksum = details.descriptors.GetInt( SAVEGAME_DETAIL_FIELD_CHECKSUM, 0 ); 201 202 // Calculate checksum 203 details.descriptors.Delete( SAVEGAME_DETAIL_FIELD_CHECKSUM ); 204 int checksum = (int)details.descriptors.Checksum(); 205 if ( readChecksum == 0 || checksum != readChecksum ) { 206 details.damaged = true; 207 } 208 } 209 210 return true; 211 } 212 213 /* 214 ======================== 215 idSaveGameDetails::idSaveGameDetails 216 ======================== 217 */ 218 idSaveGameDetails::idSaveGameDetails() { 219 Clear(); 220 } 221 222 /* 223 ======================== 224 idSaveGameDetails::Clear 225 ======================== 226 */ 227 void idSaveGameDetails::Clear() { 228 descriptors.Clear(); 229 damaged = false; 230 date = 0; 231 slotName[0] = NULL; 232 } 233 234 /* 235 ======================== 236 idSaveLoadParms::idSaveLoadParms 237 ======================== 238 */ 239 idSaveLoadParms::idSaveLoadParms() { 240 // These are not done when we set defaults because SetDefaults is called internally within the execution of the processor and 241 // these are set once and shouldn't be touched until the processor is re-initialized 242 cancelled = false; 243 244 Init(); 245 } 246 247 /* 248 ======================== 249 idSaveLoadParms::~idSaveLoadParms 250 ======================== 251 */ 252 idSaveLoadParms::~idSaveLoadParms() { 253 for ( int i = 0; i < files.Num(); ++i ) { 254 if ( files[i]->type & SAVEGAMEFILE_AUTO_DELETE ) { 255 delete files[i]; 256 } 257 } 258 } 259 260 /* 261 ======================== 262 idSaveLoadParms::ResetCancelled 263 ======================== 264 */ 265 void idSaveLoadParms::ResetCancelled() { 266 cancelled = false; 267 } 268 269 /* 270 ======================== 271 idSaveLoadParms::Init 272 273 This should not touch anything statically created outside this class! 274 ======================== 275 */ 276 void idSaveLoadParms::Init() { 277 files.Clear(); 278 mode = SAVEGAME_MBF_NONE; 279 directory = ""; 280 pattern = ""; 281 postPattern = ""; 282 requiredSpaceInBytes = 0; 283 description.Clear(); 284 detailList.Clear(); 285 callbackSignal.Clear(); 286 errorCode = SAVEGAME_E_NONE; 287 inputDeviceId = -1; 288 skipErrorDialogMask = 0; 289 290 // These are not done when we set defaults because SetDefaults is called internally within the execution of the processor and 291 // these are set once and shouldn't be touched until the processor is re-initialized 292 // cancelled = false; 293 } 294 295 /* 296 ======================== 297 idSaveLoadParms::SetDefaults 298 ======================== 299 */ 300 void idSaveLoadParms::SetDefaults( int newInputDevice ) { 301 // These are pulled out so SetDefaults() isn't called during global instantiation of objects that have savegame processors 302 // in them that then require a session reference. 303 Init(); 304 305 // fill in the user information (inputDeviceId & userId) from the master user 306 idLocalUser * user = NULL; 307 308 if ( newInputDevice != -1 ) { 309 user = session->GetSignInManager().GetLocalUserByInputDevice( newInputDevice ); 310 } else if ( session != NULL ) { 311 user = session->GetSignInManager().GetMasterLocalUser(); 312 } 313 314 if ( user != NULL ) { 315 idLocalUserWin * userWin = static_cast< idLocalUserWin * >( user ); 316 userId = idStr::Hash( userWin->GetGamerTag() ); 317 idLib::PrintfIf( saveGame_verbose.GetBool(), "profile userId/gamertag: %s (%d)\n", userWin->GetGamerTag(), userId ); 318 inputDeviceId = user->GetInputDevice(); 319 } 320 } 321 322 /* 323 ======================== 324 idSaveLoadParms::CancelSaveGameFilePipelines 325 ======================== 326 */ 327 void idSaveLoadParms::CancelSaveGameFilePipelines() { 328 for ( int i = 0; i < files.Num(); i++ ) { 329 if ( ( files[i]->type & SAVEGAMEFILE_PIPELINED ) != 0 ) { 330 idFile_SaveGamePipelined * file = dynamic_cast< idFile_SaveGamePipelined * >( files[i] ); 331 assert( file != NULL ); 332 333 if ( file->GetMode() == idFile_SaveGamePipelined::WRITE ) { 334 // Notify the save game file that all writes failed which will cause all 335 // writes on the other end of the pipeline to drop on the floor. 336 file->NextWriteBlock( NULL ); 337 } else if ( file->GetMode() == idFile_SaveGamePipelined::READ ) { 338 // Notify end-of-file to the save game file which will cause all 339 // reads on the other end of the pipeline to return zero bytes. 340 file->NextReadBlock( NULL, 0 ); 341 } 342 } 343 } 344 } 345 346 /* 347 ======================== 348 idSaveLoadParms::AbortSaveGameFilePipeline 349 ======================== 350 */ 351 void idSaveLoadParms::AbortSaveGameFilePipeline() { 352 for ( int i = 0; i < files.Num(); i++ ) { 353 if ( ( files[i]->type & SAVEGAMEFILE_PIPELINED ) != 0 ) { 354 idFile_SaveGamePipelined * file = dynamic_cast< idFile_SaveGamePipelined * >( files[i] ); 355 assert( file != NULL ); 356 file->Abort(); 357 } 358 } 359 } 360 361 /* 362 ================================================================================================ 363 idSaveGameProcessor 364 ================================================================================================ 365 */ 366 367 /* 368 ======================== 369 idSaveGameProcessor::idSaveGameProcessor 370 ======================== 371 */ 372 idSaveGameProcessor::idSaveGameProcessor() : working( false ), init( false ) { 373 } 374 375 /* 376 ======================== 377 idSaveGameProcessor::Init 378 ======================== 379 */ 380 bool idSaveGameProcessor::Init() { 381 if ( !verify( !IsWorking() ) ) { 382 idLib::Warning( "[%s] Someone is trying to execute this processor twice, this is really bad!", this->Name() ); 383 return false; 384 } 385 386 parms.ResetCancelled(); 387 parms.SetDefaults(); 388 savegameLogicTestIterator = 0; 389 working = false; 390 init = true; 391 completedCallbacks.Clear(); 392 393 return true; 394 } 395 396 /* 397 ======================== 398 idSaveGameProcessor::IsThreadFinished 399 ======================== 400 */ 401 bool idSaveGameProcessor::IsThreadFinished() { 402 return parms.callbackSignal.Wait( 0 ); 403 } 404 405 /* 406 ======================== 407 idSaveGameProcessor::AddCompletedCallback 408 ======================== 409 */ 410 void idSaveGameProcessor::AddCompletedCallback( const idCallback & callback ) { 411 completedCallbacks.Append( callback.Clone() ); 412 } 413 414 /* 415 ================================================================================================ 416 idSaveGameManager 417 ================================================================================================ 418 */ 419 420 /* 421 ======================== 422 idSaveGameManager::idSaveGameManager 423 ======================== 424 */ 425 idSaveGameManager::idSaveGameManager() : 426 processor( NULL ), 427 cancel( false ), 428 startTime( 0 ), 429 continueProcessing( false ), 430 submittedProcessorHandle( 0 ), 431 executingProcessorHandle( 0 ), 432 lastExecutedProcessorHandle( 0 ), 433 storageAvailable( true ), 434 retryFolder( NULL ) { 435 } 436 437 /* 438 ======================== 439 idSaveGameManager::~idSaveGameManager 440 ======================== 441 */ 442 idSaveGameManager::~idSaveGameManager() { 443 processor = NULL; 444 enumeratedSaveGames.Clear(); 445 } 446 447 /* 448 ======================== 449 idSaveGameManager::ExecuteProcessor 450 ======================== 451 */ 452 saveGameHandle_t idSaveGameManager::ExecuteProcessor( idSaveGameProcessor * processor ) { 453 idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] : %s\n", __FUNCTION__, processor->Name() ); 454 455 // may not be running yet, but if we've init'd successfuly, the IsWorking() call should return true if this 456 // method has been called. You have problems when callees are asking if the processor is done working by using IsWorking() 457 // the next frame after they've executed the processor. 458 processor->working = true; 459 460 if ( this->processor != NULL ) { 461 if ( !verify( this->processor != processor ) ) { 462 idLib::Warning( "[idSaveGameManager::ExecuteProcessor]:1 Someone is trying to execute this processor twice, this is really bad, learn patience padawan!" ); 463 return processor->GetHandle(); 464 } else { 465 idSaveGameProcessor ** localProcessor = processorQueue.Find( processor ); 466 if ( !verify( localProcessor == NULL ) ) { 467 idLib::Warning( "[idSaveGameManager::ExecuteProcessor]:2 Someone is trying to execute this processor twice, this is really bad, learn patience padawan!" ); 468 return (*localProcessor)->GetHandle(); 469 } 470 } 471 } 472 473 processorQueue.Append( processor ); 474 475 // Don't allow processors to start sub-processors. 476 // They need to manage their own internal state. 477 assert( idLib::IsMainThread() ); 478 479 Sys_InterlockedIncrement( submittedProcessorHandle ); 480 processor->parms.handle = submittedProcessorHandle; 481 482 return submittedProcessorHandle; 483 } 484 485 /* 486 ======================== 487 idSaveGameManager::ExecuteProcessorAndWait 488 ======================== 489 */ 490 saveGameHandle_t idSaveGameManager::ExecuteProcessorAndWait( idSaveGameProcessor * processor ) { 491 saveGameHandle_t handle = ExecuteProcessor( processor ); 492 if ( handle == 0 ) { 493 return 0; 494 } 495 496 while ( !IsSaveGameCompletedFromHandle( handle ) ) { 497 Pump(); 498 Sys_Sleep( 10 ); 499 } 500 501 // One more pump to get the completed callback 502 //Pump(); 503 504 return handle; 505 } 506 507 /* 508 ======================== 509 idSaveGameManager::WaitForAllProcessors 510 511 Since the load & nextMap processors calls execute map change, we can't wait if they are the only ones in the queue 512 If there are only resettable processors in the queue or no items in the queue, don't wait. 513 514 We would need to overrideSimpleProcessorCheck if we were sure we had done something that would cause the processors 515 to bail out nicely. Something like canceling a disc swap during a loading disc swap dialog... 516 ======================== 517 */ 518 void idSaveGameManager::WaitForAllProcessors( bool overrideSimpleProcessorCheck ) { 519 assert( idLib::IsMainThread() ); 520 521 while ( IsWorking() || ( processorQueue.Num() > 0 ) ) { 522 523 if ( !overrideSimpleProcessorCheck ) { 524 // BEFORE WE WAIT, and potentially hang everything, make sure processors about to be executed won't sit and 525 // wait for themselves to complete. 526 // Since we pull off simple processors first, we can stop waiting when the processor being executed is not simple 527 if ( processor != NULL ) { 528 if ( !processor->IsSimpleProcessor() ) { 529 break; 530 } 531 } else if ( !processorQueue[0]->IsSimpleProcessor() ) { 532 break; 533 } 534 } 535 536 saveThread.WaitForThread(); 537 Pump(); 538 } 539 } 540 541 /* 542 ======================== 543 idSaveGameManager::CancelAllProcessors 544 ======================== 545 */ 546 void idSaveGameManager::CancelAllProcessors( const bool forceCancelInFlightProcessor ) { 547 assert( idLib::IsMainThread() ); 548 549 cancel = true; 550 551 if ( forceCancelInFlightProcessor ) { 552 if ( processor != NULL ) { 553 processor->GetSignal().Raise(); 554 } 555 } 556 557 Pump(); // must be called from the main thread 558 Clear(); 559 cancel = false; 560 } 561 562 /* 563 ======================== 564 idSaveGameManager::CancelToTerminate 565 ======================== 566 */ 567 void idSaveGameManager::CancelToTerminate() { 568 if ( processor != NULL ) { 569 processor->parms.cancelled = true; 570 processor->GetSignal().Raise(); 571 saveThread.WaitForThread(); 572 } 573 } 574 575 /* 576 ======================== 577 idSaveGameManager::DeviceSelectorWaitingOnSaveRetry 578 ======================== 579 */ 580 bool idSaveGameManager::DeviceSelectorWaitingOnSaveRetry() { 581 582 if ( retryFolder == NULL ) { 583 return false; 584 } 585 586 return ( idStr::Icmp( retryFolder, "GAME-autosave" ) == 0 ); 587 } 588 589 /* 590 ======================== 591 idSaveGameManager::Set360RetrySaveAfterDeviceSelected 592 ======================== 593 */ 594 void idSaveGameManager::Set360RetrySaveAfterDeviceSelected( const char * folder, const int64 bytes ) { 595 retryFolder = folder; 596 retryBytes = bytes; 597 } 598 599 /* 600 ======================== 601 idSaveGameManager::ClearRetryInfo 602 ======================== 603 */ 604 void idSaveGameManager::ClearRetryInfo() { 605 retryFolder = NULL; 606 retryBytes = 0; 607 } 608 609 /* 610 ======================== 611 idSaveGameManager::RetrySave 612 ======================== 613 */ 614 void idSaveGameManager::RetrySave() { 615 if ( DeviceSelectorWaitingOnSaveRetry() && !common->Dialog().HasDialogMsg( GDM_WARNING_FOR_NEW_DEVICE_ABOUT_TO_LOSE_PROGRESS, false ) ) { 616 cmdSystem->AppendCommandText( "savegame autosave\n" ); 617 } 618 } 619 620 /* 621 ======================== 622 idSaveGameManager::ShowRetySaveDialog 623 ======================== 624 */ 625 void idSaveGameManager::ShowRetySaveDialog() { 626 ShowRetySaveDialog( retryFolder, retryBytes ); 627 } 628 629 /* 630 ======================== 631 idSaveGameManager::ShowRetySaveDialog 632 ======================== 633 */ 634 void idSaveGameManager::ShowRetySaveDialog( const char * folder, const int64 bytes ) { 635 636 idStaticList< idSWFScriptFunction *, 4 > callbacks; 637 idStaticList< idStrId, 4 > optionText; 638 639 class idSWFScriptFunction_Continue : public idSWFScriptFunction_RefCounted { 640 public: 641 idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { 642 common->Dialog().ClearDialog( GDM_INSUFFICENT_STORAGE_SPACE ); 643 session->GetSaveGameManager().ClearRetryInfo(); 644 return idSWFScriptVar(); 645 } 646 }; 647 648 callbacks.Append( new (TAG_SWF) idSWFScriptFunction_Continue() ); 649 optionText.Append( idStrId( "#str_dlg_continue_without_saving" ) ); 650 651 652 653 // build custom space required string 654 // #str_dlg_space_required ~= "There is insufficient storage available. Please free %s and try again." 655 idStr format = idStrId( "#str_dlg_space_required" ).GetLocalizedString(); 656 idStr size; 657 if ( bytes > ( 1024 * 1024 ) ) { 658 const float roundUp = ( ( 1024.0f * 1024.0f / 10.0f )- 1.0f ); 659 size = va( "%.1f MB", ( roundUp + (float) bytes ) / ( 1024.0f * 1024.0f ) ); 660 } else { 661 const float roundUp = 1024.0f - 1.0f; 662 size = va( "%.0f KB", ( roundUp + (float) bytes ) / 1024.0f ); 663 } 664 idStr msg = va( format.c_str(), size.c_str() ); 665 666 common->Dialog().AddDynamicDialog( GDM_INSUFFICENT_STORAGE_SPACE, callbacks, optionText, true, msg, true ); 667 } 668 669 /* 670 ======================== 671 idSaveGameManager::CancelWithHandle 672 ======================== 673 */ 674 void idSaveGameManager::CancelWithHandle( const saveGameHandle_t & handle ) { 675 if ( handle == 0 || IsSaveGameCompletedFromHandle( handle ) ) { 676 return; 677 } 678 679 // check processor in flight first 680 if ( processor != NULL ) { 681 if ( processor->GetHandle() == handle ) { 682 processor->Cancel(); 683 return; 684 } 685 } 686 687 // remove from queue 688 for ( int i = 0; i < processorQueue.Num(); ++i ) { 689 if ( processorQueue[i]->GetHandle() == handle ) { 690 processorQueue[i]->Cancel(); 691 return; 692 } 693 } 694 } 695 696 /* 697 ======================== 698 idSaveGameManager::StartNextProcessor 699 700 Get the next not-reset-capable processor. If there aren't any left, just get what's next. 701 ======================== 702 */ 703 void idSaveGameManager::StartNextProcessor() { 704 if ( cancel ) { 705 return; 706 } 707 708 idSaveGameProcessor * nextProcessor = NULL; 709 int index = 0; 710 711 // pick off the first simple processor 712 for ( int i = 0; i < processorQueue.Num(); ++i ) { 713 if ( processorQueue[i]->IsSimpleProcessor() ) { 714 index = i; 715 break; 716 } 717 } 718 719 if ( processorQueue.Num() > 0 ) { 720 nextProcessor = processorQueue[index]; 721 722 723 Sys_InterlockedIncrement( executingProcessorHandle ); 724 725 processorQueue.RemoveIndex( index ); 726 processor = nextProcessor; 727 processor->parms.callbackSignal.Raise(); // signal that the thread is ready for work 728 startTime = Sys_Milliseconds(); 729 } 730 } 731 732 /* 733 ======================== 734 idSaveGameManager::FinishProcessor 735 ======================== 736 */ 737 void idSaveGameManager::FinishProcessor( idSaveGameProcessor * localProcessor ) { 738 739 assert( localProcessor != NULL ); 740 idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] : %s, %d ms\n", __FUNCTION__, localProcessor->Name(), Sys_Milliseconds() - startTime ); 741 742 // This will delete from the files set for auto-deletion 743 // Don't remove files not set for auto-deletion, they may be used outside of the savegame manager by game-side callbacks for example 744 for ( int i = ( localProcessor->parms.files.Num() - 1 ); i >= 0; --i ) { 745 if ( localProcessor->parms.files[i]->type & SAVEGAMEFILE_AUTO_DELETE ) { 746 delete localProcessor->parms.files[i]; 747 localProcessor->parms.files.RemoveIndexFast( i ); 748 } 749 } 750 751 localProcessor->init = false; 752 localProcessor = NULL; 753 } 754 755 /* 756 ======================== 757 idSaveGameManager::Clear 758 ======================== 759 */ 760 void idSaveGameManager::Clear() { 761 processorQueue.Clear(); 762 } 763 764 /* 765 ======================== 766 idSaveGameManager::IsWorking 767 ======================== 768 */ 769 bool idSaveGameManager::IsWorking() const { 770 return processor != NULL; 771 } 772 773 /* 774 ======================== 775 idSaveGameManager::Pump 776 777 Important sections called out with -- EXTRA LARGE -- comments! 778 ======================== 779 */ 780 void idSaveGameManager::Pump() { 781 782 783 784 // After a processor is done, the next is pulled off the queue so the only way the manager isn't working is if 785 // there isn't something executing or in the queue. 786 if ( !IsWorking() ) { 787 // Unified start to initialize system on PS3 and do appropriate checks for system combination issues 788 // ------------------------------------ 789 // START 790 // ------------------------------------ 791 StartNextProcessor(); 792 793 if ( !IsWorking() ) { 794 return; 795 } 796 797 continueProcessing = true; 798 } 799 800 if ( cancel ) { 801 processor->parms.AbortSaveGameFilePipeline(); 802 } 803 804 // Quickly checks to see if the savegame thread is done, otherwise, exit and continue frame commands 805 if ( processor->IsThreadFinished() ) { 806 idLib::PrintfIf( saveGame_verbose.GetBool(), "%s waited on processor [%s], error = 0x%08X, %s\n", __FUNCTION__, processor->Name(), processor->GetError(), GetSaveGameErrorString( processor->GetError() ).c_str() ); 807 808 if ( !cancel && continueProcessing ) { 809 // Check for available storage unit 810 if ( session->GetSignInManager().GetMasterLocalUser() != NULL ) { 811 if ( !session->GetSignInManager().GetMasterLocalUser()->IsStorageDeviceAvailable() ) { 812 // this will not allow further processing 813 processor->parms.errorCode = SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE; 814 } 815 } 816 817 // Execute Process() on the processor, if there was an error in a previous Process() call, give the 818 // processor the chance to validate that error and either clean itself up or convert it to another error or none. 819 if ( processor->GetError() == SAVEGAME_E_NONE || processor->ValidateLastError() ) { 820 idLib::PrintfIf( saveGame_verbose.GetBool(), "%s calling %s::Process(), error = 0x%08X, %s\n", __FUNCTION__, processor->Name(), processor->GetError(), GetSaveGameErrorString( processor->GetError() ).c_str() ); 821 822 // ------------------------------------ 823 // PROCESS 824 // ------------------------------------ 825 continueProcessing = processor->Process(); 826 827 // If we don't return here, the completedCallback will be executed before it's done with it's async operation 828 // during it's last process stage. 829 return; 830 } else { 831 continueProcessing = false; 832 } 833 } 834 835 // This section does specific post-processing for each of the save commands 836 if ( !continueProcessing ) { 837 838 // Clear out details if we detect corruption but keep directory/slot information 839 for ( int i = 0; i < processor->parms.detailList.Num(); ++i ) { 840 idSaveGameDetails & details = processor->parms.detailList[i]; 841 if ( details.damaged ) { 842 details.descriptors.Clear(); 843 } 844 } 845 846 idLib::PrintfIf( saveGame_verbose.GetBool(), "%s calling %s::CompletedCallback()\n", __FUNCTION__, processor->Name() ); 847 processor->working = false; 848 849 // This ensures that the savegame manager will believe the processor is done when there is a potentially 850 // catastrophic thing that will happen within CompletedCallback which might try to sync all threads 851 // The most common case of this is executing a map change (which we no longer do). 852 // We flush the heap and wait for all background processes to finish. After all this is called, we will 853 // cleanup the old processor within FinishProcessor() 854 idSaveGameProcessor * localProcessor = processor; 855 processor = NULL; 856 857 // ------------------------------------ 858 // COMPLETEDCALLBACK 859 // At this point, the handle will be completed 860 // ------------------------------------ 861 Sys_InterlockedIncrement( lastExecutedProcessorHandle ); 862 863 for ( int i = 0; i < localProcessor->completedCallbacks.Num(); i++ ) { 864 localProcessor->completedCallbacks[i]->Call(); 865 } 866 localProcessor->completedCallbacks.DeleteContents( true ); 867 868 // ------------------------------------ 869 // FINISHPROCESSOR 870 // ------------------------------------ 871 FinishProcessor( localProcessor ); 872 } 873 } else if ( processor->ShouldTimeout() ) { 874 // Hack for the PS3 threading hang 875 idLib::PrintfIf( saveGame_verbose.GetBool(), "----- PROCESSOR TIMEOUT ----- (%s)\n", processor->Name() ); 876 877 idSaveGameProcessor * tempProcessor = processor; 878 879 CancelAllProcessors( true ); 880 881 class idSWFScriptFunction_TryAgain : public idSWFScriptFunction_RefCounted { 882 public: 883 idSWFScriptFunction_TryAgain( idSaveGameManager * manager, idSaveGameProcessor * processor ) { 884 this->manager = manager; 885 this->processor = processor; 886 } 887 idSWFScriptVar Call ( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { 888 common->Dialog().ClearDialog( GDM_ERROR_SAVING_SAVEGAME ); 889 manager->ExecuteProcessor( processor ); 890 return idSWFScriptVar(); 891 } 892 private: 893 idSaveGameManager * manager; 894 idSaveGameProcessor * processor; 895 }; 896 897 idStaticList< idSWFScriptFunction *, 4 > callbacks; 898 idStaticList< idStrId, 4 > optionText; 899 callbacks.Append( new (TAG_SWF) idSWFScriptFunction_TryAgain( this, tempProcessor ) ); 900 optionText.Append( idStrId( "#STR_SWF_RETRY" ) ); 901 common->Dialog().AddDynamicDialog( GDM_ERROR_SAVING_SAVEGAME, callbacks, optionText, true, "" ); 902 } 903 }