diff --git a/functions.h b/functions.h index 5d51619fe7..40604e2957 100644 --- a/functions.h +++ b/functions.h @@ -266,4 +266,7 @@ void DeterminePaths(void); char * CDECL str_fmt(const char *str, ...); void bubblesort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *)); +bool CreateOTTDThread(void *func, void *param); +void CloseOTTDThread(void); +void JoinOTTDThread(void); #endif /* FUNCTIONS_H */ diff --git a/lang/english.txt b/lang/english.txt index d72d5a0dec..487b5891e0 100644 --- a/lang/english.txt +++ b/lang/english.txt @@ -857,6 +857,8 @@ STR_032C_3_AIR_SERVICE :{BLACK}3: Air s STR_032D_4_SHIP_SERVICE :{BLACK}4: Ship service STR_032E_5_RAILROAD_SERVICE_ADVANCED :{BLACK}5: Railway service (advanced) STR_032F_AUTOSAVE :{RED}AUTOSAVE +STR_SAVING_GAME :{RED}* * SAVING GAME * * +STR_SAVE_STILL_IN_PROGRESS :{WHITE}Saving still in progress,{}please wait until it is finished! STR_0330_SELECT_EZY_STREET_STYLE :{BLACK}Select 'Ezy Street style music' programme STR_0335_6 :{BLACK}6 diff --git a/main_gui.c b/main_gui.c index 4fcc64f11e..de5c8cfe4c 100644 --- a/main_gui.c +++ b/main_gui.c @@ -2207,24 +2207,24 @@ static bool DrawScrollingStatusText(NewsItem *ni, int pos) static void StatusBarWndProc(Window *w, WindowEvent *e) { - Player *p; + switch (e->event) { + case WE_PAINT: { + const Player *p = (_local_player == OWNER_SPECTATOR) ? NULL : DEREF_PLAYER(_local_player); - switch(e->event) { - case WE_PAINT: DrawWindowWidgets(w); SetDParam(0, _date); DrawStringCentered(70, 1, ((_pause||_patches.status_long_date)?STR_00AF:STR_00AE), 0); - p = _local_player == OWNER_SPECTATOR ? NULL : DEREF_PLAYER(_local_player); - - if (p) { + if (p != NULL) { // Draw player money SetDParam64(0, p->money64); DrawStringCentered(570, 1, p->player_money >= 0 ? STR_0004 : STR_0005, 0); } // Draw status bar - if (_do_autosave) { + if (w->message.msg) { // true when saving is active + DrawStringCentered(320, 1, STR_SAVING_GAME, 0); + } else if (_do_autosave) { DrawStringCentered(320, 1, STR_032F_AUTOSAVE, 0); } else if (_pause) { DrawStringCentered(320, 1, STR_0319_PAUSED, 0); @@ -2241,17 +2241,19 @@ static void StatusBarWndProc(Window *w, WindowEvent *e) } } - if (WP(w, def_d).data_2 > 0) - DrawSprite(SPR_BLOT | PALETTE_TO_RED, 489, 2); + if (WP(w, def_d).data_2 > 0) DrawSprite(SPR_BLOT | PALETTE_TO_RED, 489, 2); + } break; + + case WE_MESSAGE: + w->message.msg = e->message.msg; + SetWindowDirty(w); break; case WE_CLICK: - if (e->click.widget == 1) { - ShowLastNewsMessage(); - } else if (e->click.widget == 2) { - if (_local_player != OWNER_SPECTATOR) ShowPlayerFinances(_local_player); - } else { - ResetObjectToPlace(); + switch (e->click.widget) { + case 1: ShowLastNewsMessage(); break; + case 2: if (_local_player != OWNER_SPECTATOR) ShowPlayerFinances(_local_player); break; + default: ResetObjectToPlace(); } break; diff --git a/saveload.c b/saveload.c index f22d7a0788..cbe3fda8f6 100644 --- a/saveload.c +++ b/saveload.c @@ -817,6 +817,44 @@ static void UninitNoComp(void) free(_sl.buf); } +//******************************************** +//********** START OF MEMORY CODE (in ram)**** +//******************************************** + +enum { + SAVELOAD_POOL_BLOCK_SIZE_BITS = 17, + SAVELOAD_POOL_MAX_BLOCKS = 500 +}; + +/* A maximum size of of 128K * 500 = 64.000KB savegames */ +static MemoryPool _saveload_pool = {"Savegame", SAVELOAD_POOL_MAX_BLOCKS, SAVELOAD_POOL_BLOCK_SIZE_BITS, sizeof(byte), NULL, 0, 0, NULL}; +static uint _save_byte_count; + +static bool InitMem(void) +{ + CleanPool(&_saveload_pool); + AddBlockToPool(&_saveload_pool); + + /* A block from the pool is a contigious area of memory, so it is safe to write to it sequentially */ + _save_byte_count = 0; + _sl.bufsize = _saveload_pool.total_items; + _sl.buf = (byte*)GetItemFromPool(&_saveload_pool, _save_byte_count); + return true; +} + +static void UnInitMem(void) +{ + CleanPool(&_saveload_pool); +} + +static void WriteMem(uint size) +{ + _save_byte_count += size; + /* Allocate new block and new buffer-pointer */ + AddBlockIfNeeded(&_saveload_pool, _save_byte_count); + _sl.buf = (byte*)GetItemFromPool(&_saveload_pool, _save_byte_count); +} + //******************************************** //********** START OF ZLIB CODE ************** //******************************************** @@ -1064,33 +1102,35 @@ typedef struct { } SaveLoadFormat; static const SaveLoadFormat _saveload_formats[] = { - {"lzo", TO_BE32X('OTTD'), InitLZO, ReadLZO, UninitLZO, InitLZO, WriteLZO, UninitLZO}, - {"none", TO_BE32X('OTTN'), InitNoComp, ReadNoComp, UninitNoComp, InitNoComp, WriteNoComp, UninitNoComp}, + {"memory", 0, NULL, NULL, NULL, InitMem, WriteMem, UnInitMem}, + {"lzo", TO_BE32X('OTTD'), InitLZO, ReadLZO, UninitLZO, InitLZO, WriteLZO, UninitLZO}, + {"none", TO_BE32X('OTTN'), InitNoComp, ReadNoComp, UninitNoComp, InitNoComp, WriteNoComp, UninitNoComp}, #if defined(WITH_ZLIB) - {"zlib", TO_BE32X('OTTZ'), InitReadZlib, ReadZlib, UninitReadZlib, InitWriteZlib, WriteZlib, UninitWriteZlib}, + {"zlib", TO_BE32X('OTTZ'), InitReadZlib, ReadZlib, UninitReadZlib, InitWriteZlib, WriteZlib, UninitWriteZlib}, #else - {"zlib", TO_BE32X('OTTZ'), NULL, NULL, NULL, NULL, NULL, NULL} + {"zlib", TO_BE32X('OTTZ'), NULL, NULL, NULL, NULL, NULL, NULL}, #endif }; /** * Return the savegameformat of the game. Whether it was create with ZLIB compression * uncompressed, or another type - * @param s Name of the savegame format + * @param s Name of the savegame format. If NULL it picks the first available one * @return Pointer to @SaveLoadFormat struct giving all characteristics of this type of savegame */ static const SaveLoadFormat *GetSavegameFormat(const char *s) { const SaveLoadFormat *def = endof(_saveload_formats) - 1; - int i; // find default savegame format, the highest one with which files can be written while (!def->init_write) def--; - if (_savegame_format[0]) { - for (i = 0; i != lengthof(_saveload_formats); i++) - if (_saveload_formats[i].init_write && !strcmp(s, _saveload_formats[i].name)) - return _saveload_formats + i; + if (s != NULL && s[0] != '\0') { + const SaveLoadFormat *slf; + for (slf = &_saveload_formats[0]; slf != endof(_saveload_formats); slf++) { + if (slf->init_write != NULL && strcmp(s, slf->name) == 0) + return slf; + } ShowInfoF("Savegame format '%s' is not available. Reverting to '%s'.", s, def->name); } @@ -1112,6 +1152,95 @@ static inline int AbortSaveLoad(void) return SL_ERROR; } +#include "network.h" +#include "table/strings.h" +#include "table/sprites.h" +#include "gfx.h" +#include "gui.h" + +static bool _saving_game = false; + +/** Update the gui accordingly when starting saving + * and set locks on saveload */ +static inline void SaveFileStart(void) +{ + SetMouseCursor(SPR_CURSOR_ZZZ); + SendWindowMessage(WC_STATUS_BAR, 0, true, 0, 0); + _saving_game = true; +} + +/** Update the gui accordingly when saving is done and release locks + * on saveload */ +static inline void SaveFileDone(void) +{ + if (_cursor.sprite == SPR_CURSOR_ZZZ) SetMouseCursor(SPR_CURSOR_MOUSE); + SendWindowMessage(WC_STATUS_BAR, 0, false, 0, 0); + _saving_game = false; +} + +/** We have written the whole game into memory, _saveload_pool, now find + * and appropiate compressor and start writing to file. + */ +static bool SaveFileToDisk(void *ptr) +{ + const SaveLoadFormat *fmt = GetSavegameFormat(_savegame_format); + /* XXX - backup _sl.buf cause it is used internally by the writer + * and we update it for our own purposes */ + byte *tmp = _sl.buf; + uint32 hdr[2]; + + SaveFileStart(); + + /* XXX - Setup setjmp error handler if an error occurs anywhere deep during + * loading/saving execute a longjmp() and continue execution here */ + if (setjmp(_sl.excpt)) { + AbortSaveLoad(); + _sl.buf = tmp; + _sl.excpt_uninit(); + + ShowInfoF("Save game failed: %s.", _sl.excpt_msg); + ShowErrorMessage(STR_4007_GAME_SAVE_FAILED, STR_NULL, 0, 0); + + SaveFileDone(); + return false; + } + + /* We have written our stuff to memory, now write it to file! */ + hdr[0] = fmt->tag; + hdr[1] = TO_BE32((SAVEGAME_MAJOR_VERSION << 16) + (SAVEGAME_MINOR_VERSION << 8)); + if (fwrite(hdr, sizeof(hdr), 1, _sl.fh) != 1) SlError("file write failed"); + + if (!fmt->init_write()) SlError("cannot initialize compressor"); + tmp = _sl.buf; // XXX - init_write can change _sl.buf, so update it + + { + uint i; + uint count = 1 << _saveload_pool.block_size_bits; + + assert(_save_byte_count == _sl.offs_base); + for (i = 0; i != _saveload_pool.current_blocks - 1; i++) { + _sl.buf = _saveload_pool.blocks[i]; + fmt->writer(count); + } + + /* The last block is (almost) always not fully filled, so only write away + * as much data as it is in there */ + _sl.buf = _saveload_pool.blocks[i]; + fmt->writer(_save_byte_count - (i * count)); + + _sl.buf = tmp; // XXX - reset _sl.buf to its original value to let it continue its internal usage + } + + fmt->uninit_write(); + assert(_save_byte_count == _sl.offs_base); + GetSavegameFormat("memory")->uninit_write(); // clean the memorypool + fclose(_sl.fh); + + SaveFileDone(); + CloseOTTDThread(); + return true; +} + /** * Main Save or Load function where the high-level saveload functions are * handled. It opens the savegame, selects format and checks versions @@ -1133,6 +1262,12 @@ int SaveOrLoad(const char *filename, int mode) return SL_OK; } + /* An instance of saving is already active, don't start any other cause of global variables */ + if (_saving_game == true) { + if (!_do_autosave) ShowErrorMessage(_error_message, STR_SAVE_STILL_IN_PROGRESS, 0, 0); + return SL_ERROR; + } + _sl.fh = fopen(filename, (mode == SL_SAVE) ? "wb" : "rb"); if (_sl.fh == NULL) { DEBUG(misc, 0) ("Cannot open savegame for saving/loading."); @@ -1147,7 +1282,8 @@ int SaveOrLoad(const char *filename, int mode) _sl.includes = _desc_includes; _sl.chs = _chunk_handlers; - /* Setup setjmp error handler, if it fails don't even bother loading the game */ + /* XXX - Setup setjmp error handler if an error occurs anywhere deep during + * loading/saving execute a longjmp() and continue execution here */ if (setjmp(_sl.excpt)) { AbortSaveLoad(); @@ -1168,8 +1304,10 @@ int SaveOrLoad(const char *filename, int mode) * be clobbered by `longjmp' or `vfork'" */ version = 0; + /* General tactic is to first save the game to memory, then use an available writer + * to write it to file, either in threaded mode if possible, or single-threaded */ if (mode == SL_SAVE) { /* SAVE game */ - fmt = GetSavegameFormat(_savegame_format); + fmt = GetSavegameFormat("memory"); // write to memory _sl.write_bytes = fmt->writer; _sl.excpt_uninit = fmt->uninit_write; @@ -1178,20 +1316,23 @@ int SaveOrLoad(const char *filename, int mode) return AbortSaveLoad(); } - hdr[0] = fmt->tag; - hdr[1] = TO_BE32((SAVEGAME_MAJOR_VERSION << 16) + (SAVEGAME_MINOR_VERSION << 8)); - if (fwrite(hdr, sizeof(hdr), 1, _sl.fh) != 1) SlError("Writing savegame header failed"); - _sl.version = SAVEGAME_MAJOR_VERSION; BeforeSaveGame(); SlSaveChunks(); SlWriteFill(); // flush the save buffer - fmt->uninit_write(); + + /* Write to file */ + if (_network_server || !CreateOTTDThread(&SaveFileToDisk, NULL)) { + DEBUG(misc, 1) ("cannot create savegame thread, reverting to single-threaded mode..."); + SaveFileToDisk(NULL); + } } else { /* LOAD game */ + assert(mode == SL_LOAD); + if (fread(hdr, sizeof(hdr), 1, _sl.fh) != 1) { - DEBUG(misc, 0) ("Cannot read Savegame header, aborting."); + DEBUG(misc, 0) ("Cannot read savegame header, aborting."); return AbortSaveLoad(); } @@ -1237,22 +1378,20 @@ int SaveOrLoad(const char *filename, int mode) return AbortSaveLoad(); } - /* XXX - ??? Set the current map to 256x256, in case of an old map. - * Else MAPS will read the wrong information. This should initialize - * to savegame mapsize no?? */ + /* Old maps were hardcoded to 256x256 and thus did not contain + * any mapsize information. Pre-initialize to 256x256 to not to + * confuse old games */ InitializeGame(8, 8); SlLoadChunks(); fmt->uninit_read(); + fclose(_sl.fh); + + /* After loading fix up savegame for any internal changes that + * might've occured since then. If it fails, load back the old game */ + if (!AfterLoadGame(version)) return SL_REINIT; } - fclose(_sl.fh); - - /* After loading fix up savegame for any internal changes that - * might've occured since then. If it fails, load back the old game */ - if (mode == SL_LOAD && !AfterLoadGame(version)) - return SL_REINIT; - return SL_OK; } diff --git a/ttd.c b/ttd.c index cddd6746ba..ba8f241f06 100644 --- a/ttd.c +++ b/ttd.c @@ -686,6 +686,7 @@ int ttd_main(int argc, char* argv[]) while (_video_driver->main_loop() == ML_SWITCHDRIVER) {} + JoinOTTDThread(); IConsoleFree(); #ifdef ENABLE_NETWORK diff --git a/unix.c b/unix.c index b4c3538786..0f98008bb4 100644 --- a/unix.c +++ b/unix.c @@ -11,6 +11,7 @@ #include #include #include +#include #if (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L) || defined(__GLIBC__) #define HAS_STATVFS @@ -554,3 +555,18 @@ bool InsertTextBufferClipboard(Textbuf *tb) { return false; } + +static pthread_t thread1 = 0; +bool CreateOTTDThread(void *func, void *param) +{ + return (pthread_create(&thread1, NULL, func, param) == 0) ? true : false; +} + +void CloseOTTDThread(void) {return;} + +void JoinOTTDThread(void) +{ + if (thread1 == 0) return; + + pthread_join(thread1, NULL); +} diff --git a/win32.c b/win32.c index a2ef163222..3c02511f8f 100644 --- a/win32.c +++ b/win32.c @@ -2242,3 +2242,26 @@ bool InsertTextBufferClipboard(Textbuf *tb) } return false; } + +static HANDLE hThread; + +bool CreateOTTDThread(void *func, void *param) +{ + DWORD dwThreadId; + hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)func, param, 0, &dwThreadId); + SetThreadPriority(hThread, THREAD_PRIORITY_BELOW_NORMAL); + + return (hThread == NULL) ? false : true; +} + +void CloseOTTDThread(void) +{ + if (!CloseHandle(hThread)) DEBUG(misc, 0) ("Failed to close thread?..."); +} + +void JoinOTTDThread(void) +{ + if (hThread == NULL) return; + + WaitForSingleObject(hThread, INFINITE); +} \ No newline at end of file