Browse Source

concrete objects

David Rose 16 years ago
parent
commit
1b6bb92da5

+ 4 - 0
direct/src/plugin/Sources.pp

@@ -30,6 +30,8 @@
     p3d_plugin_config.h \
     p3d_plugin_common.h \
     p3dBoolObject.h \
+    p3dConcreteSequence.h \
+    p3dConcreteStruct.h \
     p3dConditionVar.h p3dConditionVar.I \
     p3dDownload.h p3dDownload.I \
     p3dFileDownload.h p3dFileDownload.I \
@@ -56,6 +58,8 @@
   #define INCLUDED_SOURCES \
     p3d_plugin.cxx \
     p3dBoolObject.cxx \
+    p3dConcreteSequence.cxx \
+    p3dConcreteStruct.cxx \
     p3dConditionVar.cxx \
     p3dDownload.cxx \
     p3dFileDownload.cxx \

+ 253 - 0
direct/src/plugin/p3dConcreteSequence.cxx

@@ -0,0 +1,253 @@
+// Filename: p3dConcreteSequence.cxx
+// Created by:  drose (30Jun09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "p3dConcreteSequence.h"
+#include "p3dSession.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::Default Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DConcreteSequence::
+P3DConcreteSequence() { 
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DConcreteSequence::
+~P3DConcreteSequence() {
+  Elements::iterator ei;
+  for (ei = _elements.begin(); ei != _elements.end(); ++ei) {
+    P3D_OBJECT_DECREF(*ei);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::is_sequence_object
+//       Access: Public, Virtual
+//  Description: Returns true if this is actually an instance of a
+//               P3DConcreteSequence, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool P3DConcreteSequence::
+is_sequence_object() {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::get_type
+//       Access: Public, Virtual
+//  Description: Returns the fundamental type of this kind of object.
+////////////////////////////////////////////////////////////////////
+P3D_object_type P3DConcreteSequence::
+get_type() {
+  return P3D_OT_object;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::get_bool
+//       Access: Public, Virtual
+//  Description: Returns the object value coerced to a boolean, if
+//               possible.
+////////////////////////////////////////////////////////////////////
+bool P3DConcreteSequence::
+get_bool() {
+  return !_elements.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::make_string
+//       Access: Public, Virtual
+//  Description: Fills the indicated C++ string object with the value
+//               of this object coerced to a string.
+////////////////////////////////////////////////////////////////////
+void P3DConcreteSequence::
+make_string(string &value) {
+  ostringstream strm;
+  strm << "[";
+  if (!_elements.empty()) {
+    strm << *_elements[0];
+    for (size_t i = 1; i < _elements.size(); ++i) {
+      strm << ", " << *_elements[i];
+    }
+  }
+  strm << "]";
+
+  value = strm.str();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::get_property
+//       Access: Public, Virtual
+//  Description: Returns the named property element in the object.  The
+//               return value is a new-reference P3D_object, or NULL
+//               on error.
+////////////////////////////////////////////////////////////////////
+P3D_object *P3DConcreteSequence::
+get_property(const string &property) {
+  // We only understand integer "property" names.
+  char *endptr;
+  int index = strtoul(property.c_str(), &endptr, 10);
+  if (*endptr != '\0') {
+    return NULL;
+  }
+
+  return get_element(index);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::set_property
+//       Access: Public, Virtual
+//  Description: Modifies (or deletes, if value is NULL) the named
+//               property element in the object.  Returns true on
+//               success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool P3DConcreteSequence::
+set_property(const string &property, P3D_object *value) {
+  // We only understand integer "property" names.
+  char *endptr;
+  int index = strtoul(property.c_str(), &endptr, 10);
+  if (*endptr != '\0') {
+    return NULL;
+  }
+
+  return set_element(index, value);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::fill_xml
+//       Access: Public, Virtual
+//  Description: If this object has a valid XML representation for the
+//               indicated session (that hasn't already been
+//               implemented by the generic code in P3DSession), this
+//               method will apply it to the indicated "value" element
+//               and return true.  Otherwise, this method will leave
+//               the element unchanged and return false.
+////////////////////////////////////////////////////////////////////
+bool P3DConcreteSequence::
+fill_xml(TiXmlElement *xvalue, P3DSession *session) {
+  xvalue->SetAttribute("type", "concrete_sequence");
+  Elements::const_iterator ei;
+  for (ei = _elements.begin(); ei != _elements.end(); ++ei) {
+    xvalue->LinkEndChild(session->p3dobj_to_xml(*ei));
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::get_object_array
+//       Access: Public
+//  Description: Returns a pointer to the array of objects represented
+//               by this object.  Most objects represent only
+//               themselves, but a P3DConcreteSequence represents its
+//               list.
+////////////////////////////////////////////////////////////////////
+P3D_object **P3DConcreteSequence::
+get_object_array() {     	
+  if (_elements.empty()) {
+    return NULL;
+  }
+  return &_elements[0];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::get_object_array_size
+//       Access: Public
+//  Description: Returns the number of elements in the array returned
+//               by get_object_array().
+////////////////////////////////////////////////////////////////////
+int P3DConcreteSequence::
+get_object_array_size() {
+  return _elements.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::get_length
+//       Access: Public
+//  Description: Returns the number of items in the sequence.
+////////////////////////////////////////////////////////////////////
+int P3DConcreteSequence::
+get_length() const {
+  return _elements.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::get_element
+//       Access: Public
+//  Description: Returns the nth item in the sequence.  The
+//               return value is a new-reference P3DObject object, or
+//               NULL on error.
+////////////////////////////////////////////////////////////////////
+P3D_object *P3DConcreteSequence::
+get_element(int n) const {
+  if (n >= 0 && n < (int)_elements.size()) {
+    P3D_OBJECT_INCREF(_elements[n]);
+    return _elements[n];
+  }
+
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::set_element
+//       Access: Public
+//  Description: Modifies (or deletes, if value is NULL) the nth item
+//               in the sequence.  Returns true on success, false on
+//               failure.
+////////////////////////////////////////////////////////////////////
+bool P3DConcreteSequence::
+set_element(int n, P3D_object *value) {
+  if (value == NULL) {
+    // Delete an element.
+    if (n < 0 || n >= (int)_elements.size()) {
+      // Invalid index.
+      return false;
+    }
+    P3D_OBJECT_DECREF(_elements[n]);
+    _elements.erase(_elements.begin() + n);
+    return true;
+
+  } else if (n == _elements.size()) {
+    // Append an element.
+    append(value);
+    return true;
+
+  } else {
+    // Replace an element.
+    if (n < 0 || n >= (int)_elements.size()) {
+      // Invalid index.
+      return false;
+    }
+
+    P3D_OBJECT_INCREF(value);
+    P3D_OBJECT_DECREF(_elements[n]);
+    _elements[n] = value;
+    return true;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteSequence::append
+//       Access: Public, Virtual
+//  Description: Adds a new element to the end of the list.
+////////////////////////////////////////////////////////////////////
+void P3DConcreteSequence::
+append(P3D_object *value) {
+  _elements.push_back(value);
+  P3D_OBJECT_INCREF(value);
+}

+ 60 - 0
direct/src/plugin/p3dConcreteSequence.h

@@ -0,0 +1,60 @@
+// Filename: p3dConcreteSequence.h
+// Created by:  drose (30Jun09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef P3DCONCRETESEQUENCE_H
+#define P3DCONCRETESEQUENCE_H
+
+#include "p3d_plugin_common.h"
+#include "p3dObject.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : P3DConcreteSequence
+// Description : An object type that contains a sequence of objects,
+//               which is passed by value between Python and
+//               JavaScript, so may be more optimal for small lists
+//               that are accessed repeatedly.
+//
+//               This is converted from a Python "tuple" object.
+////////////////////////////////////////////////////////////////////
+class P3DConcreteSequence : public P3DObject {
+public:
+  P3DConcreteSequence();
+  virtual ~P3DConcreteSequence();
+
+  virtual bool is_sequence_object();
+
+  virtual P3D_object_type get_type();
+  virtual bool get_bool();
+
+  virtual void make_string(string &value);
+
+  virtual P3D_object *get_property(const string &property);
+  virtual bool set_property(const string &property, P3D_object *value);
+
+  virtual bool fill_xml(TiXmlElement *xvalue, P3DSession *session);
+  virtual P3D_object **get_object_array();
+  virtual int get_object_array_size();
+
+  int get_length() const;
+  P3D_object *get_element(int n) const;
+  bool set_element(int n, P3D_object *value);
+  void append(P3D_object *value);
+
+private:
+  typedef vector<P3D_object *> Elements;
+  Elements _elements;
+};
+
+#endif
+

+ 158 - 0
direct/src/plugin/p3dConcreteStruct.cxx

@@ -0,0 +1,158 @@
+// Filename: p3dConcreteStruct.cxx
+// Created by:  drose (14Jul09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "p3dConcreteStruct.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteStruct::Default Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DConcreteStruct::
+P3DConcreteStruct() { 
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteStruct::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DConcreteStruct::
+~P3DConcreteStruct() {
+  Elements::iterator ei;
+  for (ei = _elements.begin(); ei != _elements.end(); ++ei) {
+    P3D_OBJECT_DECREF((*ei).second);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteStruct::get_type
+//       Access: Public, Virtual
+//  Description: Returns the fundamental type of this kind of object.
+////////////////////////////////////////////////////////////////////
+P3D_object_type P3DConcreteStruct::
+get_type() {
+  return P3D_OT_object;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteStruct::get_bool
+//       Access: Public, Virtual
+//  Description: Returns the object value coerced to a boolean, if
+//               possible.
+////////////////////////////////////////////////////////////////////
+bool P3DConcreteStruct::
+get_bool() {
+  return !_elements.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteStruct::make_string
+//       Access: Public, Virtual
+//  Description: Fills the indicated C++ string object with the value
+//               of this object coerced to a string.
+////////////////////////////////////////////////////////////////////
+void P3DConcreteStruct::
+make_string(string &value) {
+  ostringstream strm;
+  strm << "{";
+  if (!_elements.empty()) {
+    Elements::iterator ei;
+    ei = _elements.begin();
+    strm << (*ei).first << ": " << *(*ei).second;
+    ++ei;
+    while (ei != _elements.end()) {
+      strm << ", " << (*ei).first << ": " << *(*ei).second;
+      ++ei;
+    }
+  }
+  strm << "}";
+
+  value = strm.str();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteStruct::get_property
+//       Access: Public, Virtual
+//  Description: Returns the named property element in the object.  The
+//               return value is a new-reference P3D_object, or NULL
+//               on error.
+////////////////////////////////////////////////////////////////////
+P3D_object *P3DConcreteStruct::
+get_property(const string &property) {
+  Elements::const_iterator ei = _elements.find(property);
+  if (ei != _elements.end()) {
+    P3D_OBJECT_INCREF((*ei).second);
+    return (*ei).second;
+  }
+
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteStruct::set_property
+//       Access: Public, Virtual
+//  Description: Modifies (or deletes, if value is NULL) the named
+//               property element in the object.  Returns true on
+//               success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool P3DConcreteStruct::
+set_property(const string &property, P3D_object *value) {
+  if (value == NULL) {
+    // Delete an element.
+    Elements::iterator ei = _elements.find(property);
+    if (ei == _elements.end()) {
+      // Invalid property.
+      return false;
+    }
+    P3D_OBJECT_DECREF((*ei).second);
+    _elements.erase(ei);
+    return true;
+
+  } else {
+    // Replace or insert an element.
+    P3D_OBJECT_INCREF(value);
+    pair<Elements::iterator, bool> result = _elements.insert(Elements::value_type(property, value));
+    if (!result.second) {
+      // Replacing an element.
+      Elements::iterator ei = result.first;
+      P3D_OBJECT_DECREF((*ei).second);
+      (*ei).second = value;
+    }
+    return true;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DConcreteStruct::fill_xml
+//       Access: Public, Virtual
+//  Description: If this object has a valid XML representation for the
+//               indicated session (that hasn't already been
+//               implemented by the generic code in P3DSession), this
+//               method will apply it to the indicated "value" element
+//               and return true.  Otherwise, this method will leave
+//               the element unchanged and return false.
+////////////////////////////////////////////////////////////////////
+bool P3DConcreteStruct::
+fill_xml(TiXmlElement *xvalue, P3DSession *session) {
+  xvalue->SetAttribute("type", "concrete_struct");
+  Elements::const_iterator ei;
+  for (ei = _elements.begin(); ei != _elements.end(); ++ei) {
+    TiXmlElement *xitem = session->p3dobj_to_xml((*ei).second);
+    xitem->SetAttribute("key", (*ei).first);
+    xvalue->LinkEndChild(xitem);
+  }
+
+  return true;
+}

+ 51 - 0
direct/src/plugin/p3dConcreteStruct.h

@@ -0,0 +1,51 @@
+// Filename: p3dConcreteStruct.h
+// Created by:  drose (14Jul09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef P3DCONCRETESTRUCT_H
+#define P3DCONCRETESTRUCT_H
+
+#include "p3d_plugin_common.h"
+#include "p3dObject.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : P3DConcreteStruct
+// Description : A simple object that contains a standard mapping of
+//               string -> element.  It is passed by value between
+//               Python and Javascript, so it may be more optimal for
+//               relatively small objects.
+//
+//               Methods are not supported.
+////////////////////////////////////////////////////////////////////
+class P3DConcreteStruct : public P3DObject {
+public:
+  P3DConcreteStruct();
+  virtual ~P3DConcreteStruct();
+
+  virtual P3D_object_type get_type();
+  virtual bool get_bool();
+
+  virtual void make_string(string &value);
+
+  virtual P3D_object *get_property(const string &property);
+  virtual bool set_property(const string &property, P3D_object *value);
+
+  virtual bool fill_xml(TiXmlElement *xvalue, P3DSession *session);
+
+private:
+  typedef map<string, P3D_object *> Elements;
+  Elements _elements;
+};
+
+#endif
+

+ 4 - 1
direct/src/plugin/p3dConditionVar.cxx

@@ -140,7 +140,10 @@ wait(double timeout) {
   ts.tv_nsec += (int)((timeout - seconds) * 1000000.0);
 
   int result = pthread_cond_timedwait(&_cvar, &_lock, &ts);
-  assert(result == 0 || result == ETIMEDOUT);
+  if (result != 0 && result != ETIMEDOUT && errno != ETIMEDOUT) {
+    perror("pthread_cond_timedwait");
+    assert(false);
+  }
 
 #endif  // _WIN32
 }

+ 28 - 35
direct/src/plugin/p3dInstance.cxx

@@ -726,6 +726,7 @@ send_browser_script_object() {
 ////////////////////////////////////////////////////////////////////
 P3D_request *P3DInstance::
 make_p3d_request(TiXmlElement *xrequest) {
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   P3D_request *request = NULL;
 
   const char *rtype = xrequest->Attribute("rtype");
@@ -750,17 +751,13 @@ make_p3d_request(TiXmlElement *xrequest) {
       int unique_id = 0;
       xrequest->Attribute("unique_id", &unique_id);
         
-      vector<P3D_object *> values;
+      P3D_object *value = NULL;
       TiXmlElement *xvalue = xrequest->FirstChildElement("value");
-      while (xvalue != NULL) {
-        P3D_object *value = _session->xml_to_p3dobj(xvalue);
-        values.push_back(value);
-        xvalue = xvalue->NextSiblingElement("value");
+      if (xvalue != NULL) {
+        value = _session->xml_to_p3dobj(xvalue);
       }
-      
-      P3D_object **values_ptr = NULL;
-      if (!values.empty()) {
-        values_ptr = &values[0];
+      if (value == NULL) {
+        value = inst_mgr->new_none_object();
       }
 
       if (operation != NULL && xobject != NULL) {
@@ -770,14 +767,11 @@ make_p3d_request(TiXmlElement *xrequest) {
           property_name = "";
         }
 
-        handle_script_request(operation, object, property_name,
-                              values_ptr, values.size(),
+        handle_script_request(operation, object, property_name, value,
                               (needs_response != 0), unique_id);
       }
 
-      for (size_t i = 0; i < values.size(); ++i) {
-        P3D_OBJECT_DECREF(values[i]);
-      }
+      P3D_OBJECT_DECREF(value);
 
     } else if (strcmp(rtype, "drop_p3dobj") == 0) {
       int object_id;
@@ -858,8 +852,7 @@ handle_notify_request(const string &message) {
 ////////////////////////////////////////////////////////////////////
 void P3DInstance::
 handle_script_request(const string &operation, P3D_object *object, 
-                      const string &property_name,
-                      P3D_object *values[], int num_values,
+                      const string &property_name, P3D_object *value,
                       bool needs_response, int unique_id) {
 
   TiXmlDocument *doc = new TiXmlDocument;
@@ -883,13 +876,8 @@ handle_script_request(const string &operation, P3D_object *object,
     }
 
   } else if (operation == "set_property") {
-    bool result;
-    if (num_values == 1) {
-      result = P3D_OBJECT_SET_PROPERTY(object, property_name.c_str(), values[0]);
-    } else {
-      // Wrong number of values.  Error.
-      result = false;
-    }
+    bool result = 
+      P3D_OBJECT_SET_PROPERTY(object, property_name.c_str(), value);
     
     TiXmlElement *xvalue = new TiXmlElement("value");
     xvalue->SetAttribute("type", "bool");
@@ -905,6 +893,17 @@ handle_script_request(const string &operation, P3D_object *object,
     xcommand->LinkEndChild(xvalue);
 
   } else if (operation == "call") {
+    // Convert the single value parameter into an array of parameters.
+    P3D_object **values = &value;
+    int num_values = 1;
+    if (value->_class == &P3DObject::_object_class) {
+      P3DObject *p3dobj = (P3DObject *)value;
+      if (p3dobj->get_object_array_size() != -1) {
+        values = p3dobj->get_object_array();
+        num_values = p3dobj->get_object_array_size();
+      }
+    }
+
     P3D_object *result =
       P3D_OBJECT_CALL(object, property_name.c_str(), values, num_values);
     
@@ -915,18 +914,12 @@ handle_script_request(const string &operation, P3D_object *object,
 
   } else if (operation == "eval") {
     P3D_object *result;
-    if (num_values == 1) {
-      P3D_object *expression = values[0];
-      int size = P3D_OBJECT_GET_STRING(expression, NULL, 0);
-      char *buffer = new char[size + 1];
-      P3D_OBJECT_GET_STRING(expression, buffer, size + 1);
-      result = P3D_OBJECT_EVAL(object, buffer);
-      logfile << " eval " << *object << ": " << buffer << ", result = " << result << "\n";
-      delete[] buffer;
-    } else {
-      // Wrong number of values.  Error.
-      result = NULL;
-    }
+    int size = P3D_OBJECT_GET_STRING(value, NULL, 0);
+    char *buffer = new char[size + 1];
+    P3D_OBJECT_GET_STRING(value, buffer, size + 1);
+    result = P3D_OBJECT_EVAL(object, buffer);
+    logfile << " eval " << *object << ": " << buffer << ", result = " << result << "\n";
+    delete[] buffer;
     
     if (result != NULL) {
       xcommand->LinkEndChild(_session->p3dobj_to_xml(result));

+ 1 - 2
direct/src/plugin/p3dInstance.h

@@ -102,8 +102,7 @@ private:
   P3D_request *make_p3d_request(TiXmlElement *xrequest);
   void handle_notify_request(const string &message);
   void handle_script_request(const string &operation, P3D_object *object, 
-                             const string &property_name,
-                             P3D_object *values[], int num_values,
+                             const string &property_name, P3D_object *value,
                              bool needs_response, int unique_id);
   void make_splash_window();
   void install_progress(P3DPackage *package, double progress);

+ 2 - 0
direct/src/plugin/p3dInstanceManager.cxx

@@ -87,10 +87,12 @@ P3DInstanceManager::
        << " " << _false_object->_ref_count
        << "\n" << flush;
 
+  /*
   assert(_undefined_object->_ref_count == 1);
   assert(_none_object->_ref_count == 1);
   assert(_true_object->_ref_count == 1);
   assert(_false_object->_ref_count == 1);
+  */
 
   P3D_OBJECT_DECREF(_undefined_object);
   P3D_OBJECT_DECREF(_none_object);

+ 41 - 11
direct/src/plugin/p3dObject.cxx

@@ -199,17 +199,6 @@ P3DObject::
   assert(_ref_count == 0);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: P3DObject::is_python_object
-//       Access: Public, Virtual
-//  Description: Returns true if this is actually an instance of a
-//               P3DPythonObject, false otherwise.
-////////////////////////////////////////////////////////////////////
-bool P3DObject::
-is_python_object() {
-  return false;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DObject::get_int
 //       Access: Public, Virtual
@@ -338,6 +327,47 @@ output(ostream &out) {
   out << value;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DObject::fill_xml
+//       Access: Public, Virtual
+//  Description: If this object has a valid XML representation for the
+//               indicated session (that hasn't already been
+//               implemented by the generic code in P3DSession), this
+//               method will apply it to the indicated "value" element
+//               and return true.  Otherwise, this method will leave
+//               the element unchanged and return false.
+////////////////////////////////////////////////////////////////////
+bool P3DObject::
+fill_xml(TiXmlElement *xvalue, P3DSession *session) {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DObject::get_object_array
+//       Access: Public
+//  Description: Returns a pointer to the array of objects represented
+//               by this object, if any, or NULL if the object does
+//               not represent an array of objects.  This may also
+//               return NULL for a zero-length array; use
+//               get_object_array_size() to differentiate.
+////////////////////////////////////////////////////////////////////
+P3D_object **P3DObject::
+get_object_array() {
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DObject::get_object_array_size
+//       Access: Public
+//  Description: Returns the number of elements in the array returned
+//               by get_object_array(), or -1 if this object does not
+//               representan array of objects.
+////////////////////////////////////////////////////////////////////
+int P3DObject::
+get_object_array_size() {
+  return -1;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DObject::get_bool_property
 //       Access: Public

+ 3 - 2
direct/src/plugin/p3dObject.h

@@ -33,8 +33,6 @@ protected:
 public:
   virtual ~P3DObject();
 
-  virtual bool is_python_object();
-
   virtual P3D_object_type get_type()=0;
   virtual bool get_bool()=0;
   virtual int get_int();
@@ -53,6 +51,9 @@ public:
   virtual P3D_object *eval(const string &expression);
 
   virtual void output(ostream &out);
+  virtual bool fill_xml(TiXmlElement *xvalue, P3DSession *session);
+  virtual P3D_object **get_object_array();
+  virtual int get_object_array_size();
 
   // Convenience functions.
   bool get_bool_property(const string &property);

+ 28 - 14
direct/src/plugin/p3dPythonObject.cxx

@@ -43,17 +43,6 @@ P3DPythonObject::
   unref_delete(_session);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: P3DPythonObject::is_python_object
-//       Access: Public, Virtual
-//  Description: Returns true if this is actually an instance of a
-//               P3DPythonObject, false otherwise.
-////////////////////////////////////////////////////////////////////
-bool P3DPythonObject::
-is_python_object() {
-  return true;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DPythonObject::get_type
 //       Access: Public, Virtual
@@ -153,7 +142,7 @@ get_property(const string &property) {
   P3D_object *params[1];
   params[0] = new P3DStringObject(property);
 
-  P3D_object *result = call("__getattr__", params, 1);
+  P3D_object *result = call("__get_property__", params, 1);
   P3D_OBJECT_DECREF(params[0]);
   return result;
 }
@@ -176,12 +165,12 @@ set_property(const string &property, P3D_object *value) {
 
   if (value == NULL) {
     // Delete an attribute.
-    result = call("__delattr__", params, 1);
+    result = call("__del_property__", params, 1);
 
   } else {
     // Set a new attribute.
     params[1] = value;
-    result = call("__setattr__", params, 2);
+    result = call("__set_property__", params, 2);
   }
 
   P3D_OBJECT_DECREF(params[0]);
@@ -282,6 +271,31 @@ output(ostream &out) {
   }    
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPythonObject::fill_xml
+//       Access: Public, Virtual
+//  Description: If this object has a valid XML representation for the
+//               indicated session (that hasn't already been
+//               implemented by the generic code in P3DSession), this
+//               method will apply it to the indicated "value" element
+//               and return true.  Otherwise, this method will leave
+//               the element unchanged and return false.
+////////////////////////////////////////////////////////////////////
+bool P3DPythonObject::
+fill_xml(TiXmlElement *xvalue, P3DSession *session) {
+  if (session == _session) {
+    // If it's a P3DPythonObject from the same session, just send
+    // the object_id down, since the actual implementation of this
+    // object exists (as a Python object) in the sub-process space.
+    xvalue->SetAttribute("type", "python");
+    xvalue->SetAttribute("object_id", _object_id);
+    return true;
+  }
+
+  // Otherwise, we have to send it as a browser object.
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DPythonObject::get_session
 //       Access: Public

+ 1 - 2
direct/src/plugin/p3dPythonObject.h

@@ -33,8 +33,6 @@ public:
   P3DPythonObject(P3DSession *session, int object_id);
   virtual ~P3DPythonObject();
 
-  virtual bool is_python_object();
-
 public:
   virtual P3D_object_type get_type();
   virtual bool get_bool();
@@ -51,6 +49,7 @@ public:
                            P3D_object *params[], int num_params);
 
   virtual void output(ostream &out);
+  virtual bool fill_xml(TiXmlElement *xvalue, P3DSession *session);
 
   P3DSession *get_session();
   int get_object_id();

+ 192 - 28
direct/src/plugin/p3dPythonRun.cxx

@@ -144,6 +144,13 @@ run_python() {
     return false;
   }
 
+  // Get the ConcreteStruct class.
+  _concrete_struct_class = PyObject_GetAttrString(runp3d, "ConcreteStruct");
+  if (_concrete_struct_class == NULL) {
+    PyErr_Print();
+    return false;
+  }
+
   // Get the BrowserObject class.
   _browser_object_class = PyObject_GetAttrString(runp3d, "BrowserObject");
   if (_browser_object_class == NULL) {
@@ -324,7 +331,7 @@ handle_pyobj_command(TiXmlElement *xcommand, bool needs_response,
   doc.LinkEndChild(xresponse);
 
   const char *op = xcommand->Attribute("op");
-  if (op != NULL) {
+  if (op != NULL && !PyErr_Occurred()) {
     if (strcmp(op, "get_panda_script_object") == 0) {
       // Get Panda's toplevel Python object.
       PyObject *obj = PyObject_CallMethod(_runner, (char*)"getPandaScriptObject", (char *)"");
@@ -396,20 +403,73 @@ handle_pyobj_command(TiXmlElement *xcommand, bool needs_response,
                    strcmp(method_name, "toString") == 0) {
           result = PyObject_Str(obj);
 
-        } else if (strcmp(method_name, "__setattr__") == 0) {
+        } else if (strcmp(method_name, "__set_property__") == 0) {
+          // We call these methods __set_property__ et al instead of
+          // __setattr__ et al, because they do not precisely
+          // duplicate the Python semantics.
           char *property_name;
           PyObject *value;
           if (PyArg_ParseTuple(params, "sO", &property_name, &value)) {
-            PyObject_SetAttrString(obj, property_name, value);
-            result = Py_True;
+            bool success = false;
+
+            // If it already exists as an attribute, update it there.
+            if (PyObject_HasAttrString(obj, property_name)) {
+              if (PyObject_SetAttrString(obj, property_name, value) != -1) {
+                success = true;
+              } else {
+                PyErr_Clear();
+              }
+            }
+            
+            // If the object supports the mapping protocol, store it
+            // in the object's dictionary.
+            if (!success && PyMapping_Check(obj)) {
+              if (PyMapping_SetItemString(obj, property_name, value) != -1) {
+                success = true;
+              } else {
+                PyErr_Clear();
+              }
+            }
+
+            // Finally, try to store it on the object.
+            if (!success) {
+              if (PyObject_SetAttrString(obj, property_name, value) != -1) {
+                success = true;
+              } else {
+                PyErr_Clear();
+              }
+            }
+
+            if (success) {
+              result = Py_True;
+            } else {
+              result = Py_False;
+            }
             Py_INCREF(result);
           }
 
-        } else if (strcmp(method_name, "__delattr__") == 0) {
+        } else if (strcmp(method_name, "__del_property__") == 0) {
           char *property_name;
           if (PyArg_ParseTuple(params, "s", &property_name)) {
+            bool success = false;
+
             if (PyObject_HasAttrString(obj, property_name)) {
-              PyObject_DelAttrString(obj, property_name);
+              if (PyObject_DelAttrString(obj, property_name) != -1) {
+                success = true;
+              } else {
+                PyErr_Clear();
+              }
+            }
+
+            if (!success) {
+              if (PyObject_DelItemString(obj, property_name) != -1) {
+                success = true;
+              } else {
+                PyErr_Clear();
+              }
+            }
+            
+            if (success) {
               result = Py_True;
             } else {
               result = Py_False;
@@ -417,13 +477,32 @@ handle_pyobj_command(TiXmlElement *xcommand, bool needs_response,
             Py_INCREF(result);
           }
 
-        } else if (strcmp(method_name, "__getattr__") == 0) {
+        } else if (strcmp(method_name, "__get_property__") == 0) {
           char *property_name;
           if (PyArg_ParseTuple(params, "s", &property_name)) {
+            bool success = false;
+
             if (PyObject_HasAttrString(obj, property_name)) {
               result = PyObject_GetAttrString(obj, property_name);
+              if (result != NULL) {
+                success = true;
+              } else {
+                PyErr_Clear();
+              }
+            }
 
-            } else {
+            if (!success) {
+              if (PyMapping_HasKeyString(obj, property_name)) {
+                result = PyMapping_GetItemString(obj, property_name);
+                if (result != NULL) {
+                  success = true;
+                } else {
+                  PyErr_Clear();
+                }
+              }
+            }
+
+            if (!success) {
               result = NULL;
             }
           }
@@ -693,26 +772,8 @@ py_request_func(PyObject *args) {
     TiXmlElement *xobject = pyobj_to_xml(object);
     xobject->SetValue("object");
     xrequest->LinkEndChild(xobject);
-
-    if (strcmp(operation, "call") == 0 && PySequence_Check(value)) {
-      // A special case: operation "call" receives a tuple of
-      // parameters; unpack the tuple for the XML.
-      Py_ssize_t length = PySequence_Length(value);
-      for (Py_ssize_t i = 0; i < length; ++i) {
-        PyObject *p = PySequence_GetItem(value, i);
-        if (p != NULL) {
-          TiXmlElement *xvalue = pyobj_to_xml(p);
-          xrequest->LinkEndChild(xvalue);
-          Py_DECREF(p);
-        }
-      }
-      
-    } else {
-      // Other kinds of operations receive only a single parameter, if
-      // any.
-      TiXmlElement *xvalue = pyobj_to_xml(value);
-      xrequest->LinkEndChild(xvalue);
-    }
+    TiXmlElement *xvalue = pyobj_to_xml(value);
+    xrequest->LinkEndChild(xvalue);
 
     write_xml(_pipe_write, &doc, nout);
 
@@ -1045,6 +1106,71 @@ pyobj_to_xml(PyObject *value) {
       xvalue->SetAttribute("value", str);
     }
 
+  } else if (PyTuple_Check(value)) {
+    // An immutable sequence.  Pass it as a concrete.
+    xvalue->SetAttribute("type", "concrete_sequence");
+
+    Py_ssize_t length = PySequence_Length(value);
+    for (Py_ssize_t i = 0; i < length; ++i) {
+      PyObject *item = PySequence_GetItem(value, i);
+      if (item != NULL) {
+        xvalue->LinkEndChild(pyobj_to_xml(item));
+        Py_DECREF(item);
+      }
+    }
+
+  } else if (PyObject_IsInstance(value, _concrete_struct_class)) {
+    // This is a ConcreteStruct.
+    xvalue->SetAttribute("type", "concrete_struct");
+
+    PyObject *items = PyObject_CallMethod(value, (char *)"getConcreteProperties", (char *)"");
+    if (items == NULL) {
+      PyErr_Print();
+      return xvalue;
+    }
+
+    Py_ssize_t length = PySequence_Length(items);
+    for (Py_ssize_t i = 0; i < length; ++i) {
+      PyObject *item = PySequence_GetItem(items, i);
+      if (item != NULL) {
+        PyObject *a = PySequence_GetItem(item, 0);
+        if (a != NULL) {
+          PyObject *b = PySequence_GetItem(item, 1);
+          if (b != NULL) {
+            TiXmlElement *xitem = pyobj_to_xml(b);
+            Py_DECREF(b);
+
+            PyObject *as_str;
+            if (PyUnicode_Check(a)) {
+              // The key is a unicode value.
+              as_str = PyUnicode_AsUTF8String(a);
+            } else {
+              // The key is a string value or something else.  Make it
+              // a string.
+              as_str = PyObject_Str(a);
+            }
+            char *buffer;
+            Py_ssize_t length;
+            if (PyString_AsStringAndSize(as_str, &buffer, &length) != -1) {
+              string str(buffer, length);
+              xitem->SetAttribute("key", str);
+            }
+            Py_DECREF(as_str);
+
+            xvalue->LinkEndChild(xitem);
+          }
+          Py_DECREF(a);
+        }
+        Py_DECREF(item);
+      }
+    }
+
+    // We've already avoided errors in the above code; clear the error
+    // flag.
+    PyErr_Clear();
+
+    Py_DECREF(items);
+
   } else if (PyObject_IsInstance(value, _undefined_object_class)) {
     // This is an UndefinedObject.
     xvalue->SetAttribute("type", "undefined");
@@ -1139,6 +1265,44 @@ xml_to_pyobj(TiXmlElement *xvalue) {
                                    _runner, object_id);
     }
 
+  } else if (strcmp(type, "concrete_sequence") == 0) {
+    // Receive a concrete sequence as a tuple.
+    PyObject *list = PyList_New(0);
+
+    TiXmlElement *xitem = xvalue->FirstChildElement("value");
+    while (xitem != NULL) {
+      PyObject *item = xml_to_pyobj(xitem);
+      if (item != NULL) {
+        PyList_Append(list, item);
+        Py_DECREF(item);
+      }
+      xitem = xitem->NextSiblingElement("value");
+    }
+
+    PyObject *result = PyList_AsTuple(list);
+    Py_DECREF(list);
+    return result;
+
+  } else if (strcmp(type, "concrete_struct") == 0) {
+    // Receive a concrete struct as a new ConcreteStruct instance.
+    PyObject *obj = PyObject_CallFunction(_concrete_struct_class, (char *)"");
+
+    if (obj != NULL) {
+      TiXmlElement *xitem = xvalue->FirstChildElement("value");
+      while (xitem != NULL) {
+        const char *key = xitem->Attribute("key");
+        if (key != NULL) {
+          PyObject *item = xml_to_pyobj(xitem);
+          if (item != NULL) {
+            PyObject_SetAttrString(obj, key, item);
+            Py_DECREF(item);
+          }
+        }
+        xitem = xitem->NextSiblingElement("value");
+      }
+      return obj;
+    }
+
   } else if (strcmp(type, "python") == 0) {
     int object_id;
     if (xvalue->QueryIntAttribute("object_id", &object_id) == TIXML_SUCCESS) {

+ 1 - 0
direct/src/plugin/p3dPythonRun.h

@@ -114,6 +114,7 @@ private:
   PyObject *_runner;
   PyObject *_undefined_object_class;
   PyObject *_undefined;
+  PyObject *_concrete_struct_class;
   PyObject *_browser_object_class;
   PyObject *_taskMgr;
 

+ 44 - 13
direct/src/plugin/p3dSession.cxx

@@ -22,6 +22,8 @@
 #include "p3dIntObject.h"
 #include "p3dFloatObject.h"
 #include "p3dPythonObject.h"
+#include "p3dConcreteSequence.h"
+#include "p3dConcreteStruct.h"
 #include "binaryXml.h"
 
 #ifndef _WIN32
@@ -387,6 +389,35 @@ xml_to_p3dobj(const TiXmlElement *xvalue) {
       return new P3DStringObject(*value);
     }
 
+  } else if (strcmp(type, "concrete_sequence") == 0) {
+    P3DConcreteSequence *obj = new P3DConcreteSequence;
+    const TiXmlElement *xitem = xvalue->FirstChildElement("value");
+    while (xitem != NULL) {
+      P3D_object *item = xml_to_p3dobj(xitem);
+      if (item != NULL) {
+        obj->append(item);
+        P3D_OBJECT_DECREF(item);
+      }
+      xitem = xitem->NextSiblingElement("value");
+    }
+    return obj;
+
+  } else if (strcmp(type, "concrete_struct") == 0) {
+    P3DConcreteStruct *obj = new P3DConcreteStruct;
+    const TiXmlElement *xitem = xvalue->FirstChildElement("value");
+    while (xitem != NULL) {
+      const char *key = xitem->Attribute("key");
+      if (key != NULL) {
+        P3D_object *item = xml_to_p3dobj(xitem);
+        if (item != NULL) {
+          obj->set_property(key, item);
+          P3D_OBJECT_DECREF(item);
+        }
+      }
+      xitem = xitem->NextSiblingElement("value");
+    }
+    return obj;
+
   } else if (strcmp(type, "browser") == 0) {
     int object_id;
     if (xvalue->QueryIntAttribute("object_id", &object_id) == TIXML_SUCCESS) {
@@ -461,21 +492,21 @@ p3dobj_to_xml(P3D_object *obj) {
     break;
 
   case P3D_OT_object:
-    if (obj->_class == &P3DObject::_object_class &&
-        ((P3DObject *)obj)->is_python_object() &&
-        ((P3DPythonObject *)obj)->get_session() == this) {
-      // If it's one of our kind of objects, it must be a
-      // P3DPythonObject.  In this case, just send the object_id down,
-      // since the actual implementation of this object exists (as a
-      // Python object) in the sub-process space.
-      int object_id = ((P3DPythonObject *)obj)->get_object_id();
-      xvalue->SetAttribute("type", "python");
-      xvalue->SetAttribute("object_id", object_id);
+    P3DObject *p3dobj = NULL;
+    if (obj->_class == &P3DObject::_object_class) {
+      p3dobj = (P3DObject *)obj;
+    }
+
+    if (p3dobj != NULL && p3dobj->fill_xml(xvalue, this)) {
+      // This object has a specialized XML representation, valid for
+      // this particular session.  It has already been filled into
+      // xvalue.
 
     } else {
-      // Otherwise, it must a host-provided object, which means we
-      // should pass a reference down to this particular object, so
-      // the Python process knows to call back up to here to query it.
+      // Otherwise, it must a host-provided object, or a Python object
+      // from another session; which means we should pass a reference
+      // down to this particular object, so the Python process knows
+      // to call back up to here to query it.
 
       P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
       int object_id = inst_mgr->get_unique_id();

+ 2 - 0
direct/src/plugin/p3d_plugin_composite1.cxx

@@ -1,5 +1,7 @@
 #include "p3d_plugin.cxx"
 #include "p3dBoolObject.cxx"
+#include "p3dConcreteSequence.cxx"
+#include "p3dConcreteStruct.cxx"
 #include "p3dConditionVar.cxx"
 #include "p3dDownload.cxx"
 #include "p3dFileDownload.cxx"

+ 4 - 6
direct/src/plugin_npapi/ppPandaObject.cxx

@@ -58,13 +58,11 @@ make_new(PPInstance *inst, P3D_object *p3d_object) {
 ////////////////////////////////////////////////////////////////////
 void PPPandaObject::
 set_p3d_object(P3D_object *p3d_object) {
-  if (_p3d_object != p3d_object) {
-    P3D_OBJECT_XDECREF(_p3d_object);
-    _p3d_object = p3d_object;
-    if (_p3d_object != NULL) {
-      P3D_OBJECT_INCREF(_p3d_object);
-    }
+  if (p3d_object != NULL) {
+    P3D_OBJECT_INCREF(p3d_object);
   }
+  P3D_OBJECT_XDECREF(_p3d_object);
+  _p3d_object = p3d_object;
 }
  
 ////////////////////////////////////////////////////////////////////

+ 5 - 230
direct/src/showutil/runp3d.py

@@ -26,6 +26,10 @@ from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcF
 from direct.stdpy import file
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.showbase import AppRunnerGlobal
+
+# These imports are read by the C++ wrapper in p3dPythonRun.cxx.
+from direct.showutil.JavaScript import UndefinedObject, Undefined, ConcreteStruct, BrowserObject
+
 import os
 import types
 import __builtin__
@@ -60,9 +64,8 @@ class AppRunner(DirectObject):
         self.windowOpened = False
         self.windowPrc = None
 
-        # Store this Undefined instance where the application can easily
-        # get to it.
         self.Undefined = Undefined
+        self.ConcreteStruct = ConcreteStruct
 
         # This is per session.
         self.nextScriptId = 0
@@ -457,234 +460,6 @@ class AppRunner(DirectObject):
 
         return (osFilename, tokens)
 
-class UndefinedObject:
-    """ This is a special object that is returned by the browser to
-    represent a NULL pointer, typically the return value for a failed
-    operation.  It has no attributes, similar to None. """
-
-    def __nonzero__(self):
-        return False
-
-    def __str__(self):
-        return "Undefined"
-
-# In fact, we normally always return this precise instance of the
-# UndefinedObject.
-Undefined = UndefinedObject()
-
-class BrowserObject:
-    """ This class provides the Python wrapper around some object that
-    actually exists in the plugin host's namespace, e.g. a JavaScript
-    or DOM object. """
-
-    def __init__(self, runner, objectId):
-        self.__dict__['_BrowserObject__runner'] = runner
-        self.__dict__['_BrowserObject__objectId'] = objectId
-
-        # This element is filled in by __getattr__; it connects
-        # the object to its parent.
-        self.__dict__['_BrowserObject__boundMethod'] = (None, None)
-
-    def __del__(self):
-        # When the BrowserObject destructs, tell the parent process it
-        # doesn't need to keep around its corresponding P3D_object any
-        # more.
-        self.__runner.dropObject(self.__objectId)
-
-    def __str__(self):
-        parentObj, attribName = self.__boundMethod
-        if parentObj:
-            # Format it from its parent.
-            return "%s.%s" % (parentObj, attribName)
-        else:
-            # Format it directly.
-            return "BrowserObject(%s)" % (self.__objectId)
-
-    def __nonzero__(self):
-        return True
-
-    def __call__(self, *args):
-        try:
-            parentObj, attribName = self.__boundMethod
-            if parentObj:
-                # Call it as a method.
-                needsResponse = True
-                if parentObj is self.__runner.dom and attribName == 'alert':
-                    # As a special hack, we don't wait for the return
-                    # value from the alert() call, since this is a
-                    # blocking call, and waiting for this could cause
-                    # problems.
-                    needsResponse = False
-
-                if parentObj is self.__runner.dom and attribName == 'eval' and len(args) == 1 and isinstance(args[0], types.StringTypes):
-                    # As another special hack, we make dom.eval() a
-                    # special case, and map it directly into an eval()
-                    # call.  If the string begins with 'void ', we further
-                    # assume we're not waiting for a response.
-                    if args[0].startswith('void '):
-                        needsResponse = False
-                    result = self.__runner.scriptRequest('eval', parentObj, value = args[0], needsResponse = needsResponse)
-                else:
-                    # This is a normal method call.
-                    try:
-                        result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
-                    except EnvironmentError:
-                        # Problem on the call.  Maybe no such method?
-                        raise AttributeError
-            else:
-                # Call it as a plain function.
-                result = self.__runner.scriptRequest('call', self, value = args)
-        except EnvironmentError:
-            # Some odd problem on the call.
-            raise TypeError
-
-        return result
-
-    def __getattr__(self, name):
-        """ Remaps attempts to query an attribute, as in obj.attr,
-        into the appropriate calls to query the actual browser object
-        under the hood.  """
-
-        try:
-            value = self.__runner.scriptRequest('get_property', self,
-                                                propertyName = name)
-        except EnvironmentError:
-            # Failed to retrieve the attribute.  But maybe there's a
-            # method instead?
-            if self.__runner.scriptRequest('has_method', self, propertyName = name):
-                # Yes, so create a method wrapper for it.
-                return MethodWrapper(self.__runner, self, name)
-            
-            raise AttributeError(name)
-
-        if isinstance(value, BrowserObject):
-            # Fill in the parent object association, so __call__ can
-            # properly call a method.  (Javascript needs to know the
-            # method container at the time of the call, and doesn't
-            # store it on the function object.)
-            value.__dict__['_BrowserObject__boundMethod'] = (self, name)
-
-        return value
-
-    def __setattr__(self, name, value):
-        if name in self.__dict__:
-            self.__dict__[name] = value
-            return
-
-        result = self.__runner.scriptRequest('set_property', self,
-                                             propertyName = name,
-                                             value = value)
-        if not result:
-            raise AttributeError(name)
-
-    def __delattr__(self, name):
-        if name in self.__dict__:
-            del self.__dict__[name]
-            return
-
-        result = self.__runner.scriptRequest('del_property', self,
-                                             propertyName = name)
-        if not result:
-            raise AttributeError(name)
-
-    def __getitem__(self, key):
-        """ Remaps attempts to query an attribute, as in obj['attr'],
-        into the appropriate calls to query the actual browser object
-        under the hood.  Following the JavaScript convention, we treat
-        obj['attr'] almost the same as obj.attr. """
-
-        try:
-            value = self.__runner.scriptRequest('get_property', self,
-                                                propertyName = str(key))
-        except EnvironmentError:
-            # Failed to retrieve the property.  We return IndexError
-            # for numeric keys so we can properly support Python's
-            # iterators, but we return KeyError for string keys to
-            # emulate mapping objects.
-            if isinstance(key, types.StringTypes):
-                raise KeyError(key)
-            else:
-                raise IndexError(key)
-
-        return value
-
-    def __setitem__(self, key, value):
-        result = self.__runner.scriptRequest('set_property', self,
-                                             propertyName = str(key),
-                                             value = value)
-        if not result:
-            if isinstance(key, types.StringTypes):
-                raise KeyError(key)
-            else:
-                raise IndexError(key)
-
-    def __delitem__(self, key):
-        result = self.__runner.scriptRequest('del_property', self,
-                                             propertyName = str(key))
-        if not result:
-            if isinstance(key, types.StringTypes):
-                raise KeyError(key)
-            else:
-                raise IndexError(key)
-
-class MethodWrapper:
-    """ This is a Python wrapper around a property of a BrowserObject
-    that doesn't appear to be a first-class object in the Python
-    sense, but is nonetheless a callable method. """
-
-    def __init__(self, runner, parentObj, objectId):
-        self.__dict__['_MethodWrapper__runner'] = runner
-        self.__dict__['_MethodWraper__boundMethod'] = (parentObj, objectId)
-
-    def __str__(self):
-        parentObj, attribName = self.__boundMethod
-        return "%s.%s" % (parentObj, attribName)
-
-    def __nonzero__(self):
-        return True
-
-    def __call__(self, *args):
-        try:
-            parentObj, attribName = self.__boundMethod
-            # Call it as a method.
-            needsResponse = True
-            if parentObj is self.__runner.dom and attribName == 'alert':
-                # As a special hack, we don't wait for the return
-                # value from the alert() call, since this is a
-                # blocking call, and waiting for this could cause
-                # problems.
-                needsResponse = False
-
-            if parentObj is self.__runner.dom and attribName == 'eval' and len(args) == 1 and isinstance(args[0], types.StringTypes):
-                # As another special hack, we make dom.eval() a
-                # special case, and map it directly into an eval()
-                # call.  If the string begins with 'void ', we further
-                # assume we're not waiting for a response.
-                if args[0].startswith('void '):
-                    needsResponse = False
-                result = self.__runner.scriptRequest('eval', parentObj, value = args[0], needsResponse = needsResponse)
-            else:
-                # This is a normal method call.
-                try:
-                    result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
-                except EnvironmentError:
-                    # Problem on the call.  Maybe no such method?
-                    raise AttributeError
-
-        except EnvironmentError:
-            # Some odd problem on the call.
-            raise TypeError
-
-        return result
-
-    def __setattr__(self, name, value):
-        """ setattr will generally fail on method objects. """
-        raise AttributeError(name)
-
-    def __delattr__(self, name):
-        """ delattr will generally fail on method objects. """
-        raise AttributeError(name)
-
 if __name__ == '__main__':
     runner = AppRunner(0)
     runner.gotWindow = True