root/juggler/branches/2.2/modules/gadgeteer/gadget/InputLogger.cpp

Revision 19729, 17.5 kB (checked in by patrick, 2 years ago)

Copyright update.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 /*************** <auto-copyright.pl BEGIN do not edit this line> **************
2  *
3  * VR Juggler is (C) Copyright 1998-2007 by Iowa State University
4  *
5  * Original Authors:
6  *   Allen Bierbaum, Christopher Just,
7  *   Patrick Hartling, Kevin Meinert,
8  *   Carolina Cruz-Neira, Albert Baker
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public
21  * License along with this library; if not, write to the
22  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23  * Boston, MA 02111-1307, USA.
24  *
25  *************** <auto-copyright.pl END do not edit this line> ***************/
26
27 #include <gadget/gadgetConfig.h>
28
29 #include <fstream>
30 #include <stdio.h>
31 #include <boost/progress.hpp>
32 #include <cppdom/cppdom.h>
33
34 #include <vpr/vpr.h>
35 #include <vpr/IO/XMLObjectWriter.h>
36 #include <vpr/IO/XMLObjectReader.h>
37 #include <vpr/System.h>
38 #include <jccl/Config/ConfigElement.h>
39
40 #include <gadget/InputLogger.h>
41
42 /*
43
44 --- Input logger format ---
45 The logger format is made up of a list of samples.
46 For each sample there is a list of devices that got sampled.
47
48 Each of these devices has an individual element with that device name
49 and then the contents are an XML serialized version of that device
50 and it's data (this includes the buffer).
51
52 There may be logger_stamp's intermixed between logger_samples at any point.
53 The stamps only become valid after a logger sample (ie. they stamp the
54 previous sample).
55
56 Note: I could probably write a DTD for this, but I haven't taken the time
57 to do this yet. :(
58 --------------------------
59
60 <gadget_logger>
61
62    <sample>
63       <device dev_name="device name">
64         XML serialized version of that device
65          <devroot...>
66          </devroot...>
67       </device>
68       * <device>
69    </sample>
70    <stamp id="log_name"/>   // Not needed after all logger_sample's
71
72    <sample>
73    ...
74
75 </gadget_logger>
76 */
77
78 namespace gadget
79 {
80
81 /** Configure the logger */
82 bool InputLogger::config( jccl::ConfigElementPtr element)
83 {
84    std::string start_name = element->getProperty<std::string>("start_digital");
85    std::string stamp_name = element->getProperty<std::string>("stamp_digital");
86
87    mStartStopButton.init(start_name);
88    mStampButton.init(stamp_name);
89
90    int max_frame_rate = element->getProperty<int>("max_framerate");
91    mCompressFactor = element->getProperty<unsigned>("compress_factor");
92
93    // Get ignore attribs and elems for compressing
94    unsigned num_ignore_elems, num_ignore_attribs;
95    num_ignore_elems = element->getNum("ignore_elems");
96    num_ignore_attribs = element->getNum("ignore_attribs");
97
98    for(unsigned i=0;i<num_ignore_elems;i++)
99    { mIgnoreElems.push_back(element->getProperty<std::string>("ignore_elems",i)); }
100
101    for(unsigned i=0;i<num_ignore_attribs;i++)
102    { mIgnoreAttribs.push_back(element->getProperty<std::string>("ignore_attribs",i)); }
103
104    if(max_frame_rate > 0)   // If we are supposed to limit frame rate
105    {
106       mLimitFrameRate = true;
107
108       mPrevFrameTimestamp.setNow();             // Initialize value
109       mMinFrameTime.set( (1000/max_frame_rate), vpr::Interval::Msec);
110    }
111
112    vprDEBUG(gadgetDBG_INPUT_MGR, vprDBG_CONFIG_LVL) << "\n--- LOGGER: Configured ---\n" << vprDEBUG_FLUSH;
113    vprDEBUG(gadgetDBG_INPUT_MGR, vprDBG_CONFIG_LVL) << "StartStop: " << start_name << std::endl
114                                                      << "Stamp: " << stamp_name << std::endl
115                                                      << "Max framerate: " << max_frame_rate << std::endl
116                                                      << "Min frametime: " << mMinFrameTime.msec() << "ms" << std::endl << vprDEBUG_FLUSH;
117
118    return true;
119 }
120
121 /** Do input logging processing.
122 *
123 * if(!playing)    // don't check for my keys during playback
124 *    If start/stop pressed
125 *       If recording
126 *          Close recording
127 *       Else
128 *          Start recording
129 *    else if(stamp pressed) && recording
130 *       collect a stamp
131 * if(recording)
132 *    Add sample
133 * else if(playing)
134 *    Playback next sample
135 */
136 void InputLogger::process()
137 {
138    // Get state of control keys
139    // Note, because of how this is called this sees the state one frame late
140    // ie. If toggle on is true here it is NOT true in the data being saved
141    const bool start_stop_triggered(mStartStopButton->getData() == Digital::TOGGLE_ON);
142    const bool stamp_triggered(mStampButton->getData() == Digital::TOGGLE_ON);
143
144    if(0 == mSleepFramesLeft)
145    {
146       // Check for control keys
147       if(Playing != mCurState)
148       {
149          if(start_stop_triggered)
150          {
151             if(Recording == mCurState)
152             {  stopRecording(); }
153             else
154             {  startRecording(); }
155          }
156          else if(stamp_triggered && (Recording == mCurState))
157          {  stampRecord(); }
158       }
159
160       if(Recording == mCurState)
161       {
162          addRecordingSample();
163
164          if(mLimitFrameRate)
165          {  limitFramerate(); }
166       }
167       else if(Playing == mCurState)
168       {
169          playNextSample();
170       }
171    }
172    else
173    {
174       vprDEBUG(vprDBG_ALL, vprDBG_CONFIG_LVL) << "InputLogger: Sleeping: " << mSleepFramesLeft << std::endl << vprDEBUG_FLUSH;
175       mSleepFramesLeft--;
176    }
177 }
178
179 /* Starting the recording and initialize all necessary stuff
180 *
181 * - Get the output filename
182 * - Initialize root node
183 * - Set to recording state
184 */
185 void InputLogger::startRecording()
186 {
187    vprDEBUG(gadgetDBG_INPUT_MGR, vprDBG_STATE_LVL) << "\n--- LOGGER: startRecording ---\n" << vprDEBUG_FLUSH;
188
189    // -- Get recording filename
190    std::cout << "/n/n------- LOGGER ------\nEnter log filename:" << std::flush;
191    std::string file_name;
192    //std::string file_name("test_logging.xml");
193    std::cin >> file_name;
194    std::cout << "\nUsing file: " << file_name << std::endl;
195
196    mRecordingFilename = file_name;
197
198    // -- Init recording
199    cppdom::ContextPtr ctx( new cppdom::Context );
200    mRootNode = cppdom::NodePtr(new cppdom::Node("gadget_logger", ctx ));
201
202    mCurState = Recording;
203 }
204
205 void InputLogger::stopRecording()
206 {
207    vprDEBUG(gadgetDBG_INPUT_MGR, vprDBG_STATE_LVL) << "\n--- LOGGER: stopRecording ---\n" << vprDEBUG_FLUSH;
208    vprDEBUG(gadgetDBG_INPUT_MGR, vprDBG_STATE_LVL) << "Done recording\n"
209                                     << "Saving data to: " << mRecordingFilename << std::endl << vprDEBUG_FLUSH;
210
211    // Compress the data before saving
212    std::cout << "Before compressing: size:" << mRootNode->getChildren().size() << std::endl;
213    compressSamples();
214    std::cout << "After compressing: size:" << mRootNode->getChildren().size() << std::endl;
215
216    std::ofstream out_file;
217    out_file.exceptions(std::ofstream::badbit | std::ofstream::failbit);
218    try
219    {
220       out_file.open(mRecordingFilename.c_str());
221       mRootNode->save(out_file);
222       out_file.flush();
223       out_file.close();
224    }
225 #if defined(__GNUC__) && __GNUC__ == 2 && __GNUC_MINOR__ == 96
226    catch(...)
227    {
228       std::cerr << "Unknown error saving file." << std::endl;
229       if(out_file.is_open())
230       {  out_file.close(); }
231    }
232 #else
233    catch(std::ofstream::failure& se)
234    {
235       std::cerr << "IOS failure saving file: desc:" << se.what() << std::endl;
236       if(out_file.is_open())
237       {  out_file.close(); }
238    }
239    catch(...)
240    {
241       std::cerr << "Unknown error saving file." << std::endl;
242    }
243 #endif
244
245    mCurState = Inactive;
246    mSleepFramesLeft = 10;     // Wait 10 frames until we start processing anything again
247 }
248
249 void InputLogger::stampRecord()
250 {
251    vprASSERT(Recording == mCurState && "Tried to stamp input while not recording");
252
253    // -- Get recording filename
254    char tag_name[1024];
255    std::cout << "/n/n------- LOGGER ------\nEnter stamp id:" << std::flush;
256    std::string stamp_id;
257    //std::cin >> stamp_id;
258 #if defined(_MSC_VER) && _MSC_VER >= 1400
259    scanf_s("%s", tag_name);
260 #else
261    scanf("%s", tag_name);
262 #endif
263    stamp_id = std::string(tag_name);
264    std::cout << "\nStamping with: " << stamp_id << std::endl;
265
266    cppdom::NodePtr stamp_node(new cppdom::Node("stamp", mRootNode->getContext()));
267    stamp_node->setAttribute("id", stamp_id);
268
269    mRootNode->addChild(stamp_node);
270 }
271
272 /**
273  * Load a log file.
274  * @param logFilename The name of the log file to load.
275  */
276 void InputLogger::load(std::string logFilename)
277 {
278    vprASSERT(Inactive == mCurState && "Tried to load a file while logger is active");
279
280    std::cout << "InputLogger: Loading file: " << logFilename << std::endl;
281
282    // Create new root to read into
283    cppdom::ContextPtr ctx( new cppdom::Context );
284    mRootNode = cppdom::NodePtr(new cppdom::Node("not_set", ctx ));
285
286    std::ifstream in_file;
287    in_file.exceptions(std::ifstream::badbit | std::ifstream::failbit);
288    try
289    {
290       in_file.open(logFilename.c_str(), std::ios::in);
291       mRootNode->load(in_file, ctx);
292       in_file.close();
293    }
294 #if defined(__GNUC__) && __GNUC__ == 2 && __GNUC_MINOR__ == 96
295    catch(...)
296    {
297       std::cerr << "Unknown error loading file." << std::endl;
298       if(in_file.is_open())
299       {  in_file.close(); }
300    }
301 #else
302    catch(std::ifstream::failure& se)
303    {
304       std::cerr << "IOS failure saving file: desc:" << se.what() << std::endl;
305       if(in_file.is_open())
306       {  in_file.close(); }
307    }
308    catch(...)
309    {
310       std::cerr << "Unknown error loading file." << std::endl;
311    }
312 #endif
313
314    vprDEBUG(vprDBG_ALL, vprDBG_STATE_LVL) << "InputLogger: Loaded file: num_samples:" << mRootNode->getChildren().size() << std::endl << vprDEBUG_FLUSH;
315 }
316
317 /* Start playing
318 *
319 * - Set state to playing
320 * - Find the first node is list
321 * - Set the start and end nodes
322 * - Clear the stamp
323 */
324 void InputLogger::play()
325 {
326    vprASSERT(mCurState == Inactive);
327
328    if(mRootNode.get() == NULL)
329    {
330       vprDEBUG(vprDBG_ALL, vprDBG_WARNING_LVL) << "Logger::play: Null root node, so can't play.\n" << vprDEBUG_FLUSH;
331       return;
332    }
333
334    mNextSample_i = mRootNode->getChildren().begin();
335    mEndSample_i = mRootNode->getChildren().end();
336    mActiveStamp.clear();
337
338    if(mNextSample_i == mEndSample_i)
339    {
340       vprDEBUG(vprDBG_ALL, vprDBG_WARNING_LVL) << "Logger::play: Zero children, so can't play.\n" << vprDEBUG_FLUSH;
341       return;
342    }
343
344    mCurState = Playing;
345 }
346
347 /** Stop playing a log */
348 void InputLogger::stop()
349 {;
350 }
351
352 /** Pause log playback. */
353 void InputLogger::pause()
354 {;
355 }
356
357 /** Get the stamp for the most recent sample
358 * @return Returns empty string if no active stamp.
359 */
360 std::string InputLogger::getStamp()
361 {
362    return mActiveStamp;
363 }
364
365
366 /** Add a recording sample to the current dom tree
367 *
368 * - Create the sample node
369 * - For each device in input manager
370 *    - Create device node for the device (named after device)
371 *    - Serialize device into a node
372 *    - Add serialized dev node as child of dev node
373 *    - Add dev node as child of sample node
374 * - Add sample node as child of root node
375 */
376 void InputLogger::addRecordingSample()
377 {
378    vprDEBUG(gadgetDBG_INPUT_MGR, vprDBG_STATE_LVL) << "LOGGER: adding sample.\n" << vprDEBUG_FLUSH;
379
380    try
381    {
382       gadget::InputManager* input_mgr = gadget::InputManager::instance();
383
384       cppdom::NodePtr sample_node(new cppdom::Node("sample", mRootNode->getContext()));
385
386       // For each device
387       for(InputManager::tDevTableType::iterator dev_i=input_mgr->mDevTable.begin();
388           dev_i != input_mgr->mDevTable.end(); ++dev_i)
389       {
390          gadget::Input* cur_dev = (*dev_i).second;
391          std::string dev_name = cur_dev->getInstanceName();
392
393          cppdom::NodePtr dev_node(new cppdom::Node("device", mRootNode->getContext()));
394          dev_node->setAttribute("dev_name", dev_name);
395
396          vpr::XMLObjectWriter xml_writer;       // Create writer for the device
397          cur_dev->writeObject(&xml_writer);
398          cppdom::NodePtr serialized_dev_node = xml_writer.getRootNode();
399          vprASSERT(serialized_dev_node.get() != NULL);
400
401          dev_node->addChild(serialized_dev_node);  // Add the child node
402          sample_node->addChild(dev_node);          // Add dev node to the sample
403       }
404
405       mRootNode->addChild(sample_node);
406    }
407    catch (cppdom::Error& ce)
408    {
409       std::cerr << "Cppdom Error [InputLogger::addRecordingSample]: " << ce.getString() << std::endl;
410       std::cerr << "Info: " << ce.getInfo() << std::endl;
411       vprASSERT(false);
412    }
413 }
414
415 /*
416 * - Get the node for the next sample
417 * - For each device element
418 *    - Get the device name
419 *    - Get handle to that device from the input manager
420 *    - Deserialize the device
421 *
422 * - Increment next sample
423 * - If nextSample is a stamp element
424 *    - Read and set the stamp
425 *    - Increment next sample
426 * - Else, clear the stamp
427 */
428 void InputLogger::playNextSample()
429 {
430    vprDEBUG_OutputGuard(gadgetDBG_INPUT_MGR, vprDBG_STATE_LVL, "InputLogger::playNextSample\n", "done playing sample\n");
431
432    vprASSERT(mNextSample_i != mEndSample_i && "Overran the logger sample list");
433    gadget::InputManager* input_mgr = gadget::InputManager::instance();
434
435    cppdom::NodePtr next_node = (*mNextSample_i);
436    vprASSERT(next_node->getName() == std::string("sample") && "Didn't get element of name sample");
437
438    // For each device element
439    cppdom::NodeList dev_nodes = next_node->getChildren();
440    for(cppdom::NodeListIterator cur_dev_node=dev_nodes.begin(); cur_dev_node != dev_nodes.end(); ++cur_dev_node)
441    {
442       vprASSERT((*cur_dev_node)->getName() == std::string("device"));
443       std::string dev_name = (*cur_dev_node)->getAttribute("dev_name").getValue<std::string>();
444       cppdom::NodePtr serial_dev_node = *((*cur_dev_node)->getChildren().begin());
445       vprASSERT(serial_dev_node.get() != NULL && "Got null serialized device node");
446
447       gadget::Input* dev_ptr = input_mgr->getDevice(dev_name);
448       if(NULL != dev_ptr)
449       {
450          vprDEBUG_OutputGuard(gadgetDBG_INPUT_MGR, vprDBG_STATE_LVL,
451                               std::string("Reading device: ") + dev_name + std::string("\n"), "done reading");
452
453          vpr::XMLObjectReader xml_reader(serial_dev_node);     // Create XML reader
454          xml_reader.setAttrib("rim.timestamp.delta", 0);       // Hack for now to work around RIM
455          dev_ptr->readObject(&xml_reader);                     // Deserialize the device
456       }
457       else
458       {
459          vprDEBUG(gadgetDBG_INPUT_MGR,vprDBG_WARNING_LVL) << "Skipping device: [" << dev_name
460                                          << "]  Could not find it.\n" << vprDEBUG_FLUSH;
461       }
462    }
463
464    // Increment next sample
465    mNextSample_i++;
466    mActiveStamp.clear();      // Clear the stamp for now
467
468    if(mNextSample_i != mEndSample_i)
469    {
470       if( (*mNextSample_i)->getName() == std::string("stamp"))
471       {
472          mActiveStamp = (*mNextSample_i)->getAttribute("id").getValue<std::string>();
473          mNextSample_i++;
474          vprDEBUG(gadgetDBG_INPUT_MGR, vprDBG_STATE_LVL) << "Logger: Got stamp: [" << mActiveStamp << "]\n" << vprDEBUG_FLUSH;
475       }
476    }
477
478    if(mNextSample_i == mEndSample_i)   // If done playing
479    {
480       mCurState = Inactive;
481       mSleepFramesLeft = 10;     // Wait 10 frames until we start processing anything again
482       vprDEBUG(gadgetDBG_INPUT_MGR, vprDBG_STATE_LVL) << "Logger: Done playing.\n" << vprDEBUG_FLUSH;
483    }
484
485 }
486
487 void InputLogger::limitFramerate()
488 {
489    vprASSERT(mLimitFrameRate);
490
491    vpr::Interval cur_frame_time = vpr::Interval::now() - mPrevFrameTimestamp;
492    if(cur_frame_time < mMinFrameTime)
493    {
494       vpr::Interval sleep_time = (mMinFrameTime - cur_frame_time);
495       std::cout << "Sleeping: " << sleep_time.msec() << "ms\n";
496       vprASSERT(sleep_time.msec() > 0);
497       vpr::System::msleep(sleep_time.msec());                     // Sleep
498    }
499
500    mPrevFrameTimestamp.setNow();
501 }
502
503 void InputLogger::compressSamples()
504 {
505    // Get the current children to compress
506    cppdom::NodeList& nodes(mRootNode->getChildren());
507    unsigned total_nodes_start = nodes.size();
508
509    // Create a nice little progress bar for the compression
510    boost::progress_display compress_progress(nodes.size());
511
512    cppdom::NodeList::iterator cur_node = nodes.begin();
513    if(nodes.end() == cur_node)
514       return;
515
516    cur_node++;       // Can't remove first node
517    //unsigned node_index(0);
518
519    // Compress the data
520    // - While nodes left
521    //   - If node is duplicate of one before it
522    //     - Erase the node and goto next
523    while(cur_node != nodes.end())
524    {
525       cppdom::NodeList::iterator dup_node = (cur_node-1);      // Initialize dup node to check
526
527       /*
528       std::cout << "-----------------------------------------------------------------------\n"
529                 << "-----------------------------------------------------------------------\n"
530                 << "Comparing nodes: index: " << node_index++ << std::endl;
531       std::cout << "------ curnode:\n";
532       (*cur_node)->save(std::cout, 1);
533       std::cout << "------ dupnode:\n";
534       (*dup_node)->save(std::cout, 1);
535       std::cout << "------ Start comparison ----\n";
536       */
537
538       if((*cur_node)->isEqual( *dup_node, mIgnoreAttribs, mIgnoreElems))
539       {
540          cur_node = nodes.erase(cur_node);         // Remove the node and get next one
541       }
542       else
543       {
544          cur_node++;                               // Go to the next
545       }
546
547       compress_progress += 1;       // Progress a little bit
548    }
549
550    // --- Print results --- //
551    std::cout << std::endl
552              << "Compression: initial/compressed:" << total_nodes_start << "/" << nodes.size() << std::endl
553              << "      ratio: " << float(nodes.size())/float(total_nodes_start) << std::endl;
554 }
555
556
557 } // namespace gadget
558
Note: See TracBrowser for help on using the browser.