From 7696a385433f815d8af8c905b45e2d7656299329 Mon Sep 17 00:00:00 2001
From: Perttu Ahola <celeron55@gmail.com>
Date: Tue, 15 Nov 2011 23:58:56 +0200
Subject: [PATCH] Improve loading screen and protocol

---
 src/client.cpp     |  93 ++++++++++++++++++++++++--------------
 src/client.h       |  18 +++++++-
 src/clientserver.h |  24 +++++-----
 src/game.cpp       | 110 +++++++++++++++++++++++++++++++--------------
 src/server.cpp     |  88 +++++++++++++++++++++++-------------
 5 files changed, 218 insertions(+), 115 deletions(-)

diff --git a/src/client.cpp b/src/client.cpp
index 4d9233d66..9752ec5e2 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -211,7 +211,11 @@ Client::Client(
 	m_time_of_day(0),
 	m_map_seed(0),
 	m_password(password),
-	m_access_denied(false)
+	m_access_denied(false),
+	m_texture_receive_progress(0),
+	m_textures_received(false),
+	m_tooldef_received(false),
+	m_nodedef_received(false)
 {
 	m_packetcounter_timer = 0.0;
 	//m_delete_unused_sectors_timer = 0.0;
@@ -661,8 +665,14 @@ void Client::deletingPeer(con::Peer *peer, bool timeout)
 void Client::ReceiveAll()
 {
 	DSTACK(__FUNCTION_NAME);
+	u32 start_ms = porting::getTimeMs();
 	for(;;)
 	{
+		// Limit time even if there would be huge amounts of data to
+		// process
+		if(porting::getTimeMs() > start_ms + 100)
+			break;
+		
 		try{
 			Receive();
 		}
@@ -1505,24 +1515,6 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 		event.deathscreen.camera_point_target_z = camera_point_target.Z;
 		m_client_event_queue.push_back(event);
 	}
-	else if(command == TOCLIENT_TOOLDEF)
-	{
-		infostream<<"Client: Received tool definitions: packet size: "
-				<<datasize<<std::endl;
-
-		std::string datastring((char*)&data[2], datasize-2);
-		std::istringstream is(datastring, std::ios_base::binary);
-
-		// Stop threads while updating content definitions
-		m_mesh_update_thread.stop();
-
-		std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary);
-		m_tooldef->deSerialize(tmp_is);
-		
-		// Resume threads
-		m_mesh_update_thread.setRun(true);
-		m_mesh_update_thread.Start();
-	}
 	else if(command == TOCLIENT_TEXTURES)
 	{
 		infostream<<"Client: Received textures: packet size: "<<datasize
@@ -1539,7 +1531,9 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 		
 		/*
 			u16 command
-			u32 number of textures
+			u16 total number of texture bunches
+			u16 index of this bunch
+			u32 number of textures in this bunch
 			for each texture {
 				u16 length of name
 				string name
@@ -1547,6 +1541,11 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 				data
 			}
 		*/
+		int num_bunches = readU16(is);
+		int bunch_i = readU16(is);
+		m_texture_receive_progress = (float)bunch_i / (float)(num_bunches - 1);
+		if(bunch_i == num_bunches - 1)
+			m_textures_received = true;
 		int num_textures = readU32(is);
 		infostream<<"Client: Received textures: count: "<<num_textures
 				<<std::endl;
@@ -1572,15 +1571,17 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 			rfile->drop();
 		}
 		
-		// Rebuild inherited images and recreate textures
-		m_tsrc->rebuildImagesAndTextures();
+		if(m_nodedef_received && m_textures_received){
+			// Rebuild inherited images and recreate textures
+			m_tsrc->rebuildImagesAndTextures();
 
-		// Update texture atlas
-		if(g_settings->getBool("enable_texture_atlas"))
-			m_tsrc->buildMainAtlas(this);
-		
-		// Update node textures
-		m_nodedef->updateTextures(m_tsrc);
+			// Update texture atlas
+			if(g_settings->getBool("enable_texture_atlas"))
+				m_tsrc->buildMainAtlas(this);
+			
+			// Update node textures
+			m_nodedef->updateTextures(m_tsrc);
+		}
 
 		// Resume threads
 		m_mesh_update_thread.setRun(true);
@@ -1590,6 +1591,26 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 		event.type = CE_TEXTURES_UPDATED;
 		m_client_event_queue.push_back(event);
 	}
+	else if(command == TOCLIENT_TOOLDEF)
+	{
+		infostream<<"Client: Received tool definitions: packet size: "
+				<<datasize<<std::endl;
+
+		std::string datastring((char*)&data[2], datasize-2);
+		std::istringstream is(datastring, std::ios_base::binary);
+
+		m_tooldef_received = true;
+
+		// Stop threads while updating content definitions
+		m_mesh_update_thread.stop();
+
+		std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary);
+		m_tooldef->deSerialize(tmp_is);
+		
+		// Resume threads
+		m_mesh_update_thread.setRun(true);
+		m_mesh_update_thread.Start();
+	}
 	else if(command == TOCLIENT_NODEDEF)
 	{
 		infostream<<"Client: Received node definitions: packet size: "
@@ -1598,18 +1619,22 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 		std::string datastring((char*)&data[2], datasize-2);
 		std::istringstream is(datastring, std::ios_base::binary);
 
+		m_nodedef_received = true;
+
 		// Stop threads while updating content definitions
 		m_mesh_update_thread.stop();
 
 		std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary);
 		m_nodedef->deSerialize(tmp_is, this);
 		
-		// Update texture atlas
-		if(g_settings->getBool("enable_texture_atlas"))
-			m_tsrc->buildMainAtlas(this);
-		
-		// Update node textures
-		m_nodedef->updateTextures(m_tsrc);
+		if(m_textures_received){
+			// Update texture atlas
+			if(g_settings->getBool("enable_texture_atlas"))
+				m_tsrc->buildMainAtlas(this);
+			
+			// Update node textures
+			m_nodedef->updateTextures(m_tsrc);
+		}
 
 		// Resume threads
 		m_mesh_update_thread.setRun(true);
diff --git a/src/client.h b/src/client.h
index b160a3bc9..625170b17 100644
--- a/src/client.h
+++ b/src/client.h
@@ -305,11 +305,21 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
 	// Get event from queue. CE_NONE is returned if queue is empty.
 	ClientEvent getClientEvent();
 	
-	inline bool accessDenied()
+	bool accessDenied()
 	{ return m_access_denied; }
 
-	inline std::wstring accessDeniedReason()
+	std::wstring accessDeniedReason()
 	{ return m_access_denied_reason; }
+
+	float textureReceiveProgress()
+	{ return m_texture_receive_progress; }
+
+	bool texturesReceived()
+	{ return m_textures_received; }
+	bool tooldefReceived()
+	{ return m_tooldef_received; }
+	bool nodedefReceived()
+	{ return m_nodedef_received; }
 	
 	float getRTT(void);
 
@@ -367,6 +377,10 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
 	std::wstring m_access_denied_reason;
 	InventoryContext m_inventory_context;
 	Queue<ClientEvent> m_client_event_queue;
+	float m_texture_receive_progress;
+	bool m_textures_received;
+	bool m_tooldef_received;
+	bool m_nodedef_received;
 	friend class FarMesh;
 };
 
diff --git a/src/clientserver.h b/src/clientserver.h
index cd54fe239..148f99cc3 100644
--- a/src/clientserver.h
+++ b/src/clientserver.h
@@ -28,8 +28,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	PROTOCOL_VERSION 3:
 		Base for writing changes here
 	PROTOCOL_VERSION 4:
-		Add TOCLIENT_TOOLDEF
 		Add TOCLIENT_TEXTURES
+		Add TOCLIENT_TOOLDEF
 		Add TOCLIENT_NODEDEF
 */
 
@@ -195,17 +195,12 @@ enum ToClientCommand
 		v3f1000 camera point target (to point the death cause or whatever)
 	*/
 
-	TOCLIENT_TOOLDEF = 0x38,
-	/*
-		u16 command
-		u32 length of the next item
-		serialized ToolDefManager
-	*/
-	
-	TOCLIENT_TEXTURES = 0x39,
+	TOCLIENT_TEXTURES = 0x38,
 	/*
 		u16 command
-		u32 number of textures
+		u16 total number of texture bunches
+		u16 index of this bunch
+		u32 number of textures in this bunch
 		for each texture {
 			u16 length of name
 			string name
@@ -214,17 +209,18 @@ enum ToClientCommand
 		}
 	*/
 	
-	TOCLIENT_NODEDEF = 0x3a,
+	TOCLIENT_TOOLDEF = 0x39,
 	/*
 		u16 command
 		u32 length of the next item
-		serialized NodeDefManager
+		serialized ToolDefManager
 	*/
 	
-	//TOCLIENT_CONTENT_SENDING_MODE = 0x38,
+	TOCLIENT_NODEDEF = 0x3a,
 	/*
 		u16 command
-		u8 mode (0 = off, 1 = on)
+		u32 length of the next item
+		serialized NodeDefManager
 	*/
 };
 
diff --git a/src/game.cpp b/src/game.cpp
index bb1998066..925dead7c 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -632,21 +632,18 @@ void the_game(
 	/*
 		Draw "Loading" screen
 	*/
-	/*gui::IGUIStaticText *gui_loadingtext = */
-	//draw_load_screen(L"Loading and connecting...", driver, font);
 
 	draw_load_screen(L"Loading...", driver, font);
 	
-	// Create tool definition manager
-	IWritableToolDefManager *tooldef = createToolDefManager();
 	// Create texture source
 	IWritableTextureSource *tsrc = createTextureSource(device);
+	
+	// These will be filled by data received from the server
+	// Create tool definition manager
+	IWritableToolDefManager *tooldef = createToolDefManager();
 	// Create node definition manager
 	IWritableNodeDefManager *nodedef = createNodeDefManager();
 
-	// Fill node feature table with default definitions
-	//content_mapnode_init(nodedef);
-
 	/*
 		Create server.
 		SharedPtr will delete it when it goes out of scope.
@@ -702,54 +699,51 @@ void the_game(
 	connect_address.print(&infostream);
 	infostream<<std::endl;
 	client.connect(connect_address);
-
-	bool could_connect = false;
 	
+	/*
+		Wait for server to accept connection
+	*/
+	bool could_connect = false;
 	try{
+		float frametime = 0.033;
+		const float timeout = 10.0;
 		float time_counter = 0.0;
 		for(;;)
 		{
-			if(client.connectedAndInitialized())
-			{
+			// Update client and server
+			client.step(frametime);
+			if(server != NULL)
+				server->step(frametime);
+			
+			// End condition
+			if(client.connectedAndInitialized()){
 				could_connect = true;
 				break;
 			}
+			// Break conditions
 			if(client.accessDenied())
-			{
 				break;
-			}
-			// Wait for 10 seconds
-			if(time_counter >= 10.0)
-			{
+			if(time_counter >= timeout)
 				break;
-			}
 			
+			// Display status
 			std::wostringstream ss;
 			ss<<L"Connecting to server... (timeout in ";
-			ss<<(int)(10.0 - time_counter + 1.0);
+			ss<<(int)(timeout - time_counter + 1.0);
 			ss<<L" seconds)";
 			draw_load_screen(ss.str(), driver, font);
-
-			/*// Update screen
-			driver->beginScene(true, true, video::SColor(255,0,0,0));
-			guienv->drawAll();
-			driver->endScene();*/
-
-			// Update client and server
-
-			client.step(0.1);
-
-			if(server != NULL)
-				server->step(0.1);
 			
 			// Delay a bit
-			sleep_ms(100);
-			time_counter += 0.1;
+			sleep_ms(1000*frametime);
+			time_counter += frametime;
 		}
 	}
 	catch(con::PeerNotFoundException &e)
 	{}
-
+	
+	/*
+		Handle failure to connect
+	*/
 	if(could_connect == false)
 	{
 		if(client.accessDenied())
@@ -766,6 +760,56 @@ void the_game(
 		//gui_loadingtext->remove();
 		return;
 	}
+	
+	/*
+		Wait until content has been received
+	*/
+	bool got_content = false;
+	{
+		float frametime = 0.033;
+		const float timeout = 5.0;
+		float time_counter = 0.0;
+		for(;;)
+		{
+			// Update client and server
+			client.step(frametime);
+			if(server != NULL)
+				server->step(frametime);
+			
+			// End condition
+			if(client.texturesReceived() &&
+					client.tooldefReceived() &&
+					client.nodedefReceived()){
+				got_content = true;
+				break;
+			}
+			// Break conditions
+			if(!client.connectedAndInitialized())
+				break;
+			if(time_counter >= timeout)
+				break;
+			
+			// Display status
+			std::wostringstream ss;
+			ss<<L"Waiting content... (continuing anyway in ";
+			ss<<(int)(timeout - time_counter + 1.0);
+			ss<<L" seconds)\n";
+
+			ss<<(client.tooldefReceived()?L"[X]":L"[  ]");
+			ss<<L" Tool definitions\n";
+			ss<<(client.nodedefReceived()?L"[X]":L"[  ]");
+			ss<<L" Node definitions\n";
+			//ss<<(client.texturesReceived()?L"[X]":L"[  ]");
+			ss<<L"["<<(int)(client.textureReceiveProgress()*100+0.5)<<L"%] ";
+			ss<<L" Textures\n";
+
+			draw_load_screen(ss.str(), driver, font);
+			
+			// Delay a bit
+			sleep_ms(1000*frametime);
+			time_counter += frametime;
+		}
+	}
 
 	/*
 		Create skybox
diff --git a/src/server.cpp b/src/server.cpp
index 45630d301..2a9aac32b 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -2139,15 +2139,15 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 			Send some initialization data
 		*/
 
-		// Send textures
-		SendTextures(peer_id);
-		
 		// Send tool definitions
 		SendToolDef(m_con, peer_id, m_toolmgr);
 		
 		// Send node definitions
 		SendNodeDef(m_con, peer_id, m_nodemgr);
 		
+		// Send textures
+		SendTextures(peer_id);
+		
 		// Send player info to all players
 		SendPlayerInfos();
 
@@ -4160,7 +4160,13 @@ void Server::SendTextures(u16 peer_id)
 	
 	/* Read textures */
 	
-	core::list<SendableTexture> textures;
+	// Put 5kB in one bunch (this is not accurate)
+	u32 bytes_per_bunch = 5000;
+	
+	core::array< core::list<SendableTexture> > texture_bunches;
+	texture_bunches.push_back(core::list<SendableTexture>());
+	
+	u32 texture_size_bunch_total = 0;
 	core::list<ModSpec> mods = getMods(m_modspaths);
 	for(core::list<ModSpec>::Iterator i = mods.begin();
 			i != mods.end(); i++){
@@ -4186,6 +4192,7 @@ void Server::SendTextures(u16 peer_id)
 				fis.read(buf, 1024);
 				std::streamsize len = fis.gcount();
 				tmp_os.write(buf, len);
+				texture_size_bunch_total += len;
 				if(fis.eof())
 					break;
 				if(!fis.good()){
@@ -4201,40 +4208,57 @@ void Server::SendTextures(u16 peer_id)
 			errorstream<<"Server::SendTextures(): Loaded \""
 					<<tname<<"\""<<std::endl;
 			// Put in list
-			textures.push_back(SendableTexture(tname, tpath, tmp_os.str()));
+			texture_bunches[texture_bunches.size()-1].push_back(
+					SendableTexture(tname, tpath, tmp_os.str()));
+			
+			// Start next bunch if got enough data
+			if(texture_size_bunch_total >= bytes_per_bunch){
+				texture_bunches.push_back(core::list<SendableTexture>());
+				texture_size_bunch_total = 0;
+			}
 		}
 	}
 
-	/* Create and send packet */
+	/* Create and send packets */
+	
+	u32 num_bunches = texture_bunches.size();
+	for(u32 i=0; i<num_bunches; i++)
+	{
+		/*
+			u16 command
+			u16 total number of texture bunches
+			u16 index of this bunch
+			u32 number of textures in this bunch
+			for each texture {
+				u16 length of name
+				string name
+				u32 length of data
+				data
+			}
+		*/
+		std::ostringstream os(std::ios_base::binary);
 
-	/*
-		u16 command
-		u32 number of textures
-		for each texture {
-			u16 length of name
-			string name
-			u32 length of data
-			data
+		writeU16(os, TOCLIENT_TEXTURES);
+		writeU16(os, num_bunches);
+		writeU16(os, i);
+		writeU32(os, texture_bunches[i].size());
+		
+		for(core::list<SendableTexture>::Iterator
+				j = texture_bunches[i].begin();
+				j != texture_bunches[i].end(); j++){
+			os<<serializeString(j->name);
+			os<<serializeLongString(j->data);
 		}
-	*/
-	std::ostringstream os(std::ios_base::binary);
-
-	writeU16(os, TOCLIENT_TEXTURES);
-	writeU32(os, textures.size());
-	
-	for(core::list<SendableTexture>::Iterator i = textures.begin();
-			i != textures.end(); i++){
-		os<<serializeString(i->name);
-		os<<serializeLongString(i->data);
+		
+		// Make data buffer
+		std::string s = os.str();
+		infostream<<"Server::SendTextures(): number of textures in bunch["
+				<<i<<"]: "<<texture_bunches[i].size()
+				<<", size: "<<s.size()<<std::endl;
+		SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+		// Send as reliable
+		m_con.Send(peer_id, 0, data, true);
 	}
-	
-	// Make data buffer
-	std::string s = os.str();
-	infostream<<"Server::SendTextures(): number of textures: "
-			<<textures.size()<<", data size: "<<s.size()<<std::endl;
-	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
-	// Send as reliable
-	m_con.Send(peer_id, 0, data, true);
 }
 
 /*
-- 
GitLab