1 /* Pioneers - Implementation of the excellent Settlers of Catan board game.
2  *   Go buy a copy.
3  *
4  * Copyright (C) 1999 Dave Cole
5  * Copyright (C) 2003, 2006 Bas Wijnen <shevek@fmf.nl>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #include "config.h"
23 #include <ctype.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdlib.h>
28 
29 #include <glib.h>
30 
31 #include "game.h"
32 #include "log.h"
33 #include "state.h"
34 #include "network.h"
35 
36 struct StateMachine {
37 	gpointer user_data;	/* parameter for mode functions */
38 	/* FIXME RC 2004-11-13 in practice:
39 	 * it is NULL or a Player*
40 	 * the value is set by sm_new.
41 	 * Why? Can the player not be bound to a
42 	 * StateMachine otherwise? */
43 
44 	StateFunc global;	/* global state - test after current state */
45 	StateFunc unhandled;	/* global state - process unhandled states */
46 	StateFunc stack[16];	/* handle sm_push() to save context */
47 	const gchar *stack_name[16];	/* state names used for a stack dump */
48 	gint stack_ptr;		/* stack index */
49 	const gchar *current_state;	/* name of current state */
50 
51 	gchar *line;		/* line passed in from network event */
52 	size_t line_offset;	/* line prefix handling */
53 
54 	Session *ses;		/* network session feeding state machine */
55 	gint use_count;		/* # functions is in use by */
56 	gboolean is_dead;	/* is this machine waiting to be killed? */
57 
58 	gboolean use_cache;	/* cache the data that is sent */
59 	GList *cache;		/* cache for the delayed data */
60 };
61 
62 static void route_event(StateMachine * sm, gint event);
63 
sm_inc_use_count(StateMachine * sm)64 void sm_inc_use_count(StateMachine * sm)
65 {
66 	sm->use_count++;
67 }
68 
sm_dec_use_count(StateMachine * sm)69 void sm_dec_use_count(StateMachine * sm)
70 {
71 	if (!--sm->use_count && sm->is_dead)
72 		sm_free(sm);
73 }
74 
sm_current_name(StateMachine * sm)75 const gchar *sm_current_name(StateMachine * sm)
76 {
77 	return sm->current_state;
78 }
79 
sm_state_name(StateMachine * sm,const gchar * name)80 void sm_state_name(StateMachine * sm, const gchar * name)
81 {
82 	sm->current_state = name;
83 	sm->stack_name[sm->stack_ptr] = name;
84 }
85 
sm_is_connected(StateMachine * sm)86 gboolean sm_is_connected(StateMachine * sm)
87 {
88 	return sm->ses != NULL && net_connected(sm->ses);
89 }
90 
route_event(StateMachine * sm,gint event)91 static void route_event(StateMachine * sm, gint event)
92 {
93 	StateFunc curr_state;
94 	gpointer user_data;
95 
96 	if (sm->stack_ptr >= 0)
97 		curr_state = sm_current(sm);
98 	else
99 		curr_state = NULL;
100 
101 	user_data = sm->user_data;
102 	if (user_data == NULL)
103 		user_data = sm;
104 
105 	/* send death notification even when dead */
106 	if (event == SM_FREE) {
107 		/* send death notifications only to global handler */
108 		if (sm->global !=NULL)
109 			sm->global (user_data, event);
110 		return;
111 	}
112 
113 	if (sm->is_dead)
114 		return;
115 
116 	switch (event) {
117 	case SM_ENTER:
118 		if (curr_state != NULL)
119 			curr_state(user_data, event);
120 		break;
121 	case SM_INIT:
122 		if (curr_state != NULL)
123 			curr_state(user_data, event);
124 		if (!sm->is_dead && sm->global !=NULL)
125 			sm->global (user_data, event);
126 		break;
127 	case SM_RECV:
128 		sm_cancel_prefix(sm);
129 		if (curr_state != NULL && curr_state(user_data, event))
130 			break;
131 		sm_cancel_prefix(sm);
132 		if (!sm->is_dead
133 		    && sm->global !=NULL && sm->global (user_data, event))
134 			break;
135 
136 		sm_cancel_prefix(sm);
137 		if (!sm->is_dead && sm->unhandled != NULL)
138 			sm->unhandled(user_data, event);
139 		break;
140 	case SM_NET_CLOSE:
141 		sm_close(sm);
142 		/* fall through */
143 	default:
144 		if (curr_state != NULL)
145 			curr_state(user_data, event);
146 		if (!sm->is_dead && sm->global !=NULL)
147 			sm->global (user_data, event);
148 		break;
149 	}
150 }
151 
sm_cancel_prefix(StateMachine * sm)152 void sm_cancel_prefix(StateMachine * sm)
153 {
154 	sm->line_offset = 0;
155 }
156 
net_event(Session * ses,NetEvent event,const gchar * line,gpointer user_data)157 static void net_event(Session * ses, NetEvent event, const gchar * line,
158 		      gpointer user_data)
159 {
160 	StateMachine *sm = (StateMachine *) user_data;
161 
162 	g_assert(ses == sm->ses);
163 
164 	sm_inc_use_count(sm);
165 
166 	switch (event) {
167 	case NET_CONNECT:
168 		route_event(sm, SM_NET_CONNECT);
169 		break;
170 	case NET_CONNECT_FAIL:
171 		route_event(sm, SM_NET_CONNECT_FAIL);
172 		break;
173 	case NET_CLOSE:
174 		route_event(sm, SM_NET_CLOSE);
175 		break;
176 	case NET_READ:
177 		if (sm->line)
178 			g_free(sm->line);
179 		sm->line = g_strdup(line);
180 		/* Only handle data if there is a context.  Fixes bug that
181 		 * clients starting to send data immediately crash the
182 		 * server */
183 		if (sm->stack_ptr != -1)
184 			route_event(sm, SM_RECV);
185 		else {
186 			sm_dec_use_count(sm);
187 			return;
188 		}
189 		break;
190 	}
191 	route_event(sm, SM_INIT);
192 
193 	sm_dec_use_count(sm);
194 }
195 
sm_connect(StateMachine * sm,const gchar * host,const gchar * port)196 gboolean sm_connect(StateMachine * sm, const gchar * host,
197 		    const gchar * port)
198 {
199 	if (sm->ses != NULL)
200 		net_free(&(sm->ses));
201 
202 	sm->ses = net_new(net_event, sm);
203 	log_message(MSG_INFO, _("Connecting to %s, port %s\n"), host,
204 		    port);
205 	if (net_connect(sm->ses, host, port))
206 		return TRUE;
207 
208 	net_free(&(sm->ses));
209 	return FALSE;
210 }
211 
sm_set_session(StateMachine * sm,Session * ses)212 void sm_set_session(StateMachine * sm, Session * ses)
213 {
214 	sm_inc_use_count(sm);
215 	if (sm->ses != NULL)
216 		net_free(&(sm->ses));
217 	sm->ses = ses;
218 	net_set_notify_func(sm->ses, net_event, sm);
219 	sm_dec_use_count(sm);
220 };
221 
sm_recv(StateMachine * sm,const gchar * fmt,...)222 gboolean sm_recv(StateMachine * sm, const gchar * fmt, ...)
223 {
224 	va_list ap;
225 	ssize_t offset;
226 
227 	va_start(ap, fmt);
228 	offset = game_vscanf(sm->line + sm->line_offset, fmt, ap);
229 	va_end(ap);
230 
231 	return offset > 0
232 	    && sm->line[sm->line_offset + (size_t) offset] == '\0';
233 }
234 
sm_recv_prefix(StateMachine * sm,const gchar * fmt,...)235 gboolean sm_recv_prefix(StateMachine * sm, const gchar * fmt, ...)
236 {
237 	va_list ap;
238 	ssize_t offset;
239 
240 	va_start(ap, fmt);
241 	offset = game_vscanf(sm->line + sm->line_offset, fmt, ap);
242 	va_end(ap);
243 
244 	if (offset < 0)
245 		return FALSE;
246 	sm->line_offset += (size_t) offset;
247 	return TRUE;
248 }
249 
sm_write(StateMachine * sm,const gchar * str)250 void sm_write(StateMachine * sm, const gchar * str)
251 {
252 	if (sm->use_cache) {
253 		/* Protect against strange/slow connects */
254 		if (g_list_length(sm->cache) > 1000) {
255 			net_write(sm->ses, "ERR connection too slow\n");
256 			net_close(sm->ses);
257 		} else {
258 			sm->cache =
259 			    g_list_append(sm->cache, g_strdup(str));
260 		}
261 	} else
262 		net_write(sm->ses, str);
263 }
264 
sm_write_uncached(StateMachine * sm,const gchar * str)265 void sm_write_uncached(StateMachine * sm, const gchar * str)
266 {
267 	g_assert(sm->ses);
268 	g_assert(sm->use_cache);
269 
270 	net_write(sm->ses, str);
271 }
272 
sm_send(StateMachine * sm,const gchar * fmt,...)273 void sm_send(StateMachine * sm, const gchar * fmt, ...)
274 {
275 	va_list ap;
276 	gchar *buff;
277 
278 	if (!sm->ses)
279 		return;
280 
281 	va_start(ap, fmt);
282 	buff = game_vprintf(fmt, ap);
283 	va_end(ap);
284 
285 	sm_write(sm, buff);
286 	g_free(buff);
287 }
288 
sm_set_use_cache(StateMachine * sm,gboolean use_cache)289 void sm_set_use_cache(StateMachine * sm, gboolean use_cache)
290 {
291 	if (sm->use_cache == use_cache)
292 		return;
293 
294 	if (!use_cache) {
295 		/* The cache is turned off, send the delayed data */
296 		GList *list = sm->cache;
297 		while (list) {
298 			gchar *data = list->data;
299 			net_write(sm->ses, data);
300 			list = g_list_remove(list, data);
301 			g_free(data);
302 		}
303 		sm->cache = NULL;
304 	} else {
305 		/* Be sure that the cache is empty */
306 		g_assert(!sm->cache);
307 	}
308 	sm->use_cache = use_cache;
309 }
310 
sm_get_use_cache(const StateMachine * sm)311 gboolean sm_get_use_cache(const StateMachine * sm)
312 {
313 	return sm->use_cache;
314 }
315 
sm_global_set(StateMachine * sm,StateFunc state)316 void sm_global_set(StateMachine * sm, StateFunc state)
317 {
318 	sm->global = state;
319 }
320 
sm_unhandled_set(StateMachine * sm,StateFunc state)321 void sm_unhandled_set(StateMachine * sm, StateFunc state)
322 {
323 	sm->unhandled = state;
324 }
325 
push_new_state(StateMachine * sm)326 static void push_new_state(StateMachine * sm)
327 {
328 	/* check for stack overflows */
329 	if (sm->stack_ptr + 1 >= (gint) G_N_ELEMENTS(sm->stack)) {
330 		log_message(MSG_ERROR,
331 			    /* Error message */
332 			    _(""
333 			      "State stack overflow. Stack dump sent to standard error.\n"));
334 		sm_stack_dump(sm);
335 		g_error("State stack overflow");
336 	}
337 	++sm->stack_ptr;
338 	sm->stack[sm->stack_ptr] = NULL;
339 	sm->stack_name[sm->stack_ptr] = NULL;
340 }
341 
do_goto(StateMachine * sm,StateFunc new_state,gboolean enter)342 static void do_goto(StateMachine * sm, StateFunc new_state, gboolean enter)
343 {
344 	sm_inc_use_count(sm);
345 
346 	if (sm->stack_ptr < 0) {
347 		/* Wait until the application window is fully
348 		 * displayed before starting state machine.
349 		 */
350 		if (driver != NULL && driver->event_queue != NULL)
351 			driver->event_queue();
352 		push_new_state(sm);
353 	}
354 
355 	sm->stack[sm->stack_ptr] = new_state;
356 	if (enter)
357 		route_event(sm, SM_ENTER);
358 	route_event(sm, SM_INIT);
359 
360 #ifdef STACK_DEBUG
361 	debug("sm_goto  -> %d:%s", sm->stack_ptr, sm->current_state);
362 #endif
363 
364 	sm_dec_use_count(sm);
365 }
366 
sm_debug(G_GNUC_UNUSED const gchar * function,G_GNUC_UNUSED const gchar * state)367 void sm_debug(G_GNUC_UNUSED const gchar * function,
368 	      G_GNUC_UNUSED const gchar * state)
369 {
370 #ifdef STACK_DEBUG
371 	debug("Call %s with %s\n", function, state);
372 #endif
373 }
374 
sm_goto_nomacro(StateMachine * sm,StateFunc new_state)375 void sm_goto_nomacro(StateMachine * sm, StateFunc new_state)
376 {
377 	do_goto(sm, new_state, TRUE);
378 }
379 
sm_goto_noenter_nomacro(StateMachine * sm,StateFunc new_state)380 void sm_goto_noenter_nomacro(StateMachine * sm, StateFunc new_state)
381 {
382 	do_goto(sm, new_state, FALSE);
383 }
384 
do_push(StateMachine * sm,StateFunc new_state,gboolean enter)385 static void do_push(StateMachine * sm, StateFunc new_state, gboolean enter)
386 {
387 	sm_inc_use_count(sm);
388 
389 	push_new_state(sm);
390 	sm->stack[sm->stack_ptr] = new_state;
391 	if (enter)
392 		route_event(sm, SM_ENTER);
393 	route_event(sm, SM_INIT);
394 #ifdef STACK_DEBUG
395 	debug("sm_push -> %d:%s (enter=%d)", sm->stack_ptr,
396 	      sm->current_state, enter);
397 #endif
398 	sm_dec_use_count(sm);
399 }
400 
sm_push_nomacro(StateMachine * sm,StateFunc new_state)401 void sm_push_nomacro(StateMachine * sm, StateFunc new_state)
402 {
403 	do_push(sm, new_state, TRUE);
404 }
405 
sm_push_noenter_nomacro(StateMachine * sm,StateFunc new_state)406 void sm_push_noenter_nomacro(StateMachine * sm, StateFunc new_state)
407 {
408 	do_push(sm, new_state, FALSE);
409 }
410 
sm_pop(StateMachine * sm)411 void sm_pop(StateMachine * sm)
412 {
413 	sm_inc_use_count(sm);
414 
415 	g_assert(sm->stack_ptr > 0);
416 	sm->stack_ptr--;
417 	route_event(sm, SM_ENTER);
418 #ifdef STACK_DEBUG
419 	debug("sm_pop  -> %d:%s", sm->stack_ptr, sm->current_state);
420 #endif
421 	route_event(sm, SM_INIT);
422 	sm_dec_use_count(sm);
423 }
424 
sm_multipop(StateMachine * sm,gint depth)425 void sm_multipop(StateMachine * sm, gint depth)
426 {
427 	sm_inc_use_count(sm);
428 
429 	g_assert(sm->stack_ptr >= depth - 1);
430 	sm->stack_ptr -= depth;
431 	route_event(sm, SM_ENTER);
432 #ifdef STACK_DEBUG
433 	debug("sm_multipop  -> %d:%s", sm->stack_ptr, sm->current_state);
434 #endif
435 	route_event(sm, SM_INIT);
436 
437 	sm_dec_use_count(sm);
438 }
439 
sm_pop_all_and_goto(StateMachine * sm,StateFunc new_state)440 void sm_pop_all_and_goto(StateMachine * sm, StateFunc new_state)
441 {
442 	sm_inc_use_count(sm);
443 
444 	sm->stack_ptr = 0;
445 	sm->stack[sm->stack_ptr] = new_state;
446 	sm->stack_name[sm->stack_ptr] = NULL;
447 	route_event(sm, SM_ENTER);
448 	route_event(sm, SM_INIT);
449 
450 	sm_dec_use_count(sm);
451 }
452 
453 /** Return the state at offset from the top of the stack.
454  *  @param sm     The StateMachine
455  *  @param offset Offset from the top (0=top, 1=previous)
456  *  @return The StateFunc, or NULL if the stack contains
457  *          less than offset entries
458  */
sm_stack_inspect(const StateMachine * sm,guint offset)459 StateFunc sm_stack_inspect(const StateMachine * sm, guint offset)
460 {
461 	if (sm->stack_ptr >= (gint) offset)
462 		return sm->stack[(guint) sm->stack_ptr - offset];
463 	else
464 		return NULL;
465 }
466 
sm_current(StateMachine * sm)467 StateFunc sm_current(StateMachine * sm)
468 {
469 	g_assert(sm->stack_ptr >= 0);
470 
471 	return sm->stack[sm->stack_ptr];
472 }
473 
474 /* Build a new state machine instance
475  */
sm_new(gpointer user_data)476 StateMachine *sm_new(gpointer user_data)
477 {
478 	StateMachine *sm = g_malloc0(sizeof(*sm));
479 
480 	sm->user_data = user_data;
481 	sm->stack_ptr = -1;
482 
483 	return sm;
484 }
485 
486 /* Free a state machine
487  */
sm_free(StateMachine * sm)488 void sm_free(StateMachine * sm)
489 {
490 	g_free(sm->line);
491 	sm->line = NULL;
492 	if (sm->ses != NULL) {
493 		net_free(&(sm->ses));
494 		return;
495 	}
496 	if (sm->use_count > 0)
497 		sm->is_dead = TRUE;
498 	else {
499 		route_event(sm, SM_FREE);
500 		g_free(sm);
501 	}
502 }
503 
sm_close(StateMachine * sm)504 void sm_close(StateMachine * sm)
505 {
506 	net_free(&(sm->ses));
507 	if (sm->use_cache) {
508 		/* Purge the cache */
509 		GList *list = sm->cache;
510 		sm->cache = NULL;
511 		sm_set_use_cache(sm, FALSE);
512 		while (list) {
513 			gchar *data = list->data;
514 			list = g_list_remove(list, data);
515 			g_free(data);
516 		}
517 	}
518 }
519 
sm_copy_stack(StateMachine * dest,const StateMachine * src)520 void sm_copy_stack(StateMachine * dest, const StateMachine * src)
521 {
522 	memcpy(dest->stack, src->stack, sizeof(dest->stack));
523 	memcpy(dest->stack_name, src->stack_name,
524 	       sizeof(dest->stack_name));
525 	dest->stack_ptr = src->stack_ptr;
526 	dest->current_state = src->current_state;
527 }
528 
sm_stack_dump(const StateMachine * sm)529 void sm_stack_dump(const StateMachine * sm)
530 {
531 	gint sp;
532 	fprintf(stderr, "Stack dump for %p\n", (const void *) sm);
533 	for (sp = 0; sp <= sm->stack_ptr; ++sp) {
534 		fprintf(stderr, "Stack %2d: %s\n", sp, sm->stack_name[sp]);
535 	}
536 }
537