Panda3D
Loading...
Searching...
No Matches
dcClass.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 dcClass.cxx
10 * @author drose
11 * @date 2000-10-05
12 */
13
14#include "dcClass.h"
15#include "dcFile.h"
16#include "dcAtomicField.h"
17#include "hashGenerator.h"
18#include "dcindent.h"
19#include "dcmsgtypes.h"
20
21#include "dcClassParameter.h"
22#include <algorithm>
23
24#ifdef HAVE_PYTHON
25#include "py_panda.h"
26#endif
27
28using std::ostream;
29using std::ostringstream;
30using std::string;
31
32#ifdef WITHIN_PANDA
33#include "pStatTimer.h"
34
35#ifndef CPPPARSER
36PStatCollector DCClass::_update_pcollector("App:Show code:readerPollTask:Update");
37PStatCollector DCClass::_generate_pcollector("App:Show code:readerPollTask:Generate");
38#endif // CPPPARSER
39
40ConfigVariableBool dc_multiple_inheritance
41("dc-multiple-inheritance", true,
42 PRC_DESC("Set this true to support multiple inheritance in the dc file. "
43 "If this is false, the old way, multiple inheritance is not "
44 "supported, but field numbers will be numbered sequentially, "
45 "which may be required to support old code that assumed this."));
46
47ConfigVariableBool dc_virtual_inheritance
48("dc-virtual-inheritance", true,
49 PRC_DESC("Set this true to support proper virtual inheritance in the "
50 "dc file, so that diamond-of-death type constructs can be used. "
51 "This also enables shadowing (overloading) of inherited method "
52 "names from a base class."));
53
54ConfigVariableBool dc_sort_inheritance_by_file
55("dc-sort-inheritance-by-file", true,
56 PRC_DESC("This is a temporary hack. This should be true if you are using "
57 "version 1.42 of the otp_server.exe binary, which sorted inherited "
58 "fields based on the order of the classes within the DC file, "
59 "rather than based on the order in which the references are made "
60 "within the class."));
61
62
63#endif // WITHIN_PANDA
64
65class SortFieldsByIndex {
66public:
67 inline bool operator ()(const DCField *a, const DCField *b) const {
68 return a->get_number() < b->get_number();
69 }
70};
71
72/**
73 *
74 */
75DCClass::
76DCClass(DCFile *dc_file, const string &name, bool is_struct, bool bogus_class) :
77#ifdef WITHIN_PANDA
78 _class_update_pcollector(_update_pcollector, name),
79 _class_generate_pcollector(_generate_pcollector, name),
80#endif
81 _dc_file(dc_file),
82 _name(name),
83 _is_struct(is_struct),
84 _bogus_class(bogus_class)
85{
86 _number = -1;
87 _constructor = nullptr;
88
89#ifdef HAVE_PYTHON
90 _class_def = nullptr;
91 _owner_class_def = nullptr;
92#endif
93}
94
95/**
96 *
97 */
98DCClass::
99~DCClass() {
100 if (_constructor != nullptr) {
101 delete _constructor;
102 }
103
104 Fields::iterator fi;
105 for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
106 delete (*fi);
107 }
108
109#ifdef HAVE_PYTHON
110 Py_XDECREF(_class_def);
111 Py_XDECREF(_owner_class_def);
112#endif
113}
114
115/**
116 *
117 */
118DCClass *DCClass::
119as_class() {
120 return this;
121}
122
123/**
124 *
125 */
126const DCClass *DCClass::
127as_class() const {
128 return this;
129}
130
131/**
132 * Returns the number of base classes this class inherits from.
133 */
135get_num_parents() const {
136 return _parents.size();
137}
138
139/**
140 * Returns the nth parent class this class inherits from.
141 */
143get_parent(int n) const {
144 nassertr(n >= 0 && n < (int)_parents.size(), nullptr);
145 return _parents[n];
146}
147
148/**
149 * Returns true if this class has a constructor method, false if it just uses
150 * the default constructor.
151 */
153has_constructor() const {
154 return (_constructor != nullptr);
155}
156
157/**
158 * Returns the constructor method for this class if it is defined, or NULL if
159 * the class uses the default constructor.
160 */
162get_constructor() const {
163 return _constructor;
164}
165
166/**
167 * Returns the number of fields defined directly in this class, ignoring
168 * inheritance.
169 */
171get_num_fields() const {
172 return _fields.size();
173}
174
175/**
176 * Returns the nth field in the class. This is not necessarily the field with
177 * index n; this is the nth field defined in the class directly, ignoring
178 * inheritance.
179 */
181get_field(int n) const {
182 #ifndef NDEBUG //[
183 if (n < 0 || n >= (int)_fields.size()) {
184 std::cerr << *this << " "
185 << "n:" << n << " _fields.size():"
186 << (int)_fields.size() << std::endl;
187 // __asm { int 3 }
188 }
189 #endif //]
190 nassertr_always(n >= 0 && n < (int)_fields.size(), nullptr);
191 return _fields[n];
192}
193
194/**
195 * Returns a pointer to the DCField that shares the indicated name. If the
196 * named field is not found in the current class, the parent classes will be
197 * searched, so the value returned may not actually be a field within this
198 * class. Returns NULL if there is no such field defined.
199 */
201get_field_by_name(const string &name) const {
202 FieldsByName::const_iterator ni;
203 ni = _fields_by_name.find(name);
204 if (ni != _fields_by_name.end()) {
205 return (*ni).second;
206 }
207
208 // We didn't have such a field, so check our parents.
209 Parents::const_iterator pi;
210 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
211 DCField *result = (*pi)->get_field_by_name(name);
212 if (result != nullptr) {
213 return result;
214 }
215 }
216
217 // Nobody knew what this field is.
218 return nullptr;
219}
220
221/**
222 * Returns a pointer to the DCField that has the indicated index number. If
223 * the numbered field is not found in the current class, the parent classes
224 * will be searched, so the value returned may not actually be a field within
225 * this class. Returns NULL if there is no such field defined.
226 */
228get_field_by_index(int index_number) const {
229 FieldsByIndex::const_iterator ni;
230 ni = _fields_by_index.find(index_number);
231 if (ni != _fields_by_index.end()) {
232 return (*ni).second;
233 }
234
235 // We didn't have such a field, so check our parents.
236 Parents::const_iterator pi;
237 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
238 DCField *result = (*pi)->get_field_by_index(index_number);
239 if (result != nullptr) {
240 // Cache this result for future lookups.
241 ((DCClass *)this)->_fields_by_index[index_number] = result;
242 return result;
243 }
244 }
245
246 // Nobody knew what this field is.
247 return nullptr;
248}
249
250/**
251 * Returns the total number of field fields defined in this class and all
252 * ancestor classes.
253 */
256 if (dc_multiple_inheritance && dc_virtual_inheritance &&
257 _dc_file != nullptr) {
258 _dc_file->check_inherited_fields();
259 if (_inherited_fields.empty()) {
260 ((DCClass *)this)->rebuild_inherited_fields();
261 }
262
263 // This assertion causes trouble when we are only parsing an incomplete DC
264 // file. nassertr(is_bogus_class() || !_inherited_fields.empty(), 0);
265 return (int)_inherited_fields.size();
266
267 } else {
268 int num_fields = get_num_fields();
269
270 Parents::const_iterator pi;
271 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
272 num_fields += (*pi)->get_num_inherited_fields();
273 }
274
275 return num_fields;
276 }
277}
278
279/**
280 * Returns the nth field field in the class and all of its ancestors.
281 *
282 * This *used* to be the same thing as get_field_by_index(), back when the
283 * fields were numbered sequentially within a class's inheritance hierarchy.
284 * Now that fields have a globally unique index number, this is no longer
285 * true.
286 */
288get_inherited_field(int n) const {
289 if (dc_multiple_inheritance && dc_virtual_inheritance &&
290 _dc_file != nullptr) {
291 _dc_file->check_inherited_fields();
292 if (_inherited_fields.empty()) {
293 ((DCClass *)this)->rebuild_inherited_fields();
294 }
295 nassertr(n >= 0 && n < (int)_inherited_fields.size(), nullptr);
296 return _inherited_fields[n];
297
298 } else {
299 Parents::const_iterator pi;
300 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
301 int psize = (*pi)->get_num_inherited_fields();
302 if (n < psize) {
303 return (*pi)->get_inherited_field(n);
304 }
305
306 n -= psize;
307 }
308
309 return get_field(n);
310 }
311}
312
313/**
314 * Returns true if this class, or any class in the inheritance heirarchy for
315 * this class, is a "bogus" class--a forward reference to an as-yet-undefined
316 * class.
317 */
320 if (is_bogus_class()) {
321 return true;
322 }
323
324 Parents::const_iterator pi;
325 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
326 if ((*pi)->inherits_from_bogus_class()) {
327 return true;
328 }
329 }
330
331 return false;
332}
333
334/**
335 * Write a string representation of this instance to <out>.
336 */
338output(ostream &out) const {
339 if (_is_struct) {
340 out << "struct";
341 } else {
342 out << "dclass";
343 }
344 if (!_name.empty()) {
345 out << " " << _name;
346 }
347}
348
349#ifdef HAVE_PYTHON
350/**
351 * Returns true if the DCClass object has an associated Python class
352 * definition, false otherwise.
353 */
354bool DCClass::
355has_class_def() const {
356 return (_class_def != nullptr);
357}
358#endif // HAVE_PYTHON
359
360#ifdef HAVE_PYTHON
361/**
362 * Sets the class object associated with this DistributedClass. This object
363 * will be used to construct new instances of the class.
364 */
365void DCClass::
366set_class_def(PyObject *class_def) {
367 Py_XINCREF(class_def);
368 Py_XDECREF(_class_def);
369 _class_def = class_def;
370}
371#endif // HAVE_PYTHON
372
373#ifdef HAVE_PYTHON
374/**
375 * Returns the class object that was previously associated with this
376 * DistributedClass. This will return a new reference to the object.
377 */
378PyObject *DCClass::
379get_class_def() const {
380 if (_class_def == nullptr) {
381 Py_INCREF(Py_None);
382 return Py_None;
383 }
384
385 Py_INCREF(_class_def);
386 return _class_def;
387}
388#endif // HAVE_PYTHON
389
390#ifdef HAVE_PYTHON
391/**
392 * Returns true if the DCClass object has an associated Python owner class
393 * definition, false otherwise.
394 */
395bool DCClass::
396has_owner_class_def() const {
397 return (_owner_class_def != nullptr);
398}
399#endif // HAVE_PYTHON
400
401#ifdef HAVE_PYTHON
402/**
403 * Sets the owner class object associated with this DistributedClass. This
404 * object will be used to construct new owner instances of the class.
405 */
406void DCClass::
407set_owner_class_def(PyObject *owner_class_def) {
408 Py_XINCREF(owner_class_def);
409 Py_XDECREF(_owner_class_def);
410 _owner_class_def = owner_class_def;
411}
412#endif // HAVE_PYTHON
413
414#ifdef HAVE_PYTHON
415/**
416 * Returns the owner class object that was previously associated with this
417 * DistributedClass. This will return a new reference to the object.
418 */
419PyObject *DCClass::
420get_owner_class_def() const {
421 if (_owner_class_def == nullptr) {
422 Py_INCREF(Py_None);
423 return Py_None;
424 }
425
426 Py_INCREF(_owner_class_def);
427 return _owner_class_def;
428}
429#endif // HAVE_PYTHON
430
431#ifdef HAVE_PYTHON
432/**
433 * Extracts the update message out of the packer and applies it to the
434 * indicated object by calling the appropriate method.
435 */
436void DCClass::
437receive_update(PyObject *distobj, DatagramIterator &di) const {
438#ifdef WITHIN_PANDA
439 PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
440#endif
441 DCPacker packer;
442 const char *data = (const char *)di.get_datagram().get_data();
443 packer.set_unpack_data(data + di.get_current_index(),
444 di.get_remaining_size(), false);
445
446 int field_id = packer.raw_unpack_uint16();
447 DCField *field = get_field_by_index(field_id);
448 if (field == nullptr) {
449 ostringstream strm;
450 strm
451 << "Received update for field " << field_id << ", not in class "
452 << get_name();
453 nassert_raise(strm.str());
454 return;
455 }
456
457 packer.begin_unpack(field);
458 field->receive_update(packer, distobj);
459 packer.end_unpack();
460
462
463}
464#endif // HAVE_PYTHON
465
466#ifdef HAVE_PYTHON
467/**
468 * Processes a big datagram that includes all of the "required" fields that
469 * are sent along with a normal "generate with required" message. This is all
470 * of the atomic fields that are marked "broadcast required".
471 */
472void DCClass::
473receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const {
474#ifdef WITHIN_PANDA
475 PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
476#endif
477 DCPacker packer;
478 const char *data = (const char *)di.get_datagram().get_data();
479 packer.set_unpack_data(data + di.get_current_index(),
480 di.get_remaining_size(), false);
481
482 int num_fields = get_num_inherited_fields();
483 for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
484 DCField *field = get_inherited_field(i);
485 if (field->as_molecular_field() == nullptr &&
486 field->is_required() && field->is_broadcast()) {
487 packer.begin_unpack(field);
488 field->receive_update(packer, distobj);
489 if (!packer.end_unpack()) {
490 break;
491 }
492 }
493 }
494
496}
497#endif // HAVE_PYTHON
498
499#ifdef HAVE_PYTHON
500/**
501 * Processes a big datagram that includes all of the "required" fields that
502 * are sent along with a normal "generate with required" message. This is all
503 * of the atomic fields that are marked "broadcast ownrecv". Should be used
504 * for 'owner-view' objects.
505 */
506void DCClass::
507receive_update_broadcast_required_owner(PyObject *distobj,
508 DatagramIterator &di) const {
509#ifdef WITHIN_PANDA
510 PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
511#endif
512 DCPacker packer;
513 const char *data = (const char *)di.get_datagram().get_data();
514 packer.set_unpack_data(data + di.get_current_index(),
515 di.get_remaining_size(), false);
516
517 int num_fields = get_num_inherited_fields();
518 for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
519 DCField *field = get_inherited_field(i);
520 if (field->as_molecular_field() == nullptr &&
521 field->is_required() && (field->is_ownrecv() || field->is_broadcast())) {
522 packer.begin_unpack(field);
523 field->receive_update(packer, distobj);
524 if (!packer.end_unpack()) {
525 break;
526 }
527 }
528 }
529
531}
532#endif // HAVE_PYTHON
533
534#ifdef HAVE_PYTHON
535/**
536 * Processes a big datagram that includes all of the "required" fields that
537 * are sent when an avatar is created. This is all of the atomic fields that
538 * are marked "required", whether they are broadcast or not.
539 */
540void DCClass::
541receive_update_all_required(PyObject *distobj, DatagramIterator &di) const {
542#ifdef WITHIN_PANDA
543 PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
544#endif
545 DCPacker packer;
546 const char *data = (const char *)di.get_datagram().get_data();
547 packer.set_unpack_data(data + di.get_current_index(),
548 di.get_remaining_size(), false);
549
550 int num_fields = get_num_inherited_fields();
551 for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
552 DCField *field = get_inherited_field(i);
553 if (field->as_molecular_field() == nullptr &&
554 field->is_required()) {
555 packer.begin_unpack(field);
556 field->receive_update(packer, distobj);
557 if (!packer.end_unpack()) {
558 break;
559 }
560 }
561 }
562
564}
565#endif // HAVE_PYTHON
566
567#ifdef HAVE_PYTHON
568/**
569 * Processes a datagram that lists some additional fields that are broadcast
570 * in one chunk.
571 */
572void DCClass::
573receive_update_other(PyObject *distobj, DatagramIterator &di) const {
574#ifdef WITHIN_PANDA
575 PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
576#endif
577 int num_fields = di.get_uint16();
578 for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
579 receive_update(distobj, di);
580 }
581}
582#endif // HAVE_PYTHON
583
584#ifdef HAVE_PYTHON
585/**
586 * Processes an update for a named field from a packed value blob.
587 */
588void DCClass::
589direct_update(PyObject *distobj, const string &field_name,
590 const vector_uchar &value_blob) {
591 DCField *field = get_field_by_name(field_name);
592 nassertv_always(field != nullptr);
593
594 DCPacker packer;
595 packer.set_unpack_data(value_blob);
596 packer.begin_unpack(field);
597 field->receive_update(packer, distobj);
598 packer.end_unpack();
599}
600#endif // HAVE_PYTHON
601
602#ifdef HAVE_PYTHON
603/**
604 * Processes an update for a named field from a packed datagram.
605 */
606void DCClass::
607direct_update(PyObject *distobj, const string &field_name,
608 const Datagram &datagram) {
609 DCField *field = get_field_by_name(field_name);
610 nassertv_always(field != nullptr);
611
612 DCPacker packer;
613 packer.set_unpack_data((const char *)datagram.get_data(), datagram.get_length(), false);
614 packer.begin_unpack(field);
615 field->receive_update(packer, distobj);
616 packer.end_unpack();
617}
618#endif // HAVE_PYTHON
619
620#ifdef HAVE_PYTHON
621/**
622 * Looks up the current value of the indicated field by calling the
623 * appropriate get*() function, then packs that value into the datagram. This
624 * field is presumably either a required field or a specified optional field,
625 * and we are building up a datagram for the generate-with-required message.
626 *
627 * Returns true on success, false on failure.
628 */
629bool DCClass::
630pack_required_field(Datagram &datagram, PyObject *distobj,
631 const DCField *field) const {
632 DCPacker packer;
633 packer.begin_pack(field);
634 if (!pack_required_field(packer, distobj, field)) {
635 return false;
636 }
637 if (!packer.end_pack()) {
638 return false;
639 }
640
641 datagram.append_data(packer.get_data(), packer.get_length());
642 return true;
643}
644#endif // HAVE_PYTHON
645
646#ifdef HAVE_PYTHON
647/**
648 * Looks up the current value of the indicated field by calling the
649 * appropriate get*() function, then packs that value into the packer. This
650 * field is presumably either a required field or a specified optional field,
651 * and we are building up a datagram for the generate-with-required message.
652 *
653 * Returns true on success, false on failure.
654 */
655bool DCClass::
656pack_required_field(DCPacker &packer, PyObject *distobj,
657 const DCField *field) const {
658 const DCParameter *parameter = field->as_parameter();
659 if (parameter != nullptr) {
660 // This is the easy case: to pack a parameter, we just look on the class
661 // object for the data element.
662 string field_name = field->get_name();
663
664 if (!PyObject_HasAttrString(distobj, (char *)field_name.c_str())) {
665 // If the attribute is not defined, but the field has a default value
666 // specified, quietly pack the default value.
667 if (field->has_default_value()) {
668 packer.pack_default_value();
669 return true;
670 }
671
672 // If there is no default value specified, it's an error.
673 ostringstream strm;
674 strm << "Data element " << field_name
675 << ", required by dc file for dclass " << get_name()
676 << ", not defined on object";
677 nassert_raise(strm.str());
678 return false;
679 }
680 PyObject *result =
681 PyObject_GetAttrString(distobj, (char *)field_name.c_str());
682 nassertr(result != nullptr, false);
683
684 // Now pack the value into the datagram.
685 bool pack_ok = parameter->pack_args(packer, result);
686 Py_DECREF(result);
687
688 return pack_ok;
689 }
690
691 if (field->as_molecular_field() != nullptr) {
692 ostringstream strm;
693 strm << "Cannot pack molecular field " << field->get_name()
694 << " for generate";
695 nassert_raise(strm.str());
696 return false;
697 }
698
699 const DCAtomicField *atom = field->as_atomic_field();
700 nassertr(atom != nullptr, false);
701
702 // We need to get the initial value of this field. There isn't a good,
703 // robust way to get this; presently, we just mangle the "setFoo()" name of
704 // the required field into "getFoo()" and call that.
705 string setter_name = atom->get_name();
706
707 if (setter_name.empty()) {
708 ostringstream strm;
709 strm << "Required field is unnamed!";
710 nassert_raise(strm.str());
711 return false;
712 }
713
714 if (atom->get_num_elements() == 0) {
715 // It sure doesn't make sense to have a required field with no parameters.
716 // What data, exactly, is required?
717 ostringstream strm;
718 strm << "Required field " << setter_name << " has no parameters!";
719 nassert_raise(strm.str());
720 return false;
721 }
722
723 string getter_name = setter_name;
724 if (setter_name.substr(0, 3) == "set") {
725 // If the original method started with "set", we mangle this directly to
726 // "get".
727 getter_name[0] = 'g';
728
729 } else {
730 // Otherwise, we add a "get" prefix, and capitalize the next letter.
731 getter_name = "get" + setter_name;
732 getter_name[3] = toupper(getter_name[3]);
733 }
734
735 // Now we have to look up the getter on the distributed object and call it.
736 if (!PyObject_HasAttrString(distobj, (char *)getter_name.c_str())) {
737 // As above, if there's no getter but the field has a default value
738 // specified, quietly pack the default value.
739 if (field->has_default_value()) {
740 packer.pack_default_value();
741 return true;
742 }
743
744 // Otherwise, with no default value it's an error.
745 ostringstream strm;
746 strm << "Distributed class " << get_name()
747 << " doesn't have getter named " << getter_name
748 << " to match required field " << setter_name;
749 nassert_raise(strm.str());
750 return false;
751 }
752 PyObject *func =
753 PyObject_GetAttrString(distobj, (char *)getter_name.c_str());
754 nassertr(func != nullptr, false);
755
756 PyObject *empty_args = PyTuple_New(0);
757 PyObject *result = PyObject_CallObject(func, empty_args);
758 Py_DECREF(empty_args);
759 Py_DECREF(func);
760 if (result == nullptr) {
761 // We don't set this as an exception, since presumably the Python method
762 // itself has already triggered a Python exception.
763 std::cerr << "Error when calling " << getter_name << "\n";
764 return false;
765 }
766
767 if (atom->get_num_elements() == 1) {
768 // In this case, we expect the getter to return one object, which we wrap
769 // up in a tuple.
770 PyObject *tuple = PyTuple_New(1);
771 PyTuple_SET_ITEM(tuple, 0, result);
772 result = tuple;
773
774 } else {
775 // Otherwise, it had better already be a sequence or tuple of some sort.
776 if (!PySequence_Check(result)) {
777 ostringstream strm;
778 strm << "Since dclass " << get_name() << " method " << setter_name
779 << " is declared to have multiple parameters, Python function "
780 << getter_name << " must return a list or tuple.\n";
781 nassert_raise(strm.str());
782 return false;
783 }
784 }
785
786 // Now pack the arguments into the datagram.
787 bool pack_ok = atom->pack_args(packer, result);
788 Py_DECREF(result);
789
790 return pack_ok;
791}
792#endif // HAVE_PYTHON
793
794#ifdef HAVE_PYTHON
795/**
796 * Generates a datagram containing the message necessary to send an update for
797 * the indicated distributed object from the client.
798 */
799Datagram DCClass::
800client_format_update(const string &field_name, DOID_TYPE do_id,
801 PyObject *args) const {
802 DCField *field = get_field_by_name(field_name);
803 if (field == nullptr) {
804 ostringstream strm;
805 strm << "No field named " << field_name << " in class " << get_name()
806 << "\n";
807 nassert_raise(strm.str());
808 return Datagram();
809 }
810
811 return field->client_format_update(do_id, args);
812}
813#endif // HAVE_PYTHON
814
815#ifdef HAVE_PYTHON
816/**
817 * Generates a datagram containing the message necessary to send an update for
818 * the indicated distributed object from the AI.
819 */
820Datagram DCClass::
821ai_format_update(const string &field_name, DOID_TYPE do_id,
822 CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
823 DCField *field = get_field_by_name(field_name);
824 if (field == nullptr) {
825 ostringstream strm;
826 strm << "No field named " << field_name << " in class " << get_name()
827 << "\n";
828 nassert_raise(strm.str());
829 return Datagram();
830 }
831
832 return field->ai_format_update(do_id, to_id, from_id, args);
833}
834#endif // HAVE_PYTHON
835
836#ifdef HAVE_PYTHON
837/**
838 * Generates a datagram containing the message necessary to send an update,
839 * using the indicated msg type for the indicated distributed object from the
840 * AI.
841 */
842Datagram DCClass::
843ai_format_update_msg_type(const string &field_name, DOID_TYPE do_id,
844 CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
845 DCField *field = get_field_by_name(field_name);
846 if (field == nullptr) {
847 ostringstream strm;
848 strm << "No field named " << field_name << " in class " << get_name()
849 << "\n";
850 nassert_raise(strm.str());
851 return Datagram();
852 }
853
854 return field->ai_format_update_msg_type(do_id, to_id, from_id, msg_type, args);
855}
856#endif // HAVE_PYTHON
857
858#ifdef HAVE_PYTHON
859/**
860 * Generates a datagram containing the message necessary to generate a new
861 * distributed object from the client. This requires querying the object for
862 * the initial value of its required fields.
863 *
864 * optional_fields is a list of fieldNames to generate in addition to the
865 * normal required fields.
866 *
867 * This method is only called by the CMU implementation.
868 */
869Datagram DCClass::
870client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
871 ZONEID_TYPE zone_id,
872 PyObject *optional_fields) const {
873 DCPacker packer;
874
875 packer.raw_pack_uint16(CLIENT_OBJECT_GENERATE_CMU);
876
877 packer.raw_pack_uint32(zone_id);
878 packer.raw_pack_uint16(_number);
879 packer.raw_pack_uint32(do_id);
880
881 // Specify all of the required fields.
882 int num_fields = get_num_inherited_fields();
883 for (int i = 0; i < num_fields; ++i) {
884 DCField *field = get_inherited_field(i);
885 if (field->is_required() && field->as_molecular_field() == nullptr) {
886 packer.begin_pack(field);
887 if (!pack_required_field(packer, distobj, field)) {
888 return Datagram();
889 }
890 packer.end_pack();
891 }
892 }
893
894 // Also specify the optional fields.
895 int num_optional_fields = 0;
896 if (PyObject_IsTrue(optional_fields)) {
897 num_optional_fields = PySequence_Size(optional_fields);
898 }
899 packer.raw_pack_uint16(num_optional_fields);
900
901 for (int i = 0; i < num_optional_fields; i++) {
902 PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
903#if PY_MAJOR_VERSION >= 3
904 string field_name = PyUnicode_AsUTF8(py_field_name);
905#else
906 string field_name = PyString_AsString(py_field_name);
907#endif
908 Py_XDECREF(py_field_name);
909
910 DCField *field = get_field_by_name(field_name);
911 if (field == nullptr) {
912 ostringstream strm;
913 strm << "No field named " << field_name << " in class " << get_name()
914 << "\n";
915 nassert_raise(strm.str());
916 return Datagram();
917 }
918 packer.raw_pack_uint16(field->get_number());
919 packer.begin_pack(field);
920 if (!pack_required_field(packer, distobj, field)) {
921 return Datagram();
922 }
923 packer.end_pack();
924 }
925
926 return Datagram(packer.get_data(), packer.get_length());
927}
928#endif // HAVE_PYTHON
929
930#ifdef HAVE_PYTHON
931/**
932 * Generates a datagram containing the message necessary to generate a new
933 * distributed object from the AI. This requires querying the object for the
934 * initial value of its required fields.
935 *
936 * optional_fields is a list of fieldNames to generate in addition to the
937 * normal required fields.
938 */
939Datagram DCClass::
940ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
941 DOID_TYPE parent_id, ZONEID_TYPE zone_id,
942 CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
943 PyObject *optional_fields) const {
944 DCPacker packer;
945
946 packer.raw_pack_uint8(1);
947 packer.RAW_PACK_CHANNEL(district_channel_id);
948 packer.RAW_PACK_CHANNEL(from_channel_id);
949 // packer.raw_pack_uint8('A');
950
951 bool has_optional_fields = (PyObject_IsTrue(optional_fields) != 0);
952
953 if (has_optional_fields) {
954 packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER);
955 } else {
956 packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED);
957 }
958
959 packer.raw_pack_uint32(do_id);
960 // Parent is a bit overloaded; this parent is not about inheritance, this
961 // one is about the visibility container parent, i.e. the zone parent:
962 packer.raw_pack_uint32(parent_id);
963 packer.raw_pack_uint32(zone_id);
964 packer.raw_pack_uint16(_number);
965
966 // Specify all of the required fields.
967 int num_fields = get_num_inherited_fields();
968 for (int i = 0; i < num_fields; ++i) {
969 DCField *field = get_inherited_field(i);
970 if (field->is_required() && field->as_molecular_field() == nullptr) {
971 packer.begin_pack(field);
972 if (!pack_required_field(packer, distobj, field)) {
973 return Datagram();
974 }
975 packer.end_pack();
976 }
977 }
978
979 // Also specify the optional fields.
980 if (has_optional_fields) {
981 int num_optional_fields = PySequence_Size(optional_fields);
982 packer.raw_pack_uint16(num_optional_fields);
983
984 for (int i = 0; i < num_optional_fields; ++i) {
985 PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
986#if PY_MAJOR_VERSION >= 3
987 string field_name = PyUnicode_AsUTF8(py_field_name);
988#else
989 string field_name = PyString_AsString(py_field_name);
990#endif
991 Py_XDECREF(py_field_name);
992
993 DCField *field = get_field_by_name(field_name);
994 if (field == nullptr) {
995 ostringstream strm;
996 strm << "No field named " << field_name << " in class " << get_name()
997 << "\n";
998 nassert_raise(strm.str());
999 return Datagram();
1000 }
1001
1002 packer.raw_pack_uint16(field->get_number());
1003
1004 packer.begin_pack(field);
1005 if (!pack_required_field(packer, distobj, field)) {
1006 return Datagram();
1007 }
1008 packer.end_pack();
1009 }
1010 }
1011
1012 return Datagram(packer.get_data(), packer.get_length());
1013}
1014#endif // HAVE_PYTHON
1015
1016/**
1017 * Write a string representation of this instance to <out>.
1018 */
1020output(ostream &out, bool brief) const {
1021 output_instance(out, brief, "", "", "");
1022}
1023
1024/**
1025 * Generates a parseable description of the object to the indicated output
1026 * stream.
1027 */
1029write(ostream &out, bool brief, int indent_level) const {
1030 indent(out, indent_level);
1031 if (_is_struct) {
1032 out << "struct";
1033 } else {
1034 out << "dclass";
1035 }
1036 if (!_name.empty()) {
1037 out << " " << _name;
1038 }
1039
1040 if (!_parents.empty()) {
1041 Parents::const_iterator pi = _parents.begin();
1042 out << " : " << (*pi)->_name;
1043 ++pi;
1044 while (pi != _parents.end()) {
1045 out << ", " << (*pi)->_name;
1046 ++pi;
1047 }
1048 }
1049
1050 out << " {";
1051 if (!brief && _number >= 0) {
1052 out << " // index " << _number;
1053 }
1054 out << "\n";
1055
1056 if (_constructor != nullptr) {
1057 _constructor->write(out, brief, indent_level + 2);
1058 }
1059
1060 Fields::const_iterator fi;
1061 for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
1062 if (!(*fi)->is_bogus_field()) {
1063 (*fi)->write(out, brief, indent_level + 2);
1064
1065 /*
1066 if (true || (*fi)->has_default_value()) {
1067 indent(out, indent_level + 2) << "// = ";
1068 DCPacker packer;
1069 packer.set_unpack_data((*fi)->get_default_value());
1070 packer.begin_unpack(*fi);
1071 packer.unpack_and_format(out, false);
1072 if (!packer.end_unpack()) {
1073 out << "<error>";
1074 }
1075 out << "\n";
1076 }
1077 */
1078 }
1079 }
1080
1081 indent(out, indent_level) << "};\n";
1082}
1083
1084/**
1085 * Generates a parseable description of the object to the indicated output
1086 * stream.
1087 */
1089output_instance(ostream &out, bool brief, const string &prename,
1090 const string &name, const string &postname) const {
1091 if (_is_struct) {
1092 out << "struct";
1093 } else {
1094 out << "dclass";
1095 }
1096 if (!_name.empty()) {
1097 out << " " << _name;
1098 }
1099
1100 if (!_parents.empty()) {
1101 Parents::const_iterator pi = _parents.begin();
1102 out << " : " << (*pi)->_name;
1103 ++pi;
1104 while (pi != _parents.end()) {
1105 out << ", " << (*pi)->_name;
1106 ++pi;
1107 }
1108 }
1109
1110 out << " {";
1111
1112 if (_constructor != nullptr) {
1113 _constructor->output(out, brief);
1114 out << "; ";
1115 }
1116
1117 Fields::const_iterator fi;
1118 for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
1119 if (!(*fi)->is_bogus_field()) {
1120 (*fi)->output(out, brief);
1121 out << "; ";
1122 }
1123 }
1124
1125 out << "}";
1126 if (!prename.empty() || !name.empty() || !postname.empty()) {
1127 out << " " << prename << name << postname;
1128 }
1129}
1130
1131/**
1132 * Accumulates the properties of this class into the hash.
1133 */
1135generate_hash(HashGenerator &hashgen) const {
1136 hashgen.add_string(_name);
1137
1138 if (is_struct()) {
1139 hashgen.add_int(1);
1140 }
1141
1142 hashgen.add_int(_parents.size());
1143 Parents::const_iterator pi;
1144 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
1145 hashgen.add_int((*pi)->get_number());
1146 }
1147
1148 if (_constructor != nullptr) {
1149 _constructor->generate_hash(hashgen);
1150 }
1151
1152 hashgen.add_int(_fields.size());
1153 Fields::const_iterator fi;
1154 for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
1155 (*fi)->generate_hash(hashgen);
1156 }
1157}
1158
1159/**
1160 * Empties the list of inherited fields for the class, so that it may be
1161 * rebuilt. This is normally only called by
1162 * DCFile::rebuild_inherited_fields().
1163 */
1166 _inherited_fields.clear();
1167}
1168
1169/**
1170 * Recomputes the list of inherited fields for the class.
1171 */
1174 typedef pset<string> Names;
1175 Names names;
1176
1177 _inherited_fields.clear();
1178
1179 // First, all of the inherited fields from our parent are at the top of the
1180 // list.
1181 Parents::const_iterator pi;
1182 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
1183 const DCClass *parent = (*pi);
1184 int num_inherited_fields = parent->get_num_inherited_fields();
1185 for (int i = 0; i < num_inherited_fields; ++i) {
1186 DCField *field = parent->get_inherited_field(i);
1187 if (field->get_name().empty()) {
1188 // Unnamed fields are always inherited. Except in the hack case.
1189 if (!dc_sort_inheritance_by_file) {
1190 _inherited_fields.push_back(field);
1191 }
1192
1193 } else {
1194 bool inserted = names.insert(field->get_name()).second;
1195 if (inserted) {
1196 // The earlier parent shadows the later parent.
1197 _inherited_fields.push_back(field);
1198 }
1199 }
1200 }
1201 }
1202
1203 // Now add the local fields at the end of the list. If any fields in this
1204 // list were already defined by a parent, we will shadow the parent
1205 // definition (that is, remove the parent's field from our list of inherited
1206 // fields).
1207 Fields::const_iterator fi;
1208 for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
1209 DCField *field = (*fi);
1210 if (field->get_name().empty()) {
1211 // Unnamed fields are always added.
1212 _inherited_fields.push_back(field);
1213
1214 } else {
1215 bool inserted = names.insert(field->get_name()).second;
1216 if (!inserted) {
1217 // This local field shadows an inherited field. Remove the parent's
1218 // field from our list.
1219 shadow_inherited_field(field->get_name());
1220 }
1221
1222 // Now add the local field.
1223 _inherited_fields.push_back(field);
1224 }
1225 }
1226
1227 if (dc_sort_inheritance_by_file) {
1228 // Temporary hack.
1229 sort(_inherited_fields.begin(), _inherited_fields.end(), SortFieldsByIndex());
1230 }
1231}
1232
1233/**
1234 * This is called only by rebuild_inherited_fields(). It removes the named
1235 * field from the list of _inherited_fields, presumably in preparation for
1236 * adding a new definition below.
1237 */
1238void DCClass::
1239shadow_inherited_field(const string &name) {
1240 Fields::iterator fi;
1241 for (fi = _inherited_fields.begin(); fi != _inherited_fields.end(); ++fi) {
1242 DCField *field = (*fi);
1243 if (field->get_name() == name) {
1244 _inherited_fields.erase(fi);
1245 return;
1246 }
1247 }
1248
1249 // If we get here, the named field wasn't in the list. Huh.
1250 nassert_raise("named field not in list");
1251}
1252
1253/**
1254 * Adds the newly-allocated field to the class. The class becomes the owner
1255 * of the pointer and will delete it when it destructs. Returns true if the
1256 * field is successfully added, or false if there was a name conflict or some
1257 * other problem.
1258 */
1260add_field(DCField *field) {
1261 nassertr(field->get_class() == this || field->get_class() == nullptr, false);
1262 field->set_class(this);
1263 if (_dc_file != nullptr) {
1264 _dc_file->mark_inherited_fields_stale();
1265 }
1266
1267 if (!field->get_name().empty()) {
1268 if (field->get_name() == _name) {
1269 // This field is a constructor.
1270 if (_constructor != nullptr) {
1271 // We already have a constructor.
1272 return false;
1273 }
1274 if (field->as_atomic_field() == nullptr) {
1275 // The constructor must be an atomic field.
1276 return false;
1277 }
1278 _constructor = field;
1279 _fields_by_name.insert
1280 (FieldsByName::value_type(field->get_name(), field));
1281 return true;
1282 }
1283
1284 bool inserted = _fields_by_name.insert
1285 (FieldsByName::value_type(field->get_name(), field)).second;
1286
1287 if (!inserted) {
1288 return false;
1289 }
1290 }
1291
1292 if (_dc_file != nullptr &&
1293 ((dc_virtual_inheritance && dc_sort_inheritance_by_file) || !is_struct())) {
1294 if (dc_multiple_inheritance) {
1295 _dc_file->set_new_index_number(field);
1296 } else {
1298 }
1299
1300 bool inserted = _fields_by_index.insert
1301 (FieldsByIndex::value_type(field->get_number(), field)).second;
1302
1303 // It shouldn't be possible for that to fail.
1304 nassertr(inserted, false);
1305 }
1306
1307 _fields.push_back(field);
1308 return true;
1309}
1310
1311/**
1312 * Adds a new parent to the inheritance hierarchy of the class. This is
1313 * normally called only during parsing.
1314 */
1316add_parent(DCClass *parent) {
1317 _parents.push_back(parent);
1318 _dc_file->mark_inherited_fields_stale();
1319}
1320
1321/**
1322 * Assigns the unique number to this class. This is normally called only by
1323 * the DCFile interface as the class is added.
1324 */
1326set_number(int number) {
1327 _number = number;
1328}
This is a convenience class to specialize ConfigVariable as a boolean type.
A single atomic field of a Distributed Class, as read from a .dc file.
int get_num_elements() const
Returns the number of elements (parameters) of the atomic field.
Defines a particular DistributedClass as read from an input .dc file.
Definition dcClass.h:44
virtual void output(std::ostream &out) const
Write a string representation of this instance to <out>.
Definition dcClass.cxx:338
virtual void write(std::ostream &out, bool brief, int indent_level) const
Generates a parseable description of the object to the indicated output stream.
Definition dcClass.cxx:1029
int get_num_fields() const
Returns the number of fields defined directly in this class, ignoring inheritance.
Definition dcClass.cxx:171
DCField * get_field(int n) const
Returns the nth field in the class.
Definition dcClass.cxx:181
void generate_hash(HashGenerator &hashgen) const
Accumulates the properties of this class into the hash.
Definition dcClass.cxx:1135
bool is_struct() const
Returns true if the class has been identified with the "struct" keyword in the dc file,...
Definition dcClass.I:44
DCField * get_inherited_field(int n) const
Returns the nth field field in the class and all of its ancestors.
Definition dcClass.cxx:288
DCClass * get_parent(int n) const
Returns the nth parent class this class inherits from.
Definition dcClass.cxx:143
DCField * get_constructor() const
Returns the constructor method for this class if it is defined, or NULL if the class uses the default...
Definition dcClass.cxx:162
void set_number(int number)
Assigns the unique number to this class.
Definition dcClass.cxx:1326
void add_parent(DCClass *parent)
Adds a new parent to the inheritance hierarchy of the class.
Definition dcClass.cxx:1316
bool add_field(DCField *field)
Adds the newly-allocated field to the class.
Definition dcClass.cxx:1260
bool has_constructor() const
Returns true if this class has a constructor method, false if it just uses the default constructor.
Definition dcClass.cxx:153
void output_instance(std::ostream &out, bool brief, const std::string &prename, const std::string &name, const std::string &postname) const
Generates a parseable description of the object to the indicated output stream.
Definition dcClass.cxx:1089
bool is_bogus_class() const
Returns true if the class has been flagged as a bogus class.
Definition dcClass.I:55
int get_num_inherited_fields() const
Returns the total number of field fields defined in this class and all ancestor classes.
Definition dcClass.cxx:255
void rebuild_inherited_fields()
Recomputes the list of inherited fields for the class.
Definition dcClass.cxx:1173
void clear_inherited_fields()
Empties the list of inherited fields for the class, so that it may be rebuilt.
Definition dcClass.cxx:1165
const std::string & get_name() const
Returns the name of this class.
Definition dcClass.I:26
int get_num_parents() const
Returns the number of base classes this class inherits from.
Definition dcClass.cxx:135
DCField * get_field_by_index(int index_number) const
Returns a pointer to the DCField that has the indicated index number.
Definition dcClass.cxx:228
bool inherits_from_bogus_class() const
Returns true if this class, or any class in the inheritance heirarchy for this class,...
Definition dcClass.cxx:319
DCField * get_field_by_name(const std::string &name) const
Returns a pointer to the DCField that shares the indicated name.
Definition dcClass.cxx:201
A single field of a Distributed Class, either atomic or molecular.
Definition dcField.h:37
bool has_default_value() const
Returns true if a default value has been explicitly established for this field, false otherwise.
Definition dcField.I:36
void set_number(int number)
Assigns the unique number to this field.
Definition dcField.I:158
DCClass * get_class() const
Returns the DCClass pointer for the class that contains this field.
Definition dcField.I:27
bool is_broadcast() const
Returns true if the "broadcast" flag is set for this field, false otherwise.
Definition dcField.I:77
int get_number() const
Returns a unique index number associated with this field.
Definition dcField.I:19
bool is_required() const
Returns true if the "required" flag is set for this field, false otherwise.
Definition dcField.I:68
void write(std::ostream &out, int indent_level) const
Write a string representation of this instance to <out>.
Definition dcField.I:149
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
void set_class(DCClass *dclass)
Assigns the class pointer to this field.
Definition dcField.I:167
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
bool is_ownrecv() const
Returns true if the "ownrecv" flag is set for this field, false otherwise.
Definition dcField.I:125
Represents the complete list of Distributed Class descriptions as read from a .dc file.
Definition dcFile.h:32
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
void set_new_index_number(DCField *field)
Sets the next sequential available index number on the indicated field.
Definition dcFile.cxx:593
void check_inherited_fields()
Rebuilds all of the inherited fields tables, if necessary.
Definition dcFile.I:28
const std::string & get_name() const
Returns the name of this field, or empty string if the field is unnamed.
This class can be used for packing a series of numeric and string data into a binary stream,...
Definition dcPacker.h:34
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
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
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 set_unpack_data(const std::vector< unsigned char > &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
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
unsigned int raw_unpack_uint16()
Unpacks the data from the buffer between unpacking sessions.
Definition dcPacker.I:897
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
A class to retrieve the individual data elements previously stored in a Datagram.
void skip_bytes(size_t size)
Skips over the indicated number of bytes in the datagram.
uint16_t get_uint16()
Extracts an unsigned 16-bit integer.
const Datagram & get_datagram() const
Return the datagram of this iterator.
size_t get_current_index() const
Returns the current position within the datagram of the next piece of data to extract.
size_t get_remaining_size() const
Return the bytes left in the datagram.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition datagram.h:38
size_t get_length() const
Returns the number of bytes in the datagram.
Definition datagram.I:335
void append_data(const void *data, size_t size)
Appends some more raw data to the end of the datagram.
Definition datagram.cxx:129
const void * get_data() const
Returns a pointer to the beginning of the datagram's data.
Definition datagram.I:327
This class generates an arbitrary hash number from a sequence of ints.
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.
A lightweight class that represents a single element that may be timed and/or counted via stats.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition pStatTimer.h:30
This is our own Panda specialization on the default STL set.
Definition pset.h:49
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.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.