Panda3D
dcField.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 dcField.cxx
10  * @author drose
11  * @date 2000-10-11
12  */
13 
14 #include "dcField.h"
15 #include "dcFile.h"
16 #include "dcPacker.h"
17 #include "dcClass.h"
18 #include "hashGenerator.h"
19 #include "dcmsgtypes.h"
20 
21 #ifdef HAVE_PYTHON
22 #include "py_panda.h"
23 #endif
24 
25 #ifdef WITHIN_PANDA
26 #include "pStatTimer.h"
27 #endif
28 
29 using std::string;
30 
31 /**
32  *
33  */
34 DCField::
35 DCField() :
36  _dclass(nullptr)
37 #ifdef WITHIN_PANDA
38  ,
39  _field_update_pcollector("DCField")
40 #endif
41 {
42  _number = -1;
43  _default_value_stale = true;
44  _has_default_value = false;
45 
46  _bogus_field = false;
47 
48  _has_nested_fields = true;
49  _num_nested_fields = 0;
50  _pack_type = PT_field;
51 
52  _has_fixed_byte_size = true;
53  _fixed_byte_size = 0;
54  _has_fixed_structure = true;
55 }
56 
57 /**
58  *
59  */
60 DCField::
61 DCField(const string &name, DCClass *dclass) :
62  DCPackerInterface(name),
63  _dclass(dclass)
64 #ifdef WITHIN_PANDA
65  ,
66  _field_update_pcollector(dclass->_class_update_pcollector, name)
67 #endif
68 {
69  _number = -1;
70  _has_default_value = false;
71  _default_value_stale = true;
72 
73  _bogus_field = false;
74 
75  _has_nested_fields = true;
76  _num_nested_fields = 0;
77  _pack_type = PT_field;
78 
79  _has_fixed_byte_size = true;
80  _fixed_byte_size = 0;
81  _has_fixed_structure = true;
82 }
83 
84 /**
85  *
86  */
87 DCField::
88 ~DCField() {
89 }
90 
91 /**
92  *
93  */
94 DCField *DCField::
95 as_field() {
96  return this;
97 }
98 
99 /**
100  *
101  */
102 const DCField *DCField::
103 as_field() const {
104  return this;
105 }
106 
107 /**
108  * Returns the same field pointer converted to an atomic field pointer, if
109  * this is in fact an atomic field; otherwise, returns NULL.
110  */
113  return nullptr;
114 }
115 
116 /**
117  * Returns the same field pointer converted to an atomic field pointer, if
118  * this is in fact an atomic field; otherwise, returns NULL.
119  */
122  return nullptr;
123 }
124 
125 /**
126  * Returns the same field pointer converted to a molecular field pointer, if
127  * this is in fact a molecular field; otherwise, returns NULL.
128  */
131  return nullptr;
132 }
133 
134 /**
135  * Returns the same field pointer converted to a molecular field pointer, if
136  * this is in fact a molecular field; otherwise, returns NULL.
137  */
140  return nullptr;
141 }
142 
143 /**
144  *
145  */
146 DCParameter *DCField::
147 as_parameter() {
148  return nullptr;
149 }
150 
151 /**
152  *
153  */
154 const DCParameter *DCField::
155 as_parameter() const {
156  return nullptr;
157 }
158 
159 /**
160  * Given a blob that represents the packed data for this field, returns a
161  * string formatting it for human consumption. Returns empty string if there
162  * is an error.
163  */
164 string DCField::
165 format_data(const vector_uchar &packed_data, bool show_field_names) {
166  DCPacker packer;
167  packer.set_unpack_data(packed_data);
168  packer.begin_unpack(this);
169  string result = packer.unpack_and_format(show_field_names);
170  if (!packer.end_unpack()) {
171  return string();
172  }
173  return result;
174 }
175 
176 /**
177  * Given a human-formatted string (for instance, as returned by format_data(),
178  * above) that represents the value of this field, parse the string and return
179  * the corresponding packed data. Returns empty string if there is an error.
180  */
181 vector_uchar DCField::
182 parse_string(const string &formatted_string) {
183  DCPacker packer;
184  packer.begin_pack(this);
185  if (!packer.parse_and_pack(formatted_string)) {
186  // Parse error.
187  return vector_uchar();
188  }
189  if (!packer.end_pack()) {
190  // Data type mismatch.
191  return vector_uchar();
192  }
193 
194  return packer.get_bytes();
195 }
196 
197 /**
198  * Verifies that all of the packed values in the field data are within the
199  * specified ranges and that there are no extra bytes on the end of the
200  * record. Returns true if all fields are valid, false otherwise.
201  */
202 bool DCField::
203 validate_ranges(const vector_uchar &packed_data) const {
204  DCPacker packer;
205  packer.set_unpack_data(packed_data);
206  packer.begin_unpack(this);
207  packer.unpack_validate();
208  if (!packer.end_unpack()) {
209  return false;
210  }
211 
212  return (packer.get_num_unpacked_bytes() == packed_data.size());
213 }
214 
215 #ifdef HAVE_PYTHON
216 /**
217  * Packs the Python arguments from the indicated tuple into the packer.
218  * Returns true on success, false on failure.
219  *
220  * It is assumed that the packer is currently positioned on this field.
221  */
222 bool DCField::
223 pack_args(DCPacker &packer, PyObject *sequence) const {
224  nassertr(!packer.had_error(), false);
225  nassertr(packer.get_current_field() == this, false);
226 
227  packer.pack_object(sequence);
228  if (!packer.had_error()) {
229  /*
230  cerr << "pack " << get_name() << get_pystr(sequence) << "\n";
231  */
232 
233  return true;
234  }
235 
236  if (!Notify::ptr()->has_assert_failed()) {
237  std::ostringstream strm;
238  PyObject *exc_type = PyExc_Exception;
239 
240  if (as_parameter() != nullptr) {
241  // If it's a parameter-type field, the value may or may not be a
242  // sequence.
243  if (packer.had_pack_error()) {
244  strm << "Incorrect arguments to field: " << get_name()
245  << " = " << get_pystr(sequence);
246  exc_type = PyExc_TypeError;
247  } else {
248  strm << "Value out of range on field: " << get_name()
249  << " = " << get_pystr(sequence);
250  exc_type = PyExc_ValueError;
251  }
252 
253  } else {
254  // If it's a molecular or atomic field, the value should be a sequence.
255  PyObject *tuple = PySequence_Tuple(sequence);
256  if (tuple == nullptr) {
257  strm << "Value for " << get_name() << " not a sequence: " \
258  << get_pystr(sequence);
259  exc_type = PyExc_TypeError;
260 
261  } else {
262  if (packer.had_pack_error()) {
263  strm << "Incorrect arguments to field: " << get_name()
264  << get_pystr(sequence);
265  exc_type = PyExc_TypeError;
266  } else {
267  strm << "Value out of range on field: " << get_name()
268  << get_pystr(sequence);
269  exc_type = PyExc_ValueError;
270  }
271 
272  Py_DECREF(tuple);
273  }
274  }
275 
276  string message = strm.str();
277  PyErr_SetString(exc_type, message.c_str());
278  }
279  return false;
280 }
281 #endif // HAVE_PYTHON
282 
283 #ifdef HAVE_PYTHON
284 /**
285  * Unpacks the values from the packer, beginning at the current point in the
286  * unpack_buffer, into a Python tuple and returns the tuple.
287  *
288  * It is assumed that the packer is currently positioned on this field.
289  */
290 PyObject *DCField::
291 unpack_args(DCPacker &packer) const {
292  nassertr(!packer.had_error(), nullptr);
293  nassertr(packer.get_current_field() == this, nullptr);
294 
295  size_t start_byte = packer.get_num_unpacked_bytes();
296  PyObject *object = packer.unpack_object();
297 
298  if (!packer.had_error()) {
299  // Successfully unpacked.
300  /*
301  cerr << "recv " << get_name() << get_pystr(object) << "\n";
302  */
303 
304  return object;
305  }
306 
307  if (!Notify::ptr()->has_assert_failed()) {
308  std::ostringstream strm;
309  PyObject *exc_type = PyExc_Exception;
310 
311  if (packer.had_pack_error()) {
312  strm << "Data error unpacking field ";
313  output(strm, true);
314  size_t length = packer.get_unpack_length() - start_byte;
315  strm << "\nGot data (" << (int)length << " bytes):\n";
316  Datagram dg(packer.get_unpack_data() + start_byte, length);
317  dg.dump_hex(strm);
318  size_t error_byte = packer.get_num_unpacked_bytes() - start_byte;
319  strm << "Error detected on byte " << error_byte
320  << " (" << std::hex << error_byte << std::dec << " hex)";
321 
322  exc_type = PyExc_RuntimeError;
323  } else {
324  strm << "Value outside specified range when unpacking field "
325  << get_name() << ": " << get_pystr(object);
326  exc_type = PyExc_ValueError;
327  }
328 
329  string message = strm.str();
330  PyErr_SetString(exc_type, message.c_str());
331  }
332 
333  Py_XDECREF(object);
334  return nullptr;
335 }
336 #endif // HAVE_PYTHON
337 
338 #ifdef HAVE_PYTHON
339 /**
340  * Extracts the update message out of the datagram and applies it to the
341  * indicated object by calling the appropriate method.
342  */
343 void DCField::
344 receive_update(DCPacker &packer, PyObject *distobj) const {
345  if (as_parameter() != nullptr) {
346  // If it's a parameter-type field, just store a new value on the object.
347  PyObject *value = unpack_args(packer);
348  if (value != nullptr) {
349  PyObject_SetAttrString(distobj, (char *)_name.c_str(), value);
350  }
351  Py_DECREF(value);
352 
353  } else {
354  // Otherwise, it must be an atomic or molecular field, so call the
355  // corresponding method.
356 
357  if (!PyObject_HasAttrString(distobj, (char *)_name.c_str())) {
358  // If there's no Python method to receive this message, don't bother
359  // unpacking it to a Python tuple--just skip past the message.
360  packer.unpack_skip();
361 
362  } else {
363  // Otherwise, get a Python tuple from the args and call the Python
364  // method.
365  PyObject *args = unpack_args(packer);
366 
367  if (args != nullptr) {
368  PyObject *func = PyObject_GetAttrString(distobj, (char *)_name.c_str());
369  nassertv(func != nullptr);
370 
371  PyObject *result;
372  {
373 #ifdef WITHIN_PANDA
374  PStatTimer timer(((DCField *)this)->_field_update_pcollector);
375 #endif
376  result = PyObject_CallObject(func, args);
377  }
378  Py_XDECREF(result);
379  Py_DECREF(func);
380  Py_DECREF(args);
381  }
382  }
383  }
384 }
385 #endif // HAVE_PYTHON
386 
387 #ifdef HAVE_PYTHON
388 /**
389  * Generates a datagram containing the message necessary to send an update for
390  * the indicated distributed object from the client.
391  */
392 Datagram DCField::
393 client_format_update(DOID_TYPE do_id, PyObject *args) const {
394  DCPacker packer;
395 
396  packer.raw_pack_uint16(CLIENT_OBJECT_SET_FIELD);
397  packer.raw_pack_uint32(do_id);
398  packer.raw_pack_uint16(_number);
399 
400  packer.begin_pack(this);
401  pack_args(packer, args);
402  if (!packer.end_pack()) {
403  return Datagram();
404  }
405 
406  return Datagram(packer.get_data(), packer.get_length());
407 }
408 #endif // HAVE_PYTHON
409 
410 #ifdef HAVE_PYTHON
411 /**
412  * Generates a datagram containing the message necessary to send an update for
413  * the indicated distributed object from the AI.
414  */
415 Datagram DCField::
416 ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
417  DCPacker packer;
418 
419  packer.raw_pack_uint8(1);
420  packer.RAW_PACK_CHANNEL(to_id);
421  packer.RAW_PACK_CHANNEL(from_id);
422  packer.raw_pack_uint16(STATESERVER_OBJECT_SET_FIELD);
423  packer.raw_pack_uint32(do_id);
424  packer.raw_pack_uint16(_number);
425 
426  packer.begin_pack(this);
427  pack_args(packer, args);
428  if (!packer.end_pack()) {
429  return Datagram();
430  }
431 
432  return Datagram(packer.get_data(), packer.get_length());
433 }
434 #endif // HAVE_PYTHON
435 
436 #ifdef HAVE_PYTHON
437 /**
438  * Generates a datagram containing the message necessary to send an update,
439  * with the msg type, for the indicated distributed object from the AI.
440  */
441 Datagram DCField::
442 ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
443  DCPacker packer;
444 
445  packer.raw_pack_uint8(1);
446  packer.RAW_PACK_CHANNEL(to_id);
447  packer.RAW_PACK_CHANNEL(from_id);
448  packer.raw_pack_uint16(msg_type);
449  packer.raw_pack_uint32(do_id);
450  packer.raw_pack_uint16(_number);
451 
452  packer.begin_pack(this);
453  pack_args(packer, args);
454  if (!packer.end_pack()) {
455  return Datagram();
456  }
457 
458  return Datagram(packer.get_data(), packer.get_length());
459 }
460 #endif // HAVE_PYTHON
461 
462 
463 /**
464  * Accumulates the properties of this field into the hash.
465  */
466 void DCField::
467 generate_hash(HashGenerator &hashgen) const {
468  // It shouldn't be necessary to explicitly add _number to the hash--this is
469  // computed based on the relative position of this field with the other
470  // fields, so adding it explicitly will be redundant. However, the field
471  // name is significant.
472  hashgen.add_string(_name);
473 
474  // Actually, we add _number anyway, since we need to ensure the hash code
475  // comes out different in the dc_multiple_inheritance case.
476  if (dc_multiple_inheritance) {
477  hashgen.add_int(_number);
478  }
479 }
480 
481 /**
482  * Packs the field's specified default value (or a sensible default if no
483  * value is specified) into the stream. Returns true if the default value is
484  * packed, false if the field doesn't know how to pack its default value.
485  */
486 bool DCField::
487 pack_default_value(DCPackData &pack_data, bool &) const {
488  // The default behavior is to pack the default value if we got it;
489  // otherwise, to return false and let the packer visit our nested elements.
490  if (!_default_value_stale) {
491  pack_data.append_data((const char *)_default_value.data(), _default_value.size());
492  return true;
493  }
494 
495  return false;
496 }
497 
498 /**
499  * Sets the name of this field.
500  */
501 void DCField::
502 set_name(const string &name) {
504  if (_dclass != nullptr) {
505  _dclass->_dc_file->mark_inherited_fields_stale();
506  }
507 }
508 
509 #ifdef HAVE_PYTHON
510 /**
511  * Returns the string representation of the indicated Python object.
512  */
513 string DCField::
514 get_pystr(PyObject *value) {
515  if (value == nullptr) {
516  return "(null)";
517  }
518 
519  PyObject *str = PyObject_Str(value);
520  if (str != nullptr) {
521 #if PY_MAJOR_VERSION >= 3
522  string result = PyUnicode_AsUTF8(str);
523 #else
524  string result = PyString_AsString(str);
525 #endif
526  Py_DECREF(str);
527  return result;
528  }
529 
530  PyObject *repr = PyObject_Repr(value);
531  if (repr != nullptr) {
532 #if PY_MAJOR_VERSION >= 3
533  string result = PyUnicode_AsUTF8(repr);
534 #else
535  string result = PyString_AsString(repr);
536 #endif
537  Py_DECREF(repr);
538  return result;
539  }
540 
541  if (value->ob_type != nullptr) {
542  PyObject *typestr = PyObject_Str((PyObject *)(value->ob_type));
543  if (typestr != nullptr) {
544 #if PY_MAJOR_VERSION >= 3
545  string result = PyUnicode_AsUTF8(typestr);
546 #else
547  string result = PyString_AsString(typestr);
548 #endif
549  Py_DECREF(typestr);
550  return result;
551  }
552  }
553 
554  return "(invalid object)";
555 }
556 #endif // HAVE_PYTHON
557 
558 /**
559  * Recomputes the default value of the field by repacking it.
560  */
561 void DCField::
562 refresh_default_value() {
563  DCPacker packer;
564  packer.begin_pack(this);
565  packer.pack_default_value();
566  if (!packer.end_pack()) {
567  std::cerr << "Error while packing default value for " << get_name() << "\n";
568  } else {
569  const unsigned char *data = (const unsigned char *)packer.get_data();
570  _default_value = vector_uchar(data, data + packer.get_length());
571  }
572  _default_value_stale = false;
573 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
vector_uchar parse_string(const std::string &formatted_string)
Given a human-formatted string (for instance, as returned by format_data(), above) that represents th...
Definition: dcField.cxx:182
bool had_error() const
Returns true if there has been any error (either a pack error or a range error) since the most recent...
Definition: dcPacker.I:563
This is a block of data that receives the results of DCPacker.
Definition: dcPackData.h:22
void append_data(const char *buffer, size_t size)
Adds the indicated bytes to the end of the data.
Definition: dcPackData.I:47
const std::string & get_name() const
Returns the name of this field, or empty string if the field is unnamed.
void dump_hex(std::ostream &out, unsigned int indent=0) const
Writes a representation of the entire datagram contents, as a sequence of hex (and ASCII) values.
Definition: datagram.cxx:44
bool validate_ranges(const vector_uchar &packed_data) const
Verifies that all of the packed values in the field data are within the specified ranges and that the...
Definition: dcField.cxx:203
void add_int(int num)
Adds another integer to the hash so far.
size_t get_num_unpacked_bytes() const
Returns the number of bytes that have been unpacked so far, or after unpack_end(),...
Definition: dcPacker.I:574
A single field of a Distributed Class, either atomic or molecular.
Definition: dcField.h:37
bool has_assert_failed() const
Returns true if an assertion test has failed (and not been ignored) since the last call to clear_asse...
Definition: pnotify.I:29
const DCPackerInterface * get_current_field() const
Returns the field that will be referenced by the next call to pack_*() or unpack_*().
Definition: dcPacker.I:87
void add_string(const std::string &str)
Adds a string to the hash, by breaking it down into a sequence of integers.
void raw_pack_uint8(unsigned int value)
Packs the data into the buffer between packing sessions.
Definition: dcPacker.I:740
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual bool pack_default_value(DCPackData &pack_data, bool &pack_error) const
Packs the field's specified default value (or a sensible default if no value is specified) into the s...
Definition: dcField.cxx:487
virtual DCAtomicField * as_atomic_field()
Returns the same field pointer converted to an atomic field pointer, if this is in fact an atomic fie...
Definition: dcField.cxx:112
Defines a particular DistributedClass as read from an input .dc file.
Definition: dcClass.h:44
void output(std::ostream &out) const
Write a string representation of this instance to <out>.
Definition: dcField.I:141
bool had_pack_error() const
Returns true if there has been an packing error since the most recent call to begin(); in particular,...
Definition: dcPacker.I:539
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:30
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void set_name(const std::string &name)
Sets the name of this field.
virtual void set_name(const std::string &name)
Sets the name of this field.
Definition: dcField.cxx:502
size_t get_length() const
Returns the current length of the buffer.
Definition: dcPacker.I:583
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A single atomic field of a Distributed Class, as read from a .dc file.
Definition: dcAtomicField.h:30
virtual DCMolecularField * as_molecular_field()
Returns the same field pointer converted to a molecular field pointer, if this is in fact a molecular...
Definition: dcField.cxx:130
virtual void generate_hash(HashGenerator &hashgen) const
Accumulates the properties of this field into the hash.
Definition: dcField.cxx:467
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void raw_pack_uint32(unsigned int value)
Packs the data into the buffer between packing sessions.
Definition: dcPacker.I:758
size_t get_unpack_length() const
Returns the total number of bytes in the unpack data buffer.
Definition: dcPacker.I:610
static Notify * ptr()
Returns the pointer to the global Notify object.
Definition: notify.cxx:289
Represents the type specification for a single parameter within a field specification.
Definition: dcParameter.h:35
void set_unpack_data(const vector_uchar &data)
Sets up the unpack_data pointer.
Definition: dcPacker.cxx:117
void pack_default_value()
Adds the default value for the current element into the stream.
Definition: dcPacker.cxx:552
void raw_pack_uint16(unsigned int value)
Packs the data into the buffer between packing sessions.
Definition: dcPacker.I:749
const char * get_data() const
Returns the beginning of the data buffer.
Definition: dcPacker.I:641
This class generates an arbitrary hash number from a sequence of ints.
Definition: hashGenerator.h:24
void mark_inherited_fields_stale()
Indicates that something has changed in one or more of the inheritance chains or the set of fields; t...
Definition: dcFile.I:40
std::string unpack_and_format(bool show_field_names=true)
Unpacks an object and formats its value into a syntax suitable for parsing in the dc file (e....
Definition: dcPacker.cxx:989
void begin_pack(const DCPackerInterface *root)
Begins a packing session.
Definition: dcPacker.cxx:73
std::string format_data(const vector_uchar &packed_data, bool show_field_names=true)
Given a blob that represents the packed data for this field, returns a string formatting it for human...
Definition: dcField.cxx:165
This class can be used for packing a series of numeric and string data into a binary stream,...
Definition: dcPacker.h:34
bool parse_and_pack(const std::string &formatted_object)
Parses an object's value according to the DC file syntax (e.g.
Definition: dcPacker.cxx:960
void unpack_skip()
Skips the current field without unpacking it and advances to the next field.
Definition: dcPacker.cxx:604
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool end_pack()
Finishes a packing session.
Definition: dcPacker.cxx:98
vector_uchar get_bytes() const
Returns the packed data buffer as a bytes object.
Definition: dcPacker.I:599
const char * get_unpack_data() const
Returns a read pointer to the unpack data buffer.
Definition: dcPacker.I:686
A single molecular field of a Distributed Class, as read from a .dc file.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
This defines the internal interface for packing values into a DCField.
void begin_unpack(const DCPackerInterface *root)
Begins an unpacking session.
Definition: dcPacker.cxx:153
bool end_unpack()
Finishes the unpacking session.
Definition: dcPacker.cxx:179
void unpack_validate()
Internally unpacks the current numeric or string value and validates it against the type range limits...
Definition: dcPacker.cxx:578