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 }
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
bool begin_playback(const Filename &filename)
Begins playing back data from the indicated filename.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A Factory can be used to create an instance of a particular subclass of some general base class.
Definition: factory.h:34
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:110
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
TypedWritable * read_object()
Reads a single object from the Bam file.
Definition: bamReader.cxx:224
bool is_recording() const
Returns true if the controller has been opened for output, false otherwise.
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
bool read_header(std::string &header, size_t num_bytes)
Reads a sequence of bytes from the beginning of the datagram file.
bool write_header(const std::string &header)
Writes a sequence of bytes to the beginning of the datagram file.
void close()
Finishes recording data to the indicated filename.
void close()
Closes the file.
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:63
bool open(const FileReference *file)
Opens the indicated filename for reading.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool open(const FileReference *file)
Opens the indicated filename for writing.
void record_frame()
Gets the next frame of data from all of the active recorders and adds it to the output file.
void play_frame(BamReader *manager)
Once the raw data has been read in from the session file, and the table has been decoded,...
bool init()
Initializes the BamReader prior to reading any objects from its source.
Definition: bamReader.cxx:85
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
This object is used by the RecorderController to write (and read) a record of the set of recorders in...
Definition: recorderTable.h:32
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
Definition: clockObject.h:94
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
A ClockObject keeps track of elapsed real time and discrete time.
Definition: clockObject.h:58
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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void clear_flags(short flags)
Clears the given flags on all recorders.
void merge_from(const RecorderTable &other)
Combines the data in the current table (presumably just read from disk, and matching exactly with the...
bool begin_record(const Filename &filename)
Begins recording data to the indicated filename.
This object represents one frame of data in the recorded session file.
Definition: recorderFrame.h:32
void play_frame()
Gets the next frame of data from all of the active recorders and adds it to the output file.
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
This object contains the header information written out at the beginning of a recorded session file.
bool is_playing() const
Returns true if the controller has been opened for input, false otherwise.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
void set_flags(short flags)
Sets the given flags on all recorders.
void close()
Closes the file.
bool init()
Initializes the BamWriter prior to writing any objects to its output stream.
Definition: bamWriter.cxx:147
bool is_eof() const
Returns true if the reader has reached end-of-file, false otherwise.
Definition: bamReader.I:66
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.