changeset 1885:4582b8432fb9 draft

(svn r2391) - Feature: saving games happen in a seperate thread so you no longer will have to wait such a long time (especially handy on bigger maps and multiplayer games). The mouse also changes into the 'ZZZ' state :P. The thread on windows is currently given a little-bit-less-than-normal priority so it should not interfere that much with the gameplay; it will take a bit longer though. Upon the exit of the game any pending saves are waited upon. - Fix: fixed GetSavegameFormat() so that it takes the best compressor (highest), or a forced one added with the parameter - Open issues: 1. Don't attempt to load a game while saving is in progress, it will kick you back to the intro-screen with only the vast ocean to look at. 2. The server is disabled from threaded-saving, but might be enabled in the future. 3. Current implementation only allows 1 additional running thread. 4. Stupid global variables.....grrr Big thanks for TrueLight and the amazing memorypool :D
author Darkvater <Darkvater@openttd.org>
date Wed, 01 Jun 2005 23:08:33 +0000
parents 2e1467660aeb
children ab24fa9c8e3d
files functions.h lang/english.txt main_gui.c saveload.c ttd.c unix.c win32.c
diffstat 7 files changed, 230 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/functions.h
+++ b/functions.h
@@ -266,4 +266,7 @@
 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 */
--- a/lang/english.txt
+++ b/lang/english.txt
@@ -857,6 +857,8 @@
 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
--- a/main_gui.c
+++ b/main_gui.c
@@ -2207,24 +2207,24 @@
 
 static void StatusBarWndProc(Window *w, WindowEvent *e)
 {
-	Player *p;
-
-	switch(e->event) {
-	case WE_PAINT:
+	switch (e->event) {
+	case WE_PAINT: {
+		const Player *p = (_local_player == OWNER_SPECTATOR) ? NULL : DEREF_PLAYER(_local_player);
+
 		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 @@
 			}
 		}
 
-		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;
 
--- a/saveload.c
+++ b/saveload.c
@@ -818,6 +818,44 @@
 }
 
 //********************************************
+//********** 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 @@
 } 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 @@
 	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 @@
 		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 @@
 	_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 @@
    * 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 @@
 			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,21 +1378,19 @@
 			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);
+		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;
+		/* 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;
+	}
 
 	return SL_OK;
 }
--- a/ttd.c
+++ b/ttd.c
@@ -686,6 +686,7 @@
 
 	while (_video_driver->main_loop() == ML_SWITCHDRIVER) {}
 
+	JoinOTTDThread();
 	IConsoleFree();
 
 #ifdef ENABLE_NETWORK
--- a/unix.c
+++ b/unix.c
@@ -11,6 +11,7 @@
 #include <time.h>
 #include <pwd.h>
 #include <signal.h>
+#include <pthread.h>
 
 #if (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L) || defined(__GLIBC__)
 	#define HAS_STATVFS
@@ -554,3 +555,18 @@
 {
 	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);
+}
--- a/win32.c
+++ b/win32.c
@@ -2242,3 +2242,26 @@
 	}
 	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