1 /**
2  * @file nthread.cpp
3  *
4  * Implementation of functions for managing game ticks.
5  */
6 #include "all.h"
7 #include "../3rdParty/Storm/Source/storm.h"
8 
9 DEVILUTION_BEGIN_NAMESPACE
10 
11 BYTE sgbNetUpdateRate;
12 DWORD gdwMsgLenTbl[MAX_PLRS];
13 static CCritSect sgMemCrit;
14 DWORD gdwDeltaBytesSec;
15 BOOLEAN nthread_should_run;
16 DWORD gdwTurnsInTransit;
17 uintptr_t glpMsgTbl[MAX_PLRS];
18 SDL_threadID glpNThreadId;
19 char sgbSyncCountdown;
20 int turn_upper_bit;
21 BOOLEAN sgbTicsOutOfSync;
22 char sgbPacketCountdown;
23 BOOLEAN sgbThreadIsRunning;
24 DWORD gdwLargestMsgSize;
25 DWORD gdwNormalMsgSize;
26 int last_tick;
27 
28 /* data */
29 static SDL_Thread *sghThread = NULL;
30 
nthread_terminate_game(const char * pszFcn)31 void nthread_terminate_game(const char *pszFcn)
32 {
33 	DWORD sErr;
34 
35 	sErr = SErrGetLastError();
36 	if (sErr == STORM_ERROR_INVALID_PLAYER) {
37 		return;
38 	} else if (sErr == STORM_ERROR_GAME_TERMINATED) {
39 		gbGameDestroyed = TRUE;
40 	} else if (sErr == STORM_ERROR_NOT_IN_GAME) {
41 		gbGameDestroyed = TRUE;
42 	} else {
43 		app_fatal("%s:\n%s", pszFcn, TraceLastError());
44 	}
45 }
46 
nthread_send_and_recv_turn(DWORD cur_turn,int turn_delta)47 DWORD nthread_send_and_recv_turn(DWORD cur_turn, int turn_delta)
48 {
49 	DWORD new_cur_turn;
50 	int turn, turn_tmp;
51 	DWORD curTurnsInTransit;
52 
53 	new_cur_turn = cur_turn;
54 	if (!SNetGetTurnsInTransit(&curTurnsInTransit)) {
55 		nthread_terminate_game("SNetGetTurnsInTransit");
56 		return 0;
57 	}
58 	while (curTurnsInTransit++ < gdwTurnsInTransit) {
59 
60 		turn_tmp = turn_upper_bit | (new_cur_turn & 0x7FFFFFFF);
61 		turn_upper_bit = 0;
62 		turn = turn_tmp;
63 
64 		if (!SNetSendTurn((char *)&turn, sizeof(turn))) {
65 			nthread_terminate_game("SNetSendTurn");
66 			return 0;
67 		}
68 
69 		new_cur_turn += turn_delta;
70 		if (new_cur_turn >= 0x7FFFFFFF)
71 			new_cur_turn &= 0xFFFF;
72 	}
73 	return new_cur_turn;
74 }
75 
nthread_recv_turns(BOOL * pfSendAsync)76 BOOL nthread_recv_turns(BOOL *pfSendAsync)
77 {
78 	*pfSendAsync = FALSE;
79 	sgbPacketCountdown--;
80 	if (sgbPacketCountdown) {
81 		last_tick += gnTickDelay;
82 		return TRUE;
83 	}
84 	sgbSyncCountdown--;
85 	sgbPacketCountdown = sgbNetUpdateRate;
86 	if (sgbSyncCountdown != 0) {
87 
88 		*pfSendAsync = TRUE;
89 		last_tick += gnTickDelay;
90 		return TRUE;
91 	}
92 #ifdef __3DS__
93 	return FALSE;
94 #else
95 	if (!SNetReceiveTurns(0, MAX_PLRS, (char **)glpMsgTbl, gdwMsgLenTbl, (LPDWORD)player_state)) {
96 		if (SErrGetLastError() != STORM_ERROR_NO_MESSAGES_WAITING)
97 			nthread_terminate_game("SNetReceiveTurns");
98 		sgbTicsOutOfSync = FALSE;
99 		sgbSyncCountdown = 1;
100 		sgbPacketCountdown = 1;
101 		return FALSE;
102 	} else {
103 		if (!sgbTicsOutOfSync) {
104 			sgbTicsOutOfSync = TRUE;
105 			last_tick = SDL_GetTicks();
106 		}
107 		sgbSyncCountdown = 4;
108 		multi_msg_countdown();
109 		*pfSendAsync = TRUE;
110 		last_tick += gnTickDelay;
111 		return TRUE;
112 	}
113 #endif
114 }
115 
nthread_handler(void * data)116 static unsigned int nthread_handler(void *data)
117 {
118 	int delta;
119 	BOOL received;
120 
121 	if (nthread_should_run) {
122 		while (1) {
123 			sgMemCrit.Enter();
124 			if (!nthread_should_run)
125 				break;
126 			nthread_send_and_recv_turn(0, 0);
127 			if (nthread_recv_turns(&received))
128 				delta = last_tick - SDL_GetTicks();
129 			else
130 				delta = gnTickDelay;
131 			sgMemCrit.Leave();
132 			if (delta > 0)
133 				SDL_Delay(delta);
134 			if (!nthread_should_run)
135 				return 0;
136 		}
137 		sgMemCrit.Leave();
138 	}
139 	return 0;
140 }
141 
nthread_set_turn_upper_bit()142 void nthread_set_turn_upper_bit()
143 {
144 	turn_upper_bit = 0x80000000;
145 }
146 
nthread_start(BOOL set_turn_upper_bit)147 void nthread_start(BOOL set_turn_upper_bit)
148 {
149 	const char *err, *err2;
150 	DWORD largestMsgSize;
151 	_SNETCAPS caps;
152 
153 	last_tick = SDL_GetTicks();
154 	sgbPacketCountdown = 1;
155 	sgbSyncCountdown = 1;
156 	sgbTicsOutOfSync = TRUE;
157 	if (set_turn_upper_bit)
158 		nthread_set_turn_upper_bit();
159 	else
160 		turn_upper_bit = 0;
161 	caps.size = 36;
162 	if (!SNetGetProviderCaps(&caps)) {
163 		err = TraceLastError();
164 		app_fatal("SNetGetProviderCaps:\n%s", err);
165 	}
166 	gdwTurnsInTransit = caps.defaultturnsintransit;
167 	if (!caps.defaultturnsintransit)
168 		gdwTurnsInTransit = 1;
169 	if (caps.defaultturnssec <= 20 && caps.defaultturnssec)
170 		sgbNetUpdateRate = 20 / caps.defaultturnssec;
171 	else
172 		sgbNetUpdateRate = 1;
173 	largestMsgSize = 512;
174 	if (caps.maxmessagesize < 0x200)
175 		largestMsgSize = caps.maxmessagesize;
176 	gdwDeltaBytesSec = caps.bytessec >> 2;
177 	gdwLargestMsgSize = largestMsgSize;
178 	gdwNormalMsgSize = caps.bytessec * sgbNetUpdateRate / 20;
179 	gdwNormalMsgSize *= 3;
180 	gdwNormalMsgSize >>= 2;
181 	if (caps.maxplayers > MAX_PLRS)
182 		caps.maxplayers = MAX_PLRS;
183 	gdwNormalMsgSize /= caps.maxplayers;
184 	while (gdwNormalMsgSize < 0x80) {
185 		gdwNormalMsgSize *= 2;
186 		sgbNetUpdateRate *= 2;
187 	}
188 	if (gdwNormalMsgSize > largestMsgSize)
189 		gdwNormalMsgSize = largestMsgSize;
190 	if (gbIsMultiplayer) {
191 		sgbThreadIsRunning = FALSE;
192 		sgMemCrit.Enter();
193 		nthread_should_run = TRUE;
194 		sghThread = CreateThread(nthread_handler, &glpNThreadId);
195 		if (sghThread == NULL) {
196 			err2 = TraceLastError();
197 			app_fatal("nthread2:\n%s", err2);
198 		}
199 	}
200 }
201 
nthread_cleanup()202 void nthread_cleanup()
203 {
204 	nthread_should_run = FALSE;
205 	gdwTurnsInTransit = 0;
206 	gdwNormalMsgSize = 0;
207 	gdwLargestMsgSize = 0;
208 	if (sghThread != NULL && glpNThreadId != SDL_GetThreadID(NULL)) {
209 		if (!sgbThreadIsRunning)
210 			sgMemCrit.Leave();
211 		SDL_WaitThread(sghThread, NULL);
212 		sghThread = NULL;
213 	}
214 }
215 
nthread_ignore_mutex(BOOL bStart)216 void nthread_ignore_mutex(BOOL bStart)
217 {
218 	if (sghThread != NULL) {
219 		if (bStart)
220 			sgMemCrit.Leave();
221 		else
222 			sgMemCrit.Enter();
223 		sgbThreadIsRunning = bStart;
224 	}
225 }
226 
227 /**
228  * @brief Checks if it's time for the logic to advance
229  * @return True if the engine should tick
230  */
nthread_has_500ms_passed()231 BOOL nthread_has_500ms_passed()
232 {
233 	DWORD currentTickCount;
234 	int ticksElapsed;
235 
236 	currentTickCount = SDL_GetTicks();
237 	ticksElapsed = currentTickCount - last_tick;
238 	if (!gbIsMultiplayer && ticksElapsed > gnTickDelay * 10) {
239 		last_tick = currentTickCount;
240 		ticksElapsed = 0;
241 	}
242 	return ticksElapsed >= 0;
243 }
244 
245 DEVILUTION_END_NAMESPACE
246