Changeset - r1885:5a3b2b72280f
[Not reviewed]
master
0 7 0
Darkvater - 19 years ago 2005-06-01 23:08:33
darkvater@openttd.org
(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
7 files changed with 223 insertions and 37 deletions:
0 comments (0 inline, 0 general)
functions.h
Show inline comments
 
@@ -263,7 +263,10 @@ int ttd_main(int argc, char* argv[]);
 
byte GetOSVersion(void);
 

	
 
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 */
lang/english.txt
Show inline comments
 
@@ -854,12 +854,14 @@ STR_0329_PURCHASE_LAND_FOR_FUTURE       
 
STR_032A_1_ROAD_VEHICLE_SERVICE                                 :{BLACK}1: Road vehicle service
 
STR_032B_2_RAILROAD_SERVICE                                     :{BLACK}2: Railway service
 
STR_032C_3_AIR_SERVICE                                          :{BLACK}3: Air service
 
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
 
STR_0336_7                                                      :{BLACK}7
 

	
 
############ start of townname region
main_gui.c
Show inline comments
 
@@ -2204,30 +2204,30 @@ static bool DrawScrollingStatusText(News
 

	
 
	return x > 0;
 
}
 

	
 
static void StatusBarWndProc(Window *w, WindowEvent *e)
 
{
 
	Player *p;
 

	
 
	switch(e->event) {
 
	case WE_PAINT:
 
	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);
 
		} else if (WP(w,def_d).data_1 > -1280 && FindWindowById(WC_NEWS_WINDOW,0) == NULL && _statusbar_news_item.string_id != 0) {
 
			// Draw the scrolling news text
 
			if (!DrawScrollingStatusText(&_statusbar_news_item, WP(w,def_d).data_1))
 
@@ -2238,23 +2238,25 @@ static void StatusBarWndProc(Window *w, 
 
				SetDParam(0, p->name_1);
 
				SetDParam(1, p->name_2);
 
				DrawStringCentered(320, 1,	STR_02BA, 0);
 
			}
 
		}
 

	
 
		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;
 

	
 
	case WE_TICK: {
 
		if (_pause) return;
 

	
saveload.c
Show inline comments
 
@@ -815,12 +815,50 @@ static bool InitNoComp(void)
 
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 **************
 
//********************************************
 

	
 
#if defined(WITH_ZLIB)
 
#include <zlib.h>
 
static z_stream _z;
 
@@ -1061,39 +1099,41 @@ typedef struct {
 
	bool (*init_write)(void);   /// function executed upon intialization of the saver
 
	WriterProc *writer;         /// function that saves the data to the file
 
	void (*uninit_write)(void); /// function executed when writing is done
 
} SaveLoadFormat;
 

	
 
static const SaveLoadFormat _saveload_formats[] = {
 
	{"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},
 
#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);
 
	}
 
	return def;
 
}
 

	
 
@@ -1109,12 +1149,101 @@ static inline int AbortSaveLoad(void)
 
	if (_sl.fh != NULL) fclose(_sl.fh);
 

	
 
	_sl.fh = NULL;
 
	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
 
 * @param filename The name of the savegame being created/loaded
 
 * @param mode Save or load. Load can also be a TTD(Patch) game. Use SL_LOAD, SL_OLD_LOAD or SL_SAVE
 
 * @return Return the results of the action. SL_OK, SL_ERROR or SL_REINIT ("unload" the game)
 
@@ -1130,12 +1259,18 @@ int SaveOrLoad(const char *filename, int
 
		InitializeGame(8, 8); // set a mapsize of 256x256 for TTDPatch games or it might get confused
 
		if (!LoadOldSaveGame(filename)) return SL_REINIT;
 
		AfterLoadGame(0);
 
		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.");
 
		return SL_ERROR;
 
	}
 

	
 
@@ -1144,13 +1279,14 @@ int SaveOrLoad(const char *filename, int
 
	_sl.int_to_ref_proc = IntToReference;
 
	_sl.ref_to_int_proc = ReferenceToInt;
 
	_sl.save = 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();
 

	
 
		// deinitialize compressor.
 
		_sl.excpt_uninit();
 

	
 
@@ -1165,36 +1301,41 @@ int SaveOrLoad(const char *filename, int
 
	}
 

	
 
  /* We first initialize here to avoid: "warning: variable `version' might
 
   * 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;
 
		if (!fmt->init_write()) {
 
			DEBUG(misc, 0) ("Initializing writer %s failed.", fmt->name);
 
			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();
 
		}
 

	
 
		// see if we have any loader for this type.
 
		for (fmt = _saveload_formats; ; fmt++) {
 
			/* No loader found, treat as version 0 and use LZO format */
 
@@ -1234,27 +1375,25 @@ int SaveOrLoad(const char *filename, int
 

	
 
		if (!fmt->init_read()) {
 
			DEBUG(misc, 0) ("Initializing loader %s failed.", fmt->name);
 
			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 (mode == SL_LOAD && !AfterLoadGame(version))
 
			return SL_REINIT;
 
		if (!AfterLoadGame(version)) return SL_REINIT;
 
	}
 

	
 
	return SL_OK;
 
}
 

	
 
/** Do a save when exiting the game (patch option) _patches.autosave_on_exit */
 
void DoExitSave(void)
ttd.c
Show inline comments
 
@@ -683,12 +683,13 @@ int ttd_main(int argc, char* argv[])
 
		}
 
	}
 
#endif /* ENABLE_NETWORK */
 

	
 
	while (_video_driver->main_loop() == ML_SWITCHDRIVER) {}
 

	
 
	JoinOTTDThread();
 
	IConsoleFree();
 

	
 
#ifdef ENABLE_NETWORK
 
	if (_network_available) {
 
		// Shut down the network and close any open connections
 
		NetworkDisconnect();
unix.c
Show inline comments
 
@@ -8,12 +8,13 @@
 
#include <dirent.h>
 
#include <unistd.h>
 
#include <sys/stat.h>
 
#include <time.h>
 
#include <pwd.h>
 
#include <signal.h>
 
#include <pthread.h>
 

	
 
#if (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L) || defined(__GLIBC__)
 
	#define HAS_STATVFS
 
#endif
 

	
 
#ifdef HAS_STATVFS
 
@@ -551,6 +552,21 @@ void DeterminePaths(void)
 
}
 

	
 
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);
 
}
win32.c
Show inline comments
 
@@ -2239,6 +2239,29 @@ bool InsertTextBufferClipboard(Textbuf *
 
		GlobalUnlock(cbuf);
 
		CloseClipboard();
 
		return true;
 
	}
 
	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
0 comments (0 inline, 0 general)