Browse Source

merge dev_bam_2009 to trunk

David Rose 16 years ago
parent
commit
aefe3d35c2
87 changed files with 2297 additions and 681 deletions
  1. 24 0
      panda/src/cftalk/Sources.pp
  2. 14 0
      panda/src/cftalk/cfChannel.I
  3. 75 0
      panda/src/cftalk/cfChannel.cxx
  4. 47 0
      panda/src/cftalk/cfChannel.h
  5. 51 0
      panda/src/cftalk/cfCommand.I
  6. 113 0
      panda/src/cftalk/cfCommand.cxx
  7. 103 0
      panda/src/cftalk/cfCommand.h
  8. 1 0
      panda/src/cftalk/cftalk_composite1.cxx
  9. 1 0
      panda/src/cftalk/cftalk_composite2.cxx
  10. 49 0
      panda/src/cftalk/config_cftalk.cxx
  11. 37 0
      panda/src/cftalk/config_cftalk.h
  12. 0 1
      panda/src/display/config_display.cxx
  13. 1 1
      panda/src/dxgsg8/dxTextureContext8.cxx
  14. 1 1
      panda/src/dxgsg9/dxTextureContext9.cxx
  15. 3 2
      panda/src/express/datagramSink.cxx
  16. 1 0
      panda/src/express/datagramSink.h
  17. 5 12
      panda/src/express/referenceCount.I
  18. 4 2
      panda/src/express/referenceCount.h
  19. 1 1
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  20. 2 2
      panda/src/gobj/geomVertexArrayData.cxx
  21. 1 1
      panda/src/gobj/geomVertexArrayFormat.cxx
  22. 1 1
      panda/src/gobj/geomVertexArrayFormat.h
  23. 1 1
      panda/src/gobj/geomVertexFormat.cxx
  24. 1 1
      panda/src/gobj/geomVertexFormat.h
  25. 1 1
      panda/src/gobj/internalName.cxx
  26. 1 1
      panda/src/gobj/internalName.h
  27. 12 12
      panda/src/gobj/texture.cxx
  28. 6 6
      panda/src/gobj/texturePool.cxx
  29. 34 0
      panda/src/net/connectionReader.cxx
  30. 24 2
      panda/src/net/datagramGeneratorNet.cxx
  31. 13 0
      panda/src/net/datagramSinkNet.cxx
  32. 1 0
      panda/src/net/datagramSinkNet.h
  33. 11 0
      panda/src/pandabase/pandasymbols.h
  34. 3 3
      panda/src/pgraph/bamFile.cxx
  35. 2 2
      panda/src/pgraph/bamFile.h
  36. 2 2
      panda/src/pgraph/loader.cxx
  37. 12 1
      panda/src/pgraph/pandaNode.I
  38. 190 36
      panda/src/pgraph/pandaNode.cxx
  39. 40 7
      panda/src/pgraph/pandaNode.h
  40. 1 1
      panda/src/pgraph/renderAttrib.cxx
  41. 1 1
      panda/src/pgraph/renderAttrib.h
  42. 1 1
      panda/src/pgraph/renderEffects.cxx
  43. 1 1
      panda/src/pgraph/renderEffects.h
  44. 1 8
      panda/src/pgraph/renderState.cxx
  45. 1 1
      panda/src/pgraph/renderState.h
  46. 3 40
      panda/src/pgraph/transformState.cxx
  47. 2 3
      panda/src/pgraph/transformState.h
  48. 3 6
      panda/src/putil/Sources.pp
  49. 2 1
      panda/src/putil/bam.h
  50. 6 5
      panda/src/putil/bamCache.cxx
  51. 58 30
      panda/src/putil/bamCacheRecord.I
  52. 6 6
      panda/src/putil/bamCacheRecord.cxx
  53. 6 5
      panda/src/putil/bamCacheRecord.h
  54. 0 52
      panda/src/putil/bamEndian.cxx
  55. 0 38
      panda/src/putil/bamEndian.h
  56. 118 0
      panda/src/putil/bamEnums.cxx
  57. 77 0
      panda/src/putil/bamEnums.h
  58. 70 1
      panda/src/putil/bamReader.I
  59. 392 103
      panda/src/putil/bamReader.cxx
  60. 65 7
      panda/src/putil/bamReader.h
  61. 0 63
      panda/src/putil/bamTextureMode.cxx
  62. 0 35
      panda/src/putil/bamTextureMode.h
  63. 6 6
      panda/src/putil/bamWriter.I
  64. 197 79
      panda/src/putil/bamWriter.cxx
  65. 21 5
      panda/src/putil/bamWriter.h
  66. 1 1
      panda/src/putil/cachedTypedWritableReferenceCount.I
  67. 5 6
      panda/src/putil/config_util.cxx
  68. 3 4
      panda/src/putil/config_util.h
  69. 4 2
      panda/src/putil/copyOnWriteObject.I
  70. 1 1
      panda/src/putil/copyOnWriteObject.cxx
  71. 1 1
      panda/src/putil/copyOnWriteObject.h
  72. 13 0
      panda/src/putil/datagramOutputFile.cxx
  73. 1 0
      panda/src/putil/datagramOutputFile.h
  74. 1 2
      panda/src/putil/putil_composite1.cxx
  75. 18 27
      panda/src/putil/test_bam.cxx
  76. 1 3
      panda/src/putil/test_bam.h
  77. 5 4
      panda/src/putil/test_bamRead.cxx
  78. 5 6
      panda/src/putil/test_bamWrite.cxx
  79. 28 0
      panda/src/putil/typedWritable.I
  80. 33 14
      panda/src/putil/typedWritable.cxx
  81. 12 3
      panda/src/putil/typedWritable.h
  82. 11 0
      panda/src/putil/typedWritableReferenceCount.cxx
  83. 2 0
      panda/src/putil/typedWritableReferenceCount.h
  84. 6 1
      pandatool/src/bam/bamInfo.cxx
  85. 200 5
      pandatool/src/bam/bamToEgg.cxx
  86. 17 2
      pandatool/src/bam/bamToEgg.h
  87. 2 2
      pandatool/src/bam/eggToBam.cxx

+ 24 - 0
panda/src/cftalk/Sources.pp

@@ -0,0 +1,24 @@
+#define BUILDING_DLL BUILDING_CFTALK
+
+#define OTHER_LIBS interrogatedb:c dconfig:c dtoolconfig:m \
+                   dtoolutil:c dtoolbase:c dtool:m prc:c 
+
+#begin lib_target
+  #define TARGET cftalk
+  #define LOCAL_LIBS \
+    gsgbase gobj display \
+    putil linmath mathutil pnmimage
+
+  #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx
+
+  #define SOURCES \
+    config_cftalk.h \
+    cfChannel.h cfChannel.I \
+    cfCommand.h cfCommand.I
+
+  #define INCLUDED_SOURCES \
+    cfChannel.cxx \
+    cfCommand.cxx
+
+#end lib_target
+

+ 14 - 0
panda/src/cftalk/cfChannel.I

@@ -0,0 +1,14 @@
+// Filename: cfChannel.I
+// Created by:  drose (26Mar09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+

+ 75 - 0
panda/src/cftalk/cfChannel.cxx

@@ -0,0 +1,75 @@
+// Filename: cfChannel.cxx
+// Created by:  drose (26Mar09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "cfChannel.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFChannel::Constructor
+//       Access: Public
+//  Description: The DatagramGenerator and DatagramSink should be
+//               newly created on the free store (via the new
+//               operator).  The CFChannel will take ownership of
+//               these pointers, and will delete them when it
+//               destructs.
+////////////////////////////////////////////////////////////////////
+CFChannel::
+CFChannel(DatagramGenerator *dggen, DatagramSink *dgsink) :
+  _dggen(dggen),
+  _dgsink(dgsink),
+  _reader(dggen),
+  _writer(dgsink)
+{
+  bool ok1 = _reader.init();
+  bool ok2 = _writer.init();
+  nassertv(ok1 && ok2);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFChannel::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+CFChannel::
+~CFChannel() {
+  delete _dggen;
+  delete _dgsink;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFChannel::send_command
+//       Access: Public
+//  Description: Delivers a single command to the process at the other
+//               end of the channel.
+////////////////////////////////////////////////////////////////////
+void CFChannel::
+send_command(CFCommand *command) {
+  bool ok = _writer.write_object(command);
+  nassertv(ok);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFChannel::receive_command
+//       Access: Public
+//  Description: Receives a single command from the process at the other
+//               end of the channel.  If no command is ready, the
+//               thread will block until one is.  Returns NULL when
+//               the connection has been closed.
+////////////////////////////////////////////////////////////////////
+PT(CFCommand) CFChannel::
+receive_command() {
+  TypedWritable *obj = _reader.read_object();
+  CFCommand *command;
+  DCAST_INTO_R(command, obj, NULL);
+  return command;
+}

+ 47 - 0
panda/src/cftalk/cfChannel.h

@@ -0,0 +1,47 @@
+// Filename: cfChannel.h
+// Created by:  drose (26Mar09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 CFCHANNEL_H
+#define CFCHANNEL_H
+
+#include "pandabase.h"
+#include "referenceCount.h"
+#include "bamReader.h"
+#include "bamWriter.h"
+#include "cfCommand.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : CFChannel
+// Description : Represents an open communication channel in the
+//               connected-frame protocol.  Commands may be sent and
+//               received on this channel.
+////////////////////////////////////////////////////////////////////
+class EXPCL_CFTALK CFChannel : public ReferenceCount {
+public:
+  CFChannel(DatagramGenerator *dggen, DatagramSink *dgsink);
+  ~CFChannel();
+
+  void send_command(CFCommand *command);
+  PT(CFCommand) receive_command();
+
+private:
+  DatagramGenerator *_dggen;
+  DatagramSink *_dgsink;
+  BamReader _reader;
+  BamWriter _writer;
+};
+
+#include "cfChannel.I"
+
+#endif

+ 51 - 0
panda/src/cftalk/cfCommand.I

@@ -0,0 +1,51 @@
+// Filename: cfCommand.I
+// Created by:  drose (19Feb09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFCommand::Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE CFCommand::
+CFCommand() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFDoCullCommand::Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE CFDoCullCommand::
+CFDoCullCommand() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFDoCullCommand::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE CFDoCullCommand::
+CFDoCullCommand(PandaNode *scene) : _scene(scene) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFDoCullCommand::get_scene
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *CFDoCullCommand::
+get_scene() const {
+  return _scene;
+}

+ 113 - 0
panda/src/cftalk/cfCommand.cxx

@@ -0,0 +1,113 @@
+// Filename: cfCommand.cxx
+// Created by:  drose (19Feb09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "cfCommand.h"
+
+TypeHandle CFCommand::_type_handle;
+TypeHandle CFDoCullCommand::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFCommand::Destructor
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+CFCommand::
+~CFCommand() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFDoCullCommand::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               CFDoCullCommand.
+////////////////////////////////////////////////////////////////////
+void CFDoCullCommand::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFDoCullCommand::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void CFDoCullCommand::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  TypedWritable::write_datagram(manager, dg);
+  manager->write_pointer(dg, _scene);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFDoCullCommand::update_bam_nested
+//       Access: Public, Virtual
+//  Description: Called by the BamWriter when this object has not
+//               itself been modified recently, but it should check
+//               its nested objects for updates.
+////////////////////////////////////////////////////////////////////
+void CFDoCullCommand::
+update_bam_nested(BamWriter *manager) {
+  manager->consider_update(_scene);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFDoCullCommand::complete_pointers
+//       Access: Public, Virtual
+//  Description: Receives an array of pointers, one for each time
+//               manager->read_pointer() was called in fillin().
+//               Returns the number of pointers processed.
+////////////////////////////////////////////////////////////////////
+int CFDoCullCommand::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = TypedWritable::complete_pointers(p_list, manager);
+  
+  PandaNode *scene;
+  DCAST_INTO_R(scene, p_list[pi++], pi);
+  _scene = scene;
+
+  return pi;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFDoCullCommand::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type CFDoCullCommand is encountered
+//               in the Bam file.  It should create the CFDoCullCommand
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *CFDoCullCommand::
+make_from_bam(const FactoryParams &params) {
+  CFDoCullCommand *node = new CFDoCullCommand;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  node->fillin(scan, manager);
+
+  return node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CFDoCullCommand::fillin
+//       Access: Protected
+//  Description: This internal function is called by make_from_bam to
+//               read in all of the relevant data from the BamFile for
+//               the new CFDoCullCommand.
+////////////////////////////////////////////////////////////////////
+void CFDoCullCommand::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  TypedWritable::fillin(scan, manager);
+  manager->read_pointer(scan);
+}

+ 103 - 0
panda/src/cftalk/cfCommand.h

@@ -0,0 +1,103 @@
+// Filename: cfCommand.h
+// Created by:  drose (19Feb09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 CFCOMMAND_H
+#define CFCOMMAND_H
+
+#include "pandabase.h"
+
+#include "typedWritableReferenceCount.h"
+#include "pandaNode.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : CFCommand
+// Description : A single command in the Connected-Frame protocol.
+//               This can be sent client-to-server or
+//               server-to-client.
+//
+//               This is an abstract base class.  Individual commands
+//               will specialize from this.
+////////////////////////////////////////////////////////////////////
+class EXPCL_CFTALK CFCommand : public TypedWritableReferenceCount {
+protected:
+  CFCommand();
+
+PUBLISHED:
+  virtual ~CFCommand();
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWritableReferenceCount::init_type();
+    register_type(_type_handle, "CFCommand",
+                  TypedWritableReferenceCount::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+////////////////////////////////////////////////////////////////////
+//       Class : CFDoCullCommand
+// Description : Starts the cull process for a particular
+//               DisplayRegion.
+////////////////////////////////////////////////////////////////////
+class EXPCL_CFTALK CFDoCullCommand : public CFCommand {
+protected:
+  INLINE CFDoCullCommand();
+PUBLISHED:
+  INLINE CFDoCullCommand(PandaNode *scene);
+
+  INLINE PandaNode *get_scene() const;
+
+private:
+  PT(PandaNode) _scene;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual void update_bam_nested(BamWriter *manager);
+  virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    CFCommand::init_type();
+    register_type(_type_handle, "CFDoCullCommand",
+                  CFCommand::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "cfCommand.I"
+
+#endif

+ 1 - 0
panda/src/cftalk/cftalk_composite1.cxx

@@ -0,0 +1 @@
+#include "cfCommand.cxx"

+ 1 - 0
panda/src/cftalk/cftalk_composite2.cxx

@@ -0,0 +1 @@
+#include "cfChannel.cxx"

+ 49 - 0
panda/src/cftalk/config_cftalk.cxx

@@ -0,0 +1,49 @@
+// Filename: config_cftalk.cxx
+// Created by:  drose (26Mar09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "config_cftalk.h"
+#include "cfCommand.h"
+#include "pandaSystem.h"
+
+ConfigureDef(config_cftalk);
+NotifyCategoryDef(cftalk, "");
+
+ConfigureFn(config_cftalk) {
+  init_libcftalk();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: init_libcftalk
+//  Description: Initializes the library.  This must be called at
+//               least once before any of the functions or classes in
+//               this library can be used.  Normally it will be
+//               called by the static initializers and need not be
+//               called explicitly, but special cases exist.
+////////////////////////////////////////////////////////////////////
+void
+init_libcftalk() {
+  static bool initialized = false;
+  if (initialized) {
+    return;
+  }
+  initialized = true;
+
+  CFCommand::init_type();
+  CFDoCullCommand::init_type();
+
+  CFDoCullCommand::register_with_read_factory();
+
+  PandaSystem *ps = PandaSystem::get_global_ptr();
+  ps->add_system("cftalk");
+}

+ 37 - 0
panda/src/cftalk/config_cftalk.h

@@ -0,0 +1,37 @@
+// Filename: config_cftalk.h
+// Created by:  drose (26Mar09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 CONFIG_CFTALK_H
+#define CONFIG_CFTALK_H
+
+#include "pandabase.h"
+#include "windowProperties.h"
+#include "notifyCategoryProxy.h"
+#include "configVariableBool.h"
+#include "configVariableString.h"
+#include "configVariableList.h"
+#include "configVariableInt.h"
+#include "configVariableEnum.h"
+#include "configVariableFilename.h"
+#include "coordinateSystem.h"
+#include "dconfig.h"
+
+#include "pvector.h"
+
+ConfigureDecl(config_cftalk, EXPCL_CFTALK, EXPTP_CFTALK);
+NotifyCategoryDecl(cftalk, EXPCL_CFTALK, EXPTP_CFTALK);
+
+extern EXPCL_CFTALK void init_libcftalk();
+
+#endif  /* CONFIG_CFTALK_H */

+ 0 - 1
panda/src/display/config_display.cxx

@@ -12,7 +12,6 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-
 #include "config_display.h"
 #include "displayRegion.h"
 #include "displayRegionCullCallbackData.h"

+ 1 - 1
panda/src/dxgsg8/dxTextureContext8.cxx

@@ -788,7 +788,7 @@ create_texture(DXScreenData &scrn) {
         BamCache *cache = BamCache::get_global_ptr();
         PT(BamCacheRecord) record = cache->lookup(tex->get_fullpath(), "txo");
         if (record != (BamCacheRecord *)NULL) {
-          record->set_data(tex, false);
+          record->set_data(tex, tex);
           cache->store(record);
         }
       }

+ 1 - 1
panda/src/dxgsg9/dxTextureContext9.cxx

@@ -938,7 +938,7 @@ create_texture(DXScreenData &scrn) {
         BamCache *cache = BamCache::get_global_ptr();
         PT(BamCacheRecord) record = cache->lookup(tex->get_fullpath(), "txo");
         if (record != (BamCacheRecord *)NULL) {
-          record->set_data(tex, false);
+          record->set_data(tex, tex);
           cache->store(record);
         }
       }

+ 3 - 2
panda/src/express/datagramSink.cxx

@@ -16,9 +16,10 @@
 
 ////////////////////////////////////////////////////////////////////
 //     Function: DatagramSink::Destructor
-//       Access: Public
+//       Access: Public, Virtual
 //  Description: Does nothing since this is class is just
 //               the definition of an interface
 ////////////////////////////////////////////////////////////////////
-DatagramSink::~DatagramSink(){
+DatagramSink::
+~DatagramSink(){
 }

+ 1 - 0
panda/src/express/datagramSink.h

@@ -32,6 +32,7 @@ PUBLISHED:
 
   virtual bool put_datagram(const Datagram &data) = 0;
   virtual bool is_error() = 0;
+  virtual void flush() = 0;
 };
 
 #include "datagramSink.I"

+ 5 - 12
panda/src/express/referenceCount.I

@@ -88,16 +88,10 @@ operator = (const ReferenceCount &) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCount::Destructor
-//       Access: Protected
-//  Description: The ReferenceCount destructor is protected to
-//               discourage users from accidentally trying to delete a
-//               ReferenceCount pointer directly.  This is almost
-//               always a bad idea, since the destructor is not
-//               virtual, and you've almost certainly got some pointer
-//               to something that inherits from ReferenceCount, not
-//               just a plain old ReferenceCount object.
+//       Access: Public, Virtual
+//  Description: 
 ////////////////////////////////////////////////////////////////////
-INLINE ReferenceCount::
+ReferenceCount::
 ~ReferenceCount() {
   TAU_PROFILE("ReferenceCount::~ReferenceCount()", " ", TAU_USER);
   nassertv(this != NULL);
@@ -193,13 +187,12 @@ ref() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCount::unref
-//       Access: Published
+//       Access: Published, Virtual
 //  Description: Explicitly decrements the reference count.  Note that
 //               the object will not be implicitly deleted by unref()
 //               simply because the reference count drops to zero.
 //               (Having a member function delete itself is
-//               problematic; plus, we don't have a virtual destructor
-//               anyway.) However, see the helper function
+//               problematic.) However, see the helper function
 //               unref_delete().
 //
 //               User code should avoid using ref() and unref()

+ 4 - 2
panda/src/express/referenceCount.h

@@ -43,12 +43,14 @@ protected:
   INLINE ReferenceCount();
   INLINE ReferenceCount(const ReferenceCount &);
   INLINE void operator = (const ReferenceCount &);
-  INLINE ~ReferenceCount();
+
+public:
+  virtual INLINE ~ReferenceCount();
 
 PUBLISHED:
   INLINE int get_ref_count() const;
   INLINE void ref() const;
-  INLINE bool unref() const;
+  virtual INLINE bool unref() const;
 
   INLINE bool test_ref_count_integrity() const;
   INLINE bool test_ref_count_nonzero() const;

+ 1 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -7691,7 +7691,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force) {
           BamCache *cache = BamCache::get_global_ptr();
           PT(BamCacheRecord) record = cache->lookup(tex->get_fullpath(), "txo");
           if (record != (BamCacheRecord *)NULL) {
-            record->set_data(tex, false);
+            record->set_data(tex, tex);
             cache->store(record);
           }
         }

+ 2 - 2
panda/src/gobj/geomVertexArrayData.cxx

@@ -612,7 +612,7 @@ write_datagram(BamWriter *manager, Datagram &dg, void *extra_data) const {
 
   dg.add_uint32(_buffer.get_size());
 
-  if (manager->get_file_endian() == BE_native) {
+  if (manager->get_file_endian() == BamWriter::BE_native) {
     // For native endianness, we only have to write the data directly.
     dg.append_data(_buffer.get_read_pointer(true), _buffer.get_size());
 
@@ -656,7 +656,7 @@ fillin(DatagramIterator &scan, BamReader *manager, void *extra_data) {
 
   bool endian_reversed = false;
 
-  if (manager->get_file_endian() != BE_native) {
+  if (manager->get_file_endian() != BamReader::BE_native) {
     // For non-native endian files, we have to convert the data.  
 
     if (array_data->_array_format == (GeomVertexArrayFormat *)NULL) {

+ 1 - 1
panda/src/gobj/geomVertexArrayFormat.cxx

@@ -194,7 +194,7 @@ GeomVertexArrayFormat::
 
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexArrayFormat::unref
-//       Access: Published
+//       Access: Published, Virtual
 //  Description: This method overrides ReferenceCount::unref() to
 //               unregister the object when its reference count goes
 //               to zero.

+ 1 - 1
panda/src/gobj/geomVertexArrayFormat.h

@@ -77,7 +77,7 @@ PUBLISHED:
   void operator = (const GeomVertexArrayFormat &copy);
   ~GeomVertexArrayFormat();
 
-  bool unref() const;
+  virtual bool unref() const;
 
   INLINE bool is_registered() const;
   INLINE static CPT(GeomVertexArrayFormat) register_format(const GeomVertexArrayFormat *format);

+ 1 - 1
panda/src/gobj/geomVertexFormat.cxx

@@ -88,7 +88,7 @@ GeomVertexFormat::
 
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexFormat::unref
-//       Access: Published
+//       Access: Published, Virtual
 //  Description: This method overrides ReferenceCount::unref() to
 //               unregister the object when its reference count goes
 //               to zero.

+ 1 - 1
panda/src/gobj/geomVertexFormat.h

@@ -66,7 +66,7 @@ PUBLISHED:
   void operator = (const GeomVertexFormat &copy);
   virtual ~GeomVertexFormat();
 
-  bool unref() const;
+  virtual bool unref() const;
 
   INLINE bool is_registered() const;
   INLINE static CPT(GeomVertexFormat) register_format(const GeomVertexFormat *format);

+ 1 - 1
panda/src/gobj/internalName.cxx

@@ -74,7 +74,7 @@ InternalName::
 
 ////////////////////////////////////////////////////////////////////
 //     Function: InternalName::unref
-//       Access: Published
+//       Access: Published, Virtual
 //  Description: This method overrides ReferenceCount::unref() to
 //               clear the pointer from its parent's table when
 //               its reference count goes to zero.

+ 1 - 1
panda/src/gobj/internalName.h

@@ -45,7 +45,7 @@ private:
 
 PUBLISHED:
   virtual ~InternalName();
-  bool unref() const;
+  virtual bool unref() const;
 
   INLINE static PT(InternalName) make(const string &name);
   static PT(InternalName) make(const string &name, int index);

+ 12 - 12
panda/src/gobj/texture.cxx

@@ -3011,7 +3011,7 @@ do_write_txo(ostream &out, const string &filename) const {
     return false;
   }
 
-  writer.set_file_texture_mode(BTM_rawdata);
+  writer.set_file_texture_mode(BamWriter::BTM_rawdata);
 
   // We have to temporarily release the lock to allow it to write
   // (since the BamWriter will call write_datagram, which in turn
@@ -3155,8 +3155,8 @@ do_reload_ram_image(bool allow_compression) {
     record = cache->lookup(_fullpath, "txo");
     if (record != (BamCacheRecord *)NULL &&
         record->has_data()) {
-      PT(Texture) tex = DCAST(Texture, record->extract_data());
-
+      PT(Texture) tex = DCAST(Texture, record->get_data());
+      
       // But don't use the cache record if the config parameters have
       // changed, and we want a different-sized texture now.
       int x_size = _orig_file_x_size;
@@ -3204,7 +3204,7 @@ do_reload_ram_image(bool allow_compression) {
               // We've re-compressed the image after loading it from the
               // cache.  To keep the cache current, rewrite it to the
               // cache now, in its newly compressed form.
-              record->set_data(this, false);
+              record->set_data(this, this);
               cache->store(record);
             }
           }
@@ -3250,7 +3250,7 @@ do_reload_ram_image(bool allow_compression) {
       if (record != (BamCacheRecord *)NULL) {
         record->add_dependent_file(_fullpath);
       }
-      record->set_data(this, false);
+      record->set_data(this, this);
       cache->store(record);
     }
   }
@@ -6124,9 +6124,9 @@ write_datagram(BamWriter *manager, Datagram &me) {
   // Otherwise, we just write out the filename, and assume whoever
   // loads the bam file later will have access to the image file on
   // disk.
-  BamTextureMode file_texture_mode = manager->get_file_texture_mode();
+  BamWriter::BamTextureMode file_texture_mode = manager->get_file_texture_mode();
   bool has_rawdata =
-    (file_texture_mode == BTM_rawdata || (do_has_ram_image() && _filename.empty()));
+    (file_texture_mode == BamWriter::BTM_rawdata || (do_has_ram_image() && _filename.empty()));
   if (has_rawdata && !do_has_ram_image()) {
     do_get_ram_image();
     if (!do_has_ram_image()) {
@@ -6143,16 +6143,16 @@ write_datagram(BamWriter *manager, Datagram &me) {
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
 
   switch (file_texture_mode) {
-  case BTM_unchanged:
-  case BTM_rawdata:
+  case BamWriter::BTM_unchanged:
+  case BamWriter::BTM_rawdata:
     break;
 
-  case BTM_fullpath:
+  case BamWriter::BTM_fullpath:
     filename = _fullpath;
     alpha_filename = _alpha_fullpath;
     break;
 
-  case BTM_relative:
+  case BamWriter::BTM_relative:
     filename = _fullpath;
     alpha_filename = _alpha_fullpath;
     bam_dir.make_absolute(vfs->get_cwd());
@@ -6174,7 +6174,7 @@ write_datagram(BamWriter *manager, Datagram &me) {
     }
     break;
 
-  case BTM_basename:
+  case BamWriter::BTM_basename:
     filename = _fullpath.get_basename();
     alpha_filename = _alpha_fullpath.get_basename();
     break;

+ 6 - 6
panda/src/gobj/texturePool.cxx

@@ -323,7 +323,7 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
 
   if (store_record && tex->has_ram_image()) {
     // Store the on-disk cache record for next time.
-    record->set_data(tex, false);
+    record->set_data(tex, tex);
     cache->store(record);
   }
 
@@ -455,7 +455,7 @@ ns_load_texture(const Filename &orig_filename,
 
   if (store_record && tex->has_ram_image()) {
     // Store the on-disk cache record for next time.
-    record->set_data(tex, false);
+    record->set_data(tex, tex);
     cache->store(record);
   }
 
@@ -563,7 +563,7 @@ ns_load_3d_texture(const Filename &filename_pattern,
 
   if (store_record && tex->has_ram_image()) {
     // Store the on-disk cache record for next time.
-    record->set_data(tex, false);
+    record->set_data(tex, tex);
     cache->store(record);
   }
 
@@ -662,7 +662,7 @@ ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps,
 
   if (store_record && tex->has_ram_image()) {
     // Store the on-disk cache record for next time.
-    record->set_data(tex, false);
+    record->set_data(tex, tex);
     cache->store(record);
   }
 
@@ -941,7 +941,7 @@ try_load_cache(PT(Texture) &tex, BamCache *cache, const Filename &filename,
       record = cache->lookup(filename, "txo");
       if (record != (BamCacheRecord *)NULL) {
         if (record->has_data()) {
-          tex = DCAST(Texture, record->extract_data());
+          tex = DCAST(Texture, record->get_data());
           compressed_cache_record = (tex->get_ram_image_compression() != Texture::CM_off);
           int x_size = tex->get_orig_file_x_size();
           int y_size = tex->get_orig_file_y_size();
@@ -1002,7 +1002,7 @@ try_load_cache(PT(Texture) &tex, BamCache *cache, const Filename &filename,
                   // from the cache.  To keep the cache current,
                   // rewrite it to the cache now, in its newly
                   // compressed form.
-                  record->set_data(tex, false);
+                  record->set_data(tex, tex);
                   cache->store(record);
                   compressed_cache_record = true;
                 }

+ 34 - 0
panda/src/net/connectionReader.cxx

@@ -567,6 +567,15 @@ process_incoming_udp_data(SocketInfo *sinfo) {
   } else {
     datagram.set_connection(sinfo->_connection);
     datagram.set_address(NetAddress(addr));
+
+    if (net_cat.is_spam()) {
+      net_cat.spam()
+        << "Received UDP datagram with " 
+        << datagram_udp_header_size + datagram.get_length() 
+        << " bytes on " << (void *)datagram.get_connection()
+        << " from " << datagram.get_address() << "\n";
+    }
+
     receive_datagram(datagram);
   }
 
@@ -683,6 +692,15 @@ process_incoming_tcp_data(SocketInfo *sinfo) {
   } else {
     datagram.set_connection(sinfo->_connection);
     datagram.set_address(NetAddress(socket->GetPeerName()));
+
+    if (net_cat.is_spam()) {
+      net_cat.spam()
+        << "Received TCP datagram with " 
+        << _tcp_header_size + datagram.get_length() 
+        << " bytes on " << (void *)datagram.get_connection()
+        << " from " << datagram.get_address() << "\n";
+    }
+    
     receive_datagram(datagram);
   }
 
@@ -734,6 +752,14 @@ process_raw_incoming_udp_data(SocketInfo *sinfo) {
   
   datagram.set_connection(sinfo->_connection);
   datagram.set_address(NetAddress(addr));
+
+  if (net_cat.is_spam()) {
+    net_cat.spam()
+      << "Received raw UDP datagram with " << datagram.get_length() 
+      << " bytes on " << (void *)datagram.get_connection()
+      << " from " << datagram.get_address() << "\n";
+  }
+
   receive_datagram(datagram);
 
   return true;
@@ -782,6 +808,14 @@ process_raw_incoming_tcp_data(SocketInfo *sinfo) {
   
   datagram.set_connection(sinfo->_connection);
   datagram.set_address(NetAddress(socket->GetPeerName()));
+
+  if (net_cat.is_spam()) {
+    net_cat.spam()
+      << "Received raw TCP datagram with " << datagram.get_length() 
+      << " bytes on " << (void *)datagram.get_connection()
+      << " from " << datagram.get_address() << "\n";
+  }
+
   receive_datagram(datagram);
 
   return true;

+ 24 - 2
panda/src/net/datagramGeneratorNet.cxx

@@ -56,11 +56,19 @@ get_datagram(Datagram &data) {
   if (is_polling()) {
     // Single-threaded case: we poll.  No need to lock.
     if (!thing_available()) {
+      if (net_cat.is_spam()) {
+        net_cat.spam()
+          << "DatagramGeneratorNet polling\n";
+      }
       poll();
     }
     while (!thing_available()) {
       if (is_eof()) {
-        return false;
+        if (net_cat.is_spam()) {
+          net_cat.spam()
+            << "DatagramGeneratorNet returning EOF\n";
+        }
+	return false;
       }
       poll();
       Thread::force_yield();
@@ -74,7 +82,15 @@ get_datagram(Datagram &data) {
     MutexHolder holder(_dg_lock);
     while (!thing_available()) {
       if (is_eof()) {
-        return false;
+        if (net_cat.is_spam()) {
+          net_cat.spam()
+            << "DatagramGeneratorNet returning EOF\n";
+        }
+	return false;
+      }
+      if (net_cat.is_spam()) {
+        net_cat.spam()
+          << "DatagramGeneratorNet waiting\n";
       }
       _dg_received.wait();
     }
@@ -83,6 +99,12 @@ get_datagram(Datagram &data) {
     _dg_processed.notify();
   }
 
+  if (net_cat.is_spam()) {
+    net_cat.spam()
+      << "DatagramGeneratorNet returning datagram of length " 
+      << data.get_length() << "\n";
+  }
+  
   return true;
 }
 

+ 13 - 0
panda/src/net/datagramSinkNet.cxx

@@ -56,3 +56,16 @@ bool DatagramSinkNet::
 is_error() {
   return (_target == (Connection *)NULL || _target->get_socket() == (Socket_IP *)NULL);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: DatagramSinkNet::flush
+//       Access: Public, Virtual
+//  Description: Ensures that all datagrams previously written will be
+//               visible on the stream.
+////////////////////////////////////////////////////////////////////
+void DatagramSinkNet::
+flush() {
+  if (_target != (Connection *)NULL) {
+    _target->flush();
+  }
+}

+ 1 - 0
panda/src/net/datagramSinkNet.h

@@ -35,6 +35,7 @@ PUBLISHED:
   // Inherited from DatagramSink
   virtual bool put_datagram(const Datagram &data);
   virtual bool is_error();
+  virtual void flush();
 
 private:
   PT(Connection) _target;

+ 11 - 0
panda/src/pandabase/pandasymbols.h

@@ -200,6 +200,14 @@
   #define EXPTP_TINYDISPLAY extern
 #endif
 
+#ifdef BUILDING_CFTALK
+  #define EXPCL_CFTALK __declspec(dllexport)
+  #define EXPTP_CFTALK
+#else
+  #define EXPCL_CFTALK __declspec(dllimport)
+  #define EXPTP_CFTALK extern
+#endif
+
 #else   /* !WIN32_VC */
 
 #define EXPCL_FRAMEWORK
@@ -268,6 +276,9 @@
 #define EXPCL_TINYDISPLAY
 #define EXPTP_TINYDISPLAY
 
+#define EXPCL_CFTALK
+#define EXPTP_CFTALK
+
 #endif  /* WIN32_VC */
 
 #if defined(WIN32_VC) && !defined(CPPPARSER)

+ 3 - 3
panda/src/pgraph/bamFile.cxx

@@ -200,8 +200,8 @@ read_node(bool report_errors) {
     if (report_errors) {
       loader_cat.error()
         << "Unable to resolve Bam file.\n";
-      result = (PandaNode *)NULL;
     }
+    result = (PandaNode *)NULL;
   }
 
   return result;
@@ -280,7 +280,7 @@ write_object(const TypedWritable *object) {
 void BamFile::
 close() {
   if (_reader != (BamReader *)NULL) {
-    resolve();
+    //    resolve();
     delete _reader;
     _reader = NULL;
   }
@@ -331,7 +331,7 @@ get_file_minor_ver() {
 //  Description: Returns the endian preference indicated by the Bam
 //               file currently being read or written.
 ////////////////////////////////////////////////////////////////////
-BamEndian BamFile::
+BamFile::BamEndian BamFile::
 get_file_endian() const {
   if (_writer != (BamWriter *)NULL) {
     return _writer->get_file_endian();

+ 2 - 2
panda/src/pgraph/bamFile.h

@@ -16,11 +16,11 @@
 #define BAMFILE_H
 
 #include "pandabase.h"
-#include "bamEndian.h"
 #include "datagramInputFile.h"
 #include "datagramOutputFile.h"
 #include "pandaNode.h"
 #include "pointerTo.h"
+#include "bamEnums.h"
 
 class BamReader;
 class BamWriter;
@@ -42,7 +42,7 @@ class Filename;
 //               ending in ".boo" to differentiate them from the more
 //               common scene graph files.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA_PGRAPH BamFile {
+class EXPCL_PANDA_PGRAPH BamFile : public BamEnums {
 PUBLISHED:
   BamFile();
   ~BamFile();

+ 2 - 2
panda/src/pgraph/loader.cxx

@@ -294,7 +294,7 @@ try_load_file(const Filename &pathname, const LoaderOptions &options,
           loader_cat.info()
             << "Model " << pathname << " found in disk cache.\n";
         }
-        PT(PandaNode) result = DCAST(PandaNode, record->extract_data());
+        PT(PandaNode) result = DCAST(PandaNode, record->get_data());
         if (premunge_data) {
           SceneGraphReducer sgr;
           sgr.premunge(result, RenderState::make_empty());
@@ -308,7 +308,7 @@ try_load_file(const Filename &pathname, const LoaderOptions &options,
     PT(PandaNode) result = requested_type->load_file(pathname, options, record);
     if (result != (PandaNode *)NULL){ 
       if (record != (BamCacheRecord *)NULL) {
-        record->set_data(result, false);
+        record->set_data(result, result);
         cache->store(record);
       }
       

+ 12 - 1
panda/src/pgraph/pandaNode.I

@@ -13,7 +13,6 @@
 ////////////////////////////////////////////////////////////////////
 
 
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_num_parents
 //       Access: Published
@@ -664,6 +663,7 @@ INLINE void PandaNode::
 set_final(bool flag) {
   CDWriter cdata(_cycler);
   cdata->_final_bounds = flag;
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1874,3 +1874,14 @@ INLINE PandaNode::Parents PandaNodePipelineReader::
 get_parents() const {
   return PandaNode::Parents(_cdata);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::BamReaderAuxDataDown::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode::BamReaderAuxDataDown::
+BamReaderAuxDataDown() :
+  _down_list(PandaNode::get_class_type())
+{
+}

+ 190 - 36
panda/src/pgraph/pandaNode.cxx

@@ -33,6 +33,8 @@
 NotifyCategoryDecl(drawmask, EXPCL_PANDA_PGRAPH, EXPTP_PANDA_PGRAPH);
 NotifyCategoryDef(drawmask, "");
 
+TypeHandle PandaNode::BamReaderAuxDataDown::_type_handle;
+
 PandaNode::SceneRootFunc *PandaNode::_scene_root_func;
 
 PandaNodeChain PandaNode::_dirty_prev_transforms;
@@ -109,12 +111,18 @@ PandaNode::
 
   // We shouldn't have any parents left by the time we destruct, or
   // there's a refcount fault somewhere.
+
+  // Actually, that's not necessarily true anymore, since we might be
+  // updating a node dynamically via the bam reader, which doesn't
+  // necessarily keep related pairs of nodes in sync with each other.
+  /*
 #ifndef NDEBUG
   {
     CDReader cdata(_cycler);
     nassertv(cdata->get_up()->empty());
   }
 #endif  // NDEBUG
+  */
 
   remove_all_children();
 }
@@ -192,6 +200,17 @@ operator = (const PandaNode &copy) {
   nassertv(false);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::as_reference_count
+//       Access: Public, Virtual
+//  Description: Returns the pointer cast to a ReferenceCount pointer,
+//               if it is in fact of that type.
+////////////////////////////////////////////////////////////////////
+ReferenceCount *PandaNode::
+as_reference_count() {
+  return this;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::make_copy
 //       Access: Public, Virtual
@@ -361,6 +380,7 @@ apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
     }
     CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
   }
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -668,6 +688,8 @@ add_child(PandaNode *child_node, int sort, Thread *current_thread) {
 
   children_changed();
   child_node->parents_changed();
+  mark_bam_modified();
+  child_node->mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -698,6 +720,8 @@ remove_child(int child_index, Thread *current_thread) {
 
   children_changed();
   child_node->parents_changed();
+  mark_bam_modified();
+  child_node->mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -829,6 +853,8 @@ stash_child(int child_index, Thread *current_thread) {
 
   children_changed();
   child_node->parents_changed();
+  mark_bam_modified();
+  child_node->mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -875,6 +901,8 @@ unstash_child(int stashed_index, Thread *current_thread) {
   force_bounds_stale();
   children_changed();
   child_node->parents_changed();
+  mark_bam_modified();
+  child_node->mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -919,6 +947,8 @@ add_stashed(PandaNode *child_node, int sort, Thread *current_thread) {
   // Call callback hooks.
   children_changed();
   child_node->parents_changed();
+  mark_bam_modified();
+  child_node->mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -947,6 +977,8 @@ remove_stashed(int child_index, Thread *current_thread) {
 
   children_changed();
   child_node->parents_changed();
+  mark_bam_modified();
+  child_node->mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -973,6 +1005,7 @@ remove_all_children(Thread *current_thread) {
       
       sever_connection(this, child_node, pipeline_stage, current_thread);
       child_node->parents_changed();
+      child_node->mark_bam_modified();
     }
     down->clear();
     
@@ -985,6 +1018,7 @@ remove_all_children(Thread *current_thread) {
       
       sever_connection(this, child_node, pipeline_stage, current_thread);
       child_node->parents_changed();
+      child_node->mark_bam_modified();
     }
     stashed.clear();
   }
@@ -992,6 +1026,7 @@ remove_all_children(Thread *current_thread) {
 
   force_bounds_stale();
   children_changed();
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1091,6 +1126,7 @@ set_attrib(const RenderAttrib *attrib, int override) {
   if (any_changed) {
     mark_bounds_stale(current_thread);
     state_changed();
+    mark_bam_modified();
   }
 }
 
@@ -1124,6 +1160,7 @@ clear_attrib(int slot) {
   if (any_changed) {
     mark_bounds_stale(current_thread);
     state_changed();
+    mark_bam_modified();
   }
 }
 
@@ -1145,6 +1182,7 @@ set_effect(const RenderEffect *effect) {
     cdata->set_fancy_bit(FB_effects, true);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1162,6 +1200,7 @@ clear_effect(TypeHandle type) {
     cdata->set_fancy_bit(FB_effects, !cdata->_effects->is_empty());
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1193,6 +1232,7 @@ set_state(const RenderState *state, Thread *current_thread) {
   if (any_changed) {
     mark_bounds_stale(current_thread);
     state_changed();
+    mark_bam_modified();
   }
 }
 
@@ -1214,6 +1254,7 @@ set_effects(const RenderEffects *effects, Thread *current_thread) {
     cdata->set_fancy_bit(FB_effects, !effects->is_empty());
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1247,6 +1288,7 @@ set_transform(const TransformState *transform, Thread *current_thread) {
   if (any_changed) {
     mark_bounds_stale(current_thread);
     transform_changed();
+    mark_bam_modified();
   }
 }
 
@@ -1274,6 +1316,7 @@ set_prev_transform(const TransformState *transform, Thread *current_thread) {
     }
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1295,6 +1338,7 @@ reset_prev_transform(Thread *current_thread) {
     cdata->_prev_transform = cdata->_transform;
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1327,6 +1371,7 @@ reset_all_prev_transform(Thread *current_thread) {
     panda_node->_prev = NULL;
     panda_node->_next = NULL;
 #endif  // NDEBUG
+    panda_node->mark_bam_modified();
   }
 
   _dirty_prev_transforms._prev = &_dirty_prev_transforms;
@@ -1356,6 +1401,7 @@ set_tag(const string &key, const string &value, Thread *current_thread) {
     cdata->set_fancy_bit(FB_tag, true);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1373,6 +1419,7 @@ clear_tag(const string &key, Thread *current_thread) {
     cdata->set_fancy_bit(FB_tag, !cdata->_tag_data.empty());
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+  mark_bam_modified();
 }
 
 #ifdef HAVE_PYTHON
@@ -1412,6 +1459,9 @@ set_python_tag(const string &key, PyObject *value) {
     Py_XDECREF(old_value);
     (*ti).second = value;
   }
+
+  // Even though the python tag isn't recorded in the bam stream?
+  mark_bam_modified();
 }
 #endif  // HAVE_PYTHON
 
@@ -1478,6 +1528,9 @@ clear_python_tag(const string &key) {
     Py_XDECREF(value);
     cdata->_python_tag_data.erase(ti);
   }
+
+  // Even though the python tag isn't recorded in the bam stream?
+  mark_bam_modified();
 }
 #endif  // HAVE_PYTHON
 
@@ -1537,6 +1590,7 @@ copy_tags(PandaNode *other) {
 #endif // HAVE_PYTHON
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1765,6 +1819,7 @@ copy_all_properties(PandaNode *other) {
     if (any_draw_mask_changed) {
       draw_mask_changed();
     }
+    mark_bam_modified();
   }
 }
 
@@ -1968,6 +2023,7 @@ adjust_draw_mask(DrawMask show_mask, DrawMask hide_mask, DrawMask clear_mask) {
   if (any_changed) {
     mark_bounds_stale(current_thread);
     draw_mask_changed();
+    mark_bam_modified();
   }
 }
 
@@ -2063,6 +2119,7 @@ set_into_collide_mask(CollideMask mask) {
 
   if (any_changed) {
     mark_bounds_stale(current_thread);
+    mark_bam_modified();
   }
 }
 
@@ -2281,6 +2338,7 @@ set_bounds_type(BoundingVolume::BoundsType bounds_type) {
     // bounds that may need to be updated when the bounds_type
     // changes.
     mark_internal_bounds_stale(pipeline_stage, current_thread);
+    mark_bam_modified();
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
 }
@@ -2322,6 +2380,7 @@ set_bounds(const BoundingVolume *volume) {
       cdata->_user_bounds = volume->make_copy();
     }
     mark_bounds_stale(pipeline_stage, current_thread);
+    mark_bam_modified();
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
 }
@@ -2573,6 +2632,7 @@ get_internal_bounds(int pipeline_stage, Thread *current_thread) const {
       cdataw->_internal_bounds_computed = mark;
       cdataw->_internal_bounds = internal_bounds;
       cdataw->_internal_vertices = internal_vertices;
+      ((PandaNode *)this)->mark_bam_modified();
       return cdataw->_internal_bounds;
     }
 
@@ -2619,6 +2679,7 @@ get_internal_vertices(int pipeline_stage, Thread *current_thread) const {
       cdataw->_internal_bounds_computed = mark;
       cdataw->_internal_bounds = internal_bounds;
       cdataw->_internal_vertices = internal_vertices;
+      ((PandaNode *)this)->mark_bam_modified();
       return cdataw->_internal_vertices;
     }
 
@@ -2647,6 +2708,7 @@ set_internal_bounds(const BoundingVolume *volume) {
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
   mark_bounds_stale(current_thread);
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -2680,6 +2742,7 @@ force_bounds_stale(int pipeline_stage, Thread *current_thread) {
   {
     CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
     ++cdata->_next_update;
+    mark_bam_modified();
 
     // It is important that we allow this lock to be dropped before we
     // continue up the graph; otherwise, we risk deadlock from another
@@ -2924,6 +2987,7 @@ set_cull_callback() {
     cdata->set_fancy_bit(FB_cull_callback, true);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -2939,6 +3003,7 @@ disable_cull_callback() {
     cdata->set_fancy_bit(FB_cull_callback, false);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+  mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -3062,6 +3127,9 @@ stage_replace_child(PandaNode *orig_child, PandaNode *new_child,
   force_bounds_stale(pipeline_stage, current_thread);
   orig_child->parents_changed();
   new_child->parents_changed();
+  mark_bam_modified();
+  orig_child->mark_bam_modified();
+  new_child->mark_bam_modified();
 
   return true;
 }
@@ -3251,6 +3319,7 @@ detach_one_stage(NodePathComponent *child, int pipeline_stage,
 
   parent_node->force_bounds_stale(pipeline_stage, current_thread);
   parent_node->children_changed();
+  parent_node->mark_bam_modified();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -3288,8 +3357,10 @@ reparent(NodePathComponent *new_parent, NodePathComponent *child, int sort,
 
   if (new_parent != (NodePathComponent *)NULL) {
     new_parent->get_node()->children_changed();
+    new_parent->get_node()->mark_bam_modified();
   }
   child->get_node()->parents_changed();
+  child->get_node()->mark_bam_modified();
 
   return any_ok;
 }
@@ -4029,7 +4100,11 @@ update_bounds(int pipeline_stage, PandaNode::CDLockedStageReader &cdata) {
             << "} " << *this << "::update_bounds();\n";
         }
 
-        nassertr(cdataw->_last_update == cdataw->_next_update, cdataw)
+        nassertr(cdataw->_last_update == cdataw->_next_update, cdataw);
+    
+        // Even though implicit bounding volume is not (yet?) part of
+        // the bam stream.
+        mark_bam_modified();
         return cdataw;
       }
       
@@ -4092,6 +4167,19 @@ write_datagram(BamWriter *manager, Datagram &dg) {
   manager->write_cdata(dg, _cycler);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::update_bam_nested
+//       Access: Public, Virtual
+//  Description: Called by the BamWriter when this object has not
+//               itself been modified recently, but it should check
+//               its nested objects for updates.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+update_bam_nested(BamWriter *manager) {
+  CDReader cdata(_cycler);
+  cdata->update_bam_nested(manager);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::write_recorder
 //       Access: Public
@@ -4296,7 +4384,26 @@ write_datagram(BamWriter *manager, Datagram &dg) const {
   write_up_list(*get_up(), manager, dg);
   write_down_list(*get_down(), manager, dg);
   write_down_list(*get_stashed(), manager, dg);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::CData::update_bam_nested
+//       Access: Public
+//  Description: Called by the BamWriter when this object has not
+//               itself been modified recently, but it should check
+//               its nested objects for updates.
+////////////////////////////////////////////////////////////////////
+void PandaNode::CData::
+update_bam_nested(BamWriter *manager) const {
+  // No need to check the state pointers for updates, since they're
+  // all immutable objects.
+  //manager->consider_update(_state);
+  //manager->consider_update(_transform);
+  //manager->consider_update(_effects);
 
+  update_up_list(*get_up(), manager);
+  update_down_list(*get_down(), manager);
+  update_down_list(*get_stashed(), manager);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -4344,9 +4451,9 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
   //
 
   // Get the parent and child pointers.
-  pi += complete_up_list(*modify_up(), p_list + pi, manager);
-  pi += complete_down_list(*modify_down(), p_list + pi, manager);
-  pi += complete_down_list(*modify_stashed(), p_list + pi, manager);
+  pi += complete_up_list(*modify_up(), "up", p_list + pi, manager);
+  pi += complete_down_list(*modify_down(), "down", p_list + pi, manager);
+  pi += complete_down_list(*modify_stashed(), "stashed", p_list + pi, manager);
 
   // Since the _effects and _states members have been finalized by
   // now, this should be safe.
@@ -4417,10 +4524,9 @@ fillin(DatagramIterator &scan, BamReader *manager) {
   }
 
   //
-  fillin_up_list(*modify_up(), scan, manager);
-  fillin_down_list(*modify_down(), scan, manager);
-  fillin_down_list(*modify_stashed(), scan, manager);
-
+  fillin_up_list(*modify_up(), "up", scan, manager);
+  fillin_down_list(*modify_down(), "down", scan, manager);
+  fillin_down_list(*modify_stashed(), "stashed", scan, manager);
 }
 
 #ifdef HAVE_PYTHON
@@ -4521,6 +4627,38 @@ write_down_list(const PandaNode::Down &down_list,
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::CData::update_up_list
+//       Access: Public
+//  Description: Calls consider_update on each node of the indicated
+//               up list.
+////////////////////////////////////////////////////////////////////
+void PandaNode::CData::
+update_up_list(const PandaNode::Up &up_list, BamWriter *manager) const {
+  Up::const_iterator ui;
+  for (ui = up_list.begin(); ui != up_list.end(); ++ui) {
+    PandaNode *parent_node = (*ui).get_parent();
+    if (manager->has_object(parent_node)) {
+      manager->consider_update(parent_node);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::CData::update_down_list
+//       Access: Public
+//  Description: Calls consider_update on each node of the indicated
+//               up list.
+////////////////////////////////////////////////////////////////////
+void PandaNode::CData::
+update_down_list(const PandaNode::Down &down_list, BamWriter *manager) const {
+  Down::const_iterator di;
+  for (di = down_list.begin(); di != down_list.end(); ++di) {
+    PandaNode *child_node = (*di).get_child();
+    manager->consider_update(child_node);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::CData::complete_up_list
 //       Access: Public
@@ -4528,27 +4666,27 @@ write_down_list(const PandaNode::Down &down_list,
 //               pointers.
 ////////////////////////////////////////////////////////////////////
 int PandaNode::CData::
-complete_up_list(PandaNode::Up &up_list,
+complete_up_list(PandaNode::Up &up_list, const string &tag,
                  TypedWritable **p_list, BamReader *manager) {
   int pi = 0;
 
-  // Get the parent pointers.
-  Up::iterator ui;
-  for (ui = up_list.begin(); ui != up_list.end(); ++ui) {
+  int num_parents = manager->get_int_tag(tag);
+  Up new_up_list(PandaNode::get_class_type());
+  new_up_list.reserve(num_parents);
+  for (int i = 0; i < num_parents; i++) {
     PandaNode *parent_node = DCAST(PandaNode, p_list[pi++]);
-
-    // For some reason, VC++ won't accept UpConnection as an inline
-    // temporary constructor here ("C2226: unexpected type
-    // PandaNode::UpConnection"), so we must make this assignment
-    // using an explicit temporary variable.
     UpConnection connection(parent_node);
-    (*ui) = connection;
+    new_up_list.push_back(connection);
   }
 
   // Now we should sort the list, since the sorting is based on
   // pointer order, which might be different from one session to the
   // next.
-  up_list.sort();
+  new_up_list.sort();
+
+  // Make it permanent.
+  up_list.swap(new_up_list);
+  new_up_list.clear();
 
   return pi;
 }
@@ -4560,21 +4698,29 @@ complete_up_list(PandaNode::Up &up_list,
 //               pointers.
 ////////////////////////////////////////////////////////////////////
 int PandaNode::CData::
-complete_down_list(PandaNode::Down &down_list,
+complete_down_list(PandaNode::Down &down_list, const string &tag,
                    TypedWritable **p_list, BamReader *manager) {
   int pi = 0;
 
-  Down::iterator di;
-  for (di = down_list.begin(); di != down_list.end(); ++di) {
-    int sort = (*di).get_sort();
-    PT(PandaNode) child_node = DCAST(PandaNode, p_list[pi++]);
-    (*di) = DownConnection(child_node, sort);
+  BamReaderAuxDataDown *aux;
+  DCAST_INTO_R(aux, manager->get_aux_tag(tag), pi);
+
+  Down &new_down_list = aux->_down_list;
+  for (Down::iterator di = new_down_list.begin();
+       di != new_down_list.end(); 
+       ++di) {
+    PandaNode *child_node = DCAST(PandaNode, p_list[pi++]);
+    (*di).set_child(child_node);
   }
 
   // Unlike the up list, we should *not* sort the down list.  The down
   // list is stored in a specific order, not related to pointer order;
   // and this order should be preserved from one session to the next.
 
+  // Make it permanent.
+  down_list.swap(new_down_list);
+  new_down_list.clear();
+
   return pi;
 }
 
@@ -4586,15 +4732,11 @@ complete_down_list(PandaNode::Down &down_list,
 //               each one).
 ////////////////////////////////////////////////////////////////////
 void PandaNode::CData::
-fillin_up_list(PandaNode::Up &up_list,
+fillin_up_list(PandaNode::Up &up_list, const string &tag,
                DatagramIterator &scan, BamReader *manager) {
   int num_parents = scan.get_uint16();
-  // Read the list of parent nodes.  Push back a NULL for each one.
-  up_list.reserve(num_parents);
-  for (int i = 0; i < num_parents; i++) {
-    manager->read_pointer(scan);
-    up_list.push_back(UpConnection(NULL));
-  }
+  manager->set_int_tag(tag, num_parents);
+  manager->read_pointers(scan, num_parents);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -4605,16 +4747,28 @@ fillin_up_list(PandaNode::Up &up_list,
 //               each one).
 ////////////////////////////////////////////////////////////////////
 void PandaNode::CData::
-fillin_down_list(PandaNode::Down &down_list,
+fillin_down_list(PandaNode::Down &down_list, const string &tag,
                  DatagramIterator &scan, BamReader *manager) {
   int num_children = scan.get_uint16();
-  // Read the list of child nodes.  Push back a NULL for each one.
-  down_list.reserve(num_children);
+
+  // Create a temporary down_list, with the right number of elements,
+  // but a NULL value for each pointer (we'll fill in the pointers
+  // later).  We need to do this to associate the sort values with
+  // their pointers.
+  Down new_down_list(PandaNode::get_class_type());
+  new_down_list.reserve(num_children);
   for (int i = 0; i < num_children; i++) {
     manager->read_pointer(scan);
     int sort = scan.get_int32();
-    down_list.push_back(DownConnection(NULL, sort));
+    DownConnection connection(NULL, sort);
+    new_down_list.push_back(connection);
   }
+
+  // Now store the temporary down_list in the BamReader, so we can get
+  // it during the call to complete_down_list().
+  PT(BamReaderAuxDataDown) aux = new BamReaderAuxDataDown;
+  aux->_down_list.swap(new_down_list);
+  manager->set_aux_tag(tag, aux);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 40 - 7
panda/src/pgraph/pandaNode.h

@@ -83,6 +83,7 @@ private:
   void operator = (const PandaNode &copy);
 
 public:
+  virtual ReferenceCount *as_reference_count();
   virtual PandaNode *make_copy() const;
   virtual PandaNode *dupe_for_flatten() const;
 
@@ -428,7 +429,33 @@ public:
   };
 
 private:
-  typedef CopyOnWriteObj1< ov_multiset<DownConnection>, TypeHandle > Down;
+  typedef ov_multiset<DownConnection> DownList;
+  typedef CopyOnWriteObj1< DownList, TypeHandle > Down;
+
+  // Store a pointer to the down_list during the bam read pass.
+  class BamReaderAuxDataDown : public BamReaderAuxData {
+  public:
+    INLINE BamReaderAuxDataDown();
+    Down _down_list;
+  public:
+    virtual TypeHandle get_type() const {
+      return get_class_type();
+    }
+    virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+    static TypeHandle get_class_type() {
+      return _type_handle;
+    }
+    
+  public:
+    static void init_type() {
+      BamReaderAuxData::init_type();
+      register_type(_type_handle, "BamReaderAuxDataDown",
+                    BamReaderAuxData::get_class_type());
+    }
+    
+  private:
+    static TypeHandle _type_handle;
+  };
 
   class EXPCL_PANDA_PGRAPH UpConnection {
   public:
@@ -441,7 +468,8 @@ private:
     // children do not circularly reference each other.
     PandaNode *_parent;
   };
-  typedef CopyOnWriteObj1< ov_set<UpConnection>, TypeHandle > Up;
+  typedef ov_set<UpConnection> UpList;
+  typedef CopyOnWriteObj1< UpList, TypeHandle > Up;
 
   // We also maintain a set of NodePathComponents in the node.  This
   // represents the set of instances of this node that we have
@@ -480,7 +508,8 @@ private:
     ALLOC_DELETED_CHAIN(CData);
 
     virtual CycleData *make_copy() const;
-    virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
+    virtual void write_datagram(BamWriter *manager, Datagram &dg) const; 
+    void update_bam_nested(BamWriter *manager) const;
     virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
     virtual TypeHandle get_parent_type() const {
@@ -579,13 +608,15 @@ private:
                        BamWriter *manager, Datagram &dg) const;
     void write_down_list(const Down &down_list,
                          BamWriter *manager, Datagram &dg) const;
-    int complete_up_list(Up &up_list,
+    void update_up_list(const Up &up_list, BamWriter *manager) const;
+    void update_down_list(const Down &down_list, BamWriter *manager) const;
+    int complete_up_list(Up &up_list, const string &tag,
                          TypedWritable **p_list, BamReader *manager);
-    int complete_down_list(Down &down_list,
+    int complete_down_list(Down &down_list, const string &tag,
                            TypedWritable **p_list, BamReader *manager);
-    void fillin_up_list(Up &up_list,
+    void fillin_up_list(Up &up_list, const string &tag,
                         DatagramIterator &scan, BamReader *manager);
-    void fillin_down_list(Down &down_list,
+    void fillin_down_list(Down &down_list, const string &tag,
                           DatagramIterator &scan, BamReader *manager);
 
     INLINE CPT(Down) get_down() const;
@@ -696,6 +727,7 @@ private:
 public:
   static void register_with_read_factory();
   virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual void update_bam_nested(BamWriter *manager);
   void write_recorder(BamWriter *manager, Datagram &dg);
 
 protected:
@@ -716,6 +748,7 @@ public:
     CData::init_type();
     Down::init_type();
     Up::init_type();
+    BamReaderAuxDataDown::init_type();
   }
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 1 - 1
panda/src/pgraph/renderAttrib.cxx

@@ -137,7 +137,7 @@ cull_callback(CullTraverser *, const CullTraverserData &) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderAttrib::unref
-//       Access: Published
+//       Access: Published, Virtual
 //  Description: This method overrides ReferenceCount::unref() to
 //               clear the pointer from the global object pool when
 //               its reference count goes to zero.

+ 1 - 1
panda/src/pgraph/renderAttrib.h

@@ -78,7 +78,7 @@ PUBLISHED:
   INLINE int compare_to(const RenderAttrib &other) const;
   INLINE CPT(RenderAttrib) get_unique() const;
 
-  bool unref() const;
+  virtual bool unref() const;
 
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level) const;

+ 1 - 1
panda/src/pgraph/renderEffects.cxx

@@ -382,7 +382,7 @@ get_effect(TypeHandle type) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderEffects::unref
-//       Access: Published
+//       Access: Published, Virtual
 //  Description: This method overrides ReferenceCount::unref() to
 //               check whether the remaining reference count is
 //               entirely in the cache, and if so, it checks for and

+ 1 - 1
panda/src/pgraph/renderEffects.h

@@ -85,7 +85,7 @@ PUBLISHED:
 
   const RenderEffect *get_effect(TypeHandle type) const;
 
-  bool unref() const;
+  virtual bool unref() const;
 
   void output(ostream &out) const;
   void write(ostream &out, int indent_level) const;

+ 1 - 8
panda/src/pgraph/renderState.cxx

@@ -696,20 +696,13 @@ adjust_all_priorities(int adjustment) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::unref
-//       Access: Published
+//       Access: Published, Virtual
 //  Description: This method overrides ReferenceCount::unref() to
 //               check whether the remaining reference count is
 //               entirely in the cache, and if so, it checks for and
 //               breaks a cycle in the cache involving this object.
 //               This is designed to prevent leaks from cyclical
 //               references within the cache.
-//
-//               Note that this is not a virtual method, and cannot be
-//               because ReferenceCount itself declares no virtual
-//               methods (it avoids the overhead of a virtual function
-//               pointer).  But this doesn't matter, because
-//               PT(TransformState) is a template class, and will call
-//               the appropriate method even though it is non-virtual.
 ////////////////////////////////////////////////////////////////////
 bool RenderState::
 unref() const {

+ 1 - 1
panda/src/pgraph/renderState.h

@@ -110,7 +110,7 @@ PUBLISHED:
 
   INLINE CPT(RenderState) get_unique() const;
 
-  bool unref() const;
+  virtual bool unref() const;
 
   INLINE void cache_ref() const;
   INLINE bool cache_unref() const;

+ 3 - 40
panda/src/pgraph/transformState.cxx

@@ -771,20 +771,13 @@ invert_compose(const TransformState *other) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::unref
-//       Access: Published
+//       Access: Published, Virtual
 //  Description: This method overrides ReferenceCount::unref() to
 //               check whether the remaining reference count is
 //               entirely in the cache, and if so, it checks for and
 //               breaks a cycle in the cache involving this object.
 //               This is designed to prevent leaks from cyclical
 //               references within the cache.
-//
-//               Note that this is not a virtual method, and cannot be
-//               because ReferenceCount itself declares no virtual
-//               methods (it avoids the overhead of a virtual function
-//               pointer).  But this doesn't matter, because
-//               PT(TransformState) is a template class, and will call
-//               the appropriate method even though it is non-virtual.
 ////////////////////////////////////////////////////////////////////
 bool TransformState::
 unref() const {
@@ -2421,47 +2414,17 @@ write_datagram(BamWriter *manager, Datagram &dg) {
 //               Once this function has been called, the old pointer
 //               will no longer be accessed.
 ////////////////////////////////////////////////////////////////////
-TypedWritable *TransformState::
-change_this(TypedWritable *old_ptr, BamReader *manager) {
+PT(TypedWritableReferenceCount) TransformState::
+change_this(TypedWritableReferenceCount *old_ptr, BamReader *manager) {
   // First, uniquify the pointer.
   TransformState *state = DCAST(TransformState, old_ptr);
   CPT(TransformState) pointer = return_unique(state);
-
-  // But now we have a problem, since we have to hold the reference
-  // count and there's no way to return a TypedWritable while still
-  // holding the reference count!  We work around this by explicitly
-  // upping the count, and also setting a finalize() callback to down
-  // it later.
-  if (pointer == state) {
-    pointer->ref();
-    manager->register_finalize(state);
-  }
   
   // We have to cast the pointer back to non-const, because the bam
   // reader expects that.
   return (TransformState *)pointer.p();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: TransformState::finalize
-//       Access: Public, Virtual
-//  Description: Called by the BamReader to perform any final actions
-//               needed for setting up the object after all objects
-//               have been read and all pointers have been completed.
-////////////////////////////////////////////////////////////////////
-void TransformState::
-finalize(BamReader *) {
-  // Unref the pointer that we explicitly reffed in change_this().
-  unref();
-
-  // We should never get back to zero after unreffing our own count,
-  // because we expect to have been stored in a pointer somewhere.  If
-  // we do get to zero, it's a memory leak; the way to avoid this is
-  // to call unref_delete() above instead of unref(), but this is
-  // dangerous to do from within a virtual function.
-  nassertv(get_ref_count() != 0);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::make_from_bam
 //       Access: Protected, Static

+ 2 - 3
panda/src/pgraph/transformState.h

@@ -173,7 +173,7 @@ PUBLISHED:
 
   INLINE int get_geom_rendering(int geom_rendering) const;
 
-  bool unref() const;
+  virtual bool unref() const;
 
   INLINE void cache_ref() const;
   INLINE bool cache_unref() const;
@@ -370,8 +370,7 @@ private:
 public:
   static void register_with_read_factory();
   virtual void write_datagram(BamWriter *manager, Datagram &dg);
-  static TypedWritable *change_this(TypedWritable *old_ptr, BamReader *manager);
-  virtual void finalize(BamReader *manager);
+  static PT(TypedWritableReferenceCount) change_this(TypedWritableReferenceCount *old_ptr, BamReader *manager);
 
 protected:
   static TypedWritable *make_from_bam(const FactoryParams &params);

+ 3 - 6
panda/src/putil/Sources.pp

@@ -14,10 +14,9 @@
     bamCache.h bamCache.I \
     bamCacheIndex.h bamCacheIndex.I \
     bamCacheRecord.h bamCacheRecord.I \
-    bamEndian.h \
+    bamEnums.h \
     bamReader.I bamReader.N bamReader.h bamReaderParam.I \
     bamReaderParam.h \
-    bamTextureMode.h \
     bamWriter.I bamWriter.h \
     bitArray.I bitArray.h \
     bitMask.I bitMask.h \
@@ -78,9 +77,8 @@
     bamCache.cxx \
     bamCacheIndex.cxx \
     bamCacheRecord.cxx \
-    bamEndian.cxx \
+    bamEnums.cxx \
     bamReader.cxx bamReaderParam.cxx \
-    bamTextureMode.cxx \
     bamWriter.cxx \
     bitArray.cxx \
     bitMask.cxx \
@@ -128,9 +126,8 @@
     bamCache.h bamCache.I \
     bamCacheIndex.h bamCacheIndex.I \
     bamCacheRecord.h bamCacheRecord.I \
-    bamEndian.h \
+    bamEnums.h \
     bamReader.I bamReader.h bamReaderParam.I bamReaderParam.h \
-    bamTextureMode.h \
     bamWriter.I bamWriter.h \
     bitArray.I bitArray.h \
     bitMask.I bitMask.h \

+ 2 - 1
panda/src/putil/bam.h

@@ -33,7 +33,7 @@ static const unsigned short _bam_major_ver = 6;
 // Bumped to major version 6 on 2/11/06 to factor out PandaNode::CData.
 
 static const unsigned short _bam_first_minor_ver = 14;
-static const unsigned short _bam_minor_ver = 20;
+static const unsigned short _bam_minor_ver = 21;
 // Bumped to minor version 14 on 12/19/07 to change default ColorAttrib.
 // Bumped to minor version 15 on 4/9/08 to add TextureAttrib::_implicit_sort.
 // Bumped to minor version 16 on 5/13/08 to add Texture::_quality_level.
@@ -41,6 +41,7 @@ static const unsigned short _bam_minor_ver = 20;
 // Bumped to minor version 18 on 8/14/08 to add Texture::_simple_ram_image.
 // Bumped to minor version 19 on 8/14/08 to add PandaNode::_bounds_type.
 // Bumped to minor version 20 on 4/21/09 to add MovingPartBase::_forced_channel.
+// Bumped to minor version 21 on 2/26/08 to add BamEnums::BamObjectCode.
 
 
 #endif

+ 6 - 5
panda/src/putil/bamCache.cxx

@@ -258,10 +258,10 @@ store(BamCacheRecord *record) {
     TypeHandle texture_type = type_registry->find_type("Texture");
     if (record->get_data()->is_of_type(texture_type)) {
       // Texture objects write the actual texture image.
-      writer.set_file_texture_mode(BTM_rawdata);
+      writer.set_file_texture_mode(BamWriter::BTM_rawdata);
     } else {
       // Any other kinds of objects write texture references.
-      writer.set_file_texture_mode(BTM_fullpath);
+      writer.set_file_texture_mode(BamWriter::BTM_fullpath);
     }
     
     if (!writer.write_object(record)) {
@@ -929,16 +929,17 @@ do_read_record(Filename &cache_pathname, bool read_data) {
   if (read_data && record->dependents_unchanged()) {
     // The cache record doesn't appear to be stale.  Load the cached
     // object.
-    object = reader.read_object();
+    TypedWritable *ptr;
+    ReferenceCount *ref_ptr;
 
-    if (object != (TypedWritable *)NULL) {
+    if (reader.read_object(ptr, ref_ptr)) {
       if (!reader.resolve()) {
         util_cat.debug()
           << "Unable to fully resolve cached object in " << cache_pathname << "\n";
         delete object;
       } else {
         // The object is valid.  Store it in the record.
-        record->set_data(object, true);
+        record->set_data(ptr, ref_ptr);
       }
     }
   }

+ 58 - 30
panda/src/putil/bamCacheRecord.I

@@ -112,7 +112,7 @@ get_dependent_pathname(int n) const {
 ////////////////////////////////////////////////////////////////////
 INLINE bool BamCacheRecord::
 has_data() const {
-  return (_data != (TypedWritable *)NULL);
+  return (_ptr != (TypedWritable *)NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -124,57 +124,85 @@ has_data() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void BamCacheRecord::
 clear_data() {
-  if (_owns_pointer) {
-    delete _data;
+  if (_ref_ptr != NULL) {
+    unref_delete(_ref_ptr);
   }
-  _data = NULL;
-  _owns_pointer = false;
+
+  _ptr = NULL;
+  _ref_ptr = NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamCacheRecord::get_data
 //       Access: Published
-//  Description: Returns a read-only pointer to the data stored in the
-//               record, or NULL if there is no data.  This does not
-//               change ownership of the data pointer.
+//  Description: Returns a pointer to the data stored in the
+//               record, or NULL if there is no data.  The pointer is
+//               not removed from the record.
 ////////////////////////////////////////////////////////////////////
-INLINE const TypedWritable *BamCacheRecord::
+INLINE TypedWritable *BamCacheRecord::
 get_data() const {
-  return _data;
+  return _ptr;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamCacheRecord::extract_data
 //       Access: Published
-//  Description: Removes the in-memory data object associated with
-//               this record, and returns it.  This transfers
-//               ownership of the data pointer; the caller will be
-//               responsible for subsequently deleting this object.
+//  Description: Fills ptr and ref_ptr with the two different-typed
+//               pointers to the same object, the data stored within
+//               this record.  This transfers ownership of the data
+//               pointer; the caller will be responsible for managing
+//               the reference counts on this object subsequently.
 //
-//               It is an error to call this if the record does not
-//               own the pointer.
+//               Returns true if the record contained any data (and
+//               the pointers have been filled), false if it didn't
+//               (and the pointers are NULL).
 ////////////////////////////////////////////////////////////////////
-INLINE TypedWritable *BamCacheRecord::
-extract_data() {
-  nassertr(_owns_pointer, NULL);
-  TypedWritable *result = _data;
-  _data = NULL;
-  _owns_pointer = false;
-  return result;
+INLINE bool BamCacheRecord::
+extract_data(TypedWritable *&ptr, ReferenceCount *&ref_ptr) {
+  ptr = _ptr;
+  ref_ptr = _ref_ptr;
+  clear_data();
+  return (ptr != (TypedWritable *)NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamCacheRecord::set_data
 //       Access: Published
-//  Description: Stores a new data object on the record.  If
-//               owns_pointer is true, the record owns the pointer,
-//               and will eventually be responsible for deleting it.
+//  Description: Stores a new data object on the record.  You should
+//               pass the same pointer twice, to both parameters; this
+//               allows the C++ typecasting to automatically convert
+//               the pointer into both a TypedWritable and a
+//               ReferenceCount pointer, so that the BamCacheRecord
+//               object can reliably manage the reference counts.
+//
+//               You may pass 0 or NULL as the second parameter.  If
+//               you do this, the BamCacheRecord will not manage the
+//               object's reference count; it will be up to you to
+//               ensure the object is not deleted during the lifetime
+//               of the BamCacheRecord object.
 ////////////////////////////////////////////////////////////////////
 INLINE void BamCacheRecord::
-set_data(TypedWritable *data, bool owns_pointer) {
-  clear_data();
-  _data = data;
-  _owns_pointer = owns_pointer;
+set_data(TypedWritable *ptr, ReferenceCount *ref_ptr) {
+  if (_ptr != ptr) {
+    clear_data();
+    _ptr = ptr;
+    _ref_ptr = ref_ptr;
+    if (_ref_ptr != NULL) {
+      _ref_ptr->ref();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamCacheRecord::set_data
+//       Access: Published
+//  Description: This variant on set_data() is provided just to allow
+//               Python code to pass a 0 as the second parameter.
+////////////////////////////////////////////////////////////////////
+INLINE void BamCacheRecord::
+set_data(TypedWritable *ptr, int dummy) {
+  nassertv(dummy == 0);
+  set_data(ptr, (ReferenceCount *)NULL);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 6 - 6
panda/src/putil/bamCacheRecord.cxx

@@ -28,8 +28,8 @@ BamCacheRecord::
 BamCacheRecord() :
   _recorded_time(0),
   _record_size(0),
-  _data(NULL),
-  _owns_pointer(false),
+  _ptr(NULL),
+  _ref_ptr(NULL),
   _record_access_time(0)
 {
 }
@@ -46,8 +46,8 @@ BamCacheRecord(const Filename &source_pathname,
   _cache_filename(cache_filename),
   _recorded_time(0),
   _record_size(0),
-  _data(NULL),
-  _owns_pointer(false),
+  _ptr(NULL),
+  _ref_ptr(NULL),
   _record_access_time(0)
 {
 }
@@ -64,8 +64,8 @@ BamCacheRecord(const BamCacheRecord &copy) :
   _cache_filename(copy._cache_filename),
   _recorded_time(copy._recorded_time),
   _record_size(copy._record_size),
-  _data(NULL),
-  _owns_pointer(false),
+  _ptr(NULL),
+  _ref_ptr(NULL),
   _record_access_time(copy._record_access_time)
 {
 }

+ 6 - 5
panda/src/putil/bamCacheRecord.h

@@ -63,9 +63,10 @@ PUBLISHED:
 
   INLINE bool has_data() const;
   INLINE void clear_data();
-  INLINE const TypedWritable *get_data() const;
-  INLINE TypedWritable *extract_data();
-  INLINE void set_data(TypedWritable *data, bool owns_pointer);
+  INLINE TypedWritable *get_data() const;
+  INLINE bool extract_data(TypedWritable *&ptr, ReferenceCount *&ref_ptr);
+  INLINE void set_data(TypedWritable *ptr, ReferenceCount *ref_ptr);
+  INLINE void set_data(TypedWritable *ptr, int dummy);
 
   void output(ostream &out) const;
   void write(ostream &out, int indent_level = 0) const;
@@ -97,8 +98,8 @@ private:
   // The following are not recorded to disk; they are preserved
   // in-memory only for the current session.
   Filename _cache_pathname;
-  TypedWritable *_data;
-  bool _owns_pointer;
+  TypedWritable *_ptr;
+  ReferenceCount *_ref_ptr;
 
   // The following are not recorded to disk, nor even returned by the
   // BamCache interface.  They are strictly meaningful to the

+ 0 - 52
panda/src/putil/bamEndian.cxx

@@ -1,52 +0,0 @@
-// Filename: bamEndian.cxx
-// Created by:  drose (07Jun05)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 "bamEndian.h"
-#include "string_utils.h"
-#include "config_util.h"
-
-ostream &
-operator << (ostream &out, BamEndian be) {
-  switch (be) {
-  case BE_bigendian:
-    return out << "bigendian";
-   
-  case BE_littleendian:
-    return out << "littleendian";
-  }
-
-  return out << "**invalid BamEndian value: (" << (int)be << ")**";
-}
-
-istream &
-operator >> (istream &in, BamEndian &be) {
-  string word;
-  in >> word;
-  if (cmp_nocase(word, "native") == 0) {
-    be = BE_native;
-
-  } else if (cmp_nocase(word, "bigendian") == 0) {
-    be = BE_bigendian;
-
-  } else if (cmp_nocase(word, "littleendian") == 0) {
-    be = BE_littleendian;
-
-  } else {
-    util_cat->error()
-      << "Invalid bam_endian string: " << word << "\n";
-    be = BE_native;
-  }
-
-  return in;
-}

+ 0 - 38
panda/src/putil/bamEndian.h

@@ -1,38 +0,0 @@
-// Filename: bamEndian.h
-// Created by:  drose (07Jun05)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 BAMENDIAN_H
-#define BAMENDIAN_H
-
-#include "pandabase.h"
-
-// This defines an enumerated type used to represent the endianness of
-// certain numeric values stored in a Bam file.  It really has only
-// two possible values, either BE_bigendian or BE_littleendian; but
-// through a preprocessor trick we also add BE_native, which is the
-// same numerically as whichever value the hardware supports natively.
-enum BamEndian {
-  BE_bigendian = 0,
-  BE_littleendian = 1,
-#ifdef WORDS_BIGENDIAN
-  BE_native = 0,
-#else
-  BE_native = 1,
-#endif
-};
-
-EXPCL_PANDA_PUTIL ostream &operator << (ostream &out, BamEndian be);
-EXPCL_PANDA_PUTIL istream &operator >> (istream &in, BamEndian &be);
-
-#endif

+ 118 - 0
panda/src/putil/bamEnums.cxx

@@ -0,0 +1,118 @@
+// Filename: bamEnums.cxx
+// Created by:  drose (26Feb09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "bamEnums.h"
+#include "string_utils.h"
+#include "config_util.h"
+
+ostream &
+operator << (ostream &out, BamEnums::BamEndian be) {
+  switch (be) {
+  case BamEnums::BE_bigendian:
+    return out << "bigendian";
+   
+  case BamEnums::BE_littleendian:
+    return out << "littleendian";
+  }
+
+  return out << "**invalid BamEnums::BamEndian value: (" << (int)be << ")**";
+}
+
+istream &
+operator >> (istream &in, BamEnums::BamEndian &be) {
+  string word;
+  in >> word;
+  if (cmp_nocase(word, "native") == 0) {
+    be = BamEnums::BE_native;
+
+  } else if (cmp_nocase(word, "bigendian") == 0) {
+    be = BamEnums::BE_bigendian;
+
+  } else if (cmp_nocase(word, "littleendian") == 0) {
+    be = BamEnums::BE_littleendian;
+
+  } else {
+    util_cat->error()
+      << "Invalid bam_endian string: " << word << "\n";
+    be = BamEnums::BE_native;
+  }
+
+  return in;
+}
+
+
+ostream &
+operator << (ostream &out, BamEnums::BamObjectCode boc) {
+  switch (boc) {
+  case BamEnums::BOC_push:
+    return out << "push";
+   
+  case BamEnums::BOC_pop:
+    return out << "pop";
+
+  case BamEnums::BOC_adjunct:
+    return out << "adjunct";
+
+  case BamEnums::BOC_remove:
+    return out << "remove";
+  }
+
+  return out << "**invalid BamEnums::BamObjectCode value: (" << (int)boc << ")**";
+}
+
+ostream &
+operator << (ostream &out, BamEnums::BamTextureMode btm) {
+  switch (btm) {
+  case BamEnums::BTM_unchanged:
+    return out << "unchanged";
+   
+  case BamEnums::BTM_fullpath:
+    return out << "fullpath";
+    
+  case BamEnums::BTM_relative:
+    return out << "relative";
+    
+  case BamEnums::BTM_basename:
+    return out << "basename";
+
+  case BamEnums::BTM_rawdata:
+    return out << "rawdata";
+  }
+
+  return out << "**invalid BamEnums::BamTextureMode (" << (int)btm << ")**";
+}
+
+istream &
+operator >> (istream &in, BamEnums::BamTextureMode &btm) {
+  string word;
+  in >> word;
+
+  if (cmp_nocase(word, "unchanged") == 0) {
+    btm = BamEnums::BTM_unchanged;
+  } else if (cmp_nocase(word, "fullpath") == 0) {
+    btm = BamEnums::BTM_fullpath;
+  } else if (cmp_nocase(word, "relative") == 0) {
+    btm = BamEnums::BTM_relative;
+  } else if (cmp_nocase(word, "basename") == 0) {
+    btm = BamEnums::BTM_basename;
+  } else if (cmp_nocase(word, "rawdata") == 0) {
+    btm = BamEnums::BTM_rawdata;
+
+  } else {
+    util_cat->error() << "Invalid BamEnums::BamTextureMode value: " << word << "\n";
+    btm = BamEnums::BTM_relative;
+  }
+
+  return in;
+}

+ 77 - 0
panda/src/putil/bamEnums.h

@@ -0,0 +1,77 @@
+// Filename: bamEnums.h
+// Created by:  drose (26Feb09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 BAMENUMS_H
+#define BAMENUMS_H
+
+#include "pandabase.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : BamEnums
+// Description : This class exists just to provide scoping for the
+//               enums shared by BamReader and BamWriter.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_PUTIL BamEnums {
+PUBLISHED:
+
+  // This defines an enumerated type used to represent the endianness of
+  // certain numeric values stored in a Bam file.  It really has only
+  // two possible values, either BE_bigendian or BE_littleendian; but
+  // through a preprocessor trick we also add BE_native, which is the
+  // same numerically as whichever value the hardware supports natively.
+  enum BamEndian {
+    BE_bigendian = 0,
+    BE_littleendian = 1,
+#ifdef WORDS_BIGENDIAN
+    BE_native = 0,
+#else
+    BE_native = 1,
+#endif
+  };
+
+  // This is the code written along with each object.  It is used to
+  // control object scoping.  A BOC_push includes an object
+  // definition, and will always be eventually paired with a BOC_pop
+  // (which does not).  A BOC_adjunct includes an object definition
+  // but does not push the level; it is associated with the current
+  // level.  BOC_remove lists object ID's that have been deallocated
+  // on the sender end.
+  enum BamObjectCode {
+    BOC_push,
+    BOC_pop,
+    BOC_adjunct,
+    BOC_remove,
+  };
+
+  // This enum is used to control how textures are written to a bam
+  // stream.
+  enum BamTextureMode {
+    BTM_unchanged,
+    BTM_fullpath,
+    BTM_relative,
+    BTM_basename,
+    BTM_rawdata
+  };
+};
+
+EXPCL_PANDA_PUTIL ostream &operator << (ostream &out, BamEnums::BamEndian be);
+EXPCL_PANDA_PUTIL istream &operator >> (istream &in, BamEnums::BamEndian &be);
+
+EXPCL_PANDA_PUTIL ostream &operator << (ostream &out, BamEnums::BamObjectCode boc);
+
+EXPCL_PANDA_PUTIL ostream &operator << (ostream &out, BamEnums::BamTextureMode btm);
+EXPCL_PANDA_PUTIL istream &operator >> (istream &in, BamEnums::BamTextureMode &btm);
+
+#endif
+

+ 70 - 1
panda/src/putil/bamReader.I

@@ -13,6 +13,15 @@
 ////////////////////////////////////////////////////////////////////
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: BamReaderAuxData::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE BamReaderAuxData::
+BamReaderAuxData() {
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BamReader::get_filename
 //       Access: Public
@@ -90,7 +99,7 @@ get_file_minor_ver() const {
 //               convention, but individual objects may choose to
 //               respect this flag when recording data.
 ////////////////////////////////////////////////////////////////////
-INLINE BamEndian BamReader::
+INLINE BamReader::BamEndian BamReader::
 get_file_endian() const {
   return _file_endian;
 }
@@ -199,6 +208,65 @@ INLINE BamReader::AuxData::
 AuxData() {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::CreatedObj::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE BamReader::CreatedObj::
+CreatedObj() :
+  _created(false),
+  _ptr(NULL),
+  _ref_ptr(NULL),
+  _change_this(NULL),
+  _change_this_ref(NULL)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::CreatedObj::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE BamReader::CreatedObj::
+~CreatedObj() {
+  set_ptr(NULL, NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::CreatedObj::set_ptr
+//       Access: Public
+//  Description: Replaces the pointer to the created object.  There
+//               are actually two pointers to the same object in
+//               different forms: a generic TypedWritable pointer, and
+//               an untyped ReferenceCount pointer.  We need both
+//               pointers because some objects (like PandaNode)
+//               inherit from TypedWritable and ReferenceCount
+//               independently.
+//
+//               Managing a typed pointer and an untyped
+//               ReferenceCount pointer to the same object takes just
+//               a bit of extra care.
+////////////////////////////////////////////////////////////////////
+INLINE void BamReader::CreatedObj::
+set_ptr(TypedWritable *ptr, ReferenceCount *ref_ptr) {
+  if (_ptr != ptr) {
+    if (_ref_ptr != NULL) {
+      nassertv(_ref_ptr != ref_ptr);
+      unref_delete(_ref_ptr);
+    }
+    
+    _ptr = ptr;
+    _ref_ptr = ref_ptr;
+    
+    if (_ref_ptr != NULL) {
+      _ref_ptr->ref();
+    }
+  } else {
+    nassertv(_ref_ptr == ref_ptr);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: parse_params
 //       Access: Private, Static
@@ -216,3 +284,4 @@ parse_params(const FactoryParams &params,
   scan = param->get_iterator();
   manager = param->get_manager();
 }
+

+ 392 - 103
panda/src/putil/bamReader.cxx

@@ -21,7 +21,7 @@
 #include "config_util.h"
 #include "pipelineCyclerBase.h"
 
-TypeHandle BamReader::_remove_flag;
+TypeHandle BamReaderAuxData::_type_handle;
 
 WritableFactory *BamReader::_factory = (WritableFactory*)0L;
 BamReader *const BamReader::Null = (BamReader*)0L;
@@ -43,6 +43,7 @@ BamReader(DatagramGenerator *generator, const Filename &name)
   : _source(generator), _filename(name)
 {
   _num_extra_objects = 0;
+  _nesting_level = 0;
   _now_creating = _created_objs.end();
   _reading_cycler = (PipelineCyclerBase *)NULL;
   _pta_id = -1;
@@ -58,6 +59,8 @@ BamReader(DatagramGenerator *generator, const Filename &name)
 ////////////////////////////////////////////////////////////////////
 BamReader::
 ~BamReader() {
+  nassertv(_num_extra_objects == 0);
+  nassertv(_nesting_level == 0);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -195,6 +198,7 @@ get_aux_data(TypedWritable *obj, const string &name) const {
   return NULL;
 }
 
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BamReader::read_object
 //       Access: Public
@@ -218,10 +222,42 @@ get_aux_data(TypedWritable *obj, const string &name) const {
 //               not be filled in; you must call resolve() to fill in
 //               all the available pointers before you can safely use
 //               any objects returned by read_object().
+//
+//               This flavor of read_object() requires the caller to
+//               know what type of object it has received in order to
+//               properly manage the reference counts.
 ////////////////////////////////////////////////////////////////////
 TypedWritable *BamReader::
 read_object() {
-  nassertr(_num_extra_objects == 0, (TypedWritable *)NULL);
+  TypedWritable *ptr;
+  ReferenceCount *ref_ptr;
+
+  if (!read_object(ptr, ref_ptr)) {
+    return NULL;
+  }
+
+  return ptr;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::read_object
+//       Access: Public
+//  Description: Reads a single object from the Bam file.  
+//
+//               This flavor of read_object() returns both a
+//               TypedWritable and a ReferenceCount pointer to the
+//               same object, so the reference count may be tracked
+//               reliably, without having to know precisely what type
+//               of object we have.  It returns true on success, or
+//               false on failure.
+////////////////////////////////////////////////////////////////////
+bool BamReader::
+read_object(TypedWritable *&ptr, ReferenceCount *&ref_ptr) {
+  ptr = NULL;
+  ref_ptr = NULL;
+  nassertr(_num_extra_objects == 0, false);
+
+  int start_level = _nesting_level;
 
   // First, read the base object.
   int object_id = p_read_object();
@@ -230,19 +266,28 @@ read_object() {
   // objects, which may still need to be read.  And those objects
   // might in turn require reading additional objects.  Read all the
   // remaining objects.
+
+  // Prior to 6.21, we kept track of _num_extra_objects to know when
+  // we're done.
   while (_num_extra_objects > 0) {
     p_read_object();
     _num_extra_objects--;
   }
 
+  // Beginning with 6.21, we use explicit nesting commands to know
+  // when we're done.
+  while (_nesting_level > start_level) {
+    p_read_object();
+  }
+
   // Now look up the pointer of the object we read first.  It should
   // be available now.
   if (object_id == 0) {
     if (bam_cat.is_spam()) {
       bam_cat.spam()
-        << "Returning NULL\n";
+        << "Returning false\n";
     }
-    return (TypedWritable *)NULL;
+    return false;
   }
 
   CreatedObjs::iterator oi = _created_objs.find(object_id);
@@ -250,25 +295,27 @@ read_object() {
   if (oi == _created_objs.end()) {
     bam_cat.error()
       << "Undefined object encountered!\n";
-    return (TypedWritable *)NULL;
+    return false;
 
   } else {
     CreatedObj &created_obj = (*oi).second;
-    TypedWritable *object = created_obj._ptr;
+    ptr = created_obj._ptr;
+    ref_ptr = created_obj._ref_ptr;
 
     if (bam_cat.is_spam()) {
-      if (object != (TypedWritable *)NULL) {
+      if (ptr != (TypedWritable *)NULL) {
         bam_cat.spam()
-          << "Returning object of type " << object->get_type() << "\n";
+          << "Returning object of type " << ptr->get_type() << "\n";
       }
     }
-    if (created_obj._change_this != NULL) {
+    if (created_obj._change_this != NULL ||
+        created_obj._change_this_ref != NULL) {
       bam_cat.warning()
-        << "Returning pointer to " << object->get_type()
+        << "Returning pointer to " << ptr->get_type()
         << " that might change.\n";
     }
 
-    return object;
+    return true;
   }
 }
 
@@ -296,6 +343,10 @@ resolve() {
   bool any_completed_this_pass;
 
   do {
+    if (bam_cat.is_spam()) {
+      bam_cat.spam()
+        << "resolve pass begin\n";
+    }
     all_completed = true;
     any_completed_this_pass = false;
     
@@ -312,6 +363,11 @@ resolve() {
       
       TypedWritable *object_ptr = created_obj._ptr;
 
+      // Update _now_creating, so a call to get_int_tag() from within
+      // complete_pointers() will come to the right place.
+      CreatedObjs::iterator was_creating = _now_creating;
+      _now_creating = ci;
+      
       if (resolve_object_pointers(object_ptr, pref)) {
         // Now remove this object from the list of things that need
         // completion.  We have to be a bit careful when deleting things
@@ -319,9 +375,35 @@ resolve() {
         ObjectPointers::iterator old = oi;
         ++oi;
         _object_pointers.erase(old);
+        any_completed_this_pass = true;
         
         // Does the pointer need to change?
-        if (created_obj._change_this != NULL) {
+        if (created_obj._change_this_ref != NULL) {
+          // Reference-counting variant.
+          TypedWritableReferenceCount *object_ref_ptr = (TypedWritableReferenceCount *)object_ptr;
+          nassertr(created_obj._ref_ptr == NULL || created_obj._ref_ptr == object_ref_ptr, false);
+          PT(TypedWritableReferenceCount) new_ptr = created_obj._change_this_ref(object_ref_ptr, this);
+          if (new_ptr != object_ref_ptr) {
+            // Also update the reverse
+            vector_int &old_refs = _created_objs_by_pointer[object_ptr];
+            vector_int &new_refs = _created_objs_by_pointer[new_ptr];
+            for (vector_int::const_iterator oi = old_refs.begin();
+                 oi != old_refs.end();
+                 ++oi) {
+              new_refs.push_back(*oi);
+            }
+            _created_objs_by_pointer.erase(object_ptr);
+
+            // Remove the pointer from the finalize list (the new
+            // pointer presumably doesn't require finalizing).
+            _finalize_list.erase(object_ptr);
+          }
+          created_obj.set_ptr(new_ptr, new_ptr);
+          created_obj._change_this = NULL;
+          created_obj._change_this_ref = NULL;
+
+        } else if (created_obj._change_this != NULL) {
+          // Non-reference-counting variant.
           TypedWritable *new_ptr = created_obj._change_this(object_ptr, this);
           if (new_ptr != object_ptr) {
             // Also update the reverse
@@ -338,18 +420,26 @@ resolve() {
             // pointer presumably doesn't require finalizing).
             _finalize_list.erase(object_ptr);
           }
-          created_obj._ptr = new_ptr;
+          created_obj.set_ptr(new_ptr, new_ptr->as_reference_count());
           created_obj._change_this = NULL;
+          created_obj._change_this_ref = NULL;
         }
-        any_completed_this_pass = true;
         
       } else {
         // Couldn't complete this object yet; it'll wait for next time.
         ++oi;
         all_completed = false;
       }
+
+      _now_creating = was_creating;
     }
 
+    if (bam_cat.is_spam()) {
+      bam_cat.spam()
+        << "resolve pass end: all_completed = " << all_completed
+        << " any_completed_this_pass = " << any_completed_this_pass
+        << "\n";
+    }
   } while (!all_completed && any_completed_this_pass);
 
   if (all_completed) {
@@ -420,7 +510,8 @@ change_pointer(const TypedWritable *orig_pointer, const TypedWritable *new_point
     nassertr(ci != _created_objs.end(), false);
     nassertr((*ci).second._ptr == orig_pointer, false);
 
-    (*ci).second._ptr = (TypedWritable *)new_pointer;
+    TypedWritable *ptr = (TypedWritable *)new_pointer;
+    (*ci).second.set_ptr(ptr, ptr->as_reference_count());
     new_refs.push_back(object_id);
   }
 
@@ -539,7 +630,7 @@ read_handle(DatagramIterator &scan) {
 //               pointer to an object that appears later in the Bam
 //               file).  Later, when all pointers are available, the
 //               complete_pointers() callback function will be called
-//               with an array of actual pointers, one for time
+//               with an array of actual pointers, one for each time
 //               read_pointer() was called.  It is then the calling
 //               object's responsibilty to store these pointers in the
 //               object properly.
@@ -566,10 +657,14 @@ read_pointer(DatagramIterator &scan) {
   // If the object ID is zero (which indicates a NULL pointer), we
   // don't have to do anything else.
   if (object_id != 0) {
-    if (_created_objs.count(object_id) == 0) {
-      // If we don't already have an entry in the map for this object
-      // ID (that is, we haven't encountered this object before), we
-      // must remember to read the object definition later.
+    /*
+    CreatedObj new_created_obj;
+    _created_objs.insert(CreatedObjs::value_type(object_id, new_created_obj)).second;
+    */
+
+    if (get_file_minor_ver() < 21) {
+      // Prior to bam version 6.21, we expect to read an adjunct
+      // object for each non-NULL pointer we read.
       _num_extra_objects++;
     }
   }
@@ -624,8 +719,8 @@ read_cdata(DatagramIterator &scan, PipelineCyclerBase &cycler) {
 ////////////////////////////////////////////////////////////////////
 //     Function: BamReader::read_cdata
 //       Access: Public
-//  Description: This flavor of BamReader allows passing an additional
-//               parameter to cdata->fillin().
+//  Description: This flavor of read_cdata allows passing an
+//               additional parameter to cdata->fillin().
 ////////////////////////////////////////////////////////////////////
 void BamReader::
 read_cdata(DatagramIterator &scan, PipelineCyclerBase &cycler,
@@ -638,6 +733,99 @@ read_cdata(DatagramIterator &scan, PipelineCyclerBase &cycler,
   _reading_cycler = old_cycler;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::set_int_tag
+//       Access: Public
+//  Description: Allows the creating object to store a temporary data
+//               value on the BamReader.  This method may be called
+//               during an object's fillin() method; it will associate
+//               an integer value with an arbitrary string key (which
+//               is in turn associated with the calling object only).
+//               Later, in the complete_pointers() method, the same
+//               object may query this data again via get_int_tag().
+//
+//               The tag string need not be unique between different
+//               objects, but it should be unique between an object
+//               and its CData object(s).
+////////////////////////////////////////////////////////////////////
+void BamReader::
+set_int_tag(const string &tag, int value) {
+  nassertv(_now_creating != _created_objs.end());
+  int requestor_id = (*_now_creating).first;
+
+  PointerReference &pref = _object_pointers[requestor_id];
+  pref._int_tags[tag] = value;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::get_int_tag
+//       Access: Public
+//  Description: Returns the value previously set via set_int_tag().
+//               It is an error if no value has been set.
+////////////////////////////////////////////////////////////////////
+int BamReader::
+get_int_tag(const string &tag) const {
+  nassertr(_now_creating != _created_objs.end(), 0);
+  int requestor_id = (*_now_creating).first;
+
+  ObjectPointers::const_iterator opi = _object_pointers.find(requestor_id);
+  nassertr(opi != _object_pointers.end(), 0);
+  const PointerReference &pref = (*opi).second;
+
+  IntTags::const_iterator iti = pref._int_tags.find(tag);
+  nassertr(iti != pref._int_tags.end(), 0);
+  return (*iti).second;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::set_aux_tag
+//       Access: Public
+//  Description: Allows the creating object to store a temporary data
+//               value on the BamReader.  This method may be called
+//               during an object's fillin() method; it will associate
+//               a newly-allocated BamReaderAuxData construct with an
+//               arbitrary string key (which is in turn associated
+//               with the calling object only).  Later, in the
+//               complete_pointers() method, the same object may query
+//               this data again via get_aux_tag().
+//
+//               The BamReader will maintain the reference count on
+//               the BamReaderAuxData, and destruct it when it is
+//               cleaned up.
+//
+//               The tag string need not be unique between different
+//               objects, but it should be unique between an object
+//               and its CData object(s).
+////////////////////////////////////////////////////////////////////
+void BamReader::
+set_aux_tag(const string &tag, BamReaderAuxData *value) {
+  nassertv(_now_creating != _created_objs.end());
+  int requestor_id = (*_now_creating).first;
+
+  PointerReference &pref = _object_pointers[requestor_id];
+  pref._aux_tags[tag] = value;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::get_aux_tag
+//       Access: Public
+//  Description: Returns the value previously set via set_aux_tag().
+//               It is an error if no value has been set.
+////////////////////////////////////////////////////////////////////
+BamReaderAuxData *BamReader::
+get_aux_tag(const string &tag) const {
+  nassertr(_now_creating != _created_objs.end(), NULL);
+  int requestor_id = (*_now_creating).first;
+
+  ObjectPointers::const_iterator opi = _object_pointers.find(requestor_id);
+  nassertr(opi != _object_pointers.end(), NULL);
+  const PointerReference &pref = (*opi).second;
+
+  AuxTags::const_iterator ati = pref._aux_tags.find(tag);
+  nassertr(ati != pref._aux_tags.end(), NULL);
+  return (*ati).second;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BamReader::register_finalize
 //       Access: Public
@@ -691,7 +879,7 @@ register_change_this(ChangeThisFunc func, TypedWritable *object) {
   // Sanity check the pointer--it should always be the same pointer
   // after we set it the first time.
   if (created_obj._ptr == (TypedWritable *)NULL) {
-    created_obj._ptr = object;
+    created_obj.set_ptr(object, object->as_reference_count());
   } else {
     // We've previously assigned this pointer, and we should have
     // assigned it to the same this pointer we have now.
@@ -700,6 +888,47 @@ register_change_this(ChangeThisFunc func, TypedWritable *object) {
 #endif  // NDEBUG
 
   created_obj._change_this = func;
+  created_obj._change_this_ref = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::register_change_this
+//       Access: Public
+//  Description: Called by an object reading itself from the bam file
+//               to indicate that the object pointer that will be
+//               returned is temporary, and will eventually need to be
+//               replaced with another pointer.
+//
+//               The supplied function pointer will later be called on
+//               the object, immediately after complete_pointers() is
+//               called; it should return the new and final pointer.
+//
+//               We use a static function pointer instead of a virtual
+//               function (as in finalize()), to allow the function to
+//               destruct the old pointer if necessary.  (It is
+//               invalid to destruct the this pointer within a virtual
+//               function.)
+////////////////////////////////////////////////////////////////////
+void BamReader::
+register_change_this(ChangeThisRefFunc func, TypedWritableReferenceCount *object) {
+  nassertv(_now_creating != _created_objs.end());
+  CreatedObj &created_obj = (*_now_creating).second;
+
+#ifndef NDEBUG
+  // Sanity check the pointer--it should always be the same pointer
+  // after we set it the first time.
+  if (created_obj._ptr == (TypedWritable *)NULL) {
+    created_obj.set_ptr(object, object);
+  } else {
+    // We've previously assigned this pointer, and we should have
+    // assigned it to the same this pointer we have now.
+    nassertv(created_obj._ptr == object);
+    nassertv(created_obj._ref_ptr == object);
+  }
+#endif  // NDEBUG
+
+  created_obj._change_this = NULL;
+  created_obj._change_this_ref = func;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -901,15 +1130,26 @@ p_read_object() {
   // Now extract the object definition from the datagram.
   DatagramIterator scan(packet);
 
-  // An object definition in a Bam file consists of a TypeHandle
-  // definition, defining the object's type, followed by an object ID
-  // index, defining the particular instance (e.g. pointer) of this
-  // object.
+  // First, read the BamObjectCode.  In bam versions prior to 6.21,
+  // there was no BamObjectCode in the stream.
+  BamObjectCode boc = BOC_adjunct;
+  if (get_file_minor_ver() >= 21) {
+    boc = (BamObjectCode)scan.get_uint8();
+  }
+  switch (boc) {
+  case BOC_push:
+    ++_nesting_level;
+    break;
 
-  TypeHandle type = read_handle(scan);
+  case BOC_pop:
+    --_nesting_level;
+    return 0;
+
+  case BOC_adjunct:
+    break;
 
-  if (type == _remove_flag) {
-    // The _remove_flag TypeHandle is a special case; it begins a
+  case BOC_remove:
+    // The BOC_remove code is a special case; it begins a
     // record that simply lists all of the object ID's that are no
     // longer important to the file and may be released.
     free_object_ids(scan);
@@ -920,6 +1160,13 @@ p_read_object() {
     return p_read_object();
   }
 
+  // An object definition in a Bam file consists of a TypeHandle
+  // definition, defining the object's type, followed by an object ID
+  // index, defining the particular instance (e.g. pointer) of this
+  // object.
+  
+  TypeHandle type = read_handle(scan);
+
   int object_id = read_object_id(scan);
 
   // There are two cases (not counting the special _remove_flag case,
@@ -940,94 +1187,127 @@ p_read_object() {
   if (type != TypeHandle::none()) {
     // Now we are going to read and create a new object.
 
-    // Define the parameters for passing to the object factory.
-    FactoryParams fparams;
-    fparams.add_param(new BamReaderParam(scan, this));
-
     // First, we must add an entry into the map for this object ID, so
     // that in case this function is called recursively during the
     // object's factory constructor, we will have some definition for
     // the object.  For now, we give it a NULL pointer.
     CreatedObj new_created_obj;
-    new_created_obj._ptr = NULL;
-    new_created_obj._change_this = NULL;
     CreatedObjs::iterator oi =
       _created_objs.insert(CreatedObjs::value_type(object_id, new_created_obj)).first;
     CreatedObj &created_obj = (*oi).second;
 
-    // Now we can call the factory to create the object.  Update
-    // _now_creating during this call so if this function calls
-    // read_pointer() or register_change_this() we'll match it up
-    // properly.  This might recursively call back into this
-    // p_read_object(), so be sure to save and restore the original
-    // value of _now_creating.
-    CreatedObjs::iterator was_creating = _now_creating;
-    _now_creating = oi;
-    TypedWritable *object =
-      _factory->make_instance_more_general(type, fparams);
-    _now_creating = was_creating;
-
-    // And now we can store the new object pointer in the map.
-    nassertr(created_obj._ptr == object || created_obj._ptr == NULL, object_id);
-    created_obj._ptr = object;
-
-    if (created_obj._change_this != NULL) {
-      // If the pointer is scheduled to change after
-      // complete_pointers(), but we have no entry in
-      // _object_pointers for this object (and hence no plan to call
-      // complete_pointers()), then just change the pointer
-      // immediately.
-      ObjectPointers::const_iterator ri = _object_pointers.find(object_id);
-      if (ri == _object_pointers.end()) {
-        Finalize::iterator fi = _finalize_list.find((TypedWritable *)object);
-
-        TypedWritable *new_ptr = created_obj._change_this(object, this);
-        created_obj._ptr = new_ptr;
-        created_obj._change_this = NULL;
-
-        // Remove the pointer from the finalize list (the new
-        // pointer presumably doesn't require finalizing).
-        if (new_ptr != object) {
-          _finalize_list.erase(object);
-        }
-        object = new_ptr;
-      }
-    }
+    if (created_obj._ptr != NULL) {
+      // This object had already existed; thus, we are just receiving
+      // an update for it.
 
-    _created_objs_by_pointer[created_obj._ptr].push_back(object_id);
+      // Update _now_creating during this call so if this function
+      // calls read_pointer() or register_change_this() we'll match it
+      // up properly.  This might recursively call back into this
+      // p_read_object(), so be sure to save and restore the original
+      // value of _now_creating.
+      CreatedObjs::iterator was_creating = _now_creating;
+      _now_creating = oi;
+      created_obj._ptr->fillin(scan, this);
+      _now_creating = was_creating;
+
+    } else {
+      // We are receiving a new object.  Now we can call the factory
+      // to create the object.
+
+      // Define the parameters for passing to the object factory.
+      FactoryParams fparams;
+      fparams.add_param(new BamReaderParam(scan, this));
+      
+      // As above, we update and preserve _now_creating during this
+      // call.
+      CreatedObjs::iterator was_creating = _now_creating;
+      _now_creating = oi;
+      TypedWritable *object =
+        _factory->make_instance_more_general(type, fparams);
+      _now_creating = was_creating;
+      
+      // And now we can store the new object pointer in the map.
+      nassertr(created_obj._ptr == object || created_obj._ptr == NULL, object_id);
+      if (object == NULL) {
+        created_obj.set_ptr(NULL, NULL);
+      } else {
+        created_obj.set_ptr(object, object->as_reference_count());
+      }
+      created_obj._created = true;
+
+      if (created_obj._change_this_ref != NULL) {
+        // If the pointer is scheduled to change after
+        // complete_pointers(), but we have no entry in
+        // _object_pointers for this object (and hence no plan to call
+        // complete_pointers()), then just change the pointer
+        // immediately.
+        ObjectPointers::const_iterator ri = _object_pointers.find(object_id);
+        if (ri == _object_pointers.end()) {
+          PT(TypedWritableReferenceCount) object_ref = (*created_obj._change_this_ref)((TypedWritableReferenceCount *)object, this);
+          TypedWritable *new_ptr = object_ref;
+          created_obj.set_ptr(object_ref, object_ref);
+          created_obj._change_this = NULL;
+          created_obj._change_this_ref = NULL;
+          
+          // Remove the pointer from the finalize list (the new
+          // pointer presumably doesn't require finalizing).
+          if (new_ptr != object) {
+            _finalize_list.erase(object);
+          }
+          object = new_ptr;
+        }
+        
+      } else if (created_obj._change_this != NULL) {
+        // Non-reference-counting variant.
+        ObjectPointers::const_iterator ri = _object_pointers.find(object_id);
+        if (ri == _object_pointers.end()) {
+          TypedWritable *new_ptr = (*created_obj._change_this)(object, this);
+          created_obj.set_ptr(new_ptr, new_ptr->as_reference_count());
+          created_obj._change_this = NULL;
+          created_obj._change_this_ref = NULL;
 
-    // Just some sanity checks
-    if (object == (TypedWritable *)NULL) {
-      if (bam_cat.is_debug()) {
-        bam_cat.debug()
-          << "Unable to create an object of type " << type << endl;
+          if (new_ptr != object) {
+            _finalize_list.erase(object);
+          }
+          object = new_ptr;
+        }
       }
+      
+      _created_objs_by_pointer[created_obj._ptr].push_back(object_id);
 
-    } else if (object->get_type() != type) {
-      if (_new_types.find(type) != _new_types.end()) {
-        // This was a type we hadn't heard of before, so it's not
-        // really surprising we didn't know how to create it.
-        // Suppress the warning (make it a debug statement instead).
+      // Just some sanity checks
+      if (object == (TypedWritable *)NULL) {
         if (bam_cat.is_debug()) {
+          bam_cat.debug()
+            << "Unable to create an object of type " << type << endl;
+        }
+
+      } else if (object->get_type() != type) {
+        if (_new_types.find(type) != _new_types.end()) {
+          // This was a type we hadn't heard of before, so it's not
+          // really surprising we didn't know how to create it.
+          // Suppress the warning (make it a debug statement instead).
+          if (bam_cat.is_debug()) {
+            bam_cat.warning()
+              << "Attempted to create a " << type.get_name()    \
+              << " but a " << object->get_type()                \
+              << " was created instead." << endl;
+          }
+          
+        } else {
+          // This was a normal type that we should have known how to
+          // create.  Report the error.
           bam_cat.warning()
-            << "Attempted to create a " << type.get_name() \
-            << " but a " << object->get_type() \
+            << "Attempted to create a " << type.get_name()      \
+            << " but a " << object->get_type()                  \
             << " was created instead." << endl;
         }
-
+        
       } else {
-        // This was a normal type that we should have known how to
-        // create.  Report the error.
-        bam_cat.warning()
-          << "Attempted to create a " << type.get_name() \
-          << " but a " << object->get_type() \
-          << " was created instead." << endl;
-      }
-
-    } else {
-      if (bam_cat.is_spam()) {
-        bam_cat.spam()
-          << "Read a " << object->get_type() << ": " << (void *)object << "\n";
+        if (bam_cat.is_spam()) {
+          bam_cat.spam()
+            << "Read a " << object->get_type() << ": " << (void *)object << "\n";
+        }
       }
     }
   }
@@ -1074,6 +1354,11 @@ resolve_object_pointers(TypedWritable *object,
 
   if (!pref._cycler_pointers.empty()) {
     // If we didn't get all the cyclers, we have to wait.
+    if (bam_cat.is_spam()) {
+      bam_cat.spam()
+        << "some cyclers pending: complete_pointers for " << (void *)object
+        << " (" << object->get_type() << ")\n";
+    }
     return false;
   }
   
@@ -1105,10 +1390,12 @@ resolve_object_pointers(TypedWritable *object,
 
       } else {
         const CreatedObj &child_obj = (*oi).second;
-        if (child_obj._change_this != NULL) {
+        if (!child_obj._created) {
+          // The child object hasn't yet been created.
+          is_complete = false;
+        } else if (child_obj._change_this != NULL || child_obj._change_this_ref != NULL) {
           // It's been created, but the pointer might still change.
           is_complete = false;
-
         } else {
           if (require_fully_complete && 
               _object_pointers.find(child_id) != _object_pointers.end()) {
@@ -1142,6 +1429,7 @@ resolve_object_pointers(TypedWritable *object,
       bam_cat.warning()
         << object->get_type() << " completed " << num_completed
         << " of " << references.size() << " pointers.\n";
+      nassertr(num_completed < (int)references.size(), true);
     }
     return true;
 
@@ -1194,7 +1482,7 @@ resolve_cycler_pointers(PipelineCyclerBase *cycler,
 
       } else {
         const CreatedObj &child_obj = (*oi).second;
-        if (child_obj._change_this != NULL) {
+        if (child_obj._change_this != NULL || child_obj._change_this_ref != NULL) {
           // It's been created, but the pointer might still change.
           is_complete = false;
 
@@ -1227,6 +1515,7 @@ resolve_cycler_pointers(PipelineCyclerBase *cycler,
       bam_cat.warning()
         << "CycleData object completed " << num_completed
         << " of " << references.size() << " pointers.\n";
+      nassertr(num_completed < (int)references.size(), true);
     }
     return true;
   }

+ 65 - 7
panda/src/putil/bamReader.h

@@ -19,10 +19,12 @@
 #include "pnotify.h"
 
 #include "typedWritable.h"
+#include "typedWritableReferenceCount.h"
+#include "pointerTo.h"
 #include "datagramGenerator.h"
 #include "datagramIterator.h"
 #include "bamReaderParam.h"
-#include "bamEndian.h"
+#include "bamEnums.h"
 #include "loaderOptions.h"
 #include "factory.h"
 #include "vector_int.h"
@@ -30,6 +32,7 @@
 #include "pmap.h"
 #include "dcast.h"
 #include "pipelineCyclerBase.h"
+#include "referenceCount.h"
 
 #include <algorithm>
 
@@ -49,6 +52,37 @@
   }                                                   \
 }
 
+////////////////////////////////////////////////////////////////////
+//       Class : BamReaderAuxData
+// Description : Stores auxiliary data that may be piggybacked on the
+//               BamReader during each object's read pass.  To use
+//               this, subclass BamReaderAuxData and add whatever
+//               additional data you require.
+////////////////////////////////////////////////////////////////////
+class BamReaderAuxData : public TypedReferenceCount {
+public:
+  INLINE BamReaderAuxData();
+
+public:
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+
+public:
+  static void init_type() {
+    TypedReferenceCount::init_type();
+    register_type(_type_handle, "BamReaderAuxData",
+                  TypedReferenceCount::get_class_type());
+  }
+
+private:
+  static TypeHandle _type_handle;
+};  
+
 ////////////////////////////////////////////////////////////////////
 //       Class : BamReader
 // Description : This is the fundamental interface for extracting
@@ -83,7 +117,7 @@
 //               See also BamFile, which defines a higher-level
 //               interface to read and write Bam files on disk.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA_PUTIL BamReader {
+class EXPCL_PANDA_PUTIL BamReader : public BamEnums {
 public:
   typedef Factory<TypedWritable> WritableFactory;
   static BamReader *const Null;
@@ -106,6 +140,8 @@ PUBLISHED:
   INLINE void set_loader_options(const LoaderOptions &options);
   
   TypedWritable *read_object();
+  bool read_object(TypedWritable *&ptr, ReferenceCount *&ref_ptr);
+
   INLINE bool is_eof() const;
   bool resolve();
 
@@ -129,10 +165,18 @@ public:
   void read_cdata(DatagramIterator &scan, PipelineCyclerBase &cycler,
                   void *extra_data);
 
+  void set_int_tag(const string &tag, int value);
+  int get_int_tag(const string &tag) const;
+
+  void set_aux_tag(const string &tag, BamReaderAuxData *value);
+  BamReaderAuxData *get_aux_tag(const string &tag) const;
+
   void register_finalize(TypedWritable *whom);
 
   typedef TypedWritable *(*ChangeThisFunc)(TypedWritable *object, BamReader *manager);
+  typedef PT(TypedWritableReferenceCount) (*ChangeThisRefFunc)(TypedWritableReferenceCount *object, BamReader *manager);
   void register_change_this(ChangeThisFunc func, TypedWritable *whom);
+  void register_change_this(ChangeThisRefFunc func, TypedWritableReferenceCount *whom);
 
   void finalize_now(TypedWritable *whom);
 
@@ -164,10 +208,6 @@ private:
   INLINE bool get_datagram(Datagram &datagram);
 
 public:
-  // This special TypeHandle is written to the bam file to indicate an
-  // object id is no longer needed.
-  static TypeHandle _remove_flag;
-
   // Inherit from this class to piggyback additional temporary data on
   // the bamReader (via set_aux_data() and get_aux_data()) for any
   // particular objects during the bam reading process.
@@ -199,8 +239,16 @@ private:
   // to the actual pointers of the corresponding generated objects.
   class CreatedObj {
   public:
+    INLINE CreatedObj();
+    INLINE ~CreatedObj();
+    INLINE void set_ptr(TypedWritable *ptr, ReferenceCount *ref_ptr);
+
+  public:
+    bool _created;
     TypedWritable *_ptr;
+    ReferenceCount *_ref_ptr;
     ChangeThisFunc _change_this;
+    ChangeThisRefFunc _change_this_ref;
   };
   typedef phash_map<int, CreatedObj, int_hash> CreatedObjs;
   CreatedObjs _created_objs;
@@ -222,19 +270,29 @@ private:
   // in the order in which read_pointer() was called, so that we may
   // call the appropriate complete_pointers() later.
   typedef phash_map<PipelineCyclerBase *, vector_int, pointer_hash> CyclerPointers;
+  typedef pmap<string, int> IntTags;
+  typedef pmap<string, PT(BamReaderAuxData) > AuxTags;
   class PointerReference {
   public:
     vector_int _objects;
     CyclerPointers _cycler_pointers;
+    IntTags _int_tags;
+    AuxTags _aux_tags;
   };
   typedef phash_map<int, PointerReference, int_hash> ObjectPointers;
   ObjectPointers _object_pointers;
 
   // This is the number of extra objects that must still be read (and
   // saved in the _created_objs map) before returning from
-  // read_object().
+  // read_object().  It is only used when read bam versions prior to
+  // 6.20.
   int _num_extra_objects;
 
+  // The current nesting level.  We are not done reading an object
+  // until we return to our starting nesting level.  It is only used
+  // when reading bam versions of 6.20 or higher.
+  int _nesting_level;
+
   // This is the set of all objects that registered themselves for
   // finalization.
   typedef phash_set<TypedWritable *, pointer_hash> Finalize;

+ 0 - 63
panda/src/putil/bamTextureMode.cxx

@@ -1,63 +0,0 @@
-// Filename: bamTextureMode.cxx
-// Created by:  drose (14Mar06)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 "bamTextureMode.h"
-#include "config_util.h"
-#include "string_utils.h"
-
-ostream &
-operator << (ostream &out, BamTextureMode btm) {
-  switch (btm) {
-  case BTM_unchanged:
-    return out << "unchanged";
-   
-  case BTM_fullpath:
-    return out << "fullpath";
-    
-  case BTM_relative:
-    return out << "relative";
-    
-  case BTM_basename:
-    return out << "basename";
-
-  case BTM_rawdata:
-    return out << "rawdata";
-  }
-
-  return out << "**invalid BamTextureMode (" << (int)btm << ")**";
-}
-
-istream &
-operator >> (istream &in, BamTextureMode &btm) {
-  string word;
-  in >> word;
-
-  if (cmp_nocase(word, "unchanged") == 0) {
-    btm = BTM_unchanged;
-  } else if (cmp_nocase(word, "fullpath") == 0) {
-    btm = BTM_fullpath;
-  } else if (cmp_nocase(word, "relative") == 0) {
-    btm = BTM_relative;
-  } else if (cmp_nocase(word, "basename") == 0) {
-    btm = BTM_basename;
-  } else if (cmp_nocase(word, "rawdata") == 0) {
-    btm = BTM_rawdata;
-
-  } else {
-    util_cat->error() << "Invalid BamTextureMode value: " << word << "\n";
-    btm = BTM_relative;
-  }
-
-  return in;
-}

+ 0 - 35
panda/src/putil/bamTextureMode.h

@@ -1,35 +0,0 @@
-// Filename: bamTextureMode.h
-// Created by:  drose (14Mar06)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 BAMTEXTUREMODE_H
-#define BAMTEXTUREMODE_H
-
-#include "pandabase.h"
-
-BEGIN_PUBLISH
-// This enum is used to control how textures are written to a bam
-// stream.
-enum BamTextureMode {
-  BTM_unchanged,
-  BTM_fullpath,
-  BTM_relative,
-  BTM_basename,
-  BTM_rawdata
-};
-END_PUBLISH
-
-EXPCL_PANDA_PUTIL ostream &operator << (ostream &out, BamTextureMode btm);
-EXPCL_PANDA_PUTIL istream &operator >> (istream &in, BamTextureMode &btm);
-
-#endif

+ 6 - 6
panda/src/putil/bamWriter.I

@@ -15,7 +15,7 @@
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::get_filename
-//       Access: Public
+//       Access: Published
 //  Description: If a BAM is a file, then the BamWriter should
 //               contain the name of the file.  This enables the
 //               writer to convert pathnames in the BAM to relative
@@ -28,34 +28,34 @@ get_filename() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::get_file_endian
-//       Access: Public
+//       Access: Published
 //  Description: Returns the endian preference indicated by the Bam
 //               file currently being written.  This does not imply
 //               that every number is stored using the indicated
 //               convention, but individual objects may choose to
 //               respect this flag when recording data.
 ////////////////////////////////////////////////////////////////////
-INLINE BamEndian BamWriter::
+INLINE BamWriter::BamEndian BamWriter::
 get_file_endian() const {
   return _file_endian;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::get_file_texture_mode
-//       Access: Public
+//       Access: Published
 //  Description: Returns the BamTextureMode preference indicated by
 //               the Bam file currently being written.  Texture
 //               objects written to this Bam file will be encoded
 //               according to the specified mode.
 ////////////////////////////////////////////////////////////////////
-INLINE BamTextureMode BamWriter::
+INLINE BamWriter::BamTextureMode BamWriter::
 get_file_texture_mode() const {
   return _file_texture_mode;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::set_file_texture_mode
-//       Access: Public
+//       Access: Published
 //  Description: Changes the BamTextureMode preference for
 //               the Bam file currently being written.  Texture
 //               objects written to this Bam file will be encoded

+ 197 - 79
panda/src/putil/bamWriter.cxx

@@ -26,7 +26,7 @@
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::Constructor
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 BamWriter::
@@ -34,11 +34,13 @@ BamWriter(DatagramSink *sink, const Filename &name) :
   _filename(name),
   _target(sink)
 {
+  ++_writing_seq;
+  _next_boc = BOC_adjunct;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::Destructor
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 BamWriter::
@@ -59,7 +61,7 @@ BamWriter::
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::init
-//       Access: Public
+//       Access: Published
 //  Description: Initializes the BamWriter prior to writing any
 //               objects to its output stream.  This includes writing
 //               out the Bam header.
@@ -96,7 +98,7 @@ init() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::write_object
-//       Access: Public
+//       Access: Published
 //  Description: Writes a single object to the Bam file, so that the
 //               BamReader::read_object() can later correctly restore
 //               the object and all its pointers.
@@ -118,15 +120,14 @@ init() {
 ////////////////////////////////////////////////////////////////////
 bool BamWriter::
 write_object(const TypedWritable *object) {
-  nassertr(_object_queue.empty(), false);
-
-  int object_id = enqueue_object(object);
-  nassertr(object_id != 0, false);
+  // Increment the _writing_seq, so we can check for newly stale
+  // objects during this operation.
+  ++_writing_seq;
 
   // If there are any freed objects to indicate, write them out now.
   if (!_freed_object_ids.empty()) {
     Datagram dg;
-    write_handle(dg, BamReader::_remove_flag);
+    dg.add_uint8(BOC_remove);
 
     FreedObjectIds::iterator fi;
     for (fi = _freed_object_ids.begin(); fi != _freed_object_ids.end(); ++fi) {
@@ -141,74 +142,19 @@ write_object(const TypedWritable *object) {
     }
   }
 
-  // Now we write out all the objects in the queue, in order.  The
-  // first one on the queue will, of course, be this object we just
-  // queued up, but each object we write may append more to the queue.
-  while (!_object_queue.empty()) {
-    object = _object_queue.front();
-    _object_queue.pop_front();
-
-    // Look up the object in the map.  It had better be there!
-    StateMap::iterator si = _state_map.find(object);
-    nassertr(si != _state_map.end(), false);
+  nassertr(_object_queue.empty(), false);
+  _next_boc = BOC_push;
 
-    int object_id = (*si).second._object_id;
-    bool already_written = (*si).second._written;
+  int object_id = enqueue_object(object);
+  nassertr(object_id != 0, false);
+  if (!flush_queue()) {
+    return false;
+  }
 
+  // Finally, write the closing pop.
+  if (_next_boc != BOC_push) {
     Datagram dg;
-
-    if (!already_written) {
-      // The first time we write a particular object, we do so by
-      // writing its TypeHandle (which had better not be
-      // TypeHandle::none(), since that's our code for a
-      // previously-written object), followed by the object ID number,
-      // followed by the object definition.
-
-      TypeHandle type = object->get_type();
-      nassertr(type != TypeHandle::none(), false);
-
-      // Determine what the nearest kind of type is that the reader
-      // will be able to handle, and write that instead.
-      TypeHandle registered_type = 
-        BamReader::get_factory()->find_registered_type(type);
-      if (registered_type == TypeHandle::none()) {
-        // We won't be able to read this type again.
-        util_cat.warning()
-          << "Objects of type " << type << " cannot be read; bam file is invalid.\n";
-      } else if (registered_type != type) {
-        util_cat.info()
-          << "Writing " << registered_type << " instead of " << type << "\n";
-        type = registered_type;
-
-      } else if (util_cat.is_debug()) {
-        util_cat.debug()
-          << "Writing " << type << " object id " << object_id
-          << " to bam file\n";
-      }
-
-      write_handle(dg, type);
-      write_object_id(dg, object_id);
-
-      // We cast the const pointer to non-const so that we may call
-      // write_datagram() on it.  Really, write_datagram() should be a
-      // const method anyway, but there may be times when a class
-      // object wants to update some transparent cache value during
-      // writing or something like that, so it's more convenient to
-      // cheat and define it as a non-const method.
-      ((TypedWritable *)object)->write_datagram(this, dg);
-
-      (*si).second._written = true;
-
-    } else {
-      // On subsequent times when we write a particular object, we
-      // write simply TypeHandle::none(), followed by the object ID.
-      // The occurrence of TypeHandle::none() is an indicator to the
-      // BamReader that this is a previously-written object.
-
-      write_handle(dg, TypeHandle::none());
-      write_object_id(dg, object_id);
-    }
-
+    dg.add_uint8(BOC_pop);
     if (!_target->put_datagram(dg)) {
       util_cat.error()
         << "Unable to write data to output.\n";
@@ -221,7 +167,7 @@ write_object(const TypedWritable *object) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::has_object
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if the object has previously been
 //               written (or at least requested to be written) to the
 //               bam file, or false if we've never heard of it before.
@@ -232,6 +178,56 @@ has_object(const TypedWritable *object) const {
   return (si != _state_map.end());
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BamWriter::flush
+//       Access: Published
+//  Description: Ensures that all data written thus far is manifested
+//               on the output stream.
+////////////////////////////////////////////////////////////////////
+void BamWriter::
+flush() {
+  _target->flush();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamWriter::consider_update
+//       Access: Public
+//  Description: Should be called from
+//               TypedWritable::update_bam_nested() to recursively
+//               check the entire hiererachy of writable objects for
+//               needed updates.  This tests the indicated
+//               TypedWritable object and writes it to the bam stream
+//               if it has recently been modified, then recurses
+//               through update_bam_nested.
+////////////////////////////////////////////////////////////////////
+void BamWriter::
+consider_update(const TypedWritable *object) {
+  StateMap::iterator si = _state_map.find(object);
+  if (si == _state_map.end()) {
+    // This object has never even been seen before.
+    enqueue_object(object);
+
+  } else if ((*si).second._written_seq.is_initial()) {
+    // This object has not been written yet.
+    enqueue_object(object);
+
+  } else if ((*si).second._written_seq == _writing_seq) {
+    // We have already visited this object this pass, so no need to
+    // look closer.
+
+  } else if ((*si).second._modified != object->get_bam_modified()) {
+    // This object has been recently modified and needs to be rewritten.
+    enqueue_object(object);
+
+  } else {
+    // Mark that we have now visited this object and pronounced it clean.
+    (*si).second._written_seq = _writing_seq;
+    
+    // Recurse to child objects.
+    ((TypedWritable *)object)->update_bam_nested(this);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::write_pointer
 //       Access: Public
@@ -262,9 +258,27 @@ write_pointer(Datagram &packet, const TypedWritable *object) {
       write_object_id(packet, object_id);
 
     } else {
-      // We have already assigned this pointer an ID; thus, we can
-      // simply write out the ID.
-      write_object_id(packet, (*si).second._object_id);
+      // We have already assigned this pointer an ID, so it has
+      // previously been written; but we might still need to rewrite
+      // it if it is stale.
+      int object_id = (*si).second._object_id;
+      bool already_written = !(*si).second._written_seq.is_initial();
+      if ((*si).second._written_seq != _writing_seq &&
+          (*si).second._modified != object->get_bam_modified()) {
+        // This object was previously written, but it has since been
+        // modified, so we should write it again.
+        already_written = false;
+      }
+
+      write_object_id(packet, object_id);
+
+      if (!already_written) {
+        // It's stale, so queue the object for rewriting too.
+        enqueue_object(object);
+      } else {
+        // Not stale, but maybe its child object is.
+        ((TypedWritable *)object)->update_bam_nested(this);
+      }
     }
   }
 }
@@ -407,6 +421,7 @@ write_handle(Datagram &packet, TypeHandle type) {
   }
 }
 
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::object_destructs
 //       Access: Private
@@ -422,7 +437,7 @@ object_destructs(TypedWritable *object) {
   if (si != _state_map.end()) {
     // We ought to have written out the object by the time it
     // destructs, or we're in trouble when we do write it out.
-    nassertv((*si).second._written);
+    nassertv(!(*si).second._written_seq.is_initial());
 
     int object_id = (*si).second._object_id;
     _freed_object_ids.push_back(object_id);
@@ -529,3 +544,106 @@ enqueue_object(const TypedWritable *object) {
   _object_queue.push_back(object);
   return object_id;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamWriter::flush_queue
+//       Access: Private
+//  Description: Writes all of the objects on the _object_queue to the
+//               bam stream, until the queue is empty.
+//
+//               Returns true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool BamWriter::
+flush_queue() {
+  // Each object we write may append more to the queue.
+  while (!_object_queue.empty()) {
+    const TypedWritable *object = _object_queue.front();
+    _object_queue.pop_front();
+
+    // Look up the object in the map.  It had better be there!
+    StateMap::iterator si = _state_map.find(object);
+    nassertr(si != _state_map.end(), false);
+
+    if ((*si).second._written_seq == _writing_seq) {
+      // We have already visited this object; no need to consider it again.
+      continue;
+    }
+
+    int object_id = (*si).second._object_id;
+    bool already_written = !(*si).second._written_seq.is_initial();
+    if ((*si).second._modified != object->get_bam_modified()) {
+      // This object was previously written, but it has since been
+      // modified, so we should write it again.
+      already_written = false;
+    }
+
+    Datagram dg;
+    dg.add_uint8(_next_boc);
+    _next_boc = BOC_adjunct;
+
+    if (!already_written) {
+      // The first time we write a particular object, or when we
+      // update the same object later, we do so by writing its
+      // TypeHandle (which had better not be TypeHandle::none(), since
+      // that's our code for a previously-written object), followed by
+      // the object ID number, followed by the object definition.
+
+      TypeHandle type = object->get_type();
+      nassertr(type != TypeHandle::none(), false);
+
+      // Determine what the nearest kind of type is that the reader
+      // will be able to handle, and write that instead.
+      TypeHandle registered_type = 
+        BamReader::get_factory()->find_registered_type(type);
+      if (registered_type == TypeHandle::none()) {
+        // We won't be able to read this type again.
+        util_cat.warning()
+          << "Objects of type " << type << " cannot be read; bam file is invalid.\n";
+      } else if (registered_type != type) {
+        util_cat.info()
+          << "Writing " << registered_type << " instead of " << type << "\n";
+        type = registered_type;
+
+      } else if (util_cat.is_debug()) {
+        util_cat.debug()
+          << "Writing " << type << " object id " << object_id
+          << " to bam file\n";
+      }
+
+      write_handle(dg, type);
+      write_object_id(dg, object_id);
+
+      // We cast the const pointer to non-const so that we may call
+      // write_datagram() on it.  Really, write_datagram() should be a
+      // const method anyway, but there may be times when a class
+      // object wants to update some transparent cache value during
+      // writing or something like that, so it's more convenient to
+      // cheat and define it as a non-const method.
+      ((TypedWritable *)object)->write_datagram(this, dg);
+
+      (*si).second._written_seq = _writing_seq;
+      (*si).second._modified = object->get_bam_modified();
+
+    } else {
+      // On subsequent times when we write a particular object, we
+      // write simply TypeHandle::none(), followed by the object ID.
+      // The occurrence of TypeHandle::none() is an indicator to the
+      // BamReader that this is a previously-written object.
+
+      write_handle(dg, TypeHandle::none());
+      write_object_id(dg, object_id);
+
+      // The object has not been modified, but maybe one of its child
+      // objects has.
+      ((TypedWritable *)object)->update_bam_nested(this);
+    }
+
+    if (!_target->put_datagram(dg)) {
+      util_cat.error()
+        << "Unable to write data to output.\n";
+      return false;
+    }
+  }
+
+  return true;
+}

+ 21 - 5
panda/src/putil/bamWriter.h

@@ -17,8 +17,7 @@
 
 #include "pandabase.h"
 #include "pnotify.h"
-#include "bamEndian.h"
-#include "bamTextureMode.h"
+#include "bamEnums.h"
 #include "typedWritable.h"
 #include "datagramSink.h"
 #include "pdeque.h"
@@ -71,7 +70,7 @@
 //               See also BamFile, which defines a higher-level
 //               interface to read and write Bam files on disk.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA_PUTIL BamWriter {
+class EXPCL_PANDA_PUTIL BamWriter : public BamEnums {
 PUBLISHED:
   BamWriter(DatagramSink *sink, const Filename &name = "");
   ~BamWriter();
@@ -80,6 +79,7 @@ PUBLISHED:
   INLINE const Filename &get_filename() const;
   bool write_object(const TypedWritable *obj);
   bool has_object(const TypedWritable *obj) const;
+  void flush();
 
   INLINE BamEndian get_file_endian() const;
 
@@ -89,6 +89,8 @@ PUBLISHED:
 public:
   // Functions to support classes that write themselves to the Bam.
 
+  void consider_update(const TypedWritable *obj);
+
   void write_pointer(Datagram &packet, const TypedWritable *dest);
   void write_cdata(Datagram &packet, const PipelineCyclerBase &cycler);
   void write_cdata(Datagram &packet, const PipelineCyclerBase &cycler,
@@ -102,6 +104,7 @@ private:
   void write_object_id(Datagram &dg, int object_id);
   void write_pta_id(Datagram &dg, int pta_id);
   int enqueue_object(const TypedWritable *object);
+  bool flush_queue();
 
   // This is the filename of the BAM, or null string if not in a file.
   Filename _filename;
@@ -118,13 +121,26 @@ private:
   class StoreState {
   public:
     int _object_id;
-    bool _written;
+    UpdateSeq _written_seq;
+    UpdateSeq _modified;
 
-    StoreState(int object_id) : _object_id(object_id), _written(false) {}
+    StoreState(int object_id) : _object_id(object_id) {}
   };
   typedef phash_map<const TypedWritable *, StoreState, pointer_hash> StateMap;
   StateMap _state_map;
 
+  // This seq number is incremented each time we write a new object
+  // using the top-level write_object() call.  It indicates the
+  // current sequence number we are writing, which is updated in the
+  // StoreState, above, and used to keep track of which objects may
+  // need to be checked for internal updates.
+  UpdateSeq _writing_seq;
+
+  // This is initialized to BOC_push in write_object(), then cleared
+  // to BOC_adjunct as each object is written, so that only the first
+  // object gets written with BOC_push.
+  BamObjectCode _next_boc;
+
   // This is the next object ID that will be assigned to a new object.
   int _next_object_id;
   bool _long_object_id;

+ 1 - 1
panda/src/putil/cachedTypedWritableReferenceCount.I

@@ -174,7 +174,7 @@ cache_unref() const {
   nassertr(_cache_ref_count > 0, 0);
   
   AtomicAdjust::dec(((CachedTypedWritableReferenceCount *)this)->_cache_ref_count);
-  return unref();
+  return ReferenceCount::unref();
 }
 
 ////////////////////////////////////////////////////////////////////

+ 5 - 6
panda/src/putil/config_util.cxx

@@ -51,8 +51,8 @@ ConfigureDef(config_util);
 NotifyCategoryDef(util, "");
 NotifyCategoryDef(bam, util_cat);
 
-ConfigVariableEnum<BamEndian> bam_endian
-("bam-endian", BE_native,
+ConfigVariableEnum<BamEnums::BamEndian> bam_endian
+("bam-endian", BamEnums::BE_native,
  PRC_DESC("The default endianness to use for writing major numeric data "
           "tables to bam files.  This does not affect all numbers written "
           "to bam files, only those for which the individual object was "
@@ -60,8 +60,8 @@ ConfigVariableEnum<BamEndian> bam_endian
           "may set it to \"littleendian\" or \"bigendian\" to target a "
           "particular platform."));
 
-ConfigVariableEnum<BamTextureMode> bam_texture_mode
-("bam-texture-mode", BTM_relative,
+ConfigVariableEnum<BamEnums::BamTextureMode> bam_texture_mode
+("bam-texture-mode", BamEnums::BTM_relative,
  PRC_DESC("Set this to specify how textures should be written into Bam files."
           "See the panda source or documentation for available options."));
 
@@ -152,6 +152,7 @@ init_libputil() {
   AnimInterface::init_type();
   BamCacheIndex::init_type();
   BamCacheRecord::init_type();
+  BamReaderAuxData::init_type();
   BamReaderParam::init_type();
   BitArray::init_type();
   BitMask32::init_type();
@@ -185,8 +186,6 @@ init_libputil() {
   KeyboardButton::init_keyboard_buttons();
   MouseButton::init_mouse_buttons();
 
-  register_type(BamReader::_remove_flag, "remove");
-
   BamCacheIndex::register_with_read_factory();
   BamCacheRecord::register_with_read_factory();
 

+ 3 - 4
panda/src/putil/config_util.h

@@ -20,8 +20,7 @@
 #include "configVariableSearchPath.h"
 #include "configVariableEnum.h"
 #include "configVariableDouble.h"
-#include "bamEndian.h"
-#include "bamTextureMode.h"
+#include "bamEnums.h"
 #include "dconfig.h"
 
 class DSearchPath;
@@ -36,8 +35,8 @@ NotifyCategoryDecl(bam, EXPCL_PANDA_PUTIL, EXPTP_PANDA_PUTIL);
 // MemoryUsage.
 //extern EXPCL_PANDA_PUTIL const bool track_memory_usage;
 
-extern EXPCL_PANDA_PUTIL ConfigVariableEnum<BamEndian> bam_endian;
-extern EXPCL_PANDA_PUTIL ConfigVariableEnum<BamTextureMode> bam_texture_mode;
+extern EXPCL_PANDA_PUTIL ConfigVariableEnum<BamEnums::BamEndian> bam_endian;
+extern EXPCL_PANDA_PUTIL ConfigVariableEnum<BamEnums::BamTextureMode> bam_texture_mode;
 
 BEGIN_PUBLISH
 EXPCL_PANDA_PUTIL ConfigVariableSearchPath &get_model_path();

+ 4 - 2
panda/src/putil/copyOnWriteObject.I

@@ -27,7 +27,8 @@ TypeHandle CopyOnWriteObj1<Base, Param1>::_type_handle;
 INLINE CopyOnWriteObject::
 CopyOnWriteObject() 
 #ifdef COW_THREADED
-  : _lock_cvar(_lock_mutex) 
+  : _lock_mutex("CopyOnWriteObject::_lock"),
+    _lock_cvar(_lock_mutex) 
 #endif
 {
 #ifdef DO_MEMORY_USAGE
@@ -48,7 +49,8 @@ INLINE CopyOnWriteObject::
 CopyOnWriteObject(const CopyOnWriteObject &copy) :
   CachedTypedWritableReferenceCount(copy)
 #ifdef COW_THREADED
-  , _lock_cvar(_lock_mutex)
+  , _lock_mutex("CopyOnWriteObject::_lock"),
+  _lock_cvar(_lock_mutex) 
 #endif
 {
 #ifdef DO_MEMORY_USAGE

+ 1 - 1
panda/src/putil/copyOnWriteObject.cxx

@@ -21,7 +21,7 @@ TypeHandle CopyOnWriteObject::_type_handle;
 #ifdef COW_THREADED
 ////////////////////////////////////////////////////////////////////
 //     Function: CopyOnWriteObject::unref
-//       Access: Public
+//       Access: Public, Virtual
 //  Description: Explicitly decrements the reference count.  See
 //               ReferenceCount::unref().
 //

+ 1 - 1
panda/src/putil/copyOnWriteObject.h

@@ -49,7 +49,7 @@ public:
 
 PUBLISHED:
 #ifdef COW_THREADED
-  bool unref() const;
+  virtual bool unref() const;
   INLINE void cache_ref() const;
 #endif  // COW_THREADED
 

+ 13 - 0
panda/src/putil/datagramOutputFile.cxx

@@ -140,3 +140,16 @@ is_error() {
   }
   return _error;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: DatagramOutputFile::flush
+//       Access: Public, Virtual
+//  Description: Ensures that all datagrams previously written will be
+//               visible in the output file.
+////////////////////////////////////////////////////////////////////
+void DatagramOutputFile::
+flush() {
+  if (_out != (ostream *)NULL) {
+    _out->flush();
+  }
+}

+ 1 - 0
panda/src/putil/datagramOutputFile.h

@@ -38,6 +38,7 @@ public:
   bool write_header(const string &header);
   virtual bool put_datagram(const Datagram &data);
   virtual bool is_error();
+  virtual void flush();
 
 private:
   bool _wrote_first_datagram;

+ 1 - 2
panda/src/putil/putil_composite1.cxx

@@ -2,10 +2,9 @@
 #include "bamCache.cxx"
 #include "bamCacheIndex.cxx"
 #include "bamCacheRecord.cxx"
-#include "bamEndian.cxx"
+#include "bamEnums.cxx"
 #include "bamReader.cxx"
 #include "bamReaderParam.cxx"
-#include "bamTextureMode.cxx"
 #include "bamWriter.cxx"
 #include "bitArray.cxx"
 #include "bitMask.cxx"

+ 18 - 27
panda/src/putil/test_bam.cxx

@@ -23,19 +23,10 @@ TypeHandle Person::_type_handle;
 TypeHandle Parent::_type_handle;
 TypeHandle Child::_type_handle;
 
-ConfigureFn(config_util)
-{
-  TypedObject::init_type();
-  ReferenceCount::init_type();
-  TypedReferenceCount::init_type();
-  init_system_type_handles();
-  FactoryParam::init_type();
-  Datagram::init_type();
-  TypedWritable::init_type();
-  WritableParam::init_type();
-  BamReaderParam::init_type();
-  TypedWritableReferenceCount::init_type();
+Configure(config_test_bam);
 
+ConfigureFn(config_test_bam)
+{
   Person::init_type();
   Parent::init_type();
   Child::init_type();
@@ -72,17 +63,18 @@ void Person::
 fillin(Person* me, DatagramIterator& scan, BamReader* manager)
 {
   _name = scan.get_string();
-  myGender = scan.get_uint8();
+  myGender = (Person::sex)scan.get_uint8();
   manager->read_pointer(scan);
   manager->read_pointer(scan);
 }
 
 int Person::
-complete_pointers(TypedWritable **p_list, BamReader *)
+complete_pointers(TypedWritable **p_list, BamReader *manager)
 {
-  _bro = (p_list[0] == TypedWritable::Null) ? (Person*)NULL : DCAST(Person, p_list[0]);
-  _sis = (p_list[1] == TypedWritable::Null) ? (Person*)NULL : DCAST(Person, p_list[1]);
-  return 2;
+  int pi = TypedWritable::complete_pointers(p_list, manager);
+  _bro = DCAST(Person, p_list[pi++]);
+  _sis = DCAST(Person, p_list[pi++]);
+  return pi;
 }
 
 void Person::
@@ -123,12 +115,12 @@ fillin(Parent* me, DatagramIterator& scan, BamReader* manager)
 }
 
 int Parent::
-complete_pointers(TypedWritable *p_list, BamReader *manager)
+complete_pointers(TypedWritable **p_list, BamReader *manager)
 {
-  int start = Person::complete_pointers(p_list, manager);
-  _son = (p_list[start] == TypedWritable::Null) ? (Child*)NULL : DCAST(Child, p_list[2]);
-  _daughter = (p_list[start+1] == TypedWritable::Null) ? (Child*)NULL : DCAST(Child, p_list[3]);
-  return start+2;
+  int pi = Person::complete_pointers(p_list, manager);
+  _son = DCAST(Child, p_list[pi++]);
+  _daughter = DCAST(Child, p_list[pi++]);
+  return pi;
 }
 
 void Parent::
@@ -169,7 +161,6 @@ make_child(const FactoryParams &params)
 
   parse_params(params, scan, manager);
   me->fillin(me, scan, manager);
-  me->fillin(me, scan, manager);
 
   return me;
 }
@@ -185,10 +176,10 @@ fillin(Child* me, DatagramIterator& scan, BamReader* manager)
 int Child::
 complete_pointers(TypedWritable ** p_list, BamReader *manager)
 {
-  int start = Person::complete_pointers(p_list, manager);
-  _dad = (p_list[start] == TypedWritable::Null) ? (Parent*)NULL : DCAST(Parent, p_list[2]);
-  _mom = (p_list[start+1] == TypedWritable::Null) ? (Parent*)NULL : DCAST(Parent, p_list[3]);
-  return start+2;
+  int pi = Person::complete_pointers(p_list, manager);
+  _dad = DCAST(Parent, p_list[pi++]);
+  _mom = DCAST(Parent, p_list[pi++]);
+  return pi;
 }
 
 

+ 1 - 3
panda/src/putil/test_bam.h

@@ -25,8 +25,6 @@
 
 #include "typedWritableReferenceCount.h"
 
-#include <ipc_file.h>
-
 class Child;
 
 class Person : public TypedWritableReferenceCount {
@@ -89,7 +87,7 @@ public:
   void write_datagram(BamWriter*, Datagram&);
 
   static TypedWritable *make_parent(const FactoryParams &params);
-  virtual int complete_pointers(TypedWritable *p_list,
+  virtual int complete_pointers(TypedWritable **p_list,
                                 BamReader *manager);
 protected:
   void fillin(Parent*,DatagramIterator&,BamReader*);

+ 5 - 4
panda/src/putil/test_bamRead.cxx

@@ -15,16 +15,17 @@
 #include "pandabase.h"
 #include "pnotify.h"
 
-#include <ipc_file.h>
 #include "test_bam.h"
 #include "bamReader.h"
+#include "datagramInputFile.h"
 
 int main(int argc, char* argv[])
 {
   string test_file = "bamTest.out";
-  datagram_file stream(test_file);
+  DatagramInputFile stream;
+  bool success = stream.open(test_file);
+  nassertr(success, 1);
 
-  stream.open(file::FILE_READ);
   BamReader manager(&stream);
 
   manager.init();
@@ -44,5 +45,5 @@ int main(int argc, char* argv[])
   nout << endl;
   sis->print_relationships();
 
-  return 1;
+  return 0;
 }

+ 5 - 6
panda/src/putil/test_bamWrite.cxx

@@ -16,17 +16,16 @@
 #include "pnotify.h"
 
 #include "test_bam.h"
+#include "datagramOutputFile.h"
 
 int main(int argc, char* argv[])
 {
    string test_file("bamTest.out");
-   datagram_file stream(test_file);
+   DatagramOutputFile stream;
+   bool success = stream.open(test_file);
+   nassertr(success, 1);
 
    BamWriter manager(&stream);
-   stream.open(file::FILE_WRITE);
-
-   // This initialization would normally be done by a ConfigureFn
-   // block.
 
    PointerTo<Parent> dad = new Parent("Attila", Person::MALE);
    PointerTo<Parent> mom = new Parent("Brunhilda", Person::FEMALE);
@@ -56,6 +55,6 @@ int main(int argc, char* argv[])
    manager.write_object(sis.p());
 
    stream.close();
-   return 1;
+   return 0;
 }
 

+ 28 - 0
panda/src/putil/typedWritable.I

@@ -38,3 +38,31 @@ TypedWritable(const TypedWritable &) : _bam_writers(NULL) {
 INLINE void TypedWritable::
 operator = (const TypedWritable &) {
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: TypedWritable::mark_bam_modified
+//       Access: Public
+//  Description: Increments the bam_modified counter, so that this
+//               object will be invalidated and retransmitted on any
+//               open bam streams.  This should normally not need to
+//               be called by user code; it should be called
+//               internally when the object has been changed in a way
+//               that legitimately requires its retransmission to any
+//               connected clients.
+////////////////////////////////////////////////////////////////////
+INLINE void TypedWritable::
+mark_bam_modified() {
+  ++_bam_modified;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TypedWritable::get_bam_modified
+//       Access: Public
+//  Description: Returns the current bam_modified counter.  This
+//               counter is normally incremented automatically
+//               whenever the object is modified.
+////////////////////////////////////////////////////////////////////
+INLINE UpdateSeq TypedWritable::
+get_bam_modified() const {
+  return _bam_modified;
+}

+ 33 - 14
panda/src/putil/typedWritable.cxx

@@ -55,6 +55,17 @@ void TypedWritable::
 write_datagram(BamWriter *, Datagram &) {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TypedWritable::update_bam_nested
+//       Access: Public, Virtual
+//  Description: Called by the BamWriter when this object has not
+//               itself been modified recently, but it should check
+//               its nested objects for updates.
+////////////////////////////////////////////////////////////////////
+void TypedWritable::
+update_bam_nested(BamWriter *) {
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TypedWritable::complete_pointers
 //       Access: Public, Virtual
@@ -97,6 +108,21 @@ require_fully_complete() const {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TypedWritable::fillin
+//       Access: Public, Virtual
+//  Description: This internal function is intended to be called by
+//               each class's make_from_bam() method to read in all of
+//               the relevant data from the BamFile for the new
+//               object.  It is also called directly by the BamReader
+//               to re-read the data for an object that has been
+//               placed on the stream for an update.
+////////////////////////////////////////////////////////////////////
+void TypedWritable::
+fillin(DatagramIterator &, BamReader *) {
+}
+
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TypedWritable::finalize
 //       Access: Public, Virtual
@@ -108,20 +134,13 @@ void TypedWritable::
 finalize(BamReader *) {
 }
 
-
 ////////////////////////////////////////////////////////////////////
-//     Function: TypedWritable::fillin
-//       Access: Protected
-//  Description: This internal function is intended to be called by
-//               each class's make_from_bam() method to read in all of
-//               the relevant data from the BamFile for the new
-//               object.
-//
-//               It is defined at the TypedWritable level so that
-//               derived classes may call up the inheritance chain
-//               from their own fillin() method.
+//     Function: TypedWritable::as_reference_count
+//       Access: Public, Virtual
+//  Description: Returns the pointer cast to a ReferenceCount pointer,
+//               if it is in fact of that type.
 ////////////////////////////////////////////////////////////////////
-void TypedWritable::
-fillin(DatagramIterator &, BamReader *) {
+ReferenceCount *TypedWritable::
+as_reference_count() {
+  return NULL;
 }
-

+ 12 - 3
panda/src/putil/typedWritable.h

@@ -19,11 +19,13 @@
 #include "vector_typedWritable.h"
 #include "pvector.h"
 #include "lightMutex.h"
+#include "updateSeq.h"
 
 class BamReader;
 class BamWriter;
 class Datagram;
 class DatagramIterator;
+class ReferenceCount;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : TypedWritable
@@ -43,15 +45,20 @@ public:
 
   virtual ~TypedWritable();
 
-  virtual void write_datagram(BamWriter *, Datagram &);
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual void update_bam_nested(BamWriter *manager);
 
   virtual int complete_pointers(TypedWritable **p_list, BamReader *manager);
   virtual bool require_fully_complete() const;
 
+  virtual void fillin(DatagramIterator &scan, BamReader *manager);
   virtual void finalize(BamReader *manager);
 
-protected:
-  void fillin(DatagramIterator &scan, BamReader *manager);
+  virtual ReferenceCount *as_reference_count();
+
+PUBLISHED:
+  INLINE void mark_bam_modified();
+  INLINE UpdateSeq get_bam_modified() const;
 
 private:
   // We may need to store a list of the BamWriter(s) that have a
@@ -61,6 +68,8 @@ private:
   BamWriters *_bam_writers;
   static LightMutex _bam_writers_lock;
 
+  UpdateSeq _bam_modified;
+
 PUBLISHED:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 11 - 0
panda/src/putil/typedWritableReferenceCount.cxx

@@ -15,3 +15,14 @@
 #include "typedWritableReferenceCount.h"
 
 TypeHandle TypedWritableReferenceCount::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TypedWritableReferenceCount::as_reference_count
+//       Access: Public, Virtual
+//  Description: Returns the pointer cast to a ReferenceCount pointer,
+//               if it is in fact of that type.
+////////////////////////////////////////////////////////////////////
+ReferenceCount *TypedWritableReferenceCount::
+as_reference_count() {
+  return this;
+}

+ 2 - 0
panda/src/putil/typedWritableReferenceCount.h

@@ -38,6 +38,8 @@ public:
   INLINE TypedWritableReferenceCount(const TypedWritableReferenceCount &copy);
   INLINE void operator = (const TypedWritableReferenceCount &copy);
 
+  virtual ReferenceCount *as_reference_count();
+
 public:
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 6 - 1
pandatool/src/bam/bamInfo.cxx

@@ -149,8 +149,11 @@ get_info(const Filename &filename) {
   }
   if (!bam_file.resolve()) {
     nout << "Unable to fully resolve file.\n";
+    return false;
   }
-  bam_file.close();
+
+  // We can't close the bam file until we have examined the objects,
+  // since closing it will decrement reference counts.
 
   if (objects.size() == 1 && 
       objects[0]->is_of_type(PandaNode::get_class_type())) {
@@ -168,6 +171,7 @@ get_info(const Filename &filename) {
     describe_session(DCAST(RecorderHeader, objects[0]), objects);
 
   } else {
+    nout << "file contains " << objects.size() << " objects:\n";
     for (int i = 0; i < (int)objects.size(); i++) {
       describe_general_object(objects[i]);
     }
@@ -280,6 +284,7 @@ describe_session(RecorderHeader *header, const BamInfo::Objects &objects) {
 ////////////////////////////////////////////////////////////////////
 void BamInfo::
 describe_general_object(TypedWritable *object) {
+  nassertv(object != (TypedWritable *)NULL);
   nout << "  " << object->get_type() << "\n";
 }
 

+ 200 - 5
pandatool/src/bam/bamToEgg.cxx

@@ -40,9 +40,17 @@
 #include "geom.h"
 #include "geomTriangles.h"
 #include "geomVertexReader.h"
+#include "transformTable.h"
+#include "animBundleNode.h"
+#include "animChannelMatrixXfmTable.h"
+#include "characterJoint.h"
+#include "character.h"
 #include "string_utils.h"
 #include "bamFile.h"
 #include "bamCacheRecord.h"
+#include "eggSAnimData.h"
+#include "eggXfmAnimData.h"
+#include "eggXfmSAnim.h"
 #include "eggGroup.h"
 #include "eggVertexPool.h"
 #include "eggVertex.h"
@@ -51,11 +59,11 @@
 #include "eggTexture.h"
 #include "eggMaterial.h"
 #include "eggRenderMode.h"
+#include "eggTable.h"
 #include "somethingToEggConverter.h"
 #include "dcast.h"
 #include "pystub.h"
 
-
 ////////////////////////////////////////////////////////////////////
 //     Function: BamToEgg::Constructor
 //       Access: Public
@@ -171,6 +179,12 @@ convert_node(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
   } else if (node->is_of_type(CollisionNode::get_class_type())) {
     convert_collision_node(DCAST(CollisionNode, node), node_path, egg_parent, has_decal);
 
+  } else if (node->is_of_type(AnimBundleNode::get_class_type())) {
+    convert_anim_node(DCAST(AnimBundleNode, node), node_path, egg_parent, has_decal);
+
+  } else if (node->is_of_type(Character::get_class_type())) {
+    convert_character_node(DCAST(Character, node), node_path, egg_parent, has_decal);
+
   } else {
     // Just a generic node.
     EggGroup *egg_group = new EggGroup(node->get_name());
@@ -293,6 +307,160 @@ convert_switch_node(SwitchNode *node, const WorkingNodePath &node_path,
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BamToEgg::convert_animGroup_node
+//       Access: Private
+//  Description: Converts the indicated AnimationGroupNodes to the corresponding
+//               Egg constructs.
+////////////////////////////////////////////////////////////////////
+EggGroupNode * BamToEgg::convert_animGroup_node(AnimGroup *animGroup, double fps ) {
+  int num_children = animGroup->get_num_children();
+
+  EggGroupNode *eggNode = NULL;
+  if (animGroup->is_of_type(AnimBundle::get_class_type())) {
+    EggTable *eggTable = new EggTable(animGroup->get_name());
+    eggTable ->set_table_type(EggTable::TT_bundle);
+    eggNode = eggTable;
+  } else if (animGroup->is_of_type(AnimGroup::get_class_type())) {
+    EggTable *eggTable = new EggTable(animGroup->get_name());
+    eggTable ->set_table_type(EggTable::TT_table);
+    eggNode = eggTable;
+  }
+
+  if (animGroup->is_of_type(AnimChannelMatrixXfmTable::get_class_type())) {
+    AnimChannelMatrixXfmTable *xmfTable = DCAST(AnimChannelMatrixXfmTable, animGroup);
+    EggXfmSAnim *egg_anim = new EggXfmSAnim("xform");
+    egg_anim->set_fps(fps);
+    for (int i = 0; i < num_matrix_components; i++) {
+      string componentName(1, matrix_component_letters[i]);
+      char table_id = matrix_component_letters[i];
+      CPTA_float table = xmfTable ->get_table(table_id);
+
+      if (xmfTable->has_table(table_id)) {
+        for (unsigned int j = 0; j < table.size(); j++) {
+          egg_anim->add_component_data(componentName, table[j]);
+        }
+      }
+    }
+    eggNode->add_child(egg_anim);
+  }
+  for (int i = 0; i < num_children; i++) {
+    AnimGroup *animChild = animGroup->get_child(i);
+    EggGroupNode *eggChildNode = convert_animGroup_node(animChild, fps);
+    if (eggChildNode!=NULL) {
+      nassertr(eggNode!=NULL, NULL);
+      eggNode->add_child(eggChildNode);
+    }
+  } 
+  return eggNode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamToEgg::convert_anim_node
+//       Access: Private
+//  Description: Converts the indicated AnimNode to the corresponding
+//               Egg constructs.
+////////////////////////////////////////////////////////////////////
+void BamToEgg::
+convert_anim_node(AnimBundleNode *node, const WorkingNodePath &node_path,
+                    EggGroupNode *egg_parent, bool has_decal) {
+  
+  // A sequence node gets converted to an ordinary EggGroup, we only apply
+  // the appropriate switch attributes to turn it into a sequence
+  EggTable *eggTable = new EggTable();
+  //egg_parent->add_child(eggTable);
+  _data->add_child(eggTable);
+ 
+  AnimBundle *animBundle = node->get_bundle();
+  // turn it into a switch..
+  //egg_group->set_switch_flag(true);
+
+  EggGroupNode *eggAnimation = convert_animGroup_node(animBundle, animBundle->get_base_frame_rate());
+  eggTable->add_child(eggAnimation);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamToEgg::convert_character_bundle
+//       Access: Private
+//  Description: Converts the indicated Character Bundle to the corresponding
+//               Egg joints structure.
+////////////////////////////////////////////////////////////////////
+void BamToEgg::
+convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, CharacterJointMap *jointMap) {
+  int num_children = bundleNode->get_num_children();
+  
+  EggGroupNode *joint_group = egg_parent;
+  if (bundleNode->is_of_type(CharacterJoint::get_class_type())) {
+    CharacterJoint *character_joint = DCAST(CharacterJoint, bundleNode);
+
+    LMatrix4f transformf;
+    character_joint->get_net_transform(transformf);
+    LMatrix4d transformd(LCAST(double, transformf));
+    EggGroup *joint = new EggGroup(bundleNode->get_name());
+    joint->add_matrix4(transformd);
+    joint->set_group_type(EggGroup::GT_joint);
+    joint_group = joint;
+    egg_parent->add_child(joint_group);
+    if (jointMap!=NULL) {
+      CharacterJointMap::iterator mi = jointMap->find(character_joint);
+      if (mi != jointMap->end()) {
+        pvector<pair<EggVertex*,float> > &joint_vertices = (*mi).second;
+        pvector<pair<EggVertex*,float> >::const_iterator vi;
+        for (vi = joint_vertices.begin(); vi != joint_vertices.end(); ++vi) {
+          joint->set_vertex_membership((*vi).first, (*vi).second);
+        }
+      }
+    }
+  }
+
+  for (int i = 0; i < num_children ; i++) {
+    PartGroup *partGroup= bundleNode->get_child(i);
+    convert_character_bundle(partGroup, joint_group, jointMap);
+  }
+
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamToEgg::convert_character_node
+//       Access: Private
+//  Description: Converts the indicated Character to the corresponding
+//               Egg constructs.
+////////////////////////////////////////////////////////////////////
+void BamToEgg::
+convert_character_node(Character *node, const WorkingNodePath &node_path,
+                    EggGroupNode *egg_parent, bool has_decal) {
+  
+  // A sequence node gets converted to an ordinary EggGroup, we only apply
+  // the appropriate switch attributes to turn it into a sequence
+  EggGroup *egg_group = new EggGroup(node->get_name());
+  egg_group->set_dart_type(EggGroup::DT_default);
+  egg_parent->add_child(egg_group);
+  apply_node_properties(egg_group, node);
+
+  CharacterJointMap jointMap;
+  
+  // turn it into a switch..
+  //egg_group->set_switch_flag(true);
+
+  int num_children = node->get_num_children();
+  int num_bundles = node->get_num_bundles();
+
+  for (int i = 0; i < num_children; i++) {
+    PandaNode *child = node->get_child(i);
+
+    if (child->is_geom_node()) {
+      convert_geom_node(DCAST(GeomNode, child), WorkingNodePath(node_path, child), egg_group, has_decal, &jointMap);
+    }
+  }
+
+  for (int i = 0; i < num_bundles ; i++) {
+    PartBundle *bundle= node->get_bundle(i);
+    convert_character_bundle(bundle, egg_group, &jointMap);
+  }
+
+}
+
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BamToEgg::convert_collision_node
 //       Access: Private
@@ -362,7 +530,7 @@ convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path,
 ////////////////////////////////////////////////////////////////////
 void BamToEgg::
 convert_geom_node(GeomNode *node, const WorkingNodePath &node_path, 
-                  EggGroupNode *egg_parent, bool has_decal) {
+                  EggGroupNode *egg_parent, bool has_decal, CharacterJointMap *jointMap) {
   PT(EggGroup) egg_group = new EggGroup(node->get_name());
   bool fancy_attributes = apply_node_properties(egg_group, node);
 
@@ -401,10 +569,10 @@ convert_geom_node(GeomNode *node, const WorkingNodePath &node_path,
       CPT(GeomPrimitive) simple = primitive->decompose();
       if (simple->is_of_type(GeomTriangles::get_class_type())) {
         CPT(GeomVertexData) vdata = geom->get_vertex_data();
-        vdata = vdata->animate_vertices(true, Thread::get_current_thread());
+        //        vdata = vdata->animate_vertices(true, Thread::get_current_thread());
         convert_triangles(vdata,
                           DCAST(GeomTriangles, simple), geom_state,
-                          net_mat, egg_parent);
+                          net_mat, egg_parent, jointMap);
       }
     }
   }
@@ -421,7 +589,8 @@ void BamToEgg::
 convert_triangles(const GeomVertexData *vertex_data,
                   const GeomTriangles *primitive, 
                   const RenderState *net_state, 
-                  const LMatrix4f &net_mat, EggGroupNode *egg_parent) {
+                  const LMatrix4f &net_mat, EggGroupNode *egg_parent,
+                  CharacterJointMap *jointMap) {
   GeomVertexReader reader(vertex_data);
 
   // Check for a color scale.
@@ -557,6 +726,7 @@ convert_triangles(const GeomVertexData *vertex_data,
 
   Normalf normal;
   Colorf color;
+  CPT(TransformBlendTable) transformBlendTable = vertex_data->get_transform_blend_table();
 
   int nprims = primitive->get_num_primitives();
   for (int i = 0; i < nprims; ++i) {
@@ -607,6 +777,31 @@ convert_triangles(const GeomVertexData *vertex_data,
       }
 
       EggVertex *new_egg_vert = _vpool->create_unique_vertex(egg_vert);
+
+      if ((vertex_data->has_column(InternalName::get_transform_blend())) && 
+      (jointMap!=NULL) && (transformBlendTable!=NULL)) {
+        reader.set_column(InternalName::get_transform_blend());
+        int idx = reader.get_data1i();
+        const TransformBlend &blend = transformBlendTable->get_blend(idx);
+        int num_weights = blend.get_num_transforms();
+        for (int k = 0; k < num_weights; ++k) {
+          float weight = blend.get_weight(k);
+          if (weight!=0) {
+            const VertexTransform *vertex_transform = blend.get_transform(k);
+            if (vertex_transform->is_of_type(JointVertexTransform::get_class_type())) {
+              const JointVertexTransform *joint_vertex_transform = DCAST(const JointVertexTransform, vertex_transform);
+
+              CharacterJointMap::iterator mi = jointMap->find(joint_vertex_transform->get_joint());
+              if (mi == jointMap->end()) {
+                mi = jointMap->insert(CharacterJointMap::value_type(joint_vertex_transform->get_joint(), pvector<pair<EggVertex*,float> >())).first;
+              }
+              pvector<pair<EggVertex*,float> > &joint_vertices = (*mi).second;
+              joint_vertices.push_back(pair<EggVertex*,float>(new_egg_vert, weight));
+            }
+          }
+        }
+      }
+
       egg_poly->add_vertex(new_egg_vert);
     }
   }

+ 17 - 2
pandatool/src/bam/bamToEgg.h

@@ -30,6 +30,10 @@ class EggTexture;
 class LODNode;
 class SequenceNode;
 class SwitchNode;
+class AnimBundleNode;
+class AnimGroup;
+class Character;
+class PartGroup;
 class CollisionNode;
 class GeomNode;
 class GeomTri;
@@ -38,6 +42,10 @@ class GeomTriangles;
 class PandaNode;
 class RenderState;
 class Texture;
+class CharacterJoint;
+class EggVertex;
+
+typedef pmap<const CharacterJoint*, pvector<pair<EggVertex*,float> > > CharacterJointMap;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : BamToEgg
@@ -60,14 +68,21 @@ private:
                         EggGroupNode *egg_parent, bool has_decal);
   void convert_switch_node(SwitchNode *node, const WorkingNodePath &node_path,
                         EggGroupNode *egg_parent, bool has_decal);
+  EggGroupNode *convert_animGroup_node(AnimGroup *animGroup, double fps );
+  void convert_anim_node(AnimBundleNode *node, const WorkingNodePath &node_path,
+                        EggGroupNode *egg_parent, bool has_decal);
+  void convert_character_node(Character *node, const WorkingNodePath &node_path,
+                        EggGroupNode *egg_parent, bool has_decal);
+  void convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, CharacterJointMap *jointMap);
   void convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path,
                         EggGroupNode *egg_parent, bool has_decal);
   void convert_geom_node(GeomNode *node, const WorkingNodePath &node_path, 
-                         EggGroupNode *egg_parent, bool has_decal);
+                         EggGroupNode *egg_parent, bool has_decal, CharacterJointMap *jointMap=NULL);
   void convert_triangles(const GeomVertexData *vertex_data,
                          const GeomTriangles *primitive, 
                          const RenderState *net_state, 
-                         const LMatrix4f &net_mat, EggGroupNode *egg_parent);
+                         const LMatrix4f &net_mat, EggGroupNode *egg_parent,
+                         CharacterJointMap *jointMap);
 
   void recurse_nodes(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
                      bool has_decal);

+ 2 - 2
pandatool/src/bam/eggToBam.cxx

@@ -352,10 +352,10 @@ handle_args(ProgramBase::Args &args) {
   // bam-texture-mode Config.prc variable directly to support this
   // (otherwise the bam code will do what it wants to do anyway).
   if (_tex_rawdata) {
-    bam_texture_mode = BTM_rawdata;
+    bam_texture_mode = BamFile::BTM_rawdata;
 
   } else if (_got_path_store) {
-    bam_texture_mode = BTM_unchanged;
+    bam_texture_mode = BamFile::BTM_unchanged;
 
   } else {
     // Otherwise, the default path store is absolute; then the