Develop and Download Open Source Software

Browse Subversion Repository

Contents of /rangesCtrl/ScipHandler.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 383 - (show annotations) (download) (as text)
Tue Dec 11 07:55:09 2012 UTC (11 years, 5 months ago) by satofumi
File MIME type: text/x-c++src
File size: 19157 byte(s)
fix compile error
1 /*!
2 \file
3 \brief SCIP ハンドラ
4
5 \author Satofumi KAMIMURA
6
7 $Id$
8
9 \todo pImpl 内部に isConnected() を定義して利用する
10 */
11
12 #include "ScipHandler.h"
13 #include "ConnectionInterface.h"
14 #include "SensorParameter.h"
15 #include "MathUtils.h"
16 #include "GetTicks.h"
17 #include "Delay.h"
18 #include <cstring>
19 #include <cstdlib>
20 #include <cstdio>
21
22 //#include "ConnectionLogger.h"
23
24 using namespace beego;
25
26
27 /*!
28 \brief ScipHandler の内部クラス
29 */
30 struct ScipHandler::pImpl {
31 enum {
32 TotalTimeout = 1000,
33 Timeout = 800,
34
35 //LineLength = 64 + 1 + 1 + 1,
36 // !!! いずれ、以下を削除して、上の実装に戻すべき
37 LineLength = 16 + 64 + 1 + 1 + 1, // 64 以上のデータが来る問題に対処
38
39 RecvIncomplete = -1,
40 PacketInvalid = -2,
41 };
42
43 std::string error_message;
44 ConnectionInterface* con;
45 SensorParameter params;
46 int first, last;
47 int data_byte;
48 int groups;
49 int skipFrames;
50 int times;
51 std::vector<long> recv_data;
52 int remain_byte;
53 char remain_data[3];
54 int max_dataLength;
55 bool laser_on;
56 bool possible_remain; //!< データの受信残りがある可能性があれば真
57
58 pImpl(void)
59 : error_message("no error."), con(NULL),
60 first(0), last(0), data_byte(2), groups(1), skipFrames(0), times(1),
61 remain_byte(0), max_dataLength(0), laser_on(false),
62 possible_remain(true) {
63 }
64
65 static bool isLF(const char ch) {
66 return ((ch == '\r') || (ch == '\n')) ? true : false;
67 }
68
69 long decode(const char* data, int data_byte) {
70 long value = 0;
71 for (int i = 0; i < data_byte; ++i) {
72 value <<= 6;
73 value &= ~0x3f;
74 value |= data[i] - 0x30;
75 }
76 return value;
77 }
78
79 int readLine(char *buffer) {
80 if (con == NULL) {
81 return 0;
82 }
83
84 int i;
85 for (i = 0; i < LineLength -1; ++i) {
86 char recv_ch;
87 int n = con->recv(&recv_ch, 1, pImpl::Timeout);
88 if (n <= 0) {
89 if (i == 0) {
90 return -1; // タイムアウト
91 }
92 break;
93 }
94 if (isLF(recv_ch)) {
95 break;
96 }
97 buffer[i] = recv_ch;
98 }
99
100 buffer[i] = '\0';
101 return i;
102 }
103
104 int sendTag(const char* tag) {
105 if (! con) {
106 return false;
107 }
108
109 char send_message[pImpl::LineLength];
110 sprintf(send_message, "%s\n", tag);
111 //fprintf(stderr, "send: %s, %d\n", tag, GetTicks());
112 int send_size = strlen(send_message);
113 int n = con->send(send_message, send_size);
114 if (n < send_size) {
115 // !!! error_message
116 return -1;
117 }
118 return n;
119 }
120
121 int sendMessage(const char* tag, int timeout, int* recv_n = NULL) {
122 if (recv_n) {
123 *recv_n = -1;
124 }
125 if (! con) {
126 return -1;
127 }
128
129 // 応答のみを返す
130 int send_size = sendTag(tag);
131 int recv_size = send_size + 2 + 1 + 2;
132 char buffer[LineLength];
133
134 int n = con->recv(buffer, recv_size, timeout);
135 #if 0
136 if (! strcmp("QT", tag)) {
137 //fprintf(stderr, "QT: n = %d\n[[[", n);
138 for (int i = 0; i < n; ++i) {
139 if (isprint(buffer[i])) {
140 fprintf(stderr, "%c", buffer[i]);
141 } else {
142 fprintf(stderr, "[%02x]", (unsigned char)buffer[i]);
143 }
144 }
145 fprintf(stderr, "]]]\n");
146 }
147 #endif
148 if (recv_n) {
149 *recv_n = n;
150 }
151 #if 0
152 fprintf(stderr, "n = %d\n", n);
153 for (int i = 0; i < n; ++i) {
154 if (isprint(buffer[i])) {
155 fprintf(stderr, "%c", buffer[i]);
156 } else {
157 fprintf(stderr, "[%02x]", (unsigned char)buffer[i]);
158 }
159 }
160 fprintf(stderr, "\n");
161 #endif
162 if (n < recv_size) {
163 // !!! error_message
164 error_message = "XX 1";
165 return RecvIncomplete;
166 }
167
168 // tag がマッチするか確認
169 if (strncmp(buffer, tag, send_size -1)) {
170 // !!! error_message
171 error_message = "XX 1.5";
172 return PacketInvalid;
173 }
174
175 int data_length = recv_size - send_size;
176 if (! checkSum(&buffer[send_size],
177 data_length - 3, buffer[send_size + data_length - 3])) {
178 // !!! error_message
179 error_message = "XX 2";
180 return PacketInvalid;
181 }
182
183 // 応答の取得
184 char reply_str[3] = "00";
185 reply_str[0] = buffer[send_size];
186 reply_str[1] = buffer[send_size + 1];
187 int reply = strtol(reply_str, NULL, 16);
188
189 return reply;
190 }
191
192 bool checkSum(const char* buffer, size_t size, char expected) {
193
194 char sum = 0;
195 for (size_t i = 0; i < size; ++i) {
196 sum += buffer[i];
197 }
198 sum = (sum & 0x3f) + 0x30;
199
200 return (sum == expected) ? true : false;
201 }
202
203 int addRecvData(const char buffer[]) {
204
205 const char* pre_p = buffer;
206 const char* p = pre_p;
207
208 if (remain_byte > 0) {
209 memmove(&remain_data[remain_byte], buffer, data_byte - remain_byte);
210 recv_data.push_back(decode(remain_data, data_byte));
211 pre_p = &buffer[data_byte - remain_byte];
212 p = pre_p;
213
214 remain_byte = 0;
215 }
216
217 do {
218 ++p;
219 if ((p - pre_p) >= static_cast<int>(data_byte)) {
220
221 // !!! ここで、間引き数だけデータを増やすべき
222 // !!! 未実装
223 for (int i = 0; i < 1; ++i) {
224 recv_data.push_back(decode(pre_p, data_byte));
225 }
226 pre_p = p;
227 }
228 } while (*p != '\0');
229 remain_byte = p - pre_p;
230 memmove(remain_data, pre_p, remain_byte);
231
232 return 0;
233 }
234
235 void updateMaxDataLength(void) {
236 max_dataLength = params.area_max +1;
237 }
238
239 int stopCapture(void) {
240
241 // 停止させる必要がなければ、戻る
242 if (! possible_remain) {
243 return 0;
244 }
245
246 return forceStopCapture();
247 }
248
249 int forceStopCapture(bool no_wait_reply = false) {
250 laser_on = false;
251
252 if (no_wait_reply) {
253 sendTag("QT");
254 return 0;
255 }
256
257 int ret = sendMessage("QT", Timeout);
258 if (ret > 0) {
259 possible_remain = false;
260 }
261 return ret;
262 }
263 };
264
265
266 ScipHandler::ScipHandler(void) : pimpl(new pImpl) {
267 }
268
269
270 ScipHandler::~ScipHandler(void) {
271 }
272
273
274 const char* ScipHandler::what(void) {
275 return pimpl->error_message.c_str();
276 }
277
278
279 void ScipHandler::setConnection(ConnectionInterface* con) {
280 pimpl->con = con;
281 //pimpl->con = new ConnectionLogger(con, true);
282 }
283
284
285 ConnectionInterface* ScipHandler::getConnection(void) {
286 return pimpl->con;
287 }
288
289
290 void ScipHandler::disconnect(void) {
291 if (pimpl->con == NULL) {
292 return;
293 }
294
295 // 受信の停止
296 stopCapture();
297 pimpl->con->disconnect();
298
299 // 変数の初期化
300 // !!! 関係なかった
301 pimpl->remain_byte = 0;
302 }
303
304
305 bool ScipHandler::isConnected(void) const {
306
307 return ((pimpl->con != NULL) && pimpl->con->isConnected()) ? true : false;
308 }
309
310
311 bool ScipHandler::adjustBaudrate(long baudrate) {
312
313 // "SCIP2.0" を用いたボーレート合わせ
314 long try_baudrate[] = { 19200, 115200, 57600, };
315 for (size_t i = 0; i < sizeof(try_baudrate)/sizeof(try_baudrate[0]); ++i) {
316 bool ret = pimpl->con->changeBaudrate(try_baudrate[i]);
317 if (ret == false) {
318 pimpl->error_message = pimpl->con->what();
319 return false;
320 }
321
322 // 既存の取得コマンドの停止
323 int reply = stopCapture();
324 //fprintf(stderr, "reply: %d\n", reply);
325
326 // SCIP2.0 コマンドを送信し、何かの応答('00' or '0e') で通信できたと見なす
327 int recv_n = 0;
328 if (reply != 0x00) {
329 reply = pimpl->sendMessage("SCIP2.0", pImpl::Timeout, &recv_n);
330 if ((recv_n < 0) && (reply < 0)) {
331 // タイムアウト時は、ボーレートが違うと考えられるので再テストする
332 continue;
333 }
334 if (reply == 0x0e) {
335 // TMx モードの可能性もあるので、一応 TM2 を送信する
336 pimpl->sendMessage("TM2", pImpl::Timeout);
337 }
338 }
339
340 enum { SCIP1_REPLY_SIZE = 11, };
341 if ((reply == 0x00) || (reply == 0x0e) || (recv_n == SCIP1_REPLY_SIZE)) {
342 size_t before_ticks = GetTicks();
343 ret = setBaudrate(baudrate);
344 if (! ret) {
345 // ボーレート変更に失敗したら、SS コマンドなし(USB 接続のみ)とみなす
346 // その場合、通信成功とみなす
347 return true;
348 }
349 int reply_msec = GetTicks() - before_ticks;
350
351 if (ret) {
352 // ホスト側のボーレートを変更する
353 pimpl->con->changeBaudrate(baudrate);
354 if ((reply == 0x00) || (recv_n == SCIP1_REPLY_SIZE)) {
355 // シリアル通信の場合、ボーレート変更後、1周分だけ待つ必要がある
356 delay(reply_msec *4/3 + 10);
357 }
358 return true;
359 }
360 }
361 }
362
363 pimpl->error_message = "Cannot adjust baudrate.";
364 return false;
365 }
366
367
368 bool ScipHandler::setBaudrate(long baudrate) {
369
370 // SS コマンドによるボーレート変更
371 char buffer[] = "SS000000";
372 sprintf(buffer, "SS%06ld", baudrate);
373 int reply = pimpl->sendMessage(buffer, pImpl::Timeout);
374
375 // Top-URG への暫定対応として、0xE でも true を返している
376 // Top-URG 用の SCIP2.0 で SS がサポートされれば、0xE の条件は削除する
377 return ((reply == 0) || (reply == 3) || (reply == 0xE)) ? true : false;
378 }
379
380
381 bool ScipHandler::getVersionInfo(std::vector<std::string>& lines) {
382
383 pimpl->sendTag("VV");
384 char buffer[pImpl::LineLength];
385 int line_length;
386 for (int i = 0; (line_length = pimpl->readLine(buffer)) > 0; ++i) {
387 //fprintf(stderr, "%s\n", buffer);
388
389 enum {
390 TagReply = 0,
391 DataReply,
392 Other,
393 };
394 if (i == TagReply) {
395 // !!!
396 // !!! false
397
398 } else if (i == DataReply) {
399 // !!!
400 // !!! false
401
402 } else if (i >= Other) {
403 buffer[line_length -2] = '\0';
404 lines.push_back(&buffer[5]);
405 }
406 }
407 return true;
408 }
409
410
411 bool ScipHandler::loadSensorParameter(SensorParameter* parameter) {
412
413 // パラメータ読み出しと反映
414 pimpl->sendTag("PP");
415 char buffer[pImpl::LineLength];
416 int line_index = 0;
417 enum {
418 TagReply = 0,
419 DataReply,
420 Other,
421 };
422 int line_length;
423 for (; (line_length = pimpl->readLine(buffer)) > 0; ++line_index) {
424
425 //fprintf(stderr, "%s\n", buffer);
426
427 // チェックサムの確認
428 // !!!
429 // !!! return false;
430
431 if (line_index == TagReply) {
432 // !!!
433 // !!! return false;
434
435 } else if (line_index == DataReply) {
436 // !!!
437 // !!! return false;
438
439 } else if (line_index == Other + SensorParameter::MODL) {
440 buffer[line_length - 2] = '\0';
441 pimpl->params.model = &buffer[5];
442
443 } else if (line_index == Other + SensorParameter::DMIN) {
444 pimpl->params.distance_min = atoi(&buffer[5]);
445
446 } else if (line_index == Other + SensorParameter::DMAX) {
447 pimpl->params.distance_max = atoi(&buffer[5]);
448 if (pimpl->params.distance_max > 4095) {
449 pimpl->data_byte = 3;
450 }
451 } else if (line_index == Other + SensorParameter::ARES) {
452 pimpl->params.area_total = atoi(&buffer[5]);
453
454 } else if (line_index == Other + SensorParameter::AMIN) {
455 pimpl->params.area_min = atoi(&buffer[5]);
456 pimpl->first = pimpl->params.area_min;
457
458 } else if (line_index == Other + SensorParameter::AMAX) {
459 pimpl->params.area_max = atoi(&buffer[5]);
460 pimpl->last = pimpl->params.area_max;
461
462 } else if (line_index == Other + SensorParameter::AFRT) {
463 pimpl->params.area_front = atoi(&buffer[5]);
464
465 } else if (line_index == Other + SensorParameter::SCAN) {
466 pimpl->params.scan_rpm = atoi(&buffer[5]);
467 }
468 }
469
470 if (line_index <= Other + SensorParameter::SCAN) {
471 return false;
472 }
473 pimpl->updateMaxDataLength();
474
475 if (parameter != NULL) {
476 // 引数へのパラメータ代入
477 *parameter = pimpl->params;
478 }
479
480 return true;
481 }
482
483
484 void ScipHandler::setSensorParameter(const SensorParameter* parameter) {
485 pimpl->params = *parameter;
486 pimpl->updateMaxDataLength();
487 }
488
489
490 void ScipHandler::setCaptureTimes(int times) {
491 pimpl->times = times;
492 }
493
494
495 void ScipHandler::sendCaptureMessage(char cmd) {
496
497 pimpl->remain_byte = 0;
498 char send_message[pImpl::LineLength];
499 if (cmd == 'M') {
500 sprintf(send_message, "M%c%04d%04d%02d%01d%02d",
501 (pimpl->data_byte == 2) ? 'S' : 'D',
502 pimpl->first, pimpl->last,
503 pimpl->groups, pimpl->skipFrames, pimpl->times);
504 pimpl->possible_remain = true;
505
506 } else {
507 sprintf(send_message, "G%c%04d%04d%02d",
508 (pimpl->data_byte == 2) ? 'S' : 'D',
509 pimpl->first, pimpl->last,
510 pimpl->groups);
511 }
512
513 // !!! 戻り値のチェックをすべき。一応
514 pimpl->sendTag(send_message);
515 }
516
517
518 int ScipHandler::recvCaptureData(long* data, size_t max_size,
519 size_t& timestamp) {
520
521 pimpl->recv_data.clear();
522
523 char message_type = 'M';
524 char buffer[pImpl::LineLength];
525 int line_length;
526 size_t local_timestamp = 0;
527 //fprintf(stderr, "%d, %d\n", pimpl->first, pimpl->last);
528 for (int i = 0; (line_length = pimpl->readLine(buffer)) >= 0; ++i) {
529
530 if (line_length < 0) {
531 return -3;
532 }
533
534 //fprintf(stderr, "%s\n", buffer);
535
536 // QT の応答をここでも監視する
537 // MD 受信処理中の QT 送信は、QT と MD のどちらが来るかはわからないため
538 if ((line_length >=3) && (! strncmp("QT", buffer, 2)) &&
539 pImpl::isLF(buffer[2])) {
540 // "00P\r\r" も読み出してしまう
541 // !!! ひどいな! 内部で読み出すこの仕組みは!
542 // !!! 受信文字列長さが数値埋め込みだし
543 int recv_n = pimpl->con->recv(buffer, 5, pImpl::Timeout);
544 if (recv_n == 5) {
545 return QT_Recv;
546 }
547 }
548
549 // チェックサムの確認
550 // !!!
551 // !!! return false;
552
553 if ((i >= 6) && (line_length == 0)) {
554
555 // データ受信の完了
556 for (size_t i = pimpl->recv_data.size(); i < max_size; ++i) {
557 // データ末尾までを 19(計測有効範囲外)で埋める
558 pimpl->recv_data.push_back(19);
559 }
560 for (size_t i = 0; i < max_size; ++i) {
561 data[i] = pimpl->recv_data[i];
562 }
563 timestamp = local_timestamp;
564 return pimpl->recv_data.size();
565
566 } else if (i == 0) {
567 // !!! ここで初期化するのは、どうだろう...。まぁ、効果あったが
568 pimpl->remain_byte = 0;
569
570 // 送信メッセージの最初の文字のみでメッセージの判定を行う
571 // !!! 文字列すべてで、判定を行うべき
572 if ((buffer[0] != 'M') && (buffer[0] != 'G')) {
573 pimpl->error_message = "YY 01";
574 //fprintf(stderr, "Packet invalid\n");
575 return pImpl::PacketInvalid;
576 }
577 message_type = buffer[0];
578
579 // 取得範囲を取り出す
580 // !!! lexical_cast を用いるべきか?
581 char first_str[] = "0000";
582 // !!! ひどいな...。せめてコピーしましょうよ
583 first_str[0] = buffer[2];
584 first_str[1] = buffer[3];
585 first_str[2] = buffer[4];
586 first_str[3] = buffer[5];
587 int first = atoi(first_str);
588
589 // !!! この処理を、first 取り出し後に行えばよい
590 // first または min までの領域を 19(計測有効範囲外)で埋める
591 //fprintf(stderr, "first: %d, %d\n", pimpl->first, first);
592 for (int i = first -1; i >= 0; --i) {
593 pimpl->recv_data.push_back(19);
594 }
595
596 } else if (! strncmp(buffer, "99b", 3)) {
597 // "99b" を検出し、以降を「タイムスタンプ」「データ」とみなす
598 i = 4;
599
600 } else if ((i == 1) && (message_type == 'G')) {
601 i = 4;
602
603 // !!! 'G' コマンド時の残り回数は、ここで取り出せる
604 // !!! 取り出して、下記を有効にすべき
605 // !!! 上記記述は、M の間違いじゃないのかな?
606 if (0) {
607 pimpl->laser_on = false;
608 pimpl->possible_remain = false;
609 }
610
611 } else if (i == 2) {
612 // 送信パケットに対する応答
613 // !!!
614 // 受信が終了したら、レーザーは停止する
615 // !!! 終了を検出して、下記を有効にすべき
616 if (0) {
617 pimpl->laser_on = false;
618 }
619
620 } else if (i == 4) {
621 // "99b" 固定
622 if (strncmp(buffer, "99b", 3)) {
623 pimpl->error_message = "YY 02";
624 return pImpl::PacketInvalid;
625 }
626
627 } else if (i == 5) {
628 // タイムスタンプ
629 local_timestamp = pimpl->decode(buffer, 4);
630
631 } else if (i >= 6) {
632 // 取得データ
633 if (line_length > (64 + 1)) {
634 // !!! Top-URG のバグで 65 byte 以上のデータがくる問題に対処
635 line_length = (64 + 1);
636 }
637 buffer[line_length -1] = '\0';
638 int ret = pimpl->addRecvData(buffer);
639 if (ret < 0) {
640 pimpl->error_message = "YY 03";
641 // !!! エラーメッセージの更新
642 return ret;
643 }
644 }
645 }
646 return -3;
647 }
648
649
650 int ScipHandler::stopCapture(void) {
651 int ret = pimpl->stopCapture();
652 if (pimpl->con) {
653 pimpl->con->skip(pImpl::TotalTimeout);
654 }
655 return ret;
656 }
657
658
659 void ScipHandler::forceStopCapture(bool no_wait_reply) {
660 pimpl->forceStopCapture(no_wait_reply);
661 }
662
663
664 void ScipHandler::setFrameSkipFrames(size_t skip_frames) {
665 if (skip_frames <= 9) {
666 pimpl->skipFrames = skip_frames;
667 }
668 }
669
670
671 void ScipHandler::setDataGroups(size_t groups) {
672 if ((groups > 0) && (groups <= 99)) {
673 pimpl->groups = groups;
674 }
675 }
676
677
678 void ScipHandler::setCaptureRange(int first_index, int last_index) {
679
680 if (first_index > last_index) {
681 std::swap(first_index, last_index);
682 }
683
684 //fprintf(stderr, "%d, %d, %d\n", first_index, last_index, pimpl->params.area_max);
685
686 if ((first_index >= 0) && (first_index <= pimpl->params.area_max)) {
687 pimpl->first = first_index;
688 } else {
689 pimpl->first = 0;
690 }
691 if (last_index <= pimpl->params.area_max) {
692 pimpl->last = last_index;
693 } else {
694 pimpl->last = pimpl->params.area_max;
695 }
696 }
697
698
699 int ScipHandler::getFrontIndex(void) const {
700 return (isConnected()) ? pimpl->params.area_front : -1;
701 }
702
703
704 long ScipHandler::getMinDistance(void) const {
705 return (isConnected()) ? pimpl->params.distance_min : -1;
706 }
707
708
709 long ScipHandler::getMaxDistance(void) const {
710 return (isConnected()) ? pimpl->params.distance_max : -1;
711 }
712
713
714 int ScipHandler::getMaxDataLength(void) const {
715
716 if (! isConnected()) {
717 // new long[0] だとエラーになるため、1を返す
718 return 1;
719 } else {
720 return pimpl->max_dataLength;
721 }
722 }
723
724
725 int ScipHandler::getScanRpm(void) {
726
727 if (! isConnected()) {
728 return -1;
729 } else {
730 return pimpl->params.scan_rpm;
731 }
732 }
733
734
735 void ScipHandler::setLaserOutput(bool on) {
736 if (pimpl->laser_on == on) {
737 // 状態が変わらない場合は、戻る
738 return;
739 }
740
741 int reply = pimpl->sendMessage("BM", pImpl::Timeout);
742
743 if (reply >= 0) {
744 pimpl->laser_on = on;
745 }
746 }
747
748
749 double ScipHandler::index2rad(const int index) {
750 if (! isConnected()) {
751 return -1.0;
752 }
753 return (index - pimpl->params.area_front)
754 * 2.0*M_PI / pimpl->params.area_total;
755 }
756
757
758 int ScipHandler::rad2index(const double radian) {
759 if (! isConnected()) {
760 return -1;
761 }
762
763 int index =
764 static_cast<int>((radian * pimpl->params.area_total) / (2.0*M_PI))
765 + pimpl->params.area_front;
766
767 if (index < 0) {
768 index = 0;
769 } else if (index > pimpl->max_dataLength) {
770 index = pimpl->max_dataLength;
771 }
772 return index;
773 }
774
775
776 size_t ScipHandler::getImmediateTimestamp(int* estimated_delay) {
777
778 // 片道の通信遅延時間を推定して estimated_delay に格納して返す
779
780 bool laser_state = pimpl->laser_on;
781 int reply = pimpl->sendMessage("TM0", pImpl::Timeout);
782 (void)reply;
783 pimpl->laser_on = false;
784
785 long last_timestamp = 0;
786 pimpl->sendTag("TM1");
787
788 // !!! 以下、いずれ作り直す。ひどすぎ...
789 if (1) {
790 char buffer[pImpl::LineLength];
791
792 #if 0
793 int line_length = pimpl->readLine(buffer);
794 line_length = pimpl->readLine(buffer);
795 line_length = pimpl->readLine(buffer);
796 #endif
797 last_timestamp = pimpl->decode(buffer, 4);
798 pimpl->readLine(buffer);
799 }
800
801 size_t last_ticks = GetTicks();
802 reply = pimpl->sendMessage("TM2", pImpl::Timeout);
803 size_t spent_ticks = GetTicks();
804
805 setLaserOutput(laser_state);
806
807 return last_timestamp + (spent_ticks - last_ticks);
808 }

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26