OSDN Git Service

Merge pull request #2163 from Hourier/Remove-Unnecessary-Braces
[hengbandforosx/hengbandosx.git] / src / io / record-play-movie.cpp
1 /*!
2  * @brief 録画・再生機能
3  * @date 2014/01/02
4  * @author 2014 Deskull rearranged comment for Doxygen.
5  */
6
7 #include "io/record-play-movie.h"
8 #include "cmd-io/cmd-dump.h"
9 #include "cmd-visual/cmd-draw.h"
10 #include "core/asking-player.h"
11 #include "io/files-util.h"
12 #include "io/inet.h"
13 #include "io/signal-handlers.h"
14 #include "locale/japanese.h"
15 #include "system/player-type-definition.h"
16 #include "term/gameterm.h"
17 #include "util/angband-files.h"
18 #include "view/display-messages.h"
19 #include <algorithm>
20
21 #ifdef WINDOWS
22 #include <windows.h>
23 #define WAIT 100
24 #else
25 #include "system/h-basic.h"
26 #ifdef HAVE_SYS_TIME_H
27 #include <sys/time.h>
28 #endif
29 #define WAIT 100 * 1000 /* ブラウズ側のウエイト(us単位) */
30 #endif
31
32 #define RINGBUF_SIZE 1024 * 1024
33 #define FRESH_QUEUE_SIZE 4096
34 #define DEFAULT_DELAY 50
35 #define RECVBUF_SIZE 1024
36
37 static long epoch_time; /* バッファ開始時刻 */
38 static int browse_delay; /* 表示するまでの時間(100ms単位)(この間にラグを吸収する) */
39 static int movie_fd;
40 static int movie_mode;
41
42 /* 描画する時刻を覚えておくキュー構造体 */
43 static struct {
44     int time[FRESH_QUEUE_SIZE];
45     int next;
46     int tail;
47 } fresh_queue;
48
49 /* リングバッファ構造体 */
50 static struct {
51     char *buf;
52     int wptr;
53     int rptr;
54     int inlen;
55 } ring;
56
57 /*
58  * Original hooks
59  */
60 static errr (*old_xtra_hook)(int n, int v);
61 static errr (*old_curs_hook)(int x, int y);
62 static errr (*old_bigcurs_hook)(int x, int y);
63 static errr (*old_wipe_hook)(int x, int y, int n);
64 static errr (*old_text_hook)(int x, int y, int n, TERM_COLOR a, concptr s);
65
66 static void disable_chuukei_server(void)
67 {
68     term_type *t = angband_term[0];
69     t->xtra_hook = old_xtra_hook;
70     t->curs_hook = old_curs_hook;
71     t->bigcurs_hook = old_bigcurs_hook;
72     t->wipe_hook = old_wipe_hook;
73     t->text_hook = old_text_hook;
74 }
75
76 /* ANSI Cによればstatic変数は0で初期化されるが一応初期化する */
77 static errr init_buffer(void)
78 {
79     fresh_queue.next = fresh_queue.tail = 0;
80     ring.wptr = ring.rptr = ring.inlen = 0;
81     fresh_queue.time[0] = 0;
82     ring.buf = static_cast<char *>(malloc(RINGBUF_SIZE));
83     if (ring.buf == nullptr)
84         return -1;
85
86     return 0;
87 }
88
89 /* 現在の時間を100ms単位で取得する */
90 static long get_current_time(void)
91 {
92 #ifdef WINDOWS
93     return timeGetTime() / 100;
94 #else
95     struct timeval tv;
96     gettimeofday(&tv, nullptr);
97
98     return tv.tv_sec * 10 + tv.tv_usec / 100000;
99 #endif
100 }
101
102 /* リングバッファ構造体に buf の内容を加える */
103 static errr insert_ringbuf(char *buf)
104 {
105     int len;
106     len = strlen(buf) + 1; /* +1は終端文字分 */
107
108     if (movie_mode) {
109         fd_write(movie_fd, buf, len);
110         return 0;
111     }
112
113     /* バッファをオーバー */
114     if (ring.inlen + len >= RINGBUF_SIZE)
115         return -1;
116
117     /* バッファの終端までに収まる */
118     if (ring.wptr + len < RINGBUF_SIZE) {
119         memcpy(ring.buf + ring.wptr, buf, len);
120         ring.wptr += len;
121     }
122     /* バッファの終端までに収まらない(ピッタリ収まる場合も含む) */
123     else {
124         int head = RINGBUF_SIZE - ring.wptr; /* 前半 */
125         int tail = len - head; /* 後半 */
126
127         memcpy(ring.buf + ring.wptr, buf, head);
128         memcpy(ring.buf, buf + head, tail);
129         ring.wptr = tail;
130     }
131
132     ring.inlen += len;
133
134     /* Success */
135     return 0;
136 }
137
138 /* strが同じ文字の繰り返しかどうか調べる */
139 static bool string_is_repeat(char *str, int len)
140 {
141     char c = str[0];
142     int i;
143
144     if (len < 2)
145         return false;
146 #ifdef JP
147     if (iskanji(c))
148         return false;
149 #endif
150
151     for (i = 1; i < len; i++) {
152 #ifdef JP
153         if (c != str[i] || iskanji(str[i]))
154             return false;
155 #else
156         if (c != str[i])
157             return false;
158 #endif
159     }
160
161     return true;
162 }
163
164 static errr send_text_to_chuukei_server(TERM_LEN x, TERM_LEN y, int len, TERM_COLOR col, concptr str)
165 {
166     char buf[1024 + 32];
167     char buf2[1024];
168
169     strncpy(buf2, str, len);
170     buf2[len] = '\0';
171
172     if (len == 1) {
173         sprintf(buf, "s%c%c%c%c", x + 1, y + 1, col, buf2[0]);
174     } else if (string_is_repeat(buf2, len)) {
175         int i;
176         for (i = len; i > 0; i -= 127) {
177             sprintf(buf, "n%c%c%c%c%c", x + 1, y + 1, std::min<int>(i, 127), col, buf2[0]);
178         }
179     } else {
180 #if defined(SJIS) && defined(JP)
181         sjis2euc(buf2);
182 #endif
183         sprintf(buf, "t%c%c%c%c%s", x + 1, y + 1, len, col, buf2);
184     }
185
186     insert_ringbuf(buf);
187
188     return (*old_text_hook)(x, y, len, col, str);
189 }
190
191 static errr send_wipe_to_chuukei_server(int x, int y, int len)
192 {
193     char buf[1024];
194
195     sprintf(buf, "w%c%c%c", x + 1, y + 1, len);
196
197     insert_ringbuf(buf);
198
199     return (*old_wipe_hook)(x, y, len);
200 }
201
202 static errr send_xtra_to_chuukei_server(int n, int v)
203 {
204     char buf[1024];
205
206     if (n == TERM_XTRA_CLEAR || n == TERM_XTRA_FRESH || n == TERM_XTRA_SHAPE) {
207         sprintf(buf, "x%c", n + 1);
208
209         insert_ringbuf(buf);
210
211         if (n == TERM_XTRA_FRESH) {
212             sprintf(buf, "d%ld", get_current_time() - epoch_time);
213             insert_ringbuf(buf);
214         }
215     }
216
217     /* Verify the hook */
218     if (!old_xtra_hook)
219         return -1;
220
221     return (*old_xtra_hook)(n, v);
222 }
223
224 static errr send_curs_to_chuukei_server(int x, int y)
225 {
226     char buf[1024];
227
228     sprintf(buf, "c%c%c", x + 1, y + 1);
229
230     insert_ringbuf(buf);
231
232     return (*old_curs_hook)(x, y);
233 }
234
235 static errr send_bigcurs_to_chuukei_server(int x, int y)
236 {
237     char buf[1024];
238
239     sprintf(buf, "C%c%c", x + 1, y + 1);
240
241     insert_ringbuf(buf);
242
243     return (*old_bigcurs_hook)(x, y);
244 }
245
246 /*
247  * Prepare z-term hooks to call send_*_to_chuukei_server()'s
248  */
249 void prepare_chuukei_hooks(void)
250 {
251     term_type *t0 = angband_term[0];
252
253     /* Save original z-term hooks */
254     old_xtra_hook = t0->xtra_hook;
255     old_curs_hook = t0->curs_hook;
256     old_bigcurs_hook = t0->bigcurs_hook;
257     old_wipe_hook = t0->wipe_hook;
258     old_text_hook = t0->text_hook;
259
260     /* Prepare z-term hooks */
261     t0->xtra_hook = send_xtra_to_chuukei_server;
262     t0->curs_hook = send_curs_to_chuukei_server;
263     t0->bigcurs_hook = send_bigcurs_to_chuukei_server;
264     t0->wipe_hook = send_wipe_to_chuukei_server;
265     t0->text_hook = send_text_to_chuukei_server;
266 }
267
268 /*
269  * Prepare z-term hooks to call send_*_to_chuukei_server()'s
270  */
271 void prepare_movie_hooks(PlayerType *player_ptr)
272 {
273     char buf[1024];
274     char tmp[80];
275
276     if (movie_mode) {
277         movie_mode = 0;
278         disable_chuukei_server();
279         fd_close(movie_fd);
280         msg_print(_("録画を終了しました。", "Stopped recording."));
281     } else {
282         sprintf(tmp, "%s.amv", player_ptr->base_name);
283         if (get_string(_("ムービー記録ファイル: ", "Movie file name: "), tmp, 80)) {
284             int fd;
285
286             path_build(buf, sizeof(buf), ANGBAND_DIR_USER, tmp);
287
288             fd = fd_open(buf, O_RDONLY);
289
290             /* Existing file */
291             if (fd >= 0) {
292                 char out_val[sizeof(buf) + 128];
293                 (void)fd_close(fd);
294
295                 /* Build query */
296                 (void)sprintf(out_val, _("現存するファイルに上書きしますか? (%s)", "Replace existing file %s? "), buf);
297
298                 /* Ask */
299                 if (!get_check(out_val))
300                     return;
301
302                 movie_fd = fd_open(buf, O_WRONLY | O_TRUNC);
303             } else {
304                 movie_fd = fd_make(buf, 0644);
305             }
306
307             if (!movie_fd) {
308                 msg_print(_("ファイルを開けません!", "Can not open file."));
309                 return;
310             }
311
312             movie_mode = 1;
313             prepare_chuukei_hooks();
314             do_cmd_redraw(player_ptr);
315         }
316     }
317 }
318
319 static int handle_movie_timestamp_data(int timestamp)
320 {
321     static int initialized = false;
322
323     /* 描画キューは空かどうか? */
324     if (!initialized) {
325         /* バッファリングし始めの時間を保存しておく */
326         epoch_time = get_current_time();
327         epoch_time += browse_delay;
328         epoch_time -= timestamp;
329         // time_diff = current_time - timestamp;
330         initialized = true;
331     }
332
333     /* 描画キューに保存し、保存位置を進める */
334     fresh_queue.time[fresh_queue.tail] = timestamp;
335     fresh_queue.tail++;
336
337     /* キューの最後尾に到達したら先頭に戻す */
338     fresh_queue.tail %= FRESH_QUEUE_SIZE;
339
340     /* Success */
341     return 0;
342 }
343
344 static int read_movie_file(void)
345 {
346     static char recv_buf[RECVBUF_SIZE];
347     static int remain_bytes = 0;
348     int recv_bytes;
349     int i;
350
351     recv_bytes = read(movie_fd, recv_buf + remain_bytes, RECVBUF_SIZE - remain_bytes);
352
353     if (recv_bytes <= 0)
354         return -1;
355
356     /* 前回残ったデータ量に今回読んだデータ量を追加 */
357     remain_bytes += recv_bytes;
358
359     for (i = 0; i < remain_bytes; i++) {
360         /* データのくぎり('\0')を探す */
361         if (recv_buf[i] == '\0') {
362             /* 'd'で始まるデータ(タイムスタンプ)の場合は
363                描画キューに保存する処理を呼ぶ */
364             if ((recv_buf[0] == 'd') && (handle_movie_timestamp_data(atoi(recv_buf + 1)) < 0))
365                 return -1;
366
367             /* 受信データを保存 */
368             if (insert_ringbuf(recv_buf) < 0)
369                 return -1;
370
371             /* 次のデータ移行をrecv_bufの先頭に移動 */
372             memmove(recv_buf, recv_buf + i + 1, remain_bytes - i - 1);
373
374             remain_bytes -= (i + 1);
375             i = 0;
376         }
377     }
378
379     return 0;
380 }
381
382 #ifndef WINDOWS
383 /* Win版の床の中点と壁の豆腐をピリオドとシャープにする。*/
384 static void win2unix(int col, char *buf)
385 {
386     char wall;
387     if (col == 9)
388         wall = '%';
389     else
390         wall = '#';
391
392     while (*buf) {
393 #ifdef JP
394         if (iskanji(*buf)) {
395             buf += 2;
396             continue;
397         }
398 #endif
399         if (*buf == 127)
400             *buf = wall;
401         else if (*buf == 31)
402             *buf = '.';
403         buf++;
404     }
405 }
406 #endif
407
408 static bool get_nextbuf(char *buf)
409 {
410     char *ptr = buf;
411
412     while (true) {
413         *ptr = ring.buf[ring.rptr++];
414         ring.inlen--;
415         if (ring.rptr == RINGBUF_SIZE)
416             ring.rptr = 0;
417         if (*ptr++ == '\0')
418             break;
419     }
420
421     if (buf[0] == 'd')
422         return false;
423
424     return true;
425 }
426
427 /* プレイホストのマップが大きいときクライアントのマップもリサイズする */
428 static void update_term_size(int x, int y, int len)
429 {
430     int ox, oy;
431     int nx, ny;
432     term_get_size(&ox, &oy);
433     nx = ox;
434     ny = oy;
435
436     /* 横方向のチェック */
437     if (x + len > ox)
438         nx = x + len;
439     /* 縦方向のチェック */
440     if (y + 1 > oy)
441         ny = y + 1;
442
443     if (nx != ox || ny != oy)
444         term_resize(nx, ny);
445 }
446
447 static bool flush_ringbuf_client(void)
448 {
449     char buf[1024];
450
451     /* 書くデータなし */
452     if (fresh_queue.next == fresh_queue.tail)
453         return false;
454
455     /* まだ書くべき時でない */
456     if (fresh_queue.time[fresh_queue.next] > get_current_time() - epoch_time)
457         return false;
458
459     /* 時間情報(区切り)が得られるまで書く */
460     while (get_nextbuf(buf)) {
461         char id;
462         int x, y, len;
463         TERM_COLOR col;
464         int i;
465         unsigned char tmp1, tmp2, tmp3, tmp4;
466         char *mesg;
467
468         sscanf(buf, "%c%c%c%c%c", &id, &tmp1, &tmp2, &tmp3, &tmp4);
469         x = tmp1 - 1;
470         y = tmp2 - 1;
471         len = tmp3;
472         col = tmp4;
473         if (id == 's') {
474             col = tmp3;
475             mesg = &buf[4];
476         } else
477             mesg = &buf[5];
478 #ifndef WINDOWS
479         win2unix(col, mesg);
480 #endif
481
482         switch (id) {
483         case 't': /* 通常 */
484 #if defined(SJIS) && defined(JP)
485             euc2sjis(mesg);
486 #endif
487             update_term_size(x, y, len);
488             (void)((*angband_term[0]->text_hook)(x, y, len, (byte)col, mesg));
489             memcpy(&Term->scr->c[y][x], mesg, len);
490             for (i = x; i < x + len; i++) {
491                 Term->scr->a[y][i] = col;
492             }
493             break;
494
495         case 'n': /* 繰り返し */
496             for (i = 1; i < len; i++) {
497                 mesg[i] = mesg[0];
498             }
499             mesg[i] = '\0';
500             update_term_size(x, y, len);
501             (void)((*angband_term[0]->text_hook)(x, y, len, (byte)col, mesg));
502             memcpy(&Term->scr->c[y][x], mesg, len);
503             for (i = x; i < x + len; i++) {
504                 Term->scr->a[y][i] = col;
505             }
506             break;
507
508         case 's': /* 一文字 */
509             update_term_size(x, y, 1);
510             (void)((*angband_term[0]->text_hook)(x, y, 1, (byte)col, mesg));
511             memcpy(&Term->scr->c[y][x], mesg, 1);
512             Term->scr->a[y][x] = col;
513             break;
514
515         case 'w':
516             update_term_size(x, y, len);
517             (void)((*angband_term[0]->wipe_hook)(x, y, len));
518             break;
519
520         case 'x':
521             if (x == TERM_XTRA_CLEAR)
522                 term_clear();
523             (void)((*angband_term[0]->xtra_hook)(x, 0));
524             break;
525
526         case 'c':
527             update_term_size(x, y, 1);
528             (void)((*angband_term[0]->curs_hook)(x, y));
529             break;
530         case 'C':
531             update_term_size(x, y, 1);
532             (void)((*angband_term[0]->bigcurs_hook)(x, y));
533             break;
534         }
535     }
536
537     fresh_queue.next++;
538     if (fresh_queue.next == FRESH_QUEUE_SIZE)
539         fresh_queue.next = 0;
540     return true;
541 }
542
543 void prepare_browse_movie_without_path_build(concptr filename)
544 {
545     movie_fd = fd_open(filename, O_RDONLY);
546     init_buffer();
547 }
548
549 void browse_movie(void)
550 {
551     term_clear();
552     term_fresh();
553     term_xtra(TERM_XTRA_REACT, 0);
554
555     while (read_movie_file() == 0) {
556         while (fresh_queue.next != fresh_queue.tail) {
557             if (!flush_ringbuf_client()) {
558                 term_xtra(TERM_XTRA_FLUSH, 0);
559
560                 /* ソケットにデータが来ているかどうか調べる */
561 #ifdef WINDOWS
562                 Sleep(WAIT);
563 #else
564                 usleep(WAIT);
565 #endif
566             }
567         }
568     }
569 }
570
571 #ifndef WINDOWS
572 void prepare_browse_movie_with_path_build(concptr filename)
573 {
574     char buf[1024];
575     path_build(buf, sizeof(buf), ANGBAND_DIR_USER, filename);
576     movie_fd = fd_open(buf, O_RDONLY);
577     init_buffer();
578 }
579 #endif