diff --git a/direct/src/dcparser/dcClass.cxx b/direct/src/dcparser/dcClass.cxx index 9f7942e997..f1d6955981 100644 --- a/direct/src/dcparser/dcClass.cxx +++ b/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 -#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 . */ 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 . */ 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"; diff --git a/direct/src/dcparser/dcClass.h b/direct/src/dcparser/dcClass.h index 481dc1bd60..2dcc6bc560 100644 --- a/direct/src/dcparser/dcClass.h +++ b/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; + 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); - 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; + 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); - 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; + 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); - 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(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 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; +#endif }; #include "dcClass.I" diff --git a/direct/src/dcparser/dcClass_ext.cxx b/direct/src/dcparser/dcClass_ext.cxx new file mode 100644 index 0000000000..4e9ffb9aa9 --- /dev/null +++ b/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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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:: +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::PythonClassDefsImpl *Extension:: +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 diff --git a/direct/src/dcparser/dcClass_ext.h b/direct/src/dcparser/dcClass_ext.h new file mode 100644 index 0000000000..9f81b7b722 --- /dev/null +++ b/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 : public ExtensionBase { +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 diff --git a/direct/src/dcparser/dcField.cxx b/direct/src/dcparser/dcField.cxx index 90bd018250..9265160b28 100644 --- a/direct/src/dcparser/dcField.cxx +++ b/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. */ diff --git a/direct/src/dcparser/dcField.h b/direct/src/dcparser/dcField.h index ec7a8fd05f..1c6fa8ce5e 100644 --- a/direct/src/dcparser/dcField.h +++ b/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; #endif }; diff --git a/direct/src/dcparser/dcField_ext.cxx b/direct/src/dcparser/dcField_ext.cxx new file mode 100644 index 0000000000..3d229b2906 --- /dev/null +++ b/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:: +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:: +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:: +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:: +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:: +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:: +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:: +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 diff --git a/direct/src/dcparser/dcField_ext.h b/direct/src/dcparser/dcField_ext.h new file mode 100644 index 0000000000..45350b4596 --- /dev/null +++ b/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 : public ExtensionBase { +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 diff --git a/direct/src/dcparser/dcPacker.cxx b/direct/src/dcparser/dcPacker.cxx index b6dbd60855..ce67a570bf 100644 --- a/direct/src/dcparser/dcPacker.cxx +++ b/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 diff --git a/direct/src/dcparser/dcPacker.h b/direct/src/dcparser/dcPacker.h index 6e73a2d9ea..32f7e57de4 100644 --- a/direct/src/dcparser/dcPacker.h +++ b/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; +#endif }; #include "dcPacker.I" diff --git a/direct/src/dcparser/dcPacker_ext.cxx b/direct/src/dcparser/dcPacker_ext.cxx new file mode 100644 index 0000000000..8e7eb1ec5f --- /dev/null +++ b/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:: +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::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:: +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:: +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:: +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:: +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:: +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 diff --git a/direct/src/dcparser/dcPacker_ext.h b/direct/src/dcparser/dcPacker_ext.h new file mode 100644 index 0000000000..c7e8333a5f --- /dev/null +++ b/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 : public ExtensionBase { +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 diff --git a/direct/src/dcparser/p3dcparser_ext_composite.cxx b/direct/src/dcparser/p3dcparser_ext_composite.cxx new file mode 100644 index 0000000000..3d05bfd964 --- /dev/null +++ b/direct/src/dcparser/p3dcparser_ext_composite.cxx @@ -0,0 +1,3 @@ +#include "dcClass_ext.cxx" +#include "dcField_ext.cxx" +#include "dcPacker_ext.cxx" diff --git a/direct/src/distributed/cConnectionRepository.cxx b/direct/src/distributed/cConnectionRepository.cxx index 16aea6e8dd..59617330f5 100644 --- a/direct/src/distributed/cConnectionRepository.cxx +++ b/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()) { diff --git a/direct/src/distributed/cConnectionRepository.h b/direct/src/distributed/cConnectionRepository.h index c67d4aad23..2db877215b 100644 --- a/direct/src/distributed/cConnectionRepository.h +++ b/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); diff --git a/direct/src/distributed/cDistributedSmoothNodeBase.h b/direct/src/distributed/cDistributedSmoothNodeBase.h index 43dfe5fc8a..bebe55fcdb 100644 --- a/direct/src/distributed/cDistributedSmoothNodeBase.h +++ b/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(); diff --git a/direct/src/distributed/config_distributed.h b/direct/src/distributed/config_distributed.h index 22814bfa13..1690295e5a 100644 --- a/direct/src/distributed/config_distributed.h +++ b/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(); diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index c03ac582bf..75d312160d 100755 --- a/makepanda/makepanda.py +++ b/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/