Panda3D
recorderController.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file recorderController.cxx
10  * @author drose
11  * @date 2004-01-24
12  */
13 
14 #include "recorderController.h"
15 #include "recorderFrame.h"
16 #include "bamReader.h"
17 #include "bamWriter.h"
18 #include "config_recorder.h"
19 #include "bam.h"
20 #include "clockObject.h"
21 
22 TypeHandle RecorderController::_type_handle;
23 RecorderController::RecorderFactory *RecorderController::_factory = nullptr;
24 
25 /**
26  *
27  */
28 RecorderController::
29 RecorderController() {
30  _clock_offset = 0.0;
31  _frame_offset = 0;
32  _writer = nullptr;
33  _reader = nullptr;
34  _frame_tie = true;
35  _user_table = new RecorderTable;
36  _user_table_modified = false;
37  _file_table = nullptr;
38  _active_table = nullptr;
39  _eof = false;
40 }
41 
42 /**
43  *
44  */
45 RecorderController::
46 ~RecorderController() {
47  close();
48  delete _user_table;
49 }
50 
51 /**
52  * Begins recording data to the indicated filename. All of the recorders in
53  * use should already have been added.
54  */
56 begin_record(const Filename &filename) {
57  close();
58  _filename = filename;
60  _clock_offset = global_clock->get_frame_time();
61  _frame_offset = global_clock->get_frame_count();
62 
63  time(&_header._start_time);
64 
65  if (!_dout.open(_filename)) {
66  recorder_cat.error() << "Unable to open " << _filename << "\n";
67  return false;
68  }
69 
70  if (!_dout.write_header(_bam_header)) {
71  recorder_cat.error() << "Unable to write to " << _filename << "\n";
72  return false;
73  }
74 
75  _writer = new BamWriter(&_dout);
76 
77  if (!_writer->init()) {
78  close();
79  return false;
80  }
81 
82  // Write out the header information.
83  _writer->write_object(&_header);
84 
85  _user_table_modified = true;
86 
87  // Tell all of our recorders that they're live now.
88  _user_table->set_flags(RecorderBase::F_recording);
89 
90  recorder_cat.info()
91  << "Recording session to " << _filename << "\n";
92 
93  return true;
94 }
95 
96 /**
97  * Begins playing back data from the indicated filename. All of the recorders
98  * in use should already have been added, although this may define additional
99  * recorders if they are present in the file (these new recorders will not be
100  * used). This may also undefine recorders that were previously added but are
101  * not present in the file.
102  */
104 begin_playback(const Filename &filename) {
105  close();
106  _filename = filename;
107  ClockObject *global_clock = ClockObject::get_global_clock();
108  _clock_offset = global_clock->get_frame_time();
109  _frame_offset = global_clock->get_frame_count();
110 
111  if (!_din.open(_filename)) {
112  recorder_cat.error() << "Unable to open " << _filename << "\n";
113  return false;
114  }
115 
116  std::string head;
117  if (!_din.read_header(head, _bam_header.size()) || head != _bam_header) {
118  recorder_cat.error() << "Unable to read " << _filename << "\n";
119  return false;
120  }
121 
122  _reader = new BamReader(&_din);
123  if (!_reader->init()) {
124  close();
125  return false;
126  }
127 
128  _user_table_modified = true;
129  _active_table = new RecorderTable;
130  _eof = false;
131 
132  // Start out by reading the RecorderHeader.
133  TypedWritable *object = _reader->read_object();
134 
135  if (object == nullptr ||
136  !object->is_of_type(RecorderHeader::get_class_type())) {
137  recorder_cat.error()
138  << _filename << " does not contain a recorded session.\n";
139  close();
140  return false;
141  }
142 
143  if (!_reader->resolve()) {
144  recorder_cat.warning()
145  << "Unable to resolve header data.\n";
146  }
147 
148  RecorderHeader *new_header = DCAST(RecorderHeader, object);
149  _header = (*new_header);
150  delete new_header;
151 
152  // Now read the first frame.
153  _next_frame = read_frame();
154  if (_next_frame == nullptr) {
155  recorder_cat.error()
156  << _filename << " does not contain any frames.\n";
157  close();
158  return false;
159  }
160 
161  recorder_cat.info()
162  << "Playing back session from " << _filename << "\n";
163 
164  return true;
165 }
166 
167 /**
168  * Finishes recording data to the indicated filename.
169  */
171 close() {
172  if (_writer != nullptr) {
173  delete _writer;
174  _writer = nullptr;
175 
176  // Tell all of our recorders that they're no longer recording.
177  _user_table->clear_flags(RecorderBase::F_recording);
178  }
179  if (_reader != nullptr) {
180  delete _reader;
181  _reader = nullptr;
182 
183  // Tell all of our recorders that they're no longer playing.
184  _active_table->clear_flags(RecorderBase::F_playing);
185  }
186  _dout.close();
187  _din.close();
188 
189  if (_file_table != nullptr) {
190  delete _file_table;
191  _file_table = nullptr;
192  }
193 
194  if (_active_table != nullptr) {
195  delete _active_table;
196  _active_table = nullptr;
197  }
198 }
199 
200 /**
201  * Gets the next frame of data from all of the active recorders and adds it to
202  * the output file.
203  */
206  if (is_recording()) {
207  ClockObject *global_clock = ClockObject::get_global_clock();
208  double now = global_clock->get_frame_time() - _clock_offset;
209  int frame = global_clock->get_frame_count() - _frame_offset;
210 
211  RecorderFrame data(now, frame, _user_table_modified, _user_table);
212  _user_table_modified = false;
213 
214  _writer->write_object(&data);
215  }
216 }
217 
218 /**
219  * Gets the next frame of data from all of the active recorders and adds it to
220  * the output file.
221  */
224  if (is_playing()) {
225  if (_eof) {
226  close();
227  return;
228  }
229 
230  ClockObject *global_clock = ClockObject::get_global_clock();
231  double now = global_clock->get_frame_time() - _clock_offset;
232  int frame = global_clock->get_frame_count() - _frame_offset;
233 
234  while (_next_frame != nullptr) {
235  if (_frame_tie) {
236  if (frame < _next_frame->_frame) {
237  // We haven't reached the next frame yet.
238  return;
239  }
240 
241  // Insist that the clock runs at the same rate as it did in the
242  // previous session.
243  // global_clock->set_frame_time(_next_frame->_timestamp +
244  // _clock_offset); global_clock->set_real_time(_next_frame->_timestamp
245  // + _clock_offset);
246 
247  // Hmm, that's crummy. Just keep the clock offset up-to-date.
248  _clock_offset = global_clock->get_frame_time() - _next_frame->_timestamp;
249 
250  } else {
251  if (now < _next_frame->_timestamp) {
252  // We haven't reached the next frame yet.
253  return;
254  }
255 
256  // Keep our frame_offset up-to-date.
257  _frame_offset = global_clock->get_frame_count() - _next_frame->_frame;
258  }
259 
260  if (_next_frame->_table_changed && _file_table != _next_frame->_table) {
261  delete _file_table;
262  _file_table = _next_frame->_table;
263  }
264 
265  if (_next_frame->_table_changed || _user_table_modified) {
266  // We're about to change the active table. Temporarily disable the
267  // playing flag on the currently-active recorders.
268  _active_table->clear_flags(RecorderBase::F_playing);
269  delete _active_table;
270  _active_table = new RecorderTable(*_file_table);
271  _active_table->merge_from(*_user_table);
272  _user_table_modified = false;
273 
274  // Now reenable the playing flag on the newly-active recorders.
275  _active_table->set_flags(RecorderBase::F_playing);
276  }
277 
278  _next_frame->_table = _active_table;
279  _next_frame->play_frame(_reader);
280 
281  delete _next_frame;
282  _next_frame = read_frame();
283  }
284 
285  if (_reader->is_eof()) {
286  recorder_cat.info()
287  << "End of recorded session.\n";
288  } else {
289  recorder_cat.error()
290  << "Unable to read datagram from recorded session.\n";
291  }
292  _eof = true;
293  }
294 }
295 
296 
297 /**
298  * Loads the next frame data from the playback session file. Returns the
299  * frame data pointer on success, or NULL on failure.
300  */
301 RecorderFrame *RecorderController::
302 read_frame() {
303  TypedWritable *object = _reader->read_object();
304 
305  if (object == nullptr ||
306  !object->is_of_type(RecorderFrame::get_class_type())) {
307  return nullptr;
308  }
309 
310  if (!_reader->resolve()) {
311  recorder_cat.warning()
312  << "Unable to resolve frame data.\n";
313  }
314 
315  return DCAST(RecorderFrame, object);
316 }
RecorderController::begin_record
bool begin_record(const Filename &filename)
Begins recording data to the indicated filename.
Definition: recorderController.cxx:56
DatagramInputFile::read_header
bool read_header(std::string &header, size_t num_bytes)
Reads a sequence of bytes from the beginning of the datagram file.
Definition: datagramInputFile.cxx:104
ClockObject
A ClockObject keeps track of elapsed real time and discrete time.
Definition: clockObject.h:58
RecorderController::play_frame
void play_frame()
Gets the next frame of data from all of the active recorders and adds it to the output file.
Definition: recorderController.cxx:223
BamReader
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:110
DatagramOutputFile::close
void close()
Closes the file.
Definition: datagramOutputFile.cxx:73
DatagramInputFile::close
void close()
Closes the file.
Definition: datagramInputFile.cxx:80
recorderFrame.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
clockObject.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ClockObject::get_global_clock
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
BamWriter
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:63
bam.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
DatagramInputFile::open
bool open(const FileReference *file)
Opens the indicated filename for reading.
Definition: datagramInputFile.cxx:33
bamReader.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TypedWritable
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
RecorderHeader
This object contains the header information written out at the beginning of a recorded session file.
Definition: recorderHeader.h:31
TypeHandle
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
RecorderTable::set_flags
void set_flags(short flags)
Sets the given flags on all recorders.
Definition: recorderTable.cxx:117
RecorderController::begin_playback
bool begin_playback(const Filename &filename)
Begins playing back data from the indicated filename.
Definition: recorderController.cxx:104
BamReader::init
bool init()
Initializes the BamReader prior to reading any objects from its source.
Definition: bamReader.cxx:85
RecorderTable::merge_from
void merge_from(const RecorderTable &other)
Combines the data in the current table (presumably just read from disk, and matching exactly with the...
Definition: recorderTable.cxx:42
RecorderController::record_frame
void record_frame()
Gets the next frame of data from all of the active recorders and adds it to the output file.
Definition: recorderController.cxx:205
config_recorder.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
BamReader::read_object
TypedWritable * read_object()
Reads a single object from the Bam file.
Definition: bamReader.cxx:224
BamWriter::write_object
bool write_object(const TypedWritable *obj)
Writes a single object to the Bam file, so that the BamReader::read_object() can later correctly rest...
Definition: bamWriter.cxx:204
DatagramOutputFile::write_header
bool write_header(const std::string &header)
Writes a sequence of bytes to the beginning of the datagram file.
Definition: datagramOutputFile.cxx:96
recorderController.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
RecorderController::is_recording
bool is_recording() const
Returns true if the controller has been opened for output, false otherwise.
Definition: recorderController.I:48
RecorderTable
This object is used by the RecorderController to write (and read) a record of the set of recorders in...
Definition: recorderTable.h:32
RecorderController::close
void close()
Finishes recording data to the indicated filename.
Definition: recorderController.cxx:171
Factory
A Factory can be used to create an instance of a particular subclass of some general base class.
Definition: factory.h:34
BamReader::resolve
bool resolve()
This may be called at any time during processing of the Bam file to resolve all the known pointers so...
Definition: bamReader.cxx:325
ClockObject::get_frame_count
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
Definition: clockObject.h:94
RecorderTable::clear_flags
void clear_flags(short flags)
Clears the given flags on all recorders.
Definition: recorderTable.cxx:131
ClockObject::get_frame_time
get_frame_time
Returns the time in seconds as of the last time tick() was called (typically, this will be as of the ...
Definition: clockObject.h:91
BamWriter::init
bool init()
Initializes the BamWriter prior to writing any objects to its output stream.
Definition: bamWriter.cxx:147
RecorderFrame
This object represents one frame of data in the recorded session file.
Definition: recorderFrame.h:32
BamReader::is_eof
bool is_eof() const
Returns true if the reader has reached end-of-file, false otherwise.
Definition: bamReader.I:66
bamWriter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
DatagramOutputFile::open
bool open(const FileReference *file)
Opens the indicated filename for writing.
Definition: datagramOutputFile.cxx:28
RecorderFrame::play_frame
void play_frame(BamReader *manager)
Once the raw data has been read in from the session file, and the table has been decoded,...
Definition: recorderFrame.cxx:27
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
TypedObject::is_of_type
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
RecorderController::is_playing
bool is_playing() const
Returns true if the controller has been opened for input, false otherwise.
Definition: recorderController.I:56