BWBalance.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. /*
  2. ** Command & Conquer Renegade(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /***********************************************************************************************
  19. *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : Command & Conquer *
  23. * *
  24. * $Archive:: /Commando/Code/wwnet/BWBalance.cpp $*
  25. * *
  26. * $Author:: Steve_t $*
  27. * *
  28. * $Modtime:: 10/13/02 9:40p $*
  29. * *
  30. * $Revision:: 12 $*
  31. * *
  32. *---------------------------------------------------------------------------------------------*
  33. * Functions: *
  34. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  35. #include <always.h>
  36. #include "bwbalance.h"
  37. #include "connect.h"
  38. #include "packetmgr.h"
  39. #include "systimer.h"
  40. BandwidthBalancerClass BandwidthBalancer;
  41. /*
  42. ** 8000 bits per second is about as low as we want to go.
  43. */
  44. #define MIN_ACCEPTABLE_BANDWIDTH 8000
  45. /***********************************************************************************************
  46. * BandwidthBalancerClass::BandwidthBalancerClass -- Class constructor *
  47. * *
  48. * *
  49. * *
  50. * INPUT: Nothing *
  51. * *
  52. * OUTPUT: Nothing *
  53. * *
  54. * WARNINGS: None *
  55. * *
  56. * HISTORY: *
  57. * 10/21/2001 8:47PM ST : Created *
  58. *=============================================================================================*/
  59. BandwidthBalancerClass::BandwidthBalancerClass(void)
  60. {
  61. NumClientStructs = 0;
  62. ClientInfo = NULL;
  63. Allocate_Client_Structs(8);
  64. IsEnabled = true;
  65. }
  66. /***********************************************************************************************
  67. * BandwidthBalancerClass::~BandwidthBalancerClass -- Class destructor *
  68. * *
  69. * *
  70. * *
  71. * INPUT: Nothing *
  72. * *
  73. * OUTPUT: Nothing *
  74. * *
  75. * WARNINGS: None *
  76. * *
  77. * HISTORY: *
  78. * 10/21/2001 8:47PM ST : Created *
  79. *=============================================================================================*/
  80. BandwidthBalancerClass::~BandwidthBalancerClass(void)
  81. {
  82. if (ClientInfo) {
  83. delete [] ClientInfo;
  84. }
  85. ClientInfo = NULL;
  86. }
  87. /***********************************************************************************************
  88. * BandwidthBalancerClass::Allocate_Client_Structs -- Make room to store per client info *
  89. * *
  90. * *
  91. * *
  92. * INPUT: Likely number of clients *
  93. * *
  94. * OUTPUT: Nothing *
  95. * *
  96. * WARNINGS: None *
  97. * *
  98. * HISTORY: *
  99. * 10/21/2001 8:47PM ST : Created *
  100. *=============================================================================================*/
  101. void BandwidthBalancerClass::Allocate_Client_Structs(int num_structs)
  102. {
  103. if (num_structs > NumClientStructs) {
  104. num_structs += 15;
  105. num_structs &= 0xfffffff0;
  106. if (ClientInfo) {
  107. delete [] ClientInfo;
  108. }
  109. ClientInfo = new ClientInfoStruct[num_structs];
  110. NumClientStructs = num_structs;
  111. }
  112. }
  113. /***********************************************************************************************
  114. * BandwidthBalancerClass::Adjust -- Adjust bandwidth to each client *
  115. * *
  116. * *
  117. * *
  118. * INPUT: Nothing *
  119. * *
  120. * OUTPUT: Nothing *
  121. * *
  122. * WARNINGS: None *
  123. * *
  124. * HISTORY: *
  125. * 10/21/2001 8:48PM ST : Created *
  126. *=============================================================================================*/
  127. void BandwidthBalancerClass::Adjust(cConnection *connection, bool is_dedicated)
  128. {
  129. static unsigned long _last_adjustment = 0; //TIMEGETTIME();
  130. /*
  131. **
  132. ** Get average average priority.
  133. **
  134. ** Do a pass and assign bandwidth to clients based on priority. Boost bandwidth to players currently loading. Clip bandwidth to
  135. ** clients max bps (and +- 50%) and keep a total of spare bandwidth.
  136. **
  137. ** Repeat until no bandwidth left.
  138. **
  139. */
  140. if (connection != NULL) {
  141. bool need_update = false;
  142. for (int i=connection->Get_Min_RHost() + (is_dedicated ? 0 : 1) ; i <= connection->Get_Max_RHost() ; i++) {
  143. cRemoteHost *client = connection->Get_Remote_Host(i);
  144. if (client && client->Get_Target_Bps() == 0) {
  145. need_update = true;
  146. break;
  147. }
  148. }
  149. if (need_update || TIMEGETTIME() - _last_adjustment > 100) {
  150. _last_adjustment = TIMEGETTIME();
  151. Adjust_Connection_Budget(connection);
  152. int num_remote_hosts = connection->Get_Num_RHosts();
  153. if (!is_dedicated) {
  154. num_remote_hosts--;
  155. }
  156. if (num_remote_hosts > NumClientStructs) {
  157. Allocate_Client_Structs(num_remote_hosts);
  158. }
  159. WWASSERT(NumClientStructs >= num_remote_hosts);
  160. NumClients = 0;
  161. /*
  162. ** Copy all the info we need into a single place.
  163. */
  164. float average_priority = 0;
  165. for (int i=connection->Get_Min_RHost() + (is_dedicated ? 0 : 1) ; i <= connection->Get_Max_RHost() ; i++) {
  166. cRemoteHost *client = connection->Get_Remote_Host(i);
  167. if (client != NULL) {
  168. ClientInfo[NumClients].AveragePriority = client->Get_Average_Priority();
  169. ClientInfo[NumClients].MaxBpsDown = client->Get_Maximum_Bps();
  170. ClientInfo[NumClients].AllocatedBBO = client->Get_Target_Bps();
  171. ClientInfo[NumClients].ID = client->Get_Id();
  172. ClientInfo[NumClients].IsLoading = client->Get_Is_Loading();
  173. ClientInfo[NumClients].IsDone = false;
  174. /*
  175. ** Track the overally average priority.
  176. */
  177. average_priority += ClientInfo[NumClients].AveragePriority;
  178. NumClients++;
  179. }
  180. }
  181. WWASSERT(NumClients == num_remote_hosts);
  182. if (NumClients) {
  183. /*
  184. ** Get the average object priority among all players.
  185. */
  186. average_priority = average_priority / NumClients;
  187. int bw_adjust = 100;
  188. unsigned long total_bbo_allocated = Allocate_Bandwidth(average_priority, bw_adjust, connection->Get_Bandwidth_Budget_Out());
  189. /*
  190. ** Ok, we have a rough bandwidth allocation. If we allocated too much or too little then we will need to do
  191. ** some fine tuning.
  192. */
  193. unsigned long total_bbo = connection->Get_Bandwidth_Budget_Out();
  194. int diff = total_bbo - total_bbo_allocated;
  195. int percent_diff = (100 * abs(diff)) / total_bbo;
  196. diff = total_bbo - total_bbo_allocated;
  197. bw_adjust = 100 + ((100 * diff) / (((int)total_bbo * 9) / 10));
  198. if (bw_adjust) {
  199. /*
  200. ** If we allocated more or less than we have then keep adjusting until it comes out at about 95%.
  201. */
  202. int tries = 5;
  203. while ((diff < 0 || percent_diff > 10) && tries >= 0) {
  204. //WWDEBUG_SAY(("Allocated too much or too little bandwidth. total_bbo = %d, total_bbo_allocated = %d, bw_adjust = %d\n", total_bbo, total_bbo_allocated, bw_adjust));
  205. total_bbo_allocated = Allocate_Bandwidth(average_priority, bw_adjust, connection->Get_Bandwidth_Budget_Out());
  206. diff = total_bbo - total_bbo_allocated;
  207. percent_diff = (100 * abs(diff)) / total_bbo;
  208. bw_adjust = bw_adjust * (100 + ((100 * diff) / (((int)total_bbo * 9) / 10))) / 100;
  209. if (bw_adjust <= 0) {
  210. break;
  211. }
  212. tries--;
  213. }
  214. /*
  215. ** If we get to this point, there should still be bandwidth left.
  216. */
  217. if (diff < 0 && percent_diff > 3) {
  218. WWDEBUG_SAY(("***** WARNING - BandwidthBalancer: Insufficient bandwidth for the number of clients in the game *********\n"));
  219. WWDEBUG_SAY(("Allocated too much bandwidth. total_bbo = %d, total_bbo_allocated = %d, bw_adjust = %d\n", total_bbo, total_bbo_allocated, bw_adjust));
  220. }
  221. /*
  222. ** Oh well, let's assume that's right. Set the bandwidth budgets back into the rhosts.
  223. */
  224. int index = 0;
  225. for (int i=connection->Get_Min_RHost() + (is_dedicated ? 0 : 1) ; i <= connection->Get_Max_RHost() ; i++) {
  226. cRemoteHost *client = connection->Get_Remote_Host(i);
  227. if (client != NULL) {
  228. client->Set_Target_Bps(ClientInfo[index++].AllocatedBBO);
  229. }
  230. }
  231. }
  232. }
  233. }
  234. }
  235. }
  236. /***********************************************************************************************
  237. * BandwidthBalancerClass::Allocate_Bandwidth -- Split bandwidth between clients *
  238. * *
  239. * *
  240. * *
  241. * INPUT: Average object priority for all clients *
  242. * *
  243. * *
  244. * OUTPUT: Percentage to apply to final bandwidth numbers *
  245. * *
  246. * WARNINGS: None *
  247. * *
  248. * HISTORY: *
  249. * 10/21/2001 8:49PM ST : Created *
  250. *=============================================================================================*/
  251. unsigned long BandwidthBalancerClass::Allocate_Bandwidth(float average_priority, int bw_adjust, unsigned long total_server_bbo)
  252. {
  253. WWASSERT(bw_adjust != 0);
  254. WWASSERT(average_priority >= 0.0f);
  255. WWASSERT(average_priority <= 1.0f);
  256. /*
  257. ** Get our total bandwidth budget out. This has to be split between all clients.
  258. */
  259. unsigned long total_bbo = total_server_bbo;
  260. unsigned long bbo_per_client = total_bbo / NumClients;
  261. unsigned long total_bbo_allocated = 0;
  262. /*
  263. ** Loop through and assign bandwidth based on average priority and other metrics.
  264. */
  265. for (int i=0 ; i<NumClients ; i++) {
  266. ClientInfoStruct *client = &ClientInfo[i];
  267. float pri = client->AveragePriority;
  268. /*
  269. ** Work out the bandwidth, initially based on average priority.
  270. */
  271. int client_bbo_adjust = (pri - average_priority) * bbo_per_client;
  272. if (client_bbo_adjust > 0) {
  273. client_bbo_adjust = min(client_bbo_adjust, (int)bbo_per_client / 2);
  274. } else {
  275. client_bbo_adjust = max(client_bbo_adjust, -((int)bbo_per_client / 2));
  276. }
  277. int new_client_bbo = bbo_per_client + client_bbo_adjust;
  278. WWASSERT(new_client_bbo > 0);
  279. /*
  280. ** Boost the bandwidth if the client is loading. He doesn't need it now but there will be a huge demand for
  281. ** bandwidth as soon as the load is done so it's good to free some up now.
  282. */
  283. if (client->IsLoading) {
  284. new_client_bbo = new_client_bbo << 1;
  285. }
  286. /*
  287. ** Apply the overall percentage adjustment.
  288. */
  289. double big_client_bbo = (double) new_client_bbo;
  290. double big_bw_adjust = (double) bw_adjust;
  291. big_client_bbo = (big_client_bbo * big_bw_adjust) / 100.0;
  292. big_client_bbo = min(big_client_bbo, (double) 0x7fffffff);
  293. //new_client_bbo = (new_client_bbo * bw_adjust) / 100;
  294. new_client_bbo = (int) big_client_bbo;
  295. /*
  296. ** OK, we have the bandwidth adjusted according to priority.
  297. ** Clamp it to the min allowed and the max the client can take.
  298. */
  299. new_client_bbo = min(new_client_bbo, (int)client->MaxBpsDown);
  300. new_client_bbo = max(new_client_bbo, MIN_ACCEPTABLE_BANDWIDTH);
  301. client->AllocatedBBO = (unsigned long) new_client_bbo;
  302. total_bbo_allocated += (unsigned long) new_client_bbo;
  303. }
  304. return(total_bbo_allocated);
  305. }
  306. /***********************************************************************************************
  307. * BandwidthBalancerClass::Adjust_Connection_Budget -- Adjust BBO of server connection *
  308. * *
  309. * *
  310. * *
  311. * INPUT: Ptr to server connection *
  312. * *
  313. * OUTPUT: Nothing *
  314. * *
  315. * WARNINGS: None *
  316. * *
  317. * HISTORY: *
  318. * 10/24/2001 1:56PM ST : Created *
  319. *=============================================================================================*/
  320. void BandwidthBalancerClass::Adjust_Connection_Budget(cConnection *connection)
  321. {
  322. static unsigned long _last_time = 0;
  323. unsigned long time = TIMEGETTIME();
  324. /*
  325. ** Check every n seconds and see if we had any send errors that would be caused by sending too much data.
  326. */
  327. if (PacketManager.Get_Error_State() == PacketManagerClass::STATE_WS_BUFFERS_FULL) {
  328. if (time - _last_time > 10000) {
  329. _last_time = time;
  330. ULONG bbo = connection->Get_Bandwidth_Budget_Out();
  331. ULONG new_bbo = (bbo * 9) / 10;
  332. connection->Set_Bandwidth_Budget_Out(new_bbo);
  333. WWDEBUG_SAY(("*** WARNING BandwidthBalancerClass - Adjusting Server connection BBO from %d to %d due to send overflow ***\n", bbo, new_bbo));
  334. }
  335. }
  336. }