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  */
112 as_atomic_field() {
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  */
121 as_atomic_field() const {
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  */
139 as_molecular_field() const {
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  */
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  */
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  */
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  */
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 }
A single atomic field of a Distributed Class, as read from a .dc file.
Definition: dcAtomicField.h:30
Defines a particular DistributedClass as read from an input .dc file.
Definition: dcClass.h:44
A single field of a Distributed Class, either atomic or molecular.
Definition: dcField.h:37
std::string format_data(const std::vector< unsigned char > &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
virtual void set_name(const std::string &name)
Sets the name of this field.
Definition: dcField.cxx:502
std::vector< unsigned char > 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
virtual void generate_hash(HashGenerator &hashgen) const
Accumulates the properties of this field into the hash.
Definition: dcField.cxx:467
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
void output(std::ostream &out) const
Write a string representation of this instance to <out>.
Definition: dcField.I:141
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 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
bool validate_ranges(const std::vector< unsigned char > &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 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
A single molecular field of a Distributed Class, as read from a .dc file.
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
This defines the internal interface for packing values into a DCField.
const std::string & get_name() const
Returns the name of this field, or empty string if the field is unnamed.
virtual void set_name(const std::string &name)
Sets the name of this field.
This class can be used for packing a series of numeric and string data into a binary stream,...
Definition: dcPacker.h:34
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
std::vector< unsigned char > get_bytes() const
Returns the packed data buffer as a bytes object.
Definition: dcPacker.I:599
void unpack_validate()
Internally unpacks the current numeric or string value and validates it against the type range limits...
Definition: dcPacker.cxx:578
void begin_pack(const DCPackerInterface *root)
Begins a packing session.
Definition: dcPacker.cxx:73
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 raw_pack_uint32(unsigned int value)
Packs the data into the buffer between packing sessions.
Definition: dcPacker.I:758
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
void raw_pack_uint16(unsigned int value)
Packs the data into the buffer between packing sessions.
Definition: dcPacker.I:749
void raw_pack_uint8(unsigned int value)
Packs the data into the buffer between packing sessions.
Definition: dcPacker.I:740
size_t get_unpack_length() const
Returns the total number of bytes in the unpack data buffer.
Definition: dcPacker.I:610
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
bool end_pack()
Finishes a packing session.
Definition: dcPacker.cxx:98
const char * get_data() const
Returns the beginning of the data buffer.
Definition: dcPacker.I:641
void unpack_skip()
Skips the current field without unpacking it and advances to the next field.
Definition: dcPacker.cxx:604
void set_unpack_data(const std::vector< unsigned char > &data)
Sets up the unpack_data pointer.
Definition: dcPacker.cxx:117
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 pack_default_value()
Adds the default value for the current element into the stream.
Definition: dcPacker.cxx:552
const char * get_unpack_data() const
Returns a read pointer to the unpack data buffer.
Definition: dcPacker.I:686
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
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
size_t get_length() const
Returns the current length of the buffer.
Definition: dcPacker.I:583
Represents the type specification for a single parameter within a field specification.
Definition: dcParameter.h:35
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
This class generates an arbitrary hash number from a sequence of ints.
Definition: hashGenerator.h:23
void add_int(int num)
Adds another integer to the hash so far.
void add_string(const std::string &str)
Adds a string to the hash, by breaking it down into a sequence of integers.
static Notify * ptr()
Returns the pointer to the global Notify object.
Definition: notify.cxx:293
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.