vrpn 07.35
Virtual Reality Peripheral Network
Loading...
Searching...
No Matches
vrpn_FileConnection.C
Go to the documentation of this file.
1// vrpn_FileConnection.C
2
3// {{{ includes and defines
4
6
7#ifndef _WIN32_WCE
8#include <fcntl.h> // for SEEK_SET
9#endif
10#include <limits.h> // for LONG_MAX, LONG_MIN
11#include <stdio.h> // for NULL, fprintf, stderr, etc
12#include <string.h> // for memcpy
13
14// Include vrpn_Shared.h _first_ to avoid conflicts with sys/time.h
15// and netinet/in.h and ...
16#include "vrpn_Shared.h" // for timeval, etc
17#if !(defined(_WIN32) && defined(VRPN_USE_WINSOCK_SOCKETS))
18#include <netinet/in.h> // for ntohl
19#endif
20
21// Global variable used to indicate whether File Connections should
22// pre-load all of their records into memory when opened. This is the
23// default behavior, but fails on very large files that eat up all
24// of the memory.
25// This is used to initialize the data member for each new file connection
26// so that it will do what is expected. This setting is stored per file
27// connection so that a given file connection will behave consistently.
28
30
31// Global variable used to indicate whether File Connections should
32// keep already-read messages stored in memory. If not, then we have
33// to re-load the file starting at the beginning on rewind.
34// This is used to initialize the data member for each new file connection
35// so that it will do what is expected. This setting is stored per file
36// connection so that a given file connection will behave consistently.
37
39
40// Global variable used to indicate whether File Connections should
41// play through all system messages and get to the first user message
42// when opened or reset to the beginning. This defaults to "true".
43// User code should set this
44// to "false" before calling vrpn_open_client_connection() or creating
45// a new vrpn_File_Connection object if it wants that file connection
46// to not skip the messages. The value is only checked at connection creation
47// time;
48// the connection behaves consistently once created. Leaving this true
49// can help with offsets in time that happen at the beginning of files.
50
52
53#define CHECK(x) \
54 if (x == -1) return -1
55
56#include "vrpn_Log.h" // for vrpn_Log
57
58struct timeval;
59
60// }}}
61// {{{ constructor
62
64 const char *local_in_logfile_name,
65 const char *local_out_logfile_name)
66 : vrpn_Connection(local_in_logfile_name, local_out_logfile_name, NULL, NULL)
67 , d_controllerId(register_sender("vrpn File Controller"))
68 , d_set_replay_rate_type(register_message_type("vrpn_File set_replay_rate"))
69 , d_reset_type(register_message_type("vrpn_File reset"))
70 , d_play_to_time_type(register_message_type("vrpn_File play_to_time"))
71 , d_fileName(NULL)
72 , d_file(NULL)
73 , d_logHead(NULL)
74 , d_logTail(NULL)
75 , d_currentLogEntry(NULL)
76 , d_startEntry(NULL)
79{
80 d_last_told.tv_sec = 0;
81 d_last_told.tv_usec = 0;
82
83 d_earliest_user_time.tv_sec = d_earliest_user_time.tv_usec = 0;
85 d_highest_user_time.tv_sec = d_highest_user_time.tv_usec = 0;
87
88 // Because we are a file connection, our status should be CONNECTED
89 // Later set this to BROKEN if there is a problem opening/reading the file.
90 if (!d_endpoints.is_valid(0)) {
91 fprintf(stderr, "vrpn_File_Connection::vrpn_File_Connection(): NULL "
92 "zeroeth endpoint\n");
93 } else {
96 }
97
98 // If we are preloading, then we must accumulate messages.
99 if (d_preload) {
100 d_accumulate = true;
101 }
102
103 // These are handlers for messages that may be sent from a
104 // vrpn_File_Controller object that may attach itself to us.
110
111 // necessary to initialize properly in mainloop()
112 d_last_time.tv_usec = d_last_time.tv_sec = 0;
113
114 d_fileName = vrpn_copy_file_name(station_name);
115 if (!d_fileName) {
116 fprintf(stderr, "vrpn_File_Connection: Out of memory!\n");
118 return;
119 }
120
121 d_file = fopen(d_fileName, "rb");
122 if (!d_file) {
123 fprintf(stderr, "vrpn_File_Connection: "
124 "Could not open file \"%s\".\n",
125 d_fileName);
127 return;
128 }
129
130 // Read the cookie from the file. It will print an error message if it
131 // can't read it, so we just pass the broken status on up the chain.
132 if (read_cookie() < 0) {
134 return;
135 }
136
137 // If we are supposed to preload the entire file into memory buffers,
138 // then keep reading until we get to the end. Otherwise, just read the
139 // first message to get things going.
140 if (d_preload) {
141 while (!read_entry()) {
142 }
143 }
144 else {
145 read_entry();
146 }
147
148 // Initialize the "current message" pointer to the first log-file
149 // entry that was read, and set the start time for the file and
150 // the current time to the one in this message.
151 if (d_logHead) {
156 }
157 else {
158 fprintf(stderr, "vrpn_File_Connection: Can't read first message\n");
160 return;
161 }
162
163 // This is useful to play the initial system messages
164 // (the sender/type ones) automatically. These might not be
165 // time synched so if we don't play them automatically they
166 // can mess up playback if their timestamps are later than
167 // the first user message.
170 if (d_currentLogEntry) {
173 }
174 }
175
176 // Add this to the list of known connections.
178}
179
180// }}}
181// {{{ play_to_user_message
182
183// Advances through the file, calling callbacks, up until
184// a user message (type >= 0) is encountered)
186{
187 // As long as the current message is a system message, play it.
188 // Also stop if we get to the end of the file.
189 while (d_currentLogEntry && (d_currentLogEntry->data.type < 0)) {
190 playone();
191 }
192
193 // we advance d_time one ahead because they're may be
194 // a large gap in the file (forward or backwards) between
195 // the last system message and first user one
196 if (d_currentLogEntry) {
198 }
199}
200
201// }}}
202// {{{ destructor
203
204// virtual
206{
207 vrpn_LOGLIST *np;
208
209 // Remove myself from the "known connections" list
210 // (or the "anonymous connections" list).
212
213 close_file();
214 try {
215 delete[] d_fileName;
216 } catch (...) {
217 fprintf(stderr, "vrpn_File_Connection::~vrpn_File_Connection: delete failed\n");
218 return;
219 }
220 d_fileName = NULL;
221
222 // Delete any messages that are in memory, and their data buffers.
223 while (d_logHead) {
224 np = d_logHead->next;
225 if (d_logHead->data.buffer) {
226 try {
227 delete[] d_logHead->data.buffer;
228 } catch (...) {
229 fprintf(stderr, "vrpn_File_Connection::~vrpn_File_Connection: delete failed\n");
230 return;
231 }
232 }
233 try {
234 delete d_logHead;
235 } catch (...) {
236 fprintf(stderr, "vrpn_File_Connection::~vrpn_File_Connection: delete failed\n");
237 return;
238 }
239 d_logHead = np;
240 }
241}
242
243// }}}
244// {{{ jump_to_time
245
246// newtime is an elapsed time from the start of the file
247int vrpn_File_Connection::jump_to_time(vrpn_float64 newtime)
248{
249 return jump_to_time(vrpn_MsecsTimeval(newtime * 1000));
250}
251
252// If the time is before our current time (or there is no current
253// time) then reset back to the beginning. Whether or not we did
254// that, search forwards until we get to or past the time we are
255// searching for.
256// newtime is an elapsed time from the start of the file
258{
261 }
262 else {
264 } // XXX get rid of this option - dtm
265
266 // If the time is earlier than where we are, or if we have
267 // run past the end (no current entry), jump back to
268 // the beginning of the file before searching.
269 if (!d_currentLogEntry ||
271 reset();
272 }
273
274 // Search forwards, as needed. Do not play the messages as they are
275 // passed, just skip over them until we get to a message that has a
276 // time greater than or equal to the one we are looking for. That is,
277 // one whose time is not less than ours.
279 int ret = advance_currentLogEntry();
280 if (ret != 0) {
281 return 0; // Didn't get where we were going!
282 }
283 }
284
285 return 1; // Got where we were going!
286}
287
289{
291 return jump_to_time(
292 vrpn_TimevalDiff(absolute_time, d_earliest_user_time));
293 }
294 else {
295 return jump_to_time(vrpn_TimevalDiff(absolute_time, d_start_time));
296 } // XX get rid of this option - dtm
297}
298
299// }}}
300// {{{ vrpn_File_Connection::FileTime_Accumulator
302 : d_replay_rate(1.0)
303{
304 d_filetime_accum_since_last_playback.tv_sec = 0;
305 d_filetime_accum_since_last_playback.tv_usec = 0;
306
307 d_time_of_last_accum.tv_sec = 0;
308 d_time_of_last_accum.tv_usec = 0;
309}
310
312 const timeval &now_time)
313{
314 timeval &accum = d_filetime_accum_since_last_playback;
315 timeval &last_accum = d_time_of_last_accum;
316
317 accum // updated elapsed filetime
318 = vrpn_TimevalSum( // summed with previous elapsed time
319 accum,
320 vrpn_TimevalScale( // scaled by replay rate
321 vrpn_TimevalDiff( // elapsed wallclock time
322 now_time, d_time_of_last_accum),
323 d_replay_rate));
324 last_accum = now_time; // wallclock time of this whole mess
325}
326
328 vrpn_float32 new_rate)
329{
330 timeval now_time;
331 vrpn_gettimeofday(&now_time, NULL);
332 accumulate_to(now_time);
333
334 d_replay_rate = new_rate;
335 // fprintf(stderr, "Set replay rate!\n");
336}
337
339 const timeval &now_time)
340{
341 // this function is confusing. It doesn't appear to do anything.
342 // (I added the next three lines. did I delete what was previously there?)
343 d_filetime_accum_since_last_playback.tv_sec = 0;
344 d_filetime_accum_since_last_playback.tv_usec = 0;
345 d_time_of_last_accum = now_time;
346}
347// {{{ end vrpn_File_Connection::FileTime_Accumulator
348// }}}
349
350// }}}
351// {{{ mainloop
352
353// {{{ -- comment
354
355// [juliano 10/11/99] the problem described below is now fixed
356// [juliano 8/26/99] I believe there to be a bug in mainloop.
357//
358// Essentially, the computation of end_time is sample-and-hold
359// integration, using the value of d_rate at the end of the integration
360// window. Consider the case where you are happily playing from your
361// file, then pause it. Pausing is accomplished by setting d_rate to
362// zero. Then, 10 minutes later, you unpause by setting d_rate to one.
363//
364// Now, you have an integration window of 10 minutes. The way it's
365// currently implemented, you will compute end_time as d_rate * 10
366// minutes. This is obviously not correct.
367//
368// So, what is the ideal way this would work? Well, if you had continuous
369// integration and asynchronous events, then the new message would come at
370// the same time as it would if we executed the same scenerio with a VCR.
371//
372// Since our value of d_rate changes by impulses at specified times (it
373// only changes from inside set_replay_rate) sample-and-hold integration
374// can still be used to do exact computation. What we need to do is
375// accumulate "virtial time", and compare with it. By "virtual time" I
376// mean the analog to VITC timestamps on a videotape in a VCR. We will
377// accumulate it in d_virtual_time_elapsed_since_last_event. The units
378// need to have enough precision that we won't run into any of the
379// problems that have already been worked around in the code below. It
380// represents the amount of time elapsed sice d_last_time was last set.
381//
382// set_replay_rate will do sample-and-hold integration over the window
383// from d_last_time to the result of vrpn_gettimeofday(), and it will
384// accumulate the result into d_virtual_time_elapsed_since_last_event.
385// then it will set d_last_time to the result of vrpn_gettimeofday().
386//
387// mainloop will also do sample-and-hold integration over the window
388// [d_last_time:vrpn_gettimeofday()], and accumulate the result into
389// d_virtual_time_elapsed_since_last_event. Then it will compute end_time
390// by adding d_time and d_virtual_time_elapsed_since_last_event. iff an
391// event is played from the file, d_last_time will be set to now_time and
392// d_virtual_time_elapsed_since_last_event will be zero'd.
393
394// }}}
395
396// virtual
397int vrpn_File_Connection::mainloop(const timeval * /*timeout*/)
398{
399 // XXX timeout ignored for now, needs to be added
400
401 timeval now_time;
402 vrpn_gettimeofday(&now_time, NULL);
403
404 if ((d_last_time.tv_sec == 0) && (d_last_time.tv_usec == 0)) {
405 // If first iteration, consider 0 time elapsed
406 d_last_time = now_time;
408 return 0;
409 }
410
411 // now_time: current wallclock time (on method entry)
412 // d_last_time: wallclock time of last call to mainloop
413 // (juliano-8/26/99) NO! It is the time the
414 // wallclock read (at the top of mainloop) when the
415 // last event was played back from the file.
416 // If you call mainloop frequently enough,
417 // these are not necessarily the same!
418 // (may call mainloop too soon and then no event
419 // is played back from the file)
420 // d_time: current time in file
421 // end_time: computed time in file
422 // d_rate: wallclock -> fileclock rate scale factor
423 // goal: compute end_time, then advance to it
424 //
425 // scale elapsed time by d_rate (rate of replay);
426 // this gives us the time to advance (skip_time)
427 // our clock to (next_time).
428 // -- see note above!
429 //
430 // const timeval real_elapsed_time // amount of ellapsed wallclock time
431 // = vrpn_TimevalDiff( now_time, d_last_time );
432 // const timeval skip_time // scale it by d_rate
433 // = vrpn_TimevalScale( real_elapsed_time, d_rate );
434 // const timeval end_time // add it to the last file-time
435 // = vrpn_TimevalSum( d_time, skip_time );
436 //
437 // ------ new way of calculating end_time ------------
438
440 const timeval end_time =
442
443 // (winston) Had to add need_to_play() because at fractional rates
444 // (even just 1/10th) the d_time didn't accumulate properly
445 // because tiny intervals after scaling were too small
446 // for a timeval to represent (1us minimum).
447 //
448 // (juliano-8/26/99) if ((end_time - timestamp of next event) < 1us)
449 // then you have run out of precision in the struct timeval when
450 // need_to_play differences those two timevals. I.e., they
451 // appear to be the same time.
452 // need_to_play will return n:n>1 only if this difference
453 // is non-zero.
454 //
455 // (juliano-8/25/99) need_to_play is not a boolean function!
456 // it returns n:n>0 if you need to play
457 // n=0 if the timevals compare equal
458 // n=-1 if there was an error reading the next event
459 // from the log file
460 const int need_to_play_retval = need_to_play(end_time);
461
462 if (need_to_play_retval > 0) {
463 d_last_time = now_time;
465 const int rv = play_to_filetime(end_time);
466 return rv;
467 }
468 else if (need_to_play_retval == 0) {
469 // (winston) we don't set d_last_time so that we can more
470 // accurately measure the (larger) interval next time around
471 //
472 // (juliano-8/26/99) sounds right. Only set d_last_time
473 // if you actually played some event from the file.
474 // You may get here if you have your data in more than one
475 // file, and are trying to play back from the files in lockstep.
476 // The tracker group does this to run the hybrid tracking
477 // algorithm on both an inertial data file and a hiball
478 // tracker file that were recorded with synchronized clocks.
479 return 0;
480 }
481 else {
482 // return something to indicate there was an error
483 // reading the file
484 return -1;
485
486 // an error occurred while reading the next event from the file
487 // let's close the connection.
488 // XXX(jj) is this the right thing to do?
489 // XXX(jj) for now, let's leave it how it was
490 // XXX(jj) come back to this and do it right
491 /*
492 fprintf( stderr, "vrpn_File_Connection::mainloop(): error
493 reading "
494 "next event from file. Skipping to end of file. "
495 "XXX Please edit this function and fix it. It should
496 probably"
497 " close the connection right here and now.\n");
498 d_last_time = now_time;
499 d_filetime_accum.reset_at_time( now_time );
500 return play_to_filetime(end_time);
501 */
502 }
503}
504
505// }}}
506// {{{ need_to_play
507
508// checks if there is at least one log entry that occurs
509// between the current d_time and the given filetime
510//
511// [juliano 9/24/99] the above comment is almost right
512// the upper bound of the interval is not open,
513// but closed at time_we_want_to_play_to.
514//
515// this function checks if the next message to play back
516// from the stream file has a timestamp LESSTHAN OR EQUAL TO
517// the argument to this function (which is the time that we
518// wish to play to). If it does, then a pos value is returned
519//
520// you can pause playback of a streamfile by ceasing to increment
521// the value that is passed to this function. However, if the next
522// message in the streamfile has the same timestamp as the previous
523// one, it will get played anyway. Pause will not be achieved until
524// all such messages have been played.
525//
526// Beware: make sure you put the correct timestamps on individual
527// messages when recording them in chunks (batches)
528// to a time to a streamfile.
529//
530int vrpn_File_Connection::need_to_play(timeval time_we_want_to_play_to)
531{
532 // This read_entry() call may be useful to test the state, but
533 // it should be the case that d_currentLogEntry is non-NULL except
534 // when we are at the end. This is because read_entry() and the
535 // constructor now both read the next one in when they are finished.
536 if (!d_currentLogEntry) {
537 int retval = read_entry();
538 if (retval < 0) {
539 return -1;
540 } // error reading from file
541 if (retval > 0) {
542 return 0;
543 } // end of file; nothing to replay
545 d_logTail; // If read_entry() returns 0, this will be non-NULL
546 }
547
549
550 // [juliano 9/24/99] is this right?
551 // this is a ">" test, not a ">=" test
552 // consequently, this function keeps returning true until the
553 // next message is timestamped greater. So, if a group of
554 // messages share a timestamp, you cannot pause streamfile
555 // replay anywhere inside the group.
556 //
557 // this is true, but do you ever want to pause in the middle of
558 // such a group? This was a problem prior to fixing the
559 // timeval overflow bug, but now that it's fixed, (who cares?)
560
561 return vrpn_TimevalGreater(time_we_want_to_play_to, header.msg_time);
562}
563
564// }}}
565// {{{ play_to_time and play_to_filetime
566
567// plays to an elapsed end_time (in seconds)
568int vrpn_File_Connection::play_to_time(vrpn_float64 end_time)
569{
570 return play_to_time(vrpn_MsecsTimeval(end_time * 1000));
571}
572
573// plays to an elapsed end_time
575{
577 return play_to_filetime(
579 }
580 else {
582 }
583}
584
585// plays all entries between d_time and end_filetime
586// returns -1 on error, 0 on success
587int vrpn_File_Connection::play_to_filetime(const timeval end_filetime)
588{
589 vrpn_uint32 playback_this_iteration = 0;
590
591 if (vrpn_TimevalGreater(d_time, end_filetime)) {
592 // end_filetime is BEFORE d_time (our current time in the stream)
593 // so, we need to go backwards in the stream
594 // currently, this is implemented by
595 // * rewinding the stream to the beginning
596 // * playing log messages one at a time until we get to end_filetime
597 reset();
598 }
599
600 int ret;
601 while ((ret = playone_to_filetime(end_filetime)) == 0) {
602 // * you get here ONLY IF playone_to_filetime returned 0
603 // * that means that it played one entry
604
605 playback_this_iteration++;
606 if ((get_Jane_value() > 0) &&
607 (playback_this_iteration >= get_Jane_value())) {
608 // Early exit from the loop
609 // Don't reset d_time to end_filetime
610 return 0;
611 }
612 }
613
614 if (ret == 1) {
615 // playone_to_filetime finished or EOF no error for us
616 // Set log position to the exact requested ending time,
617 // don't leave it at the last log entry time
618 d_time = end_filetime;
619 ret = 0;
620 }
621
622 return ret;
623}
624
625// }}}
626// {{{ rest
627
628// returns 1 if we're at the EOF, -1 on error
630{
631 if (d_currentLogEntry) {
632 return 0;
633 }
634 // read from disk if not in memory
635 int ret = read_entry();
636 if (ret == 0) {
638 d_logTail; // If read_entry() returns zero, this will be non-NULL
639 }
640
641 return ret;
642}
643
644// plays at most one entry which comes before end_filetime
645// returns
646// -1 on error (including EOF, call eof() to test)
647// 0 for normal result (played one entry)
649{
650 static const timeval tvMAX = {LONG_MAX, 999999L};
651
652 int ret = playone_to_filetime(tvMAX);
653 if (ret != 0) {
654 // consider a 1 return from playone_to_filetime() to be
655 // an error since it should never reach tvMAX
656 return -1;
657 }
658 else {
659 return 0;
660 }
661}
662
663// plays at most one entry which comes before end_filetime
664// returns
665// -1 on error (including EOF, call eof() to test)
666// 0 for normal result (played one entry)
667// 1 if we hit end_filetime
669{
670 vrpn_Endpoint *endpoint = d_endpoints.front();
671 timeval now;
672 int retval;
673
674 // If we don't have a currentLogEntry, then we've gone past the end of the
675 // file.
676 if (!d_currentLogEntry) {
677 return 1;
678 }
679
681
682 if (vrpn_TimevalGreater(header.msg_time, end_filetime)) {
683 // there are no entries to play after the current
684 // but before end_filetime
685 return 1;
686 }
687
688 // TCH July 2001
689 // XXX A big design decision: do we re-log messages exactly,
690 // or do we mark them with the time they were played back?
691 // Maybe this should be switchable, but the latter is what
692 // I need yesterday.
693 vrpn_gettimeofday(&now, NULL);
694 retval = endpoint->d_inLog->logIncomingMessage(
695 header.payload_len, now, header.type, header.sender, header.buffer);
696 if (retval) {
697 fprintf(stderr, "Couldn't log \"incoming\" message during replay!\n");
698 return -1;
699 }
700
701 // advance current file position
702 d_time = header.msg_time;
703
704 // Handle this log entry
705 if (header.type >= 0) {
706#ifdef VERBOSE
707 printf("vrpn_FC: Msg Sender (%s), Type (%s), at (%ld:%ld)\n",
708 endpoint->other_senders[header.sender].name,
709 endpoint->other_types[header.type].name, header.msg_time.tv_sec,
710 header.msg_time.tv_usec);
711#endif
712 if (endpoint->local_type_id(header.type) >= 0) {
713 if (do_callbacks_for(endpoint->local_type_id(header.type),
714 endpoint->local_sender_id(header.sender),
715 header.msg_time, header.payload_len,
716 header.buffer)) {
717 return -1;
718 }
719 }
720 }
721 else { // system handler
722
724 if (doSystemCallbacksFor(header, endpoint)) {
725 fprintf(stderr, "vrpn_File_Connection::playone_to_filename: "
726 "Nonzero system return.\n");
727 return -1;
728 }
729 }
730 }
731
733}
734
735// Advance to next entry. If there is no next entry, and if we have
736// not preloaded, then try to read one in.
738{
739 // If we don't have a currentLogEntry, then we've gone past the end of the
740 // file.
741 if (!d_currentLogEntry) {
742 return 1;
743 }
744
746 if (!d_currentLogEntry && !d_preload) {
747 int retval = read_entry();
748 if (retval != 0) {
749 return -1; // error reading from file or EOF
750 }
752 d_logTail; // If read_entry() returns zero, this will be non-NULL
753 }
754
755 return 0;
756}
757
759{
760 return vrpn_TimevalMsecs(get_length()) / 1000;
761}
762
763// virtual
765{
766 timeval len = {0, 0};
767
771 }
772
774 return len;
775}
776
778{
781}
782
784{
786 return d_highest_user_time;
787}
788
790{
791 timeval high = {0, 0};
792 timeval low = {LONG_MAX, 999999L};
793
794 // Remember where we were when we asked this question
795 bool retval = store_stream_bookmark();
796 if (retval == false) {
797#ifdef VERBOSE
798 printf("vrpn_File_Connection::find_superlative_user_times: didn't "
799 "successfully save bookmark.\n");
800#endif
801 return;
802 }
803
804 // Go to the beginning of the file and then run through all
805 // of the messages to find the one with the lowest/highest value
806 reset();
807 do {
811 }
814 }
815 }
816 } while (d_currentLogEntry && (advance_currentLogEntry() == 0));
817
818 // We have our value. Set it and go back where
819 // we came from, but don't play the records along
820 // the way.
821 retval = return_to_bookmark();
822 if (retval == false) {
823 // oops. we've really screwed things up.
824 fprintf(stderr, "vrpn_File_Connection::find_superlative_user_times "
825 "messed up the location in the file stream.\n");
826 reset();
827 return;
828 }
829
830 if (high.tv_sec != LONG_MIN) // we found something
831 {
832 d_highest_user_time = high;
834 }
835#ifdef VERBOSE
836 else {
837 fprintf( stderr, "vrpn_File_Connection::find_superlative_user_times: did not find a highest-time user message\n"
838 }
839#endif
840
841 if (low.tv_sec != LONG_MAX) // we found something
842 {
843 d_earliest_user_time = low;
844 d_earliest_user_time_valid = true;
845 }
846#ifdef VERBOSE
847 else {
848 fprintf( stderr, "vrpn_File_Connection::find_superlative_user_times: did not find an earliest user message\n"
849 }
850#endif
851
852} // end find_superlative_user_times
853
855{
856 valid = false;
857 file_pos = -1;
858 oldTime.tv_sec = 0;
859 oldTime.tv_usec = 0;
860 oldCurrentLogEntryPtr = NULL;
861 oldCurrentLogEntryCopy = NULL;
862}
863
865{
866 if (oldCurrentLogEntryCopy == NULL) return;
867 if (oldCurrentLogEntryCopy->data.buffer != NULL) {
868 try {
869 delete[] (oldCurrentLogEntryCopy->data.buffer);
870 } catch (...) {
871 fprintf(stderr, "vrpn_File_Connection::vrpn_FileBookmark::~vrpn_FileBookmark: delete failed\n");
872 return;
873 }
874 }
875 try {
876 delete oldCurrentLogEntryCopy;
877 } catch (...) {
878 fprintf(stderr, "vrpn_File_Connection::vrpn_FileBookmark::~vrpn_FileBookmark: delete failed\n");
879 return;
880 }
881}
882
884{
885 if (d_preload) {
886 // everything is already in memory, so just remember where we were
889 }
890 else if (d_accumulate) // but not pre-load
891 {
892 // our current location will remain in memory
894 d_bookmark.file_pos = ftell(d_file);
896 }
897 else // !preload and !accumulate
898 {
900 d_bookmark.file_pos = ftell(d_file);
901 if (d_currentLogEntry == NULL) // at the end of the file
902 {
905 try {
907 } catch (...) {
908 fprintf(stderr, "vrpn_File_Connection::store_stream_bookmark: delete failed\n");
909 return false;
910 }
911 }
912 try {
914 } catch (...) {
915 fprintf(stderr, "vrpn_File_Connection::store_stream_bookmark: delete failed\n");
916 return false;
917 }
918 }
920 }
921 else {
924 catch (...) {
925 fprintf(stderr, "Out of memory error: "
926 "vrpn_File_Connection::store_stream_"
927 "bookmark\n");
928 d_bookmark.valid = false;
929 return false;
930 }
934 }
946 try {
948 } catch (...) {
949 fprintf(stderr, "vrpn_File_Connection::store_stream_bookmark: delete failed\n");
950 return false;
951 }
952 }
955 catch (...) {
956 d_bookmark.valid = false;
957 return false;
958 }
959 memcpy(const_cast<char *>(d_bookmark.oldCurrentLogEntryCopy->data.buffer),
962 }
963 }
964 d_bookmark.valid = true;
965 return true;
966}
967
969{
970 int retval = 0;
971 if (!d_bookmark.valid) return false;
972 if (d_preload) {
975 }
976 else if (d_accumulate) // but not pre-load
977 {
980 retval |= fseek(d_file, d_bookmark.file_pos, SEEK_SET);
981 }
982 else // !preload and !accumulate
983 {
985 // we were at the end of the file.
988 retval |= fseek(d_file, d_bookmark.file_pos, SEEK_SET);
989 }
990 else {
991 char *newBuffer = NULL;
992 try { newBuffer =
994 catch (...) {
995 return false;
996 }
998 retval |= fseek(d_file, d_bookmark.file_pos, SEEK_SET);
999 if (d_currentLogEntry == NULL) // we are at the end of the file
1000 {
1001 try { d_currentLogEntry = new vrpn_LOGLIST; }
1002 catch (...) { return false; }
1004 }
1015 const char *temp = d_currentLogEntry->data.buffer;
1016 d_currentLogEntry->data.buffer = newBuffer;
1017 memcpy(const_cast<char *>(d_currentLogEntry->data.buffer),
1020 if (temp) {
1021 try {
1022 delete[] temp;
1023 } catch (...) {
1024 fprintf(stderr, "vrpn_File_Connection::return_to_bookmark: delete failed\n");
1025 return false;
1026 }
1027 }
1029 }
1030 }
1031 return (retval == 0);
1032}
1033
1035
1036// Returns the time since the connection opened.
1037// Some subclasses may redefine time.
1038// virtual
1040{
1043 }
1046 }
1047 else {
1048 *elapsed_time = vrpn_TimevalDiff(d_time, d_start_time);
1049 } // XXX get rid of this option - dtm
1050
1051 return 0;
1052}
1053
1054// virtual
1056{
1057 return this;
1058}
1059
1060// {{{ read_cookie and read_entry
1061
1062// Reads a cookie from the logfile and calls check_vrpn_cookie()
1063// (from vrpn_Connection.C) to check it.
1064// Returns -1 on no cookie or cookie mismatch (which should cause abort),
1065// 0 otherwise.
1066
1067// virtual
1069{
1070 char readbuf[128]; // XXX HACK!
1071 size_t bytes = fread(readbuf, vrpn_cookie_size(), 1, d_file);
1072 if (bytes == 0) {
1073 fprintf(stderr, "vrpn_File_Connection::read_cookie: "
1074 "No cookie. If you're sure this is a logfile, "
1075 "run add_vrpn_cookie on it and try again.\n");
1076 return -1;
1077 }
1078 readbuf[vrpn_cookie_size()] = '\0';
1079
1080 int retval = check_vrpn_file_cookie(readbuf);
1081 if (retval < 0) {
1082 return -1;
1083 }
1084
1085 // TCH July 2001
1086 if (!d_endpoints.is_valid(0)) {
1087 fprintf(stderr, "vrpn_File_Connection::read_cookie: "
1088 "No endpoints[0]. Internal failure.\n");
1089 return -1;
1090 }
1091 d_endpoints.front()->d_inLog->setCookie(readbuf);
1092
1093 return 0;
1094}
1095
1096// virtual
1098{
1099 vrpn_LOGLIST *newEntry;
1100 size_t retval;
1101
1102 try { newEntry = new vrpn_LOGLIST; }
1103 catch (...) {
1104 fprintf(stderr, "vrpn_File_Connection::read_entry: Out of memory.\n");
1105 return -1;
1106 }
1107
1108 // Only print this message every second or so
1109 if (!d_file) {
1110 struct timeval now;
1111 vrpn_gettimeofday(&now, NULL);
1112 if (now.tv_sec != d_last_told.tv_sec) {
1113 fprintf(stderr, "vrpn_File_Connection::read_entry: no open file\n");
1114 memcpy(&d_last_told, &now, sizeof(d_last_told));
1115 }
1116 try {
1117 delete newEntry;
1118 } catch (...) {
1119 fprintf(stderr, "vrpn_File_Connection::read_entry: delete failed\n");
1120 return -1;
1121 }
1122 return -1;
1123 }
1124
1125 // Get the header of the next message. This was done as a horrible
1126 // hack in the past, where we read the sizeof a struct from the file,
1127 // including a pointer. This of course changed on 64-bit architectures.
1128 // The pointer value was not needed. We now read it as an array of
1129 // 32-bit values and then stuff these into the structure. Unfortunately,
1130 // we now need to both send and read the bogus pointer value if we want
1131 // to be compatible with old versions of log files.
1132
1133 vrpn_HANDLERPARAM &header = newEntry->data;
1134 vrpn_int32 values[6];
1135 retval = fread(values, sizeof(vrpn_int32), 6, d_file);
1136
1137 // return 1 if nothing to read OR end-of-file;
1138 // the latter isn't an error state
1139 if (retval <= 0) {
1140 // Don't close the file because we might get a reset message...
1141 try {
1142 delete newEntry;
1143 } catch (...) {
1144 fprintf(stderr, "vrpn_File_Connection::read_entry: delete failed\n");
1145 return -1;
1146 }
1147 return 1;
1148 }
1149
1150 header.type = ntohl(values[0]);
1151 header.sender = ntohl(values[1]);
1152 header.msg_time.tv_sec = ntohl(values[2]);
1153 header.msg_time.tv_usec = ntohl(values[3]);
1154 header.payload_len = ntohl(values[4]);
1155 header.buffer =
1156 NULL; // values[5] is ignored -- it used to hold the bogus pointer.
1157
1158 // get the body of the next message
1159
1160 if (header.payload_len > 0) {
1161 try { header.buffer = new char[header.payload_len]; }
1162 catch (...) {
1163 fprintf(stderr, "vrpn_File_Connection::read_entry: "
1164 "Out of memory.\n");
1165 return -1;
1166 }
1167
1168 retval = fread(const_cast<char *>(header.buffer), 1, header.payload_len, d_file);
1169 }
1170
1171 // return 1 if nothing to read OR end-of-file;
1172 // the latter isn't an error state
1173 if (retval <= 0) {
1174 // Don't close the file because we might get a reset message...
1175 return 1;
1176 }
1177
1178 // If we are accumulating messages, keep the list of them up to
1179 // date. If we are not, toss the old to make way for the new.
1180 // Whenever this function returns 0, we need to have set the
1181 // Head and Tail to something non-NULL.
1182
1183 if (d_accumulate) {
1184
1185 // doubly-linked list maintenance, putting this one at the tail.
1186 newEntry->next = NULL;
1187 newEntry->prev = d_logTail;
1188 if (d_logTail) {
1189 d_logTail->next = newEntry;
1190 }
1191 d_logTail = newEntry;
1192
1193 // If we've not gotten any messages yet, this one is also the
1194 // head.
1195 if (!d_logHead) {
1197 }
1198 }
1199 else { // Don't keep old list entries.
1200
1201 // If we had a message before, get rid of it and its data now. We
1202 // could use either Head or Tail here because they will point
1203 // to the same message.
1204 if (d_logTail) {
1205 if (d_logTail->data.buffer) {
1206 try {
1207 delete[] d_logTail->data.buffer;
1208 } catch (...) {
1209 fprintf(stderr, "vrpn_File_Connection::read_entry: delete failed\n");
1210 return -1;
1211 }
1212 }
1213 try {
1214 delete d_logTail;
1215 } catch (...) {
1216 fprintf(stderr, "vrpn_File_Connection::read_entry: delete failed\n");
1217 return -1;
1218 }
1219 }
1220
1221 // This is the only message in memory, so it is both the
1222 // head and the tail of the memory list.
1223 d_logHead = d_logTail = newEntry;
1224
1225 // The new entry is not linked to any others (there are no others)
1226 newEntry->next = NULL;
1227 newEntry->prev = NULL;
1228 }
1229
1230 return 0;
1231}
1232// }}}
1233
1234// virtual
1236{
1237 if (d_file) {
1238 fclose(d_file);
1239 }
1240 d_file = NULL;
1241 return 0;
1242}
1243
1245{
1246 // make it as if we never saw any messages from our previous activity
1248
1249 // If we are accumulating, reset us back to the beginning of the memory
1250 // buffer chain. Otherwise, go back to the beginning of the file and
1251 // then read the magic cookie and then the first entry again.
1252 if (d_accumulate) {
1254 }
1255 else {
1256 rewind(d_file);
1257 read_cookie();
1258 read_entry();
1260 }
1262 // reset for mainloop()
1263 d_last_time.tv_usec = d_last_time.tv_sec = 0;
1265
1266 // This is useful to play the initial system messages
1267 // (the sender/type ones) automatically. These might not be
1268 // time synched so if we don't play them automatically they
1269 // can mess up playback if their timestamps are later then
1270 // the first user message.
1273 }
1274
1275 return 0;
1276}
1277
1278// static
1281{
1283
1284 const char *bufPtr = p.buffer;
1285 me->set_replay_rate(vrpn_unbuffer<vrpn_float32>(bufPtr));
1286
1287 return 0;
1288}
1289
1290// static
1292{
1294
1295 // fprintf(stderr, "In vrpn_File_Connection::handle_reset().\n");
1296
1297 return me->reset();
1298}
1299
1300// static
1303{
1305 timeval newtime;
1306
1307 newtime.tv_sec = ((const vrpn_int32 *)(p.buffer))[0];
1308 newtime.tv_usec = ((const vrpn_int32 *)(p.buffer))[1];
1309
1310 return me->play_to_time(newtime);
1311}
1312
1314{
1315 // Do nothing except clear the buffer -
1316 // file connections aren't really connected to anything.
1317
1318 d_endpoints.front()->clearBuffers(); // Clear the buffer for the next time
1319 return 0;
1320}
1321
1322// }}}
pointer front() const
Shorthand for get_by_index(0)
bool is_valid(size_type i) const
Checks to see if an index is both in-range and pointing to a still-extant object.
void addConnection(vrpn_Connection *, const char *name)
NB implementation is not particularly efficient; we expect to have O(10) connections,...
void deleteConnection(vrpn_Connection *)
static vrpn_ConnectionManager & instance(void)
The only way to get access to an instance of this class. Guarantees that there is only one,...
Generic connection class not specific to the transport mechanism.
virtual int do_callbacks_for(vrpn_int32 type, vrpn_int32 sender, struct timeval time, vrpn_uint32 len, const char *buffer)
int doSystemCallbacksFor(vrpn_HANDLERPARAM, void *)
vrpn::EndpointContainer d_endpoints
Sockets used to talk to remote Connection(s) and other information needed on a per-connection basis.
vrpn_uint32 get_Jane_value(void)
int connectionStatus
Status of the connection.
virtual int register_handler(vrpn_int32 type, vrpn_MESSAGEHANDLER handler, void *userdata, vrpn_int32 sender=vrpn_ANY_SENDER)
Set up (or remove) a handler for a message of a given type. Optionally, specify which sender to handl...
void clearBuffers(void)
Empties out the TCP and UDP send buffers. Needed by vrpn_FileConnection to get at {udp,...
void drop_connection(void)
Should only be called by vrpn_Connection::drop_connection(), since there's more housecleaning to do a...
Encapsulation of the data and methods for a single generic connection to take care of one part of man...
int local_type_id(vrpn_int32 remote_type) const
Returns the local mapping for the remote type (-1 if none).
int local_sender_id(vrpn_int32 remote_sender) const
Returns the local mapping for the remote sender (-1 if none).
vrpn_Log * d_inLog
void reset_at_time(const timeval &now_time)
void accumulate_to(const timeval &now_time)
virtual int read_cookie(void)
virtual int mainloop(const timeval *timeout=NULL)
virtual int time_since_connection_open(timeval *elapsed_time)
Returns the time since the connection opened. Some subclasses may redefine time.
virtual int close_file(void)
int playone_to_filetime(timeval end_filetime)
int jump_to_time(vrpn_float64 newtime)
virtual vrpn_File_Connection * get_File_Connection(void)
vrpn_File_Connection implements this as "return this" so it can be used to detect a File_Connection a...
int play_to_time(vrpn_float64 end_time)
virtual ~vrpn_File_Connection(void)
int need_to_play(timeval filetime)
int jump_to_filetime(timeval absolute_time)
vrpn_LOGLIST * d_currentLogEntry
static int VRPN_CALLBACK handle_reset(void *, vrpn_HANDLERPARAM)
static int VRPN_CALLBACK handle_set_replay_rate(void *, vrpn_HANDLERPARAM)
virtual int advance_currentLogEntry(void)
virtual int send_pending_reports(void)
send pending report, clear the buffer. This function was protected, now is public,...
vrpn_File_Connection(const char *station_name, const char *local_in_logfile_name=NULL, const char *local_out_logfile_name=NULL)
int play_to_filetime(const timeval end_filetime)
FileTime_Accumulator d_filetime_accum
static int VRPN_CALLBACK handle_play_to_time(void *, vrpn_HANDLERPARAM)
virtual int read_entry(void)
vrpn_FileBookmark d_bookmark
void set_replay_rate(vrpn_float32 rate)
int setCookie(const char *cookieBuffer)
The magic cookie is set to the default value of the version of VRPN compiled, but a more correct valu...
int logIncomingMessage(size_t payloadLen, struct timeval time, vrpn_int32 type, vrpn_int32 sender, const char *buffer)
Should be called with the timeval adjusted by the clock offset on the receiving Endpoint.
This structure is what is passed to a vrpn_Connection message callback.
const char * buffer
struct timeval msg_time
vrpn_int32 payload_len
Placed here so vrpn_FileConnection can use it too.
vrpn_LOGLIST * next
vrpn_HANDLERPARAM data
vrpn_LOGLIST * prev
size_t vrpn_cookie_size(void)
Returns the size of the magic cookie buffer, plus any alignment overhead.
char * vrpn_copy_file_name(const char *filespecifier)
Utility routines to parse file specifiers FROM service locations.
int check_vrpn_file_cookie(const char *buffer)
const vrpn_int32 vrpn_CONNECTION_UDP_DESCRIPTION
@ CONNECTED
@ BROKEN
bool vrpn_FILE_CONNECTIONS_SHOULD_ACCUMULATE
bool vrpn_FILE_CONNECTIONS_SHOULD_SKIP_TO_USER_MESSAGES
bool vrpn_FILE_CONNECTIONS_SHOULD_PRELOAD
VRPN_API bool vrpn_FILE_CONNECTIONS_SHOULD_SKIP_TO_USER_MESSAGES
bool vrpn_TimevalGreater(const timeval &tv1, const timeval &tv2)
Definition: vrpn_Shared.C:122
timeval vrpn_TimevalScale(const timeval &tv, double scale)
Definition: vrpn_Shared.C:111
double vrpn_TimevalMsecs(const timeval &tv)
Definition: vrpn_Shared.C:150
timeval vrpn_TimevalDiff(const timeval &tv1, const timeval &tv2)
Definition: vrpn_Shared.C:101
timeval vrpn_MsecsTimeval(const double dMsecs)
Definition: vrpn_Shared.C:155
timeval vrpn_TimevalSum(const timeval &tv1, const timeval &tv2)
Definition: vrpn_Shared.C:54
#define vrpn_gettimeofday
Definition: vrpn_Shared.h:99