hrydgard / ppsspp

A PSP emulator for Android, Windows, Mac and Linux, written in C++. Want to contribute? Join us on Discord at https://discord.gg/5NJB6dD or just send pull requests / issues. For discussion use the forums at forums.ppsspp.org.

Home Page:https://www.ppsspp.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Make the proAdhocServer lighter

Anuskuss opened this issue · comments

What should happen

I'm running my own proAdhocServer (using this mirror) but PPSSPP suffers from the same issue, i.e. it's running in a loop which wastes CPU cycles. Right now my Adhoc server sits at 1-2% idle while lighttpd (a HTTP server) sits at a constant 0%. The only improvement that can be made with the current design is to increase the sleep (like @anr2me has already done here) but that only reduces the CPU usage to 0-0.7%.
My question is how do you actually put the thread to sleep and only wake it up once something tries to read/write from/to the socket/port (like you'd see with a lock)? I mean it has to be possible because lighttpd listens on port 80, runs at 0% AND also responds instantly. Just increasing the sleep further probably results in connection problems so I don't think that is the solution.

Who would this benefit

PPSSPP users that enable the built-in proAdhocServer. Also energy isn't free, battery life, heat etc.

Platform (if relevant)

None

Games this would be useful in

Other emulators or software with a similar feature

No response

Checklist

I wrote some shitty code that does what I want but I'm not confident in making those changes to PPSSPP:

--- a/src/main.c
+++ b/src/main.c
@@ -31,16 +31,19 @@
 #include <config.h>
 #include <user.h>
 #include <status.h>
+#include <unistd.h>
 
 // Server Status
-int _status = 0;
+int server;
+int status = 0;
+int users = 0;
 
 // Function Prototypes
 void interrupt(int sig);
 void enable_address_reuse(int fd);
 void change_blocking_mode(int fd, int nonblocking);
 int create_listen_socket(uint16_t port);
-int server_loop(int server);
+int server_loop();
 
 /**
  * Server Entry Point
@@ -63,7 +66,7 @@ int main(int argc, char * argv[])
 	signal(SIGTERM, interrupt);
 	
 	// Create Listening Socket
-	int server = create_listen_socket(SERVER_PORT);
+	server = create_listen_socket(SERVER_PORT);
 	
 	// Created Listening Socket
 	if(server != -1)
@@ -72,7 +75,7 @@ int main(int argc, char * argv[])
 		printf("Listening for Connections on TCP Port %u.\n", SERVER_PORT);
 		
 		// Enter Server Loop
-		result = server_loop(server);
+		result = server_loop();
 		
 		// Notify User
 		printf("Shutdown complete.\n");
@@ -92,7 +95,8 @@ void interrupt(int sig)
 	printf("Shutting down... please wait.\n");
 	
 	// Trigger Shutdown
-	_status = 0;
+	status = 0;
+	close(server);
 }
 
 /**
@@ -146,7 +150,7 @@ int create_listen_socket(uint16_t port)
 		enable_address_reuse(fd);
 		
 		// Make Socket Nonblocking
-		change_blocking_mode(fd, 1);
+		//change_blocking_mode(fd, 1);
 		
 		// Prepare Local Address Information
 		struct sockaddr_in local;
@@ -187,22 +191,22 @@ int create_listen_socket(uint16_t port)
  * @param server Server Listening Socket
  * @return OS Error Code
  */
-int server_loop(int server)
+int server_loop()
 {
 	// Set Running Status
-	_status = 1;
+	status = 1;
 	
 	// Create Empty Status Logfile
 	update_status();
 	
 	// Handling Loop
-	while(_status == 1)
+	while(status == 1)
 	{
 		// Login Block
 		{
 			// Login Result
 			int loginresult = 0;
-			
+
 			// Login Processing Loop
 			do
 			{
@@ -213,7 +217,7 @@ int server_loop(int server)
 				
 				// Accept Login Requests
 				// loginresult = accept4(server, (struct sockaddr *)&addr, &addrlen, SOCK_NONBLOCK);
-				
+
 				// Alternative Accept Approach (some Linux Kernel don't support the accept4 Syscall... wtf?)
 				loginresult = accept(server, (struct sockaddr *)&addr, &addrlen);
 				if(loginresult != -1)
@@ -223,7 +227,11 @@ int server_loop(int server)
 				}
 				
 				// Login User (Stream)
-				if(loginresult != -1) login_user_stream(loginresult, addr.sin_addr.s_addr);
+				if(loginresult != -1) {
+					users++;
+					change_blocking_mode(server, 1);
+					login_user_stream(loginresult, addr.sin_addr.s_addr);
+				}
 			} while(loginresult != -1);
 		}
 		
@@ -241,6 +249,7 @@ int server_loop(int server)
 			if(recvresult == 0 || (recvresult == -1 && errno != EAGAIN && errno != EWOULDBLOCK) || get_user_state(user) == USER_STATE_TIMED_OUT)
 			{
 				// Logout User
+				if (--users == 0) change_blocking_mode(server, 0);
 				logout_user(user);
 			}
 			
@@ -285,6 +294,7 @@ int server_loop(int server)
 						printf("Invalid Opcode 0x%02X in Waiting State from %u.%u.%u.%u.\n", user->rx[0], ip[0], ip[1], ip[2], ip[3]);
 						
 						// Logout User
+						if (--users == 0) change_blocking_mode(server, 0);
 						logout_user(user);
 					}
 				}
@@ -369,6 +379,7 @@ int server_loop(int server)
 						printf("Invalid Opcode 0x%02X in Logged-In State from %s (MAC: %02X:%02X:%02X:%02X:%02X:%02X - IP: %u.%u.%u.%u).\n", user->rx[0], (char *)user->resolver.name.data, user->resolver.mac.data[0], user->resolver.mac.data[1], user->resolver.mac.data[2], user->resolver.mac.data[3], user->resolver.mac.data[4], user->resolver.mac.data[5], ip[0], ip[1], ip[2], ip[3]);
 
 						// Logout User
+						if (--users == 0) change_blocking_mode(server, 0);
 						logout_user(user);
 					}
 				}
--- a/src/user.c
+++ b/src/user.c
@@ -25,6 +25,8 @@
 #include <status.h>
 #include <config.h>
 #include <sqlite3.h>
+#include <unistd.h>
+#include <sys/socket.h>
 
 // User Count
 uint32_t _db_user_count = 0;

Increasing the loop delay will only slowdown the response time.

What you need to do is to use blocking socket and use select to detect data availability/readability and respond to it, since select will blocks the thread more efficiently than sleep, but you will probably need to use sleep for about 1~10ms too on each loop, in case there are a lot of incoming data (ie. if you have a lot of active players) making select unable to block the thread until there is no data left (where sleep will let your CPU to rest a bit during high traffic but also gives extra response delay).

PS: as i remembered select may have different behavior/returned error code on different platform, so make sure you've tested it on all platform if the changes are for PPSSPP built-in server (probably only need to test Windows, Linux, and MacOS)

PPS: there is also poll/epoll that are recommended for modern app, but not all platforms support it, thus will also need to use select as a fallback for unsupported platforms.

Well I'm not a network engineer so in the end I decided to just leave accept blocking when no users are logged in, non-blocking as soon as somebody joins (change_blocking_mode(server,1)) and then blocking again once all players left (change_blocking_mode(server,0)). I'm sure you'll come up with something less awful though ;)

Well since it's running on it's own thread it will probably safe to use blocking mode, i don't remembered much why it use non-blocking mode tho :) may be it won't be able to respond for another player's communication while blocking the thread waiting for one player's data.