OSDN Git Service

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