瀏覽代碼

dcparser: move Python-specific code to extension files

We can now build dcparser without linking to Python and dcparse.exe's dependency on pystub is removed.

Co-authored-by: rdb <[email protected]>
Sam Edwards 6 年之前
父節點
當前提交
6c3013a29c

+ 5 - 692
direct/src/dcparser/dcClass.cxx

@@ -16,25 +16,13 @@
 #include "dcAtomicField.h"
 #include "hashGenerator.h"
 #include "dcindent.h"
-#include "dcmsgtypes.h"
-
-#include "datagram.h"
-#include "datagramIterator.h"
 
 #include "dcClassParameter.h"
 #include <algorithm>
 
-#ifdef HAVE_PYTHON
-#include "py_panda.h"
-#endif
-
-using std::ostream;
-using std::ostringstream;
 using std::string;
 
 #ifdef WITHIN_PANDA
-#include "pStatTimer.h"
-
 #ifndef CPPPARSER
 PStatCollector DCClass::_update_pcollector("App:Show code:readerPollTask:Update");
 PStatCollector DCClass::_generate_pcollector("App:Show code:readerPollTask:Generate");
@@ -89,10 +77,7 @@ DCClass(DCFile *dc_file, const string &name, bool is_struct, bool bogus_class) :
   _number = -1;
   _constructor = nullptr;
 
-#ifdef HAVE_PYTHON
-  _class_def = nullptr;
-  _owner_class_def = nullptr;
-#endif
+  _python_class_defs = nullptr;
 }
 
 /**
@@ -108,11 +93,6 @@ DCClass::
   for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
     delete (*fi);
   }
-
-#ifdef HAVE_PYTHON
-  Py_XDECREF(_class_def);
-  Py_XDECREF(_owner_class_def);
-#endif
 }
 
 /**
@@ -338,7 +318,7 @@ inherits_from_bogus_class() const {
  * Write a string representation of this instance to <out>.
  */
 void DCClass::
-output(ostream &out) const {
+output(std::ostream &out) const {
   if (_is_struct) {
     out << "struct";
   } else {
@@ -349,678 +329,11 @@ output(ostream &out) const {
   }
 }
 
-#ifdef HAVE_PYTHON
-/**
- * Returns true if the DCClass object has an associated Python class
- * definition, false otherwise.
- */
-bool DCClass::
-has_class_def() const {
-  return (_class_def != nullptr);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Sets the class object associated with this DistributedClass.  This object
- * will be used to construct new instances of the class.
- */
-void DCClass::
-set_class_def(PyObject *class_def) {
-  Py_XINCREF(class_def);
-  Py_XDECREF(_class_def);
-  _class_def = class_def;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Returns the class object that was previously associated with this
- * DistributedClass.  This will return a new reference to the object.
- */
-PyObject *DCClass::
-get_class_def() const {
-  if (_class_def == nullptr) {
-    Py_INCREF(Py_None);
-    return Py_None;
-  }
-
-  Py_INCREF(_class_def);
-  return _class_def;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Returns true if the DCClass object has an associated Python owner class
- * definition, false otherwise.
- */
-bool DCClass::
-has_owner_class_def() const {
-  return (_owner_class_def != nullptr);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Sets the owner class object associated with this DistributedClass.  This
- * object will be used to construct new owner instances of the class.
- */
-void DCClass::
-set_owner_class_def(PyObject *owner_class_def) {
-  Py_XINCREF(owner_class_def);
-  Py_XDECREF(_owner_class_def);
-  _owner_class_def = owner_class_def;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Returns the owner class object that was previously associated with this
- * DistributedClass.  This will return a new reference to the object.
- */
-PyObject *DCClass::
-get_owner_class_def() const {
-  if (_owner_class_def == nullptr) {
-    Py_INCREF(Py_None);
-    return Py_None;
-  }
-
-  Py_INCREF(_owner_class_def);
-  return _owner_class_def;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Extracts the update message out of the packer and applies it to the
- * indicated object by calling the appropriate method.
- */
-void DCClass::
-receive_update(PyObject *distobj, DatagramIterator &di) const {
-#ifdef WITHIN_PANDA
-  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
-#endif
-    DCPacker packer;
-    const char *data = (const char *)di.get_datagram().get_data();
-    packer.set_unpack_data(data + di.get_current_index(),
-                           di.get_remaining_size(), false);
-
-    int field_id = packer.raw_unpack_uint16();
-    DCField *field = get_field_by_index(field_id);
-    if (field == nullptr) {
-            ostringstream strm;
-            strm
-                << "Received update for field " << field_id << ", not in class "
-                << get_name();
-            nassert_raise(strm.str());
-            return;
-    }
-
-    packer.begin_unpack(field);
-    field->receive_update(packer, distobj);
-    packer.end_unpack();
-
-    di.skip_bytes(packer.get_num_unpacked_bytes());
-
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes a big datagram that includes all of the "required" fields that
- * are sent along with a normal "generate with required" message.  This is all
- * of the atomic fields that are marked "broadcast required".
- */
-void DCClass::
-receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const {
-#ifdef WITHIN_PANDA
-  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
-#endif
-  DCPacker packer;
-  const char *data = (const char *)di.get_datagram().get_data();
-  packer.set_unpack_data(data + di.get_current_index(),
-                         di.get_remaining_size(), false);
-
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->as_molecular_field() == nullptr &&
-        field->is_required() && field->is_broadcast()) {
-      packer.begin_unpack(field);
-      field->receive_update(packer, distobj);
-      if (!packer.end_unpack()) {
-        break;
-      }
-    }
-  }
-
-  di.skip_bytes(packer.get_num_unpacked_bytes());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes a big datagram that includes all of the "required" fields that
- * are sent along with a normal "generate with required" message.  This is all
- * of the atomic fields that are marked "broadcast ownrecv". Should be used
- * for 'owner-view' objects.
- */
-void DCClass::
-receive_update_broadcast_required_owner(PyObject *distobj,
-                                        DatagramIterator &di) const {
-#ifdef WITHIN_PANDA
-  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
-#endif
-  DCPacker packer;
-  const char *data = (const char *)di.get_datagram().get_data();
-  packer.set_unpack_data(data + di.get_current_index(),
-                         di.get_remaining_size(), false);
-
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->as_molecular_field() == nullptr &&
-        field->is_required() && (field->is_ownrecv() || field->is_broadcast())) {
-      packer.begin_unpack(field);
-      field->receive_update(packer, distobj);
-      if (!packer.end_unpack()) {
-        break;
-      }
-    }
-  }
-
-  di.skip_bytes(packer.get_num_unpacked_bytes());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes a big datagram that includes all of the "required" fields that
- * are sent when an avatar is created.  This is all of the atomic fields that
- * are marked "required", whether they are broadcast or not.
- */
-void DCClass::
-receive_update_all_required(PyObject *distobj, DatagramIterator &di) const {
-#ifdef WITHIN_PANDA
-  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
-#endif
-  DCPacker packer;
-  const char *data = (const char *)di.get_datagram().get_data();
-  packer.set_unpack_data(data + di.get_current_index(),
-                         di.get_remaining_size(), false);
-
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->as_molecular_field() == nullptr &&
-        field->is_required()) {
-      packer.begin_unpack(field);
-      field->receive_update(packer, distobj);
-      if (!packer.end_unpack()) {
-        break;
-      }
-    }
-  }
-
-  di.skip_bytes(packer.get_num_unpacked_bytes());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes a datagram that lists some additional fields that are broadcast
- * in one chunk.
- */
-void DCClass::
-receive_update_other(PyObject *distobj, DatagramIterator &di) const {
-#ifdef WITHIN_PANDA
-  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
-#endif
-  int num_fields = di.get_uint16();
-  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
-    receive_update(distobj, di);
-  }
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes an update for a named field from a packed value blob.
- */
-void DCClass::
-direct_update(PyObject *distobj, const string &field_name,
-              const vector_uchar &value_blob) {
-  DCField *field = get_field_by_name(field_name);
-  nassertv_always(field != nullptr);
-
-  DCPacker packer;
-  packer.set_unpack_data(value_blob);
-  packer.begin_unpack(field);
-  field->receive_update(packer, distobj);
-  packer.end_unpack();
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes an update for a named field from a packed datagram.
- */
-void DCClass::
-direct_update(PyObject *distobj, const string &field_name,
-              const Datagram &datagram) {
-  DCField *field = get_field_by_name(field_name);
-  nassertv_always(field != nullptr);
-
-  DCPacker packer;
-  packer.set_unpack_data((const char *)datagram.get_data(), datagram.get_length(), false);
-  packer.begin_unpack(field);
-  field->receive_update(packer, distobj);
-  packer.end_unpack();
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Looks up the current value of the indicated field by calling the
- * appropriate get*() function, then packs that value into the datagram.  This
- * field is presumably either a required field or a specified optional field,
- * and we are building up a datagram for the generate-with-required message.
- *
- * Returns true on success, false on failure.
- */
-bool DCClass::
-pack_required_field(Datagram &datagram, PyObject *distobj,
-                    const DCField *field) const {
-  DCPacker packer;
-  packer.begin_pack(field);
-  if (!pack_required_field(packer, distobj, field)) {
-    return false;
-  }
-  if (!packer.end_pack()) {
-    return false;
-  }
-
-  datagram.append_data(packer.get_data(), packer.get_length());
-  return true;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Looks up the current value of the indicated field by calling the
- * appropriate get*() function, then packs that value into the packer.  This
- * field is presumably either a required field or a specified optional field,
- * and we are building up a datagram for the generate-with-required message.
- *
- * Returns true on success, false on failure.
- */
-bool DCClass::
-pack_required_field(DCPacker &packer, PyObject *distobj,
-                    const DCField *field) const {
-  const DCParameter *parameter = field->as_parameter();
-  if (parameter != nullptr) {
-    // This is the easy case: to pack a parameter, we just look on the class
-    // object for the data element.
-    string field_name = field->get_name();
-
-    if (!PyObject_HasAttrString(distobj, (char *)field_name.c_str())) {
-      // If the attribute is not defined, but the field has a default value
-      // specified, quietly pack the default value.
-      if (field->has_default_value()) {
-        packer.pack_default_value();
-        return true;
-      }
-
-      // If there is no default value specified, it's an error.
-      ostringstream strm;
-      strm << "Data element " << field_name
-           << ", required by dc file for dclass " << get_name()
-           << ", not defined on object";
-      nassert_raise(strm.str());
-      return false;
-    }
-    PyObject *result =
-      PyObject_GetAttrString(distobj, (char *)field_name.c_str());
-    nassertr(result != nullptr, false);
-
-    // Now pack the value into the datagram.
-    bool pack_ok = parameter->pack_args(packer, result);
-    Py_DECREF(result);
-
-    return pack_ok;
-  }
-
-  if (field->as_molecular_field() != nullptr) {
-    ostringstream strm;
-    strm << "Cannot pack molecular field " << field->get_name()
-         << " for generate";
-    nassert_raise(strm.str());
-    return false;
-  }
-
-  const DCAtomicField *atom = field->as_atomic_field();
-  nassertr(atom != nullptr, false);
-
-  // We need to get the initial value of this field.  There isn't a good,
-  // robust way to get this; presently, we just mangle the "setFoo()" name of
-  // the required field into "getFoo()" and call that.
-  string setter_name = atom->get_name();
-
-  if (setter_name.empty()) {
-    ostringstream strm;
-    strm << "Required field is unnamed!";
-    nassert_raise(strm.str());
-    return false;
-  }
-
-  if (atom->get_num_elements() == 0) {
-    // It sure doesn't make sense to have a required field with no parameters.
-    // What data, exactly, is required?
-    ostringstream strm;
-    strm << "Required field " << setter_name << " has no parameters!";
-    nassert_raise(strm.str());
-    return false;
-  }
-
-  string getter_name = setter_name;
-  if (setter_name.substr(0, 3) == "set") {
-    // If the original method started with "set", we mangle this directly to
-    // "get".
-    getter_name[0] = 'g';
-
-  } else {
-    // Otherwise, we add a "get" prefix, and capitalize the next letter.
-    getter_name = "get" + setter_name;
-    getter_name[3] = toupper(getter_name[3]);
-  }
-
-  // Now we have to look up the getter on the distributed object and call it.
-  if (!PyObject_HasAttrString(distobj, (char *)getter_name.c_str())) {
-    // As above, if there's no getter but the field has a default value
-    // specified, quietly pack the default value.
-    if (field->has_default_value()) {
-      packer.pack_default_value();
-      return true;
-    }
-
-    // Otherwise, with no default value it's an error.
-    ostringstream strm;
-    strm << "Distributed class " << get_name()
-         << " doesn't have getter named " << getter_name
-         << " to match required field " << setter_name;
-    nassert_raise(strm.str());
-    return false;
-  }
-  PyObject *func =
-    PyObject_GetAttrString(distobj, (char *)getter_name.c_str());
-  nassertr(func != nullptr, false);
-
-  PyObject *empty_args = PyTuple_New(0);
-  PyObject *result = PyObject_CallObject(func, empty_args);
-  Py_DECREF(empty_args);
-  Py_DECREF(func);
-  if (result == nullptr) {
-    // We don't set this as an exception, since presumably the Python method
-    // itself has already triggered a Python exception.
-    std::cerr << "Error when calling " << getter_name << "\n";
-    return false;
-  }
-
-  if (atom->get_num_elements() == 1) {
-    // In this case, we expect the getter to return one object, which we wrap
-    // up in a tuple.
-    PyObject *tuple = PyTuple_New(1);
-    PyTuple_SET_ITEM(tuple, 0, result);
-    result = tuple;
-
-  } else {
-    // Otherwise, it had better already be a sequence or tuple of some sort.
-    if (!PySequence_Check(result)) {
-      ostringstream strm;
-      strm << "Since dclass " << get_name() << " method " << setter_name
-           << " is declared to have multiple parameters, Python function "
-           << getter_name << " must return a list or tuple.\n";
-      nassert_raise(strm.str());
-      return false;
-    }
-  }
-
-  // Now pack the arguments into the datagram.
-  bool pack_ok = atom->pack_args(packer, result);
-  Py_DECREF(result);
-
-  return pack_ok;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update for
- * the indicated distributed object from the client.
- */
-Datagram DCClass::
-client_format_update(const string &field_name, DOID_TYPE do_id,
-                     PyObject *args) const {
-  DCField *field = get_field_by_name(field_name);
-  if (field == nullptr) {
-    ostringstream strm;
-    strm << "No field named " << field_name << " in class " << get_name()
-         << "\n";
-    nassert_raise(strm.str());
-    return Datagram();
-  }
-
-  return field->client_format_update(do_id, args);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update for
- * the indicated distributed object from the AI.
- */
-Datagram DCClass::
-ai_format_update(const string &field_name, DOID_TYPE do_id,
-                 CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
-  DCField *field = get_field_by_name(field_name);
-  if (field == nullptr) {
-    ostringstream strm;
-    strm << "No field named " << field_name << " in class " << get_name()
-         << "\n";
-    nassert_raise(strm.str());
-    return Datagram();
-  }
-
-  return field->ai_format_update(do_id, to_id, from_id, args);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update,
- * using the indicated msg type for the indicated distributed object from the
- * AI.
- */
-Datagram DCClass::
-ai_format_update_msg_type(const string &field_name, DOID_TYPE do_id,
-                 CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
-  DCField *field = get_field_by_name(field_name);
-  if (field == nullptr) {
-    ostringstream strm;
-    strm << "No field named " << field_name << " in class " << get_name()
-         << "\n";
-    nassert_raise(strm.str());
-    return Datagram();
-  }
-
-  return field->ai_format_update_msg_type(do_id, to_id, from_id, msg_type, args);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to generate a new
- * distributed object from the client.  This requires querying the object for
- * the initial value of its required fields.
- *
- * optional_fields is a list of fieldNames to generate in addition to the
- * normal required fields.
- *
- * This method is only called by the CMU implementation.
- */
-Datagram DCClass::
-client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
-                           ZONEID_TYPE zone_id,
-                           PyObject *optional_fields) const {
-  DCPacker packer;
-
-  packer.raw_pack_uint16(CLIENT_OBJECT_GENERATE_CMU);
-
-  packer.raw_pack_uint32(zone_id);
-  packer.raw_pack_uint16(_number);
-  packer.raw_pack_uint32(do_id);
-
-  // Specify all of the required fields.
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields; ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->is_required() && field->as_molecular_field() == nullptr) {
-      packer.begin_pack(field);
-      if (!pack_required_field(packer, distobj, field)) {
-        return Datagram();
-      }
-      packer.end_pack();
-    }
-  }
-
-  // Also specify the optional fields.
-  int num_optional_fields = 0;
-  if (PyObject_IsTrue(optional_fields)) {
-    num_optional_fields = PySequence_Size(optional_fields);
-  }
-  packer.raw_pack_uint16(num_optional_fields);
-
-  for (int i = 0; i < num_optional_fields; i++) {
-    PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
-#if PY_MAJOR_VERSION >= 3
-    string field_name = PyUnicode_AsUTF8(py_field_name);
-#else
-    string field_name = PyString_AsString(py_field_name);
-#endif
-    Py_XDECREF(py_field_name);
-
-    DCField *field = get_field_by_name(field_name);
-    if (field == nullptr) {
-      ostringstream strm;
-      strm << "No field named " << field_name << " in class " << get_name()
-           << "\n";
-      nassert_raise(strm.str());
-      return Datagram();
-    }
-    packer.raw_pack_uint16(field->get_number());
-    packer.begin_pack(field);
-    if (!pack_required_field(packer, distobj, field)) {
-      return Datagram();
-    }
-    packer.end_pack();
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to generate a new
- * distributed object from the AI. This requires querying the object for the
- * initial value of its required fields.
- *
- * optional_fields is a list of fieldNames to generate in addition to the
- * normal required fields.
- */
-Datagram DCClass::
-ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
-                   DOID_TYPE parent_id, ZONEID_TYPE zone_id,
-                   CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
-                   PyObject *optional_fields) const {
-  DCPacker packer;
-
-  packer.raw_pack_uint8(1);
-  packer.RAW_PACK_CHANNEL(district_channel_id);
-  packer.RAW_PACK_CHANNEL(from_channel_id);
-    // packer.raw_pack_uint8('A');
-
-  bool has_optional_fields = (PyObject_IsTrue(optional_fields) != 0);
-
-  if (has_optional_fields) {
-    packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER);
-  } else {
-    packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED);
-  }
-
-  packer.raw_pack_uint32(do_id);
-  // Parent is a bit overloaded; this parent is not about inheritance, this
-  // one is about the visibility container parent, i.e.  the zone parent:
-  packer.raw_pack_uint32(parent_id);
-  packer.raw_pack_uint32(zone_id);
-  packer.raw_pack_uint16(_number);
-
-  // Specify all of the required fields.
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields; ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->is_required() && field->as_molecular_field() == nullptr) {
-      packer.begin_pack(field);
-      if (!pack_required_field(packer, distobj, field)) {
-        return Datagram();
-      }
-      packer.end_pack();
-    }
-  }
-
-  // Also specify the optional fields.
-  if (has_optional_fields) {
-    int num_optional_fields = PySequence_Size(optional_fields);
-    packer.raw_pack_uint16(num_optional_fields);
-
-    for (int i = 0; i < num_optional_fields; ++i) {
-      PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
-#if PY_MAJOR_VERSION >= 3
-      string field_name = PyUnicode_AsUTF8(py_field_name);
-#else
-      string field_name = PyString_AsString(py_field_name);
-#endif
-      Py_XDECREF(py_field_name);
-
-      DCField *field = get_field_by_name(field_name);
-      if (field == nullptr) {
-        ostringstream strm;
-        strm << "No field named " << field_name << " in class " << get_name()
-             << "\n";
-        nassert_raise(strm.str());
-        return Datagram();
-      }
-
-      packer.raw_pack_uint16(field->get_number());
-
-      packer.begin_pack(field);
-      if (!pack_required_field(packer, distobj, field)) {
-        return Datagram();
-      }
-      packer.end_pack();
-    }
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
 /**
  * Write a string representation of this instance to <out>.
  */
 void DCClass::
-output(ostream &out, bool brief) const {
+output(std::ostream &out, bool brief) const {
   output_instance(out, brief, "", "", "");
 }
 
@@ -1029,7 +342,7 @@ output(ostream &out, bool brief) const {
  * stream.
  */
 void DCClass::
-write(ostream &out, bool brief, int indent_level) const {
+write(std::ostream &out, bool brief, int indent_level) const {
   indent(out, indent_level);
   if (_is_struct) {
     out << "struct";
@@ -1089,7 +402,7 @@ write(ostream &out, bool brief, int indent_level) const {
  * stream.
  */
 void DCClass::
-output_instance(ostream &out, bool brief, const string &prename,
+output_instance(std::ostream &out, bool brief, const string &prename,
                 const string &name, const string &postname) const {
   if (_is_struct) {
     out << "struct";

+ 58 - 43
direct/src/dcparser/dcClass.h

@@ -21,6 +21,8 @@
 #ifdef WITHIN_PANDA
 #include "pStatCollector.h"
 #include "configVariableBool.h"
+#include "extension.h"
+#include "datagramIterator.h"
 
 extern ConfigVariableBool dc_multiple_inheritance;
 extern ConfigVariableBool dc_virtual_inheritance;
@@ -79,44 +81,52 @@ PUBLISHED:
 
   virtual void output(std::ostream &out) const;
 
-#ifdef HAVE_PYTHON
-  bool has_class_def() const;
-  void set_class_def(PyObject *class_def);
-  PyObject *get_class_def() const;
-  bool has_owner_class_def() const;
-  void set_owner_class_def(PyObject *owner_class_def);
-  PyObject *get_owner_class_def() const;
-
-  void receive_update(PyObject *distobj, DatagramIterator &di) const;
-  void receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const;
-  void receive_update_broadcast_required_owner(PyObject *distobj, DatagramIterator &di) const;
-  void receive_update_all_required(PyObject *distobj, DatagramIterator &di) const;
-  void receive_update_other(PyObject *distobj, DatagramIterator &di) const;
-
-  void direct_update(PyObject *distobj, const std::string &field_name,
-                     const vector_uchar &value_blob);
-  void direct_update(PyObject *distobj, const std::string &field_name,
-                     const Datagram &datagram);
-  bool pack_required_field(Datagram &datagram, PyObject *distobj,
-                           const DCField *field) const;
-  bool pack_required_field(DCPacker &packer, PyObject *distobj,
-                           const DCField *field) const;
-
-
-
-  Datagram client_format_update(const std::string &field_name,
-                                DOID_TYPE do_id, PyObject *args) const;
-  Datagram ai_format_update(const std::string &field_name, DOID_TYPE do_id,
-                            CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const;
-  Datagram ai_format_update_msg_type(const std::string &field_name, DOID_TYPE do_id,
-                            CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const;
-  Datagram ai_format_generate(PyObject *distobj, DOID_TYPE do_id, ZONEID_TYPE parent_id, ZONEID_TYPE zone_id,
-                              CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
-                              PyObject *optional_fields) const;
-  Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
-                                      ZONEID_TYPE zone_id,                                                           PyObject *optional_fields) const;
+  EXTENSION(bool has_class_def() const);
+  EXTENSION(void set_class_def(PyObject *class_def));
+  EXTENSION(PyObject *get_class_def() const);
+  EXTENSION(bool has_owner_class_def() const);
+  EXTENSION(void set_owner_class_def(PyObject *owner_class_def));
+  EXTENSION(PyObject *get_owner_class_def() const);
+
+  EXTENSION(void receive_update(PyObject *distobj, DatagramIterator &di) const);
+  EXTENSION(void receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const);
+  EXTENSION(void receive_update_broadcast_required_owner(PyObject *distobj, DatagramIterator &di) const);
+  EXTENSION(void receive_update_all_required(PyObject *distobj, DatagramIterator &di) const);
+  EXTENSION(void receive_update_other(PyObject *distobj, DatagramIterator &di) const);
+
+  EXTENSION(void direct_update(PyObject *distobj, const std::string &field_name,
+                               const vector_uchar &value_blob));
+  EXTENSION(void direct_update(PyObject *distobj, const std::string &field_name,
+                               const Datagram &datagram));
+  EXTENSION(bool pack_required_field(Datagram &datagram, PyObject *distobj,
+                                     const DCField *field) const);
+  EXTENSION(bool pack_required_field(DCPacker &packer, PyObject *distobj,
+                                     const DCField *field) const);
+
+
+
+  EXTENSION(Datagram client_format_update(const std::string &field_name,
+                                          DOID_TYPE do_id, PyObject *args) const);
+  EXTENSION(Datagram ai_format_update(const std::string &field_name,
+                                      DOID_TYPE do_id,
+                                      CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
+                                      PyObject *args) const);
+  EXTENSION(Datagram ai_format_update_msg_type(const std::string &field_name,
+                                               DOID_TYPE do_id,
+                                               CHANNEL_TYPE to_id,
+                                               CHANNEL_TYPE from_id,
+                                               int msg_type,
+                                               PyObject *args) const);
+  EXTENSION(Datagram ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
+                                        ZONEID_TYPE parent_id,
+                                        ZONEID_TYPE zone_id,
+                                        CHANNEL_TYPE district_channel_id,
+                                        CHANNEL_TYPE from_channel_id,
+                                        PyObject *optional_fields) const);
+  EXTENSION(Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
+                                                ZONEID_TYPE zone_id,
+                                                PyObject *optional_fields) const);
 
-#endif
 
 public:
   virtual void output(std::ostream &out, bool brief) const;
@@ -135,8 +145,8 @@ private:
   void shadow_inherited_field(const std::string &name);
 
 #ifdef WITHIN_PANDA
-  PStatCollector _class_update_pcollector;
-  PStatCollector _class_generate_pcollector;
+  mutable PStatCollector _class_update_pcollector;
+  mutable PStatCollector _class_generate_pcollector;
   static PStatCollector _update_pcollector;
   static PStatCollector _generate_pcollector;
 #endif
@@ -162,12 +172,17 @@ private:
   typedef pmap<int, DCField *> FieldsByIndex;
   FieldsByIndex _fields_by_index;
 
-#ifdef HAVE_PYTHON
-  PyObject *_class_def;
-  PyObject *_owner_class_def;
-#endif
+  // See pandaNode.h for an explanation of this trick
+  class PythonClassDefs : public ReferenceCount {
+  public:
+    virtual ~PythonClassDefs() {};
+  };
+  PT(PythonClassDefs) _python_class_defs;
 
   friend class DCField;
+#ifdef WITHIN_PANDA
+  friend class Extension<DCClass>;
+#endif
 };
 
 #include "dcClass.I"

+ 665 - 0
direct/src/dcparser/dcClass_ext.cxx

@@ -0,0 +1,665 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcClass_ext.cxx
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#include "dcClass_ext.h"
+#include "dcField_ext.h"
+#include "dcAtomicField.h"
+#include "dcPacker.h"
+#include "dcmsgtypes.h"
+
+#include "datagram.h"
+#include "datagramIterator.h"
+#include "pStatTimer.h"
+
+#ifdef HAVE_PYTHON
+
+/**
+ * Returns true if the DCClass object has an associated Python class
+ * definition, false otherwise.
+ */
+bool Extension<DCClass>::
+has_class_def() const {
+  return _this->_python_class_defs != nullptr
+      && ((PythonClassDefsImpl *)_this->_python_class_defs.p())->_class_def != nullptr;
+}
+
+/**
+ * Sets the class object associated with this DistributedClass.  This object
+ * will be used to construct new instances of the class.
+ */
+void Extension<DCClass>::
+set_class_def(PyObject *class_def) {
+  PythonClassDefsImpl *defs = do_get_defs();
+
+  Py_XINCREF(class_def);
+  Py_XDECREF(defs->_class_def);
+  defs->_class_def = class_def;
+}
+
+/**
+ * Returns the class object that was previously associated with this
+ * DistributedClass.  This will return a new reference to the object.
+ */
+PyObject *Extension<DCClass>::
+get_class_def() const {
+  if (!has_class_def()) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  PythonClassDefsImpl *defs = do_get_defs();
+  Py_INCREF(defs->_class_def);
+  return defs->_class_def;
+}
+
+/**
+ * Returns true if the DCClass object has an associated Python owner class
+ * definition, false otherwise.
+ */
+bool Extension<DCClass>::
+has_owner_class_def() const {
+  return _this->_python_class_defs != nullptr
+      && ((PythonClassDefsImpl *)_this->_python_class_defs.p())->_owner_class_def != nullptr;
+}
+
+/**
+ * Sets the owner class object associated with this DistributedClass.  This
+ * object will be used to construct new owner instances of the class.
+ */
+void Extension<DCClass>::
+set_owner_class_def(PyObject *owner_class_def) {
+  PythonClassDefsImpl *defs = do_get_defs();
+
+  Py_XINCREF(owner_class_def);
+  Py_XDECREF(defs->_owner_class_def);
+  defs->_owner_class_def = owner_class_def;
+}
+
+/**
+ * Returns the owner class object that was previously associated with this
+ * DistributedClass.  This will return a new reference to the object.
+ */
+PyObject *Extension<DCClass>::
+get_owner_class_def() const {
+  if (!has_owner_class_def()) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  PythonClassDefsImpl *defs = do_get_defs();
+  Py_INCREF(defs->_owner_class_def);
+  return defs->_owner_class_def;
+}
+
+/**
+ * Extracts the update message out of the packer and applies it to the
+ * indicated object by calling the appropriate method.
+ */
+void Extension<DCClass>::
+receive_update(PyObject *distobj, DatagramIterator &di) const {
+  PStatTimer timer(_this->_class_update_pcollector);
+  DCPacker packer;
+  const char *data = (const char *)di.get_datagram().get_data();
+  packer.set_unpack_data(data + di.get_current_index(),
+                         di.get_remaining_size(), false);
+
+  int field_id = packer.raw_unpack_uint16();
+  DCField *field = _this->get_field_by_index(field_id);
+  if (field == nullptr) {
+    ostringstream strm;
+    strm
+        << "Received update for field " << field_id << ", not in class "
+        << _this->get_name();
+    nassert_raise(strm.str());
+    return;
+  }
+
+  packer.begin_unpack(field);
+  invoke_extension(field).receive_update(packer, distobj);
+  packer.end_unpack();
+
+  di.skip_bytes(packer.get_num_unpacked_bytes());
+}
+
+/**
+ * Processes a big datagram that includes all of the "required" fields that
+ * are sent along with a normal "generate with required" message.  This is all
+ * of the atomic fields that are marked "broadcast required".
+ */
+void Extension<DCClass>::
+receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const {
+  PStatTimer timer(_this->_class_update_pcollector);
+  DCPacker packer;
+  const char *data = (const char *)di.get_datagram().get_data();
+  packer.set_unpack_data(data + di.get_current_index(),
+                         di.get_remaining_size(), false);
+
+  int num_fields = _this->get_num_inherited_fields();
+  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
+    DCField *field = _this->get_inherited_field(i);
+    if (field->as_molecular_field() == nullptr &&
+        field->is_required() && field->is_broadcast()) {
+      packer.begin_unpack(field);
+      invoke_extension(field).receive_update(packer, distobj);
+      if (!packer.end_unpack()) {
+        break;
+      }
+    }
+  }
+
+  di.skip_bytes(packer.get_num_unpacked_bytes());
+}
+
+/**
+ * Processes a big datagram that includes all of the "required" fields that
+ * are sent along with a normal "generate with required" message.  This is all
+ * of the atomic fields that are marked "broadcast ownrecv". Should be used
+ * for 'owner-view' objects.
+ */
+void Extension<DCClass>::
+receive_update_broadcast_required_owner(PyObject *distobj,
+                                        DatagramIterator &di) const {
+  PStatTimer timer(_this->_class_update_pcollector);
+  DCPacker packer;
+  const char *data = (const char *)di.get_datagram().get_data();
+  packer.set_unpack_data(data + di.get_current_index(),
+                         di.get_remaining_size(), false);
+
+  int num_fields = _this->get_num_inherited_fields();
+  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
+    DCField *field = _this->get_inherited_field(i);
+    if (field->as_molecular_field() == nullptr &&
+        field->is_required() && (field->is_ownrecv() || field->is_broadcast())) {
+      packer.begin_unpack(field);
+      invoke_extension(field).receive_update(packer, distobj);
+      if (!packer.end_unpack()) {
+        break;
+      }
+    }
+  }
+
+  di.skip_bytes(packer.get_num_unpacked_bytes());
+}
+
+/**
+ * Processes a big datagram that includes all of the "required" fields that
+ * are sent when an avatar is created.  This is all of the atomic fields that
+ * are marked "required", whether they are broadcast or not.
+ */
+void Extension<DCClass>::
+receive_update_all_required(PyObject *distobj, DatagramIterator &di) const {
+  PStatTimer timer(_this->_class_update_pcollector);
+  DCPacker packer;
+  const char *data = (const char *)di.get_datagram().get_data();
+  packer.set_unpack_data(data + di.get_current_index(),
+                         di.get_remaining_size(), false);
+
+  int num_fields = _this->get_num_inherited_fields();
+  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
+    DCField *field = _this->get_inherited_field(i);
+    if (field->as_molecular_field() == nullptr &&
+        field->is_required()) {
+      packer.begin_unpack(field);
+      invoke_extension(field).receive_update(packer, distobj);
+      if (!packer.end_unpack()) {
+        break;
+      }
+    }
+  }
+
+  di.skip_bytes(packer.get_num_unpacked_bytes());
+}
+
+/**
+ * Processes a datagram that lists some additional fields that are broadcast
+ * in one chunk.
+ */
+void Extension<DCClass>::
+receive_update_other(PyObject *distobj, DatagramIterator &di) const {
+  PStatTimer timer(_this->_class_update_pcollector);
+  int num_fields = di.get_uint16();
+  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
+    receive_update(distobj, di);
+  }
+}
+
+/**
+ * Processes an update for a named field from a packed value blob.
+ */
+void Extension<DCClass>::
+direct_update(PyObject *distobj, const std::string &field_name,
+              const vector_uchar &value_blob) {
+  DCField *field = _this->get_field_by_name(field_name);
+  nassertv_always(field != nullptr);
+
+  DCPacker packer;
+  packer.set_unpack_data(value_blob);
+  packer.begin_unpack(field);
+  invoke_extension(field).receive_update(packer, distobj);
+  packer.end_unpack();
+}
+
+/**
+ * Processes an update for a named field from a packed datagram.
+ */
+void Extension<DCClass>::
+direct_update(PyObject *distobj, const std::string &field_name,
+              const Datagram &datagram) {
+  DCField *field = _this->get_field_by_name(field_name);
+  nassertv_always(field != nullptr);
+
+  DCPacker packer;
+  packer.set_unpack_data((const char *)datagram.get_data(), datagram.get_length(), false);
+  packer.begin_unpack(field);
+  invoke_extension(field).receive_update(packer, distobj);
+  packer.end_unpack();
+}
+
+/**
+ * Looks up the current value of the indicated field by calling the
+ * appropriate get*() function, then packs that value into the datagram.  This
+ * field is presumably either a required field or a specified optional field,
+ * and we are building up a datagram for the generate-with-required message.
+ *
+ * Returns true on success, false on failure.
+ */
+bool Extension<DCClass>::
+pack_required_field(Datagram &datagram, PyObject *distobj,
+                    const DCField *field) const {
+  DCPacker packer;
+  packer.begin_pack(field);
+  if (!pack_required_field(packer, distobj, field)) {
+    return false;
+  }
+  if (!packer.end_pack()) {
+    return false;
+  }
+
+  datagram.append_data(packer.get_data(), packer.get_length());
+  return true;
+}
+
+/**
+ * Looks up the current value of the indicated field by calling the
+ * appropriate get*() function, then packs that value into the packer.  This
+ * field is presumably either a required field or a specified optional field,
+ * and we are building up a datagram for the generate-with-required message.
+ *
+ * Returns true on success, false on failure.
+ */
+bool Extension<DCClass>::
+pack_required_field(DCPacker &packer, PyObject *distobj,
+                    const DCField *field) const {
+  using std::ostringstream;
+
+  const DCParameter *parameter = field->as_parameter();
+  if (parameter != nullptr) {
+    // This is the easy case: to pack a parameter, we just look on the class
+    // object for the data element.
+    std::string field_name = field->get_name();
+
+    if (!PyObject_HasAttrString(distobj, (char *)field_name.c_str())) {
+      // If the attribute is not defined, but the field has a default value
+      // specified, quietly pack the default value.
+      if (field->has_default_value()) {
+        packer.pack_default_value();
+        return true;
+      }
+
+      // If there is no default value specified, it's an error.
+      ostringstream strm;
+      strm << "Data element " << field_name
+           << ", required by dc file for dclass " << _this->get_name()
+           << ", not defined on object";
+      nassert_raise(strm.str());
+      return false;
+    }
+    PyObject *result =
+      PyObject_GetAttrString(distobj, (char *)field_name.c_str());
+    nassertr(result != nullptr, false);
+
+    // Now pack the value into the datagram.
+    bool pack_ok = invoke_extension((DCField *)parameter).pack_args(packer, result);
+    Py_DECREF(result);
+
+    return pack_ok;
+  }
+
+  if (field->as_molecular_field() != nullptr) {
+    ostringstream strm;
+    strm << "Cannot pack molecular field " << field->get_name()
+         << " for generate";
+    nassert_raise(strm.str());
+    return false;
+  }
+
+  const DCAtomicField *atom = field->as_atomic_field();
+  nassertr(atom != nullptr, false);
+
+  // We need to get the initial value of this field.  There isn't a good,
+  // robust way to get this; presently, we just mangle the "setFoo()" name of
+  // the required field into "getFoo()" and call that.
+  std::string setter_name = atom->get_name();
+
+  if (setter_name.empty()) {
+    ostringstream strm;
+    strm << "Required field is unnamed!";
+    nassert_raise(strm.str());
+    return false;
+  }
+
+  if (atom->get_num_elements() == 0) {
+    // It sure doesn't make sense to have a required field with no parameters.
+    // What data, exactly, is required?
+    ostringstream strm;
+    strm << "Required field " << setter_name << " has no parameters!";
+    nassert_raise(strm.str());
+    return false;
+  }
+
+  std::string getter_name = setter_name;
+  if (setter_name.substr(0, 3) == "set") {
+    // If the original method started with "set", we mangle this directly to
+    // "get".
+    getter_name[0] = 'g';
+
+  } else {
+    // Otherwise, we add a "get" prefix, and capitalize the next letter.
+    getter_name = "get" + setter_name;
+    getter_name[3] = toupper(getter_name[3]);
+  }
+
+  // Now we have to look up the getter on the distributed object and call it.
+  if (!PyObject_HasAttrString(distobj, (char *)getter_name.c_str())) {
+    // As above, if there's no getter but the field has a default value
+    // specified, quietly pack the default value.
+    if (field->has_default_value()) {
+      packer.pack_default_value();
+      return true;
+    }
+
+    // Otherwise, with no default value it's an error.
+    ostringstream strm;
+    strm << "Distributed class " << _this->get_name()
+         << " doesn't have getter named " << getter_name
+         << " to match required field " << setter_name;
+    nassert_raise(strm.str());
+    return false;
+  }
+  PyObject *func =
+    PyObject_GetAttrString(distobj, (char *)getter_name.c_str());
+  nassertr(func != nullptr, false);
+
+  PyObject *empty_args = PyTuple_New(0);
+  PyObject *result = PyObject_CallObject(func, empty_args);
+  Py_DECREF(empty_args);
+  Py_DECREF(func);
+  if (result == nullptr) {
+    // We don't set this as an exception, since presumably the Python method
+    // itself has already triggered a Python exception.
+    std::cerr << "Error when calling " << getter_name << "\n";
+    return false;
+  }
+
+  if (atom->get_num_elements() == 1) {
+    // In this case, we expect the getter to return one object, which we wrap
+    // up in a tuple.
+    PyObject *tuple = PyTuple_New(1);
+    PyTuple_SET_ITEM(tuple, 0, result);
+    result = tuple;
+
+  } else {
+    // Otherwise, it had better already be a sequence or tuple of some sort.
+    if (!PySequence_Check(result)) {
+      ostringstream strm;
+      strm << "Since dclass " << _this->get_name() << " method " << setter_name
+           << " is declared to have multiple parameters, Python function "
+           << getter_name << " must return a list or tuple.\n";
+      nassert_raise(strm.str());
+      return false;
+    }
+  }
+
+  // Now pack the arguments into the datagram.
+  bool pack_ok = invoke_extension((DCField *)atom).pack_args(packer, result);
+  Py_DECREF(result);
+
+  return pack_ok;
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update for
+ * the indicated distributed object from the client.
+ */
+Datagram Extension<DCClass>::
+client_format_update(const std::string &field_name, DOID_TYPE do_id,
+                     PyObject *args) const {
+  DCField *field = _this->get_field_by_name(field_name);
+  if (field == nullptr) {
+    std::ostringstream strm;
+    strm << "No field named " << field_name << " in class " << _this->get_name()
+         << "\n";
+    nassert_raise(strm.str());
+    return Datagram();
+  }
+
+  return invoke_extension(field).client_format_update(do_id, args);
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update for
+ * the indicated distributed object from the AI.
+ */
+Datagram Extension<DCClass>::
+ai_format_update(const std::string &field_name, DOID_TYPE do_id,
+                 CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
+  DCField *field = _this->get_field_by_name(field_name);
+  if (field == nullptr) {
+    std::ostringstream strm;
+    strm << "No field named " << field_name << " in class " << _this->get_name()
+         << "\n";
+    nassert_raise(strm.str());
+    return Datagram();
+  }
+
+  return invoke_extension(field).ai_format_update(do_id, to_id, from_id, args);
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update,
+ * using the indicated msg type for the indicated distributed object from the
+ * AI.
+ */
+Datagram Extension<DCClass>::
+ai_format_update_msg_type(const std::string &field_name, DOID_TYPE do_id,
+                          CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
+                          int msg_type, PyObject *args) const {
+  DCField *field = _this->get_field_by_name(field_name);
+  if (field == nullptr) {
+    std::ostringstream strm;
+    strm << "No field named " << field_name << " in class " << _this->get_name()
+         << "\n";
+    nassert_raise(strm.str());
+    return Datagram();
+  }
+
+  return invoke_extension(field).ai_format_update_msg_type(do_id, to_id, from_id, msg_type, args);
+}
+
+/**
+ * Generates a datagram containing the message necessary to generate a new
+ * distributed object from the client.  This requires querying the object for
+ * the initial value of its required fields.
+ *
+ * optional_fields is a list of fieldNames to generate in addition to the
+ * normal required fields.
+ *
+ * This method is only called by the CMU implementation.
+ */
+Datagram Extension<DCClass>::
+client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
+                           ZONEID_TYPE zone_id,
+                           PyObject *optional_fields) const {
+  DCPacker packer;
+
+  packer.raw_pack_uint16(CLIENT_OBJECT_GENERATE_CMU);
+
+  packer.raw_pack_uint32(zone_id);
+  packer.raw_pack_uint16(_this->_number);
+  packer.raw_pack_uint32(do_id);
+
+  // Specify all of the required fields.
+  int num_fields = _this->get_num_inherited_fields();
+  for (int i = 0; i < num_fields; ++i) {
+    DCField *field = _this->get_inherited_field(i);
+    if (field->is_required() && field->as_molecular_field() == nullptr) {
+      packer.begin_pack(field);
+      if (!pack_required_field(packer, distobj, field)) {
+        return Datagram();
+      }
+      packer.end_pack();
+    }
+  }
+
+  // Also specify the optional fields.
+  int num_optional_fields = 0;
+  if (PyObject_IsTrue(optional_fields)) {
+    num_optional_fields = PySequence_Size(optional_fields);
+  }
+  packer.raw_pack_uint16(num_optional_fields);
+
+  for (int i = 0; i < num_optional_fields; i++) {
+    PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
+#if PY_MAJOR_VERSION >= 3
+    std::string field_name = PyUnicode_AsUTF8(py_field_name);
+#else
+    std::string field_name = PyString_AsString(py_field_name);
+#endif
+    Py_XDECREF(py_field_name);
+
+    DCField *field = _this->get_field_by_name(field_name);
+    if (field == nullptr) {
+      std::ostringstream strm;
+      strm << "No field named " << field_name << " in class " << _this->get_name()
+           << "\n";
+      nassert_raise(strm.str());
+      return Datagram();
+    }
+    packer.raw_pack_uint16(field->get_number());
+    packer.begin_pack(field);
+    if (!pack_required_field(packer, distobj, field)) {
+      return Datagram();
+    }
+    packer.end_pack();
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+
+/**
+ * Generates a datagram containing the message necessary to generate a new
+ * distributed object from the AI. This requires querying the object for the
+ * initial value of its required fields.
+ *
+ * optional_fields is a list of fieldNames to generate in addition to the
+ * normal required fields.
+ */
+Datagram Extension<DCClass>::
+ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
+                   DOID_TYPE parent_id, ZONEID_TYPE zone_id,
+                   CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
+                   PyObject *optional_fields) const {
+  DCPacker packer;
+
+  packer.raw_pack_uint8(1);
+  packer.RAW_PACK_CHANNEL(district_channel_id);
+  packer.RAW_PACK_CHANNEL(from_channel_id);
+    // packer.raw_pack_uint8('A');
+
+  bool has_optional_fields = (PyObject_IsTrue(optional_fields) != 0);
+
+  if (has_optional_fields) {
+    packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER);
+  } else {
+    packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED);
+  }
+
+  packer.raw_pack_uint32(do_id);
+  // Parent is a bit overloaded; this parent is not about inheritance, this
+  // one is about the visibility container parent, i.e.  the zone parent:
+  packer.raw_pack_uint32(parent_id);
+  packer.raw_pack_uint32(zone_id);
+  packer.raw_pack_uint16(_this->_number);
+
+  // Specify all of the required fields.
+  int num_fields = _this->get_num_inherited_fields();
+  for (int i = 0; i < num_fields; ++i) {
+    DCField *field = _this->get_inherited_field(i);
+    if (field->is_required() && field->as_molecular_field() == nullptr) {
+      packer.begin_pack(field);
+      if (!pack_required_field(packer, distobj, field)) {
+        return Datagram();
+      }
+      packer.end_pack();
+    }
+  }
+
+  // Also specify the optional fields.
+  if (has_optional_fields) {
+    int num_optional_fields = PySequence_Size(optional_fields);
+    packer.raw_pack_uint16(num_optional_fields);
+
+    for (int i = 0; i < num_optional_fields; ++i) {
+      PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
+#if PY_MAJOR_VERSION >= 3
+      std::string field_name = PyUnicode_AsUTF8(py_field_name);
+#else
+      std::string field_name = PyString_AsString(py_field_name);
+#endif
+      Py_XDECREF(py_field_name);
+
+      DCField *field = _this->get_field_by_name(field_name);
+      if (field == nullptr) {
+        std::ostringstream strm;
+        strm << "No field named " << field_name << " in class "
+             << _this->get_name() << "\n";
+        nassert_raise(strm.str());
+        return Datagram();
+      }
+
+      packer.raw_pack_uint16(field->get_number());
+
+      packer.begin_pack(field);
+      if (!pack_required_field(packer, distobj, field)) {
+        return Datagram();
+      }
+      packer.end_pack();
+    }
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+
+/**
+ * Returns the PythonClassDefsImpl object stored on the DCClass object,
+ * creating it if it didn't yet exist.
+ */
+Extension<DCClass>::PythonClassDefsImpl *Extension<DCClass>::
+do_get_defs() const {
+  if (!_this->_python_class_defs) {
+    _this->_python_class_defs = new PythonClassDefsImpl();
+  }
+  return (PythonClassDefsImpl *)_this->_python_class_defs.p();
+}
+
+#endif  // HAVE_PYTHON

+ 93 - 0
direct/src/dcparser/dcClass_ext.h

@@ -0,0 +1,93 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcClass_ext.h
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#ifndef DCCLASS_EXT_H
+#define DCCLASS_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "dcClass.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for DCClass, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<DCClass> : public ExtensionBase<DCClass> {
+public:
+  bool has_class_def() const;
+  void set_class_def(PyObject *class_def);
+  PyObject *get_class_def() const;
+  bool has_owner_class_def() const;
+  void set_owner_class_def(PyObject *owner_class_def);
+  PyObject *get_owner_class_def() const;
+
+  void receive_update(PyObject *distobj, DatagramIterator &di) const;
+  void receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const;
+  void receive_update_broadcast_required_owner(PyObject *distobj, DatagramIterator &di) const;
+  void receive_update_all_required(PyObject *distobj, DatagramIterator &di) const;
+  void receive_update_other(PyObject *distobj, DatagramIterator &di) const;
+
+  void direct_update(PyObject *distobj, const std::string &field_name,
+                     const vector_uchar &value_blob);
+  void direct_update(PyObject *distobj, const std::string &field_name,
+                     const Datagram &datagram);
+  bool pack_required_field(Datagram &datagram, PyObject *distobj,
+                           const DCField *field) const;
+  bool pack_required_field(DCPacker &packer, PyObject *distobj,
+                           const DCField *field) const;
+
+
+
+  Datagram client_format_update(const std::string &field_name,
+                                DOID_TYPE do_id, PyObject *args) const;
+  Datagram ai_format_update(const std::string &field_name, DOID_TYPE do_id,
+                            CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const;
+  Datagram ai_format_update_msg_type(const std::string &field_name, DOID_TYPE do_id,
+                            CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const;
+  Datagram ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
+                              ZONEID_TYPE parent_id, ZONEID_TYPE zone_id,
+                              CHANNEL_TYPE district_channel_id,
+                              CHANNEL_TYPE from_channel_id,
+                              PyObject *optional_fields) const;
+  Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
+                                      ZONEID_TYPE zone_id,
+                                      PyObject *optional_fields) const;
+
+private:
+  /**
+   * Implementation of DCClass::PythonClassDefs which actually stores the
+   * Python pointers.  This needs to be defined here rather than on DCClass
+   * itself, since DCClass cannot include Python.h or call Python functions.
+   */
+  class PythonClassDefsImpl : public DCClass::PythonClassDefs {
+  public:
+    virtual ~PythonClassDefsImpl() {
+      Py_XDECREF(_class_def);
+      Py_XDECREF(_owner_class_def);
+    }
+
+    PyObject *_class_def = nullptr;
+    PyObject *_owner_class_def = nullptr;
+  };
+
+  PythonClassDefsImpl *do_get_defs() const;
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // DCCLASS_EXT_H

+ 6 - 316
direct/src/dcparser/dcField.cxx

@@ -18,19 +18,6 @@
 #include "hashGenerator.h"
 #include "dcmsgtypes.h"
 
-#include "datagram.h"
-#include "datagramIterator.h"
-
-#ifdef HAVE_PYTHON
-#include "py_panda.h"
-#endif
-
-#ifdef WITHIN_PANDA
-#include "pStatTimer.h"
-#endif
-
-using std::string;
-
 /**
  *
  */
@@ -61,7 +48,7 @@ DCField() :
  *
  */
 DCField::
-DCField(const string &name, DCClass *dclass) :
+DCField(const std::string &name, DCClass *dclass) :
   DCPackerInterface(name),
   _dclass(dclass)
 #ifdef WITHIN_PANDA
@@ -164,14 +151,14 @@ as_parameter() const {
  * string formatting it for human consumption.  Returns empty string if there
  * is an error.
  */
-string DCField::
+std::string DCField::
 format_data(const vector_uchar &packed_data, bool show_field_names) {
   DCPacker packer;
   packer.set_unpack_data(packed_data);
   packer.begin_unpack(this);
-  string result = packer.unpack_and_format(show_field_names);
+  std::string result = packer.unpack_and_format(show_field_names);
   if (!packer.end_unpack()) {
-    return string();
+    return std::string();
   }
   return result;
 }
@@ -182,7 +169,7 @@ format_data(const vector_uchar &packed_data, bool show_field_names) {
  * the corresponding packed data.  Returns empty string if there is an error.
  */
 vector_uchar DCField::
-parse_string(const string &formatted_string) {
+parse_string(const std::string &formatted_string) {
   DCPacker packer;
   packer.begin_pack(this);
   if (!packer.parse_and_pack(formatted_string)) {
@@ -215,254 +202,6 @@ validate_ranges(const vector_uchar &packed_data) const {
   return (packer.get_num_unpacked_bytes() == packed_data.size());
 }
 
-#ifdef HAVE_PYTHON
-/**
- * Packs the Python arguments from the indicated tuple into the packer.
- * Returns true on success, false on failure.
- *
- * It is assumed that the packer is currently positioned on this field.
- */
-bool DCField::
-pack_args(DCPacker &packer, PyObject *sequence) const {
-  nassertr(!packer.had_error(), false);
-  nassertr(packer.get_current_field() == this, false);
-
-  packer.pack_object(sequence);
-  if (!packer.had_error()) {
-    /*
-    cerr << "pack " << get_name() << get_pystr(sequence) << "\n";
-    */
-
-    return true;
-  }
-
-  if (!Notify::ptr()->has_assert_failed()) {
-    std::ostringstream strm;
-    PyObject *exc_type = PyExc_Exception;
-
-    if (as_parameter() != nullptr) {
-      // If it's a parameter-type field, the value may or may not be a
-      // sequence.
-      if (packer.had_pack_error()) {
-        strm << "Incorrect arguments to field: " << get_name()
-             << " = " << get_pystr(sequence);
-        exc_type = PyExc_TypeError;
-      } else {
-        strm << "Value out of range on field: " << get_name()
-             << " = " << get_pystr(sequence);
-        exc_type = PyExc_ValueError;
-      }
-
-    } else {
-      // If it's a molecular or atomic field, the value should be a sequence.
-      PyObject *tuple = PySequence_Tuple(sequence);
-      if (tuple == nullptr) {
-        strm << "Value for " << get_name() << " not a sequence: " \
-             << get_pystr(sequence);
-        exc_type = PyExc_TypeError;
-
-      } else {
-        if (packer.had_pack_error()) {
-          strm << "Incorrect arguments to field: " << get_name()
-               << get_pystr(sequence);
-          exc_type = PyExc_TypeError;
-        } else {
-          strm << "Value out of range on field: " << get_name()
-               << get_pystr(sequence);
-          exc_type = PyExc_ValueError;
-        }
-
-        Py_DECREF(tuple);
-      }
-    }
-
-    string message = strm.str();
-    PyErr_SetString(exc_type, message.c_str());
-  }
-  return false;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Unpacks the values from the packer, beginning at the current point in the
- * unpack_buffer, into a Python tuple and returns the tuple.
- *
- * It is assumed that the packer is currently positioned on this field.
- */
-PyObject *DCField::
-unpack_args(DCPacker &packer) const {
-  nassertr(!packer.had_error(), nullptr);
-  nassertr(packer.get_current_field() == this, nullptr);
-
-  size_t start_byte = packer.get_num_unpacked_bytes();
-  PyObject *object = packer.unpack_object();
-
-  if (!packer.had_error()) {
-    // Successfully unpacked.
-    /*
-    cerr << "recv " << get_name() << get_pystr(object) << "\n";
-    */
-
-    return object;
-  }
-
-  if (!Notify::ptr()->has_assert_failed()) {
-    std::ostringstream strm;
-    PyObject *exc_type = PyExc_Exception;
-
-    if (packer.had_pack_error()) {
-      strm << "Data error unpacking field ";
-      output(strm, true);
-      size_t length = packer.get_unpack_length() - start_byte;
-      strm << "\nGot data (" << (int)length << " bytes):\n";
-      Datagram dg(packer.get_unpack_data() + start_byte, length);
-      dg.dump_hex(strm);
-      size_t error_byte = packer.get_num_unpacked_bytes() - start_byte;
-      strm << "Error detected on byte " << error_byte
-           << " (" << std::hex << error_byte << std::dec << " hex)";
-
-      exc_type = PyExc_RuntimeError;
-    } else {
-      strm << "Value outside specified range when unpacking field "
-           << get_name() << ": " << get_pystr(object);
-      exc_type = PyExc_ValueError;
-    }
-
-    string message = strm.str();
-    PyErr_SetString(exc_type, message.c_str());
-  }
-
-  Py_XDECREF(object);
-  return nullptr;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Extracts the update message out of the datagram and applies it to the
- * indicated object by calling the appropriate method.
- */
-void DCField::
-receive_update(DCPacker &packer, PyObject *distobj) const {
-  if (as_parameter() != nullptr) {
-    // If it's a parameter-type field, just store a new value on the object.
-    PyObject *value = unpack_args(packer);
-    if (value != nullptr) {
-      PyObject_SetAttrString(distobj, (char *)_name.c_str(), value);
-    }
-    Py_DECREF(value);
-
-  } else {
-    // Otherwise, it must be an atomic or molecular field, so call the
-    // corresponding method.
-
-    if (!PyObject_HasAttrString(distobj, (char *)_name.c_str())) {
-      // If there's no Python method to receive this message, don't bother
-      // unpacking it to a Python tuple--just skip past the message.
-      packer.unpack_skip();
-
-    } else {
-      // Otherwise, get a Python tuple from the args and call the Python
-      // method.
-      PyObject *args = unpack_args(packer);
-
-      if (args != nullptr) {
-        PyObject *func = PyObject_GetAttrString(distobj, (char *)_name.c_str());
-        nassertv(func != nullptr);
-
-        PyObject *result;
-        {
-#ifdef WITHIN_PANDA
-          PStatTimer timer(((DCField *)this)->_field_update_pcollector);
-#endif
-          result = PyObject_CallObject(func, args);
-        }
-        Py_XDECREF(result);
-        Py_DECREF(func);
-        Py_DECREF(args);
-      }
-    }
-  }
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update for
- * the indicated distributed object from the client.
- */
-Datagram DCField::
-client_format_update(DOID_TYPE do_id, PyObject *args) const {
-  DCPacker packer;
-
-  packer.raw_pack_uint16(CLIENT_OBJECT_SET_FIELD);
-  packer.raw_pack_uint32(do_id);
-  packer.raw_pack_uint16(_number);
-
-  packer.begin_pack(this);
-  pack_args(packer, args);
-  if (!packer.end_pack()) {
-    return Datagram();
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update for
- * the indicated distributed object from the AI.
- */
-Datagram DCField::
-ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
-  DCPacker packer;
-
-  packer.raw_pack_uint8(1);
-  packer.RAW_PACK_CHANNEL(to_id);
-  packer.RAW_PACK_CHANNEL(from_id);
-  packer.raw_pack_uint16(STATESERVER_OBJECT_SET_FIELD);
-  packer.raw_pack_uint32(do_id);
-  packer.raw_pack_uint16(_number);
-
-  packer.begin_pack(this);
-  pack_args(packer, args);
-  if (!packer.end_pack()) {
-    return Datagram();
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update,
- * with the msg type, for the indicated distributed object from the AI.
- */
-Datagram DCField::
-ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
-  DCPacker packer;
-
-  packer.raw_pack_uint8(1);
-  packer.RAW_PACK_CHANNEL(to_id);
-  packer.RAW_PACK_CHANNEL(from_id);
-  packer.raw_pack_uint16(msg_type);
-  packer.raw_pack_uint32(do_id);
-  packer.raw_pack_uint16(_number);
-
-  packer.begin_pack(this);
-  pack_args(packer, args);
-  if (!packer.end_pack()) {
-    return Datagram();
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
-
 /**
  * Accumulates the properties of this field into the hash.
  */
@@ -502,62 +241,13 @@ pack_default_value(DCPackData &pack_data, bool &) const {
  * Sets the name of this field.
  */
 void DCField::
-set_name(const string &name) {
+set_name(const std::string &name) {
   DCPackerInterface::set_name(name);
   if (_dclass != nullptr) {
     _dclass->_dc_file->mark_inherited_fields_stale();
   }
 }
 
-#ifdef HAVE_PYTHON
-/**
- * Returns the string representation of the indicated Python object.
- */
-string DCField::
-get_pystr(PyObject *value) {
-  if (value == nullptr) {
-    return "(null)";
-  }
-
-  PyObject *str = PyObject_Str(value);
-  if (str != nullptr) {
-#if PY_MAJOR_VERSION >= 3
-    string result = PyUnicode_AsUTF8(str);
-#else
-    string result = PyString_AsString(str);
-#endif
-    Py_DECREF(str);
-    return result;
-  }
-
-  PyObject *repr = PyObject_Repr(value);
-  if (repr != nullptr) {
-#if PY_MAJOR_VERSION >= 3
-    string result = PyUnicode_AsUTF8(repr);
-#else
-    string result = PyString_AsString(repr);
-#endif
-    Py_DECREF(repr);
-    return result;
-  }
-
-  if (value->ob_type != nullptr) {
-    PyObject *typestr = PyObject_Str((PyObject *)(value->ob_type));
-    if (typestr != nullptr) {
-#if PY_MAJOR_VERSION >= 3
-      string result = PyUnicode_AsUTF8(typestr);
-#else
-      string result = PyString_AsString(typestr);
-#endif
-      Py_DECREF(typestr);
-      return result;
-    }
-  }
-
-  return "(invalid object)";
-}
-#endif  // HAVE_PYTHON
-
 /**
  * Recomputes the default value of the field by repacking it.
  */

+ 13 - 14
direct/src/dcparser/dcField.h

@@ -20,6 +20,8 @@
 
 #ifdef WITHIN_PANDA
 #include "pStatCollector.h"
+#include "extension.h"
+#include "datagram.h"
 #endif
 
 class DCPacker;
@@ -75,18 +77,17 @@ PUBLISHED:
   INLINE void output(std::ostream &out) const;
   INLINE void write(std::ostream &out, int indent_level) const;
 
-#ifdef HAVE_PYTHON
-  bool pack_args(DCPacker &packer, PyObject *sequence) const;
-  PyObject *unpack_args(DCPacker &packer) const;
+  EXTENSION(bool pack_args(DCPacker &packer, PyObject *sequence) const);
+  EXTENSION(PyObject *unpack_args(DCPacker &packer) const);
 
-  void receive_update(DCPacker &packer, PyObject *distobj) const;
+  EXTENSION(void receive_update(DCPacker &packer, PyObject *distobj) const);
 
-  Datagram client_format_update(DOID_TYPE do_id, PyObject *args) const;
-  Datagram ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
-                            PyObject *args) const;
-  Datagram ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
-                            int msg_type, PyObject *args) const;
-#endif
+  EXTENSION(Datagram client_format_update(DOID_TYPE do_id, PyObject *args) const);
+  EXTENSION(Datagram ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id,
+                                      CHANNEL_TYPE from_id, PyObject *args) const);
+  EXTENSION(Datagram ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id,
+                                               CHANNEL_TYPE from_id, int msg_type,
+                                               PyObject *args) const);
 
 public:
   virtual void output(std::ostream &out, bool brief) const=0;
@@ -99,10 +100,6 @@ public:
   INLINE void set_class(DCClass *dclass);
   INLINE void set_default_value(vector_uchar default_value);
 
-#ifdef HAVE_PYTHON
-  static std::string get_pystr(PyObject *value);
-#endif
-
 protected:
   void refresh_default_value();
 
@@ -118,6 +115,8 @@ private:
 
 #ifdef WITHIN_PANDA
   PStatCollector _field_update_pcollector;
+
+  friend class Extension<DCField>;
 #endif
 };
 

+ 305 - 0
direct/src/dcparser/dcField_ext.cxx

@@ -0,0 +1,305 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcField_ext.cxx
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#include "dcField_ext.h"
+#include "dcPacker_ext.h"
+#include "dcmsgtypes.h"
+
+#include "datagram.h"
+#include "pStatTimer.h"
+
+#ifdef HAVE_PYTHON
+
+/**
+ * Packs the Python arguments from the indicated tuple into the packer.
+ * Returns true on success, false on failure.
+ *
+ * It is assumed that the packer is currently positioned on this field.
+ */
+bool Extension<DCField>::
+pack_args(DCPacker &packer, PyObject *sequence) const {
+  nassertr(!packer.had_error(), false);
+  nassertr(packer.get_current_field() == _this, false);
+
+  invoke_extension(&packer).pack_object(sequence);
+  if (!packer.had_error()) {
+    /*
+    cerr << "pack " << _this->get_name() << get_pystr(sequence) << "\n";
+    */
+
+    return true;
+  }
+
+  if (!Notify::ptr()->has_assert_failed()) {
+    std::ostringstream strm;
+    PyObject *exc_type = PyExc_Exception;
+
+    if (_this->as_parameter() != nullptr) {
+      // If it's a parameter-type field, the value may or may not be a
+      // sequence.
+      if (packer.had_pack_error()) {
+        strm << "Incorrect arguments to field: " << _this->get_name()
+             << " = " << get_pystr(sequence);
+        exc_type = PyExc_TypeError;
+      } else {
+        strm << "Value out of range on field: " << _this->get_name()
+             << " = " << get_pystr(sequence);
+        exc_type = PyExc_ValueError;
+      }
+
+    } else {
+      // If it's a molecular or atomic field, the value should be a sequence.
+      PyObject *tuple = PySequence_Tuple(sequence);
+      if (tuple == nullptr) {
+        strm << "Value for " << _this->get_name() << " not a sequence: " \
+             << get_pystr(sequence);
+        exc_type = PyExc_TypeError;
+
+      } else {
+        if (packer.had_pack_error()) {
+          strm << "Incorrect arguments to field: " << _this->get_name()
+               << get_pystr(sequence);
+          exc_type = PyExc_TypeError;
+        } else {
+          strm << "Value out of range on field: " << _this->get_name()
+               << get_pystr(sequence);
+          exc_type = PyExc_ValueError;
+        }
+
+        Py_DECREF(tuple);
+      }
+    }
+
+    std::string message = strm.str();
+    PyErr_SetString(exc_type, message.c_str());
+  }
+  return false;
+}
+
+/**
+ * Unpacks the values from the packer, beginning at the current point in the
+ * unpack_buffer, into a Python tuple and returns the tuple.
+ *
+ * It is assumed that the packer is currently positioned on this field.
+ */
+PyObject *Extension<DCField>::
+unpack_args(DCPacker &packer) const {
+  nassertr(!packer.had_error(), nullptr);
+  nassertr(packer.get_current_field() == _this, nullptr);
+
+  size_t start_byte = packer.get_num_unpacked_bytes();
+  PyObject *object = invoke_extension(&packer).unpack_object();
+
+  if (!packer.had_error()) {
+    // Successfully unpacked.
+    /*
+    cerr << "recv " << _this->get_name() << get_pystr(object) << "\n";
+    */
+
+    return object;
+  }
+
+  if (!Notify::ptr()->has_assert_failed()) {
+    std::ostringstream strm;
+    PyObject *exc_type = PyExc_Exception;
+
+    if (packer.had_pack_error()) {
+      strm << "Data error unpacking field ";
+      _this->output(strm, true);
+      size_t length = packer.get_unpack_length() - start_byte;
+      strm << "\nGot data (" << (int)length << " bytes):\n";
+      Datagram dg(packer.get_unpack_data() + start_byte, length);
+      dg.dump_hex(strm);
+      size_t error_byte = packer.get_num_unpacked_bytes() - start_byte;
+      strm << "Error detected on byte " << error_byte
+           << " (" << std::hex << error_byte << std::dec << " hex)";
+
+      exc_type = PyExc_RuntimeError;
+    } else {
+      strm << "Value outside specified range when unpacking field "
+           << _this->get_name() << ": " << get_pystr(object);
+      exc_type = PyExc_ValueError;
+    }
+
+    std::string message = strm.str();
+    PyErr_SetString(exc_type, message.c_str());
+  }
+
+  Py_XDECREF(object);
+  return nullptr;
+}
+
+/**
+ * Extracts the update message out of the datagram and applies it to the
+ * indicated object by calling the appropriate method.
+ */
+void Extension<DCField>::
+receive_update(DCPacker &packer, PyObject *distobj) const {
+  if (_this->as_parameter() != nullptr) {
+    // If it's a parameter-type field, just store a new value on the object.
+    PyObject *value = unpack_args(packer);
+    if (value != nullptr) {
+      PyObject_SetAttrString(distobj, (char *)_this->_name.c_str(), value);
+    }
+    Py_DECREF(value);
+
+  } else {
+    // Otherwise, it must be an atomic or molecular field, so call the
+    // corresponding method.
+
+    if (!PyObject_HasAttrString(distobj, (char *)_this->_name.c_str())) {
+      // If there's no Python method to receive this message, don't bother
+      // unpacking it to a Python tuple--just skip past the message.
+      packer.unpack_skip();
+
+    } else {
+      // Otherwise, get a Python tuple from the args and call the Python
+      // method.
+      PyObject *args = unpack_args(packer);
+
+      if (args != nullptr) {
+        PyObject *func = PyObject_GetAttrString(distobj, (char *)_this->_name.c_str());
+        nassertv(func != nullptr);
+
+        PyObject *result;
+        {
+#ifdef WITHIN_PANDA
+          PStatTimer timer(_this->_field_update_pcollector);
+#endif
+          result = PyObject_CallObject(func, args);
+        }
+        Py_XDECREF(result);
+        Py_DECREF(func);
+        Py_DECREF(args);
+      }
+    }
+  }
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update for
+ * the indicated distributed object from the client.
+ */
+Datagram Extension<DCField>::
+client_format_update(DOID_TYPE do_id, PyObject *args) const {
+  DCPacker packer;
+
+  packer.raw_pack_uint16(CLIENT_OBJECT_SET_FIELD);
+  packer.raw_pack_uint32(do_id);
+  packer.raw_pack_uint16(_this->_number);
+
+  packer.begin_pack(_this);
+  pack_args(packer, args);
+  if (!packer.end_pack()) {
+    return Datagram();
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update for
+ * the indicated distributed object from the AI.
+ */
+Datagram Extension<DCField>::
+ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
+  DCPacker packer;
+
+  packer.raw_pack_uint8(1);
+  packer.RAW_PACK_CHANNEL(to_id);
+  packer.RAW_PACK_CHANNEL(from_id);
+  packer.raw_pack_uint16(STATESERVER_OBJECT_SET_FIELD);
+  packer.raw_pack_uint32(do_id);
+  packer.raw_pack_uint16(_this->_number);
+
+  packer.begin_pack(_this);
+  pack_args(packer, args);
+  if (!packer.end_pack()) {
+    return Datagram();
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update,
+ * with the msg type, for the indicated distributed object from the AI.
+ */
+Datagram Extension<DCField>::
+ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
+  DCPacker packer;
+
+  packer.raw_pack_uint8(1);
+  packer.RAW_PACK_CHANNEL(to_id);
+  packer.RAW_PACK_CHANNEL(from_id);
+  packer.raw_pack_uint16(msg_type);
+  packer.raw_pack_uint32(do_id);
+  packer.raw_pack_uint16(_this->_number);
+
+  packer.begin_pack(_this);
+  pack_args(packer, args);
+  if (!packer.end_pack()) {
+    return Datagram();
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+
+/**
+ * Returns the string representation of the indicated Python object.
+ */
+std::string Extension<DCField>::
+get_pystr(PyObject *value) {
+  if (value == nullptr) {
+    return "(null)";
+  }
+
+  PyObject *str = PyObject_Str(value);
+  if (str != nullptr) {
+#if PY_MAJOR_VERSION >= 3
+    std::string result = PyUnicode_AsUTF8(str);
+#else
+    std::string result = PyString_AsString(str);
+#endif
+    Py_DECREF(str);
+    return result;
+  }
+
+  PyObject *repr = PyObject_Repr(value);
+  if (repr != nullptr) {
+#if PY_MAJOR_VERSION >= 3
+    std::string result = PyUnicode_AsUTF8(repr);
+#else
+    std::string result = PyString_AsString(repr);
+#endif
+    Py_DECREF(repr);
+    return result;
+  }
+
+  if (value->ob_type != nullptr) {
+    PyObject *typestr = PyObject_Str((PyObject *)(value->ob_type));
+    if (typestr != nullptr) {
+#if PY_MAJOR_VERSION >= 3
+      std::string result = PyUnicode_AsUTF8(typestr);
+#else
+      std::string result = PyString_AsString(typestr);
+#endif
+      Py_DECREF(typestr);
+      return result;
+    }
+  }
+
+  return "(invalid object)";
+}
+
+#endif  // HAVE_PYTHON

+ 48 - 0
direct/src/dcparser/dcField_ext.h

@@ -0,0 +1,48 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcField_ext.h
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#ifndef DCFIELD_EXT_H
+#define DCFIELD_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "dcField.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for DCField, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<DCField> : public ExtensionBase<DCField> {
+public:
+  bool pack_args(DCPacker &packer, PyObject *sequence) const;
+  PyObject *unpack_args(DCPacker &packer) const;
+
+  void receive_update(DCPacker &packer, PyObject *distobj) const;
+
+  Datagram client_format_update(DOID_TYPE do_id, PyObject *args) const;
+  Datagram ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
+                            PyObject *args) const;
+  Datagram ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
+                            int msg_type, PyObject *args) const;
+
+  static std::string get_pystr(PyObject *value);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // DCFIELD_EXT_H

+ 0 - 508
direct/src/dcparser/dcPacker.cxx

@@ -19,10 +19,6 @@
 #include "dcSwitchParameter.h"
 #include "dcClass.h"
 
-#ifdef HAVE_PYTHON
-#include "py_panda.h"
-#endif
-
 using std::istream;
 using std::istringstream;
 using std::ostream;
@@ -622,335 +618,6 @@ unpack_skip() {
   }
 }
 
-#ifdef HAVE_PYTHON
-/**
- * Packs the Python object of whatever type into the packer.  Each numeric
- * object and string object maps to the corresponding pack_value() call; a
- * tuple or sequence maps to a push() followed by all of the tuple's contents
- * followed by a pop().
- */
-void DCPacker::
-pack_object(PyObject *object) {
-  nassertv(_mode == M_pack || _mode == M_repack);
-  DCPackType pack_type = get_pack_type();
-
-  // had to add this for basic 64 and unsigned data to get packed right .. Not
-  // sure if we can just do the rest this way..
-
- switch(pack_type)
-  {
-  case PT_int64:
-      if(PyLong_Check(object))
-      {
-            pack_int64(PyLong_AsLongLong(object));
-            return;
-      }
-#if PY_MAJOR_VERSION < 3
-      else if (PyInt_Check(object))
-      {
-            pack_int64(PyInt_AsLong(object));
-            return;
-      }
-#endif
-      break;
-  case PT_uint64:
-      if(PyLong_Check(object))
-      {
-            pack_uint64(PyLong_AsUnsignedLongLong(object));
-            return;
-      }
-#if PY_MAJOR_VERSION < 3
-      else if(PyInt_Check(object))
-      {
-            PyObject  *obj1 = PyNumber_Long(object);
-            pack_int(PyLong_AsUnsignedLongLong(obj1));
-            Py_DECREF(obj1);
-            return;
-      }
-#endif
-      break;
-  case PT_int:
-      if(PyLong_Check(object))
-      {
-            pack_int(PyLong_AsLong(object));
-            return;
-      }
-#if PY_MAJOR_VERSION < 3
-      else if (PyInt_Check(object))
-      {
-            pack_int(PyInt_AsLong(object));
-            return;
-      }
-#endif
-      break;
-  case PT_uint:
-      if(PyLong_Check(object))
-      {
-            pack_uint(PyLong_AsUnsignedLong(object));
-            return;
-      }
-#if PY_MAJOR_VERSION < 3
-      else if (PyInt_Check(object))
-      {
-            PyObject *obj1 = PyNumber_Long(object);
-            pack_uint(PyLong_AsUnsignedLong(obj1));
-            Py_DECREF(obj1);
-            return;
-      }
-#endif
-      break;
-  default:
-      break;
-  }
-  if (PyLong_Check(object)) {
-    pack_int(PyLong_AsLong(object));
-#if PY_MAJOR_VERSION < 3
-  } else if (PyInt_Check(object)) {
-    pack_int(PyInt_AS_LONG(object));
-#endif
-  } else if (PyFloat_Check(object)) {
-    pack_double(PyFloat_AS_DOUBLE(object));
-  } else if (PyLong_Check(object)) {
-    pack_int64(PyLong_AsLongLong(object));
-#if PY_MAJOR_VERSION >= 3
-  } else if (PyUnicode_Check(object)) {
-    const char *buffer;
-    Py_ssize_t length;
-    buffer = PyUnicode_AsUTF8AndSize(object, &length);
-    if (buffer) {
-      pack_string(string(buffer, length));
-    }
-  } else if (PyBytes_Check(object)) {
-    const unsigned char *buffer;
-    Py_ssize_t length;
-    PyBytes_AsStringAndSize(object, (char **)&buffer, &length);
-    if (buffer) {
-      pack_blob(vector_uchar(buffer, buffer + length));
-    }
-#else
-  } else if (PyString_Check(object) || PyUnicode_Check(object)) {
-    char *buffer;
-    Py_ssize_t length;
-    PyString_AsStringAndSize(object, &buffer, &length);
-    if (buffer) {
-      pack_string(string(buffer, length));
-    }
-#endif
-  } else {
-    // For some reason, PySequence_Check() is incorrectly reporting that a
-    // class instance is a sequence, even if it doesn't provide __len__, so we
-    // double-check by testing for __len__ explicitly.
-    bool is_sequence =
-      (PySequence_Check(object) != 0) &&
-      (PyObject_HasAttrString(object, "__len__") != 0);
-    bool is_instance = false;
-
-    const DCClass *dclass = nullptr;
-    const DCPackerInterface *current_field = get_current_field();
-    if (current_field != nullptr) {
-      const DCClassParameter *class_param = get_current_field()->as_class_parameter();
-      if (class_param != nullptr) {
-        dclass = class_param->get_class();
-
-        if (dclass->has_class_def()) {
-          PyObject *class_def = dclass->get_class_def();
-          is_instance = (PyObject_IsInstance(object, dclass->get_class_def()) != 0);
-          Py_DECREF(class_def);
-        }
-      }
-    }
-
-    // If dclass is not NULL, the packer is expecting a class object.  There
-    // are then two cases: (1) the user has supplied a matching class object,
-    // or (2) the user has supplied a sequence object.  Unfortunately, it may
-    // be difficult to differentiate these two cases, since a class object may
-    // also be a sequence object.
-
-    // The rule to differentiate them is:
-
-    // (1) If the supplied class object is an instance of the expected class
-    // object, it is considered to be a class object.
-
-    // (2) Otherwise, if the supplied class object has a __len__() method
-    // (i.e.  PySequence_Check() returns true), then it is considered to be a
-    // sequence.
-
-    // (3) Otherwise, it is considered to be a class object.
-
-    if (dclass != nullptr && (is_instance || !is_sequence)) {
-      // The supplied object is either an instance of the expected class
-      // object, or it is not a sequence--this is case (1) or (3).
-      pack_class_object(dclass, object);
-    } else if (is_sequence) {
-      // The supplied object is not an instance of the expected class object,
-      // but it is a sequence.  This is case (2).
-      push();
-      int size = PySequence_Size(object);
-      for (int i = 0; i < size; ++i) {
-        PyObject *element = PySequence_GetItem(object, i);
-        if (element != nullptr) {
-          pack_object(element);
-          Py_DECREF(element);
-        } else {
-          std::cerr << "Unable to extract item " << i << " from sequence.\n";
-        }
-      }
-      pop();
-    } else {
-      // The supplied object is not a sequence, and we weren't expecting a
-      // class parameter.  This is none of the above, an error.
-      ostringstream strm;
-      strm << "Don't know how to pack object: "
-           << DCField::get_pystr(object);
-      nassert_raise(strm.str());
-      _pack_error = true;
-    }
-  }
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Unpacks a Python object of the appropriate type from the stream for the
- * current field.  This may be an integer or a string for a simple field
- * object; if the current field represents a list of fields it will be a
- * tuple.
- */
-PyObject *DCPacker::
-unpack_object() {
-  PyObject *object = nullptr;
-
-  DCPackType pack_type = get_pack_type();
-
-  switch (pack_type) {
-  case PT_invalid:
-    object = Py_None;
-    Py_INCREF(object);
-    unpack_skip();
-    break;
-
-  case PT_double:
-    {
-      double value = unpack_double();
-      object = PyFloat_FromDouble(value);
-    }
-    break;
-
-  case PT_int:
-    {
-      int value = unpack_int();
-#if PY_MAJOR_VERSION >= 3
-      object = PyLong_FromLong(value);
-#else
-      object = PyInt_FromLong(value);
-#endif
-    }
-    break;
-
-  case PT_uint:
-    {
-      unsigned int value = unpack_uint();
-#if PY_MAJOR_VERSION >= 3
-      object = PyLong_FromLong(value);
-#else
-      if (value & 0x80000000) {
-        object = PyLong_FromUnsignedLong(value);
-      } else {
-        object = PyInt_FromLong(value);
-      }
-#endif
-    }
-    break;
-
-  case PT_int64:
-    {
-      int64_t value = unpack_int64();
-      object = PyLong_FromLongLong(value);
-    }
-    break;
-
-  case PT_uint64:
-    {
-      uint64_t value = unpack_uint64();
-      object = PyLong_FromUnsignedLongLong(value);
-    }
-    break;
-
-  case PT_blob:
-#if PY_MAJOR_VERSION >= 3
-    {
-      string str;
-      unpack_string(str);
-      object = PyBytes_FromStringAndSize(str.data(), str.size());
-    }
-    break;
-#endif
-    // On Python 2, fall through to below.
-
-  case PT_string:
-    {
-      string str;
-      unpack_string(str);
-#if PY_MAJOR_VERSION >= 3
-      object = PyUnicode_FromStringAndSize(str.data(), str.size());
-#else
-      object = PyString_FromStringAndSize(str.data(), str.size());
-#endif
-    }
-    break;
-
-  case PT_class:
-    {
-      const DCClassParameter *class_param = get_current_field()->as_class_parameter();
-      if (class_param != nullptr) {
-        const DCClass *dclass = class_param->get_class();
-        if (dclass->has_class_def()) {
-          // If we know what kind of class object this is and it has a valid
-          // constructor, create the class object instead of just a tuple.
-          object = unpack_class_object(dclass);
-          if (object == nullptr) {
-            std::cerr << "Unable to construct object of class "
-                 << dclass->get_name() << "\n";
-          } else {
-            break;
-          }
-        }
-      }
-    }
-    // Fall through (if no constructor)
-
-    // If we don't know what kind of class object it is, or it doesn't have a
-    // constructor, fall through and make a tuple.
-  default:
-    {
-      // First, build up a list from the nested objects.
-      object = PyList_New(0);
-
-      push();
-      while (more_nested_fields()) {
-        PyObject *element = unpack_object();
-        PyList_Append(object, element);
-        Py_DECREF(element);
-      }
-      pop();
-
-      if (pack_type != PT_array) {
-        // For these other kinds of objects, we'll convert the list into a
-        // tuple.
-        PyObject *tuple = PyList_AsTuple(object);
-        Py_DECREF(object);
-        object = tuple;
-      }
-    }
-    break;
-  }
-
-  nassertr(object != nullptr, nullptr);
-  return object;
-}
-#endif  // HAVE_PYTHON
-
-
 /**
  * Parses an object's value according to the DC file syntax (e.g.  as a
  * default value string) and packs it.  Returns true on success, false on a
@@ -1206,178 +873,3 @@ clear_stack() {
     _stack = next;
   }
 }
-
-#ifdef HAVE_PYTHON
-/**
- * Given that the current element is a ClassParameter for a Python class
- * object, try to extract the appropriate values from the class object and
- * pack in.
- */
-void DCPacker::
-pack_class_object(const DCClass *dclass, PyObject *object) {
-  push();
-  while (more_nested_fields() && !_pack_error) {
-    const DCField *field = get_current_field()->as_field();
-    nassertv(field != nullptr);
-    get_class_element(dclass, object, field);
-  }
-  pop();
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Given that the current element is a ClassParameter for a Python class for
- * which we have a valid constructor, unpack it and fill in its values.
- */
-PyObject *DCPacker::
-unpack_class_object(const DCClass *dclass) {
-  PyObject *class_def = dclass->get_class_def();
-  nassertr(class_def != nullptr, nullptr);
-
-  PyObject *object = nullptr;
-
-  if (!dclass->has_constructor()) {
-    // If the class uses a default constructor, go ahead and create the Python
-    // object for it now.
-    object = PyObject_CallObject(class_def, nullptr);
-    if (object == nullptr) {
-      return nullptr;
-    }
-  }
-
-  push();
-  if (object == nullptr && more_nested_fields()) {
-    // The first nested field will be the constructor.
-    const DCField *field = get_current_field()->as_field();
-    nassertr(field != nullptr, object);
-    nassertr(field == dclass->get_constructor(), object);
-
-    set_class_element(class_def, object, field);
-
-    // By now, the object should have been constructed.
-    if (object == nullptr) {
-      return nullptr;
-    }
-  }
-  while (more_nested_fields()) {
-    const DCField *field = get_current_field()->as_field();
-    nassertr(field != nullptr, object);
-
-    set_class_element(class_def, object, field);
-  }
-  pop();
-
-  return object;
-}
-#endif  // HAVE_PYTHON
-
-
-#ifdef HAVE_PYTHON
-/**
- * Unpacks the current element and stuffs it on the Python class object in
- * whatever way is appropriate.
- */
-void DCPacker::
-set_class_element(PyObject *class_def, PyObject *&object,
-                  const DCField *field) {
-  string field_name = field->get_name();
-  DCPackType pack_type = get_pack_type();
-
-  if (field_name.empty()) {
-    switch (pack_type) {
-    case PT_class:
-    case PT_switch:
-      // If the field has no name, but it is one of these container objects,
-      // we want to unpack its nested objects directly into the class.
-      push();
-      while (more_nested_fields()) {
-        const DCField *field = get_current_field()->as_field();
-        nassertv(field != nullptr);
-        nassertv(object != nullptr);
-        set_class_element(class_def, object, field);
-      }
-      pop();
-      break;
-
-    default:
-      // Otherwise, we just skip over the field.
-      unpack_skip();
-    }
-
-  } else {
-    // If the field does have a name, we will want to store it on the class,
-    // either by calling a method (for a PT_field pack_type) or by setting a
-    // value (for any other kind of pack_type).
-
-    PyObject *element = unpack_object();
-
-    if (pack_type == PT_field) {
-      if (object == nullptr) {
-        // If the object hasn't been constructed yet, assume this is the
-        // constructor.
-        object = PyObject_CallObject(class_def, element);
-
-      } else {
-        if (PyObject_HasAttrString(object, (char *)field_name.c_str())) {
-          PyObject *func = PyObject_GetAttrString(object, (char *)field_name.c_str());
-          if (func != nullptr) {
-            PyObject *result = PyObject_CallObject(func, element);
-            Py_XDECREF(result);
-            Py_DECREF(func);
-          }
-        }
-      }
-
-    } else {
-      nassertv(object != nullptr);
-      PyObject_SetAttrString(object, (char *)field_name.c_str(), element);
-    }
-
-    Py_DECREF(element);
-  }
-}
-#endif  // HAVE_PYTHON
-
-
-#ifdef HAVE_PYTHON
-/**
- * Gets the current element from the Python object and packs it.
- */
-void DCPacker::
-get_class_element(const DCClass *dclass, PyObject *object,
-                  const DCField *field) {
-  string field_name = field->get_name();
-  DCPackType pack_type = get_pack_type();
-
-  if (field_name.empty()) {
-    switch (pack_type) {
-    case PT_class:
-    case PT_switch:
-      // If the field has no name, but it is one of these container objects,
-      // we want to get its nested objects directly from the class.
-      push();
-      while (more_nested_fields() && !_pack_error) {
-        const DCField *field = get_current_field()->as_field();
-        nassertv(field != nullptr);
-        get_class_element(dclass, object, field);
-      }
-      pop();
-      break;
-
-    default:
-      // Otherwise, we just pack the default value.
-      pack_default_value();
-    }
-
-  } else {
-    // If the field does have a name, we will want to get it from the class
-    // and pack it.  It just so happens that there's already a method that
-    // does this on DCClass.
-
-    if (!dclass->pack_required_field(*this, object, field)) {
-      _pack_error = true;
-    }
-  }
-}
-#endif  // HAVE_PYTHON

+ 17 - 13
direct/src/dcparser/dcPacker.h

@@ -20,6 +20,10 @@
 #include "dcPackData.h"
 #include "dcPackerCatalog.h"
 
+#ifdef WITHIN_PANDA
+#include "extension.h"
+#endif
+
 class DCClass;
 class DCSwitchParameter;
 
@@ -103,10 +107,8 @@ public:
 
 PUBLISHED:
 
-#ifdef HAVE_PYTHON
-  void pack_object(PyObject *object);
-  PyObject *unpack_object();
-#endif
+  EXTENSION(void pack_object(PyObject *object));
+  EXTENSION(PyObject *unpack_object());
 
   bool parse_and_pack(const std::string &formatted_object);
   bool parse_and_pack(std::istream &in);
@@ -194,14 +196,12 @@ private:
   void clear();
   void clear_stack();
 
-#ifdef HAVE_PYTHON
-  void pack_class_object(const DCClass *dclass, PyObject *object);
-  PyObject *unpack_class_object(const DCClass *dclass);
-  void set_class_element(PyObject *class_def, PyObject *&object,
-                         const DCField *field);
-  void get_class_element(const DCClass *dclass, PyObject *object,
-                         const DCField *field);
-#endif
+  EXTENSION(void pack_class_object(const DCClass *dclass, PyObject *object));
+  EXTENSION(PyObject *unpack_class_object(const DCClass *dclass));
+  EXTENSION(void set_class_element(PyObject *class_def, PyObject *&object,
+                                   const DCField *field));
+  EXTENSION(void get_class_element(const DCClass *dclass, PyObject *object,
+                                   const DCField *field));
 
 private:
   enum Mode {
@@ -222,7 +222,7 @@ private:
   const DCPackerCatalog *_catalog;
   const DCPackerCatalog::LiveCatalog *_live_catalog;
 
-  class StackElement {
+  class EXPCL_DIRECT_DCPARSER StackElement {
   public:
     // As an optimization, we implement operator new and delete here to
     // minimize allocation overhead during push() and pop().
@@ -257,6 +257,10 @@ private:
   bool _parse_error;
   bool _pack_error;
   bool _range_error;
+
+#ifdef WITHIN_PANDA
+  friend class Extension<DCPacker>;
+#endif
 };
 
 #include "dcPacker.I"

+ 508 - 0
direct/src/dcparser/dcPacker_ext.cxx

@@ -0,0 +1,508 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcPacker_ext.cxx
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#include "dcPacker_ext.h"
+#include "dcClass_ext.h"
+#include "dcField_ext.h"
+
+#include "dcClassParameter.h"
+
+#ifdef HAVE_PYTHON
+
+/**
+ * Packs the Python object of whatever type into the packer.  Each numeric
+ * object and string object maps to the corresponding pack_value() call; a
+ * tuple or sequence maps to a push() followed by all of the tuple's contents
+ * followed by a pop().
+ */
+void Extension<DCPacker>::
+pack_object(PyObject *object) {
+  nassertv(_this->_mode == DCPacker::Mode::M_pack ||
+           _this->_mode == DCPacker::Mode::M_repack);
+  DCPackType pack_type = _this->get_pack_type();
+
+  // had to add this for basic 64 and unsigned data to get packed right .. Not
+  // sure if we can just do the rest this way..
+
+  switch(pack_type) {
+  case PT_int64:
+    if (PyLong_Check(object)) {
+      _this->pack_int64(PyLong_AsLongLong(object));
+      return;
+    }
+#if PY_MAJOR_VERSION < 3
+    else if (PyInt_Check(object)) {
+      _this->pack_int64(PyInt_AsLong(object));
+      return;
+    }
+#endif
+    break;
+
+  case PT_uint64:
+    if (PyLong_Check(object)) {
+      _this->pack_uint64(PyLong_AsUnsignedLongLong(object));
+      return;
+    }
+#if PY_MAJOR_VERSION < 3
+    else if (PyInt_Check(object)) {
+      PyObject *obj1 = PyNumber_Long(object);
+      _this->pack_int(PyLong_AsUnsignedLongLong(obj1));
+      Py_DECREF(obj1);
+      return;
+    }
+#endif
+    break;
+
+  case PT_int:
+    if (PyLong_Check(object)) {
+      _this->pack_int(PyLong_AsLong(object));
+      return;
+    }
+#if PY_MAJOR_VERSION < 3
+    else if (PyInt_Check(object)) {
+      _this->pack_int(PyInt_AsLong(object));
+      return;
+    }
+#endif
+    break;
+
+  case PT_uint:
+    if (PyLong_Check(object)) {
+      _this->pack_uint(PyLong_AsUnsignedLong(object));
+      return;
+    }
+#if PY_MAJOR_VERSION < 3
+    else if (PyInt_Check(object)) {
+      PyObject *obj1 = PyNumber_Long(object);
+      _this->pack_uint(PyLong_AsUnsignedLong(obj1));
+      Py_DECREF(obj1);
+      return;
+    }
+#endif
+    break;
+
+  default:
+    break;
+  }
+
+  if (PyLong_Check(object)) {
+    _this->pack_int(PyLong_AsLong(object));
+#if PY_MAJOR_VERSION < 3
+  } else if (PyInt_Check(object)) {
+    _this->pack_int(PyInt_AS_LONG(object));
+#endif
+  } else if (PyFloat_Check(object)) {
+    _this->pack_double(PyFloat_AS_DOUBLE(object));
+  } else if (PyLong_Check(object)) {
+    _this->pack_int64(PyLong_AsLongLong(object));
+#if PY_MAJOR_VERSION >= 3
+  } else if (PyUnicode_Check(object)) {
+    const char *buffer;
+    Py_ssize_t length;
+    buffer = PyUnicode_AsUTF8AndSize(object, &length);
+    if (buffer) {
+      _this->pack_string(std::string(buffer, length));
+    }
+  } else if (PyBytes_Check(object)) {
+    const unsigned char *buffer;
+    Py_ssize_t length;
+    PyBytes_AsStringAndSize(object, (char **)&buffer, &length);
+    if (buffer) {
+      _this->pack_blob(vector_uchar(buffer, buffer + length));
+    }
+#else
+  } else if (PyString_Check(object) || PyUnicode_Check(object)) {
+    char *buffer;
+    Py_ssize_t length;
+    PyString_AsStringAndSize(object, &buffer, &length);
+    if (buffer) {
+      _this->pack_string(std::string(buffer, length));
+    }
+#endif
+  } else {
+    // For some reason, PySequence_Check() is incorrectly reporting that a
+    // class instance is a sequence, even if it doesn't provide __len__, so we
+    // double-check by testing for __len__ explicitly.
+    bool is_sequence =
+      (PySequence_Check(object) != 0) &&
+      (PyObject_HasAttrString(object, "__len__") != 0);
+    bool is_instance = false;
+
+    const DCClass *dclass = nullptr;
+    const DCPackerInterface *current_field = _this->get_current_field();
+    if (current_field != nullptr) {
+      const DCClassParameter *class_param = _this->get_current_field()->as_class_parameter();
+      if (class_param != nullptr) {
+        dclass = class_param->get_class();
+
+        if (invoke_extension(dclass).has_class_def()) {
+          PyObject *class_def = invoke_extension(dclass).get_class_def();
+          is_instance = (PyObject_IsInstance(object, invoke_extension(dclass).get_class_def()) != 0);
+          Py_DECREF(class_def);
+        }
+      }
+    }
+
+    // If dclass is not NULL, the packer is expecting a class object.  There
+    // are then two cases: (1) the user has supplied a matching class object,
+    // or (2) the user has supplied a sequence object.  Unfortunately, it may
+    // be difficult to differentiate these two cases, since a class object may
+    // also be a sequence object.
+
+    // The rule to differentiate them is:
+
+    // (1) If the supplied class object is an instance of the expected class
+    // object, it is considered to be a class object.
+
+    // (2) Otherwise, if the supplied class object has a __len__() method
+    // (i.e.  PySequence_Check() returns true), then it is considered to be a
+    // sequence.
+
+    // (3) Otherwise, it is considered to be a class object.
+
+    if (dclass != nullptr && (is_instance || !is_sequence)) {
+      // The supplied object is either an instance of the expected class
+      // object, or it is not a sequence--this is case (1) or (3).
+      pack_class_object(dclass, object);
+    } else if (is_sequence) {
+      // The supplied object is not an instance of the expected class object,
+      // but it is a sequence.  This is case (2).
+      _this->push();
+      int size = PySequence_Size(object);
+      for (int i = 0; i < size; ++i) {
+        PyObject *element = PySequence_GetItem(object, i);
+        if (element != nullptr) {
+          pack_object(element);
+          Py_DECREF(element);
+        } else {
+          std::cerr << "Unable to extract item " << i << " from sequence.\n";
+        }
+      }
+      _this->pop();
+    } else {
+      // The supplied object is not a sequence, and we weren't expecting a
+      // class parameter.  This is none of the above, an error.
+      std::ostringstream strm;
+      strm << "Don't know how to pack object: "
+           << Extension<DCField>::get_pystr(object);
+      nassert_raise(strm.str());
+      _this->_pack_error = true;
+    }
+  }
+}
+
+/**
+ * Unpacks a Python object of the appropriate type from the stream for the
+ * current field.  This may be an integer or a string for a simple field
+ * object; if the current field represents a list of fields it will be a
+ * tuple.
+ */
+PyObject *Extension<DCPacker>::
+unpack_object() {
+  PyObject *object = nullptr;
+
+  DCPackType pack_type = _this->get_pack_type();
+
+  switch (pack_type) {
+  case PT_invalid:
+    object = Py_None;
+    Py_INCREF(object);
+    _this->unpack_skip();
+    break;
+
+  case PT_double:
+    {
+      double value = _this->unpack_double();
+      object = PyFloat_FromDouble(value);
+    }
+    break;
+
+  case PT_int:
+    {
+      int value = _this->unpack_int();
+#if PY_MAJOR_VERSION >= 3
+      object = PyLong_FromLong(value);
+#else
+      object = PyInt_FromLong(value);
+#endif
+    }
+    break;
+
+  case PT_uint:
+    {
+      unsigned int value = _this->unpack_uint();
+#if PY_MAJOR_VERSION >= 3
+      object = PyLong_FromLong(value);
+#else
+      if (value & 0x80000000) {
+        object = PyLong_FromUnsignedLong(value);
+      } else {
+        object = PyInt_FromLong(value);
+      }
+#endif
+    }
+    break;
+
+  case PT_int64:
+    {
+      int64_t value = _this->unpack_int64();
+      object = PyLong_FromLongLong(value);
+    }
+    break;
+
+  case PT_uint64:
+    {
+      uint64_t value = _this->unpack_uint64();
+      object = PyLong_FromUnsignedLongLong(value);
+    }
+    break;
+
+  case PT_blob:
+#if PY_MAJOR_VERSION >= 3
+    {
+      std::string str;
+      _this->unpack_string(str);
+      object = PyBytes_FromStringAndSize(str.data(), str.size());
+    }
+    break;
+#endif
+    // On Python 2, fall through to below.
+
+  case PT_string:
+    {
+      std::string str;
+      _this->unpack_string(str);
+#if PY_MAJOR_VERSION >= 3
+      object = PyUnicode_FromStringAndSize(str.data(), str.size());
+#else
+      object = PyString_FromStringAndSize(str.data(), str.size());
+#endif
+    }
+    break;
+
+  case PT_class:
+    {
+      const DCClassParameter *class_param = _this->get_current_field()->as_class_parameter();
+      if (class_param != nullptr) {
+        const DCClass *dclass = class_param->get_class();
+        if (invoke_extension(dclass).has_class_def()) {
+          // If we know what kind of class object this is and it has a valid
+          // constructor, create the class object instead of just a tuple.
+          object = unpack_class_object(dclass);
+          if (object == nullptr) {
+            std::cerr << "Unable to construct object of class "
+                 << dclass->get_name() << "\n";
+          } else {
+            break;
+          }
+        }
+      }
+    }
+    // Fall through (if no constructor)
+
+    // If we don't know what kind of class object it is, or it doesn't have a
+    // constructor, fall through and make a tuple.
+  default:
+    {
+      // First, build up a list from the nested objects.
+      object = PyList_New(0);
+
+      _this->push();
+      while (_this->more_nested_fields()) {
+        PyObject *element = unpack_object();
+        PyList_Append(object, element);
+        Py_DECREF(element);
+      }
+      _this->pop();
+
+      if (pack_type != PT_array) {
+        // For these other kinds of objects, we'll convert the list into a
+        // tuple.
+        PyObject *tuple = PyList_AsTuple(object);
+        Py_DECREF(object);
+        object = tuple;
+      }
+    }
+    break;
+  }
+
+  nassertr(object != nullptr, nullptr);
+  return object;
+}
+
+/**
+ * Given that the current element is a ClassParameter for a Python class
+ * object, try to extract the appropriate values from the class object and
+ * pack in.
+ */
+void Extension<DCPacker>::
+pack_class_object(const DCClass *dclass, PyObject *object) {
+  _this->push();
+  while (_this->more_nested_fields() && !_this->_pack_error) {
+    const DCField *field = _this->get_current_field()->as_field();
+    nassertv(field != nullptr);
+    get_class_element(dclass, object, field);
+  }
+  _this->pop();
+}
+
+/**
+ * Given that the current element is a ClassParameter for a Python class for
+ * which we have a valid constructor, unpack it and fill in its values.
+ */
+PyObject *Extension<DCPacker>::
+unpack_class_object(const DCClass *dclass) {
+  PyObject *class_def = invoke_extension(dclass).get_class_def();
+  nassertr(class_def != nullptr, nullptr);
+
+  PyObject *object = nullptr;
+
+  if (!dclass->has_constructor()) {
+    // If the class uses a default constructor, go ahead and create the Python
+    // object for it now.
+    object = PyObject_CallObject(class_def, nullptr);
+    if (object == nullptr) {
+      return nullptr;
+    }
+  }
+
+  _this->push();
+  if (object == nullptr && _this->more_nested_fields()) {
+    // The first nested field will be the constructor.
+    const DCField *field = _this->get_current_field()->as_field();
+    nassertr(field != nullptr, object);
+    nassertr(field == dclass->get_constructor(), object);
+
+    set_class_element(class_def, object, field);
+
+    // By now, the object should have been constructed.
+    if (object == nullptr) {
+      return nullptr;
+    }
+  }
+  while (_this->more_nested_fields()) {
+    const DCField *field = _this->get_current_field()->as_field();
+    nassertr(field != nullptr, object);
+
+    set_class_element(class_def, object, field);
+  }
+  _this->pop();
+
+  return object;
+}
+
+/**
+ * Unpacks the current element and stuffs it on the Python class object in
+ * whatever way is appropriate.
+ */
+void Extension<DCPacker>::
+set_class_element(PyObject *class_def, PyObject *&object,
+                  const DCField *field) {
+  std::string field_name = field->get_name();
+  DCPackType pack_type = _this->get_pack_type();
+
+  if (field_name.empty()) {
+    switch (pack_type) {
+    case PT_class:
+    case PT_switch:
+      // If the field has no name, but it is one of these container objects,
+      // we want to unpack its nested objects directly into the class.
+      _this->push();
+      while (_this->more_nested_fields()) {
+        const DCField *field = _this->get_current_field()->as_field();
+        nassertv(field != nullptr);
+        nassertv(object != nullptr);
+        set_class_element(class_def, object, field);
+      }
+      _this->pop();
+      break;
+
+    default:
+      // Otherwise, we just skip over the field.
+      _this->unpack_skip();
+    }
+
+  } else {
+    // If the field does have a name, we will want to store it on the class,
+    // either by calling a method (for a PT_field pack_type) or by setting a
+    // value (for any other kind of pack_type).
+
+    PyObject *element = unpack_object();
+
+    if (pack_type == PT_field) {
+      if (object == nullptr) {
+        // If the object hasn't been constructed yet, assume this is the
+        // constructor.
+        object = PyObject_CallObject(class_def, element);
+
+      } else {
+        if (PyObject_HasAttrString(object, (char *)field_name.c_str())) {
+          PyObject *func = PyObject_GetAttrString(object, (char *)field_name.c_str());
+          if (func != nullptr) {
+            PyObject *result = PyObject_CallObject(func, element);
+            Py_XDECREF(result);
+            Py_DECREF(func);
+          }
+        }
+      }
+
+    } else {
+      nassertv(object != nullptr);
+      PyObject_SetAttrString(object, (char *)field_name.c_str(), element);
+    }
+
+    Py_DECREF(element);
+  }
+}
+
+/**
+ * Gets the current element from the Python object and packs it.
+ */
+void Extension<DCPacker>::
+get_class_element(const DCClass *dclass, PyObject *object,
+                  const DCField *field) {
+  std::string field_name = field->get_name();
+  DCPackType pack_type = _this->get_pack_type();
+
+  if (field_name.empty()) {
+    switch (pack_type) {
+    case PT_class:
+    case PT_switch:
+      // If the field has no name, but it is one of these container objects,
+      // we want to get its nested objects directly from the class.
+      _this->push();
+      while (_this->more_nested_fields() && !_this->_pack_error) {
+        const DCField *field = _this->get_current_field()->as_field();
+        nassertv(field != nullptr);
+        get_class_element(dclass, object, field);
+      }
+      _this->pop();
+      break;
+
+    default:
+      // Otherwise, we just pack the default value.
+      _this->pack_default_value();
+    }
+
+  } else {
+    // If the field does have a name, we will want to get it from the class
+    // and pack it.  It just so happens that there's already a method that
+    // does this on DCClass.
+
+    if (!invoke_extension(dclass).pack_required_field(*_this, object, field)) {
+      _this->_pack_error = true;
+    }
+  }
+}
+
+#endif  // HAVE_PYTHON

+ 45 - 0
direct/src/dcparser/dcPacker_ext.h

@@ -0,0 +1,45 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcPacker_ext.h
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#ifndef DCPACKER_EXT_H
+#define DCPACKER_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "dcPacker.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for DCPacker, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<DCPacker> : public ExtensionBase<DCPacker> {
+public:
+  void pack_object(PyObject *object);
+  PyObject *unpack_object();
+
+  void pack_class_object(const DCClass *dclass, PyObject *object);
+  PyObject *unpack_class_object(const DCClass *dclass);
+  void set_class_element(PyObject *class_def, PyObject *&object,
+                         const DCField *field);
+  void get_class_element(const DCClass *dclass, PyObject *object,
+                         const DCField *field);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // DCPACKER_EXT_H

+ 3 - 0
direct/src/dcparser/p3dcparser_ext_composite.cxx

@@ -0,0 +1,3 @@
+#include "dcClass_ext.cxx"
+#include "dcField_ext.cxx"
+#include "dcPacker_ext.cxx"

+ 4 - 3
direct/src/distributed/cConnectionRepository.cxx

@@ -26,6 +26,7 @@
 
 #ifdef HAVE_PYTHON
 #include "py_panda.h"
+#include "dcClass_ext.h"
 #endif
 
 using std::endl;
@@ -736,7 +737,7 @@ handle_update_field() {
       // get into trouble if it tried to delete the object from the doId2do
       // map.
       Py_INCREF(distobj);
-      dclass->receive_update(distobj, _di);
+      invoke_extension(dclass).receive_update(distobj, _di);
       Py_DECREF(distobj);
 
       if (PyErr_Occurred()) {
@@ -820,7 +821,7 @@ handle_update_field_owner() {
         // make a copy of the datagram iterator so that we can use the main
         // iterator for the non-owner update
         DatagramIterator _odi(_di);
-        dclass->receive_update(distobjOV, _odi);
+        invoke_extension(dclass).receive_update(distobjOV, _odi);
         Py_DECREF(distobjOV);
 
         if (PyErr_Occurred()) {
@@ -861,7 +862,7 @@ handle_update_field_owner() {
         // get into trouble if it tried to delete the object from the doId2do
         // map.
         Py_INCREF(distobj);
-        dclass->receive_update(distobj, _di);
+        invoke_extension(dclass).receive_update(distobj, _di);
         Py_DECREF(distobj);
 
         if (PyErr_Occurred()) {

+ 1 - 1
direct/src/distributed/cConnectionRepository.h

@@ -53,7 +53,7 @@ class SocketStream;
  * the C++ layer, while server messages that are not understood by the C++
  * layer are returned up to the Python layer for processing.
  */
-class EXPCL_DIRECT_DISTRIBUTED CConnectionRepository {
+class CConnectionRepository {
 PUBLISHED:
   explicit CConnectionRepository(bool has_owner_view = false,
                                  bool threaded_net = false);

+ 1 - 1
direct/src/distributed/cDistributedSmoothNodeBase.h

@@ -27,7 +27,7 @@ class CConnectionRepository;
  * This class defines some basic methods of DistributedSmoothNodeBase which
  * have been moved into C++ as a performance optimization.
  */
-class EXPCL_DIRECT_DISTRIBUTED CDistributedSmoothNodeBase {
+class CDistributedSmoothNodeBase {
 PUBLISHED:
   CDistributedSmoothNodeBase();
   ~CDistributedSmoothNodeBase();

+ 4 - 4
direct/src/distributed/config_distributed.h

@@ -23,10 +23,10 @@
 
 NotifyCategoryDecl(distributed, EXPCL_DIRECT_DISTRIBUTED, EXPTP_DIRECT_DISTRIBUTED);
 
-extern ConfigVariableInt game_server_timeout_ms;
-extern ConfigVariableDouble min_lag;
-extern ConfigVariableDouble max_lag;
-extern ConfigVariableBool handle_datagrams_internally;
+extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableInt game_server_timeout_ms;
+extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableDouble min_lag;
+extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableDouble max_lag;
+extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableBool handle_datagrams_internally;
 
 extern EXPCL_DIRECT_DISTRIBUTED void init_libdistributed();
 

+ 19 - 22
makepanda/makepanda.py

@@ -5256,11 +5256,11 @@ if (PkgSkip("DIRECT")==0):
 if (PkgSkip("DIRECT")==0):
   OPTS=['DIR:direct/src/dcparser', 'BUILDING:DIRECT_DCPARSER', 'WITHINPANDA', 'BISONPREFIX_dcyy']
   CreateFile(GetOutputDir()+"/include/dcParser.h")
-  PyTargetAdd('p3dcparser_dcParser.obj', opts=OPTS, input='dcParser.yxx')
+  TargetAdd('p3dcparser_dcParser.obj', opts=OPTS, input='dcParser.yxx')
   #TargetAdd('dcParser.h', input='p3dcparser_dcParser.obj', opts=['DEPENDENCYONLY'])
-  PyTargetAdd('p3dcparser_dcLexer.obj', opts=OPTS, input='dcLexer.lxx')
-  PyTargetAdd('p3dcparser_composite1.obj', opts=OPTS, input='p3dcparser_composite1.cxx')
-  PyTargetAdd('p3dcparser_composite2.obj', opts=OPTS, input='p3dcparser_composite2.cxx')
+  TargetAdd('p3dcparser_dcLexer.obj', opts=OPTS, input='dcLexer.lxx')
+  TargetAdd('p3dcparser_composite1.obj', opts=OPTS, input='p3dcparser_composite1.cxx')
+  TargetAdd('p3dcparser_composite2.obj', opts=OPTS, input='p3dcparser_composite2.cxx')
 
   OPTS=['DIR:direct/src/dcparser', 'WITHINPANDA']
   IGATEFILES=GetDirectoryContents('direct/src/dcparser', ["*.h", "*_composite*.cxx"])
@@ -5268,6 +5268,7 @@ if (PkgSkip("DIRECT")==0):
   if "dcmsgtypes.h" in IGATEFILES: IGATEFILES.remove('dcmsgtypes.h')
   TargetAdd('libp3dcparser.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3dcparser.in', opts=['IMOD:panda3d.direct', 'ILIB:libp3dcparser', 'SRCDIR:direct/src/dcparser'])
+  PyTargetAdd('p3dcparser_ext_composite.obj', opts=OPTS, input='p3dcparser_ext_composite.cxx')
 
 #
 # DIRECTORY: direct/src/deadrec/
@@ -5289,13 +5290,13 @@ if (PkgSkip("DIRECT")==0):
 if (PkgSkip("DIRECT")==0):
   OPTS=['DIR:direct/src/distributed', 'DIR:direct/src/dcparser', 'WITHINPANDA', 'BUILDING:DIRECT', 'OPENSSL']
   TargetAdd('p3distributed_config_distributed.obj', opts=OPTS, input='config_distributed.cxx')
-  PyTargetAdd('p3distributed_cConnectionRepository.obj', opts=OPTS, input='cConnectionRepository.cxx')
-  PyTargetAdd('p3distributed_cDistributedSmoothNodeBase.obj', opts=OPTS, input='cDistributedSmoothNodeBase.cxx')
 
   OPTS=['DIR:direct/src/distributed', 'WITHINPANDA', 'OPENSSL']
   IGATEFILES=GetDirectoryContents('direct/src/distributed', ["*.h", "*.cxx"])
   TargetAdd('libp3distributed.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3distributed.in', opts=['IMOD:panda3d.direct', 'ILIB:libp3distributed', 'SRCDIR:direct/src/distributed'])
+  PyTargetAdd('p3distributed_cConnectionRepository.obj', opts=OPTS, input='cConnectionRepository.cxx')
+  PyTargetAdd('p3distributed_cDistributedSmoothNodeBase.obj', opts=OPTS, input='cDistributedSmoothNodeBase.cxx')
 
 #
 # DIRECTORY: direct/src/interval/
@@ -5345,10 +5346,15 @@ if (PkgSkip("DIRECT")==0):
 
 if (PkgSkip("DIRECT")==0):
   TargetAdd('libp3direct.dll', input='p3directbase_directbase.obj')
+  TargetAdd('libp3direct.dll', input='p3dcparser_composite1.obj')
+  TargetAdd('libp3direct.dll', input='p3dcparser_composite2.obj')
+  TargetAdd('libp3direct.dll', input='p3dcparser_dcParser.obj')
+  TargetAdd('libp3direct.dll', input='p3dcparser_dcLexer.obj')
   TargetAdd('libp3direct.dll', input='p3showbase_showBase.obj')
   if GetTarget() == 'darwin':
     TargetAdd('libp3direct.dll', input='p3showbase_showBase_assist.obj')
   TargetAdd('libp3direct.dll', input='p3deadrec_composite1.obj')
+  TargetAdd('libp3direct.dll', input='p3distributed_config_distributed.obj')
   TargetAdd('libp3direct.dll', input='p3interval_composite1.obj')
   TargetAdd('libp3direct.dll', input='p3motiontrail_config_motiontrail.obj')
   TargetAdd('libp3direct.dll', input='p3motiontrail_cMotionTrail.obj')
@@ -5373,11 +5379,7 @@ if (PkgSkip("DIRECT")==0):
   # These are part of direct.pyd, not libp3direct.dll, because they rely on
   # the Python libraries.  If a C++ user needs these modules, we can move them
   # back and filter out the Python-specific code.
-  PyTargetAdd('direct.pyd', input='p3dcparser_composite1.obj')
-  PyTargetAdd('direct.pyd', input='p3dcparser_composite2.obj')
-  PyTargetAdd('direct.pyd', input='p3dcparser_dcParser.obj')
-  PyTargetAdd('direct.pyd', input='p3dcparser_dcLexer.obj')
-  PyTargetAdd('direct.pyd', input='p3distributed_config_distributed.obj')
+  PyTargetAdd('direct.pyd', input='p3dcparser_ext_composite.obj')
   PyTargetAdd('direct.pyd', input='p3distributed_cConnectionRepository.obj')
   PyTargetAdd('direct.pyd', input='p3distributed_cDistributedSmoothNodeBase.obj')
 
@@ -5391,18 +5393,13 @@ if (PkgSkip("DIRECT")==0):
 # DIRECTORY: direct/src/dcparse/
 #
 
-if (PkgSkip("PYTHON")==0 and PkgSkip("DIRECT")==0 and not RTDIST and not RUNTIME):
+if (PkgSkip("DIRECT")==0 and not RTDIST and not RUNTIME):
   OPTS=['DIR:direct/src/dcparse', 'DIR:direct/src/dcparser', 'WITHINPANDA', 'ADVAPI']
-  PyTargetAdd('dcparse_dcparse.obj', opts=OPTS, input='dcparse.cxx')
-  PyTargetAdd('p3dcparse.exe', input='p3dcparser_composite1.obj')
-  PyTargetAdd('p3dcparse.exe', input='p3dcparser_composite2.obj')
-  PyTargetAdd('p3dcparse.exe', input='p3dcparser_dcParser.obj')
-  PyTargetAdd('p3dcparse.exe', input='p3dcparser_dcLexer.obj')
-  PyTargetAdd('p3dcparse.exe', input='dcparse_dcparse.obj')
-  PyTargetAdd('p3dcparse.exe', input='libp3direct.dll')
-  PyTargetAdd('p3dcparse.exe', input=COMMON_PANDA_LIBS)
-  PyTargetAdd('p3dcparse.exe', input='libp3pystub.lib')
-  PyTargetAdd('p3dcparse.exe', opts=['ADVAPI'])
+  TargetAdd('dcparse_dcparse.obj', opts=OPTS, input='dcparse.cxx')
+  TargetAdd('p3dcparse.exe', input='dcparse_dcparse.obj')
+  TargetAdd('p3dcparse.exe', input='libp3direct.dll')
+  TargetAdd('p3dcparse.exe', input=COMMON_PANDA_LIBS)
+  TargetAdd('p3dcparse.exe', opts=['ADVAPI'])
 
 #
 # DIRECTORY: direct/src/plugin/